2 * \file InsetInclude.cpp
3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Lars Gullik Bjønnes
7 * \author Richard Kimberly Heck (conversion to InsetCommand)
9 * Full author contact details are available in file CREDITS.
14 #include "InsetInclude.h"
17 #include "buffer_funcs.h"
18 #include "BufferList.h"
19 #include "BufferParams.h"
20 #include "BufferView.h"
21 #include "Converter.h"
24 #include "ErrorList.h"
27 #include "FuncRequest.h"
28 #include "FuncStatus.h"
29 #include "LaTeXFeatures.h"
30 #include "LayoutModuleList.h"
32 #include "MetricsInfo.h"
33 #include "output_plaintext.h"
34 #include "output_xhtml.h"
35 #include "texstream.h"
36 #include "TextClass.h"
37 #include "TocBackend.h"
39 #include "frontends/alert.h"
40 #include "frontends/Painter.h"
42 #include "graphics/PreviewImage.h"
43 #include "graphics/PreviewLoader.h"
45 #include "insets/InsetLabel.h"
46 #include "insets/InsetListingsParams.h"
47 #include "insets/RenderPreview.h"
49 #include "mathed/MacroTable.h"
51 #include "support/convert.h"
52 #include "support/debug.h"
53 #include "support/docstream.h"
54 #include "support/FileName.h"
55 #include "support/FileNameList.h"
56 #include "support/filetools.h"
57 #include "support/gettext.h"
58 #include "support/lassert.h"
59 #include "support/lstrings.h" // contains
60 #include "support/mutex.h"
61 #include "support/ExceptionMessage.h"
64 using namespace lyx::support;
68 namespace Alert = frontend::Alert;
73 docstring const uniqueID()
75 static unsigned int seed = 1000;
77 Mutex::Locker lock(&mutex);
78 return "file" + convert<docstring>(++seed);
82 /// the type of inclusion
84 INCLUDE, VERB, INPUT, VERBAST, LISTINGS, NONE
88 Types type(string const & s)
92 if (s == "verbatiminput")
94 if (s == "verbatiminput*")
96 if (s == "lstinputlisting" || s == "inputminted")
104 Types type(InsetCommandParams const & params)
106 return type(params.getCmdName());
110 bool isListings(InsetCommandParams const & params)
112 return type(params) == LISTINGS;
116 bool isVerbatim(InsetCommandParams const & params)
118 Types const t = type(params);
119 return t == VERB || t == VERBAST;
123 bool isInputOrInclude(InsetCommandParams const & params)
125 Types const t = type(params);
126 return t == INPUT || t == INCLUDE;
130 FileName const masterFileName(Buffer const & buffer)
132 return buffer.masterBuffer()->fileName();
136 void add_preview(RenderMonitoredPreview &, InsetInclude const &, Buffer const &);
139 string const parentFileName(Buffer const & buffer)
141 return buffer.absFileName();
145 FileName const includedFileName(Buffer const & buffer,
146 InsetCommandParams const & params)
148 return makeAbsPath(ltrim(to_utf8(params["filename"])),
149 onlyPath(parentFileName(buffer)));
153 InsetLabel * createLabel(Buffer * buf, docstring const & label_str)
155 if (label_str.empty())
157 InsetCommandParams icp(LABEL_CODE);
158 icp["name"] = label_str;
159 return new InsetLabel(buf, icp);
163 char_type replaceCommaInBraces(docstring & params)
165 // Code point from private use area
166 char_type private_char = 0xE000;
168 for (char_type & c : params) {
173 else if (c == ',' && count)
179 docstring stripOuterBraces(docstring & str)
181 // trim only first and last occurrence of { and }
182 if (prefixIs(str, from_ascii("{")))
183 str = str.substr(1, docstring::npos);
184 if (suffixIs(str, from_ascii("}")))
185 str = str.substr(0, str.size() - 1);
192 InsetInclude::InsetInclude(Buffer * buf, InsetCommandParams const & p)
193 : InsetCommand(buf, p), include_label(uniqueID()),
194 preview_(make_unique<RenderMonitoredPreview>(this)), failedtoload_(false),
195 label_(nullptr), child_buffer_(nullptr), file_exist_(false),
196 recursion_error_(false)
198 preview_->connect([this](){ fileChanged(); });
200 if (isListings(params())) {
201 InsetListingsParams listing_params(to_utf8(p["lstparams"]));
202 label_ = createLabel(buffer_, from_utf8(listing_params.getParamValue("label")));
203 } else if (isInputOrInclude(params()) && buf)
208 InsetInclude::InsetInclude(InsetInclude const & other)
209 : InsetCommand(other), include_label(other.include_label),
210 preview_(make_unique<RenderMonitoredPreview>(this)), failedtoload_(false),
211 label_(nullptr), child_buffer_(nullptr),
212 file_exist_(other.file_exist_),recursion_error_(other.recursion_error_)
214 preview_->connect([this](){ fileChanged(); });
217 label_ = new InsetLabel(*other.label_);
221 InsetInclude::~InsetInclude()
227 void InsetInclude::setBuffer(Buffer & buffer)
229 InsetCommand::setBuffer(buffer);
231 label_->setBuffer(buffer);
235 void InsetInclude::setChildBuffer(Buffer * buffer)
237 child_buffer_ = buffer;
241 ParamInfo const & InsetInclude::findInfo(string const & /* cmdName */)
244 // This is only correct for the case of listings, but it'll do for now.
245 // In the other cases, this second parameter should just be empty.
246 static ParamInfo param_info_;
247 if (param_info_.empty()) {
248 param_info_.add("filename", ParamInfo::LATEX_REQUIRED);
249 param_info_.add("lstparams", ParamInfo::LATEX_OPTIONAL);
250 param_info_.add("literal", ParamInfo::LYX_INTERNAL);
256 bool InsetInclude::isCompatibleCommand(string const & s)
258 return type(s) != NONE;
262 bool InsetInclude::needsCProtection(bool const /*maintext*/, bool const fragile) const
264 // We need to \cprotect all types in fragile context
269 void InsetInclude::doDispatch(Cursor & cur, FuncRequest & cmd)
271 switch (cmd.action()) {
273 case LFUN_INSET_EDIT: {
274 editIncluded(ltrim(to_utf8(params()["filename"])));
278 case LFUN_INSET_MODIFY: {
279 // It should be OK just to invalidate the cache in setParams()
281 // child_buffer_ = 0;
282 InsetCommandParams p(INCLUDE_CODE);
283 if (cmd.getArg(0) == "changetype") {
285 InsetCommand::doDispatch(cur, cmd);
288 InsetCommand::string2params(to_utf8(cmd.argument()), p);
289 if (!p.getCmdName().empty()) {
291 InsetListingsParams new_params(to_utf8(p["lstparams"]));
292 docstring const new_label =
293 from_utf8(new_params.getParamValue("label"));
295 if (new_label.empty()) {
301 old_label = label_->getParam("name");
303 label_ = createLabel(buffer_, new_label);
304 label_->setBuffer(buffer());
307 if (new_label != old_label) {
308 label_->updateLabelAndRefs(new_label, &cur);
309 // the label might have been adapted (duplicate)
310 if (new_label != label_->getParam("name")) {
311 new_params.addParam("label", "{" +
312 to_utf8(label_->getParam("name")) + "}", true);
313 p["lstparams"] = from_utf8(new_params.params());
320 cur.forceBufferUpdate();
322 cur.noScreenUpdate();
326 case LFUN_MOUSE_RELEASE: {
327 if (cmd.modifier() == ControlModifier) {
328 FileName const incfile = includedFileName(buffer(), params());
329 string const & incname = incfile.absFileName();
330 editIncluded(incname);
336 //pass everything else up the chain
338 InsetCommand::doDispatch(cur, cmd);
344 void InsetInclude::editIncluded(string const & f)
346 if (isLyXFileName(f)) {
347 FuncRequest fr(LFUN_BUFFER_CHILD_OPEN, f);
350 // tex file or other text file in verbatim mode
351 theFormats().edit(buffer(),
352 support::makeAbsPath(f, support::onlyPath(buffer().absFileName())),
357 bool InsetInclude::getStatus(Cursor & cur, FuncRequest const & cmd,
358 FuncStatus & flag) const
360 switch (cmd.action()) {
362 case LFUN_INSET_EDIT:
363 flag.setEnabled(true);
366 case LFUN_INSET_MODIFY:
367 if (cmd.getArg(0) == "changetype")
368 return InsetCommand::getStatus(cur, cmd, flag);
370 flag.setEnabled(true);
374 return InsetCommand::getStatus(cur, cmd, flag);
379 void InsetInclude::setParams(InsetCommandParams const & p)
381 // invalidate the cache
382 child_buffer_ = nullptr;
384 // reset in order to allow loading new file
385 failedtoload_ = false;
386 recursion_error_ = false;
388 InsetCommand::setParams(p);
390 if (preview_->monitoring())
391 preview_->stopMonitoring();
393 if (type(params()) == INPUT)
394 add_preview(*preview_, *this, buffer());
398 bool InsetInclude::isChildIncluded() const
400 std::list<std::string> includeonlys =
401 buffer().params().getIncludedChildren();
402 if (includeonlys.empty())
404 return (std::find(includeonlys.begin(),
406 ltrim(to_utf8(params()["filename"]))) != includeonlys.end());
410 docstring InsetInclude::screenLabel() const
412 docstring pre = file_exist_ ? docstring() : _("MISSING:");
416 switch (type(params())) {
418 temp = buffer().B_("Input");
421 temp = buffer().B_("Verbatim");
424 temp = buffer().B_("Verbatim*");
427 if (isChildIncluded())
428 temp = buffer().B_("Include");
430 temp += buffer().B_("Include (excluded)");
433 temp = listings_label_;
436 LASSERT(false, temp = buffer().B_("Unknown"));
442 if (ltrim(params()["filename"]).empty())
445 temp += from_utf8(onlyFileName(ltrim(to_utf8(params()["filename"]))));
447 return pre.empty() ? temp : pre + from_ascii(" ") + temp;
451 Buffer * InsetInclude::loadIfNeeded() const
453 // This is for background export and preview. We don't even want to
454 // try to load the cloned child document again.
455 if (buffer().isClone())
456 return child_buffer_;
458 // Don't try to load it again if we failed before.
459 if (failedtoload_ || isVerbatim(params()) || isListings(params()))
462 FileName const included_file = includedFileName(buffer(), params());
463 // Use cached Buffer if possible.
464 if (child_buffer_ != nullptr) {
465 if (theBufferList().isLoaded(child_buffer_)
466 // additional sanity check: make sure the Buffer really is
467 // associated with the file we want.
468 && child_buffer_ == theBufferList().getBuffer(included_file))
469 return child_buffer_;
470 // Buffer vanished, so invalidate cache and try to reload.
471 child_buffer_ = nullptr;
474 if (!isLyXFileName(included_file.absFileName()))
477 Buffer * child = theBufferList().getBuffer(included_file);
478 if (checkForRecursiveInclude(child))
482 // the readonly flag can/will be wrong, not anymore I think.
483 if (!included_file.exists()) {
484 failedtoload_ = true;
488 child = theBufferList().newBuffer(included_file.absFileName());
490 // Buffer creation is not possible.
493 buffer().pushIncludedBuffer(child);
494 // Set parent before loading, such that macros can be tracked
495 child->setParent(&buffer());
497 if (child->loadLyXFile() != Buffer::ReadSuccess) {
498 failedtoload_ = true;
499 child->setParent(nullptr);
500 //close the buffer we just opened
501 theBufferList().release(child);
502 buffer().popIncludedBuffer();
506 buffer().popIncludedBuffer();
507 if (!child->errorList("Parse").empty()) {
508 // FIXME: Do something.
511 // The file was already loaded, so, simply
512 // inform parent buffer about local macros.
513 Buffer const * parent = &buffer();
514 child->setParent(parent);
516 child->listMacroNames(macros);
517 MacroNameSet::const_iterator cit = macros.begin();
518 MacroNameSet::const_iterator end = macros.end();
519 for (; cit != end; ++cit)
520 parent->usermacros.insert(*cit);
523 // Cache the child buffer.
524 child_buffer_ = child;
529 bool InsetInclude::checkForRecursiveInclude(
530 Buffer const * cbuf, bool silent) const
532 if (recursion_error_)
535 if (!buffer().isBufferIncluded(cbuf))
539 docstring const msg = _("The file\n%1$s\n has attempted to include itself.\n"
540 "The document set will not work properly until this is fixed!");
541 frontend::Alert::warning(_("Recursive Include"),
542 bformat(msg, from_utf8(cbuf->fileName().absFileName())));
544 recursion_error_ = true;
549 void InsetInclude::latex(otexstream & os, OutputParams const & runparams) const
551 string incfile = ltrim(to_utf8(params()["filename"]));
553 // Warn if no file name has been specified
554 if (incfile.empty()) {
555 frontend::Alert::warning(_("No file name specified"),
556 _("An included file name is empty.\n"
557 "Ignoring Inclusion"),
561 // Warn if file doesn't exist
562 if (!includedFileExist()) {
563 frontend::Alert::warning(_("Included file not found"),
564 bformat(_("The included file\n"
566 "has not been found. LyX will ignore the inclusion."),
572 FileName const included_file = includedFileName(buffer(), params());
573 Buffer const * const masterBuffer = buffer().masterBuffer();
575 if (runparams.inDeletedInset) {
576 // We cannot strike-out whole children,
577 // so we just output a note.
579 << bformat(buffer().B_("[INCLUDED FILE %1$s DELETED!]"),
580 from_utf8(included_file.onlyFileName()))
585 // if incfile is relative, make it relative to the master
587 if (!FileName::isAbsolute(incfile)) {
589 incfile = to_utf8(makeRelPath(from_utf8(included_file.absFileName()),
590 from_utf8(masterBuffer->filePath())));
593 string exppath = incfile;
594 if (!runparams.export_folder.empty()) {
595 exppath = makeAbsPath(exppath, runparams.export_folder).realPath();
598 // write it to a file (so far the complete file)
602 if (type(params()) == LISTINGS) {
603 exportfile = exppath;
604 mangled = DocFileName(included_file).mangledFileName();
606 exportfile = changeExtension(exppath, ".tex");
607 mangled = DocFileName(changeExtension(included_file.absFileName(), ".tex")).
613 else if (!runparams.silent)
614 ; // no warning wanted
615 else if (!isValidLaTeXFileName(incfile)) {
616 frontend::Alert::warning(_("Invalid filename"),
617 _("The following filename will cause troubles "
618 "when running the exported file through LaTeX: ") +
620 } else if (!isValidDVIFileName(incfile)) {
621 frontend::Alert::warning(_("Problematic filename for DVI"),
622 _("The following filename can cause troubles "
623 "when running the exported file through LaTeX "
624 "and opening the resulting DVI: ") +
625 from_utf8(incfile), true);
628 FileName const writefile(makeAbsPath(mangled, runparams.for_preview ?
629 buffer().temppath() : masterBuffer->temppath()));
631 LYXERR(Debug::OUTFILE, "incfile:" << incfile);
632 LYXERR(Debug::OUTFILE, "exportfile:" << exportfile);
633 LYXERR(Debug::OUTFILE, "writefile:" << writefile);
635 string const tex_format = flavor2format(runparams.flavor);
637 switch (type(params())) {
640 incfile = latex_path(incfile);
642 os << '\\' << from_ascii(params().getCmdName()) << '{'
643 << from_utf8(incfile) << '}';
647 runparams.exportdata->addExternalFile(tex_format, writefile,
650 // \input wants file with extension (default is .tex)
651 if (!isLyXFileName(included_file.absFileName())) {
652 incfile = latex_path(incfile);
654 os << '\\' << from_ascii(params().getCmdName())
655 << '{' << from_utf8(incfile) << '}';
657 incfile = changeExtension(incfile, ".tex");
658 incfile = latex_path(incfile);
660 os << '\\' << from_ascii(params().getCmdName())
661 << '{' << from_utf8(incfile) << '}';
666 // Here, listings and minted have slightly different behaviors.
667 // Using listings, it is always possible to have a caption,
668 // even for non-floats. Using minted, only floats can have a
669 // caption. So, with minted we use the following strategy.
670 // If a caption was specified but the float parameter was not,
671 // we ourselves add a caption above the listing (because the
672 // listing comes from a file and might span several pages).
673 // Otherwise, if float was specified, the floating listing
674 // environment provided by minted is used. In either case, the
675 // label parameter is taken as the label by which the float
676 // can be referenced, otherwise it will have the meaning
677 // intended by minted. In this last case, the label will
678 // serve as a sort of caption that, however, will be shown
679 // by minted only if the frame parameter is also specified.
680 bool const use_minted = buffer().params().use_minted;
681 runparams.exportdata->addExternalFile(tex_format, writefile,
683 string const opt = to_utf8(params()["lstparams"]);
684 // opt is set in QInclude dialog and should have passed validation.
685 InsetListingsParams lstparams(opt);
686 docstring parameters = from_utf8(lstparams.params());
691 bool isfloat = lstparams.isFloat();
692 // We are going to split parameters at commas, so
693 // replace commas that are not parameter separators
694 // with a code point from the private use area
695 char_type comma = replaceCommaInBraces(parameters);
696 // Get float placement, language, caption, and
697 // label, then remove the relative options if minted.
698 vector<docstring> opts =
699 getVectorFromString(parameters, from_ascii(","), false);
700 vector<docstring> latexed_opts;
701 for (size_t i = 0; i < opts.size(); ++i) {
702 // Restore replaced commas
703 opts[i] = subst(opts[i], comma, ',');
704 if (use_minted && prefixIs(opts[i], from_ascii("float"))) {
705 if (prefixIs(opts[i], from_ascii("float=")))
706 placement = opts[i].substr(6);
707 opts.erase(opts.begin() + i--);
708 } else if (use_minted && prefixIs(opts[i], from_ascii("language="))) {
709 language = opts[i].substr(9);
710 opts.erase(opts.begin() + i--);
711 } else if (prefixIs(opts[i], from_ascii("caption="))) {
712 caption = opts[i].substr(8);
713 caption = params().prepareCommand(runparams, stripOuterBraces(caption),
714 ParamInfo::HANDLING_LATEXIFY);
715 opts.erase(opts.begin() + i--);
717 latexed_opts.push_back(from_ascii("caption={") + caption + "}");
718 } else if (prefixIs(opts[i], from_ascii("label="))) {
719 label = opts[i].substr(6);
720 label = params().prepareCommand(runparams, stripOuterBraces(label),
721 ParamInfo::HANDLING_ESCAPE);
722 opts.erase(opts.begin() + i--);
724 latexed_opts.push_back(from_ascii("label={") + label + "}");
726 if (use_minted && !label.empty()) {
727 if (isfloat || !caption.empty())
728 label = stripOuterBraces(label);
730 opts.push_back(from_ascii("label=") + label);
733 if (!latexed_opts.empty())
734 opts.insert(opts.end(), latexed_opts.begin(), latexed_opts.end());
735 parameters = getStringFromVector(opts, from_ascii(","));
736 if (language.empty())
737 language = from_ascii("TeX");
738 if (use_minted && isfloat) {
739 os << breakln << "\\begin{listing}";
740 if (!placement.empty())
741 os << '[' << placement << "]";
743 } else if (use_minted && !caption.empty()) {
744 os << breakln << "\\lyxmintcaption[t]{" << caption;
746 os << "\\label{" << label << "}";
749 os << (use_minted ? "\\inputminted" : "\\lstinputlisting");
750 if (!parameters.empty())
751 os << "[" << parameters << "]";
753 os << '{' << ascii_lowercase(language) << '}';
754 os << '{' << incfile << '}';
755 if (use_minted && isfloat) {
756 if (!caption.empty())
757 os << breakln << "\\caption{" << caption << "}";
759 os << breakln << "\\label{" << label << "}";
760 os << breakln << "\\end{listing}\n";
765 runparams.exportdata->addExternalFile(tex_format, writefile,
768 // \include don't want extension and demands that the
769 // file really have .tex
770 incfile = changeExtension(incfile, string());
771 incfile = latex_path(incfile);
773 os << '\\' << from_ascii(params().getCmdName()) << '{'
774 << from_utf8(incfile) << '}';
781 if (runparams.inComment || runparams.dryrun)
782 // Don't try to load or copy the file if we're
783 // in a comment or doing a dryrun
786 if (!isInputOrInclude(params()) ||
787 !isLyXFileName(included_file.absFileName())) {
788 // In this case, it's not a LyX file, so we copy the file
789 // to the temp dir, so that .aux files etc. are not created
790 // in the original dir. Files included by this file will be
791 // found via either the environment variable TEXINPUTS, or
792 // input@path, see ../Buffer.cpp.
793 unsigned long const checksum_in = included_file.checksum();
794 unsigned long const checksum_out = writefile.checksum();
795 if (checksum_in != checksum_out) {
796 if (!included_file.copyTo(writefile)) {
798 LYXERR(Debug::OUTFILE,
799 to_utf8(bformat(_("Could not copy the file\n%1$s\n"
800 "into the temporary directory."),
801 from_utf8(included_file.absFileName()))));
808 // it's a LyX file and we're inputting or including, so
809 // try to load it so we can write the associated latex
810 Buffer * tmp = loadIfNeeded();
812 if (!runparams.silent) {
813 docstring text = bformat(_("Could not load included "
815 "Please, check whether it actually exists."),
816 included_file.displayName());
817 throw ExceptionMessage(ErrorException, _("Error: "),
823 if (recursion_error_)
826 if (!runparams.silent) {
827 if (tmp->params().baseClass() != masterBuffer->params().baseClass()) {
829 docstring text = bformat(_("Included file `%1$s'\n"
830 "has textclass `%2$s'\n"
831 "while parent file has textclass `%3$s'."),
832 included_file.displayName(),
833 from_utf8(tmp->params().documentClass().name()),
834 from_utf8(masterBuffer->params().documentClass().name()));
835 Alert::warning(_("Different textclasses"), text, true);
838 string const child_tf = tmp->params().useNonTeXFonts ? "true" : "false";
839 string const master_tf = masterBuffer->params().useNonTeXFonts ? "true" : "false";
840 if (tmp->params().useNonTeXFonts != masterBuffer->params().useNonTeXFonts) {
841 docstring text = bformat(_("Included file `%1$s'\n"
842 "has use-non-TeX-fonts set to `%2$s'\n"
843 "while parent file has use-non-TeX-fonts set to `%3$s'."),
844 included_file.displayName(),
846 from_utf8(master_tf));
847 Alert::warning(_("Different use-non-TeX-fonts settings"), text, true);
849 else if (tmp->params().inputenc != masterBuffer->params().inputenc) {
850 docstring text = bformat(_("Included file `%1$s'\n"
851 "uses input encoding \"%2$s\" [%3$s]\n"
852 "while parent file uses input encoding \"%4$s\" [%5$s]."),
853 included_file.displayName(),
854 _(tmp->params().inputenc),
855 from_utf8(tmp->params().encoding().guiName()),
856 _(masterBuffer->params().inputenc),
857 from_utf8(masterBuffer->params().encoding().guiName()));
858 Alert::warning(_("Different LaTeX input encodings"), text, true);
861 // Make sure modules used in child are all included in master
862 // FIXME It might be worth loading the children's modules into the master
863 // over in BufferParams rather than doing this check.
864 LayoutModuleList const masterModules = masterBuffer->params().getModules();
865 LayoutModuleList const childModules = tmp->params().getModules();
866 LayoutModuleList::const_iterator it = childModules.begin();
867 LayoutModuleList::const_iterator end = childModules.end();
868 for (; it != end; ++it) {
869 string const module = *it;
870 LayoutModuleList::const_iterator found =
871 find(masterModules.begin(), masterModules.end(), module);
872 if (found == masterModules.end()) {
873 docstring text = bformat(_("Included file `%1$s'\n"
874 "uses module `%2$s'\n"
875 "which is not used in parent file."),
876 included_file.displayName(), from_utf8(module));
877 Alert::warning(_("Module not found"), text, true);
882 tmp->markDepClean(masterBuffer->temppath());
884 // Don't assume the child's format is latex
885 string const inc_format = tmp->params().bufferFormat();
886 FileName const tmpwritefile(changeExtension(writefile.absFileName(),
887 theFormats().extension(inc_format)));
889 // FIXME: handle non existing files
890 // The included file might be written in a different encoding
892 Encoding const * const oldEnc = runparams.encoding;
893 Language const * const oldLang = runparams.master_language;
894 // If the master uses non-TeX fonts (XeTeX, LuaTeX),
895 // the children must be encoded in plain utf8!
896 if (masterBuffer->params().useNonTeXFonts)
897 runparams.encoding = encodings.fromLyXName("utf8-plain");
899 runparams.encoding = oldEnc;
900 else runparams.encoding = &tmp->params().encoding();
901 runparams.master_language = buffer().params().language;
902 runparams.par_begin = 0;
903 runparams.par_end = tmp->paragraphs().size();
904 runparams.is_child = true;
905 Buffer::ExportStatus retval =
906 tmp->makeLaTeXFile(tmpwritefile, masterFileName(buffer()).
907 onlyPath().absFileName(), runparams, Buffer::OnlyBody);
908 if (retval == Buffer::ExportKilled && buffer().isClone() &&
909 buffer().isExporting()) {
910 // We really shouldn't get here, I don't think.
911 LYXERR0("No conversion exception?");
912 throw ConversionException();
914 else if (retval != Buffer::ExportSuccess) {
915 if (!runparams.silent) {
916 docstring msg = bformat(_("Included file `%1$s' "
917 "was not exported correctly.\n "
918 "LaTeX export is probably incomplete."),
919 included_file.displayName());
920 ErrorList const & el = tmp->errorList("Export");
922 msg = bformat(from_ascii("%1$s\n\n%2$s\n\n%3$s"),
923 msg, el.begin()->error, el.begin()->description);
924 throw ExceptionMessage(ErrorException, _("Error: "), msg);
927 runparams.encoding = oldEnc;
928 runparams.master_language = oldLang;
929 runparams.is_child = false;
931 // If needed, use converters to produce a latex file from the child
932 if (tmpwritefile != writefile) {
934 Converters::RetVal const conv_retval =
935 theConverters().convert(tmp, tmpwritefile, writefile,
936 included_file, inc_format, tex_format, el);
937 if (conv_retval == Converters::KILLED && buffer().isClone() &&
938 buffer().isExporting()) {
939 // We really shouldn't get here, I don't think.
940 LYXERR0("No conversion exception?");
941 throw ConversionException();
942 } else if (conv_retval != Converters::SUCCESS && !runparams.silent) {
943 docstring msg = bformat(_("Included file `%1$s' "
944 "was not exported correctly.\n "
945 "LaTeX export is probably incomplete."),
946 included_file.displayName());
948 msg = bformat(from_ascii("%1$s\n\n%2$s\n\n%3$s"),
949 msg, el.begin()->error, el.begin()->description);
950 throw ExceptionMessage(ErrorException, _("Error: "), msg);
956 docstring InsetInclude::xhtml(XMLStream & xs, OutputParams const & rp) const
961 // For verbatim and listings, we just include the contents of the file as-is.
962 // In the case of listings, we wrap it in <pre>.
963 bool const listing = isListings(params());
964 if (listing || isVerbatim(params())) {
966 xs << xml::StartTag("pre");
967 // FIXME: We don't know the encoding of the file, default to UTF-8.
968 xs << includedFileName(buffer(), params()).fileContents("UTF-8");
970 xs << xml::EndTag("pre");
974 // We don't (yet) know how to Input or Include non-LyX files.
975 // (If we wanted to get really arcane, we could run some tex2html
976 // converter on the included file. But that's just masochistic.)
977 FileName const included_file = includedFileName(buffer(), params());
978 if (!isLyXFileName(included_file.absFileName())) {
980 frontend::Alert::warning(_("Unsupported Inclusion"),
981 bformat(_("LyX does not know how to include non-LyX files when "
982 "generating HTML output. Offending file:\n%1$s"),
983 ltrim(params()["filename"])));
987 // In the other cases, we will generate the HTML and include it.
989 Buffer const * const ibuf = loadIfNeeded();
993 if (recursion_error_)
996 // are we generating only some paragraphs, or all of them?
997 bool const all_pars = !rp.dryrun ||
998 (rp.par_begin == 0 &&
999 rp.par_end == (int)buffer().text().paragraphs().size());
1001 OutputParams op = rp;
1005 ibuf->writeLyXHTMLSource(xs.os(), op, Buffer::IncludedFile);
1007 xs << XMLStream::ESCAPE_NONE << "<!-- Included file: ";
1008 xs << from_utf8(included_file.absFileName());
1009 xs << XMLStream::ESCAPE_NONE << " -->";
1016 int InsetInclude::plaintext(odocstringstream & os,
1017 OutputParams const & op, size_t) const
1019 // just write the filename if we're making a tooltip or toc entry,
1020 // or are generating this for advanced search
1021 if (op.for_tooltip || op.for_toc || op.find_effective()) {
1022 os << '[' << screenLabel() << '\n'
1023 << ltrim(getParam("filename")) << "\n]";
1024 return PLAINTEXT_NEWLINE + 1; // one char on a separate line
1027 if (isVerbatim(params()) || isListings(params())) {
1028 if (op.find_effective()) {
1029 os << '[' << screenLabel() << ']';
1032 os << '[' << screenLabel() << '\n'
1033 // FIXME: We don't know the encoding of the file, default to UTF-8.
1034 << includedFileName(buffer(), params()).fileContents("UTF-8")
1037 return PLAINTEXT_NEWLINE + 1; // one char on a separate line
1040 Buffer const * const ibuf = loadIfNeeded();
1042 docstring const str = '[' + screenLabel() + ']';
1047 if (recursion_error_)
1050 writePlaintextFile(*ibuf, os, op);
1055 void InsetInclude::docbook(XMLStream & xs, OutputParams const & rp) const
1060 // For verbatim and listings, we just include the contents of the file as-is.
1061 bool const verbatim = isVerbatim(params());
1062 bool const listing = isListings(params());
1063 if (listing || verbatim) {
1065 xs << xml::StartTag("programlisting");
1067 xs << xml::StartTag("literallayout");
1069 // FIXME: We don't know the encoding of the file, default to UTF-8.
1070 xs << includedFileName(buffer(), params()).fileContents("UTF-8");
1073 xs << xml::EndTag("programlisting");
1075 xs << xml::EndTag("literallayout");
1080 // We don't know how to input or include non-LyX files. Input it as a comment.
1081 FileName const included_file = includedFileName(buffer(), params());
1082 if (!isLyXFileName(included_file.absFileName())) {
1084 frontend::Alert::warning(_("Unsupported Inclusion"),
1085 bformat(_("LyX does not know how to process included non-LyX files when "
1086 "generating DocBook output. The content of the file will be output as a "
1087 "comment. Offending file:\n%1$s"),
1088 ltrim(params()["filename"])));
1090 // Read the file, output it wrapped into comments.
1091 xs << XMLStream::ESCAPE_NONE << "<!-- Included file: ";
1092 xs << from_utf8(included_file.absFileName());
1093 xs << XMLStream::ESCAPE_NONE << " -->";
1095 xs << XMLStream::ESCAPE_NONE << "<!-- ";
1096 xs << included_file.fileContents("UTF-8");
1097 xs << XMLStream::ESCAPE_NONE << " -->";
1099 xs << XMLStream::ESCAPE_NONE << "<!-- End of included file: ";
1100 xs << from_utf8(included_file.absFileName());
1101 xs << XMLStream::ESCAPE_NONE << " -->";
1104 // In the other cases, we generate the DocBook version and include it.
1105 Buffer const * const ibuf = loadIfNeeded();
1109 if (recursion_error_)
1112 // are we generating only some paragraphs, or all of them?
1113 bool const all_pars = !rp.dryrun ||
1114 (rp.par_begin == 0 &&
1115 rp.par_end == (int) buffer().text().paragraphs().size());
1117 OutputParams op = rp;
1121 op.inInclude = true;
1122 op.docbook_in_par = false;
1123 ibuf->writeDocBookSource(xs.os(), op, Buffer::IncludedFile);
1125 xs << XMLStream::ESCAPE_NONE << "<!-- Included file: ";
1126 xs << from_utf8(included_file.absFileName());
1127 xs << XMLStream::ESCAPE_NONE << " -->";
1132 void InsetInclude::validate(LaTeXFeatures & features) const
1134 LATTEST(&buffer() == &features.buffer());
1136 string incfile = ltrim(to_utf8(params()["filename"]));
1137 string const included_file =
1138 includedFileName(buffer(), params()).absFileName();
1141 if (isLyXFileName(included_file))
1142 writefile = changeExtension(included_file, ".sgml");
1144 writefile = included_file;
1146 if (!features.runparams().nice && !isVerbatim(params()) && !isListings(params())) {
1147 incfile = DocFileName(writefile).mangledFileName();
1148 writefile = makeAbsPath(incfile,
1149 buffer().masterBuffer()->temppath()).absFileName();
1152 features.includeFile(include_label, writefile);
1154 features.useInsetLayout(getLayout());
1155 if (isVerbatim(params()))
1156 features.require("verbatim");
1157 else if (isListings(params())) {
1158 if (buffer().params().use_minted) {
1159 features.require("minted");
1160 string const opts = to_utf8(params()["lstparams"]);
1161 InsetListingsParams lstpars(opts);
1162 if (!lstpars.isFloat() && contains(opts, "caption="))
1163 features.require("lyxmintcaption");
1165 features.require("listings");
1168 // Here we must do the fun stuff...
1169 // Load the file in the include if it needs
1171 Buffer * const tmp = loadIfNeeded();
1175 // the file is loaded
1176 if (checkForRecursiveInclude(tmp))
1178 buffer().pushIncludedBuffer(tmp);
1180 // We must temporarily change features.buffer,
1181 // otherwise it would always be the master buffer,
1182 // and nested includes would not work.
1183 features.setBuffer(*tmp);
1184 // Maybe this is already a child
1185 bool const is_child =
1186 features.runparams().is_child;
1187 features.runparams().is_child = true;
1188 tmp->validate(features);
1189 features.runparams().is_child = is_child;
1190 features.setBuffer(buffer());
1192 buffer().popIncludedBuffer();
1196 void InsetInclude::collectBibKeys(InsetIterator const & /*di*/, FileNameList & checkedFiles) const
1198 Buffer * ibuf = loadIfNeeded();
1202 if (checkForRecursiveInclude(ibuf))
1204 buffer().pushIncludedBuffer(ibuf);
1205 ibuf->collectBibKeys(checkedFiles);
1206 buffer().popIncludedBuffer();
1210 bool InsetInclude::inheritFont() const
1212 return !isVerbatim(params());
1216 void InsetInclude::metrics(MetricsInfo & mi, Dimension & dim) const
1218 LBUFERR(mi.base.bv);
1220 bool use_preview = false;
1221 if (RenderPreview::previewText()) {
1222 graphics::PreviewImage const * pimage =
1223 preview_->getPreviewImage(mi.base.bv->buffer());
1224 use_preview = pimage && pimage->image();
1228 preview_->metrics(mi, dim);
1230 setBroken(!file_exist_ || recursion_error_);
1231 InsetCommand::metrics(mi, dim);
1236 void InsetInclude::draw(PainterInfo & pi, int x, int y) const
1238 LBUFERR(pi.base.bv);
1240 bool use_preview = false;
1241 if (RenderPreview::previewText()) {
1242 graphics::PreviewImage const * pimage =
1243 preview_->getPreviewImage(pi.base.bv->buffer());
1244 use_preview = pimage && pimage->image();
1248 preview_->draw(pi, x, y);
1250 InsetCommand::draw(pi, x, y);
1254 void InsetInclude::write(ostream & os) const
1256 params().Write(os, &buffer());
1260 string InsetInclude::contextMenuName() const
1262 return "context-include";
1266 int InsetInclude::rowFlags() const
1268 return type(params()) == INPUT ? Inline : Display;
1272 docstring InsetInclude::layoutName() const
1274 if (isListings(params()))
1275 return from_ascii("IncludeListings");
1276 return InsetCommand::layoutName();
1284 void InsetInclude::fileChanged() const
1286 Buffer const * const buffer = updateFrontend();
1290 preview_->removePreview(*buffer);
1291 add_preview(*preview_, *this, *buffer);
1292 preview_->startLoading(*buffer);
1298 bool preview_wanted(InsetCommandParams const & params, Buffer const & buffer)
1300 FileName const included_file = includedFileName(buffer, params);
1302 return type(params) == INPUT && params.preview() &&
1303 included_file.isReadableFile();
1307 docstring latexString(InsetInclude const & inset)
1309 odocstringstream ods;
1311 // We don't need to set runparams.encoding since this will be done
1312 // by latex() anyway.
1313 OutputParams runparams(nullptr);
1314 runparams.flavor = Flavor::LaTeX;
1315 runparams.for_preview = true;
1316 inset.latex(os, runparams);
1322 void add_preview(RenderMonitoredPreview & renderer, InsetInclude const & inset,
1323 Buffer const & buffer)
1325 InsetCommandParams const & params = inset.params();
1326 if (RenderPreview::previewText() && preview_wanted(params, buffer)) {
1327 renderer.setAbsFile(includedFileName(buffer, params));
1330 // InsetInclude::latex() throws if generation of LaTeX
1331 // fails, e.g. if lyx2lyx fails because file is too
1332 // new, or knitr fails.
1333 snippet = latexString(inset);
1335 // remove current preview because it is likely
1336 // associated with the previous included file name
1337 renderer.removePreview(buffer);
1338 LYXERR0("Preview of include failed.");
1341 renderer.addPreview(snippet, buffer);
1348 void InsetInclude::addPreview(DocIterator const & /*inset_pos*/,
1349 graphics::PreviewLoader & ploader) const
1351 Buffer const & buffer = ploader.buffer();
1352 if (!preview_wanted(params(), buffer))
1354 preview_->setAbsFile(includedFileName(buffer, params()));
1355 docstring const snippet = latexString(*this);
1356 preview_->addPreview(snippet, ploader);
1360 void InsetInclude::addToToc(DocIterator const & cpit, bool output_active,
1361 UpdateType utype, TocBackend & backend) const
1363 if (isListings(params())) {
1365 label_->addToToc(cpit, output_active, utype, backend);
1366 TocBuilder & b = backend.builder("listing");
1367 b.pushItem(cpit, screenLabel(), output_active);
1368 InsetListingsParams p(to_utf8(params()["lstparams"]));
1369 b.argumentItem(from_utf8(p.getParamValue("caption")));
1373 if (isVerbatim(params())) {
1374 TocBuilder & b = backend.builder("child");
1375 b.pushItem(cpit, screenLabel(), output_active);
1380 Buffer const * const childbuffer = loadIfNeeded();
1382 TocBuilder & b = backend.builder("child");
1383 string const fname = ltrim(to_utf8(params()["filename"]));
1384 // mark non-existent file with MISSING
1385 docstring const str = (file_exist_ ? from_ascii("") : _("MISSING: "))
1386 + from_utf8(onlyFileName(fname)) + " (" + from_utf8(fname) + ")";
1387 b.pushItem(cpit, str, output_active);
1393 if (checkForRecursiveInclude(childbuffer))
1395 buffer().pushIncludedBuffer(childbuffer);
1396 // Update the child's tocBackend. The outliner uses the master's, but
1397 // the navigation menu uses the child's.
1398 childbuffer->tocBackend().update(output_active, utype);
1399 // Include Tocs from children
1400 childbuffer->inset().addToToc(DocIterator(), output_active, utype,
1402 buffer().popIncludedBuffer();
1403 // Copy missing outliner names (though the user has been warned against
1404 // having different document class and module selection between master
1406 for (auto const & name
1407 : childbuffer->params().documentClass().outlinerNames())
1408 backend.addName(name.first, translateIfPossible(name.second));
1412 void InsetInclude::updateCommand()
1417 docstring old_label = label_->getParam("name");
1418 label_->updateLabel(old_label);
1419 // the label might have been adapted (duplicate)
1420 docstring new_label = label_->getParam("name");
1421 if (old_label == new_label)
1424 // update listings parameters...
1425 InsetCommandParams p(INCLUDE_CODE);
1427 InsetListingsParams par(to_utf8(params()["lstparams"]));
1428 par.addParam("label", "{" + to_utf8(new_label) + "}", true);
1429 p["lstparams"] = from_utf8(par.params());
1434 void InsetInclude::updateBuffer(ParIterator const & it, UpdateType utype, bool const deleted)
1436 file_exist_ = includedFileExist();
1437 Buffer const * const childbuffer = loadIfNeeded();
1439 if (!checkForRecursiveInclude(childbuffer))
1440 childbuffer->updateBuffer(Buffer::UpdateChildOnly, utype);
1444 if (!isListings(params()))
1447 Buffer const & master = *buffer().masterBuffer();
1448 listings_label_ = master.B_("Program Listing");
1449 Counters & counters = master.params().documentClass().counters();
1450 docstring const cnt = from_ascii("listing");
1451 bool const hasCounter = counters.hasCounter(cnt);
1453 counters.saveLastCounter();
1454 counters.step(cnt, utype);
1455 listings_label_ += " " + convert<docstring>(counters.value(cnt));
1459 label_->updateBuffer(it, utype, deleted);
1462 counters.restoreLastCounter();
1464 InsetListingsParams const par(to_utf8(params()["lstparams"]));
1465 if (par.getParamValue("caption").empty()) {
1466 listings_label_ = buffer().B_("Program Listing");
1472 bool InsetInclude::includedFileExist() const
1474 // check whether the included file exist
1475 string incFileName = ltrim(to_utf8(params()["filename"]));
1477 support::makeAbsPath(incFileName,
1478 support::onlyPath(buffer().absFileName()));