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"
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>
107 case xml::FontTypes::FT_UBAR:
110 // All other roles are non-standard for DocBook.
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());
264 // end of convenience functions
266 ParagraphList::const_iterator findLastParagraph(
267 ParagraphList::const_iterator p,
268 ParagraphList::const_iterator const & pend) {
269 for (++p; p != pend && p->layout().latextype == LATEX_PARAGRAPH; ++p);
275 ParagraphList::const_iterator findEndOfEnvironment(
276 ParagraphList::const_iterator const & pstart,
277 ParagraphList::const_iterator const & pend)
279 ParagraphList::const_iterator p = pstart;
280 Layout const &bstyle = p->layout();
281 size_t const depth = p->params().depth();
282 for (++p; p != pend; ++p) {
283 Layout const &style = p->layout();
284 // It shouldn't happen that e.g. a section command occurs inside
285 // a quotation environment, at a higher depth, but as of 6/2009,
286 // it can happen. We pretend that it's just at lowest depth.
287 if (style.latextype == LATEX_COMMAND)
290 // If depth is down, we're done
291 if (p->params().depth() < depth)
294 // If depth is up, we're not done
295 if (p->params().depth() > depth)
298 // FIXME I am not sure about the first check.
299 // Surely we *could* have different layouts that count as
300 // LATEX_PARAGRAPH, right?
301 if (style.latextype == LATEX_PARAGRAPH || style != bstyle)
308 ParagraphList::const_iterator makeParagraphBibliography(
311 OutputParams const &runparams,
313 ParagraphList::const_iterator const & pbegin,
314 ParagraphList::const_iterator const & pend)
316 auto const begin = text.paragraphs().begin();
317 auto const end = text.paragraphs().end();
319 // Find the paragraph *before* pbegin.
320 ParagraphList::const_iterator pbegin_before = begin;
321 if (pbegin != begin) {
322 ParagraphList::const_iterator pbegin_before_next = begin;
323 ++pbegin_before_next;
325 while (pbegin_before_next != pbegin) {
327 ++pbegin_before_next;
331 ParagraphList::const_iterator par = pbegin;
333 // If this is the first paragraph in a bibliography, open the bibliography tag.
334 if (pbegin != begin && pbegin_before->layout().latextype != LATEX_BIB_ENVIRONMENT) {
335 xs << xml::StartTag("bibliography");
339 // Generate the required paragraphs.
340 for (; par != pend; ++par) {
341 // Start the precooked bibliography entry. This is very much like opening a paragraph tag.
342 // Don't forget the citation ID!
344 for (auto i = 0; i < par->size(); ++i) {
345 if (par->getInset(0)->lyxCode() == BIBITEM_CODE) {
346 const auto * bibitem = dynamic_cast<const InsetBibitem*>(par->getInset(i));
347 attr = from_utf8("xml:id='") + bibitem->bibLabel() + from_utf8("'");
351 xs << xml::StartTag(from_utf8("bibliomixed"), attr);
353 // Generate the entry.
354 par->simpleDocBookOnePar(buf, xs, runparams, text.outerFont(distance(begin, par)), true, true, 0);
356 // End the precooked bibliography entry.
357 xs << xml::EndTag("bibliomixed");
361 // If this is the last paragraph in a bibliography, close the bibliography tag.
362 if (par == end || par->layout().latextype != LATEX_BIB_ENVIRONMENT) {
363 xs << xml::EndTag("bibliography");
371 ParagraphList::const_iterator makeParagraphs(
374 OutputParams const &runparams,
376 ParagraphList::const_iterator const & pbegin,
377 ParagraphList::const_iterator const & pend)
379 ParagraphList::const_iterator const begin = text.paragraphs().begin();
380 ParagraphList::const_iterator par = pbegin;
381 for (; par != pend; ++par) {
382 Layout const &lay = par->layout();
384 // We want to open the paragraph tag if:
385 // (i) the current layout permits multiple paragraphs
386 // (ii) we are either not already inside a paragraph (HTMLIsBlock) OR
387 // we are, but this is not the first paragraph
389 // But there is also a special case, and we first see whether we are in it.
390 // We do not want to open the paragraph tag if this paragraph contains
391 // only one item, and that item is "inline", i.e., not HTMLIsBlock (such
392 // as a branch). On the other hand, if that single item has a font change
393 // applied to it, then we still do need to open the paragraph.
395 // Obviously, this is very fragile. The main reason we need to do this is
396 // because of branches, e.g., a branch that contains an entire new section.
397 // We do not really want to wrap that whole thing in a <div>...</div>.
398 bool special_case = false;
399 Inset const *specinset = par->size() == 1 ? par->getInset(0) : 0;
400 if (specinset && !specinset->getLayout().htmlisblock()) { // TODO: Convert htmlisblock to a DocBook parameter?
401 Layout const &style = par->layout();
402 FontInfo const first_font = style.labeltype == LABEL_MANUAL ?
403 style.labelfont : style.font;
404 FontInfo const our_font =
405 par->getFont(buf.masterBuffer()->params(), 0,
406 text.outerFont(distance(begin, par))).fontInfo();
408 if (first_font == our_font)
412 // Plain layouts must be ignored.
413 if (!special_case && buf.params().documentClass().isPlainLayout(lay) && !runparams.docbook_force_pars)
415 // TODO: Could get rid of this with a DocBook equivalent to htmlisblock?
416 if (!special_case && par->size() == 1 && par->getInset(0)) {
417 Inset const * firstInset = par->getInset(0);
419 // Floats cannot be in paragraphs.
420 special_case = to_ascii(firstInset->layoutName()).substr(0, 6) == "Float:";
422 // Bibliographies cannot be in paragraphs.
423 if (!special_case && firstInset->asInsetCommand())
424 special_case = firstInset->asInsetCommand()->params().getCmdName() == "bibtex";
426 // Equations do not deserve their own paragraph (DocBook allows them outside paragraphs).
427 if (!special_case && firstInset->asInsetMath())
430 // ERTs are in comments, not paragraphs.
431 if (!special_case && firstInset->lyxCode() == lyx::ERT_CODE)
434 // Listings should not get into their own paragraph.
435 if (!special_case && firstInset->lyxCode() == lyx::LISTINGS_CODE)
439 bool const open_par = runparams.docbook_make_pars
440 && (!runparams.docbook_in_par || par != pbegin)
443 // We want to issue the closing tag if either:
444 // (i) We opened it, and either docbook_in_par is false,
445 // or we're not in the last paragraph, anyway.
446 // (ii) We didn't open it and docbook_in_par is true,
447 // but we are in the first par, and there is a next par.
448 ParagraphList::const_iterator nextpar = par;
450 bool const close_par =
451 ((open_par && (!runparams.docbook_in_par || nextpar != pend))
452 || (!open_par && runparams.docbook_in_par && par == pbegin && nextpar != pend));
458 par->simpleDocBookOnePar(buf, xs, runparams, text.outerFont(distance(begin, par)), open_par, close_par, 0);
469 bool isNormalEnv(Layout const &lay)
471 return lay.latextype == LATEX_ENVIRONMENT
472 || lay.latextype == LATEX_BIB_ENVIRONMENT;
476 ParagraphList::const_iterator makeEnvironment(
479 OutputParams const &runparams,
481 ParagraphList::const_iterator const & pbegin,
482 ParagraphList::const_iterator const & pend)
484 ParagraphList::const_iterator const begin = text.paragraphs().begin();
485 ParagraphList::const_iterator par = pbegin;
486 Layout const &bstyle = par->layout();
487 depth_type const origdepth = pbegin->params().depth();
489 // open tag for this environment
490 openParTag(xs, bstyle);
493 // we will on occasion need to remember a layout from before.
494 Layout const *lastlay = nullptr;
496 while (par != pend) {
497 Layout const & style = par->layout();
498 ParagraphList::const_iterator send;
500 // Actual content of this paragraph.
501 switch (style.latextype) {
502 case LATEX_ENVIRONMENT:
503 case LATEX_LIST_ENVIRONMENT:
504 case LATEX_ITEM_ENVIRONMENT: {
505 // There are two possibilities in this case.
506 // One is that we are still in the environment in which we
507 // started---which we will be if the depth is the same.
508 if (par->params().depth() == origdepth) {
509 LATTEST(bstyle == style);
510 if (lastlay != nullptr) {
511 closeItemTag(xs, *lastlay);
512 if (lastlay->docbookitemwrappertag() != "NONE") {
513 xs << xml::EndTag(lastlay->docbookitemwrappertag());
519 // this will be positive if we want to skip the
520 // initial word (if it's been taken for the label).
523 // Open a wrapper tag if needed.
524 if (style.docbookitemwrappertag() != "NONE") {
525 xs << xml::StartTag(style.docbookitemwrappertag(), style.docbookitemwrapperattr());
530 if (style.labeltype != LABEL_NO_LABEL &&
531 style.docbookitemlabeltag() != "NONE") {
533 if (isNormalEnv(style)) {
534 // in this case, we print the label only for the first
535 // paragraph (as in a theorem or an abstract).
537 docstring const lbl = pbegin->params().labelString();
539 openLabelTag(xs, style);
541 closeLabelTag(xs, style);
543 // No new line after closeLabelTag.
547 } else { // some kind of list
548 if (style.labeltype == LABEL_MANUAL) {
549 // Only variablelist gets here.
551 openLabelTag(xs, style);
552 sep = par->firstWordDocBook(xs, runparams);
553 closeLabelTag(xs, style);
555 openLabelTag(xs, style);
556 xs << par->params().labelString();
557 closeLabelTag(xs, style);
560 } // end label output
562 // Start generating the item.
563 bool wasInParagraph = runparams.docbook_in_par;
564 openItemTag(xs, style);
565 bool getsIntoParagraph = openInnerItemTag(xs, style);
566 OutputParams rp = runparams;
567 rp.docbook_in_par = wasInParagraph | getsIntoParagraph;
569 // Maybe the item is completely empty, i.e. if the first word ends at the end of the current paragraph
570 // AND if the next paragraph doesn't have the same depth (if there is such a paragraph).
571 // Common case: there is only the first word on the line, but there is a nested list instead
573 bool emptyItem = false;
574 if (sep == par->size()) {
577 if (next_par == text.paragraphs().end()) // There is no next paragraph.
579 else // There is a next paragraph: check depth.
580 emptyItem = par->params().depth() >= next_par->params().depth();
584 // Avoid having an empty item, this is not valid DocBook. A single character is enough to force
585 // generation of a full <para>.
588 // Generate the rest of the paragraph, if need be.
589 par->simpleDocBookOnePar(buf, xs, rp, text.outerFont(distance(begin, par)), true, true, sep);
593 if (getsIntoParagraph)
594 closeInnerItemTag(xs, style);
596 // We may not want to close the tag yet, in particular:
597 // If we're not at the end of the item...
599 // and are doing items...
600 && !isNormalEnv(style)
601 // and if the depth has changed...
602 && par->params().depth() != origdepth) {
603 // then we'll save this layout for later, and close it when
604 // we get another item.
607 closeItemTag(xs, style);
609 // Eventually, close the item wrapper.
610 if (style.docbookitemwrappertag() != "NONE") {
611 xs << xml::EndTag(style.docbookitemwrappertag());
616 // The other possibility is that the depth has increased.
618 send = findEndOfEnvironment(par, pend);
619 par = makeEnvironment(buf, xs, runparams, text, par, send);
623 case LATEX_PARAGRAPH:
624 send = findLastParagraph(par, pend);
625 par = makeParagraphs(buf, xs, runparams, text, par, send);
627 case LATEX_BIB_ENVIRONMENT:
628 send = findLastParagraph(par, pend);
629 par = makeParagraphBibliography(buf, xs, runparams, text, par, send);
637 if (lastlay != nullptr) {
638 closeItemTag(xs, *lastlay);
639 if (lastlay->docbookitemwrappertag() != "NONE") {
640 xs << xml::EndTag(lastlay->docbookitemwrappertag());
644 closeTag(xs, bstyle);
653 OutputParams const & runparams,
655 ParagraphList::const_iterator const & pbegin)
657 Layout const &style = pbegin->layout();
659 // No need for labels, as they are handled by DocBook tags.
661 openParTag(xs, style);
663 ParagraphList::const_iterator const begin = text.paragraphs().begin();
664 pbegin->simpleDocBookOnePar(buf, xs, runparams,
665 text.outerFont(distance(begin, pbegin)));
670 pair<ParagraphList::const_iterator, ParagraphList::const_iterator> makeAny(
674 OutputParams const &ourparams,
675 ParagraphList::const_iterator par,
676 ParagraphList::const_iterator send,
677 ParagraphList::const_iterator pend)
679 Layout const & style = par->layout();
681 switch (style.latextype) {
682 case LATEX_COMMAND: {
683 // The files with which we are working never have more than
684 // one paragraph in a command structure.
686 // if (ourparams.docbook_in_par)
687 // fix it so we don't get sections inside standard, e.g.
688 // note that we may then need to make runparams not const, so we
689 // can communicate that back.
690 // FIXME Maybe this fix should be in the routines themselves, in case
691 // they are called from elsewhere.
692 makeCommand(buf, xs, ourparams, text, par);
696 case LATEX_ENVIRONMENT:
697 case LATEX_LIST_ENVIRONMENT:
698 case LATEX_ITEM_ENVIRONMENT: {
699 // FIXME Same fix here.
700 send = findEndOfEnvironment(par, pend);
701 par = makeEnvironment(buf, xs, ourparams, text, par, send);
704 case LATEX_BIB_ENVIRONMENT: {
705 send = findLastParagraph(par, pend);
706 par = makeParagraphBibliography(buf, xs, ourparams, text, par, send);
709 case LATEX_PARAGRAPH: {
710 send = findLastParagraph(par, pend);
711 par = makeParagraphs(buf, xs, ourparams, text, par, send);
716 return make_pair(par, send);
719 } // end anonymous namespace
722 using DocBookDocumentSectioning = tuple<bool, pit_type>;
723 using DocBookInfoTag = tuple<set<pit_type>, set<pit_type>, pit_type, pit_type>;
726 DocBookDocumentSectioning hasDocumentSectioning(ParagraphList const ¶graphs, pit_type bpit, pit_type const epit) {
727 bool documentHasSections = false;
729 while (bpit < epit) {
730 Layout const &style = paragraphs[bpit].layout();
731 documentHasSections |= style.category() == from_utf8("Sectioning");
733 if (documentHasSections) {
738 // Paragraphs before the first section: [ runparams.par_begin ; eppit )
740 return make_tuple(documentHasSections, bpit);
744 DocBookInfoTag getParagraphsWithInfo(ParagraphList const ¶graphs, pit_type const bpit, pit_type const epit) {
745 set<pit_type> shouldBeInInfo;
746 set<pit_type> mustBeInInfo;
748 pit_type cpit = bpit;
749 while (cpit < epit) {
750 // Skip paragraphs only containing one note.
751 Paragraph const &par = paragraphs[cpit];
752 if (par.size() == 1 && dynamic_cast<InsetNote*>(paragraphs[cpit].insetList().get(0))) {
757 // Based on layout information, store this paragraph in one set: should be in <info>, must be.
758 Layout const &style = par.layout();
760 if (style.docbookininfo() == "always") {
761 mustBeInInfo.emplace(cpit);
762 } else if (style.docbookininfo() == "maybe") {
763 shouldBeInInfo.emplace(cpit);
765 // Hypothesis: the <info> parts should be grouped together near the beginning bpit.
770 // Now, cpit points to the last paragraph that has things that could go in <info>.
771 // bpit is still the beginning of the <info> part.
773 return make_tuple(shouldBeInInfo, mustBeInInfo, bpit, cpit);
777 bool hasAbstractBetween(ParagraphList const ¶graphs, pit_type const bpitAbstract, pit_type const epitAbstract)
779 // Hypothesis: the paragraphs between bpitAbstract and epitAbstract can be considered an abstract because they
780 // are just after a document or part title.
781 if (epitAbstract - bpitAbstract <= 0)
784 // If there is something between these paragraphs, check if it's compatible with an abstract (i.e. some text).
785 pit_type bpit = bpitAbstract;
786 while (bpit < epitAbstract) {
787 const Paragraph &p = paragraphs.at(bpit);
789 if (p.layout().name() == from_ascii("Abstract"))
792 if (!p.insetList().empty()) {
793 for (const auto &i : p.insetList()) {
794 if (i.inset->getText(0) != nullptr) {
805 pit_type generateDocBookParagraphWithoutSectioning(
809 OutputParams const & runparams,
810 ParagraphList const & paragraphs,
814 auto par = paragraphs.iterator_at(bpit);
815 auto lastStartedPar = par;
816 ParagraphList::const_iterator send;
818 (epit == (int) paragraphs.size()) ?
819 paragraphs.end() : paragraphs.iterator_at(epit);
821 while (bpit < epit) {
822 tie(par, send) = makeAny(text, buf, xs, runparams, par, send, pend);
823 bpit += distance(lastStartedPar, par);
824 lastStartedPar = par;
831 void outputDocBookInfo(
835 OutputParams const & runparams,
836 ParagraphList const & paragraphs,
837 DocBookInfoTag const & info,
838 pit_type bpitAbstract,
839 pit_type const epitAbstract)
841 // Consider everything between bpitAbstract and epitAbstract (excluded) as paragraphs for the abstract.
842 // Use bpitAbstract >= epitAbstract to indicate there is no abstract.
844 set<pit_type> shouldBeInInfo;
845 set<pit_type> mustBeInInfo;
848 tie(shouldBeInInfo, mustBeInInfo, bpitInfo, epitInfo) = info;
850 // The abstract must go in <info>.
851 bool hasAbstract = hasAbstractBetween(paragraphs, bpitAbstract, epitAbstract);
852 bool needInfo = !mustBeInInfo.empty() || hasAbstract;
854 // Start the <info> tag if required.
856 xs.startDivision(false);
857 xs << xml::StartTag("info");
861 // Output the elements that should go in <info>.
862 generateDocBookParagraphWithoutSectioning(text, buf, xs, runparams, paragraphs, bpitInfo, epitInfo);
865 string tag = paragraphs[bpitAbstract].layout().docbookforceabstracttag();
869 xs << xml::StartTag(tag);
871 xs.startDivision(false);
872 generateDocBookParagraphWithoutSectioning(text, buf, xs, runparams, paragraphs, bpitAbstract, epitAbstract);
874 xs << xml::EndTag(tag);
878 // End the <info> tag if it was started.
880 xs << xml::EndTag("info");
887 void docbookFirstParagraphs(
891 OutputParams const &runparams,
894 // Handle the beginning of the document, supposing it has sections.
895 // Major role: output the first <info> tag.
897 ParagraphList const ¶graphs = text.paragraphs();
898 pit_type bpit = runparams.par_begin;
899 DocBookInfoTag info = getParagraphsWithInfo(paragraphs, bpit, epit);
900 outputDocBookInfo(text, buf, xs, runparams, paragraphs, info, get<3>(info), epit);
904 bool isParagraphEmpty(const Paragraph &par)
906 InsetList const &insets = par.insetList();
907 size_t insetsLength = distance(insets.begin(), insets.end());
908 bool hasParagraphOnlyNote = insetsLength == 1 && insets.get(0) && insets.get(0)->asInsetCollapsible() &&
909 dynamic_cast<InsetNote *>(insets.get(0));
910 return hasParagraphOnlyNote;
914 void docbookSimpleAllParagraphs(
918 OutputParams const & runparams)
920 // Handle the document, supposing it has no sections (i.e. a "simple" document).
922 // First, the <info> tag.
923 ParagraphList const ¶graphs = text.paragraphs();
924 pit_type bpit = runparams.par_begin;
925 pit_type const epit = runparams.par_end;
926 DocBookInfoTag info = getParagraphsWithInfo(paragraphs, bpit, epit);
927 outputDocBookInfo(text, buf, xs, runparams, paragraphs, info, 0, 0);
928 bpit = get<3>(info); // Generate the content starting from the end of the <info> part.
930 // Then, the content.
931 ParagraphList::const_iterator const pend =
932 (epit == (int) paragraphs.size()) ?
933 paragraphs.end() : paragraphs.iterator_at(epit);
935 while (bpit < epit) {
936 auto par = paragraphs.iterator_at(bpit);
937 ParagraphList::const_iterator const lastStartedPar = par;
938 ParagraphList::const_iterator send;
940 if (isParagraphEmpty(*par)) {
942 bpit += distance(lastStartedPar, par);
946 // Generate this paragraph.
947 tie(par, send) = makeAny(text, buf, xs, runparams, par, send, pend);
948 bpit += distance(lastStartedPar, par);
953 void docbookParagraphs(Text const &text,
956 OutputParams const &runparams) {
957 ParagraphList const ¶graphs = text.paragraphs();
958 if (runparams.par_begin == runparams.par_end) {
959 runparams.par_begin = 0;
960 runparams.par_end = paragraphs.size();
962 pit_type bpit = runparams.par_begin;
963 pit_type const epit = runparams.par_end;
966 xs << XMLStream::ESCAPE_NONE << "<!-- DocBook output error! -->\n";
970 ParagraphList::const_iterator const pend =
971 (epit == (int) paragraphs.size()) ?
972 paragraphs.end() : paragraphs.iterator_at(epit);
973 std::stack<std::pair<int, string>> headerLevels; // Used to determine when to open/close sections: store the depth
974 // of the section and the tag that was used to open it.
976 // Detect whether the document contains sections. If there are no sections, there can be no automatically
977 // discovered abstract.
978 bool documentHasSections;
980 tie(documentHasSections, eppit) = hasDocumentSectioning(paragraphs, bpit, epit);
982 if (documentHasSections) {
983 docbookFirstParagraphs(text, buf, xs, runparams, eppit);
986 docbookSimpleAllParagraphs(text, buf, xs, runparams);
990 bool currentlyInAppendix = false;
992 while (bpit < epit) {
993 OutputParams ourparams = runparams;
995 auto par = paragraphs.iterator_at(bpit);
996 if (par->params().startOfAppendix())
997 currentlyInAppendix = true;
998 Layout const &style = par->layout();
999 ParagraphList::const_iterator const lastStartedPar = par;
1000 ParagraphList::const_iterator send;
1002 if (isParagraphEmpty(*par)) {
1004 bpit += distance(lastStartedPar, par);
1008 // Think about adding <section> and/or </section>s.
1009 const bool isLayoutSectioning = style.category() == from_utf8("Sectioning");
1010 if (isLayoutSectioning) {
1011 int level = style.toclevel;
1013 // Need to close a previous section if it has the same level or a higher one (close <section> if opening a <h2>
1014 // after a <h2>, <h3>, <h4>, <h5> or <h6>). More examples:
1015 // - current: h2; back: h1; do not close any <section>
1016 // - current: h1; back: h2; close two <section> (first the <h2>, then the <h1>, so a new <h1> can come)
1017 while (!headerLevels.empty() && level <= headerLevels.top().first) {
1018 int stackLevel = headerLevels.top().first;
1019 docstring stackTag = from_utf8("</" + headerLevels.top().second + ">");
1022 // Output the tag only if it corresponds to a legit section.
1023 if (stackLevel != Layout::NOT_IN_TOC)
1024 xs << XMLStream::ESCAPE_NONE << stackTag << xml::CR();
1027 // Open the new section: first push it onto the stack, then output it in DocBook.
1028 string sectionTag = (currentlyInAppendix && style.docbooksectiontag() == "chapter") ?
1029 "appendix" : style.docbooksectiontag();
1030 headerLevels.push(std::make_pair(level, sectionTag));
1032 // Some sectioning-like elements should not be output (such as FrontMatter).
1033 if (level != Layout::NOT_IN_TOC) {
1034 // Look for a label in the title, i.e. a InsetLabel as a child.
1035 docstring id = docstring();
1036 for (pos_type i = 0; i < par->size(); ++i) {
1037 Inset const *inset = par->getInset(i);
1039 if (auto label = dynamic_cast<InsetLabel const *>(inset)) {
1040 // Generate the attributes for the section if need be.
1041 id += "xml:id=\"" + xml::cleanID(label->screenLabel()) + "\"";
1043 // Don't output the ID as a DocBook <anchor>.
1044 ourparams.docbook_anchors_to_ignore.emplace(label->screenLabel());
1046 // Cannot have multiple IDs per tag.
1052 // Write the open tag for this section.
1053 docstring tag = from_utf8("<" + sectionTag);
1055 tag += from_utf8(" ") + id;
1056 tag += from_utf8(">");
1057 xs << XMLStream::ESCAPE_NONE << tag;
1062 // Close all sections before the bibliography.
1063 // 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)?
1064 auto insetsLength = distance(par->insetList().begin(), par->insetList().end());
1065 if (insetsLength > 0) {
1066 Inset const *firstInset = par->getInset(0);
1067 if (firstInset && dynamic_cast<InsetBibtex const *>(firstInset)) {
1068 while (!headerLevels.empty()) {
1069 int level = headerLevels.top().first;
1070 docstring tag = from_utf8("</" + headerLevels.top().second + ">");
1073 // Output the tag only if it corresponds to a legit section.
1074 if (level != Layout::NOT_IN_TOC) {
1075 xs << XMLStream::ESCAPE_NONE << tag;
1082 // Generate this paragraph.
1083 tie(par, send) = makeAny(text, buf, xs, ourparams, par, send, pend);
1084 bpit += distance(lastStartedPar, par);
1087 // If need be, close <section>s, but only at the end of the document (otherwise, dealt with at the beginning
1089 while (!headerLevels.empty() && headerLevels.top().first > Layout::NOT_IN_TOC) {
1090 docstring tag = from_utf8("</" + headerLevels.top().second + ">");
1092 xs << XMLStream::ESCAPE_NONE << tag;