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"
39 #include "support/regex.h"
47 using namespace lyx::support;
53 std::string const fontToDocBookTag(xml::FontTypes type)
56 case xml::FontTypes::FT_EMPH:
57 case xml::FontTypes::FT_BOLD:
59 case xml::FontTypes::FT_NOUN:
61 case xml::FontTypes::FT_UBAR:
62 case xml::FontTypes::FT_WAVE:
63 case xml::FontTypes::FT_DBAR:
64 case xml::FontTypes::FT_SOUT:
65 case xml::FontTypes::FT_XOUT:
66 case xml::FontTypes::FT_ITALIC:
67 case xml::FontTypes::FT_UPRIGHT:
68 case xml::FontTypes::FT_SLANTED:
69 case xml::FontTypes::FT_SMALLCAPS:
70 case xml::FontTypes::FT_ROMAN:
71 case xml::FontTypes::FT_SANS:
73 case xml::FontTypes::FT_TYPE:
75 case xml::FontTypes::FT_SIZE_TINY:
76 case xml::FontTypes::FT_SIZE_SCRIPT:
77 case xml::FontTypes::FT_SIZE_FOOTNOTE:
78 case xml::FontTypes::FT_SIZE_SMALL:
79 case xml::FontTypes::FT_SIZE_NORMAL:
80 case xml::FontTypes::FT_SIZE_LARGE:
81 case xml::FontTypes::FT_SIZE_LARGER:
82 case xml::FontTypes::FT_SIZE_LARGEST:
83 case xml::FontTypes::FT_SIZE_HUGE:
84 case xml::FontTypes::FT_SIZE_HUGER:
85 case xml::FontTypes::FT_SIZE_INCREASE:
86 case xml::FontTypes::FT_SIZE_DECREASE:
93 string fontToRole(xml::FontTypes type)
95 // Specific fonts are achieved with roles. The only common ones are "" for basic emphasis,
96 // and "bold"/"strong" for bold. With some specific options, other roles are copied into
97 // HTML output (via the DocBook XSLT sheets); otherwise, if not recognised, they are just ignored.
98 // Hence, it is not a problem to have many roles by default here.
99 // See https://www.sourceware.org/ml/docbook/2003-05/msg00269.html
101 case xml::FontTypes::FT_ITALIC:
102 case xml::FontTypes::FT_EMPH:
104 case xml::FontTypes::FT_BOLD:
106 case xml::FontTypes::FT_NOUN:
107 return ""; // Outputs a <person>
108 case xml::FontTypes::FT_TYPE:
109 return ""; // Outputs a <code>
110 case xml::FontTypes::FT_UBAR:
113 // All other roles are non-standard for DocBook.
115 case xml::FontTypes::FT_WAVE:
117 case xml::FontTypes::FT_DBAR:
119 case xml::FontTypes::FT_SOUT:
121 case xml::FontTypes::FT_XOUT:
123 case xml::FontTypes::FT_UPRIGHT:
125 case xml::FontTypes::FT_SLANTED:
127 case xml::FontTypes::FT_SMALLCAPS:
129 case xml::FontTypes::FT_ROMAN:
131 case xml::FontTypes::FT_SANS:
133 case xml::FontTypes::FT_SIZE_TINY:
135 case xml::FontTypes::FT_SIZE_SCRIPT:
136 return "size_script";
137 case xml::FontTypes::FT_SIZE_FOOTNOTE:
138 return "size_footnote";
139 case xml::FontTypes::FT_SIZE_SMALL:
141 case xml::FontTypes::FT_SIZE_NORMAL:
142 return "size_normal";
143 case xml::FontTypes::FT_SIZE_LARGE:
145 case xml::FontTypes::FT_SIZE_LARGER:
146 return "size_larger";
147 case xml::FontTypes::FT_SIZE_LARGEST:
148 return "size_largest";
149 case xml::FontTypes::FT_SIZE_HUGE:
151 case xml::FontTypes::FT_SIZE_HUGER:
153 case xml::FontTypes::FT_SIZE_INCREASE:
154 return "size_increase";
155 case xml::FontTypes::FT_SIZE_DECREASE:
156 return "size_decrease";
162 string fontToAttribute(xml::FontTypes type) {
163 // If there is a role (i.e. nonstandard use of a tag), output the attribute. Otherwise, the sheer tag is sufficient
165 string role = fontToRole(type);
167 return "role='" + role + "'";
173 } // end anonymous namespace
176 xml::FontTag docbookStartFontTag(xml::FontTypes type)
178 return xml::FontTag(from_utf8(fontToDocBookTag(type)), from_utf8(fontToAttribute(type)), type);
182 xml::EndFontTag docbookEndFontTag(xml::FontTypes type)
184 return xml::EndFontTag(from_utf8(fontToDocBookTag(type)), type);
190 // convenience functions
192 void openParTag(XMLStream & xs, Layout const & lay)
194 if (lay.docbookwrappertag() != "NONE") {
195 xs << xml::StartTag(lay.docbookwrappertag(), lay.docbookwrapperattr());
198 string tag = lay.docbooktag();
199 if (tag == "Plain Layout")
202 xs << xml::ParTag(tag, lay.docbookattr());
206 void closeTag(XMLStream & xs, Layout const & lay)
208 string tag = lay.docbooktag();
209 if (tag == "Plain Layout")
212 xs << xml::EndTag(tag);
213 if (lay.docbookwrappertag() != "NONE")
214 xs << xml::EndTag(lay.docbookwrappertag());
218 void openLabelTag(XMLStream & xs, Layout const & lay) // Mostly for definition lists.
220 xs << xml::StartTag(lay.docbookitemlabeltag(), lay.docbookitemlabelattr());
224 void closeLabelTag(XMLStream & xs, Layout const & lay)
226 xs << xml::EndTag(lay.docbookitemlabeltag());
231 void openItemTag(XMLStream & xs, Layout const & lay)
233 xs << xml::StartTag(lay.docbookitemtag(), lay.docbookitemattr());
237 // Return true when new elements are output in a paragraph, false otherwise.
238 bool openInnerItemTag(XMLStream & xs, Layout const & lay)
240 if (lay.docbookiteminnertag() != "NONE") {
242 xs << xml::ParTag(lay.docbookiteminnertag(), lay.docbookiteminnerattr());
244 if (lay.docbookiteminnertag() == "para") {
252 void closeInnerItemTag(XMLStream & xs, Layout const & lay)
254 if (lay.docbookiteminnertag()!= "NONE") {
255 xs << xml::EndTag(lay.docbookiteminnertag());
261 inline void closeItemTag(XMLStream & xs, Layout const & lay)
263 xs << xml::EndTag(lay.docbookitemtag());
267 // end of convenience functions
269 ParagraphList::const_iterator findLastParagraph(
270 ParagraphList::const_iterator p,
271 ParagraphList::const_iterator const & pend) {
272 for (++p; p != pend && p->layout().latextype == LATEX_PARAGRAPH; ++p);
277 ParagraphList::const_iterator findLastBibliographyParagraph(
278 ParagraphList::const_iterator p,
279 ParagraphList::const_iterator const & pend) {
280 for (++p; p != pend && p->layout().latextype == LATEX_BIB_ENVIRONMENT; ++p);
286 ParagraphList::const_iterator findEndOfEnvironment(
287 ParagraphList::const_iterator const & pstart,
288 ParagraphList::const_iterator const & pend)
290 ParagraphList::const_iterator p = pstart;
291 Layout const &bstyle = p->layout();
292 size_t const depth = p->params().depth();
293 for (++p; p != pend; ++p) {
294 Layout const &style = p->layout();
295 // It shouldn't happen that e.g. a section command occurs inside
296 // a quotation environment, at a higher depth, but as of 6/2009,
297 // it can happen. We pretend that it's just at lowest depth.
298 if (style.latextype == LATEX_COMMAND)
301 // If depth is down, we're done
302 if (p->params().depth() < depth)
305 // If depth is up, we're not done
306 if (p->params().depth() > depth)
309 // FIXME I am not sure about the first check.
310 // Surely we *could* have different layouts that count as
311 // LATEX_PARAGRAPH, right?
312 if (style.latextype == LATEX_PARAGRAPH || style != bstyle)
319 ParagraphList::const_iterator makeParagraphBibliography(
322 OutputParams const &runparams,
324 ParagraphList::const_iterator const & pbegin,
325 ParagraphList::const_iterator const & pend)
327 auto const begin = text.paragraphs().begin();
328 auto const end = text.paragraphs().end();
330 // Find the paragraph *before* pbegin.
331 ParagraphList::const_iterator pbegin_before = begin;
332 if (pbegin != begin) {
333 ParagraphList::const_iterator pbegin_before_next = begin;
334 ++pbegin_before_next;
336 while (pbegin_before_next != pbegin) {
338 ++pbegin_before_next;
342 ParagraphList::const_iterator par = pbegin;
344 // If this is the first paragraph in a bibliography, open the bibliography tag.
345 if (pbegin != begin && pbegin_before->layout().latextype != LATEX_BIB_ENVIRONMENT) {
346 xs << xml::StartTag("bibliography");
350 // Generate the required paragraphs, but only if they are .
351 for (; par != pend; ++par) {
352 // Start the precooked bibliography entry. This is very much like opening a paragraph tag.
353 // Don't forget the citation ID!
355 for (auto i = 0; i < par->size(); ++i) {
356 Inset const *ip = par->getInset(0);
357 if (ip != nullptr && ip->lyxCode() == BIBITEM_CODE) {
358 const auto * bibitem = dynamic_cast<const InsetBibitem*>(par->getInset(i));
359 attr = from_utf8("xml:id='") + bibitem->getParam("key") + from_utf8("'");
363 xs << xml::StartTag(from_utf8("bibliomixed"), attr);
365 // Generate the entry.
366 par->simpleDocBookOnePar(buf, xs, runparams, text.outerFont(distance(begin, par)), true, true, 0);
368 // End the precooked bibliography entry.
369 xs << xml::EndTag("bibliomixed");
373 // If this is the last paragraph in a bibliography, close the bibliography tag.
374 if (par == end || par->layout().latextype != LATEX_BIB_ENVIRONMENT) {
375 xs << xml::EndTag("bibliography");
383 ParagraphList::const_iterator makeParagraphs(
386 OutputParams const &runparams,
388 ParagraphList::const_iterator const & pbegin,
389 ParagraphList::const_iterator const & pend)
391 ParagraphList::const_iterator const begin = text.paragraphs().begin();
392 ParagraphList::const_iterator par = pbegin;
393 for (; par != pend; ++par) {
394 Layout const &lay = par->layout();
396 // We want to open the paragraph tag if:
397 // (i) the current layout permits multiple paragraphs
398 // (ii) we are either not already inside a paragraph (HTMLIsBlock) OR
399 // we are, but this is not the first paragraph
401 // But there is also a special case, and we first see whether we are in it.
402 // We do not want to open the paragraph tag if this paragraph contains
403 // only one item, and that item is "inline", i.e., not HTMLIsBlock (such
404 // as a branch). On the other hand, if that single item has a font change
405 // applied to it, then we still do need to open the paragraph.
407 // Obviously, this is very fragile. The main reason we need to do this is
408 // because of branches, e.g., a branch that contains an entire new section.
409 // We do not really want to wrap that whole thing in a <div>...</div>.
410 bool special_case = false;
411 Inset const *specinset = par->size() == 1 ? par->getInset(0) : 0;
412 if (specinset && !specinset->getLayout().htmlisblock()) { // TODO: Convert htmlisblock to a DocBook parameter?
413 Layout const &style = par->layout();
414 FontInfo const first_font = style.labeltype == LABEL_MANUAL ?
415 style.labelfont : style.font;
416 FontInfo const our_font =
417 par->getFont(buf.masterBuffer()->params(), 0,
418 text.outerFont(distance(begin, par))).fontInfo();
420 if (first_font == our_font)
424 // Plain layouts must be ignored.
425 if (!special_case && buf.params().documentClass().isPlainLayout(lay) && !runparams.docbook_force_pars)
427 // TODO: Could get rid of this with a DocBook equivalent to htmlisblock?
428 if (!special_case && par->size() == 1 && par->getInset(0)) {
429 Inset const * firstInset = par->getInset(0);
431 // Floats cannot be in paragraphs.
432 special_case = to_utf8(firstInset->layoutName()).substr(0, 6) == "Float:";
434 // Bibliographies cannot be in paragraphs.
435 if (!special_case && firstInset->asInsetCommand())
436 special_case = firstInset->asInsetCommand()->params().getCmdName() == "bibtex";
438 // Equations do not deserve their own paragraph (DocBook allows them outside paragraphs).
439 if (!special_case && firstInset->asInsetMath())
442 // ERTs are in comments, not paragraphs.
443 if (!special_case && firstInset->lyxCode() == lyx::ERT_CODE)
446 // Listings should not get into their own paragraph.
447 if (!special_case && firstInset->lyxCode() == lyx::LISTINGS_CODE)
451 bool const open_par = runparams.docbook_make_pars
452 && (!runparams.docbook_in_par || par != pbegin)
455 // We want to issue the closing tag if either:
456 // (i) We opened it, and either docbook_in_par is false,
457 // or we're not in the last paragraph, anyway.
458 // (ii) We didn't open it and docbook_in_par is true,
459 // but we are in the first par, and there is a next par.
460 ParagraphList::const_iterator nextpar = par;
462 bool const close_par =
463 ((open_par && (!runparams.docbook_in_par || nextpar != pend))
464 || (!open_par && runparams.docbook_in_par && par == pbegin && nextpar != pend));
466 // Determine if this paragraph has some real content. Things like new pages are not caught
467 // by Paragraph::empty(), even though they do not generate anything useful in DocBook.
468 odocstringstream os2;
470 par->simpleDocBookOnePar(buf, xs2, runparams, text.outerFont(distance(begin, par)), open_par, close_par, 0);
472 docstring cleaned = os2.str();
473 static const lyx::regex reg("[ \\r\\n]*");
474 cleaned = from_utf8(lyx::regex_replace(to_utf8(cleaned), reg, string("")));
476 if (!cleaned.empty()) {
480 xs << XMLStream::ESCAPE_NONE << os2.str();
492 bool isNormalEnv(Layout const &lay)
494 return lay.latextype == LATEX_ENVIRONMENT
495 || lay.latextype == LATEX_BIB_ENVIRONMENT;
499 ParagraphList::const_iterator makeEnvironment(
502 OutputParams const &runparams,
504 ParagraphList::const_iterator const & pbegin,
505 ParagraphList::const_iterator const & pend)
507 ParagraphList::const_iterator const begin = text.paragraphs().begin();
508 ParagraphList::const_iterator par = pbegin;
509 Layout const &bstyle = par->layout();
510 depth_type const origdepth = pbegin->params().depth();
512 // open tag for this environment
513 openParTag(xs, bstyle);
516 // we will on occasion need to remember a layout from before.
517 Layout const *lastlay = nullptr;
519 while (par != pend) {
520 Layout const & style = par->layout();
521 ParagraphList::const_iterator send;
523 // Actual content of this paragraph.
524 switch (style.latextype) {
525 case LATEX_ENVIRONMENT:
526 case LATEX_LIST_ENVIRONMENT:
527 case LATEX_ITEM_ENVIRONMENT: {
528 // There are two possibilities in this case.
529 // One is that we are still in the environment in which we
530 // started---which we will be if the depth is the same.
531 if (par->params().depth() == origdepth) {
532 LATTEST(bstyle == style);
533 if (lastlay != nullptr) {
534 closeItemTag(xs, *lastlay);
535 if (lastlay->docbookitemwrappertag() != "NONE") {
536 xs << xml::EndTag(lastlay->docbookitemwrappertag());
542 // this will be positive if we want to skip the
543 // initial word (if it's been taken for the label).
546 // Open a wrapper tag if needed.
547 if (style.docbookitemwrappertag() != "NONE") {
548 xs << xml::StartTag(style.docbookitemwrappertag(), style.docbookitemwrapperattr());
553 if (style.labeltype != LABEL_NO_LABEL &&
554 style.docbookitemlabeltag() != "NONE") {
556 if (isNormalEnv(style)) {
557 // in this case, we print the label only for the first
558 // paragraph (as in a theorem or an abstract).
560 docstring const lbl = pbegin->params().labelString();
562 openLabelTag(xs, style);
564 closeLabelTag(xs, style);
566 // No new line after closeLabelTag.
570 } else { // some kind of list
571 if (style.labeltype == LABEL_MANUAL) {
572 // Only variablelist gets here.
574 openLabelTag(xs, style);
575 sep = par->firstWordDocBook(xs, runparams);
576 closeLabelTag(xs, style);
578 openLabelTag(xs, style);
579 xs << par->params().labelString();
580 closeLabelTag(xs, style);
583 } // end label output
585 // Start generating the item.
586 bool wasInParagraph = runparams.docbook_in_par;
587 openItemTag(xs, style);
588 bool getsIntoParagraph = openInnerItemTag(xs, style);
589 OutputParams rp = runparams;
590 rp.docbook_in_par = wasInParagraph | getsIntoParagraph;
592 // Maybe the item is completely empty, i.e. if the first word ends at the end of the current paragraph
593 // AND if the next paragraph doesn't have the same depth (if there is such a paragraph).
594 // Common case: there is only the first word on the line, but there is a nested list instead
596 bool emptyItem = false;
597 if (sep == par->size()) {
600 if (next_par == text.paragraphs().end()) // There is no next paragraph.
602 else // There is a next paragraph: check depth.
603 emptyItem = par->params().depth() >= next_par->params().depth();
607 // Avoid having an empty item, this is not valid DocBook. A single character is enough to force
608 // generation of a full <para>.
611 // Generate the rest of the paragraph, if need be.
612 par->simpleDocBookOnePar(buf, xs, rp, text.outerFont(distance(begin, par)), true, true, sep);
616 if (getsIntoParagraph)
617 closeInnerItemTag(xs, style);
619 // We may not want to close the tag yet, in particular:
620 // If we're not at the end of the item...
622 // and are doing items...
623 && !isNormalEnv(style)
624 // and if the depth has changed...
625 && par->params().depth() != origdepth) {
626 // then we'll save this layout for later, and close it when
627 // we get another item.
630 closeItemTag(xs, style);
632 // Eventually, close the item wrapper.
633 if (style.docbookitemwrappertag() != "NONE") {
634 xs << xml::EndTag(style.docbookitemwrappertag());
639 // The other possibility is that the depth has increased.
641 send = findEndOfEnvironment(par, pend);
642 par = makeEnvironment(buf, xs, runparams, text, par, send);
646 case LATEX_PARAGRAPH:
647 send = findLastParagraph(par, pend);
648 par = makeParagraphs(buf, xs, runparams, text, par, send);
650 case LATEX_BIB_ENVIRONMENT:
651 send = findLastBibliographyParagraph(par, pend);
652 par = makeParagraphBibliography(buf, xs, runparams, text, par, send);
660 if (lastlay != nullptr) {
661 closeItemTag(xs, *lastlay);
662 if (lastlay->docbookitemwrappertag() != "NONE") {
663 xs << xml::EndTag(lastlay->docbookitemwrappertag());
667 closeTag(xs, bstyle);
676 OutputParams const & runparams,
678 ParagraphList::const_iterator const & pbegin)
680 Layout const &style = pbegin->layout();
682 // No need for labels, as they are handled by DocBook tags.
684 openParTag(xs, style);
686 ParagraphList::const_iterator const begin = text.paragraphs().begin();
687 pbegin->simpleDocBookOnePar(buf, xs, runparams,
688 text.outerFont(distance(begin, pbegin)));
693 pair<ParagraphList::const_iterator, ParagraphList::const_iterator> makeAny(
697 OutputParams const &ourparams,
698 ParagraphList::const_iterator par,
699 ParagraphList::const_iterator send,
700 ParagraphList::const_iterator pend)
702 Layout const & style = par->layout();
704 switch (style.latextype) {
705 case LATEX_COMMAND: {
706 // The files with which we are working never have more than
707 // one paragraph in a command structure.
709 // if (ourparams.docbook_in_par)
710 // fix it so we don't get sections inside standard, e.g.
711 // note that we may then need to make runparams not const, so we
712 // can communicate that back.
713 // FIXME Maybe this fix should be in the routines themselves, in case
714 // they are called from elsewhere.
715 makeCommand(buf, xs, ourparams, text, par);
719 case LATEX_ENVIRONMENT:
720 case LATEX_LIST_ENVIRONMENT:
721 case LATEX_ITEM_ENVIRONMENT: {
722 // FIXME Same fix here.
723 send = findEndOfEnvironment(par, pend);
724 par = makeEnvironment(buf, xs, ourparams, text, par, send);
727 case LATEX_BIB_ENVIRONMENT: {
728 send = findLastBibliographyParagraph(par, pend);
729 par = makeParagraphBibliography(buf, xs, ourparams, text, par, send);
732 case LATEX_PARAGRAPH: {
733 send = findLastParagraph(par, pend);
734 par = makeParagraphs(buf, xs, ourparams, text, par, send);
739 return make_pair(par, send);
742 } // end anonymous namespace
745 using DocBookDocumentSectioning = tuple<bool, pit_type>;
746 using DocBookInfoTag = tuple<set<pit_type>, set<pit_type>, pit_type, pit_type>;
749 DocBookDocumentSectioning hasDocumentSectioning(ParagraphList const ¶graphs, pit_type bpit, pit_type const epit) {
750 bool documentHasSections = false;
752 while (bpit < epit) {
753 Layout const &style = paragraphs[bpit].layout();
754 documentHasSections |= style.category() == from_utf8("Sectioning");
756 if (documentHasSections) {
761 // Paragraphs before the first section: [ runparams.par_begin ; eppit )
763 return make_tuple(documentHasSections, bpit);
767 bool hasOnlyNotes(Paragraph const & par)
769 for (int i = 0; i < par.size(); ++i)
770 if (!par.isInset(i) || !dynamic_cast<InsetNote *>(par.insetList().get(i)))
776 DocBookInfoTag getParagraphsWithInfo(ParagraphList const ¶graphs, pit_type bpit, pit_type const epit) {
777 set<pit_type> shouldBeInInfo;
778 set<pit_type> mustBeInInfo;
780 // Find the first non empty paragraph by mutating bpit.
781 while (bpit < epit) {
782 Paragraph const &par = paragraphs[bpit];
783 if (par.empty() || hasOnlyNotes(par))
789 // Find the last info-like paragraph.
790 pit_type cpit = bpit;
791 while (cpit < epit) {
792 // Skip paragraphs only containing one note.
793 Paragraph const &par = paragraphs[cpit];
794 if (hasOnlyNotes(par)) {
799 // Based on layout information, store this paragraph in one set: should be in <info>, must be.
800 Layout const &style = par.layout();
802 if (style.docbookininfo() == "always") {
803 mustBeInInfo.emplace(cpit);
804 } else if (style.docbookininfo() == "maybe") {
805 shouldBeInInfo.emplace(cpit);
807 // Hypothesis: the <info> parts should be grouped together near the beginning bpit.
812 // Now, cpit points to the last paragraph that has things that could go in <info>.
813 // bpit is still the beginning of the <info> part.
815 return make_tuple(shouldBeInInfo, mustBeInInfo, bpit, cpit);
819 bool hasAbstractBetween(ParagraphList const ¶graphs, pit_type const bpitAbstract, pit_type const epitAbstract)
821 // Hypothesis: the paragraphs between bpitAbstract and epitAbstract can be considered an abstract because they
822 // are just after a document or part title.
823 if (epitAbstract - bpitAbstract <= 0)
826 // If there is something between these paragraphs, check if it's compatible with an abstract (i.e. some text).
827 pit_type bpit = bpitAbstract;
828 while (bpit < epitAbstract) {
829 const Paragraph &p = paragraphs.at(bpit);
831 if (p.layout().name() == from_ascii("Abstract"))
834 if (!p.insetList().empty()) {
835 for (const auto &i : p.insetList()) {
836 if (i.inset->getText(0) != nullptr) {
847 pit_type generateDocBookParagraphWithoutSectioning(
851 OutputParams const & runparams,
852 ParagraphList const & paragraphs,
856 auto par = paragraphs.iterator_at(bpit);
857 auto lastStartedPar = par;
858 ParagraphList::const_iterator send;
860 (epit == (int) paragraphs.size()) ?
861 paragraphs.end() : paragraphs.iterator_at(epit);
863 while (bpit < epit) {
864 tie(par, send) = makeAny(text, buf, xs, runparams, par, send, pend);
865 bpit += distance(lastStartedPar, par);
866 lastStartedPar = par;
873 void outputDocBookInfo(
877 OutputParams const & runparams,
878 ParagraphList const & paragraphs,
879 DocBookInfoTag const & info,
880 pit_type bpitAbstract,
881 pit_type const epitAbstract)
883 // Consider everything between bpitAbstract and epitAbstract (excluded) as paragraphs for the abstract.
884 // Use bpitAbstract >= epitAbstract to indicate there is no abstract.
886 set<pit_type> shouldBeInInfo;
887 set<pit_type> mustBeInInfo;
890 tie(shouldBeInInfo, mustBeInInfo, bpitInfo, epitInfo) = info;
892 // Perform an additional check on the abstract. Sometimes, there are many paragraphs that should go
893 // into the abstract, but none generates actual content. Thus, first generate to a temporary stream,
894 // then only create the <abstract> tag if these paragraphs generate some content.
895 // This check must be performed *before* a decision on whether or not to output <info> is made.
896 bool hasAbstract = hasAbstractBetween(paragraphs, bpitAbstract, epitAbstract);
899 odocstringstream os2;
901 generateDocBookParagraphWithoutSectioning(text, buf, xs2, runparams, paragraphs, bpitAbstract, epitAbstract);
903 // Actually output the abstract if there is something to do. Don't count line feeds or spaces in this,
904 // even though they must be properly output if there is some abstract.
905 docstring abstractContent = os2.str();
906 static const lyx::regex reg("[ \\r\\n]*");
907 abstractContent = from_utf8(lyx::regex_replace(to_utf8(abstractContent), reg, string("")));
909 // Nothing? Then there is no abstract!
910 if (abstractContent.empty())
914 // The abstract must go in <info>.
915 bool needInfo = !mustBeInInfo.empty() || hasAbstract;
917 // Start the <info> tag if required.
919 xs.startDivision(false);
920 xs << xml::StartTag("info");
924 // Output the elements that should go in <info>.
925 generateDocBookParagraphWithoutSectioning(text, buf, xs, runparams, paragraphs, bpitInfo, epitInfo);
927 if (hasAbstract && !abstract.empty()) { // The second test is probably superfluous.
928 string tag = paragraphs[bpitAbstract].layout().docbookforceabstracttag();
932 xs << xml::StartTag(tag);
934 xs << XMLStream::ESCAPE_NONE << abstract;
935 xs << xml::EndTag(tag);
939 // End the <info> tag if it was started.
941 xs << xml::EndTag("info");
948 void docbookFirstParagraphs(
952 OutputParams const &runparams,
955 // Handle the beginning of the document, supposing it has sections.
956 // Major role: output the first <info> tag.
958 ParagraphList const ¶graphs = text.paragraphs();
959 pit_type bpit = runparams.par_begin;
960 DocBookInfoTag info = getParagraphsWithInfo(paragraphs, bpit, epit);
961 outputDocBookInfo(text, buf, xs, runparams, paragraphs, info, get<3>(info), epit);
965 bool isParagraphEmpty(const Paragraph &par)
967 InsetList const &insets = par.insetList();
968 size_t insetsLength = distance(insets.begin(), insets.end());
969 bool hasParagraphOnlyNote = insetsLength == 1 && insets.get(0) && insets.get(0)->asInsetCollapsible() &&
970 dynamic_cast<InsetNote *>(insets.get(0));
971 return hasParagraphOnlyNote;
975 void docbookSimpleAllParagraphs(
979 OutputParams const & runparams)
981 // Handle the document, supposing it has no sections (i.e. a "simple" document).
983 // First, the <info> tag.
984 ParagraphList const ¶graphs = text.paragraphs();
985 pit_type bpit = runparams.par_begin;
986 pit_type const epit = runparams.par_end;
987 DocBookInfoTag info = getParagraphsWithInfo(paragraphs, bpit, epit);
988 outputDocBookInfo(text, buf, xs, runparams, paragraphs, info, 0, 0);
989 bpit = get<3>(info); // Generate the content starting from the end of the <info> part.
991 // Then, the content.
992 ParagraphList::const_iterator const pend =
993 (epit == (int) paragraphs.size()) ?
994 paragraphs.end() : paragraphs.iterator_at(epit);
996 while (bpit < epit) {
997 auto par = paragraphs.iterator_at(bpit);
998 ParagraphList::const_iterator const lastStartedPar = par;
999 ParagraphList::const_iterator send;
1001 if (isParagraphEmpty(*par)) {
1003 bpit += distance(lastStartedPar, par);
1007 // Generate this paragraph.
1008 tie(par, send) = makeAny(text, buf, xs, runparams, par, send, pend);
1009 bpit += distance(lastStartedPar, par);
1014 void docbookParagraphs(Text const &text,
1017 OutputParams const &runparams) {
1018 ParagraphList const ¶graphs = text.paragraphs();
1019 if (runparams.par_begin == runparams.par_end) {
1020 runparams.par_begin = 0;
1021 runparams.par_end = paragraphs.size();
1023 pit_type bpit = runparams.par_begin;
1024 pit_type const epit = runparams.par_end;
1025 LASSERT(bpit < epit,
1027 xs << XMLStream::ESCAPE_NONE << "<!-- DocBook output error! -->\n";
1031 ParagraphList::const_iterator const pend =
1032 (epit == (int) paragraphs.size()) ?
1033 paragraphs.end() : paragraphs.iterator_at(epit);
1034 std::stack<std::pair<int, string>> headerLevels; // Used to determine when to open/close sections: store the depth
1035 // of the section and the tag that was used to open it.
1037 // Detect whether the document contains sections. If there are no sections, there can be no automatically
1038 // discovered abstract.
1039 bool documentHasSections;
1041 tie(documentHasSections, eppit) = hasDocumentSectioning(paragraphs, bpit, epit);
1043 if (documentHasSections) {
1044 docbookFirstParagraphs(text, buf, xs, runparams, eppit);
1047 docbookSimpleAllParagraphs(text, buf, xs, runparams);
1051 bool currentlyInAppendix = false;
1053 while (bpit < epit) {
1054 OutputParams ourparams = runparams;
1056 auto par = paragraphs.iterator_at(bpit);
1057 if (par->params().startOfAppendix())
1058 currentlyInAppendix = true;
1059 Layout const &style = par->layout();
1060 ParagraphList::const_iterator const lastStartedPar = par;
1061 ParagraphList::const_iterator send;
1063 if (isParagraphEmpty(*par)) {
1065 bpit += distance(lastStartedPar, par);
1069 // Think about adding <section> and/or </section>s.
1070 const bool isLayoutSectioning = style.category() == from_utf8("Sectioning");
1071 if (isLayoutSectioning) {
1072 int level = style.toclevel;
1074 // Need to close a previous section if it has the same level or a higher one (close <section> if opening a <h2>
1075 // after a <h2>, <h3>, <h4>, <h5> or <h6>). More examples:
1076 // - current: h2; back: h1; do not close any <section>
1077 // - current: h1; back: h2; close two <section> (first the <h2>, then the <h1>, so a new <h1> can come)
1078 while (!headerLevels.empty() && level <= headerLevels.top().first) {
1079 int stackLevel = headerLevels.top().first;
1080 docstring stackTag = from_utf8("</" + headerLevels.top().second + ">");
1083 // Output the tag only if it corresponds to a legit section.
1084 if (stackLevel != Layout::NOT_IN_TOC)
1085 xs << XMLStream::ESCAPE_NONE << stackTag << xml::CR();
1088 // Open the new section: first push it onto the stack, then output it in DocBook.
1089 string sectionTag = (currentlyInAppendix && style.docbooksectiontag() == "chapter") ?
1090 "appendix" : style.docbooksectiontag();
1091 headerLevels.push(std::make_pair(level, sectionTag));
1093 // Some sectioning-like elements should not be output (such as FrontMatter).
1094 if (level != Layout::NOT_IN_TOC) {
1095 // Look for a label in the title, i.e. a InsetLabel as a child.
1096 docstring id = docstring();
1097 for (pos_type i = 0; i < par->size(); ++i) {
1098 Inset const *inset = par->getInset(i);
1100 if (auto label = dynamic_cast<InsetLabel const *>(inset)) {
1101 // Generate the attributes for the section if need be.
1102 id += "xml:id=\"" + xml::cleanID(label->screenLabel()) + "\"";
1104 // Don't output the ID as a DocBook <anchor>.
1105 ourparams.docbook_anchors_to_ignore.emplace(label->screenLabel());
1107 // Cannot have multiple IDs per tag.
1113 // Write the open tag for this section.
1114 docstring tag = from_utf8("<" + sectionTag);
1116 tag += from_utf8(" ") + id;
1117 tag += from_utf8(">");
1118 xs << XMLStream::ESCAPE_NONE << tag;
1123 // Close all sections before the bibliography.
1124 // 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)?
1125 auto insetsLength = distance(par->insetList().begin(), par->insetList().end());
1126 if (insetsLength > 0) {
1127 Inset const *firstInset = par->getInset(0);
1128 if (firstInset && dynamic_cast<InsetBibtex const *>(firstInset)) {
1129 while (!headerLevels.empty()) {
1130 int level = headerLevels.top().first;
1131 docstring tag = from_utf8("</" + headerLevels.top().second + ">");
1134 // Output the tag only if it corresponds to a legit section.
1135 if (level != Layout::NOT_IN_TOC) {
1136 xs << XMLStream::ESCAPE_NONE << tag;
1143 // Generate this paragraph.
1144 tie(par, send) = makeAny(text, buf, xs, ourparams, par, send, pend);
1145 bpit += distance(lastStartedPar, par);
1148 // If need be, close <section>s, but only at the end of the document (otherwise, dealt with at the beginning
1150 while (!headerLevels.empty() && headerLevels.top().first > Layout::NOT_IN_TOC) {
1151 docstring tag = from_utf8("</" + headerLevels.top().second + ">");
1153 xs << XMLStream::ESCAPE_NONE << tag;