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