]> git.lyx.org Git - lyx.git/blobdiff - src/output_docbook.cpp
Capitalize labels of floats, etc. Fixes #11993.
[lyx.git] / src / output_docbook.cpp
index 63b4ff9b16b6b880022aebb4d9c2a698388496b1..ce6b702f647fedf73adfdd59dd68ff4049a019e1 100644 (file)
@@ -170,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
@@ -182,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 <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() &&
@@ -209,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() &&
@@ -236,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)
@@ -338,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 <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).
@@ -354,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<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;
@@ -412,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);
        }
 }
 
@@ -446,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();
@@ -483,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);
 }
 
 
@@ -630,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);
 }
 
 
@@ -737,6 +763,8 @@ DocBookInfoTag getParagraphsWithInfo(ParagraphList const &paragraphs,
 
        // 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.
@@ -755,10 +783,26 @@ DocBookInfoTag getParagraphsWithInfo(ParagraphList const &paragraphs,
                // 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")
@@ -769,7 +813,7 @@ DocBookInfoTag getParagraphsWithInfo(ParagraphList const &paragraphs,
                                (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;
@@ -901,6 +945,7 @@ void outputDocBookInfo(
 
                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) {
@@ -908,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);
@@ -968,23 +1014,18 @@ void outputDocBookInfo(
        // - 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.
@@ -1044,9 +1085,6 @@ void docbookParagraphs(Text const &text,
                                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;
@@ -1064,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<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;
@@ -1100,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).
@@ -1140,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.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();