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? docbooknotinpara should be enough in most cases.
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;
364 // Equations do not deserve their own paragraph (DocBook allows them outside paragraphs).
365 // Exception: any case that generates an <inlineequation> must still get a paragraph to be valid.
366 auto isEquationSpecialCase = [](InsetList::Element inset) {
367 return inset.inset && inset.inset->asInsetMath() && inset.inset->asInsetMath()->getType() != hullSimple;
369 special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), isEquationSpecialCase);
371 // Things that should not get into their own paragraph. (Only valid for DocBook.)
372 static std::set<InsetCode> lyxCodeSpecialCases = {
375 BIBTEX_CODE, // Bibliographies cannot be in paragraphs. Bibitems should still be handled as paragraphs,
376 // though (see makeParagraphBibliography).
377 ERT_CODE, // ERTs are in comments, not paragraphs.
382 TOC_CODE, // To be ignored in DocBook, the processor afterwards should deal with ToCs.
383 NOTE_CODE // Notes do not produce any output.
385 auto isLyxCodeSpecialCase = [](InsetList::Element inset) {
386 return lyxCodeSpecialCases.find(inset.inset->lyxCode()) != lyxCodeSpecialCases.end();
388 special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), isLyxCodeSpecialCase);
390 // Flex elements (InsetLayout) have their own parameter to control the special case.
391 auto isFlexSpecialCase = [](InsetList::Element inset) {
392 if (inset.inset->lyxCode() != FLEX_CODE)
395 // Standard condition: check the parameter.
396 if (inset.inset->getLayout().docbooknotinpara())
399 // If the parameter is not set, maybe the flex inset only contains things that should match the standard
400 // condition. In this case, isLyxCodeSpecialCase must also check for bibitems...
401 auto isLyxCodeSpecialCase = [](InsetList::Element inset) {
402 return lyxCodeSpecialCases.find(inset.inset->lyxCode()) != lyxCodeSpecialCases.end() ||
403 inset.inset->lyxCode() == BIBITEM_CODE;
405 if (InsetText * text = inset.inset->asInsetText()) {
406 for (auto const & par : text->paragraphs()) {
407 size_t nInsets = std::distance(par.insetList().begin(), par.insetList().end());
408 auto parSize = (size_t) par.size();
410 if (nInsets == 1 && par.insetList().begin()->inset->lyxCode() == BIBITEM_CODE)
412 if (nInsets != parSize)
414 if (!std::all_of(par.insetList().begin(), par.insetList().end(), isLyxCodeSpecialCase))
420 // No case matched: give up.
423 special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), isFlexSpecialCase);
425 // If the insets should be rendered as images, enter the special case.
426 auto isRenderedAsImageSpecialCase = [](InsetList::Element inset) {
427 return inset.inset && inset.inset->getLayout().docbookrenderasimage();
429 special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), isRenderedAsImageSpecialCase);
431 // Open a paragraph if it is allowed, we are not already within a paragraph, and the insets in the paragraph do
432 // not forbid paragraphs (aka special cases).
433 bool const open_par = runparams.docbook_make_pars
434 && !runparams.docbook_in_par
437 // We want to issue the closing tag if either:
438 // (i) We opened it, and either docbook_in_par is false,
439 // or we're not in the last paragraph, anyway.
440 // (ii) We didn't open it and docbook_in_par is true,
441 // but we are in the first par, and there is a next par.
442 bool const close_par = open_par && !runparams.docbook_in_par;
444 // Determine if this paragraph has some real content. Things like new pages are not caught
445 // by Paragraph::empty(), even though they do not generate anything useful in DocBook.
446 // Thus, remove all spaces (including new lines: \r, \n) before checking for emptiness.
447 // std::all_of allows doing this check without having to copy the string.
448 // Open and close tags around each contained paragraph.
452 std::vector<docstring> pars_prepend;
453 std::vector<docstring> pars;
454 std::vector<docstring> pars_append;
455 tie(pars_prepend, pars, pars_append) = par->simpleDocBookOnePar(buf, runparams, text.outerFont(distance(begin, par)), 0, nextpar == end, special_case);
457 for (docstring const & parXML : pars_prepend)
458 xs << XMLStream::ESCAPE_NONE << parXML;
459 for (docstring const & parXML : pars) {
460 if (!xml::isNotOnlySpace(parXML))
464 openParTag(xs, &*par, prevpar, runparams);
466 xs << XMLStream::ESCAPE_NONE << parXML;
469 closeParTag(xs, &*par, (nextpar != end) ? &*nextpar : nullptr, runparams);
471 for (docstring const & parXML : pars_append)
472 xs << XMLStream::ESCAPE_NONE << parXML;
476 void makeEnvironment(Text const &text,
479 OutputParams const &runparams,
480 ParagraphList::const_iterator const & par)
483 auto const end = text.paragraphs().end();
487 // Special cases for listing-like environments provided in layouts. This is quite ad-hoc, but provides a useful
488 // default. This should not be used by too many environments (only LyX-Code right now).
489 // This would be much simpler if LyX-Code was implemented as InsetListings...
490 bool mimicListing = false;
491 bool ignoreFonts = false;
492 if (par->layout().docbooktag() == "programlisting") {
497 // Output the opening tag for this environment, but only if it has not been previously opened (condition
498 // implemented in openParTag).
499 auto prevpar = text.paragraphs().getParagraphBefore(par);
500 openParTag(xs, &*par, prevpar, runparams);
502 // Generate the contents of this environment. There is a special case if this is like some environment.
503 Layout const & style = par->layout();
504 if (style.latextype == LATEX_COMMAND) {
505 // Nothing to do (otherwise, infinite loops).
506 } else if (style.latextype == LATEX_ENVIRONMENT) {
507 // Generate the paragraph, if need be.
508 std::vector<docstring> pars_prepend;
509 std::vector<docstring> pars;
510 std::vector<docstring> pars_append;
511 tie(pars_prepend, pars, pars_append) = par->simpleDocBookOnePar(buf, runparams, text.outerFont(std::distance(text.paragraphs().begin(), par)), 0, false, ignoreFonts);
513 for (docstring const & parXML : pars_prepend)
514 xs << XMLStream::ESCAPE_NONE << parXML;
516 auto p = pars.begin();
517 while (p != pars.end()) {
518 xml::openTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnerattr(),
519 par->layout().docbookiteminnertagtype());
520 xs << XMLStream::ESCAPE_NONE << *p;
521 xml::closeTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnertagtype());
524 // Insert a new line after each "paragraph" (i.e. line in the listing), except for the last one.
525 // Otherwise, there would one more new line in the output than in the LyX document.
530 for (auto const & p : pars) {
531 xml::openTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnerattr(),
532 par->layout().docbookiteminnertagtype());
533 xs << XMLStream::ESCAPE_NONE << p;
534 xml::closeTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnertagtype());
537 for (docstring const & parXML : pars_append)
538 xs << XMLStream::ESCAPE_NONE << parXML;
540 makeAny(text, buf, xs, runparams, par);
543 // Close the environment.
544 closeParTag(xs, &*par, (nextpar != end) ? &*nextpar : nullptr, runparams);
548 ParagraphList::const_iterator findEndOfEnvironment(
549 ParagraphList::const_iterator const & pstart,
550 ParagraphList::const_iterator const & pend)
552 // Copy-paste from XHTML. Should be factored out at some point...
553 ParagraphList::const_iterator p = pstart;
554 Layout const & bstyle = p->layout();
555 size_t const depth = p->params().depth();
556 for (++p; p != pend; ++p) {
557 Layout const & style = p->layout();
558 // It shouldn't happen that e.g. a section command occurs inside
559 // a quotation environment, at a higher depth, but as of 6/2009,
560 // it can happen. We pretend that it's just at lowest depth.
561 if (style.latextype == LATEX_COMMAND)
564 // If depth is down, we're done
565 if (p->params().depth() < depth)
568 // If depth is up, we're not done
569 if (p->params().depth() > depth)
572 // FIXME I am not sure about the first check.
573 // Surely we *could* have different layouts that count as
574 // LATEX_PARAGRAPH, right?
575 if (style.latextype == LATEX_PARAGRAPH || style != bstyle)
582 ParagraphList::const_iterator makeListEnvironment(Text const &text,
585 OutputParams const &runparams,
586 ParagraphList::const_iterator const & begin)
590 auto const end = text.paragraphs().end();
591 auto const envend = findEndOfEnvironment(par, end);
593 // Output the opening tag for this environment.
594 Layout const & envstyle = par->layout();
595 xml::openTag(xs, envstyle.docbookwrappertag(), envstyle.docbookwrapperattr(), envstyle.docbookwrappertagtype());
596 xml::openTag(xs, envstyle.docbooktag(), envstyle.docbookattr(), envstyle.docbooktagtype());
598 // Handle the content of the list environment, item by item.
599 while (par != envend) {
600 // Skip this paragraph if it is both empty and the last one (otherwise, there may be deeper paragraphs after).
603 if (par->empty() && nextpar == envend)
606 // Open the item wrapper.
607 Layout const & style = par->layout();
608 xml::openTag(xs, style.docbookitemwrappertag(), style.docbookitemwrapperattr(),
609 style.docbookitemwrappertagtype());
611 // Generate the label, if need be. If it is taken from the text, sep != 0 and corresponds to the first
612 // character after the label.
614 if (style.labeltype != LABEL_NO_LABEL && style.docbookitemlabeltag() != "NONE") {
615 if (style.labeltype == LABEL_MANUAL) {
616 // Only variablelist gets here (or similar items defined as an extension in the layout).
617 xml::openTag(xs, style.docbookitemlabeltag(), style.docbookitemlabelattr(),
618 style.docbookitemlabeltagtype());
619 sep = 1 + par->firstWordDocBook(xs, runparams);
620 xml::closeTag(xs, style.docbookitemlabeltag(), style.docbookitemlabeltagtype());
622 // Usual cases: maybe there is something specified at the layout level. Highly unlikely, though.
623 docstring const lbl = par->params().labelString();
626 xml::openTag(xs, style.docbookitemlabeltag(), style.docbookitemlabelattr(),
627 style.docbookitemlabeltagtype());
629 xml::closeTag(xs, style.docbookitemlabeltag(), style.docbookitemlabeltagtype());
634 // Open the item (after the wrapper and the label).
635 xml::openTag(xs, style.docbookitemtag(), style.docbookitemattr(), style.docbookitemtagtype());
637 // Generate the content of the item.
638 if (sep < par->size()) {
639 std::vector<docstring> pars_prepend;
640 std::vector<docstring> pars;
641 std::vector<docstring> pars_append;
642 tie(pars_prepend, pars, pars_append) = par->simpleDocBookOnePar(buf, runparams,
643 text.outerFont(std::distance(text.paragraphs().begin(), par)), sep);
644 for (docstring const & parXML : pars_prepend)
645 xs << XMLStream::ESCAPE_NONE << parXML;
646 for (auto &p : pars) {
647 xml::openTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnerattr(),
648 par->layout().docbookiteminnertagtype());
649 xs << XMLStream::ESCAPE_NONE << p;
650 xml::closeTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnertagtype());
652 for (docstring const & parXML : pars_append)
653 xs << XMLStream::ESCAPE_NONE << parXML;
655 // DocBook doesn't like emptiness.
656 xml::compTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnerattr(),
657 par->layout().docbookiteminnertagtype());
660 // If the next item is deeper, it must go entirely within this item (do it recursively).
661 // By construction, with findEndOfEnvironment, depth can only stay constant or increase, never decrease.
662 depth_type currentDepth = par->getDepth();
664 while (par != envend && par->getDepth() != currentDepth)
665 par = makeAny(text, buf, xs, runparams, par);
666 // Usually, this loop only makes one iteration, except in complex scenarios, like an item with a paragraph,
667 // a list, and another paragraph; or an item with two types of list (itemise then enumerate, for instance).
670 xml::closeTag(xs, style.docbookitemtag(), style.docbookitemtagtype());
671 xml::closeTag(xs, style.docbookitemwrappertag(), style.docbookitemwrappertagtype());
674 // Close this environment in exactly the same way as it was opened.
675 xml::closeTag(xs, envstyle.docbooktag(), envstyle.docbooktagtype());
676 xml::closeTag(xs, envstyle.docbookwrappertag(), envstyle.docbookwrappertagtype());
686 OutputParams const & runparams,
687 ParagraphList::const_iterator const & par)
690 // Unlike XHTML, no need for labels, as they are handled by DocBook tags.
691 auto const begin = text.paragraphs().begin();
692 auto const end = text.paragraphs().end();
696 // Generate this command.
697 auto prevpar = text.paragraphs().getParagraphBefore(par);
699 std::vector<docstring> pars_prepend;
700 std::vector<docstring> pars;
701 std::vector<docstring> pars_append;
702 tie(pars_prepend, pars, pars_append) = par->simpleDocBookOnePar(buf, runparams,text.outerFont(distance(begin, par)));
704 for (docstring const & parXML : pars_prepend)
705 xs << XMLStream::ESCAPE_NONE << parXML;
707 openParTag(xs, &*par, prevpar, runparams);
708 for (auto & parXML : pars)
709 // TODO: decide what to do with openParTag/closeParTag in new lines.
710 xs << XMLStream::ESCAPE_NONE << parXML;
711 closeParTag(xs, &*par, (nextpar != end) ? &*nextpar : nullptr, runparams);
713 for (docstring const & parXML : pars_append)
714 xs << XMLStream::ESCAPE_NONE << parXML;
718 bool isLayoutSectioning(Layout const & lay)
720 if (lay.docbooksection()) // Special case: some DocBook styles must be handled as sections.
722 else if (lay.category() == from_utf8("Sectioning") || lay.docbooktag() == "section") // Generic case.
723 return lay.toclevel != Layout::NOT_IN_TOC;
728 bool isLayoutSectioningOrSimilar(Layout const & lay)
730 return isLayoutSectioning(lay) || lay.docbooktag() == "bridgehead";
734 using DocBookDocumentSectioning = tuple<bool, pit_type>;
737 struct DocBookInfoTag
739 const set<pit_type> shouldBeInInfo;
740 const set<pit_type> mustBeInInfo; // With the notable exception of the abstract!
741 const set<pit_type> abstract;
742 const bool abstractLayout;
746 DocBookInfoTag(const set<pit_type> & shouldBeInInfo, const set<pit_type> & mustBeInInfo,
747 const set<pit_type> & abstract, bool abstractLayout, pit_type bpit, pit_type epit) :
748 shouldBeInInfo(shouldBeInInfo), mustBeInInfo(mustBeInInfo), abstract(abstract),
749 abstractLayout(abstractLayout), bpit(bpit), epit(epit) {}
753 DocBookDocumentSectioning hasDocumentSectioning(ParagraphList const ¶graphs, pit_type bpit, pit_type const epit) {
754 bool documentHasSections = false;
756 while (bpit < epit) {
757 LASSERT(static_cast<size_t>(bpit) < paragraphs.size(), return make_tuple(documentHasSections, bpit));
759 Layout const &style = paragraphs[bpit].layout();
760 documentHasSections |= isLayoutSectioningOrSimilar(style);
762 if (documentHasSections)
766 // Paragraphs before the first section: [ runparams.par_begin ; eppit )
768 return make_tuple(documentHasSections, bpit);
772 bool hasOnlyNotes(Paragraph const & par)
774 // Precondition: the paragraph is not empty. Otherwise, the function will always return true...
775 for (int i = 0; i < par.size(); ++i)
776 // If you find something that is not an inset (like actual text) or an inset that is not a note,
778 if (!par.isInset(i) || par.getInset(i)->lyxCode() != NOTE_CODE)
781 // An empty paragraph may still require some output.
782 if (par.layout().docbooksection())
785 // There should be really no content here.
790 DocBookInfoTag getParagraphsWithInfo(ParagraphList const ¶graphs,
791 pit_type bpit, pit_type const epit,
792 // Typically, bpit is the beginning of the document and epit the end of the
793 // document *or* the first section.
794 bool documentHasSections,
795 bool detectUnlayoutedAbstract
796 // Whether paragraphs with no specific layout should be detected as abstracts.
797 // For inner sections, an abstract should only be detected if it has a specific
798 // layout. For others, anything that might look like an abstract should be sought.
800 set<pit_type> shouldBeInInfo;
801 set<pit_type> mustBeInInfo;
802 set<pit_type> abstractWithLayout;
803 set<pit_type> abstractNoLayout;
805 // Find the first nonempty paragraph by mutating bpit.
806 while (bpit < epit) {
807 Paragraph const &par = paragraphs[bpit];
808 if (par.empty() || hasOnlyNotes(par))
814 // Traverse everything that might belong to <info>.
815 bool hasAbstractLayout = false;
816 static depth_type INVALID_DEPTH = 100000;
817 depth_type abstractDepth = INVALID_DEPTH;
818 pit_type cpit = bpit;
819 for (; cpit < epit; ++cpit) {
820 // Skip paragraphs that don't generate anything in DocBook.
821 Paragraph const & par = paragraphs[cpit];
822 Layout const &style = par.layout();
823 if (hasOnlyNotes(par))
826 // There should never be any section here, except for the first paragraph (a title can be part of <info>).
827 // (Just a sanity check: if this fails, this function could end up processing the whole document.)
828 if (cpit != bpit && isLayoutSectioningOrSimilar(par.layout())) {
829 LYXERR(Debug::OUTFILE, "Assertion failed: section found in potential <info> paragraphs.");
833 // If this is marked as an abstract by the layout, put it in the right set.
834 if (style.docbookabstract()) {
835 hasAbstractLayout = true;
836 abstractDepth = par.getDepth();
837 abstractWithLayout.emplace(cpit);
841 // Deeper paragraphs following the abstract must still be considered as part of the abstract.
842 // For instance, this includes lists. There should not be any other kind of paragraph in between.
843 if (abstractDepth != INVALID_DEPTH && style.docbookininfo() == "never") {
844 if (par.getDepth() > abstractDepth) {
845 abstractWithLayout.emplace(cpit);
848 if (par.getDepth() == abstractDepth) {
849 // This is not an abstract paragraph and it should not either be considered as part
850 // of it. It breaks the rule that abstract paragraphs must follow each other.
851 abstractDepth = INVALID_DEPTH;
856 // Based on layout information, store this paragraph in one set: should be in <info>, must be,
857 // or abstract (either because of layout or of position).
858 if (style.docbookininfo() == "always")
859 mustBeInInfo.emplace(cpit);
860 else if (style.docbookininfo() == "maybe")
861 shouldBeInInfo.emplace(cpit);
862 else if (documentHasSections && !hasAbstractLayout && detectUnlayoutedAbstract &&
863 (style.docbooktag() == "NONE" || style.docbooktag() == "para") &&
864 style.docbookwrappertag() == "NONE")
865 // In this case, it is very likely that style.docbookininfo() == "never"! Be extra careful
866 // about anything that gets caught here. For instance, don't ake into account
867 abstractNoLayout.emplace(cpit);
868 else // This should definitely not be in <info>.
871 // Now, cpit points to the first paragraph that no more has things that could go in <info>.
872 // bpit is the beginning of the <info> part.
874 return DocBookInfoTag(shouldBeInInfo, mustBeInInfo,
875 hasAbstractLayout ? abstractWithLayout : abstractNoLayout,
876 hasAbstractLayout, bpit, cpit);
879 } // end anonymous namespace
882 std::set<const Inset *> gatherInfo(ParagraphList::const_iterator par)
884 // This function has a structure highly similar to makeAny and its friends. It's only made to be called on what
885 // should become the document's <abstract>.
886 std::set<const Inset *> values;
888 // If this kind of layout should be ignored, already leave.
889 if (par->layout().docbooktag() == "IGNORE")
892 // If this should go in info, mark it as such. Dive deep into the abstract, as it may hide many things that
893 // DocBook doesn't want to be inside the abstract.
894 for (pos_type i = 0; i < par->size(); ++i) {
895 if (par->getInset(i) && par->getInset(i)->asInsetText()) {
896 InsetText const *inset = par->getInset(i)->asInsetText();
898 if (inset->getLayout().docbookininfo() != "never") {
899 values.insert(inset);
901 auto subpar = inset->paragraphs().begin();
902 while (subpar != inset->paragraphs().end()) {
903 auto subinfos = gatherInfo(subpar);
904 for (auto & subinfo: subinfos)
905 values.insert(subinfo);
916 ParagraphList::const_iterator makeAny(Text const &text,
919 OutputParams const &runparams,
920 ParagraphList::const_iterator par)
922 bool ignoreParagraph = false;
924 // If this kind of layout should be ignored, already leave.
925 ignoreParagraph |= par->layout().docbooktag() == "IGNORE";
927 // For things that should go into <info>, check the variable rp.docbook_generate_info. This does not apply to the
929 bool isAbstract = par->layout().docbookabstract() || par->layout().docbooktag() == "abstract";
930 ignoreParagraph |= !isAbstract && par->layout().docbookininfo() != "never" && !runparams.docbook_generate_info;
932 // Switch on the type of paragraph to call the right handler.
933 if (!ignoreParagraph) {
934 switch (par->layout().latextype) {
936 makeCommand(text, buf, xs, runparams, par);
938 case LATEX_ENVIRONMENT:
939 makeEnvironment(text, buf, xs, runparams, par);
941 case LATEX_LIST_ENVIRONMENT:
942 case LATEX_ITEM_ENVIRONMENT:
943 // Only case when makeAny() might consume more than one paragraph.
944 return makeListEnvironment(text, buf, xs, runparams, par);
945 case LATEX_PARAGRAPH:
946 makeParagraph(text, buf, xs, runparams, par);
948 case LATEX_BIB_ENVIRONMENT:
949 makeBibliography(text, buf, xs, runparams, par);
954 // For cases that are not lists, the next paragraph to handle is the next one.
960 xml::FontTag docbookStartFontTag(xml::FontTypes type)
962 return xml::FontTag(from_utf8(fontToDocBookTag(type)), from_utf8(fontToAttribute(type)), type);
966 xml::EndFontTag docbookEndFontTag(xml::FontTypes type)
968 return xml::EndFontTag(from_utf8(fontToDocBookTag(type)), type);
972 void outputDocBookInfo(
976 OutputParams const & runparams,
977 ParagraphList const & paragraphs,
978 DocBookInfoTag const & info)
980 // Perform an additional check on the abstract. Sometimes, there are many paragraphs that should go
981 // into the abstract, but none generates actual content. Thus, first generate to a temporary stream,
982 // then only create the <abstract> tag if these paragraphs generate some content.
983 // This check must be performed *before* a decision on whether or not to output <info> is made.
984 bool hasAbstract = !info.abstract.empty();
986 set<const Inset *> infoInsets; // Paragraphs that should go into <info>, but are hidden in an <abstract>
987 // paragraph. (This happens for quite a few layouts, unfortunately.)
990 // Generate the abstract XML into a string before further checks.
991 // Usually, makeAny only generates one paragraph at a time. However, for the specific case of lists, it might
992 // generate more than one paragraph, as indicated in the return value.
993 odocstringstream os2;
997 rp.docbook_generate_info = false;
998 rp.docbook_ignore_wrapper = true;
1000 set<pit_type> doneParas; // Paragraphs that have already been converted (mostly to deal with lists).
1001 for (auto const & p : info.abstract) {
1002 if (doneParas.find(p) == doneParas.end()) {
1003 auto oldPar = paragraphs.iterator_at(p);
1004 auto newPar = makeAny(text, buf, xs2, rp, oldPar);
1006 // Find insets that should go outside the abstract.
1007 auto subinfos = gatherInfo(oldPar);
1008 for (auto & subinfo: subinfos)
1009 infoInsets.insert(subinfo);
1011 // Insert the indices of all the paragraphs that were just generated (typically, one).
1012 // **Make the hypothesis that, when an abstract has a list, all its items are consecutive.**
1013 // Otherwise, makeAny and makeListEnvironment would have to be adapted too.
1015 while (oldPar != newPar) {
1016 doneParas.emplace(id);
1023 // Actually output the abstract if there is something to do. Don't count line feeds, spaces, or comments
1024 // in this -- even though line feeds and spaces must be properly output if there is some abstract.
1025 abstract = os2.str();
1026 docstring cleaned = abstract;
1027 cleaned.erase(std::remove_if(cleaned.begin(), cleaned.end(), lyx::isSpace), cleaned.end());
1029 size_t beginComment;
1031 while ((beginComment = cleaned.find(from_ascii("<!--"))) != lyx::docstring::npos) {
1032 if ((endComment = cleaned.find(from_ascii("-->"), beginComment)) != lyx::docstring::npos) {
1033 cleaned.erase(cleaned.begin() + beginComment, cleaned.begin() + endComment + 3);
1037 // Nothing? Then there is no abstract!
1038 if (cleaned.empty())
1039 hasAbstract = false;
1042 // The abstract must go in <info>. Otherwise, decide whether to open <info> based on the layouts.
1043 bool needInfo = !info.mustBeInInfo.empty() || hasAbstract;
1045 // Start the <info> tag if required.
1047 xs.startDivision(false);
1048 xs << xml::StartTag("info");
1052 // Output the elements that should go in <info>.
1053 // - First, the title.
1054 for (auto pit : info.shouldBeInInfo) // Typically, the title: these elements are so important and ubiquitous
1055 // that mandating a wrapper like <info> would repel users. Thus, generate them first.
1056 makeAny(text, buf, xs, runparams, paragraphs.iterator_at(pit));
1057 // If there is no title, generate one (required for the document to be valid).
1058 // This code is called for the main document, for table cells, etc., so be precise in this condition.
1059 if (text.isMainText() && info.shouldBeInInfo.empty() && !runparams.inInclude) {
1060 xs << xml::StartTag("title");
1061 xs << "Untitled Document";
1062 xs << xml::EndTag("title");
1066 // - Then, other metadata.
1067 for (auto pit : info.mustBeInInfo)
1068 makeAny(text, buf, xs, runparams, paragraphs.iterator_at(pit));
1069 for (auto const * inset : infoInsets)
1070 inset->docbook(xs, runparams);
1072 // - Finally, always output the abstract as the last item of the <info>, as it requires special treatment
1073 // (especially if it contains several paragraphs that are empty).
1075 string tag = paragraphs[*info.abstract.begin()].layout().docbookforceabstracttag();
1079 if (!xs.isLastTagCR())
1082 xs << xml::StartTag(tag);
1084 xs << XMLStream::ESCAPE_NONE << abstract;
1085 xs << xml::EndTag(tag);
1089 // End the <info> tag if it was started.
1091 if (!xs.isLastTagCR())
1094 xs << xml::EndTag("info");
1101 void docbookSimpleAllParagraphs(
1105 OutputParams const & runparams)
1107 // Handle the given text, supposing it has no sections (i.e. a "simple" text). The input may vary in length
1108 // between a single paragraph to a whole document.
1109 pit_type const bpit = runparams.par_begin;
1110 pit_type const epit = runparams.par_end;
1111 ParagraphList const ¶graphs = text.paragraphs();
1113 // First, the <info> tag.
1114 DocBookInfoTag info = getParagraphsWithInfo(paragraphs, bpit, epit, false, true);
1115 outputDocBookInfo(text, buf, xs, runparams, paragraphs, info);
1117 // Then, the content. It starts where the <info> ends.
1118 auto par = paragraphs.iterator_at(info.epit);
1119 auto end = paragraphs.iterator_at(epit);
1120 while (par != end) {
1121 if (!hasOnlyNotes(*par))
1122 par = makeAny(text, buf, xs, runparams, par);
1129 void docbookParagraphs(Text const &text,
1132 OutputParams const &runparams) {
1133 ParagraphList const ¶graphs = text.paragraphs();
1134 if (runparams.par_begin == runparams.par_end) {
1135 runparams.par_begin = 0;
1136 runparams.par_end = paragraphs.size();
1138 pit_type bpit = runparams.par_begin;
1139 pit_type const epit = runparams.par_end;
1140 LASSERT(bpit < epit,
1142 xs << XMLStream::ESCAPE_NONE << "<!-- DocBook output error! -->\n";
1146 // Detect whether the document contains sections. If there are no sections, treatment is largely simplified.
1147 // In particular, there can't be an abstract, unless it is manually marked.
1148 bool documentHasSections;
1150 tie(documentHasSections, eppit) = hasDocumentSectioning(paragraphs, bpit, epit);
1152 // Deal with "simple" documents, i.e. those without sections.
1153 if (!documentHasSections) {
1154 docbookSimpleAllParagraphs(text, buf, xs, runparams);
1158 // Output the first <info> tag (or just the title).
1159 DocBookInfoTag info = getParagraphsWithInfo(paragraphs, bpit, eppit, true, true);
1160 outputDocBookInfo(text, buf, xs, runparams, paragraphs, info);
1163 // In the specific case of books, there must be parts or chapters. In some cases, star sections are used at the
1164 // beginning for many things like acknowledgements or licenses. DocBook has tags for many of these cases, but not
1165 // the LyX layouts... Gather everything in a <preface>, that's the closest in meaning.
1166 // This is only useful if the things after the <info> tag are not already parts or chapters!
1167 if (buf.params().documentClass().docbookroot() == "book") {
1168 // Check the condition on the first few elements.
1169 bool hasPreface = false;
1170 pit_type pref_bpit = bpit;
1171 pit_type pref_epit = bpit;
1173 static const std::set<std::string> allowedElements = {
1174 // List from https://tdg.docbook.org/tdg/5.2/book.html
1175 "acknowledgements", "appendix", "article", "bibliography", "chapter", "colophon", "dedication",
1176 "glossary", "index", "part", "preface", "reference", "toc"
1179 for (; pref_epit < epit; ++pref_epit) {
1180 auto par = text.paragraphs().iterator_at(pref_epit);
1181 if (allowedElements.find(par->layout().docbooktag()) != allowedElements.end() ||
1182 allowedElements.find(par->layout().docbooksectiontag()) != allowedElements.end())
1188 // Output a preface if required. A title is needed for the document to be valid...
1190 xs << xml::StartTag("preface");
1193 xs << xml::StartTag("title");
1195 xs << xml::EndTag("title");
1198 auto pref_par = text.paragraphs().iterator_at(pref_bpit);
1199 auto pref_end = text.paragraphs().iterator_at(pref_epit);
1200 while (pref_par != pref_end) {
1201 // Skip paragraphs not producing any output.
1202 if (hasOnlyNotes(*pref_par)) {
1207 // TODO: must sections be handled here? If so, it might be useful to extract the corresponding loop
1208 // in the rest of this function to use the same here (and avoid copy-paste mistakes).
1209 pref_par = makeAny(text, buf, xs, runparams, pref_par);
1212 xs << xml::EndTag("preface");
1215 // Skip what has just been generated in the preface.
1220 std::stack<std::pair<int, string>> headerLevels; // Used to determine when to open/close sections: store the depth
1221 // of the section and the tag that was used to open it.
1223 // Then, iterate through the paragraphs of this document.
1224 auto par = text.paragraphs().iterator_at(bpit);
1225 auto end = text.paragraphs().iterator_at(epit);
1226 while (par != end) {
1227 // Skip paragraphs not producing any output.
1228 if (hasOnlyNotes(*par)) {
1233 OutputParams ourparams = runparams;
1234 Layout const &style = par->layout();
1236 // Think about adding <section> and/or </section>s.
1237 if (isLayoutSectioning(style) || par->params().startOfAppendix()) {
1238 int level = style.toclevel;
1240 // Need to close a previous section if it has the same level or a higher one (close <section> if opening a
1241 // <h2> after a <h2>, <h3>, <h4>, <h5> or <h6>). More examples:
1242 // - current: h2; back: h1; do not close any <section>
1243 // - current: h1; back: h2; close two <section> (first the <h2>, then the <h1>, so a new <h1> can come)
1244 // Some layouts require that Layout::NOT_IN_TOC sections still cause closing of previous sections. This is
1245 // mostly to ensure that the section is positioned at a DocBook-compatible level (acknowledgements: cannot
1246 // be under a section!).
1247 while (!headerLevels.empty() && level <= headerLevels.top().first) {
1248 // Output the tag only if it corresponds to a legit section.
1249 int stackLevel = headerLevels.top().first;
1250 if (stackLevel != Layout::NOT_IN_TOC) {
1251 xs << xml::EndTag(headerLevels.top().second);
1257 // Open the new section: first push it onto the stack, then output it in DocBook.
1258 string sectionTag = (par->params().startOfAppendix()) ? "appendix" : style.docbooksectiontag();
1259 headerLevels.push(std::make_pair(level, sectionTag));
1261 // Some sectioning-like elements should not be output (such as FrontMatter).
1262 if (level != Layout::NOT_IN_TOC) {
1263 // Look for a label in the title, i.e. a InsetLabel as a child.
1264 docstring id = docstring();
1265 for (pos_type i = 0; i < par->size(); ++i) {
1266 Inset const *inset = par->getInset(i);
1268 if (auto label = dynamic_cast<InsetLabel const *>(inset)) {
1269 // Generate the attributes for the section if need be.
1270 id += "xml:id=\"" + xml::cleanID(label->screenLabel()) + "\"";
1272 // Don't output the ID as a DocBook <anchor>.
1273 ourparams.docbook_anchors_to_ignore.emplace(label->screenLabel());
1275 // Cannot have multiple IDs per tag. If there is another ID inset in the document, it will
1276 // be output as a DocBook anchor.
1282 // Write the open tag for this section.
1286 xs << xml::StartTag(sectionTag, attrs);
1291 // Close all sections before the bibliography.
1292 // 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)?
1293 if (!par->insetList().empty()) {
1294 Inset const *firstInset = par->getInset(0);
1295 if (firstInset && (firstInset->lyxCode() == BIBITEM_CODE || firstInset->lyxCode() == BIBTEX_CODE)) {
1296 while (!headerLevels.empty()) {
1297 // Don't close appendices before bibliographies.
1298 if (headerLevels.top().second == "appendix")
1301 // Pop the section from the stack.
1302 int level = headerLevels.top().first;
1303 docstring tag = from_utf8("</" + headerLevels.top().second + ">");
1306 // Output the tag only if it corresponds to a legit section, as the rest of the code.
1307 if (level != Layout::NOT_IN_TOC) {
1308 xs << XMLStream::ESCAPE_NONE << tag;
1315 // Generate the <info> tag if a section was just opened.
1316 // Some sections may require abstracts (mostly parts, in books: DocBookForceAbstractTag will not be NONE),
1317 // others can still have an abstract (it must be detected so that it can be output at the right place).
1318 // 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.
1319 if (isLayoutSectioning(style)) {
1320 // This abstract may be found between the next paragraph and the next title.
1321 pit_type cpit = std::distance(text.paragraphs().begin(), par);
1322 pit_type ppit = std::get<1>(hasDocumentSectioning(paragraphs, cpit + 1L, epit));
1324 // Generate this abstract (this code corresponds to parts of outputDocBookInfo).
1325 DocBookInfoTag secInfo = getParagraphsWithInfo(paragraphs, cpit, ppit, true,
1326 style.docbookforceabstracttag() != "NONE");
1328 if (!secInfo.mustBeInInfo.empty() || !secInfo.shouldBeInInfo.empty() || !secInfo.abstract.empty()) {
1329 // Generate the <info>, if required. If DocBookForceAbstractTag != NONE, this abstract will not be in
1330 // <info>, unlike other ("standard") abstracts.
1331 bool hasStandardAbstract = !secInfo.abstract.empty() && style.docbookforceabstracttag() == "NONE";
1332 bool needInfo = !secInfo.mustBeInInfo.empty() || hasStandardAbstract;
1335 xs.startDivision(false);
1336 xs << xml::StartTag("info");
1340 // Output the elements that should go in <info>, before and after the abstract.
1341 for (auto pit : secInfo.shouldBeInInfo) // Typically, the title: these elements are so important and ubiquitous
1342 // that mandating a wrapper like <info> would repel users. Thus, generate them first.
1343 makeAny(text, buf, xs, ourparams, paragraphs.iterator_at(pit));
1344 for (auto pit : secInfo.mustBeInInfo)
1345 makeAny(text, buf, xs, ourparams, paragraphs.iterator_at(pit));
1347 // Deal with the abstract in <info> if it is standard (i.e. its tag is <abstract>).
1348 if (!secInfo.abstract.empty() && hasStandardAbstract) {
1349 if (!secInfo.abstractLayout) {
1350 xs << xml::StartTag("abstract");
1354 for (auto const &p : secInfo.abstract)
1355 makeAny(text, buf, xs, ourparams, paragraphs.iterator_at(p));
1357 if (!secInfo.abstractLayout) {
1358 xs << xml::EndTag("abstract");
1363 // End the <info> tag if it was started.
1365 if (!xs.isLastTagCR())
1368 xs << xml::EndTag("info");
1373 // Deal with the abstract outside <info> if it is not standard (i.e. its tag is layout-defined).
1374 if (!secInfo.abstract.empty() && !hasStandardAbstract) {
1375 // Assert: style.docbookforceabstracttag() != NONE.
1376 xs << xml::StartTag(style.docbookforceabstracttag());
1378 for (auto const &p : secInfo.abstract)
1379 makeAny(text, buf, xs, ourparams, paragraphs.iterator_at(p));
1380 xs << xml::EndTag(style.docbookforceabstracttag());
1384 // Skip all the text that has just been generated.
1385 par = paragraphs.iterator_at(secInfo.epit);
1387 // No <info> tag to generate, proceed as for normal paragraphs.
1388 par = makeAny(text, buf, xs, ourparams, par);
1391 // Generate this paragraph, as it has nothing special.
1392 par = makeAny(text, buf, xs, ourparams, par);
1396 // If need be, close <section>s, but only at the end of the document (otherwise, dealt with at the beginning
1398 while (!headerLevels.empty() && headerLevels.top().first > Layout::NOT_IN_TOC) {
1399 docstring tag = from_utf8("</" + headerLevels.top().second + ">");
1401 xs << XMLStream::ESCAPE_NONE << tag;