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 const string & tag = lay.docbooktag();
208 auto xmltag = xml::ParTag(tag, lay.docbookattr());
209 if (!xs.isTagOpen(xmltag, 1)) { // Don't nest a paragraph directly in a paragraph.
210 // TODO: required or not?
211 // TODO: avoid creating a ParTag object just for this query...
212 xml::openTag(xs, lay.docbooktag(), lay.docbookattr(), lay.docbooktagtype());
213 xml::openTag(xs, lay.docbookinnertag(), lay.docbookinnerattr(), lay.docbookinnertagtype());
217 xml::openTag(xs, lay.docbookitemwrappertag(), lay.docbookitemwrapperattr(), lay.docbookitemwrappertagtype());
218 xml::openTag(xs, lay.docbookitemtag(), lay.docbookitemattr(), lay.docbookitemtagtype());
219 xml::openTag(xs, lay.docbookiteminnertag(), lay.docbookiteminnerattr(), lay.docbookiteminnertagtype());
223 void closeParTag(XMLStream & xs, Paragraph const * par, Paragraph const * nextpar, const OutputParams & runparams)
228 // If the next paragraph is empty, don't consider it when closing wrappers.
229 if (nextpar && nextpar->empty() && !nextpar->allowEmpty())
232 // See comment in openParTag.
233 Layout const & lay = par->layout();
234 bool closeWrapper = lay.docbookwrappertag() != "NONE" && !runparams.docbook_ignore_wrapper;
236 if (nextpar != nullptr && !runparams.docbook_ignore_wrapper) {
237 Layout const & nextlay = nextpar->layout();
238 if (nextlay.docbookwrappertag() != "NONE") {
239 if (nextlay.docbookwrappertag() == lay.docbookwrappertag() &&
240 nextlay.docbookwrapperattr() == lay.docbookwrapperattr())
241 closeWrapper = !nextlay.docbookwrappermergewithprevious();
248 xml::closeTag(xs, lay.docbookiteminnertag(), lay.docbookiteminnertagtype());
249 xml::closeTag(xs, lay.docbookitemtag(), lay.docbookitemtagtype());
250 xml::closeTag(xs, lay.docbookitemwrappertag(), lay.docbookitemwrappertagtype());
251 xml::closeTag(xs, lay.docbookinnertag(), lay.docbookinnertagtype());
252 xml::closeTag(xs, lay.docbooktag(), lay.docbooktagtype());
254 xml::closeTag(xs, lay.docbookwrappertag(), lay.docbookwrappertagtype());
258 void makeBibliography(
262 OutputParams const & runparams,
263 ParagraphList::const_iterator const & par)
265 // If this is the first paragraph in a bibliography, open the bibliography tag.
266 auto const * pbegin_before = text.paragraphs().getParagraphBefore(par);
267 if (pbegin_before == nullptr || (pbegin_before && pbegin_before->layout().latextype != LATEX_BIB_ENVIRONMENT)) {
268 xs << xml::StartTag("bibliography");
272 // Start the precooked bibliography entry. This is very much like opening a paragraph tag.
273 // Don't forget the citation ID!
275 for (auto i = 0; i < par->size(); ++i) {
276 Inset const *ip = par->getInset(i);
279 if (const auto * bibitem = dynamic_cast<const InsetBibitem*>(ip)) {
280 auto id = xml::cleanID(bibitem->getParam("key"));
281 attr = from_utf8("xml:id='") + id + from_utf8("'");
285 xs << xml::StartTag(from_utf8("bibliomixed"), attr);
287 // Generate the entry. Concatenate the different parts of the paragraph if any.
288 auto const begin = text.paragraphs().begin();
289 std::vector<docstring> pars_prepend;
290 std::vector<docstring> pars;
291 std::vector<docstring> pars_append;
292 tie(pars_prepend, pars, pars_append) = par->simpleDocBookOnePar(buf, runparams, text.outerFont(std::distance(begin, par)), 0);
294 for (auto & parXML : pars_prepend)
295 xs << XMLStream::ESCAPE_NONE << parXML;
296 for (auto & parXML : pars)
297 xs << XMLStream::ESCAPE_NONE << parXML;
298 for (auto & parXML : pars_append)
299 xs << XMLStream::ESCAPE_NONE << parXML;
301 // End the precooked bibliography entry.
302 xs << xml::EndTag("bibliomixed");
305 // If this is the last paragraph in a bibliography, close the bibliography tag.
306 auto const end = text.paragraphs().end();
309 bool endBibliography = nextpar == end || nextpar->layout().latextype != LATEX_BIB_ENVIRONMENT;
311 if (endBibliography) {
312 xs << xml::EndTag("bibliography");
322 OutputParams const & runparams,
323 ParagraphList::const_iterator const & par)
326 auto const begin = text.paragraphs().begin();
327 auto const end = text.paragraphs().end();
328 auto prevpar = text.paragraphs().getParagraphBefore(par);
330 // We want to open the paragraph tag if:
331 // (i) the current layout permits multiple paragraphs
332 // (ii) we are either not already inside a paragraph (HTMLIsBlock) OR
333 // we are, but this is not the first paragraph
335 // But there is also a special case, and we first see whether we are in it.
336 // We do not want to open the paragraph tag if this paragraph contains
337 // only one item, and that item is "inline", i.e., not HTMLIsBlock (such
338 // as a branch). On the other hand, if that single item has a font change
339 // applied to it, then we still do need to open the paragraph.
341 // Obviously, this is very fragile. The main reason we need to do this is
342 // because of branches, e.g., a branch that contains an entire new section.
343 // We do not really want to wrap that whole thing in a <div>...</div>.
344 bool special_case = false;
345 Inset const *specinset = par->size() == 1 ? par->getInset(0) : nullptr;
346 if (specinset && !specinset->getLayout().htmlisblock()) { // TODO: Convert htmlisblock to a DocBook parameter?
347 Layout const &style = par->layout();
348 FontInfo const first_font = style.labeltype == LABEL_MANUAL ?
349 style.labelfont : style.font;
350 FontInfo const our_font =
351 par->getFont(buf.masterBuffer()->params(), 0,
352 text.outerFont(std::distance(begin, par))).fontInfo();
354 if (first_font == our_font)
358 size_t nInsets = std::distance(par->insetList().begin(), par->insetList().end());
359 auto parSize = (size_t) par->size();
361 // Plain layouts must be ignored.
362 special_case |= buf.params().documentClass().isPlainLayout(par->layout()) && !runparams.docbook_force_pars;
363 // Equations do not deserve their own paragraph (DocBook allows them outside paragraphs).
364 // Exception: any case that generates an <inlineequation> must still get a paragraph to be valid.
365 special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), [](InsetList::Element inset) {
366 return inset.inset && inset.inset->asInsetMath() && inset.inset->asInsetMath()->getType() != hullSimple;
369 // Things that should not get into their own paragraph. (Only valid for DocBook.)
370 static std::set<InsetCode> lyxCodeSpecialCases = {
373 BIBTEX_CODE, // Bibliographies cannot be in paragraphs. Bibitems should still be handled as paragraphs,
374 // though (see makeParagraphBibliography).
375 ERT_CODE, // ERTs are in comments, not paragraphs.
380 TOC_CODE, // To be ignored in DocBook, the processor afterwards should deal with ToCs.
381 NOTE_CODE // Notes do not produce any output.
383 auto isLyxCodeSpecialCase = [](InsetList::Element inset) {
384 return lyxCodeSpecialCases.find(inset.inset->lyxCode()) != lyxCodeSpecialCases.end();
386 special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), isLyxCodeSpecialCase);
388 // Flex elements (InsetLayout) have their own parameter to control the special case.
389 auto isFlexSpecialCase = [](InsetList::Element inset) {
390 if (inset.inset->lyxCode() != FLEX_CODE)
393 // Standard condition: check the parameter.
394 if (inset.inset->getLayout().docbooknotinpara())
397 // If the parameter is not set, maybe the flex inset only contains things that should match the standard
398 // condition. In this case, isLyxCodeSpecialCase must also check for bibitems...
399 auto isLyxCodeSpecialCase = [](InsetList::Element inset) {
400 return lyxCodeSpecialCases.find(inset.inset->lyxCode()) != lyxCodeSpecialCases.end() ||
401 inset.inset->lyxCode() == BIBITEM_CODE;
403 if (InsetText * text = inset.inset->asInsetText()) {
404 for (auto const & par : text->paragraphs()) {
405 size_t nInsets = std::distance(par.insetList().begin(), par.insetList().end());
406 auto parSize = (size_t) par.size();
408 if (nInsets == 1 && par.insetList().begin()->inset->lyxCode() == BIBITEM_CODE)
410 if (nInsets != parSize)
412 if (!std::all_of(par.insetList().begin(), par.insetList().end(), isLyxCodeSpecialCase))
418 // No case matched: give up.
421 special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), isFlexSpecialCase);
423 // Open a paragraph if it is allowed, we are not already within a paragraph, and the insets in the paragraph do
424 // not forbid paragraphs (aka special cases).
425 bool const open_par = runparams.docbook_make_pars
426 && !runparams.docbook_in_par
429 // We want to issue the closing tag if either:
430 // (i) We opened it, and either docbook_in_par is false,
431 // or we're not in the last paragraph, anyway.
432 // (ii) We didn't open it and docbook_in_par is true,
433 // but we are in the first par, and there is a next par.
434 bool const close_par = open_par && !runparams.docbook_in_par;
436 // Determine if this paragraph has some real content. Things like new pages are not caught
437 // by Paragraph::empty(), even though they do not generate anything useful in DocBook.
438 // Thus, remove all spaces (including new lines: \r, \n) before checking for emptiness.
439 // std::all_of allows doing this check without having to copy the string.
440 // Open and close tags around each contained paragraph.
444 std::vector<docstring> pars_prepend;
445 std::vector<docstring> pars;
446 std::vector<docstring> pars_append;
447 tie(pars_prepend, pars, pars_append) = par->simpleDocBookOnePar(buf, runparams, text.outerFont(distance(begin, par)), 0, nextpar == end, special_case);
449 for (docstring const & parXML : pars_prepend)
450 xs << XMLStream::ESCAPE_NONE << parXML;
451 for (docstring const & parXML : pars) {
452 if (!xml::isNotOnlySpace(parXML))
456 openParTag(xs, &*par, prevpar, runparams);
458 xs << XMLStream::ESCAPE_NONE << parXML;
461 closeParTag(xs, &*par, (nextpar != end) ? &*nextpar : nullptr, runparams);
463 for (docstring const & parXML : pars_append)
464 xs << XMLStream::ESCAPE_NONE << parXML;
468 void makeEnvironment(Text const &text,
471 OutputParams const &runparams,
472 ParagraphList::const_iterator const & par)
475 auto const end = text.paragraphs().end();
479 // Special cases for listing-like environments provided in layouts. This is quite ad-hoc, but provides a useful
480 // default. This should not be used by too many environments (only LyX-Code right now).
481 // This would be much simpler if LyX-Code was implemented as InsetListings...
482 bool mimicListing = false;
483 bool ignoreFonts = false;
484 if (par->layout().docbooktag() == "programlisting") {
489 // Output the opening tag for this environment, but only if it has not been previously opened (condition
490 // implemented in openParTag).
491 auto prevpar = text.paragraphs().getParagraphBefore(par);
492 openParTag(xs, &*par, prevpar, runparams);
494 // Generate the contents of this environment. There is a special case if this is like some environment.
495 Layout const & style = par->layout();
496 if (style.latextype == LATEX_COMMAND) {
497 // Nothing to do (otherwise, infinite loops).
498 } else if (style.latextype == LATEX_ENVIRONMENT) {
499 // Generate the paragraph, if need be.
500 std::vector<docstring> pars_prepend;
501 std::vector<docstring> pars;
502 std::vector<docstring> pars_append;
503 tie(pars_prepend, pars, pars_append) = par->simpleDocBookOnePar(buf, runparams, text.outerFont(std::distance(text.paragraphs().begin(), par)), 0, false, ignoreFonts);
505 for (docstring const & parXML : pars_prepend)
506 xs << XMLStream::ESCAPE_NONE << parXML;
508 auto p = pars.begin();
509 while (p != pars.end()) {
510 xml::openTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnerattr(),
511 par->layout().docbookiteminnertagtype());
512 xs << XMLStream::ESCAPE_NONE << *p;
513 xml::closeTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnertagtype());
516 // Insert a new line after each "paragraph" (i.e. line in the listing), except for the last one.
517 // Otherwise, there would one more new line in the output than in the LyX document.
522 for (auto const & p : pars) {
523 xml::openTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnerattr(),
524 par->layout().docbookiteminnertagtype());
525 xs << XMLStream::ESCAPE_NONE << p;
526 xml::closeTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnertagtype());
529 for (docstring const & parXML : pars_append)
530 xs << XMLStream::ESCAPE_NONE << parXML;
532 makeAny(text, buf, xs, runparams, par);
535 // Close the environment.
536 closeParTag(xs, &*par, (nextpar != end) ? &*nextpar : nullptr, runparams);
540 ParagraphList::const_iterator findEndOfEnvironment(
541 ParagraphList::const_iterator const & pstart,
542 ParagraphList::const_iterator const & pend)
544 // Copy-paste from XHTML. Should be factored out at some point...
545 ParagraphList::const_iterator p = pstart;
546 Layout const & bstyle = p->layout();
547 size_t const depth = p->params().depth();
548 for (++p; p != pend; ++p) {
549 Layout const & style = p->layout();
550 // It shouldn't happen that e.g. a section command occurs inside
551 // a quotation environment, at a higher depth, but as of 6/2009,
552 // it can happen. We pretend that it's just at lowest depth.
553 if (style.latextype == LATEX_COMMAND)
556 // If depth is down, we're done
557 if (p->params().depth() < depth)
560 // If depth is up, we're not done
561 if (p->params().depth() > depth)
564 // FIXME I am not sure about the first check.
565 // Surely we *could* have different layouts that count as
566 // LATEX_PARAGRAPH, right?
567 if (style.latextype == LATEX_PARAGRAPH || style != bstyle)
574 ParagraphList::const_iterator makeListEnvironment(Text const &text,
577 OutputParams const &runparams,
578 ParagraphList::const_iterator const & begin)
582 auto const end = text.paragraphs().end();
583 auto const envend = findEndOfEnvironment(par, end);
585 // Output the opening tag for this environment.
586 Layout const & envstyle = par->layout();
587 xml::openTag(xs, envstyle.docbookwrappertag(), envstyle.docbookwrapperattr(), envstyle.docbookwrappertagtype());
588 xml::openTag(xs, envstyle.docbooktag(), envstyle.docbookattr(), envstyle.docbooktagtype());
590 // Handle the content of the list environment, item by item.
591 while (par != envend) {
592 // Skip this paragraph if it is both empty and the last one (otherwise, there may be deeper paragraphs after).
595 if (par->empty() && nextpar == envend)
598 // Open the item wrapper.
599 Layout const & style = par->layout();
600 xml::openTag(xs, style.docbookitemwrappertag(), style.docbookitemwrapperattr(),
601 style.docbookitemwrappertagtype());
603 // Generate the label, if need be. If it is taken from the text, sep != 0 and corresponds to the first
604 // character after the label.
606 if (style.labeltype != LABEL_NO_LABEL && style.docbookitemlabeltag() != "NONE") {
607 if (style.labeltype == LABEL_MANUAL) {
608 // Only variablelist gets here (or similar items defined as an extension in the layout).
609 xml::openTag(xs, style.docbookitemlabeltag(), style.docbookitemlabelattr(),
610 style.docbookitemlabeltagtype());
611 sep = 1 + par->firstWordDocBook(xs, runparams);
612 xml::closeTag(xs, style.docbookitemlabeltag(), style.docbookitemlabeltagtype());
614 // Usual cases: maybe there is something specified at the layout level. Highly unlikely, though.
615 docstring const lbl = par->params().labelString();
618 xml::openTag(xs, style.docbookitemlabeltag(), style.docbookitemlabelattr(),
619 style.docbookitemlabeltagtype());
621 xml::closeTag(xs, style.docbookitemlabeltag(), style.docbookitemlabeltagtype());
626 // Open the item (after the wrapper and the label).
627 xml::openTag(xs, style.docbookitemtag(), style.docbookitemattr(), style.docbookitemtagtype());
629 // Generate the content of the item.
630 if (sep < par->size()) {
631 std::vector<docstring> pars_prepend;
632 std::vector<docstring> pars;
633 std::vector<docstring> pars_append;
634 tie(pars_prepend, pars, pars_append) = par->simpleDocBookOnePar(buf, runparams,
635 text.outerFont(std::distance(text.paragraphs().begin(), par)), sep);
636 for (docstring const & parXML : pars_prepend)
637 xs << XMLStream::ESCAPE_NONE << parXML;
638 for (auto &p : pars) {
639 xml::openTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnerattr(),
640 par->layout().docbookiteminnertagtype());
641 xs << XMLStream::ESCAPE_NONE << p;
642 xml::closeTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnertagtype());
644 for (docstring const & parXML : pars_append)
645 xs << XMLStream::ESCAPE_NONE << parXML;
647 // DocBook doesn't like emptiness.
648 xml::compTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnerattr(),
649 par->layout().docbookiteminnertagtype());
652 // If the next item is deeper, it must go entirely within this item (do it recursively).
653 // By construction, with findEndOfEnvironment, depth can only stay constant or increase, never decrease.
654 depth_type currentDepth = par->getDepth();
656 while (par != envend && par->getDepth() != currentDepth)
657 par = makeAny(text, buf, xs, runparams, par);
658 // Usually, this loop only makes one iteration, except in complex scenarios, like an item with a paragraph,
659 // a list, and another paragraph; or an item with two types of list (itemise then enumerate, for instance).
662 xml::closeTag(xs, style.docbookitemtag(), style.docbookitemtagtype());
663 xml::closeTag(xs, style.docbookitemwrappertag(), style.docbookitemwrappertagtype());
666 // Close this environment in exactly the same way as it was opened.
667 xml::closeTag(xs, envstyle.docbooktag(), envstyle.docbooktagtype());
668 xml::closeTag(xs, envstyle.docbookwrappertag(), envstyle.docbookwrappertagtype());
678 OutputParams const & runparams,
679 ParagraphList::const_iterator const & par)
682 // Unlike XHTML, no need for labels, as they are handled by DocBook tags.
683 auto const begin = text.paragraphs().begin();
684 auto const end = text.paragraphs().end();
688 // Generate this command.
689 auto prevpar = text.paragraphs().getParagraphBefore(par);
691 std::vector<docstring> pars_prepend;
692 std::vector<docstring> pars;
693 std::vector<docstring> pars_append;
694 tie(pars_prepend, pars, pars_append) = par->simpleDocBookOnePar(buf, runparams,text.outerFont(distance(begin, par)));
696 for (docstring const & parXML : pars_prepend)
697 xs << XMLStream::ESCAPE_NONE << parXML;
699 openParTag(xs, &*par, prevpar, runparams);
700 for (auto & parXML : pars)
701 // TODO: decide what to do with openParTag/closeParTag in new lines.
702 xs << XMLStream::ESCAPE_NONE << parXML;
703 closeParTag(xs, &*par, (nextpar != end) ? &*nextpar : nullptr, runparams);
705 for (docstring const & parXML : pars_append)
706 xs << XMLStream::ESCAPE_NONE << parXML;
710 bool isLayoutSectioning(Layout const & lay)
712 if (lay.docbooksection()) // Special case: some DocBook styles must be handled as sections.
714 else if (lay.category() == from_utf8("Sectioning") || lay.docbooktag() == "section") // Generic case.
715 return lay.toclevel != Layout::NOT_IN_TOC;
720 bool isLayoutSectioningOrSimilar(Layout const & lay)
722 return isLayoutSectioning(lay) || lay.docbooktag() == "bridgehead";
726 using DocBookDocumentSectioning = tuple<bool, pit_type>;
729 struct DocBookInfoTag
731 const set<pit_type> shouldBeInInfo;
732 const set<pit_type> mustBeInInfo; // With the notable exception of the abstract!
733 const set<pit_type> abstract;
734 const bool abstractLayout;
738 DocBookInfoTag(const set<pit_type> & shouldBeInInfo, const set<pit_type> & mustBeInInfo,
739 const set<pit_type> & abstract, bool abstractLayout, pit_type bpit, pit_type epit) :
740 shouldBeInInfo(shouldBeInInfo), mustBeInInfo(mustBeInInfo), abstract(abstract),
741 abstractLayout(abstractLayout), bpit(bpit), epit(epit) {}
745 DocBookDocumentSectioning hasDocumentSectioning(ParagraphList const ¶graphs, pit_type bpit, pit_type const epit) {
746 bool documentHasSections = false;
748 while (bpit < epit) {
749 Layout const &style = paragraphs[bpit].layout();
750 documentHasSections |= isLayoutSectioningOrSimilar(style);
752 if (documentHasSections)
756 // Paragraphs before the first section: [ runparams.par_begin ; eppit )
758 return make_tuple(documentHasSections, bpit);
762 bool hasOnlyNotes(Paragraph const & par)
764 // Precondition: the paragraph is not empty. Otherwise, the function will always return true...
765 for (int i = 0; i < par.size(); ++i)
766 // If you find something that is not an inset (like actual text) or an inset that is not a note,
768 if (!par.isInset(i) || par.getInset(i)->lyxCode() != NOTE_CODE)
771 // An empty paragraph may still require some output.
772 if (par.layout().docbooksection())
775 // There should be really no content here.
780 DocBookInfoTag getParagraphsWithInfo(ParagraphList const ¶graphs,
781 pit_type bpit, pit_type const epit,
782 // Typically, bpit is the beginning of the document and epit the end of the
783 // document *or* the first section.
784 bool documentHasSections,
785 bool detectUnlayoutedAbstract
786 // Whether paragraphs with no specific layout should be detected as abstracts.
787 // For inner sections, an abstract should only be detected if it has a specific
788 // layout. For others, anything that might look like an abstract should be sought.
790 set<pit_type> shouldBeInInfo;
791 set<pit_type> mustBeInInfo;
792 set<pit_type> abstractWithLayout;
793 set<pit_type> abstractNoLayout;
795 // Find the first non empty paragraph by mutating bpit.
796 while (bpit < epit) {
797 Paragraph const &par = paragraphs[bpit];
798 if (par.empty() || hasOnlyNotes(par))
804 // Traverse everything that might belong to <info>.
805 bool hasAbstractLayout = false;
806 static depth_type INVALID_DEPTH = 100000;
807 depth_type abstractDepth = INVALID_DEPTH;
808 pit_type cpit = bpit;
809 for (; cpit < epit; ++cpit) {
810 // Skip paragraphs that don't generate anything in DocBook.
811 Paragraph const & par = paragraphs[cpit];
812 Layout const &style = par.layout();
813 if (hasOnlyNotes(par))
816 // There should never be any section here, except for the first paragraph (a title can be part of <info>).
817 // (Just a sanity check: if this fails, this function could end up processing the whole document.)
818 if (cpit != bpit && isLayoutSectioningOrSimilar(par.layout())) {
819 LYXERR0("Assertion failed: section found in potential <info> paragraphs.");
823 // If this is marked as an abstract by the layout, put it in the right set.
824 if (style.docbookabstract()) {
825 hasAbstractLayout = true;
826 abstractDepth = par.getDepth();
827 abstractWithLayout.emplace(cpit);
831 // Deeper paragraphs following the abstract must still be considered as part of the abstract.
832 // For instance, this includes lists. There should not be any other kind of paragraph in between.
833 if (abstractDepth != INVALID_DEPTH && style.docbookininfo() == "never") {
834 if (par.getDepth() > abstractDepth) {
835 abstractWithLayout.emplace(cpit);
838 if (par.getDepth() == abstractDepth) {
839 // This is not an abstract paragraph and it should not either be considered as part
840 // of it. It breaks the rule that abstract paragraphs must follow each other.
841 abstractDepth = INVALID_DEPTH;
846 // Based on layout information, store this paragraph in one set: should be in <info>, must be,
847 // or abstract (either because of layout or of position).
848 if (style.docbookininfo() == "always")
849 mustBeInInfo.emplace(cpit);
850 else if (style.docbookininfo() == "maybe")
851 shouldBeInInfo.emplace(cpit);
852 else if (documentHasSections && !hasAbstractLayout && detectUnlayoutedAbstract &&
853 (style.docbooktag() == "NONE" || style.docbooktag() == "para") &&
854 style.docbookwrappertag() == "NONE")
855 // In this case, it is very likely that style.docbookininfo() == "never"! Be extra careful
856 // about anything that gets caught here. For instance, don't ake into account
857 abstractNoLayout.emplace(cpit);
858 else // This should definitely not be in <info>.
861 // Now, cpit points to the first paragraph that no more has things that could go in <info>.
862 // bpit is the beginning of the <info> part.
864 return DocBookInfoTag(shouldBeInInfo, mustBeInInfo,
865 hasAbstractLayout ? abstractWithLayout : abstractNoLayout,
866 hasAbstractLayout, bpit, cpit);
869 } // end anonymous namespace
872 std::set<const Inset *> gatherInfo(ParagraphList::const_iterator par)
874 // This function has a structure highly similar to makeAny and its friends. It's only made to be called on what
875 // should become the document's <abstract>.
876 std::set<const Inset *> values;
878 // If this kind of layout should be ignored, already leave.
879 if (par->layout().docbooktag() == "IGNORE")
882 // If this should go in info, mark it as such. Dive deep into the abstract, as it may hide many things that
883 // DocBook doesn't want to be inside the abstract.
884 for (pos_type i = 0; i < par->size(); ++i) {
885 if (par->getInset(i) && par->getInset(i)->asInsetText()) {
886 InsetText const *inset = par->getInset(i)->asInsetText();
888 if (inset->getLayout().docbookininfo() != "never") {
889 values.insert(inset);
891 auto subpar = inset->paragraphs().begin();
892 while (subpar != inset->paragraphs().end()) {
893 auto subinfos = gatherInfo(subpar);
894 for (auto & subinfo: subinfos)
895 values.insert(subinfo);
906 ParagraphList::const_iterator makeAny(Text const &text,
909 OutputParams const &runparams,
910 ParagraphList::const_iterator par)
912 bool ignoreParagraph = false;
914 // If this kind of layout should be ignored, already leave.
915 ignoreParagraph |= par->layout().docbooktag() == "IGNORE";
917 // For things that should go into <info>, check the variable rp.docbook_generate_info. This does not apply to the
919 bool isAbstract = par->layout().docbookabstract() || par->layout().docbooktag() == "abstract";
920 ignoreParagraph |= !isAbstract && par->layout().docbookininfo() != "never" && !runparams.docbook_generate_info;
922 // Switch on the type of paragraph to call the right handler.
923 if (!ignoreParagraph) {
924 switch (par->layout().latextype) {
926 makeCommand(text, buf, xs, runparams, par);
928 case LATEX_ENVIRONMENT:
929 makeEnvironment(text, buf, xs, runparams, par);
931 case LATEX_LIST_ENVIRONMENT:
932 case LATEX_ITEM_ENVIRONMENT:
933 // Only case when makeAny() might consume more than one paragraph.
934 return makeListEnvironment(text, buf, xs, runparams, par);
935 case LATEX_PARAGRAPH:
936 makeParagraph(text, buf, xs, runparams, par);
938 case LATEX_BIB_ENVIRONMENT:
939 makeBibliography(text, buf, xs, runparams, par);
944 // For cases that are not lists, the next paragraph to handle is the next one.
950 xml::FontTag docbookStartFontTag(xml::FontTypes type)
952 return xml::FontTag(from_utf8(fontToDocBookTag(type)), from_utf8(fontToAttribute(type)), type);
956 xml::EndFontTag docbookEndFontTag(xml::FontTypes type)
958 return xml::EndFontTag(from_utf8(fontToDocBookTag(type)), type);
962 void outputDocBookInfo(
966 OutputParams const & runparams,
967 ParagraphList const & paragraphs,
968 DocBookInfoTag const & info)
970 // Perform an additional check on the abstract. Sometimes, there are many paragraphs that should go
971 // into the abstract, but none generates actual content. Thus, first generate to a temporary stream,
972 // then only create the <abstract> tag if these paragraphs generate some content.
973 // This check must be performed *before* a decision on whether or not to output <info> is made.
974 bool hasAbstract = !info.abstract.empty();
976 set<const Inset *> infoInsets; // Paragraphs that should go into <info>, but are hidden in an <abstract>
977 // paragraph. (This happens for quite a few layouts, unfortunately.)
980 // Generate the abstract XML into a string before further checks.
981 // Usually, makeAny only generates one paragraph at a time. However, for the specific case of lists, it might
982 // generate more than one paragraph, as indicated in the return value.
983 odocstringstream os2;
987 rp.docbook_generate_info = false;
988 rp.docbook_ignore_wrapper = true;
990 set<pit_type> doneParas; // Paragraphs that have already been converted (mostly to deal with lists).
991 for (auto const & p : info.abstract) {
992 if (doneParas.find(p) == doneParas.end()) {
993 auto oldPar = paragraphs.iterator_at(p);
994 auto newPar = makeAny(text, buf, xs2, rp, oldPar);
996 // Find insets that should go outside the abstract.
997 auto subinfos = gatherInfo(oldPar);
998 for (auto & subinfo: subinfos)
999 infoInsets.insert(subinfo);
1001 // Insert the indices of all the paragraphs that were just generated (typically, one).
1002 // **Make the hypothesis that, when an abstract has a list, all its items are consecutive.**
1003 // Otherwise, makeAny and makeListEnvironment would have to be adapted too.
1005 while (oldPar != newPar) {
1006 doneParas.emplace(id);
1013 // Actually output the abstract if there is something to do. Don't count line feeds or spaces in this,
1014 // even though they must be properly output if there is some abstract.
1015 abstract = os2.str();
1016 docstring cleaned = abstract;
1017 cleaned.erase(std::remove_if(cleaned.begin(), cleaned.end(), lyx::isSpace), cleaned.end());
1019 // Nothing? Then there is no abstract!
1020 if (cleaned.empty())
1021 hasAbstract = false;
1024 // The abstract must go in <info>. Otherwise, decide whether to open <info> based on the layouts.
1025 bool needInfo = !info.mustBeInInfo.empty() || hasAbstract;
1027 // Start the <info> tag if required.
1029 xs.startDivision(false);
1030 xs << xml::StartTag("info");
1034 // Output the elements that should go in <info>.
1035 // - First, the title.
1036 for (auto pit : info.shouldBeInInfo) // Typically, the title: these elements are so important and ubiquitous
1037 // that mandating a wrapper like <info> would repel users. Thus, generate them first.
1038 makeAny(text, buf, xs, runparams, paragraphs.iterator_at(pit));
1039 // If there is no title, generate one (required for the document to be valid).
1040 // This code is called for the main document, for table cells, etc., so be precise in this condition.
1041 if (text.isMainText() && info.shouldBeInInfo.empty() && !runparams.inInclude) {
1042 xs << xml::StartTag("title");
1043 xs << "Untitled Document";
1044 xs << xml::EndTag("title");
1048 // - Then, other metadata.
1049 for (auto pit : info.mustBeInInfo)
1050 makeAny(text, buf, xs, runparams, paragraphs.iterator_at(pit));
1051 for (auto const * inset : infoInsets)
1052 inset->docbook(xs, runparams);
1054 // - Finally, always output the abstract as the last item of the <info>, as it requires special treatment
1055 // (especially if it contains several paragraphs that are empty).
1057 string tag = paragraphs[*info.abstract.begin()].layout().docbookforceabstracttag();
1061 if (!xs.isLastTagCR())
1064 xs << xml::StartTag(tag);
1066 xs << XMLStream::ESCAPE_NONE << abstract;
1067 xs << xml::EndTag(tag);
1071 // End the <info> tag if it was started.
1073 if (!xs.isLastTagCR())
1076 xs << xml::EndTag("info");
1083 void docbookSimpleAllParagraphs(
1087 OutputParams const & runparams)
1089 // Handle the given text, supposing it has no sections (i.e. a "simple" text). The input may vary in length
1090 // between a single paragraph to a whole document.
1091 pit_type const bpit = runparams.par_begin;
1092 pit_type const epit = runparams.par_end;
1093 ParagraphList const ¶graphs = text.paragraphs();
1095 // First, the <info> tag.
1096 DocBookInfoTag info = getParagraphsWithInfo(paragraphs, bpit, epit, false, true);
1097 outputDocBookInfo(text, buf, xs, runparams, paragraphs, info);
1099 // Then, the content. It starts where the <info> ends.
1100 auto par = paragraphs.iterator_at(info.epit);
1101 auto end = paragraphs.iterator_at(epit);
1102 while (par != end) {
1103 if (!hasOnlyNotes(*par))
1104 par = makeAny(text, buf, xs, runparams, par);
1111 void docbookParagraphs(Text const &text,
1114 OutputParams const &runparams) {
1115 ParagraphList const ¶graphs = text.paragraphs();
1116 if (runparams.par_begin == runparams.par_end) {
1117 runparams.par_begin = 0;
1118 runparams.par_end = paragraphs.size();
1120 pit_type bpit = runparams.par_begin;
1121 pit_type const epit = runparams.par_end;
1122 LASSERT(bpit < epit,
1124 xs << XMLStream::ESCAPE_NONE << "<!-- DocBook output error! -->\n";
1128 // Detect whether the document contains sections. If there are no sections, treatment is largely simplified.
1129 // In particular, there can't be an abstract, unless it is manually marked.
1130 bool documentHasSections;
1132 tie(documentHasSections, eppit) = hasDocumentSectioning(paragraphs, bpit, epit);
1134 // Deal with "simple" documents, i.e. those without sections.
1135 if (!documentHasSections) {
1136 docbookSimpleAllParagraphs(text, buf, xs, runparams);
1140 // Output the first <info> tag (or just the title).
1141 DocBookInfoTag info = getParagraphsWithInfo(paragraphs, bpit, eppit, true, true);
1142 outputDocBookInfo(text, buf, xs, runparams, paragraphs, info);
1145 // In the specific case of books, there must be parts or chapters. In some cases, star sections are used at the
1146 // beginning for many things like acknowledgements or licenses. DocBook has tags for many of these cases, but not
1147 // the LyX layouts... Gather everything in a <preface>, that's the closest in meaning.
1148 // This is only useful if the things after the <info> tag are not already parts or chapters!
1149 if (buf.params().documentClass().docbookroot() == "book") {
1150 // Check the condition on the first few elements.
1151 bool hasPreface = false;
1152 pit_type pref_bpit = bpit;
1153 pit_type pref_epit = bpit;
1155 static const std::set<std::string> allowedElements = {
1156 // List from https://tdg.docbook.org/tdg/5.2/book.html
1157 "acknowledgements", "appendix", "article", "bibliography", "chapter", "colophon", "dedication",
1158 "glossary", "index", "part", "preface", "reference", "toc"
1161 for (; pref_epit < epit; ++pref_epit) {
1162 auto par = text.paragraphs().iterator_at(pref_epit);
1163 if (allowedElements.find(par->layout().docbooktag()) != allowedElements.end() ||
1164 allowedElements.find(par->layout().docbooksectiontag()) != allowedElements.end())
1170 // Output a preface if required. A title is needed for the document to be valid...
1172 xs << xml::StartTag("preface");
1175 xs << xml::StartTag("title");
1177 xs << xml::EndTag("title");
1180 auto pref_par = text.paragraphs().iterator_at(pref_bpit);
1181 auto pref_end = text.paragraphs().iterator_at(pref_epit);
1182 while (pref_par != pref_end) {
1183 // Skip paragraphs not producing any output.
1184 if (hasOnlyNotes(*pref_par)) {
1189 // TODO: must sections be handled here? If so, it might be useful to extract the corresponding loop
1190 // in the rest of this function to use the same here (and avoid copy-paste mistakes).
1191 pref_par = makeAny(text, buf, xs, runparams, pref_par);
1194 xs << xml::EndTag("preface");
1197 // Skip what has just been generated in the preface.
1202 std::stack<std::pair<int, string>> headerLevels; // Used to determine when to open/close sections: store the depth
1203 // of the section and the tag that was used to open it.
1205 // Then, iterate through the paragraphs of this document.
1206 auto par = text.paragraphs().iterator_at(bpit);
1207 auto end = text.paragraphs().iterator_at(epit);
1208 while (par != end) {
1209 // Skip paragraphs not producing any output.
1210 if (hasOnlyNotes(*par)) {
1215 OutputParams ourparams = runparams;
1216 Layout const &style = par->layout();
1218 // Think about adding <section> and/or </section>s.
1219 if (isLayoutSectioning(style) || par->params().startOfAppendix()) {
1220 int level = style.toclevel;
1222 // Need to close a previous section if it has the same level or a higher one (close <section> if opening a
1223 // <h2> after a <h2>, <h3>, <h4>, <h5> or <h6>). More examples:
1224 // - current: h2; back: h1; do not close any <section>
1225 // - current: h1; back: h2; close two <section> (first the <h2>, then the <h1>, so a new <h1> can come)
1226 // Some layouts require that Layout::NOT_IN_TOC sections still cause closing of previous sections. This is
1227 // mostly to ensure that the section is positioned at a DocBook-compatible level (acknowledgements: cannot
1228 // be under a section!).
1229 while (!headerLevels.empty() && level <= headerLevels.top().first) {
1230 // Output the tag only if it corresponds to a legit section.
1231 int stackLevel = headerLevels.top().first;
1232 if (stackLevel != Layout::NOT_IN_TOC) {
1233 xs << xml::EndTag(headerLevels.top().second);
1239 // Open the new section: first push it onto the stack, then output it in DocBook.
1240 string sectionTag = (par->params().startOfAppendix()) ? "appendix" : style.docbooksectiontag();
1241 headerLevels.push(std::make_pair(level, sectionTag));
1243 // Some sectioning-like elements should not be output (such as FrontMatter).
1244 if (level != Layout::NOT_IN_TOC) {
1245 // Look for a label in the title, i.e. a InsetLabel as a child.
1246 docstring id = docstring();
1247 for (pos_type i = 0; i < par->size(); ++i) {
1248 Inset const *inset = par->getInset(i);
1250 if (auto label = dynamic_cast<InsetLabel const *>(inset)) {
1251 // Generate the attributes for the section if need be.
1252 id += "xml:id=\"" + xml::cleanID(label->screenLabel()) + "\"";
1254 // Don't output the ID as a DocBook <anchor>.
1255 ourparams.docbook_anchors_to_ignore.emplace(label->screenLabel());
1257 // Cannot have multiple IDs per tag. If there is another ID inset in the document, it will
1258 // be output as a DocBook anchor.
1264 // Write the open tag for this section.
1268 xs << xml::StartTag(sectionTag, attrs);
1273 // Close all sections before the bibliography.
1274 // 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)?
1275 if (!par->insetList().empty()) {
1276 Inset const *firstInset = par->getInset(0);
1277 if (firstInset && (firstInset->lyxCode() == BIBITEM_CODE || firstInset->lyxCode() == BIBTEX_CODE)) {
1278 while (!headerLevels.empty()) {
1279 // Don't close appendices before bibliographies.
1280 if (headerLevels.top().second == "appendix")
1283 // Pop the section from the stack.
1284 int level = headerLevels.top().first;
1285 docstring tag = from_utf8("</" + headerLevels.top().second + ">");
1288 // Output the tag only if it corresponds to a legit section, as the rest of the code.
1289 if (level != Layout::NOT_IN_TOC) {
1290 xs << XMLStream::ESCAPE_NONE << tag;
1297 // Generate the <info> tag if a section was just opened.
1298 // Some sections may require abstracts (mostly parts, in books: DocBookForceAbstractTag will not be NONE),
1299 // others can still have an abstract (it must be detected so that it can be output at the right place).
1300 // 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.
1301 if (isLayoutSectioning(style)) {
1302 // This abstract may be found between the next paragraph and the next title.
1303 pit_type cpit = std::distance(text.paragraphs().begin(), par);
1304 pit_type ppit = std::get<1>(hasDocumentSectioning(paragraphs, cpit + 1L, epit));
1306 // Generate this abstract (this code corresponds to parts of outputDocBookInfo).
1307 DocBookInfoTag secInfo = getParagraphsWithInfo(paragraphs, cpit, ppit, true,
1308 style.docbookforceabstracttag() != "NONE");
1310 if (!secInfo.mustBeInInfo.empty() || !secInfo.shouldBeInInfo.empty() || !secInfo.abstract.empty()) {
1311 // Generate the <info>, if required. If DocBookForceAbstractTag != NONE, this abstract will not be in
1312 // <info>, unlike other ("standard") abstracts.
1313 bool hasStandardAbstract = !secInfo.abstract.empty() && style.docbookforceabstracttag() == "NONE";
1314 bool needInfo = !secInfo.mustBeInInfo.empty() || hasStandardAbstract;
1317 xs.startDivision(false);
1318 xs << xml::StartTag("info");
1322 // Output the elements that should go in <info>, before and after the abstract.
1323 for (auto pit : secInfo.shouldBeInInfo) // Typically, the title: these elements are so important and ubiquitous
1324 // that mandating a wrapper like <info> would repel users. Thus, generate them first.
1325 makeAny(text, buf, xs, ourparams, paragraphs.iterator_at(pit));
1326 for (auto pit : secInfo.mustBeInInfo)
1327 makeAny(text, buf, xs, ourparams, paragraphs.iterator_at(pit));
1329 // Deal with the abstract in <info> if it is standard (i.e. its tag is <abstract>).
1330 if (!secInfo.abstract.empty() && hasStandardAbstract) {
1331 if (!secInfo.abstractLayout) {
1332 xs << xml::StartTag("abstract");
1336 for (auto const &p : secInfo.abstract)
1337 makeAny(text, buf, xs, ourparams, paragraphs.iterator_at(p));
1339 if (!secInfo.abstractLayout) {
1340 xs << xml::EndTag("abstract");
1345 // End the <info> tag if it was started.
1347 if (!xs.isLastTagCR())
1350 xs << xml::EndTag("info");
1355 // Deal with the abstract outside <info> if it is not standard (i.e. its tag is layout-defined).
1356 if (!secInfo.abstract.empty() && !hasStandardAbstract) {
1357 // Assert: style.docbookforceabstracttag() != NONE.
1358 xs << xml::StartTag(style.docbookforceabstracttag());
1360 for (auto const &p : secInfo.abstract)
1361 makeAny(text, buf, xs, ourparams, paragraphs.iterator_at(p));
1362 xs << xml::EndTag(style.docbookforceabstracttag());
1366 // Skip all the text that has just been generated.
1367 par = paragraphs.iterator_at(secInfo.epit);
1369 // No <info> tag to generate, proceed as for normal paragraphs.
1370 par = makeAny(text, buf, xs, ourparams, par);
1373 // Generate this paragraph, as it has nothing special.
1374 par = makeAny(text, buf, xs, ourparams, par);
1378 // If need be, close <section>s, but only at the end of the document (otherwise, dealt with at the beginning
1380 while (!headerLevels.empty() && headerLevels.top().first > Layout::NOT_IN_TOC) {
1381 docstring tag = from_utf8("</" + headerLevels.top().second + ">");
1383 xs << XMLStream::ESCAPE_NONE << tag;