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