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