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