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()) << 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 makeParagraphBibliography(
310 OutputParams const &runparams,
312 ParagraphList::const_iterator const & pbegin,
313 ParagraphList::const_iterator const & pend)
315 auto const begin = text.paragraphs().begin();
316 auto const end = text.paragraphs().end();
318 // Find the paragraph *before* pbegin.
319 ParagraphList::const_iterator pbegin_before = begin;
320 if (pbegin != begin) {
321 ParagraphList::const_iterator pbegin_before_next = begin;
322 ++pbegin_before_next;
324 while (pbegin_before_next != pbegin) {
326 ++pbegin_before_next;
330 ParagraphList::const_iterator par = pbegin;
332 // If this is the first paragraph in a bibliography, open the bibliography tag.
333 if (pbegin != begin && pbegin_before->layout().latextype != LATEX_BIB_ENVIRONMENT) {
334 xs << xml::StartTag("bibliography");
338 // Generate the required paragraphs.
339 for (; par != pend; ++par) {
340 // Start the precooked bibliography entry. This is very much like opening a paragraph tag.
341 // Don't forget the citation ID!
343 for (auto i = 0; i < par->size(); ++i) {
344 if (par->getInset(0)->lyxCode() == BIBITEM_CODE) {
345 const auto * bibitem = dynamic_cast<const InsetBibitem*>(par->getInset(i));
346 attr = from_utf8("xml:id='") + bibitem->bibLabel() + from_utf8("'");
350 xs << xml::StartTag(from_utf8("bibliomixed"), attr);
352 // Generate the entry.
353 par->simpleDocBookOnePar(buf, xs, runparams, text.outerFont(distance(begin, par)), true, true, 0);
355 // End the precooked bibliography entry.
356 xs << xml::EndTag("bibliomixed");
360 // If this is the last paragraph in a bibliography, close the bibliography tag.
361 if (par == end || par->layout().latextype != LATEX_BIB_ENVIRONMENT) {
362 xs << xml::EndTag("bibliography");
370 ParagraphList::const_iterator makeParagraphs(
373 OutputParams const &runparams,
375 ParagraphList::const_iterator const & pbegin,
376 ParagraphList::const_iterator const & pend)
378 ParagraphList::const_iterator const begin = text.paragraphs().begin();
379 ParagraphList::const_iterator par = pbegin;
380 for (; par != pend; ++par) {
381 Layout const &lay = par->layout();
383 // We want to open the paragraph tag if:
384 // (i) the current layout permits multiple paragraphs
385 // (ii) we are either not already inside a paragraph (HTMLIsBlock) OR
386 // we are, but this is not the first paragraph
388 // But there is also a special case, and we first see whether we are in it.
389 // We do not want to open the paragraph tag if this paragraph contains
390 // only one item, and that item is "inline", i.e., not HTMLIsBlock (such
391 // as a branch). On the other hand, if that single item has a font change
392 // applied to it, then we still do need to open the paragraph.
394 // Obviously, this is very fragile. The main reason we need to do this is
395 // because of branches, e.g., a branch that contains an entire new section.
396 // We do not really want to wrap that whole thing in a <div>...</div>.
397 bool special_case = false;
398 Inset const *specinset = par->size() == 1 ? par->getInset(0) : 0;
399 if (specinset && !specinset->getLayout().htmlisblock()) { // TODO: Convert htmlisblock to a DocBook parameter?
400 Layout const &style = par->layout();
401 FontInfo const first_font = style.labeltype == LABEL_MANUAL ?
402 style.labelfont : style.font;
403 FontInfo const our_font =
404 par->getFont(buf.masterBuffer()->params(), 0,
405 text.outerFont(distance(begin, par))).fontInfo();
407 if (first_font == our_font)
411 // Plain layouts must be ignored.
412 if (!special_case && buf.params().documentClass().isPlainLayout(lay) && !runparams.docbook_force_pars)
414 // TODO: Could get rid of this with a DocBook equivalent to htmlisblock?
415 if (!special_case && par->size() == 1 && par->getInset(0)) {
416 Inset const * firstInset = par->getInset(0);
418 // Floats cannot be in paragraphs.
419 special_case = to_ascii(firstInset->layoutName()).substr(0, 6) == "Float:";
421 // Bibliographies cannot be in paragraphs.
422 if (!special_case && firstInset->asInsetCommand())
423 special_case = firstInset->asInsetCommand()->params().getCmdName() == "bibtex";
425 // Equations do not deserve their own paragraph (DocBook allows them outside paragraphs).
426 if (!special_case && firstInset->asInsetMath())
429 // ERTs are in comments, not paragraphs.
430 if (!special_case && firstInset->lyxCode() == lyx::ERT_CODE)
433 // Listings should not get into their own paragraph.
434 if (!special_case && firstInset->lyxCode() == lyx::LISTINGS_CODE)
438 bool const open_par = runparams.docbook_make_pars
439 && (!runparams.docbook_in_par || par != pbegin)
442 // We want to issue the closing tag if either:
443 // (i) We opened it, and either docbook_in_par is false,
444 // or we're not in the last paragraph, anyway.
445 // (ii) We didn't open it and docbook_in_par is true,
446 // but we are in the first par, and there is a next par.
447 ParagraphList::const_iterator nextpar = par;
449 bool const close_par =
450 ((open_par && (!runparams.docbook_in_par || nextpar != pend))
451 || (!open_par && runparams.docbook_in_par && par == pbegin && nextpar != pend));
457 par->simpleDocBookOnePar(buf, xs, runparams, text.outerFont(distance(begin, par)), open_par, close_par, 0);
468 bool isNormalEnv(Layout const &lay)
470 return lay.latextype == LATEX_ENVIRONMENT
471 || lay.latextype == LATEX_BIB_ENVIRONMENT;
475 ParagraphList::const_iterator makeEnvironment(
478 OutputParams const &runparams,
480 ParagraphList::const_iterator const & pbegin,
481 ParagraphList::const_iterator const & pend)
483 ParagraphList::const_iterator const begin = text.paragraphs().begin();
484 ParagraphList::const_iterator par = pbegin;
485 Layout const &bstyle = par->layout();
486 depth_type const origdepth = pbegin->params().depth();
488 // open tag for this environment
489 openParTag(xs, bstyle);
492 // we will on occasion need to remember a layout from before.
493 Layout const *lastlay = nullptr;
495 while (par != pend) {
496 Layout const & style = par->layout();
497 ParagraphList::const_iterator send;
499 // Actual content of this paragraph.
500 switch (style.latextype) {
501 case LATEX_ENVIRONMENT:
502 case LATEX_LIST_ENVIRONMENT:
503 case LATEX_ITEM_ENVIRONMENT: {
504 // There are two possibilities in this case.
505 // One is that we are still in the environment in which we
506 // started---which we will be if the depth is the same.
507 if (par->params().depth() == origdepth) {
508 LATTEST(bstyle == style);
509 if (lastlay != nullptr) {
510 closeItemTag(xs, *lastlay);
514 // this will be positive, if we want to skip the
515 // initial word (if it's been taken for the label).
518 // Open a wrapper tag if needed.
519 if (style.docbookitemwrappertag() != "NONE") {
520 xs << xml::StartTag(style.docbookitemwrappertag(), style.docbookitemwrapperattr());
525 if (style.labeltype != LABEL_NO_LABEL &&
526 style.docbookitemlabeltag() != "NONE") {
528 if (isNormalEnv(style)) {
529 // in this case, we print the label only for the first
530 // paragraph (as in a theorem or an abstract).
532 docstring const lbl = pbegin->params().labelString();
534 openLabelTag(xs, style);
536 closeLabelTag(xs, style);
540 } else { // some kind of list
541 if (style.labeltype == LABEL_MANUAL) {
542 // Only variablelist gets here.
544 openLabelTag(xs, style);
545 sep = par->firstWordDocBook(xs, runparams);
546 closeLabelTag(xs, style);
549 openLabelTag(xs, style);
550 xs << par->params().labelString();
551 closeLabelTag(xs, style);
555 } // end label output
557 bool wasInParagraph = runparams.docbook_in_par;
558 openItemTag(xs, style);
559 bool getsIntoParagraph = openInnerItemTag(xs, style);
560 OutputParams rp = runparams;
561 rp.docbook_in_par = wasInParagraph | getsIntoParagraph;
563 par->simpleDocBookOnePar(buf, xs, rp, text.outerFont(distance(begin, par)), true, true, sep);
565 if (getsIntoParagraph)
566 closeInnerItemTag(xs, style);
568 // We may not want to close the tag yet, in particular:
569 // If we're not at the end of the item...
571 // and are doing items...
572 && !isNormalEnv(style)
573 // and if the depth has changed...
574 && par->params().depth() != origdepth) {
575 // then we'll save this layout for later, and close it when
576 // we get another item.
579 closeItemTag(xs, style);
581 // Eventually, close the item wrapper.
582 if (style.docbookitemwrappertag() != "NONE") {
583 xs << xml::EndTag(style.docbookitemwrappertag());
588 // The other possibility is that the depth has increased, in which
589 // case we need to recurse.
591 send = findEndOfEnvironment(par, pend);
592 par = makeEnvironment(buf, xs, runparams, text, par, send);
596 case LATEX_PARAGRAPH:
597 send = findLastParagraph(par, pend);
598 par = makeParagraphs(buf, xs, runparams, text, par, send);
600 case LATEX_BIB_ENVIRONMENT:
601 send = findLastParagraph(par, pend);
602 par = makeParagraphBibliography(buf, xs, runparams, text, par, send);
611 closeItemTag(xs, *lastlay);
612 closeTag(xs, bstyle);
621 OutputParams const & runparams,
623 ParagraphList::const_iterator const & pbegin)
625 Layout const &style = pbegin->layout();
627 // No need for labels, as they are handled by DocBook tags.
629 openParTag(xs, style);
631 ParagraphList::const_iterator const begin = text.paragraphs().begin();
632 pbegin->simpleDocBookOnePar(buf, xs, runparams,
633 text.outerFont(distance(begin, pbegin)));
638 pair<ParagraphList::const_iterator, ParagraphList::const_iterator> makeAny(
642 OutputParams const &ourparams,
643 ParagraphList::const_iterator par,
644 ParagraphList::const_iterator send,
645 ParagraphList::const_iterator pend)
647 Layout const & style = par->layout();
649 switch (style.latextype) {
650 case LATEX_COMMAND: {
651 // The files with which we are working never have more than
652 // one paragraph in a command structure.
654 // if (ourparams.docbook_in_par)
655 // fix it so we don't get sections inside standard, e.g.
656 // note that we may then need to make runparams not const, so we
657 // can communicate that back.
658 // FIXME Maybe this fix should be in the routines themselves, in case
659 // they are called from elsewhere.
660 makeCommand(buf, xs, ourparams, text, par);
664 case LATEX_ENVIRONMENT:
665 case LATEX_LIST_ENVIRONMENT:
666 case LATEX_ITEM_ENVIRONMENT: {
667 // FIXME Same fix here.
668 send = findEndOfEnvironment(par, pend);
669 par = makeEnvironment(buf, xs, ourparams, text, par, send);
672 case LATEX_BIB_ENVIRONMENT: {
673 send = findLastParagraph(par, pend);
674 par = makeParagraphBibliography(buf, xs, ourparams, text, par, send);
677 case LATEX_PARAGRAPH: {
678 send = findLastParagraph(par, pend);
679 par = makeParagraphs(buf, xs, ourparams, text, par, send);
684 return make_pair(par, send);
687 } // end anonymous namespace
690 using DocBookDocumentSectioning = tuple<bool, pit_type>;
691 using DocBookInfoTag = tuple<set<pit_type>, set<pit_type>, pit_type, pit_type>;
694 DocBookDocumentSectioning hasDocumentSectioning(ParagraphList const ¶graphs, pit_type bpit, pit_type const epit) {
695 bool documentHasSections = false;
697 while (bpit < epit) {
698 Layout const &style = paragraphs[bpit].layout();
699 documentHasSections |= style.category() == from_utf8("Sectioning");
701 if (documentHasSections) {
706 // Paragraphs before the first section: [ runparams.par_begin ; eppit )
708 return make_tuple(documentHasSections, bpit);
712 DocBookInfoTag getParagraphsWithInfo(ParagraphList const ¶graphs, pit_type const bpit, pit_type const epit) {
713 set<pit_type> shouldBeInInfo;
714 set<pit_type> mustBeInInfo;
716 pit_type cpit = bpit;
717 while (cpit < epit) {
718 // Skip paragraphs only containing one note.
719 Paragraph const &par = paragraphs[cpit];
720 if (par.size() == 1 && dynamic_cast<InsetNote*>(paragraphs[cpit].insetList().get(0))) {
725 // Based on layout information, store this paragraph in one set: should be in <info>, must be.
726 Layout const &style = par.layout();
728 std::cout << "Name: " << to_utf8(style.name()) << std::endl;
729 std::cout << " DocBook tag: " << style.docbooktag() << std::endl;
730 std::cout << " In info: " << style.docbookininfo() << std::endl;
732 if (style.docbookininfo() == "always") {
733 mustBeInInfo.emplace(cpit);
734 } else if (style.docbookininfo() == "maybe") {
735 shouldBeInInfo.emplace(cpit);
737 // Hypothesis: the <info> parts should be grouped together near the beginning bpit.
742 // Now, cpit points to the last paragraph that has things that could go in <info>.
743 // bpit is still the beginning of the <info> part.
745 return make_tuple(shouldBeInInfo, mustBeInInfo, bpit, cpit);
749 bool hasAbstractBetween(ParagraphList const ¶graphs, pit_type const bpitAbstract, pit_type const epitAbstract)
751 // Hypothesis: the paragraphs between bpitAbstract and epitAbstract can be considered an abstract because they
752 // are just after a document or part title.
753 if (epitAbstract - bpitAbstract <= 0)
756 // If there is something between these paragraphs, check if it's compatible with an abstract (i.e. some text).
757 pit_type bpit = bpitAbstract;
758 while (bpit < epitAbstract) {
759 const Paragraph &p = paragraphs.at(bpit);
761 if (p.layout().name() == from_ascii("Abstract"))
764 if (!p.insetList().empty()) {
765 for (const auto &i : p.insetList()) {
766 if (i.inset->getText(0) != nullptr) {
777 pit_type generateDocBookParagraphWithoutSectioning(
781 OutputParams const & runparams,
782 ParagraphList const & paragraphs,
786 auto par = paragraphs.iterator_at(bpit);
787 auto lastStartedPar = par;
788 ParagraphList::const_iterator send;
790 (epit == (int) paragraphs.size()) ?
791 paragraphs.end() : paragraphs.iterator_at(epit);
793 std::cout << "generateDocBookParagraphWithoutSectioning" << std::endl;
794 while (bpit < epit) {
795 std::cout << "iteration; bpit: " << bpit << std::endl;
796 tie(par, send) = makeAny(text, buf, xs, runparams, par, send, pend);
797 bpit += distance(lastStartedPar, par);
798 lastStartedPar = par;
800 std::cout << "generateDocBookParagraphWithoutSectioning has looped; bpit: " << bpit << std::endl;
806 void outputDocBookInfo(
810 OutputParams const & runparams,
811 ParagraphList const & paragraphs,
812 DocBookInfoTag const & info,
813 pit_type bpitAbstract,
814 pit_type const epitAbstract)
816 // Consider everything between bpitAbstract and epitAbstract (excluded) as paragraphs for the abstract.
817 // Use bpitAbstract >= epitAbstract to indicate there is no abstract.
819 set<pit_type> shouldBeInInfo;
820 set<pit_type> mustBeInInfo;
823 tie(shouldBeInInfo, mustBeInInfo, bpitInfo, epitInfo) = info;
825 // The abstract must go in <info>.
826 bool hasAbstract = hasAbstractBetween(paragraphs, bpitAbstract, epitAbstract);
827 bool needInfo = !mustBeInInfo.empty() || hasAbstract;
829 // Start the <info> tag if required.
831 xs.startDivision(false);
832 xs << xml::StartTag("info");
836 // Output the elements that should go in <info>.
837 generateDocBookParagraphWithoutSectioning(text, buf, xs, runparams, paragraphs, bpitInfo, epitInfo);
840 string tag = paragraphs[bpitAbstract].layout().docbookforceabstracttag();
844 xs << xml::StartTag(tag);
846 xs.startDivision(false);
847 generateDocBookParagraphWithoutSectioning(text, buf, xs, runparams, paragraphs, bpitAbstract, epitAbstract);
849 xs << xml::EndTag(tag);
853 // End the <info> tag if it was started.
855 xs << xml::EndTag("info");
862 void docbookFirstParagraphs(
866 OutputParams const &runparams,
869 // Handle the beginning of the document, supposing it has sections.
870 // Major role: output the first <info> tag.
872 ParagraphList const ¶graphs = text.paragraphs();
873 pit_type bpit = runparams.par_begin;
874 DocBookInfoTag info = getParagraphsWithInfo(paragraphs, bpit, epit);
875 outputDocBookInfo(text, buf, xs, runparams, paragraphs, info, get<3>(info), epit);
879 bool isParagraphEmpty(const Paragraph &par)
881 InsetList const &insets = par.insetList();
882 size_t insetsLength = distance(insets.begin(), insets.end());
883 bool hasParagraphOnlyNote = insetsLength == 1 && insets.get(0) && insets.get(0)->asInsetCollapsible() &&
884 dynamic_cast<InsetNote *>(insets.get(0));
885 return hasParagraphOnlyNote;
889 void docbookSimpleAllParagraphs(
893 OutputParams const & runparams)
895 // Handle the document, supposing it has no sections (i.e. a "simple" document).
897 // First, the <info> tag.
898 ParagraphList const ¶graphs = text.paragraphs();
899 pit_type bpit = runparams.par_begin;
900 pit_type const epit = runparams.par_end;
901 DocBookInfoTag info = getParagraphsWithInfo(paragraphs, bpit, epit);
902 outputDocBookInfo(text, buf, xs, runparams, paragraphs, info, 0, 0);
903 bpit = get<3>(info); // Generate the content starting from the end of the <info> part.
905 // Then, the content.
906 ParagraphList::const_iterator const pend =
907 (epit == (int) paragraphs.size()) ?
908 paragraphs.end() : paragraphs.iterator_at(epit);
910 while (bpit < epit) {
911 auto par = paragraphs.iterator_at(bpit);
912 ParagraphList::const_iterator const lastStartedPar = par;
913 ParagraphList::const_iterator send;
915 if (isParagraphEmpty(*par)) {
917 bpit += distance(lastStartedPar, par);
921 // Generate this paragraph.
922 tie(par, send) = makeAny(text, buf, xs, runparams, par, send, pend);
923 bpit += distance(lastStartedPar, par);
928 void docbookParagraphs(Text const &text,
931 OutputParams const &runparams) {
932 ParagraphList const ¶graphs = text.paragraphs();
933 if (runparams.par_begin == runparams.par_end) {
934 runparams.par_begin = 0;
935 runparams.par_end = paragraphs.size();
937 pit_type bpit = runparams.par_begin;
938 pit_type const epit = runparams.par_end;
941 xs << XMLStream::ESCAPE_NONE << "<!-- DocBook output error! -->\n";
945 ParagraphList::const_iterator const pend =
946 (epit == (int) paragraphs.size()) ?
947 paragraphs.end() : paragraphs.iterator_at(epit);
948 std::stack<std::pair<int, string>> headerLevels; // Used to determine when to open/close sections: store the depth
949 // of the section and the tag that was used to open it.
951 // Detect whether the document contains sections. If there are no sections, there can be no automatically
952 // discovered abstract.
953 bool documentHasSections;
955 tie(documentHasSections, eppit) = hasDocumentSectioning(paragraphs, bpit, epit);
957 if (documentHasSections) {
958 docbookFirstParagraphs(text, buf, xs, runparams, eppit);
961 docbookSimpleAllParagraphs(text, buf, xs, runparams);
965 bool currentlyInAppendix = false;
967 while (bpit < epit) {
968 OutputParams ourparams = runparams;
970 auto par = paragraphs.iterator_at(bpit);
971 if (par->params().startOfAppendix())
972 currentlyInAppendix = true;
973 Layout const &style = par->layout();
974 ParagraphList::const_iterator const lastStartedPar = par;
975 ParagraphList::const_iterator send;
977 if (isParagraphEmpty(*par)) {
979 bpit += distance(lastStartedPar, par);
983 // Think about adding <section> and/or </section>s.
984 const bool isLayoutSectioning = style.category() == from_utf8("Sectioning");
985 if (isLayoutSectioning) {
986 int level = style.toclevel;
988 // Need to close a previous section if it has the same level or a higher one (close <section> if opening a <h2>
989 // after a <h2>, <h3>, <h4>, <h5> or <h6>). More examples:
990 // - current: h2; back: h1; do not close any <section>
991 // - current: h1; back: h2; close two <section> (first the <h2>, then the <h1>, so a new <h1> can come)
992 while (!headerLevels.empty() && level <= headerLevels.top().first) {
993 int stackLevel = headerLevels.top().first;
994 docstring stackTag = from_utf8("</" + headerLevels.top().second + ">");
997 // Output the tag only if it corresponds to a legit section.
998 if (stackLevel != Layout::NOT_IN_TOC)
999 xs << XMLStream::ESCAPE_NONE << stackTag << xml::CR();
1002 // Open the new section: first push it onto the stack, then output it in DocBook.
1003 string sectionTag = (currentlyInAppendix && style.docbooksectiontag() == "chapter") ?
1004 "appendix" : style.docbooksectiontag();
1005 headerLevels.push(std::make_pair(level, sectionTag));
1007 // Some sectioning-like elements should not be output (such as FrontMatter).
1008 if (level != Layout::NOT_IN_TOC) {
1009 // Look for a label in the title, i.e. a InsetLabel as a child.
1010 docstring id = docstring();
1011 for (pos_type i = 0; i < par->size(); ++i) {
1012 Inset const *inset = par->getInset(i);
1014 if (auto label = dynamic_cast<InsetLabel const *>(inset)) {
1015 // Generate the attributes for the section if need be.
1016 id += "xml:id=\"" + xml::cleanID(label->screenLabel()) + "\"";
1018 // Don't output the ID as a DocBook <anchor>.
1019 ourparams.docbook_anchors_to_ignore.emplace(label->screenLabel());
1021 // Cannot have multiple IDs per tag.
1027 // Write the open tag for this section.
1028 docstring tag = from_utf8("<" + sectionTag);
1030 tag += from_utf8(" ") + id;
1031 tag += from_utf8(">");
1032 xs << XMLStream::ESCAPE_NONE << tag;
1037 // Close all sections before the bibliography.
1038 // 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)?
1039 auto insetsLength = distance(par->insetList().begin(), par->insetList().end());
1040 if (insetsLength > 0) {
1041 Inset const *firstInset = par->getInset(0);
1042 if (firstInset && dynamic_cast<InsetBibtex const *>(firstInset)) {
1043 while (!headerLevels.empty()) {
1044 int level = headerLevels.top().first;
1045 docstring tag = from_utf8("</" + headerLevels.top().second + ">");
1048 // Output the tag only if it corresponds to a legit section.
1049 if (level != Layout::NOT_IN_TOC) {
1050 xs << XMLStream::ESCAPE_NONE << tag;
1057 // Generate this paragraph.
1058 tie(par, send) = makeAny(text, buf, xs, ourparams, par, send, pend);
1059 bpit += distance(lastStartedPar, par);
1062 // If need be, close <section>s, but only at the end of the document (otherwise, dealt with at the beginning
1064 while (!headerLevels.empty() && headerLevels.top().first > Layout::NOT_IN_TOC) {
1065 docstring tag = from_utf8("</" + headerLevels.top().second + ">");
1067 xs << XMLStream::ESCAPE_NONE << tag;