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"
45 using namespace lyx::support;
51 std::string const fontToDocBookTag(xml::FontTypes type)
54 case xml::FontTypes::FT_EMPH:
55 case xml::FontTypes::FT_BOLD:
57 case xml::FontTypes::FT_NOUN:
59 case xml::FontTypes::FT_UBAR:
60 case xml::FontTypes::FT_WAVE:
61 case xml::FontTypes::FT_DBAR:
62 case xml::FontTypes::FT_SOUT:
63 case xml::FontTypes::FT_XOUT:
64 case xml::FontTypes::FT_ITALIC:
65 case xml::FontTypes::FT_UPRIGHT:
66 case xml::FontTypes::FT_SLANTED:
67 case xml::FontTypes::FT_SMALLCAPS:
68 case xml::FontTypes::FT_ROMAN:
69 case xml::FontTypes::FT_SANS:
71 case xml::FontTypes::FT_TYPE:
73 case xml::FontTypes::FT_SIZE_TINY:
74 case xml::FontTypes::FT_SIZE_SCRIPT:
75 case xml::FontTypes::FT_SIZE_FOOTNOTE:
76 case xml::FontTypes::FT_SIZE_SMALL:
77 case xml::FontTypes::FT_SIZE_NORMAL:
78 case xml::FontTypes::FT_SIZE_LARGE:
79 case xml::FontTypes::FT_SIZE_LARGER:
80 case xml::FontTypes::FT_SIZE_LARGEST:
81 case xml::FontTypes::FT_SIZE_HUGE:
82 case xml::FontTypes::FT_SIZE_HUGER:
83 case xml::FontTypes::FT_SIZE_INCREASE:
84 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:
105 return ""; // Outputs a <person>
106 case xml::FontTypes::FT_TYPE:
107 return ""; // Outputs a <code>
108 case xml::FontTypes::FT_UBAR:
111 // All other roles are non-standard for DocBook.
113 case xml::FontTypes::FT_WAVE:
115 case xml::FontTypes::FT_DBAR:
117 case xml::FontTypes::FT_SOUT:
119 case xml::FontTypes::FT_XOUT:
121 case xml::FontTypes::FT_UPRIGHT:
123 case xml::FontTypes::FT_SLANTED:
125 case xml::FontTypes::FT_SMALLCAPS:
127 case xml::FontTypes::FT_ROMAN:
129 case xml::FontTypes::FT_SANS:
131 case xml::FontTypes::FT_SIZE_TINY:
133 case xml::FontTypes::FT_SIZE_SCRIPT:
134 return "size_script";
135 case xml::FontTypes::FT_SIZE_FOOTNOTE:
136 return "size_footnote";
137 case xml::FontTypes::FT_SIZE_SMALL:
139 case xml::FontTypes::FT_SIZE_NORMAL:
140 return "size_normal";
141 case xml::FontTypes::FT_SIZE_LARGE:
143 case xml::FontTypes::FT_SIZE_LARGER:
144 return "size_larger";
145 case xml::FontTypes::FT_SIZE_LARGEST:
146 return "size_largest";
147 case xml::FontTypes::FT_SIZE_HUGE:
149 case xml::FontTypes::FT_SIZE_HUGER:
151 case xml::FontTypes::FT_SIZE_INCREASE:
152 return "size_increase";
153 case xml::FontTypes::FT_SIZE_DECREASE:
154 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 } // end anonymous namespace
174 xml::FontTag docbookStartFontTag(xml::FontTypes type)
176 return xml::FontTag(from_utf8(fontToDocBookTag(type)), from_utf8(fontToAttribute(type)), type);
180 xml::EndFontTag docbookEndFontTag(xml::FontTypes type)
182 return xml::EndFontTag(from_utf8(fontToDocBookTag(type)), type);
188 // convenience functions
190 void openParTag(XMLStream &xs, Layout const &lay)
192 if (lay.docbookwrappertag() != "NONE") {
193 xs << xml::StartTag(lay.docbookwrappertag(), lay.docbookwrapperattr());
196 string tag = lay.docbooktag();
197 if (tag == "Plain Layout")
200 xs << xml::ParTag(tag, lay.docbookattr());
204 void closeTag(XMLStream &xs, Layout const &lay)
206 string tag = lay.docbooktag();
207 if (tag == "Plain Layout")
210 xs << xml::EndTag(tag);
211 if (lay.docbookwrappertag() != "NONE")
212 xs << xml::EndTag(lay.docbookwrappertag());
216 void openLabelTag(XMLStream & xs, Layout const & lay) // Mostly for definition lists.
218 xs << xml::StartTag(lay.docbookitemlabeltag(), lay.docbookitemlabelattr());
222 void closeLabelTag(XMLStream & xs, Layout const & lay)
224 xs << xml::EndTag(lay.docbookitemlabeltag());
229 void openItemTag(XMLStream &xs, Layout const &lay)
231 xs << xml::StartTag(lay.docbookitemtag(), lay.docbookitemattr());
235 // Return true when new elements are output in a paragraph, false otherwise.
236 bool openInnerItemTag(XMLStream &xs, Layout const &lay)
238 if (lay.docbookiteminnertag() != "NONE") {
240 xs << xml::ParTag(lay.docbookiteminnertag(), lay.docbookiteminnerattr());
242 if (lay.docbookiteminnertag() == "para") {
250 void closeInnerItemTag(XMLStream &xs, Layout const &lay)
252 if (lay.docbookiteminnertag()!= "NONE") {
253 xs << xml::EndTag(lay.docbookiteminnertag());
259 inline void closeItemTag(XMLStream &xs, Layout const &lay)
261 xs << xml::EndTag(lay.docbookitemtag());
265 // end of convenience functions
267 ParagraphList::const_iterator findLastParagraph(
268 ParagraphList::const_iterator p,
269 ParagraphList::const_iterator const & pend) {
270 for (++p; p != pend && p->layout().latextype == LATEX_PARAGRAPH; ++p);
276 ParagraphList::const_iterator findEndOfEnvironment(
277 ParagraphList::const_iterator const & pstart,
278 ParagraphList::const_iterator const & pend)
280 ParagraphList::const_iterator p = pstart;
281 Layout const &bstyle = p->layout();
282 size_t const depth = p->params().depth();
283 for (++p; p != pend; ++p) {
284 Layout const &style = p->layout();
285 // It shouldn't happen that e.g. a section command occurs inside
286 // a quotation environment, at a higher depth, but as of 6/2009,
287 // it can happen. We pretend that it's just at lowest depth.
288 if (style.latextype == LATEX_COMMAND)
291 // If depth is down, we're done
292 if (p->params().depth() < depth)
295 // If depth is up, we're not done
296 if (p->params().depth() > depth)
299 // FIXME I am not sure about the first check.
300 // Surely we *could* have different layouts that count as
301 // LATEX_PARAGRAPH, right?
302 if (style.latextype == LATEX_PARAGRAPH || style != bstyle)
309 ParagraphList::const_iterator makeParagraphBibliography(
312 OutputParams const &runparams,
314 ParagraphList::const_iterator const & pbegin,
315 ParagraphList::const_iterator const & pend)
317 auto const begin = text.paragraphs().begin();
318 auto const end = text.paragraphs().end();
320 // Find the paragraph *before* pbegin.
321 ParagraphList::const_iterator pbegin_before = begin;
322 if (pbegin != begin) {
323 ParagraphList::const_iterator pbegin_before_next = begin;
324 ++pbegin_before_next;
326 while (pbegin_before_next != pbegin) {
328 ++pbegin_before_next;
332 ParagraphList::const_iterator par = pbegin;
334 // If this is the first paragraph in a bibliography, open the bibliography tag.
335 if (pbegin != begin && pbegin_before->layout().latextype != LATEX_BIB_ENVIRONMENT) {
336 xs << xml::StartTag("bibliography");
340 // Generate the required paragraphs.
341 for (; par != pend; ++par) {
342 // Start the precooked bibliography entry. This is very much like opening a paragraph tag.
343 // Don't forget the citation ID!
345 for (auto i = 0; i < par->size(); ++i) {
346 Inset const *ip = par->getInset(0);
347 if (ip != nullptr && ip->lyxCode() == BIBITEM_CODE) {
348 const auto * bibitem = dynamic_cast<const InsetBibitem*>(par->getInset(i));
349 attr = from_utf8("xml:id='") + bibitem->bibLabel() + from_utf8("'");
353 xs << xml::StartTag(from_utf8("bibliomixed"), attr);
355 // Generate the entry.
356 par->simpleDocBookOnePar(buf, xs, runparams, text.outerFont(distance(begin, par)), true, true, 0);
358 // End the precooked bibliography entry.
359 xs << xml::EndTag("bibliomixed");
363 // If this is the last paragraph in a bibliography, close the bibliography tag.
364 if (par == end || par->layout().latextype != LATEX_BIB_ENVIRONMENT) {
365 xs << xml::EndTag("bibliography");
373 ParagraphList::const_iterator makeParagraphs(
376 OutputParams const &runparams,
378 ParagraphList::const_iterator const & pbegin,
379 ParagraphList::const_iterator const & pend)
381 ParagraphList::const_iterator const begin = text.paragraphs().begin();
382 ParagraphList::const_iterator par = pbegin;
383 for (; par != pend; ++par) {
384 Layout const &lay = par->layout();
386 // We want to open the paragraph tag if:
387 // (i) the current layout permits multiple paragraphs
388 // (ii) we are either not already inside a paragraph (HTMLIsBlock) OR
389 // we are, but this is not the first paragraph
391 // But there is also a special case, and we first see whether we are in it.
392 // We do not want to open the paragraph tag if this paragraph contains
393 // only one item, and that item is "inline", i.e., not HTMLIsBlock (such
394 // as a branch). On the other hand, if that single item has a font change
395 // applied to it, then we still do need to open the paragraph.
397 // Obviously, this is very fragile. The main reason we need to do this is
398 // because of branches, e.g., a branch that contains an entire new section.
399 // We do not really want to wrap that whole thing in a <div>...</div>.
400 bool special_case = false;
401 Inset const *specinset = par->size() == 1 ? par->getInset(0) : 0;
402 if (specinset && !specinset->getLayout().htmlisblock()) { // TODO: Convert htmlisblock to a DocBook parameter?
403 Layout const &style = par->layout();
404 FontInfo const first_font = style.labeltype == LABEL_MANUAL ?
405 style.labelfont : style.font;
406 FontInfo const our_font =
407 par->getFont(buf.masterBuffer()->params(), 0,
408 text.outerFont(distance(begin, par))).fontInfo();
410 if (first_font == our_font)
414 // Plain layouts must be ignored.
415 if (!special_case && buf.params().documentClass().isPlainLayout(lay) && !runparams.docbook_force_pars)
417 // TODO: Could get rid of this with a DocBook equivalent to htmlisblock?
418 if (!special_case && par->size() == 1 && par->getInset(0)) {
419 Inset const * firstInset = par->getInset(0);
421 // Floats cannot be in paragraphs.
422 special_case = to_utf8(firstInset->layoutName()).substr(0, 6) == "Float:";
424 // Bibliographies cannot be in paragraphs.
425 if (!special_case && firstInset->asInsetCommand())
426 special_case = firstInset->asInsetCommand()->params().getCmdName() == "bibtex";
428 // Equations do not deserve their own paragraph (DocBook allows them outside paragraphs).
429 if (!special_case && firstInset->asInsetMath())
432 // ERTs are in comments, not paragraphs.
433 if (!special_case && firstInset->lyxCode() == lyx::ERT_CODE)
436 // Listings should not get into their own paragraph.
437 if (!special_case && firstInset->lyxCode() == lyx::LISTINGS_CODE)
441 bool const open_par = runparams.docbook_make_pars
442 && (!runparams.docbook_in_par || par != pbegin)
445 // We want to issue the closing tag if either:
446 // (i) We opened it, and either docbook_in_par is false,
447 // or we're not in the last paragraph, anyway.
448 // (ii) We didn't open it and docbook_in_par is true,
449 // but we are in the first par, and there is a next par.
450 ParagraphList::const_iterator nextpar = par;
452 bool const close_par =
453 ((open_par && (!runparams.docbook_in_par || nextpar != pend))
454 || (!open_par && runparams.docbook_in_par && par == pbegin && nextpar != pend));
460 par->simpleDocBookOnePar(buf, xs, runparams, text.outerFont(distance(begin, par)), open_par, close_par, 0);
471 bool isNormalEnv(Layout const &lay)
473 return lay.latextype == LATEX_ENVIRONMENT
474 || lay.latextype == LATEX_BIB_ENVIRONMENT;
478 ParagraphList::const_iterator makeEnvironment(
481 OutputParams const &runparams,
483 ParagraphList::const_iterator const & pbegin,
484 ParagraphList::const_iterator const & pend)
486 ParagraphList::const_iterator const begin = text.paragraphs().begin();
487 ParagraphList::const_iterator par = pbegin;
488 Layout const &bstyle = par->layout();
489 depth_type const origdepth = pbegin->params().depth();
491 // open tag for this environment
492 openParTag(xs, bstyle);
495 // we will on occasion need to remember a layout from before.
496 Layout const *lastlay = nullptr;
498 while (par != pend) {
499 Layout const & style = par->layout();
500 ParagraphList::const_iterator send;
502 // Actual content of this paragraph.
503 switch (style.latextype) {
504 case LATEX_ENVIRONMENT:
505 case LATEX_LIST_ENVIRONMENT:
506 case LATEX_ITEM_ENVIRONMENT: {
507 // There are two possibilities in this case.
508 // One is that we are still in the environment in which we
509 // started---which we will be if the depth is the same.
510 if (par->params().depth() == origdepth) {
511 LATTEST(bstyle == style);
512 if (lastlay != nullptr) {
513 closeItemTag(xs, *lastlay);
514 if (lastlay->docbookitemwrappertag() != "NONE") {
515 xs << xml::EndTag(lastlay->docbookitemwrappertag());
521 // this will be positive if we want to skip the
522 // initial word (if it's been taken for the label).
525 // Open a wrapper tag if needed.
526 if (style.docbookitemwrappertag() != "NONE") {
527 xs << xml::StartTag(style.docbookitemwrappertag(), style.docbookitemwrapperattr());
532 if (style.labeltype != LABEL_NO_LABEL &&
533 style.docbookitemlabeltag() != "NONE") {
535 if (isNormalEnv(style)) {
536 // in this case, we print the label only for the first
537 // paragraph (as in a theorem or an abstract).
539 docstring const lbl = pbegin->params().labelString();
541 openLabelTag(xs, style);
543 closeLabelTag(xs, style);
545 // No new line after closeLabelTag.
549 } else { // some kind of list
550 if (style.labeltype == LABEL_MANUAL) {
551 // Only variablelist gets here.
553 openLabelTag(xs, style);
554 sep = par->firstWordDocBook(xs, runparams);
555 closeLabelTag(xs, style);
557 openLabelTag(xs, style);
558 xs << par->params().labelString();
559 closeLabelTag(xs, style);
562 } // end label output
564 // Start generating the item.
565 bool wasInParagraph = runparams.docbook_in_par;
566 openItemTag(xs, style);
567 bool getsIntoParagraph = openInnerItemTag(xs, style);
568 OutputParams rp = runparams;
569 rp.docbook_in_par = wasInParagraph | getsIntoParagraph;
571 // Maybe the item is completely empty, i.e. if the first word ends at the end of the current paragraph
572 // AND if the next paragraph doesn't have the same depth (if there is such a paragraph).
573 // Common case: there is only the first word on the line, but there is a nested list instead
575 bool emptyItem = false;
576 if (sep == par->size()) {
579 if (next_par == text.paragraphs().end()) // There is no next paragraph.
581 else // There is a next paragraph: check depth.
582 emptyItem = par->params().depth() >= next_par->params().depth();
586 // Avoid having an empty item, this is not valid DocBook. A single character is enough to force
587 // generation of a full <para>.
590 // Generate the rest of the paragraph, if need be.
591 par->simpleDocBookOnePar(buf, xs, rp, text.outerFont(distance(begin, par)), true, true, sep);
595 if (getsIntoParagraph)
596 closeInnerItemTag(xs, style);
598 // We may not want to close the tag yet, in particular:
599 // If we're not at the end of the item...
601 // and are doing items...
602 && !isNormalEnv(style)
603 // and if the depth has changed...
604 && par->params().depth() != origdepth) {
605 // then we'll save this layout for later, and close it when
606 // we get another item.
609 closeItemTag(xs, style);
611 // Eventually, close the item wrapper.
612 if (style.docbookitemwrappertag() != "NONE") {
613 xs << xml::EndTag(style.docbookitemwrappertag());
618 // The other possibility is that the depth has increased.
620 send = findEndOfEnvironment(par, pend);
621 par = makeEnvironment(buf, xs, runparams, text, par, send);
625 case LATEX_PARAGRAPH:
626 send = findLastParagraph(par, pend);
627 par = makeParagraphs(buf, xs, runparams, text, par, send);
629 case LATEX_BIB_ENVIRONMENT:
630 send = findLastParagraph(par, pend);
631 par = makeParagraphBibliography(buf, xs, runparams, text, par, send);
639 if (lastlay != nullptr) {
640 closeItemTag(xs, *lastlay);
641 if (lastlay->docbookitemwrappertag() != "NONE") {
642 xs << xml::EndTag(lastlay->docbookitemwrappertag());
646 closeTag(xs, bstyle);
655 OutputParams const & runparams,
657 ParagraphList::const_iterator const & pbegin)
659 Layout const &style = pbegin->layout();
661 // No need for labels, as they are handled by DocBook tags.
663 openParTag(xs, style);
665 ParagraphList::const_iterator const begin = text.paragraphs().begin();
666 pbegin->simpleDocBookOnePar(buf, xs, runparams,
667 text.outerFont(distance(begin, pbegin)));
672 pair<ParagraphList::const_iterator, ParagraphList::const_iterator> makeAny(
676 OutputParams const &ourparams,
677 ParagraphList::const_iterator par,
678 ParagraphList::const_iterator send,
679 ParagraphList::const_iterator pend)
681 Layout const & style = par->layout();
683 switch (style.latextype) {
684 case LATEX_COMMAND: {
685 // The files with which we are working never have more than
686 // one paragraph in a command structure.
688 // if (ourparams.docbook_in_par)
689 // fix it so we don't get sections inside standard, e.g.
690 // note that we may then need to make runparams not const, so we
691 // can communicate that back.
692 // FIXME Maybe this fix should be in the routines themselves, in case
693 // they are called from elsewhere.
694 makeCommand(buf, xs, ourparams, text, par);
698 case LATEX_ENVIRONMENT:
699 case LATEX_LIST_ENVIRONMENT:
700 case LATEX_ITEM_ENVIRONMENT: {
701 // FIXME Same fix here.
702 send = findEndOfEnvironment(par, pend);
703 par = makeEnvironment(buf, xs, ourparams, text, par, send);
706 case LATEX_BIB_ENVIRONMENT: {
707 send = findLastParagraph(par, pend);
708 par = makeParagraphBibliography(buf, xs, ourparams, text, par, send);
711 case LATEX_PARAGRAPH: {
712 send = findLastParagraph(par, pend);
713 par = makeParagraphs(buf, xs, ourparams, text, par, send);
718 return make_pair(par, send);
721 } // end anonymous namespace
724 using DocBookDocumentSectioning = tuple<bool, pit_type>;
725 using DocBookInfoTag = tuple<set<pit_type>, set<pit_type>, pit_type, pit_type>;
728 DocBookDocumentSectioning hasDocumentSectioning(ParagraphList const ¶graphs, pit_type bpit, pit_type const epit) {
729 bool documentHasSections = false;
731 while (bpit < epit) {
732 Layout const &style = paragraphs[bpit].layout();
733 documentHasSections |= style.category() == from_utf8("Sectioning");
735 if (documentHasSections) {
740 // Paragraphs before the first section: [ runparams.par_begin ; eppit )
742 return make_tuple(documentHasSections, bpit);
746 DocBookInfoTag getParagraphsWithInfo(ParagraphList const ¶graphs, pit_type const bpit, pit_type const epit) {
747 set<pit_type> shouldBeInInfo;
748 set<pit_type> mustBeInInfo;
750 pit_type cpit = bpit;
751 while (cpit < epit) {
752 // Skip paragraphs only containing one note.
753 Paragraph const &par = paragraphs[cpit];
754 if (par.size() == 1 && dynamic_cast<InsetNote*>(paragraphs[cpit].insetList().get(0))) {
759 // Based on layout information, store this paragraph in one set: should be in <info>, must be.
760 Layout const &style = par.layout();
762 if (style.docbookininfo() == "always") {
763 mustBeInInfo.emplace(cpit);
764 } else if (style.docbookininfo() == "maybe") {
765 shouldBeInInfo.emplace(cpit);
767 // Hypothesis: the <info> parts should be grouped together near the beginning bpit.
772 // Now, cpit points to the last paragraph that has things that could go in <info>.
773 // bpit is still the beginning of the <info> part.
775 return make_tuple(shouldBeInInfo, mustBeInInfo, bpit, cpit);
779 bool hasAbstractBetween(ParagraphList const ¶graphs, pit_type const bpitAbstract, pit_type const epitAbstract)
781 // Hypothesis: the paragraphs between bpitAbstract and epitAbstract can be considered an abstract because they
782 // are just after a document or part title.
783 if (epitAbstract - bpitAbstract <= 0)
786 // If there is something between these paragraphs, check if it's compatible with an abstract (i.e. some text).
787 pit_type bpit = bpitAbstract;
788 while (bpit < epitAbstract) {
789 const Paragraph &p = paragraphs.at(bpit);
791 if (p.layout().name() == from_ascii("Abstract"))
794 if (!p.insetList().empty()) {
795 for (const auto &i : p.insetList()) {
796 if (i.inset->getText(0) != nullptr) {
807 pit_type generateDocBookParagraphWithoutSectioning(
811 OutputParams const & runparams,
812 ParagraphList const & paragraphs,
816 auto par = paragraphs.iterator_at(bpit);
817 auto lastStartedPar = par;
818 ParagraphList::const_iterator send;
820 (epit == (int) paragraphs.size()) ?
821 paragraphs.end() : paragraphs.iterator_at(epit);
823 while (bpit < epit) {
824 tie(par, send) = makeAny(text, buf, xs, runparams, par, send, pend);
825 bpit += distance(lastStartedPar, par);
826 lastStartedPar = par;
833 void outputDocBookInfo(
837 OutputParams const & runparams,
838 ParagraphList const & paragraphs,
839 DocBookInfoTag const & info,
840 pit_type bpitAbstract,
841 pit_type const epitAbstract)
843 // Consider everything between bpitAbstract and epitAbstract (excluded) as paragraphs for the abstract.
844 // Use bpitAbstract >= epitAbstract to indicate there is no abstract.
846 set<pit_type> shouldBeInInfo;
847 set<pit_type> mustBeInInfo;
850 tie(shouldBeInInfo, mustBeInInfo, bpitInfo, epitInfo) = info;
852 // The abstract must go in <info>.
853 bool hasAbstract = hasAbstractBetween(paragraphs, bpitAbstract, epitAbstract);
854 bool needInfo = !mustBeInInfo.empty() || hasAbstract;
856 // Start the <info> tag if required.
858 xs.startDivision(false);
859 xs << xml::StartTag("info");
863 // Output the elements that should go in <info>.
864 generateDocBookParagraphWithoutSectioning(text, buf, xs, runparams, paragraphs, bpitInfo, epitInfo);
867 // Sometimes, there are many paragraphs that should go into the abstract, but none generates actual content.
868 // Thus, first generate to a temporary stream, then only create the <abstract> tag if these paragraphs
869 // generate some content.
870 odocstringstream os2;
872 generateDocBookParagraphWithoutSectioning(text, buf, xs2, runparams, paragraphs, bpitAbstract, epitAbstract);
874 // Actually output the abstract if there is something to do.
875 if (!os2.str().empty()) {
876 string tag = paragraphs[bpitAbstract].layout().docbookforceabstracttag();
880 xs << xml::StartTag(tag);
882 xs << XMLStream::ESCAPE_NONE << os2.str();
883 xs << xml::EndTag(tag);
888 // End the <info> tag if it was started.
890 xs << xml::EndTag("info");
897 void docbookFirstParagraphs(
901 OutputParams const &runparams,
904 // Handle the beginning of the document, supposing it has sections.
905 // Major role: output the first <info> tag.
907 ParagraphList const ¶graphs = text.paragraphs();
908 pit_type bpit = runparams.par_begin;
909 DocBookInfoTag info = getParagraphsWithInfo(paragraphs, bpit, epit);
910 outputDocBookInfo(text, buf, xs, runparams, paragraphs, info, get<3>(info), epit);
914 bool isParagraphEmpty(const Paragraph &par)
916 InsetList const &insets = par.insetList();
917 size_t insetsLength = distance(insets.begin(), insets.end());
918 bool hasParagraphOnlyNote = insetsLength == 1 && insets.get(0) && insets.get(0)->asInsetCollapsible() &&
919 dynamic_cast<InsetNote *>(insets.get(0));
920 return hasParagraphOnlyNote;
924 void docbookSimpleAllParagraphs(
928 OutputParams const & runparams)
930 // Handle the document, supposing it has no sections (i.e. a "simple" document).
932 // First, the <info> tag.
933 ParagraphList const ¶graphs = text.paragraphs();
934 pit_type bpit = runparams.par_begin;
935 pit_type const epit = runparams.par_end;
936 DocBookInfoTag info = getParagraphsWithInfo(paragraphs, bpit, epit);
937 outputDocBookInfo(text, buf, xs, runparams, paragraphs, info, 0, 0);
938 bpit = get<3>(info); // Generate the content starting from the end of the <info> part.
940 // Then, the content.
941 ParagraphList::const_iterator const pend =
942 (epit == (int) paragraphs.size()) ?
943 paragraphs.end() : paragraphs.iterator_at(epit);
945 while (bpit < epit) {
946 auto par = paragraphs.iterator_at(bpit);
947 ParagraphList::const_iterator const lastStartedPar = par;
948 ParagraphList::const_iterator send;
950 if (isParagraphEmpty(*par)) {
952 bpit += distance(lastStartedPar, par);
956 // Generate this paragraph.
957 tie(par, send) = makeAny(text, buf, xs, runparams, par, send, pend);
958 bpit += distance(lastStartedPar, par);
963 void docbookParagraphs(Text const &text,
966 OutputParams const &runparams) {
967 ParagraphList const ¶graphs = text.paragraphs();
968 if (runparams.par_begin == runparams.par_end) {
969 runparams.par_begin = 0;
970 runparams.par_end = paragraphs.size();
972 pit_type bpit = runparams.par_begin;
973 pit_type const epit = runparams.par_end;
976 xs << XMLStream::ESCAPE_NONE << "<!-- DocBook output error! -->\n";
980 ParagraphList::const_iterator const pend =
981 (epit == (int) paragraphs.size()) ?
982 paragraphs.end() : paragraphs.iterator_at(epit);
983 std::stack<std::pair<int, string>> headerLevels; // Used to determine when to open/close sections: store the depth
984 // of the section and the tag that was used to open it.
986 // Detect whether the document contains sections. If there are no sections, there can be no automatically
987 // discovered abstract.
988 bool documentHasSections;
990 tie(documentHasSections, eppit) = hasDocumentSectioning(paragraphs, bpit, epit);
992 if (documentHasSections) {
993 docbookFirstParagraphs(text, buf, xs, runparams, eppit);
996 docbookSimpleAllParagraphs(text, buf, xs, runparams);
1000 bool currentlyInAppendix = false;
1002 while (bpit < epit) {
1003 OutputParams ourparams = runparams;
1005 auto par = paragraphs.iterator_at(bpit);
1006 if (par->params().startOfAppendix())
1007 currentlyInAppendix = true;
1008 Layout const &style = par->layout();
1009 ParagraphList::const_iterator const lastStartedPar = par;
1010 ParagraphList::const_iterator send;
1012 if (isParagraphEmpty(*par)) {
1014 bpit += distance(lastStartedPar, par);
1018 // Think about adding <section> and/or </section>s.
1019 const bool isLayoutSectioning = style.category() == from_utf8("Sectioning");
1020 if (isLayoutSectioning) {
1021 int level = style.toclevel;
1023 // Need to close a previous section if it has the same level or a higher one (close <section> if opening a <h2>
1024 // after a <h2>, <h3>, <h4>, <h5> or <h6>). More examples:
1025 // - current: h2; back: h1; do not close any <section>
1026 // - current: h1; back: h2; close two <section> (first the <h2>, then the <h1>, so a new <h1> can come)
1027 while (!headerLevels.empty() && level <= headerLevels.top().first) {
1028 int stackLevel = headerLevels.top().first;
1029 docstring stackTag = from_utf8("</" + headerLevels.top().second + ">");
1032 // Output the tag only if it corresponds to a legit section.
1033 if (stackLevel != Layout::NOT_IN_TOC)
1034 xs << XMLStream::ESCAPE_NONE << stackTag << xml::CR();
1037 // Open the new section: first push it onto the stack, then output it in DocBook.
1038 string sectionTag = (currentlyInAppendix && style.docbooksectiontag() == "chapter") ?
1039 "appendix" : style.docbooksectiontag();
1040 headerLevels.push(std::make_pair(level, sectionTag));
1042 // Some sectioning-like elements should not be output (such as FrontMatter).
1043 if (level != Layout::NOT_IN_TOC) {
1044 // Look for a label in the title, i.e. a InsetLabel as a child.
1045 docstring id = docstring();
1046 for (pos_type i = 0; i < par->size(); ++i) {
1047 Inset const *inset = par->getInset(i);
1049 if (auto label = dynamic_cast<InsetLabel const *>(inset)) {
1050 // Generate the attributes for the section if need be.
1051 id += "xml:id=\"" + xml::cleanID(label->screenLabel()) + "\"";
1053 // Don't output the ID as a DocBook <anchor>.
1054 ourparams.docbook_anchors_to_ignore.emplace(label->screenLabel());
1056 // Cannot have multiple IDs per tag.
1062 // Write the open tag for this section.
1063 docstring tag = from_utf8("<" + sectionTag);
1065 tag += from_utf8(" ") + id;
1066 tag += from_utf8(">");
1067 xs << XMLStream::ESCAPE_NONE << tag;
1072 // Close all sections before the bibliography.
1073 // 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)?
1074 auto insetsLength = distance(par->insetList().begin(), par->insetList().end());
1075 if (insetsLength > 0) {
1076 Inset const *firstInset = par->getInset(0);
1077 if (firstInset && dynamic_cast<InsetBibtex const *>(firstInset)) {
1078 while (!headerLevels.empty()) {
1079 int level = headerLevels.top().first;
1080 docstring tag = from_utf8("</" + headerLevels.top().second + ">");
1083 // Output the tag only if it corresponds to a legit section.
1084 if (level != Layout::NOT_IN_TOC) {
1085 xs << XMLStream::ESCAPE_NONE << tag;
1092 // Generate this paragraph.
1093 tie(par, send) = makeAny(text, buf, xs, ourparams, par, send, pend);
1094 bpit += distance(lastStartedPar, par);
1097 // If need be, close <section>s, but only at the end of the document (otherwise, dealt with at the beginning
1099 while (!headerLevels.empty() && headerLevels.top().first > Layout::NOT_IN_TOC) {
1100 docstring tag = from_utf8("</" + headerLevels.top().second + ">");
1102 xs << XMLStream::ESCAPE_NONE << tag;