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