* Licence details can be found in the file COPYING.
*
* \author Lars Gullik Bjønnes
- * \author Richard Heck (conversion to InsetCommand)
+ * \author Richard Kimberly Heck (conversion to InsetCommand)
*
* Full author contact details are available in file CREDITS.
*/
#include "BufferView.h"
#include "Converter.h"
#include "Cursor.h"
-#include "DispatchResult.h"
#include "Encoding.h"
#include "ErrorList.h"
#include "Exporter.h"
#include "FuncRequest.h"
#include "FuncStatus.h"
#include "LaTeXFeatures.h"
-#include "LayoutFile.h"
#include "LayoutModuleList.h"
#include "LyX.h"
-#include "Lexer.h"
#include "MetricsInfo.h"
#include "output_plaintext.h"
#include "output_xhtml.h"
-#include "OutputParams.h"
#include "texstream.h"
#include "TextClass.h"
#include "TocBackend.h"
#include "support/gettext.h"
#include "support/lassert.h"
#include "support/lstrings.h" // contains
-#include "support/lyxalgo.h"
#include "support/mutex.h"
#include "support/ExceptionMessage.h"
-#include "support/bind.h"
-
using namespace std;
using namespace lyx::support;
InsetInclude::InsetInclude(Buffer * buf, InsetCommandParams const & p)
: InsetCommand(buf, p), include_label(uniqueID()),
preview_(make_unique<RenderMonitoredPreview>(this)), failedtoload_(false),
- set_label_(false), label_(nullptr), child_buffer_(nullptr), file_exist_(false)
+ label_(nullptr), child_buffer_(nullptr), file_exist_(false),
+ recursion_error_(false)
{
- preview_->connect([=](){ fileChanged(); });
+ preview_->connect([this](){ fileChanged(); });
if (isListings(params())) {
InsetListingsParams listing_params(to_utf8(p["lstparams"]));
InsetInclude::InsetInclude(InsetInclude const & other)
: InsetCommand(other), include_label(other.include_label),
preview_(make_unique<RenderMonitoredPreview>(this)), failedtoload_(false),
- set_label_(false), label_(nullptr), child_buffer_(nullptr),
- file_exist_(other.file_exist_)
+ label_(nullptr), child_buffer_(nullptr),
+ file_exist_(other.file_exist_),recursion_error_(other.recursion_error_)
{
- preview_->connect([=](){ fileChanged(); });
+ preview_->connect([this](){ fileChanged(); });
if (other.label_)
label_ = new InsetLabel(*other.label_);
// reset in order to allow loading new file
failedtoload_ = false;
+ recursion_error_ = false;
InsetCommand::setParams(p);
- set_label_ = false;
if (preview_->monitoring())
preview_->stopMonitoring();
docstring InsetInclude::screenLabel() const
{
- docstring pre = file_exist_ ? docstring() : _("FILE MISSING:");
+ docstring pre = file_exist_ ? docstring() : _("MISSING:");
docstring temp;
}
-Buffer * InsetInclude::getChildBuffer() const
-{
- Buffer * childBuffer = loadIfNeeded();
-
- // FIXME RECURSIVE INCLUDE
- // This isn't sufficient, as the inclusion could be downstream.
- // But it'll have to do for now.
- return (childBuffer == &buffer()) ? nullptr : childBuffer;
-}
-
-
Buffer * InsetInclude::loadIfNeeded() const
{
// This is for background export and preview. We don't even want to
return nullptr;
Buffer * child = theBufferList().getBuffer(included_file);
+ if (checkForRecursiveInclude(child))
+ return child;
+
if (!child) {
// the readonly flag can/will be wrong, not anymore I think.
if (!included_file.exists()) {
// Buffer creation is not possible.
return nullptr;
+ buffer().pushIncludedBuffer(child);
// Set parent before loading, such that macros can be tracked
child->setParent(&buffer());
child->setParent(nullptr);
//close the buffer we just opened
theBufferList().release(child);
+ buffer().popIncludedBuffer();
return nullptr;
}
+ buffer().popIncludedBuffer();
if (!child->errorList("Parse").empty()) {
// FIXME: Do something.
}
}
+bool InsetInclude::checkForRecursiveInclude(
+ Buffer const * cbuf, bool silent) const
+{
+ if (recursion_error_)
+ return true;
+
+ if (!buffer().isBufferIncluded(cbuf))
+ return false;
+
+ if (!silent) {
+ docstring const msg = _("The file\n%1$s\n has attempted to include itself.\n"
+ "The document set will not work properly until this is fixed!");
+ frontend::Alert::warning(_("Recursive Include"),
+ bformat(msg, from_utf8(cbuf->fileName().absFileName())));
+ }
+ recursion_error_ = true;
+ return true;
+}
+
+
void InsetInclude::latex(otexstream & os, OutputParams const & runparams) const
{
string incfile = ltrim(to_utf8(params()["filename"]));
}
FileName const included_file = includedFileName(buffer(), params());
+ Buffer const * const masterBuffer = buffer().masterBuffer();
- // Check we're not trying to include ourselves.
- // FIXME RECURSIVE INCLUDE
- // This isn't sufficient, as the inclusion could be downstream.
- // But it'll have to do for now.
- if (isInputOrInclude(params()) &&
- buffer().absFileName() == included_file.absFileName())
- {
- Alert::error(_("Recursive input"),
- bformat(_("Attempted to include file %1$s in itself! "
- "Ignoring inclusion."), from_utf8(incfile)));
+ if (runparams.inDeletedInset) {
+ // We cannot strike-out whole children,
+ // so we just output a note.
+ os << "\\textbf{"
+ << bformat(buffer().B_("[INCLUDED FILE %1$s DELETED!]"),
+ from_utf8(included_file.onlyFileName()))
+ << "}";
return;
}
- Buffer const * const masterBuffer = buffer().masterBuffer();
-
// if incfile is relative, make it relative to the master
// buffer directory.
if (!FileName::isAbsolute(incfile)) {
FileName const writefile(makeAbsPath(mangled, runparams.for_preview ?
buffer().temppath() : masterBuffer->temppath()));
- LYXERR(Debug::LATEX, "incfile:" << incfile);
- LYXERR(Debug::LATEX, "exportfile:" << exportfile);
- LYXERR(Debug::LATEX, "writefile:" << writefile);
+ LYXERR(Debug::OUTFILE, "incfile:" << incfile);
+ LYXERR(Debug::OUTFILE, "exportfile:" << exportfile);
+ LYXERR(Debug::OUTFILE, "writefile:" << writefile);
string const tex_format = flavor2format(runparams.flavor);
break;
}
case LISTINGS: {
- // Here, listings and minted have sligthly different behaviors.
+ // Here, listings and minted have slightly different behaviors.
// Using listings, it is always possible to have a caption,
// even for non-floats. Using minted, only floats can have a
// caption. So, with minted we use the following strategy.
if (checksum_in != checksum_out) {
if (!included_file.copyTo(writefile)) {
// FIXME UNICODE
- LYXERR(Debug::LATEX,
+ LYXERR(Debug::OUTFILE,
to_utf8(bformat(_("Could not copy the file\n%1$s\n"
"into the temporary directory."),
from_utf8(included_file.absFileName()))));
return;
}
+ if (recursion_error_)
+ return;
+
if (!runparams.silent) {
if (tmp->params().baseClass() != masterBuffer->params().baseClass()) {
// FIXME UNICODE
}
-docstring InsetInclude::xhtml(XHTMLStream & xs, OutputParams const & rp) const
+docstring InsetInclude::xhtml(XMLStream & xs, OutputParams const & rp) const
{
if (rp.inComment)
return docstring();
bool const listing = isListings(params());
if (listing || isVerbatim(params())) {
if (listing)
- xs << html::StartTag("pre");
+ xs << xml::StartTag("pre");
// FIXME: We don't know the encoding of the file, default to UTF-8.
xs << includedFileName(buffer(), params()).fileContents("UTF-8");
if (listing)
- xs << html::EndTag("pre");
+ xs << xml::EndTag("pre");
return docstring();
}
// In the other cases, we will generate the HTML and include it.
- // Check we're not trying to include ourselves.
- // FIXME RECURSIVE INCLUDE
- if (buffer().absFileName() == included_file.absFileName()) {
- Alert::error(_("Recursive input"),
- bformat(_("Attempted to include file %1$s in itself! "
- "Ignoring inclusion."), ltrim(params()["filename"])));
- return docstring();
- }
-
Buffer const * const ibuf = loadIfNeeded();
if (!ibuf)
return docstring();
+ if (recursion_error_)
+ return docstring();
+
// are we generating only some paragraphs, or all of them?
bool const all_pars = !rp.dryrun ||
(rp.par_begin == 0 &&
op.par_begin = 0;
op.par_end = 0;
ibuf->writeLyXHTMLSource(xs.os(), op, Buffer::IncludedFile);
- } else
- xs << XHTMLStream::ESCAPE_NONE
- << "<!-- Included file: "
- << from_utf8(included_file.absFileName())
- << XHTMLStream::ESCAPE_NONE
- << " -->";
+ } else {
+ xs << XMLStream::ESCAPE_NONE << "<!-- Included file: ";
+ xs << from_utf8(included_file.absFileName());
+ xs << XMLStream::ESCAPE_NONE << " -->";
+ }
+
return docstring();
}
{
// just write the filename if we're making a tooltip or toc entry,
// or are generating this for advanced search
- if (op.for_tooltip || op.for_toc || op.for_search) {
+ if (op.for_tooltip || op.for_toc || op.find_effective()) {
os << '[' << screenLabel() << '\n'
<< ltrim(getParam("filename")) << "\n]";
return PLAINTEXT_NEWLINE + 1; // one char on a separate line
}
if (isVerbatim(params()) || isListings(params())) {
- if (op.for_search) {
+ if (op.find_effective()) {
os << '[' << screenLabel() << ']';
}
else {
os << str;
return str.size();
}
+
+ if (recursion_error_)
+ return 0;
+
writePlaintextFile(*ibuf, os, op);
return 0;
}
-int InsetInclude::docbook(odocstream & os, OutputParams const & runparams) const
+void InsetInclude::docbook(XMLStream & xs, OutputParams const & rp) const
{
- string incfile = ltrim(to_utf8(params()["filename"]));
+ if (rp.inComment)
+ return;
- // Do nothing if no file name has been specified
- if (incfile.empty())
- return 0;
+ // For verbatim and listings, we just include the contents of the file as-is.
+ bool const verbatim = isVerbatim(params());
+ bool const listing = isListings(params());
+ if (listing || verbatim) {
+ if (listing)
+ xs << xml::StartTag("programlisting");
+ else if (verbatim)
+ xs << xml::StartTag("literallayout");
- string const included_file = includedFileName(buffer(), params()).absFileName();
+ // FIXME: We don't know the encoding of the file, default to UTF-8.
+ xs << includedFileName(buffer(), params()).fileContents("UTF-8");
- // Check we're not trying to include ourselves.
- // FIXME RECURSIVE INCLUDE
- // This isn't sufficient, as the inclusion could be downstream.
- // But it'll have to do for now.
- if (buffer().absFileName() == included_file) {
- Alert::error(_("Recursive input"),
- bformat(_("Attempted to include file %1$s in itself! "
- "Ignoring inclusion."), from_utf8(incfile)));
- return 0;
- }
+ if (listing)
+ xs << xml::EndTag("programlisting");
+ else if (verbatim)
+ xs << xml::EndTag("literallayout");
- string exppath = incfile;
- if (!runparams.export_folder.empty()) {
- exppath = makeAbsPath(exppath, runparams.export_folder).realPath();
- FileName(exppath).onlyPath().createPath();
+ return;
}
- // write it to a file (so far the complete file)
- string const exportfile = changeExtension(exppath, ".sgml");
- DocFileName writefile(changeExtension(included_file, ".sgml"));
-
- Buffer * tmp = loadIfNeeded();
- if (tmp) {
- string const mangled = writefile.mangledFileName();
- writefile = makeAbsPath(mangled,
- buffer().masterBuffer()->temppath());
- if (!runparams.nice)
- incfile = mangled;
-
- LYXERR(Debug::LATEX, "incfile:" << incfile);
- LYXERR(Debug::LATEX, "exportfile:" << exportfile);
- LYXERR(Debug::LATEX, "writefile:" << writefile);
-
- tmp->makeDocBookFile(writefile, runparams, Buffer::OnlyBody);
+ // We don't know how to input or include non-LyX files. Input it as a comment.
+ FileName const included_file = includedFileName(buffer(), params());
+ if (!isLyXFileName(included_file.absFileName())) {
+ if (!rp.silent)
+ frontend::Alert::warning(_("Unsupported Inclusion"),
+ bformat(_("LyX does not know how to process included non-LyX files when "
+ "generating DocBook output. The content of the file will be output as a "
+ "comment. Offending file:\n%1$s"),
+ ltrim(params()["filename"])));
+
+ // Read the file, output it wrapped into comments.
+ xs << XMLStream::ESCAPE_NONE << "<!-- Included file: ";
+ xs << from_utf8(included_file.absFileName());
+ xs << XMLStream::ESCAPE_NONE << " -->";
+
+ xs << XMLStream::ESCAPE_NONE << "<!-- ";
+ xs << included_file.fileContents("UTF-8");
+ xs << XMLStream::ESCAPE_NONE << " -->";
+
+ xs << XMLStream::ESCAPE_NONE << "<!-- End of included file: ";
+ xs << from_utf8(included_file.absFileName());
+ xs << XMLStream::ESCAPE_NONE << " -->";
}
- runparams.exportdata->addExternalFile("docbook", writefile,
- exportfile);
- runparams.exportdata->addExternalFile("docbook-xml", writefile,
- exportfile);
+ // In the other cases, we generate the DocBook version and include it.
+ Buffer const * const ibuf = loadIfNeeded();
+ if (!ibuf)
+ return;
- if (isVerbatim(params()) || isListings(params())) {
- os << "<inlinegraphic fileref=\""
- << '&' << include_label << ';'
- << "\" format=\"linespecific\">";
- } else
- os << '&' << include_label << ';';
+ if (recursion_error_)
+ return;
- return 0;
+ // are we generating only some paragraphs, or all of them?
+ bool const all_pars = !rp.dryrun ||
+ (rp.par_begin == 0 &&
+ rp.par_end == (int) buffer().text().paragraphs().size());
+
+ OutputParams op = rp;
+ if (all_pars) {
+ op.par_begin = 0;
+ op.par_end = 0;
+ op.inInclude = true;
+ op.docbook_in_par = false;
+ ibuf->writeDocBookSource(xs.os(), op, Buffer::IncludedFile);
+ } else {
+ xs << XMLStream::ESCAPE_NONE << "<!-- Included file: ";
+ xs << from_utf8(included_file.absFileName());
+ xs << XMLStream::ESCAPE_NONE << " -->";
+ }
}
// Load the file in the include if it needs
// to be loaded:
Buffer * const tmp = loadIfNeeded();
- if (tmp) {
- // the file is loaded
- // make sure the buffer isn't us
- // FIXME RECURSIVE INCLUDES
- // This is not sufficient, as recursive includes could be
- // more than a file away. But it will do for now.
- if (tmp && tmp != &buffer()) {
- // We must temporarily change features.buffer,
- // otherwise it would always be the master buffer,
- // and nested includes would not work.
- features.setBuffer(*tmp);
- // Maybe this is already a child
- bool const is_child =
- features.runparams().is_child;
- features.runparams().is_child = true;
- tmp->validate(features);
- features.runparams().is_child = is_child;
- features.setBuffer(buffer());
- }
- }
+ if (!tmp)
+ return;
+
+ // the file is loaded
+ if (checkForRecursiveInclude(tmp))
+ return;
+ buffer().pushIncludedBuffer(tmp);
+
+ // We must temporarily change features.buffer,
+ // otherwise it would always be the master buffer,
+ // and nested includes would not work.
+ features.setBuffer(*tmp);
+ // Maybe this is already a child
+ bool const is_child =
+ features.runparams().is_child;
+ features.runparams().is_child = true;
+ tmp->validate(features);
+ features.runparams().is_child = is_child;
+ features.setBuffer(buffer());
+
+ buffer().popIncludedBuffer();
}
void InsetInclude::collectBibKeys(InsetIterator const & /*di*/, FileNameList & checkedFiles) const
{
- Buffer * child = loadIfNeeded();
- if (!child)
+ Buffer * ibuf = loadIfNeeded();
+ if (!ibuf)
return;
- // FIXME RECURSIVE INCLUDE
- // This isn't sufficient, as the inclusion could be downstream.
- // But it'll have to do for now.
- if (child == &buffer())
+
+ if (checkForRecursiveInclude(ibuf))
return;
- child->collectBibKeys(checkedFiles);
+ buffer().pushIncludedBuffer(ibuf);
+ ibuf->collectBibKeys(checkedFiles);
+ buffer().popIncludedBuffer();
+}
+
+
+bool InsetInclude::inheritFont() const
+{
+ return !isVerbatim(params());
}
if (use_preview) {
preview_->metrics(mi, dim);
} else {
- if (!set_label_) {
- set_label_ = true;
- button_.update(screenLabel(), true, false, !file_exist_);
- }
- button_.metrics(mi, dim);
+ setBroken(!file_exist_ || recursion_error_);
+ InsetCommand::metrics(mi, dim);
}
-
- Box b(0, dim.wid, -dim.asc, dim.des);
- button_.setBox(b);
}
if (use_preview)
preview_->draw(pi, x, y);
else
- button_.draw(pi, x, y);
+ InsetCommand::draw(pi, x, y);
}
}
-Inset::DisplayType InsetInclude::display() const
+int InsetInclude::rowFlags() const
{
- return type(params()) == INPUT ? Inline : AlignCenter;
+ return type(params()) == INPUT ? Inline : Display;
}
// We don't need to set runparams.encoding since this will be done
// by latex() anyway.
OutputParams runparams(nullptr);
- runparams.flavor = OutputParams::LATEX;
+ runparams.flavor = Flavor::LaTeX;
runparams.for_preview = true;
inset.latex(os, runparams);
InsetCommandParams const & params = inset.params();
if (RenderPreview::previewText() && preview_wanted(params, buffer)) {
renderer.setAbsFile(includedFileName(buffer, params));
- docstring const snippet = latexString(inset);
+ docstring snippet;
+ try {
+ // InsetInclude::latex() throws if generation of LaTeX
+ // fails, e.g. if lyx2lyx fails because file is too
+ // new, or knitr fails.
+ snippet = latexString(inset);
+ } catch (...) {
+ // remove current preview because it is likely
+ // associated with the previous included file name
+ renderer.removePreview(buffer);
+ LYXERR0("Preview of include failed.");
+ return;
+ }
renderer.addPreview(snippet, buffer);
}
}
InsetListingsParams p(to_utf8(params()["lstparams"]));
b.argumentItem(from_utf8(p.getParamValue("caption")));
b.pop();
- } else if (isVerbatim(params())) {
+ return;
+ }
+ if (isVerbatim(params())) {
TocBuilder & b = backend.builder("child");
b.pushItem(cpit, screenLabel(), output_active);
b.pop();
- } else {
- Buffer const * const childbuffer = getChildBuffer();
-
- TocBuilder & b = backend.builder("child");
- docstring str = childbuffer ? childbuffer->fileName().displayName()
- : from_ascii("?");
- b.pushItem(cpit, str, output_active);
- b.pop();
-
- if (!childbuffer)
- return;
-
- // Update the child's tocBackend. The outliner uses the master's, but
- // the navigation menu uses the child's.
- childbuffer->tocBackend().update(output_active, utype);
- // Include Tocs from children
- childbuffer->inset().addToToc(DocIterator(), output_active, utype,
- backend);
- //Copy missing outliner names (though the user has been warned against
- //having different document class and module selection between master
- //and child).
- for (auto const & name
- : childbuffer->params().documentClass().outlinerNames())
- backend.addName(name.first, translateIfPossible(name.second));
+ return;
}
+ // the common case
+ Buffer const * const childbuffer = loadIfNeeded();
+
+ TocBuilder & b = backend.builder("child");
+ string const fname = ltrim(to_utf8(params()["filename"]));
+ // mark non-existent file with MISSING
+ docstring const str = (file_exist_ ? from_ascii("") : _("MISSING: "))
+ + from_utf8(onlyFileName(fname)) + " (" + from_utf8(fname) + ")";
+ b.pushItem(cpit, str, output_active);
+ b.pop();
+
+ if (!childbuffer)
+ return;
+
+ if (checkForRecursiveInclude(childbuffer))
+ return;
+ buffer().pushIncludedBuffer(childbuffer);
+ // Update the child's tocBackend. The outliner uses the master's, but
+ // the navigation menu uses the child's.
+ childbuffer->tocBackend().update(output_active, utype);
+ // Include Tocs from children
+ childbuffer->inset().addToToc(DocIterator(), output_active, utype,
+ backend);
+ buffer().popIncludedBuffer();
+ // Copy missing outliner names (though the user has been warned against
+ // having different document class and module selection between master
+ // and child).
+ for (auto const & name
+ : childbuffer->params().documentClass().outlinerNames())
+ backend.addName(name.first, translateIfPossible(name.second));
}
void InsetInclude::updateBuffer(ParIterator const & it, UpdateType utype, bool const deleted)
{
file_exist_ = includedFileExist();
-
- button_.update(screenLabel(), true, false, !file_exist_);
-
- Buffer const * const childbuffer = getChildBuffer();
+ Buffer const * const childbuffer = loadIfNeeded();
if (childbuffer) {
- childbuffer->updateBuffer(Buffer::UpdateChildOnly, utype);
+ if (!checkForRecursiveInclude(childbuffer))
+ childbuffer->updateBuffer(Buffer::UpdateChildOnly, utype);
return;
}
+
if (!isListings(params()))
return;