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 makeParagraphBibliography).
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 end = paragraphs.iterator_at(epit);
1131 while (par != end) {
1132 if (!hasOnlyNotes(*par))
1133 par = makeAny(text, buf, xs, runparams, par);
1140 void docbookParagraphs(Text const &text,
1143 OutputParams const &runparams) {
1144 ParagraphList const ¶graphs = text.paragraphs();
1145 if (runparams.par_begin == runparams.par_end) {
1146 runparams.par_begin = 0;
1147 runparams.par_end = paragraphs.size();
1149 pit_type bpit = runparams.par_begin;
1150 pit_type const epit = runparams.par_end;
1151 LASSERT(bpit < epit,
1153 xs << XMLStream::ESCAPE_NONE << "<!-- DocBook output error! -->\n";
1157 // Detect whether the document contains sections. If there are no sections, treatment is largely simplified.
1158 // In particular, there can't be an abstract, unless it is manually marked.
1159 bool documentHasSections;
1161 tie(documentHasSections, eppit) = hasDocumentSectioning(paragraphs, bpit, epit);
1163 // Deal with "simple" documents, i.e. those without sections.
1164 if (!documentHasSections) {
1165 docbookSimpleAllParagraphs(text, buf, xs, runparams);
1169 // Output the first <info> tag (or just the title).
1170 DocBookInfoTag info = getParagraphsWithInfo(paragraphs, bpit, eppit, true, true);
1171 outputDocBookInfo(text, buf, xs, runparams, paragraphs, info);
1174 // In the specific case of books, there must be parts or chapters. In some cases, star sections are used at the
1175 // beginning for many things like acknowledgements or licenses. DocBook has tags for many of these cases, but not
1176 // the LyX layouts... Gather everything in a <preface>, that's the closest in meaning.
1177 // This is only useful if the things after the <info> tag are not already parts or chapters!
1178 if (buf.params().documentClass().docbookroot() == "book") {
1179 // Check the condition on the first few elements.
1180 bool hasPreface = false;
1181 pit_type pref_bpit = bpit;
1182 pit_type pref_epit = bpit;
1184 static const std::set<std::string> allowedElements = {
1185 // List from https://tdg.docbook.org/tdg/5.2/book.html
1186 "acknowledgements", "appendix", "article", "bibliography", "chapter", "colophon", "dedication",
1187 "glossary", "index", "part", "preface", "reference", "toc"
1190 for (; pref_epit < epit; ++pref_epit) {
1191 auto par = text.paragraphs().iterator_at(pref_epit);
1192 if (allowedElements.find(par->layout().docbooktag()) != allowedElements.end() ||
1193 allowedElements.find(par->layout().docbooksectiontag()) != allowedElements.end())
1199 // Output a preface if required. A title is needed for the document to be valid...
1201 xs << xml::StartTag("preface");
1204 xs << xml::StartTag("title");
1206 xs << xml::EndTag("title");
1209 auto pref_par = text.paragraphs().iterator_at(pref_bpit);
1210 auto pref_end = text.paragraphs().iterator_at(pref_epit);
1211 while (pref_par != pref_end) {
1212 // Skip paragraphs not producing any output.
1213 if (hasOnlyNotes(*pref_par)) {
1218 // TODO: must sections be handled here? If so, it might be useful to extract the corresponding loop
1219 // in the rest of this function to use the same here (and avoid copy-paste mistakes).
1220 pref_par = makeAny(text, buf, xs, runparams, pref_par);
1223 xs << xml::EndTag("preface");
1226 // Skip what has just been generated in the preface.
1231 std::stack<std::pair<int, string>> headerLevels; // Used to determine when to open/close sections: store the depth
1232 // of the section and the tag that was used to open it.
1234 // Then, iterate through the paragraphs of this document.
1235 auto par = text.paragraphs().iterator_at(bpit);
1236 auto end = text.paragraphs().iterator_at(epit);
1237 while (par != end) {
1238 // Skip paragraphs not producing any output.
1239 if (hasOnlyNotes(*par)) {
1244 OutputParams ourparams = runparams;
1245 Layout const &style = par->layout();
1247 // Think about adding <section> and/or </section>s.
1248 if (isLayoutSectioning(style) || par->params().startOfAppendix()) {
1249 int level = style.toclevel;
1251 // Need to close a previous section if it has the same level or a higher one (close <section> if opening a
1252 // <h2> after a <h2>, <h3>, <h4>, <h5> or <h6>). More examples:
1253 // - current: h2; back: h1; do not close any <section>
1254 // - current: h1; back: h2; close two <section> (first the <h2>, then the <h1>, so a new <h1> can come)
1255 // Some layouts require that Layout::NOT_IN_TOC sections still cause closing of previous sections. This is
1256 // mostly to ensure that the section is positioned at a DocBook-compatible level (acknowledgements: cannot
1257 // be under a section!).
1258 while (!headerLevels.empty() && level <= headerLevels.top().first) {
1259 // Output the tag only if it corresponds to a legit section.
1260 int stackLevel = headerLevels.top().first;
1261 if (stackLevel != Layout::NOT_IN_TOC) {
1262 xs << xml::EndTag(headerLevels.top().second);
1268 // Open the new section: first push it onto the stack, then output it in DocBook.
1269 string sectionTag = (par->params().startOfAppendix()) ? "appendix" : style.docbooksectiontag();
1270 headerLevels.push(std::make_pair(level, sectionTag));
1272 // Some sectioning-like elements should not be output (such as FrontMatter).
1273 if (level != Layout::NOT_IN_TOC) {
1274 // Look for a label in the title, i.e. a InsetLabel as a child.
1275 docstring id = docstring();
1276 for (pos_type i = 0; i < par->size(); ++i) {
1277 Inset const *inset = par->getInset(i);
1279 if (auto label = dynamic_cast<InsetLabel const *>(inset)) {
1280 // Generate the attributes for the section if need be.
1281 id += "xml:id=\"" + xml::cleanID(label->screenLabel()) + "\"";
1283 // Don't output the ID as a DocBook <anchor>.
1284 ourparams.docbook_anchors_to_ignore.emplace(label->screenLabel());
1286 // Cannot have multiple IDs per tag. If there is another ID inset in the document, it will
1287 // be output as a DocBook anchor.
1293 // Write the open tag for this section.
1297 xs << xml::StartTag(sectionTag, attrs);
1302 // Close all sections before the bibliography.
1303 // TODO: Only close all when the bibliography is at the end of the document? Or force to output the bibliography at the end of the document? Or don't care (as allowed by DocBook)?
1304 if (!par->insetList().empty()) {
1305 Inset const *firstInset = par->getInset(0);
1306 if (firstInset && (firstInset->lyxCode() == BIBITEM_CODE || firstInset->lyxCode() == BIBTEX_CODE)) {
1307 while (!headerLevels.empty()) {
1308 // Don't close appendices before bibliographies.
1309 if (headerLevels.top().second == "appendix")
1312 // Pop the section from the stack.
1313 int level = headerLevels.top().first;
1314 docstring tag = from_utf8("</" + headerLevels.top().second + ">");
1317 // Output the tag only if it corresponds to a legit section, as the rest of the code.
1318 if (level != Layout::NOT_IN_TOC) {
1319 xs << XMLStream::ESCAPE_NONE << tag;
1326 // Generate the <info> tag if a section was just opened.
1327 // Some sections may require abstracts (mostly parts, in books: DocBookForceAbstractTag will not be NONE),
1328 // others can still have an abstract (it must be detected so that it can be output at the right place).
1329 // 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.
1330 if (isLayoutSectioning(style)) {
1331 // This abstract may be found between the next paragraph and the next title.
1332 pit_type cpit = std::distance(text.paragraphs().begin(), par);
1333 pit_type ppit = std::get<1>(hasDocumentSectioning(paragraphs, cpit + 1L, epit));
1335 // Generate this abstract (this code corresponds to parts of outputDocBookInfo).
1336 DocBookInfoTag secInfo = getParagraphsWithInfo(paragraphs, cpit, ppit, true,
1337 style.docbookforceabstracttag() != "NONE");
1339 if (!secInfo.mustBeInInfo.empty() || !secInfo.shouldBeInInfo.empty() || !secInfo.abstract.empty()) {
1340 // Generate the <info>, if required. If DocBookForceAbstractTag != NONE, this abstract will not be in
1341 // <info>, unlike other ("standard") abstracts.
1342 bool hasStandardAbstract = !secInfo.abstract.empty() && style.docbookforceabstracttag() == "NONE";
1343 bool needInfo = !secInfo.mustBeInInfo.empty() || hasStandardAbstract;
1346 xs.startDivision(false);
1347 xs << xml::StartTag("info");
1351 // Output the elements that should go in <info>, before and after the abstract.
1352 for (auto pit : secInfo.shouldBeInInfo) // Typically, the title: these elements are so important and ubiquitous
1353 // that mandating a wrapper like <info> would repel users. Thus, generate them first.
1354 makeAny(text, buf, xs, ourparams, paragraphs.iterator_at(pit));
1355 for (auto pit : secInfo.mustBeInInfo)
1356 makeAny(text, buf, xs, ourparams, paragraphs.iterator_at(pit));
1358 // Deal with the abstract in <info> if it is standard (i.e. its tag is <abstract>).
1359 if (!secInfo.abstract.empty() && hasStandardAbstract) {
1360 if (!secInfo.abstractLayout) {
1361 xs << xml::StartTag("abstract");
1365 for (auto const &p : secInfo.abstract)
1366 makeAny(text, buf, xs, ourparams, paragraphs.iterator_at(p));
1368 if (!secInfo.abstractLayout) {
1369 xs << xml::EndTag("abstract");
1374 // End the <info> tag if it was started.
1376 if (!xs.isLastTagCR())
1379 xs << xml::EndTag("info");
1384 // Deal with the abstract outside <info> if it is not standard (i.e. its tag is layout-defined).
1385 if (!secInfo.abstract.empty() && !hasStandardAbstract) {
1386 // Assert: style.docbookforceabstracttag() != NONE.
1387 xs << xml::StartTag(style.docbookforceabstracttag());
1389 for (auto const &p : secInfo.abstract)
1390 makeAny(text, buf, xs, ourparams, paragraphs.iterator_at(p));
1391 xs << xml::EndTag(style.docbookforceabstracttag());
1395 // Skip all the text that has just been generated.
1396 par = paragraphs.iterator_at(secInfo.epit);
1398 // No <info> tag to generate, proceed as for normal paragraphs.
1399 par = makeAny(text, buf, xs, ourparams, par);
1402 // Generate this paragraph, as it has nothing special.
1403 par = makeAny(text, buf, xs, ourparams, par);
1407 // If need be, close <section>s, but only at the end of the document (otherwise, dealt with at the beginning
1409 while (!headerLevels.empty() && headerLevels.top().first > Layout::NOT_IN_TOC) {
1410 docstring tag = from_utf8("</" + headerLevels.top().second + ">");
1412 xs << XMLStream::ESCAPE_NONE << tag;