]> git.lyx.org Git - features.git/blob - src/output_docbook.cpp
DocBook: fix float tags (was unduly overridden).
[features.git] / src / output_docbook.cpp
1 /**
2  * \file output_docbook.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Lars Gullik Bjønnes
7  * \author José Matos
8  *
9  * Full author contact details are available in file CREDITS.
10  */
11
12 #include <config.h>
13
14 #include "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 using DocBookDocumentSectioning = tuple<bool, pit_type>;
777
778
779 struct DocBookInfoTag
780 {
781         const set<pit_type> shouldBeInInfo;
782         const set<pit_type> mustBeInInfo;
783         const set<pit_type> abstract;
784         pit_type bpit;
785         pit_type epit;
786
787         DocBookInfoTag(const set<pit_type> & shouldBeInInfo, const set<pit_type> & mustBeInInfo,
788                                    const set<pit_type> & abstract, pit_type bpit, pit_type epit) :
789                                    shouldBeInInfo(shouldBeInInfo), mustBeInInfo(mustBeInInfo), abstract(abstract),
790                                    bpit(bpit), epit(epit) {}
791 };
792
793
794 DocBookDocumentSectioning hasDocumentSectioning(ParagraphList const &paragraphs, pit_type bpit, pit_type const epit) {
795         bool documentHasSections = false;
796
797         while (bpit < epit) {
798                 Layout const &style = paragraphs[bpit].layout();
799                 documentHasSections |= style.category() == from_utf8("Sectioning");
800
801                 if (documentHasSections)
802                         break;
803                 bpit += 1;
804         }
805         // Paragraphs before the first section: [ runparams.par_begin ; eppit )
806
807         return make_tuple(documentHasSections, bpit);
808 }
809
810
811 bool hasOnlyNotes(Paragraph const & par)
812 {
813         // Precondition: the paragraph is not empty. Otherwise, the function will always return true...
814         for (int i = 0; i < par.size(); ++i)
815                 // If you find something that is not an inset (like actual text) or an inset that is not a note,
816                 // return false.
817                 if (!par.isInset(i) || !dynamic_cast<InsetNote *>(par.insetList().get(i)))
818                         return false;
819         return true;
820 }
821
822
823 DocBookInfoTag getParagraphsWithInfo(ParagraphList const &paragraphs, pit_type bpit, pit_type const epit) {
824         set<pit_type> shouldBeInInfo;
825         set<pit_type> mustBeInInfo;
826         set<pit_type> abstract;
827
828         // Find the first non empty paragraph by mutating bpit.
829         while (bpit < epit) {
830                 Paragraph const &par = paragraphs[bpit];
831                 if (par.empty() || hasOnlyNotes(par))
832                         bpit += 1;
833                 else
834                         break;
835         }
836
837         // Find the last info-like paragraph.
838         pit_type cpit = bpit;
839         bool hasAbstractLayout = false;
840         while (cpit < epit) {
841                 // Skip paragraphs only containing one note.
842                 Paragraph const & par = paragraphs[cpit];
843                 if (hasOnlyNotes(par)) {
844                         cpit += 1;
845                         continue;
846                 }
847
848                 if (par.layout().docbookabstract())
849                         hasAbstractLayout = true;
850
851                 // Based on layout information, store this paragraph in one set: should be in <info>, must be.
852                 Layout const &style = par.layout();
853
854                 if (style.docbookininfo() == "always") {
855                         mustBeInInfo.emplace(cpit);
856                 } else if (style.docbookininfo() == "maybe") {
857                         shouldBeInInfo.emplace(cpit);
858                 } else {
859                         // Hypothesis: the <info> parts should be grouped together near the beginning bpit.
860                         // There may be notes in between, but nothing else.
861                         break;
862                 }
863                 cpit += 1;
864         }
865         // Now, cpit points to the last paragraph that has things that could go in <info>.
866         // bpit is the beginning of the <info> part.
867
868         // Go once again through the list of paragraphs to find the abstract. If there is an abstract
869         // layout, only consider it. Otherwise, an abstract is just a sequence of paragraphs with text.
870         if (hasAbstractLayout) {
871                 pit_type pit = bpit;
872                 while (pit < cpit) { // Don't overshoot the <info> part.
873                         if (paragraphs[pit].layout().docbookabstract())
874                                 abstract.emplace(pit);
875                         pit++;
876                 }
877         } else {
878                 pit_type lastAbstract = epit + 1; // A nonsensical value.
879                 docstring lastAbstractLayout;
880
881                 pit_type pit = bpit;
882                 while (pit < cpit) { // Don't overshoot the <info> part.
883                         const Paragraph & par = paragraphs.at(pit);
884                         if (!par.insetList().empty()) {
885                                 for (const auto &i : par.insetList()) {
886                                         if (i.inset->getText(0) != nullptr) {
887                                                 if (lastAbstract == epit + 1) {
888                                                         // First paragraph that matches the heuristic definition of abstract.
889                                                         lastAbstract = pit;
890                                                         lastAbstractLayout = par.layout().name();
891                                                 } else if (pit > lastAbstract + 1 || par.layout().name() != lastAbstractLayout) {
892                                                         // This is either too far from the last abstract paragraph or doesn't
893                                                         // have the right layout name, BUT there has already been an abstract
894                                                         // in this document: done with detecting the abstract.
895                                                         goto done; // Easier to get out of two nested loops.
896                                                 }
897
898                                                 abstract.emplace(pit);
899                                                 break;
900                                         }
901                                 }
902                         }
903                         pit++;
904                 }
905         }
906
907         done:
908         return DocBookInfoTag(shouldBeInInfo, mustBeInInfo, abstract, bpit, cpit);
909 }
910
911 } // end anonymous namespace
912
913
914 xml::FontTag docbookStartFontTag(xml::FontTypes type)
915 {
916         return xml::FontTag(from_utf8(fontToDocBookTag(type)), from_utf8(fontToAttribute(type)), type);
917 }
918
919
920 xml::EndFontTag docbookEndFontTag(xml::FontTypes type)
921 {
922         return xml::EndFontTag(from_utf8(fontToDocBookTag(type)), type);
923 }
924
925
926 void outputDocBookInfo(
927                 Text const & text,
928                 Buffer const & buf,
929                 XMLStream & xs,
930                 OutputParams const & runparams,
931                 ParagraphList const & paragraphs,
932                 DocBookInfoTag const & info)
933 {
934         // Perform an additional check on the abstract. Sometimes, there are many paragraphs that should go
935         // into the abstract, but none generates actual content. Thus, first generate to a temporary stream,
936         // then only create the <abstract> tag if these paragraphs generate some content.
937         // This check must be performed *before* a decision on whether or not to output <info> is made.
938         bool hasAbstract = !info.abstract.empty();
939         docstring abstract;
940         if (hasAbstract) {
941                 // Generate the abstract XML into a string before further checks.
942                 odocstringstream os2;
943                 {
944                         XMLStream xs2(os2);
945                         auto bpit = *std::min_element(info.abstract.begin(), info.abstract.end());
946                         auto epit = 1 + *std::max_element(info.abstract.begin(), info.abstract.end());
947                         // info.abstract is inclusive, epit is exclusive, hence +1 for looping.
948
949                         while (bpit < epit) {
950                                 makeAny(text, buf, xs2, runparams, paragraphs.iterator_at(bpit));
951                                 bpit += 1;
952                         }
953                 }
954
955                 // Actually output the abstract if there is something to do. Don't count line feeds or spaces in this,
956                 // even though they must be properly output if there is some abstract.
957                 abstract = os2.str();
958                 docstring cleaned = abstract;
959                 cleaned.erase(std::remove_if(cleaned.begin(), cleaned.end(), ::isspace), cleaned.end());
960
961                 // Nothing? Then there is no abstract!
962                 if (cleaned.empty())
963                         hasAbstract = false;
964         }
965
966         // The abstract must go in <info>. Otherwise, decide whether to open <info> based on the layouts.
967         bool needInfo = !info.mustBeInInfo.empty() || hasAbstract;
968
969         // Start the <info> tag if required.
970         if (needInfo) {
971                 xs.startDivision(false);
972                 xs << xml::StartTag("info");
973                 xs << xml::CR();
974         }
975
976         // Output the elements that should go in <info>, before and after the abstract.
977         for (auto pit : info.shouldBeInInfo) { // Typically, the title: these elements are so important and ubiquitous
978                 // that mandating a wrapper like <info> would repel users. Thus, generate them first.
979                 makeAny(text, buf, xs, runparams, paragraphs.iterator_at(pit));
980         }
981         for (auto pit : info.mustBeInInfo) {
982                 if (info.abstract.find(pit) == info.abstract.end()) // The abstract must be in info, but is dealt with after.
983                         makeAny(text, buf, xs, runparams, paragraphs.iterator_at(pit));
984         }
985
986         // Always output the abstract as the last item of the <info>, as it requires special treatment (especially if
987         // it contains several paragraphs that are empty).
988         if (hasAbstract) {
989 //              string tag = paragraphs[*info.abstract.begin()].layout().docbookforceabstracttag();
990 //              if (tag == "NONE")
991 //                      tag = "abstract";
992 //
993 //              xs << xml::StartTag(tag);
994 //              xs << xml::CR();
995                 xs << XMLStream::ESCAPE_NONE << abstract;
996 //              xs << xml::EndTag(tag);
997 //              xs << xml::CR();
998         }
999
1000         // End the <info> tag if it was started.
1001         if (needInfo) {
1002                 xs << xml::EndTag("info");
1003                 xs << xml::CR();
1004                 xs.endDivision();
1005         }
1006 }
1007
1008
1009 void docbookFirstParagraphs(
1010                 Text const &text,
1011                 Buffer const &buf,
1012                 XMLStream &xs,
1013                 OutputParams const &runparams,
1014                 pit_type epit)
1015 {
1016         // Handle the beginning of the document, supposing it has sections.
1017         // Major role: output the first <info> tag.
1018
1019         ParagraphList const &paragraphs = text.paragraphs();
1020         pit_type bpit = runparams.par_begin;
1021         DocBookInfoTag info = getParagraphsWithInfo(paragraphs, bpit, epit);
1022         outputDocBookInfo(text, buf, xs, runparams, paragraphs, info);
1023 }
1024
1025
1026 void docbookSimpleAllParagraphs(
1027                 Text const & text,
1028                 Buffer const & buf,
1029                 XMLStream & xs,
1030                 OutputParams const & runparams)
1031 {
1032         // Handle the given text, supposing it has no sections (i.e. a "simple" text). The input may vary in length
1033         // between a single paragraph to a whole document.
1034
1035         // First, the <info> tag.
1036         ParagraphList const &paragraphs = text.paragraphs();
1037         pit_type bpit = runparams.par_begin;
1038         pit_type const epit = runparams.par_end;
1039         DocBookInfoTag info = getParagraphsWithInfo(paragraphs, bpit, epit);
1040         outputDocBookInfo(text, buf, xs, runparams, paragraphs, info);
1041
1042         // Then, the content. It starts where the <info> ends.
1043         auto par = text.paragraphs().iterator_at(info.epit);
1044         auto end = text.paragraphs().iterator_at(epit);
1045         while (par != end) {
1046                 if (!hasOnlyNotes(*par))
1047                         par = makeAny(text, buf, xs, runparams, par);
1048                 else
1049                         ++par;
1050         }
1051 }
1052
1053
1054 void docbookParagraphs(Text const &text,
1055                                            Buffer const &buf,
1056                                            XMLStream &xs,
1057                                            OutputParams const &runparams) {
1058         ParagraphList const &paragraphs = text.paragraphs();
1059         if (runparams.par_begin == runparams.par_end) {
1060                 runparams.par_begin = 0;
1061                 runparams.par_end = paragraphs.size();
1062         }
1063         pit_type bpit = runparams.par_begin;
1064         pit_type const epit = runparams.par_end;
1065         LASSERT(bpit < epit,
1066                         {
1067                                 xs << XMLStream::ESCAPE_NONE << "<!-- DocBook output error! -->\n";
1068                                 return;
1069                         });
1070
1071         std::stack<std::pair<int, string>> headerLevels; // Used to determine when to open/close sections: store the depth
1072         // of the section and the tag that was used to open it.
1073
1074         // Detect whether the document contains sections. If there are no sections, there can be no automatically
1075         // discovered abstract.
1076         bool documentHasSections;
1077         pit_type eppit;
1078         tie(documentHasSections, eppit) = hasDocumentSectioning(paragraphs, bpit, epit);
1079
1080         if (documentHasSections) {
1081                 docbookFirstParagraphs(text, buf, xs, runparams, eppit);
1082                 bpit = eppit;
1083         } else {
1084                 docbookSimpleAllParagraphs(text, buf, xs, runparams);
1085                 return;
1086         }
1087
1088         bool currentlyInAppendix = false;
1089
1090         auto par = text.paragraphs().iterator_at(bpit);
1091         auto end = text.paragraphs().iterator_at(epit);
1092         while (par != end) {
1093                 OutputParams ourparams = runparams;
1094
1095                 if (par->params().startOfAppendix())
1096                         currentlyInAppendix = true;
1097                 if (hasOnlyNotes(*par)) {
1098                         ++par;
1099                         continue;
1100                 }
1101
1102                 Layout const &style = par->layout();
1103
1104                 // Think about adding <section> and/or </section>s.
1105                 const bool isLayoutSectioning = style.category() == from_utf8("Sectioning");
1106                 if (isLayoutSectioning) {
1107                         int level = style.toclevel;
1108
1109                         // Need to close a previous section if it has the same level or a higher one (close <section> if opening a <h2>
1110                         // after a <h2>, <h3>, <h4>, <h5> or <h6>). More examples:
1111                         //   - current: h2; back: h1; do not close any <section>
1112                         //   - current: h1; back: h2; close two <section> (first the <h2>, then the <h1>, so a new <h1> can come)
1113                         while (!headerLevels.empty() && level <= headerLevels.top().first) {
1114                                 int stackLevel = headerLevels.top().first;
1115                                 docstring stackTag = from_utf8("</" + headerLevels.top().second + ">");
1116                                 headerLevels.pop();
1117
1118                                 // Output the tag only if it corresponds to a legit section.
1119                                 if (stackLevel != Layout::NOT_IN_TOC)
1120                                         xs << XMLStream::ESCAPE_NONE << stackTag << xml::CR();
1121                         }
1122
1123                         // Open the new section: first push it onto the stack, then output it in DocBook.
1124                         string sectionTag = (currentlyInAppendix && style.docbooksectiontag() == "chapter") ?
1125                                                                 "appendix" : style.docbooksectiontag();
1126                         headerLevels.push(std::make_pair(level, sectionTag));
1127
1128                         // Some sectioning-like elements should not be output (such as FrontMatter).
1129                         if (level != Layout::NOT_IN_TOC) {
1130                                 // Look for a label in the title, i.e. a InsetLabel as a child.
1131                                 docstring id = docstring();
1132                                 for (pos_type i = 0; i < par->size(); ++i) {
1133                                         Inset const *inset = par->getInset(i);
1134                                         if (inset) {
1135                                                 if (auto label = dynamic_cast<InsetLabel const *>(inset)) {
1136                                                         // Generate the attributes for the section if need be.
1137                                                         id += "xml:id=\"" + xml::cleanID(label->screenLabel()) + "\"";
1138
1139                                                         // Don't output the ID as a DocBook <anchor>.
1140                                                         ourparams.docbook_anchors_to_ignore.emplace(label->screenLabel());
1141
1142                                                         // Cannot have multiple IDs per tag.
1143                                                         break;
1144                                                 }
1145                                         }
1146                                 }
1147
1148                                 // Write the open tag for this section.
1149                                 docstring tag = from_utf8("<" + sectionTag);
1150                                 if (!id.empty())
1151                                         tag += from_utf8(" ") + id;
1152                                 tag += from_utf8(">");
1153                                 xs << XMLStream::ESCAPE_NONE << tag;
1154                                 xs << xml::CR();
1155                         }
1156                 }
1157
1158                 // Close all sections before the bibliography.
1159                 // 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)?
1160                 auto insetsLength = distance(par->insetList().begin(), par->insetList().end());
1161                 if (insetsLength > 0) {
1162                         Inset const *firstInset = par->getInset(0);
1163                         if (firstInset && dynamic_cast<InsetBibtex const *>(firstInset)) {
1164                                 while (!headerLevels.empty()) {
1165                                         int level = headerLevels.top().first;
1166                                         docstring tag = from_utf8("</" + headerLevels.top().second + ">");
1167                                         headerLevels.pop();
1168
1169                                         // Output the tag only if it corresponds to a legit section.
1170                                         if (level != Layout::NOT_IN_TOC) {
1171                                                 xs << XMLStream::ESCAPE_NONE << tag;
1172                                                 xs << xml::CR();
1173                                         }
1174                                 }
1175                         }
1176                 }
1177
1178                 // Generate this paragraph.
1179                 par = makeAny(text, buf, xs, ourparams, par);
1180         }
1181
1182         // If need be, close <section>s, but only at the end of the document (otherwise, dealt with at the beginning
1183         // of the loop).
1184         while (!headerLevels.empty() && headerLevels.top().first > Layout::NOT_IN_TOC) {
1185                 docstring tag = from_utf8("</" + headerLevels.top().second + ">");
1186                 headerLevels.pop();
1187                 xs << XMLStream::ESCAPE_NONE << tag;
1188                 xs << xml::CR();
1189         }
1190 }
1191
1192 } // namespace lyx