]> git.lyx.org Git - features.git/blob - src/output_docbook.cpp
Update layouts
[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/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
662                 std::cout << "Name: " << to_utf8(style.name()) << std::endl;
663                 std::cout << "  DocBook tag: " << style.docbooktag() << std::endl;
664                 std::cout << "  In info: " << style.docbookininfo() << std::endl;
665
666                 if (style.docbookininfo() == "always") {
667                         mustBeInInfo.emplace(cpit);
668                 } else if (style.docbookininfo() == "maybe") {
669                         shouldBeInInfo.emplace(cpit);
670                 } else {
671                         // Hypothesis: the <info> parts should be grouped together near the beginning bpit.
672                         break;
673                 }
674                 cpit += 1;
675         }
676         // Now, cpit points to the last paragraph that has things that could go in <info>.
677         // bpit is still the beginning of the <info> part.
678
679         return make_tuple(shouldBeInInfo, mustBeInInfo, bpit, cpit);
680 }
681
682
683 bool hasAbstractBetween(ParagraphList const &paragraphs, pit_type const bpitAbstract, pit_type const epitAbstract)
684 {
685         // Hypothesis: the paragraphs between bpitAbstract and epitAbstract can be considered an abstract because they
686         // are just after a document or part title.
687         if (epitAbstract - bpitAbstract <= 0)
688                 return false;
689
690         // If there is something between these paragraphs, check if it's compatible with an abstract (i.e. some text).
691         pit_type bpit = bpitAbstract;
692         while (bpit < epitAbstract) {
693                 const Paragraph &p = paragraphs.at(bpit);
694
695                 if (p.layout().name() == from_ascii("Abstract"))
696                         return true;
697
698                 if (!p.insetList().empty()) {
699                         for (const auto &i : p.insetList()) {
700                                 if (i.inset->getText(0) != nullptr) {
701                                         return true;
702                                 }
703                         }
704                 }
705                 bpit++;
706         }
707         return false;
708 }
709
710
711 pit_type generateDocBookParagraphWithoutSectioning(
712                 Text const & text,
713                 Buffer const & buf,
714                 XMLStream & xs,
715                 OutputParams const & runparams,
716                 ParagraphList const & paragraphs,
717                 pit_type bpit,
718                 pit_type epit)
719 {
720         auto par = paragraphs.iterator_at(bpit);
721         auto lastStartedPar = par;
722         ParagraphList::const_iterator send;
723         auto const pend =
724                         (epit == (int) paragraphs.size()) ?
725                         paragraphs.end() : paragraphs.iterator_at(epit);
726
727         std::cout << "generateDocBookParagraphWithoutSectioning" << std::endl;
728         while (bpit < epit) {
729                 std::cout << "iteration; bpit: " << bpit << std::endl;
730                 tie(par, send) = makeAny(text, buf, xs, runparams, par, send, pend);
731                 bpit += distance(lastStartedPar, par);
732                 lastStartedPar = par;
733         }
734         std::cout << "generateDocBookParagraphWithoutSectioning has looped; bpit: " << bpit << std::endl;
735
736         return bpit;
737 }
738
739
740 void outputDocBookInfo(
741                 Text const & text,
742                 Buffer const & buf,
743                 XMLStream & xs,
744                 OutputParams const & runparams,
745                 ParagraphList const & paragraphs,
746                 DocBookInfoTag const & info,
747                 pit_type bpitAbstract,
748                 pit_type const epitAbstract)
749 {
750         // Consider everything between bpitAbstract and epitAbstract (excluded) as paragraphs for the abstract.
751         // Use bpitAbstract >= epitAbstract to indicate there is no abstract.
752
753         set<pit_type> shouldBeInInfo;
754         set<pit_type> mustBeInInfo;
755         pit_type bpitInfo;
756         pit_type epitInfo;
757         tie(shouldBeInInfo, mustBeInInfo, bpitInfo, epitInfo) = info;
758
759         // The abstract must go in <info>.
760         bool hasAbstract = hasAbstractBetween(paragraphs, bpitAbstract, epitAbstract);
761         bool needInfo = !mustBeInInfo.empty() || hasAbstract;
762
763         // Start the <info> tag if required.
764         if (needInfo) {
765                 xs.startDivision(false);
766                 xs << xml::StartTag("info");
767                 xs << xml::CR();
768         }
769
770         // Output the elements that should go in <info>.
771         generateDocBookParagraphWithoutSectioning(text, buf, xs, runparams, paragraphs, bpitInfo, epitInfo);
772
773         if (hasAbstract) {
774                 string tag = paragraphs[bpitAbstract].layout().docbookforceabstracttag();
775                 if (tag == "NONE")
776                         tag = "abstract";
777
778                 xs << xml::StartTag(tag);
779                 xs << xml::CR();
780                 xs.startDivision(false);
781                 generateDocBookParagraphWithoutSectioning(text, buf, xs, runparams, paragraphs, bpitAbstract, epitAbstract);
782                 xs.endDivision();
783                 xs << xml::EndTag(tag);
784                 xs << xml::CR();
785         }
786
787         // End the <info> tag if it was started.
788         if (needInfo) {
789                 xs << xml::EndTag("info");
790                 xs << xml::CR();
791                 xs.endDivision();
792         }
793 }
794
795
796 void docbookFirstParagraphs(
797                 Text const &text,
798                 Buffer const &buf,
799                 XMLStream &xs,
800                 OutputParams const &runparams,
801                 pit_type epit)
802 {
803         // Handle the beginning of the document, supposing it has sections.
804         // Major role: output the first <info> tag.
805
806         ParagraphList const &paragraphs = text.paragraphs();
807         pit_type bpit = runparams.par_begin;
808         DocBookInfoTag info = getParagraphsWithInfo(paragraphs, bpit, epit);
809         outputDocBookInfo(text, buf, xs, runparams, paragraphs, info, get<3>(info), epit);
810 }
811
812
813 bool isParagraphEmpty(const Paragraph &par)
814 {
815         InsetList const &insets = par.insetList();
816         size_t insetsLength = distance(insets.begin(), insets.end());
817         bool hasParagraphOnlyNote = insetsLength == 1 && insets.get(0) && insets.get(0)->asInsetCollapsible() &&
818                                                                 dynamic_cast<InsetNote *>(insets.get(0));
819         return hasParagraphOnlyNote;
820 }
821
822
823 void docbookSimpleAllParagraphs(
824                 Text const & text,
825                 Buffer const & buf,
826                 XMLStream & xs,
827                 OutputParams const & runparams)
828 {
829         // Handle the document, supposing it has no sections (i.e. a "simple" document).
830
831         // First, the <info> tag.
832         ParagraphList const &paragraphs = text.paragraphs();
833         pit_type bpit = runparams.par_begin;
834         pit_type const epit = runparams.par_end;
835         DocBookInfoTag info = getParagraphsWithInfo(paragraphs, bpit, epit);
836         outputDocBookInfo(text, buf, xs, runparams, paragraphs, info, 0, 0);
837         bpit = get<3>(info); // Generate the content starting from the end of the <info> part.
838
839         // Then, the content.
840         ParagraphList::const_iterator const pend =
841                         (epit == (int) paragraphs.size()) ?
842                         paragraphs.end() : paragraphs.iterator_at(epit);
843
844         while (bpit < epit) {
845                 auto par = paragraphs.iterator_at(bpit);
846                 ParagraphList::const_iterator const lastStartedPar = par;
847                 ParagraphList::const_iterator send;
848
849                 if (isParagraphEmpty(*par)) {
850                         ++par;
851                         bpit += distance(lastStartedPar, par);
852                         continue;
853                 }
854
855                 // Generate this paragraph.
856                 tie(par, send) = makeAny(text, buf, xs, runparams, par, send, pend);
857                 bpit += distance(lastStartedPar, par);
858         }
859 }
860
861
862 void docbookParagraphs(Text const &text,
863                                            Buffer const &buf,
864                                            XMLStream &xs,
865                                            OutputParams const &runparams) {
866         ParagraphList const &paragraphs = text.paragraphs();
867         if (runparams.par_begin == runparams.par_end) {
868                 runparams.par_begin = 0;
869                 runparams.par_end = paragraphs.size();
870         }
871         pit_type bpit = runparams.par_begin;
872         pit_type const epit = runparams.par_end;
873         LASSERT(bpit < epit,
874                         {
875                                 xs << XMLStream::ESCAPE_NONE << "<!-- DocBook output error! -->\n";
876                                 return;
877                         });
878
879         ParagraphList::const_iterator const pend =
880                         (epit == (int) paragraphs.size()) ?
881                         paragraphs.end() : paragraphs.iterator_at(epit);
882         std::stack<std::pair<int, string>> headerLevels; // Used to determine when to open/close sections: store the depth
883         // of the section and the tag that was used to open it.
884
885         // Detect whether the document contains sections. If there are no sections, there can be no automatically
886         // discovered abstract.
887         bool documentHasSections;
888         pit_type eppit;
889         tie(documentHasSections, eppit) = hasDocumentSectioning(paragraphs, bpit, epit);
890
891         if (documentHasSections) {
892                 docbookFirstParagraphs(text, buf, xs, runparams, eppit);
893                 bpit = eppit;
894         } else {
895                 docbookSimpleAllParagraphs(text, buf, xs, runparams);
896                 return;
897         }
898
899         bool currentlyInAppendix = false;
900
901         while (bpit < epit) {
902                 OutputParams ourparams = runparams;
903
904                 auto par = paragraphs.iterator_at(bpit);
905                 if (par->params().startOfAppendix())
906                         currentlyInAppendix = true;
907                 Layout const &style = par->layout();
908                 ParagraphList::const_iterator const lastStartedPar = par;
909                 ParagraphList::const_iterator send;
910
911                 if (isParagraphEmpty(*par)) {
912                         ++par;
913                         bpit += distance(lastStartedPar, par);
914                         continue;
915                 }
916
917                 // Think about adding <section> and/or </section>s.
918                 const bool isLayoutSectioning = style.category() == from_utf8("Sectioning");
919                 if (isLayoutSectioning) {
920                         int level = style.toclevel;
921
922                         // Need to close a previous section if it has the same level or a higher one (close <section> if opening a <h2>
923                         // after a <h2>, <h3>, <h4>, <h5> or <h6>). More examples:
924                         //   - current: h2; back: h1; do not close any <section>
925                         //   - current: h1; back: h2; close two <section> (first the <h2>, then the <h1>, so a new <h1> can come)
926                         while (!headerLevels.empty() && level <= headerLevels.top().first) {
927                                 int stackLevel = headerLevels.top().first;
928                                 docstring stackTag = from_utf8("</" + headerLevels.top().second + ">");
929                                 headerLevels.pop();
930
931                                 // Output the tag only if it corresponds to a legit section.
932                                 if (stackLevel != Layout::NOT_IN_TOC)
933                                         xs << XMLStream::ESCAPE_NONE << stackTag << xml::CR();
934                         }
935
936                         // Open the new section: first push it onto the stack, then output it in DocBook.
937                         string sectionTag = (currentlyInAppendix && style.docbooksectiontag() == "chapter") ?
938                                                                 "appendix" : style.docbooksectiontag();
939                         headerLevels.push(std::make_pair(level, sectionTag));
940
941                         // Some sectioning-like elements should not be output (such as FrontMatter).
942                         if (level != Layout::NOT_IN_TOC) {
943                                 // Look for a label in the title, i.e. a InsetLabel as a child.
944                                 docstring id = docstring();
945                                 for (pos_type i = 0; i < par->size(); ++i) {
946                                         Inset const *inset = par->getInset(i);
947                                         if (inset) {
948                                                 if (auto label = dynamic_cast<InsetLabel const *>(inset)) {
949                                                         // Generate the attributes for the section if need be.
950                                                         id += "xml:id=\"" + xml::cleanID(label->screenLabel()) + "\"";
951
952                                                         // Don't output the ID as a DocBook <anchor>.
953                                                         ourparams.docbook_anchors_to_ignore.emplace(label->screenLabel());
954
955                                                         // Cannot have multiple IDs per tag.
956                                                         break;
957                                                 }
958                                         }
959                                 }
960
961                                 // Write the open tag for this section.
962                                 docstring tag = from_utf8("<" + sectionTag);
963                                 if (!id.empty())
964                                         tag += from_utf8(" ") + id;
965                                 tag += from_utf8(">");
966                                 xs << XMLStream::ESCAPE_NONE << tag;
967                                 xs << xml::CR();
968                         }
969                 }
970
971                 // Close all sections before the bibliography.
972                 // 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)?
973                 auto insetsLength = distance(par->insetList().begin(), par->insetList().end());
974                 if (insetsLength > 0) {
975                         Inset const *firstInset = par->getInset(0);
976                         if (firstInset && dynamic_cast<InsetBibtex const *>(firstInset)) {
977                                 while (!headerLevels.empty()) {
978                                         int level = headerLevels.top().first;
979                                         docstring tag = from_utf8("</" + headerLevels.top().second + ">");
980                                         headerLevels.pop();
981
982                                         // Output the tag only if it corresponds to a legit section.
983                                         if (level != Layout::NOT_IN_TOC) {
984                                                 xs << XMLStream::ESCAPE_NONE << tag;
985                                                 xs << xml::CR();
986                                         }
987                                 }
988                         }
989                 }
990
991                 // Generate this paragraph.
992                 tie(par, send) = makeAny(text, buf, xs, ourparams, par, send, pend);
993                 bpit += distance(lastStartedPar, par);
994         }
995
996         // If need be, close <section>s, but only at the end of the document (otherwise, dealt with at the beginning
997         // of the loop).
998         while (!headerLevels.empty() && headerLevels.top().first > Layout::NOT_IN_TOC) {
999                 docstring tag = from_utf8("</" + headerLevels.top().second + ">");
1000                 headerLevels.pop();
1001                 xs << XMLStream::ESCAPE_NONE << tag;
1002                 xs << xml::CR();
1003         }
1004 }
1005
1006 } // namespace lyx