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