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