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