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/lassert.h"
35 #include "support/textutils.h"
43 using namespace lyx::support;
49 std::string fontToDocBookTag(xml::FontTypes type)
52 case xml::FontTypes::FT_EMPH:
53 case xml::FontTypes::FT_BOLD:
55 case xml::FontTypes::FT_NOUN:
57 case xml::FontTypes::FT_UBAR:
58 case xml::FontTypes::FT_WAVE:
59 case xml::FontTypes::FT_DBAR:
60 case xml::FontTypes::FT_SOUT:
61 case xml::FontTypes::FT_XOUT:
62 case xml::FontTypes::FT_ITALIC:
63 case xml::FontTypes::FT_UPRIGHT:
64 case xml::FontTypes::FT_SLANTED:
65 case xml::FontTypes::FT_SMALLCAPS:
66 case xml::FontTypes::FT_ROMAN:
67 case xml::FontTypes::FT_SANS:
69 case xml::FontTypes::FT_TYPE:
71 case xml::FontTypes::FT_SIZE_TINY:
72 case xml::FontTypes::FT_SIZE_SCRIPT:
73 case xml::FontTypes::FT_SIZE_FOOTNOTE:
74 case xml::FontTypes::FT_SIZE_SMALL:
75 case xml::FontTypes::FT_SIZE_NORMAL:
76 case xml::FontTypes::FT_SIZE_LARGE:
77 case xml::FontTypes::FT_SIZE_LARGER:
78 case xml::FontTypes::FT_SIZE_LARGEST:
79 case xml::FontTypes::FT_SIZE_HUGE:
80 case xml::FontTypes::FT_SIZE_HUGER:
81 case xml::FontTypes::FT_SIZE_INCREASE:
82 case xml::FontTypes::FT_SIZE_DECREASE:
90 string fontToRole(xml::FontTypes type)
92 // Specific fonts are achieved with roles. The only common ones are "" for basic emphasis,
93 // and "bold"/"strong" for bold. With some specific options, other roles are copied into
94 // HTML output (via the DocBook XSLT sheets); otherwise, if not recognised, they are just ignored.
95 // Hence, it is not a problem to have many roles by default here.
96 // See https://www.sourceware.org/ml/docbook/2003-05/msg00269.html
98 case xml::FontTypes::FT_ITALIC:
99 case xml::FontTypes::FT_EMPH:
101 case xml::FontTypes::FT_BOLD:
103 case xml::FontTypes::FT_NOUN: // Outputs a <person>
104 case xml::FontTypes::FT_TYPE: // Outputs a <code>
106 case xml::FontTypes::FT_UBAR:
109 // All other roles are non-standard for DocBook.
111 case xml::FontTypes::FT_WAVE:
113 case xml::FontTypes::FT_DBAR:
115 case xml::FontTypes::FT_SOUT:
117 case xml::FontTypes::FT_XOUT:
119 case xml::FontTypes::FT_UPRIGHT:
121 case xml::FontTypes::FT_SLANTED:
123 case xml::FontTypes::FT_SMALLCAPS:
125 case xml::FontTypes::FT_ROMAN:
127 case xml::FontTypes::FT_SANS:
129 case xml::FontTypes::FT_SIZE_TINY:
131 case xml::FontTypes::FT_SIZE_SCRIPT:
132 return "size_script";
133 case xml::FontTypes::FT_SIZE_FOOTNOTE:
134 return "size_footnote";
135 case xml::FontTypes::FT_SIZE_SMALL:
137 case xml::FontTypes::FT_SIZE_NORMAL:
138 return "size_normal";
139 case xml::FontTypes::FT_SIZE_LARGE:
141 case xml::FontTypes::FT_SIZE_LARGER:
142 return "size_larger";
143 case xml::FontTypes::FT_SIZE_LARGEST:
144 return "size_largest";
145 case xml::FontTypes::FT_SIZE_HUGE:
147 case xml::FontTypes::FT_SIZE_HUGER:
149 case xml::FontTypes::FT_SIZE_INCREASE:
150 return "size_increase";
151 case xml::FontTypes::FT_SIZE_DECREASE:
152 return "size_decrease";
159 string fontToAttribute(xml::FontTypes type) {
160 // If there is a role (i.e. nonstandard use of a tag), output the attribute. Otherwise, the sheer tag is sufficient
162 string role = fontToRole(type);
164 return "role='" + role + "'";
170 // Higher-level convenience functions.
172 void openParTag(XMLStream & xs, const Paragraph * par, const Paragraph * prevpar)
177 // When should the wrapper be opened here? Only if the previous paragraph has the SAME wrapper tag
178 // (usually, they won't have the same layout) and the CURRENT one allows merging.
179 // The main use case is author information in several paragraphs: if the name of the author is the
180 // first paragraph of an author, then merging with the previous tag does not make sense. Say the
181 // next paragraph is the affiliation, then it should be output in the same <author> tag (different
182 // layout, same wrapper tag).
183 Layout const & lay = par->layout();
184 bool openWrapper = lay.docbookwrappertag() != "NONE";
185 if (prevpar != nullptr) {
186 Layout const & prevlay = prevpar->layout();
187 if (prevlay.docbookwrappertag() != "NONE") {
188 if (prevlay.docbookwrappertag() == lay.docbookwrappertag() &&
189 prevlay.docbookwrapperattr() == lay.docbookwrapperattr())
190 openWrapper = !lay.docbookwrappermergewithprevious();
198 xml::openTag(xs, lay.docbookwrappertag(), lay.docbookwrapperattr(), lay.docbookwrappertagtype());
200 const string & tag = lay.docbooktag();
202 auto xmltag = xml::ParTag(tag, lay.docbookattr());
203 if (!xs.isTagOpen(xmltag, 1)) { // Don't nest a paragraph directly in a paragraph.
204 // TODO: required or not?
205 // TODO: avoid creating a ParTag object just for this query...
206 xml::openTag(xs, lay.docbooktag(), lay.docbookattr(), lay.docbooktagtype());
207 xml::openTag(xs, lay.docbookinnertag(), lay.docbookinnerattr(), lay.docbookinnertagtype());
211 xml::openTag(xs, lay.docbookitemtag(), lay.docbookitemattr(), lay.docbookitemtagtype());
212 xml::openTag(xs, lay.docbookiteminnertag(), lay.docbookiteminnerattr(), lay.docbookiteminnertagtype());
216 void closeParTag(XMLStream & xs, Paragraph const * par, Paragraph const * nextpar)
221 // See comment in openParTag.
222 Layout const & lay = par->layout();
223 bool closeWrapper = lay.docbookwrappertag() != "NONE";
224 if (nextpar != nullptr) {
225 Layout const & nextlay = nextpar->layout();
226 if (nextlay.docbookwrappertag() != "NONE") {
227 if (nextlay.docbookwrappertag() == lay.docbookwrappertag() &&
228 nextlay.docbookwrapperattr() == lay.docbookwrapperattr())
229 closeWrapper = !nextlay.docbookwrappermergewithprevious();
236 xml::closeTag(xs, lay.docbookiteminnertag(), lay.docbookiteminnertagtype());
237 xml::closeTag(xs, lay.docbookitemtag(), lay.docbookitemtagtype());
238 xml::closeTag(xs, lay.docbookinnertag(), lay.docbookinnertagtype());
239 xml::closeTag(xs, lay.docbooktag(), lay.docbooktagtype());
241 xml::closeTag(xs, lay.docbookwrappertag(), lay.docbookwrappertagtype());
245 void makeBibliography(
249 OutputParams const & runparams,
250 ParagraphList::const_iterator const & par)
252 // If this is the first paragraph in a bibliography, open the bibliography tag.
253 auto const * pbegin_before = text.paragraphs().getParagraphBefore(par);
254 if (pbegin_before == nullptr || (pbegin_before && pbegin_before->layout().latextype != LATEX_BIB_ENVIRONMENT)) {
255 xs << xml::StartTag("bibliography");
259 // Start the precooked bibliography entry. This is very much like opening a paragraph tag.
260 // Don't forget the citation ID!
262 for (auto i = 0; i < par->size(); ++i) {
263 Inset const *ip = par->getInset(i);
266 if (const auto * bibitem = dynamic_cast<const InsetBibitem*>(ip)) {
267 auto id = xml::cleanID(bibitem->getParam("key"));
268 attr = from_utf8("xml:id='") + id + from_utf8("'");
272 xs << xml::StartTag(from_utf8("bibliomixed"), attr);
274 // Generate the entry. Concatenate the different parts of the paragraph if any.
275 auto const begin = text.paragraphs().begin();
276 auto pars = par->simpleDocBookOnePar(buf, runparams, text.outerFont(std::distance(begin, par)), 0);
277 for (auto & parXML : pars)
278 xs << XMLStream::ESCAPE_NONE << parXML;
280 // End the precooked bibliography entry.
281 xs << xml::EndTag("bibliomixed");
284 // If this is the last paragraph in a bibliography, close the bibliography tag.
285 auto const end = text.paragraphs().end();
288 bool endBibliography = nextpar == end || nextpar->layout().latextype != LATEX_BIB_ENVIRONMENT;
290 if (endBibliography) {
291 xs << xml::EndTag("bibliography");
301 OutputParams const & runparams,
302 ParagraphList::const_iterator const & par)
305 auto const begin = text.paragraphs().begin();
306 auto const end = text.paragraphs().end();
307 auto prevpar = text.paragraphs().getParagraphBefore(par);
309 // We want to open the paragraph tag if:
310 // (i) the current layout permits multiple paragraphs
311 // (ii) we are either not already inside a paragraph (HTMLIsBlock) OR
312 // we are, but this is not the first paragraph
314 // But there is also a special case, and we first see whether we are in it.
315 // We do not want to open the paragraph tag if this paragraph contains
316 // only one item, and that item is "inline", i.e., not HTMLIsBlock (such
317 // as a branch). On the other hand, if that single item has a font change
318 // applied to it, then we still do need to open the paragraph.
320 // Obviously, this is very fragile. The main reason we need to do this is
321 // because of branches, e.g., a branch that contains an entire new section.
322 // We do not really want to wrap that whole thing in a <div>...</div>.
323 bool special_case = false;
324 Inset const *specinset = par->size() == 1 ? par->getInset(0) : nullptr;
325 if (specinset && !specinset->getLayout().htmlisblock()) { // TODO: Convert htmlisblock to a DocBook parameter?
326 Layout const &style = par->layout();
327 FontInfo const first_font = style.labeltype == LABEL_MANUAL ?
328 style.labelfont : style.font;
329 FontInfo const our_font =
330 par->getFont(buf.masterBuffer()->params(), 0,
331 text.outerFont(std::distance(begin, par))).fontInfo();
333 if (first_font == our_font)
337 size_t nInsets = std::distance(par->insetList().begin(), par->insetList().end());
338 auto parSize = (size_t) par->size();
340 // If this LyX code does not produce any output, it can be safely ignored in the following checks: if this thing
341 // is present in the paragraph, it has no impact on the definition of the special case (i.e. whether or not
342 // a <para> tag should be output).
343 auto isLyxCodeToIgnore = [](InsetCode x) { return x == TOC_CODE || x == NOTE_CODE; };
345 // TODO: if a paragraph *only* contains floats, listings, bibliographies, etc., should this be considered as a
346 // special case? If so, the code could be largely simplifies (all the calls to all_of, basically) and optimised
347 // at the compilation stage.
349 // Plain layouts must be ignored.
350 special_case |= buf.params().documentClass().isPlainLayout(par->layout()) && !runparams.docbook_force_pars;
351 // Equations do not deserve their own paragraph (DocBook allows them outside paragraphs).
352 // Exception: any case that generates an <inlineequation> must still get a paragraph to be valid.
353 special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), [](InsetList::Element inset) {
354 return inset.inset && inset.inset->asInsetMath() && inset.inset->asInsetMath()->getType() != hullSimple;
356 // Tables do not deserve their own paragraphs (DocBook allows them outside paragraphs).
357 special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), [isLyxCodeToIgnore](InsetList::Element inset) {
358 return inset.inset->lyxCode() == TABULAR_CODE || isLyxCodeToIgnore(inset.inset->lyxCode());
360 // Floats cannot be in paragraphs.
361 special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), [isLyxCodeToIgnore](InsetList::Element inset) {
362 return inset.inset->lyxCode() == FLOAT_CODE || isLyxCodeToIgnore(inset.inset->lyxCode());
364 // Bibliographies cannot be in paragraphs. Bibitems should still be handled as paragraphs, though
365 // (see makeParagraphBibliography).
366 special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), [isLyxCodeToIgnore](InsetList::Element inset) {
367 return inset.inset->lyxCode() == BIBTEX_CODE || isLyxCodeToIgnore(inset.inset->lyxCode());
369 // ERTs are in comments, not paragraphs.
370 special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), [isLyxCodeToIgnore](InsetList::Element inset) {
371 return inset.inset->lyxCode() == ERT_CODE || isLyxCodeToIgnore(inset.inset->lyxCode());
373 // Listings should not get into their own paragraph.
374 special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), [isLyxCodeToIgnore](InsetList::Element inset) {
375 return inset.inset->lyxCode() == LISTINGS_CODE || isLyxCodeToIgnore(inset.inset->lyxCode());
377 // Boxes cannot get into their own paragraph.
378 special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), [isLyxCodeToIgnore](InsetList::Element inset) {
379 return inset.inset->lyxCode() == BOX_CODE || isLyxCodeToIgnore(inset.inset->lyxCode());
381 // Includes should not have a paragraph.
382 special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), [isLyxCodeToIgnore](InsetList::Element inset) {
383 return inset.inset->lyxCode() == INCLUDE_CODE || isLyxCodeToIgnore(inset.inset->lyxCode());
385 // Glossaries should not have a paragraph.
386 special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), [isLyxCodeToIgnore](InsetList::Element inset) {
387 return inset.inset->lyxCode() == NOMENCL_PRINT_CODE || isLyxCodeToIgnore(inset.inset->lyxCode());
390 bool const open_par = runparams.docbook_make_pars
391 && !runparams.docbook_in_par
394 // We want to issue the closing tag if either:
395 // (i) We opened it, and either docbook_in_par is false,
396 // or we're not in the last paragraph, anyway.
397 // (ii) We didn't open it and docbook_in_par is true,
398 // but we are in the first par, and there is a next par.
399 bool const close_par = open_par && !runparams.docbook_in_par;
401 // Determine if this paragraph has some real content. Things like new pages are not caught
402 // by Paragraph::empty(), even though they do not generate anything useful in DocBook.
403 // Thus, remove all spaces (including new lines: \r, \n) before checking for emptiness.
404 // std::all_of allows doing this check without having to copy the string.
405 // Open and close tags around each contained paragraph.
408 auto pars = par->simpleDocBookOnePar(buf, runparams, text.outerFont(distance(begin, par)), 0, nextpar == end, special_case);
409 for (docstring const & parXML : pars) {
410 if (!xml::isNotOnlySpace(parXML))
414 openParTag(xs, &*par, prevpar);
416 xs << XMLStream::ESCAPE_NONE << parXML;
419 closeParTag(xs, &*par, (nextpar != end) ? &*nextpar : nullptr);
424 void makeEnvironment(Text const &text,
427 OutputParams const &runparams,
428 ParagraphList::const_iterator const & par)
431 auto const end = text.paragraphs().end();
435 // Special cases for listing-like environments provided in layouts. This is quite ad-hoc, but provides a useful
436 // default. This should not be used by too many environments (only LyX-Code right now).
437 // This would be much simpler if LyX-Code was implemented as InsetListings...
438 bool mimicListing = false;
439 bool ignoreFonts = false;
440 if (par->layout().docbooktag() == "programlisting") {
445 // Output the opening tag for this environment, but only if it has not been previously opened (condition
446 // implemented in openParTag).
447 auto prevpar = text.paragraphs().getParagraphBefore(par);
448 openParTag(xs, &*par, prevpar); // TODO: switch in layout for par/block?
450 // Generate the contents of this environment. There is a special case if this is like some environment.
451 Layout const & style = par->layout();
452 if (style.latextype == LATEX_COMMAND) {
453 // Nothing to do (otherwise, infinite loops).
454 } else if (style.latextype == LATEX_ENVIRONMENT) {
455 // Generate the paragraph, if need be.
456 auto pars = par->simpleDocBookOnePar(buf, runparams, text.outerFont(std::distance(text.paragraphs().begin(), par)), 0, false, ignoreFonts);
459 auto p = pars.begin();
460 while (p != pars.end()) {
461 xml::openTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnerattr(),
462 par->layout().docbookiteminnertagtype());
463 xs << XMLStream::ESCAPE_NONE << *p;
464 xml::closeTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnertagtype());
467 // Insert a new line after each "paragraph" (i.e. line in the listing), except for the last one.
468 // Otherwise, there would one more new line in the output than in the LyX document.
473 for (auto const & p : pars) {
474 xml::openTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnerattr(),
475 par->layout().docbookiteminnertagtype());
476 xs << XMLStream::ESCAPE_NONE << p;
477 xml::closeTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnertagtype());
481 makeAny(text, buf, xs, runparams, par);
484 // Close the environment.
485 closeParTag(xs, &*par, (nextpar != end) ? &*nextpar : nullptr); // TODO: switch in layout for par/block?
489 ParagraphList::const_iterator findEndOfEnvironment(
490 ParagraphList::const_iterator const & pstart,
491 ParagraphList::const_iterator const & pend)
493 // Copy-paste from XHTML. Should be factored out at some point...
494 ParagraphList::const_iterator p = pstart;
495 Layout const & bstyle = p->layout();
496 size_t const depth = p->params().depth();
497 for (++p; p != pend; ++p) {
498 Layout const & style = p->layout();
499 // It shouldn't happen that e.g. a section command occurs inside
500 // a quotation environment, at a higher depth, but as of 6/2009,
501 // it can happen. We pretend that it's just at lowest depth.
502 if (style.latextype == LATEX_COMMAND)
505 // If depth is down, we're done
506 if (p->params().depth() < depth)
509 // If depth is up, we're not done
510 if (p->params().depth() > depth)
513 // FIXME I am not sure about the first check.
514 // Surely we *could* have different layouts that count as
515 // LATEX_PARAGRAPH, right?
516 if (style.latextype == LATEX_PARAGRAPH || style != bstyle)
523 ParagraphList::const_iterator makeListEnvironment(Text const &text,
526 OutputParams const &runparams,
527 ParagraphList::const_iterator const & begin)
531 auto const end = text.paragraphs().end();
532 auto const envend = findEndOfEnvironment(par, end);
534 // Output the opening tag for this environment.
535 Layout const & envstyle = par->layout();
536 xml::openTag(xs, envstyle.docbookwrappertag(), envstyle.docbookwrapperattr(), envstyle.docbookwrappertagtype());
537 xml::openTag(xs, envstyle.docbooktag(), envstyle.docbookattr(), envstyle.docbooktagtype());
539 // Handle the content of the list environment, item by item.
540 while (par != envend) {
541 // Skip this paragraph if it is both empty and the last one (otherwise, there may be deeper paragraphs after).
544 if (par->empty() && nextpar == envend)
547 // Open the item wrapper.
548 Layout const & style = par->layout();
549 xml::openTag(xs, style.docbookitemwrappertag(), style.docbookitemwrapperattr(),
550 style.docbookitemwrappertagtype());
552 // Generate the label, if need be. If it is taken from the text, sep != 0 and corresponds to the first
553 // character after the label.
555 if (style.labeltype != LABEL_NO_LABEL && style.docbookitemlabeltag() != "NONE") {
556 if (style.labeltype == LABEL_MANUAL) {
557 // Only variablelist gets here (or similar items defined as an extension in the layout).
558 xml::openTag(xs, style.docbookitemlabeltag(), style.docbookitemlabelattr(),
559 style.docbookitemlabeltagtype());
560 sep = 1 + par->firstWordDocBook(xs, runparams);
561 xml::closeTag(xs, style.docbookitemlabeltag(), style.docbookitemlabeltagtype());
563 // Usual cases: maybe there is something specified at the layout level. Highly unlikely, though.
564 docstring const lbl = par->params().labelString();
567 xml::openTag(xs, style.docbookitemlabeltag(), style.docbookitemlabelattr(),
568 style.docbookitemlabeltagtype());
570 xml::closeTag(xs, style.docbookitemlabeltag(), style.docbookitemlabeltagtype());
575 // Open the item (after the wrapper and the label).
576 xml::openTag(xs, style.docbookitemtag(), style.docbookitemattr(), style.docbookitemtagtype());
578 // Generate the content of the item.
579 if (sep < par->size()) {
580 auto pars = par->simpleDocBookOnePar(buf, runparams,
581 text.outerFont(std::distance(text.paragraphs().begin(), par)), sep);
582 for (auto &p : pars) {
583 xml::openTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnerattr(),
584 par->layout().docbookiteminnertagtype());
585 xs << XMLStream::ESCAPE_NONE << p;
586 xml::closeTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnertagtype());
589 // DocBook doesn't like emptiness.
590 xml::compTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnerattr(),
591 par->layout().docbookiteminnertagtype());
594 // If the next item is deeper, it must go entirely within this item (do it recursively).
595 // By construction, with findEndOfEnvironment, depth can only stay constant or increase, never decrease.
596 depth_type currentDepth = par->getDepth();
598 while (par != envend && par->getDepth() != currentDepth)
599 par = makeAny(text, buf, xs, runparams, par);
600 // Usually, this loop only makes one iteration, except in complex scenarios, like an item with a paragraph,
601 // a list, and another paragraph; or an item with two types of list (itemise then enumerate, for instance).
604 xml::closeTag(xs, style.docbookitemtag(), style.docbookitemtagtype());
605 xml::closeTag(xs, style.docbookitemwrappertag(), style.docbookitemwrappertagtype());
608 // Close this environment in exactly the same way as it was opened.
609 xml::closeTag(xs, envstyle.docbooktag(), envstyle.docbooktagtype());
610 xml::closeTag(xs, envstyle.docbookwrappertag(), envstyle.docbookwrappertagtype());
620 OutputParams const & runparams,
621 ParagraphList::const_iterator const & par)
624 // Unlike XHTML, no need for labels, as they are handled by DocBook tags.
625 auto const begin = text.paragraphs().begin();
626 auto const end = text.paragraphs().end();
630 // Generate this command.
631 auto prevpar = text.paragraphs().getParagraphBefore(par);
632 openParTag(xs, &*par, prevpar);
634 auto pars = par->simpleDocBookOnePar(buf, runparams,text.outerFont(distance(begin, par)));
635 for (auto & parXML : pars)
636 // TODO: decide what to do with openParTag/closeParTag in new lines.
637 xs << XMLStream::ESCAPE_NONE << parXML;
639 closeParTag(xs, &*par, (nextpar != end) ? &*nextpar : nullptr);
643 bool isLayoutSectioning(Layout const & lay)
645 if (lay.docbooksection()) // Special case: some DocBook styles must be handled as sections.
647 else if (lay.category() == from_utf8("Sectioning") || lay.docbooktag() == "section") // Generic case.
648 return lay.toclevel != Layout::NOT_IN_TOC;
653 bool isLayoutSectioningOrSimilar(Layout const & lay)
655 return isLayoutSectioning(lay) || lay.docbooktag() == "bridgehead";
659 using DocBookDocumentSectioning = tuple<bool, pit_type>;
662 struct DocBookInfoTag
664 const set<pit_type> shouldBeInInfo;
665 const set<pit_type> mustBeInInfo; // With the notable exception of the abstract!
666 const set<pit_type> abstract;
667 const bool abstractLayout;
671 DocBookInfoTag(const set<pit_type> & shouldBeInInfo, const set<pit_type> & mustBeInInfo,
672 const set<pit_type> & abstract, bool abstractLayout, pit_type bpit, pit_type epit) :
673 shouldBeInInfo(shouldBeInInfo), mustBeInInfo(mustBeInInfo), abstract(abstract),
674 abstractLayout(abstractLayout), bpit(bpit), epit(epit) {}
678 DocBookDocumentSectioning hasDocumentSectioning(ParagraphList const ¶graphs, pit_type bpit, pit_type const epit) {
679 bool documentHasSections = false;
681 while (bpit < epit) {
682 Layout const &style = paragraphs[bpit].layout();
683 documentHasSections |= isLayoutSectioningOrSimilar(style);
685 if (documentHasSections)
689 // Paragraphs before the first section: [ runparams.par_begin ; eppit )
691 return make_tuple(documentHasSections, bpit);
695 bool hasOnlyNotes(Paragraph const & par)
697 // Precondition: the paragraph is not empty. Otherwise, the function will always return true...
698 for (int i = 0; i < par.size(); ++i)
699 // If you find something that is not an inset (like actual text) or an inset that is not a note,
701 if (!par.isInset(i) || par.getInset(i)->lyxCode() != NOTE_CODE)
704 // An empty paragraph may still require some output.
705 if (par.layout().docbooksection())
708 // There should be really no content here.
713 DocBookInfoTag getParagraphsWithInfo(ParagraphList const ¶graphs,
714 pit_type bpit, pit_type const epit,
715 // Typically, bpit is the beginning of the document and epit the end of the
716 // document *or* the first section.
717 bool documentHasSections,
718 bool detectUnlayoutedAbstract
719 // Whether paragraphs with no specific layout should be detected as abstracts.
720 // For inner sections, an abstract should only be detected if it has a specific
721 // layout. For others, anything that might look like an abstract should be sought.
723 set<pit_type> shouldBeInInfo;
724 set<pit_type> mustBeInInfo;
725 set<pit_type> abstractWithLayout;
726 set<pit_type> abstractNoLayout;
728 // Find the first non empty paragraph by mutating bpit.
729 while (bpit < epit) {
730 Paragraph const &par = paragraphs[bpit];
731 if (par.empty() || hasOnlyNotes(par))
737 // Traverse everything that might belong to <info>.
738 bool hasAbstractLayout = false;
739 pit_type cpit = bpit;
740 for (; cpit < epit; ++cpit) {
741 // Skip paragraphs that don't generate anything in DocBook.
742 Paragraph const & par = paragraphs[cpit];
743 Layout const &style = par.layout();
744 if (hasOnlyNotes(par))
747 // There should never be any section here, except for the first paragraph (a title can be part of <info>).
748 // (Just a sanity check: if this fails, this function could end up processing the whole document.)
749 if (cpit != bpit && isLayoutSectioningOrSimilar(par.layout())) {
750 LYXERR0("Assertion failed: section found in potential <info> paragraphs.");
754 // If this is marked as an abstract by the layout, put it in the right set.
755 if (style.docbookabstract()) {
756 hasAbstractLayout = true;
757 abstractWithLayout.emplace(cpit);
761 // Based on layout information, store this paragraph in one set: should be in <info>, must be,
762 // or abstract (either because of layout or of position).
763 if (style.docbookininfo() == "always")
764 mustBeInInfo.emplace(cpit);
765 else if (style.docbookininfo() == "maybe")
766 shouldBeInInfo.emplace(cpit);
767 else if (documentHasSections && !hasAbstractLayout && detectUnlayoutedAbstract &&
768 (style.docbooktag() == "NONE" || style.docbooktag() == "para") &&
769 style.docbookwrappertag() == "NONE")
770 // In this case, it is very likely that style.docbookininfo() == "never"! Be extra careful
771 // about anything that gets caught here.
772 abstractNoLayout.emplace(cpit);
773 else // This should definitely not be in <info>.
776 // Now, cpit points to the first paragraph that no more has things that could go in <info>.
777 // bpit is the beginning of the <info> part.
779 return DocBookInfoTag(shouldBeInInfo, mustBeInInfo,
780 hasAbstractLayout ? abstractWithLayout : abstractNoLayout,
781 hasAbstractLayout, bpit, cpit);
784 } // end anonymous namespace
787 std::set<const Inset *> gatherInfo(ParagraphList::const_iterator par)
789 // This function has a structure highly similar to makeAny and its friends. It's only made to be called on what
790 // should become the document's <abstract>.
791 std::set<const Inset *> values;
793 // If this kind of layout should be ignored, already leave.
794 if (par->layout().docbooktag() == "IGNORE")
797 // If this should go in info, mark it as such. Dive deep into the abstract, as it may hide many things that
798 // DocBook doesn't want to be inside the abstract.
799 for (pos_type i = 0; i < par->size(); ++i) {
800 if (par->getInset(i) && par->getInset(i)->asInsetText()) {
801 InsetText const *inset = par->getInset(i)->asInsetText();
803 if (inset->getLayout().docbookininfo() != "never") {
804 values.insert(inset);
806 auto subpar = inset->paragraphs().begin();
807 while (subpar != inset->paragraphs().end()) {
808 values.merge(gatherInfo(subpar));
819 ParagraphList::const_iterator makeAny(Text const &text,
822 OutputParams const &runparams,
823 ParagraphList::const_iterator par)
825 bool ignoreParagraph = false;
827 // If this kind of layout should be ignored, already leave.
828 ignoreParagraph |= par->layout().docbooktag() == "IGNORE";
830 // For things that should go into <info>, check the variable rp.docbook_generate_info. This does not apply to the
832 bool isAbstract = par->layout().docbookabstract() || par->layout().docbooktag() == "abstract";
833 ignoreParagraph |= !isAbstract && par->layout().docbookininfo() != "never" && !runparams.docbook_generate_info;
835 // Switch on the type of paragraph to call the right handler.
836 if (!ignoreParagraph) {
837 switch (par->layout().latextype) {
839 makeCommand(text, buf, xs, runparams, par);
841 case LATEX_ENVIRONMENT:
842 makeEnvironment(text, buf, xs, runparams, par);
844 case LATEX_LIST_ENVIRONMENT:
845 case LATEX_ITEM_ENVIRONMENT:
846 // Only case when makeAny() might consume more than one paragraph.
847 return makeListEnvironment(text, buf, xs, runparams, par);
848 case LATEX_PARAGRAPH:
849 makeParagraph(text, buf, xs, runparams, par);
851 case LATEX_BIB_ENVIRONMENT:
852 makeBibliography(text, buf, xs, runparams, par);
857 // For cases that are not lists, the next paragraph to handle is the next one.
863 xml::FontTag docbookStartFontTag(xml::FontTypes type)
865 return xml::FontTag(from_utf8(fontToDocBookTag(type)), from_utf8(fontToAttribute(type)), type);
869 xml::EndFontTag docbookEndFontTag(xml::FontTypes type)
871 return xml::EndFontTag(from_utf8(fontToDocBookTag(type)), type);
875 void outputDocBookInfo(
879 OutputParams const & runparams,
880 ParagraphList const & paragraphs,
881 DocBookInfoTag const & info)
883 // Perform an additional check on the abstract. Sometimes, there are many paragraphs that should go
884 // into the abstract, but none generates actual content. Thus, first generate to a temporary stream,
885 // then only create the <abstract> tag if these paragraphs generate some content.
886 // This check must be performed *before* a decision on whether or not to output <info> is made.
887 bool hasAbstract = !info.abstract.empty();
889 set<const Inset *> infoInsets; // Paragraphs that should go into <info>, but are hidden in an <abstract>
890 // paragraph. (This happens for quite a few layouts, unfortunately.)
893 // Generate the abstract XML into a string before further checks.
894 // Usually, makeAny only generates one paragraph at a time. However, for the specific case of lists, it might
895 // generate more than one paragraph, as indicated in the return value.
896 odocstringstream os2;
900 rp.docbook_generate_info = false;
902 set<pit_type> doneParas; // Paragraphs that have already been converted (mostly to deal with lists).
903 for (auto const & p : info.abstract) {
904 if (doneParas.find(p) == doneParas.end()) {
905 auto oldPar = paragraphs.iterator_at(p);
906 auto newPar = makeAny(text, buf, xs2, rp, oldPar);
908 infoInsets.merge(gatherInfo(oldPar));
910 // Insert the indices of all the paragraphs that were just generated (typically, one).
911 // **Make the hypothesis that, when an abstract has a list, all its items are consecutive.**
912 // Otherwise, makeAny and makeListEnvironment would have to be adapted too.
914 while (oldPar != newPar) {
915 doneParas.emplace(id);
922 // Actually output the abstract if there is something to do. Don't count line feeds or spaces in this,
923 // even though they must be properly output if there is some abstract.
924 abstract = os2.str();
925 docstring cleaned = abstract;
926 cleaned.erase(std::remove_if(cleaned.begin(), cleaned.end(), lyx::isSpace), cleaned.end());
928 // Nothing? Then there is no abstract!
933 // The abstract must go in <info>. Otherwise, decide whether to open <info> based on the layouts.
934 bool needInfo = !info.mustBeInInfo.empty() || hasAbstract;
936 // Start the <info> tag if required.
938 xs.startDivision(false);
939 xs << xml::StartTag("info");
943 // Output the elements that should go in <info>.
944 // - First, the title.
945 for (auto pit : info.shouldBeInInfo) // Typically, the title: these elements are so important and ubiquitous
946 // that mandating a wrapper like <info> would repel users. Thus, generate them first.
947 makeAny(text, buf, xs, runparams, paragraphs.iterator_at(pit));
948 // If there is no title, generate one (required for the document to be valid).
949 // This code is called for the main document, for table cells, etc., so be precise in this condition.
950 if (text.isMainText() && info.shouldBeInInfo.empty() && !runparams.inInclude) {
951 xs << xml::StartTag("title");
952 xs << "Untitled Document";
953 xs << xml::EndTag("title");
957 // - Then, other metadata.
958 for (auto pit : info.mustBeInInfo)
959 makeAny(text, buf, xs, runparams, paragraphs.iterator_at(pit));
960 for (auto const * inset : infoInsets)
961 inset->docbook(xs, runparams);
963 // - Finally, always output the abstract as the last item of the <info>, as it requires special treatment
964 // (especially if it contains several paragraphs that are empty).
966 if (info.abstractLayout) {
967 xs << XMLStream::ESCAPE_NONE << abstract;
970 string tag = paragraphs[*info.abstract.begin()].layout().docbookforceabstracttag();
974 if (!xs.isLastTagCR())
977 xs << xml::StartTag(tag);
979 xs << XMLStream::ESCAPE_NONE << abstract;
980 xs << xml::EndTag(tag);
985 // End the <info> tag if it was started.
987 if (!xs.isLastTagCR())
990 xs << xml::EndTag("info");
997 void docbookSimpleAllParagraphs(
1001 OutputParams const & runparams)
1003 // Handle the given text, supposing it has no sections (i.e. a "simple" text). The input may vary in length
1004 // between a single paragraph to a whole document.
1005 pit_type const bpit = runparams.par_begin;
1006 pit_type const epit = runparams.par_end;
1007 ParagraphList const ¶graphs = text.paragraphs();
1009 // First, the <info> tag.
1010 DocBookInfoTag info = getParagraphsWithInfo(paragraphs, bpit, epit, false, true);
1011 outputDocBookInfo(text, buf, xs, runparams, paragraphs, info);
1013 // Then, the content. It starts where the <info> ends.
1014 auto par = paragraphs.iterator_at(info.epit);
1015 auto end = paragraphs.iterator_at(epit);
1016 while (par != end) {
1017 if (!hasOnlyNotes(*par))
1018 par = makeAny(text, buf, xs, runparams, par);
1025 void docbookParagraphs(Text const &text,
1028 OutputParams const &runparams) {
1029 ParagraphList const ¶graphs = text.paragraphs();
1030 if (runparams.par_begin == runparams.par_end) {
1031 runparams.par_begin = 0;
1032 runparams.par_end = paragraphs.size();
1034 pit_type bpit = runparams.par_begin;
1035 pit_type const epit = runparams.par_end;
1036 LASSERT(bpit < epit,
1038 xs << XMLStream::ESCAPE_NONE << "<!-- DocBook output error! -->\n";
1042 std::stack<std::pair<int, string>> headerLevels; // Used to determine when to open/close sections: store the depth
1043 // of the section and the tag that was used to open it.
1045 // Detect whether the document contains sections. If there are no sections, treatment is largely simplified.
1046 // In particular, there can't be an abstract, unless it is manually marked.
1047 bool documentHasSections;
1049 tie(documentHasSections, eppit) = hasDocumentSectioning(paragraphs, bpit, epit);
1051 // Deal with "simple" documents, i.e. those without sections.
1052 if (!documentHasSections) {
1053 docbookSimpleAllParagraphs(text, buf, xs, runparams);
1057 // Output the first <info> tag (or just the title).
1058 DocBookInfoTag info = getParagraphsWithInfo(paragraphs, bpit, eppit, true, true);
1059 outputDocBookInfo(text, buf, xs, runparams, paragraphs, info);
1062 // Then, iterate through the paragraphs of this document.
1063 bool currentlyInAppendix = false;
1065 auto par = text.paragraphs().iterator_at(bpit);
1066 auto end = text.paragraphs().iterator_at(epit);
1067 while (par != end) {
1068 OutputParams ourparams = runparams;
1070 if (par->params().startOfAppendix())
1071 currentlyInAppendix = true;
1072 if (hasOnlyNotes(*par)) {
1077 Layout const &style = par->layout();
1079 // Think about adding <section> and/or </section>s.
1080 if (isLayoutSectioning(style)) {
1081 int level = style.toclevel;
1083 // Need to close a previous section if it has the same level or a higher one (close <section> if opening a
1084 // <h2> after a <h2>, <h3>, <h4>, <h5> or <h6>). More examples:
1085 // - current: h2; back: h1; do not close any <section>
1086 // - current: h1; back: h2; close two <section> (first the <h2>, then the <h1>, so a new <h1> can come)
1087 while (!headerLevels.empty() && level <= headerLevels.top().first) {
1088 // Output the tag only if it corresponds to a legit section.
1089 int stackLevel = headerLevels.top().first;
1090 if (stackLevel != Layout::NOT_IN_TOC) {
1091 xs << xml::EndTag(headerLevels.top().second);
1097 // Open the new section: first push it onto the stack, then output it in DocBook.
1098 string sectionTag = (currentlyInAppendix && style.docbooksectiontag() == "chapter") ?
1099 "appendix" : style.docbooksectiontag();
1100 headerLevels.push(std::make_pair(level, sectionTag));
1102 // Some sectioning-like elements should not be output (such as FrontMatter).
1103 if (level != Layout::NOT_IN_TOC) {
1104 // Look for a label in the title, i.e. a InsetLabel as a child.
1105 docstring id = docstring();
1106 for (pos_type i = 0; i < par->size(); ++i) {
1107 Inset const *inset = par->getInset(i);
1109 if (auto label = dynamic_cast<InsetLabel const *>(inset)) {
1110 // Generate the attributes for the section if need be.
1111 id += "xml:id=\"" + xml::cleanID(label->screenLabel()) + "\"";
1113 // Don't output the ID as a DocBook <anchor>.
1114 ourparams.docbook_anchors_to_ignore.emplace(label->screenLabel());
1116 // Cannot have multiple IDs per tag. If there is another ID inset in the document, it will
1117 // be output as a DocBook anchor.
1123 // Write the open tag for this section.
1127 xs << xml::StartTag(sectionTag, attrs);
1132 // Close all sections before the bibliography.
1133 // 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)?
1134 if (!par->insetList().empty()) {
1135 Inset const *firstInset = par->getInset(0);
1136 if (firstInset && (firstInset->lyxCode() == BIBITEM_CODE || firstInset->lyxCode() == BIBTEX_CODE)) {
1137 while (!headerLevels.empty()) {
1138 int level = headerLevels.top().first;
1139 docstring tag = from_utf8("</" + headerLevels.top().second + ">");
1142 // Output the tag only if it corresponds to a legit section.
1143 if (level != Layout::NOT_IN_TOC) {
1144 xs << XMLStream::ESCAPE_NONE << tag;
1151 // Generate the <info> tag if a section was just opened.
1152 // Some sections may require abstracts (mostly parts, in books: DocBookForceAbstractTag will not be NONE),
1153 // others can still have an abstract (it must be detected so that it can be output at the right place).
1154 // 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.
1155 if (isLayoutSectioning(style)) {
1156 // This abstract may be found between the next paragraph and the next title.
1157 pit_type cpit = std::distance(text.paragraphs().begin(), par);
1158 pit_type ppit = std::get<1>(hasDocumentSectioning(paragraphs, cpit + 1L, epit));
1160 // Generate this abstract (this code corresponds to parts of outputDocBookInfo).
1161 DocBookInfoTag secInfo = getParagraphsWithInfo(paragraphs, cpit, ppit, true,
1162 style.docbookforceabstracttag() != "NONE");
1164 if (!secInfo.mustBeInInfo.empty() || !secInfo.shouldBeInInfo.empty() || !secInfo.abstract.empty()) {
1165 // Generate the <info>, if required. If DocBookForceAbstractTag != NONE, this abstract will not be in
1166 // <info>, unlike other ("standard") abstracts.
1167 bool hasStandardAbstract = !secInfo.abstract.empty() && style.docbookforceabstracttag() == "NONE";
1168 bool needInfo = !secInfo.mustBeInInfo.empty() || hasStandardAbstract;
1171 xs.startDivision(false);
1172 xs << xml::StartTag("info");
1176 // Output the elements that should go in <info>, before and after the abstract.
1177 for (auto pit : secInfo.shouldBeInInfo) // Typically, the title: these elements are so important and ubiquitous
1178 // that mandating a wrapper like <info> would repel users. Thus, generate them first.
1179 makeAny(text, buf, xs, ourparams, paragraphs.iterator_at(pit));
1180 for (auto pit : secInfo.mustBeInInfo)
1181 makeAny(text, buf, xs, ourparams, paragraphs.iterator_at(pit));
1183 // Deal with the abstract in <info> if it is standard (i.e. its tag is <abstract>).
1184 if (!secInfo.abstract.empty() && hasStandardAbstract) {
1185 if (!secInfo.abstractLayout) {
1186 xs << xml::StartTag("abstract");
1190 for (auto const &p : secInfo.abstract)
1191 makeAny(text, buf, xs, ourparams, paragraphs.iterator_at(p));
1193 if (!secInfo.abstractLayout) {
1194 xs << xml::EndTag("abstract");
1199 // End the <info> tag if it was started.
1201 if (!xs.isLastTagCR())
1204 xs << xml::EndTag("info");
1209 // Deal with the abstract outside <info> if it is not standard (i.e. its tag is layout-defined).
1210 if (!secInfo.abstract.empty() && !hasStandardAbstract) {
1211 // Assert: style.docbookforceabstracttag() != NONE.
1212 xs << xml::StartTag(style.docbookforceabstracttag());
1214 for (auto const &p : secInfo.abstract)
1215 makeAny(text, buf, xs, ourparams, paragraphs.iterator_at(p));
1216 xs << xml::EndTag(style.docbookforceabstracttag());
1220 // Skip all the text that has just been generated.
1221 par = paragraphs.iterator_at(secInfo.epit);
1223 // No <info> tag to generate, proceed as for normal paragraphs.
1224 par = makeAny(text, buf, xs, ourparams, par);
1227 // Generate this paragraph, as it has nothing special.
1228 par = makeAny(text, buf, xs, ourparams, par);
1232 // If need be, close <section>s, but only at the end of the document (otherwise, dealt with at the beginning
1234 while (!headerLevels.empty() && headerLevels.top().first > Layout::NOT_IN_TOC) {
1235 docstring tag = from_utf8("</" + headerLevels.top().second + ">");
1237 xs << XMLStream::ESCAPE_NONE << tag;