#include "mathed/InsetMath.h"
#include "insets/InsetNote.h"
+#include "support/debug.h"
#include "support/lassert.h"
#include "support/textutils.h"
// Higher-level convenience functions.
-void openParTag(XMLStream & xs, const Paragraph * par, const Paragraph * prevpar)
+void openParTag(XMLStream & xs, const Paragraph * par, const Paragraph * prevpar, const OutputParams & runparams)
{
if (par == prevpar)
prevpar = nullptr;
+ // If the previous paragraph is empty, don't consider it when opening wrappers.
+ if (prevpar && prevpar->empty() && !prevpar->allowEmpty())
+ prevpar = nullptr;
+
// When should the wrapper be opened here? Only if the previous paragraph has the SAME wrapper tag
// (usually, they won't have the same layout) and the CURRENT one allows merging.
// The main use case is author information in several paragraphs: if the name of the author is the
// next paragraph is the affiliation, then it should be output in the same <author> tag (different
// layout, same wrapper tag).
Layout const & lay = par->layout();
- bool openWrapper = lay.docbookwrappertag() != "NONE";
- if (prevpar != nullptr) {
+ bool openWrapper = lay.docbookwrappertag() != "NONE" && !runparams.docbook_ignore_wrapper;
+
+ if (prevpar != nullptr && !runparams.docbook_ignore_wrapper) {
Layout const & prevlay = prevpar->layout();
if (prevlay.docbookwrappertag() != "NONE") {
if (prevlay.docbookwrappertag() == lay.docbookwrappertag() &&
}
}
+ xml::openTag(xs, lay.docbookitemwrappertag(), lay.docbookitemwrapperattr(), lay.docbookitemwrappertagtype());
xml::openTag(xs, lay.docbookitemtag(), lay.docbookitemattr(), lay.docbookitemtagtype());
xml::openTag(xs, lay.docbookiteminnertag(), lay.docbookiteminnerattr(), lay.docbookiteminnertagtype());
}
-void closeParTag(XMLStream & xs, Paragraph const * par, Paragraph const * nextpar)
+void closeParTag(XMLStream & xs, Paragraph const * par, Paragraph const * nextpar, const OutputParams & runparams)
{
if (par == nextpar)
nextpar = nullptr;
+ // If the next paragraph is empty, don't consider it when closing wrappers.
+ if (nextpar && nextpar->empty() && !nextpar->allowEmpty())
+ nextpar = nullptr;
+
// See comment in openParTag.
Layout const & lay = par->layout();
- bool closeWrapper = lay.docbookwrappertag() != "NONE";
- if (nextpar != nullptr) {
+ bool closeWrapper = lay.docbookwrappertag() != "NONE" && !runparams.docbook_ignore_wrapper;
+
+ if (nextpar != nullptr && !runparams.docbook_ignore_wrapper) {
Layout const & nextlay = nextpar->layout();
if (nextlay.docbookwrappertag() != "NONE") {
if (nextlay.docbookwrappertag() == lay.docbookwrappertag() &&
// Main logic.
xml::closeTag(xs, lay.docbookiteminnertag(), lay.docbookiteminnertagtype());
xml::closeTag(xs, lay.docbookitemtag(), lay.docbookitemtagtype());
+ xml::closeTag(xs, lay.docbookitemwrappertag(), lay.docbookitemwrappertagtype());
xml::closeTag(xs, lay.docbookinnertag(), lay.docbookinnertagtype());
xml::closeTag(xs, lay.docbooktag(), lay.docbooktagtype());
if (closeWrapper)
size_t nInsets = std::distance(par->insetList().begin(), par->insetList().end());
auto parSize = (size_t) par->size();
- // If this LyX code does not produce any output, it can be safely ignored in the following checks: if this thing
- // is present in the paragraph, it has no impact on the definition of the special case (i.e. whether or not
- // a <para> tag should be output).
- auto isLyxCodeToIgnore = [](InsetCode x) { return x == TOC_CODE || x == NOTE_CODE; };
-
- // TODO: if a paragraph *only* contains floats, listings, bibliographies, etc., should this be considered as a
- // special case? If so, the code could be largely simplifies (all the calls to all_of, basically) and optimised
- // at the compilation stage.
-
// Plain layouts must be ignored.
special_case |= buf.params().documentClass().isPlainLayout(par->layout()) && !runparams.docbook_force_pars;
// Equations do not deserve their own paragraph (DocBook allows them outside paragraphs).
special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), [](InsetList::Element inset) {
return inset.inset && inset.inset->asInsetMath() && inset.inset->asInsetMath()->getType() != hullSimple;
});
- // Tables do not deserve their own paragraphs (DocBook allows them outside paragraphs).
- special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), [isLyxCodeToIgnore](InsetList::Element inset) {
- return inset.inset->lyxCode() == TABULAR_CODE || isLyxCodeToIgnore(inset.inset->lyxCode());
- });
- // Floats cannot be in paragraphs.
- special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), [isLyxCodeToIgnore](InsetList::Element inset) {
- return inset.inset->lyxCode() == FLOAT_CODE || isLyxCodeToIgnore(inset.inset->lyxCode());
- });
- // Bibliographies cannot be in paragraphs. Bibitems should still be handled as paragraphs, though
- // (see makeParagraphBibliography).
- special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), [isLyxCodeToIgnore](InsetList::Element inset) {
- return inset.inset->lyxCode() == BIBTEX_CODE || isLyxCodeToIgnore(inset.inset->lyxCode());
- });
- // ERTs are in comments, not paragraphs.
- special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), [isLyxCodeToIgnore](InsetList::Element inset) {
- return inset.inset->lyxCode() == ERT_CODE || isLyxCodeToIgnore(inset.inset->lyxCode());
- });
- // Listings should not get into their own paragraph.
- special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), [isLyxCodeToIgnore](InsetList::Element inset) {
- return inset.inset->lyxCode() == LISTINGS_CODE || isLyxCodeToIgnore(inset.inset->lyxCode());
- });
- // Boxes cannot get into their own paragraph.
- special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), [isLyxCodeToIgnore](InsetList::Element inset) {
- return inset.inset->lyxCode() == BOX_CODE || isLyxCodeToIgnore(inset.inset->lyxCode());
- });
- // Includes should not have a paragraph.
- special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), [isLyxCodeToIgnore](InsetList::Element inset) {
- return inset.inset->lyxCode() == INCLUDE_CODE || isLyxCodeToIgnore(inset.inset->lyxCode());
- });
- // Glossaries should not have a paragraph.
- special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), [isLyxCodeToIgnore](InsetList::Element inset) {
- return inset.inset->lyxCode() == NOMENCL_PRINT_CODE || isLyxCodeToIgnore(inset.inset->lyxCode());
- });
+ // Things that should not get into their own paragraph. (Only valid for DocBook.)
+ static std::set<InsetCode> lyxCodeSpecialCases = {
+ TABULAR_CODE,
+ FLOAT_CODE,
+ BIBTEX_CODE, // Bibliographies cannot be in paragraphs. Bibitems should still be handled as paragraphs,
+ // though (see makeParagraphBibliography).
+ ERT_CODE, // ERTs are in comments, not paragraphs.
+ LISTINGS_CODE,
+ BOX_CODE,
+ INCLUDE_CODE,
+ NOMENCL_PRINT_CODE,
+ TOC_CODE, // To be ignored in DocBook, the processor afterwards should deal with ToCs.
+ NOTE_CODE // Notes do not produce any output.
+ };
+ auto isLyxCodeSpecialCase = [](InsetList::Element inset) {
+ return lyxCodeSpecialCases.find(inset.inset->lyxCode()) != lyxCodeSpecialCases.end();
+ };
+ special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), isLyxCodeSpecialCase);
+
+ // Flex elements (InsetLayout) have their own parameter to control the special case.
+ auto isFlexSpecialCase = [](InsetList::Element inset) {
+ if (inset.inset->lyxCode() != FLEX_CODE)
+ return false;
+
+ // Standard condition: check the parameter.
+ if (inset.inset->getLayout().docbooknotinpara())
+ return true;
+
+ // If the parameter is not set, maybe the flex inset only contains things that should match the standard
+ // condition. In this case, isLyxCodeSpecialCase must also check for bibitems...
+ auto isLyxCodeSpecialCase = [](InsetList::Element inset) {
+ return lyxCodeSpecialCases.find(inset.inset->lyxCode()) != lyxCodeSpecialCases.end() ||
+ inset.inset->lyxCode() == BIBITEM_CODE;
+ };
+ if (InsetText * text = inset.inset->asInsetText()) {
+ for (auto const & par : text->paragraphs()) {
+ size_t nInsets = std::distance(par.insetList().begin(), par.insetList().end());
+ auto parSize = (size_t) par.size();
+
+ if (nInsets == 1 && par.insetList().begin()->inset->lyxCode() == BIBITEM_CODE)
+ return true;
+ if (nInsets != parSize)
+ return false;
+ if (!std::all_of(par.insetList().begin(), par.insetList().end(), isLyxCodeSpecialCase))
+ return false;
+ }
+ return true;
+ }
+
+ // No case matched: give up.
+ return false;
+ };
+ special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), isFlexSpecialCase);
+
+ // Open a paragraph if it is allowed, we are not already within a paragraph, and the insets in the paragraph do
+ // not forbid paragraphs (aka special cases).
bool const open_par = runparams.docbook_make_pars
&& !runparams.docbook_in_par
&& !special_case;
continue;
if (open_par)
- openParTag(xs, &*par, prevpar);
+ openParTag(xs, &*par, prevpar, runparams);
xs << XMLStream::ESCAPE_NONE << parXML;
if (close_par)
- closeParTag(xs, &*par, (nextpar != end) ? &*nextpar : nullptr);
+ closeParTag(xs, &*par, (nextpar != end) ? &*nextpar : nullptr, runparams);
}
}
// Output the opening tag for this environment, but only if it has not been previously opened (condition
// implemented in openParTag).
auto prevpar = text.paragraphs().getParagraphBefore(par);
- openParTag(xs, &*par, prevpar); // TODO: switch in layout for par/block?
+ openParTag(xs, &*par, prevpar, runparams);
// Generate the contents of this environment. There is a special case if this is like some environment.
Layout const & style = par->layout();
}
// Close the environment.
- closeParTag(xs, &*par, (nextpar != end) ? &*nextpar : nullptr); // TODO: switch in layout for par/block?
+ closeParTag(xs, &*par, (nextpar != end) ? &*nextpar : nullptr, runparams);
}
// Generate this command.
auto prevpar = text.paragraphs().getParagraphBefore(par);
- openParTag(xs, &*par, prevpar);
+ openParTag(xs, &*par, prevpar, runparams);
auto pars = par->simpleDocBookOnePar(buf, runparams,text.outerFont(distance(begin, par)));
for (auto & parXML : pars)
// TODO: decide what to do with openParTag/closeParTag in new lines.
xs << XMLStream::ESCAPE_NONE << parXML;
- closeParTag(xs, &*par, (nextpar != end) ? &*nextpar : nullptr);
+ closeParTag(xs, &*par, (nextpar != end) ? &*nextpar : nullptr, runparams);
}
// Traverse everything that might belong to <info>.
bool hasAbstractLayout = false;
+ static depth_type INVALID_DEPTH = 100000;
+ depth_type abstractDepth = INVALID_DEPTH;
pit_type cpit = bpit;
for (; cpit < epit; ++cpit) {
// Skip paragraphs that don't generate anything in DocBook.
// If this is marked as an abstract by the layout, put it in the right set.
if (style.docbookabstract()) {
hasAbstractLayout = true;
+ abstractDepth = par.getDepth();
abstractWithLayout.emplace(cpit);
continue;
}
+ // Deeper paragraphs following the abstract must still be considered as part of the abstract.
+ // For instance, this includes lists. There should not be any other kind of paragraph in between.
+ if (abstractDepth != INVALID_DEPTH && style.docbookininfo() == "never") {
+ if (par.getDepth() > abstractDepth) {
+ abstractWithLayout.emplace(cpit);
+ continue;
+ }
+ if (par.getDepth() == abstractDepth) {
+ // This is not an abstract paragraph and it should not either be considered as part
+ // of it. It breaks the rule that abstract paragraphs must follow each other.
+ abstractDepth = INVALID_DEPTH;
+ break;
+ }
+ }
+
// Based on layout information, store this paragraph in one set: should be in <info>, must be,
// or abstract (either because of layout or of position).
if (style.docbookininfo() == "always")
(style.docbooktag() == "NONE" || style.docbooktag() == "para") &&
style.docbookwrappertag() == "NONE")
// In this case, it is very likely that style.docbookininfo() == "never"! Be extra careful
- // about anything that gets caught here.
+ // about anything that gets caught here. For instance, don't ake into account
abstractNoLayout.emplace(cpit);
else // This should definitely not be in <info>.
break;
auto rp = runparams;
rp.docbook_generate_info = false;
+ rp.docbook_ignore_wrapper = true;
set<pit_type> doneParas; // Paragraphs that have already been converted (mostly to deal with lists).
for (auto const & p : info.abstract) {
auto oldPar = paragraphs.iterator_at(p);
auto newPar = makeAny(text, buf, xs2, rp, oldPar);
+ // Find insets that should go outside the abstract.
auto subinfos = gatherInfo(oldPar);
for (auto & subinfo: subinfos)
infoInsets.insert(subinfo);
// - Finally, always output the abstract as the last item of the <info>, as it requires special treatment
// (especially if it contains several paragraphs that are empty).
if (hasAbstract) {
- if (info.abstractLayout) {
- xs << XMLStream::ESCAPE_NONE << abstract;
- xs << xml::CR();
- } else {
- string tag = paragraphs[*info.abstract.begin()].layout().docbookforceabstracttag();
- if (tag == "NONE")
- tag = "abstract";
-
- if (!xs.isLastTagCR())
- xs << xml::CR();
+ string tag = paragraphs[*info.abstract.begin()].layout().docbookforceabstracttag();
+ if (tag == "NONE")
+ tag = "abstract";
- xs << xml::StartTag(tag);
- xs << xml::CR();
- xs << XMLStream::ESCAPE_NONE << abstract;
- xs << xml::EndTag(tag);
+ if (!xs.isLastTagCR())
xs << xml::CR();
- }
+
+ xs << xml::StartTag(tag);
+ xs << xml::CR();
+ xs << XMLStream::ESCAPE_NONE << abstract;
+ xs << xml::EndTag(tag);
+ xs << xml::CR();
}
// End the <info> tag if it was started.
return;
});
- std::stack<std::pair<int, string>> headerLevels; // Used to determine when to open/close sections: store the depth
- // of the section and the tag that was used to open it.
-
// Detect whether the document contains sections. If there are no sections, treatment is largely simplified.
// In particular, there can't be an abstract, unless it is manually marked.
bool documentHasSections;
outputDocBookInfo(text, buf, xs, runparams, paragraphs, info);
bpit = info.epit;
- // Then, iterate through the paragraphs of this document.
- bool currentlyInAppendix = false;
+ std::stack<std::pair<int, string>> headerLevels; // Used to determine when to open/close sections: store the depth
+ // of the section and the tag that was used to open it.
+ // Then, iterate through the paragraphs of this document.
auto par = text.paragraphs().iterator_at(bpit);
auto end = text.paragraphs().iterator_at(epit);
while (par != end) {
- OutputParams ourparams = runparams;
-
- if (par->params().startOfAppendix())
- currentlyInAppendix = true;
+ // Skip paragraphs not producing any output.
if (hasOnlyNotes(*par)) {
++par;
continue;
}
+ OutputParams ourparams = runparams;
Layout const &style = par->layout();
// Think about adding <section> and/or </section>s.
- if (isLayoutSectioning(style)) {
+ if (isLayoutSectioning(style) || par->params().startOfAppendix()) {
int level = style.toclevel;
// Need to close a previous section if it has the same level or a higher one (close <section> if opening a
// <h2> after a <h2>, <h3>, <h4>, <h5> or <h6>). More examples:
// - current: h2; back: h1; do not close any <section>
// - current: h1; back: h2; close two <section> (first the <h2>, then the <h1>, so a new <h1> can come)
+ // Some layouts require that Layout::NOT_IN_TOC sections still cause closing of previous sections. This is
+ // mostly to ensure that the section is positioned at a DocBook-compatible level (acknowledgements: cannot
+ // be under a section!).
while (!headerLevels.empty() && level <= headerLevels.top().first) {
// Output the tag only if it corresponds to a legit section.
int stackLevel = headerLevels.top().first;
}
// Open the new section: first push it onto the stack, then output it in DocBook.
- string sectionTag = (currentlyInAppendix && style.docbooksectiontag() == "chapter") ?
- "appendix" : style.docbooksectiontag();
+ string sectionTag = (par->params().startOfAppendix()) ? "appendix" : style.docbooksectiontag();
headerLevels.push(std::make_pair(level, sectionTag));
// Some sectioning-like elements should not be output (such as FrontMatter).
Inset const *firstInset = par->getInset(0);
if (firstInset && (firstInset->lyxCode() == BIBITEM_CODE || firstInset->lyxCode() == BIBTEX_CODE)) {
while (!headerLevels.empty()) {
+ // Don't close appendices before bibliographies.
+ if (headerLevels.top().second == "appendix")
+ break;
+
+ // Pop the section from the stack.
int level = headerLevels.top().first;
docstring tag = from_utf8("</" + headerLevels.top().second + ">");
headerLevels.pop();
- // Output the tag only if it corresponds to a legit section.
+ // Output the tag only if it corresponds to a legit section, as the rest of the code.
if (level != Layout::NOT_IN_TOC) {
xs << XMLStream::ESCAPE_NONE << tag;
xs << xml::CR();