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