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