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