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 auto subinfos = gatherInfo(subpar);
809 for (auto & subinfo: subinfos)
810 values.insert(subinfo);
821 ParagraphList::const_iterator makeAny(Text const &text,
824 OutputParams const &runparams,
825 ParagraphList::const_iterator par)
827 bool ignoreParagraph = false;
829 // If this kind of layout should be ignored, already leave.
830 ignoreParagraph |= par->layout().docbooktag() == "IGNORE";
832 // For things that should go into <info>, check the variable rp.docbook_generate_info. This does not apply to the
834 bool isAbstract = par->layout().docbookabstract() || par->layout().docbooktag() == "abstract";
835 ignoreParagraph |= !isAbstract && par->layout().docbookininfo() != "never" && !runparams.docbook_generate_info;
837 // Switch on the type of paragraph to call the right handler.
838 if (!ignoreParagraph) {
839 switch (par->layout().latextype) {
841 makeCommand(text, buf, xs, runparams, par);
843 case LATEX_ENVIRONMENT:
844 makeEnvironment(text, buf, xs, runparams, par);
846 case LATEX_LIST_ENVIRONMENT:
847 case LATEX_ITEM_ENVIRONMENT:
848 // Only case when makeAny() might consume more than one paragraph.
849 return makeListEnvironment(text, buf, xs, runparams, par);
850 case LATEX_PARAGRAPH:
851 makeParagraph(text, buf, xs, runparams, par);
853 case LATEX_BIB_ENVIRONMENT:
854 makeBibliography(text, buf, xs, runparams, par);
859 // For cases that are not lists, the next paragraph to handle is the next one.
865 xml::FontTag docbookStartFontTag(xml::FontTypes type)
867 return xml::FontTag(from_utf8(fontToDocBookTag(type)), from_utf8(fontToAttribute(type)), type);
871 xml::EndFontTag docbookEndFontTag(xml::FontTypes type)
873 return xml::EndFontTag(from_utf8(fontToDocBookTag(type)), type);
877 void outputDocBookInfo(
881 OutputParams const & runparams,
882 ParagraphList const & paragraphs,
883 DocBookInfoTag const & info)
885 // Perform an additional check on the abstract. Sometimes, there are many paragraphs that should go
886 // into the abstract, but none generates actual content. Thus, first generate to a temporary stream,
887 // then only create the <abstract> tag if these paragraphs generate some content.
888 // This check must be performed *before* a decision on whether or not to output <info> is made.
889 bool hasAbstract = !info.abstract.empty();
891 set<const Inset *> infoInsets; // Paragraphs that should go into <info>, but are hidden in an <abstract>
892 // paragraph. (This happens for quite a few layouts, unfortunately.)
895 // Generate the abstract XML into a string before further checks.
896 // Usually, makeAny only generates one paragraph at a time. However, for the specific case of lists, it might
897 // generate more than one paragraph, as indicated in the return value.
898 odocstringstream os2;
902 rp.docbook_generate_info = false;
904 set<pit_type> doneParas; // Paragraphs that have already been converted (mostly to deal with lists).
905 for (auto const & p : info.abstract) {
906 if (doneParas.find(p) == doneParas.end()) {
907 auto oldPar = paragraphs.iterator_at(p);
908 auto newPar = makeAny(text, buf, xs2, rp, oldPar);
910 auto subinfos = gatherInfo(oldPar);
911 for (auto & subinfo: subinfos)
912 infoInsets.insert(subinfo);
914 // Insert the indices of all the paragraphs that were just generated (typically, one).
915 // **Make the hypothesis that, when an abstract has a list, all its items are consecutive.**
916 // Otherwise, makeAny and makeListEnvironment would have to be adapted too.
918 while (oldPar != newPar) {
919 doneParas.emplace(id);
926 // Actually output the abstract if there is something to do. Don't count line feeds or spaces in this,
927 // even though they must be properly output if there is some abstract.
928 abstract = os2.str();
929 docstring cleaned = abstract;
930 cleaned.erase(std::remove_if(cleaned.begin(), cleaned.end(), lyx::isSpace), cleaned.end());
932 // Nothing? Then there is no abstract!
937 // The abstract must go in <info>. Otherwise, decide whether to open <info> based on the layouts.
938 bool needInfo = !info.mustBeInInfo.empty() || hasAbstract;
940 // Start the <info> tag if required.
942 xs.startDivision(false);
943 xs << xml::StartTag("info");
947 // Output the elements that should go in <info>.
948 // - First, the title.
949 for (auto pit : info.shouldBeInInfo) // Typically, the title: these elements are so important and ubiquitous
950 // that mandating a wrapper like <info> would repel users. Thus, generate them first.
951 makeAny(text, buf, xs, runparams, paragraphs.iterator_at(pit));
952 // If there is no title, generate one (required for the document to be valid).
953 // This code is called for the main document, for table cells, etc., so be precise in this condition.
954 if (text.isMainText() && info.shouldBeInInfo.empty() && !runparams.inInclude) {
955 xs << xml::StartTag("title");
956 xs << "Untitled Document";
957 xs << xml::EndTag("title");
961 // - Then, other metadata.
962 for (auto pit : info.mustBeInInfo)
963 makeAny(text, buf, xs, runparams, paragraphs.iterator_at(pit));
964 for (auto const * inset : infoInsets)
965 inset->docbook(xs, runparams);
967 // - Finally, always output the abstract as the last item of the <info>, as it requires special treatment
968 // (especially if it contains several paragraphs that are empty).
970 if (info.abstractLayout) {
971 xs << XMLStream::ESCAPE_NONE << abstract;
974 string tag = paragraphs[*info.abstract.begin()].layout().docbookforceabstracttag();
978 if (!xs.isLastTagCR())
981 xs << xml::StartTag(tag);
983 xs << XMLStream::ESCAPE_NONE << abstract;
984 xs << xml::EndTag(tag);
989 // End the <info> tag if it was started.
991 if (!xs.isLastTagCR())
994 xs << xml::EndTag("info");
1001 void docbookSimpleAllParagraphs(
1005 OutputParams const & runparams)
1007 // Handle the given text, supposing it has no sections (i.e. a "simple" text). The input may vary in length
1008 // between a single paragraph to a whole document.
1009 pit_type const bpit = runparams.par_begin;
1010 pit_type const epit = runparams.par_end;
1011 ParagraphList const ¶graphs = text.paragraphs();
1013 // First, the <info> tag.
1014 DocBookInfoTag info = getParagraphsWithInfo(paragraphs, bpit, epit, false, true);
1015 outputDocBookInfo(text, buf, xs, runparams, paragraphs, info);
1017 // Then, the content. It starts where the <info> ends.
1018 auto par = paragraphs.iterator_at(info.epit);
1019 auto end = paragraphs.iterator_at(epit);
1020 while (par != end) {
1021 if (!hasOnlyNotes(*par))
1022 par = makeAny(text, buf, xs, runparams, par);
1029 void docbookParagraphs(Text const &text,
1032 OutputParams const &runparams) {
1033 ParagraphList const ¶graphs = text.paragraphs();
1034 if (runparams.par_begin == runparams.par_end) {
1035 runparams.par_begin = 0;
1036 runparams.par_end = paragraphs.size();
1038 pit_type bpit = runparams.par_begin;
1039 pit_type const epit = runparams.par_end;
1040 LASSERT(bpit < epit,
1042 xs << XMLStream::ESCAPE_NONE << "<!-- DocBook output error! -->\n";
1046 std::stack<std::pair<int, string>> headerLevels; // Used to determine when to open/close sections: store the depth
1047 // of the section and the tag that was used to open it.
1049 // Detect whether the document contains sections. If there are no sections, treatment is largely simplified.
1050 // In particular, there can't be an abstract, unless it is manually marked.
1051 bool documentHasSections;
1053 tie(documentHasSections, eppit) = hasDocumentSectioning(paragraphs, bpit, epit);
1055 // Deal with "simple" documents, i.e. those without sections.
1056 if (!documentHasSections) {
1057 docbookSimpleAllParagraphs(text, buf, xs, runparams);
1061 // Output the first <info> tag (or just the title).
1062 DocBookInfoTag info = getParagraphsWithInfo(paragraphs, bpit, eppit, true, true);
1063 outputDocBookInfo(text, buf, xs, runparams, paragraphs, info);
1066 // Then, iterate through the paragraphs of this document.
1067 bool currentlyInAppendix = false;
1069 auto par = text.paragraphs().iterator_at(bpit);
1070 auto end = text.paragraphs().iterator_at(epit);
1071 while (par != end) {
1072 OutputParams ourparams = runparams;
1074 if (par->params().startOfAppendix())
1075 currentlyInAppendix = true;
1076 if (hasOnlyNotes(*par)) {
1081 Layout const &style = par->layout();
1083 // Think about adding <section> and/or </section>s.
1084 if (isLayoutSectioning(style)) {
1085 int level = style.toclevel;
1087 // Need to close a previous section if it has the same level or a higher one (close <section> if opening a
1088 // <h2> after a <h2>, <h3>, <h4>, <h5> or <h6>). More examples:
1089 // - current: h2; back: h1; do not close any <section>
1090 // - current: h1; back: h2; close two <section> (first the <h2>, then the <h1>, so a new <h1> can come)
1091 while (!headerLevels.empty() && level <= headerLevels.top().first) {
1092 // Output the tag only if it corresponds to a legit section.
1093 int stackLevel = headerLevels.top().first;
1094 if (stackLevel != Layout::NOT_IN_TOC) {
1095 xs << xml::EndTag(headerLevels.top().second);
1101 // Open the new section: first push it onto the stack, then output it in DocBook.
1102 string sectionTag = (currentlyInAppendix && style.docbooksectiontag() == "chapter") ?
1103 "appendix" : style.docbooksectiontag();
1104 headerLevels.push(std::make_pair(level, sectionTag));
1106 // Some sectioning-like elements should not be output (such as FrontMatter).
1107 if (level != Layout::NOT_IN_TOC) {
1108 // Look for a label in the title, i.e. a InsetLabel as a child.
1109 docstring id = docstring();
1110 for (pos_type i = 0; i < par->size(); ++i) {
1111 Inset const *inset = par->getInset(i);
1113 if (auto label = dynamic_cast<InsetLabel const *>(inset)) {
1114 // Generate the attributes for the section if need be.
1115 id += "xml:id=\"" + xml::cleanID(label->screenLabel()) + "\"";
1117 // Don't output the ID as a DocBook <anchor>.
1118 ourparams.docbook_anchors_to_ignore.emplace(label->screenLabel());
1120 // Cannot have multiple IDs per tag. If there is another ID inset in the document, it will
1121 // be output as a DocBook anchor.
1127 // Write the open tag for this section.
1131 xs << xml::StartTag(sectionTag, attrs);
1136 // Close all sections before the bibliography.
1137 // 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)?
1138 if (!par->insetList().empty()) {
1139 Inset const *firstInset = par->getInset(0);
1140 if (firstInset && (firstInset->lyxCode() == BIBITEM_CODE || firstInset->lyxCode() == BIBTEX_CODE)) {
1141 while (!headerLevels.empty()) {
1142 int level = headerLevels.top().first;
1143 docstring tag = from_utf8("</" + headerLevels.top().second + ">");
1146 // Output the tag only if it corresponds to a legit section.
1147 if (level != Layout::NOT_IN_TOC) {
1148 xs << XMLStream::ESCAPE_NONE << tag;
1155 // Generate the <info> tag if a section was just opened.
1156 // Some sections may require abstracts (mostly parts, in books: DocBookForceAbstractTag will not be NONE),
1157 // others can still have an abstract (it must be detected so that it can be output at the right place).
1158 // 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.
1159 if (isLayoutSectioning(style)) {
1160 // This abstract may be found between the next paragraph and the next title.
1161 pit_type cpit = std::distance(text.paragraphs().begin(), par);
1162 pit_type ppit = std::get<1>(hasDocumentSectioning(paragraphs, cpit + 1L, epit));
1164 // Generate this abstract (this code corresponds to parts of outputDocBookInfo).
1165 DocBookInfoTag secInfo = getParagraphsWithInfo(paragraphs, cpit, ppit, true,
1166 style.docbookforceabstracttag() != "NONE");
1168 if (!secInfo.mustBeInInfo.empty() || !secInfo.shouldBeInInfo.empty() || !secInfo.abstract.empty()) {
1169 // Generate the <info>, if required. If DocBookForceAbstractTag != NONE, this abstract will not be in
1170 // <info>, unlike other ("standard") abstracts.
1171 bool hasStandardAbstract = !secInfo.abstract.empty() && style.docbookforceabstracttag() == "NONE";
1172 bool needInfo = !secInfo.mustBeInInfo.empty() || hasStandardAbstract;
1175 xs.startDivision(false);
1176 xs << xml::StartTag("info");
1180 // Output the elements that should go in <info>, before and after the abstract.
1181 for (auto pit : secInfo.shouldBeInInfo) // Typically, the title: these elements are so important and ubiquitous
1182 // that mandating a wrapper like <info> would repel users. Thus, generate them first.
1183 makeAny(text, buf, xs, ourparams, paragraphs.iterator_at(pit));
1184 for (auto pit : secInfo.mustBeInInfo)
1185 makeAny(text, buf, xs, ourparams, paragraphs.iterator_at(pit));
1187 // Deal with the abstract in <info> if it is standard (i.e. its tag is <abstract>).
1188 if (!secInfo.abstract.empty() && hasStandardAbstract) {
1189 if (!secInfo.abstractLayout) {
1190 xs << xml::StartTag("abstract");
1194 for (auto const &p : secInfo.abstract)
1195 makeAny(text, buf, xs, ourparams, paragraphs.iterator_at(p));
1197 if (!secInfo.abstractLayout) {
1198 xs << xml::EndTag("abstract");
1203 // End the <info> tag if it was started.
1205 if (!xs.isLastTagCR())
1208 xs << xml::EndTag("info");
1213 // Deal with the abstract outside <info> if it is not standard (i.e. its tag is layout-defined).
1214 if (!secInfo.abstract.empty() && !hasStandardAbstract) {
1215 // Assert: style.docbookforceabstracttag() != NONE.
1216 xs << xml::StartTag(style.docbookforceabstracttag());
1218 for (auto const &p : secInfo.abstract)
1219 makeAny(text, buf, xs, ourparams, paragraphs.iterator_at(p));
1220 xs << xml::EndTag(style.docbookforceabstracttag());
1224 // Skip all the text that has just been generated.
1225 par = paragraphs.iterator_at(secInfo.epit);
1227 // No <info> tag to generate, proceed as for normal paragraphs.
1228 par = makeAny(text, buf, xs, ourparams, par);
1231 // Generate this paragraph, as it has nothing special.
1232 par = makeAny(text, buf, xs, ourparams, par);
1236 // If need be, close <section>s, but only at the end of the document (otherwise, dealt with at the beginning
1238 while (!headerLevels.empty() && headerLevels.top().first > Layout::NOT_IN_TOC) {
1239 docstring tag = from_utf8("</" + headerLevels.top().second + ">");
1241 xs << XMLStream::ESCAPE_NONE << tag;