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 // When should the wrapper be opened here? Only if the previous paragraph has the SAME wrapper tag
179 // (usually, they won't have the same layout) and the CURRENT one allows merging.
180 // The main use case is author information in several paragraphs: if the name of the author is the
181 // first paragraph of an author, then merging with the previous tag does not make sense. Say the
182 // next paragraph is the affiliation, then it should be output in the same <author> tag (different
183 // layout, same wrapper tag).
184 Layout const & lay = par->layout();
185 bool openWrapper = lay.docbookwrappertag() != "NONE" && !runparams.docbook_ignore_wrapper;
186 if (prevpar != nullptr) {
187 Layout const & prevlay = prevpar->layout();
188 if (prevlay.docbookwrappertag() != "NONE") {
189 if (prevlay.docbookwrappertag() == lay.docbookwrappertag() &&
190 prevlay.docbookwrapperattr() == lay.docbookwrapperattr())
191 openWrapper = !lay.docbookwrappermergewithprevious();
199 xml::openTag(xs, lay.docbookwrappertag(), lay.docbookwrapperattr(), lay.docbookwrappertagtype());
201 const string & tag = lay.docbooktag();
203 auto xmltag = xml::ParTag(tag, lay.docbookattr());
204 if (!xs.isTagOpen(xmltag, 1)) { // Don't nest a paragraph directly in a paragraph.
205 // TODO: required or not?
206 // TODO: avoid creating a ParTag object just for this query...
207 xml::openTag(xs, lay.docbooktag(), lay.docbookattr(), lay.docbooktagtype());
208 xml::openTag(xs, lay.docbookinnertag(), lay.docbookinnerattr(), lay.docbookinnertagtype());
212 xml::openTag(xs, lay.docbookitemwrappertag(), lay.docbookitemwrapperattr(), lay.docbookitemwrappertagtype());
213 xml::openTag(xs, lay.docbookitemtag(), lay.docbookitemattr(), lay.docbookitemtagtype());
214 xml::openTag(xs, lay.docbookiteminnertag(), lay.docbookiteminnerattr(), lay.docbookiteminnertagtype());
218 void closeParTag(XMLStream & xs, Paragraph const * par, Paragraph const * nextpar, const OutputParams & runparams)
223 // See comment in openParTag.
224 Layout const & lay = par->layout();
225 bool closeWrapper = lay.docbookwrappertag() != "NONE" && !runparams.docbook_ignore_wrapper;
226 if (nextpar != nullptr) {
227 Layout const & nextlay = nextpar->layout();
228 if (nextlay.docbookwrappertag() != "NONE") {
229 if (nextlay.docbookwrappertag() == lay.docbookwrappertag() &&
230 nextlay.docbookwrapperattr() == lay.docbookwrapperattr())
231 closeWrapper = !nextlay.docbookwrappermergewithprevious();
238 xml::closeTag(xs, lay.docbookiteminnertag(), lay.docbookiteminnertagtype());
239 xml::closeTag(xs, lay.docbookitemtag(), lay.docbookitemtagtype());
240 xml::closeTag(xs, lay.docbookitemwrappertag(), lay.docbookitemwrappertagtype());
241 xml::closeTag(xs, lay.docbookinnertag(), lay.docbookinnertagtype());
242 xml::closeTag(xs, lay.docbooktag(), lay.docbooktagtype());
244 xml::closeTag(xs, lay.docbookwrappertag(), lay.docbookwrappertagtype());
248 void makeBibliography(
252 OutputParams const & runparams,
253 ParagraphList::const_iterator const & par)
255 // If this is the first paragraph in a bibliography, open the bibliography tag.
256 auto const * pbegin_before = text.paragraphs().getParagraphBefore(par);
257 if (pbegin_before == nullptr || (pbegin_before && pbegin_before->layout().latextype != LATEX_BIB_ENVIRONMENT)) {
258 xs << xml::StartTag("bibliography");
262 // Start the precooked bibliography entry. This is very much like opening a paragraph tag.
263 // Don't forget the citation ID!
265 for (auto i = 0; i < par->size(); ++i) {
266 Inset const *ip = par->getInset(i);
269 if (const auto * bibitem = dynamic_cast<const InsetBibitem*>(ip)) {
270 auto id = xml::cleanID(bibitem->getParam("key"));
271 attr = from_utf8("xml:id='") + id + from_utf8("'");
275 xs << xml::StartTag(from_utf8("bibliomixed"), attr);
277 // Generate the entry. Concatenate the different parts of the paragraph if any.
278 auto const begin = text.paragraphs().begin();
279 auto pars = par->simpleDocBookOnePar(buf, runparams, text.outerFont(std::distance(begin, par)), 0);
280 for (auto & parXML : pars)
281 xs << XMLStream::ESCAPE_NONE << parXML;
283 // End the precooked bibliography entry.
284 xs << xml::EndTag("bibliomixed");
287 // If this is the last paragraph in a bibliography, close the bibliography tag.
288 auto const end = text.paragraphs().end();
291 bool endBibliography = nextpar == end || nextpar->layout().latextype != LATEX_BIB_ENVIRONMENT;
293 if (endBibliography) {
294 xs << xml::EndTag("bibliography");
304 OutputParams const & runparams,
305 ParagraphList::const_iterator const & par)
308 auto const begin = text.paragraphs().begin();
309 auto const end = text.paragraphs().end();
310 auto prevpar = text.paragraphs().getParagraphBefore(par);
312 // We want to open the paragraph tag if:
313 // (i) the current layout permits multiple paragraphs
314 // (ii) we are either not already inside a paragraph (HTMLIsBlock) OR
315 // we are, but this is not the first paragraph
317 // But there is also a special case, and we first see whether we are in it.
318 // We do not want to open the paragraph tag if this paragraph contains
319 // only one item, and that item is "inline", i.e., not HTMLIsBlock (such
320 // as a branch). On the other hand, if that single item has a font change
321 // applied to it, then we still do need to open the paragraph.
323 // Obviously, this is very fragile. The main reason we need to do this is
324 // because of branches, e.g., a branch that contains an entire new section.
325 // We do not really want to wrap that whole thing in a <div>...</div>.
326 bool special_case = false;
327 Inset const *specinset = par->size() == 1 ? par->getInset(0) : nullptr;
328 if (specinset && !specinset->getLayout().htmlisblock()) { // TODO: Convert htmlisblock to a DocBook parameter?
329 Layout const &style = par->layout();
330 FontInfo const first_font = style.labeltype == LABEL_MANUAL ?
331 style.labelfont : style.font;
332 FontInfo const our_font =
333 par->getFont(buf.masterBuffer()->params(), 0,
334 text.outerFont(std::distance(begin, par))).fontInfo();
336 if (first_font == our_font)
340 size_t nInsets = std::distance(par->insetList().begin(), par->insetList().end());
341 auto parSize = (size_t) par->size();
343 // If this LyX code does not produce any output, it can be safely ignored in the following checks: if this thing
344 // is present in the paragraph, it has no impact on the definition of the special case (i.e. whether or not
345 // a <para> tag should be output).
346 auto isLyxCodeToIgnore = [](InsetCode x) { return x == TOC_CODE || x == NOTE_CODE; };
348 // TODO: if a paragraph *only* contains floats, listings, bibliographies, etc., should this be considered as a
349 // special case? If so, the code could be largely simplifies (all the calls to all_of, basically) and optimised
350 // at the compilation stage.
352 // Plain layouts must be ignored.
353 special_case |= buf.params().documentClass().isPlainLayout(par->layout()) && !runparams.docbook_force_pars;
354 // Equations do not deserve their own paragraph (DocBook allows them outside paragraphs).
355 // Exception: any case that generates an <inlineequation> must still get a paragraph to be valid.
356 special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), [](InsetList::Element inset) {
357 return inset.inset && inset.inset->asInsetMath() && inset.inset->asInsetMath()->getType() != hullSimple;
359 // Tables do not deserve their own paragraphs (DocBook allows them outside paragraphs).
360 special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), [isLyxCodeToIgnore](InsetList::Element inset) {
361 return inset.inset->lyxCode() == TABULAR_CODE || isLyxCodeToIgnore(inset.inset->lyxCode());
363 // Floats cannot be in paragraphs.
364 special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), [isLyxCodeToIgnore](InsetList::Element inset) {
365 return inset.inset->lyxCode() == FLOAT_CODE || isLyxCodeToIgnore(inset.inset->lyxCode());
367 // Bibliographies cannot be in paragraphs. Bibitems should still be handled as paragraphs, though
368 // (see makeParagraphBibliography).
369 special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), [isLyxCodeToIgnore](InsetList::Element inset) {
370 return inset.inset->lyxCode() == BIBTEX_CODE || isLyxCodeToIgnore(inset.inset->lyxCode());
372 // ERTs are in comments, not paragraphs.
373 special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), [isLyxCodeToIgnore](InsetList::Element inset) {
374 return inset.inset->lyxCode() == ERT_CODE || isLyxCodeToIgnore(inset.inset->lyxCode());
376 // Listings should not get into their own paragraph.
377 special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), [isLyxCodeToIgnore](InsetList::Element inset) {
378 return inset.inset->lyxCode() == LISTINGS_CODE || isLyxCodeToIgnore(inset.inset->lyxCode());
380 // Boxes cannot get into their own paragraph.
381 special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), [isLyxCodeToIgnore](InsetList::Element inset) {
382 return inset.inset->lyxCode() == BOX_CODE || isLyxCodeToIgnore(inset.inset->lyxCode());
384 // Includes should not have a paragraph.
385 special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), [isLyxCodeToIgnore](InsetList::Element inset) {
386 return inset.inset->lyxCode() == INCLUDE_CODE || isLyxCodeToIgnore(inset.inset->lyxCode());
388 // Glossaries should not have a paragraph.
389 special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), [isLyxCodeToIgnore](InsetList::Element inset) {
390 return inset.inset->lyxCode() == NOMENCL_PRINT_CODE || isLyxCodeToIgnore(inset.inset->lyxCode());
393 bool const open_par = runparams.docbook_make_pars
394 && !runparams.docbook_in_par
397 // We want to issue the closing tag if either:
398 // (i) We opened it, and either docbook_in_par is false,
399 // or we're not in the last paragraph, anyway.
400 // (ii) We didn't open it and docbook_in_par is true,
401 // but we are in the first par, and there is a next par.
402 bool const close_par = open_par && !runparams.docbook_in_par;
404 // Determine if this paragraph has some real content. Things like new pages are not caught
405 // by Paragraph::empty(), even though they do not generate anything useful in DocBook.
406 // Thus, remove all spaces (including new lines: \r, \n) before checking for emptiness.
407 // std::all_of allows doing this check without having to copy the string.
408 // Open and close tags around each contained paragraph.
411 auto pars = par->simpleDocBookOnePar(buf, runparams, text.outerFont(distance(begin, par)), 0, nextpar == end, special_case);
412 for (docstring const & parXML : pars) {
413 if (!xml::isNotOnlySpace(parXML))
417 openParTag(xs, &*par, prevpar, runparams);
419 xs << XMLStream::ESCAPE_NONE << parXML;
422 closeParTag(xs, &*par, (nextpar != end) ? &*nextpar : nullptr, runparams);
427 void makeEnvironment(Text const &text,
430 OutputParams const &runparams,
431 ParagraphList::const_iterator const & par)
434 auto const end = text.paragraphs().end();
438 // Special cases for listing-like environments provided in layouts. This is quite ad-hoc, but provides a useful
439 // default. This should not be used by too many environments (only LyX-Code right now).
440 // This would be much simpler if LyX-Code was implemented as InsetListings...
441 bool mimicListing = false;
442 bool ignoreFonts = false;
443 if (par->layout().docbooktag() == "programlisting") {
448 // Output the opening tag for this environment, but only if it has not been previously opened (condition
449 // implemented in openParTag).
450 auto prevpar = text.paragraphs().getParagraphBefore(par);
451 openParTag(xs, &*par, prevpar, runparams);
453 // Generate the contents of this environment. There is a special case if this is like some environment.
454 Layout const & style = par->layout();
455 if (style.latextype == LATEX_COMMAND) {
456 // Nothing to do (otherwise, infinite loops).
457 } else if (style.latextype == LATEX_ENVIRONMENT) {
458 // Generate the paragraph, if need be.
459 auto pars = par->simpleDocBookOnePar(buf, runparams, text.outerFont(std::distance(text.paragraphs().begin(), par)), 0, false, ignoreFonts);
462 auto p = pars.begin();
463 while (p != pars.end()) {
464 xml::openTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnerattr(),
465 par->layout().docbookiteminnertagtype());
466 xs << XMLStream::ESCAPE_NONE << *p;
467 xml::closeTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnertagtype());
470 // Insert a new line after each "paragraph" (i.e. line in the listing), except for the last one.
471 // Otherwise, there would one more new line in the output than in the LyX document.
476 for (auto const & p : pars) {
477 xml::openTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnerattr(),
478 par->layout().docbookiteminnertagtype());
479 xs << XMLStream::ESCAPE_NONE << p;
480 xml::closeTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnertagtype());
484 makeAny(text, buf, xs, runparams, par);
487 // Close the environment.
488 closeParTag(xs, &*par, (nextpar != end) ? &*nextpar : nullptr, runparams);
492 ParagraphList::const_iterator findEndOfEnvironment(
493 ParagraphList::const_iterator const & pstart,
494 ParagraphList::const_iterator const & pend)
496 // Copy-paste from XHTML. Should be factored out at some point...
497 ParagraphList::const_iterator p = pstart;
498 Layout const & bstyle = p->layout();
499 size_t const depth = p->params().depth();
500 for (++p; p != pend; ++p) {
501 Layout const & style = p->layout();
502 // It shouldn't happen that e.g. a section command occurs inside
503 // a quotation environment, at a higher depth, but as of 6/2009,
504 // it can happen. We pretend that it's just at lowest depth.
505 if (style.latextype == LATEX_COMMAND)
508 // If depth is down, we're done
509 if (p->params().depth() < depth)
512 // If depth is up, we're not done
513 if (p->params().depth() > depth)
516 // FIXME I am not sure about the first check.
517 // Surely we *could* have different layouts that count as
518 // LATEX_PARAGRAPH, right?
519 if (style.latextype == LATEX_PARAGRAPH || style != bstyle)
526 ParagraphList::const_iterator makeListEnvironment(Text const &text,
529 OutputParams const &runparams,
530 ParagraphList::const_iterator const & begin)
534 auto const end = text.paragraphs().end();
535 auto const envend = findEndOfEnvironment(par, end);
537 // Output the opening tag for this environment.
538 Layout const & envstyle = par->layout();
539 xml::openTag(xs, envstyle.docbookwrappertag(), envstyle.docbookwrapperattr(), envstyle.docbookwrappertagtype());
540 xml::openTag(xs, envstyle.docbooktag(), envstyle.docbookattr(), envstyle.docbooktagtype());
542 // Handle the content of the list environment, item by item.
543 while (par != envend) {
544 // Skip this paragraph if it is both empty and the last one (otherwise, there may be deeper paragraphs after).
547 if (par->empty() && nextpar == envend)
550 // Open the item wrapper.
551 Layout const & style = par->layout();
552 xml::openTag(xs, style.docbookitemwrappertag(), style.docbookitemwrapperattr(),
553 style.docbookitemwrappertagtype());
555 // Generate the label, if need be. If it is taken from the text, sep != 0 and corresponds to the first
556 // character after the label.
558 if (style.labeltype != LABEL_NO_LABEL && style.docbookitemlabeltag() != "NONE") {
559 if (style.labeltype == LABEL_MANUAL) {
560 // Only variablelist gets here (or similar items defined as an extension in the layout).
561 xml::openTag(xs, style.docbookitemlabeltag(), style.docbookitemlabelattr(),
562 style.docbookitemlabeltagtype());
563 sep = 1 + par->firstWordDocBook(xs, runparams);
564 xml::closeTag(xs, style.docbookitemlabeltag(), style.docbookitemlabeltagtype());
566 // Usual cases: maybe there is something specified at the layout level. Highly unlikely, though.
567 docstring const lbl = par->params().labelString();
570 xml::openTag(xs, style.docbookitemlabeltag(), style.docbookitemlabelattr(),
571 style.docbookitemlabeltagtype());
573 xml::closeTag(xs, style.docbookitemlabeltag(), style.docbookitemlabeltagtype());
578 // Open the item (after the wrapper and the label).
579 xml::openTag(xs, style.docbookitemtag(), style.docbookitemattr(), style.docbookitemtagtype());
581 // Generate the content of the item.
582 if (sep < par->size()) {
583 auto pars = par->simpleDocBookOnePar(buf, runparams,
584 text.outerFont(std::distance(text.paragraphs().begin(), par)), sep);
585 for (auto &p : pars) {
586 xml::openTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnerattr(),
587 par->layout().docbookiteminnertagtype());
588 xs << XMLStream::ESCAPE_NONE << p;
589 xml::closeTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnertagtype());
592 // DocBook doesn't like emptiness.
593 xml::compTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnerattr(),
594 par->layout().docbookiteminnertagtype());
597 // If the next item is deeper, it must go entirely within this item (do it recursively).
598 // By construction, with findEndOfEnvironment, depth can only stay constant or increase, never decrease.
599 depth_type currentDepth = par->getDepth();
601 while (par != envend && par->getDepth() != currentDepth)
602 par = makeAny(text, buf, xs, runparams, par);
603 // Usually, this loop only makes one iteration, except in complex scenarios, like an item with a paragraph,
604 // a list, and another paragraph; or an item with two types of list (itemise then enumerate, for instance).
607 xml::closeTag(xs, style.docbookitemtag(), style.docbookitemtagtype());
608 xml::closeTag(xs, style.docbookitemwrappertag(), style.docbookitemwrappertagtype());
611 // Close this environment in exactly the same way as it was opened.
612 xml::closeTag(xs, envstyle.docbooktag(), envstyle.docbooktagtype());
613 xml::closeTag(xs, envstyle.docbookwrappertag(), envstyle.docbookwrappertagtype());
623 OutputParams const & runparams,
624 ParagraphList::const_iterator const & par)
627 // Unlike XHTML, no need for labels, as they are handled by DocBook tags.
628 auto const begin = text.paragraphs().begin();
629 auto const end = text.paragraphs().end();
633 // Generate this command.
634 auto prevpar = text.paragraphs().getParagraphBefore(par);
635 openParTag(xs, &*par, prevpar, runparams);
637 auto pars = par->simpleDocBookOnePar(buf, runparams,text.outerFont(distance(begin, par)));
638 for (auto & parXML : pars)
639 // TODO: decide what to do with openParTag/closeParTag in new lines.
640 xs << XMLStream::ESCAPE_NONE << parXML;
642 closeParTag(xs, &*par, (nextpar != end) ? &*nextpar : nullptr, runparams);
646 bool isLayoutSectioning(Layout const & lay)
648 if (lay.docbooksection()) // Special case: some DocBook styles must be handled as sections.
650 else if (lay.category() == from_utf8("Sectioning") || lay.docbooktag() == "section") // Generic case.
651 return lay.toclevel != Layout::NOT_IN_TOC;
656 bool isLayoutSectioningOrSimilar(Layout const & lay)
658 return isLayoutSectioning(lay) || lay.docbooktag() == "bridgehead";
662 using DocBookDocumentSectioning = tuple<bool, pit_type>;
665 struct DocBookInfoTag
667 const set<pit_type> shouldBeInInfo;
668 const set<pit_type> mustBeInInfo; // With the notable exception of the abstract!
669 const set<pit_type> abstract;
670 const bool abstractLayout;
674 DocBookInfoTag(const set<pit_type> & shouldBeInInfo, const set<pit_type> & mustBeInInfo,
675 const set<pit_type> & abstract, bool abstractLayout, pit_type bpit, pit_type epit) :
676 shouldBeInInfo(shouldBeInInfo), mustBeInInfo(mustBeInInfo), abstract(abstract),
677 abstractLayout(abstractLayout), bpit(bpit), epit(epit) {}
681 DocBookDocumentSectioning hasDocumentSectioning(ParagraphList const ¶graphs, pit_type bpit, pit_type const epit) {
682 bool documentHasSections = false;
684 while (bpit < epit) {
685 Layout const &style = paragraphs[bpit].layout();
686 documentHasSections |= isLayoutSectioningOrSimilar(style);
688 if (documentHasSections)
692 // Paragraphs before the first section: [ runparams.par_begin ; eppit )
694 return make_tuple(documentHasSections, bpit);
698 bool hasOnlyNotes(Paragraph const & par)
700 // Precondition: the paragraph is not empty. Otherwise, the function will always return true...
701 for (int i = 0; i < par.size(); ++i)
702 // If you find something that is not an inset (like actual text) or an inset that is not a note,
704 if (!par.isInset(i) || par.getInset(i)->lyxCode() != NOTE_CODE)
707 // An empty paragraph may still require some output.
708 if (par.layout().docbooksection())
711 // There should be really no content here.
716 DocBookInfoTag getParagraphsWithInfo(ParagraphList const ¶graphs,
717 pit_type bpit, pit_type const epit,
718 // Typically, bpit is the beginning of the document and epit the end of the
719 // document *or* the first section.
720 bool documentHasSections,
721 bool detectUnlayoutedAbstract
722 // Whether paragraphs with no specific layout should be detected as abstracts.
723 // For inner sections, an abstract should only be detected if it has a specific
724 // layout. For others, anything that might look like an abstract should be sought.
726 set<pit_type> shouldBeInInfo;
727 set<pit_type> mustBeInInfo;
728 set<pit_type> abstractWithLayout;
729 set<pit_type> abstractNoLayout;
731 // Find the first non empty paragraph by mutating bpit.
732 while (bpit < epit) {
733 Paragraph const &par = paragraphs[bpit];
734 if (par.empty() || hasOnlyNotes(par))
740 // Traverse everything that might belong to <info>.
741 bool hasAbstractLayout = false;
742 depth_type abstractDepth = -1;
743 pit_type cpit = bpit;
744 for (; cpit < epit; ++cpit) {
745 // Skip paragraphs that don't generate anything in DocBook.
746 Paragraph const & par = paragraphs[cpit];
747 Layout const &style = par.layout();
748 if (hasOnlyNotes(par))
751 // There should never be any section here, except for the first paragraph (a title can be part of <info>).
752 // (Just a sanity check: if this fails, this function could end up processing the whole document.)
753 if (cpit != bpit && isLayoutSectioningOrSimilar(par.layout())) {
754 LYXERR0("Assertion failed: section found in potential <info> paragraphs.");
758 // If this is marked as an abstract by the layout, put it in the right set.
759 if (style.docbookabstract()) {
760 hasAbstractLayout = true;
761 abstractDepth = par.getDepth();
762 abstractWithLayout.emplace(cpit);
766 // Deeper paragraphs following the abstract must still be considered as part of the abstract.
767 // For instance, this includes lists. There should not be any other kind of paragraph in between.
768 if (abstractDepth != -1 && style.docbookininfo() == "never") {
769 if (par.getDepth() > abstractDepth) {
770 abstractWithLayout.emplace(cpit);
773 if (par.getDepth() == abstractDepth) {
774 // This is not an abstract paragraph and it should not either be considered as part
775 // of it. It breaks the rule that abstract paragraphs must follow each other.
781 // Based on layout information, store this paragraph in one set: should be in <info>, must be,
782 // or abstract (either because of layout or of position).
783 if (style.docbookininfo() == "always")
784 mustBeInInfo.emplace(cpit);
785 else if (style.docbookininfo() == "maybe")
786 shouldBeInInfo.emplace(cpit);
787 else if (documentHasSections && !hasAbstractLayout && detectUnlayoutedAbstract &&
788 (style.docbooktag() == "NONE" || style.docbooktag() == "para") &&
789 style.docbookwrappertag() == "NONE")
790 // In this case, it is very likely that style.docbookininfo() == "never"! Be extra careful
791 // about anything that gets caught here. For instance, don't ake into account
792 abstractNoLayout.emplace(cpit);
793 else // This should definitely not be in <info>.
796 // Now, cpit points to the first paragraph that no more has things that could go in <info>.
797 // bpit is the beginning of the <info> part.
799 return DocBookInfoTag(shouldBeInInfo, mustBeInInfo,
800 hasAbstractLayout ? abstractWithLayout : abstractNoLayout,
801 hasAbstractLayout, bpit, cpit);
804 } // end anonymous namespace
807 std::set<const Inset *> gatherInfo(ParagraphList::const_iterator par)
809 // This function has a structure highly similar to makeAny and its friends. It's only made to be called on what
810 // should become the document's <abstract>.
811 std::set<const Inset *> values;
813 // If this kind of layout should be ignored, already leave.
814 if (par->layout().docbooktag() == "IGNORE")
817 // If this should go in info, mark it as such. Dive deep into the abstract, as it may hide many things that
818 // DocBook doesn't want to be inside the abstract.
819 for (pos_type i = 0; i < par->size(); ++i) {
820 if (par->getInset(i) && par->getInset(i)->asInsetText()) {
821 InsetText const *inset = par->getInset(i)->asInsetText();
823 if (inset->getLayout().docbookininfo() != "never") {
824 values.insert(inset);
826 auto subpar = inset->paragraphs().begin();
827 while (subpar != inset->paragraphs().end()) {
828 auto subinfos = gatherInfo(subpar);
829 for (auto & subinfo: subinfos)
830 values.insert(subinfo);
841 ParagraphList::const_iterator makeAny(Text const &text,
844 OutputParams const &runparams,
845 ParagraphList::const_iterator par)
847 bool ignoreParagraph = false;
849 // If this kind of layout should be ignored, already leave.
850 ignoreParagraph |= par->layout().docbooktag() == "IGNORE";
852 // For things that should go into <info>, check the variable rp.docbook_generate_info. This does not apply to the
854 bool isAbstract = par->layout().docbookabstract() || par->layout().docbooktag() == "abstract";
855 ignoreParagraph |= !isAbstract && par->layout().docbookininfo() != "never" && !runparams.docbook_generate_info;
857 // Switch on the type of paragraph to call the right handler.
858 if (!ignoreParagraph) {
859 switch (par->layout().latextype) {
861 makeCommand(text, buf, xs, runparams, par);
863 case LATEX_ENVIRONMENT:
864 makeEnvironment(text, buf, xs, runparams, par);
866 case LATEX_LIST_ENVIRONMENT:
867 case LATEX_ITEM_ENVIRONMENT:
868 // Only case when makeAny() might consume more than one paragraph.
869 return makeListEnvironment(text, buf, xs, runparams, par);
870 case LATEX_PARAGRAPH:
871 makeParagraph(text, buf, xs, runparams, par);
873 case LATEX_BIB_ENVIRONMENT:
874 makeBibliography(text, buf, xs, runparams, par);
879 // For cases that are not lists, the next paragraph to handle is the next one.
885 xml::FontTag docbookStartFontTag(xml::FontTypes type)
887 return xml::FontTag(from_utf8(fontToDocBookTag(type)), from_utf8(fontToAttribute(type)), type);
891 xml::EndFontTag docbookEndFontTag(xml::FontTypes type)
893 return xml::EndFontTag(from_utf8(fontToDocBookTag(type)), type);
897 void outputDocBookInfo(
901 OutputParams const & runparams,
902 ParagraphList const & paragraphs,
903 DocBookInfoTag const & info)
905 // Perform an additional check on the abstract. Sometimes, there are many paragraphs that should go
906 // into the abstract, but none generates actual content. Thus, first generate to a temporary stream,
907 // then only create the <abstract> tag if these paragraphs generate some content.
908 // This check must be performed *before* a decision on whether or not to output <info> is made.
909 bool hasAbstract = !info.abstract.empty();
911 set<const Inset *> infoInsets; // Paragraphs that should go into <info>, but are hidden in an <abstract>
912 // paragraph. (This happens for quite a few layouts, unfortunately.)
915 // Generate the abstract XML into a string before further checks.
916 // Usually, makeAny only generates one paragraph at a time. However, for the specific case of lists, it might
917 // generate more than one paragraph, as indicated in the return value.
918 odocstringstream os2;
922 rp.docbook_generate_info = false;
923 rp.docbook_ignore_wrapper = true;
925 set<pit_type> doneParas; // Paragraphs that have already been converted (mostly to deal with lists).
926 for (auto const & p : info.abstract) {
927 if (doneParas.find(p) == doneParas.end()) {
928 auto oldPar = paragraphs.iterator_at(p);
929 auto newPar = makeAny(text, buf, xs2, rp, oldPar);
931 // Find insets that should go outside the abstract.
932 auto subinfos = gatherInfo(oldPar);
933 for (auto & subinfo: subinfos)
934 infoInsets.insert(subinfo);
936 // Insert the indices of all the paragraphs that were just generated (typically, one).
937 // **Make the hypothesis that, when an abstract has a list, all its items are consecutive.**
938 // Otherwise, makeAny and makeListEnvironment would have to be adapted too.
940 while (oldPar != newPar) {
941 doneParas.emplace(id);
948 // Actually output the abstract if there is something to do. Don't count line feeds or spaces in this,
949 // even though they must be properly output if there is some abstract.
950 abstract = os2.str();
951 docstring cleaned = abstract;
952 cleaned.erase(std::remove_if(cleaned.begin(), cleaned.end(), lyx::isSpace), cleaned.end());
954 // Nothing? Then there is no abstract!
959 // The abstract must go in <info>. Otherwise, decide whether to open <info> based on the layouts.
960 bool needInfo = !info.mustBeInInfo.empty() || hasAbstract;
962 // Start the <info> tag if required.
964 xs.startDivision(false);
965 xs << xml::StartTag("info");
969 // Output the elements that should go in <info>.
970 // - First, the title.
971 for (auto pit : info.shouldBeInInfo) // Typically, the title: these elements are so important and ubiquitous
972 // that mandating a wrapper like <info> would repel users. Thus, generate them first.
973 makeAny(text, buf, xs, runparams, paragraphs.iterator_at(pit));
974 // If there is no title, generate one (required for the document to be valid).
975 // This code is called for the main document, for table cells, etc., so be precise in this condition.
976 if (text.isMainText() && info.shouldBeInInfo.empty() && !runparams.inInclude) {
977 xs << xml::StartTag("title");
978 xs << "Untitled Document";
979 xs << xml::EndTag("title");
983 // - Then, other metadata.
984 for (auto pit : info.mustBeInInfo)
985 makeAny(text, buf, xs, runparams, paragraphs.iterator_at(pit));
986 for (auto const * inset : infoInsets)
987 inset->docbook(xs, runparams);
989 // - Finally, always output the abstract as the last item of the <info>, as it requires special treatment
990 // (especially if it contains several paragraphs that are empty).
992 string tag = paragraphs[*info.abstract.begin()].layout().docbookforceabstracttag();
996 if (!xs.isLastTagCR())
999 xs << xml::StartTag(tag);
1001 xs << XMLStream::ESCAPE_NONE << abstract;
1002 xs << xml::EndTag(tag);
1006 // End the <info> tag if it was started.
1008 if (!xs.isLastTagCR())
1011 xs << xml::EndTag("info");
1018 void docbookSimpleAllParagraphs(
1022 OutputParams const & runparams)
1024 // Handle the given text, supposing it has no sections (i.e. a "simple" text). The input may vary in length
1025 // between a single paragraph to a whole document.
1026 pit_type const bpit = runparams.par_begin;
1027 pit_type const epit = runparams.par_end;
1028 ParagraphList const ¶graphs = text.paragraphs();
1030 // First, the <info> tag.
1031 DocBookInfoTag info = getParagraphsWithInfo(paragraphs, bpit, epit, false, true);
1032 outputDocBookInfo(text, buf, xs, runparams, paragraphs, info);
1034 // Then, the content. It starts where the <info> ends.
1035 auto par = paragraphs.iterator_at(info.epit);
1036 auto end = paragraphs.iterator_at(epit);
1037 while (par != end) {
1038 if (!hasOnlyNotes(*par))
1039 par = makeAny(text, buf, xs, runparams, par);
1046 void docbookParagraphs(Text const &text,
1049 OutputParams const &runparams) {
1050 ParagraphList const ¶graphs = text.paragraphs();
1051 if (runparams.par_begin == runparams.par_end) {
1052 runparams.par_begin = 0;
1053 runparams.par_end = paragraphs.size();
1055 pit_type bpit = runparams.par_begin;
1056 pit_type const epit = runparams.par_end;
1057 LASSERT(bpit < epit,
1059 xs << XMLStream::ESCAPE_NONE << "<!-- DocBook output error! -->\n";
1063 std::stack<std::pair<int, string>> headerLevels; // Used to determine when to open/close sections: store the depth
1064 // of the section and the tag that was used to open it.
1066 // Detect whether the document contains sections. If there are no sections, treatment is largely simplified.
1067 // In particular, there can't be an abstract, unless it is manually marked.
1068 bool documentHasSections;
1070 tie(documentHasSections, eppit) = hasDocumentSectioning(paragraphs, bpit, epit);
1072 // Deal with "simple" documents, i.e. those without sections.
1073 if (!documentHasSections) {
1074 docbookSimpleAllParagraphs(text, buf, xs, runparams);
1078 // Output the first <info> tag (or just the title).
1079 DocBookInfoTag info = getParagraphsWithInfo(paragraphs, bpit, eppit, true, true);
1080 outputDocBookInfo(text, buf, xs, runparams, paragraphs, info);
1083 // Then, iterate through the paragraphs of this document.
1084 bool currentlyInAppendix = false;
1086 auto par = text.paragraphs().iterator_at(bpit);
1087 auto end = text.paragraphs().iterator_at(epit);
1088 while (par != end) {
1089 OutputParams ourparams = runparams;
1091 if (par->params().startOfAppendix())
1092 currentlyInAppendix = true;
1093 if (hasOnlyNotes(*par)) {
1098 Layout const &style = par->layout();
1100 // Think about adding <section> and/or </section>s.
1101 if (isLayoutSectioning(style)) {
1102 int level = style.toclevel;
1104 // Need to close a previous section if it has the same level or a higher one (close <section> if opening a
1105 // <h2> after a <h2>, <h3>, <h4>, <h5> or <h6>). More examples:
1106 // - current: h2; back: h1; do not close any <section>
1107 // - current: h1; back: h2; close two <section> (first the <h2>, then the <h1>, so a new <h1> can come)
1108 while (!headerLevels.empty() && level <= headerLevels.top().first) {
1109 // Output the tag only if it corresponds to a legit section.
1110 int stackLevel = headerLevels.top().first;
1111 if (stackLevel != Layout::NOT_IN_TOC) {
1112 xs << xml::EndTag(headerLevels.top().second);
1118 // Open the new section: first push it onto the stack, then output it in DocBook.
1119 string sectionTag = (currentlyInAppendix && style.docbooksectiontag() == "chapter") ?
1120 "appendix" : style.docbooksectiontag();
1121 headerLevels.push(std::make_pair(level, sectionTag));
1123 // Some sectioning-like elements should not be output (such as FrontMatter).
1124 if (level != Layout::NOT_IN_TOC) {
1125 // Look for a label in the title, i.e. a InsetLabel as a child.
1126 docstring id = docstring();
1127 for (pos_type i = 0; i < par->size(); ++i) {
1128 Inset const *inset = par->getInset(i);
1130 if (auto label = dynamic_cast<InsetLabel const *>(inset)) {
1131 // Generate the attributes for the section if need be.
1132 id += "xml:id=\"" + xml::cleanID(label->screenLabel()) + "\"";
1134 // Don't output the ID as a DocBook <anchor>.
1135 ourparams.docbook_anchors_to_ignore.emplace(label->screenLabel());
1137 // Cannot have multiple IDs per tag. If there is another ID inset in the document, it will
1138 // be output as a DocBook anchor.
1144 // Write the open tag for this section.
1148 xs << xml::StartTag(sectionTag, attrs);
1153 // Close all sections before the bibliography.
1154 // 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)?
1155 if (!par->insetList().empty()) {
1156 Inset const *firstInset = par->getInset(0);
1157 if (firstInset && (firstInset->lyxCode() == BIBITEM_CODE || firstInset->lyxCode() == BIBTEX_CODE)) {
1158 while (!headerLevels.empty()) {
1159 int level = headerLevels.top().first;
1160 docstring tag = from_utf8("</" + headerLevels.top().second + ">");
1163 // Output the tag only if it corresponds to a legit section.
1164 if (level != Layout::NOT_IN_TOC) {
1165 xs << XMLStream::ESCAPE_NONE << tag;
1172 // Generate the <info> tag if a section was just opened.
1173 // Some sections may require abstracts (mostly parts, in books: DocBookForceAbstractTag will not be NONE),
1174 // others can still have an abstract (it must be detected so that it can be output at the right place).
1175 // 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.
1176 if (isLayoutSectioning(style)) {
1177 // This abstract may be found between the next paragraph and the next title.
1178 pit_type cpit = std::distance(text.paragraphs().begin(), par);
1179 pit_type ppit = std::get<1>(hasDocumentSectioning(paragraphs, cpit + 1L, epit));
1181 // Generate this abstract (this code corresponds to parts of outputDocBookInfo).
1182 DocBookInfoTag secInfo = getParagraphsWithInfo(paragraphs, cpit, ppit, true,
1183 style.docbookforceabstracttag() != "NONE");
1185 if (!secInfo.mustBeInInfo.empty() || !secInfo.shouldBeInInfo.empty() || !secInfo.abstract.empty()) {
1186 // Generate the <info>, if required. If DocBookForceAbstractTag != NONE, this abstract will not be in
1187 // <info>, unlike other ("standard") abstracts.
1188 bool hasStandardAbstract = !secInfo.abstract.empty() && style.docbookforceabstracttag() == "NONE";
1189 bool needInfo = !secInfo.mustBeInInfo.empty() || hasStandardAbstract;
1192 xs.startDivision(false);
1193 xs << xml::StartTag("info");
1197 // Output the elements that should go in <info>, before and after the abstract.
1198 for (auto pit : secInfo.shouldBeInInfo) // Typically, the title: these elements are so important and ubiquitous
1199 // that mandating a wrapper like <info> would repel users. Thus, generate them first.
1200 makeAny(text, buf, xs, ourparams, paragraphs.iterator_at(pit));
1201 for (auto pit : secInfo.mustBeInInfo)
1202 makeAny(text, buf, xs, ourparams, paragraphs.iterator_at(pit));
1204 // Deal with the abstract in <info> if it is standard (i.e. its tag is <abstract>).
1205 if (!secInfo.abstract.empty() && hasStandardAbstract) {
1206 if (!secInfo.abstractLayout) {
1207 xs << xml::StartTag("abstract");
1211 for (auto const &p : secInfo.abstract)
1212 makeAny(text, buf, xs, ourparams, paragraphs.iterator_at(p));
1214 if (!secInfo.abstractLayout) {
1215 xs << xml::EndTag("abstract");
1220 // End the <info> tag if it was started.
1222 if (!xs.isLastTagCR())
1225 xs << xml::EndTag("info");
1230 // Deal with the abstract outside <info> if it is not standard (i.e. its tag is layout-defined).
1231 if (!secInfo.abstract.empty() && !hasStandardAbstract) {
1232 // Assert: style.docbookforceabstracttag() != NONE.
1233 xs << xml::StartTag(style.docbookforceabstracttag());
1235 for (auto const &p : secInfo.abstract)
1236 makeAny(text, buf, xs, ourparams, paragraphs.iterator_at(p));
1237 xs << xml::EndTag(style.docbookforceabstracttag());
1241 // Skip all the text that has just been generated.
1242 par = paragraphs.iterator_at(secInfo.epit);
1244 // No <info> tag to generate, proceed as for normal paragraphs.
1245 par = makeAny(text, buf, xs, ourparams, par);
1248 // Generate this paragraph, as it has nothing special.
1249 par = makeAny(text, buf, xs, ourparams, par);
1253 // If need be, close <section>s, but only at the end of the document (otherwise, dealt with at the beginning
1255 while (!headerLevels.empty() && headerLevels.top().first > Layout::NOT_IN_TOC) {
1256 docstring tag = from_utf8("</" + headerLevels.top().second + ">");
1258 xs << XMLStream::ESCAPE_NONE << tag;