]> git.lyx.org Git - lyx.git/blob - src/output_docbook.cpp
DocBook: Kornel's patch to get rid of many assertions
[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 "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
43 using namespace std;
44 using namespace lyx::support;
45
46 namespace lyx {
47
48 namespace {
49
50 std::string const fontToDocBookTag(xml::FontTypes type)
51 {
52         switch (type) {
53         case xml::FontTypes::FT_EMPH:
54         case xml::FontTypes::FT_BOLD:
55                 return "emphasis";
56         case xml::FontTypes::FT_NOUN:
57                 return "person";
58         case xml::FontTypes::FT_UBAR:
59         case xml::FontTypes::FT_WAVE:
60         case xml::FontTypes::FT_DBAR:
61         case xml::FontTypes::FT_SOUT:
62         case xml::FontTypes::FT_XOUT:
63         case xml::FontTypes::FT_ITALIC:
64         case xml::FontTypes::FT_UPRIGHT:
65         case xml::FontTypes::FT_SLANTED:
66         case xml::FontTypes::FT_SMALLCAPS:
67         case xml::FontTypes::FT_ROMAN:
68         case xml::FontTypes::FT_SANS:
69                 return "emphasis";
70         case xml::FontTypes::FT_TYPE:
71                 return "code";
72         case xml::FontTypes::FT_SIZE_TINY:
73         case xml::FontTypes::FT_SIZE_SCRIPT:
74         case xml::FontTypes::FT_SIZE_FOOTNOTE:
75         case xml::FontTypes::FT_SIZE_SMALL:
76         case xml::FontTypes::FT_SIZE_NORMAL:
77         case xml::FontTypes::FT_SIZE_LARGE:
78         case xml::FontTypes::FT_SIZE_LARGER:
79         case xml::FontTypes::FT_SIZE_LARGEST:
80         case xml::FontTypes::FT_SIZE_HUGE:
81         case xml::FontTypes::FT_SIZE_HUGER:
82         case xml::FontTypes::FT_SIZE_INCREASE:
83         case xml::FontTypes::FT_SIZE_DECREASE:
84                 return "emphasis";
85         default:
86                 return "";
87         }
88 }
89
90 string fontToRole(xml::FontTypes type)
91 {
92         // Specific fonts are achieved with roles. The only common ones are "" for basic emphasis,
93         // and "bold"/"strong" for bold. With some specific options, other roles are copied into
94         // HTML output (via the DocBook XSLT sheets); otherwise, if not recognised, they are just ignored.
95         // Hence, it is not a problem to have many roles by default here.
96         // See https://www.sourceware.org/ml/docbook/2003-05/msg00269.html
97         switch (type) {
98         case xml::FontTypes::FT_ITALIC:
99         case xml::FontTypes::FT_EMPH:
100                 return "";
101         case xml::FontTypes::FT_BOLD:
102                 return "bold";
103         case xml::FontTypes::FT_NOUN:
104                 return ""; // Outputs a <person>
105         case xml::FontTypes::FT_TYPE:
106                 return ""; // Outputs a <code>
107         case xml::FontTypes::FT_UBAR:
108                 return "underline";
109
110                 // All other roles are non-standard for DocBook.
111
112         case xml::FontTypes::FT_WAVE:
113                 return "wave";
114         case xml::FontTypes::FT_DBAR:
115                 return "dbar";
116         case xml::FontTypes::FT_SOUT:
117                 return "sout";
118         case xml::FontTypes::FT_XOUT:
119                 return "xout";
120         case xml::FontTypes::FT_UPRIGHT:
121                 return "upright";
122         case xml::FontTypes::FT_SLANTED:
123                 return "slanted";
124         case xml::FontTypes::FT_SMALLCAPS:
125                 return "smallcaps";
126         case xml::FontTypes::FT_ROMAN:
127                 return "roman";
128         case xml::FontTypes::FT_SANS:
129                 return "sans";
130         case xml::FontTypes::FT_SIZE_TINY:
131                 return "tiny";
132         case xml::FontTypes::FT_SIZE_SCRIPT:
133                 return "size_script";
134         case xml::FontTypes::FT_SIZE_FOOTNOTE:
135                 return "size_footnote";
136         case xml::FontTypes::FT_SIZE_SMALL:
137                 return "size_small";
138         case xml::FontTypes::FT_SIZE_NORMAL:
139                 return "size_normal";
140         case xml::FontTypes::FT_SIZE_LARGE:
141                 return "size_large";
142         case xml::FontTypes::FT_SIZE_LARGER:
143                 return "size_larger";
144         case xml::FontTypes::FT_SIZE_LARGEST:
145                 return "size_largest";
146         case xml::FontTypes::FT_SIZE_HUGE:
147                 return "size_huge";
148         case xml::FontTypes::FT_SIZE_HUGER:
149                 return "size_huger";
150         case xml::FontTypes::FT_SIZE_INCREASE:
151                 return "size_increase";
152         case xml::FontTypes::FT_SIZE_DECREASE:
153                 return "size_decrease";
154         default:
155                 return "";
156         }
157 }
158
159 string fontToAttribute(xml::FontTypes type) {
160         // If there is a role (i.e. nonstandard use of a tag), output the attribute. Otherwise, the sheer tag is sufficient
161         // for the font.
162         string role = fontToRole(type);
163         if (!role.empty()) {
164                 return "role='" + role + "'";
165         } else {
166                 return "";
167         }
168 }
169
170 } // end anonymous namespace
171
172
173 xml::FontTag docbookStartFontTag(xml::FontTypes type)
174 {
175         return xml::FontTag(from_utf8(fontToDocBookTag(type)), from_utf8(fontToAttribute(type)), type);
176 }
177
178
179 xml::EndFontTag docbookEndFontTag(xml::FontTypes type)
180 {
181         return xml::EndFontTag(from_utf8(fontToDocBookTag(type)), type);
182 }
183
184
185 namespace {
186
187 // convenience functions
188
189 void openParTag(XMLStream &xs, Layout const &lay)
190 {
191         if (lay.docbookwrappertag() != "NONE") {
192                 xs << xml::StartTag(lay.docbookwrappertag(), lay.docbookwrapperattr());
193         }
194
195         string tag = lay.docbooktag();
196         if (tag == "Plain Layout")
197                 tag = "para";
198
199         xs << xml::ParTag(tag, lay.docbookattr());
200 }
201
202
203 void closeTag(XMLStream &xs, Layout const &lay)
204 {
205         string tag = lay.docbooktag();
206         if (tag == "Plain Layout")
207                 tag = "para";
208
209         xs << xml::EndTag(tag);
210         if (lay.docbookwrappertag() != "NONE")
211                 xs << xml::EndTag(lay.docbookwrappertag());
212 }
213
214
215 void openLabelTag(XMLStream & xs, Layout const & lay) // Mostly for definition lists.
216 {
217         xs << xml::StartTag(lay.docbookitemlabeltag(), lay.docbookitemlabelattr());
218 }
219
220
221 void closeLabelTag(XMLStream & xs, Layout const & lay)
222 {
223         xs << xml::EndTag(lay.docbookitemlabeltag());
224         xs << xml::CR();
225 }
226
227
228 void openItemTag(XMLStream &xs, Layout const &lay)
229 {
230         xs << xml::StartTag(lay.docbookitemtag(), lay.docbookitemattr());
231 }
232
233
234 // Return true when new elements are output in a paragraph, false otherwise.
235 bool openInnerItemTag(XMLStream &xs, Layout const &lay)
236 {
237         if (lay.docbookiteminnertag() != "NONE") {
238                 xs << xml::CR();
239                 xs << xml::ParTag(lay.docbookiteminnertag(), lay.docbookiteminnerattr());
240
241                 if (lay.docbookiteminnertag() == "para") {
242                         return true;
243                 }
244         }
245         return false;
246 }
247
248
249 void closeInnerItemTag(XMLStream &xs, Layout const &lay)
250 {
251         if (lay.docbookiteminnertag()!= "NONE") {
252                 xs << xml::EndTag(lay.docbookiteminnertag());
253                 xs << xml::CR();
254         }
255 }
256
257
258 inline void closeItemTag(XMLStream &xs, Layout const &lay)
259 {
260         xs << xml::EndTag(lay.docbookitemtag());
261         xs << xml::CR();
262 }
263
264 // end of convenience functions
265
266 ParagraphList::const_iterator findLastParagraph(
267                 ParagraphList::const_iterator p,
268                 ParagraphList::const_iterator const & pend) {
269         for (++p; p != pend && p->layout().latextype == LATEX_PARAGRAPH; ++p);
270
271         return p;
272 }
273
274
275 ParagraphList::const_iterator findEndOfEnvironment(
276                 ParagraphList::const_iterator const & pstart,
277                 ParagraphList::const_iterator const & pend)
278 {
279         ParagraphList::const_iterator p = pstart;
280         Layout const &bstyle = p->layout();
281         size_t const depth = p->params().depth();
282         for (++p; p != pend; ++p) {
283                 Layout const &style = p->layout();
284                 // It shouldn't happen that e.g. a section command occurs inside
285                 // a quotation environment, at a higher depth, but as of 6/2009,
286                 // it can happen. We pretend that it's just at lowest depth.
287                 if (style.latextype == LATEX_COMMAND)
288                         return p;
289
290                 // If depth is down, we're done
291                 if (p->params().depth() < depth)
292                         return p;
293
294                 // If depth is up, we're not done
295                 if (p->params().depth() > depth)
296                         continue;
297
298                 // FIXME I am not sure about the first check.
299                 // Surely we *could* have different layouts that count as
300                 // LATEX_PARAGRAPH, right?
301                 if (style.latextype == LATEX_PARAGRAPH || style != bstyle)
302                         return p;
303         }
304         return pend;
305 }
306
307
308 ParagraphList::const_iterator makeParagraphBibliography(
309                 Buffer const &buf,
310                 XMLStream &xs,
311                 OutputParams const &runparams,
312                 Text const &text,
313                 ParagraphList::const_iterator const & pbegin,
314                 ParagraphList::const_iterator const & pend)
315 {
316         auto const begin = text.paragraphs().begin();
317         auto const end = text.paragraphs().end();
318
319         // Find the paragraph *before* pbegin.
320         ParagraphList::const_iterator pbegin_before = begin;
321         if (pbegin != begin) {
322                 ParagraphList::const_iterator pbegin_before_next = begin;
323                 ++pbegin_before_next;
324
325                 while (pbegin_before_next != pbegin) {
326                         ++pbegin_before;
327                         ++pbegin_before_next;
328                 }
329         }
330
331         ParagraphList::const_iterator par = pbegin;
332
333         // If this is the first paragraph in a bibliography, open the bibliography tag.
334         if (pbegin != begin && pbegin_before->layout().latextype != LATEX_BIB_ENVIRONMENT) {
335                 xs << xml::StartTag("bibliography");
336                 xs << xml::CR();
337         }
338
339         // Generate the required paragraphs.
340         for (; par != pend; ++par) {
341                 // Start the precooked bibliography entry. This is very much like opening a paragraph tag.
342                 // Don't forget the citation ID!
343                 docstring attr;
344                 for (auto i = 0; i < par->size(); ++i) {
345                         Inset const *ip = par->getInset(0);
346                         if (ip != nullptr && ip->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_utf8(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                 string tag = paragraphs[bpitAbstract].layout().docbookforceabstracttag();
867                 if (tag == "NONE")
868                         tag = "abstract";
869
870                 xs << xml::StartTag(tag);
871                 xs << xml::CR();
872                 xs.startDivision(false);
873                 generateDocBookParagraphWithoutSectioning(text, buf, xs, runparams, paragraphs, bpitAbstract, epitAbstract);
874                 xs.endDivision();
875                 xs << xml::EndTag(tag);
876                 xs << xml::CR();
877         }
878
879         // End the <info> tag if it was started.
880         if (needInfo) {
881                 xs << xml::EndTag("info");
882                 xs << xml::CR();
883                 xs.endDivision();
884         }
885 }
886
887
888 void docbookFirstParagraphs(
889                 Text const &text,
890                 Buffer const &buf,
891                 XMLStream &xs,
892                 OutputParams const &runparams,
893                 pit_type epit)
894 {
895         // Handle the beginning of the document, supposing it has sections.
896         // Major role: output the first <info> tag.
897
898         ParagraphList const &paragraphs = text.paragraphs();
899         pit_type bpit = runparams.par_begin;
900         DocBookInfoTag info = getParagraphsWithInfo(paragraphs, bpit, epit);
901         outputDocBookInfo(text, buf, xs, runparams, paragraphs, info, get<3>(info), epit);
902 }
903
904
905 bool isParagraphEmpty(const Paragraph &par)
906 {
907         InsetList const &insets = par.insetList();
908         size_t insetsLength = distance(insets.begin(), insets.end());
909         bool hasParagraphOnlyNote = insetsLength == 1 && insets.get(0) && insets.get(0)->asInsetCollapsible() &&
910                                                                 dynamic_cast<InsetNote *>(insets.get(0));
911         return hasParagraphOnlyNote;
912 }
913
914
915 void docbookSimpleAllParagraphs(
916                 Text const & text,
917                 Buffer const & buf,
918                 XMLStream & xs,
919                 OutputParams const & runparams)
920 {
921         // Handle the document, supposing it has no sections (i.e. a "simple" document).
922
923         // First, the <info> tag.
924         ParagraphList const &paragraphs = text.paragraphs();
925         pit_type bpit = runparams.par_begin;
926         pit_type const epit = runparams.par_end;
927         DocBookInfoTag info = getParagraphsWithInfo(paragraphs, bpit, epit);
928         outputDocBookInfo(text, buf, xs, runparams, paragraphs, info, 0, 0);
929         bpit = get<3>(info); // Generate the content starting from the end of the <info> part.
930
931         // Then, the content.
932         ParagraphList::const_iterator const pend =
933                         (epit == (int) paragraphs.size()) ?
934                         paragraphs.end() : paragraphs.iterator_at(epit);
935
936         while (bpit < epit) {
937                 auto par = paragraphs.iterator_at(bpit);
938                 ParagraphList::const_iterator const lastStartedPar = par;
939                 ParagraphList::const_iterator send;
940
941                 if (isParagraphEmpty(*par)) {
942                         ++par;
943                         bpit += distance(lastStartedPar, par);
944                         continue;
945                 }
946
947                 // Generate this paragraph.
948                 tie(par, send) = makeAny(text, buf, xs, runparams, par, send, pend);
949                 bpit += distance(lastStartedPar, par);
950         }
951 }
952
953
954 void docbookParagraphs(Text const &text,
955                                            Buffer const &buf,
956                                            XMLStream &xs,
957                                            OutputParams const &runparams) {
958         ParagraphList const &paragraphs = text.paragraphs();
959         if (runparams.par_begin == runparams.par_end) {
960                 runparams.par_begin = 0;
961                 runparams.par_end = paragraphs.size();
962         }
963         pit_type bpit = runparams.par_begin;
964         pit_type const epit = runparams.par_end;
965         LASSERT(bpit < epit,
966                         {
967                                 xs << XMLStream::ESCAPE_NONE << "<!-- DocBook output error! -->\n";
968                                 return;
969                         });
970
971         ParagraphList::const_iterator const pend =
972                         (epit == (int) paragraphs.size()) ?
973                         paragraphs.end() : paragraphs.iterator_at(epit);
974         std::stack<std::pair<int, string>> headerLevels; // Used to determine when to open/close sections: store the depth
975         // of the section and the tag that was used to open it.
976
977         // Detect whether the document contains sections. If there are no sections, there can be no automatically
978         // discovered abstract.
979         bool documentHasSections;
980         pit_type eppit;
981         tie(documentHasSections, eppit) = hasDocumentSectioning(paragraphs, bpit, epit);
982
983         if (documentHasSections) {
984                 docbookFirstParagraphs(text, buf, xs, runparams, eppit);
985                 bpit = eppit;
986         } else {
987                 docbookSimpleAllParagraphs(text, buf, xs, runparams);
988                 return;
989         }
990
991         bool currentlyInAppendix = false;
992
993         while (bpit < epit) {
994                 OutputParams ourparams = runparams;
995
996                 auto par = paragraphs.iterator_at(bpit);
997                 if (par->params().startOfAppendix())
998                         currentlyInAppendix = true;
999                 Layout const &style = par->layout();
1000                 ParagraphList::const_iterator const lastStartedPar = par;
1001                 ParagraphList::const_iterator send;
1002
1003                 if (isParagraphEmpty(*par)) {
1004                         ++par;
1005                         bpit += distance(lastStartedPar, par);
1006                         continue;
1007                 }
1008
1009                 // Think about adding <section> and/or </section>s.
1010                 const bool isLayoutSectioning = style.category() == from_utf8("Sectioning");
1011                 if (isLayoutSectioning) {
1012                         int level = style.toclevel;
1013
1014                         // Need to close a previous section if it has the same level or a higher one (close <section> if opening a <h2>
1015                         // after a <h2>, <h3>, <h4>, <h5> or <h6>). More examples:
1016                         //   - current: h2; back: h1; do not close any <section>
1017                         //   - current: h1; back: h2; close two <section> (first the <h2>, then the <h1>, so a new <h1> can come)
1018                         while (!headerLevels.empty() && level <= headerLevels.top().first) {
1019                                 int stackLevel = headerLevels.top().first;
1020                                 docstring stackTag = from_utf8("</" + headerLevels.top().second + ">");
1021                                 headerLevels.pop();
1022
1023                                 // Output the tag only if it corresponds to a legit section.
1024                                 if (stackLevel != Layout::NOT_IN_TOC)
1025                                         xs << XMLStream::ESCAPE_NONE << stackTag << xml::CR();
1026                         }
1027
1028                         // Open the new section: first push it onto the stack, then output it in DocBook.
1029                         string sectionTag = (currentlyInAppendix && style.docbooksectiontag() == "chapter") ?
1030                                                                 "appendix" : style.docbooksectiontag();
1031                         headerLevels.push(std::make_pair(level, sectionTag));
1032
1033                         // Some sectioning-like elements should not be output (such as FrontMatter).
1034                         if (level != Layout::NOT_IN_TOC) {
1035                                 // Look for a label in the title, i.e. a InsetLabel as a child.
1036                                 docstring id = docstring();
1037                                 for (pos_type i = 0; i < par->size(); ++i) {
1038                                         Inset const *inset = par->getInset(i);
1039                                         if (inset) {
1040                                                 if (auto label = dynamic_cast<InsetLabel const *>(inset)) {
1041                                                         // Generate the attributes for the section if need be.
1042                                                         id += "xml:id=\"" + xml::cleanID(label->screenLabel()) + "\"";
1043
1044                                                         // Don't output the ID as a DocBook <anchor>.
1045                                                         ourparams.docbook_anchors_to_ignore.emplace(label->screenLabel());
1046
1047                                                         // Cannot have multiple IDs per tag.
1048                                                         break;
1049                                                 }
1050                                         }
1051                                 }
1052
1053                                 // Write the open tag for this section.
1054                                 docstring tag = from_utf8("<" + sectionTag);
1055                                 if (!id.empty())
1056                                         tag += from_utf8(" ") + id;
1057                                 tag += from_utf8(">");
1058                                 xs << XMLStream::ESCAPE_NONE << tag;
1059                                 xs << xml::CR();
1060                         }
1061                 }
1062
1063                 // Close all sections before the bibliography.
1064                 // 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)?
1065                 auto insetsLength = distance(par->insetList().begin(), par->insetList().end());
1066                 if (insetsLength > 0) {
1067                         Inset const *firstInset = par->getInset(0);
1068                         if (firstInset && dynamic_cast<InsetBibtex const *>(firstInset)) {
1069                                 while (!headerLevels.empty()) {
1070                                         int level = headerLevels.top().first;
1071                                         docstring tag = from_utf8("</" + headerLevels.top().second + ">");
1072                                         headerLevels.pop();
1073
1074                                         // Output the tag only if it corresponds to a legit section.
1075                                         if (level != Layout::NOT_IN_TOC) {
1076                                                 xs << XMLStream::ESCAPE_NONE << tag;
1077                                                 xs << xml::CR();
1078                                         }
1079                                 }
1080                         }
1081                 }
1082
1083                 // Generate this paragraph.
1084                 tie(par, send) = makeAny(text, buf, xs, ourparams, par, send, pend);
1085                 bpit += distance(lastStartedPar, par);
1086         }
1087
1088         // If need be, close <section>s, but only at the end of the document (otherwise, dealt with at the beginning
1089         // of the loop).
1090         while (!headerLevels.empty() && headerLevels.top().first > Layout::NOT_IN_TOC) {
1091                 docstring tag = from_utf8("</" + headerLevels.top().second + ">");
1092                 headerLevels.pop();
1093                 xs << XMLStream::ESCAPE_NONE << tag;
1094                 xs << xml::CR();
1095         }
1096 }
1097
1098 } // namespace lyx