+void docbookFirstParagraphs(Text const &text,
+ Buffer const &buf,
+ XMLStream &xs,
+ OutputParams const &runparams,
+ pit_type epit) {
+ // Handle the beginning of the document, supposing it has sections.
+ // Major role: output the first <info> tag.
+
+ ParagraphList const ¶graphs = text.paragraphs();
+ pit_type bpit = runparams.par_begin;
+ DocBookInfoTag info = getParagraphsWithInfo(paragraphs, bpit, epit);
+ outputDocBookInfo(text, buf, xs, runparams, paragraphs, info, get<3>(info), epit);
+}
+
+
+bool isParagraphEmpty(const Paragraph &par)
+{
+ InsetList const &insets = par.insetList();
+ size_t insetsLength = distance(insets.begin(), insets.end());
+ bool hasParagraphOnlyNote = insetsLength == 1 && insets.get(0) && insets.get(0)->asInsetCollapsible() &&
+ dynamic_cast<InsetNote *>(insets.get(0));
+ return hasParagraphOnlyNote;
+}
+
+
+void docbookSimpleAllParagraphs(Text const &text,
+ Buffer const &buf,
+ XMLStream &xs,
+ OutputParams const &runparams) {
+ // Handle the document, supposing it has no sections (i.e. a "simple" document).
+
+ // First, the <info> tag.
+ ParagraphList const ¶graphs = text.paragraphs();
+ pit_type bpit = runparams.par_begin;
+ pit_type const epit = runparams.par_end;
+ DocBookInfoTag info = getParagraphsWithInfo(paragraphs, bpit, epit);
+ outputDocBookInfo(text, buf, xs, runparams, paragraphs, info, 0, 0);
+ bpit = get<3>(info); // Generate the content starting from the end of the <info> part.
+
+ // Then, the content.
+ ParagraphList::const_iterator const pend =
+ (epit == (int) paragraphs.size()) ?
+ paragraphs.end() : paragraphs.iterator_at(epit);
+
+ while (bpit < epit) {
+ auto par = paragraphs.iterator_at(bpit);
+ ParagraphList::const_iterator const lastStartedPar = par;
+ ParagraphList::const_iterator send;
+
+ if (isParagraphEmpty(*par)) {
+ ++par;
+ bpit += distance(lastStartedPar, par);
+ continue;
+ }
+
+ // Generate this paragraph.
+ tie(par, send) = makeAny(text, buf, xs, runparams, par, send, pend);
+ bpit += distance(lastStartedPar, par);
+ }
+}
+
+
+void docbookParagraphs(Text const &text,
+ Buffer const &buf,
+ XMLStream &xs,
+ OutputParams const &runparams) {
+ ParagraphList const ¶graphs = text.paragraphs();
+ if (runparams.par_begin == runparams.par_end) {
+ runparams.par_begin = 0;
+ runparams.par_end = paragraphs.size();
+ }
+ pit_type bpit = runparams.par_begin;
+ pit_type const epit = runparams.par_end;
+ LASSERT(bpit < epit,
+ {
+ xs << XMLStream::ESCAPE_NONE << "<!-- DocBook output error! -->\n";
+ return;
+ });
+
+ ParagraphList::const_iterator const pend =
+ (epit == (int) paragraphs.size()) ?
+ paragraphs.end() : paragraphs.iterator_at(epit);
+ std::stack<std::pair<int, string>> headerLevels; // Used to determine when to open/close sections: store the depth
+ // of the section and the tag that was used to open it.
+
+ // Detect whether the document contains sections. If there are no sections, there can be no automatically
+ // discovered abstract.
+ bool documentHasSections;
+ pit_type eppit;
+ tie(documentHasSections, eppit) = hasDocumentSectioning(paragraphs, bpit, epit);
+
+ if (documentHasSections) {
+ docbookFirstParagraphs(text, buf, xs, runparams, eppit);
+ bpit = eppit;
+ } else {
+ docbookSimpleAllParagraphs(text, buf, xs, runparams);
+ return;
+ }
+
+ bool currentlyInAppendix = false;
+
+ while (bpit < epit) {
+ OutputParams ourparams = runparams;
+
+ auto par = paragraphs.iterator_at(bpit);
+ if (par->params().startOfAppendix())
+ currentlyInAppendix = true;
+ Layout const &style = par->layout();
+ ParagraphList::const_iterator const lastStartedPar = par;
+ ParagraphList::const_iterator send;
+
+ if (isParagraphEmpty(*par)) {
+ ++par;
+ bpit += distance(lastStartedPar, par);
+ continue;
+ }
+
+ // Think about adding <section> and/or </section>s.
+ const bool isLayoutSectioning = style.category() == from_utf8("Sectioning");
+ if (isLayoutSectioning) {
+ int level = style.toclevel;
+
+ // Need to close a previous section if it has the same level or a higher one (close <section> if opening a <h2>
+ // after a <h2>, <h3>, <h4>, <h5> or <h6>). More examples:
+ // - current: h2; back: h1; do not close any <section>
+ // - current: h1; back: h2; close two <section> (first the <h2>, then the <h1>, so a new <h1> can come)
+ while (!headerLevels.empty() && level <= headerLevels.top().first) {
+ int stackLevel = headerLevels.top().first;
+ docstring stackTag = from_utf8("</" + headerLevels.top().second + ">");
+ headerLevels.pop();
+
+ // Output the tag only if it corresponds to a legit section.
+ if (stackLevel != Layout::NOT_IN_TOC)
+ xs << XMLStream::ESCAPE_NONE << stackTag << xml::CR();
+ }
+
+ // Open the new section: first push it onto the stack, then output it in DocBook.
+ string sectionTag = (currentlyInAppendix && style.docbooksectiontag() == "chapter") ?
+ "appendix" : style.docbooksectiontag();
+ headerLevels.push(std::make_pair(level, sectionTag));
+
+ // Some sectioning-like elements should not be output (such as FrontMatter).
+ if (level != Layout::NOT_IN_TOC) {
+ // Look for a label in the title, i.e. a InsetLabel as a child.
+ docstring id = docstring();
+ for (pos_type i = 0; i < par->size(); ++i) {
+ Inset const *inset = par->getInset(i);
+ if (inset && dynamic_cast<InsetLabel const *>(inset)) {
+ // Generate the attributes for the section if need be.
+ auto label = dynamic_cast<InsetLabel const *>(inset);
+ id += "xml:id=\"" + xml::cleanID(label->screenLabel()) + "\"";
+
+ // Don't output the ID as a DocBook <anchor>.
+ ourparams.docbook_anchors_to_ignore.emplace(label->screenLabel());
+
+ // Cannot have multiple IDs per tag.
+ break;
+ }
+ }
+
+ // Write the open tag for this section.
+ docstring tag = from_utf8("<" + sectionTag);
+ if (!id.empty())
+ tag += from_utf8(" ") + id;
+ tag += from_utf8(">");
+ xs << XMLStream::ESCAPE_NONE << tag;
+ xs << xml::CR();
+ }
+ }
+
+ // Close all sections before the bibliography.
+ // 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)?
+ auto insetsLength = distance(par->insetList().begin(), par->insetList().end());
+ if (insetsLength > 0) {
+ Inset const *firstInset = par->getInset(0);
+ if (firstInset && dynamic_cast<InsetBibtex const *>(firstInset)) {
+ while (!headerLevels.empty()) {
+ int level = headerLevels.top().first;
+ docstring tag = from_utf8("</" + headerLevels.top().second + ">");
+ headerLevels.pop();
+
+ // Output the tag only if it corresponds to a legit section.
+ if (level != Layout::NOT_IN_TOC) {
+ xs << XMLStream::ESCAPE_NONE << tag;
+ xs << xml::CR();
+ }
+ }
+ }
+ }
+
+ // Generate this paragraph.
+ tie(par, send) = makeAny(text, buf, xs, ourparams, par, send, pend);
+ bpit += distance(lastStartedPar, par);
+ }
+
+ // If need be, close <section>s, but only at the end of the document (otherwise, dealt with at the beginning
+ // of the loop).
+ while (!headerLevels.empty() && headerLevels.top().first > Layout::NOT_IN_TOC) {
+ docstring tag = from_utf8("</" + headerLevels.top().second + ">");
+ headerLevels.pop();
+ xs << XMLStream::ESCAPE_NONE << tag;
+ xs << xml::CR();
+ }
+}
+