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