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/InsetLabel.h"
30 #include "insets/InsetNote.h"
32 #include "support/convert.h"
33 #include "support/debug.h"
34 #include "support/lassert.h"
35 #include "support/lstrings.h"
36 #include "support/textutils.h"
43 using namespace lyx::support;
49 std::string const fontToDocBookTag(xml::FontTypes type)
52 case xml::FontTypes::FT_EMPH:
53 case xml::FontTypes::FT_BOLD:
55 case xml::FontTypes::FT_NOUN:
57 case xml::FontTypes::FT_UBAR:
58 case xml::FontTypes::FT_WAVE:
59 case xml::FontTypes::FT_DBAR:
60 case xml::FontTypes::FT_SOUT:
61 case xml::FontTypes::FT_XOUT:
62 case xml::FontTypes::FT_ITALIC:
63 case xml::FontTypes::FT_UPRIGHT:
64 case xml::FontTypes::FT_SLANTED:
65 case xml::FontTypes::FT_SMALLCAPS:
66 case xml::FontTypes::FT_ROMAN:
67 case xml::FontTypes::FT_SANS:
69 case xml::FontTypes::FT_TYPE:
71 case xml::FontTypes::FT_SIZE_TINY:
72 case xml::FontTypes::FT_SIZE_SCRIPT:
73 case xml::FontTypes::FT_SIZE_FOOTNOTE:
74 case xml::FontTypes::FT_SIZE_SMALL:
75 case xml::FontTypes::FT_SIZE_NORMAL:
76 case xml::FontTypes::FT_SIZE_LARGE:
77 case xml::FontTypes::FT_SIZE_LARGER:
78 case xml::FontTypes::FT_SIZE_LARGEST:
79 case xml::FontTypes::FT_SIZE_HUGE:
80 case xml::FontTypes::FT_SIZE_HUGER:
81 case xml::FontTypes::FT_SIZE_INCREASE:
82 case xml::FontTypes::FT_SIZE_DECREASE:
89 string fontToRole(xml::FontTypes type)
91 // Specific fonts are achieved with roles. The only common ones are "" for basic emphasis,
92 // and "bold"/"strong" for bold. With some specific options, other roles are copied into
93 // HTML output (via the DocBook XSLT sheets); otherwise, if not recognised, they are just ignored.
94 // Hence, it is not a problem to have many roles by default here.
95 // See https://www.sourceware.org/ml/docbook/2003-05/msg00269.html
97 case xml::FontTypes::FT_ITALIC:
98 case xml::FontTypes::FT_EMPH:
100 case xml::FontTypes::FT_BOLD:
102 case xml::FontTypes::FT_NOUN:
103 return ""; // Outputs a <person>
104 case xml::FontTypes::FT_TYPE:
105 return ""; // Outputs a <code>
106 case xml::FontTypes::FT_UBAR:
109 // All other roles are non-standard for DocBook.
111 case xml::FontTypes::FT_WAVE:
113 case xml::FontTypes::FT_DBAR:
115 case xml::FontTypes::FT_SOUT:
117 case xml::FontTypes::FT_XOUT:
119 case xml::FontTypes::FT_UPRIGHT:
121 case xml::FontTypes::FT_SLANTED:
123 case xml::FontTypes::FT_SMALLCAPS:
125 case xml::FontTypes::FT_ROMAN:
127 case xml::FontTypes::FT_SANS:
129 case xml::FontTypes::FT_SIZE_TINY:
131 case xml::FontTypes::FT_SIZE_SCRIPT:
132 return "size_script";
133 case xml::FontTypes::FT_SIZE_FOOTNOTE:
134 return "size_footnote";
135 case xml::FontTypes::FT_SIZE_SMALL:
137 case xml::FontTypes::FT_SIZE_NORMAL:
138 return "size_normal";
139 case xml::FontTypes::FT_SIZE_LARGE:
141 case xml::FontTypes::FT_SIZE_LARGER:
142 return "size_larger";
143 case xml::FontTypes::FT_SIZE_LARGEST:
144 return "size_largest";
145 case xml::FontTypes::FT_SIZE_HUGE:
147 case xml::FontTypes::FT_SIZE_HUGER:
149 case xml::FontTypes::FT_SIZE_INCREASE:
150 return "size_increase";
151 case xml::FontTypes::FT_SIZE_DECREASE:
152 return "size_decrease";
158 string fontToAttribute(xml::FontTypes type) {
159 // If there is a role (i.e. nonstandard use of a tag), output the attribute. Otherwise, the sheer tag is sufficient
161 string role = fontToRole(type);
163 return "role='" + role + "'";
169 } // end anonymous namespace
172 xml::FontTag docbookStartFontTag(xml::FontTypes type)
174 return xml::FontTag(from_utf8(fontToDocBookTag(type)), from_utf8(fontToAttribute(type)), type);
178 xml::EndFontTag docbookEndFontTag(xml::FontTypes type)
180 return xml::EndFontTag(from_utf8(fontToDocBookTag(type)), type);
186 // convenience functions
188 void openParTag(XMLStream &xs, Layout const &lay)
190 if (lay.docbookwrappertag() != "NONE") {
191 xs << xml::StartTag(lay.docbookwrappertag(), lay.docbookwrapperattr());
194 string tag = lay.docbooktag();
195 if (tag == "Plain Layout")
198 xs << xml::ParTag(tag, lay.docbookattr());
202 void closeTag(XMLStream &xs, Layout const &lay)
204 string tag = lay.docbooktag();
205 if (tag == "Plain Layout")
208 xs << xml::EndTag(tag);
209 if (lay.docbookwrappertag() != "NONE")
210 xs << xml::EndTag(lay.docbookwrappertag());
214 void openLabelTag(XMLStream & xs, Layout const & lay) // Mostly for definition lists.
216 xs << xml::StartTag(lay.docbookitemlabeltag(), lay.docbookitemlabelattr());
220 void closeLabelTag(XMLStream & xs, Layout const & lay)
222 xs << xml::EndTag(lay.docbookitemlabeltag());
227 void openItemTag(XMLStream &xs, Layout const &lay)
229 xs << xml::StartTag(lay.docbookitemtag(), lay.docbookitemattr());
233 // Return true when new elements are output in a paragraph, false otherwise.
234 bool openInnerItemTag(XMLStream &xs, Layout const &lay)
236 if (lay.docbookiteminnertag() != "NONE") {
238 xs << xml::ParTag(lay.docbookiteminnertag(), lay.docbookiteminnerattr());
240 if (lay.docbookiteminnertag() == "para") {
248 void closeInnerItemTag(XMLStream &xs, Layout const &lay)
250 if (lay.docbookiteminnertag()!= "NONE") {
251 xs << xml::EndTag(lay.docbookiteminnertag());
257 inline void closeItemTag(XMLStream &xs, Layout const &lay)
259 xs << xml::EndTag(lay.docbookitemtag()) << xml::CR();
262 // end of convenience functions
264 ParagraphList::const_iterator findLastParagraph(
265 ParagraphList::const_iterator p,
266 ParagraphList::const_iterator const & pend) {
267 for (++p; p != pend && p->layout().latextype == LATEX_PARAGRAPH; ++p);
273 ParagraphList::const_iterator findEndOfEnvironment(
274 ParagraphList::const_iterator const & pstart,
275 ParagraphList::const_iterator const & pend)
277 ParagraphList::const_iterator p = pstart;
278 Layout const &bstyle = p->layout();
279 size_t const depth = p->params().depth();
280 for (++p; p != pend; ++p) {
281 Layout const &style = p->layout();
282 // It shouldn't happen that e.g. a section command occurs inside
283 // a quotation environment, at a higher depth, but as of 6/2009,
284 // it can happen. We pretend that it's just at lowest depth.
285 if (style.latextype == LATEX_COMMAND)
288 // If depth is down, we're done
289 if (p->params().depth() < depth)
292 // If depth is up, we're not done
293 if (p->params().depth() > depth)
296 // FIXME I am not sure about the first check.
297 // Surely we *could* have different layouts that count as
298 // LATEX_PARAGRAPH, right?
299 if (style.latextype == LATEX_PARAGRAPH || style != bstyle)
306 ParagraphList::const_iterator makeParagraphs(
309 OutputParams const &runparams,
311 ParagraphList::const_iterator const & pbegin,
312 ParagraphList::const_iterator const & pend)
314 ParagraphList::const_iterator const begin = text.paragraphs().begin();
315 ParagraphList::const_iterator par = pbegin;
316 for (; par != pend; ++par) {
317 Layout const &lay = par->layout();
319 // We want to open the paragraph tag if:
320 // (i) the current layout permits multiple paragraphs
321 // (ii) we are either not already inside a paragraph (HTMLIsBlock) OR
322 // we are, but this is not the first paragraph
324 // But there is also a special case, and we first see whether we are in it.
325 // We do not want to open the paragraph tag if this paragraph contains
326 // only one item, and that item is "inline", i.e., not HTMLIsBlock (such
327 // as a branch). On the other hand, if that single item has a font change
328 // applied to it, then we still do need to open the paragraph.
330 // Obviously, this is very fragile. The main reason we need to do this is
331 // because of branches, e.g., a branch that contains an entire new section.
332 // We do not really want to wrap that whole thing in a <div>...</div>.
333 bool special_case = false;
334 Inset const *specinset = par->size() == 1 ? par->getInset(0) : 0;
335 if (specinset && !specinset->getLayout().htmlisblock()) { // TODO: Convert htmlisblock to a DocBook parameter?
336 Layout const &style = par->layout();
337 FontInfo const first_font = style.labeltype == LABEL_MANUAL ?
338 style.labelfont : style.font;
339 FontInfo const our_font =
340 par->getFont(buf.masterBuffer()->params(), 0,
341 text.outerFont(distance(begin, par))).fontInfo();
343 if (first_font == our_font)
347 // Plain layouts must be ignored.
348 if (!special_case && buf.params().documentClass().isPlainLayout(lay) && !runparams.docbook_force_pars)
350 // TODO: Could get rid of this with a DocBook equivalent to htmlisblock?
351 if (!special_case && par->size() == 1 && par->getInset(0)) {
352 Inset const * firstInset = par->getInset(0);
354 // Floats cannot be in paragraphs.
355 special_case = to_ascii(firstInset->layoutName()).substr(0, 6) == "Float:";
357 // Bibliographies cannot be in paragraphs.
358 if (!special_case && firstInset->asInsetCommand())
359 special_case = firstInset->asInsetCommand()->params().getCmdName() == "bibtex";
361 // Equations do not deserve their own paragraph (DocBook allows them outside paragraphs).
362 if (!special_case && firstInset->asInsetMath())
365 // ERTs are in comments, not paragraphs.
366 if (!special_case && firstInset->lyxCode() == lyx::ERT_CODE)
369 // Listings should not get into their own paragraph.
370 if (!special_case && firstInset->lyxCode() == lyx::LISTINGS_CODE)
374 bool const open_par = runparams.docbook_make_pars
375 && (!runparams.docbook_in_par || par != pbegin)
378 // We want to issue the closing tag if either:
379 // (i) We opened it, and either docbook_in_par is false,
380 // or we're not in the last paragraph, anyway.
381 // (ii) We didn't open it and docbook_in_par is true,
382 // but we are in the first par, and there is a next par.
383 ParagraphList::const_iterator nextpar = par;
385 bool const close_par =
386 ((open_par && (!runparams.docbook_in_par || nextpar != pend))
387 || (!open_par && runparams.docbook_in_par && par == pbegin && nextpar != pend));
393 par->simpleDocBookOnePar(buf, xs, runparams, text.outerFont(distance(begin, par)), open_par, close_par, 0);
404 bool isNormalEnv(Layout const &lay)
406 return lay.latextype == LATEX_ENVIRONMENT
407 || lay.latextype == LATEX_BIB_ENVIRONMENT;
411 ParagraphList::const_iterator makeEnvironment(
414 OutputParams const &runparams,
416 ParagraphList::const_iterator const & pbegin,
417 ParagraphList::const_iterator const & pend)
419 ParagraphList::const_iterator const begin = text.paragraphs().begin();
420 ParagraphList::const_iterator par = pbegin;
421 Layout const &bstyle = par->layout();
422 depth_type const origdepth = pbegin->params().depth();
424 // open tag for this environment
425 openParTag(xs, bstyle);
428 // we will on occasion need to remember a layout from before.
429 Layout const *lastlay = nullptr;
431 while (par != pend) {
432 Layout const & style = par->layout();
433 ParagraphList::const_iterator send;
435 // Actual content of this paragraph.
436 switch (style.latextype) {
437 case LATEX_ENVIRONMENT:
438 case LATEX_LIST_ENVIRONMENT:
439 case LATEX_ITEM_ENVIRONMENT: {
440 // There are two possibilities in this case.
441 // One is that we are still in the environment in which we
442 // started---which we will be if the depth is the same.
443 if (par->params().depth() == origdepth) {
444 LATTEST(bstyle == style);
445 if (lastlay != nullptr) {
446 closeItemTag(xs, *lastlay);
450 // this will be positive, if we want to skip the
451 // initial word (if it's been taken for the label).
454 // Open a wrapper tag if needed.
455 if (style.docbookitemwrappertag() != "NONE") {
456 xs << xml::StartTag(style.docbookitemwrappertag(), style.docbookitemwrapperattr());
461 if (style.labeltype != LABEL_NO_LABEL &&
462 style.docbookitemlabeltag() != "NONE") {
464 if (isNormalEnv(style)) {
465 // in this case, we print the label only for the first
466 // paragraph (as in a theorem or an abstract).
468 docstring const lbl = pbegin->params().labelString();
470 openLabelTag(xs, style);
472 closeLabelTag(xs, style);
476 } else { // some kind of list
477 if (style.labeltype == LABEL_MANUAL) {
478 // Only variablelist gets here.
480 openLabelTag(xs, style);
481 sep = par->firstWordDocBook(xs, runparams);
482 closeLabelTag(xs, style);
485 openLabelTag(xs, style);
486 xs << par->params().labelString();
487 closeLabelTag(xs, style);
491 } // end label output
493 bool wasInParagraph = runparams.docbook_in_par;
494 openItemTag(xs, style);
495 bool getsIntoParagraph = openInnerItemTag(xs, style);
496 OutputParams rp = runparams;
497 rp.docbook_in_par = wasInParagraph | getsIntoParagraph;
499 par->simpleDocBookOnePar(buf, xs, rp, text.outerFont(distance(begin, par)), true, true, sep);
501 if (getsIntoParagraph)
502 closeInnerItemTag(xs, style);
504 // We may not want to close the tag yet, in particular:
505 // If we're not at the end of the item...
507 // and are doing items...
508 && !isNormalEnv(style)
509 // and if the depth has changed...
510 && par->params().depth() != origdepth) {
511 // then we'll save this layout for later, and close it when
512 // we get another item.
515 closeItemTag(xs, style);
517 // Eventually, close the item wrapper.
518 if (style.docbookitemwrappertag() != "NONE") {
519 xs << xml::EndTag(style.docbookitemwrappertag());
524 // The other possibility is that the depth has increased, in which
525 // case we need to recurse.
527 send = findEndOfEnvironment(par, pend);
528 par = makeEnvironment(buf, xs, runparams, text, par, send);
532 case LATEX_PARAGRAPH:
533 send = findLastParagraph(par, pend);
534 par = makeParagraphs(buf, xs, runparams, text, par, send);
536 case LATEX_BIB_ENVIRONMENT:
537 // Handled in InsetBibtex.
546 closeItemTag(xs, *lastlay);
547 closeTag(xs, bstyle);
556 OutputParams const & runparams,
558 ParagraphList::const_iterator const & pbegin)
560 Layout const &style = pbegin->layout();
562 // No need for labels, as they are handled by DocBook tags.
564 openParTag(xs, style);
566 ParagraphList::const_iterator const begin = text.paragraphs().begin();
567 pbegin->simpleDocBookOnePar(buf, xs, runparams,
568 text.outerFont(distance(begin, pbegin)));
573 pair<ParagraphList::const_iterator, ParagraphList::const_iterator> makeAny(
577 OutputParams const &ourparams,
578 ParagraphList::const_iterator par,
579 ParagraphList::const_iterator send,
580 ParagraphList::const_iterator pend)
582 Layout const & style = par->layout();
584 switch (style.latextype) {
585 case LATEX_COMMAND: {
586 // The files with which we are working never have more than
587 // one paragraph in a command structure.
589 // if (ourparams.docbook_in_par)
590 // fix it so we don't get sections inside standard, e.g.
591 // note that we may then need to make runparams not const, so we
592 // can communicate that back.
593 // FIXME Maybe this fix should be in the routines themselves, in case
594 // they are called from elsewhere.
595 makeCommand(buf, xs, ourparams, text, par);
599 case LATEX_ENVIRONMENT:
600 case LATEX_LIST_ENVIRONMENT:
601 case LATEX_ITEM_ENVIRONMENT: {
602 // FIXME Same fix here.
603 send = findEndOfEnvironment(par, pend);
604 par = makeEnvironment(buf, xs, ourparams, text, par, send);
607 case LATEX_BIB_ENVIRONMENT: {
608 // Handled in InsetBibtex.
611 case LATEX_PARAGRAPH: {
612 send = findLastParagraph(par, pend);
613 par = makeParagraphs(buf, xs, ourparams, text, par, send);
618 return make_pair(par, send);
621 } // end anonymous namespace
624 using DocBookDocumentSectioning = tuple<bool, pit_type>;
625 using DocBookInfoTag = tuple<set<pit_type>, set<pit_type>, pit_type, pit_type>;
628 DocBookDocumentSectioning hasDocumentSectioning(ParagraphList const ¶graphs, pit_type bpit, pit_type const epit) {
629 bool documentHasSections = false;
631 while (bpit < epit) {
632 Layout const &style = paragraphs[bpit].layout();
633 documentHasSections |= style.category() == from_utf8("Sectioning");
635 if (documentHasSections) {
640 // Paragraphs before the first section: [ runparams.par_begin ; eppit )
642 return make_tuple(documentHasSections, bpit);
646 DocBookInfoTag getParagraphsWithInfo(ParagraphList const ¶graphs, pit_type const bpit, pit_type const epit) {
647 set<pit_type> shouldBeInInfo;
648 set<pit_type> mustBeInInfo;
650 pit_type cpit = bpit;
651 while (cpit < epit) {
652 // Skip paragraphs only containing one note.
653 Paragraph const &par = paragraphs[cpit];
654 if (par.size() == 1 && dynamic_cast<InsetNote*>(paragraphs[cpit].insetList().get(0))) {
659 // Based on layout information, store this paragraph in one set: should be in <info>, must be.
660 Layout const &style = par.layout();
661 if (style.docbookininfo() == "always") {
662 mustBeInInfo.emplace(cpit);
663 } else if (style.docbookininfo() == "maybe") {
664 shouldBeInInfo.emplace(cpit);
666 // Hypothesis: the <info> parts should be grouped together near the beginning bpit.
671 // Now, cpit points to the last paragraph that has things that could go in <info>.
672 // bpit is still the beginning of the <info> part.
674 return make_tuple(shouldBeInInfo, mustBeInInfo, bpit, cpit);
678 bool hasAbstractBetween(ParagraphList const ¶graphs, pit_type const bpitAbstract, pit_type const epitAbstract)
680 // Hypothesis: the paragraphs between bpitAbstract and epitAbstract can be considered an abstract because they
681 // are just after a document or part title.
682 if (epitAbstract - bpitAbstract <= 0)
685 // If there is something between these paragraphs, check if it's compatible with an abstract (i.e. some text).
686 pit_type bpit = bpitAbstract;
687 while (bpit < epitAbstract) {
688 const Paragraph &p = paragraphs.at(bpit);
690 if (p.layout().name() == from_ascii("Abstract"))
693 if (!p.insetList().empty()) {
694 for (const auto &i : p.insetList()) {
695 if (i.inset->getText(0) != nullptr) {
706 pit_type generateDocBookParagraphWithoutSectioning(
710 OutputParams const & runparams,
711 ParagraphList const & paragraphs,
715 auto par = paragraphs.iterator_at(bpit);
716 auto lastStartedPar = par;
717 ParagraphList::const_iterator send;
719 (epit == (int) paragraphs.size()) ?
720 paragraphs.end() : paragraphs.iterator_at(epit);
722 while (bpit < epit) {
723 tie(par, send) = makeAny(text, buf, xs, runparams, par, send, pend);
724 bpit += distance(lastStartedPar, par);
731 void outputDocBookInfo(
735 OutputParams const & runparams,
736 ParagraphList const & paragraphs,
737 DocBookInfoTag const & info,
738 pit_type bpitAbstract,
739 pit_type const epitAbstract)
741 // Consider everything between bpitAbstract and epitAbstract (excluded) as paragraphs for the abstract.
742 // Use bpitAbstract >= epitAbstract to indicate there is no abstract.
744 set<pit_type> shouldBeInInfo;
745 set<pit_type> mustBeInInfo;
748 tie(shouldBeInInfo, mustBeInInfo, bpitInfo, epitInfo) = info;
750 // The abstract must go in <info>.
751 bool hasAbstract = hasAbstractBetween(paragraphs, bpitAbstract, epitAbstract);
752 bool needInfo = !mustBeInInfo.empty() || hasAbstract;
754 // Start the <info> tag if required.
756 xs.startDivision(false);
757 xs << xml::StartTag("info");
761 // Output the elements that should go in <info>.
762 generateDocBookParagraphWithoutSectioning(text, buf, xs, runparams, paragraphs, bpitInfo, epitInfo);
765 string tag = paragraphs[bpitAbstract].layout().docbookforceabstracttag();
769 xs << xml::StartTag(tag);
771 xs.startDivision(false);
772 generateDocBookParagraphWithoutSectioning(text, buf, xs, runparams, paragraphs, bpitAbstract, epitAbstract);
774 xs << xml::EndTag(tag);
778 // End the <info> tag if it was started.
780 xs << xml::EndTag("info");
787 void docbookFirstParagraphs(
791 OutputParams const &runparams,
794 // Handle the beginning of the document, supposing it has sections.
795 // Major role: output the first <info> tag.
797 ParagraphList const ¶graphs = text.paragraphs();
798 pit_type bpit = runparams.par_begin;
799 DocBookInfoTag info = getParagraphsWithInfo(paragraphs, bpit, epit);
800 outputDocBookInfo(text, buf, xs, runparams, paragraphs, info, get<3>(info), epit);
804 bool isParagraphEmpty(const Paragraph &par)
806 InsetList const &insets = par.insetList();
807 size_t insetsLength = distance(insets.begin(), insets.end());
808 bool hasParagraphOnlyNote = insetsLength == 1 && insets.get(0) && insets.get(0)->asInsetCollapsible() &&
809 dynamic_cast<InsetNote *>(insets.get(0));
810 return hasParagraphOnlyNote;
814 void docbookSimpleAllParagraphs(
818 OutputParams const & runparams)
820 // Handle the document, supposing it has no sections (i.e. a "simple" document).
822 // First, the <info> tag.
823 ParagraphList const ¶graphs = text.paragraphs();
824 pit_type bpit = runparams.par_begin;
825 pit_type const epit = runparams.par_end;
826 DocBookInfoTag info = getParagraphsWithInfo(paragraphs, bpit, epit);
827 outputDocBookInfo(text, buf, xs, runparams, paragraphs, info, 0, 0);
828 bpit = get<3>(info); // Generate the content starting from the end of the <info> part.
830 // Then, the content.
831 ParagraphList::const_iterator const pend =
832 (epit == (int) paragraphs.size()) ?
833 paragraphs.end() : paragraphs.iterator_at(epit);
835 while (bpit < epit) {
836 auto par = paragraphs.iterator_at(bpit);
837 ParagraphList::const_iterator const lastStartedPar = par;
838 ParagraphList::const_iterator send;
840 if (isParagraphEmpty(*par)) {
842 bpit += distance(lastStartedPar, par);
846 // Generate this paragraph.
847 tie(par, send) = makeAny(text, buf, xs, runparams, par, send, pend);
848 bpit += distance(lastStartedPar, par);
853 void docbookParagraphs(Text const &text,
856 OutputParams const &runparams) {
857 ParagraphList const ¶graphs = text.paragraphs();
858 if (runparams.par_begin == runparams.par_end) {
859 runparams.par_begin = 0;
860 runparams.par_end = paragraphs.size();
862 pit_type bpit = runparams.par_begin;
863 pit_type const epit = runparams.par_end;
866 xs << XMLStream::ESCAPE_NONE << "<!-- DocBook output error! -->\n";
870 ParagraphList::const_iterator const pend =
871 (epit == (int) paragraphs.size()) ?
872 paragraphs.end() : paragraphs.iterator_at(epit);
873 std::stack<std::pair<int, string>> headerLevels; // Used to determine when to open/close sections: store the depth
874 // of the section and the tag that was used to open it.
876 // Detect whether the document contains sections. If there are no sections, there can be no automatically
877 // discovered abstract.
878 bool documentHasSections;
880 tie(documentHasSections, eppit) = hasDocumentSectioning(paragraphs, bpit, epit);
882 if (documentHasSections) {
883 docbookFirstParagraphs(text, buf, xs, runparams, eppit);
886 docbookSimpleAllParagraphs(text, buf, xs, runparams);
890 bool currentlyInAppendix = false;
892 while (bpit < epit) {
893 OutputParams ourparams = runparams;
895 auto par = paragraphs.iterator_at(bpit);
896 if (par->params().startOfAppendix())
897 currentlyInAppendix = true;
898 Layout const &style = par->layout();
899 ParagraphList::const_iterator const lastStartedPar = par;
900 ParagraphList::const_iterator send;
902 if (isParagraphEmpty(*par)) {
904 bpit += distance(lastStartedPar, par);
908 // Think about adding <section> and/or </section>s.
909 const bool isLayoutSectioning = style.category() == from_utf8("Sectioning");
910 if (isLayoutSectioning) {
911 int level = style.toclevel;
913 // Need to close a previous section if it has the same level or a higher one (close <section> if opening a <h2>
914 // after a <h2>, <h3>, <h4>, <h5> or <h6>). More examples:
915 // - current: h2; back: h1; do not close any <section>
916 // - current: h1; back: h2; close two <section> (first the <h2>, then the <h1>, so a new <h1> can come)
917 while (!headerLevels.empty() && level <= headerLevels.top().first) {
918 int stackLevel = headerLevels.top().first;
919 docstring stackTag = from_utf8("</" + headerLevels.top().second + ">");
922 // Output the tag only if it corresponds to a legit section.
923 if (stackLevel != Layout::NOT_IN_TOC)
924 xs << XMLStream::ESCAPE_NONE << stackTag << xml::CR();
927 // Open the new section: first push it onto the stack, then output it in DocBook.
928 string sectionTag = (currentlyInAppendix && style.docbooksectiontag() == "chapter") ?
929 "appendix" : style.docbooksectiontag();
930 headerLevels.push(std::make_pair(level, sectionTag));
932 // Some sectioning-like elements should not be output (such as FrontMatter).
933 if (level != Layout::NOT_IN_TOC) {
934 // Look for a label in the title, i.e. a InsetLabel as a child.
935 docstring id = docstring();
936 for (pos_type i = 0; i < par->size(); ++i) {
937 Inset const *inset = par->getInset(i);
939 if (auto label = dynamic_cast<InsetLabel const *>(inset)) {
940 // Generate the attributes for the section if need be.
941 id += "xml:id=\"" + xml::cleanID(label->screenLabel()) + "\"";
943 // Don't output the ID as a DocBook <anchor>.
944 ourparams.docbook_anchors_to_ignore.emplace(label->screenLabel());
946 // Cannot have multiple IDs per tag.
952 // Write the open tag for this section.
953 docstring tag = from_utf8("<" + sectionTag);
955 tag += from_utf8(" ") + id;
956 tag += from_utf8(">");
957 xs << XMLStream::ESCAPE_NONE << tag;
962 // Close all sections before the bibliography.
963 // 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)?
964 auto insetsLength = distance(par->insetList().begin(), par->insetList().end());
965 if (insetsLength > 0) {
966 Inset const *firstInset = par->getInset(0);
967 if (firstInset && dynamic_cast<InsetBibtex const *>(firstInset)) {
968 while (!headerLevels.empty()) {
969 int level = headerLevels.top().first;
970 docstring tag = from_utf8("</" + headerLevels.top().second + ">");
973 // Output the tag only if it corresponds to a legit section.
974 if (level != Layout::NOT_IN_TOC) {
975 xs << XMLStream::ESCAPE_NONE << tag;
982 // Generate this paragraph.
983 tie(par, send) = makeAny(text, buf, xs, ourparams, par, send, pend);
984 bpit += distance(lastStartedPar, par);
987 // If need be, close <section>s, but only at the end of the document (otherwise, dealt with at the beginning
989 while (!headerLevels.empty() && headerLevels.top().first > Layout::NOT_IN_TOC) {
990 docstring tag = from_utf8("</" + headerLevels.top().second + ">");
992 xs << XMLStream::ESCAPE_NONE << tag;