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 // Convenience functions to open and close tags. First, very low-level ones to ensure a consistent new-line behaviour.
174 // Contents of the block.
179 // <paratag>Contents of the paragraph.</paratag>
182 // Content before<inlinetag>Contents of the paragraph.</inlinetag>Content after
184 void openInlineTag(XMLStream & xs, const std::string & tag, const std::string & attr)
186 xs << xml::StartTag(tag, attr);
190 void closeInlineTag(XMLStream & xs, const std::string & tag)
192 xs << xml::EndTag(tag);
196 void openParTag(XMLStream & xs, const std::string & tag, const std::string & attr)
198 if (!xs.isLastTagCR())
200 xs << xml::StartTag(tag, attr);
204 void closeParTag(XMLStream & xs, const std::string & tag)
206 xs << xml::EndTag(tag);
211 void openBlockTag(XMLStream & xs, const std::string & tag, const std::string & attr)
213 if (!xs.isLastTagCR())
215 xs << xml::StartTag(tag, attr);
220 void closeBlockTag(XMLStream & xs, const std::string & tag)
222 if (!xs.isLastTagCR())
224 xs << xml::EndTag(tag);
229 void openTag(XMLStream & xs, const std::string & tag, const std::string & attr, const std::string & tagtype)
231 if (tag.empty() || tag == "NONE") // Common check to be performed elsewhere, if it was not here.
234 if (tag == "para" || tagtype == "paragraph") // Special case for <para>: always considered as a paragraph.
235 openParTag(xs, tag, attr);
236 else if (tagtype == "block")
237 openBlockTag(xs, tag, attr);
238 else if (tagtype == "inline")
239 openInlineTag(xs, tag, attr);
241 xs.writeError("Unrecognised tag type '" + tagtype + "' for '" + tag + " " + attr + "'");
245 void closeTag(XMLStream & xs, const std::string & tag, const std::string & tagtype)
247 if (tag.empty() || tag == "NONE")
250 if (tag == "para" || tagtype == "paragraph") // Special case for <para>: always considered as a paragraph.
251 closeParTag(xs, tag);
252 else if (tagtype == "block")
253 closeBlockTag(xs, tag);
254 else if (tagtype == "inline")
255 closeInlineTag(xs, tag);
257 xs.writeError("Unrecognised tag type '" + tagtype + "' for '" + tag + "'");
261 void compTag(XMLStream & xs, const std::string & tag, const std::string & attr, const std::string & tagtype)
263 if (tag.empty() || tag == "NONE")
266 // Special case for <para>: always considered as a paragraph.
267 if (tag == "para" || tagtype == "paragraph" || tagtype == "block") {
268 if (!xs.isLastTagCR())
270 xs << xml::CompTag(tag, attr);
272 } else if (tagtype == "inline") {
273 xs << xml::CompTag(tag, attr);
275 xs.writeError("Unrecognised tag type '" + tagtype + "' for '" + tag + "'");
280 // Higher-level convenience functions.
282 void openParTag(XMLStream & xs, const Paragraph * par, const Paragraph * prevpar)
287 // When should the wrapper be opened here? Only if the previous paragraph has the SAME wrapper tag
288 // (usually, they won't have the same layout) and the CURRENT one allows merging.
289 // The main use case is author information in several paragraphs: if the name of the author is the
290 // first paragraph of an author, then merging with the previous tag does not make sense. Say the
291 // next paragraph is the affiliation, then it should be output in the same <author> tag (different
292 // layout, same wrapper tag).
293 Layout const & lay = par->layout();
294 bool openWrapper = lay.docbookwrappertag() != "NONE";
295 if (prevpar != nullptr) {
296 Layout const & prevlay = prevpar->layout();
297 if (prevlay.docbookwrappertag() != "NONE") {
298 if (prevlay.docbookwrappertag() == lay.docbookwrappertag() &&
299 prevlay.docbookwrapperattr() == lay.docbookwrapperattr())
300 openWrapper = !lay.docbookwrappermergewithprevious();
308 openTag(xs, lay.docbookwrappertag(), lay.docbookwrapperattr(), lay.docbookwrappertagtype());
310 const string & tag = lay.docbooktag();
312 auto xmltag = xml::ParTag(tag, lay.docbookattr());
313 if (!xs.isTagOpen(xmltag, 1)) { // Don't nest a paragraph directly in a paragraph.
314 // TODO: required or not?
315 // TODO: avoid creating a ParTag object just for this query...
316 openTag(xs, lay.docbooktag(), lay.docbookattr(), lay.docbooktagtype());
317 openTag(xs, lay.docbookinnertag(), lay.docbookinnerattr(), lay.docbookinnertagtype());
321 openTag(xs, lay.docbookitemtag(), lay.docbookitemattr(), lay.docbookitemtagtype());
322 openTag(xs, lay.docbookiteminnertag(), lay.docbookiteminnerattr(), lay.docbookiteminnertagtype());
326 void closeParTag(XMLStream & xs, Paragraph const * par, Paragraph const * nextpar)
331 // See comment in openParTag.
332 Layout const & lay = par->layout();
333 bool closeWrapper = lay.docbookwrappertag() != "NONE";
334 if (nextpar != nullptr) {
335 Layout const & nextlay = nextpar->layout();
336 if (nextlay.docbookwrappertag() != "NONE") {
337 if (nextlay.docbookwrappertag() == lay.docbookwrappertag() &&
338 nextlay.docbookwrapperattr() == lay.docbookwrapperattr())
339 closeWrapper = !nextlay.docbookwrappermergewithprevious();
346 closeTag(xs, lay.docbookiteminnertag(), lay.docbookiteminnertagtype());
347 closeTag(xs, lay.docbookitemtag(), lay.docbookitemtagtype());
348 closeTag(xs, lay.docbookinnertag(), lay.docbookinnertagtype());
349 closeTag(xs, lay.docbooktag(), lay.docbooktagtype());
351 closeTag(xs, lay.docbookwrappertag(), lay.docbookwrappertagtype());
355 void makeBibliography(
359 OutputParams const & runparams,
360 ParagraphList::const_iterator const & par)
362 // If this is the first paragraph in a bibliography, open the bibliography tag.
363 auto const * pbegin_before = text.paragraphs().getParagraphBefore(par);
364 if (pbegin_before == nullptr || (pbegin_before && pbegin_before->layout().latextype != LATEX_BIB_ENVIRONMENT)) {
365 xs << xml::StartTag("bibliography");
369 // Start the precooked bibliography entry. This is very much like opening a paragraph tag.
370 // Don't forget the citation ID!
372 for (auto i = 0; i < par->size(); ++i) {
373 Inset const *ip = par->getInset(i);
376 if (const auto * bibitem = dynamic_cast<const InsetBibitem*>(ip)) {
377 auto id = xml::cleanID(bibitem->getParam("key"));
378 attr = from_utf8("xml:id='") + id + from_utf8("'");
382 xs << xml::StartTag(from_utf8("bibliomixed"), attr);
384 // Generate the entry. Concatenate the different parts of the paragraph if any.
385 auto const begin = text.paragraphs().begin();
386 auto pars = par->simpleDocBookOnePar(buf, runparams, text.outerFont(std::distance(begin, par)), 0);
387 for (auto & parXML : pars)
388 xs << XMLStream::ESCAPE_NONE << parXML;
390 // End the precooked bibliography entry.
391 xs << xml::EndTag("bibliomixed");
394 // If this is the last paragraph in a bibliography, close the bibliography tag.
395 auto const end = text.paragraphs().end();
398 bool endBibliography = nextpar == end || nextpar->layout().latextype != LATEX_BIB_ENVIRONMENT;
400 if (endBibliography) {
401 xs << xml::EndTag("bibliography");
411 OutputParams const & runparams,
412 ParagraphList::const_iterator const & par)
415 auto const begin = text.paragraphs().begin();
416 auto const end = text.paragraphs().end();
417 auto prevpar = text.paragraphs().getParagraphBefore(par);
419 // We want to open the paragraph tag if:
420 // (i) the current layout permits multiple paragraphs
421 // (ii) we are either not already inside a paragraph (HTMLIsBlock) OR
422 // we are, but this is not the first paragraph
424 // But there is also a special case, and we first see whether we are in it.
425 // We do not want to open the paragraph tag if this paragraph contains
426 // only one item, and that item is "inline", i.e., not HTMLIsBlock (such
427 // as a branch). On the other hand, if that single item has a font change
428 // applied to it, then we still do need to open the paragraph.
430 // Obviously, this is very fragile. The main reason we need to do this is
431 // because of branches, e.g., a branch that contains an entire new section.
432 // We do not really want to wrap that whole thing in a <div>...</div>.
433 bool special_case = false;
434 Inset const *specinset = par->size() == 1 ? par->getInset(0) : nullptr;
435 if (specinset && !specinset->getLayout().htmlisblock()) { // TODO: Convert htmlisblock to a DocBook parameter?
436 Layout const &style = par->layout();
437 FontInfo const first_font = style.labeltype == LABEL_MANUAL ?
438 style.labelfont : style.font;
439 FontInfo const our_font =
440 par->getFont(buf.masterBuffer()->params(), 0,
441 text.outerFont(std::distance(begin, par))).fontInfo();
443 if (first_font == our_font)
447 size_t nInsets = std::distance(par->insetList().begin(), par->insetList().end());
448 auto parSize = (size_t) par->size();
450 // If this LyX code does not produce any output, it can be safely ignored in the following checks: if this thing
451 // is present in the paragraph, it has no impact on the definition of the special case (i.e. whether or not
452 // a <para> tag should be output).
453 auto isLyxCodeToIgnore = [](InsetCode x) { return x == TOC_CODE || x == NOTE_CODE; };
455 // TODO: if a paragraph *only* contains floats, listings, bibliographies, etc., should this be considered as a
456 // special case? If so, the code could be largely simplifies (all the calls to all_of, basically) and optimised
457 // at the compilation stage.
459 // Plain layouts must be ignored.
460 special_case |= buf.params().documentClass().isPlainLayout(par->layout()) && !runparams.docbook_force_pars;
461 // Equations do not deserve their own paragraph (DocBook allows them outside paragraphs).
462 // Exception: any case that generates an <inlineequation> must still get a paragraph to be valid.
463 special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), [](InsetList::Element inset) {
464 return inset.inset && inset.inset->asInsetMath() && inset.inset->asInsetMath()->getType() != hullSimple;
466 // Tables do not deserve their own paragraphs (DocBook allows them outside paragraphs).
467 special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), [isLyxCodeToIgnore](InsetList::Element inset) {
468 return inset.inset->lyxCode() == TABULAR_CODE || isLyxCodeToIgnore(inset.inset->lyxCode());
470 // Floats cannot be in paragraphs.
471 special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), [isLyxCodeToIgnore](InsetList::Element inset) {
472 return inset.inset->lyxCode() == FLOAT_CODE || isLyxCodeToIgnore(inset.inset->lyxCode());
474 // Bibliographies cannot be in paragraphs. Bibitems should still be handled as paragraphs, though
475 // (see makeParagraphBibliography).
476 special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), [isLyxCodeToIgnore](InsetList::Element inset) {
477 return inset.inset->lyxCode() == BIBTEX_CODE || isLyxCodeToIgnore(inset.inset->lyxCode());
479 // ERTs are in comments, not paragraphs.
480 special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), [isLyxCodeToIgnore](InsetList::Element inset) {
481 return inset.inset->lyxCode() == ERT_CODE || isLyxCodeToIgnore(inset.inset->lyxCode());
483 // Listings should not get into their own paragraph.
484 special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), [isLyxCodeToIgnore](InsetList::Element inset) {
485 return inset.inset->lyxCode() == LISTINGS_CODE || isLyxCodeToIgnore(inset.inset->lyxCode());
487 // Boxes cannot get into their own paragraph.
488 special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), [isLyxCodeToIgnore](InsetList::Element inset) {
489 return inset.inset->lyxCode() == BOX_CODE || isLyxCodeToIgnore(inset.inset->lyxCode());
491 // Includes should not have a paragraph.
492 special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), [isLyxCodeToIgnore](InsetList::Element inset) {
493 return inset.inset->lyxCode() == INCLUDE_CODE || isLyxCodeToIgnore(inset.inset->lyxCode());
495 // Glossaries should not have a paragraph.
496 special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), [isLyxCodeToIgnore](InsetList::Element inset) {
497 return inset.inset->lyxCode() == NOMENCL_PRINT_CODE || isLyxCodeToIgnore(inset.inset->lyxCode());
500 bool const open_par = runparams.docbook_make_pars
501 && !runparams.docbook_in_par
504 // We want to issue the closing tag if either:
505 // (i) We opened it, and either docbook_in_par is false,
506 // or we're not in the last paragraph, anyway.
507 // (ii) We didn't open it and docbook_in_par is true,
508 // but we are in the first par, and there is a next par.
509 bool const close_par = open_par && !runparams.docbook_in_par;
511 // Determine if this paragraph has some real content. Things like new pages are not caught
512 // by Paragraph::empty(), even though they do not generate anything useful in DocBook.
513 // Thus, remove all spaces (including new lines: \r, \n) before checking for emptiness.
514 // std::all_of allows doing this check without having to copy the string.
515 // Open and close tags around each contained paragraph.
518 auto pars = par->simpleDocBookOnePar(buf, runparams, text.outerFont(distance(begin, par)), 0, nextpar == end, special_case);
519 for (docstring const & parXML : pars) {
520 if (!xml::isNotOnlySpace(parXML))
524 openParTag(xs, &*par, prevpar);
526 xs << XMLStream::ESCAPE_NONE << parXML;
529 closeParTag(xs, &*par, (nextpar != end) ? &*nextpar : nullptr);
534 void makeEnvironment(Text const &text,
537 OutputParams const &runparams,
538 ParagraphList::const_iterator const & par)
541 auto const end = text.paragraphs().end();
545 // Special cases for listing-like environments provided in layouts. This is quite ad-hoc, but provides a useful
546 // default. This should not be used by too many environments (only LyX-Code right now).
547 // This would be much simpler if LyX-Code was implemented as InsetListings...
548 bool mimicListing = false;
549 bool ignoreFonts = false;
550 if (par->layout().docbooktag() == "programlisting") {
555 // Output the opening tag for this environment, but only if it has not been previously opened (condition
556 // implemented in openParTag).
557 auto prevpar = text.paragraphs().getParagraphBefore(par);
558 openParTag(xs, &*par, prevpar); // TODO: switch in layout for par/block?
560 // Generate the contents of this environment. There is a special case if this is like some environment.
561 Layout const & style = par->layout();
562 if (style.latextype == LATEX_COMMAND) {
563 // Nothing to do (otherwise, infinite loops).
564 } else if (style.latextype == LATEX_ENVIRONMENT) {
565 // Generate the paragraph, if need be.
566 auto pars = par->simpleDocBookOnePar(buf, runparams, text.outerFont(std::distance(text.paragraphs().begin(), par)), 0, false, ignoreFonts);
569 auto p = pars.begin();
570 while (p != pars.end()) {
571 openTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnerattr(), par->layout().docbookiteminnertagtype());
572 xs << XMLStream::ESCAPE_NONE << *p;
573 closeTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnertagtype());
576 // Insert a new line after each "paragraph" (i.e. line in the listing), except for the last one.
577 // Otherwise, there would one more new line in the output than in the LyX document.
582 for (auto const & p : pars) {
583 openTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnerattr(), par->layout().docbookiteminnertagtype());
584 xs << XMLStream::ESCAPE_NONE << p;
585 closeTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnertagtype());
589 makeAny(text, buf, xs, runparams, par);
592 // Close the environment.
593 closeParTag(xs, &*par, (nextpar != end) ? &*nextpar : nullptr); // TODO: switch in layout for par/block?
597 ParagraphList::const_iterator findEndOfEnvironment(
598 ParagraphList::const_iterator const & pstart,
599 ParagraphList::const_iterator const & pend)
601 // Copy-paste from XHTML. Should be factored out at some point...
602 ParagraphList::const_iterator p = pstart;
603 Layout const & bstyle = p->layout();
604 size_t const depth = p->params().depth();
605 for (++p; p != pend; ++p) {
606 Layout const & style = p->layout();
607 // It shouldn't happen that e.g. a section command occurs inside
608 // a quotation environment, at a higher depth, but as of 6/2009,
609 // it can happen. We pretend that it's just at lowest depth.
610 if (style.latextype == LATEX_COMMAND)
613 // If depth is down, we're done
614 if (p->params().depth() < depth)
617 // If depth is up, we're not done
618 if (p->params().depth() > depth)
621 // FIXME I am not sure about the first check.
622 // Surely we *could* have different layouts that count as
623 // LATEX_PARAGRAPH, right?
624 if (style.latextype == LATEX_PARAGRAPH || style != bstyle)
631 ParagraphList::const_iterator makeListEnvironment(Text const &text,
634 OutputParams const &runparams,
635 ParagraphList::const_iterator const & begin)
639 auto const end = text.paragraphs().end();
640 auto const envend = findEndOfEnvironment(par, end);
642 // Output the opening tag for this environment.
643 Layout const & envstyle = par->layout();
644 openTag(xs, envstyle.docbookwrappertag(), envstyle.docbookwrapperattr(), envstyle.docbookwrappertagtype());
645 openTag(xs, envstyle.docbooktag(), envstyle.docbookattr(), envstyle.docbooktagtype());
647 // Handle the content of the list environment, item by item.
648 while (par != envend) {
649 // Skip this paragraph if it is both empty and the last one (otherwise, there may be deeper paragraphs after).
652 if (par->empty() && nextpar == envend)
655 // Open the item wrapper.
656 Layout const & style = par->layout();
657 openTag(xs, style.docbookitemwrappertag(), style.docbookitemwrapperattr(), style.docbookitemwrappertagtype());
659 // Generate the label, if need be. If it is taken from the text, sep != 0 and corresponds to the first
660 // character after the label.
662 if (style.labeltype != LABEL_NO_LABEL && style.docbookitemlabeltag() != "NONE") {
663 if (style.labeltype == LABEL_MANUAL) {
664 // Only variablelist gets here (or similar items defined as an extension in the layout).
665 openTag(xs, style.docbookitemlabeltag(), style.docbookitemlabelattr(), style.docbookitemlabeltagtype());
666 sep = 1 + par->firstWordDocBook(xs, runparams);
667 closeTag(xs, style.docbookitemlabeltag(), style.docbookitemlabeltagtype());
669 // Usual cases: maybe there is something specified at the layout level. Highly unlikely, though.
670 docstring const lbl = par->params().labelString();
673 openTag(xs, style.docbookitemlabeltag(), style.docbookitemlabelattr(), style.docbookitemlabeltagtype());
675 closeTag(xs, style.docbookitemlabeltag(), style.docbookitemlabeltagtype());
680 // Open the item (after the wrapper and the label).
681 openTag(xs, style.docbookitemtag(), style.docbookitemattr(), style.docbookitemtagtype());
683 // Generate the content of the item.
684 if (sep < par->size()) {
685 auto pars = par->simpleDocBookOnePar(buf, runparams,
686 text.outerFont(std::distance(text.paragraphs().begin(), par)), sep);
687 for (auto &p : pars) {
688 openTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnerattr(),
689 par->layout().docbookiteminnertagtype());
690 xs << XMLStream::ESCAPE_NONE << p;
691 closeTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnertagtype());
694 // DocBook doesn't like emptiness.
695 compTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnerattr(),
696 par->layout().docbookiteminnertagtype());
699 // If the next item is deeper, it must go entirely within this item (do it recursively).
700 // By construction, with findEndOfEnvironment, depth can only stay constant or increase, never decrease.
701 depth_type currentDepth = par->getDepth();
703 while (par != envend && par->getDepth() != currentDepth)
704 par = makeAny(text, buf, xs, runparams, par);
705 // Usually, this loop only makes one iteration, except in complex scenarios, like an item with a paragraph,
706 // a list, and another paragraph; or an item with two types of list (itemise then enumerate, for instance).
709 closeTag(xs, style.docbookitemtag(), style.docbookitemtagtype());
710 closeTag(xs, style.docbookitemwrappertag(), style.docbookitemwrappertagtype());
713 // Close this environment in exactly the same way as it was opened.
714 closeTag(xs, envstyle.docbooktag(), envstyle.docbooktagtype());
715 closeTag(xs, envstyle.docbookwrappertag(), envstyle.docbookwrappertagtype());
725 OutputParams const & runparams,
726 ParagraphList::const_iterator const & par)
730 // Unlike XHTML, no need for labels, as they are handled by DocBook tags.
731 auto const begin = text.paragraphs().begin();
732 auto const end = text.paragraphs().end();
736 // Generate this command.
737 auto prevpar = text.paragraphs().getParagraphBefore(par);
738 openParTag(xs, &*par, prevpar);
740 auto pars = par->simpleDocBookOnePar(buf, runparams,text.outerFont(distance(begin, par)));
741 for (auto & parXML : pars)
742 // TODO: decide what to do with openParTag/closeParTag in new lines.
743 xs << XMLStream::ESCAPE_NONE << parXML;
745 closeParTag(xs, &*par, (nextpar != end) ? &*nextpar : nullptr);
749 bool isLayoutSectioning(Layout const & lay)
751 if (lay.docbooksection()) // Special case: some DocBook styles must be handled as sections.
753 else if (lay.category() == from_utf8("Sectioning") || lay.docbooktag() == "section") // Generic case.
754 return lay.toclevel != Layout::NOT_IN_TOC;
759 bool isLayoutSectioningOrSimilar(Layout const & lay)
761 return isLayoutSectioning(lay) || lay.docbooktag() == "bridgehead";
765 using DocBookDocumentSectioning = tuple<bool, pit_type>;
768 struct DocBookInfoTag
770 const set<pit_type> shouldBeInInfo;
771 const set<pit_type> mustBeInInfo; // With the notable exception of the abstract!
772 const set<pit_type> abstract;
773 const bool abstractLayout;
777 DocBookInfoTag(const set<pit_type> & shouldBeInInfo, const set<pit_type> & mustBeInInfo,
778 const set<pit_type> & abstract, bool abstractLayout, pit_type bpit, pit_type epit) :
779 shouldBeInInfo(shouldBeInInfo), mustBeInInfo(mustBeInInfo), abstract(abstract),
780 abstractLayout(abstractLayout), bpit(bpit), epit(epit) {}
784 DocBookDocumentSectioning hasDocumentSectioning(ParagraphList const ¶graphs, pit_type bpit, pit_type const epit) {
785 bool documentHasSections = false;
787 while (bpit < epit) {
788 Layout const &style = paragraphs[bpit].layout();
789 documentHasSections |= isLayoutSectioningOrSimilar(style);
791 if (documentHasSections)
795 // Paragraphs before the first section: [ runparams.par_begin ; eppit )
797 return make_tuple(documentHasSections, bpit);
801 bool hasOnlyNotes(Paragraph const & par)
803 // Precondition: the paragraph is not empty. Otherwise, the function will always return true...
804 for (int i = 0; i < par.size(); ++i)
805 // If you find something that is not an inset (like actual text) or an inset that is not a note,
807 if (!par.isInset(i) || par.getInset(i)->lyxCode() != NOTE_CODE)
810 // An empty paragraph may still require some output.
811 if (par.layout().docbooksection())
814 // There should be really no content here.
819 DocBookInfoTag getParagraphsWithInfo(ParagraphList const ¶graphs,
820 pit_type bpit, pit_type const epit,
821 // Typically, bpit is the beginning of the document and epit the end of the
822 // document *or* the first section.
823 bool documentHasSections,
824 bool detectUnlayoutedAbstract
825 // Whether paragraphs with no specific layout should be detected as abstracts.
826 // For inner sections, an abstract should only be detected if it has a specific
827 // layout. For others, anything that might look like an abstract should be sought.
829 set<pit_type> shouldBeInInfo;
830 set<pit_type> mustBeInInfo;
831 set<pit_type> abstractWithLayout;
832 set<pit_type> abstractNoLayout;
834 // Find the first non empty paragraph by mutating bpit.
835 while (bpit < epit) {
836 Paragraph const &par = paragraphs[bpit];
837 if (par.empty() || hasOnlyNotes(par))
843 // Traverse everything that might belong to <info>.
844 bool hasAbstractLayout = false;
845 pit_type cpit = bpit;
846 for (; cpit < epit; ++cpit) {
847 // Skip paragraphs that don't generate anything in DocBook.
848 Paragraph const & par = paragraphs[cpit];
849 Layout const &style = par.layout();
850 if (hasOnlyNotes(par))
853 // There should never be any section here, except for the first paragraph (a title can be part of <info>).
854 // (Just a sanity check: if this fails, this function could end up processing the whole document.)
855 if (cpit != bpit && isLayoutSectioningOrSimilar(par.layout())) {
856 LYXERR0("Assertion failed: section found in potential <info> paragraphs.");
860 // If this is marked as an abstract by the layout, put it in the right set.
861 if (style.docbookabstract()) {
862 hasAbstractLayout = true;
863 abstractWithLayout.emplace(cpit);
867 // Based on layout information, store this paragraph in one set: should be in <info>, must be,
868 // or abstract (either because of layout or of position).
869 if (style.docbookininfo() == "always")
870 mustBeInInfo.emplace(cpit);
871 else if (style.docbookininfo() == "maybe")
872 shouldBeInInfo.emplace(cpit);
873 else if (documentHasSections && !hasAbstractLayout && detectUnlayoutedAbstract &&
874 (style.docbooktag() == "NONE" || style.docbooktag() == "para") &&
875 style.docbookwrappertag() == "NONE")
876 // In this case, it is very likely that style.docbookininfo() == "never"! Be extra careful
877 // about anything that gets caught here.
878 abstractNoLayout.emplace(cpit);
879 else // This should definitely not be in <info>.
882 // Now, cpit points to the first paragraph that no more has things that could go in <info>.
883 // bpit is the beginning of the <info> part.
885 return DocBookInfoTag(shouldBeInInfo, mustBeInInfo,
886 hasAbstractLayout ? abstractWithLayout : abstractNoLayout,
887 hasAbstractLayout, bpit, cpit);
890 } // end anonymous namespace
893 std::set<const Inset *> gatherInfo(ParagraphList::const_iterator par)
895 // This function has a structure highly similar to makeAny and its friends. It's only made to be called on what
896 // should become the document's <abstract>.
897 std::set<const Inset *> values;
899 // If this kind of layout should be ignored, already leave.
900 if (par->layout().docbooktag() == "IGNORE")
903 // If this should go in info, mark it as such. Dive deep into the abstract, as it may hide many things that
904 // DocBook doesn't want to be inside the abstract.
905 for (pos_type i = 0; i < par->size(); ++i) {
906 if (par->getInset(i) && par->getInset(i)->asInsetText()) {
907 InsetText const *inset = par->getInset(i)->asInsetText();
909 if (inset->getLayout().docbookininfo() != "never") {
910 values.insert(inset);
912 auto subpar = inset->paragraphs().begin();
913 while (subpar != inset->paragraphs().end()) {
914 values.merge(gatherInfo(subpar));
925 ParagraphList::const_iterator makeAny(Text const &text,
928 OutputParams const &runparams,
929 ParagraphList::const_iterator par)
931 bool ignoreParagraph = false;
933 // If this kind of layout should be ignored, already leave.
934 ignoreParagraph |= par->layout().docbooktag() == "IGNORE";
936 // For things that should go into <info>, check the variable rp.docbook_generate_info. This does not apply to the
938 bool isAbstract = par->layout().docbookabstract() || par->layout().docbooktag() == "abstract";
939 ignoreParagraph |= !isAbstract && par->layout().docbookininfo() != "never" && !runparams.docbook_generate_info;
941 // Switch on the type of paragraph to call the right handler.
942 if (!ignoreParagraph) {
943 switch (par->layout().latextype) {
945 makeCommand(text, buf, xs, runparams, par);
947 case LATEX_ENVIRONMENT:
948 makeEnvironment(text, buf, xs, runparams, par);
950 case LATEX_LIST_ENVIRONMENT:
951 case LATEX_ITEM_ENVIRONMENT:
952 // Only case when makeAny() might consume more than one paragraph.
953 return makeListEnvironment(text, buf, xs, runparams, par);
954 case LATEX_PARAGRAPH:
955 makeParagraph(text, buf, xs, runparams, par);
957 case LATEX_BIB_ENVIRONMENT:
958 makeBibliography(text, buf, xs, runparams, par);
963 // For cases that are not lists, the next paragraph to handle is the next one.
969 xml::FontTag docbookStartFontTag(xml::FontTypes type)
971 return xml::FontTag(from_utf8(fontToDocBookTag(type)), from_utf8(fontToAttribute(type)), type);
975 xml::EndFontTag docbookEndFontTag(xml::FontTypes type)
977 return xml::EndFontTag(from_utf8(fontToDocBookTag(type)), type);
981 void outputDocBookInfo(
985 OutputParams const & runparams,
986 ParagraphList const & paragraphs,
987 DocBookInfoTag const & info)
989 // Perform an additional check on the abstract. Sometimes, there are many paragraphs that should go
990 // into the abstract, but none generates actual content. Thus, first generate to a temporary stream,
991 // then only create the <abstract> tag if these paragraphs generate some content.
992 // This check must be performed *before* a decision on whether or not to output <info> is made.
993 bool hasAbstract = !info.abstract.empty();
995 set<const Inset *> infoInsets; // Paragraphs that should go into <info>, but are hidden in an <abstract>
996 // paragraph. (This happens for quite a few layouts, unfortunately.)
999 // Generate the abstract XML into a string before further checks.
1000 // Usually, makeAny only generates one paragraph at a time. However, for the specific case of lists, it might
1001 // generate more than one paragraph, as indicated in the return value.
1002 odocstringstream os2;
1005 auto rp = runparams;
1006 rp.docbook_generate_info = false;
1008 set<pit_type> doneParas; // Paragraphs that have already been converted (mostly to deal with lists).
1009 for (auto const & p : info.abstract) {
1010 if (doneParas.find(p) == doneParas.end()) {
1011 auto oldPar = paragraphs.iterator_at(p);
1012 auto newPar = makeAny(text, buf, xs2, rp, oldPar);
1014 infoInsets.merge(gatherInfo(oldPar));
1016 // Insert the indices of all the paragraphs that were just generated (typically, one).
1017 // **Make the hypothesis that, when an abstract has a list, all its items are consecutive.**
1018 // Otherwise, makeAny and makeListEnvironment would have to be adapted too.
1020 while (oldPar != newPar) {
1021 doneParas.emplace(id);
1028 // Actually output the abstract if there is something to do. Don't count line feeds or spaces in this,
1029 // even though they must be properly output if there is some abstract.
1030 abstract = os2.str();
1031 docstring cleaned = abstract;
1032 cleaned.erase(std::remove_if(cleaned.begin(), cleaned.end(), lyx::isSpace), cleaned.end());
1034 // Nothing? Then there is no abstract!
1035 if (cleaned.empty())
1036 hasAbstract = false;
1039 // The abstract must go in <info>. Otherwise, decide whether to open <info> based on the layouts.
1040 bool needInfo = !info.mustBeInInfo.empty() || hasAbstract;
1042 // Start the <info> tag if required.
1044 xs.startDivision(false);
1045 xs << xml::StartTag("info");
1049 // Output the elements that should go in <info>.
1050 // - First, the title.
1051 for (auto pit : info.shouldBeInInfo) // Typically, the title: these elements are so important and ubiquitous
1052 // that mandating a wrapper like <info> would repel users. Thus, generate them first.
1053 makeAny(text, buf, xs, runparams, paragraphs.iterator_at(pit));
1054 // If there is no title, generate one (required for the document to be valid).
1055 // This code is called for the main document, for table cells, etc., so be precise in this condition.
1056 if (text.isMainText() && info.shouldBeInInfo.empty() && !runparams.inInclude) {
1057 xs << xml::StartTag("title");
1058 xs << "Untitled Document";
1059 xs << xml::EndTag("title");
1063 // - Then, other metadata.
1064 for (auto pit : info.mustBeInInfo)
1065 makeAny(text, buf, xs, runparams, paragraphs.iterator_at(pit));
1066 for (auto const * inset : infoInsets)
1067 inset->docbook(xs, runparams);
1069 // - Finally, always output the abstract as the last item of the <info>, as it requires special treatment
1070 // (especially if it contains several paragraphs that are empty).
1072 if (info.abstractLayout) {
1073 xs << XMLStream::ESCAPE_NONE << abstract;
1076 string tag = paragraphs[*info.abstract.begin()].layout().docbookforceabstracttag();
1080 if (!xs.isLastTagCR())
1083 xs << xml::StartTag(tag);
1085 xs << XMLStream::ESCAPE_NONE << abstract;
1086 xs << xml::EndTag(tag);
1091 // End the <info> tag if it was started.
1093 if (!xs.isLastTagCR())
1096 xs << xml::EndTag("info");
1103 void docbookSimpleAllParagraphs(
1107 OutputParams const & runparams)
1109 // Handle the given text, supposing it has no sections (i.e. a "simple" text). The input may vary in length
1110 // between a single paragraph to a whole document.
1111 pit_type const bpit = runparams.par_begin;
1112 pit_type const epit = runparams.par_end;
1113 ParagraphList const ¶graphs = text.paragraphs();
1115 // First, the <info> tag.
1116 DocBookInfoTag info = getParagraphsWithInfo(paragraphs, bpit, epit, false, true);
1117 outputDocBookInfo(text, buf, xs, runparams, paragraphs, info);
1119 // Then, the content. It starts where the <info> ends.
1120 auto par = paragraphs.iterator_at(info.epit);
1121 auto end = paragraphs.iterator_at(epit);
1122 while (par != end) {
1123 if (!hasOnlyNotes(*par))
1124 par = makeAny(text, buf, xs, runparams, par);
1131 void docbookParagraphs(Text const &text,
1134 OutputParams const &runparams) {
1135 ParagraphList const ¶graphs = text.paragraphs();
1136 if (runparams.par_begin == runparams.par_end) {
1137 runparams.par_begin = 0;
1138 runparams.par_end = paragraphs.size();
1140 pit_type bpit = runparams.par_begin;
1141 pit_type const epit = runparams.par_end;
1142 LASSERT(bpit < epit,
1144 xs << XMLStream::ESCAPE_NONE << "<!-- DocBook output error! -->\n";
1148 std::stack<std::pair<int, string>> headerLevels; // Used to determine when to open/close sections: store the depth
1149 // of the section and the tag that was used to open it.
1151 // Detect whether the document contains sections. If there are no sections, treatment is largely simplified.
1152 // In particular, there can't be an abstract, unless it is manually marked.
1153 bool documentHasSections;
1155 tie(documentHasSections, eppit) = hasDocumentSectioning(paragraphs, bpit, epit);
1157 // Deal with "simple" documents, i.e. those without sections.
1158 if (!documentHasSections) {
1159 docbookSimpleAllParagraphs(text, buf, xs, runparams);
1163 // Output the first <info> tag (or just the title).
1164 DocBookInfoTag info = getParagraphsWithInfo(paragraphs, bpit, eppit, true, true);
1165 outputDocBookInfo(text, buf, xs, runparams, paragraphs, info);
1168 // Then, iterate through the paragraphs of this document.
1169 bool currentlyInAppendix = false;
1171 auto par = text.paragraphs().iterator_at(bpit);
1172 auto end = text.paragraphs().iterator_at(epit);
1173 while (par != end) {
1174 OutputParams ourparams = runparams;
1176 if (par->params().startOfAppendix())
1177 currentlyInAppendix = true;
1178 if (hasOnlyNotes(*par)) {
1183 Layout const &style = par->layout();
1185 // Think about adding <section> and/or </section>s.
1186 if (isLayoutSectioning(style)) {
1187 int level = style.toclevel;
1189 // Need to close a previous section if it has the same level or a higher one (close <section> if opening a
1190 // <h2> after a <h2>, <h3>, <h4>, <h5> or <h6>). More examples:
1191 // - current: h2; back: h1; do not close any <section>
1192 // - current: h1; back: h2; close two <section> (first the <h2>, then the <h1>, so a new <h1> can come)
1193 while (!headerLevels.empty() && level <= headerLevels.top().first) {
1194 // Output the tag only if it corresponds to a legit section.
1195 int stackLevel = headerLevels.top().first;
1196 if (stackLevel != Layout::NOT_IN_TOC) {
1197 xs << xml::EndTag(headerLevels.top().second);
1203 // Open the new section: first push it onto the stack, then output it in DocBook.
1204 string sectionTag = (currentlyInAppendix && style.docbooksectiontag() == "chapter") ?
1205 "appendix" : style.docbooksectiontag();
1206 headerLevels.push(std::make_pair(level, sectionTag));
1208 // Some sectioning-like elements should not be output (such as FrontMatter).
1209 if (level != Layout::NOT_IN_TOC) {
1210 // Look for a label in the title, i.e. a InsetLabel as a child.
1211 docstring id = docstring();
1212 for (pos_type i = 0; i < par->size(); ++i) {
1213 Inset const *inset = par->getInset(i);
1215 if (auto label = dynamic_cast<InsetLabel const *>(inset)) {
1216 // Generate the attributes for the section if need be.
1217 id += "xml:id=\"" + xml::cleanID(label->screenLabel()) + "\"";
1219 // Don't output the ID as a DocBook <anchor>.
1220 ourparams.docbook_anchors_to_ignore.emplace(label->screenLabel());
1222 // Cannot have multiple IDs per tag. If there is another ID inset in the document, it will
1223 // be output as a DocBook anchor.
1229 // Write the open tag for this section.
1233 xs << xml::StartTag(sectionTag, attrs);
1238 // Close all sections before the bibliography.
1239 // 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)?
1240 if (!par->insetList().empty()) {
1241 Inset const *firstInset = par->getInset(0);
1242 if (firstInset && (firstInset->lyxCode() == BIBITEM_CODE || firstInset->lyxCode() == BIBTEX_CODE)) {
1243 while (!headerLevels.empty()) {
1244 int level = headerLevels.top().first;
1245 docstring tag = from_utf8("</" + headerLevels.top().second + ">");
1248 // Output the tag only if it corresponds to a legit section.
1249 if (level != Layout::NOT_IN_TOC) {
1250 xs << XMLStream::ESCAPE_NONE << tag;
1257 // Generate the <info> tag if a section was just opened.
1258 // Some sections may require abstracts (mostly parts, in books: DocBookForceAbstractTag will not be NONE),
1259 // others can still have an abstract (it must be detected so that it can be output at the right place).
1260 // 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.
1261 if (isLayoutSectioning(style)) {
1262 // This abstract may be found between the next paragraph and the next title.
1263 pit_type cpit = std::distance(text.paragraphs().begin(), par);
1264 pit_type ppit = std::get<1>(hasDocumentSectioning(paragraphs, cpit + 1L, epit));
1266 // Generate this abstract (this code corresponds to parts of outputDocBookInfo).
1267 DocBookInfoTag secInfo = getParagraphsWithInfo(paragraphs, cpit, ppit, true,
1268 style.docbookforceabstracttag() != "NONE");
1270 if (!secInfo.mustBeInInfo.empty() || !secInfo.shouldBeInInfo.empty() || !secInfo.abstract.empty()) {
1271 // Generate the <info>, if required. If DocBookForceAbstractTag != NONE, this abstract will not be in
1272 // <info>, unlike other ("standard") abstracts.
1273 bool hasStandardAbstract = !secInfo.abstract.empty() && style.docbookforceabstracttag() == "NONE";
1274 bool needInfo = !secInfo.mustBeInInfo.empty() || hasStandardAbstract;
1277 xs.startDivision(false);
1278 xs << xml::StartTag("info");
1282 // Output the elements that should go in <info>, before and after the abstract.
1283 for (auto pit : secInfo.shouldBeInInfo) // Typically, the title: these elements are so important and ubiquitous
1284 // that mandating a wrapper like <info> would repel users. Thus, generate them first.
1285 makeAny(text, buf, xs, ourparams, paragraphs.iterator_at(pit));
1286 for (auto pit : secInfo.mustBeInInfo)
1287 makeAny(text, buf, xs, ourparams, paragraphs.iterator_at(pit));
1289 // Deal with the abstract in <info> if it is standard (i.e. its tag is <abstract>).
1290 if (!secInfo.abstract.empty() && hasStandardAbstract) {
1291 if (!secInfo.abstractLayout) {
1292 xs << xml::StartTag("abstract");
1296 for (auto const &p : secInfo.abstract)
1297 makeAny(text, buf, xs, ourparams, paragraphs.iterator_at(p));
1299 if (!secInfo.abstractLayout) {
1300 xs << xml::EndTag("abstract");
1305 // End the <info> tag if it was started.
1307 if (!xs.isLastTagCR())
1310 xs << xml::EndTag("info");
1315 // Deal with the abstract outside <info> if it is not standard (i.e. its tag is layout-defined).
1316 if (!secInfo.abstract.empty() && !hasStandardAbstract) {
1317 // Assert: style.docbookforceabstracttag() != NONE.
1318 xs << xml::StartTag(style.docbookforceabstracttag());
1320 for (auto const &p : secInfo.abstract)
1321 makeAny(text, buf, xs, ourparams, paragraphs.iterator_at(p));
1322 xs << xml::EndTag(style.docbookforceabstracttag());
1326 // Skip all the text that has just been generated.
1327 par = paragraphs.iterator_at(secInfo.epit);
1329 // No <info> tag to generate, proceed as for normal paragraphs.
1330 par = makeAny(text, buf, xs, ourparams, par);
1333 // Generate this paragraph, as it has nothing special.
1334 par = makeAny(text, buf, xs, ourparams, par);
1338 // If need be, close <section>s, but only at the end of the document (otherwise, dealt with at the beginning
1340 while (!headerLevels.empty() && headerLevels.top().first > Layout::NOT_IN_TOC) {
1341 docstring tag = from_utf8("</" + headerLevels.top().second + ">");
1343 xs << XMLStream::ESCAPE_NONE << tag;