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