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.
15 #include "buffer_funcs.h"
16 #include "BufferParams.h"
18 #include "InsetList.h"
20 #include "OutputParams.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 "insets/InsetNote.h"
33 #include "support/convert.h"
34 #include "support/debug.h"
35 #include "support/lassert.h"
36 #include "support/lstrings.h"
37 #include "support/textutils.h"
39 #include "support/regex.h"
47 using namespace lyx::support;
53 std::string const fontToDocBookTag(xml::FontTypes type)
56 case xml::FontTypes::FT_EMPH:
57 case xml::FontTypes::FT_BOLD:
59 case xml::FontTypes::FT_NOUN:
61 case xml::FontTypes::FT_UBAR:
62 case xml::FontTypes::FT_WAVE:
63 case xml::FontTypes::FT_DBAR:
64 case xml::FontTypes::FT_SOUT:
65 case xml::FontTypes::FT_XOUT:
66 case xml::FontTypes::FT_ITALIC:
67 case xml::FontTypes::FT_UPRIGHT:
68 case xml::FontTypes::FT_SLANTED:
69 case xml::FontTypes::FT_SMALLCAPS:
70 case xml::FontTypes::FT_ROMAN:
71 case xml::FontTypes::FT_SANS:
73 case xml::FontTypes::FT_TYPE:
75 case xml::FontTypes::FT_SIZE_TINY:
76 case xml::FontTypes::FT_SIZE_SCRIPT:
77 case xml::FontTypes::FT_SIZE_FOOTNOTE:
78 case xml::FontTypes::FT_SIZE_SMALL:
79 case xml::FontTypes::FT_SIZE_NORMAL:
80 case xml::FontTypes::FT_SIZE_LARGE:
81 case xml::FontTypes::FT_SIZE_LARGER:
82 case xml::FontTypes::FT_SIZE_LARGEST:
83 case xml::FontTypes::FT_SIZE_HUGE:
84 case xml::FontTypes::FT_SIZE_HUGER:
85 case xml::FontTypes::FT_SIZE_INCREASE:
86 case xml::FontTypes::FT_SIZE_DECREASE:
93 string fontToRole(xml::FontTypes type)
95 // Specific fonts are achieved with roles. The only common ones are "" for basic emphasis,
96 // and "bold"/"strong" for bold. With some specific options, other roles are copied into
97 // HTML output (via the DocBook XSLT sheets); otherwise, if not recognised, they are just ignored.
98 // Hence, it is not a problem to have many roles by default here.
99 // See https://www.sourceware.org/ml/docbook/2003-05/msg00269.html
101 case xml::FontTypes::FT_ITALIC:
102 case xml::FontTypes::FT_EMPH:
104 case xml::FontTypes::FT_BOLD:
106 case xml::FontTypes::FT_NOUN:
107 return ""; // Outputs a <person>
108 case xml::FontTypes::FT_TYPE:
109 return ""; // Outputs a <code>
110 case xml::FontTypes::FT_UBAR:
113 // All other roles are non-standard for DocBook.
115 case xml::FontTypes::FT_WAVE:
117 case xml::FontTypes::FT_DBAR:
119 case xml::FontTypes::FT_SOUT:
121 case xml::FontTypes::FT_XOUT:
123 case xml::FontTypes::FT_UPRIGHT:
125 case xml::FontTypes::FT_SLANTED:
127 case xml::FontTypes::FT_SMALLCAPS:
129 case xml::FontTypes::FT_ROMAN:
131 case xml::FontTypes::FT_SANS:
133 case xml::FontTypes::FT_SIZE_TINY:
135 case xml::FontTypes::FT_SIZE_SCRIPT:
136 return "size_script";
137 case xml::FontTypes::FT_SIZE_FOOTNOTE:
138 return "size_footnote";
139 case xml::FontTypes::FT_SIZE_SMALL:
141 case xml::FontTypes::FT_SIZE_NORMAL:
142 return "size_normal";
143 case xml::FontTypes::FT_SIZE_LARGE:
145 case xml::FontTypes::FT_SIZE_LARGER:
146 return "size_larger";
147 case xml::FontTypes::FT_SIZE_LARGEST:
148 return "size_largest";
149 case xml::FontTypes::FT_SIZE_HUGE:
151 case xml::FontTypes::FT_SIZE_HUGER:
153 case xml::FontTypes::FT_SIZE_INCREASE:
154 return "size_increase";
155 case xml::FontTypes::FT_SIZE_DECREASE:
156 return "size_decrease";
162 string fontToAttribute(xml::FontTypes type) {
163 // If there is a role (i.e. nonstandard use of a tag), output the attribute. Otherwise, the sheer tag is sufficient
165 string role = fontToRole(type);
167 return "role='" + role + "'";
173 } // end anonymous namespace
176 xml::FontTag docbookStartFontTag(xml::FontTypes type)
178 return xml::FontTag(from_utf8(fontToDocBookTag(type)), from_utf8(fontToAttribute(type)), type);
182 xml::EndFontTag docbookEndFontTag(xml::FontTypes type)
184 return xml::EndFontTag(from_utf8(fontToDocBookTag(type)), type);
190 // convenience functions
192 void openParTag(XMLStream &xs, Layout const &lay)
194 if (lay.docbookwrappertag() != "NONE") {
195 xs << xml::StartTag(lay.docbookwrappertag(), lay.docbookwrapperattr());
198 string tag = lay.docbooktag();
199 if (tag == "Plain Layout")
202 xs << xml::ParTag(tag, lay.docbookattr());
206 void closeTag(XMLStream &xs, Layout const &lay)
208 string tag = lay.docbooktag();
209 if (tag == "Plain Layout")
212 xs << xml::EndTag(tag);
213 if (lay.docbookwrappertag() != "NONE")
214 xs << xml::EndTag(lay.docbookwrappertag());
218 void openLabelTag(XMLStream & xs, Layout const & lay) // Mostly for definition lists.
220 xs << xml::StartTag(lay.docbookitemlabeltag(), lay.docbookitemlabelattr());
224 void closeLabelTag(XMLStream & xs, Layout const & lay)
226 xs << xml::EndTag(lay.docbookitemlabeltag());
231 void openItemTag(XMLStream &xs, Layout const &lay)
233 xs << xml::StartTag(lay.docbookitemtag(), lay.docbookitemattr());
237 // Return true when new elements are output in a paragraph, false otherwise.
238 bool openInnerItemTag(XMLStream &xs, Layout const &lay)
240 if (lay.docbookiteminnertag() != "NONE") {
242 xs << xml::ParTag(lay.docbookiteminnertag(), lay.docbookiteminnerattr());
244 if (lay.docbookiteminnertag() == "para") {
252 void closeInnerItemTag(XMLStream &xs, Layout const &lay)
254 if (lay.docbookiteminnertag()!= "NONE") {
255 xs << xml::EndTag(lay.docbookiteminnertag());
261 inline void closeItemTag(XMLStream &xs, Layout const &lay)
263 xs << xml::EndTag(lay.docbookitemtag());
267 // end of convenience functions
269 ParagraphList::const_iterator findLastParagraph(
270 ParagraphList::const_iterator p,
271 ParagraphList::const_iterator const & pend) {
272 for (++p; p != pend && p->layout().latextype == LATEX_PARAGRAPH; ++p);
278 ParagraphList::const_iterator findEndOfEnvironment(
279 ParagraphList::const_iterator const & pstart,
280 ParagraphList::const_iterator const & pend)
282 ParagraphList::const_iterator p = pstart;
283 Layout const &bstyle = p->layout();
284 size_t const depth = p->params().depth();
285 for (++p; p != pend; ++p) {
286 Layout const &style = p->layout();
287 // It shouldn't happen that e.g. a section command occurs inside
288 // a quotation environment, at a higher depth, but as of 6/2009,
289 // it can happen. We pretend that it's just at lowest depth.
290 if (style.latextype == LATEX_COMMAND)
293 // If depth is down, we're done
294 if (p->params().depth() < depth)
297 // If depth is up, we're not done
298 if (p->params().depth() > depth)
301 // FIXME I am not sure about the first check.
302 // Surely we *could* have different layouts that count as
303 // LATEX_PARAGRAPH, right?
304 if (style.latextype == LATEX_PARAGRAPH || style != bstyle)
311 ParagraphList::const_iterator makeParagraphBibliography(
314 OutputParams const &runparams,
316 ParagraphList::const_iterator const & pbegin,
317 ParagraphList::const_iterator const & pend)
319 auto const begin = text.paragraphs().begin();
320 auto const end = text.paragraphs().end();
322 // Find the paragraph *before* pbegin.
323 ParagraphList::const_iterator pbegin_before = begin;
324 if (pbegin != begin) {
325 ParagraphList::const_iterator pbegin_before_next = begin;
326 ++pbegin_before_next;
328 while (pbegin_before_next != pbegin) {
330 ++pbegin_before_next;
334 ParagraphList::const_iterator par = pbegin;
336 // If this is the first paragraph in a bibliography, open the bibliography tag.
337 if (pbegin != begin && pbegin_before->layout().latextype != LATEX_BIB_ENVIRONMENT) {
338 xs << xml::StartTag("bibliography");
342 // Generate the required paragraphs.
343 for (; par != pend; ++par) {
344 // Start the precooked bibliography entry. This is very much like opening a paragraph tag.
345 // Don't forget the citation ID!
347 for (auto i = 0; i < par->size(); ++i) {
348 Inset const *ip = par->getInset(0);
349 if (ip != nullptr && ip->lyxCode() == BIBITEM_CODE) {
350 const auto * bibitem = dynamic_cast<const InsetBibitem*>(par->getInset(i));
351 attr = from_utf8("xml:id='") + bibitem->bibLabel() + from_utf8("'");
355 xs << xml::StartTag(from_utf8("bibliomixed"), attr);
357 // Generate the entry.
358 par->simpleDocBookOnePar(buf, xs, runparams, text.outerFont(distance(begin, par)), true, true, 0);
360 // End the precooked bibliography entry.
361 xs << xml::EndTag("bibliomixed");
365 // If this is the last paragraph in a bibliography, close the bibliography tag.
366 if (par == end || par->layout().latextype != LATEX_BIB_ENVIRONMENT) {
367 xs << xml::EndTag("bibliography");
375 ParagraphList::const_iterator makeParagraphs(
378 OutputParams const &runparams,
380 ParagraphList::const_iterator const & pbegin,
381 ParagraphList::const_iterator const & pend)
383 ParagraphList::const_iterator const begin = text.paragraphs().begin();
384 ParagraphList::const_iterator par = pbegin;
385 for (; par != pend; ++par) {
386 Layout const &lay = par->layout();
388 // We want to open the paragraph tag if:
389 // (i) the current layout permits multiple paragraphs
390 // (ii) we are either not already inside a paragraph (HTMLIsBlock) OR
391 // we are, but this is not the first paragraph
393 // But there is also a special case, and we first see whether we are in it.
394 // We do not want to open the paragraph tag if this paragraph contains
395 // only one item, and that item is "inline", i.e., not HTMLIsBlock (such
396 // as a branch). On the other hand, if that single item has a font change
397 // applied to it, then we still do need to open the paragraph.
399 // Obviously, this is very fragile. The main reason we need to do this is
400 // because of branches, e.g., a branch that contains an entire new section.
401 // We do not really want to wrap that whole thing in a <div>...</div>.
402 bool special_case = false;
403 Inset const *specinset = par->size() == 1 ? par->getInset(0) : 0;
404 if (specinset && !specinset->getLayout().htmlisblock()) { // TODO: Convert htmlisblock to a DocBook parameter?
405 Layout const &style = par->layout();
406 FontInfo const first_font = style.labeltype == LABEL_MANUAL ?
407 style.labelfont : style.font;
408 FontInfo const our_font =
409 par->getFont(buf.masterBuffer()->params(), 0,
410 text.outerFont(distance(begin, par))).fontInfo();
412 if (first_font == our_font)
416 // Plain layouts must be ignored.
417 if (!special_case && buf.params().documentClass().isPlainLayout(lay) && !runparams.docbook_force_pars)
419 // TODO: Could get rid of this with a DocBook equivalent to htmlisblock?
420 if (!special_case && par->size() == 1 && par->getInset(0)) {
421 Inset const * firstInset = par->getInset(0);
423 // Floats cannot be in paragraphs.
424 special_case = to_utf8(firstInset->layoutName()).substr(0, 6) == "Float:";
426 // Bibliographies cannot be in paragraphs.
427 if (!special_case && firstInset->asInsetCommand())
428 special_case = firstInset->asInsetCommand()->params().getCmdName() == "bibtex";
430 // Equations do not deserve their own paragraph (DocBook allows them outside paragraphs).
431 if (!special_case && firstInset->asInsetMath())
434 // ERTs are in comments, not paragraphs.
435 if (!special_case && firstInset->lyxCode() == lyx::ERT_CODE)
438 // Listings should not get into their own paragraph.
439 if (!special_case && firstInset->lyxCode() == lyx::LISTINGS_CODE)
443 bool const open_par = runparams.docbook_make_pars
444 && (!runparams.docbook_in_par || par != pbegin)
447 // We want to issue the closing tag if either:
448 // (i) We opened it, and either docbook_in_par is false,
449 // or we're not in the last paragraph, anyway.
450 // (ii) We didn't open it and docbook_in_par is true,
451 // but we are in the first par, and there is a next par.
452 ParagraphList::const_iterator nextpar = par;
454 bool const close_par =
455 ((open_par && (!runparams.docbook_in_par || nextpar != pend))
456 || (!open_par && runparams.docbook_in_par && par == pbegin && nextpar != pend));
461 par->simpleDocBookOnePar(buf, xs, runparams, text.outerFont(distance(begin, par)), open_par, close_par, 0);
472 bool isNormalEnv(Layout const &lay)
474 return lay.latextype == LATEX_ENVIRONMENT
475 || lay.latextype == LATEX_BIB_ENVIRONMENT;
479 ParagraphList::const_iterator makeEnvironment(
482 OutputParams const &runparams,
484 ParagraphList::const_iterator const & pbegin,
485 ParagraphList::const_iterator const & pend)
487 ParagraphList::const_iterator const begin = text.paragraphs().begin();
488 ParagraphList::const_iterator par = pbegin;
489 Layout const &bstyle = par->layout();
490 depth_type const origdepth = pbegin->params().depth();
492 // open tag for this environment
493 openParTag(xs, bstyle);
496 // we will on occasion need to remember a layout from before.
497 Layout const *lastlay = nullptr;
499 while (par != pend) {
500 Layout const & style = par->layout();
501 ParagraphList::const_iterator send;
503 // Actual content of this paragraph.
504 switch (style.latextype) {
505 case LATEX_ENVIRONMENT:
506 case LATEX_LIST_ENVIRONMENT:
507 case LATEX_ITEM_ENVIRONMENT: {
508 // There are two possibilities in this case.
509 // One is that we are still in the environment in which we
510 // started---which we will be if the depth is the same.
511 if (par->params().depth() == origdepth) {
512 LATTEST(bstyle == style);
513 if (lastlay != nullptr) {
514 closeItemTag(xs, *lastlay);
515 if (lastlay->docbookitemwrappertag() != "NONE") {
516 xs << xml::EndTag(lastlay->docbookitemwrappertag());
522 // this will be positive if we want to skip the
523 // initial word (if it's been taken for the label).
526 // Open a wrapper tag if needed.
527 if (style.docbookitemwrappertag() != "NONE") {
528 xs << xml::StartTag(style.docbookitemwrappertag(), style.docbookitemwrapperattr());
533 if (style.labeltype != LABEL_NO_LABEL &&
534 style.docbookitemlabeltag() != "NONE") {
536 if (isNormalEnv(style)) {
537 // in this case, we print the label only for the first
538 // paragraph (as in a theorem or an abstract).
540 docstring const lbl = pbegin->params().labelString();
542 openLabelTag(xs, style);
544 closeLabelTag(xs, style);
546 // No new line after closeLabelTag.
550 } else { // some kind of list
551 if (style.labeltype == LABEL_MANUAL) {
552 // Only variablelist gets here.
554 openLabelTag(xs, style);
555 sep = par->firstWordDocBook(xs, runparams);
556 closeLabelTag(xs, style);
558 openLabelTag(xs, style);
559 xs << par->params().labelString();
560 closeLabelTag(xs, style);
563 } // end label output
565 // Start generating the item.
566 bool wasInParagraph = runparams.docbook_in_par;
567 openItemTag(xs, style);
568 bool getsIntoParagraph = openInnerItemTag(xs, style);
569 OutputParams rp = runparams;
570 rp.docbook_in_par = wasInParagraph | getsIntoParagraph;
572 // Maybe the item is completely empty, i.e. if the first word ends at the end of the current paragraph
573 // AND if the next paragraph doesn't have the same depth (if there is such a paragraph).
574 // Common case: there is only the first word on the line, but there is a nested list instead
576 bool emptyItem = false;
577 if (sep == par->size()) {
580 if (next_par == text.paragraphs().end()) // There is no next paragraph.
582 else // There is a next paragraph: check depth.
583 emptyItem = par->params().depth() >= next_par->params().depth();
587 // Avoid having an empty item, this is not valid DocBook. A single character is enough to force
588 // generation of a full <para>.
591 // Generate the rest of the paragraph, if need be.
592 par->simpleDocBookOnePar(buf, xs, rp, text.outerFont(distance(begin, par)), true, true, sep);
596 if (getsIntoParagraph)
597 closeInnerItemTag(xs, style);
599 // We may not want to close the tag yet, in particular:
600 // If we're not at the end of the item...
602 // and are doing items...
603 && !isNormalEnv(style)
604 // and if the depth has changed...
605 && par->params().depth() != origdepth) {
606 // then we'll save this layout for later, and close it when
607 // we get another item.
610 closeItemTag(xs, style);
612 // Eventually, close the item wrapper.
613 if (style.docbookitemwrappertag() != "NONE") {
614 xs << xml::EndTag(style.docbookitemwrappertag());
619 // The other possibility is that the depth has increased.
621 send = findEndOfEnvironment(par, pend);
622 par = makeEnvironment(buf, xs, runparams, text, par, send);
626 case LATEX_PARAGRAPH:
627 send = findLastParagraph(par, pend);
628 par = makeParagraphs(buf, xs, runparams, text, par, send);
630 case LATEX_BIB_ENVIRONMENT:
631 send = findLastParagraph(par, pend);
632 par = makeParagraphBibliography(buf, xs, runparams, text, par, send);
640 if (lastlay != nullptr) {
641 closeItemTag(xs, *lastlay);
642 if (lastlay->docbookitemwrappertag() != "NONE") {
643 xs << xml::EndTag(lastlay->docbookitemwrappertag());
647 closeTag(xs, bstyle);
656 OutputParams const & runparams,
658 ParagraphList::const_iterator const & pbegin)
660 Layout const &style = pbegin->layout();
662 // No need for labels, as they are handled by DocBook tags.
664 openParTag(xs, style);
666 ParagraphList::const_iterator const begin = text.paragraphs().begin();
667 pbegin->simpleDocBookOnePar(buf, xs, runparams,
668 text.outerFont(distance(begin, pbegin)));
673 pair<ParagraphList::const_iterator, ParagraphList::const_iterator> makeAny(
677 OutputParams const &ourparams,
678 ParagraphList::const_iterator par,
679 ParagraphList::const_iterator send,
680 ParagraphList::const_iterator pend)
682 Layout const & style = par->layout();
684 switch (style.latextype) {
685 case LATEX_COMMAND: {
686 // The files with which we are working never have more than
687 // one paragraph in a command structure.
689 // if (ourparams.docbook_in_par)
690 // fix it so we don't get sections inside standard, e.g.
691 // note that we may then need to make runparams not const, so we
692 // can communicate that back.
693 // FIXME Maybe this fix should be in the routines themselves, in case
694 // they are called from elsewhere.
695 makeCommand(buf, xs, ourparams, text, par);
699 case LATEX_ENVIRONMENT:
700 case LATEX_LIST_ENVIRONMENT:
701 case LATEX_ITEM_ENVIRONMENT: {
702 // FIXME Same fix here.
703 send = findEndOfEnvironment(par, pend);
704 par = makeEnvironment(buf, xs, ourparams, text, par, send);
707 case LATEX_BIB_ENVIRONMENT: {
708 send = findLastParagraph(par, pend);
709 par = makeParagraphBibliography(buf, xs, ourparams, text, par, send);
712 case LATEX_PARAGRAPH: {
713 send = findLastParagraph(par, pend);
714 par = makeParagraphs(buf, xs, ourparams, text, par, send);
719 return make_pair(par, send);
722 } // end anonymous namespace
725 using DocBookDocumentSectioning = tuple<bool, pit_type>;
726 using DocBookInfoTag = tuple<set<pit_type>, set<pit_type>, pit_type, pit_type>;
729 DocBookDocumentSectioning hasDocumentSectioning(ParagraphList const ¶graphs, pit_type bpit, pit_type const epit) {
730 bool documentHasSections = false;
732 while (bpit < epit) {
733 Layout const &style = paragraphs[bpit].layout();
734 documentHasSections |= style.category() == from_utf8("Sectioning");
736 if (documentHasSections) {
741 // Paragraphs before the first section: [ runparams.par_begin ; eppit )
743 return make_tuple(documentHasSections, bpit);
747 DocBookInfoTag getParagraphsWithInfo(ParagraphList const ¶graphs, pit_type const bpit, pit_type const epit) {
748 set<pit_type> shouldBeInInfo;
749 set<pit_type> mustBeInInfo;
751 pit_type cpit = bpit;
752 while (cpit < epit) {
753 // Skip paragraphs only containing one note.
754 Paragraph const &par = paragraphs[cpit];
755 if (par.size() == 1 && dynamic_cast<InsetNote*>(paragraphs[cpit].insetList().get(0))) {
760 // Based on layout information, store this paragraph in one set: should be in <info>, must be.
761 Layout const &style = par.layout();
763 if (style.docbookininfo() == "always") {
764 mustBeInInfo.emplace(cpit);
765 } else if (style.docbookininfo() == "maybe") {
766 shouldBeInInfo.emplace(cpit);
768 // Hypothesis: the <info> parts should be grouped together near the beginning bpit.
773 // Now, cpit points to the last paragraph that has things that could go in <info>.
774 // bpit is still the beginning of the <info> part.
776 return make_tuple(shouldBeInInfo, mustBeInInfo, bpit, cpit);
780 bool hasAbstractBetween(ParagraphList const ¶graphs, pit_type const bpitAbstract, pit_type const epitAbstract)
782 // Hypothesis: the paragraphs between bpitAbstract and epitAbstract can be considered an abstract because they
783 // are just after a document or part title.
784 if (epitAbstract - bpitAbstract <= 0)
787 // If there is something between these paragraphs, check if it's compatible with an abstract (i.e. some text).
788 pit_type bpit = bpitAbstract;
789 while (bpit < epitAbstract) {
790 const Paragraph &p = paragraphs.at(bpit);
792 if (p.layout().name() == from_ascii("Abstract"))
795 if (!p.insetList().empty()) {
796 for (const auto &i : p.insetList()) {
797 if (i.inset->getText(0) != nullptr) {
808 pit_type generateDocBookParagraphWithoutSectioning(
812 OutputParams const & runparams,
813 ParagraphList const & paragraphs,
817 auto par = paragraphs.iterator_at(bpit);
818 auto lastStartedPar = par;
819 ParagraphList::const_iterator send;
821 (epit == (int) paragraphs.size()) ?
822 paragraphs.end() : paragraphs.iterator_at(epit);
824 while (bpit < epit) {
825 tie(par, send) = makeAny(text, buf, xs, runparams, par, send, pend);
826 bpit += distance(lastStartedPar, par);
827 lastStartedPar = par;
834 void outputDocBookInfo(
838 OutputParams const & runparams,
839 ParagraphList const & paragraphs,
840 DocBookInfoTag const & info,
841 pit_type bpitAbstract,
842 pit_type const epitAbstract)
844 // Consider everything between bpitAbstract and epitAbstract (excluded) as paragraphs for the abstract.
845 // Use bpitAbstract >= epitAbstract to indicate there is no abstract.
847 set<pit_type> shouldBeInInfo;
848 set<pit_type> mustBeInInfo;
851 tie(shouldBeInInfo, mustBeInInfo, bpitInfo, epitInfo) = info;
853 // Perform an additional check on the abstract. Sometimes, there are many paragraphs that should go
854 // into the abstract, but none generates actual content. Thus, first generate to a temporary stream,
855 // then only create the <abstract> tag if these paragraphs generate some content.
856 // This check must be performed *before* a decision on whether or not to output <info> is made.
857 bool hasAbstract = hasAbstractBetween(paragraphs, bpitAbstract, epitAbstract);
860 odocstringstream os2;
862 generateDocBookParagraphWithoutSectioning(text, buf, xs2, runparams, paragraphs, bpitAbstract, epitAbstract);
864 // Actually output the abstract if there is something to do. Don't count line feeds or spaces in this,
865 // even though they must be properly output if there is some abstract.
866 docstring abstractContent = os2.str();
867 static const lyx::regex reg("[ \\r\\n]*");
868 abstractContent = from_utf8(lyx::regex_replace(to_utf8(abstractContent), reg, string("")));
870 // Nothing? Then there is no abstract!
871 if (abstractContent.empty())
875 // The abstract must go in <info>.
876 bool needInfo = !mustBeInInfo.empty() || hasAbstract;
878 // Start the <info> tag if required.
880 xs.startDivision(false);
881 xs << xml::StartTag("info");
885 // Output the elements that should go in <info>.
886 generateDocBookParagraphWithoutSectioning(text, buf, xs, runparams, paragraphs, bpitInfo, epitInfo);
888 if (hasAbstract && !abstract.empty()) { // The second test is probably superfluous.
889 string tag = paragraphs[bpitAbstract].layout().docbookforceabstracttag();
893 xs << xml::StartTag(tag);
895 xs << XMLStream::ESCAPE_NONE << abstract;
896 xs << xml::EndTag(tag);
900 // End the <info> tag if it was started.
902 xs << xml::EndTag("info");
909 void docbookFirstParagraphs(
913 OutputParams const &runparams,
916 // Handle the beginning of the document, supposing it has sections.
917 // Major role: output the first <info> tag.
919 ParagraphList const ¶graphs = text.paragraphs();
920 pit_type bpit = runparams.par_begin;
921 DocBookInfoTag info = getParagraphsWithInfo(paragraphs, bpit, epit);
922 outputDocBookInfo(text, buf, xs, runparams, paragraphs, info, get<3>(info), epit);
926 bool isParagraphEmpty(const Paragraph &par)
928 InsetList const &insets = par.insetList();
929 size_t insetsLength = distance(insets.begin(), insets.end());
930 bool hasParagraphOnlyNote = insetsLength == 1 && insets.get(0) && insets.get(0)->asInsetCollapsible() &&
931 dynamic_cast<InsetNote *>(insets.get(0));
932 return hasParagraphOnlyNote;
936 void docbookSimpleAllParagraphs(
940 OutputParams const & runparams)
942 // Handle the document, supposing it has no sections (i.e. a "simple" document).
944 // First, the <info> tag.
945 ParagraphList const ¶graphs = text.paragraphs();
946 pit_type bpit = runparams.par_begin;
947 pit_type const epit = runparams.par_end;
948 DocBookInfoTag info = getParagraphsWithInfo(paragraphs, bpit, epit);
949 outputDocBookInfo(text, buf, xs, runparams, paragraphs, info, 0, 0);
950 bpit = get<3>(info); // Generate the content starting from the end of the <info> part.
952 // Then, the content.
953 ParagraphList::const_iterator const pend =
954 (epit == (int) paragraphs.size()) ?
955 paragraphs.end() : paragraphs.iterator_at(epit);
957 while (bpit < epit) {
958 auto par = paragraphs.iterator_at(bpit);
959 ParagraphList::const_iterator const lastStartedPar = par;
960 ParagraphList::const_iterator send;
962 if (isParagraphEmpty(*par)) {
964 bpit += distance(lastStartedPar, par);
968 // Generate this paragraph.
969 tie(par, send) = makeAny(text, buf, xs, runparams, par, send, pend);
970 bpit += distance(lastStartedPar, par);
975 void docbookParagraphs(Text const &text,
978 OutputParams const &runparams) {
979 ParagraphList const ¶graphs = text.paragraphs();
980 if (runparams.par_begin == runparams.par_end) {
981 runparams.par_begin = 0;
982 runparams.par_end = paragraphs.size();
984 pit_type bpit = runparams.par_begin;
985 pit_type const epit = runparams.par_end;
988 xs << XMLStream::ESCAPE_NONE << "<!-- DocBook output error! -->\n";
992 ParagraphList::const_iterator const pend =
993 (epit == (int) paragraphs.size()) ?
994 paragraphs.end() : paragraphs.iterator_at(epit);
995 std::stack<std::pair<int, string>> headerLevels; // Used to determine when to open/close sections: store the depth
996 // of the section and the tag that was used to open it.
998 // Detect whether the document contains sections. If there are no sections, there can be no automatically
999 // discovered abstract.
1000 bool documentHasSections;
1002 tie(documentHasSections, eppit) = hasDocumentSectioning(paragraphs, bpit, epit);
1004 if (documentHasSections) {
1005 docbookFirstParagraphs(text, buf, xs, runparams, eppit);
1008 docbookSimpleAllParagraphs(text, buf, xs, runparams);
1012 bool currentlyInAppendix = false;
1014 while (bpit < epit) {
1015 OutputParams ourparams = runparams;
1017 auto par = paragraphs.iterator_at(bpit);
1018 if (par->params().startOfAppendix())
1019 currentlyInAppendix = true;
1020 Layout const &style = par->layout();
1021 ParagraphList::const_iterator const lastStartedPar = par;
1022 ParagraphList::const_iterator send;
1024 if (isParagraphEmpty(*par)) {
1026 bpit += distance(lastStartedPar, par);
1030 // Think about adding <section> and/or </section>s.
1031 const bool isLayoutSectioning = style.category() == from_utf8("Sectioning");
1032 if (isLayoutSectioning) {
1033 int level = style.toclevel;
1035 // Need to close a previous section if it has the same level or a higher one (close <section> if opening a <h2>
1036 // after a <h2>, <h3>, <h4>, <h5> or <h6>). More examples:
1037 // - current: h2; back: h1; do not close any <section>
1038 // - current: h1; back: h2; close two <section> (first the <h2>, then the <h1>, so a new <h1> can come)
1039 while (!headerLevels.empty() && level <= headerLevels.top().first) {
1040 int stackLevel = headerLevels.top().first;
1041 docstring stackTag = from_utf8("</" + headerLevels.top().second + ">");
1044 // Output the tag only if it corresponds to a legit section.
1045 if (stackLevel != Layout::NOT_IN_TOC)
1046 xs << XMLStream::ESCAPE_NONE << stackTag << xml::CR();
1049 // Open the new section: first push it onto the stack, then output it in DocBook.
1050 string sectionTag = (currentlyInAppendix && style.docbooksectiontag() == "chapter") ?
1051 "appendix" : style.docbooksectiontag();
1052 headerLevels.push(std::make_pair(level, sectionTag));
1054 // Some sectioning-like elements should not be output (such as FrontMatter).
1055 if (level != Layout::NOT_IN_TOC) {
1056 // Look for a label in the title, i.e. a InsetLabel as a child.
1057 docstring id = docstring();
1058 for (pos_type i = 0; i < par->size(); ++i) {
1059 Inset const *inset = par->getInset(i);
1061 if (auto label = dynamic_cast<InsetLabel const *>(inset)) {
1062 // Generate the attributes for the section if need be.
1063 id += "xml:id=\"" + xml::cleanID(label->screenLabel()) + "\"";
1065 // Don't output the ID as a DocBook <anchor>.
1066 ourparams.docbook_anchors_to_ignore.emplace(label->screenLabel());
1068 // Cannot have multiple IDs per tag.
1074 // Write the open tag for this section.
1075 docstring tag = from_utf8("<" + sectionTag);
1077 tag += from_utf8(" ") + id;
1078 tag += from_utf8(">");
1079 xs << XMLStream::ESCAPE_NONE << tag;
1084 // Close all sections before the bibliography.
1085 // 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)?
1086 auto insetsLength = distance(par->insetList().begin(), par->insetList().end());
1087 if (insetsLength > 0) {
1088 Inset const *firstInset = par->getInset(0);
1089 if (firstInset && dynamic_cast<InsetBibtex const *>(firstInset)) {
1090 while (!headerLevels.empty()) {
1091 int level = headerLevels.top().first;
1092 docstring tag = from_utf8("</" + headerLevels.top().second + ">");
1095 // Output the tag only if it corresponds to a legit section.
1096 if (level != Layout::NOT_IN_TOC) {
1097 xs << XMLStream::ESCAPE_NONE << tag;
1104 // Generate this paragraph.
1105 tie(par, send) = makeAny(text, buf, xs, ourparams, par, send, pend);
1106 bpit += distance(lastStartedPar, par);
1109 // If need be, close <section>s, but only at the end of the document (otherwise, dealt with at the beginning
1111 while (!headerLevels.empty() && headerLevels.top().first > Layout::NOT_IN_TOC) {
1112 docstring tag = from_utf8("</" + headerLevels.top().second + ">");
1114 xs << XMLStream::ESCAPE_NONE << tag;