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