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 findLast(
270 ParagraphList::const_iterator p,
271 ParagraphList::const_iterator const & pend,
273 for (++p; p != pend && p->layout().latextype == type; ++p);
278 ParagraphList::const_iterator findLastBibliographyParagraph(
279 ParagraphList::const_iterator p,
280 ParagraphList::const_iterator const & pend) {
281 for (++p; p != pend && p->layout().latextype == LATEX_BIB_ENVIRONMENT; ++p);
287 ParagraphList::const_iterator findEndOfEnvironment(
288 ParagraphList::const_iterator const & pstart,
289 ParagraphList::const_iterator const & pend)
291 ParagraphList::const_iterator p = pstart;
292 Layout const &bstyle = p->layout();
293 size_t const depth = p->params().depth();
294 for (++p; p != pend; ++p) {
295 Layout const &style = p->layout();
296 // It shouldn't happen that e.g. a section command occurs inside
297 // a quotation environment, at a higher depth, but as of 6/2009,
298 // it can happen. We pretend that it's just at lowest depth.
299 if (style.latextype == LATEX_COMMAND)
302 // If depth is down, we're done
303 if (p->params().depth() < depth)
306 // If depth is up, we're not done
307 if (p->params().depth() > depth)
310 // FIXME I am not sure about the first check.
311 // Surely we *could* have different layouts that count as
312 // LATEX_PARAGRAPH, right?
313 if (style.latextype == LATEX_PARAGRAPH || style != bstyle)
320 ParagraphList::const_iterator makeParagraphBibliography(
323 OutputParams const &runparams,
325 ParagraphList::const_iterator const & pbegin,
326 ParagraphList::const_iterator const & pend)
328 auto const begin = text.paragraphs().begin();
329 auto const end = text.paragraphs().end();
331 // Find the paragraph *before* pbegin.
332 ParagraphList::const_iterator pbegin_before = begin;
333 if (pbegin != begin) {
334 ParagraphList::const_iterator pbegin_before_next = begin;
335 ++pbegin_before_next;
337 while (pbegin_before_next != pbegin) {
339 ++pbegin_before_next;
343 ParagraphList::const_iterator par = pbegin;
345 // If this is the first paragraph in a bibliography, open the bibliography tag.
346 if (pbegin != begin && pbegin_before->layout().latextype != LATEX_BIB_ENVIRONMENT) {
347 xs << xml::StartTag("bibliography");
351 // Generate the required paragraphs, but only if they are .
352 for (; par != pend; ++par) {
353 // Start the precooked bibliography entry. This is very much like opening a paragraph tag.
354 // Don't forget the citation ID!
356 for (auto i = 0; i < par->size(); ++i) {
357 Inset const *ip = par->getInset(0);
358 if (ip != nullptr && ip->lyxCode() == BIBITEM_CODE) {
359 const auto * bibitem = dynamic_cast<const InsetBibitem*>(par->getInset(i));
360 attr = from_utf8("xml:id='") + bibitem->getParam("key") + from_utf8("'");
364 xs << xml::StartTag(from_utf8("bibliomixed"), attr);
366 // Generate the entry.
367 par->simpleDocBookOnePar(buf, xs, runparams, text.outerFont(distance(begin, par)), true, true, 0);
369 // End the precooked bibliography entry.
370 xs << xml::EndTag("bibliomixed");
374 // If this is the last paragraph in a bibliography, close the bibliography tag.
375 if (par == end || par->layout().latextype != LATEX_BIB_ENVIRONMENT) {
376 xs << xml::EndTag("bibliography");
384 ParagraphList::const_iterator makeParagraphs(
387 OutputParams const &runparams,
389 ParagraphList::const_iterator const & pbegin,
390 ParagraphList::const_iterator const & pend)
392 ParagraphList::const_iterator const begin = text.paragraphs().begin();
393 ParagraphList::const_iterator par = pbegin;
394 for (; par != pend; ++par) {
395 Layout const &lay = par->layout();
397 // We want to open the paragraph tag if:
398 // (i) the current layout permits multiple paragraphs
399 // (ii) we are either not already inside a paragraph (HTMLIsBlock) OR
400 // we are, but this is not the first paragraph
402 // But there is also a special case, and we first see whether we are in it.
403 // We do not want to open the paragraph tag if this paragraph contains
404 // only one item, and that item is "inline", i.e., not HTMLIsBlock (such
405 // as a branch). On the other hand, if that single item has a font change
406 // applied to it, then we still do need to open the paragraph.
408 // Obviously, this is very fragile. The main reason we need to do this is
409 // because of branches, e.g., a branch that contains an entire new section.
410 // We do not really want to wrap that whole thing in a <div>...</div>.
411 bool special_case = false;
412 Inset const *specinset = par->size() == 1 ? par->getInset(0) : 0;
413 if (specinset && !specinset->getLayout().htmlisblock()) { // TODO: Convert htmlisblock to a DocBook parameter?
414 Layout const &style = par->layout();
415 FontInfo const first_font = style.labeltype == LABEL_MANUAL ?
416 style.labelfont : style.font;
417 FontInfo const our_font =
418 par->getFont(buf.masterBuffer()->params(), 0,
419 text.outerFont(distance(begin, par))).fontInfo();
421 if (first_font == our_font)
425 // Plain layouts must be ignored.
426 if (!special_case && buf.params().documentClass().isPlainLayout(lay) && !runparams.docbook_force_pars)
428 // TODO: Could get rid of this with a DocBook equivalent to htmlisblock?
429 if (!special_case && par->size() == 1 && par->getInset(0)) {
430 Inset const * firstInset = par->getInset(0);
432 // Floats cannot be in paragraphs.
433 special_case = to_utf8(firstInset->layoutName()).substr(0, 6) == "Float:";
435 // Bibliographies cannot be in paragraphs.
436 if (!special_case && firstInset->asInsetCommand())
437 special_case = firstInset->asInsetCommand()->params().getCmdName() == "bibtex";
439 // Equations do not deserve their own paragraph (DocBook allows them outside paragraphs).
440 if (!special_case && firstInset->asInsetMath())
443 // ERTs are in comments, not paragraphs.
444 if (!special_case && firstInset->lyxCode() == lyx::ERT_CODE)
447 // Listings should not get into their own paragraph.
448 if (!special_case && firstInset->lyxCode() == lyx::LISTINGS_CODE)
452 bool const open_par = runparams.docbook_make_pars
453 && (!runparams.docbook_in_par || par != pbegin)
456 // We want to issue the closing tag if either:
457 // (i) We opened it, and either docbook_in_par is false,
458 // or we're not in the last paragraph, anyway.
459 // (ii) We didn't open it and docbook_in_par is true,
460 // but we are in the first par, and there is a next par.
461 ParagraphList::const_iterator nextpar = par;
463 bool const close_par =
464 ((open_par && (!runparams.docbook_in_par || nextpar != pend))
465 || (!open_par && runparams.docbook_in_par && par == pbegin && nextpar != pend));
467 // Determine if this paragraph has some real content. Things like new pages are not caught
468 // by Paragraph::empty(), even though they do not generate anything useful in DocBook.
469 odocstringstream os2;
471 par->simpleDocBookOnePar(buf, xs2, runparams, text.outerFont(distance(begin, par)), open_par, close_par, 0);
473 docstring cleaned = os2.str();
474 static const lyx::regex reg("[ \\r\\n]*");
475 cleaned = from_utf8(lyx::regex_replace(to_utf8(cleaned), reg, string("")));
477 if (!cleaned.empty()) {
481 xs << XMLStream::ESCAPE_NONE << os2.str();
493 bool isNormalEnv(Layout const &lay)
495 return lay.latextype == LATEX_ENVIRONMENT
496 || lay.latextype == LATEX_BIB_ENVIRONMENT;
500 ParagraphList::const_iterator makeEnvironment(
503 OutputParams const &runparams,
505 ParagraphList::const_iterator const & pbegin,
506 ParagraphList::const_iterator const & pend)
508 ParagraphList::const_iterator const begin = text.paragraphs().begin();
509 ParagraphList::const_iterator par = pbegin;
510 Layout const &bstyle = par->layout();
511 depth_type const origdepth = pbegin->params().depth();
513 // open tag for this environment
514 openParTag(xs, bstyle);
517 // we will on occasion need to remember a layout from before.
518 Layout const *lastlay = nullptr;
520 while (par != pend) {
521 Layout const & style = par->layout();
522 ParagraphList::const_iterator send;
524 // Actual content of this paragraph.
525 switch (style.latextype) {
526 case LATEX_ENVIRONMENT:
527 case LATEX_LIST_ENVIRONMENT:
528 case LATEX_ITEM_ENVIRONMENT: {
529 // There are two possibilities in this case.
530 // One is that we are still in the environment in which we
531 // started---which we will be if the depth is the same.
532 if (par->params().depth() == origdepth) {
533 LATTEST(bstyle == style);
534 if (lastlay != nullptr) {
535 closeItemTag(xs, *lastlay);
536 if (lastlay->docbookitemwrappertag() != "NONE") {
537 xs << xml::EndTag(lastlay->docbookitemwrappertag());
543 // this will be positive if we want to skip the
544 // initial word (if it's been taken for the label).
547 // Open a wrapper tag if needed.
548 if (style.docbookitemwrappertag() != "NONE") {
549 xs << xml::StartTag(style.docbookitemwrappertag(), style.docbookitemwrapperattr());
554 if (style.labeltype != LABEL_NO_LABEL &&
555 style.docbookitemlabeltag() != "NONE") {
557 if (isNormalEnv(style)) {
558 // in this case, we print the label only for the first
559 // paragraph (as in a theorem or an abstract).
561 docstring const lbl = pbegin->params().labelString();
563 openLabelTag(xs, style);
565 closeLabelTag(xs, style);
567 // No new line after closeLabelTag.
571 } else { // some kind of list
572 if (style.labeltype == LABEL_MANUAL) {
573 // Only variablelist gets here.
575 openLabelTag(xs, style);
576 sep = par->firstWordDocBook(xs, runparams);
577 closeLabelTag(xs, style);
579 openLabelTag(xs, style);
580 xs << par->params().labelString();
581 closeLabelTag(xs, style);
584 } // end label output
586 // Start generating the item.
587 bool wasInParagraph = runparams.docbook_in_par;
588 openItemTag(xs, style);
589 bool getsIntoParagraph = openInnerItemTag(xs, style);
590 OutputParams rp = runparams;
591 rp.docbook_in_par = wasInParagraph | getsIntoParagraph;
593 // Maybe the item is completely empty, i.e. if the first word ends at the end of the current paragraph
594 // AND if the next paragraph doesn't have the same depth (if there is such a paragraph).
595 // Common case: there is only the first word on the line, but there is a nested list instead
597 bool emptyItem = false;
598 if (sep == par->size()) {
601 if (next_par == text.paragraphs().end()) // There is no next paragraph.
603 else // There is a next paragraph: check depth.
604 emptyItem = par->params().depth() >= next_par->params().depth();
608 // Avoid having an empty item, this is not valid DocBook. A single character is enough to force
609 // generation of a full <para>.
612 // Generate the rest of the paragraph, if need be.
613 par->simpleDocBookOnePar(buf, xs, rp, text.outerFont(distance(begin, par)), true, true, sep);
617 if (getsIntoParagraph)
618 closeInnerItemTag(xs, style);
620 // We may not want to close the tag yet, in particular:
621 // If we're not at the end of the item...
623 // and are doing items...
624 && !isNormalEnv(style)
625 // and if the depth has changed...
626 && par->params().depth() != origdepth) {
627 // then we'll save this layout for later, and close it when
628 // we get another item.
631 closeItemTag(xs, style);
633 // Eventually, close the item wrapper.
634 if (style.docbookitemwrappertag() != "NONE") {
635 xs << xml::EndTag(style.docbookitemwrappertag());
640 // The other possibility is that the depth has increased.
642 send = findEndOfEnvironment(par, pend);
643 par = makeEnvironment(buf, xs, runparams, text, par, send);
647 case LATEX_PARAGRAPH:
648 send = findLast(par, pend, LATEX_PARAGRAPH);
649 par = makeParagraphs(buf, xs, runparams, text, par, send);
651 case LATEX_BIB_ENVIRONMENT:
652 send = findLast(par, pend, LATEX_BIB_ENVIRONMENT);
653 par = makeParagraphBibliography(buf, xs, runparams, text, par, send);
661 if (lastlay != nullptr) {
662 closeItemTag(xs, *lastlay);
663 if (lastlay->docbookitemwrappertag() != "NONE") {
664 xs << xml::EndTag(lastlay->docbookitemwrappertag());
668 closeTag(xs, bstyle);
677 OutputParams const & runparams,
679 ParagraphList::const_iterator const & pbegin)
681 Layout const &style = pbegin->layout();
683 // No need for labels, as they are handled by DocBook tags.
685 openParTag(xs, style);
687 ParagraphList::const_iterator const begin = text.paragraphs().begin();
688 pbegin->simpleDocBookOnePar(buf, xs, runparams,
689 text.outerFont(distance(begin, pbegin)));
694 pair<ParagraphList::const_iterator, ParagraphList::const_iterator> makeAny(
698 OutputParams const &ourparams,
699 ParagraphList::const_iterator par,
700 ParagraphList::const_iterator send,
701 ParagraphList::const_iterator pend)
703 Layout const & style = par->layout();
705 switch (style.latextype) {
706 case LATEX_COMMAND: {
707 // The files with which we are working never have more than
708 // one paragraph in a command structure.
710 // if (ourparams.docbook_in_par)
711 // fix it so we don't get sections inside standard, e.g.
712 // note that we may then need to make runparams not const, so we
713 // can communicate that back.
714 // FIXME Maybe this fix should be in the routines themselves, in case
715 // they are called from elsewhere.
716 makeCommand(buf, xs, ourparams, text, par);
720 case LATEX_ENVIRONMENT:
721 case LATEX_LIST_ENVIRONMENT:
722 case LATEX_ITEM_ENVIRONMENT:
723 // FIXME Same fix here.
724 send = findEndOfEnvironment(par, pend);
725 par = makeEnvironment(buf, xs, ourparams, text, par, send);
727 case LATEX_PARAGRAPH:
728 send = findLast(par, pend, LATEX_PARAGRAPH);
729 par = makeParagraphs(buf, xs, ourparams, text, par, send);
731 case LATEX_BIB_ENVIRONMENT:
732 send = findLast(par, pend, LATEX_BIB_ENVIRONMENT);
733 par = makeParagraphBibliography(buf, xs, ourparams, text, par, send);
737 return make_pair(par, send);
740 } // end anonymous namespace
743 using DocBookDocumentSectioning = tuple<bool, pit_type>;
744 using DocBookInfoTag = tuple<set<pit_type>, set<pit_type>, pit_type, pit_type>;
747 DocBookDocumentSectioning hasDocumentSectioning(ParagraphList const ¶graphs, pit_type bpit, pit_type const epit) {
748 bool documentHasSections = false;
750 while (bpit < epit) {
751 Layout const &style = paragraphs[bpit].layout();
752 documentHasSections |= style.category() == from_utf8("Sectioning");
754 if (documentHasSections) {
759 // Paragraphs before the first section: [ runparams.par_begin ; eppit )
761 return make_tuple(documentHasSections, bpit);
765 bool hasOnlyNotes(Paragraph const & par)
767 for (int i = 0; i < par.size(); ++i)
768 if (!par.isInset(i) || !dynamic_cast<InsetNote *>(par.insetList().get(i)))
774 DocBookInfoTag getParagraphsWithInfo(ParagraphList const ¶graphs, pit_type bpit, pit_type const epit) {
775 set<pit_type> shouldBeInInfo;
776 set<pit_type> mustBeInInfo;
778 // Find the first non empty paragraph by mutating bpit.
779 while (bpit < epit) {
780 Paragraph const &par = paragraphs[bpit];
781 if (par.empty() || hasOnlyNotes(par))
787 // Find the last info-like paragraph.
788 pit_type cpit = bpit;
789 while (cpit < epit) {
790 // Skip paragraphs only containing one note.
791 Paragraph const &par = paragraphs[cpit];
792 if (hasOnlyNotes(par)) {
797 // Based on layout information, store this paragraph in one set: should be in <info>, must be.
798 Layout const &style = par.layout();
800 if (style.docbookininfo() == "always") {
801 mustBeInInfo.emplace(cpit);
802 } else if (style.docbookininfo() == "maybe") {
803 shouldBeInInfo.emplace(cpit);
805 // Hypothesis: the <info> parts should be grouped together near the beginning bpit.
810 // Now, cpit points to the last paragraph that has things that could go in <info>.
811 // bpit is still the beginning of the <info> part.
813 return make_tuple(shouldBeInInfo, mustBeInInfo, bpit, cpit);
817 bool hasAbstractBetween(ParagraphList const ¶graphs, pit_type const bpitAbstract, pit_type const epitAbstract)
819 // Hypothesis: the paragraphs between bpitAbstract and epitAbstract can be considered an abstract because they
820 // are just after a document or part title.
821 if (epitAbstract - bpitAbstract <= 0)
824 // If there is something between these paragraphs, check if it's compatible with an abstract (i.e. some text).
825 pit_type bpit = bpitAbstract;
826 while (bpit < epitAbstract) {
827 const Paragraph &p = paragraphs.at(bpit);
829 if (p.layout().name() == from_ascii("Abstract"))
832 if (!p.insetList().empty()) {
833 for (const auto &i : p.insetList()) {
834 if (i.inset->getText(0) != nullptr) {
845 pit_type generateDocBookParagraphWithoutSectioning(
849 OutputParams const & runparams,
850 ParagraphList const & paragraphs,
854 auto par = paragraphs.iterator_at(bpit);
855 auto lastStartedPar = par;
856 ParagraphList::const_iterator send;
858 (epit == (int) paragraphs.size()) ?
859 paragraphs.end() : paragraphs.iterator_at(epit);
861 while (bpit < epit) {
862 tie(par, send) = makeAny(text, buf, xs, runparams, par, send, pend);
863 bpit += distance(lastStartedPar, par);
864 lastStartedPar = par;
871 void outputDocBookInfo(
875 OutputParams const & runparams,
876 ParagraphList const & paragraphs,
877 DocBookInfoTag const & info,
878 pit_type bpitAbstract,
879 pit_type const epitAbstract)
881 // Consider everything between bpitAbstract and epitAbstract (excluded) as paragraphs for the abstract.
882 // Use bpitAbstract >= epitAbstract to indicate there is no abstract.
884 set<pit_type> shouldBeInInfo;
885 set<pit_type> mustBeInInfo;
888 tie(shouldBeInInfo, mustBeInInfo, bpitInfo, epitInfo) = info;
890 // Perform an additional check on the abstract. Sometimes, there are many paragraphs that should go
891 // into the abstract, but none generates actual content. Thus, first generate to a temporary stream,
892 // then only create the <abstract> tag if these paragraphs generate some content.
893 // This check must be performed *before* a decision on whether or not to output <info> is made.
894 bool hasAbstract = hasAbstractBetween(paragraphs, bpitAbstract, epitAbstract);
897 odocstringstream os2;
899 generateDocBookParagraphWithoutSectioning(text, buf, xs2, runparams, paragraphs, bpitAbstract, epitAbstract);
901 // Actually output the abstract if there is something to do. Don't count line feeds or spaces in this,
902 // even though they must be properly output if there is some abstract.
903 docstring abstractContent = os2.str();
904 static const lyx::regex reg("[ \\r\\n]*");
905 abstractContent = from_utf8(lyx::regex_replace(to_utf8(abstractContent), reg, string("")));
907 // Nothing? Then there is no abstract!
908 if (abstractContent.empty())
912 // The abstract must go in <info>.
913 bool needInfo = !mustBeInInfo.empty() || hasAbstract;
915 // Start the <info> tag if required.
917 xs.startDivision(false);
918 xs << xml::StartTag("info");
922 // Output the elements that should go in <info>.
923 generateDocBookParagraphWithoutSectioning(text, buf, xs, runparams, paragraphs, bpitInfo, epitInfo);
925 if (hasAbstract && !abstract.empty()) { // The second test is probably superfluous.
926 string tag = paragraphs[bpitAbstract].layout().docbookforceabstracttag();
930 xs << xml::StartTag(tag);
932 xs << XMLStream::ESCAPE_NONE << abstract;
933 xs << xml::EndTag(tag);
937 // End the <info> tag if it was started.
939 xs << xml::EndTag("info");
946 void docbookFirstParagraphs(
950 OutputParams const &runparams,
953 // Handle the beginning of the document, supposing it has sections.
954 // Major role: output the first <info> tag.
956 ParagraphList const ¶graphs = text.paragraphs();
957 pit_type bpit = runparams.par_begin;
958 DocBookInfoTag info = getParagraphsWithInfo(paragraphs, bpit, epit);
959 outputDocBookInfo(text, buf, xs, runparams, paragraphs, info, get<3>(info), epit);
963 bool isParagraphEmpty(const Paragraph &par)
965 InsetList const &insets = par.insetList();
966 size_t insetsLength = distance(insets.begin(), insets.end());
967 bool hasParagraphOnlyNote = insetsLength == 1 && insets.get(0) && insets.get(0)->asInsetCollapsible() &&
968 dynamic_cast<InsetNote *>(insets.get(0));
969 return hasParagraphOnlyNote;
973 void docbookSimpleAllParagraphs(
977 OutputParams const & runparams)
979 // Handle the document, supposing it has no sections (i.e. a "simple" document).
981 // First, the <info> tag.
982 ParagraphList const ¶graphs = text.paragraphs();
983 pit_type bpit = runparams.par_begin;
984 pit_type const epit = runparams.par_end;
985 DocBookInfoTag info = getParagraphsWithInfo(paragraphs, bpit, epit);
986 outputDocBookInfo(text, buf, xs, runparams, paragraphs, info, 0, 0);
987 bpit = get<3>(info); // Generate the content starting from the end of the <info> part.
989 // Then, the content.
990 ParagraphList::const_iterator const pend =
991 (epit == (int) paragraphs.size()) ?
992 paragraphs.end() : paragraphs.iterator_at(epit);
994 while (bpit < epit) {
995 auto par = paragraphs.iterator_at(bpit);
996 ParagraphList::const_iterator const lastStartedPar = par;
997 ParagraphList::const_iterator send;
999 if (isParagraphEmpty(*par)) {
1001 bpit += distance(lastStartedPar, par);
1005 // Generate this paragraph.
1006 tie(par, send) = makeAny(text, buf, xs, runparams, par, send, pend);
1007 bpit += distance(lastStartedPar, par);
1012 void docbookParagraphs(Text const &text,
1015 OutputParams const &runparams) {
1016 ParagraphList const ¶graphs = text.paragraphs();
1017 if (runparams.par_begin == runparams.par_end) {
1018 runparams.par_begin = 0;
1019 runparams.par_end = paragraphs.size();
1021 pit_type bpit = runparams.par_begin;
1022 pit_type const epit = runparams.par_end;
1023 LASSERT(bpit < epit,
1025 xs << XMLStream::ESCAPE_NONE << "<!-- DocBook output error! -->\n";
1029 ParagraphList::const_iterator const pend =
1030 (epit == (int) paragraphs.size()) ?
1031 paragraphs.end() : paragraphs.iterator_at(epit);
1032 std::stack<std::pair<int, string>> headerLevels; // Used to determine when to open/close sections: store the depth
1033 // of the section and the tag that was used to open it.
1035 // Detect whether the document contains sections. If there are no sections, there can be no automatically
1036 // discovered abstract.
1037 bool documentHasSections;
1039 tie(documentHasSections, eppit) = hasDocumentSectioning(paragraphs, bpit, epit);
1041 if (documentHasSections) {
1042 docbookFirstParagraphs(text, buf, xs, runparams, eppit);
1045 docbookSimpleAllParagraphs(text, buf, xs, runparams);
1049 bool currentlyInAppendix = false;
1051 while (bpit < epit) {
1052 OutputParams ourparams = runparams;
1054 auto par = paragraphs.iterator_at(bpit);
1055 if (par->params().startOfAppendix())
1056 currentlyInAppendix = true;
1057 Layout const &style = par->layout();
1058 ParagraphList::const_iterator const lastStartedPar = par;
1059 ParagraphList::const_iterator send;
1061 if (isParagraphEmpty(*par)) {
1063 bpit += distance(lastStartedPar, par);
1067 // Think about adding <section> and/or </section>s.
1068 const bool isLayoutSectioning = style.category() == from_utf8("Sectioning");
1069 if (isLayoutSectioning) {
1070 int level = style.toclevel;
1072 // Need to close a previous section if it has the same level or a higher one (close <section> if opening a <h2>
1073 // after a <h2>, <h3>, <h4>, <h5> or <h6>). More examples:
1074 // - current: h2; back: h1; do not close any <section>
1075 // - current: h1; back: h2; close two <section> (first the <h2>, then the <h1>, so a new <h1> can come)
1076 while (!headerLevels.empty() && level <= headerLevels.top().first) {
1077 int stackLevel = headerLevels.top().first;
1078 docstring stackTag = from_utf8("</" + headerLevels.top().second + ">");
1081 // Output the tag only if it corresponds to a legit section.
1082 if (stackLevel != Layout::NOT_IN_TOC)
1083 xs << XMLStream::ESCAPE_NONE << stackTag << xml::CR();
1086 // Open the new section: first push it onto the stack, then output it in DocBook.
1087 string sectionTag = (currentlyInAppendix && style.docbooksectiontag() == "chapter") ?
1088 "appendix" : style.docbooksectiontag();
1089 headerLevels.push(std::make_pair(level, sectionTag));
1091 // Some sectioning-like elements should not be output (such as FrontMatter).
1092 if (level != Layout::NOT_IN_TOC) {
1093 // Look for a label in the title, i.e. a InsetLabel as a child.
1094 docstring id = docstring();
1095 for (pos_type i = 0; i < par->size(); ++i) {
1096 Inset const *inset = par->getInset(i);
1098 if (auto label = dynamic_cast<InsetLabel const *>(inset)) {
1099 // Generate the attributes for the section if need be.
1100 id += "xml:id=\"" + xml::cleanID(label->screenLabel()) + "\"";
1102 // Don't output the ID as a DocBook <anchor>.
1103 ourparams.docbook_anchors_to_ignore.emplace(label->screenLabel());
1105 // Cannot have multiple IDs per tag.
1111 // Write the open tag for this section.
1112 docstring tag = from_utf8("<" + sectionTag);
1114 tag += from_utf8(" ") + id;
1115 tag += from_utf8(">");
1116 xs << XMLStream::ESCAPE_NONE << tag;
1121 // Close all sections before the bibliography.
1122 // 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)?
1123 auto insetsLength = distance(par->insetList().begin(), par->insetList().end());
1124 if (insetsLength > 0) {
1125 Inset const *firstInset = par->getInset(0);
1126 if (firstInset && dynamic_cast<InsetBibtex const *>(firstInset)) {
1127 while (!headerLevels.empty()) {
1128 int level = headerLevels.top().first;
1129 docstring tag = from_utf8("</" + headerLevels.top().second + ">");
1132 // Output the tag only if it corresponds to a legit section.
1133 if (level != Layout::NOT_IN_TOC) {
1134 xs << XMLStream::ESCAPE_NONE << tag;
1141 // Generate this paragraph.
1142 tie(par, send) = makeAny(text, buf, xs, ourparams, par, send, pend);
1143 bpit += distance(lastStartedPar, par);
1146 // If need be, close <section>s, but only at the end of the document (otherwise, dealt with at the beginning
1148 while (!headerLevels.empty() && headerLevels.top().first > Layout::NOT_IN_TOC) {
1149 docstring tag = from_utf8("</" + headerLevels.top().second + ">");
1151 xs << XMLStream::ESCAPE_NONE << tag;