]> git.lyx.org Git - features.git/blob - src/output_docbook.cpp
Support the mathbbm font.
[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 "Layout.h"
20 #include "OutputParams.h"
21 #include "Paragraph.h"
22 #include "ParagraphList.h"
23 #include "ParagraphParameters.h"
24 #include "xml.h"
25 #include "Text.h"
26 #include "TextClass.h"
27
28 #include "insets/InsetBibtex.h"
29 #include "insets/InsetBibitem.h"
30 #include "insets/InsetLabel.h"
31 #include "insets/InsetNote.h"
32
33 #include "support/convert.h"
34 #include "support/debug.h"
35 #include "support/lassert.h"
36 #include "support/lstrings.h"
37 #include "support/textutils.h"
38
39 #include <stack>
40 #include <iostream>
41 #include <algorithm>
42 #include <sstream>
43
44 using namespace std;
45 using namespace lyx::support;
46
47 namespace lyx {
48
49 namespace {
50
51 std::string const fontToDocBookTag(xml::FontTypes type)
52 {
53         switch (type) {
54         case xml::FontTypes::FT_EMPH:
55         case xml::FontTypes::FT_BOLD:
56                 return "emphasis";
57         case xml::FontTypes::FT_NOUN:
58                 return "person";
59         case xml::FontTypes::FT_UBAR:
60         case xml::FontTypes::FT_WAVE:
61         case xml::FontTypes::FT_DBAR:
62         case xml::FontTypes::FT_SOUT:
63         case xml::FontTypes::FT_XOUT:
64         case xml::FontTypes::FT_ITALIC:
65         case xml::FontTypes::FT_UPRIGHT:
66         case xml::FontTypes::FT_SLANTED:
67         case xml::FontTypes::FT_SMALLCAPS:
68         case xml::FontTypes::FT_ROMAN:
69         case xml::FontTypes::FT_SANS:
70                 return "emphasis";
71         case xml::FontTypes::FT_TYPE:
72                 return "code";
73         case xml::FontTypes::FT_SIZE_TINY:
74         case xml::FontTypes::FT_SIZE_SCRIPT:
75         case xml::FontTypes::FT_SIZE_FOOTNOTE:
76         case xml::FontTypes::FT_SIZE_SMALL:
77         case xml::FontTypes::FT_SIZE_NORMAL:
78         case xml::FontTypes::FT_SIZE_LARGE:
79         case xml::FontTypes::FT_SIZE_LARGER:
80         case xml::FontTypes::FT_SIZE_LARGEST:
81         case xml::FontTypes::FT_SIZE_HUGE:
82         case xml::FontTypes::FT_SIZE_HUGER:
83         case xml::FontTypes::FT_SIZE_INCREASE:
84         case xml::FontTypes::FT_SIZE_DECREASE:
85                 return "emphasis";
86         default:
87                 return "";
88         }
89 }
90
91 string fontToRole(xml::FontTypes type)
92 {
93         // Specific fonts are achieved with roles. The only common ones are "" for basic emphasis,
94         // and "bold"/"strong" for bold. With some specific options, other roles are copied into
95         // HTML output (via the DocBook XSLT sheets); otherwise, if not recognised, they are just ignored.
96         // Hence, it is not a problem to have many roles by default here.
97         // See https://www.sourceware.org/ml/docbook/2003-05/msg00269.html
98         switch (type) {
99         case xml::FontTypes::FT_ITALIC:
100         case xml::FontTypes::FT_EMPH:
101                 return "";
102         case xml::FontTypes::FT_BOLD:
103                 return "bold";
104         case xml::FontTypes::FT_NOUN:
105                 return ""; // Outputs a <person>
106         case xml::FontTypes::FT_TYPE:
107                 return ""; // Outputs a <code>
108         case xml::FontTypes::FT_UBAR:
109                 return "underline";
110
111                 // All other roles are non-standard for DocBook.
112
113         case xml::FontTypes::FT_WAVE:
114                 return "wave";
115         case xml::FontTypes::FT_DBAR:
116                 return "dbar";
117         case xml::FontTypes::FT_SOUT:
118                 return "sout";
119         case xml::FontTypes::FT_XOUT:
120                 return "xout";
121         case xml::FontTypes::FT_UPRIGHT:
122                 return "upright";
123         case xml::FontTypes::FT_SLANTED:
124                 return "slanted";
125         case xml::FontTypes::FT_SMALLCAPS:
126                 return "smallcaps";
127         case xml::FontTypes::FT_ROMAN:
128                 return "roman";
129         case xml::FontTypes::FT_SANS:
130                 return "sans";
131         case xml::FontTypes::FT_SIZE_TINY:
132                 return "tiny";
133         case xml::FontTypes::FT_SIZE_SCRIPT:
134                 return "size_script";
135         case xml::FontTypes::FT_SIZE_FOOTNOTE:
136                 return "size_footnote";
137         case xml::FontTypes::FT_SIZE_SMALL:
138                 return "size_small";
139         case xml::FontTypes::FT_SIZE_NORMAL:
140                 return "size_normal";
141         case xml::FontTypes::FT_SIZE_LARGE:
142                 return "size_large";
143         case xml::FontTypes::FT_SIZE_LARGER:
144                 return "size_larger";
145         case xml::FontTypes::FT_SIZE_LARGEST:
146                 return "size_largest";
147         case xml::FontTypes::FT_SIZE_HUGE:
148                 return "size_huge";
149         case xml::FontTypes::FT_SIZE_HUGER:
150                 return "size_huger";
151         case xml::FontTypes::FT_SIZE_INCREASE:
152                 return "size_increase";
153         case xml::FontTypes::FT_SIZE_DECREASE:
154                 return "size_decrease";
155         default:
156                 return "";
157         }
158 }
159
160 string fontToAttribute(xml::FontTypes type) {
161         // If there is a role (i.e. nonstandard use of a tag), output the attribute. Otherwise, the sheer tag is sufficient
162         // for the font.
163         string role = fontToRole(type);
164         if (!role.empty()) {
165                 return "role='" + role + "'";
166         } else {
167                 return "";
168         }
169 }
170
171 } // end anonymous namespace
172
173
174 xml::FontTag docbookStartFontTag(xml::FontTypes type)
175 {
176         return xml::FontTag(from_utf8(fontToDocBookTag(type)), from_utf8(fontToAttribute(type)), type);
177 }
178
179
180 xml::EndFontTag docbookEndFontTag(xml::FontTypes type)
181 {
182         return xml::EndFontTag(from_utf8(fontToDocBookTag(type)), type);
183 }
184
185
186 namespace {
187
188 // convenience functions
189
190 void openParTag(XMLStream &xs, Layout const &lay)
191 {
192         if (lay.docbookwrappertag() != "NONE") {
193                 xs << xml::StartTag(lay.docbookwrappertag(), lay.docbookwrapperattr());
194         }
195
196         string tag = lay.docbooktag();
197         if (tag == "Plain Layout")
198                 tag = "para";
199
200         xs << xml::ParTag(tag, lay.docbookattr());
201 }
202
203
204 void closeTag(XMLStream &xs, Layout const &lay)
205 {
206         string tag = lay.docbooktag();
207         if (tag == "Plain Layout")
208                 tag = "para";
209
210         xs << xml::EndTag(tag);
211         if (lay.docbookwrappertag() != "NONE")
212                 xs << xml::EndTag(lay.docbookwrappertag());
213 }
214
215
216 void openLabelTag(XMLStream & xs, Layout const & lay) // Mostly for definition lists.
217 {
218         xs << xml::StartTag(lay.docbookitemlabeltag(), lay.docbookitemlabelattr());
219 }
220
221
222 void closeLabelTag(XMLStream & xs, Layout const & lay)
223 {
224         xs << xml::EndTag(lay.docbookitemlabeltag());
225         xs << xml::CR();
226 }
227
228
229 void openItemTag(XMLStream &xs, Layout const &lay)
230 {
231         xs << xml::StartTag(lay.docbookitemtag(), lay.docbookitemattr());
232 }
233
234
235 // Return true when new elements are output in a paragraph, false otherwise.
236 bool openInnerItemTag(XMLStream &xs, Layout const &lay)
237 {
238         if (lay.docbookiteminnertag() != "NONE") {
239                 xs << xml::CR();
240                 xs << xml::ParTag(lay.docbookiteminnertag(), lay.docbookiteminnerattr());
241
242                 if (lay.docbookiteminnertag() == "para") {
243                         return true;
244                 }
245         }
246         return false;
247 }
248
249
250 void closeInnerItemTag(XMLStream &xs, Layout const &lay)
251 {
252         if (lay.docbookiteminnertag()!= "NONE") {
253                 xs << xml::EndTag(lay.docbookiteminnertag());
254                 xs << xml::CR();
255         }
256 }
257
258
259 inline void closeItemTag(XMLStream &xs, Layout const &lay)
260 {
261         xs << xml::EndTag(lay.docbookitemtag());
262         xs << xml::CR();
263 }
264
265 // end of convenience functions
266
267 ParagraphList::const_iterator findLastParagraph(
268                 ParagraphList::const_iterator p,
269                 ParagraphList::const_iterator const & pend) {
270         for (++p; p != pend && p->layout().latextype == LATEX_PARAGRAPH; ++p);
271
272         return p;
273 }
274
275
276 ParagraphList::const_iterator findEndOfEnvironment(
277                 ParagraphList::const_iterator const & pstart,
278                 ParagraphList::const_iterator const & pend)
279 {
280         ParagraphList::const_iterator p = pstart;
281         Layout const &bstyle = p->layout();
282         size_t const depth = p->params().depth();
283         for (++p; p != pend; ++p) {
284                 Layout const &style = p->layout();
285                 // It shouldn't happen that e.g. a section command occurs inside
286                 // a quotation environment, at a higher depth, but as of 6/2009,
287                 // it can happen. We pretend that it's just at lowest depth.
288                 if (style.latextype == LATEX_COMMAND)
289                         return p;
290
291                 // If depth is down, we're done
292                 if (p->params().depth() < depth)
293                         return p;
294
295                 // If depth is up, we're not done
296                 if (p->params().depth() > depth)
297                         continue;
298
299                 // FIXME I am not sure about the first check.
300                 // Surely we *could* have different layouts that count as
301                 // LATEX_PARAGRAPH, right?
302                 if (style.latextype == LATEX_PARAGRAPH || style != bstyle)
303                         return p;
304         }
305         return pend;
306 }
307
308
309 ParagraphList::const_iterator makeParagraphBibliography(
310                 Buffer const &buf,
311                 XMLStream &xs,
312                 OutputParams const &runparams,
313                 Text const &text,
314                 ParagraphList::const_iterator const & pbegin,
315                 ParagraphList::const_iterator const & pend)
316 {
317         auto const begin = text.paragraphs().begin();
318         auto const end = text.paragraphs().end();
319
320         // Find the paragraph *before* pbegin.
321         ParagraphList::const_iterator pbegin_before = begin;
322         if (pbegin != begin) {
323                 ParagraphList::const_iterator pbegin_before_next = begin;
324                 ++pbegin_before_next;
325
326                 while (pbegin_before_next != pbegin) {
327                         ++pbegin_before;
328                         ++pbegin_before_next;
329                 }
330         }
331
332         ParagraphList::const_iterator par = pbegin;
333
334         // If this is the first paragraph in a bibliography, open the bibliography tag.
335         if (pbegin != begin && pbegin_before->layout().latextype != LATEX_BIB_ENVIRONMENT) {
336                 xs << xml::StartTag("bibliography");
337                 xs << xml::CR();
338         }
339
340         // Generate the required paragraphs.
341         for (; par != pend; ++par) {
342                 // Start the precooked bibliography entry. This is very much like opening a paragraph tag.
343                 // Don't forget the citation ID!
344                 docstring attr;
345                 for (auto i = 0; i < par->size(); ++i) {
346                         if (par->getInset(0)->lyxCode() == BIBITEM_CODE) {
347                                 const auto * bibitem = dynamic_cast<const InsetBibitem*>(par->getInset(i));
348                                 attr = from_utf8("xml:id='") + bibitem->bibLabel() + from_utf8("'");
349                                 break;
350                         }
351                 }
352                 xs << xml::StartTag(from_utf8("bibliomixed"), attr);
353
354                 // Generate the entry.
355                 par->simpleDocBookOnePar(buf, xs, runparams, text.outerFont(distance(begin, par)), true, true, 0);
356
357                 // End the precooked bibliography entry.
358                 xs << xml::EndTag("bibliomixed");
359                 xs << xml::CR();
360         }
361
362         // If this is the last paragraph in a bibliography, close the bibliography tag.
363         if (par == end || par->layout().latextype != LATEX_BIB_ENVIRONMENT) {
364                 xs << xml::EndTag("bibliography");
365                 xs << xml::CR();
366         }
367
368         return pend;
369 }
370
371
372 ParagraphList::const_iterator makeParagraphs(
373                 Buffer const &buf,
374                 XMLStream &xs,
375                 OutputParams const &runparams,
376                 Text const &text,
377                 ParagraphList::const_iterator const & pbegin,
378                 ParagraphList::const_iterator const & pend)
379 {
380         ParagraphList::const_iterator const begin = text.paragraphs().begin();
381         ParagraphList::const_iterator par = pbegin;
382         for (; par != pend; ++par) {
383                 Layout const &lay = par->layout();
384
385                 // We want to open the paragraph tag if:
386                 //   (i) the current layout permits multiple paragraphs
387                 //  (ii) we are either not already inside a paragraph (HTMLIsBlock) OR
388                 //         we are, but this is not the first paragraph
389                 //
390                 // But there is also a special case, and we first see whether we are in it.
391                 // We do not want to open the paragraph tag if this paragraph contains
392                 // only one item, and that item is "inline", i.e., not HTMLIsBlock (such
393                 // as a branch). On the other hand, if that single item has a font change
394                 // applied to it, then we still do need to open the paragraph.
395                 //
396                 // Obviously, this is very fragile. The main reason we need to do this is
397                 // because of branches, e.g., a branch that contains an entire new section.
398                 // We do not really want to wrap that whole thing in a <div>...</div>.
399                 bool special_case = false;
400                 Inset const *specinset = par->size() == 1 ? par->getInset(0) : 0;
401                 if (specinset && !specinset->getLayout().htmlisblock()) { // TODO: Convert htmlisblock to a DocBook parameter?
402                         Layout const &style = par->layout();
403                         FontInfo const first_font = style.labeltype == LABEL_MANUAL ?
404                                                                                 style.labelfont : style.font;
405                         FontInfo const our_font =
406                                         par->getFont(buf.masterBuffer()->params(), 0,
407                                                                  text.outerFont(distance(begin, par))).fontInfo();
408
409                         if (first_font == our_font)
410                                 special_case = true;
411                 }
412
413                 // Plain layouts must be ignored.
414                 if (!special_case && buf.params().documentClass().isPlainLayout(lay) && !runparams.docbook_force_pars)
415                         special_case = true;
416                 // TODO: Could get rid of this with a DocBook equivalent to htmlisblock?
417                 if (!special_case && par->size() == 1 && par->getInset(0)) {
418                         Inset const * firstInset = par->getInset(0);
419
420                         // Floats cannot be in paragraphs.
421                         special_case = to_ascii(firstInset->layoutName()).substr(0, 6) == "Float:";
422
423                         // Bibliographies cannot be in paragraphs.
424                         if (!special_case && firstInset->asInsetCommand())
425                                 special_case = firstInset->asInsetCommand()->params().getCmdName() == "bibtex";
426
427                         // Equations do not deserve their own paragraph (DocBook allows them outside paragraphs).
428                         if (!special_case && firstInset->asInsetMath())
429                                 special_case = true;
430
431                         // ERTs are in comments, not paragraphs.
432                         if (!special_case && firstInset->lyxCode() == lyx::ERT_CODE)
433                                 special_case = true;
434
435                         // Listings should not get into their own paragraph.
436                         if (!special_case && firstInset->lyxCode() == lyx::LISTINGS_CODE)
437                                 special_case = true;
438                 }
439
440                 bool const open_par = runparams.docbook_make_pars
441                                                           && (!runparams.docbook_in_par || par != pbegin)
442                                                           && !special_case;
443
444                 // We want to issue the closing tag if either:
445                 //   (i)  We opened it, and either docbook_in_par is false,
446                 //              or we're not in the last paragraph, anyway.
447                 //   (ii) We didn't open it and docbook_in_par is true,
448                 //              but we are in the first par, and there is a next par.
449                 ParagraphList::const_iterator nextpar = par;
450                 ++nextpar;
451                 bool const close_par =
452                                 ((open_par && (!runparams.docbook_in_par || nextpar != pend))
453                                 || (!open_par && runparams.docbook_in_par && par == pbegin && nextpar != pend));
454
455                 if (open_par) {
456                         openParTag(xs, lay);
457                 }
458
459                 par->simpleDocBookOnePar(buf, xs, runparams, text.outerFont(distance(begin, par)), open_par, close_par, 0);
460
461                 if (close_par) {
462                         closeTag(xs, lay);
463                         xs << xml::CR();
464                 }
465         }
466         return pend;
467 }
468
469
470 bool isNormalEnv(Layout const &lay)
471 {
472         return lay.latextype == LATEX_ENVIRONMENT
473                    || lay.latextype == LATEX_BIB_ENVIRONMENT;
474 }
475
476
477 ParagraphList::const_iterator makeEnvironment(
478                 Buffer const &buf,
479                 XMLStream &xs,
480                 OutputParams const &runparams,
481                 Text const &text,
482                 ParagraphList::const_iterator const & pbegin,
483                 ParagraphList::const_iterator const & pend)
484 {
485         ParagraphList::const_iterator const begin = text.paragraphs().begin();
486         ParagraphList::const_iterator par = pbegin;
487         Layout const &bstyle = par->layout();
488         depth_type const origdepth = pbegin->params().depth();
489
490         // open tag for this environment
491         openParTag(xs, bstyle);
492         xs << xml::CR();
493
494         // we will on occasion need to remember a layout from before.
495         Layout const *lastlay = nullptr;
496
497         while (par != pend) {
498                 Layout const & style = par->layout();
499                 ParagraphList::const_iterator send;
500
501                 // Actual content of this paragraph.
502                 switch (style.latextype) {
503                 case LATEX_ENVIRONMENT:
504                 case LATEX_LIST_ENVIRONMENT:
505                 case LATEX_ITEM_ENVIRONMENT: {
506                         // There are two possibilities in this case.
507                         // One is that we are still in the environment in which we
508                         // started---which we will be if the depth is the same.
509                         if (par->params().depth() == origdepth) {
510                                 LATTEST(bstyle == style);
511                                 if (lastlay != nullptr) {
512                                         closeItemTag(xs, *lastlay);
513                                         if (lastlay->docbookitemwrappertag() != "NONE") {
514                                                 xs << xml::EndTag(lastlay->docbookitemwrappertag());
515                                                 xs << xml::CR();
516                                         }
517                                         lastlay = nullptr;
518                                 }
519
520                                 // this will be positive if we want to skip the
521                                 // initial word (if it's been taken for the label).
522                                 pos_type sep = 0;
523
524                                 // Open a wrapper tag if needed.
525                                 if (style.docbookitemwrappertag() != "NONE") {
526                                         xs << xml::StartTag(style.docbookitemwrappertag(), style.docbookitemwrapperattr());
527                                         xs << xml::CR();
528                                 }
529
530                                 // label output
531                                 if (style.labeltype != LABEL_NO_LABEL &&
532                                                 style.docbookitemlabeltag() != "NONE") {
533
534                                         if (isNormalEnv(style)) {
535                                                 // in this case, we print the label only for the first
536                                                 // paragraph (as in a theorem or an abstract).
537                                                 if (par == pbegin) {
538                                                         docstring const lbl = pbegin->params().labelString();
539                                                         if (!lbl.empty()) {
540                                                                 openLabelTag(xs, style);
541                                                                 xs << lbl;
542                                                                 closeLabelTag(xs, style);
543                                                         } else {
544                                                                 // No new line after closeLabelTag.
545                                                                 xs << xml::CR();
546                                                         }
547                                                 }
548                                         } else { // some kind of list
549                                                 if (style.labeltype == LABEL_MANUAL) {
550                                                         // Only variablelist gets here.
551
552                                                         openLabelTag(xs, style);
553                                                         sep = par->firstWordDocBook(xs, runparams);
554                                                         closeLabelTag(xs, style);
555                                                 } else {
556                                                         openLabelTag(xs, style);
557                                                         xs << par->params().labelString();
558                                                         closeLabelTag(xs, style);
559                                                 }
560                                         }
561                                 } // end label output
562
563                                 // Start generating the item.
564                                 bool wasInParagraph = runparams.docbook_in_par;
565                                 openItemTag(xs, style);
566                                 bool getsIntoParagraph = openInnerItemTag(xs, style);
567                                 OutputParams rp = runparams;
568                                 rp.docbook_in_par = wasInParagraph | getsIntoParagraph;
569
570                                 // Maybe the item is completely empty, i.e. if the first word ends at the end of the current paragraph
571                                 // AND if the next paragraph doesn't have the same depth (if there is such a paragraph).
572                                 // Common case: there is only the first word on the line, but there is a nested list instead
573                                 // of more text.
574                                 bool emptyItem = false;
575                                 if (sep == par->size()) {
576                                         auto next_par = par;
577                                         ++next_par;
578                                         if (next_par == text.paragraphs().end()) // There is no next paragraph.
579                                                 emptyItem = true;
580                                         else // There is a next paragraph: check depth.
581                                                 emptyItem = par->params().depth() >= next_par->params().depth();
582                                 }
583
584                                 if (emptyItem) {
585                                         // Avoid having an empty item, this is not valid DocBook. A single character is enough to force
586                                         // generation of a full <para>.
587                                         xs << ' ';
588                                 } else {
589                                         // Generate the rest of the paragraph, if need be.
590                                         par->simpleDocBookOnePar(buf, xs, rp, text.outerFont(distance(begin, par)), true, true, sep);
591                                 }
592
593                                 ++par;
594                                 if (getsIntoParagraph)
595                                         closeInnerItemTag(xs, style);
596
597                                 // We may not want to close the tag yet, in particular:
598                                 // If we're not at the end of the item...
599                                 if (par != pend
600                                         //  and are doing items...
601                                         && !isNormalEnv(style)
602                                         // and if the depth has changed...
603                                         && par->params().depth() != origdepth) {
604                                         // then we'll save this layout for later, and close it when
605                                         // we get another item.
606                                         lastlay = &style;
607                                 } else {
608                                         closeItemTag(xs, style);
609
610                                         // Eventually, close the item wrapper.
611                                         if (style.docbookitemwrappertag() != "NONE") {
612                                                 xs << xml::EndTag(style.docbookitemwrappertag());
613                                                 xs << xml::CR();
614                                         }
615                                 }
616                         }
617                         // The other possibility is that the depth has increased.
618                         else {
619                                 send = findEndOfEnvironment(par, pend);
620                                 par = makeEnvironment(buf, xs, runparams, text, par, send);
621                         }
622                         break;
623                 }
624                 case LATEX_PARAGRAPH:
625                         send = findLastParagraph(par, pend);
626                         par = makeParagraphs(buf, xs, runparams, text, par, send);
627                         break;
628                 case LATEX_BIB_ENVIRONMENT:
629                         send = findLastParagraph(par, pend);
630                         par = makeParagraphBibliography(buf, xs, runparams, text, par, send);
631                         break;
632                 case LATEX_COMMAND:
633                         ++par;
634                         break;
635                 }
636         }
637
638         if (lastlay != nullptr) {
639                 closeItemTag(xs, *lastlay);
640                 if (lastlay->docbookitemwrappertag() != "NONE") {
641                         xs << xml::EndTag(lastlay->docbookitemwrappertag());
642                         xs << xml::CR();
643                 }
644         }
645         closeTag(xs, bstyle);
646         xs << xml::CR();
647         return pend;
648 }
649
650
651 void makeCommand(
652                 Buffer const & buf,
653                 XMLStream & xs,
654                 OutputParams const & runparams,
655                 Text const & text,
656                 ParagraphList::const_iterator const & pbegin)
657 {
658         Layout const &style = pbegin->layout();
659
660         // No need for labels, as they are handled by DocBook tags.
661
662         openParTag(xs, style);
663
664         ParagraphList::const_iterator const begin = text.paragraphs().begin();
665         pbegin->simpleDocBookOnePar(buf, xs, runparams,
666                                                                 text.outerFont(distance(begin, pbegin)));
667         closeTag(xs, style);
668         xs << xml::CR();
669 }
670
671 pair<ParagraphList::const_iterator, ParagraphList::const_iterator> makeAny(
672                 Text const &text,
673                 Buffer const &buf,
674                 XMLStream &xs,
675                 OutputParams const &ourparams,
676                 ParagraphList::const_iterator par,
677                 ParagraphList::const_iterator send,
678                 ParagraphList::const_iterator pend)
679 {
680         Layout const & style = par->layout();
681
682         switch (style.latextype) {
683                 case LATEX_COMMAND: {
684                         // The files with which we are working never have more than
685                         // one paragraph in a command structure.
686                         // FIXME
687                         // if (ourparams.docbook_in_par)
688                         //   fix it so we don't get sections inside standard, e.g.
689                         // note that we may then need to make runparams not const, so we
690                         // can communicate that back.
691                         // FIXME Maybe this fix should be in the routines themselves, in case
692                         // they are called from elsewhere.
693                         makeCommand(buf, xs, ourparams, text, par);
694                         ++par;
695                         break;
696                 }
697                 case LATEX_ENVIRONMENT:
698                 case LATEX_LIST_ENVIRONMENT:
699                 case LATEX_ITEM_ENVIRONMENT: {
700                         // FIXME Same fix here.
701                         send = findEndOfEnvironment(par, pend);
702                         par = makeEnvironment(buf, xs, ourparams, text, par, send);
703                         break;
704                 }
705                 case LATEX_BIB_ENVIRONMENT: {
706                         send = findLastParagraph(par, pend);
707                         par = makeParagraphBibliography(buf, xs, ourparams, text, par, send);
708                         break;
709                 }
710                 case LATEX_PARAGRAPH: {
711                         send = findLastParagraph(par, pend);
712                         par = makeParagraphs(buf, xs, ourparams, text, par, send);
713                         break;
714                 }
715         }
716
717         return make_pair(par, send);
718 }
719
720 } // end anonymous namespace
721
722
723 using DocBookDocumentSectioning = tuple<bool, pit_type>;
724 using DocBookInfoTag = tuple<set<pit_type>, set<pit_type>, pit_type, pit_type>;
725
726
727 DocBookDocumentSectioning hasDocumentSectioning(ParagraphList const &paragraphs, pit_type bpit, pit_type const epit) {
728         bool documentHasSections = false;
729
730         while (bpit < epit) {
731                 Layout const &style = paragraphs[bpit].layout();
732                 documentHasSections |= style.category() == from_utf8("Sectioning");
733
734                 if (documentHasSections) {
735                         break;
736                 }
737                 bpit += 1;
738         }
739         // Paragraphs before the first section: [ runparams.par_begin ; eppit )
740
741         return make_tuple(documentHasSections, bpit);
742 }
743
744
745 DocBookInfoTag getParagraphsWithInfo(ParagraphList const &paragraphs, pit_type const bpit, pit_type const epit) {
746         set<pit_type> shouldBeInInfo;
747         set<pit_type> mustBeInInfo;
748
749         pit_type cpit = bpit;
750         while (cpit < epit) {
751                 // Skip paragraphs only containing one note.
752                 Paragraph const &par = paragraphs[cpit];
753                 if (par.size() == 1 && dynamic_cast<InsetNote*>(paragraphs[cpit].insetList().get(0))) {
754                         cpit += 1;
755                         continue;
756                 }
757
758                 // Based on layout information, store this paragraph in one set: should be in <info>, must be.
759                 Layout const &style = par.layout();
760
761                 if (style.docbookininfo() == "always") {
762                         mustBeInInfo.emplace(cpit);
763                 } else if (style.docbookininfo() == "maybe") {
764                         shouldBeInInfo.emplace(cpit);
765                 } else {
766                         // Hypothesis: the <info> parts should be grouped together near the beginning bpit.
767                         break;
768                 }
769                 cpit += 1;
770         }
771         // Now, cpit points to the last paragraph that has things that could go in <info>.
772         // bpit is still the beginning of the <info> part.
773
774         return make_tuple(shouldBeInInfo, mustBeInInfo, bpit, cpit);
775 }
776
777
778 bool hasAbstractBetween(ParagraphList const &paragraphs, pit_type const bpitAbstract, pit_type const epitAbstract)
779 {
780         // Hypothesis: the paragraphs between bpitAbstract and epitAbstract can be considered an abstract because they
781         // are just after a document or part title.
782         if (epitAbstract - bpitAbstract <= 0)
783                 return false;
784
785         // If there is something between these paragraphs, check if it's compatible with an abstract (i.e. some text).
786         pit_type bpit = bpitAbstract;
787         while (bpit < epitAbstract) {
788                 const Paragraph &p = paragraphs.at(bpit);
789
790                 if (p.layout().name() == from_ascii("Abstract"))
791                         return true;
792
793                 if (!p.insetList().empty()) {
794                         for (const auto &i : p.insetList()) {
795                                 if (i.inset->getText(0) != nullptr) {
796                                         return true;
797                                 }
798                         }
799                 }
800                 bpit++;
801         }
802         return false;
803 }
804
805
806 pit_type generateDocBookParagraphWithoutSectioning(
807                 Text const & text,
808                 Buffer const & buf,
809                 XMLStream & xs,
810                 OutputParams const & runparams,
811                 ParagraphList const & paragraphs,
812                 pit_type bpit,
813                 pit_type epit)
814 {
815         auto par = paragraphs.iterator_at(bpit);
816         auto lastStartedPar = par;
817         ParagraphList::const_iterator send;
818         auto const pend =
819                         (epit == (int) paragraphs.size()) ?
820                         paragraphs.end() : paragraphs.iterator_at(epit);
821
822         while (bpit < epit) {
823                 tie(par, send) = makeAny(text, buf, xs, runparams, par, send, pend);
824                 bpit += distance(lastStartedPar, par);
825                 lastStartedPar = par;
826         }
827
828         return bpit;
829 }
830
831
832 void outputDocBookInfo(
833                 Text const & text,
834                 Buffer const & buf,
835                 XMLStream & xs,
836                 OutputParams const & runparams,
837                 ParagraphList const & paragraphs,
838                 DocBookInfoTag const & info,
839                 pit_type bpitAbstract,
840                 pit_type const epitAbstract)
841 {
842         // Consider everything between bpitAbstract and epitAbstract (excluded) as paragraphs for the abstract.
843         // Use bpitAbstract >= epitAbstract to indicate there is no abstract.
844
845         set<pit_type> shouldBeInInfo;
846         set<pit_type> mustBeInInfo;
847         pit_type bpitInfo;
848         pit_type epitInfo;
849         tie(shouldBeInInfo, mustBeInInfo, bpitInfo, epitInfo) = info;
850
851         // The abstract must go in <info>.
852         bool hasAbstract = hasAbstractBetween(paragraphs, bpitAbstract, epitAbstract);
853         bool needInfo = !mustBeInInfo.empty() || hasAbstract;
854
855         // Start the <info> tag if required.
856         if (needInfo) {
857                 xs.startDivision(false);
858                 xs << xml::StartTag("info");
859                 xs << xml::CR();
860         }
861
862         // Output the elements that should go in <info>.
863         generateDocBookParagraphWithoutSectioning(text, buf, xs, runparams, paragraphs, bpitInfo, epitInfo);
864
865         if (hasAbstract) {
866                 // Sometimes, there are many paragraphs that should go into the abstract, but none generates actual content.
867                 // Thus, first generate to a temporary stream, then only create the <abstract> tag if these paragraphs
868                 // generate some content.
869                 odocstringstream os2;
870                 XMLStream xs2(os2);
871                 generateDocBookParagraphWithoutSectioning(text, buf, xs2, runparams, paragraphs, bpitAbstract, epitAbstract);
872
873                 // Actually output the abstract if there is something to do.
874                 if (!os2.str().empty()) {
875                         string tag = paragraphs[bpitAbstract].layout().docbookforceabstracttag();
876                         if (tag == "NONE")
877                                 tag = "abstract";
878
879                         xs << xml::StartTag(tag);
880                         xs << xml::CR();
881                         xs << XMLStream::ESCAPE_NONE << os2.str();
882                         xs << xml::EndTag(tag);
883                         xs << xml::CR();
884                 }
885         }
886
887         // End the <info> tag if it was started.
888         if (needInfo) {
889                 xs << xml::EndTag("info");
890                 xs << xml::CR();
891                 xs.endDivision();
892         }
893 }
894
895
896 void docbookFirstParagraphs(
897                 Text const &text,
898                 Buffer const &buf,
899                 XMLStream &xs,
900                 OutputParams const &runparams,
901                 pit_type epit)
902 {
903         // Handle the beginning of the document, supposing it has sections.
904         // Major role: output the first <info> tag.
905
906         ParagraphList const &paragraphs = text.paragraphs();
907         pit_type bpit = runparams.par_begin;
908         DocBookInfoTag info = getParagraphsWithInfo(paragraphs, bpit, epit);
909         outputDocBookInfo(text, buf, xs, runparams, paragraphs, info, get<3>(info), epit);
910 }
911
912
913 bool isParagraphEmpty(const Paragraph &par)
914 {
915         InsetList const &insets = par.insetList();
916         size_t insetsLength = distance(insets.begin(), insets.end());
917         bool hasParagraphOnlyNote = insetsLength == 1 && insets.get(0) && insets.get(0)->asInsetCollapsible() &&
918                                                                 dynamic_cast<InsetNote *>(insets.get(0));
919         return hasParagraphOnlyNote;
920 }
921
922
923 void docbookSimpleAllParagraphs(
924                 Text const & text,
925                 Buffer const & buf,
926                 XMLStream & xs,
927                 OutputParams const & runparams)
928 {
929         // Handle the document, supposing it has no sections (i.e. a "simple" document).
930
931         // First, the <info> tag.
932         ParagraphList const &paragraphs = text.paragraphs();
933         pit_type bpit = runparams.par_begin;
934         pit_type const epit = runparams.par_end;
935         DocBookInfoTag info = getParagraphsWithInfo(paragraphs, bpit, epit);
936         outputDocBookInfo(text, buf, xs, runparams, paragraphs, info, 0, 0);
937         bpit = get<3>(info); // Generate the content starting from the end of the <info> part.
938
939         // Then, the content.
940         ParagraphList::const_iterator const pend =
941                         (epit == (int) paragraphs.size()) ?
942                         paragraphs.end() : paragraphs.iterator_at(epit);
943
944         while (bpit < epit) {
945                 auto par = paragraphs.iterator_at(bpit);
946                 ParagraphList::const_iterator const lastStartedPar = par;
947                 ParagraphList::const_iterator send;
948
949                 if (isParagraphEmpty(*par)) {
950                         ++par;
951                         bpit += distance(lastStartedPar, par);
952                         continue;
953                 }
954
955                 // Generate this paragraph.
956                 tie(par, send) = makeAny(text, buf, xs, runparams, par, send, pend);
957                 bpit += distance(lastStartedPar, par);
958         }
959 }
960
961
962 void docbookParagraphs(Text const &text,
963                                            Buffer const &buf,
964                                            XMLStream &xs,
965                                            OutputParams const &runparams) {
966         ParagraphList const &paragraphs = text.paragraphs();
967         if (runparams.par_begin == runparams.par_end) {
968                 runparams.par_begin = 0;
969                 runparams.par_end = paragraphs.size();
970         }
971         pit_type bpit = runparams.par_begin;
972         pit_type const epit = runparams.par_end;
973         LASSERT(bpit < epit,
974                         {
975                                 xs << XMLStream::ESCAPE_NONE << "<!-- DocBook output error! -->\n";
976                                 return;
977                         });
978
979         ParagraphList::const_iterator const pend =
980                         (epit == (int) paragraphs.size()) ?
981                         paragraphs.end() : paragraphs.iterator_at(epit);
982         std::stack<std::pair<int, string>> headerLevels; // Used to determine when to open/close sections: store the depth
983         // of the section and the tag that was used to open it.
984
985         // Detect whether the document contains sections. If there are no sections, there can be no automatically
986         // discovered abstract.
987         bool documentHasSections;
988         pit_type eppit;
989         tie(documentHasSections, eppit) = hasDocumentSectioning(paragraphs, bpit, epit);
990
991         if (documentHasSections) {
992                 docbookFirstParagraphs(text, buf, xs, runparams, eppit);
993                 bpit = eppit;
994         } else {
995                 docbookSimpleAllParagraphs(text, buf, xs, runparams);
996                 return;
997         }
998
999         bool currentlyInAppendix = false;
1000
1001         while (bpit < epit) {
1002                 OutputParams ourparams = runparams;
1003
1004                 auto par = paragraphs.iterator_at(bpit);
1005                 if (par->params().startOfAppendix())
1006                         currentlyInAppendix = true;
1007                 Layout const &style = par->layout();
1008                 ParagraphList::const_iterator const lastStartedPar = par;
1009                 ParagraphList::const_iterator send;
1010
1011                 if (isParagraphEmpty(*par)) {
1012                         ++par;
1013                         bpit += distance(lastStartedPar, par);
1014                         continue;
1015                 }
1016
1017                 // Think about adding <section> and/or </section>s.
1018                 const bool isLayoutSectioning = style.category() == from_utf8("Sectioning");
1019                 if (isLayoutSectioning) {
1020                         int level = style.toclevel;
1021
1022                         // Need to close a previous section if it has the same level or a higher one (close <section> if opening a <h2>
1023                         // after a <h2>, <h3>, <h4>, <h5> or <h6>). More examples:
1024                         //   - current: h2; back: h1; do not close any <section>
1025                         //   - current: h1; back: h2; close two <section> (first the <h2>, then the <h1>, so a new <h1> can come)
1026                         while (!headerLevels.empty() && level <= headerLevels.top().first) {
1027                                 int stackLevel = headerLevels.top().first;
1028                                 docstring stackTag = from_utf8("</" + headerLevels.top().second + ">");
1029                                 headerLevels.pop();
1030
1031                                 // Output the tag only if it corresponds to a legit section.
1032                                 if (stackLevel != Layout::NOT_IN_TOC)
1033                                         xs << XMLStream::ESCAPE_NONE << stackTag << xml::CR();
1034                         }
1035
1036                         // Open the new section: first push it onto the stack, then output it in DocBook.
1037                         string sectionTag = (currentlyInAppendix && style.docbooksectiontag() == "chapter") ?
1038                                                                 "appendix" : style.docbooksectiontag();
1039                         headerLevels.push(std::make_pair(level, sectionTag));
1040
1041                         // Some sectioning-like elements should not be output (such as FrontMatter).
1042                         if (level != Layout::NOT_IN_TOC) {
1043                                 // Look for a label in the title, i.e. a InsetLabel as a child.
1044                                 docstring id = docstring();
1045                                 for (pos_type i = 0; i < par->size(); ++i) {
1046                                         Inset const *inset = par->getInset(i);
1047                                         if (inset) {
1048                                                 if (auto label = dynamic_cast<InsetLabel const *>(inset)) {
1049                                                         // Generate the attributes for the section if need be.
1050                                                         id += "xml:id=\"" + xml::cleanID(label->screenLabel()) + "\"";
1051
1052                                                         // Don't output the ID as a DocBook <anchor>.
1053                                                         ourparams.docbook_anchors_to_ignore.emplace(label->screenLabel());
1054
1055                                                         // Cannot have multiple IDs per tag.
1056                                                         break;
1057                                                 }
1058                                         }
1059                                 }
1060
1061                                 // Write the open tag for this section.
1062                                 docstring tag = from_utf8("<" + sectionTag);
1063                                 if (!id.empty())
1064                                         tag += from_utf8(" ") + id;
1065                                 tag += from_utf8(">");
1066                                 xs << XMLStream::ESCAPE_NONE << tag;
1067                                 xs << xml::CR();
1068                         }
1069                 }
1070
1071                 // Close all sections before the bibliography.
1072                 // 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)?
1073                 auto insetsLength = distance(par->insetList().begin(), par->insetList().end());
1074                 if (insetsLength > 0) {
1075                         Inset const *firstInset = par->getInset(0);
1076                         if (firstInset && dynamic_cast<InsetBibtex const *>(firstInset)) {
1077                                 while (!headerLevels.empty()) {
1078                                         int level = headerLevels.top().first;
1079                                         docstring tag = from_utf8("</" + headerLevels.top().second + ">");
1080                                         headerLevels.pop();
1081
1082                                         // Output the tag only if it corresponds to a legit section.
1083                                         if (level != Layout::NOT_IN_TOC) {
1084                                                 xs << XMLStream::ESCAPE_NONE << tag;
1085                                                 xs << xml::CR();
1086                                         }
1087                                 }
1088                         }
1089                 }
1090
1091                 // Generate this paragraph.
1092                 tie(par, send) = makeAny(text, buf, xs, ourparams, par, send, pend);
1093                 bpit += distance(lastStartedPar, par);
1094         }
1095
1096         // If need be, close <section>s, but only at the end of the document (otherwise, dealt with at the beginning
1097         // of the loop).
1098         while (!headerLevels.empty() && headerLevels.top().first > Layout::NOT_IN_TOC) {
1099                 docstring tag = from_utf8("</" + headerLevels.top().second + ">");
1100                 headerLevels.pop();
1101                 xs << XMLStream::ESCAPE_NONE << tag;
1102                 xs << xml::CR();
1103         }
1104 }
1105
1106 } // namespace lyx