2 * \file output_docbook.cpp
3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Lars Gullik Bjønnes
9 * Full author contact details are available in file CREDITS.
14 #include "output_docbook.h"
17 #include "buffer_funcs.h"
18 #include "BufferParams.h"
20 #include "InsetList.h"
21 #include "Paragraph.h"
22 #include "ParagraphList.h"
23 #include "ParagraphParameters.h"
26 #include "TextClass.h"
28 #include "insets/InsetBibtex.h"
29 #include "insets/InsetBibitem.h"
30 #include "insets/InsetLabel.h"
31 #include "mathed/InsetMath.h"
32 #include "insets/InsetNote.h"
34 #include "support/debug.h"
35 #include "support/lassert.h"
36 #include "support/textutils.h"
44 using namespace lyx::support;
50 std::string fontToDocBookTag(xml::FontTypes type)
53 case xml::FontTypes::FT_EMPH:
54 case xml::FontTypes::FT_BOLD:
56 case xml::FontTypes::FT_NOUN:
58 case xml::FontTypes::FT_UBAR:
59 case xml::FontTypes::FT_WAVE:
60 case xml::FontTypes::FT_DBAR:
61 case xml::FontTypes::FT_SOUT:
62 case xml::FontTypes::FT_XOUT:
63 case xml::FontTypes::FT_ITALIC:
64 case xml::FontTypes::FT_UPRIGHT:
65 case xml::FontTypes::FT_SLANTED:
66 case xml::FontTypes::FT_SMALLCAPS:
67 case xml::FontTypes::FT_ROMAN:
68 case xml::FontTypes::FT_SANS:
70 case xml::FontTypes::FT_TYPE:
72 case xml::FontTypes::FT_SIZE_TINY:
73 case xml::FontTypes::FT_SIZE_SCRIPT:
74 case xml::FontTypes::FT_SIZE_FOOTNOTE:
75 case xml::FontTypes::FT_SIZE_SMALL:
76 case xml::FontTypes::FT_SIZE_NORMAL:
77 case xml::FontTypes::FT_SIZE_LARGE:
78 case xml::FontTypes::FT_SIZE_LARGER:
79 case xml::FontTypes::FT_SIZE_LARGEST:
80 case xml::FontTypes::FT_SIZE_HUGE:
81 case xml::FontTypes::FT_SIZE_HUGER:
82 case xml::FontTypes::FT_SIZE_INCREASE:
83 case xml::FontTypes::FT_SIZE_DECREASE:
91 string fontToRole(xml::FontTypes type)
93 // Specific fonts are achieved with roles. The only common ones are "" for basic emphasis,
94 // and "bold"/"strong" for bold. With some specific options, other roles are copied into
95 // HTML output (via the DocBook XSLT sheets); otherwise, if not recognised, they are just ignored.
96 // Hence, it is not a problem to have many roles by default here.
97 // See https://www.sourceware.org/ml/docbook/2003-05/msg00269.html
99 case xml::FontTypes::FT_ITALIC:
100 case xml::FontTypes::FT_EMPH:
102 case xml::FontTypes::FT_BOLD:
104 case xml::FontTypes::FT_NOUN: // Outputs a <person>
105 case xml::FontTypes::FT_TYPE: // Outputs a <code>
107 case xml::FontTypes::FT_UBAR:
110 // All other roles are non-standard for DocBook.
112 case xml::FontTypes::FT_WAVE:
114 case xml::FontTypes::FT_DBAR:
116 case xml::FontTypes::FT_SOUT:
118 case xml::FontTypes::FT_XOUT:
120 case xml::FontTypes::FT_UPRIGHT:
122 case xml::FontTypes::FT_SLANTED:
124 case xml::FontTypes::FT_SMALLCAPS:
126 case xml::FontTypes::FT_ROMAN:
128 case xml::FontTypes::FT_SANS:
130 case xml::FontTypes::FT_SIZE_TINY:
132 case xml::FontTypes::FT_SIZE_SCRIPT:
133 return "size_script";
134 case xml::FontTypes::FT_SIZE_FOOTNOTE:
135 return "size_footnote";
136 case xml::FontTypes::FT_SIZE_SMALL:
138 case xml::FontTypes::FT_SIZE_NORMAL:
139 return "size_normal";
140 case xml::FontTypes::FT_SIZE_LARGE:
142 case xml::FontTypes::FT_SIZE_LARGER:
143 return "size_larger";
144 case xml::FontTypes::FT_SIZE_LARGEST:
145 return "size_largest";
146 case xml::FontTypes::FT_SIZE_HUGE:
148 case xml::FontTypes::FT_SIZE_HUGER:
150 case xml::FontTypes::FT_SIZE_INCREASE:
151 return "size_increase";
152 case xml::FontTypes::FT_SIZE_DECREASE:
153 return "size_decrease";
160 string fontToAttribute(xml::FontTypes type) {
161 // If there is a role (i.e. nonstandard use of a tag), output the attribute. Otherwise, the sheer tag is sufficient
163 string role = fontToRole(type);
165 return "role='" + role + "'";
171 // Higher-level convenience functions.
173 void openParTag(XMLStream & xs, const Paragraph * par, const Paragraph * prevpar, const OutputParams & runparams)
178 // If the previous paragraph is empty, don't consider it when opening wrappers.
179 if (prevpar && prevpar->empty() && !prevpar->allowEmpty())
182 // When should the wrapper be opened here? Only if the previous paragraph has the SAME wrapper tag
183 // (usually, they won't have the same layout) and the CURRENT one allows merging.
184 // The main use case is author information in several paragraphs: if the name of the author is the
185 // first paragraph of an author, then merging with the previous tag does not make sense. Say the
186 // next paragraph is the affiliation, then it should be output in the same <author> tag (different
187 // layout, same wrapper tag).
188 Layout const & lay = par->layout();
189 bool openWrapper = lay.docbookwrappertag() != "NONE" && !runparams.docbook_ignore_wrapper;
191 if (prevpar != nullptr && !runparams.docbook_ignore_wrapper) {
192 Layout const & prevlay = prevpar->layout();
193 if (prevlay.docbookwrappertag() != "NONE") {
194 if (prevlay.docbookwrappertag() == lay.docbookwrappertag() &&
195 prevlay.docbookwrapperattr() == lay.docbookwrapperattr())
196 openWrapper = !lay.docbookwrappermergewithprevious();
204 xml::openTag(xs, lay.docbookwrappertag(), lay.docbookwrapperattr(), lay.docbookwrappertagtype());
206 const string & tag = lay.docbooktag();
208 auto xmltag = xml::ParTag(tag, lay.docbookattr());
209 if (!xs.isTagOpen(xmltag, 1)) { // Don't nest a paragraph directly in a paragraph.
210 // TODO: required or not?
211 // TODO: avoid creating a ParTag object just for this query...
212 xml::openTag(xs, lay.docbooktag(), lay.docbookattr(), lay.docbooktagtype());
213 xml::openTag(xs, lay.docbookinnertag(), lay.docbookinnerattr(), lay.docbookinnertagtype());
217 xml::openTag(xs, lay.docbookitemwrappertag(), lay.docbookitemwrapperattr(), lay.docbookitemwrappertagtype());
218 xml::openTag(xs, lay.docbookitemtag(), lay.docbookitemattr(), lay.docbookitemtagtype());
219 xml::openTag(xs, lay.docbookiteminnertag(), lay.docbookiteminnerattr(), lay.docbookiteminnertagtype());
223 void closeParTag(XMLStream & xs, Paragraph const * par, Paragraph const * nextpar, const OutputParams & runparams)
228 // If the next paragraph is empty, don't consider it when closing wrappers.
229 if (nextpar && nextpar->empty() && !nextpar->allowEmpty())
232 // See comment in openParTag.
233 Layout const & lay = par->layout();
234 bool closeWrapper = lay.docbookwrappertag() != "NONE" && !runparams.docbook_ignore_wrapper;
236 if (nextpar != nullptr && !runparams.docbook_ignore_wrapper) {
237 Layout const & nextlay = nextpar->layout();
238 if (nextlay.docbookwrappertag() != "NONE") {
239 if (nextlay.docbookwrappertag() == lay.docbookwrappertag() &&
240 nextlay.docbookwrapperattr() == lay.docbookwrapperattr())
241 closeWrapper = !nextlay.docbookwrappermergewithprevious();
248 xml::closeTag(xs, lay.docbookiteminnertag(), lay.docbookiteminnertagtype());
249 xml::closeTag(xs, lay.docbookitemtag(), lay.docbookitemtagtype());
250 xml::closeTag(xs, lay.docbookitemwrappertag(), lay.docbookitemwrappertagtype());
251 xml::closeTag(xs, lay.docbookinnertag(), lay.docbookinnertagtype());
252 xml::closeTag(xs, lay.docbooktag(), lay.docbooktagtype());
254 xml::closeTag(xs, lay.docbookwrappertag(), lay.docbookwrappertagtype());
258 void makeBibliography(
262 OutputParams const & runparams,
263 ParagraphList::const_iterator const & par)
265 // If this is the first paragraph in a bibliography, open the bibliography tag.
266 auto const * pbegin_before = text.paragraphs().getParagraphBefore(par);
267 if (pbegin_before == nullptr || (pbegin_before && pbegin_before->layout().latextype != LATEX_BIB_ENVIRONMENT)) {
268 xs << xml::StartTag("bibliography");
272 // Start the precooked bibliography entry. This is very much like opening a paragraph tag.
273 // Don't forget the citation ID!
275 for (auto i = 0; i < par->size(); ++i) {
276 Inset const *ip = par->getInset(i);
279 if (const auto * bibitem = dynamic_cast<const InsetBibitem*>(ip)) {
280 auto id = xml::cleanID(bibitem->getParam("key"));
281 attr = from_utf8("xml:id='") + id + from_utf8("'");
285 xs << xml::StartTag(from_utf8("bibliomixed"), attr);
287 // Generate the entry. Concatenate the different parts of the paragraph if any.
288 auto const begin = text.paragraphs().begin();
289 auto pars = par->simpleDocBookOnePar(buf, runparams, text.outerFont(std::distance(begin, par)), 0);
290 for (auto & parXML : pars)
291 xs << XMLStream::ESCAPE_NONE << parXML;
293 // End the precooked bibliography entry.
294 xs << xml::EndTag("bibliomixed");
297 // If this is the last paragraph in a bibliography, close the bibliography tag.
298 auto const end = text.paragraphs().end();
301 bool endBibliography = nextpar == end || nextpar->layout().latextype != LATEX_BIB_ENVIRONMENT;
303 if (endBibliography) {
304 xs << xml::EndTag("bibliography");
314 OutputParams const & runparams,
315 ParagraphList::const_iterator const & par)
318 auto const begin = text.paragraphs().begin();
319 auto const end = text.paragraphs().end();
320 auto prevpar = text.paragraphs().getParagraphBefore(par);
322 // We want to open the paragraph tag if:
323 // (i) the current layout permits multiple paragraphs
324 // (ii) we are either not already inside a paragraph (HTMLIsBlock) OR
325 // we are, but this is not the first paragraph
327 // But there is also a special case, and we first see whether we are in it.
328 // We do not want to open the paragraph tag if this paragraph contains
329 // only one item, and that item is "inline", i.e., not HTMLIsBlock (such
330 // as a branch). On the other hand, if that single item has a font change
331 // applied to it, then we still do need to open the paragraph.
333 // Obviously, this is very fragile. The main reason we need to do this is
334 // because of branches, e.g., a branch that contains an entire new section.
335 // We do not really want to wrap that whole thing in a <div>...</div>.
336 bool special_case = false;
337 Inset const *specinset = par->size() == 1 ? par->getInset(0) : nullptr;
338 if (specinset && !specinset->getLayout().htmlisblock()) { // TODO: Convert htmlisblock to a DocBook parameter?
339 Layout const &style = par->layout();
340 FontInfo const first_font = style.labeltype == LABEL_MANUAL ?
341 style.labelfont : style.font;
342 FontInfo const our_font =
343 par->getFont(buf.masterBuffer()->params(), 0,
344 text.outerFont(std::distance(begin, par))).fontInfo();
346 if (first_font == our_font)
350 size_t nInsets = std::distance(par->insetList().begin(), par->insetList().end());
351 auto parSize = (size_t) par->size();
353 // If this LyX code does not produce any output, it can be safely ignored in the following checks: if this thing
354 // is present in the paragraph, it has no impact on the definition of the special case (i.e. whether or not
355 // a <para> tag should be output).
356 auto isLyxCodeToIgnore = [](InsetCode x) { return x == TOC_CODE || x == NOTE_CODE; };
358 // TODO: if a paragraph *only* contains floats, listings, bibliographies, etc., should this be considered as a
359 // special case? If so, the code could be largely simplifies (all the calls to all_of, basically) and optimised
360 // at the compilation stage.
362 // Plain layouts must be ignored.
363 special_case |= buf.params().documentClass().isPlainLayout(par->layout()) && !runparams.docbook_force_pars;
364 // Equations do not deserve their own paragraph (DocBook allows them outside paragraphs).
365 // Exception: any case that generates an <inlineequation> must still get a paragraph to be valid.
366 special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), [](InsetList::Element inset) {
367 return inset.inset && inset.inset->asInsetMath() && inset.inset->asInsetMath()->getType() != hullSimple;
369 // Tables do not deserve their own paragraphs (DocBook allows them outside paragraphs).
370 special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), [isLyxCodeToIgnore](InsetList::Element inset) {
371 return inset.inset->lyxCode() == TABULAR_CODE || isLyxCodeToIgnore(inset.inset->lyxCode());
373 // Floats cannot be in paragraphs.
374 special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), [isLyxCodeToIgnore](InsetList::Element inset) {
375 return inset.inset->lyxCode() == FLOAT_CODE || isLyxCodeToIgnore(inset.inset->lyxCode());
377 // Bibliographies cannot be in paragraphs. Bibitems should still be handled as paragraphs, though
378 // (see makeParagraphBibliography).
379 special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), [isLyxCodeToIgnore](InsetList::Element inset) {
380 return inset.inset->lyxCode() == BIBTEX_CODE || isLyxCodeToIgnore(inset.inset->lyxCode());
382 // ERTs are in comments, not paragraphs.
383 special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), [isLyxCodeToIgnore](InsetList::Element inset) {
384 return inset.inset->lyxCode() == ERT_CODE || isLyxCodeToIgnore(inset.inset->lyxCode());
386 // Listings should not get into their own paragraph.
387 special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), [isLyxCodeToIgnore](InsetList::Element inset) {
388 return inset.inset->lyxCode() == LISTINGS_CODE || isLyxCodeToIgnore(inset.inset->lyxCode());
390 // Boxes cannot get into their own paragraph.
391 special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), [isLyxCodeToIgnore](InsetList::Element inset) {
392 return inset.inset->lyxCode() == BOX_CODE || isLyxCodeToIgnore(inset.inset->lyxCode());
394 // Includes should not have a paragraph.
395 special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), [isLyxCodeToIgnore](InsetList::Element inset) {
396 return inset.inset->lyxCode() == INCLUDE_CODE || isLyxCodeToIgnore(inset.inset->lyxCode());
398 // Glossaries should not have a paragraph.
399 special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), [isLyxCodeToIgnore](InsetList::Element inset) {
400 return inset.inset->lyxCode() == NOMENCL_PRINT_CODE || isLyxCodeToIgnore(inset.inset->lyxCode());
403 bool const open_par = runparams.docbook_make_pars
404 && !runparams.docbook_in_par
407 // We want to issue the closing tag if either:
408 // (i) We opened it, and either docbook_in_par is false,
409 // or we're not in the last paragraph, anyway.
410 // (ii) We didn't open it and docbook_in_par is true,
411 // but we are in the first par, and there is a next par.
412 bool const close_par = open_par && !runparams.docbook_in_par;
414 // Determine if this paragraph has some real content. Things like new pages are not caught
415 // by Paragraph::empty(), even though they do not generate anything useful in DocBook.
416 // Thus, remove all spaces (including new lines: \r, \n) before checking for emptiness.
417 // std::all_of allows doing this check without having to copy the string.
418 // Open and close tags around each contained paragraph.
421 auto pars = par->simpleDocBookOnePar(buf, runparams, text.outerFont(distance(begin, par)), 0, nextpar == end, special_case);
422 for (docstring const & parXML : pars) {
423 if (!xml::isNotOnlySpace(parXML))
427 openParTag(xs, &*par, prevpar, runparams);
429 xs << XMLStream::ESCAPE_NONE << parXML;
432 closeParTag(xs, &*par, (nextpar != end) ? &*nextpar : nullptr, runparams);
437 void makeEnvironment(Text const &text,
440 OutputParams const &runparams,
441 ParagraphList::const_iterator const & par)
444 auto const end = text.paragraphs().end();
448 // Special cases for listing-like environments provided in layouts. This is quite ad-hoc, but provides a useful
449 // default. This should not be used by too many environments (only LyX-Code right now).
450 // This would be much simpler if LyX-Code was implemented as InsetListings...
451 bool mimicListing = false;
452 bool ignoreFonts = false;
453 if (par->layout().docbooktag() == "programlisting") {
458 // Output the opening tag for this environment, but only if it has not been previously opened (condition
459 // implemented in openParTag).
460 auto prevpar = text.paragraphs().getParagraphBefore(par);
461 openParTag(xs, &*par, prevpar, runparams);
463 // Generate the contents of this environment. There is a special case if this is like some environment.
464 Layout const & style = par->layout();
465 if (style.latextype == LATEX_COMMAND) {
466 // Nothing to do (otherwise, infinite loops).
467 } else if (style.latextype == LATEX_ENVIRONMENT) {
468 // Generate the paragraph, if need be.
469 auto pars = par->simpleDocBookOnePar(buf, runparams, text.outerFont(std::distance(text.paragraphs().begin(), par)), 0, false, ignoreFonts);
472 auto p = pars.begin();
473 while (p != pars.end()) {
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());
480 // Insert a new line after each "paragraph" (i.e. line in the listing), except for the last one.
481 // Otherwise, there would one more new line in the output than in the LyX document.
486 for (auto const & p : pars) {
487 xml::openTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnerattr(),
488 par->layout().docbookiteminnertagtype());
489 xs << XMLStream::ESCAPE_NONE << p;
490 xml::closeTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnertagtype());
494 makeAny(text, buf, xs, runparams, par);
497 // Close the environment.
498 closeParTag(xs, &*par, (nextpar != end) ? &*nextpar : nullptr, runparams);
502 ParagraphList::const_iterator findEndOfEnvironment(
503 ParagraphList::const_iterator const & pstart,
504 ParagraphList::const_iterator const & pend)
506 // Copy-paste from XHTML. Should be factored out at some point...
507 ParagraphList::const_iterator p = pstart;
508 Layout const & bstyle = p->layout();
509 size_t const depth = p->params().depth();
510 for (++p; p != pend; ++p) {
511 Layout const & style = p->layout();
512 // It shouldn't happen that e.g. a section command occurs inside
513 // a quotation environment, at a higher depth, but as of 6/2009,
514 // it can happen. We pretend that it's just at lowest depth.
515 if (style.latextype == LATEX_COMMAND)
518 // If depth is down, we're done
519 if (p->params().depth() < depth)
522 // If depth is up, we're not done
523 if (p->params().depth() > depth)
526 // FIXME I am not sure about the first check.
527 // Surely we *could* have different layouts that count as
528 // LATEX_PARAGRAPH, right?
529 if (style.latextype == LATEX_PARAGRAPH || style != bstyle)
536 ParagraphList::const_iterator makeListEnvironment(Text const &text,
539 OutputParams const &runparams,
540 ParagraphList::const_iterator const & begin)
544 auto const end = text.paragraphs().end();
545 auto const envend = findEndOfEnvironment(par, end);
547 // Output the opening tag for this environment.
548 Layout const & envstyle = par->layout();
549 xml::openTag(xs, envstyle.docbookwrappertag(), envstyle.docbookwrapperattr(), envstyle.docbookwrappertagtype());
550 xml::openTag(xs, envstyle.docbooktag(), envstyle.docbookattr(), envstyle.docbooktagtype());
552 // Handle the content of the list environment, item by item.
553 while (par != envend) {
554 // Skip this paragraph if it is both empty and the last one (otherwise, there may be deeper paragraphs after).
557 if (par->empty() && nextpar == envend)
560 // Open the item wrapper.
561 Layout const & style = par->layout();
562 xml::openTag(xs, style.docbookitemwrappertag(), style.docbookitemwrapperattr(),
563 style.docbookitemwrappertagtype());
565 // Generate the label, if need be. If it is taken from the text, sep != 0 and corresponds to the first
566 // character after the label.
568 if (style.labeltype != LABEL_NO_LABEL && style.docbookitemlabeltag() != "NONE") {
569 if (style.labeltype == LABEL_MANUAL) {
570 // Only variablelist gets here (or similar items defined as an extension in the layout).
571 xml::openTag(xs, style.docbookitemlabeltag(), style.docbookitemlabelattr(),
572 style.docbookitemlabeltagtype());
573 sep = 1 + par->firstWordDocBook(xs, runparams);
574 xml::closeTag(xs, style.docbookitemlabeltag(), style.docbookitemlabeltagtype());
576 // Usual cases: maybe there is something specified at the layout level. Highly unlikely, though.
577 docstring const lbl = par->params().labelString();
580 xml::openTag(xs, style.docbookitemlabeltag(), style.docbookitemlabelattr(),
581 style.docbookitemlabeltagtype());
583 xml::closeTag(xs, style.docbookitemlabeltag(), style.docbookitemlabeltagtype());
588 // Open the item (after the wrapper and the label).
589 xml::openTag(xs, style.docbookitemtag(), style.docbookitemattr(), style.docbookitemtagtype());
591 // Generate the content of the item.
592 if (sep < par->size()) {
593 auto pars = par->simpleDocBookOnePar(buf, runparams,
594 text.outerFont(std::distance(text.paragraphs().begin(), par)), sep);
595 for (auto &p : pars) {
596 xml::openTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnerattr(),
597 par->layout().docbookiteminnertagtype());
598 xs << XMLStream::ESCAPE_NONE << p;
599 xml::closeTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnertagtype());
602 // DocBook doesn't like emptiness.
603 xml::compTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnerattr(),
604 par->layout().docbookiteminnertagtype());
607 // If the next item is deeper, it must go entirely within this item (do it recursively).
608 // By construction, with findEndOfEnvironment, depth can only stay constant or increase, never decrease.
609 depth_type currentDepth = par->getDepth();
611 while (par != envend && par->getDepth() != currentDepth)
612 par = makeAny(text, buf, xs, runparams, par);
613 // Usually, this loop only makes one iteration, except in complex scenarios, like an item with a paragraph,
614 // a list, and another paragraph; or an item with two types of list (itemise then enumerate, for instance).
617 xml::closeTag(xs, style.docbookitemtag(), style.docbookitemtagtype());
618 xml::closeTag(xs, style.docbookitemwrappertag(), style.docbookitemwrappertagtype());
621 // Close this environment in exactly the same way as it was opened.
622 xml::closeTag(xs, envstyle.docbooktag(), envstyle.docbooktagtype());
623 xml::closeTag(xs, envstyle.docbookwrappertag(), envstyle.docbookwrappertagtype());
633 OutputParams const & runparams,
634 ParagraphList::const_iterator const & par)
637 // Unlike XHTML, no need for labels, as they are handled by DocBook tags.
638 auto const begin = text.paragraphs().begin();
639 auto const end = text.paragraphs().end();
643 // Generate this command.
644 auto prevpar = text.paragraphs().getParagraphBefore(par);
645 openParTag(xs, &*par, prevpar, runparams);
647 auto pars = par->simpleDocBookOnePar(buf, runparams,text.outerFont(distance(begin, par)));
648 for (auto & parXML : pars)
649 // TODO: decide what to do with openParTag/closeParTag in new lines.
650 xs << XMLStream::ESCAPE_NONE << parXML;
652 closeParTag(xs, &*par, (nextpar != end) ? &*nextpar : nullptr, runparams);
656 bool isLayoutSectioning(Layout const & lay)
658 if (lay.docbooksection()) // Special case: some DocBook styles must be handled as sections.
660 else if (lay.category() == from_utf8("Sectioning") || lay.docbooktag() == "section") // Generic case.
661 return lay.toclevel != Layout::NOT_IN_TOC;
666 bool isLayoutSectioningOrSimilar(Layout const & lay)
668 return isLayoutSectioning(lay) || lay.docbooktag() == "bridgehead";
672 using DocBookDocumentSectioning = tuple<bool, pit_type>;
675 struct DocBookInfoTag
677 const set<pit_type> shouldBeInInfo;
678 const set<pit_type> mustBeInInfo; // With the notable exception of the abstract!
679 const set<pit_type> abstract;
680 const bool abstractLayout;
684 DocBookInfoTag(const set<pit_type> & shouldBeInInfo, const set<pit_type> & mustBeInInfo,
685 const set<pit_type> & abstract, bool abstractLayout, pit_type bpit, pit_type epit) :
686 shouldBeInInfo(shouldBeInInfo), mustBeInInfo(mustBeInInfo), abstract(abstract),
687 abstractLayout(abstractLayout), bpit(bpit), epit(epit) {}
691 DocBookDocumentSectioning hasDocumentSectioning(ParagraphList const ¶graphs, pit_type bpit, pit_type const epit) {
692 bool documentHasSections = false;
694 while (bpit < epit) {
695 Layout const &style = paragraphs[bpit].layout();
696 documentHasSections |= isLayoutSectioningOrSimilar(style);
698 if (documentHasSections)
702 // Paragraphs before the first section: [ runparams.par_begin ; eppit )
704 return make_tuple(documentHasSections, bpit);
708 bool hasOnlyNotes(Paragraph const & par)
710 // Precondition: the paragraph is not empty. Otherwise, the function will always return true...
711 for (int i = 0; i < par.size(); ++i)
712 // If you find something that is not an inset (like actual text) or an inset that is not a note,
714 if (!par.isInset(i) || par.getInset(i)->lyxCode() != NOTE_CODE)
717 // An empty paragraph may still require some output.
718 if (par.layout().docbooksection())
721 // There should be really no content here.
726 DocBookInfoTag getParagraphsWithInfo(ParagraphList const ¶graphs,
727 pit_type bpit, pit_type const epit,
728 // Typically, bpit is the beginning of the document and epit the end of the
729 // document *or* the first section.
730 bool documentHasSections,
731 bool detectUnlayoutedAbstract
732 // Whether paragraphs with no specific layout should be detected as abstracts.
733 // For inner sections, an abstract should only be detected if it has a specific
734 // layout. For others, anything that might look like an abstract should be sought.
736 set<pit_type> shouldBeInInfo;
737 set<pit_type> mustBeInInfo;
738 set<pit_type> abstractWithLayout;
739 set<pit_type> abstractNoLayout;
741 // Find the first non empty paragraph by mutating bpit.
742 while (bpit < epit) {
743 Paragraph const &par = paragraphs[bpit];
744 if (par.empty() || hasOnlyNotes(par))
750 // Traverse everything that might belong to <info>.
751 bool hasAbstractLayout = false;
752 static depth_type INVALID_DEPTH = 100000;
753 depth_type abstractDepth = INVALID_DEPTH;
754 pit_type cpit = bpit;
755 for (; cpit < epit; ++cpit) {
756 // Skip paragraphs that don't generate anything in DocBook.
757 Paragraph const & par = paragraphs[cpit];
758 Layout const &style = par.layout();
759 if (hasOnlyNotes(par))
762 // There should never be any section here, except for the first paragraph (a title can be part of <info>).
763 // (Just a sanity check: if this fails, this function could end up processing the whole document.)
764 if (cpit != bpit && isLayoutSectioningOrSimilar(par.layout())) {
765 LYXERR0("Assertion failed: section found in potential <info> paragraphs.");
769 // If this is marked as an abstract by the layout, put it in the right set.
770 if (style.docbookabstract()) {
771 hasAbstractLayout = true;
772 abstractDepth = par.getDepth();
773 abstractWithLayout.emplace(cpit);
777 // Deeper paragraphs following the abstract must still be considered as part of the abstract.
778 // For instance, this includes lists. There should not be any other kind of paragraph in between.
779 if (abstractDepth != INVALID_DEPTH && style.docbookininfo() == "never") {
780 if (par.getDepth() > abstractDepth) {
781 abstractWithLayout.emplace(cpit);
784 if (par.getDepth() == abstractDepth) {
785 // This is not an abstract paragraph and it should not either be considered as part
786 // of it. It breaks the rule that abstract paragraphs must follow each other.
787 abstractDepth = INVALID_DEPTH;
792 // Based on layout information, store this paragraph in one set: should be in <info>, must be,
793 // or abstract (either because of layout or of position).
794 if (style.docbookininfo() == "always")
795 mustBeInInfo.emplace(cpit);
796 else if (style.docbookininfo() == "maybe")
797 shouldBeInInfo.emplace(cpit);
798 else if (documentHasSections && !hasAbstractLayout && detectUnlayoutedAbstract &&
799 (style.docbooktag() == "NONE" || style.docbooktag() == "para") &&
800 style.docbookwrappertag() == "NONE")
801 // In this case, it is very likely that style.docbookininfo() == "never"! Be extra careful
802 // about anything that gets caught here. For instance, don't ake into account
803 abstractNoLayout.emplace(cpit);
804 else // This should definitely not be in <info>.
807 // Now, cpit points to the first paragraph that no more has things that could go in <info>.
808 // bpit is the beginning of the <info> part.
810 return DocBookInfoTag(shouldBeInInfo, mustBeInInfo,
811 hasAbstractLayout ? abstractWithLayout : abstractNoLayout,
812 hasAbstractLayout, bpit, cpit);
815 } // end anonymous namespace
818 std::set<const Inset *> gatherInfo(ParagraphList::const_iterator par)
820 // This function has a structure highly similar to makeAny and its friends. It's only made to be called on what
821 // should become the document's <abstract>.
822 std::set<const Inset *> values;
824 // If this kind of layout should be ignored, already leave.
825 if (par->layout().docbooktag() == "IGNORE")
828 // If this should go in info, mark it as such. Dive deep into the abstract, as it may hide many things that
829 // DocBook doesn't want to be inside the abstract.
830 for (pos_type i = 0; i < par->size(); ++i) {
831 if (par->getInset(i) && par->getInset(i)->asInsetText()) {
832 InsetText const *inset = par->getInset(i)->asInsetText();
834 if (inset->getLayout().docbookininfo() != "never") {
835 values.insert(inset);
837 auto subpar = inset->paragraphs().begin();
838 while (subpar != inset->paragraphs().end()) {
839 auto subinfos = gatherInfo(subpar);
840 for (auto & subinfo: subinfos)
841 values.insert(subinfo);
852 ParagraphList::const_iterator makeAny(Text const &text,
855 OutputParams const &runparams,
856 ParagraphList::const_iterator par)
858 bool ignoreParagraph = false;
860 // If this kind of layout should be ignored, already leave.
861 ignoreParagraph |= par->layout().docbooktag() == "IGNORE";
863 // For things that should go into <info>, check the variable rp.docbook_generate_info. This does not apply to the
865 bool isAbstract = par->layout().docbookabstract() || par->layout().docbooktag() == "abstract";
866 ignoreParagraph |= !isAbstract && par->layout().docbookininfo() != "never" && !runparams.docbook_generate_info;
868 // Switch on the type of paragraph to call the right handler.
869 if (!ignoreParagraph) {
870 switch (par->layout().latextype) {
872 makeCommand(text, buf, xs, runparams, par);
874 case LATEX_ENVIRONMENT:
875 makeEnvironment(text, buf, xs, runparams, par);
877 case LATEX_LIST_ENVIRONMENT:
878 case LATEX_ITEM_ENVIRONMENT:
879 // Only case when makeAny() might consume more than one paragraph.
880 return makeListEnvironment(text, buf, xs, runparams, par);
881 case LATEX_PARAGRAPH:
882 makeParagraph(text, buf, xs, runparams, par);
884 case LATEX_BIB_ENVIRONMENT:
885 makeBibliography(text, buf, xs, runparams, par);
890 // For cases that are not lists, the next paragraph to handle is the next one.
896 xml::FontTag docbookStartFontTag(xml::FontTypes type)
898 return xml::FontTag(from_utf8(fontToDocBookTag(type)), from_utf8(fontToAttribute(type)), type);
902 xml::EndFontTag docbookEndFontTag(xml::FontTypes type)
904 return xml::EndFontTag(from_utf8(fontToDocBookTag(type)), type);
908 void outputDocBookInfo(
912 OutputParams const & runparams,
913 ParagraphList const & paragraphs,
914 DocBookInfoTag const & info)
916 // Perform an additional check on the abstract. Sometimes, there are many paragraphs that should go
917 // into the abstract, but none generates actual content. Thus, first generate to a temporary stream,
918 // then only create the <abstract> tag if these paragraphs generate some content.
919 // This check must be performed *before* a decision on whether or not to output <info> is made.
920 bool hasAbstract = !info.abstract.empty();
922 set<const Inset *> infoInsets; // Paragraphs that should go into <info>, but are hidden in an <abstract>
923 // paragraph. (This happens for quite a few layouts, unfortunately.)
926 // Generate the abstract XML into a string before further checks.
927 // Usually, makeAny only generates one paragraph at a time. However, for the specific case of lists, it might
928 // generate more than one paragraph, as indicated in the return value.
929 odocstringstream os2;
933 rp.docbook_generate_info = false;
934 rp.docbook_ignore_wrapper = true;
936 set<pit_type> doneParas; // Paragraphs that have already been converted (mostly to deal with lists).
937 for (auto const & p : info.abstract) {
938 if (doneParas.find(p) == doneParas.end()) {
939 auto oldPar = paragraphs.iterator_at(p);
940 auto newPar = makeAny(text, buf, xs2, rp, oldPar);
942 // Find insets that should go outside the abstract.
943 auto subinfos = gatherInfo(oldPar);
944 for (auto & subinfo: subinfos)
945 infoInsets.insert(subinfo);
947 // Insert the indices of all the paragraphs that were just generated (typically, one).
948 // **Make the hypothesis that, when an abstract has a list, all its items are consecutive.**
949 // Otherwise, makeAny and makeListEnvironment would have to be adapted too.
951 while (oldPar != newPar) {
952 doneParas.emplace(id);
959 // Actually output the abstract if there is something to do. Don't count line feeds or spaces in this,
960 // even though they must be properly output if there is some abstract.
961 abstract = os2.str();
962 docstring cleaned = abstract;
963 cleaned.erase(std::remove_if(cleaned.begin(), cleaned.end(), lyx::isSpace), cleaned.end());
965 // Nothing? Then there is no abstract!
970 // The abstract must go in <info>. Otherwise, decide whether to open <info> based on the layouts.
971 bool needInfo = !info.mustBeInInfo.empty() || hasAbstract;
973 // Start the <info> tag if required.
975 xs.startDivision(false);
976 xs << xml::StartTag("info");
980 // Output the elements that should go in <info>.
981 // - First, the title.
982 for (auto pit : info.shouldBeInInfo) // Typically, the title: these elements are so important and ubiquitous
983 // that mandating a wrapper like <info> would repel users. Thus, generate them first.
984 makeAny(text, buf, xs, runparams, paragraphs.iterator_at(pit));
985 // If there is no title, generate one (required for the document to be valid).
986 // This code is called for the main document, for table cells, etc., so be precise in this condition.
987 if (text.isMainText() && info.shouldBeInInfo.empty() && !runparams.inInclude) {
988 xs << xml::StartTag("title");
989 xs << "Untitled Document";
990 xs << xml::EndTag("title");
994 // - Then, other metadata.
995 for (auto pit : info.mustBeInInfo)
996 makeAny(text, buf, xs, runparams, paragraphs.iterator_at(pit));
997 for (auto const * inset : infoInsets)
998 inset->docbook(xs, runparams);
1000 // - Finally, always output the abstract as the last item of the <info>, as it requires special treatment
1001 // (especially if it contains several paragraphs that are empty).
1003 string tag = paragraphs[*info.abstract.begin()].layout().docbookforceabstracttag();
1007 if (!xs.isLastTagCR())
1010 xs << xml::StartTag(tag);
1012 xs << XMLStream::ESCAPE_NONE << abstract;
1013 xs << xml::EndTag(tag);
1017 // End the <info> tag if it was started.
1019 if (!xs.isLastTagCR())
1022 xs << xml::EndTag("info");
1029 void docbookSimpleAllParagraphs(
1033 OutputParams const & runparams)
1035 // Handle the given text, supposing it has no sections (i.e. a "simple" text). The input may vary in length
1036 // between a single paragraph to a whole document.
1037 pit_type const bpit = runparams.par_begin;
1038 pit_type const epit = runparams.par_end;
1039 ParagraphList const ¶graphs = text.paragraphs();
1041 // First, the <info> tag.
1042 DocBookInfoTag info = getParagraphsWithInfo(paragraphs, bpit, epit, false, true);
1043 outputDocBookInfo(text, buf, xs, runparams, paragraphs, info);
1045 // Then, the content. It starts where the <info> ends.
1046 auto par = paragraphs.iterator_at(info.epit);
1047 auto end = paragraphs.iterator_at(epit);
1048 while (par != end) {
1049 if (!hasOnlyNotes(*par))
1050 par = makeAny(text, buf, xs, runparams, par);
1057 void docbookParagraphs(Text const &text,
1060 OutputParams const &runparams) {
1061 ParagraphList const ¶graphs = text.paragraphs();
1062 if (runparams.par_begin == runparams.par_end) {
1063 runparams.par_begin = 0;
1064 runparams.par_end = paragraphs.size();
1066 pit_type bpit = runparams.par_begin;
1067 pit_type const epit = runparams.par_end;
1068 LASSERT(bpit < epit,
1070 xs << XMLStream::ESCAPE_NONE << "<!-- DocBook output error! -->\n";
1074 // Detect whether the document contains sections. If there are no sections, treatment is largely simplified.
1075 // In particular, there can't be an abstract, unless it is manually marked.
1076 bool documentHasSections;
1078 tie(documentHasSections, eppit) = hasDocumentSectioning(paragraphs, bpit, epit);
1080 // Deal with "simple" documents, i.e. those without sections.
1081 if (!documentHasSections) {
1082 docbookSimpleAllParagraphs(text, buf, xs, runparams);
1086 // Output the first <info> tag (or just the title).
1087 DocBookInfoTag info = getParagraphsWithInfo(paragraphs, bpit, eppit, true, true);
1088 outputDocBookInfo(text, buf, xs, runparams, paragraphs, info);
1091 std::stack<std::pair<int, string>> headerLevels; // Used to determine when to open/close sections: store the depth
1092 // of the section and the tag that was used to open it.
1094 // Then, iterate through the paragraphs of this document.
1095 auto par = text.paragraphs().iterator_at(bpit);
1096 auto end = text.paragraphs().iterator_at(epit);
1097 while (par != end) {
1098 // Skip paragraphs not producing any output.
1099 if (hasOnlyNotes(*par)) {
1104 OutputParams ourparams = runparams;
1105 Layout const &style = par->layout();
1107 // Think about adding <section> and/or </section>s.
1108 if (isLayoutSectioning(style) || par->params().startOfAppendix()) {
1109 int level = style.toclevel;
1111 // Need to close a previous section if it has the same level or a higher one (close <section> if opening a
1112 // <h2> after a <h2>, <h3>, <h4>, <h5> or <h6>). More examples:
1113 // - current: h2; back: h1; do not close any <section>
1114 // - current: h1; back: h2; close two <section> (first the <h2>, then the <h1>, so a new <h1> can come)
1115 // Some layouts require that Layout::NOT_IN_TOC sections still cause closing of previous sections. This is
1116 // mostly to ensure that the section is positioned at a DocBook-compatible level (acknowledgements: cannot
1117 // be under a section!).
1118 while (!headerLevels.empty() && level <= headerLevels.top().first) {
1119 // Output the tag only if it corresponds to a legit section.
1120 int stackLevel = headerLevels.top().first;
1121 if (stackLevel != Layout::NOT_IN_TOC) {
1122 xs << xml::EndTag(headerLevels.top().second);
1128 // Open the new section: first push it onto the stack, then output it in DocBook.
1129 string sectionTag = (par->params().startOfAppendix()) ? "appendix" : style.docbooksectiontag();
1130 headerLevels.push(std::make_pair(level, sectionTag));
1132 // Some sectioning-like elements should not be output (such as FrontMatter).
1133 if (level != Layout::NOT_IN_TOC) {
1134 // Look for a label in the title, i.e. a InsetLabel as a child.
1135 docstring id = docstring();
1136 for (pos_type i = 0; i < par->size(); ++i) {
1137 Inset const *inset = par->getInset(i);
1139 if (auto label = dynamic_cast<InsetLabel const *>(inset)) {
1140 // Generate the attributes for the section if need be.
1141 id += "xml:id=\"" + xml::cleanID(label->screenLabel()) + "\"";
1143 // Don't output the ID as a DocBook <anchor>.
1144 ourparams.docbook_anchors_to_ignore.emplace(label->screenLabel());
1146 // Cannot have multiple IDs per tag. If there is another ID inset in the document, it will
1147 // be output as a DocBook anchor.
1153 // Write the open tag for this section.
1157 xs << xml::StartTag(sectionTag, attrs);
1162 // Close all sections before the bibliography.
1163 // 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)?
1164 if (!par->insetList().empty()) {
1165 Inset const *firstInset = par->getInset(0);
1166 if (firstInset && (firstInset->lyxCode() == BIBITEM_CODE || firstInset->lyxCode() == BIBTEX_CODE)) {
1167 while (!headerLevels.empty()) {
1168 // Don't close appendices before bibliographies.
1169 if (headerLevels.top().second == "appendix")
1172 // Pop the section from the stack.
1173 int level = headerLevels.top().first;
1174 docstring tag = from_utf8("</" + headerLevels.top().second + ">");
1177 // Output the tag only if it corresponds to a legit section, as the rest of the code.
1178 if (level != Layout::NOT_IN_TOC) {
1179 xs << XMLStream::ESCAPE_NONE << tag;
1186 // Generate the <info> tag if a section was just opened.
1187 // Some sections may require abstracts (mostly parts, in books: DocBookForceAbstractTag will not be NONE),
1188 // others can still have an abstract (it must be detected so that it can be output at the right place).
1189 // 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.
1190 if (isLayoutSectioning(style)) {
1191 // This abstract may be found between the next paragraph and the next title.
1192 pit_type cpit = std::distance(text.paragraphs().begin(), par);
1193 pit_type ppit = std::get<1>(hasDocumentSectioning(paragraphs, cpit + 1L, epit));
1195 // Generate this abstract (this code corresponds to parts of outputDocBookInfo).
1196 DocBookInfoTag secInfo = getParagraphsWithInfo(paragraphs, cpit, ppit, true,
1197 style.docbookforceabstracttag() != "NONE");
1199 if (!secInfo.mustBeInInfo.empty() || !secInfo.shouldBeInInfo.empty() || !secInfo.abstract.empty()) {
1200 // Generate the <info>, if required. If DocBookForceAbstractTag != NONE, this abstract will not be in
1201 // <info>, unlike other ("standard") abstracts.
1202 bool hasStandardAbstract = !secInfo.abstract.empty() && style.docbookforceabstracttag() == "NONE";
1203 bool needInfo = !secInfo.mustBeInInfo.empty() || hasStandardAbstract;
1206 xs.startDivision(false);
1207 xs << xml::StartTag("info");
1211 // Output the elements that should go in <info>, before and after the abstract.
1212 for (auto pit : secInfo.shouldBeInInfo) // Typically, the title: these elements are so important and ubiquitous
1213 // that mandating a wrapper like <info> would repel users. Thus, generate them first.
1214 makeAny(text, buf, xs, ourparams, paragraphs.iterator_at(pit));
1215 for (auto pit : secInfo.mustBeInInfo)
1216 makeAny(text, buf, xs, ourparams, paragraphs.iterator_at(pit));
1218 // Deal with the abstract in <info> if it is standard (i.e. its tag is <abstract>).
1219 if (!secInfo.abstract.empty() && hasStandardAbstract) {
1220 if (!secInfo.abstractLayout) {
1221 xs << xml::StartTag("abstract");
1225 for (auto const &p : secInfo.abstract)
1226 makeAny(text, buf, xs, ourparams, paragraphs.iterator_at(p));
1228 if (!secInfo.abstractLayout) {
1229 xs << xml::EndTag("abstract");
1234 // End the <info> tag if it was started.
1236 if (!xs.isLastTagCR())
1239 xs << xml::EndTag("info");
1244 // Deal with the abstract outside <info> if it is not standard (i.e. its tag is layout-defined).
1245 if (!secInfo.abstract.empty() && !hasStandardAbstract) {
1246 // Assert: style.docbookforceabstracttag() != NONE.
1247 xs << xml::StartTag(style.docbookforceabstracttag());
1249 for (auto const &p : secInfo.abstract)
1250 makeAny(text, buf, xs, ourparams, paragraphs.iterator_at(p));
1251 xs << xml::EndTag(style.docbookforceabstracttag());
1255 // Skip all the text that has just been generated.
1256 par = paragraphs.iterator_at(secInfo.epit);
1258 // No <info> tag to generate, proceed as for normal paragraphs.
1259 par = makeAny(text, buf, xs, ourparams, par);
1262 // Generate this paragraph, as it has nothing special.
1263 par = makeAny(text, buf, xs, ourparams, par);
1267 // If need be, close <section>s, but only at the end of the document (otherwise, dealt with at the beginning
1269 while (!headerLevels.empty() && headerLevels.top().first > Layout::NOT_IN_TOC) {
1270 docstring tag = from_utf8("</" + headerLevels.top().second + ">");
1272 xs << XMLStream::ESCAPE_NONE << tag;