X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2Foutput_docbook.cpp;h=5e1f6d12cf2829fc281a1cd95268d9967249270f;hb=9716e79624a4d;hp=0deed246634fe7d3119f870be6a50cb9d5c67156;hpb=587f1f5571ca0ef26bdef8201491475c1e69c91e;p=lyx.git diff --git a/src/output_docbook.cpp b/src/output_docbook.cpp index 0deed24663..5e1f6d12cf 100644 --- a/src/output_docbook.cpp +++ b/src/output_docbook.cpp @@ -175,6 +175,10 @@ void openParTag(XMLStream & xs, const Paragraph * par, const Paragraph * prevpar 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 @@ -221,6 +225,10 @@ void closeParTag(XMLStream & xs, Paragraph const * par, Paragraph const * nextpa 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" && !runparams.docbook_ignore_wrapper; @@ -278,9 +286,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 pars_prepend; + std::vector pars; + std::vector 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"); @@ -327,7 +343,7 @@ void makeParagraph( // We do not really want to wrap that whole thing in a
...
. 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; @@ -342,56 +358,78 @@ void makeParagraph( size_t nInsets = std::distance(par->insetList().begin(), par->insetList().end()); auto parSize = (size_t) par->size(); - // If this LyX code does not produce any output, it can be safely ignored in the following checks: if this thing - // is present in the paragraph, it has no impact on the definition of the special case (i.e. whether or not - // a tag should be output). - auto isLyxCodeToIgnore = [](InsetCode x) { return x == TOC_CODE || x == NOTE_CODE; }; - - // TODO: if a paragraph *only* contains floats, listings, bibliographies, etc., should this be considered as a - // special case? If so, the code could be largely simplifies (all the calls to all_of, basically) and optimised - // at the compilation stage. - // Plain layouts must be ignored. special_case |= buf.params().documentClass().isPlainLayout(par->layout()) && !runparams.docbook_force_pars; + // Equations do not deserve their own paragraph (DocBook allows them outside paragraphs). // Exception: any case that generates an must still get a paragraph to be valid. - special_case |= nInsets == parSize && 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 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()); - }); + }; + 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 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; @@ -410,7 +448,14 @@ 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 pars_prepend; + std::vector pars; + std::vector 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)) continue; @@ -423,6 +468,8 @@ void makeParagraph( if (close_par) closeParTag(xs, &*par, (nextpar != end) ? &*nextpar : nullptr, runparams); } + for (docstring const & parXML : pars_append) + xs << XMLStream::ESCAPE_NONE << parXML; } @@ -458,8 +505,13 @@ 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 pars_prepend; + std::vector pars; + std::vector 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()) { @@ -482,6 +534,8 @@ void makeEnvironment(Text const &text, 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); } @@ -582,14 +636,21 @@ ParagraphList::const_iterator makeListEnvironment(Text const &text, // Generate the content of the item. if (sep < par->size()) { - auto pars = par->simpleDocBookOnePar(buf, runparams, + std::vector pars_prepend; + std::vector pars; + std::vector 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) { xml::openTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnerattr(), par->layout().docbookiteminnertagtype()); xs << XMLStream::ESCAPE_NONE << p; 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. xml::compTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnerattr(), @@ -634,14 +695,23 @@ void makeCommand( // Generate this command. auto prevpar = text.paragraphs().getParagraphBefore(par); - openParTag(xs, &*par, prevpar, runparams); - auto pars = par->simpleDocBookOnePar(buf, runparams,text.outerFont(distance(begin, par))); + std::vector pars_prepend; + std::vector pars; + std::vector 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, runparams); + for (docstring const & parXML : pars_append) + xs << XMLStream::ESCAPE_NONE << parXML; } @@ -684,6 +754,8 @@ DocBookDocumentSectioning hasDocumentSectioning(ParagraphList const ¶graphs, bool documentHasSections = false; while (bpit < epit) { + LASSERT(static_cast(bpit) < paragraphs.size(), return make_tuple(documentHasSections, bpit)); + Layout const &style = paragraphs[bpit].layout(); documentHasSections |= isLayoutSectioningOrSimilar(style); @@ -730,7 +802,7 @@ DocBookInfoTag getParagraphsWithInfo(ParagraphList const ¶graphs, set abstractWithLayout; set 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)) @@ -754,7 +826,7 @@ DocBookInfoTag getParagraphsWithInfo(ParagraphList const ¶graphs, // There should never be any section here, except for the first paragraph (a title can be part of ). // (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 paragraphs."); + LYXERR(Debug::OUTFILE, "Assertion failed: section found in potential paragraphs."); break; } @@ -948,12 +1020,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(""), beginComment)) != lyx::docstring::npos) { + cleaned.erase(cleaned.begin() + beginComment, cleaned.begin() + endComment + 3); + } + } + // Nothing? Then there is no abstract! if (cleaned.empty()) hasAbstract = false; @@ -1063,9 +1143,6 @@ void docbookParagraphs(Text const &text, return; }); - std::stack> headerLevels; // Used to determine when to open/close sections: store the depth - // of the section and the tag that was used to open it. - // Detect whether the document contains sections. If there are no sections, treatment is largely simplified. // In particular, there can't be an abstract, unless it is manually marked. bool documentHasSections; @@ -1083,25 +1160,81 @@ 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 , that's the closest in meaning. + // This is only useful if the things after the 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 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> headerLevels; // Used to determine when to open/close sections: store the depth + // of the section and the tag that was used to open it. + + // Then, iterate through the paragraphs of this document. auto par = text.paragraphs().iterator_at(bpit); auto end = text.paragraphs().iterator_at(epit); while (par != end) { - OutputParams ourparams = runparams; - - if (par->params().startOfAppendix()) - currentlyInAppendix = true; + // Skip paragraphs not producing any output. if (hasOnlyNotes(*par)) { ++par; continue; } + OutputParams ourparams = runparams; Layout const &style = par->layout(); // Think about adding
and/or
s. - if (isLayoutSectioning(style)) { + if (isLayoutSectioning(style) || par->params().startOfAppendix()) { int level = style.toclevel; // Need to close a previous section if it has the same level or a higher one (close
if opening a @@ -1122,8 +1255,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 +1294,16 @@ void docbookParagraphs(Text const &text, Inset const *firstInset = par->getInset(0); if (firstInset && (firstInset->lyxCode() == BIBITEM_CODE || firstInset->lyxCode() == BIBTEX_CODE)) { while (!headerLevels.empty()) { + // Don't close appendices before bibliographies. + if (headerLevels.top().second == "appendix") + break; + + // Pop the section from the stack. int level = headerLevels.top().first; docstring tag = from_utf8(""); headerLevels.pop(); - // Output the tag only if it corresponds to a legit section. + // Output the tag only if it corresponds to a legit section, as the rest of the code. if (level != Layout::NOT_IN_TOC) { xs << XMLStream::ESCAPE_NONE << tag; xs << xml::CR();