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 if (par->getInset(0)->lyxCode() == BIBITEM_CODE) {
347 const auto * bibitem = dynamic_cast<const InsetBibitem*>(par->getInset(i));
348 attr = from_utf8("xml:id='") + bibitem->bibLabel() + from_utf8("'");
352 xs << xml::StartTag(from_utf8("bibliomixed"), attr);
354 // Generate the entry.
355 par->simpleDocBookOnePar(buf, xs, runparams, text.outerFont(distance(begin, par)), true, true, 0);
357 // End the precooked bibliography entry.
358 xs << xml::EndTag("bibliomixed");
362 // If this is the last paragraph in a bibliography, close the bibliography tag.
363 if (par == end || par->layout().latextype != LATEX_BIB_ENVIRONMENT) {
364 xs << xml::EndTag("bibliography");
372 ParagraphList::const_iterator makeParagraphs(
375 OutputParams const &runparams,
377 ParagraphList::const_iterator const & pbegin,
378 ParagraphList::const_iterator const & pend)
380 ParagraphList::const_iterator const begin = text.paragraphs().begin();
381 ParagraphList::const_iterator par = pbegin;
382 for (; par != pend; ++par) {
383 Layout const &lay = par->layout();
385 // We want to open the paragraph tag if:
386 // (i) the current layout permits multiple paragraphs
387 // (ii) we are either not already inside a paragraph (HTMLIsBlock) OR
388 // we are, but this is not the first paragraph
390 // But there is also a special case, and we first see whether we are in it.
391 // We do not want to open the paragraph tag if this paragraph contains
392 // only one item, and that item is "inline", i.e., not HTMLIsBlock (such
393 // as a branch). On the other hand, if that single item has a font change
394 // applied to it, then we still do need to open the paragraph.
396 // Obviously, this is very fragile. The main reason we need to do this is
397 // because of branches, e.g., a branch that contains an entire new section.
398 // We do not really want to wrap that whole thing in a <div>...</div>.
399 bool special_case = false;
400 Inset const *specinset = par->size() == 1 ? par->getInset(0) : 0;
401 if (specinset && !specinset->getLayout().htmlisblock()) { // TODO: Convert htmlisblock to a DocBook parameter?
402 Layout const &style = par->layout();
403 FontInfo const first_font = style.labeltype == LABEL_MANUAL ?
404 style.labelfont : style.font;
405 FontInfo const our_font =
406 par->getFont(buf.masterBuffer()->params(), 0,
407 text.outerFont(distance(begin, par))).fontInfo();
409 if (first_font == our_font)
413 // Plain layouts must be ignored.
414 if (!special_case && buf.params().documentClass().isPlainLayout(lay) && !runparams.docbook_force_pars)
416 // TODO: Could get rid of this with a DocBook equivalent to htmlisblock?
417 if (!special_case && par->size() == 1 && par->getInset(0)) {
418 Inset const * firstInset = par->getInset(0);
420 // Floats cannot be in paragraphs.
421 special_case = to_ascii(firstInset->layoutName()).substr(0, 6) == "Float:";
423 // Bibliographies cannot be in paragraphs.
424 if (!special_case && firstInset->asInsetCommand())
425 special_case = firstInset->asInsetCommand()->params().getCmdName() == "bibtex";
427 // Equations do not deserve their own paragraph (DocBook allows them outside paragraphs).
428 if (!special_case && firstInset->asInsetMath())
431 // ERTs are in comments, not paragraphs.
432 if (!special_case && firstInset->lyxCode() == lyx::ERT_CODE)
435 // Listings should not get into their own paragraph.
436 if (!special_case && firstInset->lyxCode() == lyx::LISTINGS_CODE)
440 bool const open_par = runparams.docbook_make_pars
441 && (!runparams.docbook_in_par || par != pbegin)
444 // We want to issue the closing tag if either:
445 // (i) We opened it, and either docbook_in_par is false,
446 // or we're not in the last paragraph, anyway.
447 // (ii) We didn't open it and docbook_in_par is true,
448 // but we are in the first par, and there is a next par.
449 ParagraphList::const_iterator nextpar = par;
451 bool const close_par =
452 ((open_par && (!runparams.docbook_in_par || nextpar != pend))
453 || (!open_par && runparams.docbook_in_par && par == pbegin && nextpar != pend));
459 par->simpleDocBookOnePar(buf, xs, runparams, text.outerFont(distance(begin, par)), open_par, close_par, 0);
470 bool isNormalEnv(Layout const &lay)
472 return lay.latextype == LATEX_ENVIRONMENT
473 || lay.latextype == LATEX_BIB_ENVIRONMENT;
477 ParagraphList::const_iterator makeEnvironment(
480 OutputParams const &runparams,
482 ParagraphList::const_iterator const & pbegin,
483 ParagraphList::const_iterator const & pend)
485 ParagraphList::const_iterator const begin = text.paragraphs().begin();
486 ParagraphList::const_iterator par = pbegin;
487 Layout const &bstyle = par->layout();
488 depth_type const origdepth = pbegin->params().depth();
490 // open tag for this environment
491 openParTag(xs, bstyle);
494 // we will on occasion need to remember a layout from before.
495 Layout const *lastlay = nullptr;
497 while (par != pend) {
498 Layout const & style = par->layout();
499 ParagraphList::const_iterator send;
501 // Actual content of this paragraph.
502 switch (style.latextype) {
503 case LATEX_ENVIRONMENT:
504 case LATEX_LIST_ENVIRONMENT:
505 case LATEX_ITEM_ENVIRONMENT: {
506 // There are two possibilities in this case.
507 // One is that we are still in the environment in which we
508 // started---which we will be if the depth is the same.
509 if (par->params().depth() == origdepth) {
510 LATTEST(bstyle == style);
511 if (lastlay != nullptr) {
512 closeItemTag(xs, *lastlay);
513 if (lastlay->docbookitemwrappertag() != "NONE") {
514 xs << xml::EndTag(lastlay->docbookitemwrappertag());
520 // this will be positive if we want to skip the
521 // initial word (if it's been taken for the label).
524 // Open a wrapper tag if needed.
525 if (style.docbookitemwrappertag() != "NONE") {
526 xs << xml::StartTag(style.docbookitemwrappertag(), style.docbookitemwrapperattr());
531 if (style.labeltype != LABEL_NO_LABEL &&
532 style.docbookitemlabeltag() != "NONE") {
534 if (isNormalEnv(style)) {
535 // in this case, we print the label only for the first
536 // paragraph (as in a theorem or an abstract).
538 docstring const lbl = pbegin->params().labelString();
540 openLabelTag(xs, style);
542 closeLabelTag(xs, style);
544 // No new line after closeLabelTag.
548 } else { // some kind of list
549 if (style.labeltype == LABEL_MANUAL) {
550 // Only variablelist gets here.
552 openLabelTag(xs, style);
553 sep = par->firstWordDocBook(xs, runparams);
554 closeLabelTag(xs, style);
556 openLabelTag(xs, style);
557 xs << par->params().labelString();
558 closeLabelTag(xs, style);
561 } // end label output
563 // Start generating the item.
564 bool wasInParagraph = runparams.docbook_in_par;
565 openItemTag(xs, style);
566 bool getsIntoParagraph = openInnerItemTag(xs, style);
567 OutputParams rp = runparams;
568 rp.docbook_in_par = wasInParagraph | getsIntoParagraph;
570 // Maybe the item is completely empty, i.e. if the first word ends at the end of the current paragraph
571 // AND if the next paragraph doesn't have the same depth (if there is such a paragraph).
572 // Common case: there is only the first word on the line, but there is a nested list instead
574 bool emptyItem = false;
575 if (sep == par->size()) {
578 if (next_par == text.paragraphs().end()) // There is no next paragraph.
580 else // There is a next paragraph: check depth.
581 emptyItem = par->params().depth() >= next_par->params().depth();
585 // Avoid having an empty item, this is not valid DocBook. A single character is enough to force
586 // generation of a full <para>.
589 // Generate the rest of the paragraph, if need be.
590 par->simpleDocBookOnePar(buf, xs, rp, text.outerFont(distance(begin, par)), true, true, sep);
594 if (getsIntoParagraph)
595 closeInnerItemTag(xs, style);
597 // We may not want to close the tag yet, in particular:
598 // If we're not at the end of the item...
600 // and are doing items...
601 && !isNormalEnv(style)
602 // and if the depth has changed...
603 && par->params().depth() != origdepth) {
604 // then we'll save this layout for later, and close it when
605 // we get another item.
608 closeItemTag(xs, style);
610 // Eventually, close the item wrapper.
611 if (style.docbookitemwrappertag() != "NONE") {
612 xs << xml::EndTag(style.docbookitemwrappertag());
617 // The other possibility is that the depth has increased.
619 send = findEndOfEnvironment(par, pend);
620 par = makeEnvironment(buf, xs, runparams, text, par, send);
624 case LATEX_PARAGRAPH:
625 send = findLastParagraph(par, pend);
626 par = makeParagraphs(buf, xs, runparams, text, par, send);
628 case LATEX_BIB_ENVIRONMENT:
629 send = findLastParagraph(par, pend);
630 par = makeParagraphBibliography(buf, xs, runparams, text, par, send);
638 if (lastlay != nullptr) {
639 closeItemTag(xs, *lastlay);
640 if (lastlay->docbookitemwrappertag() != "NONE") {
641 xs << xml::EndTag(lastlay->docbookitemwrappertag());
645 closeTag(xs, bstyle);
654 OutputParams const & runparams,
656 ParagraphList::const_iterator const & pbegin)
658 Layout const &style = pbegin->layout();
660 // No need for labels, as they are handled by DocBook tags.
662 openParTag(xs, style);
664 ParagraphList::const_iterator const begin = text.paragraphs().begin();
665 pbegin->simpleDocBookOnePar(buf, xs, runparams,
666 text.outerFont(distance(begin, pbegin)));
671 pair<ParagraphList::const_iterator, ParagraphList::const_iterator> makeAny(
675 OutputParams const &ourparams,
676 ParagraphList::const_iterator par,
677 ParagraphList::const_iterator send,
678 ParagraphList::const_iterator pend)
680 Layout const & style = par->layout();
682 switch (style.latextype) {
683 case LATEX_COMMAND: {
684 // The files with which we are working never have more than
685 // one paragraph in a command structure.
687 // if (ourparams.docbook_in_par)
688 // fix it so we don't get sections inside standard, e.g.
689 // note that we may then need to make runparams not const, so we
690 // can communicate that back.
691 // FIXME Maybe this fix should be in the routines themselves, in case
692 // they are called from elsewhere.
693 makeCommand(buf, xs, ourparams, text, par);
697 case LATEX_ENVIRONMENT:
698 case LATEX_LIST_ENVIRONMENT:
699 case LATEX_ITEM_ENVIRONMENT: {
700 // FIXME Same fix here.
701 send = findEndOfEnvironment(par, pend);
702 par = makeEnvironment(buf, xs, ourparams, text, par, send);
705 case LATEX_BIB_ENVIRONMENT: {
706 send = findLastParagraph(par, pend);
707 par = makeParagraphBibliography(buf, xs, ourparams, text, par, send);
710 case LATEX_PARAGRAPH: {
711 send = findLastParagraph(par, pend);
712 par = makeParagraphs(buf, xs, ourparams, text, par, send);
717 return make_pair(par, send);
720 } // end anonymous namespace
723 using DocBookDocumentSectioning = tuple<bool, pit_type>;
724 using DocBookInfoTag = tuple<set<pit_type>, set<pit_type>, pit_type, pit_type>;
727 DocBookDocumentSectioning hasDocumentSectioning(ParagraphList const ¶graphs, pit_type bpit, pit_type const epit) {
728 bool documentHasSections = false;
730 while (bpit < epit) {
731 Layout const &style = paragraphs[bpit].layout();
732 documentHasSections |= style.category() == from_utf8("Sectioning");
734 if (documentHasSections) {
739 // Paragraphs before the first section: [ runparams.par_begin ; eppit )
741 return make_tuple(documentHasSections, bpit);
745 DocBookInfoTag getParagraphsWithInfo(ParagraphList const ¶graphs, pit_type const bpit, pit_type const epit) {
746 set<pit_type> shouldBeInInfo;
747 set<pit_type> mustBeInInfo;
749 pit_type cpit = bpit;
750 while (cpit < epit) {
751 // Skip paragraphs only containing one note.
752 Paragraph const &par = paragraphs[cpit];
753 if (par.size() == 1 && dynamic_cast<InsetNote*>(paragraphs[cpit].insetList().get(0))) {
758 // Based on layout information, store this paragraph in one set: should be in <info>, must be.
759 Layout const &style = par.layout();
761 if (style.docbookininfo() == "always") {
762 mustBeInInfo.emplace(cpit);
763 } else if (style.docbookininfo() == "maybe") {
764 shouldBeInInfo.emplace(cpit);
766 // Hypothesis: the <info> parts should be grouped together near the beginning bpit.
771 // Now, cpit points to the last paragraph that has things that could go in <info>.
772 // bpit is still the beginning of the <info> part.
774 return make_tuple(shouldBeInInfo, mustBeInInfo, bpit, cpit);
778 bool hasAbstractBetween(ParagraphList const ¶graphs, pit_type const bpitAbstract, pit_type const epitAbstract)
780 // Hypothesis: the paragraphs between bpitAbstract and epitAbstract can be considered an abstract because they
781 // are just after a document or part title.
782 if (epitAbstract - bpitAbstract <= 0)
785 // If there is something between these paragraphs, check if it's compatible with an abstract (i.e. some text).
786 pit_type bpit = bpitAbstract;
787 while (bpit < epitAbstract) {
788 const Paragraph &p = paragraphs.at(bpit);
790 if (p.layout().name() == from_ascii("Abstract"))
793 if (!p.insetList().empty()) {
794 for (const auto &i : p.insetList()) {
795 if (i.inset->getText(0) != nullptr) {
806 pit_type generateDocBookParagraphWithoutSectioning(
810 OutputParams const & runparams,
811 ParagraphList const & paragraphs,
815 auto par = paragraphs.iterator_at(bpit);
816 auto lastStartedPar = par;
817 ParagraphList::const_iterator send;
819 (epit == (int) paragraphs.size()) ?
820 paragraphs.end() : paragraphs.iterator_at(epit);
822 while (bpit < epit) {
823 tie(par, send) = makeAny(text, buf, xs, runparams, par, send, pend);
824 bpit += distance(lastStartedPar, par);
825 lastStartedPar = par;
832 void outputDocBookInfo(
836 OutputParams const & runparams,
837 ParagraphList const & paragraphs,
838 DocBookInfoTag const & info,
839 pit_type bpitAbstract,
840 pit_type const epitAbstract)
842 // Consider everything between bpitAbstract and epitAbstract (excluded) as paragraphs for the abstract.
843 // Use bpitAbstract >= epitAbstract to indicate there is no abstract.
845 set<pit_type> shouldBeInInfo;
846 set<pit_type> mustBeInInfo;
849 tie(shouldBeInInfo, mustBeInInfo, bpitInfo, epitInfo) = info;
851 // The abstract must go in <info>.
852 bool hasAbstract = hasAbstractBetween(paragraphs, bpitAbstract, epitAbstract);
853 bool needInfo = !mustBeInInfo.empty() || hasAbstract;
855 // Start the <info> tag if required.
857 xs.startDivision(false);
858 xs << xml::StartTag("info");
862 // Output the elements that should go in <info>.
863 generateDocBookParagraphWithoutSectioning(text, buf, xs, runparams, paragraphs, bpitInfo, epitInfo);
866 // Sometimes, there are many paragraphs that should go into the abstract, but none generates actual content.
867 // Thus, first generate to a temporary stream, then only create the <abstract> tag if these paragraphs
868 // generate some content.
869 odocstringstream os2;
871 generateDocBookParagraphWithoutSectioning(text, buf, xs2, runparams, paragraphs, bpitAbstract, epitAbstract);
873 // Actually output the abstract if there is something to do.
874 if (!os2.str().empty()) {
875 string tag = paragraphs[bpitAbstract].layout().docbookforceabstracttag();
879 xs << xml::StartTag(tag);
881 xs << XMLStream::ESCAPE_NONE << os2.str();
882 xs << xml::EndTag(tag);
887 // End the <info> tag if it was started.
889 xs << xml::EndTag("info");
896 void docbookFirstParagraphs(
900 OutputParams const &runparams,
903 // Handle the beginning of the document, supposing it has sections.
904 // Major role: output the first <info> tag.
906 ParagraphList const ¶graphs = text.paragraphs();
907 pit_type bpit = runparams.par_begin;
908 DocBookInfoTag info = getParagraphsWithInfo(paragraphs, bpit, epit);
909 outputDocBookInfo(text, buf, xs, runparams, paragraphs, info, get<3>(info), epit);
913 bool isParagraphEmpty(const Paragraph &par)
915 InsetList const &insets = par.insetList();
916 size_t insetsLength = distance(insets.begin(), insets.end());
917 bool hasParagraphOnlyNote = insetsLength == 1 && insets.get(0) && insets.get(0)->asInsetCollapsible() &&
918 dynamic_cast<InsetNote *>(insets.get(0));
919 return hasParagraphOnlyNote;
923 void docbookSimpleAllParagraphs(
927 OutputParams const & runparams)
929 // Handle the document, supposing it has no sections (i.e. a "simple" document).
931 // First, the <info> tag.
932 ParagraphList const ¶graphs = text.paragraphs();
933 pit_type bpit = runparams.par_begin;
934 pit_type const epit = runparams.par_end;
935 DocBookInfoTag info = getParagraphsWithInfo(paragraphs, bpit, epit);
936 outputDocBookInfo(text, buf, xs, runparams, paragraphs, info, 0, 0);
937 bpit = get<3>(info); // Generate the content starting from the end of the <info> part.
939 // Then, the content.
940 ParagraphList::const_iterator const pend =
941 (epit == (int) paragraphs.size()) ?
942 paragraphs.end() : paragraphs.iterator_at(epit);
944 while (bpit < epit) {
945 auto par = paragraphs.iterator_at(bpit);
946 ParagraphList::const_iterator const lastStartedPar = par;
947 ParagraphList::const_iterator send;
949 if (isParagraphEmpty(*par)) {
951 bpit += distance(lastStartedPar, par);
955 // Generate this paragraph.
956 tie(par, send) = makeAny(text, buf, xs, runparams, par, send, pend);
957 bpit += distance(lastStartedPar, par);
962 void docbookParagraphs(Text const &text,
965 OutputParams const &runparams) {
966 ParagraphList const ¶graphs = text.paragraphs();
967 if (runparams.par_begin == runparams.par_end) {
968 runparams.par_begin = 0;
969 runparams.par_end = paragraphs.size();
971 pit_type bpit = runparams.par_begin;
972 pit_type const epit = runparams.par_end;
975 xs << XMLStream::ESCAPE_NONE << "<!-- DocBook output error! -->\n";
979 ParagraphList::const_iterator const pend =
980 (epit == (int) paragraphs.size()) ?
981 paragraphs.end() : paragraphs.iterator_at(epit);
982 std::stack<std::pair<int, string>> headerLevels; // Used to determine when to open/close sections: store the depth
983 // of the section and the tag that was used to open it.
985 // Detect whether the document contains sections. If there are no sections, there can be no automatically
986 // discovered abstract.
987 bool documentHasSections;
989 tie(documentHasSections, eppit) = hasDocumentSectioning(paragraphs, bpit, epit);
991 if (documentHasSections) {
992 docbookFirstParagraphs(text, buf, xs, runparams, eppit);
995 docbookSimpleAllParagraphs(text, buf, xs, runparams);
999 bool currentlyInAppendix = false;
1001 while (bpit < epit) {
1002 OutputParams ourparams = runparams;
1004 auto par = paragraphs.iterator_at(bpit);
1005 if (par->params().startOfAppendix())
1006 currentlyInAppendix = true;
1007 Layout const &style = par->layout();
1008 ParagraphList::const_iterator const lastStartedPar = par;
1009 ParagraphList::const_iterator send;
1011 if (isParagraphEmpty(*par)) {
1013 bpit += distance(lastStartedPar, par);
1017 // Think about adding <section> and/or </section>s.
1018 const bool isLayoutSectioning = style.category() == from_utf8("Sectioning");
1019 if (isLayoutSectioning) {
1020 int level = style.toclevel;
1022 // Need to close a previous section if it has the same level or a higher one (close <section> if opening a <h2>
1023 // after a <h2>, <h3>, <h4>, <h5> or <h6>). More examples:
1024 // - current: h2; back: h1; do not close any <section>
1025 // - current: h1; back: h2; close two <section> (first the <h2>, then the <h1>, so a new <h1> can come)
1026 while (!headerLevels.empty() && level <= headerLevels.top().first) {
1027 int stackLevel = headerLevels.top().first;
1028 docstring stackTag = from_utf8("</" + headerLevels.top().second + ">");
1031 // Output the tag only if it corresponds to a legit section.
1032 if (stackLevel != Layout::NOT_IN_TOC)
1033 xs << XMLStream::ESCAPE_NONE << stackTag << xml::CR();
1036 // Open the new section: first push it onto the stack, then output it in DocBook.
1037 string sectionTag = (currentlyInAppendix && style.docbooksectiontag() == "chapter") ?
1038 "appendix" : style.docbooksectiontag();
1039 headerLevels.push(std::make_pair(level, sectionTag));
1041 // Some sectioning-like elements should not be output (such as FrontMatter).
1042 if (level != Layout::NOT_IN_TOC) {
1043 // Look for a label in the title, i.e. a InsetLabel as a child.
1044 docstring id = docstring();
1045 for (pos_type i = 0; i < par->size(); ++i) {
1046 Inset const *inset = par->getInset(i);
1048 if (auto label = dynamic_cast<InsetLabel const *>(inset)) {
1049 // Generate the attributes for the section if need be.
1050 id += "xml:id=\"" + xml::cleanID(label->screenLabel()) + "\"";
1052 // Don't output the ID as a DocBook <anchor>.
1053 ourparams.docbook_anchors_to_ignore.emplace(label->screenLabel());
1055 // Cannot have multiple IDs per tag.
1061 // Write the open tag for this section.
1062 docstring tag = from_utf8("<" + sectionTag);
1064 tag += from_utf8(" ") + id;
1065 tag += from_utf8(">");
1066 xs << XMLStream::ESCAPE_NONE << tag;
1071 // Close all sections before the bibliography.
1072 // 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)?
1073 auto insetsLength = distance(par->insetList().begin(), par->insetList().end());
1074 if (insetsLength > 0) {
1075 Inset const *firstInset = par->getInset(0);
1076 if (firstInset && dynamic_cast<InsetBibtex const *>(firstInset)) {
1077 while (!headerLevels.empty()) {
1078 int level = headerLevels.top().first;
1079 docstring tag = from_utf8("</" + headerLevels.top().second + ">");
1082 // Output the tag only if it corresponds to a legit section.
1083 if (level != Layout::NOT_IN_TOC) {
1084 xs << XMLStream::ESCAPE_NONE << tag;
1091 // Generate this paragraph.
1092 tie(par, send) = makeAny(text, buf, xs, ourparams, par, send, pend);
1093 bpit += distance(lastStartedPar, par);
1096 // If need be, close <section>s, but only at the end of the document (otherwise, dealt with at the beginning
1098 while (!headerLevels.empty() && headerLevels.top().first > Layout::NOT_IN_TOC) {
1099 docstring tag = from_utf8("</" + headerLevels.top().second + ">");
1101 xs << XMLStream::ESCAPE_NONE << tag;