]> git.lyx.org Git - lyx.git/blobdiff - src/output_docbook.cpp
Fix bug #12772
[lyx.git] / src / output_docbook.cpp
index 58e1b810f1b43cd4e6b0fd1e52b39eb3703a67f9..c80a7cdb91b7e4dab05591cc308c23e63ac205ae 100644 (file)
@@ -31,6 +31,7 @@
 #include "mathed/InsetMath.h"
 #include "insets/InsetNote.h"
 
+#include "support/debug.h"
 #include "support/lassert.h"
 #include "support/textutils.h"
 
@@ -160,131 +161,24 @@ string fontToAttribute(xml::FontTypes type) {
        // If there is a role (i.e. nonstandard use of a tag), output the attribute. Otherwise, the sheer tag is sufficient
        // for the font.
        string role = fontToRole(type);
-       if (!role.empty()) {
+       if (!role.empty())
                return "role='" + role + "'";
-       } else {
-               return "";
-       }
-}
-
-
-// Convenience functions to open and close tags. First, very low-level ones to ensure a consistent new-line behaviour.
-// Block style:
-//       Content before
-//       <blocktag>
-//         Contents of the block.
-//       </blocktag>
-//       Content after
-// Paragraph style:
-//       Content before
-//         <paratag>Contents of the paragraph.</paratag>
-//       Content after
-// Inline style:
-//    Content before<inlinetag>Contents of the paragraph.</inlinetag>Content after
-
-void openInlineTag(XMLStream & xs, const std::string & tag, const std::string & attr)
-{
-       xs << xml::StartTag(tag, attr);
-}
-
-
-void closeInlineTag(XMLStream & xs, const std::string & tag)
-{
-       xs << xml::EndTag(tag);
-}
-
-
-void openParTag(XMLStream & xs, const std::string & tag, const std::string & attr)
-{
-       if (!xs.isLastTagCR())
-               xs << xml::CR();
-       xs << xml::StartTag(tag, attr);
-}
-
-
-void closeParTag(XMLStream & xs, const std::string & tag)
-{
-       xs << xml::EndTag(tag);
-       xs << xml::CR();
-}
-
-
-void openBlockTag(XMLStream & xs, const std::string & tag, const std::string & attr)
-{
-       if (!xs.isLastTagCR())
-               xs << xml::CR();
-       xs << xml::StartTag(tag, attr);
-       xs << xml::CR();
-}
-
-
-void closeBlockTag(XMLStream & xs, const std::string & tag)
-{
-       if (!xs.isLastTagCR())
-               xs << xml::CR();
-       xs << xml::EndTag(tag);
-       xs << xml::CR();
-}
-
-
-void openTag(XMLStream & xs, const std::string & tag, const std::string & attr, const std::string & tagtype)
-{
-       if (tag.empty() || tag == "NONE") // Common check to be performed elsewhere, if it was not here.
-               return;
-
-       if (tag == "para" || tagtype == "paragraph") // Special case for <para>: always considered as a paragraph.
-               openParTag(xs, tag, attr);
-       else if (tagtype == "block")
-               openBlockTag(xs, tag, attr);
-       else if (tagtype == "inline")
-               openInlineTag(xs, tag, attr);
-       else
-               xs.writeError("Unrecognised tag type '" + tagtype + "' for '" + tag + " " + attr + "'");
-}
-
-
-void closeTag(XMLStream & xs, const std::string & tag, const std::string & tagtype)
-{
-       if (tag.empty() || tag == "NONE")
-               return;
-
-       if (tag == "para" || tagtype == "paragraph") // Special case for <para>: always considered as a paragraph.
-               closeParTag(xs, tag);
-       else if (tagtype == "block")
-               closeBlockTag(xs, tag);
-       else if (tagtype == "inline")
-               closeInlineTag(xs, tag);
        else
-               xs.writeError("Unrecognised tag type '" + tagtype + "' for '" + tag + "'");
-}
-
-
-void compTag(XMLStream & xs, const std::string & tag, const std::string & attr, const std::string & tagtype)
-{
-       if (tag.empty() || tag == "NONE")
-               return;
-
-       // Special case for <para>: always considered as a paragraph.
-       if (tag == "para" || tagtype == "paragraph" || tagtype == "block") {
-               if (!xs.isLastTagCR())
-                       xs << xml::CR();
-               xs << xml::CompTag(tag, attr);
-               xs << xml::CR();
-       } else if (tagtype == "inline") {
-               xs << xml::CompTag(tag, attr);
-       } else {
-               xs.writeError("Unrecognised tag type '" + tagtype + "' for '" + tag + "'");
-       }
+               return "";
 }
 
 
 // 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
@@ -292,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() &&
@@ -305,8 +200,17 @@ void openParTag(XMLStream & xs, const Paragraph * par, const Paragraph * prevpar
        }
 
        // Main logic.
-       if (openWrapper)
-               openTag(xs, lay.docbookwrappertag(), lay.docbookwrapperattr(), lay.docbookwrappertagtype());
+       if (openWrapper) {
+               xml::openTag(xs, lay.docbookwrappertag(), lay.docbookwrapperattr(), lay.docbookwrappertagtype());
+
+               if (lay.docbookgeneratetitle()) {
+                       docstring const label = par->params().labelString();
+
+                       xml::openTag(xs, "title", "", "paragraph");
+                       xs << (!label.empty() ? label : from_ascii("No title"));
+                       xml::closeTag(xs, "title", "paragraph");
+               }
+       }
 
        const string & tag = lay.docbooktag();
        if (tag != "NONE") {
@@ -314,25 +218,31 @@ void openParTag(XMLStream & xs, const Paragraph * par, const Paragraph * prevpar
                if (!xs.isTagOpen(xmltag, 1)) { // Don't nest a paragraph directly in a paragraph.
                        // TODO: required or not?
                        // TODO: avoid creating a ParTag object just for this query...
-                       openTag(xs, lay.docbooktag(), lay.docbookattr(), lay.docbooktagtype());
-                       openTag(xs, lay.docbookinnertag(), lay.docbookinnerattr(), lay.docbookinnertagtype());
+                       xml::openTag(xs, lay.docbooktag(), lay.docbookattr(), lay.docbooktagtype());
+                       xml::openTag(xs, lay.docbookinnertag(), lay.docbookinnerattr(), lay.docbookinnertagtype());
                }
        }
 
-       openTag(xs, lay.docbookitemtag(), lay.docbookitemattr(), lay.docbookitemtagtype());
-       openTag(xs, lay.docbookiteminnertag(), lay.docbookiteminnerattr(), lay.docbookiteminnertagtype());
+       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() &&
@@ -344,12 +254,13 @@ void closeParTag(XMLStream & xs, Paragraph const * par, Paragraph const * nextpa
        }
 
        // Main logic.
-       closeTag(xs, lay.docbookiteminnertag(), lay.docbookiteminnertagtype());
-       closeTag(xs, lay.docbookitemtag(), lay.docbookitemtagtype());
-       closeTag(xs, lay.docbookinnertag(), lay.docbookinnertagtype());
-       closeTag(xs, lay.docbooktag(), lay.docbooktagtype());
+       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)
-               closeTag(xs, lay.docbookwrappertag(), lay.docbookwrappertagtype());
+               xml::closeTag(xs, lay.docbookwrappertag(), lay.docbookwrappertagtype());
 }
 
 
@@ -384,9 +295,17 @@ void makeBibliography(
 
        // Generate the entry. Concatenate the different parts of the paragraph if any.
        auto const begin = text.paragraphs().begin();
-       auto pars = par->simpleDocBookOnePar(buf, runparams, text.outerFont(std::distance(begin, par)), 0);
+       std::vector<docstring> pars_prepend;
+       std::vector<docstring> pars;
+       std::vector<docstring> pars_append;
+       tie(pars_prepend, pars, pars_append) = par->simpleDocBookOnePar(buf, runparams, text.outerFont(std::distance(begin, par)), 0);
+
+       for (auto & parXML : pars_prepend)
+               xs << XMLStream::ESCAPE_NONE << parXML;
        for (auto & parXML : pars)
                xs << XMLStream::ESCAPE_NONE << parXML;
+       for (auto & parXML : pars_append)
+               xs << XMLStream::ESCAPE_NONE << parXML;
 
        // End the precooked bibliography entry.
        xs << xml::EndTag("bibliomixed");
@@ -412,6 +331,7 @@ void makeParagraph(
                OutputParams const & runparams,
                ParagraphList::const_iterator const & par)
 {
+       // Useful variables.
        auto const begin = text.paragraphs().begin();
        auto const end = text.paragraphs().end();
        auto prevpar = text.paragraphs().getParagraphBefore(par);
@@ -432,7 +352,7 @@ void makeParagraph(
        // We do not really want to wrap that whole thing in a <div>...</div>.
        bool special_case = false;
        Inset const *specinset = par->size() == 1 ? par->getInset(0) : nullptr;
-       if (specinset && !specinset->getLayout().htmlisblock()) { // TODO: Convert htmlisblock to a DocBook parameter?
+       if (specinset && !specinset->getLayout().htmlisblock()) { // TODO: Convert htmlisblock to a DocBook parameter? docbooknotinpara should be enough in most cases.
                Layout const &style = par->layout();
                FontInfo const first_font = style.labeltype == LABEL_MANUAL ?
                                                                        style.labelfont : style.font;
@@ -445,44 +365,80 @@ void makeParagraph(
        }
 
        size_t nInsets = std::distance(par->insetList().begin(), par->insetList().end());
+       auto parSize = (size_t) par->size();
 
        // 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).
        // Exception: any case that generates an <inlineequation> must still get a paragraph to be valid.
-       special_case |= nInsets == (size_t) par->size() && std::all_of(par->insetList().begin(), par->insetList().end(), [](InsetList::Element inset) {
+       auto isEquationSpecialCase = [](InsetList::Element inset) {
                return inset.inset && inset.inset->asInsetMath() && inset.inset->asInsetMath()->getType() != hullSimple;
-       });
-       // Tables doe not deserve their own paragraphs (DocBook allows them outside paragraphs).
-       special_case |= nInsets == (size_t) par->size() && std::all_of(par->insetList().begin(), par->insetList().end(), [](InsetList::Element inset) {
-               return inset.inset->lyxCode() == TABULAR_CODE;
-       });
-       // Floats cannot be in paragraphs.
-       special_case |= nInsets == (size_t) par->size() && std::all_of(par->insetList().begin(), par->insetList().end(), [](InsetList::Element inset) {
-               return inset.inset->lyxCode() == FLOAT_CODE;
-       });
-       // Bibliographies cannot be in paragraphs. Bibitems should still be handled as paragraphs, though
-       // (see makeParagraphBibliography).
-       special_case |= nInsets == (size_t) par->size() && std::all_of(par->insetList().begin(), par->insetList().end(), [](InsetList::Element inset) {
-               return inset.inset->lyxCode() == BIBTEX_CODE;
-       });
-       // ERTs are in comments, not paragraphs.
-       special_case |= nInsets == (size_t) par->size() && std::all_of(par->insetList().begin(), par->insetList().end(), [](InsetList::Element inset) {
-               return inset.inset->lyxCode() == ERT_CODE;
-       });
-       // Listings should not get into their own paragraph.
-       special_case |= nInsets == (size_t) par->size() && std::all_of(par->insetList().begin(), par->insetList().end(), [](InsetList::Element inset) {
-               return inset.inset->lyxCode() == LISTINGS_CODE;
-       });
-       // Boxes cannot get into their own paragraph.
-       special_case |= nInsets == (size_t) par->size() && std::all_of(par->insetList().begin(), par->insetList().end(), [](InsetList::Element inset) {
-               return inset.inset->lyxCode() == BOX_CODE;
-       });
-       // Includes should not have a paragraph.
-       special_case |= nInsets == (size_t) par->size() && std::all_of(par->insetList().begin(), par->insetList().end(), [](InsetList::Element inset) {
-               return inset.inset->lyxCode() == INCLUDE_CODE;
-       });
+       };
+       special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), isEquationSpecialCase);
+
+       // 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);
+
+       // If the insets should be rendered as images, enter the special case.
+       auto isRenderedAsImageSpecialCase = [](InsetList::Element inset) {
+               return inset.inset && inset.inset->getLayout().docbookrenderasimage();
+       };
+       special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), isRenderedAsImageSpecialCase);
 
+       // 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;
@@ -492,7 +448,7 @@ void makeParagraph(
        //              or we're not in the last paragraph, anyway.
        //   (ii) We didn't open it and docbook_in_par is true,
        //              but we are in the first par, and there is a next par.
-       bool const close_par = open_par && (!runparams.docbook_in_par);
+       bool const close_par = open_par && !runparams.docbook_in_par;
 
        // Determine if this paragraph has some real content. Things like new pages are not caught
        // by Paragraph::empty(), even though they do not generate anything useful in DocBook.
@@ -501,18 +457,28 @@ void makeParagraph(
        // Open and close tags around each contained paragraph.
        auto nextpar = par;
        ++nextpar;
-       auto pars = par->simpleDocBookOnePar(buf, runparams, text.outerFont(distance(begin, par)), 0, nextpar == end, special_case);
+
+       std::vector<docstring> pars_prepend;
+       std::vector<docstring> pars;
+       std::vector<docstring> pars_append;
+       tie(pars_prepend, pars, pars_append) = par->simpleDocBookOnePar(buf, runparams, text.outerFont(distance(begin, par)), 0, nextpar == end, special_case);
+
+       for (docstring const & parXML : pars_prepend)
+           xs << XMLStream::ESCAPE_NONE << parXML;
        for (docstring const & parXML : pars) {
-               if (xml::isNotOnlySpace(parXML)) {
-                       if (open_par)
-                               openParTag(xs, &*par, prevpar);
+               if (!xml::isNotOnlySpace(parXML))
+                       continue;
 
-                       xs << XMLStream::ESCAPE_NONE << parXML;
+               if (open_par)
+                       openParTag(xs, &*par, prevpar, runparams);
 
-                       if (close_par)
-                               closeParTag(xs, &*par, (nextpar != end) ? &*nextpar : nullptr);
-               }
+               xs << XMLStream::ESCAPE_NONE << parXML;
+
+               if (close_par)
+                       closeParTag(xs, &*par, (nextpar != end) ? &*nextpar : nullptr, runparams);
        }
+       for (docstring const & parXML : pars_append)
+           xs << XMLStream::ESCAPE_NONE << parXML;
 }
 
 
@@ -522,6 +488,7 @@ void makeEnvironment(Text const &text,
                      OutputParams const &runparams,
                      ParagraphList::const_iterator const & par)
 {
+       // Useful variables.
        auto const end = text.paragraphs().end();
        auto nextpar = par;
        ++nextpar;
@@ -539,7 +506,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();
@@ -547,14 +514,22 @@ void makeEnvironment(Text const &text,
                // Nothing to do (otherwise, infinite loops).
        } else if (style.latextype == LATEX_ENVIRONMENT) {
                // Generate the paragraph, if need be.
-               auto pars = par->simpleDocBookOnePar(buf, runparams, text.outerFont(std::distance(text.paragraphs().begin(), par)), 0, false, ignoreFonts);
-
+               std::vector<docstring> pars_prepend;
+        std::vector<docstring> pars;
+        std::vector<docstring> pars_append;
+        tie(pars_prepend, pars, pars_append) =
+                               par->simpleDocBookOnePar(buf, runparams, text.outerFont(std::distance(text.paragraphs().begin(), par)),
+                                                                                0, false, ignoreFonts);
+
+        for (docstring const & parXML : pars_prepend)
+            xs << XMLStream::ESCAPE_NONE << parXML;
                if (mimicListing) {
                        auto p = pars.begin();
                        while (p != pars.end()) {
-                               openTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnerattr(), par->layout().docbookiteminnertagtype());
+                               xml::openTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnerattr(),
+                                            par->layout().docbookiteminnertagtype());
                                xs << XMLStream::ESCAPE_NONE << *p;
-                               closeTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnertagtype());
+                               xml::closeTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnertagtype());
                                ++p;
 
                                // Insert a new line after each "paragraph" (i.e. line in the listing), except for the last one.
@@ -564,17 +539,20 @@ void makeEnvironment(Text const &text,
                        }
                } else {
                        for (auto const & p : pars) {
-                               openTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnerattr(), par->layout().docbookiteminnertagtype());
+                               xml::openTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnerattr(),
+                                            par->layout().docbookiteminnertagtype());
                                xs << XMLStream::ESCAPE_NONE << p;
-                               closeTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnertagtype());
+                               xml::closeTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnertagtype());
                        }
                }
+        for (docstring const & parXML : pars_append)
+            xs << XMLStream::ESCAPE_NONE << parXML;
        } else {
                makeAny(text, buf, xs, runparams, par);
        }
 
        // 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);
 }
 
 
@@ -618,14 +596,15 @@ ParagraphList::const_iterator makeListEnvironment(Text const &text,
                                                          OutputParams const &runparams,
                                                          ParagraphList::const_iterator const & begin)
 {
+       // Useful variables.
        auto par = begin;
        auto const end = text.paragraphs().end();
        auto const envend = findEndOfEnvironment(par, end);
 
        // Output the opening tag for this environment.
        Layout const & envstyle = par->layout();
-       openTag(xs, envstyle.docbookwrappertag(), envstyle.docbookwrapperattr(), envstyle.docbookwrappertagtype());
-       openTag(xs, envstyle.docbooktag(), envstyle.docbookattr(), envstyle.docbooktagtype());
+       xml::openTag(xs, envstyle.docbookwrappertag(), envstyle.docbookwrapperattr(), envstyle.docbookwrappertagtype());
+       xml::openTag(xs, envstyle.docbooktag(), envstyle.docbookattr(), envstyle.docbooktagtype());
 
        // Handle the content of the list environment, item by item.
        while (par != envend) {
@@ -637,7 +616,8 @@ ParagraphList::const_iterator makeListEnvironment(Text const &text,
 
                // Open the item wrapper.
                Layout const & style = par->layout();
-               openTag(xs, style.docbookitemwrappertag(), style.docbookitemwrapperattr(), style.docbookitemwrappertagtype());
+               xml::openTag(xs, style.docbookitemwrappertag(), style.docbookitemwrapperattr(),
+                            style.docbookitemwrappertagtype());
 
                // Generate the label, if need be. If it is taken from the text, sep != 0 and corresponds to the first
                // character after the label.
@@ -645,38 +625,47 @@ ParagraphList::const_iterator makeListEnvironment(Text const &text,
                if (style.labeltype != LABEL_NO_LABEL && style.docbookitemlabeltag() != "NONE") {
                        if (style.labeltype == LABEL_MANUAL) {
                                // Only variablelist gets here (or similar items defined as an extension in the layout).
-                               openTag(xs, style.docbookitemlabeltag(), style.docbookitemlabelattr(), style.docbookitemlabeltagtype());
+                               xml::openTag(xs, style.docbookitemlabeltag(), style.docbookitemlabelattr(),
+                                            style.docbookitemlabeltagtype());
                                sep = 1 + par->firstWordDocBook(xs, runparams);
-                               closeTag(xs, style.docbookitemlabeltag(), style.docbookitemlabeltagtype());
+                               xml::closeTag(xs, style.docbookitemlabeltag(), style.docbookitemlabeltagtype());
                        } else {
                                // Usual cases: maybe there is something specified at the layout level. Highly unlikely, though.
                                docstring const lbl = par->params().labelString();
 
                                if (!lbl.empty()) {
-                                       openTag(xs, style.docbookitemlabeltag(), style.docbookitemlabelattr(), style.docbookitemlabeltagtype());
+                                       xml::openTag(xs, style.docbookitemlabeltag(), style.docbookitemlabelattr(),
+                                                    style.docbookitemlabeltagtype());
                                        xs << lbl;
-                                       closeTag(xs, style.docbookitemlabeltag(), style.docbookitemlabeltagtype());
+                                       xml::closeTag(xs, style.docbookitemlabeltag(), style.docbookitemlabeltagtype());
                                }
                        }
                }
 
                // Open the item (after the wrapper and the label).
-               openTag(xs, style.docbookitemtag(), style.docbookitemattr(), style.docbookitemtagtype());
+               xml::openTag(xs, style.docbookitemtag(), style.docbookitemattr(), style.docbookitemtagtype());
 
                // Generate the content of the item.
                if (sep < par->size()) {
-                       auto pars = par->simpleDocBookOnePar(buf, runparams,
+            std::vector<docstring> pars_prepend;
+            std::vector<docstring> pars;
+            std::vector<docstring> pars_append;
+            tie(pars_prepend, pars, pars_append) = par->simpleDocBookOnePar(buf, runparams,
                                                             text.outerFont(std::distance(text.paragraphs().begin(), par)), sep);
+            for (docstring const & parXML : pars_prepend)
+                xs << XMLStream::ESCAPE_NONE << parXML;
                        for (auto &p : pars) {
-                               openTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnerattr(),
-                                       par->layout().docbookiteminnertagtype());
+                               xml::openTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnerattr(),
+                                            par->layout().docbookiteminnertagtype());
                                xs << XMLStream::ESCAPE_NONE << p;
-                               closeTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnertagtype());
+                               xml::closeTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnertagtype());
                        }
+            for (docstring const & parXML : pars_append)
+                xs << XMLStream::ESCAPE_NONE << parXML;
                } else {
                        // DocBook doesn't like emptiness.
-                       compTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnerattr(),
-                               par->layout().docbookiteminnertagtype());
+                       xml::compTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnerattr(),
+                                    par->layout().docbookiteminnertagtype());
                }
 
                // If the next item is deeper, it must go entirely within this item (do it recursively).
@@ -689,13 +678,13 @@ ParagraphList::const_iterator makeListEnvironment(Text const &text,
                // a list, and another paragraph; or an item with two types of list (itemise then enumerate, for instance).
 
                // Close the item.
-               closeTag(xs, style.docbookitemtag(), style.docbookitemtagtype());
-               closeTag(xs, style.docbookitemwrappertag(), style.docbookitemwrappertagtype());
+               xml::closeTag(xs, style.docbookitemtag(), style.docbookitemtagtype());
+               xml::closeTag(xs, style.docbookitemwrappertag(), style.docbookitemwrappertagtype());
        }
 
        // Close this environment in exactly the same way as it was opened.
-       closeTag(xs, envstyle.docbooktag(), envstyle.docbooktagtype());
-       closeTag(xs, envstyle.docbookwrappertag(), envstyle.docbookwrappertagtype());
+       xml::closeTag(xs, envstyle.docbooktag(), envstyle.docbooktagtype());
+       xml::closeTag(xs, envstyle.docbookwrappertag(), envstyle.docbookwrappertagtype());
 
        return envend;
 }
@@ -708,6 +697,7 @@ void makeCommand(
                OutputParams const & runparams,
                ParagraphList::const_iterator const & par)
 {
+       // Useful variables.
        // Unlike XHTML, no need for labels, as they are handled by DocBook tags.
        auto const begin = text.paragraphs().begin();
        auto const end = text.paragraphs().end();
@@ -716,14 +706,23 @@ void makeCommand(
 
        // Generate this command.
        auto prevpar = text.paragraphs().getParagraphBefore(par);
-       openParTag(xs, &*par, prevpar);
 
-       auto pars = par->simpleDocBookOnePar(buf, runparams,text.outerFont(distance(begin, par)));
+    std::vector<docstring> pars_prepend;
+    std::vector<docstring> pars;
+    std::vector<docstring> pars_append;
+    tie(pars_prepend, pars, pars_append) = par->simpleDocBookOnePar(buf, runparams,text.outerFont(distance(begin, par)));
+
+    for (docstring const & parXML : pars_prepend)
+        xs << XMLStream::ESCAPE_NONE << parXML;
+
+    openParTag(xs, &*par, prevpar, runparams);
        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, runparams);
 
-       closeParTag(xs, &*par, (nextpar != end) ? &*nextpar : nullptr);
+    for (docstring const & parXML : pars_append)
+        xs << XMLStream::ESCAPE_NONE << parXML;
 }
 
 
@@ -766,6 +765,8 @@ DocBookDocumentSectioning hasDocumentSectioning(ParagraphList const &paragraphs,
        bool documentHasSections = false;
 
        while (bpit < epit) {
+               LASSERT(static_cast<size_t>(bpit) < paragraphs.size(), return make_tuple(documentHasSections, bpit));
+
                Layout const &style = paragraphs[bpit].layout();
                documentHasSections |= isLayoutSectioningOrSimilar(style);
 
@@ -812,7 +813,7 @@ DocBookInfoTag getParagraphsWithInfo(ParagraphList const &paragraphs,
        set<pit_type> abstractWithLayout;
        set<pit_type> abstractNoLayout;
 
-       // Find the first non empty paragraph by mutating bpit.
+       // Find the first nonempty paragraph by mutating bpit.
        while (bpit < epit) {
                Paragraph const &par = paragraphs[bpit];
                if (par.empty() || hasOnlyNotes(par))
@@ -823,6 +824,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.
@@ -834,17 +837,33 @@ DocBookInfoTag getParagraphsWithInfo(ParagraphList const &paragraphs,
                // There should never be any section here, except for the first paragraph (a title can be part of <info>).
                // (Just a sanity check: if this fails, this function could end up processing the whole document.)
                if (cpit != bpit && isLayoutSectioningOrSimilar(par.layout())) {
-                       LYXERR0("Assertion failed: section found in potential <info> paragraphs.");
+                       LYXERR(Debug::OUTFILE, "Assertion failed: section found in potential <info> paragraphs.");
                        break;
                }
 
                // 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")
@@ -855,7 +874,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;
@@ -871,30 +890,79 @@ DocBookInfoTag getParagraphsWithInfo(ParagraphList const &paragraphs,
 } // end anonymous namespace
 
 
+std::set<const Inset *> gatherInfo(ParagraphList::const_iterator par)
+{
+       // This function has a structure highly similar to makeAny and its friends. It's only made to be called on what
+       // should become the document's <abstract>.
+       std::set<const Inset *> values;
+
+       // If this kind of layout should be ignored, already leave.
+       if (par->layout().docbooktag() == "IGNORE")
+               return values;
+
+       // If this should go in info, mark it as such. Dive deep into the abstract, as it may hide many things that
+       // DocBook doesn't want to be inside the abstract.
+       for (pos_type i = 0; i < par->size(); ++i) {
+               if (par->getInset(i) && par->getInset(i)->asInsetText()) {
+                       InsetText const *inset = par->getInset(i)->asInsetText();
+
+                       if (inset->getLayout().docbookininfo() != "never") {
+                               values.insert(inset);
+                       } else {
+                               auto subpar = inset->paragraphs().begin();
+                               while (subpar != inset->paragraphs().end()) {
+                                       auto subinfos = gatherInfo(subpar);
+                                       for (auto & subinfo: subinfos)
+                                               values.insert(subinfo);
+                                       ++subpar;
+                               }
+                       }
+               }
+       }
+
+       return values;
+}
+
+
 ParagraphList::const_iterator makeAny(Text const &text,
                                       Buffer const &buf,
                                       XMLStream &xs,
                                       OutputParams const &runparams,
                                       ParagraphList::const_iterator par)
 {
-       switch (par->layout().latextype) {
-       case LATEX_COMMAND:
-               makeCommand(text, buf, xs, runparams, par);
-               break;
-       case LATEX_ENVIRONMENT:
-               makeEnvironment(text, buf, xs, runparams, par);
-               break;
-       case LATEX_LIST_ENVIRONMENT:
-       case LATEX_ITEM_ENVIRONMENT:
-               // Only case when makeAny() might consume more than one paragraph.
-               return makeListEnvironment(text, buf, xs, runparams, par);
-       case LATEX_PARAGRAPH:
-               makeParagraph(text, buf, xs, runparams, par);
-               break;
-       case LATEX_BIB_ENVIRONMENT:
-               makeBibliography(text, buf, xs, runparams, par);
-               break;
+       bool ignoreParagraph = false;
+
+       // If this kind of layout should be ignored, already leave.
+       ignoreParagraph |= par->layout().docbooktag() == "IGNORE";
+
+       // For things that should go into <info>, check the variable rp.docbook_generate_info. This does not apply to the
+       // abstract itself.
+       bool isAbstract = par->layout().docbookabstract() || par->layout().docbooktag() == "abstract";
+       ignoreParagraph |= !isAbstract && par->layout().docbookininfo() != "never" && !runparams.docbook_generate_info;
+
+       // Switch on the type of paragraph to call the right handler.
+       if (!ignoreParagraph) {
+               switch (par->layout().latextype) {
+               case LATEX_COMMAND:
+                       makeCommand(text, buf, xs, runparams, par);
+                       break;
+               case LATEX_ENVIRONMENT:
+                       makeEnvironment(text, buf, xs, runparams, par);
+                       break;
+               case LATEX_LIST_ENVIRONMENT:
+               case LATEX_ITEM_ENVIRONMENT:
+                       // Only case when makeAny() might consume more than one paragraph.
+                       return makeListEnvironment(text, buf, xs, runparams, par);
+               case LATEX_PARAGRAPH:
+                       makeParagraph(text, buf, xs, runparams, par);
+                       break;
+               case LATEX_BIB_ENVIRONMENT:
+                       makeBibliography(text, buf, xs, runparams, par);
+                       break;
+               }
        }
+
+       // For cases that are not lists, the next paragraph to handle is the next one.
        ++par;
        return par;
 }
@@ -926,6 +994,9 @@ void outputDocBookInfo(
        // This check must be performed *before* a decision on whether or not to output <info> is made.
        bool hasAbstract = !info.abstract.empty();
        docstring abstract;
+       set<const Inset *> infoInsets; // Paragraphs that should go into <info>, but are hidden in an <abstract>
+       // paragraph. (This happens for quite a few layouts, unfortunately.)
+
        if (hasAbstract) {
                // Generate the abstract XML into a string before further checks.
                // Usually, makeAny only generates one paragraph at a time. However, for the specific case of lists, it might
@@ -933,14 +1004,24 @@ void outputDocBookInfo(
                odocstringstream os2;
                XMLStream xs2(os2);
 
-               set<pit_type> doneParas;
+               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) {
                        if (doneParas.find(p) == doneParas.end()) {
                                auto oldPar = paragraphs.iterator_at(p);
-                               auto newPar = makeAny(text, buf, xs2, runparams, oldPar);
+                               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);
 
                                // Insert the indices of all the paragraphs that were just generated (typically, one).
                                // **Make the hypothesis that, when an abstract has a list, all its items are consecutive.**
+                               // Otherwise, makeAny and makeListEnvironment would have to be adapted too.
                                pit_type id = p;
                                while (oldPar != newPar) {
                                        doneParas.emplace(id);
@@ -950,12 +1031,20 @@ void outputDocBookInfo(
                        }
                }
 
-               // Actually output the abstract if there is something to do. Don't count line feeds or spaces in this,
-               // even though they must be properly output if there is some abstract.
+               // Actually output the abstract if there is something to do. Don't count line feeds, spaces, or comments
+               // in this -- even though line feeds and spaces must be properly output if there is some abstract.
                abstract = os2.str();
                docstring cleaned = abstract;
                cleaned.erase(std::remove_if(cleaned.begin(), cleaned.end(), lyx::isSpace), cleaned.end());
 
+               size_t beginComment;
+               size_t endComment;
+               while ((beginComment = cleaned.find(from_ascii("<!--"))) != lyx::docstring::npos) {
+                       if ((endComment = cleaned.find(from_ascii("-->"), beginComment)) != lyx::docstring::npos) {
+                               cleaned.erase(cleaned.begin() + beginComment, cleaned.begin() + endComment + 3);
+                       }
+               }
+
                // Nothing? Then there is no abstract!
                if (cleaned.empty())
                        hasAbstract = false;
@@ -971,13 +1060,11 @@ void outputDocBookInfo(
                xs << xml::CR();
        }
 
-       // Output the elements that should go in <info>, before and after the abstract.
+       // Output the elements that should go in <info>.
+       // - First, the title.
        for (auto pit : info.shouldBeInInfo) // Typically, the title: these elements are so important and ubiquitous
                // that mandating a wrapper like <info> would repel users. Thus, generate them first.
                makeAny(text, buf, xs, runparams, paragraphs.iterator_at(pit));
-       for (auto pit : info.mustBeInInfo)
-               makeAny(text, buf, xs, runparams, paragraphs.iterator_at(pit));
-
        // If there is no title, generate one (required for the document to be valid).
        // This code is called for the main document, for table cells, etc., so be precise in this condition.
        if (text.isMainText() && info.shouldBeInInfo.empty() && !runparams.inInclude) {
@@ -987,26 +1074,27 @@ void outputDocBookInfo(
                xs << xml::CR();
        }
 
-       // 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";
+       // - Then, other metadata.
+       for (auto pit : info.mustBeInInfo)
+               makeAny(text, buf, xs, runparams, paragraphs.iterator_at(pit));
+       for (auto const * inset : infoInsets)
+               inset->docbook(xs, runparams);
 
-                       if (!xs.isLastTagCR())
-                               xs << xml::CR();
+       // - 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) {
+               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.
@@ -1066,9 +1154,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;
@@ -1086,31 +1171,90 @@ 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;
+       // In the specific case of books, there must be parts or chapters. In some cases, star sections are used at the
+       // beginning for many things like acknowledgements or licenses. DocBook has tags for many of these cases, but not
+       // the LyX layouts... Gather everything in a <preface>, that's the closest in meaning.
+       // This is only useful if the things after the <info> tag are not already parts or chapters!
+       if (buf.params().documentClass().docbookroot() == "book") {
+           // Check the condition on the first few elements.
+           bool hasPreface = false;
+           pit_type pref_bpit = bpit;
+           pit_type pref_epit = bpit;
+
+           static const std::set<std::string> allowedElements = {
+                   // List from https://tdg.docbook.org/tdg/5.2/book.html
+                   "acknowledgements", "appendix", "article", "bibliography", "chapter", "colophon", "dedication",
+                   "glossary", "index", "part", "preface", "reference", "toc"
+           };
+
+           for (; pref_epit < epit; ++pref_epit) {
+            auto par = text.paragraphs().iterator_at(pref_epit);
+            if (allowedElements.find(par->layout().docbooktag()) != allowedElements.end() ||
+                    allowedElements.find(par->layout().docbooksectiontag()) != allowedElements.end())
+                break;
+
+            hasPreface = true;
+           }
+
+           // Output a preface if required. A title is needed for the document to be valid...
+           if (hasPreface) {
+               xs << xml::StartTag("preface");
+               xs << xml::CR();
+
+               xs << xml::StartTag("title");
+               xs << "Preface";
+               xs << xml::EndTag("title");
+            xs << xml::CR();
+
+            auto pref_par = text.paragraphs().iterator_at(pref_bpit);
+            auto pref_end = text.paragraphs().iterator_at(pref_epit);
+            while (pref_par != pref_end) {
+                // Skip paragraphs not producing any output.
+                if (hasOnlyNotes(*pref_par)) {
+                    ++pref_par;
+                    continue;
+                }
+
+                // TODO: must sections be handled here? If so, it might be useful to extract the corresponding loop
+                // in the rest of this function to use the same here (and avoid copy-paste mistakes).
+                pref_par = makeAny(text, buf, xs, runparams, pref_par);
+            }
+
+               xs << xml::EndTag("preface");
+            xs << xml::CR();
+
+            // Skip what has just been generated in the preface.
+            bpit = pref_epit;
+           }
+       }
 
+       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;
@@ -1122,8 +1266,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).
@@ -1162,11 +1305,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();