]> git.lyx.org Git - lyx.git/blob - src/output_docbook.cpp
Refactor InsetQuotes.h enums
[lyx.git] / src / output_docbook.cpp
1 /**
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.
5  *
6  * \author Lars Gullik Bjønnes
7  * \author José Matos
8  *
9  * Full author contact details are available in file CREDITS.
10  */
11
12 #include <config.h>
13
14 #include "output_docbook.h"
15
16 #include "Buffer.h"
17 #include "buffer_funcs.h"
18 #include "BufferParams.h"
19 #include "Font.h"
20 #include "InsetList.h"
21 #include "Paragraph.h"
22 #include "ParagraphList.h"
23 #include "ParagraphParameters.h"
24 #include "xml.h"
25 #include "Text.h"
26 #include "TextClass.h"
27
28 #include "insets/InsetBibtex.h"
29 #include "insets/InsetBibitem.h"
30 #include "insets/InsetLabel.h"
31 #include "mathed/InsetMath.h"
32 #include "insets/InsetNote.h"
33
34 #include "support/debug.h"
35 #include "support/lassert.h"
36 #include "support/textutils.h"
37
38 #include <stack>
39 #include <iostream>
40 #include <algorithm>
41 #include <sstream>
42
43 using namespace std;
44 using namespace lyx::support;
45
46 namespace lyx {
47
48 namespace {
49
50 std::string fontToDocBookTag(xml::FontTypes type)
51 {
52         switch (type) {
53         case xml::FontTypes::FT_EMPH:
54         case xml::FontTypes::FT_BOLD:
55                 return "emphasis";
56         case xml::FontTypes::FT_NOUN:
57                 return "personname";
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:
69                 return "emphasis";
70         case xml::FontTypes::FT_TYPE:
71                 return "code";
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:
84                 return "emphasis";
85         default:
86                 return "";
87         }
88 }
89
90
91 string fontToRole(xml::FontTypes type)
92 {
93         // Specific fonts are achieved with roles. The only common ones are "" for basic emphasis,
94         // and "bold"/"strong" for bold. With some specific options, other roles are copied into
95         // HTML output (via the DocBook XSLT sheets); otherwise, if not recognised, they are just ignored.
96         // Hence, it is not a problem to have many roles by default here.
97         // See https://www.sourceware.org/ml/docbook/2003-05/msg00269.html
98         switch (type) {
99         case xml::FontTypes::FT_ITALIC:
100         case xml::FontTypes::FT_EMPH:
101                 return "";
102         case xml::FontTypes::FT_BOLD:
103                 return "bold";
104         case xml::FontTypes::FT_NOUN: // Outputs a <person>
105         case xml::FontTypes::FT_TYPE: // Outputs a <code>
106                 return "";
107         case xml::FontTypes::FT_UBAR:
108                 return "underline";
109
110         // All other roles are non-standard for DocBook.
111
112         case xml::FontTypes::FT_WAVE:
113                 return "wave";
114         case xml::FontTypes::FT_DBAR:
115                 return "dbar";
116         case xml::FontTypes::FT_SOUT:
117                 return "sout";
118         case xml::FontTypes::FT_XOUT:
119                 return "xout";
120         case xml::FontTypes::FT_UPRIGHT:
121                 return "upright";
122         case xml::FontTypes::FT_SLANTED:
123                 return "slanted";
124         case xml::FontTypes::FT_SMALLCAPS:
125                 return "smallcaps";
126         case xml::FontTypes::FT_ROMAN:
127                 return "roman";
128         case xml::FontTypes::FT_SANS:
129                 return "sans";
130         case xml::FontTypes::FT_SIZE_TINY:
131                 return "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:
137                 return "size_small";
138         case xml::FontTypes::FT_SIZE_NORMAL:
139                 return "size_normal";
140         case xml::FontTypes::FT_SIZE_LARGE:
141                 return "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:
147                 return "size_huge";
148         case xml::FontTypes::FT_SIZE_HUGER:
149                 return "size_huger";
150         case xml::FontTypes::FT_SIZE_INCREASE:
151                 return "size_increase";
152         case xml::FontTypes::FT_SIZE_DECREASE:
153                 return "size_decrease";
154         default:
155                 return "";
156         }
157 }
158
159
160 string fontToAttribute(xml::FontTypes type) {
161         // If there is a role (i.e. nonstandard use of a tag), output the attribute. Otherwise, the sheer tag is sufficient
162         // for the font.
163         string role = fontToRole(type);
164         if (!role.empty())
165                 return "role='" + role + "'";
166         else
167                 return "";
168 }
169
170
171 // Higher-level convenience functions.
172
173 void openParTag(XMLStream & xs, const Paragraph * par, const Paragraph * prevpar, const OutputParams & runparams)
174 {
175         if (par == prevpar)
176                 prevpar = nullptr;
177
178         // If the previous paragraph is empty, don't consider it when opening wrappers.
179         if (prevpar && prevpar->empty() && !prevpar->allowEmpty())
180                 prevpar = nullptr;
181
182         // When should the wrapper be opened here? Only if the previous paragraph has the SAME wrapper tag
183         // (usually, they won't have the same layout) and the CURRENT one allows merging.
184         // The main use case is author information in several paragraphs: if the name of the author is the
185         // first paragraph of an author, then merging with the previous tag does not make sense. Say the
186         // next paragraph is the affiliation, then it should be output in the same <author> tag (different
187         // layout, same wrapper tag).
188         Layout const & lay = par->layout();
189         bool openWrapper = lay.docbookwrappertag() != "NONE" && !runparams.docbook_ignore_wrapper;
190
191         if (prevpar != nullptr && !runparams.docbook_ignore_wrapper) {
192                 Layout const & prevlay = prevpar->layout();
193                 if (prevlay.docbookwrappertag() != "NONE") {
194                         if (prevlay.docbookwrappertag() == lay.docbookwrappertag() &&
195                                         prevlay.docbookwrapperattr() == lay.docbookwrapperattr())
196                                 openWrapper = !lay.docbookwrappermergewithprevious();
197                         else
198                                 openWrapper = true;
199                 }
200         }
201
202         // Main logic.
203         if (openWrapper)
204                 xml::openTag(xs, lay.docbookwrappertag(), lay.docbookwrapperattr(), lay.docbookwrappertagtype());
205
206         const string & tag = lay.docbooktag();
207         if (tag != "NONE") {
208                 auto xmltag = xml::ParTag(tag, lay.docbookattr());
209                 if (!xs.isTagOpen(xmltag, 1)) { // Don't nest a paragraph directly in a paragraph.
210                         // TODO: required or not?
211                         // TODO: avoid creating a ParTag object just for this query...
212                         xml::openTag(xs, lay.docbooktag(), lay.docbookattr(), lay.docbooktagtype());
213                         xml::openTag(xs, lay.docbookinnertag(), lay.docbookinnerattr(), lay.docbookinnertagtype());
214                 }
215         }
216
217         xml::openTag(xs, lay.docbookitemwrappertag(), lay.docbookitemwrapperattr(), lay.docbookitemwrappertagtype());
218         xml::openTag(xs, lay.docbookitemtag(), lay.docbookitemattr(), lay.docbookitemtagtype());
219         xml::openTag(xs, lay.docbookiteminnertag(), lay.docbookiteminnerattr(), lay.docbookiteminnertagtype());
220 }
221
222
223 void closeParTag(XMLStream & xs, Paragraph const * par, Paragraph const * nextpar, const OutputParams & runparams)
224 {
225         if (par == nextpar)
226                 nextpar = nullptr;
227
228         // If the next paragraph is empty, don't consider it when closing wrappers.
229         if (nextpar && nextpar->empty() && !nextpar->allowEmpty())
230                 nextpar = nullptr;
231
232         // See comment in openParTag.
233         Layout const & lay = par->layout();
234         bool closeWrapper = lay.docbookwrappertag() != "NONE" && !runparams.docbook_ignore_wrapper;
235
236         if (nextpar != nullptr && !runparams.docbook_ignore_wrapper) {
237                 Layout const & nextlay = nextpar->layout();
238                 if (nextlay.docbookwrappertag() != "NONE") {
239                         if (nextlay.docbookwrappertag() == lay.docbookwrappertag() &&
240                                         nextlay.docbookwrapperattr() == lay.docbookwrapperattr())
241                                 closeWrapper = !nextlay.docbookwrappermergewithprevious();
242                         else
243                                 closeWrapper = true;
244                 }
245         }
246
247         // Main logic.
248         xml::closeTag(xs, lay.docbookiteminnertag(), lay.docbookiteminnertagtype());
249         xml::closeTag(xs, lay.docbookitemtag(), lay.docbookitemtagtype());
250         xml::closeTag(xs, lay.docbookitemwrappertag(), lay.docbookitemwrappertagtype());
251         xml::closeTag(xs, lay.docbookinnertag(), lay.docbookinnertagtype());
252         xml::closeTag(xs, lay.docbooktag(), lay.docbooktagtype());
253         if (closeWrapper)
254                 xml::closeTag(xs, lay.docbookwrappertag(), lay.docbookwrappertagtype());
255 }
256
257
258 void makeBibliography(
259                 Text const & text,
260                 Buffer const & buf,
261                 XMLStream & xs,
262                 OutputParams const & runparams,
263                 ParagraphList::const_iterator const & par)
264 {
265         // If this is the first paragraph in a bibliography, open the bibliography tag.
266         auto const * pbegin_before = text.paragraphs().getParagraphBefore(par);
267         if (pbegin_before == nullptr || (pbegin_before && pbegin_before->layout().latextype != LATEX_BIB_ENVIRONMENT)) {
268                 xs << xml::StartTag("bibliography");
269                 xs << xml::CR();
270         }
271
272         // Start the precooked bibliography entry. This is very much like opening a paragraph tag.
273         // Don't forget the citation ID!
274         docstring attr;
275         for (auto i = 0; i < par->size(); ++i) {
276                 Inset const *ip = par->getInset(i);
277                 if (!ip)
278                         continue;
279                 if (const auto * bibitem = dynamic_cast<const InsetBibitem*>(ip)) {
280                         auto id = xml::cleanID(bibitem->getParam("key"));
281                         attr = from_utf8("xml:id='") + id + from_utf8("'");
282                         break;
283                 }
284         }
285         xs << xml::StartTag(from_utf8("bibliomixed"), attr);
286
287         // Generate the entry. Concatenate the different parts of the paragraph if any.
288         auto const begin = text.paragraphs().begin();
289         auto pars = par->simpleDocBookOnePar(buf, runparams, text.outerFont(std::distance(begin, par)), 0);
290         for (auto & parXML : pars)
291                 xs << XMLStream::ESCAPE_NONE << parXML;
292
293         // End the precooked bibliography entry.
294         xs << xml::EndTag("bibliomixed");
295         xs << xml::CR();
296
297         // If this is the last paragraph in a bibliography, close the bibliography tag.
298         auto const end = text.paragraphs().end();
299         auto nextpar = par;
300         ++nextpar;
301         bool endBibliography = nextpar == end || nextpar->layout().latextype != LATEX_BIB_ENVIRONMENT;
302
303         if (endBibliography) {
304                 xs << xml::EndTag("bibliography");
305                 xs << xml::CR();
306         }
307 }
308
309
310 void makeParagraph(
311                 Text const & text,
312                 Buffer const & buf,
313                 XMLStream & xs,
314                 OutputParams const & runparams,
315                 ParagraphList::const_iterator const & par)
316 {
317         // Useful variables.
318         auto const begin = text.paragraphs().begin();
319         auto const end = text.paragraphs().end();
320         auto prevpar = text.paragraphs().getParagraphBefore(par);
321
322         // We want to open the paragraph tag if:
323         //   (i) the current layout permits multiple paragraphs
324         //  (ii) we are either not already inside a paragraph (HTMLIsBlock) OR
325         //         we are, but this is not the first paragraph
326         //
327         // But there is also a special case, and we first see whether we are in it.
328         // We do not want to open the paragraph tag if this paragraph contains
329         // only one item, and that item is "inline", i.e., not HTMLIsBlock (such
330         // as a branch). On the other hand, if that single item has a font change
331         // applied to it, then we still do need to open the paragraph.
332         //
333         // Obviously, this is very fragile. The main reason we need to do this is
334         // because of branches, e.g., a branch that contains an entire new section.
335         // We do not really want to wrap that whole thing in a <div>...</div>.
336         bool special_case = false;
337         Inset const *specinset = par->size() == 1 ? par->getInset(0) : nullptr;
338         if (specinset && !specinset->getLayout().htmlisblock()) { // TODO: Convert htmlisblock to a DocBook parameter?
339                 Layout const &style = par->layout();
340                 FontInfo const first_font = style.labeltype == LABEL_MANUAL ?
341                                                                         style.labelfont : style.font;
342                 FontInfo const our_font =
343                                 par->getFont(buf.masterBuffer()->params(), 0,
344                                                          text.outerFont(std::distance(begin, par))).fontInfo();
345
346                 if (first_font == our_font)
347                         special_case = true;
348         }
349
350         auto nInsets = std::distance(par->insetList().begin(), par->insetList().end());
351         auto parSize = (size_t) par->size();
352
353         // Plain layouts must be ignored.
354         special_case |= buf.params().documentClass().isPlainLayout(par->layout()) && !runparams.docbook_force_pars;
355         // Equations do not deserve their own paragraph (DocBook allows them outside paragraphs).
356         // Exception: any case that generates an <inlineequation> must still get a paragraph to be valid.
357         special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), [](InsetList::Element inset) {
358                 return inset.inset && inset.inset->asInsetMath() && inset.inset->asInsetMath()->getType() != hullSimple;
359         });
360
361         // Things that should not get into their own paragraph. (Only valid for DocBook.)
362         static std::set<InsetCode> lyxCodeSpecialCases = {
363                         TABULAR_CODE,
364                         FLOAT_CODE,
365                         BIBTEX_CODE, // Bibliographies cannot be in paragraphs. Bibitems should still be handled as paragraphs,
366                         // though (see makeParagraphBibliography).
367                         ERT_CODE, // ERTs are in comments, not paragraphs.
368                         LISTINGS_CODE,
369                         BOX_CODE,
370                         INCLUDE_CODE,
371                         NOMENCL_PRINT_CODE,
372                         TOC_CODE, // To be ignored in DocBook, the processor afterwards should deal with ToCs.
373                         NOTE_CODE // Notes do not produce any output.
374         };
375         auto isLyxCodeSpecialCase = [](InsetList::Element inset) {
376                 return lyxCodeSpecialCases.find(inset.inset->lyxCode()) != lyxCodeSpecialCases.end();
377         };
378         special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), isLyxCodeSpecialCase);
379
380         // Flex elements (InsetLayout) have their own parameter to control the special case.
381         auto isFlexSpecialCase = [](InsetList::Element inset) {
382                 if (inset.inset->lyxCode() != FLEX_CODE)
383                         return false;
384                 // Standard condition: check the parameter.
385                 if (inset.inset->getLayout().docbooknotinpara())
386                         return true;
387
388                 // If the parameter is not set, maybe the flex inset only contains things that should match the standard
389                 // condition. In this case, isLyxCodeSpecialCase must also check for bibitems...
390                 auto isLyxCodeSpecialCase = [](InsetList::Element inset) {
391                         return lyxCodeSpecialCases.find(inset.inset->lyxCode()) != lyxCodeSpecialCases.end() ||
392                                         inset.inset->lyxCode() == BIBITEM_CODE;
393                 };
394                 if (InsetText * text = inset.inset->asInsetText()) {
395                         for (auto const & par : text->paragraphs()) {
396                                 auto nInsets = std::distance(par.insetList().begin(), par.insetList().end());
397                                 auto parSize = (size_t) par.size();
398
399                                 if (nInsets == 1 && par.insetList().begin()->inset->lyxCode() == BIBITEM_CODE)
400                                         return true;
401                                 if (nInsets != parSize)
402                                         return false;
403                                 if (!std::all_of(par.insetList().begin(), par.insetList().end(), isLyxCodeSpecialCase))
404                                         return false;
405                         }
406                         return true;
407                 }
408
409                 // No case matched: give up.
410                 return false;
411         };
412         special_case |= nInsets == parSize && std::all_of(par->insetList().begin(), par->insetList().end(), isFlexSpecialCase);
413
414         // Open a paragraph if it is allowed, we are not already within a paragraph, and the insets in the paragraph do
415         // not forbid paragraphs (aka special cases).
416         bool const open_par = runparams.docbook_make_pars
417                                                   && !runparams.docbook_in_par
418                                                   && !special_case;
419
420         // We want to issue the closing tag if either:
421         //   (i)  We opened it, and either docbook_in_par is false,
422         //              or we're not in the last paragraph, anyway.
423         //   (ii) We didn't open it and docbook_in_par is true,
424         //              but we are in the first par, and there is a next par.
425         bool const close_par = open_par && !runparams.docbook_in_par;
426
427         // Determine if this paragraph has some real content. Things like new pages are not caught
428         // by Paragraph::empty(), even though they do not generate anything useful in DocBook.
429         // Thus, remove all spaces (including new lines: \r, \n) before checking for emptiness.
430         // std::all_of allows doing this check without having to copy the string.
431         // Open and close tags around each contained paragraph.
432         auto nextpar = par;
433         ++nextpar;
434         auto pars = par->simpleDocBookOnePar(buf, runparams, text.outerFont(distance(begin, par)), 0, nextpar == end, special_case);
435         for (docstring const & parXML : pars) {
436                 if (!xml::isNotOnlySpace(parXML))
437                         continue;
438
439                 if (open_par)
440                         openParTag(xs, &*par, prevpar, runparams);
441
442                 xs << XMLStream::ESCAPE_NONE << parXML;
443
444                 if (close_par)
445                         closeParTag(xs, &*par, (nextpar != end) ? &*nextpar : nullptr, runparams);
446         }
447 }
448
449
450 void makeEnvironment(Text const &text,
451                                          Buffer const &buf,
452                      XMLStream &xs,
453                      OutputParams const &runparams,
454                      ParagraphList::const_iterator const & par)
455 {
456         // Useful variables.
457         auto const end = text.paragraphs().end();
458         auto nextpar = par;
459         ++nextpar;
460
461         // Special cases for listing-like environments provided in layouts. This is quite ad-hoc, but provides a useful
462         // default. This should not be used by too many environments (only LyX-Code right now).
463         // This would be much simpler if LyX-Code was implemented as InsetListings...
464         bool mimicListing = false;
465         bool ignoreFonts = false;
466         if (par->layout().docbooktag() == "programlisting") {
467                 mimicListing = true;
468                 ignoreFonts = true;
469         }
470
471         // Output the opening tag for this environment, but only if it has not been previously opened (condition
472         // implemented in openParTag).
473         auto prevpar = text.paragraphs().getParagraphBefore(par);
474         openParTag(xs, &*par, prevpar, runparams);
475
476         // Generate the contents of this environment. There is a special case if this is like some environment.
477         Layout const & style = par->layout();
478         if (style.latextype == LATEX_COMMAND) {
479                 // Nothing to do (otherwise, infinite loops).
480         } else if (style.latextype == LATEX_ENVIRONMENT) {
481                 // Generate the paragraph, if need be.
482                 auto pars = par->simpleDocBookOnePar(buf, runparams, text.outerFont(std::distance(text.paragraphs().begin(), par)), 0, false, ignoreFonts);
483
484                 if (mimicListing) {
485                         auto p = pars.begin();
486                         while (p != pars.end()) {
487                                 xml::openTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnerattr(),
488                                              par->layout().docbookiteminnertagtype());
489                                 xs << XMLStream::ESCAPE_NONE << *p;
490                                 xml::closeTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnertagtype());
491                                 ++p;
492
493                                 // Insert a new line after each "paragraph" (i.e. line in the listing), except for the last one.
494                                 // Otherwise, there would one more new line in the output than in the LyX document.
495                                 if (p != pars.end())
496                                         xs << xml::CR();
497                         }
498                 } else {
499                         for (auto const & p : pars) {
500                                 xml::openTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnerattr(),
501                                              par->layout().docbookiteminnertagtype());
502                                 xs << XMLStream::ESCAPE_NONE << p;
503                                 xml::closeTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnertagtype());
504                         }
505                 }
506         } else {
507                 makeAny(text, buf, xs, runparams, par);
508         }
509
510         // Close the environment.
511         closeParTag(xs, &*par, (nextpar != end) ? &*nextpar : nullptr, runparams);
512 }
513
514
515 ParagraphList::const_iterator findEndOfEnvironment(
516                 ParagraphList::const_iterator const & pstart,
517                 ParagraphList::const_iterator const & pend)
518 {
519         // Copy-paste from XHTML. Should be factored out at some point...
520         ParagraphList::const_iterator p = pstart;
521         Layout const & bstyle = p->layout();
522         size_t const depth = p->params().depth();
523         for (++p; p != pend; ++p) {
524                 Layout const & style = p->layout();
525                 // It shouldn't happen that e.g. a section command occurs inside
526                 // a quotation environment, at a higher depth, but as of 6/2009,
527                 // it can happen. We pretend that it's just at lowest depth.
528                 if (style.latextype == LATEX_COMMAND)
529                         return p;
530
531                 // If depth is down, we're done
532                 if (p->params().depth() < depth)
533                         return p;
534
535                 // If depth is up, we're not done
536                 if (p->params().depth() > depth)
537                         continue;
538
539                 // FIXME I am not sure about the first check.
540                 // Surely we *could* have different layouts that count as
541                 // LATEX_PARAGRAPH, right?
542                 if (style.latextype == LATEX_PARAGRAPH || style != bstyle)
543                         return p;
544         }
545         return pend;
546 }
547
548
549 ParagraphList::const_iterator makeListEnvironment(Text const &text,
550                                                                                                   Buffer const &buf,
551                                                           XMLStream &xs,
552                                                           OutputParams const &runparams,
553                                                           ParagraphList::const_iterator const & begin)
554 {
555         // Useful variables.
556         auto par = begin;
557         auto const end = text.paragraphs().end();
558         auto const envend = findEndOfEnvironment(par, end);
559
560         // Output the opening tag for this environment.
561         Layout const & envstyle = par->layout();
562         xml::openTag(xs, envstyle.docbookwrappertag(), envstyle.docbookwrapperattr(), envstyle.docbookwrappertagtype());
563         xml::openTag(xs, envstyle.docbooktag(), envstyle.docbookattr(), envstyle.docbooktagtype());
564
565         // Handle the content of the list environment, item by item.
566         while (par != envend) {
567                 // Skip this paragraph if it is both empty and the last one (otherwise, there may be deeper paragraphs after).
568                 auto nextpar = par;
569                 ++nextpar;
570                 if (par->empty() && nextpar == envend)
571                         break;
572
573                 // Open the item wrapper.
574                 Layout const & style = par->layout();
575                 xml::openTag(xs, style.docbookitemwrappertag(), style.docbookitemwrapperattr(),
576                              style.docbookitemwrappertagtype());
577
578                 // Generate the label, if need be. If it is taken from the text, sep != 0 and corresponds to the first
579                 // character after the label.
580                 pos_type sep = 0;
581                 if (style.labeltype != LABEL_NO_LABEL && style.docbookitemlabeltag() != "NONE") {
582                         if (style.labeltype == LABEL_MANUAL) {
583                                 // Only variablelist gets here (or similar items defined as an extension in the layout).
584                                 xml::openTag(xs, style.docbookitemlabeltag(), style.docbookitemlabelattr(),
585                                              style.docbookitemlabeltagtype());
586                                 sep = 1 + par->firstWordDocBook(xs, runparams);
587                                 xml::closeTag(xs, style.docbookitemlabeltag(), style.docbookitemlabeltagtype());
588                         } else {
589                                 // Usual cases: maybe there is something specified at the layout level. Highly unlikely, though.
590                                 docstring const lbl = par->params().labelString();
591
592                                 if (!lbl.empty()) {
593                                         xml::openTag(xs, style.docbookitemlabeltag(), style.docbookitemlabelattr(),
594                                                      style.docbookitemlabeltagtype());
595                                         xs << lbl;
596                                         xml::closeTag(xs, style.docbookitemlabeltag(), style.docbookitemlabeltagtype());
597                                 }
598                         }
599                 }
600
601                 // Open the item (after the wrapper and the label).
602                 xml::openTag(xs, style.docbookitemtag(), style.docbookitemattr(), style.docbookitemtagtype());
603
604                 // Generate the content of the item.
605                 if (sep < par->size()) {
606                         auto pars = par->simpleDocBookOnePar(buf, runparams,
607                                                              text.outerFont(std::distance(text.paragraphs().begin(), par)), sep);
608                         for (auto &p : pars) {
609                                 xml::openTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnerattr(),
610                                              par->layout().docbookiteminnertagtype());
611                                 xs << XMLStream::ESCAPE_NONE << p;
612                                 xml::closeTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnertagtype());
613                         }
614                 } else {
615                         // DocBook doesn't like emptiness.
616                         xml::compTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnerattr(),
617                                      par->layout().docbookiteminnertagtype());
618                 }
619
620                 // If the next item is deeper, it must go entirely within this item (do it recursively).
621                 // By construction, with findEndOfEnvironment, depth can only stay constant or increase, never decrease.
622                 depth_type currentDepth = par->getDepth();
623                 ++par;
624                 while (par != envend && par->getDepth() != currentDepth)
625                         par = makeAny(text, buf, xs, runparams, par);
626                 // Usually, this loop only makes one iteration, except in complex scenarios, like an item with a paragraph,
627                 // a list, and another paragraph; or an item with two types of list (itemise then enumerate, for instance).
628
629                 // Close the item.
630                 xml::closeTag(xs, style.docbookitemtag(), style.docbookitemtagtype());
631                 xml::closeTag(xs, style.docbookitemwrappertag(), style.docbookitemwrappertagtype());
632         }
633
634         // Close this environment in exactly the same way as it was opened.
635         xml::closeTag(xs, envstyle.docbooktag(), envstyle.docbooktagtype());
636         xml::closeTag(xs, envstyle.docbookwrappertag(), envstyle.docbookwrappertagtype());
637
638         return envend;
639 }
640
641
642 void makeCommand(
643                 Text const & text,
644                 Buffer const & buf,
645                 XMLStream & xs,
646                 OutputParams const & runparams,
647                 ParagraphList::const_iterator const & par)
648 {
649         // Useful variables.
650         // Unlike XHTML, no need for labels, as they are handled by DocBook tags.
651         auto const begin = text.paragraphs().begin();
652         auto const end = text.paragraphs().end();
653         auto nextpar = par;
654         ++nextpar;
655
656         // Generate this command.
657         auto prevpar = text.paragraphs().getParagraphBefore(par);
658         openParTag(xs, &*par, prevpar, runparams);
659
660         auto pars = par->simpleDocBookOnePar(buf, runparams,text.outerFont(distance(begin, par)));
661         for (auto & parXML : pars)
662                 // TODO: decide what to do with openParTag/closeParTag in new lines.
663                 xs << XMLStream::ESCAPE_NONE << parXML;
664
665         closeParTag(xs, &*par, (nextpar != end) ? &*nextpar : nullptr, runparams);
666 }
667
668
669 bool isLayoutSectioning(Layout const & lay)
670 {
671         if (lay.docbooksection()) // Special case: some DocBook styles must be handled as sections.
672                 return true;
673         else if (lay.category() == from_utf8("Sectioning") || lay.docbooktag() == "section") // Generic case.
674                 return lay.toclevel != Layout::NOT_IN_TOC;
675         return false;
676 }
677
678
679 bool isLayoutSectioningOrSimilar(Layout const & lay)
680 {
681         return isLayoutSectioning(lay) || lay.docbooktag() == "bridgehead";
682 }
683
684
685 using DocBookDocumentSectioning = tuple<bool, pit_type>;
686
687
688 struct DocBookInfoTag
689 {
690         const set<pit_type> shouldBeInInfo;
691         const set<pit_type> mustBeInInfo; // With the notable exception of the abstract!
692         const set<pit_type> abstract;
693         const bool abstractLayout;
694         pit_type bpit;
695         pit_type epit;
696
697         DocBookInfoTag(const set<pit_type> & shouldBeInInfo, const set<pit_type> & mustBeInInfo,
698                                    const set<pit_type> & abstract, bool abstractLayout, pit_type bpit, pit_type epit) :
699                                    shouldBeInInfo(shouldBeInInfo), mustBeInInfo(mustBeInInfo), abstract(abstract),
700                                    abstractLayout(abstractLayout), bpit(bpit), epit(epit) {}
701 };
702
703
704 DocBookDocumentSectioning hasDocumentSectioning(ParagraphList const &paragraphs, pit_type bpit, pit_type const epit) {
705         bool documentHasSections = false;
706
707         while (bpit < epit) {
708                 Layout const &style = paragraphs[bpit].layout();
709                 documentHasSections |= isLayoutSectioningOrSimilar(style);
710
711                 if (documentHasSections)
712                         break;
713                 bpit += 1;
714         }
715         // Paragraphs before the first section: [ runparams.par_begin ; eppit )
716
717         return make_tuple(documentHasSections, bpit);
718 }
719
720
721 bool hasOnlyNotes(Paragraph const & par)
722 {
723         // Precondition: the paragraph is not empty. Otherwise, the function will always return true...
724         for (int i = 0; i < par.size(); ++i)
725                 // If you find something that is not an inset (like actual text) or an inset that is not a note,
726                 // return false.
727                 if (!par.isInset(i) || par.getInset(i)->lyxCode() != NOTE_CODE)
728                         return false;
729
730         // An empty paragraph may still require some output.
731         if (par.layout().docbooksection())
732                 return false;
733
734         // There should be really no content here.
735         return true;
736 }
737
738
739 DocBookInfoTag getParagraphsWithInfo(ParagraphList const &paragraphs,
740                                                                          pit_type bpit, pit_type const epit,
741                                                                          // Typically, bpit is the beginning of the document and epit the end of the
742                                                                          // document *or* the first section.
743                                                                          bool documentHasSections,
744                                                                          bool detectUnlayoutedAbstract
745                                                                          // Whether paragraphs with no specific layout should be detected as abstracts.
746                                                                          // For inner sections, an abstract should only be detected if it has a specific
747                                                                          // layout. For others, anything that might look like an abstract should be sought.
748                                                                          ) {
749         set<pit_type> shouldBeInInfo;
750         set<pit_type> mustBeInInfo;
751         set<pit_type> abstractWithLayout;
752         set<pit_type> abstractNoLayout;
753
754         // Find the first non empty paragraph by mutating bpit.
755         while (bpit < epit) {
756                 Paragraph const &par = paragraphs[bpit];
757                 if (par.empty() || hasOnlyNotes(par))
758                         bpit += 1;
759                 else
760                         break;
761         }
762
763         // Traverse everything that might belong to <info>.
764         bool hasAbstractLayout = false;
765         static depth_type INVALID_DEPTH = 100000;
766         depth_type abstractDepth = INVALID_DEPTH;
767         pit_type cpit = bpit;
768         for (; cpit < epit; ++cpit) {
769                 // Skip paragraphs that don't generate anything in DocBook.
770                 Paragraph const & par = paragraphs[cpit];
771                 Layout const &style = par.layout();
772                 if (hasOnlyNotes(par))
773                         continue;
774
775                 // There should never be any section here, except for the first paragraph (a title can be part of <info>).
776                 // (Just a sanity check: if this fails, this function could end up processing the whole document.)
777                 if (cpit != bpit && isLayoutSectioningOrSimilar(par.layout())) {
778                         LYXERR0("Assertion failed: section found in potential <info> paragraphs.");
779                         break;
780                 }
781
782                 // If this is marked as an abstract by the layout, put it in the right set.
783                 if (style.docbookabstract()) {
784                         hasAbstractLayout = true;
785                         abstractDepth = par.getDepth();
786                         abstractWithLayout.emplace(cpit);
787                         continue;
788                 }
789
790                 // Deeper paragraphs following the abstract must still be considered as part of the abstract.
791                 // For instance, this includes lists. There should not be any other kind of paragraph in between.
792                 if (abstractDepth != INVALID_DEPTH && style.docbookininfo() == "never") {
793                         if (par.getDepth() > abstractDepth) {
794                                 abstractWithLayout.emplace(cpit);
795                                 continue;
796                         }
797                         if (par.getDepth() == abstractDepth) {
798                                 // This is not an abstract paragraph and it should not either be considered as part
799                                 // of it. It breaks the rule that abstract paragraphs must follow each other.
800                                 abstractDepth = INVALID_DEPTH;
801                                 break;
802                         }
803                 }
804
805                 // Based on layout information, store this paragraph in one set: should be in <info>, must be,
806                 // or abstract (either because of layout or of position).
807                 if (style.docbookininfo() == "always")
808                         mustBeInInfo.emplace(cpit);
809                 else if (style.docbookininfo() == "maybe")
810                         shouldBeInInfo.emplace(cpit);
811                 else if (documentHasSections && !hasAbstractLayout && detectUnlayoutedAbstract &&
812                                 (style.docbooktag() == "NONE" || style.docbooktag() == "para") &&
813                                 style.docbookwrappertag() == "NONE")
814                         // In this case, it is very likely that style.docbookininfo() == "never"! Be extra careful
815                         // about anything that gets caught here. For instance, don't ake into account
816                         abstractNoLayout.emplace(cpit);
817                 else // This should definitely not be in <info>.
818                         break;
819         }
820         // Now, cpit points to the first paragraph that no more has things that could go in <info>.
821         // bpit is the beginning of the <info> part.
822
823         return DocBookInfoTag(shouldBeInInfo, mustBeInInfo,
824                                               hasAbstractLayout ? abstractWithLayout : abstractNoLayout,
825                                               hasAbstractLayout, bpit, cpit);
826 }
827
828 } // end anonymous namespace
829
830
831 std::set<const Inset *> gatherInfo(ParagraphList::const_iterator par)
832 {
833         // This function has a structure highly similar to makeAny and its friends. It's only made to be called on what
834         // should become the document's <abstract>.
835         std::set<const Inset *> values;
836
837         // If this kind of layout should be ignored, already leave.
838         if (par->layout().docbooktag() == "IGNORE")
839                 return values;
840
841         // If this should go in info, mark it as such. Dive deep into the abstract, as it may hide many things that
842         // DocBook doesn't want to be inside the abstract.
843         for (pos_type i = 0; i < par->size(); ++i) {
844                 if (par->getInset(i) && par->getInset(i)->asInsetText()) {
845                         InsetText const *inset = par->getInset(i)->asInsetText();
846
847                         if (inset->getLayout().docbookininfo() != "never") {
848                                 values.insert(inset);
849                         } else {
850                                 auto subpar = inset->paragraphs().begin();
851                                 while (subpar != inset->paragraphs().end()) {
852                                         auto subinfos = gatherInfo(subpar);
853                                         for (auto & subinfo: subinfos)
854                                                 values.insert(subinfo);
855                                         ++subpar;
856                                 }
857                         }
858                 }
859         }
860
861         return values;
862 }
863
864
865 ParagraphList::const_iterator makeAny(Text const &text,
866                                       Buffer const &buf,
867                                       XMLStream &xs,
868                                       OutputParams const &runparams,
869                                       ParagraphList::const_iterator par)
870 {
871         bool ignoreParagraph = false;
872
873         // If this kind of layout should be ignored, already leave.
874         ignoreParagraph |= par->layout().docbooktag() == "IGNORE";
875
876         // For things that should go into <info>, check the variable rp.docbook_generate_info. This does not apply to the
877         // abstract itself.
878         bool isAbstract = par->layout().docbookabstract() || par->layout().docbooktag() == "abstract";
879         ignoreParagraph |= !isAbstract && par->layout().docbookininfo() != "never" && !runparams.docbook_generate_info;
880
881         // Switch on the type of paragraph to call the right handler.
882         if (!ignoreParagraph) {
883                 switch (par->layout().latextype) {
884                 case LATEX_COMMAND:
885                         makeCommand(text, buf, xs, runparams, par);
886                         break;
887                 case LATEX_ENVIRONMENT:
888                         makeEnvironment(text, buf, xs, runparams, par);
889                         break;
890                 case LATEX_LIST_ENVIRONMENT:
891                 case LATEX_ITEM_ENVIRONMENT:
892                         // Only case when makeAny() might consume more than one paragraph.
893                         return makeListEnvironment(text, buf, xs, runparams, par);
894                 case LATEX_PARAGRAPH:
895                         makeParagraph(text, buf, xs, runparams, par);
896                         break;
897                 case LATEX_BIB_ENVIRONMENT:
898                         makeBibliography(text, buf, xs, runparams, par);
899                         break;
900                 }
901         }
902
903         // For cases that are not lists, the next paragraph to handle is the next one.
904         ++par;
905         return par;
906 }
907
908
909 xml::FontTag docbookStartFontTag(xml::FontTypes type)
910 {
911         return xml::FontTag(from_utf8(fontToDocBookTag(type)), from_utf8(fontToAttribute(type)), type);
912 }
913
914
915 xml::EndFontTag docbookEndFontTag(xml::FontTypes type)
916 {
917         return xml::EndFontTag(from_utf8(fontToDocBookTag(type)), type);
918 }
919
920
921 void outputDocBookInfo(
922                 Text const & text,
923                 Buffer const & buf,
924                 XMLStream & xs,
925                 OutputParams const & runparams,
926                 ParagraphList const & paragraphs,
927                 DocBookInfoTag const & info)
928 {
929         // Perform an additional check on the abstract. Sometimes, there are many paragraphs that should go
930         // into the abstract, but none generates actual content. Thus, first generate to a temporary stream,
931         // then only create the <abstract> tag if these paragraphs generate some content.
932         // This check must be performed *before* a decision on whether or not to output <info> is made.
933         bool hasAbstract = !info.abstract.empty();
934         docstring abstract;
935         set<const Inset *> infoInsets; // Paragraphs that should go into <info>, but are hidden in an <abstract>
936         // paragraph. (This happens for quite a few layouts, unfortunately.)
937
938         if (hasAbstract) {
939                 // Generate the abstract XML into a string before further checks.
940                 // Usually, makeAny only generates one paragraph at a time. However, for the specific case of lists, it might
941                 // generate more than one paragraph, as indicated in the return value.
942                 odocstringstream os2;
943                 XMLStream xs2(os2);
944
945                 auto rp = runparams;
946                 rp.docbook_generate_info = false;
947                 rp.docbook_ignore_wrapper = true;
948
949                 set<pit_type> doneParas; // Paragraphs that have already been converted (mostly to deal with lists).
950                 for (auto const & p : info.abstract) {
951                         if (doneParas.find(p) == doneParas.end()) {
952                                 auto oldPar = paragraphs.iterator_at(p);
953                                 auto newPar = makeAny(text, buf, xs2, rp, oldPar);
954
955                                 // Find insets that should go outside the abstract.
956                                 auto subinfos = gatherInfo(oldPar);
957                                 for (auto & subinfo: subinfos)
958                                         infoInsets.insert(subinfo);
959
960                                 // Insert the indices of all the paragraphs that were just generated (typically, one).
961                                 // **Make the hypothesis that, when an abstract has a list, all its items are consecutive.**
962                                 // Otherwise, makeAny and makeListEnvironment would have to be adapted too.
963                                 pit_type id = p;
964                                 while (oldPar != newPar) {
965                                         doneParas.emplace(id);
966                                         ++oldPar;
967                                         ++id;
968                                 }
969                         }
970                 }
971
972                 // Actually output the abstract if there is something to do. Don't count line feeds or spaces in this,
973                 // even though they must be properly output if there is some abstract.
974                 abstract = os2.str();
975                 docstring cleaned = abstract;
976                 cleaned.erase(std::remove_if(cleaned.begin(), cleaned.end(), lyx::isSpace), cleaned.end());
977
978                 // Nothing? Then there is no abstract!
979                 if (cleaned.empty())
980                         hasAbstract = false;
981         }
982
983         // The abstract must go in <info>. Otherwise, decide whether to open <info> based on the layouts.
984         bool needInfo = !info.mustBeInInfo.empty() || hasAbstract;
985
986         // Start the <info> tag if required.
987         if (needInfo) {
988                 xs.startDivision(false);
989                 xs << xml::StartTag("info");
990                 xs << xml::CR();
991         }
992
993         // Output the elements that should go in <info>.
994         // - First, the title.
995         for (auto pit : info.shouldBeInInfo) // Typically, the title: these elements are so important and ubiquitous
996                 // that mandating a wrapper like <info> would repel users. Thus, generate them first.
997                 makeAny(text, buf, xs, runparams, paragraphs.iterator_at(pit));
998         // If there is no title, generate one (required for the document to be valid).
999         // This code is called for the main document, for table cells, etc., so be precise in this condition.
1000         if (text.isMainText() && info.shouldBeInInfo.empty() && !runparams.inInclude) {
1001                 xs << xml::StartTag("title");
1002                 xs << "Untitled Document";
1003                 xs << xml::EndTag("title");
1004                 xs << xml::CR();
1005         }
1006
1007         // - Then, other metadata.
1008         for (auto pit : info.mustBeInInfo)
1009                 makeAny(text, buf, xs, runparams, paragraphs.iterator_at(pit));
1010         for (auto const * inset : infoInsets)
1011                 inset->docbook(xs, runparams);
1012
1013         // - Finally, always output the abstract as the last item of the <info>, as it requires special treatment
1014         // (especially if it contains several paragraphs that are empty).
1015         if (hasAbstract) {
1016                 string tag = paragraphs[*info.abstract.begin()].layout().docbookforceabstracttag();
1017                 if (tag == "NONE")
1018                         tag = "abstract";
1019
1020                 if (!xs.isLastTagCR())
1021                         xs << xml::CR();
1022
1023                 xs << xml::StartTag(tag);
1024                 xs << xml::CR();
1025                 xs << XMLStream::ESCAPE_NONE << abstract;
1026                 xs << xml::EndTag(tag);
1027                 xs << xml::CR();
1028         }
1029
1030         // End the <info> tag if it was started.
1031         if (needInfo) {
1032                 if (!xs.isLastTagCR())
1033                         xs << xml::CR();
1034
1035                 xs << xml::EndTag("info");
1036                 xs << xml::CR();
1037                 xs.endDivision();
1038         }
1039 }
1040
1041
1042 void docbookSimpleAllParagraphs(
1043                 Text const & text,
1044                 Buffer const & buf,
1045                 XMLStream & xs,
1046                 OutputParams const & runparams)
1047 {
1048         // Handle the given text, supposing it has no sections (i.e. a "simple" text). The input may vary in length
1049         // between a single paragraph to a whole document.
1050         pit_type const bpit = runparams.par_begin;
1051         pit_type const epit = runparams.par_end;
1052         ParagraphList const &paragraphs = text.paragraphs();
1053
1054         // First, the <info> tag.
1055         DocBookInfoTag info = getParagraphsWithInfo(paragraphs, bpit, epit, false, true);
1056         outputDocBookInfo(text, buf, xs, runparams, paragraphs, info);
1057
1058         // Then, the content. It starts where the <info> ends.
1059         auto par = paragraphs.iterator_at(info.epit);
1060         auto end = paragraphs.iterator_at(epit);
1061         while (par != end) {
1062                 if (!hasOnlyNotes(*par))
1063                         par = makeAny(text, buf, xs, runparams, par);
1064                 else
1065                         ++par;
1066         }
1067 }
1068
1069
1070 void docbookParagraphs(Text const &text,
1071                                            Buffer const &buf,
1072                                            XMLStream &xs,
1073                                            OutputParams const &runparams) {
1074         ParagraphList const &paragraphs = text.paragraphs();
1075         if (runparams.par_begin == runparams.par_end) {
1076                 runparams.par_begin = 0;
1077                 runparams.par_end = paragraphs.size();
1078         }
1079         pit_type bpit = runparams.par_begin;
1080         pit_type const epit = runparams.par_end;
1081         LASSERT(bpit < epit,
1082                         {
1083                                 xs << XMLStream::ESCAPE_NONE << "<!-- DocBook output error! -->\n";
1084                                 return;
1085                         });
1086
1087         // Detect whether the document contains sections. If there are no sections, treatment is largely simplified.
1088         // In particular, there can't be an abstract, unless it is manually marked.
1089         bool documentHasSections;
1090         pit_type eppit;
1091         tie(documentHasSections, eppit) = hasDocumentSectioning(paragraphs, bpit, epit);
1092
1093         // Deal with "simple" documents, i.e. those without sections.
1094         if (!documentHasSections) {
1095                 docbookSimpleAllParagraphs(text, buf, xs, runparams);
1096                 return;
1097         }
1098
1099         // Output the first <info> tag (or just the title).
1100         DocBookInfoTag info = getParagraphsWithInfo(paragraphs, bpit, eppit, true, true);
1101         outputDocBookInfo(text, buf, xs, runparams, paragraphs, info);
1102         bpit = info.epit;
1103
1104         std::stack<std::pair<int, string>> headerLevels; // Used to determine when to open/close sections: store the depth
1105         // of the section and the tag that was used to open it.
1106
1107         // Then, iterate through the paragraphs of this document.
1108         auto par = text.paragraphs().iterator_at(bpit);
1109         auto end = text.paragraphs().iterator_at(epit);
1110         while (par != end) {
1111                 // Skip paragraphs not producing any output.
1112                 if (hasOnlyNotes(*par)) {
1113                         ++par;
1114                         continue;
1115                 }
1116
1117                 OutputParams ourparams = runparams;
1118                 Layout const &style = par->layout();
1119
1120                 // Think about adding <section> and/or </section>s.
1121                 if (isLayoutSectioning(style) || par->params().startOfAppendix()) {
1122                         int level = style.toclevel;
1123
1124                         // Need to close a previous section if it has the same level or a higher one (close <section> if opening a
1125                         // <h2> after a <h2>, <h3>, <h4>, <h5> or <h6>). More examples:
1126                         //   - current: h2; back: h1; do not close any <section>
1127                         //   - current: h1; back: h2; close two <section> (first the <h2>, then the <h1>, so a new <h1> can come)
1128                         // Some layouts require that Layout::NOT_IN_TOC sections still cause closing of previous sections. This is
1129                         // mostly to ensure that the section is positioned at a DocBook-compatible level (acknowledgements: cannot
1130                         // be under a section!).
1131                         while (!headerLevels.empty() && level <= headerLevels.top().first) {
1132                                 // Output the tag only if it corresponds to a legit section.
1133                                 int stackLevel = headerLevels.top().first;
1134                                 if (stackLevel != Layout::NOT_IN_TOC) {
1135                                         xs << xml::EndTag(headerLevels.top().second);
1136                                         xs << xml::CR();
1137                                 }
1138                                 headerLevels.pop();
1139                         }
1140
1141                         // Open the new section: first push it onto the stack, then output it in DocBook.
1142                         string sectionTag = (par->params().startOfAppendix()) ? "appendix" : style.docbooksectiontag();
1143                         headerLevels.push(std::make_pair(level, sectionTag));
1144
1145                         // Some sectioning-like elements should not be output (such as FrontMatter).
1146                         if (level != Layout::NOT_IN_TOC) {
1147                                 // Look for a label in the title, i.e. a InsetLabel as a child.
1148                                 docstring id = docstring();
1149                                 for (pos_type i = 0; i < par->size(); ++i) {
1150                                         Inset const *inset = par->getInset(i);
1151                                         if (inset) {
1152                                                 if (auto label = dynamic_cast<InsetLabel const *>(inset)) {
1153                                                         // Generate the attributes for the section if need be.
1154                                                         id += "xml:id=\"" + xml::cleanID(label->screenLabel()) + "\"";
1155
1156                                                         // Don't output the ID as a DocBook <anchor>.
1157                                                         ourparams.docbook_anchors_to_ignore.emplace(label->screenLabel());
1158
1159                                                         // Cannot have multiple IDs per tag. If there is another ID inset in the document, it will
1160                                                         // be output as a DocBook anchor.
1161                                                         break;
1162                                                 }
1163                                         }
1164                                 }
1165
1166                                 // Write the open tag for this section.
1167                                 docstring attrs;
1168                                 if (!id.empty())
1169                                         attrs = id;
1170                                 xs << xml::StartTag(sectionTag, attrs);
1171                                 xs << xml::CR();
1172                         }
1173                 }
1174
1175                 // Close all sections before the bibliography.
1176                 // 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)?
1177                 if (!par->insetList().empty()) {
1178                         Inset const *firstInset = par->getInset(0);
1179                         if (firstInset && (firstInset->lyxCode() == BIBITEM_CODE || firstInset->lyxCode() == BIBTEX_CODE)) {
1180                                 while (!headerLevels.empty()) {
1181                                         // Don't close appendices before bibliographies.
1182                                         if (headerLevels.top().second == "appendix")
1183                                                 break;
1184
1185                                         // Pop the section from the stack.
1186                                         int level = headerLevels.top().first;
1187                                         docstring tag = from_utf8("</" + headerLevels.top().second + ">");
1188                                         headerLevels.pop();
1189
1190                                         // Output the tag only if it corresponds to a legit section, as the rest of the code.
1191                                         if (level != Layout::NOT_IN_TOC) {
1192                                                 xs << XMLStream::ESCAPE_NONE << tag;
1193                                                 xs << xml::CR();
1194                                         }
1195                                 }
1196                         }
1197                 }
1198
1199                 // Generate the <info> tag if a section was just opened.
1200                 // Some sections may require abstracts (mostly parts, in books: DocBookForceAbstractTag will not be NONE),
1201                 // others can still have an abstract (it must be detected so that it can be output at the right place).
1202                 // TODO: docbookforceabstracttag is a bit contrived here, but it does the job. Having another field just for this would be cleaner, but that's just for <part> and <partintro>, so it's probably not worth the effort.
1203                 if (isLayoutSectioning(style)) {
1204                         // This abstract may be found between the next paragraph and the next title.
1205                         pit_type cpit = std::distance(text.paragraphs().begin(), par);
1206                         pit_type ppit = std::get<1>(hasDocumentSectioning(paragraphs, cpit + 1L, epit));
1207
1208                         // Generate this abstract (this code corresponds to parts of outputDocBookInfo).
1209                         DocBookInfoTag secInfo = getParagraphsWithInfo(paragraphs, cpit, ppit, true,
1210                                                                                                   style.docbookforceabstracttag() != "NONE");
1211
1212                         if (!secInfo.mustBeInInfo.empty() || !secInfo.shouldBeInInfo.empty() || !secInfo.abstract.empty()) {
1213                                 // Generate the <info>, if required. If DocBookForceAbstractTag != NONE, this abstract will not be in
1214                                 // <info>, unlike other ("standard") abstracts.
1215                                 bool hasStandardAbstract = !secInfo.abstract.empty() && style.docbookforceabstracttag() == "NONE";
1216                                 bool needInfo = !secInfo.mustBeInInfo.empty() || hasStandardAbstract;
1217
1218                                 if (needInfo) {
1219                                         xs.startDivision(false);
1220                                         xs << xml::StartTag("info");
1221                                         xs << xml::CR();
1222                                 }
1223
1224                                 // Output the elements that should go in <info>, before and after the abstract.
1225                                 for (auto pit : secInfo.shouldBeInInfo) // Typically, the title: these elements are so important and ubiquitous
1226                                         // that mandating a wrapper like <info> would repel users. Thus, generate them first.
1227                                         makeAny(text, buf, xs, ourparams, paragraphs.iterator_at(pit));
1228                                 for (auto pit : secInfo.mustBeInInfo)
1229                                         makeAny(text, buf, xs, ourparams, paragraphs.iterator_at(pit));
1230
1231                                 // Deal with the abstract in <info> if it is standard (i.e. its tag is <abstract>).
1232                                 if (!secInfo.abstract.empty() && hasStandardAbstract) {
1233                                         if (!secInfo.abstractLayout) {
1234                                                 xs << xml::StartTag("abstract");
1235                                                 xs << xml::CR();
1236                                         }
1237
1238                                         for (auto const &p : secInfo.abstract)
1239                                                 makeAny(text, buf, xs, ourparams, paragraphs.iterator_at(p));
1240
1241                                         if (!secInfo.abstractLayout) {
1242                                                 xs << xml::EndTag("abstract");
1243                                                 xs << xml::CR();
1244                                         }
1245                                 }
1246
1247                                 // End the <info> tag if it was started.
1248                                 if (needInfo) {
1249                                         if (!xs.isLastTagCR())
1250                                                 xs << xml::CR();
1251
1252                                         xs << xml::EndTag("info");
1253                                         xs << xml::CR();
1254                                         xs.endDivision();
1255                                 }
1256
1257                                 // Deal with the abstract outside <info> if it is not standard (i.e. its tag is layout-defined).
1258                                 if (!secInfo.abstract.empty() && !hasStandardAbstract) {
1259                                         // Assert: style.docbookforceabstracttag() != NONE.
1260                                         xs << xml::StartTag(style.docbookforceabstracttag());
1261                                         xs << xml::CR();
1262                                         for (auto const &p : secInfo.abstract)
1263                                                 makeAny(text, buf, xs, ourparams, paragraphs.iterator_at(p));
1264                                         xs << xml::EndTag(style.docbookforceabstracttag());
1265                                         xs << xml::CR();
1266                                 }
1267
1268                                 // Skip all the text that has just been generated.
1269                                 par = paragraphs.iterator_at(secInfo.epit);
1270                         } else {
1271                                 // No <info> tag to generate, proceed as for normal paragraphs.
1272                                 par = makeAny(text, buf, xs, ourparams, par);
1273                         }
1274                 } else {
1275                         // Generate this paragraph, as it has nothing special.
1276                         par = makeAny(text, buf, xs, ourparams, par);
1277                 }
1278         }
1279
1280         // If need be, close <section>s, but only at the end of the document (otherwise, dealt with at the beginning
1281         // of the loop).
1282         while (!headerLevels.empty() && headerLevels.top().first > Layout::NOT_IN_TOC) {
1283                 docstring tag = from_utf8("</" + headerLevels.top().second + ">");
1284                 headerLevels.pop();
1285                 xs << XMLStream::ESCAPE_NONE << tag;
1286                 xs << xml::CR();
1287         }
1288 }
1289
1290 } // namespace lyx