]> git.lyx.org Git - features.git/blobdiff - src/output_docbook.cpp
DocBook: specific fix for Localization_Test.lyx.
[features.git] / src / output_docbook.cpp
index a0316ba39c58ef53b46b9b9c53e70caac4633158..ae65aedbe50fdd8c62f4ecbef009a987632c5a92 100644 (file)
@@ -282,8 +282,6 @@ void compTag(XMLStream & xs, const std::string & tag, const std::string & attr,
 
 void openParTag(XMLStream & xs, const Paragraph * par, const Paragraph * prevpar)
 {
-       Layout const & lay = par->layout();
-
        if (par == prevpar)
                prevpar = nullptr;
 
@@ -293,6 +291,7 @@ void openParTag(XMLStream & xs, const Paragraph * par, const Paragraph * prevpar
        // first paragraph of an author, then merging with the previous tag does not make sense. Say the
        // next paragraph is the affiliation, then it should be output in the same <author> tag (different
        // layout, same wrapper tag).
+       Layout const & lay = par->layout();
        bool openWrapper = lay.docbookwrappertag() != "NONE";
        if (prevpar != nullptr) {
                Layout const & prevlay = prevpar->layout();
@@ -376,7 +375,8 @@ void makeBibliography(
                if (!ip)
                        continue;
                if (const auto * bibitem = dynamic_cast<const InsetBibitem*>(ip)) {
-                       attr = from_utf8("xml:id='") + bibitem->getParam("key") + from_utf8("'");
+                       auto id = xml::cleanID(bibitem->getParam("key"));
+                       attr = from_utf8("xml:id='") + id + from_utf8("'");
                        break;
                }
        }
@@ -412,6 +412,11 @@ void makeParagraph(
                OutputParams const & runparams,
                ParagraphList::const_iterator const & par)
 {
+       // If this kind of layout should be ignored, already leave.
+       if (par->layout().docbooktag() == "IGNORE")
+               return;
+
+       // Useful variables.
        auto const begin = text.paragraphs().begin();
        auto const end = text.paragraphs().end();
        auto prevpar = text.paragraphs().getParagraphBefore(par);
@@ -445,38 +450,55 @@ void makeParagraph(
        }
 
        size_t nInsets = std::distance(par->insetList().begin(), par->insetList().end());
+       auto parSize = (size_t) par->size();
+
+       auto isLyxCodeToIgnore = [](InsetCode x) { return x == TOC_CODE; }; // 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).
+
+       // 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).
        // 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) {
+       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 == (size_t) par->size() && std::all_of(par->insetList().begin(), par->insetList().end(), [](InsetList::Element inset) {
-               return inset.inset->lyxCode() == FLOAT_CODE;
+       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 == (size_t) par->size() && std::all_of(par->insetList().begin(), par->insetList().end(), [](InsetList::Element inset) {
-               return inset.inset->lyxCode() == BIBTEX_CODE;
+       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 == (size_t) par->size() && std::all_of(par->insetList().begin(), par->insetList().end(), [](InsetList::Element inset) {
-               return inset.inset->lyxCode() == ERT_CODE;
+       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 == (size_t) par->size() && std::all_of(par->insetList().begin(), par->insetList().end(), [](InsetList::Element inset) {
-               return inset.inset->lyxCode() == LISTINGS_CODE;
+       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 == (size_t) par->size() && std::all_of(par->insetList().begin(), par->insetList().end(), [](InsetList::Element inset) {
-               return inset.inset->lyxCode() == BOX_CODE;
+       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 == (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(), [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());
        });
 
        bool const open_par = runparams.docbook_make_pars
@@ -499,15 +521,16 @@ void makeParagraph(
        ++nextpar;
        auto pars = par->simpleDocBookOnePar(buf, runparams, text.outerFont(distance(begin, par)), 0, nextpar == end, special_case);
        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);
 
-                       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);
        }
 }
 
@@ -518,6 +541,11 @@ void makeEnvironment(Text const &text,
                      OutputParams const &runparams,
                      ParagraphList::const_iterator const & par)
 {
+       // If this kind of layout should be ignored, already leave.
+       if (par->layout().docbooktag() == "IGNORE")
+               return;
+
+       // Useful variables.
        auto const end = text.paragraphs().end();
        auto nextpar = par;
        ++nextpar;
@@ -614,10 +642,18 @@ 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);
 
+       // If this kind of layout should be ignored, already leave.
+       if (begin->layout().docbooktag() == "IGNORE") {
+               auto nextpar = par;
+               ++nextpar;
+               return nextpar;
+       }
+
        // Output the opening tag for this environment.
        Layout const & envstyle = par->layout();
        openTag(xs, envstyle.docbookwrappertag(), envstyle.docbookwrapperattr(), envstyle.docbookwrappertagtype());
@@ -625,9 +661,14 @@ ParagraphList::const_iterator makeListEnvironment(Text const &text,
 
        // Handle the content of the list environment, item by item.
        while (par != envend) {
-               Layout const & style = par->layout();
+               // Skip this paragraph if it is both empty and the last one (otherwise, there may be deeper paragraphs after).
+               auto nextpar = par;
+               ++nextpar;
+               if (par->empty() && nextpar == envend)
+                       break;
 
                // Open the item wrapper.
+               Layout const & style = par->layout();
                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
@@ -699,6 +740,11 @@ void makeCommand(
                OutputParams const & runparams,
                ParagraphList::const_iterator const & par)
 {
+       // If this kind of layout should be ignored, already leave.
+       if (par->layout().docbooktag() == "IGNORE")
+               return;
+
+       // 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();
@@ -722,12 +768,18 @@ bool isLayoutSectioning(Layout const & lay)
 {
        if (lay.docbooksection()) // Special case: some DocBook styles must be handled as sections.
                return true;
-       else if (lay.category() == from_utf8("Sectioning")) // Generic case.
+       else if (lay.category() == from_utf8("Sectioning") || lay.docbooktag() == "section") // Generic case.
                return lay.toclevel != Layout::NOT_IN_TOC;
        return false;
 }
 
 
+bool isLayoutSectioningOrSimilar(Layout const & lay)
+{
+       return isLayoutSectioning(lay) || lay.docbooktag() == "bridgehead";
+}
+
+
 using DocBookDocumentSectioning = tuple<bool, pit_type>;
 
 
@@ -752,7 +804,7 @@ DocBookDocumentSectioning hasDocumentSectioning(ParagraphList const &paragraphs,
 
        while (bpit < epit) {
                Layout const &style = paragraphs[bpit].layout();
-               documentHasSections |= isLayoutSectioning(style);
+               documentHasSections |= isLayoutSectioningOrSimilar(style);
 
                if (documentHasSections)
                        break;
@@ -818,7 +870,7 @@ 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 && isLayoutSectioning(par.layout())) {
+               if (cpit != bpit && isLayoutSectioningOrSimilar(par.layout())) {
                        LYXERR0("Assertion failed: section found in potential <info> paragraphs.");
                        break;
                }
@@ -836,7 +888,11 @@ DocBookInfoTag getParagraphsWithInfo(ParagraphList const &paragraphs,
                        mustBeInInfo.emplace(cpit);
                else if (style.docbookininfo() == "maybe")
                        shouldBeInInfo.emplace(cpit);
-               else if (documentHasSections && !hasAbstractLayout && detectUnlayoutedAbstract)
+               else if (documentHasSections && !hasAbstractLayout && detectUnlayoutedAbstract &&
+                               (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.
                        abstractNoLayout.emplace(cpit);
                else // This should definitely not be in <info>.
                        break;
@@ -909,10 +965,27 @@ void outputDocBookInfo(
        docstring abstract;
        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
+               // generate more than one paragraph, as indicated in the return value.
                odocstringstream os2;
                XMLStream xs2(os2);
-               for (auto const & p : info.abstract)
-                       makeAny(text, buf, xs2, runparams, paragraphs.iterator_at(p));
+
+               set<pit_type> doneParas;
+               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);
+
+                               // 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.**
+                               pit_type id = p;
+                               while (oldPar != newPar) {
+                                       doneParas.emplace(id);
+                                       ++oldPar;
+                                       ++id;
+                               }
+                       }
+               }
 
                // 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.