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) {
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) {
90 // Specific fonts are achieved with roles. The only common ones are "" for basic emphasis,
91 // and "bold"/"strong" for bold. With some specific options, other roles are copied into
92 // HTML output (via the DocBook XSLT sheets); otherwise, if not recognised, they are just ignored.
93 // Hence, it is not a problem to have many roles by default here.
94 // See https://www.sourceware.org/ml/docbook/2003-05/msg00269.html
96 case xml::FontTypes::FT_ITALIC:
97 case xml::FontTypes::FT_EMPH:
99 case xml::FontTypes::FT_BOLD:
101 case xml::FontTypes::FT_NOUN:
102 return ""; // Outputs a <person>
103 case xml::FontTypes::FT_TYPE:
104 return ""; // Outputs a <code>
106 // All other roles are non-standard for DocBook.
108 case xml::FontTypes::FT_UBAR:
110 case xml::FontTypes::FT_WAVE:
112 case xml::FontTypes::FT_DBAR:
114 case xml::FontTypes::FT_SOUT:
116 case xml::FontTypes::FT_XOUT:
118 case xml::FontTypes::FT_UPRIGHT:
120 case xml::FontTypes::FT_SLANTED:
122 case xml::FontTypes::FT_SMALLCAPS:
124 case xml::FontTypes::FT_ROMAN:
126 case xml::FontTypes::FT_SANS:
128 case xml::FontTypes::FT_SIZE_TINY:
130 case xml::FontTypes::FT_SIZE_SCRIPT:
131 return "size_script";
132 case xml::FontTypes::FT_SIZE_FOOTNOTE:
133 return "size_footnote";
134 case xml::FontTypes::FT_SIZE_SMALL:
136 case xml::FontTypes::FT_SIZE_NORMAL:
137 return "size_normal";
138 case xml::FontTypes::FT_SIZE_LARGE:
140 case xml::FontTypes::FT_SIZE_LARGER:
141 return "size_larger";
142 case xml::FontTypes::FT_SIZE_LARGEST:
143 return "size_largest";
144 case xml::FontTypes::FT_SIZE_HUGE:
146 case xml::FontTypes::FT_SIZE_HUGER:
148 case xml::FontTypes::FT_SIZE_INCREASE:
149 return "size_increase";
150 case xml::FontTypes::FT_SIZE_DECREASE:
151 return "size_decrease";
157 string fontToAttribute(xml::FontTypes type) {
158 // If there is a role (i.e. nonstandard use of a tag), output the attribute. Otherwise, the sheer tag is sufficient
160 string role = fontToRole(type);
162 return "role='" + role + "'";
168 } // end anonymous namespace
171 xml::FontTag docbookStartFontTag(xml::FontTypes type)
173 return xml::FontTag(from_utf8(fontToDocBookTag(type)), from_utf8(fontToAttribute(type)), type);
177 xml::EndFontTag docbookEndFontTag(xml::FontTypes type)
179 return xml::EndFontTag(from_utf8(fontToDocBookTag(type)), type);
185 // convenience functions
187 void openParTag(XMLStream &xs, Layout const &lay) {
188 if (lay.docbookwrappertag() != "NONE") {
189 xs << xml::StartTag(lay.docbookwrappertag(), lay.docbookwrapperattr());
192 string tag = lay.docbooktag();
193 if (tag == "Plain Layout")
196 xs << xml::ParTag(tag, lay.docbookattr());
200 void closeTag(XMLStream &xs, Layout const &lay) {
201 string tag = lay.docbooktag();
202 if (tag == "Plain Layout")
205 xs << xml::EndTag(tag);
206 if (lay.docbookwrappertag() != "NONE")
207 xs << xml::EndTag(lay.docbookwrappertag());
211 void openLabelTag(XMLStream & xs, Layout const & lay) // Mostly for definition lists.
213 xs << xml::StartTag(lay.docbookitemlabeltag(), lay.docbookitemlabelattr());
217 void closeLabelTag(XMLStream & xs, Layout const & lay)
219 xs << xml::EndTag(lay.docbookitemlabeltag());
224 void openItemTag(XMLStream &xs, Layout const &lay) {
225 xs << xml::StartTag(lay.docbookitemtag(), lay.docbookitemattr());
229 // Return true when new elements are output in a paragraph, false otherwise.
230 bool openInnerItemTag(XMLStream &xs, Layout const &lay) {
231 if (lay.docbookiteminnertag() != "NONE") {
233 xs << xml::ParTag(lay.docbookiteminnertag(), lay.docbookiteminnerattr());
235 if (lay.docbookiteminnertag() == "para") {
243 void closeInnerItemTag(XMLStream &xs, Layout const &lay) {
244 if (lay.docbookiteminnertag()!= "NONE") {
245 xs << xml::EndTag(lay.docbookiteminnertag());
251 inline void closeItemTag(XMLStream &xs, Layout const &lay) {
252 xs << xml::EndTag(lay.docbookitemtag()) << xml::CR();
255 // end of convenience functions
257 ParagraphList::const_iterator findLastParagraph(
258 ParagraphList::const_iterator p,
259 ParagraphList::const_iterator const &pend) {
260 for (++p; p != pend && p->layout().latextype == LATEX_PARAGRAPH; ++p);
266 ParagraphList::const_iterator findEndOfEnvironment(
267 ParagraphList::const_iterator const &pstart,
268 ParagraphList::const_iterator const &pend) {
269 ParagraphList::const_iterator p = pstart;
270 Layout const &bstyle = p->layout();
271 size_t const depth = p->params().depth();
272 for (++p; p != pend; ++p) {
273 Layout const &style = p->layout();
274 // It shouldn't happen that e.g. a section command occurs inside
275 // a quotation environment, at a higher depth, but as of 6/2009,
276 // it can happen. We pretend that it's just at lowest depth.
277 if (style.latextype == LATEX_COMMAND)
280 // If depth is down, we're done
281 if (p->params().depth() < depth)
284 // If depth is up, we're not done
285 if (p->params().depth() > depth)
288 // FIXME I am not sure about the first check.
289 // Surely we *could* have different layouts that count as
290 // LATEX_PARAGRAPH, right?
291 if (style.latextype == LATEX_PARAGRAPH || style != bstyle)
298 ParagraphList::const_iterator makeParagraphs(Buffer const &buf,
300 OutputParams const &runparams,
302 ParagraphList::const_iterator const &pbegin,
303 ParagraphList::const_iterator const &pend) {
304 ParagraphList::const_iterator const begin = text.paragraphs().begin();
305 ParagraphList::const_iterator par = pbegin;
306 for (; par != pend; ++par) {
307 Layout const &lay = par->layout();
308 if (!lay.counter.empty())
309 buf.masterBuffer()->params().
310 documentClass().counters().step(lay.counter, OutputUpdate);
312 // We want to open the paragraph tag if:
313 // (i) the current layout permits multiple paragraphs
314 // (ii) we are either not already inside a paragraph (HTMLIsBlock) OR
315 // we are, but this is not the first paragraph
317 // But there is also a special case, and we first see whether we are in it.
318 // We do not want to open the paragraph tag if this paragraph contains
319 // only one item, and that item is "inline", i.e., not HTMLIsBlock (such
320 // as a branch). On the other hand, if that single item has a font change
321 // applied to it, then we still do need to open the paragraph.
323 // Obviously, this is very fragile. The main reason we need to do this is
324 // because of branches, e.g., a branch that contains an entire new section.
325 // We do not really want to wrap that whole thing in a <div>...</div>.
326 bool special_case = false;
327 Inset const *specinset = par->size() == 1 ? par->getInset(0) : 0;
328 if (specinset && !specinset->getLayout().htmlisblock()) { // TODO: Convert htmlisblock to a DocBook parameter?
329 Layout const &style = par->layout();
330 FontInfo const first_font = style.labeltype == LABEL_MANUAL ?
331 style.labelfont : style.font;
332 FontInfo const our_font =
333 par->getFont(buf.masterBuffer()->params(), 0,
334 text.outerFont(distance(begin, par))).fontInfo();
336 if (first_font == our_font)
340 // Plain layouts must be ignored.
341 if (!special_case && buf.params().documentClass().isPlainLayout(lay) && !runparams.docbook_force_pars)
343 // TODO: Could get rid of this with a DocBook equivalent to htmlisblock?
344 if (!special_case && par->size() == 1 && par->getInset(0)) {
345 Inset const * firstInset = par->getInset(0);
347 // Floats cannot be in paragraphs.
348 special_case = to_ascii(firstInset->layoutName()).substr(0, 6) == "Float:";
350 // Bibliographies cannot be in paragraphs.
351 if (!special_case && firstInset->asInsetCommand())
352 special_case = firstInset->asInsetCommand()->params().getCmdName() == "bibtex";
354 // Equations do not deserve their own paragraph (DocBook allows them outside paragraphs).
355 if (!special_case && firstInset->asInsetMath())
358 // ERTs are in comments, not paragraphs.
359 if (!special_case && firstInset->lyxCode() == lyx::ERT_CODE)
362 // Listings should not get into their own paragraph.
363 if (!special_case && firstInset->lyxCode() == lyx::LISTINGS_CODE)
367 bool const open_par = runparams.docbook_make_pars
368 && (!runparams.docbook_in_par || par != pbegin)
371 // We want to issue the closing tag if either:
372 // (i) We opened it, and either docbook_in_par is false,
373 // or we're not in the last paragraph, anyway.
374 // (ii) We didn't open it and docbook_in_par is true,
375 // but we are in the first par, and there is a next par.
376 ParagraphList::const_iterator nextpar = par;
378 bool const close_par =
379 ((open_par && (!runparams.docbook_in_par || nextpar != pend))
380 || (!open_par && runparams.docbook_in_par && par == pbegin && nextpar != pend));
386 par->simpleDocBookOnePar(buf, xs, runparams, text.outerFont(distance(begin, par)), open_par, close_par, 0);
397 bool isNormalEnv(Layout const &lay) {
398 return lay.latextype == LATEX_ENVIRONMENT
399 || lay.latextype == LATEX_BIB_ENVIRONMENT;
403 ParagraphList::const_iterator makeEnvironment(Buffer const &buf,
405 OutputParams const &runparams,
407 ParagraphList::const_iterator const &pbegin,
408 ParagraphList::const_iterator const &pend) {
409 ParagraphList::const_iterator const begin = text.paragraphs().begin();
410 ParagraphList::const_iterator par = pbegin;
411 Layout const &bstyle = par->layout();
412 depth_type const origdepth = pbegin->params().depth();
414 // open tag for this environment
415 openParTag(xs, bstyle);
418 // we will on occasion need to remember a layout from before.
419 Layout const *lastlay = nullptr;
421 while (par != pend) {
422 Layout const & style = par->layout();
423 // the counter only gets stepped if we're in some kind of list,
424 // or if it's the first time through.
425 // note that enum, etc, are handled automatically.
426 // FIXME There may be a bug here about user defined enumeration
427 // types. If so, then we'll need to take the counter and add "i",
428 // "ii", etc, as with enum.
429 Counters & cnts = buf.masterBuffer()->params().documentClass().counters();
430 docstring const & cntr = style.counter;
431 if (!style.counter.empty()
432 && (par == pbegin || !isNormalEnv(style))
433 && cnts.hasCounter(cntr)
435 cnts.step(cntr, OutputUpdate);
436 ParagraphList::const_iterator send;
438 // Actual content of this paragraph.
439 switch (style.latextype) {
440 case LATEX_ENVIRONMENT:
441 case LATEX_LIST_ENVIRONMENT:
442 case LATEX_ITEM_ENVIRONMENT: {
443 // There are two possibilities in this case.
444 // One is that we are still in the environment in which we
445 // started---which we will be if the depth is the same.
446 if (par->params().depth() == origdepth) {
447 LATTEST(bstyle == style);
448 if (lastlay != nullptr) {
449 closeItemTag(xs, *lastlay);
453 // this will be positive, if we want to skip the
454 // initial word (if it's been taken for the label).
457 // Open a wrapper tag if needed.
458 if (style.docbookitemwrappertag() != "NONE") {
459 xs << xml::StartTag(style.docbookitemwrappertag(), style.docbookitemwrapperattr());
464 if (style.labeltype != LABEL_NO_LABEL &&
465 style.docbookitemlabeltag() != "NONE") {
467 if (isNormalEnv(style)) {
468 // in this case, we print the label only for the first
469 // paragraph (as in a theorem or an abstract).
471 docstring const lbl = pbegin->params().labelString();
473 openLabelTag(xs, style);
475 closeLabelTag(xs, style);
479 } else { // some kind of list
480 if (style.labeltype == LABEL_MANUAL) {
481 // Only variablelist gets here.
483 openLabelTag(xs, style);
484 sep = par->firstWordDocBook(xs, runparams);
485 closeLabelTag(xs, style);
488 openLabelTag(xs, style);
489 xs << par->params().labelString();
490 closeLabelTag(xs, style);
494 } // end label output
496 bool wasInParagraph = runparams.docbook_in_par;
497 openItemTag(xs, style);
498 bool getsIntoParagraph = openInnerItemTag(xs, style);
499 OutputParams rp = runparams;
500 rp.docbook_in_par = wasInParagraph | getsIntoParagraph;
502 par->simpleDocBookOnePar(buf, xs, rp, text.outerFont(distance(begin, par)), true, true, sep);
504 if (getsIntoParagraph)
505 closeInnerItemTag(xs, style);
507 // We may not want to close the tag yet, in particular:
508 // If we're not at the end of the item...
510 // and are doing items...
511 && !isNormalEnv(style)
512 // and if the depth has changed...
513 && par->params().depth() != origdepth) {
514 // then we'll save this layout for later, and close it when
515 // we get another item.
518 closeItemTag(xs, style);
520 // Eventually, close the item wrapper.
521 if (style.docbookitemwrappertag() != "NONE") {
522 xs << xml::EndTag(style.docbookitemwrappertag());
527 // The other possibility is that the depth has increased, in which
528 // case we need to recurse.
530 send = findEndOfEnvironment(par, pend);
531 par = makeEnvironment(buf, xs, runparams, text, par, send);
535 case LATEX_PARAGRAPH:
536 send = findLastParagraph(par, pend);
537 par = makeParagraphs(buf, xs, runparams, text, par, send);
539 case LATEX_BIB_ENVIRONMENT:
540 // Handled in InsetBibtex.
549 closeItemTag(xs, *lastlay);
550 closeTag(xs, bstyle);
556 void makeCommand(Buffer const &buf,
558 OutputParams const &runparams,
560 ParagraphList::const_iterator const &pbegin) {
561 Layout const &style = pbegin->layout();
562 if (!style.counter.empty())
563 buf.masterBuffer()->params().
564 documentClass().counters().step(style.counter, OutputUpdate);
566 // No need for labels, as they are handled by DocBook tags.
568 openParTag(xs, style);
570 ParagraphList::const_iterator const begin = text.paragraphs().begin();
571 pbegin->simpleDocBookOnePar(buf, xs, runparams,
572 text.outerFont(distance(begin, pbegin)));
577 pair<ParagraphList::const_iterator, ParagraphList::const_iterator> makeAny(Text const &text,
580 OutputParams const &ourparams,
581 ParagraphList::const_iterator par,
582 ParagraphList::const_iterator send,
583 ParagraphList::const_iterator pend) {
584 Layout const &style = par->layout();
586 switch (style.latextype) {
587 case LATEX_COMMAND: {
588 // The files with which we are working never have more than
589 // one paragraph in a command structure.
591 // if (ourparams.docbook_in_par)
592 // fix it so we don't get sections inside standard, e.g.
593 // note that we may then need to make runparams not const, so we
594 // can communicate that back.
595 // FIXME Maybe this fix should be in the routines themselves, in case
596 // they are called from elsewhere.
597 makeCommand(buf, xs, ourparams, text, par);
601 case LATEX_ENVIRONMENT:
602 case LATEX_LIST_ENVIRONMENT:
603 case LATEX_ITEM_ENVIRONMENT: {
604 // FIXME Same fix here.
605 send = findEndOfEnvironment(par, pend);
606 par = makeEnvironment(buf, xs, ourparams, text, par, send);
609 case LATEX_BIB_ENVIRONMENT: {
610 // Handled in InsetBibtex.
613 case LATEX_PARAGRAPH: {
614 send = findLastParagraph(par, pend);
615 par = makeParagraphs(buf, xs, ourparams, text, par, send);
620 return make_pair(par, send);
623 } // end anonymous namespace
626 using DocBookDocumentSectioning = tuple<bool, pit_type>;
627 using DocBookInfoTag = tuple<set<pit_type>, set<pit_type>, pit_type, pit_type>;
630 DocBookDocumentSectioning hasDocumentSectioning(ParagraphList const ¶graphs, pit_type bpit, pit_type const epit) {
631 bool documentHasSections = false;
633 while (bpit < epit) {
634 Layout const &style = paragraphs[bpit].layout();
635 documentHasSections |= style.category() == from_utf8("Sectioning");
637 if (documentHasSections) {
642 // Paragraphs before the first section: [ runparams.par_begin ; eppit )
644 return make_tuple(documentHasSections, bpit);
648 DocBookInfoTag getParagraphsWithInfo(ParagraphList const ¶graphs, pit_type const bpit, pit_type const epit) {
649 set<pit_type> shouldBeInInfo;
650 set<pit_type> mustBeInInfo;
652 pit_type cpit = bpit;
653 while (cpit < epit) {
654 Layout const &style = paragraphs[cpit].layout();
655 if (style.docbookininfo() == "always") {
656 mustBeInInfo.emplace(cpit);
657 } else if (style.docbookininfo() == "maybe") {
658 shouldBeInInfo.emplace(cpit);
660 // Hypothesis: the <info> parts should be grouped together near the beginning bpit.
665 // Now, bpit points to the last paragraph that has things that could go in <info>.
667 return make_tuple(shouldBeInInfo, mustBeInInfo, bpit, cpit);
671 bool hasAbstractBetween(ParagraphList const ¶graphs, pit_type const bpitAbstract, pit_type const epitAbstract)
673 // Hypothesis: the paragraphs between bpitAbstract and epitAbstract can be considered an abstract because they
674 // are just after a document or part title.
675 if (epitAbstract - bpitAbstract <= 0)
678 // If there is something between these paragraphs, check if it's compatible with an abstract (i.e. some text).
679 pit_type bpit = bpitAbstract;
680 while (bpit < epitAbstract) {
681 const Paragraph &p = paragraphs.at(bpit);
683 if (p.layout().name() == from_ascii("Abstract"))
686 if (!p.insetList().empty()) {
687 for (const auto &i : p.insetList()) {
688 if (i.inset->getText(0) != nullptr) {
699 pit_type generateDocBookParagraphWithoutSectioning(Text const &text,
702 OutputParams const &runparams,
703 ParagraphList const ¶graphs,
706 auto par = paragraphs.iterator_at(bpit);
707 auto lastStartedPar = par;
708 ParagraphList::const_iterator send;
710 (epit == (int) paragraphs.size()) ?
711 paragraphs.end() : paragraphs.iterator_at(epit);
713 while (bpit < epit) {
714 tie(par, send) = makeAny(text, buf, xs, runparams, par, send, pend);
715 bpit += distance(lastStartedPar, par);
722 void outputDocBookInfo(Text const &text,
725 OutputParams const &runparams,
726 ParagraphList const ¶graphs,
727 DocBookInfoTag const &info,
728 pit_type bpitAbstract,
729 pit_type const epitAbstract) {
730 // Consider everything between bpitAbstract and epitAbstract (excluded) as paragraphs for the abstract.
731 // Use bpitAbstract >= epitAbstract to indicate there is no abstract.
733 set<pit_type> shouldBeInInfo;
734 set<pit_type> mustBeInInfo;
737 tie(shouldBeInInfo, mustBeInInfo, bpitInfo, epitInfo) = info;
739 // The abstract must go in <info>.
740 bool hasAbstract = hasAbstractBetween(paragraphs, bpitAbstract, epitAbstract);
741 bool needInfo = !mustBeInInfo.empty() || hasAbstract;
743 // Start the <info> tag if required.
745 xs.startDivision(false);
746 xs << xml::StartTag("info");
750 // Output the elements that should go in <info>.
751 generateDocBookParagraphWithoutSectioning(text, buf, xs, runparams, paragraphs, bpitInfo, epitInfo);
754 string tag = paragraphs[bpitAbstract].layout().docbookforceabstracttag();
758 xs << xml::StartTag(tag);
760 xs.startDivision(false);
761 generateDocBookParagraphWithoutSectioning(text, buf, xs, runparams, paragraphs, bpitAbstract, epitAbstract);
763 xs << xml::EndTag(tag);
767 // End the <info> tag if it was started.
769 xs << xml::EndTag("info");
776 void docbookFirstParagraphs(Text const &text,
779 OutputParams const &runparams,
781 // Handle the beginning of the document, supposing it has sections.
782 // Major role: output the first <info> tag.
784 ParagraphList const ¶graphs = text.paragraphs();
785 pit_type bpit = runparams.par_begin;
786 DocBookInfoTag info = getParagraphsWithInfo(paragraphs, bpit, epit);
787 outputDocBookInfo(text, buf, xs, runparams, paragraphs, info, get<3>(info), epit);
791 bool isParagraphEmpty(const Paragraph &par)
793 InsetList const &insets = par.insetList();
794 size_t insetsLength = distance(insets.begin(), insets.end());
795 bool hasParagraphOnlyNote = insetsLength == 1 && insets.get(0) && insets.get(0)->asInsetCollapsible() &&
796 dynamic_cast<InsetNote *>(insets.get(0));
797 return hasParagraphOnlyNote;
801 void docbookSimpleAllParagraphs(Text const &text,
804 OutputParams const &runparams) {
805 // Handle the document, supposing it has no sections (i.e. a "simple" document).
807 // First, the <info> tag.
808 ParagraphList const ¶graphs = text.paragraphs();
809 pit_type bpit = runparams.par_begin;
810 pit_type const epit = runparams.par_end;
811 DocBookInfoTag info = getParagraphsWithInfo(paragraphs, bpit, epit);
812 outputDocBookInfo(text, buf, xs, runparams, paragraphs, info, 0, 0);
813 bpit = get<3>(info); // Generate the content starting from the end of the <info> part.
815 // Then, the content.
816 ParagraphList::const_iterator const pend =
817 (epit == (int) paragraphs.size()) ?
818 paragraphs.end() : paragraphs.iterator_at(epit);
820 while (bpit < epit) {
821 auto par = paragraphs.iterator_at(bpit);
822 ParagraphList::const_iterator const lastStartedPar = par;
823 ParagraphList::const_iterator send;
825 if (isParagraphEmpty(*par)) {
827 bpit += distance(lastStartedPar, par);
831 // Generate this paragraph.
832 tie(par, send) = makeAny(text, buf, xs, runparams, par, send, pend);
833 bpit += distance(lastStartedPar, par);
838 void docbookParagraphs(Text const &text,
841 OutputParams const &runparams) {
842 ParagraphList const ¶graphs = text.paragraphs();
843 if (runparams.par_begin == runparams.par_end) {
844 runparams.par_begin = 0;
845 runparams.par_end = paragraphs.size();
847 pit_type bpit = runparams.par_begin;
848 pit_type const epit = runparams.par_end;
851 xs << XMLStream::ESCAPE_NONE << "<!-- DocBook output error! -->\n";
855 ParagraphList::const_iterator const pend =
856 (epit == (int) paragraphs.size()) ?
857 paragraphs.end() : paragraphs.iterator_at(epit);
858 std::stack<std::pair<int, string>> headerLevels; // Used to determine when to open/close sections: store the depth
859 // of the section and the tag that was used to open it.
861 // Detect whether the document contains sections. If there are no sections, there can be no automatically
862 // discovered abstract.
863 bool documentHasSections;
865 tie(documentHasSections, eppit) = hasDocumentSectioning(paragraphs, bpit, epit);
867 if (documentHasSections) {
868 docbookFirstParagraphs(text, buf, xs, runparams, eppit);
871 docbookSimpleAllParagraphs(text, buf, xs, runparams);
875 bool currentlyInAppendix = false;
877 while (bpit < epit) {
878 OutputParams ourparams = runparams;
880 auto par = paragraphs.iterator_at(bpit);
881 if (par->params().startOfAppendix())
882 currentlyInAppendix = true;
883 Layout const &style = par->layout();
884 ParagraphList::const_iterator const lastStartedPar = par;
885 ParagraphList::const_iterator send;
887 if (isParagraphEmpty(*par)) {
889 bpit += distance(lastStartedPar, par);
893 // Think about adding <section> and/or </section>s.
894 const bool isLayoutSectioning = style.category() == from_utf8("Sectioning");
895 if (isLayoutSectioning) {
896 int level = style.toclevel;
898 // Need to close a previous section if it has the same level or a higher one (close <section> if opening a <h2>
899 // after a <h2>, <h3>, <h4>, <h5> or <h6>). More examples:
900 // - current: h2; back: h1; do not close any <section>
901 // - current: h1; back: h2; close two <section> (first the <h2>, then the <h1>, so a new <h1> can come)
902 while (!headerLevels.empty() && level <= headerLevels.top().first) {
903 int stackLevel = headerLevels.top().first;
904 docstring stackTag = from_utf8("</" + headerLevels.top().second + ">");
907 // Output the tag only if it corresponds to a legit section.
908 if (stackLevel != Layout::NOT_IN_TOC)
909 xs << XMLStream::ESCAPE_NONE << stackTag << xml::CR();
912 // Open the new section: first push it onto the stack, then output it in DocBook.
913 string sectionTag = (currentlyInAppendix && style.docbooksectiontag() == "chapter") ?
914 "appendix" : style.docbooksectiontag();
915 headerLevels.push(std::make_pair(level, sectionTag));
917 // Some sectioning-like elements should not be output (such as FrontMatter).
918 if (level != Layout::NOT_IN_TOC) {
919 // Look for a label in the title, i.e. a InsetLabel as a child.
920 docstring id = docstring();
921 for (pos_type i = 0; i < par->size(); ++i) {
922 Inset const *inset = par->getInset(i);
923 if (inset && dynamic_cast<InsetLabel const *>(inset)) {
924 // Generate the attributes for the section if need be.
925 auto label = dynamic_cast<InsetLabel const *>(inset);
926 id += "xml:id=\"" + xml::cleanID(label->screenLabel()) + "\"";
928 // Don't output the ID as a DocBook <anchor>.
929 ourparams.docbook_anchors_to_ignore.emplace(label->screenLabel());
931 // Cannot have multiple IDs per tag.
936 // Write the open tag for this section.
937 docstring tag = from_utf8("<" + sectionTag);
939 tag += from_utf8(" ") + id;
940 tag += from_utf8(">");
941 xs << XMLStream::ESCAPE_NONE << tag;
946 // Close all sections before the bibliography.
947 // 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)?
948 auto insetsLength = distance(par->insetList().begin(), par->insetList().end());
949 if (insetsLength > 0) {
950 Inset const *firstInset = par->getInset(0);
951 if (firstInset && dynamic_cast<InsetBibtex const *>(firstInset)) {
952 while (!headerLevels.empty()) {
953 int level = headerLevels.top().first;
954 docstring tag = from_utf8("</" + headerLevels.top().second + ">");
957 // Output the tag only if it corresponds to a legit section.
958 if (level != Layout::NOT_IN_TOC) {
959 xs << XMLStream::ESCAPE_NONE << tag;
966 // Generate this paragraph.
967 tie(par, send) = makeAny(text, buf, xs, ourparams, par, send, pend);
968 bpit += distance(lastStartedPar, par);
971 // If need be, close <section>s, but only at the end of the document (otherwise, dealt with at the beginning
973 while (!headerLevels.empty() && headerLevels.top().first > Layout::NOT_IN_TOC) {
974 docstring tag = from_utf8("</" + headerLevels.top().second + ">");
976 xs << XMLStream::ESCAPE_NONE << tag;