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"
19 #include "InsetList.h"
21 #include "OutputParams.h"
22 #include "Paragraph.h"
23 #include "ParagraphList.h"
24 #include "ParagraphParameters.h"
27 #include "TextClass.h"
29 #include "insets/InsetBibtex.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"
44 using namespace lyx::support;
50 std::string const fontToDocBookTag(xml::FontTypes type)
53 case xml::FontTypes::FT_EMPH:
54 case xml::FontTypes::FT_BOLD:
56 case xml::FontTypes::FT_NOUN:
58 case xml::FontTypes::FT_UBAR:
59 case xml::FontTypes::FT_WAVE:
60 case xml::FontTypes::FT_DBAR:
61 case xml::FontTypes::FT_SOUT:
62 case xml::FontTypes::FT_XOUT:
63 case xml::FontTypes::FT_ITALIC:
64 case xml::FontTypes::FT_UPRIGHT:
65 case xml::FontTypes::FT_SLANTED:
66 case xml::FontTypes::FT_SMALLCAPS:
67 case xml::FontTypes::FT_ROMAN:
68 case xml::FontTypes::FT_SANS:
70 case xml::FontTypes::FT_TYPE:
72 case xml::FontTypes::FT_SIZE_TINY:
73 case xml::FontTypes::FT_SIZE_SCRIPT:
74 case xml::FontTypes::FT_SIZE_FOOTNOTE:
75 case xml::FontTypes::FT_SIZE_SMALL:
76 case xml::FontTypes::FT_SIZE_NORMAL:
77 case xml::FontTypes::FT_SIZE_LARGE:
78 case xml::FontTypes::FT_SIZE_LARGER:
79 case xml::FontTypes::FT_SIZE_LARGEST:
80 case xml::FontTypes::FT_SIZE_HUGE:
81 case xml::FontTypes::FT_SIZE_HUGER:
82 case xml::FontTypes::FT_SIZE_INCREASE:
83 case xml::FontTypes::FT_SIZE_DECREASE:
90 string fontToRole(xml::FontTypes type)
92 // Specific fonts are achieved with roles. The only common ones are "" for basic emphasis,
93 // and "bold"/"strong" for bold. With some specific options, other roles are copied into
94 // HTML output (via the DocBook XSLT sheets); otherwise, if not recognised, they are just ignored.
95 // Hence, it is not a problem to have many roles by default here.
96 // See https://www.sourceware.org/ml/docbook/2003-05/msg00269.html
98 case xml::FontTypes::FT_ITALIC:
99 case xml::FontTypes::FT_EMPH:
101 case xml::FontTypes::FT_BOLD:
103 case xml::FontTypes::FT_NOUN:
104 return ""; // Outputs a <person>
105 case xml::FontTypes::FT_TYPE:
106 return ""; // Outputs a <code>
108 // All other roles are non-standard for DocBook.
110 case xml::FontTypes::FT_UBAR:
112 case xml::FontTypes::FT_WAVE:
114 case xml::FontTypes::FT_DBAR:
116 case xml::FontTypes::FT_SOUT:
118 case xml::FontTypes::FT_XOUT:
120 case xml::FontTypes::FT_UPRIGHT:
122 case xml::FontTypes::FT_SLANTED:
124 case xml::FontTypes::FT_SMALLCAPS:
126 case xml::FontTypes::FT_ROMAN:
128 case xml::FontTypes::FT_SANS:
130 case xml::FontTypes::FT_SIZE_TINY:
132 case xml::FontTypes::FT_SIZE_SCRIPT:
133 return "size_script";
134 case xml::FontTypes::FT_SIZE_FOOTNOTE:
135 return "size_footnote";
136 case xml::FontTypes::FT_SIZE_SMALL:
138 case xml::FontTypes::FT_SIZE_NORMAL:
139 return "size_normal";
140 case xml::FontTypes::FT_SIZE_LARGE:
142 case xml::FontTypes::FT_SIZE_LARGER:
143 return "size_larger";
144 case xml::FontTypes::FT_SIZE_LARGEST:
145 return "size_largest";
146 case xml::FontTypes::FT_SIZE_HUGE:
148 case xml::FontTypes::FT_SIZE_HUGER:
150 case xml::FontTypes::FT_SIZE_INCREASE:
151 return "size_increase";
152 case xml::FontTypes::FT_SIZE_DECREASE:
153 return "size_decrease";
159 string fontToAttribute(xml::FontTypes type) {
160 // If there is a role (i.e. nonstandard use of a tag), output the attribute. Otherwise, the sheer tag is sufficient
162 string role = fontToRole(type);
164 return "role='" + role + "'";
170 } // end anonymous namespace
173 xml::FontTag docbookStartFontTag(xml::FontTypes type)
175 return xml::FontTag(from_utf8(fontToDocBookTag(type)), from_utf8(fontToAttribute(type)), type);
179 xml::EndFontTag docbookEndFontTag(xml::FontTypes type)
181 return xml::EndFontTag(from_utf8(fontToDocBookTag(type)), type);
187 // convenience functions
189 void openParTag(XMLStream &xs, Layout const &lay)
191 if (lay.docbookwrappertag() != "NONE") {
192 xs << xml::StartTag(lay.docbookwrappertag(), lay.docbookwrapperattr());
195 string tag = lay.docbooktag();
196 if (tag == "Plain Layout")
199 xs << xml::ParTag(tag, lay.docbookattr());
203 void closeTag(XMLStream &xs, Layout const &lay)
205 string tag = lay.docbooktag();
206 if (tag == "Plain Layout")
209 xs << xml::EndTag(tag);
210 if (lay.docbookwrappertag() != "NONE")
211 xs << xml::EndTag(lay.docbookwrappertag());
215 void openLabelTag(XMLStream & xs, Layout const & lay) // Mostly for definition lists.
217 xs << xml::StartTag(lay.docbookitemlabeltag(), lay.docbookitemlabelattr());
221 void closeLabelTag(XMLStream & xs, Layout const & lay)
223 xs << xml::EndTag(lay.docbookitemlabeltag());
228 void openItemTag(XMLStream &xs, Layout const &lay)
230 xs << xml::StartTag(lay.docbookitemtag(), lay.docbookitemattr());
234 // Return true when new elements are output in a paragraph, false otherwise.
235 bool openInnerItemTag(XMLStream &xs, Layout const &lay)
237 if (lay.docbookiteminnertag() != "NONE") {
239 xs << xml::ParTag(lay.docbookiteminnertag(), lay.docbookiteminnerattr());
241 if (lay.docbookiteminnertag() == "para") {
249 void closeInnerItemTag(XMLStream &xs, Layout const &lay)
251 if (lay.docbookiteminnertag()!= "NONE") {
252 xs << xml::EndTag(lay.docbookiteminnertag());
258 inline void closeItemTag(XMLStream &xs, Layout const &lay)
260 xs << xml::EndTag(lay.docbookitemtag()) << xml::CR();
263 // end of convenience functions
265 ParagraphList::const_iterator findLastParagraph(
266 ParagraphList::const_iterator p,
267 ParagraphList::const_iterator const & pend) {
268 for (++p; p != pend && p->layout().latextype == LATEX_PARAGRAPH; ++p);
274 ParagraphList::const_iterator findEndOfEnvironment(
275 ParagraphList::const_iterator const & pstart,
276 ParagraphList::const_iterator const & pend)
278 ParagraphList::const_iterator p = pstart;
279 Layout const &bstyle = p->layout();
280 size_t const depth = p->params().depth();
281 for (++p; p != pend; ++p) {
282 Layout const &style = p->layout();
283 // It shouldn't happen that e.g. a section command occurs inside
284 // a quotation environment, at a higher depth, but as of 6/2009,
285 // it can happen. We pretend that it's just at lowest depth.
286 if (style.latextype == LATEX_COMMAND)
289 // If depth is down, we're done
290 if (p->params().depth() < depth)
293 // If depth is up, we're not done
294 if (p->params().depth() > depth)
297 // FIXME I am not sure about the first check.
298 // Surely we *could* have different layouts that count as
299 // LATEX_PARAGRAPH, right?
300 if (style.latextype == LATEX_PARAGRAPH || style != bstyle)
307 ParagraphList::const_iterator makeParagraphs(
310 OutputParams const &runparams,
312 ParagraphList::const_iterator const & pbegin,
313 ParagraphList::const_iterator const & pend)
315 ParagraphList::const_iterator const begin = text.paragraphs().begin();
316 ParagraphList::const_iterator par = pbegin;
317 for (; par != pend; ++par) {
318 Layout const &lay = par->layout();
319 if (!lay.counter.empty())
320 buf.masterBuffer()->params().
321 documentClass().counters().step(lay.counter, OutputUpdate);
323 // We want to open the paragraph tag if:
324 // (i) the current layout permits multiple paragraphs
325 // (ii) we are either not already inside a paragraph (HTMLIsBlock) OR
326 // we are, but this is not the first paragraph
328 // But there is also a special case, and we first see whether we are in it.
329 // We do not want to open the paragraph tag if this paragraph contains
330 // only one item, and that item is "inline", i.e., not HTMLIsBlock (such
331 // as a branch). On the other hand, if that single item has a font change
332 // applied to it, then we still do need to open the paragraph.
334 // Obviously, this is very fragile. The main reason we need to do this is
335 // because of branches, e.g., a branch that contains an entire new section.
336 // We do not really want to wrap that whole thing in a <div>...</div>.
337 bool special_case = false;
338 Inset const *specinset = par->size() == 1 ? par->getInset(0) : 0;
339 if (specinset && !specinset->getLayout().htmlisblock()) { // TODO: Convert htmlisblock to a DocBook parameter?
340 Layout const &style = par->layout();
341 FontInfo const first_font = style.labeltype == LABEL_MANUAL ?
342 style.labelfont : style.font;
343 FontInfo const our_font =
344 par->getFont(buf.masterBuffer()->params(), 0,
345 text.outerFont(distance(begin, par))).fontInfo();
347 if (first_font == our_font)
351 // Plain layouts must be ignored.
352 if (!special_case && buf.params().documentClass().isPlainLayout(lay) && !runparams.docbook_force_pars)
354 // TODO: Could get rid of this with a DocBook equivalent to htmlisblock?
355 if (!special_case && par->size() == 1 && par->getInset(0)) {
356 Inset const * firstInset = par->getInset(0);
358 // Floats cannot be in paragraphs.
359 special_case = to_ascii(firstInset->layoutName()).substr(0, 6) == "Float:";
361 // Bibliographies cannot be in paragraphs.
362 if (!special_case && firstInset->asInsetCommand())
363 special_case = firstInset->asInsetCommand()->params().getCmdName() == "bibtex";
365 // Equations do not deserve their own paragraph (DocBook allows them outside paragraphs).
366 if (!special_case && firstInset->asInsetMath())
369 // ERTs are in comments, not paragraphs.
370 if (!special_case && firstInset->lyxCode() == lyx::ERT_CODE)
373 // Listings should not get into their own paragraph.
374 if (!special_case && firstInset->lyxCode() == lyx::LISTINGS_CODE)
378 bool const open_par = runparams.docbook_make_pars
379 && (!runparams.docbook_in_par || par != pbegin)
382 // We want to issue the closing tag if either:
383 // (i) We opened it, and either docbook_in_par is false,
384 // or we're not in the last paragraph, anyway.
385 // (ii) We didn't open it and docbook_in_par is true,
386 // but we are in the first par, and there is a next par.
387 ParagraphList::const_iterator nextpar = par;
389 bool const close_par =
390 ((open_par && (!runparams.docbook_in_par || nextpar != pend))
391 || (!open_par && runparams.docbook_in_par && par == pbegin && nextpar != pend));
397 par->simpleDocBookOnePar(buf, xs, runparams, text.outerFont(distance(begin, par)), open_par, close_par, 0);
408 bool isNormalEnv(Layout const &lay)
410 return lay.latextype == LATEX_ENVIRONMENT
411 || lay.latextype == LATEX_BIB_ENVIRONMENT;
415 ParagraphList::const_iterator makeEnvironment(
418 OutputParams const &runparams,
420 ParagraphList::const_iterator const & pbegin,
421 ParagraphList::const_iterator const & pend)
423 ParagraphList::const_iterator const begin = text.paragraphs().begin();
424 ParagraphList::const_iterator par = pbegin;
425 Layout const &bstyle = par->layout();
426 depth_type const origdepth = pbegin->params().depth();
428 // open tag for this environment
429 openParTag(xs, bstyle);
432 // we will on occasion need to remember a layout from before.
433 Layout const *lastlay = nullptr;
435 while (par != pend) {
436 Layout const & style = par->layout();
437 // the counter only gets stepped if we're in some kind of list,
438 // or if it's the first time through.
439 // note that enum, etc, are handled automatically.
440 // FIXME There may be a bug here about user defined enumeration
441 // types. If so, then we'll need to take the counter and add "i",
442 // "ii", etc, as with enum.
443 Counters & cnts = buf.masterBuffer()->params().documentClass().counters();
444 docstring const & cntr = style.counter;
445 if (!style.counter.empty()
446 && (par == pbegin || !isNormalEnv(style))
447 && cnts.hasCounter(cntr)
449 cnts.step(cntr, OutputUpdate);
450 ParagraphList::const_iterator send;
452 // Actual content of this paragraph.
453 switch (style.latextype) {
454 case LATEX_ENVIRONMENT:
455 case LATEX_LIST_ENVIRONMENT:
456 case LATEX_ITEM_ENVIRONMENT: {
457 // There are two possibilities in this case.
458 // One is that we are still in the environment in which we
459 // started---which we will be if the depth is the same.
460 if (par->params().depth() == origdepth) {
461 LATTEST(bstyle == style);
462 if (lastlay != nullptr) {
463 closeItemTag(xs, *lastlay);
467 // this will be positive, if we want to skip the
468 // initial word (if it's been taken for the label).
471 // Open a wrapper tag if needed.
472 if (style.docbookitemwrappertag() != "NONE") {
473 xs << xml::StartTag(style.docbookitemwrappertag(), style.docbookitemwrapperattr());
478 if (style.labeltype != LABEL_NO_LABEL &&
479 style.docbookitemlabeltag() != "NONE") {
481 if (isNormalEnv(style)) {
482 // in this case, we print the label only for the first
483 // paragraph (as in a theorem or an abstract).
485 docstring const lbl = pbegin->params().labelString();
487 openLabelTag(xs, style);
489 closeLabelTag(xs, style);
493 } else { // some kind of list
494 if (style.labeltype == LABEL_MANUAL) {
495 // Only variablelist gets here.
497 openLabelTag(xs, style);
498 sep = par->firstWordDocBook(xs, runparams);
499 closeLabelTag(xs, style);
502 openLabelTag(xs, style);
503 xs << par->params().labelString();
504 closeLabelTag(xs, style);
508 } // end label output
510 bool wasInParagraph = runparams.docbook_in_par;
511 openItemTag(xs, style);
512 bool getsIntoParagraph = openInnerItemTag(xs, style);
513 OutputParams rp = runparams;
514 rp.docbook_in_par = wasInParagraph | getsIntoParagraph;
516 par->simpleDocBookOnePar(buf, xs, rp, text.outerFont(distance(begin, par)), true, true, sep);
518 if (getsIntoParagraph)
519 closeInnerItemTag(xs, style);
521 // We may not want to close the tag yet, in particular:
522 // If we're not at the end of the item...
524 // and are doing items...
525 && !isNormalEnv(style)
526 // and if the depth has changed...
527 && par->params().depth() != origdepth) {
528 // then we'll save this layout for later, and close it when
529 // we get another item.
532 closeItemTag(xs, style);
534 // Eventually, close the item wrapper.
535 if (style.docbookitemwrappertag() != "NONE") {
536 xs << xml::EndTag(style.docbookitemwrappertag());
541 // The other possibility is that the depth has increased, in which
542 // case we need to recurse.
544 send = findEndOfEnvironment(par, pend);
545 par = makeEnvironment(buf, xs, runparams, text, par, send);
549 case LATEX_PARAGRAPH:
550 send = findLastParagraph(par, pend);
551 par = makeParagraphs(buf, xs, runparams, text, par, send);
553 case LATEX_BIB_ENVIRONMENT:
554 // Handled in InsetBibtex.
563 closeItemTag(xs, *lastlay);
564 closeTag(xs, bstyle);
573 OutputParams const & runparams,
575 ParagraphList::const_iterator const & pbegin)
577 Layout const &style = pbegin->layout();
578 if (!style.counter.empty())
579 buf.masterBuffer()->params().
580 documentClass().counters().step(style.counter, OutputUpdate);
582 // No need for labels, as they are handled by DocBook tags.
584 openParTag(xs, style);
586 ParagraphList::const_iterator const begin = text.paragraphs().begin();
587 pbegin->simpleDocBookOnePar(buf, xs, runparams,
588 text.outerFont(distance(begin, pbegin)));
593 pair<ParagraphList::const_iterator, ParagraphList::const_iterator> makeAny(
597 OutputParams const &ourparams,
598 ParagraphList::const_iterator par,
599 ParagraphList::const_iterator send,
600 ParagraphList::const_iterator pend)
602 Layout const & style = par->layout();
604 switch (style.latextype) {
605 case LATEX_COMMAND: {
606 // The files with which we are working never have more than
607 // one paragraph in a command structure.
609 // if (ourparams.docbook_in_par)
610 // fix it so we don't get sections inside standard, e.g.
611 // note that we may then need to make runparams not const, so we
612 // can communicate that back.
613 // FIXME Maybe this fix should be in the routines themselves, in case
614 // they are called from elsewhere.
615 makeCommand(buf, xs, ourparams, text, par);
619 case LATEX_ENVIRONMENT:
620 case LATEX_LIST_ENVIRONMENT:
621 case LATEX_ITEM_ENVIRONMENT: {
622 // FIXME Same fix here.
623 send = findEndOfEnvironment(par, pend);
624 par = makeEnvironment(buf, xs, ourparams, text, par, send);
627 case LATEX_BIB_ENVIRONMENT: {
628 // Handled in InsetBibtex.
631 case LATEX_PARAGRAPH: {
632 send = findLastParagraph(par, pend);
633 par = makeParagraphs(buf, xs, ourparams, text, par, send);
638 return make_pair(par, send);
641 } // end anonymous namespace
644 using DocBookDocumentSectioning = tuple<bool, pit_type>;
645 using DocBookInfoTag = tuple<set<pit_type>, set<pit_type>, pit_type, pit_type>;
648 DocBookDocumentSectioning hasDocumentSectioning(ParagraphList const ¶graphs, pit_type bpit, pit_type const epit) {
649 bool documentHasSections = false;
651 while (bpit < epit) {
652 Layout const &style = paragraphs[bpit].layout();
653 documentHasSections |= style.category() == from_utf8("Sectioning");
655 if (documentHasSections) {
660 // Paragraphs before the first section: [ runparams.par_begin ; eppit )
662 return make_tuple(documentHasSections, bpit);
666 DocBookInfoTag getParagraphsWithInfo(ParagraphList const ¶graphs, pit_type const bpit, pit_type const epit) {
667 set<pit_type> shouldBeInInfo;
668 set<pit_type> mustBeInInfo;
670 pit_type cpit = bpit;
671 while (cpit < epit) {
672 Layout const &style = paragraphs[cpit].layout();
673 if (style.docbookininfo() == "always") {
674 mustBeInInfo.emplace(cpit);
675 } else if (style.docbookininfo() == "maybe") {
676 shouldBeInInfo.emplace(cpit);
678 // Hypothesis: the <info> parts should be grouped together near the beginning bpit.
683 // Now, bpit points to the last paragraph that has things that could go in <info>.
685 return make_tuple(shouldBeInInfo, mustBeInInfo, bpit, cpit);
689 bool hasAbstractBetween(ParagraphList const ¶graphs, pit_type const bpitAbstract, pit_type const epitAbstract)
691 // Hypothesis: the paragraphs between bpitAbstract and epitAbstract can be considered an abstract because they
692 // are just after a document or part title.
693 if (epitAbstract - bpitAbstract <= 0)
696 // If there is something between these paragraphs, check if it's compatible with an abstract (i.e. some text).
697 pit_type bpit = bpitAbstract;
698 while (bpit < epitAbstract) {
699 const Paragraph &p = paragraphs.at(bpit);
701 if (p.layout().name() == from_ascii("Abstract"))
704 if (!p.insetList().empty()) {
705 for (const auto &i : p.insetList()) {
706 if (i.inset->getText(0) != nullptr) {
717 pit_type generateDocBookParagraphWithoutSectioning(
721 OutputParams const & runparams,
722 ParagraphList const & paragraphs,
726 auto par = paragraphs.iterator_at(bpit);
727 auto lastStartedPar = par;
728 ParagraphList::const_iterator send;
730 (epit == (int) paragraphs.size()) ?
731 paragraphs.end() : paragraphs.iterator_at(epit);
733 while (bpit < epit) {
734 tie(par, send) = makeAny(text, buf, xs, runparams, par, send, pend);
735 bpit += distance(lastStartedPar, par);
742 void outputDocBookInfo(
746 OutputParams const & runparams,
747 ParagraphList const & paragraphs,
748 DocBookInfoTag const & info,
749 pit_type bpitAbstract,
750 pit_type const epitAbstract)
752 // Consider everything between bpitAbstract and epitAbstract (excluded) as paragraphs for the abstract.
753 // Use bpitAbstract >= epitAbstract to indicate there is no abstract.
755 set<pit_type> shouldBeInInfo;
756 set<pit_type> mustBeInInfo;
759 tie(shouldBeInInfo, mustBeInInfo, bpitInfo, epitInfo) = info;
761 // The abstract must go in <info>.
762 bool hasAbstract = hasAbstractBetween(paragraphs, bpitAbstract, epitAbstract);
763 bool needInfo = !mustBeInInfo.empty() || hasAbstract;
765 // Start the <info> tag if required.
767 xs.startDivision(false);
768 xs << xml::StartTag("info");
772 // Output the elements that should go in <info>.
773 generateDocBookParagraphWithoutSectioning(text, buf, xs, runparams, paragraphs, bpitInfo, epitInfo);
776 string tag = paragraphs[bpitAbstract].layout().docbookforceabstracttag();
780 xs << xml::StartTag(tag);
782 xs.startDivision(false);
783 generateDocBookParagraphWithoutSectioning(text, buf, xs, runparams, paragraphs, bpitAbstract, epitAbstract);
785 xs << xml::EndTag(tag);
789 // End the <info> tag if it was started.
791 xs << xml::EndTag("info");
798 void docbookFirstParagraphs(
802 OutputParams const &runparams,
805 // Handle the beginning of the document, supposing it has sections.
806 // Major role: output the first <info> tag.
808 ParagraphList const ¶graphs = text.paragraphs();
809 pit_type bpit = runparams.par_begin;
810 DocBookInfoTag info = getParagraphsWithInfo(paragraphs, bpit, epit);
811 outputDocBookInfo(text, buf, xs, runparams, paragraphs, info, get<3>(info), epit);
815 bool isParagraphEmpty(const Paragraph &par)
817 InsetList const &insets = par.insetList();
818 size_t insetsLength = distance(insets.begin(), insets.end());
819 bool hasParagraphOnlyNote = insetsLength == 1 && insets.get(0) && insets.get(0)->asInsetCollapsible() &&
820 dynamic_cast<InsetNote *>(insets.get(0));
821 return hasParagraphOnlyNote;
825 void docbookSimpleAllParagraphs(
829 OutputParams const & runparams)
831 // Handle the document, supposing it has no sections (i.e. a "simple" document).
833 // First, the <info> tag.
834 ParagraphList const ¶graphs = text.paragraphs();
835 pit_type bpit = runparams.par_begin;
836 pit_type const epit = runparams.par_end;
837 DocBookInfoTag info = getParagraphsWithInfo(paragraphs, bpit, epit);
838 outputDocBookInfo(text, buf, xs, runparams, paragraphs, info, 0, 0);
839 bpit = get<3>(info); // Generate the content starting from the end of the <info> part.
841 // Then, the content.
842 ParagraphList::const_iterator const pend =
843 (epit == (int) paragraphs.size()) ?
844 paragraphs.end() : paragraphs.iterator_at(epit);
846 while (bpit < epit) {
847 auto par = paragraphs.iterator_at(bpit);
848 ParagraphList::const_iterator const lastStartedPar = par;
849 ParagraphList::const_iterator send;
851 if (isParagraphEmpty(*par)) {
853 bpit += distance(lastStartedPar, par);
857 // Generate this paragraph.
858 tie(par, send) = makeAny(text, buf, xs, runparams, par, send, pend);
859 bpit += distance(lastStartedPar, par);
864 void docbookParagraphs(Text const &text,
867 OutputParams const &runparams) {
868 ParagraphList const ¶graphs = text.paragraphs();
869 if (runparams.par_begin == runparams.par_end) {
870 runparams.par_begin = 0;
871 runparams.par_end = paragraphs.size();
873 pit_type bpit = runparams.par_begin;
874 pit_type const epit = runparams.par_end;
877 xs << XMLStream::ESCAPE_NONE << "<!-- DocBook output error! -->\n";
881 ParagraphList::const_iterator const pend =
882 (epit == (int) paragraphs.size()) ?
883 paragraphs.end() : paragraphs.iterator_at(epit);
884 std::stack<std::pair<int, string>> headerLevels; // Used to determine when to open/close sections: store the depth
885 // of the section and the tag that was used to open it.
887 // Detect whether the document contains sections. If there are no sections, there can be no automatically
888 // discovered abstract.
889 bool documentHasSections;
891 tie(documentHasSections, eppit) = hasDocumentSectioning(paragraphs, bpit, epit);
893 if (documentHasSections) {
894 docbookFirstParagraphs(text, buf, xs, runparams, eppit);
897 docbookSimpleAllParagraphs(text, buf, xs, runparams);
901 bool currentlyInAppendix = false;
903 while (bpit < epit) {
904 OutputParams ourparams = runparams;
906 auto par = paragraphs.iterator_at(bpit);
907 if (par->params().startOfAppendix())
908 currentlyInAppendix = true;
909 Layout const &style = par->layout();
910 ParagraphList::const_iterator const lastStartedPar = par;
911 ParagraphList::const_iterator send;
913 if (isParagraphEmpty(*par)) {
915 bpit += distance(lastStartedPar, par);
919 // Think about adding <section> and/or </section>s.
920 const bool isLayoutSectioning = style.category() == from_utf8("Sectioning");
921 if (isLayoutSectioning) {
922 int level = style.toclevel;
924 // Need to close a previous section if it has the same level or a higher one (close <section> if opening a <h2>
925 // after a <h2>, <h3>, <h4>, <h5> or <h6>). More examples:
926 // - current: h2; back: h1; do not close any <section>
927 // - current: h1; back: h2; close two <section> (first the <h2>, then the <h1>, so a new <h1> can come)
928 while (!headerLevels.empty() && level <= headerLevels.top().first) {
929 int stackLevel = headerLevels.top().first;
930 docstring stackTag = from_utf8("</" + headerLevels.top().second + ">");
933 // Output the tag only if it corresponds to a legit section.
934 if (stackLevel != Layout::NOT_IN_TOC)
935 xs << XMLStream::ESCAPE_NONE << stackTag << xml::CR();
938 // Open the new section: first push it onto the stack, then output it in DocBook.
939 string sectionTag = (currentlyInAppendix && style.docbooksectiontag() == "chapter") ?
940 "appendix" : style.docbooksectiontag();
941 headerLevels.push(std::make_pair(level, sectionTag));
943 // Some sectioning-like elements should not be output (such as FrontMatter).
944 if (level != Layout::NOT_IN_TOC) {
945 // Look for a label in the title, i.e. a InsetLabel as a child.
946 docstring id = docstring();
947 for (pos_type i = 0; i < par->size(); ++i) {
948 Inset const *inset = par->getInset(i);
950 if (auto label = dynamic_cast<InsetLabel const *>(inset)) {
951 // Generate the attributes for the section if need be.
952 id += "xml:id=\"" + xml::cleanID(label->screenLabel()) + "\"";
954 // Don't output the ID as a DocBook <anchor>.
955 ourparams.docbook_anchors_to_ignore.emplace(label->screenLabel());
957 // Cannot have multiple IDs per tag.
963 // Write the open tag for this section.
964 docstring tag = from_utf8("<" + sectionTag);
966 tag += from_utf8(" ") + id;
967 tag += from_utf8(">");
968 xs << XMLStream::ESCAPE_NONE << tag;
973 // Close all sections before the bibliography.
974 // 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)?
975 auto insetsLength = distance(par->insetList().begin(), par->insetList().end());
976 if (insetsLength > 0) {
977 Inset const *firstInset = par->getInset(0);
978 if (firstInset && dynamic_cast<InsetBibtex const *>(firstInset)) {
979 while (!headerLevels.empty()) {
980 int level = headerLevels.top().first;
981 docstring tag = from_utf8("</" + headerLevels.top().second + ">");
984 // Output the tag only if it corresponds to a legit section.
985 if (level != Layout::NOT_IN_TOC) {
986 xs << XMLStream::ESCAPE_NONE << tag;
993 // Generate this paragraph.
994 tie(par, send) = makeAny(text, buf, xs, ourparams, par, send, pend);
995 bpit += distance(lastStartedPar, par);
998 // If need be, close <section>s, but only at the end of the document (otherwise, dealt with at the beginning
1000 while (!headerLevels.empty() && headerLevels.top().first > Layout::NOT_IN_TOC) {
1001 docstring tag = from_utf8("</" + headerLevels.top().second + ">");
1003 xs << XMLStream::ESCAPE_NONE << tag;