]> git.lyx.org Git - lyx.git/blob - src/output_docbook.cpp
95d6aaa185ec25017957bf74586b21dd4becf215
[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                                         auto subinfos = gatherInfo(subpar);
809                                         for (auto & subinfo: subinfos)
810                                                 values.insert(subinfo);
811                                         ++subpar;
812                                 }
813                         }
814                 }
815         }
816
817         return values;
818 }
819
820
821 ParagraphList::const_iterator makeAny(Text const &text,
822                                       Buffer const &buf,
823                                       XMLStream &xs,
824                                       OutputParams const &runparams,
825                                       ParagraphList::const_iterator par)
826 {
827         bool ignoreParagraph = false;
828
829         // If this kind of layout should be ignored, already leave.
830         ignoreParagraph |= par->layout().docbooktag() == "IGNORE";
831
832         // For things that should go into <info>, check the variable rp.docbook_generate_info. This does not apply to the
833         // abstract itself.
834         bool isAbstract = par->layout().docbookabstract() || par->layout().docbooktag() == "abstract";
835         ignoreParagraph |= !isAbstract && par->layout().docbookininfo() != "never" && !runparams.docbook_generate_info;
836
837         // Switch on the type of paragraph to call the right handler.
838         if (!ignoreParagraph) {
839                 switch (par->layout().latextype) {
840                 case LATEX_COMMAND:
841                         makeCommand(text, buf, xs, runparams, par);
842                         break;
843                 case LATEX_ENVIRONMENT:
844                         makeEnvironment(text, buf, xs, runparams, par);
845                         break;
846                 case LATEX_LIST_ENVIRONMENT:
847                 case LATEX_ITEM_ENVIRONMENT:
848                         // Only case when makeAny() might consume more than one paragraph.
849                         return makeListEnvironment(text, buf, xs, runparams, par);
850                 case LATEX_PARAGRAPH:
851                         makeParagraph(text, buf, xs, runparams, par);
852                         break;
853                 case LATEX_BIB_ENVIRONMENT:
854                         makeBibliography(text, buf, xs, runparams, par);
855                         break;
856                 }
857         }
858
859         // For cases that are not lists, the next paragraph to handle is the next one.
860         ++par;
861         return par;
862 }
863
864
865 xml::FontTag docbookStartFontTag(xml::FontTypes type)
866 {
867         return xml::FontTag(from_utf8(fontToDocBookTag(type)), from_utf8(fontToAttribute(type)), type);
868 }
869
870
871 xml::EndFontTag docbookEndFontTag(xml::FontTypes type)
872 {
873         return xml::EndFontTag(from_utf8(fontToDocBookTag(type)), type);
874 }
875
876
877 void outputDocBookInfo(
878                 Text const & text,
879                 Buffer const & buf,
880                 XMLStream & xs,
881                 OutputParams const & runparams,
882                 ParagraphList const & paragraphs,
883                 DocBookInfoTag const & info)
884 {
885         // Perform an additional check on the abstract. Sometimes, there are many paragraphs that should go
886         // into the abstract, but none generates actual content. Thus, first generate to a temporary stream,
887         // then only create the <abstract> tag if these paragraphs generate some content.
888         // This check must be performed *before* a decision on whether or not to output <info> is made.
889         bool hasAbstract = !info.abstract.empty();
890         docstring abstract;
891         set<const Inset *> infoInsets; // Paragraphs that should go into <info>, but are hidden in an <abstract>
892         // paragraph. (This happens for quite a few layouts, unfortunately.)
893
894         if (hasAbstract) {
895                 // Generate the abstract XML into a string before further checks.
896                 // Usually, makeAny only generates one paragraph at a time. However, for the specific case of lists, it might
897                 // generate more than one paragraph, as indicated in the return value.
898                 odocstringstream os2;
899                 XMLStream xs2(os2);
900
901                 auto rp = runparams;
902                 rp.docbook_generate_info = false;
903
904                 set<pit_type> doneParas; // Paragraphs that have already been converted (mostly to deal with lists).
905                 for (auto const & p : info.abstract) {
906                         if (doneParas.find(p) == doneParas.end()) {
907                                 auto oldPar = paragraphs.iterator_at(p);
908                                 auto newPar = makeAny(text, buf, xs2, rp, oldPar);
909
910                                 auto subinfos = gatherInfo(oldPar);
911                                 for (auto & subinfo: subinfos)
912                                         infoInsets.insert(subinfo);
913
914                                 // Insert the indices of all the paragraphs that were just generated (typically, one).
915                                 // **Make the hypothesis that, when an abstract has a list, all its items are consecutive.**
916                                 // Otherwise, makeAny and makeListEnvironment would have to be adapted too.
917                                 pit_type id = p;
918                                 while (oldPar != newPar) {
919                                         doneParas.emplace(id);
920                                         ++oldPar;
921                                         ++id;
922                                 }
923                         }
924                 }
925
926                 // Actually output the abstract if there is something to do. Don't count line feeds or spaces in this,
927                 // even though they must be properly output if there is some abstract.
928                 abstract = os2.str();
929                 docstring cleaned = abstract;
930                 cleaned.erase(std::remove_if(cleaned.begin(), cleaned.end(), lyx::isSpace), cleaned.end());
931
932                 // Nothing? Then there is no abstract!
933                 if (cleaned.empty())
934                         hasAbstract = false;
935         }
936
937         // The abstract must go in <info>. Otherwise, decide whether to open <info> based on the layouts.
938         bool needInfo = !info.mustBeInInfo.empty() || hasAbstract;
939
940         // Start the <info> tag if required.
941         if (needInfo) {
942                 xs.startDivision(false);
943                 xs << xml::StartTag("info");
944                 xs << xml::CR();
945         }
946
947         // Output the elements that should go in <info>.
948         // - First, the title.
949         for (auto pit : info.shouldBeInInfo) // Typically, the title: these elements are so important and ubiquitous
950                 // that mandating a wrapper like <info> would repel users. Thus, generate them first.
951                 makeAny(text, buf, xs, runparams, paragraphs.iterator_at(pit));
952         // If there is no title, generate one (required for the document to be valid).
953         // This code is called for the main document, for table cells, etc., so be precise in this condition.
954         if (text.isMainText() && info.shouldBeInInfo.empty() && !runparams.inInclude) {
955                 xs << xml::StartTag("title");
956                 xs << "Untitled Document";
957                 xs << xml::EndTag("title");
958                 xs << xml::CR();
959         }
960
961         // - Then, other metadata.
962         for (auto pit : info.mustBeInInfo)
963                 makeAny(text, buf, xs, runparams, paragraphs.iterator_at(pit));
964         for (auto const * inset : infoInsets)
965                 inset->docbook(xs, runparams);
966
967         // - Finally, always output the abstract as the last item of the <info>, as it requires special treatment
968         // (especially if it contains several paragraphs that are empty).
969         if (hasAbstract) {
970                 if (info.abstractLayout) {
971                         xs << XMLStream::ESCAPE_NONE << abstract;
972                         xs << xml::CR();
973                 } else {
974                         string tag = paragraphs[*info.abstract.begin()].layout().docbookforceabstracttag();
975                         if (tag == "NONE")
976                                 tag = "abstract";
977
978                         if (!xs.isLastTagCR())
979                                 xs << xml::CR();
980
981                         xs << xml::StartTag(tag);
982                         xs << xml::CR();
983                         xs << XMLStream::ESCAPE_NONE << abstract;
984                         xs << xml::EndTag(tag);
985                         xs << xml::CR();
986                 }
987         }
988
989         // End the <info> tag if it was started.
990         if (needInfo) {
991                 if (!xs.isLastTagCR())
992                         xs << xml::CR();
993
994                 xs << xml::EndTag("info");
995                 xs << xml::CR();
996                 xs.endDivision();
997         }
998 }
999
1000
1001 void docbookSimpleAllParagraphs(
1002                 Text const & text,
1003                 Buffer const & buf,
1004                 XMLStream & xs,
1005                 OutputParams const & runparams)
1006 {
1007         // Handle the given text, supposing it has no sections (i.e. a "simple" text). The input may vary in length
1008         // between a single paragraph to a whole document.
1009         pit_type const bpit = runparams.par_begin;
1010         pit_type const epit = runparams.par_end;
1011         ParagraphList const &paragraphs = text.paragraphs();
1012
1013         // First, the <info> tag.
1014         DocBookInfoTag info = getParagraphsWithInfo(paragraphs, bpit, epit, false, true);
1015         outputDocBookInfo(text, buf, xs, runparams, paragraphs, info);
1016
1017         // Then, the content. It starts where the <info> ends.
1018         auto par = paragraphs.iterator_at(info.epit);
1019         auto end = paragraphs.iterator_at(epit);
1020         while (par != end) {
1021                 if (!hasOnlyNotes(*par))
1022                         par = makeAny(text, buf, xs, runparams, par);
1023                 else
1024                         ++par;
1025         }
1026 }
1027
1028
1029 void docbookParagraphs(Text const &text,
1030                                            Buffer const &buf,
1031                                            XMLStream &xs,
1032                                            OutputParams const &runparams) {
1033         ParagraphList const &paragraphs = text.paragraphs();
1034         if (runparams.par_begin == runparams.par_end) {
1035                 runparams.par_begin = 0;
1036                 runparams.par_end = paragraphs.size();
1037         }
1038         pit_type bpit = runparams.par_begin;
1039         pit_type const epit = runparams.par_end;
1040         LASSERT(bpit < epit,
1041                         {
1042                                 xs << XMLStream::ESCAPE_NONE << "<!-- DocBook output error! -->\n";
1043                                 return;
1044                         });
1045
1046         std::stack<std::pair<int, string>> headerLevels; // Used to determine when to open/close sections: store the depth
1047         // of the section and the tag that was used to open it.
1048
1049         // Detect whether the document contains sections. If there are no sections, treatment is largely simplified.
1050         // In particular, there can't be an abstract, unless it is manually marked.
1051         bool documentHasSections;
1052         pit_type eppit;
1053         tie(documentHasSections, eppit) = hasDocumentSectioning(paragraphs, bpit, epit);
1054
1055         // Deal with "simple" documents, i.e. those without sections.
1056         if (!documentHasSections) {
1057                 docbookSimpleAllParagraphs(text, buf, xs, runparams);
1058                 return;
1059         }
1060
1061         // Output the first <info> tag (or just the title).
1062         DocBookInfoTag info = getParagraphsWithInfo(paragraphs, bpit, eppit, true, true);
1063         outputDocBookInfo(text, buf, xs, runparams, paragraphs, info);
1064         bpit = info.epit;
1065
1066         // Then, iterate through the paragraphs of this document.
1067         bool currentlyInAppendix = false;
1068
1069         auto par = text.paragraphs().iterator_at(bpit);
1070         auto end = text.paragraphs().iterator_at(epit);
1071         while (par != end) {
1072                 OutputParams ourparams = runparams;
1073
1074                 if (par->params().startOfAppendix())
1075                         currentlyInAppendix = true;
1076                 if (hasOnlyNotes(*par)) {
1077                         ++par;
1078                         continue;
1079                 }
1080
1081                 Layout const &style = par->layout();
1082
1083                 // Think about adding <section> and/or </section>s.
1084                 if (isLayoutSectioning(style)) {
1085                         int level = style.toclevel;
1086
1087                         // Need to close a previous section if it has the same level or a higher one (close <section> if opening a
1088                         // <h2> after a <h2>, <h3>, <h4>, <h5> or <h6>). More examples:
1089                         //   - current: h2; back: h1; do not close any <section>
1090                         //   - current: h1; back: h2; close two <section> (first the <h2>, then the <h1>, so a new <h1> can come)
1091                         while (!headerLevels.empty() && level <= headerLevels.top().first) {
1092                                 // Output the tag only if it corresponds to a legit section.
1093                                 int stackLevel = headerLevels.top().first;
1094                                 if (stackLevel != Layout::NOT_IN_TOC) {
1095                                         xs << xml::EndTag(headerLevels.top().second);
1096                                         xs << xml::CR();
1097                                 }
1098                                 headerLevels.pop();
1099                         }
1100
1101                         // Open the new section: first push it onto the stack, then output it in DocBook.
1102                         string sectionTag = (currentlyInAppendix && style.docbooksectiontag() == "chapter") ?
1103                                                                 "appendix" : style.docbooksectiontag();
1104                         headerLevels.push(std::make_pair(level, sectionTag));
1105
1106                         // Some sectioning-like elements should not be output (such as FrontMatter).
1107                         if (level != Layout::NOT_IN_TOC) {
1108                                 // Look for a label in the title, i.e. a InsetLabel as a child.
1109                                 docstring id = docstring();
1110                                 for (pos_type i = 0; i < par->size(); ++i) {
1111                                         Inset const *inset = par->getInset(i);
1112                                         if (inset) {
1113                                                 if (auto label = dynamic_cast<InsetLabel const *>(inset)) {
1114                                                         // Generate the attributes for the section if need be.
1115                                                         id += "xml:id=\"" + xml::cleanID(label->screenLabel()) + "\"";
1116
1117                                                         // Don't output the ID as a DocBook <anchor>.
1118                                                         ourparams.docbook_anchors_to_ignore.emplace(label->screenLabel());
1119
1120                                                         // Cannot have multiple IDs per tag. If there is another ID inset in the document, it will
1121                                                         // be output as a DocBook anchor.
1122                                                         break;
1123                                                 }
1124                                         }
1125                                 }
1126
1127                                 // Write the open tag for this section.
1128                                 docstring attrs;
1129                                 if (!id.empty())
1130                                         attrs = id;
1131                                 xs << xml::StartTag(sectionTag, attrs);
1132                                 xs << xml::CR();
1133                         }
1134                 }
1135
1136                 // Close all sections before the bibliography.
1137                 // 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)?
1138                 if (!par->insetList().empty()) {
1139                         Inset const *firstInset = par->getInset(0);
1140                         if (firstInset && (firstInset->lyxCode() == BIBITEM_CODE || firstInset->lyxCode() == BIBTEX_CODE)) {
1141                                 while (!headerLevels.empty()) {
1142                                         int level = headerLevels.top().first;
1143                                         docstring tag = from_utf8("</" + headerLevels.top().second + ">");
1144                                         headerLevels.pop();
1145
1146                                         // Output the tag only if it corresponds to a legit section.
1147                                         if (level != Layout::NOT_IN_TOC) {
1148                                                 xs << XMLStream::ESCAPE_NONE << tag;
1149                                                 xs << xml::CR();
1150                                         }
1151                                 }
1152                         }
1153                 }
1154
1155                 // Generate the <info> tag if a section was just opened.
1156                 // Some sections may require abstracts (mostly parts, in books: DocBookForceAbstractTag will not be NONE),
1157                 // others can still have an abstract (it must be detected so that it can be output at the right place).
1158                 // 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.
1159                 if (isLayoutSectioning(style)) {
1160                         // This abstract may be found between the next paragraph and the next title.
1161                         pit_type cpit = std::distance(text.paragraphs().begin(), par);
1162                         pit_type ppit = std::get<1>(hasDocumentSectioning(paragraphs, cpit + 1L, epit));
1163
1164                         // Generate this abstract (this code corresponds to parts of outputDocBookInfo).
1165                         DocBookInfoTag secInfo = getParagraphsWithInfo(paragraphs, cpit, ppit, true,
1166                                                                                                   style.docbookforceabstracttag() != "NONE");
1167
1168                         if (!secInfo.mustBeInInfo.empty() || !secInfo.shouldBeInInfo.empty() || !secInfo.abstract.empty()) {
1169                                 // Generate the <info>, if required. If DocBookForceAbstractTag != NONE, this abstract will not be in
1170                                 // <info>, unlike other ("standard") abstracts.
1171                                 bool hasStandardAbstract = !secInfo.abstract.empty() && style.docbookforceabstracttag() == "NONE";
1172                                 bool needInfo = !secInfo.mustBeInInfo.empty() || hasStandardAbstract;
1173
1174                                 if (needInfo) {
1175                                         xs.startDivision(false);
1176                                         xs << xml::StartTag("info");
1177                                         xs << xml::CR();
1178                                 }
1179
1180                                 // Output the elements that should go in <info>, before and after the abstract.
1181                                 for (auto pit : secInfo.shouldBeInInfo) // Typically, the title: these elements are so important and ubiquitous
1182                                         // that mandating a wrapper like <info> would repel users. Thus, generate them first.
1183                                         makeAny(text, buf, xs, ourparams, paragraphs.iterator_at(pit));
1184                                 for (auto pit : secInfo.mustBeInInfo)
1185                                         makeAny(text, buf, xs, ourparams, paragraphs.iterator_at(pit));
1186
1187                                 // Deal with the abstract in <info> if it is standard (i.e. its tag is <abstract>).
1188                                 if (!secInfo.abstract.empty() && hasStandardAbstract) {
1189                                         if (!secInfo.abstractLayout) {
1190                                                 xs << xml::StartTag("abstract");
1191                                                 xs << xml::CR();
1192                                         }
1193
1194                                         for (auto const &p : secInfo.abstract)
1195                                                 makeAny(text, buf, xs, ourparams, paragraphs.iterator_at(p));
1196
1197                                         if (!secInfo.abstractLayout) {
1198                                                 xs << xml::EndTag("abstract");
1199                                                 xs << xml::CR();
1200                                         }
1201                                 }
1202
1203                                 // End the <info> tag if it was started.
1204                                 if (needInfo) {
1205                                         if (!xs.isLastTagCR())
1206                                                 xs << xml::CR();
1207
1208                                         xs << xml::EndTag("info");
1209                                         xs << xml::CR();
1210                                         xs.endDivision();
1211                                 }
1212
1213                                 // Deal with the abstract outside <info> if it is not standard (i.e. its tag is layout-defined).
1214                                 if (!secInfo.abstract.empty() && !hasStandardAbstract) {
1215                                         // Assert: style.docbookforceabstracttag() != NONE.
1216                                         xs << xml::StartTag(style.docbookforceabstracttag());
1217                                         xs << xml::CR();
1218                                         for (auto const &p : secInfo.abstract)
1219                                                 makeAny(text, buf, xs, ourparams, paragraphs.iterator_at(p));
1220                                         xs << xml::EndTag(style.docbookforceabstracttag());
1221                                         xs << xml::CR();
1222                                 }
1223
1224                                 // Skip all the text that has just been generated.
1225                                 par = paragraphs.iterator_at(secInfo.epit);
1226                         } else {
1227                                 // No <info> tag to generate, proceed as for normal paragraphs.
1228                                 par = makeAny(text, buf, xs, ourparams, par);
1229                         }
1230                 } else {
1231                         // Generate this paragraph, as it has nothing special.
1232                         par = makeAny(text, buf, xs, ourparams, par);
1233                 }
1234         }
1235
1236         // If need be, close <section>s, but only at the end of the document (otherwise, dealt with at the beginning
1237         // of the loop).
1238         while (!headerLevels.empty() && headerLevels.top().first > Layout::NOT_IN_TOC) {
1239                 docstring tag = from_utf8("</" + headerLevels.top().second + ">");
1240                 headerLevels.pop();
1241                 xs << XMLStream::ESCAPE_NONE << tag;
1242                 xs << xml::CR();
1243         }
1244 }
1245
1246 } // namespace lyx