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