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