]> git.lyx.org Git - lyx.git/blob - src/output_docbook.cpp
DocBook: don't output abstract if it would have no content.
[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 #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                         Inset const *ip = par->getInset(0);
347                         if (ip != nullptr && ip->lyxCode() == BIBITEM_CODE) {
348                                 const auto * bibitem = dynamic_cast<const InsetBibitem*>(par->getInset(i));
349                                 attr = from_utf8("xml:id='") + bibitem->bibLabel() + from_utf8("'");
350                                 break;
351                         }
352                 }
353                 xs << xml::StartTag(from_utf8("bibliomixed"), attr);
354
355                 // Generate the entry.
356                 par->simpleDocBookOnePar(buf, xs, runparams, text.outerFont(distance(begin, par)), true, true, 0);
357
358                 // End the precooked bibliography entry.
359                 xs << xml::EndTag("bibliomixed");
360                 xs << xml::CR();
361         }
362
363         // If this is the last paragraph in a bibliography, close the bibliography tag.
364         if (par == end || par->layout().latextype != LATEX_BIB_ENVIRONMENT) {
365                 xs << xml::EndTag("bibliography");
366                 xs << xml::CR();
367         }
368
369         return pend;
370 }
371
372
373 ParagraphList::const_iterator makeParagraphs(
374                 Buffer const &buf,
375                 XMLStream &xs,
376                 OutputParams const &runparams,
377                 Text const &text,
378                 ParagraphList::const_iterator const & pbegin,
379                 ParagraphList::const_iterator const & pend)
380 {
381         ParagraphList::const_iterator const begin = text.paragraphs().begin();
382         ParagraphList::const_iterator par = pbegin;
383         for (; par != pend; ++par) {
384                 Layout const &lay = par->layout();
385
386                 // We want to open the paragraph tag if:
387                 //   (i) the current layout permits multiple paragraphs
388                 //  (ii) we are either not already inside a paragraph (HTMLIsBlock) OR
389                 //         we are, but this is not the first paragraph
390                 //
391                 // But there is also a special case, and we first see whether we are in it.
392                 // We do not want to open the paragraph tag if this paragraph contains
393                 // only one item, and that item is "inline", i.e., not HTMLIsBlock (such
394                 // as a branch). On the other hand, if that single item has a font change
395                 // applied to it, then we still do need to open the paragraph.
396                 //
397                 // Obviously, this is very fragile. The main reason we need to do this is
398                 // because of branches, e.g., a branch that contains an entire new section.
399                 // We do not really want to wrap that whole thing in a <div>...</div>.
400                 bool special_case = false;
401                 Inset const *specinset = par->size() == 1 ? par->getInset(0) : 0;
402                 if (specinset && !specinset->getLayout().htmlisblock()) { // TODO: Convert htmlisblock to a DocBook parameter?
403                         Layout const &style = par->layout();
404                         FontInfo const first_font = style.labeltype == LABEL_MANUAL ?
405                                                                                 style.labelfont : style.font;
406                         FontInfo const our_font =
407                                         par->getFont(buf.masterBuffer()->params(), 0,
408                                                                  text.outerFont(distance(begin, par))).fontInfo();
409
410                         if (first_font == our_font)
411                                 special_case = true;
412                 }
413
414                 // Plain layouts must be ignored.
415                 if (!special_case && buf.params().documentClass().isPlainLayout(lay) && !runparams.docbook_force_pars)
416                         special_case = true;
417                 // TODO: Could get rid of this with a DocBook equivalent to htmlisblock?
418                 if (!special_case && par->size() == 1 && par->getInset(0)) {
419                         Inset const * firstInset = par->getInset(0);
420
421                         // Floats cannot be in paragraphs.
422                         special_case = to_utf8(firstInset->layoutName()).substr(0, 6) == "Float:";
423
424                         // Bibliographies cannot be in paragraphs.
425                         if (!special_case && firstInset->asInsetCommand())
426                                 special_case = firstInset->asInsetCommand()->params().getCmdName() == "bibtex";
427
428                         // Equations do not deserve their own paragraph (DocBook allows them outside paragraphs).
429                         if (!special_case && firstInset->asInsetMath())
430                                 special_case = true;
431
432                         // ERTs are in comments, not paragraphs.
433                         if (!special_case && firstInset->lyxCode() == lyx::ERT_CODE)
434                                 special_case = true;
435
436                         // Listings should not get into their own paragraph.
437                         if (!special_case && firstInset->lyxCode() == lyx::LISTINGS_CODE)
438                                 special_case = true;
439                 }
440
441                 bool const open_par = runparams.docbook_make_pars
442                                                           && (!runparams.docbook_in_par || par != pbegin)
443                                                           && !special_case;
444
445                 // We want to issue the closing tag if either:
446                 //   (i)  We opened it, and either docbook_in_par is false,
447                 //              or we're not in the last paragraph, anyway.
448                 //   (ii) We didn't open it and docbook_in_par is true,
449                 //              but we are in the first par, and there is a next par.
450                 ParagraphList::const_iterator nextpar = par;
451                 ++nextpar;
452                 bool const close_par =
453                                 ((open_par && (!runparams.docbook_in_par || nextpar != pend))
454                                 || (!open_par && runparams.docbook_in_par && par == pbegin && nextpar != pend));
455
456                 if (open_par) {
457                         openParTag(xs, lay);
458                 }
459
460                 par->simpleDocBookOnePar(buf, xs, runparams, text.outerFont(distance(begin, par)), open_par, close_par, 0);
461
462                 if (close_par) {
463                         closeTag(xs, lay);
464                         xs << xml::CR();
465                 }
466         }
467         return pend;
468 }
469
470
471 bool isNormalEnv(Layout const &lay)
472 {
473         return lay.latextype == LATEX_ENVIRONMENT
474                    || lay.latextype == LATEX_BIB_ENVIRONMENT;
475 }
476
477
478 ParagraphList::const_iterator makeEnvironment(
479                 Buffer const &buf,
480                 XMLStream &xs,
481                 OutputParams const &runparams,
482                 Text const &text,
483                 ParagraphList::const_iterator const & pbegin,
484                 ParagraphList::const_iterator const & pend)
485 {
486         ParagraphList::const_iterator const begin = text.paragraphs().begin();
487         ParagraphList::const_iterator par = pbegin;
488         Layout const &bstyle = par->layout();
489         depth_type const origdepth = pbegin->params().depth();
490
491         // open tag for this environment
492         openParTag(xs, bstyle);
493         xs << xml::CR();
494
495         // we will on occasion need to remember a layout from before.
496         Layout const *lastlay = nullptr;
497
498         while (par != pend) {
499                 Layout const & style = par->layout();
500                 ParagraphList::const_iterator send;
501
502                 // Actual content of this paragraph.
503                 switch (style.latextype) {
504                 case LATEX_ENVIRONMENT:
505                 case LATEX_LIST_ENVIRONMENT:
506                 case LATEX_ITEM_ENVIRONMENT: {
507                         // There are two possibilities in this case.
508                         // One is that we are still in the environment in which we
509                         // started---which we will be if the depth is the same.
510                         if (par->params().depth() == origdepth) {
511                                 LATTEST(bstyle == style);
512                                 if (lastlay != nullptr) {
513                                         closeItemTag(xs, *lastlay);
514                                         if (lastlay->docbookitemwrappertag() != "NONE") {
515                                                 xs << xml::EndTag(lastlay->docbookitemwrappertag());
516                                                 xs << xml::CR();
517                                         }
518                                         lastlay = nullptr;
519                                 }
520
521                                 // this will be positive if we want to skip the
522                                 // initial word (if it's been taken for the label).
523                                 pos_type sep = 0;
524
525                                 // Open a wrapper tag if needed.
526                                 if (style.docbookitemwrappertag() != "NONE") {
527                                         xs << xml::StartTag(style.docbookitemwrappertag(), style.docbookitemwrapperattr());
528                                         xs << xml::CR();
529                                 }
530
531                                 // label output
532                                 if (style.labeltype != LABEL_NO_LABEL &&
533                                                 style.docbookitemlabeltag() != "NONE") {
534
535                                         if (isNormalEnv(style)) {
536                                                 // in this case, we print the label only for the first
537                                                 // paragraph (as in a theorem or an abstract).
538                                                 if (par == pbegin) {
539                                                         docstring const lbl = pbegin->params().labelString();
540                                                         if (!lbl.empty()) {
541                                                                 openLabelTag(xs, style);
542                                                                 xs << lbl;
543                                                                 closeLabelTag(xs, style);
544                                                         } else {
545                                                                 // No new line after closeLabelTag.
546                                                                 xs << xml::CR();
547                                                         }
548                                                 }
549                                         } else { // some kind of list
550                                                 if (style.labeltype == LABEL_MANUAL) {
551                                                         // Only variablelist gets here.
552
553                                                         openLabelTag(xs, style);
554                                                         sep = par->firstWordDocBook(xs, runparams);
555                                                         closeLabelTag(xs, style);
556                                                 } else {
557                                                         openLabelTag(xs, style);
558                                                         xs << par->params().labelString();
559                                                         closeLabelTag(xs, style);
560                                                 }
561                                         }
562                                 } // end label output
563
564                                 // Start generating the item.
565                                 bool wasInParagraph = runparams.docbook_in_par;
566                                 openItemTag(xs, style);
567                                 bool getsIntoParagraph = openInnerItemTag(xs, style);
568                                 OutputParams rp = runparams;
569                                 rp.docbook_in_par = wasInParagraph | getsIntoParagraph;
570
571                                 // Maybe the item is completely empty, i.e. if the first word ends at the end of the current paragraph
572                                 // AND if the next paragraph doesn't have the same depth (if there is such a paragraph).
573                                 // Common case: there is only the first word on the line, but there is a nested list instead
574                                 // of more text.
575                                 bool emptyItem = false;
576                                 if (sep == par->size()) {
577                                         auto next_par = par;
578                                         ++next_par;
579                                         if (next_par == text.paragraphs().end()) // There is no next paragraph.
580                                                 emptyItem = true;
581                                         else // There is a next paragraph: check depth.
582                                                 emptyItem = par->params().depth() >= next_par->params().depth();
583                                 }
584
585                                 if (emptyItem) {
586                                         // Avoid having an empty item, this is not valid DocBook. A single character is enough to force
587                                         // generation of a full <para>.
588                                         xs << ' ';
589                                 } else {
590                                         // Generate the rest of the paragraph, if need be.
591                                         par->simpleDocBookOnePar(buf, xs, rp, text.outerFont(distance(begin, par)), true, true, sep);
592                                 }
593
594                                 ++par;
595                                 if (getsIntoParagraph)
596                                         closeInnerItemTag(xs, style);
597
598                                 // We may not want to close the tag yet, in particular:
599                                 // If we're not at the end of the item...
600                                 if (par != pend
601                                         //  and are doing items...
602                                         && !isNormalEnv(style)
603                                         // and if the depth has changed...
604                                         && par->params().depth() != origdepth) {
605                                         // then we'll save this layout for later, and close it when
606                                         // we get another item.
607                                         lastlay = &style;
608                                 } else {
609                                         closeItemTag(xs, style);
610
611                                         // Eventually, close the item wrapper.
612                                         if (style.docbookitemwrappertag() != "NONE") {
613                                                 xs << xml::EndTag(style.docbookitemwrappertag());
614                                                 xs << xml::CR();
615                                         }
616                                 }
617                         }
618                         // The other possibility is that the depth has increased.
619                         else {
620                                 send = findEndOfEnvironment(par, pend);
621                                 par = makeEnvironment(buf, xs, runparams, text, par, send);
622                         }
623                         break;
624                 }
625                 case LATEX_PARAGRAPH:
626                         send = findLastParagraph(par, pend);
627                         par = makeParagraphs(buf, xs, runparams, text, par, send);
628                         break;
629                 case LATEX_BIB_ENVIRONMENT:
630                         send = findLastParagraph(par, pend);
631                         par = makeParagraphBibliography(buf, xs, runparams, text, par, send);
632                         break;
633                 case LATEX_COMMAND:
634                         ++par;
635                         break;
636                 }
637         }
638
639         if (lastlay != nullptr) {
640                 closeItemTag(xs, *lastlay);
641                 if (lastlay->docbookitemwrappertag() != "NONE") {
642                         xs << xml::EndTag(lastlay->docbookitemwrappertag());
643                         xs << xml::CR();
644                 }
645         }
646         closeTag(xs, bstyle);
647         xs << xml::CR();
648         return pend;
649 }
650
651
652 void makeCommand(
653                 Buffer const & buf,
654                 XMLStream & xs,
655                 OutputParams const & runparams,
656                 Text const & text,
657                 ParagraphList::const_iterator const & pbegin)
658 {
659         Layout const &style = pbegin->layout();
660
661         // No need for labels, as they are handled by DocBook tags.
662
663         openParTag(xs, style);
664
665         ParagraphList::const_iterator const begin = text.paragraphs().begin();
666         pbegin->simpleDocBookOnePar(buf, xs, runparams,
667                                                                 text.outerFont(distance(begin, pbegin)));
668         closeTag(xs, style);
669         xs << xml::CR();
670 }
671
672 pair<ParagraphList::const_iterator, ParagraphList::const_iterator> makeAny(
673                 Text const &text,
674                 Buffer const &buf,
675                 XMLStream &xs,
676                 OutputParams const &ourparams,
677                 ParagraphList::const_iterator par,
678                 ParagraphList::const_iterator send,
679                 ParagraphList::const_iterator pend)
680 {
681         Layout const & style = par->layout();
682
683         switch (style.latextype) {
684                 case LATEX_COMMAND: {
685                         // The files with which we are working never have more than
686                         // one paragraph in a command structure.
687                         // FIXME
688                         // if (ourparams.docbook_in_par)
689                         //   fix it so we don't get sections inside standard, e.g.
690                         // note that we may then need to make runparams not const, so we
691                         // can communicate that back.
692                         // FIXME Maybe this fix should be in the routines themselves, in case
693                         // they are called from elsewhere.
694                         makeCommand(buf, xs, ourparams, text, par);
695                         ++par;
696                         break;
697                 }
698                 case LATEX_ENVIRONMENT:
699                 case LATEX_LIST_ENVIRONMENT:
700                 case LATEX_ITEM_ENVIRONMENT: {
701                         // FIXME Same fix here.
702                         send = findEndOfEnvironment(par, pend);
703                         par = makeEnvironment(buf, xs, ourparams, text, par, send);
704                         break;
705                 }
706                 case LATEX_BIB_ENVIRONMENT: {
707                         send = findLastParagraph(par, pend);
708                         par = makeParagraphBibliography(buf, xs, ourparams, text, par, send);
709                         break;
710                 }
711                 case LATEX_PARAGRAPH: {
712                         send = findLastParagraph(par, pend);
713                         par = makeParagraphs(buf, xs, ourparams, text, par, send);
714                         break;
715                 }
716         }
717
718         return make_pair(par, send);
719 }
720
721 } // end anonymous namespace
722
723
724 using DocBookDocumentSectioning = tuple<bool, pit_type>;
725 using DocBookInfoTag = tuple<set<pit_type>, set<pit_type>, pit_type, pit_type>;
726
727
728 DocBookDocumentSectioning hasDocumentSectioning(ParagraphList const &paragraphs, pit_type bpit, pit_type const epit) {
729         bool documentHasSections = false;
730
731         while (bpit < epit) {
732                 Layout const &style = paragraphs[bpit].layout();
733                 documentHasSections |= style.category() == from_utf8("Sectioning");
734
735                 if (documentHasSections) {
736                         break;
737                 }
738                 bpit += 1;
739         }
740         // Paragraphs before the first section: [ runparams.par_begin ; eppit )
741
742         return make_tuple(documentHasSections, bpit);
743 }
744
745
746 DocBookInfoTag getParagraphsWithInfo(ParagraphList const &paragraphs, pit_type const bpit, pit_type const epit) {
747         set<pit_type> shouldBeInInfo;
748         set<pit_type> mustBeInInfo;
749
750         pit_type cpit = bpit;
751         while (cpit < epit) {
752                 // Skip paragraphs only containing one note.
753                 Paragraph const &par = paragraphs[cpit];
754                 if (par.size() == 1 && dynamic_cast<InsetNote*>(paragraphs[cpit].insetList().get(0))) {
755                         cpit += 1;
756                         continue;
757                 }
758
759                 // Based on layout information, store this paragraph in one set: should be in <info>, must be.
760                 Layout const &style = par.layout();
761
762                 if (style.docbookininfo() == "always") {
763                         mustBeInInfo.emplace(cpit);
764                 } else if (style.docbookininfo() == "maybe") {
765                         shouldBeInInfo.emplace(cpit);
766                 } else {
767                         // Hypothesis: the <info> parts should be grouped together near the beginning bpit.
768                         break;
769                 }
770                 cpit += 1;
771         }
772         // Now, cpit points to the last paragraph that has things that could go in <info>.
773         // bpit is still the beginning of the <info> part.
774
775         return make_tuple(shouldBeInInfo, mustBeInInfo, bpit, cpit);
776 }
777
778
779 bool hasAbstractBetween(ParagraphList const &paragraphs, pit_type const bpitAbstract, pit_type const epitAbstract)
780 {
781         // Hypothesis: the paragraphs between bpitAbstract and epitAbstract can be considered an abstract because they
782         // are just after a document or part title.
783         if (epitAbstract - bpitAbstract <= 0)
784                 return false;
785
786         // If there is something between these paragraphs, check if it's compatible with an abstract (i.e. some text).
787         pit_type bpit = bpitAbstract;
788         while (bpit < epitAbstract) {
789                 const Paragraph &p = paragraphs.at(bpit);
790
791                 if (p.layout().name() == from_ascii("Abstract"))
792                         return true;
793
794                 if (!p.insetList().empty()) {
795                         for (const auto &i : p.insetList()) {
796                                 if (i.inset->getText(0) != nullptr) {
797                                         return true;
798                                 }
799                         }
800                 }
801                 bpit++;
802         }
803         return false;
804 }
805
806
807 pit_type generateDocBookParagraphWithoutSectioning(
808                 Text const & text,
809                 Buffer const & buf,
810                 XMLStream & xs,
811                 OutputParams const & runparams,
812                 ParagraphList const & paragraphs,
813                 pit_type bpit,
814                 pit_type epit)
815 {
816         auto par = paragraphs.iterator_at(bpit);
817         auto lastStartedPar = par;
818         ParagraphList::const_iterator send;
819         auto const pend =
820                         (epit == (int) paragraphs.size()) ?
821                         paragraphs.end() : paragraphs.iterator_at(epit);
822
823         while (bpit < epit) {
824                 tie(par, send) = makeAny(text, buf, xs, runparams, par, send, pend);
825                 bpit += distance(lastStartedPar, par);
826                 lastStartedPar = par;
827         }
828
829         return bpit;
830 }
831
832
833 void outputDocBookInfo(
834                 Text const & text,
835                 Buffer const & buf,
836                 XMLStream & xs,
837                 OutputParams const & runparams,
838                 ParagraphList const & paragraphs,
839                 DocBookInfoTag const & info,
840                 pit_type bpitAbstract,
841                 pit_type const epitAbstract)
842 {
843         // Consider everything between bpitAbstract and epitAbstract (excluded) as paragraphs for the abstract.
844         // Use bpitAbstract >= epitAbstract to indicate there is no abstract.
845
846         set<pit_type> shouldBeInInfo;
847         set<pit_type> mustBeInInfo;
848         pit_type bpitInfo;
849         pit_type epitInfo;
850         tie(shouldBeInInfo, mustBeInInfo, bpitInfo, epitInfo) = info;
851
852         // The abstract must go in <info>.
853         bool hasAbstract = hasAbstractBetween(paragraphs, bpitAbstract, epitAbstract);
854         bool needInfo = !mustBeInInfo.empty() || hasAbstract;
855
856         // Start the <info> tag if required.
857         if (needInfo) {
858                 xs.startDivision(false);
859                 xs << xml::StartTag("info");
860                 xs << xml::CR();
861         }
862
863         // Output the elements that should go in <info>.
864         generateDocBookParagraphWithoutSectioning(text, buf, xs, runparams, paragraphs, bpitInfo, epitInfo);
865
866         if (hasAbstract) {
867                 // Sometimes, there are many paragraphs that should go into the abstract, but none generates actual content.
868                 // Thus, first generate to a temporary stream, then only create the <abstract> tag if these paragraphs
869                 // generate some content.
870                 odocstringstream os2;
871                 XMLStream xs2(os2);
872                 generateDocBookParagraphWithoutSectioning(text, buf, xs2, runparams, paragraphs, bpitAbstract, epitAbstract);
873
874                 // Actually output the abstract if there is something to do.
875                 if (!os2.str().empty()) {
876                         string tag = paragraphs[bpitAbstract].layout().docbookforceabstracttag();
877                         if (tag == "NONE")
878                                 tag = "abstract";
879
880                         xs << xml::StartTag(tag);
881                         xs << xml::CR();
882                         xs << XMLStream::ESCAPE_NONE << os2.str();
883                         xs << xml::EndTag(tag);
884                         xs << xml::CR();
885                 }
886         }
887
888         // End the <info> tag if it was started.
889         if (needInfo) {
890                 xs << xml::EndTag("info");
891                 xs << xml::CR();
892                 xs.endDivision();
893         }
894 }
895
896
897 void docbookFirstParagraphs(
898                 Text const &text,
899                 Buffer const &buf,
900                 XMLStream &xs,
901                 OutputParams const &runparams,
902                 pit_type epit)
903 {
904         // Handle the beginning of the document, supposing it has sections.
905         // Major role: output the first <info> tag.
906
907         ParagraphList const &paragraphs = text.paragraphs();
908         pit_type bpit = runparams.par_begin;
909         DocBookInfoTag info = getParagraphsWithInfo(paragraphs, bpit, epit);
910         outputDocBookInfo(text, buf, xs, runparams, paragraphs, info, get<3>(info), epit);
911 }
912
913
914 bool isParagraphEmpty(const Paragraph &par)
915 {
916         InsetList const &insets = par.insetList();
917         size_t insetsLength = distance(insets.begin(), insets.end());
918         bool hasParagraphOnlyNote = insetsLength == 1 && insets.get(0) && insets.get(0)->asInsetCollapsible() &&
919                                                                 dynamic_cast<InsetNote *>(insets.get(0));
920         return hasParagraphOnlyNote;
921 }
922
923
924 void docbookSimpleAllParagraphs(
925                 Text const & text,
926                 Buffer const & buf,
927                 XMLStream & xs,
928                 OutputParams const & runparams)
929 {
930         // Handle the document, supposing it has no sections (i.e. a "simple" document).
931
932         // First, the <info> tag.
933         ParagraphList const &paragraphs = text.paragraphs();
934         pit_type bpit = runparams.par_begin;
935         pit_type const epit = runparams.par_end;
936         DocBookInfoTag info = getParagraphsWithInfo(paragraphs, bpit, epit);
937         outputDocBookInfo(text, buf, xs, runparams, paragraphs, info, 0, 0);
938         bpit = get<3>(info); // Generate the content starting from the end of the <info> part.
939
940         // Then, the content.
941         ParagraphList::const_iterator const pend =
942                         (epit == (int) paragraphs.size()) ?
943                         paragraphs.end() : paragraphs.iterator_at(epit);
944
945         while (bpit < epit) {
946                 auto par = paragraphs.iterator_at(bpit);
947                 ParagraphList::const_iterator const lastStartedPar = par;
948                 ParagraphList::const_iterator send;
949
950                 if (isParagraphEmpty(*par)) {
951                         ++par;
952                         bpit += distance(lastStartedPar, par);
953                         continue;
954                 }
955
956                 // Generate this paragraph.
957                 tie(par, send) = makeAny(text, buf, xs, runparams, par, send, pend);
958                 bpit += distance(lastStartedPar, par);
959         }
960 }
961
962
963 void docbookParagraphs(Text const &text,
964                                            Buffer const &buf,
965                                            XMLStream &xs,
966                                            OutputParams const &runparams) {
967         ParagraphList const &paragraphs = text.paragraphs();
968         if (runparams.par_begin == runparams.par_end) {
969                 runparams.par_begin = 0;
970                 runparams.par_end = paragraphs.size();
971         }
972         pit_type bpit = runparams.par_begin;
973         pit_type const epit = runparams.par_end;
974         LASSERT(bpit < epit,
975                         {
976                                 xs << XMLStream::ESCAPE_NONE << "<!-- DocBook output error! -->\n";
977                                 return;
978                         });
979
980         ParagraphList::const_iterator const pend =
981                         (epit == (int) paragraphs.size()) ?
982                         paragraphs.end() : paragraphs.iterator_at(epit);
983         std::stack<std::pair<int, string>> headerLevels; // Used to determine when to open/close sections: store the depth
984         // of the section and the tag that was used to open it.
985
986         // Detect whether the document contains sections. If there are no sections, there can be no automatically
987         // discovered abstract.
988         bool documentHasSections;
989         pit_type eppit;
990         tie(documentHasSections, eppit) = hasDocumentSectioning(paragraphs, bpit, epit);
991
992         if (documentHasSections) {
993                 docbookFirstParagraphs(text, buf, xs, runparams, eppit);
994                 bpit = eppit;
995         } else {
996                 docbookSimpleAllParagraphs(text, buf, xs, runparams);
997                 return;
998         }
999
1000         bool currentlyInAppendix = false;
1001
1002         while (bpit < epit) {
1003                 OutputParams ourparams = runparams;
1004
1005                 auto par = paragraphs.iterator_at(bpit);
1006                 if (par->params().startOfAppendix())
1007                         currentlyInAppendix = true;
1008                 Layout const &style = par->layout();
1009                 ParagraphList::const_iterator const lastStartedPar = par;
1010                 ParagraphList::const_iterator send;
1011
1012                 if (isParagraphEmpty(*par)) {
1013                         ++par;
1014                         bpit += distance(lastStartedPar, par);
1015                         continue;
1016                 }
1017
1018                 // Think about adding <section> and/or </section>s.
1019                 const bool isLayoutSectioning = style.category() == from_utf8("Sectioning");
1020                 if (isLayoutSectioning) {
1021                         int level = style.toclevel;
1022
1023                         // Need to close a previous section if it has the same level or a higher one (close <section> if opening a <h2>
1024                         // after a <h2>, <h3>, <h4>, <h5> or <h6>). More examples:
1025                         //   - current: h2; back: h1; do not close any <section>
1026                         //   - current: h1; back: h2; close two <section> (first the <h2>, then the <h1>, so a new <h1> can come)
1027                         while (!headerLevels.empty() && level <= headerLevels.top().first) {
1028                                 int stackLevel = headerLevels.top().first;
1029                                 docstring stackTag = from_utf8("</" + headerLevels.top().second + ">");
1030                                 headerLevels.pop();
1031
1032                                 // Output the tag only if it corresponds to a legit section.
1033                                 if (stackLevel != Layout::NOT_IN_TOC)
1034                                         xs << XMLStream::ESCAPE_NONE << stackTag << xml::CR();
1035                         }
1036
1037                         // Open the new section: first push it onto the stack, then output it in DocBook.
1038                         string sectionTag = (currentlyInAppendix && style.docbooksectiontag() == "chapter") ?
1039                                                                 "appendix" : style.docbooksectiontag();
1040                         headerLevels.push(std::make_pair(level, sectionTag));
1041
1042                         // Some sectioning-like elements should not be output (such as FrontMatter).
1043                         if (level != Layout::NOT_IN_TOC) {
1044                                 // Look for a label in the title, i.e. a InsetLabel as a child.
1045                                 docstring id = docstring();
1046                                 for (pos_type i = 0; i < par->size(); ++i) {
1047                                         Inset const *inset = par->getInset(i);
1048                                         if (inset) {
1049                                                 if (auto label = dynamic_cast<InsetLabel const *>(inset)) {
1050                                                         // Generate the attributes for the section if need be.
1051                                                         id += "xml:id=\"" + xml::cleanID(label->screenLabel()) + "\"";
1052
1053                                                         // Don't output the ID as a DocBook <anchor>.
1054                                                         ourparams.docbook_anchors_to_ignore.emplace(label->screenLabel());
1055
1056                                                         // Cannot have multiple IDs per tag.
1057                                                         break;
1058                                                 }
1059                                         }
1060                                 }
1061
1062                                 // Write the open tag for this section.
1063                                 docstring tag = from_utf8("<" + sectionTag);
1064                                 if (!id.empty())
1065                                         tag += from_utf8(" ") + id;
1066                                 tag += from_utf8(">");
1067                                 xs << XMLStream::ESCAPE_NONE << tag;
1068                                 xs << xml::CR();
1069                         }
1070                 }
1071
1072                 // Close all sections before the bibliography.
1073                 // 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)?
1074                 auto insetsLength = distance(par->insetList().begin(), par->insetList().end());
1075                 if (insetsLength > 0) {
1076                         Inset const *firstInset = par->getInset(0);
1077                         if (firstInset && dynamic_cast<InsetBibtex const *>(firstInset)) {
1078                                 while (!headerLevels.empty()) {
1079                                         int level = headerLevels.top().first;
1080                                         docstring tag = from_utf8("</" + headerLevels.top().second + ">");
1081                                         headerLevels.pop();
1082
1083                                         // Output the tag only if it corresponds to a legit section.
1084                                         if (level != Layout::NOT_IN_TOC) {
1085                                                 xs << XMLStream::ESCAPE_NONE << tag;
1086                                                 xs << xml::CR();
1087                                         }
1088                                 }
1089                         }
1090                 }
1091
1092                 // Generate this paragraph.
1093                 tie(par, send) = makeAny(text, buf, xs, ourparams, par, send, pend);
1094                 bpit += distance(lastStartedPar, par);
1095         }
1096
1097         // If need be, close <section>s, but only at the end of the document (otherwise, dealt with at the beginning
1098         // of the loop).
1099         while (!headerLevels.empty() && headerLevels.top().first > Layout::NOT_IN_TOC) {
1100                 docstring tag = from_utf8("</" + headerLevels.top().second + ">");
1101                 headerLevels.pop();
1102                 xs << XMLStream::ESCAPE_NONE << tag;
1103                 xs << xml::CR();
1104         }
1105 }
1106
1107 } // namespace lyx