#include <config.h>
+#include "output_docbook.h"
+
#include "Buffer.h"
#include "buffer_funcs.h"
#include "BufferParams.h"
#include "Font.h"
#include "InsetList.h"
-#include "output_docbook.h"
#include "Paragraph.h"
#include "ParagraphList.h"
#include "ParagraphParameters.h"
#include "insets/InsetBibtex.h"
#include "insets/InsetBibitem.h"
#include "insets/InsetLabel.h"
+#include "mathed/InsetMath.h"
#include "insets/InsetNote.h"
+#include "support/debug.h"
#include "support/lassert.h"
+#include "support/textutils.h"
#include <stack>
#include <iostream>
// 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)
{
- Layout const & lay = par->layout();
-
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
// 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).
- bool openWrapper = lay.docbookwrappertag() != "NONE";
- if (prevpar != nullptr) {
+ Layout const & lay = par->layout();
+ 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() &&
// Main logic.
if (openWrapper)
- openTag(xs, lay.docbookwrappertag(), lay.docbookwrapperattr(), lay.docbookwrappertagtype());
+ xml::openTag(xs, lay.docbookwrappertag(), lay.docbookwrapperattr(), lay.docbookwrappertagtype());
const string & tag = lay.docbooktag();
if (tag != "NONE") {
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() &&
}
// 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());
}
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;
}
}
// 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");
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);
}
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).
- special_case |= nInsets == (size_t) par->size() && std::all_of(par->insetList().begin(), par->insetList().end(), [](InsetList::Element inset) {
- return inset.inset && inset.inset->asInsetMath();
+ // Exception: any case that generates an <inlineequation> must still get a paragraph to be valid.
+ 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;
});
- // TODO: Could get rid of this with a DocBook equivalent to htmlisblock? Not for all cases, unfortunately... See above for those that have been determined not to be allowable for this potential refactoring.
- if (!special_case && par->size() == 1 && par->getInset(0)) {
- Inset const * firstInset = par->getInset(0);
-
- // Floats cannot be in paragraphs.
- special_case = to_utf8(firstInset->layoutName()).substr(0, 6) == "Float:";
-
- // Bibliographies cannot be in paragraphs.
- if (!special_case && firstInset->asInsetCommand())
- special_case = firstInset->asInsetCommand()->params().getCmdName() == "bibtex";
+ // 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;
- // ERTs are in comments, not paragraphs.
- if (!special_case && firstInset->lyxCode() == lyx::ERT_CODE)
- special_case = true;
+ // 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;
+ }
- // Listings should not get into their own paragraph.
- if (!special_case && firstInset->lyxCode() == lyx::LISTINGS_CODE)
- special_case = true;
-
- // Boxes cannot get into their own paragraph.
- if (!special_case && firstInset->lyxCode() == lyx::BOX_CODE)
- special_case = true;
- }
+ // No case matched: give up.
+ return false;
+ };
+ special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), isFlexSpecialCase);
+ // Open a paragraph if it is allowed, we are not already within a paragraph, and the insets in the paragraph do
+ // not forbid paragraphs (aka special cases).
bool const open_par = runparams.docbook_make_pars
&& !runparams.docbook_in_par
&& !special_case;
// 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.
// 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;
}
OutputParams const &runparams,
ParagraphList::const_iterator const & par)
{
+ // Useful variables.
auto const end = text.paragraphs().end();
auto nextpar = par;
++nextpar;
// 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();
// 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.
+ // Otherwise, there would one more new line in the output than in the LyX document.
if (p != pars.end())
xs << xml::CR();
}
} 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);
}
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) {
- 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.
- openTag(xs, style.docbookitemwrappertag(), style.docbookitemwrapperattr(), style.docbookitemwrappertagtype());
+ Layout const & style = par->layout();
+ 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.
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).
// 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;
}
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();
// 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;
}
bool isLayoutSectioning(Layout const & lay)
{
- return lay.category() == from_utf8("Sectioning");
+ if (lay.docbooksection()) // Special case: some DocBook styles must be handled as sections.
+ return true;
+ 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";
}
while (bpit < epit) {
Layout const &style = paragraphs[bpit].layout();
- documentHasSections |= isLayoutSectioning(style);
+ documentHasSections |= isLayoutSectioningOrSimilar(style);
if (documentHasSections)
break;
// return false.
if (!par.isInset(i) || par.getInset(i)->lyxCode() != NOTE_CODE)
return false;
+
+ // An empty paragraph may still require some output.
+ if (par.layout().docbooksection())
+ return false;
+
+ // There should be really no content here.
return true;
}
DocBookInfoTag getParagraphsWithInfo(ParagraphList const ¶graphs,
pit_type bpit, pit_type const epit,
- // Typically, bpit is the beginning of the document and epit the end *or* the first section.
- bool documentHasSections) {
+ // Typically, bpit is the beginning of the document and epit the end of the
+ // document *or* the first section.
+ bool documentHasSections,
+ bool detectUnlayoutedAbstract
+ // Whether paragraphs with no specific layout should be detected as abstracts.
+ // For inner sections, an abstract should only be detected if it has a specific
+ // layout. For others, anything that might look like an abstract should be sought.
+ ) {
set<pit_type> shouldBeInInfo;
set<pit_type> mustBeInInfo;
set<pit_type> abstractWithLayout;
// 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.
Paragraph const & par = paragraphs[cpit];
+ Layout const &style = par.layout();
if (hasOnlyNotes(par))
continue;
- // There should never be any section here. (Just a sanity check: if this fails, this function could end up
- // processing the whole document.)
- if (isLayoutSectioning(par.layout())) {
+ // 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.");
break;
}
// If this is marked as an abstract by the layout, put it in the right set.
- if (par.layout().docbookabstract()) {
+ 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).
- Layout const &style = par.layout();
-
if (style.docbookininfo() == "always")
mustBeInInfo.emplace(cpit);
else if (style.docbookininfo() == "maybe")
shouldBeInInfo.emplace(cpit);
- else if (documentHasSections && !hasAbstractLayout)
+ 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. For instance, don't ake into account
abstractNoLayout.emplace(cpit);
else // This should definitely not be in <info>.
break;
} // 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;
}
// 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
+ // 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));
+
+ 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, 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);
+ ++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.
abstract = os2.str();
docstring cleaned = abstract;
- cleaned.erase(std::remove_if(cleaned.begin(), cleaned.end(), ::isspace), cleaned.end());
+ cleaned.erase(std::remove_if(cleaned.begin(), cleaned.end(), lyx::isSpace), cleaned.end());
// Nothing? Then there is no abstract!
if (cleaned.empty())
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));
+ // 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) {
+ xs << xml::StartTag("title");
+ xs << "Untitled Document";
+ xs << xml::EndTag("title");
+ xs << xml::CR();
+ }
+
+ // - 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);
- // 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).
+ // - Finally, always output the abstract as the last item of the <info>, as it requires special treatment
+ // (especially if it contains several paragraphs that are empty).
if (hasAbstract) {
- if (info.abstractLayout) {
- xs << XMLStream::ESCAPE_NONE << abstract;
- xs << xml::CR();
- } else {
- string tag = paragraphs[*info.abstract.begin()].layout().docbookforceabstracttag();
- if (tag == "NONE")
- tag = "abstract";
-
- if (!xs.isLastTagCR())
- xs << xml::CR();
+ string tag = paragraphs[*info.abstract.begin()].layout().docbookforceabstracttag();
+ if (tag == "NONE")
+ tag = "abstract";
- xs << xml::StartTag(tag);
- xs << xml::CR();
- xs << XMLStream::ESCAPE_NONE << abstract;
- xs << xml::EndTag(tag);
+ if (!xs.isLastTagCR())
xs << xml::CR();
- }
+
+ xs << xml::StartTag(tag);
+ xs << xml::CR();
+ xs << XMLStream::ESCAPE_NONE << abstract;
+ xs << xml::EndTag(tag);
+ xs << xml::CR();
}
// End the <info> tag if it was started.
ParagraphList const ¶graphs = text.paragraphs();
// First, the <info> tag.
- DocBookInfoTag info = getParagraphsWithInfo(paragraphs, bpit, epit, false);
+ DocBookInfoTag info = getParagraphsWithInfo(paragraphs, bpit, epit, false, true);
outputDocBookInfo(text, buf, xs, runparams, paragraphs, info);
// Then, the content. It starts where the <info> ends.
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;
}
// Output the first <info> tag (or just the title).
- DocBookInfoTag info = getParagraphsWithInfo(paragraphs, bpit, eppit, true);
+ DocBookInfoTag info = getParagraphsWithInfo(paragraphs, bpit, eppit, true, true);
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;
}
// 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).
// Don't output the ID as a DocBook <anchor>.
ourparams.docbook_anchors_to_ignore.emplace(label->screenLabel());
- // Cannot have multiple IDs per tag.
+ // Cannot have multiple IDs per tag. If there is another ID inset in the document, it will
+ // be output as a DocBook anchor.
break;
}
}
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();
}
}
- // Generate this paragraph.
- par = makeAny(text, buf, xs, ourparams, par);
-
- // Some special sections may require abstracts (mostly parts, in books).
+ // Generate the <info> tag if a section was just opened.
+ // Some sections may require abstracts (mostly parts, in books: DocBookForceAbstractTag will not be NONE),
+ // others can still have an abstract (it must be detected so that it can be output at the right place).
// TODO: docbookforceabstracttag is a bit contrived here, but it does the job. Having another field just for this would be cleaner, but that's just for <part> and <partintro>, so it's probably not worth the effort.
- if (isLayoutSectioning(style) && style.docbookforceabstracttag() != "NONE") {
+ if (isLayoutSectioning(style)) {
// This abstract may be found between the next paragraph and the next title.
pit_type cpit = std::distance(text.paragraphs().begin(), par);
- pit_type ppit = std::get<1>(hasDocumentSectioning(paragraphs, cpit, epit));
+ pit_type ppit = std::get<1>(hasDocumentSectioning(paragraphs, cpit + 1L, epit));
// Generate this abstract (this code corresponds to parts of outputDocBookInfo).
- DocBookInfoTag secInfo = getParagraphsWithInfo(paragraphs, cpit, ppit, true);
+ DocBookInfoTag secInfo = getParagraphsWithInfo(paragraphs, cpit, ppit, true,
+ style.docbookforceabstracttag() != "NONE");
+
+ if (!secInfo.mustBeInInfo.empty() || !secInfo.shouldBeInInfo.empty() || !secInfo.abstract.empty()) {
+ // Generate the <info>, if required. If DocBookForceAbstractTag != NONE, this abstract will not be in
+ // <info>, unlike other ("standard") abstracts.
+ bool hasStandardAbstract = !secInfo.abstract.empty() && style.docbookforceabstracttag() == "NONE";
+ bool needInfo = !secInfo.mustBeInInfo.empty() || hasStandardAbstract;
+
+ if (needInfo) {
+ xs.startDivision(false);
+ xs << xml::StartTag("info");
+ xs << xml::CR();
+ }
- if (!secInfo.abstract.empty()) {
- xs << xml::StartTag(style.docbookforceabstracttag());
- xs << xml::CR();
- for (auto const &p : secInfo.abstract)
- makeAny(text, buf, xs, runparams, paragraphs.iterator_at(p));
- xs << xml::EndTag(style.docbookforceabstracttag());
- xs << xml::CR();
- }
+ // Output the elements that should go in <info>, before and after the abstract.
+ for (auto pit : secInfo.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, ourparams, paragraphs.iterator_at(pit));
+ for (auto pit : secInfo.mustBeInInfo)
+ makeAny(text, buf, xs, ourparams, paragraphs.iterator_at(pit));
+
+ // Deal with the abstract in <info> if it is standard (i.e. its tag is <abstract>).
+ if (!secInfo.abstract.empty() && hasStandardAbstract) {
+ if (!secInfo.abstractLayout) {
+ xs << xml::StartTag("abstract");
+ xs << xml::CR();
+ }
- // Skip all the text that just has been generated.
- par = paragraphs.iterator_at(ppit);
+ for (auto const &p : secInfo.abstract)
+ makeAny(text, buf, xs, ourparams, paragraphs.iterator_at(p));
+
+ if (!secInfo.abstractLayout) {
+ xs << xml::EndTag("abstract");
+ xs << xml::CR();
+ }
+ }
+
+ // End the <info> tag if it was started.
+ if (needInfo) {
+ if (!xs.isLastTagCR())
+ xs << xml::CR();
+
+ xs << xml::EndTag("info");
+ xs << xml::CR();
+ xs.endDivision();
+ }
+
+ // Deal with the abstract outside <info> if it is not standard (i.e. its tag is layout-defined).
+ if (!secInfo.abstract.empty() && !hasStandardAbstract) {
+ // Assert: style.docbookforceabstracttag() != NONE.
+ xs << xml::StartTag(style.docbookforceabstracttag());
+ xs << xml::CR();
+ for (auto const &p : secInfo.abstract)
+ makeAny(text, buf, xs, ourparams, paragraphs.iterator_at(p));
+ xs << xml::EndTag(style.docbookforceabstracttag());
+ xs << xml::CR();
+ }
+
+ // Skip all the text that has just been generated.
+ par = paragraphs.iterator_at(secInfo.epit);
+ } else {
+ // No <info> tag to generate, proceed as for normal paragraphs.
+ par = makeAny(text, buf, xs, ourparams, par);
+ }
+ } else {
+ // Generate this paragraph, as it has nothing special.
+ par = makeAny(text, buf, xs, ourparams, par);
}
}