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