2 * \file output_docbook.cpp
3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Lars Gullik Bjønnes
9 * Full author contact details are available in file CREDITS.
14 #include "output_docbook.h"
17 #include "buffer_funcs.h"
18 #include "BufferParams.h"
20 #include "InsetList.h"
21 #include "Paragraph.h"
22 #include "ParagraphList.h"
23 #include "ParagraphParameters.h"
26 #include "TextClass.h"
28 #include "insets/InsetBibtex.h"
29 #include "insets/InsetBibitem.h"
30 #include "insets/InsetLabel.h"
31 #include "mathed/InsetMath.h"
32 #include "insets/InsetNote.h"
34 #include "support/debug.h"
35 #include "support/lassert.h"
36 #include "support/textutils.h"
44 using namespace lyx::support;
50 std::string fontToDocBookTag(xml::FontTypes type)
53 case xml::FontTypes::FT_EMPH:
54 case xml::FontTypes::FT_BOLD:
56 case xml::FontTypes::FT_NOUN:
58 case xml::FontTypes::FT_UBAR:
59 case xml::FontTypes::FT_WAVE:
60 case xml::FontTypes::FT_DBAR:
61 case xml::FontTypes::FT_SOUT:
62 case xml::FontTypes::FT_XOUT:
63 case xml::FontTypes::FT_ITALIC:
64 case xml::FontTypes::FT_UPRIGHT:
65 case xml::FontTypes::FT_SLANTED:
66 case xml::FontTypes::FT_SMALLCAPS:
67 case xml::FontTypes::FT_ROMAN:
68 case xml::FontTypes::FT_SANS:
70 case xml::FontTypes::FT_TYPE:
72 case xml::FontTypes::FT_SIZE_TINY:
73 case xml::FontTypes::FT_SIZE_SCRIPT:
74 case xml::FontTypes::FT_SIZE_FOOTNOTE:
75 case xml::FontTypes::FT_SIZE_SMALL:
76 case xml::FontTypes::FT_SIZE_NORMAL:
77 case xml::FontTypes::FT_SIZE_LARGE:
78 case xml::FontTypes::FT_SIZE_LARGER:
79 case xml::FontTypes::FT_SIZE_LARGEST:
80 case xml::FontTypes::FT_SIZE_HUGE:
81 case xml::FontTypes::FT_SIZE_HUGER:
82 case xml::FontTypes::FT_SIZE_INCREASE:
83 case xml::FontTypes::FT_SIZE_DECREASE:
91 string fontToRole(xml::FontTypes type)
93 // Specific fonts are achieved with roles. The only common ones are "" for basic emphasis,
94 // and "bold"/"strong" for bold. With some specific options, other roles are copied into
95 // HTML output (via the DocBook XSLT sheets); otherwise, if not recognised, they are just ignored.
96 // Hence, it is not a problem to have many roles by default here.
97 // See https://www.sourceware.org/ml/docbook/2003-05/msg00269.html
99 case xml::FontTypes::FT_ITALIC:
100 case xml::FontTypes::FT_EMPH:
102 case xml::FontTypes::FT_BOLD:
104 case xml::FontTypes::FT_NOUN: // Outputs a <person>
105 case xml::FontTypes::FT_TYPE: // Outputs a <code>
107 case xml::FontTypes::FT_UBAR:
110 // All other roles are non-standard for DocBook.
112 case xml::FontTypes::FT_WAVE:
114 case xml::FontTypes::FT_DBAR:
116 case xml::FontTypes::FT_SOUT:
118 case xml::FontTypes::FT_XOUT:
120 case xml::FontTypes::FT_UPRIGHT:
122 case xml::FontTypes::FT_SLANTED:
124 case xml::FontTypes::FT_SMALLCAPS:
126 case xml::FontTypes::FT_ROMAN:
128 case xml::FontTypes::FT_SANS:
130 case xml::FontTypes::FT_SIZE_TINY:
132 case xml::FontTypes::FT_SIZE_SCRIPT:
133 return "size_script";
134 case xml::FontTypes::FT_SIZE_FOOTNOTE:
135 return "size_footnote";
136 case xml::FontTypes::FT_SIZE_SMALL:
138 case xml::FontTypes::FT_SIZE_NORMAL:
139 return "size_normal";
140 case xml::FontTypes::FT_SIZE_LARGE:
142 case xml::FontTypes::FT_SIZE_LARGER:
143 return "size_larger";
144 case xml::FontTypes::FT_SIZE_LARGEST:
145 return "size_largest";
146 case xml::FontTypes::FT_SIZE_HUGE:
148 case xml::FontTypes::FT_SIZE_HUGER:
150 case xml::FontTypes::FT_SIZE_INCREASE:
151 return "size_increase";
152 case xml::FontTypes::FT_SIZE_DECREASE:
153 return "size_decrease";
160 string fontToAttribute(xml::FontTypes type) {
161 // If there is a role (i.e. nonstandard use of a tag), output the attribute. Otherwise, the sheer tag is sufficient
163 string role = fontToRole(type);
165 return "role='" + role + "'";
171 // Higher-level convenience functions.
173 void openParTag(XMLStream & xs, const Paragraph * par, const Paragraph * prevpar, const OutputParams & runparams)
178 // If the previous paragraph is empty, don't consider it when opening wrappers.
179 if (prevpar && prevpar->empty() && !prevpar->allowEmpty())
182 // When should the wrapper be opened here? Only if the previous paragraph has the SAME wrapper tag
183 // (usually, they won't have the same layout) and the CURRENT one allows merging.
184 // The main use case is author information in several paragraphs: if the name of the author is the
185 // first paragraph of an author, then merging with the previous tag does not make sense. Say the
186 // next paragraph is the affiliation, then it should be output in the same <author> tag (different
187 // layout, same wrapper tag).
188 Layout const & lay = par->layout();
189 bool openWrapper = lay.docbookwrappertag() != "NONE" && !runparams.docbook_ignore_wrapper;
191 if (prevpar != nullptr && !runparams.docbook_ignore_wrapper) {
192 Layout const & prevlay = prevpar->layout();
193 if (prevlay.docbookwrappertag() != "NONE") {
194 if (prevlay.docbookwrappertag() == lay.docbookwrappertag() &&
195 prevlay.docbookwrapperattr() == lay.docbookwrapperattr())
196 openWrapper = !lay.docbookwrappermergewithprevious();
204 xml::openTag(xs, lay.docbookwrappertag(), lay.docbookwrapperattr(), lay.docbookwrappertagtype());
206 if (lay.docbookgeneratetitle()) {
207 docstring const label = par->params().labelString();
209 xml::openTag(xs, "title", "", "paragraph");
210 xs << (!label.empty() ? label : from_ascii("No title"));
211 xml::closeTag(xs, "title", "paragraph");
215 const string & tag = lay.docbooktag();
217 auto xmltag = xml::ParTag(tag, lay.docbookattr());
218 if (!xs.isTagOpen(xmltag, 1)) { // Don't nest a paragraph directly in a paragraph.
219 // TODO: required or not?
220 // TODO: avoid creating a ParTag object just for this query...
221 xml::openTag(xs, lay.docbooktag(), lay.docbookattr(), lay.docbooktagtype());
222 xml::openTag(xs, lay.docbookinnertag(), lay.docbookinnerattr(), lay.docbookinnertagtype());
226 xml::openTag(xs, lay.docbookitemwrappertag(), lay.docbookitemwrapperattr(), lay.docbookitemwrappertagtype());
227 xml::openTag(xs, lay.docbookitemtag(), lay.docbookitemattr(), lay.docbookitemtagtype());
228 xml::openTag(xs, lay.docbookiteminnertag(), lay.docbookiteminnerattr(), lay.docbookiteminnertagtype());
232 void closeParTag(XMLStream & xs, Paragraph const * par, Paragraph const * nextpar, const OutputParams & runparams)
237 // If the next paragraph is empty, don't consider it when closing wrappers.
238 if (nextpar && nextpar->empty() && !nextpar->allowEmpty())
241 // See comment in openParTag.
242 Layout const & lay = par->layout();
243 bool closeWrapper = lay.docbookwrappertag() != "NONE" && !runparams.docbook_ignore_wrapper;
245 if (nextpar != nullptr && !runparams.docbook_ignore_wrapper) {
246 Layout const & nextlay = nextpar->layout();
247 if (nextlay.docbookwrappertag() != "NONE") {
248 if (nextlay.docbookwrappertag() == lay.docbookwrappertag() &&
249 nextlay.docbookwrapperattr() == lay.docbookwrapperattr())
250 closeWrapper = !nextlay.docbookwrappermergewithprevious();
257 xml::closeTag(xs, lay.docbookiteminnertag(), lay.docbookiteminnertagtype());
258 xml::closeTag(xs, lay.docbookitemtag(), lay.docbookitemtagtype());
259 xml::closeTag(xs, lay.docbookitemwrappertag(), lay.docbookitemwrappertagtype());
260 xml::closeTag(xs, lay.docbookinnertag(), lay.docbookinnertagtype());
261 xml::closeTag(xs, lay.docbooktag(), lay.docbooktagtype());
263 xml::closeTag(xs, lay.docbookwrappertag(), lay.docbookwrappertagtype());
267 void makeBibliography(
271 OutputParams const & runparams,
272 ParagraphList::const_iterator const & par)
274 // If this is the first paragraph in a bibliography, open the bibliography tag.
275 auto const * pbegin_before = text.paragraphs().getParagraphBefore(par);
276 if (pbegin_before == nullptr || (pbegin_before && pbegin_before->layout().latextype != LATEX_BIB_ENVIRONMENT)) {
277 xs << xml::StartTag("bibliography");
281 // Start the precooked bibliography entry. This is very much like opening a paragraph tag.
282 // Don't forget the citation ID!
284 for (auto i = 0; i < par->size(); ++i) {
285 Inset const *ip = par->getInset(i);
288 if (const auto * bibitem = dynamic_cast<const InsetBibitem*>(ip)) {
289 auto id = xml::cleanID(bibitem->getParam("key"));
290 attr = from_utf8("xml:id='") + id + from_utf8("'");
294 xs << xml::StartTag(from_utf8("bibliomixed"), attr);
296 // Generate the entry. Concatenate the different parts of the paragraph if any.
297 auto const begin = text.paragraphs().begin();
298 std::vector<docstring> pars_prepend;
299 std::vector<docstring> pars;
300 std::vector<docstring> pars_append;
301 tie(pars_prepend, pars, pars_append) = par->simpleDocBookOnePar(buf, runparams, text.outerFont(std::distance(begin, par)), 0);
303 for (auto & parXML : pars_prepend)
304 xs << XMLStream::ESCAPE_NONE << parXML;
305 for (auto & parXML : pars)
306 xs << XMLStream::ESCAPE_NONE << parXML;
307 for (auto & parXML : pars_append)
308 xs << XMLStream::ESCAPE_NONE << parXML;
310 // End the precooked bibliography entry.
311 xs << xml::EndTag("bibliomixed");
314 // If this is the last paragraph in a bibliography, close the bibliography tag.
315 auto const end = text.paragraphs().end();
318 bool endBibliography = nextpar == end || nextpar->layout().latextype != LATEX_BIB_ENVIRONMENT;
320 if (endBibliography) {
321 xs << xml::EndTag("bibliography");
331 OutputParams const & runparams,
332 ParagraphList::const_iterator const & par)
335 auto const begin = text.paragraphs().begin();
336 auto const end = text.paragraphs().end();
337 auto prevpar = text.paragraphs().getParagraphBefore(par);
339 // We want to open the paragraph tag if:
340 // (i) the current layout permits multiple paragraphs
341 // (ii) we are either not already inside a paragraph (HTMLIsBlock) OR
342 // we are, but this is not the first paragraph
344 // But there is also a special case, and we first see whether we are in it.
345 // We do not want to open the paragraph tag if this paragraph contains
346 // only one item, and that item is "inline", i.e., not HTMLIsBlock (such
347 // as a branch). On the other hand, if that single item has a font change
348 // applied to it, then we still do need to open the paragraph.
350 // Obviously, this is very fragile. The main reason we need to do this is
351 // because of branches, e.g., a branch that contains an entire new section.
352 // We do not really want to wrap that whole thing in a <div>...</div>.
353 bool special_case = false;
354 Inset const *specinset = par->size() == 1 ? par->getInset(0) : nullptr;
355 if (specinset && !specinset->getLayout().htmlisblock()) { // TODO: Convert htmlisblock to a DocBook parameter? docbooknotinpara should be enough in most cases.
356 Layout const &style = par->layout();
357 FontInfo const first_font = style.labeltype == LABEL_MANUAL ?
358 style.labelfont : style.font;
359 FontInfo const our_font =
360 par->getFont(buf.masterBuffer()->params(), 0,
361 text.outerFont(std::distance(begin, par))).fontInfo();
363 if (first_font == our_font)
367 size_t nInsets = std::distance(par->insetList().begin(), par->insetList().end());
368 auto parSize = (size_t) par->size();
370 // Plain layouts must be ignored.
371 special_case |= buf.params().documentClass().isPlainLayout(par->layout()) && !runparams.docbook_force_pars;
373 // Equations do not deserve their own paragraph (DocBook allows them outside paragraphs).
374 // Exception: any case that generates an <inlineequation> must still get a paragraph to be valid.
375 auto isEquationSpecialCase = [](InsetList::Element inset) {
376 return inset.inset && inset.inset->asInsetMath() && inset.inset->asInsetMath()->getType() != hullSimple;
378 special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), isEquationSpecialCase);
380 // Things that should not get into their own paragraph. (Only valid for DocBook.)
381 static std::set<InsetCode> lyxCodeSpecialCases = {
384 BIBTEX_CODE, // Bibliographies cannot be in paragraphs. Bibitems should still be handled as paragraphs,
385 // though (see makeBibliography).
386 ERT_CODE, // ERTs are in comments, not paragraphs.
391 TOC_CODE, // To be ignored in DocBook, the processor afterwards should deal with ToCs.
392 NOTE_CODE // Notes do not produce any output.
394 auto isLyxCodeSpecialCase = [](InsetList::Element inset) {
395 return lyxCodeSpecialCases.find(inset.inset->lyxCode()) != lyxCodeSpecialCases.end();
397 special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), isLyxCodeSpecialCase);
399 // Flex elements (InsetLayout) have their own parameter to control the special case.
400 auto isFlexSpecialCase = [](InsetList::Element inset) {
401 if (inset.inset->lyxCode() != FLEX_CODE)
404 // Standard condition: check the parameter.
405 if (inset.inset->getLayout().docbooknotinpara())
408 // If the parameter is not set, maybe the flex inset only contains things that should match the standard
409 // condition. In this case, isLyxCodeSpecialCase must also check for bibitems...
410 auto isLyxCodeSpecialCase = [](InsetList::Element inset) {
411 return lyxCodeSpecialCases.find(inset.inset->lyxCode()) != lyxCodeSpecialCases.end() ||
412 inset.inset->lyxCode() == BIBITEM_CODE;
414 if (InsetText * text = inset.inset->asInsetText()) {
415 for (auto const & par : text->paragraphs()) {
416 size_t nInsets = std::distance(par.insetList().begin(), par.insetList().end());
417 auto parSize = (size_t) par.size();
419 if (nInsets == 1 && par.insetList().begin()->inset->lyxCode() == BIBITEM_CODE)
421 if (nInsets != parSize)
423 if (!std::all_of(par.insetList().begin(), par.insetList().end(), isLyxCodeSpecialCase))
429 // No case matched: give up.
432 special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), isFlexSpecialCase);
434 // If the insets should be rendered as images, enter the special case.
435 auto isRenderedAsImageSpecialCase = [](InsetList::Element inset) {
436 return inset.inset && inset.inset->getLayout().docbookrenderasimage();
438 special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), isRenderedAsImageSpecialCase);
440 // Open a paragraph if it is allowed, we are not already within a paragraph, and the insets in the paragraph do
441 // not forbid paragraphs (aka special cases).
442 bool const open_par = runparams.docbook_make_pars
443 && !runparams.docbook_in_par
446 // We want to issue the closing tag if either:
447 // (i) We opened it, and either docbook_in_par is false,
448 // or we're not in the last paragraph, anyway.
449 // (ii) We didn't open it and docbook_in_par is true,
450 // but we are in the first par, and there is a next par.
451 bool const close_par = open_par && !runparams.docbook_in_par;
453 // Determine if this paragraph has some real content. Things like new pages are not caught
454 // by Paragraph::empty(), even though they do not generate anything useful in DocBook.
455 // Thus, remove all spaces (including new lines: \r, \n) before checking for emptiness.
456 // std::all_of allows doing this check without having to copy the string.
457 // Open and close tags around each contained paragraph.
461 std::vector<docstring> pars_prepend;
462 std::vector<docstring> pars;
463 std::vector<docstring> pars_append;
464 tie(pars_prepend, pars, pars_append) = par->simpleDocBookOnePar(buf, runparams, text.outerFont(distance(begin, par)), 0, nextpar == end, special_case);
466 for (docstring const & parXML : pars_prepend)
467 xs << XMLStream::ESCAPE_NONE << parXML;
468 for (docstring const & parXML : pars) {
469 if (!xml::isNotOnlySpace(parXML))
473 openParTag(xs, &*par, prevpar, runparams);
475 xs << XMLStream::ESCAPE_NONE << parXML;
478 closeParTag(xs, &*par, (nextpar != end) ? &*nextpar : nullptr, runparams);
480 for (docstring const & parXML : pars_append)
481 xs << XMLStream::ESCAPE_NONE << parXML;
485 void makeEnvironment(Text const &text,
488 OutputParams const &runparams,
489 ParagraphList::const_iterator const & par)
492 auto const end = text.paragraphs().end();
496 // Special cases for listing-like environments provided in layouts. This is quite ad-hoc, but provides a useful
497 // default. This should not be used by too many environments (only LyX-Code right now).
498 // This would be much simpler if LyX-Code was implemented as InsetListings...
499 bool mimicListing = false;
500 bool ignoreFonts = false;
501 if (par->layout().docbooktag() == "programlisting") {
506 // Output the opening tag for this environment, but only if it has not been previously opened (condition
507 // implemented in openParTag).
508 auto prevpar = text.paragraphs().getParagraphBefore(par);
509 openParTag(xs, &*par, prevpar, runparams);
511 // Generate the contents of this environment. There is a special case if this is like some environment.
512 Layout const & style = par->layout();
513 if (style.latextype == LATEX_COMMAND) {
514 // Nothing to do (otherwise, infinite loops).
515 } else if (style.latextype == LATEX_ENVIRONMENT) {
516 // Generate the paragraph, if need be.
517 std::vector<docstring> pars_prepend;
518 std::vector<docstring> pars;
519 std::vector<docstring> pars_append;
520 tie(pars_prepend, pars, pars_append) =
521 par->simpleDocBookOnePar(buf, runparams, text.outerFont(std::distance(text.paragraphs().begin(), par)),
522 0, false, ignoreFonts);
524 for (docstring const & parXML : pars_prepend)
525 xs << XMLStream::ESCAPE_NONE << parXML;
527 auto p = pars.begin();
528 while (p != pars.end()) {
529 xml::openTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnerattr(),
530 par->layout().docbookiteminnertagtype());
531 xs << XMLStream::ESCAPE_NONE << *p;
532 xml::closeTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnertagtype());
535 // Insert a new line after each "paragraph" (i.e. line in the listing), except for the last one.
536 // Otherwise, there would one more new line in the output than in the LyX document.
541 for (auto const & p : pars) {
542 xml::openTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnerattr(),
543 par->layout().docbookiteminnertagtype());
544 xs << XMLStream::ESCAPE_NONE << p;
545 xml::closeTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnertagtype());
548 for (docstring const & parXML : pars_append)
549 xs << XMLStream::ESCAPE_NONE << parXML;
551 makeAny(text, buf, xs, runparams, par);
554 // Close the environment.
555 closeParTag(xs, &*par, (nextpar != end) ? &*nextpar : nullptr, runparams);
559 ParagraphList::const_iterator findEndOfEnvironment(
560 ParagraphList::const_iterator const & pstart,
561 ParagraphList::const_iterator const & pend)
563 // Copy-paste from XHTML. Should be factored out at some point...
564 ParagraphList::const_iterator p = pstart;
565 Layout const & bstyle = p->layout();
566 size_t const depth = p->params().depth();
567 for (++p; p != pend; ++p) {
568 Layout const & style = p->layout();
569 // It shouldn't happen that e.g. a section command occurs inside
570 // a quotation environment, at a higher depth, but as of 6/2009,
571 // it can happen. We pretend that it's just at lowest depth.
572 if (style.latextype == LATEX_COMMAND)
575 // If depth is down, we're done
576 if (p->params().depth() < depth)
579 // If depth is up, we're not done
580 if (p->params().depth() > depth)
583 // FIXME I am not sure about the first check.
584 // Surely we *could* have different layouts that count as
585 // LATEX_PARAGRAPH, right?
586 if (style.latextype == LATEX_PARAGRAPH || style != bstyle)
593 ParagraphList::const_iterator makeListEnvironment(Text const &text,
596 OutputParams const &runparams,
597 ParagraphList::const_iterator const & begin)
601 auto const end = text.paragraphs().end();
602 auto const envend = findEndOfEnvironment(par, end);
604 // Output the opening tag for this environment.
605 Layout const & envstyle = par->layout();
606 xml::openTag(xs, envstyle.docbookwrappertag(), envstyle.docbookwrapperattr(), envstyle.docbookwrappertagtype());
607 xml::openTag(xs, envstyle.docbooktag(), envstyle.docbookattr(), envstyle.docbooktagtype());
609 // Handle the content of the list environment, item by item.
610 while (par != envend) {
611 // Skip this paragraph if it is both empty and the last one (otherwise, there may be deeper paragraphs after).
614 if (par->empty() && nextpar == envend)
617 // Open the item wrapper.
618 Layout const & style = par->layout();
619 xml::openTag(xs, style.docbookitemwrappertag(), style.docbookitemwrapperattr(),
620 style.docbookitemwrappertagtype());
622 // Generate the label, if need be. If it is taken from the text, sep != 0 and corresponds to the first
623 // character after the label.
625 if (style.labeltype != LABEL_NO_LABEL && style.docbookitemlabeltag() != "NONE") {
626 if (style.labeltype == LABEL_MANUAL) {
627 // Only variablelist gets here (or similar items defined as an extension in the layout).
628 xml::openTag(xs, style.docbookitemlabeltag(), style.docbookitemlabelattr(),
629 style.docbookitemlabeltagtype());
630 sep = 1 + par->firstWordDocBook(xs, runparams);
631 xml::closeTag(xs, style.docbookitemlabeltag(), style.docbookitemlabeltagtype());
633 // Usual cases: maybe there is something specified at the layout level. Highly unlikely, though.
634 docstring const lbl = par->params().labelString();
637 xml::openTag(xs, style.docbookitemlabeltag(), style.docbookitemlabelattr(),
638 style.docbookitemlabeltagtype());
640 xml::closeTag(xs, style.docbookitemlabeltag(), style.docbookitemlabeltagtype());
645 // Open the item (after the wrapper and the label).
646 xml::openTag(xs, style.docbookitemtag(), style.docbookitemattr(), style.docbookitemtagtype());
648 // Generate the content of the item.
649 if (sep < par->size()) {
650 std::vector<docstring> pars_prepend;
651 std::vector<docstring> pars;
652 std::vector<docstring> pars_append;
653 tie(pars_prepend, pars, pars_append) = par->simpleDocBookOnePar(buf, runparams,
654 text.outerFont(std::distance(text.paragraphs().begin(), par)), sep);
655 for (docstring const & parXML : pars_prepend)
656 xs << XMLStream::ESCAPE_NONE << parXML;
657 for (auto &p : pars) {
658 xml::openTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnerattr(),
659 par->layout().docbookiteminnertagtype());
660 xs << XMLStream::ESCAPE_NONE << p;
661 xml::closeTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnertagtype());
663 for (docstring const & parXML : pars_append)
664 xs << XMLStream::ESCAPE_NONE << parXML;
666 // DocBook doesn't like emptiness.
667 xml::compTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnerattr(),
668 par->layout().docbookiteminnertagtype());
671 // If the next item is deeper, it must go entirely within this item (do it recursively).
672 // By construction, with findEndOfEnvironment, depth can only stay constant or increase, never decrease.
673 depth_type currentDepth = par->getDepth();
675 while (par != envend && par->getDepth() != currentDepth)
676 par = makeAny(text, buf, xs, runparams, par);
677 // Usually, this loop only makes one iteration, except in complex scenarios, like an item with a paragraph,
678 // a list, and another paragraph; or an item with two types of list (itemise then enumerate, for instance).
681 xml::closeTag(xs, style.docbookitemtag(), style.docbookitemtagtype());
682 xml::closeTag(xs, style.docbookitemwrappertag(), style.docbookitemwrappertagtype());
685 // Close this environment in exactly the same way as it was opened.
686 xml::closeTag(xs, envstyle.docbooktag(), envstyle.docbooktagtype());
687 xml::closeTag(xs, envstyle.docbookwrappertag(), envstyle.docbookwrappertagtype());
697 OutputParams const & runparams,
698 ParagraphList::const_iterator const & par)
701 // Unlike XHTML, no need for labels, as they are handled by DocBook tags.
702 auto const begin = text.paragraphs().begin();
703 auto const end = text.paragraphs().end();
707 // Generate this command.
708 auto prevpar = text.paragraphs().getParagraphBefore(par);
710 std::vector<docstring> pars_prepend;
711 std::vector<docstring> pars;
712 std::vector<docstring> pars_append;
713 tie(pars_prepend, pars, pars_append) = par->simpleDocBookOnePar(buf, runparams,text.outerFont(distance(begin, par)));
715 for (docstring const & parXML : pars_prepend)
716 xs << XMLStream::ESCAPE_NONE << parXML;
718 openParTag(xs, &*par, prevpar, runparams);
719 for (auto & parXML : pars)
720 // TODO: decide what to do with openParTag/closeParTag in new lines.
721 xs << XMLStream::ESCAPE_NONE << parXML;
722 closeParTag(xs, &*par, (nextpar != end) ? &*nextpar : nullptr, runparams);
724 for (docstring const & parXML : pars_append)
725 xs << XMLStream::ESCAPE_NONE << parXML;
729 bool isLayoutSectioning(Layout const & lay)
731 if (lay.docbooksection()) // Special case: some DocBook styles must be handled as sections.
733 else if (lay.category() == from_utf8("Sectioning") || lay.docbooktag() == "section") // Generic case.
734 return lay.toclevel != Layout::NOT_IN_TOC;
739 bool isLayoutSectioningOrSimilar(Layout const & lay)
741 return isLayoutSectioning(lay) || lay.docbooktag() == "bridgehead";
745 using DocBookDocumentSectioning = tuple<bool, pit_type>;
748 struct DocBookInfoTag
750 const set<pit_type> shouldBeInInfo;
751 const set<pit_type> mustBeInInfo; // With the notable exception of the abstract!
752 const set<pit_type> abstract;
753 const bool abstractLayout;
757 DocBookInfoTag(const set<pit_type> & shouldBeInInfo, const set<pit_type> & mustBeInInfo,
758 const set<pit_type> & abstract, bool abstractLayout, pit_type bpit, pit_type epit) :
759 shouldBeInInfo(shouldBeInInfo), mustBeInInfo(mustBeInInfo), abstract(abstract),
760 abstractLayout(abstractLayout), bpit(bpit), epit(epit) {}
764 DocBookDocumentSectioning hasDocumentSectioning(ParagraphList const ¶graphs, pit_type bpit, pit_type const epit) {
765 bool documentHasSections = false;
767 while (bpit < epit) {
768 LASSERT(static_cast<size_t>(bpit) < paragraphs.size(), return make_tuple(documentHasSections, bpit));
770 Layout const &style = paragraphs[bpit].layout();
771 documentHasSections |= isLayoutSectioningOrSimilar(style);
773 if (documentHasSections)
777 // Paragraphs before the first section: [ runparams.par_begin ; eppit )
779 return make_tuple(documentHasSections, bpit);
783 bool hasOnlyNotes(Paragraph const & par)
785 // Precondition: the paragraph is not empty. Otherwise, the function will always return true...
786 for (int i = 0; i < par.size(); ++i)
787 // If you find something that is not an inset (like actual text) or an inset that is not a note,
789 if (!par.isInset(i) || par.getInset(i)->lyxCode() != NOTE_CODE)
792 // An empty paragraph may still require some output.
793 if (par.layout().docbooksection())
796 // There should be really no content here.
801 DocBookInfoTag getParagraphsWithInfo(ParagraphList const ¶graphs,
802 pit_type bpit, pit_type const epit,
803 // Typically, bpit is the beginning of the document and epit the end of the
804 // document *or* the first section.
805 bool documentHasSections,
806 bool detectUnlayoutedAbstract
807 // Whether paragraphs with no specific layout should be detected as abstracts.
808 // For inner sections, an abstract should only be detected if it has a specific
809 // layout. For others, anything that might look like an abstract should be sought.
811 set<pit_type> shouldBeInInfo;
812 set<pit_type> mustBeInInfo;
813 set<pit_type> abstractWithLayout;
814 set<pit_type> abstractNoLayout;
816 // Find the first nonempty paragraph by mutating bpit.
817 while (bpit < epit) {
818 Paragraph const &par = paragraphs[bpit];
819 if (par.empty() || hasOnlyNotes(par))
825 // Traverse everything that might belong to <info>.
826 bool hasAbstractLayout = false;
827 static depth_type INVALID_DEPTH = 100000;
828 depth_type abstractDepth = INVALID_DEPTH;
829 pit_type cpit = bpit;
830 for (; cpit < epit; ++cpit) {
831 // Skip paragraphs that don't generate anything in DocBook.
832 Paragraph const & par = paragraphs[cpit];
833 Layout const &style = par.layout();
834 if (hasOnlyNotes(par))
837 // There should never be any section here, except for the first paragraph (a title can be part of <info>).
838 // (Just a sanity check: if this fails, this function could end up processing the whole document.)
839 if (cpit != bpit && isLayoutSectioningOrSimilar(par.layout())) {
840 LYXERR(Debug::OUTFILE, "Assertion failed: section found in potential <info> paragraphs.");
844 // If this is marked as an abstract by the layout, put it in the right set.
845 if (style.docbookabstract()) {
846 hasAbstractLayout = true;
847 abstractDepth = par.getDepth();
848 abstractWithLayout.emplace(cpit);
852 // Deeper paragraphs following the abstract must still be considered as part of the abstract.
853 // For instance, this includes lists. There should not be any other kind of paragraph in between.
854 if (abstractDepth != INVALID_DEPTH && style.docbookininfo() == "never") {
855 if (par.getDepth() > abstractDepth) {
856 abstractWithLayout.emplace(cpit);
859 if (par.getDepth() == abstractDepth) {
860 // This is not an abstract paragraph and it should not either be considered as part
861 // of it. It breaks the rule that abstract paragraphs must follow each other.
862 abstractDepth = INVALID_DEPTH;
867 // Based on layout information, store this paragraph in one set: should be in <info>, must be,
868 // or abstract (either because of layout or of position).
869 if (style.docbookininfo() == "always")
870 mustBeInInfo.emplace(cpit);
871 else if (style.docbookininfo() == "maybe")
872 shouldBeInInfo.emplace(cpit);
873 else if (documentHasSections && !hasAbstractLayout && detectUnlayoutedAbstract &&
874 (style.docbooktag() == "NONE" || style.docbooktag() == "para") &&
875 style.docbookwrappertag() == "NONE")
876 // In this case, it is very likely that style.docbookininfo() == "never"! Be extra careful
877 // about anything that gets caught here. For instance, don't ake into account
878 abstractNoLayout.emplace(cpit);
879 else // This should definitely not be in <info>.
882 // Now, cpit points to the first paragraph that no more has things that could go in <info>.
883 // bpit is the beginning of the <info> part.
885 return DocBookInfoTag(shouldBeInInfo, mustBeInInfo,
886 hasAbstractLayout ? abstractWithLayout : abstractNoLayout,
887 hasAbstractLayout, bpit, cpit);
890 } // end anonymous namespace
893 std::set<const Inset *> gatherInfo(ParagraphList::const_iterator par)
895 // This function has a structure highly similar to makeAny and its friends. It's only made to be called on what
896 // should become the document's <abstract>.
897 std::set<const Inset *> values;
899 // If this kind of layout should be ignored, already leave.
900 if (par->layout().docbooktag() == "IGNORE")
903 // If this should go in info, mark it as such. Dive deep into the abstract, as it may hide many things that
904 // DocBook doesn't want to be inside the abstract.
905 for (pos_type i = 0; i < par->size(); ++i) {
906 if (par->getInset(i) && par->getInset(i)->asInsetText()) {
907 InsetText const *inset = par->getInset(i)->asInsetText();
909 if (inset->getLayout().docbookininfo() != "never") {
910 values.insert(inset);
912 auto subpar = inset->paragraphs().begin();
913 while (subpar != inset->paragraphs().end()) {
914 auto subinfos = gatherInfo(subpar);
915 for (auto & subinfo: subinfos)
916 values.insert(subinfo);
927 ParagraphList::const_iterator makeAny(Text const &text,
930 OutputParams const &runparams,
931 ParagraphList::const_iterator par)
933 bool ignoreParagraph = false;
935 // If this kind of layout should be ignored, already leave.
936 ignoreParagraph |= par->layout().docbooktag() == "IGNORE";
938 // For things that should go into <info>, check the variable rp.docbook_generate_info. This does not apply to the
940 bool isAbstract = par->layout().docbookabstract() || par->layout().docbooktag() == "abstract";
941 ignoreParagraph |= !isAbstract && par->layout().docbookininfo() != "never" && !runparams.docbook_generate_info;
943 // Switch on the type of paragraph to call the right handler.
944 if (!ignoreParagraph) {
945 switch (par->layout().latextype) {
947 makeCommand(text, buf, xs, runparams, par);
949 case LATEX_ENVIRONMENT:
950 makeEnvironment(text, buf, xs, runparams, par);
952 case LATEX_LIST_ENVIRONMENT:
953 case LATEX_ITEM_ENVIRONMENT:
954 // Only case when makeAny() might consume more than one paragraph.
955 return makeListEnvironment(text, buf, xs, runparams, par);
956 case LATEX_PARAGRAPH:
957 makeParagraph(text, buf, xs, runparams, par);
959 case LATEX_BIB_ENVIRONMENT:
960 makeBibliography(text, buf, xs, runparams, par);
965 // For cases that are not lists, the next paragraph to handle is the next one.
971 xml::FontTag docbookStartFontTag(xml::FontTypes type)
973 return xml::FontTag(from_utf8(fontToDocBookTag(type)), from_utf8(fontToAttribute(type)), type);
977 xml::EndFontTag docbookEndFontTag(xml::FontTypes type)
979 return xml::EndFontTag(from_utf8(fontToDocBookTag(type)), type);
983 void outputDocBookInfo(
987 OutputParams const & runparams,
988 ParagraphList const & paragraphs,
989 DocBookInfoTag const & info)
991 // Perform an additional check on the abstract. Sometimes, there are many paragraphs that should go
992 // into the abstract, but none generates actual content. Thus, first generate to a temporary stream,
993 // then only create the <abstract> tag if these paragraphs generate some content.
994 // This check must be performed *before* a decision on whether or not to output <info> is made.
995 bool hasAbstract = !info.abstract.empty();
997 set<const Inset *> infoInsets; // Paragraphs that should go into <info>, but are hidden in an <abstract>
998 // paragraph. (This happens for quite a few layouts, unfortunately.)
1001 // Generate the abstract XML into a string before further checks.
1002 // Usually, makeAny only generates one paragraph at a time. However, for the specific case of lists, it might
1003 // generate more than one paragraph, as indicated in the return value.
1004 odocstringstream os2;
1007 auto rp = runparams;
1008 rp.docbook_generate_info = false;
1009 rp.docbook_ignore_wrapper = true;
1011 set<pit_type> doneParas; // Paragraphs that have already been converted (mostly to deal with lists).
1012 for (auto const & p : info.abstract) {
1013 if (doneParas.find(p) == doneParas.end()) {
1014 auto oldPar = paragraphs.iterator_at(p);
1015 auto newPar = makeAny(text, buf, xs2, rp, oldPar);
1017 // Find insets that should go outside the abstract.
1018 auto subinfos = gatherInfo(oldPar);
1019 for (auto & subinfo: subinfos)
1020 infoInsets.insert(subinfo);
1022 // Insert the indices of all the paragraphs that were just generated (typically, one).
1023 // **Make the hypothesis that, when an abstract has a list, all its items are consecutive.**
1024 // Otherwise, makeAny and makeListEnvironment would have to be adapted too.
1026 while (oldPar != newPar) {
1027 doneParas.emplace(id);
1034 // Actually output the abstract if there is something to do. Don't count line feeds, spaces, or comments
1035 // in this -- even though line feeds and spaces must be properly output if there is some abstract.
1036 abstract = os2.str();
1037 docstring cleaned = abstract;
1038 cleaned.erase(std::remove_if(cleaned.begin(), cleaned.end(), lyx::isSpace), cleaned.end());
1040 size_t beginComment;
1042 while ((beginComment = cleaned.find(from_ascii("<!--"))) != lyx::docstring::npos) {
1043 if ((endComment = cleaned.find(from_ascii("-->"), beginComment)) != lyx::docstring::npos) {
1044 cleaned.erase(cleaned.begin() + beginComment, cleaned.begin() + endComment + 3);
1048 // Nothing? Then there is no abstract!
1049 if (cleaned.empty())
1050 hasAbstract = false;
1053 // The abstract must go in <info>. Otherwise, decide whether to open <info> based on the layouts.
1054 bool needInfo = !info.mustBeInInfo.empty() || hasAbstract;
1056 // Start the <info> tag if required.
1058 xs.startDivision(false);
1059 xs << xml::StartTag("info");
1063 // Output the elements that should go in <info>.
1064 // - First, the title.
1065 for (auto pit : info.shouldBeInInfo) // Typically, the title: these elements are so important and ubiquitous
1066 // that mandating a wrapper like <info> would repel users. Thus, generate them first.
1067 makeAny(text, buf, xs, runparams, paragraphs.iterator_at(pit));
1068 // If there is no title, generate one (required for the document to be valid).
1069 // This code is called for the main document, for table cells, etc., so be precise in this condition.
1070 if (text.isMainText() && info.shouldBeInInfo.empty() && !runparams.inInclude) {
1071 xs << xml::StartTag("title");
1072 xs << "Untitled Document";
1073 xs << xml::EndTag("title");
1077 // - Then, other metadata.
1078 for (auto pit : info.mustBeInInfo)
1079 makeAny(text, buf, xs, runparams, paragraphs.iterator_at(pit));
1080 for (auto const * inset : infoInsets)
1081 inset->docbook(xs, runparams);
1083 // - Finally, always output the abstract as the last item of the <info>, as it requires special treatment
1084 // (especially if it contains several paragraphs that are empty).
1086 string tag = paragraphs[*info.abstract.begin()].layout().docbookforceabstracttag();
1090 if (!xs.isLastTagCR())
1093 xs << xml::StartTag(tag);
1095 xs << XMLStream::ESCAPE_NONE << abstract;
1096 xs << xml::EndTag(tag);
1100 // End the <info> tag if it was started.
1102 if (!xs.isLastTagCR())
1105 xs << xml::EndTag("info");
1112 void docbookSimpleAllParagraphs(
1116 OutputParams const & runparams)
1118 // Handle the given text, supposing it has no sections (i.e. a "simple" text). The input may vary in length
1119 // between a single paragraph to a whole document.
1120 pit_type const bpit = runparams.par_begin;
1121 pit_type const epit = runparams.par_end;
1122 ParagraphList const ¶graphs = text.paragraphs();
1124 // First, the <info> tag.
1125 DocBookInfoTag info = getParagraphsWithInfo(paragraphs, bpit, epit, false, true);
1126 outputDocBookInfo(text, buf, xs, runparams, paragraphs, info);
1128 // Then, the content. It starts where the <info> ends.
1129 auto par = paragraphs.iterator_at(info.epit);
1130 auto par_epit = paragraphs.iterator_at(epit);
1131 auto par_end = paragraphs.end();
1132 while (par != par_epit && par != par_end) {
1133 if (!hasOnlyNotes(*par))
1134 par = makeAny(text, buf, xs, runparams, par);
1141 void docbookParagraphs(Text const &text,
1144 OutputParams const &runparams) {
1145 ParagraphList const ¶graphs = text.paragraphs();
1146 if (runparams.par_begin == runparams.par_end) {
1147 runparams.par_begin = 0;
1148 runparams.par_end = paragraphs.size();
1150 pit_type bpit = runparams.par_begin;
1151 pit_type const epit = runparams.par_end;
1152 LASSERT(bpit < epit,
1154 xs << XMLStream::ESCAPE_NONE << "<!-- DocBook output error! -->\n";
1158 // Detect whether the document contains sections. If there are no sections, treatment is largely simplified.
1159 // In particular, there can't be an abstract, unless it is manually marked.
1160 bool documentHasSections;
1162 tie(documentHasSections, eppit) = hasDocumentSectioning(paragraphs, bpit, epit);
1164 // Deal with "simple" documents, i.e. those without sections.
1165 if (!documentHasSections) {
1166 docbookSimpleAllParagraphs(text, buf, xs, runparams);
1170 // Output the first <info> tag (or just the title).
1171 DocBookInfoTag info = getParagraphsWithInfo(paragraphs, bpit, eppit, true, true);
1172 outputDocBookInfo(text, buf, xs, runparams, paragraphs, info);
1175 // In the specific case of books, there must be parts or chapters. In some cases, star sections are used at the
1176 // beginning for many things like acknowledgements or licenses. DocBook has tags for many of these cases, but not
1177 // the LyX layouts... Gather everything in a <preface>, that's the closest in meaning.
1178 // This is only useful if the things after the <info> tag are not already parts or chapters!
1179 if (buf.params().documentClass().docbookroot() == "book") {
1180 // Check the condition on the first few elements.
1181 bool hasPreface = false;
1182 pit_type pref_bpit = bpit;
1183 pit_type pref_epit = bpit;
1185 static const std::set<std::string> allowedElements = {
1186 // List from https://tdg.docbook.org/tdg/5.2/book.html
1187 "acknowledgements", "appendix", "article", "bibliography", "chapter", "colophon", "dedication",
1188 "glossary", "index", "part", "preface", "reference", "toc"
1191 for (; pref_epit < epit; ++pref_epit) {
1192 auto par = text.paragraphs().iterator_at(pref_epit);
1193 if (allowedElements.find(par->layout().docbooktag()) != allowedElements.end() ||
1194 allowedElements.find(par->layout().docbooksectiontag()) != allowedElements.end())
1200 // Output a preface if required. A title is needed for the document to be valid...
1202 xs << xml::StartTag("preface");
1205 xs << xml::StartTag("title");
1207 xs << xml::EndTag("title");
1210 auto pref_par = text.paragraphs().iterator_at(pref_bpit);
1211 auto pref_end = text.paragraphs().iterator_at(pref_epit);
1212 while (pref_par != pref_end) {
1213 // Skip paragraphs not producing any output.
1214 if (hasOnlyNotes(*pref_par)) {
1219 // TODO: must sections be handled here? If so, it might be useful to extract the corresponding loop
1220 // in the rest of this function to use the same here (and avoid copy-paste mistakes).
1221 pref_par = makeAny(text, buf, xs, runparams, pref_par);
1224 xs << xml::EndTag("preface");
1227 // Skip what has just been generated in the preface.
1232 std::stack<std::pair<int, string>> headerLevels; // Used to determine when to open/close sections: store the depth
1233 // of the section and the tag that was used to open it.
1235 // Then, iterate through the paragraphs of this document.
1236 auto par = text.paragraphs().iterator_at(bpit);
1237 auto end = text.paragraphs().iterator_at(epit);
1238 while (par != end) {
1239 // Skip paragraphs not producing any output.
1240 if (hasOnlyNotes(*par)) {
1245 OutputParams ourparams = runparams;
1246 Layout const &style = par->layout();
1248 // Think about adding <section> and/or </section>s.
1249 if (isLayoutSectioning(style) || par->params().startOfAppendix()) {
1250 int level = style.toclevel;
1252 // Need to close a previous section if it has the same level or a higher one (close <section> if opening a
1253 // <h2> after a <h2>, <h3>, <h4>, <h5> or <h6>). More examples:
1254 // - current: h2; back: h1; do not close any <section>
1255 // - current: h1; back: h2; close two <section> (first the <h2>, then the <h1>, so a new <h1> can come)
1256 // Some layouts require that Layout::NOT_IN_TOC sections still cause closing of previous sections. This is
1257 // mostly to ensure that the section is positioned at a DocBook-compatible level (acknowledgements: cannot
1258 // be under a section!).
1259 while (!headerLevels.empty() && level <= headerLevels.top().first) {
1260 // Output the tag only if it corresponds to a legit section.
1261 int stackLevel = headerLevels.top().first;
1262 if (stackLevel != Layout::NOT_IN_TOC) {
1263 xs << xml::EndTag(headerLevels.top().second);
1269 // Open the new section: first push it onto the stack, then output it in DocBook.
1270 string sectionTag = (par->params().startOfAppendix()) ? "appendix" : style.docbooksectiontag();
1271 headerLevels.push(std::make_pair(level, sectionTag));
1273 // Some sectioning-like elements should not be output (such as FrontMatter).
1274 if (level != Layout::NOT_IN_TOC) {
1275 // Look for a label in the title, i.e. a InsetLabel as a child.
1276 docstring id = docstring();
1277 for (pos_type i = 0; i < par->size(); ++i) {
1278 Inset const *inset = par->getInset(i);
1280 if (auto label = dynamic_cast<InsetLabel const *>(inset)) {
1281 // Generate the attributes for the section if need be.
1282 id += "xml:id=\"" + xml::cleanID(label->screenLabel()) + "\"";
1284 // Don't output the ID as a DocBook <anchor>.
1285 ourparams.docbook_anchors_to_ignore.emplace(label->screenLabel());
1287 // Cannot have multiple IDs per tag. If there is another ID inset in the document, it will
1288 // be output as a DocBook anchor.
1294 // Write the open tag for this section.
1298 xs << xml::StartTag(sectionTag, attrs);
1303 // Close all sections before the bibliography.
1304 // TODO: Only close all when the bibliography is at the end of the document? Or force to output the bibliography
1305 // at the end of the document? Or don't care (as allowed by DocBook)?
1306 if (!par->insetList().empty()) {
1307 Inset const *firstInset = par->getInset(0);
1308 if (firstInset && (firstInset->lyxCode() == BIBITEM_CODE || firstInset->lyxCode() == BIBTEX_CODE)) {
1309 while (!headerLevels.empty()) {
1310 // Don't close appendices before bibliographies.
1311 if (headerLevels.top().second == "appendix")
1314 // Pop the section from the stack.
1315 int level = headerLevels.top().first;
1316 docstring tag = from_utf8("</" + headerLevels.top().second + ">");
1319 // Output the tag only if it corresponds to a legit section, as the rest of the code.
1320 if (level != Layout::NOT_IN_TOC) {
1321 xs << XMLStream::ESCAPE_NONE << tag;
1328 // Generate the <info> tag if a section was just opened.
1329 // Some sections may require abstracts (mostly parts, in books: DocBookForceAbstractTag will not be NONE),
1330 // others can still have an abstract (it must be detected so that it can be output at the right place).
1331 // TODO: docbookforceabstracttag is a bit contrived here, but it does the job. Having another field just for
1332 // this would be cleaner, but that's just for <part> and <partintro>, so it's probably not worth the effort.
1333 if (isLayoutSectioning(style)) {
1334 // This abstract may be found between the next paragraph and the next title.
1335 pit_type cpit = std::distance(text.paragraphs().begin(), par);
1336 pit_type ppit = std::get<1>(hasDocumentSectioning(paragraphs, cpit + 1L, epit));
1338 // Generate this abstract (this code corresponds to parts of outputDocBookInfo).
1339 DocBookInfoTag secInfo = getParagraphsWithInfo(paragraphs, cpit, ppit, true,
1340 style.docbookforceabstracttag() != "NONE");
1342 if (!secInfo.mustBeInInfo.empty() || !secInfo.shouldBeInInfo.empty() || !secInfo.abstract.empty()) {
1343 // Generate the <info>, if required. If DocBookForceAbstractTag != NONE, this abstract will not be in
1344 // <info>, unlike other ("standard") abstracts.
1345 bool hasStandardAbstract = !secInfo.abstract.empty() && style.docbookforceabstracttag() == "NONE";
1346 bool needInfo = !secInfo.mustBeInInfo.empty() || hasStandardAbstract;
1349 xs.startDivision(false);
1350 xs << xml::StartTag("info");
1354 // Output the elements that should go in <info>, before and after the abstract.
1355 for (auto pit : secInfo.shouldBeInInfo) // Typically, the title: these elements are so important and ubiquitous
1356 // that mandating a wrapper like <info> would repel users. Thus, generate them first.
1357 makeAny(text, buf, xs, ourparams, paragraphs.iterator_at(pit));
1358 for (auto pit : secInfo.mustBeInInfo)
1359 makeAny(text, buf, xs, ourparams, paragraphs.iterator_at(pit));
1361 // Deal with the abstract in <info> if it is standard (i.e. its tag is <abstract>).
1362 if (!secInfo.abstract.empty() && hasStandardAbstract) {
1363 if (!secInfo.abstractLayout) {
1364 xs << xml::StartTag("abstract");
1368 for (auto const &p : secInfo.abstract)
1369 makeAny(text, buf, xs, ourparams, paragraphs.iterator_at(p));
1371 if (!secInfo.abstractLayout) {
1372 xs << xml::EndTag("abstract");
1377 // End the <info> tag if it was started.
1379 if (!xs.isLastTagCR())
1382 xs << xml::EndTag("info");
1387 // Deal with the abstract outside <info> if it is not standard (i.e. its tag is layout-defined).
1388 if (!secInfo.abstract.empty() && !hasStandardAbstract) {
1389 // Assert: style.docbookforceabstracttag() != NONE.
1390 xs << xml::StartTag(style.docbookforceabstracttag());
1392 for (auto const &p : secInfo.abstract)
1393 makeAny(text, buf, xs, ourparams, paragraphs.iterator_at(p));
1394 xs << xml::EndTag(style.docbookforceabstracttag());
1398 // Skip all the text that has just been generated.
1399 par = paragraphs.iterator_at(secInfo.epit);
1401 // No <info> tag to generate, proceed as for normal paragraphs.
1402 par = makeAny(text, buf, xs, ourparams, par);
1405 // Generate this paragraph, as it has nothing special.
1406 par = makeAny(text, buf, xs, ourparams, par);
1410 // If need be, close <section>s, but only at the end of the document (otherwise, dealt with at the beginning
1412 while (!headerLevels.empty() && headerLevels.top().first > Layout::NOT_IN_TOC) {
1413 docstring tag = from_utf8("</" + headerLevels.top().second + ">");
1415 xs << XMLStream::ESCAPE_NONE << tag;