From fc2c36289b5c0cd15f453c4905bb56a2b20aef25 Mon Sep 17 00:00:00 2001 From: Thibaut Cuvelier Date: Thu, 27 Aug 2020 00:52:06 +0200 Subject: [PATCH] DocBook: rewrite makeListEnvironment. --- autotests/export/docbook/lists.lyx | 129 ++++++++++++++++++++++-- autotests/export/docbook/lists.xml | 136 +++++++++++++++++++++++++ lib/layouts/stdlists.inc | 5 +- src/output_docbook.cpp | 156 +++++++++++------------------ src/xml.cpp | 6 ++ src/xml.h | 1 - 6 files changed, 326 insertions(+), 107 deletions(-) diff --git a/autotests/export/docbook/lists.lyx b/autotests/export/docbook/lists.lyx index f4a58c2e10..1c1e88bcb0 100644 --- a/autotests/export/docbook/lists.lyx +++ b/autotests/export/docbook/lists.lyx @@ -102,11 +102,71 @@ Second item on two lines I'm the second line \end_layout +\begin_layout Itemize +Third item +\end_layout + \begin_layout Standard -\begin_inset Note Note -status collapsed +A simple enumerated list: +\end_layout + +\begin_layout Enumerate +First item +\end_layout + +\begin_layout Enumerate +Second item on two lines +\begin_inset Newline newline +\end_inset + +I'm the second line +\end_layout + +\begin_layout Standard +A simple description list: +\end_layout + +\begin_layout Description +Word description +\end_layout + +\begin_layout Description +Sentence meaning +\end_layout + +\begin_layout Standard +Nested lists: +\end_layout + +\begin_layout Itemize +First item +\end_layout + +\begin_deeper +\begin_layout Itemize +First first item +\end_layout + +\begin_layout Itemize +First second item +\end_layout + +\end_deeper +\begin_layout Itemize +Second item +\end_layout + +\begin_deeper +\begin_layout Itemize +Second first item +\end_layout + +\begin_layout Itemize +Second second item +\end_layout -\begin_layout Plain Layout +\end_deeper +\begin_layout Standard A complex list: \end_layout @@ -123,7 +183,47 @@ First first item First second item \end_layout -\begin_layout Plain Layout +\begin_layout Standard +Text after first item +\end_layout + +\end_deeper +\begin_layout Itemize +Second item +\end_layout + +\begin_deeper +\begin_layout Itemize +Second first item +\end_layout + +\begin_layout Itemize +Second second item +\end_layout + +\begin_layout Standard +Text after second item +\end_layout + +\end_deeper +\begin_layout Standard +A very complex list: +\end_layout + +\begin_layout Itemize +First item +\end_layout + +\begin_deeper +\begin_layout Itemize +First first item +\end_layout + +\begin_layout Itemize +First second item +\end_layout + +\begin_layout Standard Text after first item \end_layout @@ -141,15 +241,32 @@ Second first item Second second item \end_layout -\begin_layout Plain Layout +\begin_layout Standard Text after second item \end_layout \end_deeper -\end_inset +\begin_layout Standard +Nested description lists: +\end_layout +\begin_layout Description +LyX: +\end_layout +\begin_deeper +\begin_layout Description +lyx16 LyX 1.6 file format (lyx2lyx) +\end_layout + +\begin_layout Description +lyx21 LyX 2.1 file format (lyx2lyx) \end_layout +\begin_layout Description +xhtml LyXHTML (native LyX HTML export) +\end_layout + +\end_deeper \end_body \end_document diff --git a/autotests/export/docbook/lists.xml b/autotests/export/docbook/lists.xml index d7b24f6da7..6812230064 100644 --- a/autotests/export/docbook/lists.xml +++ b/autotests/export/docbook/lists.xml @@ -12,5 +12,141 @@ Second item on two lines I'm the second line + +Third item + + +A simple enumerated list: + + +First item + + +Second item on two lines +I'm the second line + + +A simple description list: + + +Word + +description + + + +Sentence + +meaning + + + +Nested lists: + + +First item + + +First first item + + +First second item + + + + +Second item + + +Second first item + + +Second second item + + + + +A complex list: + + +First item + + +First first item + + +First second item + + +Text after first item + + +Second item + + +Second first item + + +Second second item + + +Text after second item + + +A very complex list: + + +First item + + +First first item + + +First second item + +Text after first item + + +Second item + + +Second first item + + +Second second item + + +Text after second item + + +Nested description lists: + + +LyX: + + + + +lyx16 + +LyX 1.6 file format (lyx2lyx) + + + +lyx21 + +LyX 2.1 file format (lyx2lyx) + + + +xhtml + +LyXHTML (native LyX HTML export) + + + + + + \ No newline at end of file diff --git a/lib/layouts/stdlists.inc b/lib/layouts/stdlists.inc index c0b346dbd1..d05e896e39 100644 --- a/lib/layouts/stdlists.inc +++ b/lib/layouts/stdlists.inc @@ -43,9 +43,7 @@ Style Itemize Color latex EndFont EndArgument - DocBookWrapperTag itemizedlist - DocBookWrapperMergeWithPrevious true - DocBookTag NONE + DocBookTag itemizedlist DocBookItemTag listitem DocBookItemInnerTag para End @@ -134,6 +132,7 @@ Style Description DocBookItemTag listitem DocBookItemInnerTag para DocBookItemLabelTag term + DocBookItemLabelTagType paragraph End diff --git a/src/output_docbook.cpp b/src/output_docbook.cpp index 356de072b9..9d518539e7 100644 --- a/src/output_docbook.cpp +++ b/src/output_docbook.cpp @@ -225,7 +225,7 @@ void closeBlockTag(XMLStream & xs, const std::string & tag) void openTag(XMLStream & xs, const std::string & tag, const std::string & attr, const std::string & tagtype) { - if (tag.empty() || tag == "NONE") + 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 : always considered as a paragraph. @@ -275,7 +275,7 @@ void openParTag(XMLStream & xs, const Paragraph * par, const Paragraph * prevpar Layout const & prevlay = prevpar->layout(); if (prevlay.docbookwrappertag() != "NONE") { openWrapper = prevlay.docbookwrappertag() == lay.docbookwrappertag() - && !lay.docbookwrappermergewithprevious(); + && !lay.docbookwrappermergewithprevious(); } } @@ -308,32 +308,8 @@ void closeParTag(XMLStream & xs, Paragraph const * par, Paragraph const * nextpa if (nextpar != nullptr) { Layout const & nextlay = nextpar->layout(); if (nextlay.docbookwrappertag() != "NONE") { - if (nextpar->getDepth() == par->getDepth()) { - // Same depth: the basic condition applies. - closeWrapper = nextlay.docbookwrappertag() == lay.docbookwrappertag() - && !nextlay.docbookwrappermergewithprevious(); - } else if (nextpar->getDepth() > par->getDepth()) { - // The next paragraph is deeper: no need to close the wrapper, only to open it (cf. openParTag). - closeWrapper = 0; - } else { - // This paragraph is deeper than the next one: close the wrapper, - // disregarding docbookwrappermergewithprevious. - // Hypothesis: nextlay.docbookwrappertag() == lay.docbookwrappertag(). TODO: THIS IS WRONG! Loop back until a layout with the right depth is found? - closeWrapper = 1L + (long long) par->getDepth() - (long long) nextpar->getDepth(); // > 0, as nextpar->getDepth() < par->getDepth() - } - } else { - if (nextpar->getDepth() == par->getDepth()) { - // This is not wrapped: this must be the rest of the item, still within the wrapper. - closeWrapper = 1; - } else if (nextpar->getDepth() > par->getDepth()) { - // The next paragraph is deeper: no need to close the wrapper, only to open it (cf. openParTag). - closeWrapper = 0; - } else { - // This paragraph is deeper than the next one: close the wrapper, - // disregarding docbookwrappermergewithprevious. - // Hypothesis: nextlay.docbookwrappertag() == lay.docbookwrappertag(). TODO: THIS IS WRONG! Loop back until a layout with the right depth is found? - closeWrapper = 1L + (long long) par->getDepth() - (long long) nextpar->getDepth(); // > 0, as nextpar->getDepth() < par->getDepth() - } + closeWrapper = nextlay.docbookwrappertag() == lay.docbookwrappertag() + && !nextlay.docbookwrappermergewithprevious(); } } @@ -564,9 +540,7 @@ void makeEnvironment(Text const &text, // Usual cases: maybe there is something specified at the layout level. Highly unlikely, though. docstring const lbl = par->params().labelString(); - if (lbl.empty()) { - xs << xml::CR(); - } else { + if (!lbl.empty()) { openLabelTag(xs, style); xs << lbl; closeLabelTag(xs, style); @@ -628,7 +602,6 @@ ParagraphList::const_iterator findEndOfEnvironment( ParagraphList::const_iterator const & pend) { // Copy-paste from XHTML. Should be factored out at some point... - ParagraphList::const_iterator p = pstart; Layout const & bstyle = p->layout(); size_t const depth = p->params().depth(); @@ -662,95 +635,84 @@ ParagraphList::const_iterator makeListEnvironment(Text const &text, Buffer const &buf, XMLStream &xs, OutputParams const &runparams, - ParagraphList::const_iterator const & par) + ParagraphList::const_iterator const & begin) { + auto par = begin; auto const end = text.paragraphs().end(); + auto const envend = findEndOfEnvironment(par, end); - // 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? + // 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()); - // Generate the contents of this environment. There is a special case if this is like some environment. - Layout const & style = par->layout(); - if (style.latextype == LATEX_COMMAND) { - // Nothing to do (otherwise, infinite loops). - } else if (style.latextype == LATEX_ENVIRONMENT || - style.latextype == LATEX_LIST_ENVIRONMENT || - style.latextype == LATEX_ITEM_ENVIRONMENT) { - // Open a wrapper tag if needed. - if (style.docbookitemwrappertag() != "NONE") - openTag(xs, style.docbookitemwrappertag(), style.docbookitemwrapperattr(), style.docbookitemwrappertagtype()); + // Handle the content of the list environment, item by item. + while (par != envend) { + Layout const & style = par->layout(); + + // Open the item wrapper. + 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. pos_type sep = 0; if (style.labeltype != LABEL_NO_LABEL && style.docbookitemlabeltag() != "NONE") { - // At least one condition must be met: - // - this environment is not a list - // - if this is a list, the label must not be manual (i.e. it must be taken from the layout) - if (style.latextype != LATEX_LIST_ENVIRONMENT || style.labeltype != LABEL_MANUAL) { + if (style.labeltype == LABEL_MANUAL) { + // Only variablelist gets here (or similar items defined as an extension in the layout). + openLabelTag(xs, style); + sep = 1 + par->firstWordDocBook(xs, runparams); + closeLabelTag(xs, style); + } else { // Usual cases: maybe there is something specified at the layout level. Highly unlikely, though. docstring const lbl = par->params().labelString(); - if (lbl.empty()) { - xs << xml::CR(); - } else { + if (!lbl.empty()) { openLabelTag(xs, style); xs << lbl; closeLabelTag(xs, style); } - } else { - // Only variablelist gets here (or similar items defined as an extension in the layout). - openLabelTag(xs, style); - sep = par->firstWordDocBook(xs, runparams); - closeLabelTag(xs, style); } } - // Maybe the item is completely empty, i.e. if the first word ends at the end of the current paragraph - // AND if the next paragraph doesn't have the same depth (if there is such a paragraph). - // Common case: there is only the first word on the line, but there is a nested list instead - // of more text. - bool emptyItem = false; - if (sep == par->size()) { // If the separator is already at the end of this paragraph... - auto next_par = par; - ++next_par; - if (next_par == text.paragraphs().end()) // There is no next paragraph. - emptyItem = true; - else // There is a next paragraph: check depth. - emptyItem = par->params().depth() >= next_par->params().depth(); - } - - if (emptyItem) { - // Avoid having an empty item, this is not valid DocBook. A single character is enough to force - // generation of a full . - // TODO: this always worked only by magic... - xs << ' '; - } else { - // Generate the rest of the paragraph, if need be. Open as many inner tags as necessary. - auto pars = par->simpleDocBookOnePar(buf, runparams, text.outerFont(std::distance(text.paragraphs().begin(), par)), sep); - auto p = pars.begin(); - while (true) { - xs << XMLStream::ESCAPE_NONE << *p; - ++p; - if (p != pars.end()) { - closeTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnertagtype()); - openTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnerattr(), par->layout().docbookiteminnertagtype()); - } else - break; + // Open the item (after the wrapper and the label). + openTag(xs, style.docbookitemtag(), style.docbookitemattr(), style.docbookitemtagtype()); + + // Generate the content of the item. + if (sep < par->size()) { + auto pars = par->simpleDocBookOnePar(buf, runparams, + text.outerFont(std::distance(text.paragraphs().begin(), par)), sep); + for (auto &p : pars) { + openTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnerattr(), + par->layout().docbookiteminnertagtype()); + xs << XMLStream::ESCAPE_NONE << p; + closeTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnertagtype()); } + } else { + // DocBook doesn't like emptiness. + openTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnerattr(), + par->layout().docbookiteminnertagtype()); + closeTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnertagtype()); } - } else { - makeAny(text, buf, xs, runparams, par); + + // If the next item is deeper, it must go entirely within this item (do it recursively). + // By construction, with findEndOfEnvironment, depth can only stay constant or increase, never decrease. + depth_type currentDepth = par->getDepth(); + ++par; + while (par != envend && par->getDepth() != currentDepth) + par = makeAny(text, buf, xs, runparams, par); + // Usually, this loop only makes one iteration, except in complex scenarios, like an item with a paragraph, + // 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()); } - // Close the environment. - auto nextpar = par; - ++nextpar; - closeParTag(xs, &*par, (nextpar != end) ? &*nextpar : nullptr); // TODO: switch in layout for par/block? + // Close this environment in exactly the same way as it was opened. + closeTag(xs, envstyle.docbooktag(), envstyle.docbooktagtype()); + closeTag(xs, envstyle.docbookwrappertag(), envstyle.docbookwrappertagtype()); - return nextpar; + return envend; } diff --git a/src/xml.cpp b/src/xml.cpp index 2f496679c9..0daa9d84e2 100644 --- a/src/xml.cpp +++ b/src/xml.cpp @@ -185,6 +185,12 @@ void XMLStream::writeError(docstring const &s) } +XMLStream::TagPtr XMLStream::getLastStackTag() +{ + return tag_stack_.back(); +} + + bool XMLStream::closeFontTags() { if (isTagPending(xml::parsep_tag)) diff --git a/src/xml.h b/src/xml.h index 45c62aba32..56e5d81114 100644 --- a/src/xml.h +++ b/src/xml.h @@ -125,7 +125,6 @@ private: // own these pointers and how they will be deleted, so we use shared // pointers. /// - typedef std::shared_ptr TagPtr; typedef std::deque TagDeque; /// template -- 2.39.5