X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2Foutput_docbook.cpp;h=ce6b702f647fedf73adfdd59dd68ff4049a019e1;hb=33f9cfb7961501b38c573eede468b011687f46de;hp=95d6aaa185ec25017957bf74586b21dd4becf215;hpb=2bbfc726c4840f491c8299101143cecd107948c2;p=lyx.git diff --git a/src/output_docbook.cpp b/src/output_docbook.cpp index 95d6aaa185..ce6b702f64 100644 --- a/src/output_docbook.cpp +++ b/src/output_docbook.cpp @@ -31,6 +31,7 @@ #include "mathed/InsetMath.h" #include "insets/InsetNote.h" +#include "support/debug.h" #include "support/lassert.h" #include "support/textutils.h" @@ -169,11 +170,15 @@ string fontToAttribute(xml::FontTypes type) { // 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 @@ -181,8 +186,9 @@ void openParTag(XMLStream & xs, const Paragraph * par, const Paragraph * prevpar // next paragraph is the affiliation, then it should be output in the same 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() && @@ -208,20 +214,26 @@ void openParTag(XMLStream & xs, const Paragraph * par, const Paragraph * prevpar } } + 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() && @@ -235,6 +247,7 @@ void closeParTag(XMLStream & xs, Paragraph const * par, Paragraph const * nextpa // 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) @@ -337,15 +350,6 @@ void makeParagraph( 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 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). @@ -353,40 +357,63 @@ void makeParagraph( 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 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; @@ -411,12 +438,12 @@ void makeParagraph( 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); } } @@ -445,7 +472,7 @@ void makeEnvironment(Text const &text, // 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(); @@ -482,7 +509,7 @@ void makeEnvironment(Text const &text, } // 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); } @@ -629,14 +656,14 @@ void makeCommand( // 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); } @@ -736,6 +763,8 @@ DocBookInfoTag getParagraphsWithInfo(ParagraphList const ¶graphs, // Traverse everything that might belong to . 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. @@ -754,10 +783,26 @@ DocBookInfoTag getParagraphsWithInfo(ParagraphList const ¶graphs, // 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 , must be, // or abstract (either because of layout or of position). if (style.docbookininfo() == "always") @@ -768,7 +813,7 @@ DocBookInfoTag getParagraphsWithInfo(ParagraphList const ¶graphs, (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 . break; @@ -900,6 +945,7 @@ void outputDocBookInfo( auto rp = runparams; rp.docbook_generate_info = false; + rp.docbook_ignore_wrapper = true; set doneParas; // Paragraphs that have already been converted (mostly to deal with lists). for (auto const & p : info.abstract) { @@ -907,6 +953,7 @@ void outputDocBookInfo( 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); @@ -967,23 +1014,18 @@ void outputDocBookInfo( // - Finally, always output the abstract as the last item of the , 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 tag if it was started. @@ -1043,9 +1085,6 @@ void docbookParagraphs(Text const &text, return; }); - std::stack> 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; @@ -1063,31 +1102,33 @@ void docbookParagraphs(Text const &text, outputDocBookInfo(text, buf, xs, runparams, paragraphs, info); bpit = info.epit; - // Then, iterate through the paragraphs of this document. - bool currentlyInAppendix = false; + std::stack> 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
and/or
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
if opening a //

after a

,

,

,

or
). More examples: // - current: h2; back: h1; do not close any
// - current: h1; back: h2; close two
(first the

, then the

, so a new

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; @@ -1099,8 +1140,7 @@ void docbookParagraphs(Text const &text, } // 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). @@ -1139,11 +1179,16 @@ void docbookParagraphs(Text const &text, 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.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();