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