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