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