]> git.lyx.org Git - features.git/blob - src/output_docbook.cpp
1f17351d3fc25160e361c17c025d8b9a1e5e331b
[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 "Paragraph.h"
20 #include "ParagraphList.h"
21 #include "ParagraphParameters.h"
22 #include "xml.h"
23 #include "Text.h"
24 #include "TextClass.h"
25
26 #include "insets/InsetBibtex.h"
27 #include "insets/InsetBibitem.h"
28 #include "insets/InsetLabel.h"
29 #include "insets/InsetNote.h"
30
31 #include "support/lassert.h"
32
33 #include <stack>
34 #include <iostream>
35 #include <algorithm>
36 #include <sstream>
37
38 using namespace std;
39 using namespace lyx::support;
40
41 namespace lyx {
42
43 namespace {
44
45 std::string fontToDocBookTag(xml::FontTypes type)
46 {
47         switch (type) {
48         case xml::FontTypes::FT_EMPH:
49         case xml::FontTypes::FT_BOLD:
50                 return "emphasis";
51         case xml::FontTypes::FT_NOUN:
52                 return "personname";
53         case xml::FontTypes::FT_UBAR:
54         case xml::FontTypes::FT_WAVE:
55         case xml::FontTypes::FT_DBAR:
56         case xml::FontTypes::FT_SOUT:
57         case xml::FontTypes::FT_XOUT:
58         case xml::FontTypes::FT_ITALIC:
59         case xml::FontTypes::FT_UPRIGHT:
60         case xml::FontTypes::FT_SLANTED:
61         case xml::FontTypes::FT_SMALLCAPS:
62         case xml::FontTypes::FT_ROMAN:
63         case xml::FontTypes::FT_SANS:
64                 return "emphasis";
65         case xml::FontTypes::FT_TYPE:
66                 return "code";
67         case xml::FontTypes::FT_SIZE_TINY:
68         case xml::FontTypes::FT_SIZE_SCRIPT:
69         case xml::FontTypes::FT_SIZE_FOOTNOTE:
70         case xml::FontTypes::FT_SIZE_SMALL:
71         case xml::FontTypes::FT_SIZE_NORMAL:
72         case xml::FontTypes::FT_SIZE_LARGE:
73         case xml::FontTypes::FT_SIZE_LARGER:
74         case xml::FontTypes::FT_SIZE_LARGEST:
75         case xml::FontTypes::FT_SIZE_HUGE:
76         case xml::FontTypes::FT_SIZE_HUGER:
77         case xml::FontTypes::FT_SIZE_INCREASE:
78         case xml::FontTypes::FT_SIZE_DECREASE:
79                 return "emphasis";
80         default:
81                 return "";
82         }
83 }
84
85
86 string fontToRole(xml::FontTypes type)
87 {
88         // Specific fonts are achieved with roles. The only common ones are "" for basic emphasis,
89         // and "bold"/"strong" for bold. With some specific options, other roles are copied into
90         // HTML output (via the DocBook XSLT sheets); otherwise, if not recognised, they are just ignored.
91         // Hence, it is not a problem to have many roles by default here.
92         // See https://www.sourceware.org/ml/docbook/2003-05/msg00269.html
93         switch (type) {
94         case xml::FontTypes::FT_ITALIC:
95         case xml::FontTypes::FT_EMPH:
96                 return "";
97         case xml::FontTypes::FT_BOLD:
98                 return "bold";
99         case xml::FontTypes::FT_NOUN: // Outputs a <person>
100         case xml::FontTypes::FT_TYPE: // Outputs a <code>
101                 return "";
102         case xml::FontTypes::FT_UBAR:
103                 return "underline";
104
105         // All other roles are non-standard for DocBook.
106
107         case xml::FontTypes::FT_WAVE:
108                 return "wave";
109         case xml::FontTypes::FT_DBAR:
110                 return "dbar";
111         case xml::FontTypes::FT_SOUT:
112                 return "sout";
113         case xml::FontTypes::FT_XOUT:
114                 return "xout";
115         case xml::FontTypes::FT_UPRIGHT:
116                 return "upright";
117         case xml::FontTypes::FT_SLANTED:
118                 return "slanted";
119         case xml::FontTypes::FT_SMALLCAPS:
120                 return "smallcaps";
121         case xml::FontTypes::FT_ROMAN:
122                 return "roman";
123         case xml::FontTypes::FT_SANS:
124                 return "sans";
125         case xml::FontTypes::FT_SIZE_TINY:
126                 return "tiny";
127         case xml::FontTypes::FT_SIZE_SCRIPT:
128                 return "size_script";
129         case xml::FontTypes::FT_SIZE_FOOTNOTE:
130                 return "size_footnote";
131         case xml::FontTypes::FT_SIZE_SMALL:
132                 return "size_small";
133         case xml::FontTypes::FT_SIZE_NORMAL:
134                 return "size_normal";
135         case xml::FontTypes::FT_SIZE_LARGE:
136                 return "size_large";
137         case xml::FontTypes::FT_SIZE_LARGER:
138                 return "size_larger";
139         case xml::FontTypes::FT_SIZE_LARGEST:
140                 return "size_largest";
141         case xml::FontTypes::FT_SIZE_HUGE:
142                 return "size_huge";
143         case xml::FontTypes::FT_SIZE_HUGER:
144                 return "size_huger";
145         case xml::FontTypes::FT_SIZE_INCREASE:
146                 return "size_increase";
147         case xml::FontTypes::FT_SIZE_DECREASE:
148                 return "size_decrease";
149         default:
150                 return "";
151         }
152 }
153
154
155 string fontToAttribute(xml::FontTypes type) {
156         // If there is a role (i.e. nonstandard use of a tag), output the attribute. Otherwise, the sheer tag is sufficient
157         // for the font.
158         string role = fontToRole(type);
159         if (!role.empty()) {
160                 return "role='" + role + "'";
161         } else {
162                 return "";
163         }
164 }
165
166
167 // Convenience functions to open and close tags. First, very low-level ones to ensure a consistent new-line behaviour.
168 // Block style:
169 //        Content before
170 //        <blocktag>
171 //          Contents of the block.
172 //        </blocktag>
173 //        Content after
174 // Paragraph style:
175 //        Content before
176 //          <paratag>Contents of the paragraph.</paratag>
177 //        Content after
178 // Inline style:
179 //    Content before<inlinetag>Contents of the paragraph.</inlinetag>Content after
180
181 void openInlineTag(XMLStream & xs, const std::string & tag, const std::string & attr)
182 {
183         xs << xml::StartTag(tag, attr);
184 }
185
186
187 void closeInlineTag(XMLStream & xs, const std::string & tag)
188 {
189         xs << xml::EndTag(tag);
190 }
191
192
193 void openParTag(XMLStream & xs, const std::string & tag, const std::string & attr)
194 {
195         if (!xs.isLastTagCR())
196                 xs << xml::CR();
197         xs << xml::StartTag(tag, attr);
198 }
199
200
201 void closeParTag(XMLStream & xs, const std::string & tag)
202 {
203         xs << xml::EndTag(tag);
204         xs << xml::CR();
205 }
206
207
208 void openBlockTag(XMLStream & xs, const std::string & tag, const std::string & attr)
209 {
210         if (!xs.isLastTagCR())
211                 xs << xml::CR();
212         xs << xml::StartTag(tag, attr);
213         xs << xml::CR();
214 }
215
216
217 void closeBlockTag(XMLStream & xs, const std::string & tag)
218 {
219         if (!xs.isLastTagCR())
220                 xs << xml::CR();
221         xs << xml::EndTag(tag);
222         xs << xml::CR();
223 }
224
225
226 void openTag(XMLStream & xs, const std::string & tag, const std::string & attr, const std::string & tagtype)
227 {
228         if (tag.empty() || tag == "NONE") // Common check to be performed elsewhere, if it was not here.
229                 return;
230
231         if (tag == "para" || tagtype == "paragraph") // Special case for <para>: always considered as a paragraph.
232                 openParTag(xs, tag, attr);
233         else if (tagtype == "block")
234                 openBlockTag(xs, tag, attr);
235         else if (tagtype == "inline")
236                 openInlineTag(xs, tag, attr);
237         else
238                 xs.writeError("Unrecognised tag type '" + tagtype + "' for '" + tag + " " + attr + "'");
239 }
240
241
242 void closeTag(XMLStream & xs, const std::string & tag, const std::string & tagtype)
243 {
244         if (tag.empty() || tag == "NONE")
245                 return;
246
247         if (tag == "para" || tagtype == "paragraph") // Special case for <para>: always considered as a paragraph.
248                 closeParTag(xs, tag);
249         else if (tagtype == "block")
250                 closeBlockTag(xs, tag);
251         else if (tagtype == "inline")
252                 closeInlineTag(xs, tag);
253         else
254                 xs.writeError("Unrecognised tag type '" + tagtype + "' for '" + tag + "'");
255 }
256
257
258 void compTag(XMLStream & xs, const std::string & tag, const std::string & attr, const std::string & tagtype)
259 {
260         if (tag.empty() || tag == "NONE")
261                 return;
262
263         // Special case for <para>: always considered as a paragraph.
264         if (tag == "para" || tagtype == "paragraph" || tagtype == "block") {
265                 if (!xs.isLastTagCR())
266                         xs << xml::CR();
267                 xs << xml::CompTag(tag, attr);
268                 xs << xml::CR();
269         } else if (tagtype == "inline") {
270                 xs << xml::CompTag(tag, attr);
271         } else {
272                 xs.writeError("Unrecognised tag type '" + tagtype + "' for '" + tag + "'");
273         }
274 }
275
276
277 // Higher-level convenience functions.
278
279 void openParTag(XMLStream & xs, const Paragraph * par, const Paragraph * prevpar)
280 {
281         Layout const & lay = par->layout();
282
283         if (par == prevpar)
284                 prevpar = nullptr;
285
286         // When should the wrapper be opened here? Only if the previous paragraph has the SAME wrapper tag
287         // (usually, they won't have the same layout) and the CURRENT one allows merging.
288         // The main use case is author information in several paragraphs: if the name of the author is the
289         // first paragraph of an author, then merging with the previous tag does not make sense. Say the
290         // next paragraph is the affiliation, then it should be output in the same <author> tag (different
291         // layout, same wrapper tag).
292         bool openWrapper = lay.docbookwrappertag() != "NONE";
293         if (prevpar != nullptr) {
294                 Layout const & prevlay = prevpar->layout();
295                 if (prevlay.docbookwrappertag() != "NONE") {
296                         if (prevlay.docbookwrappertag() == lay.docbookwrappertag())
297                                 openWrapper = !lay.docbookwrappermergewithprevious();
298                         else
299                                 openWrapper = true;
300                 }
301         }
302
303         // Main logic.
304         if (openWrapper)
305                 openTag(xs, lay.docbookwrappertag(), lay.docbookwrapperattr(), lay.docbookwrappertagtype());
306
307         const string & tag = lay.docbooktag();
308         if (tag != "NONE") {
309                 auto xmltag = xml::ParTag(tag, lay.docbookattr());
310                 if (!xs.isTagOpen(xmltag, 1)) // Don't nest a paragraph directly in a paragraph.
311                         // TODO: required or not?
312                         // TODO: avoid creating a ParTag object just for this query...
313                         openTag(xs, lay.docbooktag(), lay.docbookattr(), lay.docbooktagtype());
314         }
315
316         openTag(xs, lay.docbookitemtag(), lay.docbookitemattr(), lay.docbookitemtagtype());
317         openTag(xs, lay.docbookiteminnertag(), lay.docbookiteminnerattr(), lay.docbookiteminnertagtype());
318 }
319
320
321 void closeParTag(XMLStream & xs, Paragraph const * par, Paragraph const * nextpar)
322 {
323         if (par == nextpar)
324                 nextpar = nullptr;
325
326         // See comment in openParTag.
327         Layout const & lay = par->layout();
328         bool closeWrapper = lay.docbookwrappertag() != "NONE";
329         if (nextpar != nullptr) {
330                 Layout const & nextlay = nextpar->layout();
331                 if (nextlay.docbookwrappertag() != "NONE") {
332                         if (nextlay.docbookwrappertag() == lay.docbookwrappertag())
333                                 closeWrapper = !nextlay.docbookwrappermergewithprevious();
334                         else
335                                 closeWrapper = true;
336                 }
337         }
338
339         // Main logic.
340         closeTag(xs, lay.docbookiteminnertag(), lay.docbookiteminnertagtype());
341         closeTag(xs, lay.docbookitemtag(), lay.docbookitemtagtype());
342         closeTag(xs, lay.docbooktag(), lay.docbooktagtype());
343         if (closeWrapper)
344                 closeTag(xs, lay.docbookwrappertag(), lay.docbookwrappertagtype());
345 }
346
347
348 void openLabelTag(XMLStream & xs, Layout const & lay) // Mostly for definition lists.
349 {
350         openTag(xs, lay.docbookitemlabeltag(), lay.docbookitemlabelattr(), lay.docbookitemlabeltagtype());
351 }
352
353
354 void closeLabelTag(XMLStream & xs, Layout const & lay)
355 {
356         closeTag(xs, lay.docbookitemlabeltag(), lay.docbookitemlabeltagtype());
357 }
358
359
360 void openItemTag(XMLStream & xs, Layout const & lay)
361 {
362         openTag(xs, lay.docbookitemtag(), lay.docbookitemattr(), lay.docbookitemtagtype());
363 }
364
365
366 void closeItemTag(XMLStream & xs, Layout const & lay)
367 {
368         closeTag(xs, lay.docbookitemtag(), lay.docbookitemtagtype());
369 }
370
371
372 ParagraphList::const_iterator makeAny(Text const &,
373                                               Buffer const &,
374                                               XMLStream &,
375                                               OutputParams const &,
376                                               ParagraphList::const_iterator);
377
378
379 void makeBibliography(
380                 Text const & text,
381                 Buffer const & buf,
382                 XMLStream & xs,
383                 OutputParams const & runparams,
384                 ParagraphList::const_iterator const & par)
385 {
386         // If this is the first paragraph in a bibliography, open the bibliography tag.
387         auto pbegin_before = text.paragraphs().getParagraphBefore(par);
388         if (pbegin_before->layout().latextype != LATEX_BIB_ENVIRONMENT) {
389                 xs << xml::StartTag("bibliography");
390                 xs << xml::CR();
391         }
392
393         // Start the precooked bibliography entry. This is very much like opening a paragraph tag.
394         // Don't forget the citation ID!
395         docstring attr;
396         for (auto i = 0; i < par->size(); ++i) {
397                 Inset const *ip = par->getInset(i);
398                 if (!ip)
399                         continue;
400                 if (const auto * bibitem = dynamic_cast<const InsetBibitem*>(ip)) {
401                         attr = from_utf8("xml:id='") + bibitem->getParam("key") + from_utf8("'");
402                         break;
403                 }
404         }
405         xs << xml::StartTag(from_utf8("bibliomixed"), attr);
406
407         // Generate the entry. Concatenate the different parts of the paragraph if any.
408         auto const begin = text.paragraphs().begin();
409         auto pars = par->simpleDocBookOnePar(buf, runparams, text.outerFont(std::distance(begin, par)), 0);
410         for (auto & parXML : pars)
411                 xs << XMLStream::ESCAPE_NONE << parXML;
412
413         // End the precooked bibliography entry.
414         xs << xml::EndTag("bibliomixed");
415         xs << xml::CR();
416
417         // If this is the last paragraph in a bibliography, close the bibliography tag.
418         auto const end = text.paragraphs().end();
419         auto nextpar = par;
420         ++nextpar;
421         bool endBibliography = nextpar == end || nextpar->layout().latextype != LATEX_BIB_ENVIRONMENT;
422
423         if (endBibliography) {
424                 xs << xml::EndTag("bibliography");
425                 xs << xml::CR();
426         }
427 }
428
429
430 void makeParagraph(
431                 Text const & text,
432                 Buffer const & buf,
433                 XMLStream & xs,
434                 OutputParams const & runparams,
435                 ParagraphList::const_iterator const & par)
436 {
437         auto const begin = text.paragraphs().begin();
438         auto const end = text.paragraphs().end();
439         auto prevpar = text.paragraphs().getParagraphBefore(par);
440
441         // We want to open the paragraph tag if:
442         //   (i) the current layout permits multiple paragraphs
443         //  (ii) we are either not already inside a paragraph (HTMLIsBlock) OR
444         //         we are, but this is not the first paragraph
445         //
446         // But there is also a special case, and we first see whether we are in it.
447         // We do not want to open the paragraph tag if this paragraph contains
448         // only one item, and that item is "inline", i.e., not HTMLIsBlock (such
449         // as a branch). On the other hand, if that single item has a font change
450         // applied to it, then we still do need to open the paragraph.
451         //
452         // Obviously, this is very fragile. The main reason we need to do this is
453         // because of branches, e.g., a branch that contains an entire new section.
454         // We do not really want to wrap that whole thing in a <div>...</div>.
455         bool special_case = false;
456         Inset const *specinset = par->size() == 1 ? par->getInset(0) : nullptr;
457         if (specinset && !specinset->getLayout().htmlisblock()) { // TODO: Convert htmlisblock to a DocBook parameter?
458                 Layout const &style = par->layout();
459                 FontInfo const first_font = style.labeltype == LABEL_MANUAL ?
460                                                                         style.labelfont : style.font;
461                 FontInfo const our_font =
462                                 par->getFont(buf.masterBuffer()->params(), 0,
463                                                          text.outerFont(std::distance(begin, par))).fontInfo();
464
465                 if (first_font == our_font)
466                         special_case = true;
467         }
468
469         size_t nInsets = std::distance(par->insetList().begin(), par->insetList().end());
470
471         // Plain layouts must be ignored.
472         special_case |= buf.params().documentClass().isPlainLayout(par->layout()) && !runparams.docbook_force_pars;
473         // Equations do not deserve their own paragraph (DocBook allows them outside paragraphs).
474         special_case |= nInsets == par->size() && std::all_of(par->insetList().begin(), par->insetList().end(), [](InsetList::Element inset) {
475                 return inset.inset && inset.inset->asInsetMath();
476         });
477
478         // TODO: Could get rid of this with a DocBook equivalent to htmlisblock? Not for all cases, unfortunately... See above for those that have been determined not to be allowable for this potential refactoring.
479         if (!special_case && par->size() == 1 && par->getInset(0)) {
480                 Inset const * firstInset = par->getInset(0);
481
482                 // Floats cannot be in paragraphs.
483                 special_case = to_utf8(firstInset->layoutName()).substr(0, 6) == "Float:";
484
485                 // Bibliographies cannot be in paragraphs.
486                 if (!special_case && firstInset->asInsetCommand())
487                         special_case = firstInset->asInsetCommand()->params().getCmdName() == "bibtex";
488
489                 // ERTs are in comments, not paragraphs.
490                 if (!special_case && firstInset->lyxCode() == lyx::ERT_CODE)
491                         special_case = true;
492
493                 // Listings should not get into their own paragraph.
494                 if (!special_case && firstInset->lyxCode() == lyx::LISTINGS_CODE)
495                         special_case = true;
496
497                 // Boxes cannot get into their own paragraph.
498                 if (!special_case && firstInset->lyxCode() == lyx::BOX_CODE)
499                         special_case = true;
500         }
501
502         bool const open_par = runparams.docbook_make_pars
503                                                   && !runparams.docbook_in_par
504                                                   && !special_case;
505
506         // We want to issue the closing tag if either:
507         //   (i)  We opened it, and either docbook_in_par is false,
508         //              or we're not in the last paragraph, anyway.
509         //   (ii) We didn't open it and docbook_in_par is true,
510         //              but we are in the first par, and there is a next par.
511         bool const close_par = open_par && (!runparams.docbook_in_par);
512
513         // Determine if this paragraph has some real content. Things like new pages are not caught
514         // by Paragraph::empty(), even though they do not generate anything useful in DocBook.
515         // Thus, remove all spaces (including new lines: \r, \n) before checking for emptiness.
516         // std::all_of allows doing this check without having to copy the string.
517         // Open and close tags around each contained paragraph.
518         auto nextpar = par;
519         ++nextpar;
520         auto pars = par->simpleDocBookOnePar(buf, runparams, text.outerFont(distance(begin, par)), 0, nextpar == end, special_case);
521         for (auto & parXML : pars) {
522                 if (!std::all_of(parXML.begin(), parXML.end(), ::isspace)) {
523                         if (open_par)
524                                 openParTag(xs, &*par, prevpar);
525
526                         xs << XMLStream::ESCAPE_NONE << parXML;
527
528                         if (close_par)
529                                 closeParTag(xs, &*par, (nextpar != end) ? &*nextpar : nullptr);
530                 }
531         }
532 }
533
534
535 void makeEnvironment(Text const &text,
536                                          Buffer const &buf,
537                      XMLStream &xs,
538                      OutputParams const &runparams,
539                      ParagraphList::const_iterator const & par)
540 {
541         auto const end = text.paragraphs().end();
542         auto nextpar = par;
543         ++nextpar;
544
545         // Special cases for listing-like environments provided in layouts. This is quite ad-hoc, but provides a useful
546         // default. This should not be used by too many environments (only LyX-Code right now).
547         // This would be much simpler if LyX-Code was implemented as InsetListings...
548         bool mimicListing = false;
549         bool ignoreFonts = false;
550         if (par->layout().docbooktag() == "programlisting") {
551                 mimicListing = true;
552                 ignoreFonts = true;
553         }
554
555         // Output the opening tag for this environment, but only if it has not been previously opened (condition
556         // implemented in openParTag).
557         auto prevpar = text.paragraphs().getParagraphBefore(par);
558         openParTag(xs, &*par, prevpar); // TODO: switch in layout for par/block?
559
560         // Generate the contents of this environment. There is a special case if this is like some environment.
561         Layout const & style = par->layout();
562         if (style.latextype == LATEX_COMMAND) {
563                 // Nothing to do (otherwise, infinite loops).
564         } else if (style.latextype == LATEX_ENVIRONMENT) {
565                 // Generate the paragraph, if need be.
566                 auto pars = par->simpleDocBookOnePar(buf, runparams, text.outerFont(std::distance(text.paragraphs().begin(), par)), 0, false, ignoreFonts);
567
568                 if (mimicListing) {
569                         auto p = pars.begin();
570                         while (p != pars.end()) {
571                                 openTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnerattr(), par->layout().docbookiteminnertagtype());
572                                 xs << XMLStream::ESCAPE_NONE << *p;
573                                 closeTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnertagtype());
574                                 ++p;
575
576                                 if (p != pars.end())
577                                         xs << xml::CR();
578                         }
579                 } else {
580                         for (auto const & p : pars) {
581                                 openTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnerattr(), par->layout().docbookiteminnertagtype());
582                                 xs << XMLStream::ESCAPE_NONE << p;
583                                 closeTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnertagtype());
584                         }
585                 }
586         } else {
587                 makeAny(text, buf, xs, runparams, par);
588         }
589
590         // Close the environment.
591         closeParTag(xs, &*par, (nextpar != end) ? &*nextpar : nullptr); // TODO: switch in layout for par/block?
592 }
593
594
595 ParagraphList::const_iterator findEndOfEnvironment(
596                 ParagraphList::const_iterator const & pstart,
597                 ParagraphList::const_iterator const & pend)
598 {
599         // Copy-paste from XHTML. Should be factored out at some point...
600         ParagraphList::const_iterator p = pstart;
601         Layout const & bstyle = p->layout();
602         size_t const depth = p->params().depth();
603         for (++p; p != pend; ++p) {
604                 Layout const & style = p->layout();
605                 // It shouldn't happen that e.g. a section command occurs inside
606                 // a quotation environment, at a higher depth, but as of 6/2009,
607                 // it can happen. We pretend that it's just at lowest depth.
608                 if (style.latextype == LATEX_COMMAND)
609                         return p;
610
611                 // If depth is down, we're done
612                 if (p->params().depth() < depth)
613                         return p;
614
615                 // If depth is up, we're not done
616                 if (p->params().depth() > depth)
617                         continue;
618
619                 // FIXME I am not sure about the first check.
620                 // Surely we *could* have different layouts that count as
621                 // LATEX_PARAGRAPH, right?
622                 if (style.latextype == LATEX_PARAGRAPH || style != bstyle)
623                         return p;
624         }
625         return pend;
626 }
627
628
629 ParagraphList::const_iterator makeListEnvironment(Text const &text,
630                                                                                                   Buffer const &buf,
631                                                           XMLStream &xs,
632                                                           OutputParams const &runparams,
633                                                           ParagraphList::const_iterator const & begin)
634 {
635         auto par = begin;
636         auto const end = text.paragraphs().end();
637         auto const envend = findEndOfEnvironment(par, end);
638
639         // Output the opening tag for this environment.
640         Layout const & envstyle = par->layout();
641         openTag(xs, envstyle.docbookwrappertag(), envstyle.docbookwrapperattr(), envstyle.docbookwrappertagtype());
642         openTag(xs, envstyle.docbooktag(), envstyle.docbookattr(), envstyle.docbooktagtype());
643
644         // Handle the content of the list environment, item by item.
645         while (par != envend) {
646                 Layout const & style = par->layout();
647
648                 // Open the item wrapper.
649                 openTag(xs, style.docbookitemwrappertag(), style.docbookitemwrapperattr(), style.docbookitemwrappertagtype());
650
651                 // Generate the label, if need be. If it is taken from the text, sep != 0 and corresponds to the first
652                 // character after the label.
653                 pos_type sep = 0;
654                 if (style.labeltype != LABEL_NO_LABEL && style.docbookitemlabeltag() != "NONE") {
655                         if (style.labeltype == LABEL_MANUAL) {
656                                 // Only variablelist gets here (or similar items defined as an extension in the layout).
657                                 openLabelTag(xs, style);
658                                 sep = 1 + par->firstWordDocBook(xs, runparams);
659                                 closeLabelTag(xs, style);
660                         } else {
661                                 // Usual cases: maybe there is something specified at the layout level. Highly unlikely, though.
662                                 docstring const lbl = par->params().labelString();
663
664                                 if (!lbl.empty()) {
665                                         openLabelTag(xs, style);
666                                         xs << lbl;
667                                         closeLabelTag(xs, style);
668                                 }
669                         }
670                 }
671
672                 // Open the item (after the wrapper and the label).
673                 openTag(xs, style.docbookitemtag(), style.docbookitemattr(), style.docbookitemtagtype());
674
675                 // Generate the content of the item.
676                 if (sep < par->size()) {
677                         auto pars = par->simpleDocBookOnePar(buf, runparams,
678                                                              text.outerFont(std::distance(text.paragraphs().begin(), par)), sep);
679                         for (auto &p : pars) {
680                                 openTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnerattr(),
681                                         par->layout().docbookiteminnertagtype());
682                                 xs << XMLStream::ESCAPE_NONE << p;
683                                 closeTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnertagtype());
684                         }
685                 } else {
686                         // DocBook doesn't like emptiness.
687                         compTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnerattr(),
688                                 par->layout().docbookiteminnertagtype());
689                 }
690
691                 // If the next item is deeper, it must go entirely within this item (do it recursively).
692                 // By construction, with findEndOfEnvironment, depth can only stay constant or increase, never decrease.
693                 depth_type currentDepth = par->getDepth();
694                 ++par;
695                 while (par != envend && par->getDepth() != currentDepth)
696                         par = makeAny(text, buf, xs, runparams, par);
697                 // Usually, this loop only makes one iteration, except in complex scenarios, like an item with a paragraph,
698                 // a list, and another paragraph; or an item with two types of list (itemise then enumerate, for instance).
699
700                 // Close the item.
701                 closeTag(xs, style.docbookitemtag(), style.docbookitemtagtype());
702                 closeTag(xs, style.docbookitemwrappertag(), style.docbookitemwrappertagtype());
703         }
704
705         // Close this environment in exactly the same way as it was opened.
706         closeTag(xs, envstyle.docbooktag(), envstyle.docbooktagtype());
707         closeTag(xs, envstyle.docbookwrappertag(), envstyle.docbookwrappertagtype());
708
709         return envend;
710 }
711
712
713 void makeCommand(
714                 Text const & text,
715                 Buffer const & buf,
716                 XMLStream & xs,
717                 OutputParams const & runparams,
718                 ParagraphList::const_iterator const & par)
719 {
720         // Unlike XHTML, no need for labels, as they are handled by DocBook tags.
721         auto const begin = text.paragraphs().begin();
722         auto const end = text.paragraphs().end();
723         auto nextpar = par;
724         ++nextpar;
725
726         // Generate this command.
727         auto prevpar = text.paragraphs().getParagraphBefore(par);
728         openParTag(xs, &*par, prevpar);
729
730         auto pars = par->simpleDocBookOnePar(buf, runparams,text.outerFont(distance(begin, par)));
731         for (auto & parXML : pars)
732                 // TODO: decide what to do with openParTag/closeParTag in new lines.
733                 xs << XMLStream::ESCAPE_NONE << parXML;
734
735         closeParTag(xs, &*par, (nextpar != end) ? &*nextpar : nullptr);
736 }
737
738
739 ParagraphList::const_iterator makeAny(Text const &text,
740                                               Buffer const &buf,
741                                               XMLStream &xs,
742                                               OutputParams const &runparams,
743                                               ParagraphList::const_iterator par)
744 {
745         switch (par->layout().latextype) {
746         case LATEX_COMMAND:
747                 makeCommand(text, buf, xs, runparams, par);
748                 break;
749         case LATEX_ENVIRONMENT:
750                 makeEnvironment(text, buf, xs, runparams, par);
751                 break;
752         case LATEX_LIST_ENVIRONMENT:
753         case LATEX_ITEM_ENVIRONMENT:
754                 // Only case when makeAny() might consume more than one paragraph.
755                 return makeListEnvironment(text, buf, xs, runparams, par);
756         case LATEX_PARAGRAPH:
757                 makeParagraph(text, buf, xs, runparams, par);
758                 break;
759         case LATEX_BIB_ENVIRONMENT:
760                 makeBibliography(text, buf, xs, runparams, par);
761                 break;
762         }
763         ++par;
764         return par;
765 }
766
767
768 bool isLayoutSectioning(Layout const & lay)
769 {
770         return lay.category() == from_utf8("Sectioning");
771 }
772
773
774 using DocBookDocumentSectioning = tuple<bool, pit_type>;
775
776
777 struct DocBookInfoTag
778 {
779         const set<pit_type> shouldBeInInfo;
780         const set<pit_type> mustBeInInfo; // With the notable exception of the abstract!
781         const set<pit_type> abstract;
782         const bool abstractLayout;
783         pit_type bpit;
784         pit_type epit;
785
786         DocBookInfoTag(const set<pit_type> & shouldBeInInfo, const set<pit_type> & mustBeInInfo,
787                                    const set<pit_type> & abstract, bool abstractLayout, pit_type bpit, pit_type epit) :
788                                    shouldBeInInfo(shouldBeInInfo), mustBeInInfo(mustBeInInfo), abstract(abstract),
789                                    abstractLayout(abstractLayout), bpit(bpit), epit(epit) {}
790 };
791
792
793 DocBookDocumentSectioning hasDocumentSectioning(ParagraphList const &paragraphs, pit_type bpit, pit_type const epit) {
794         bool documentHasSections = false;
795
796         while (bpit < epit) {
797                 Layout const &style = paragraphs[bpit].layout();
798                 documentHasSections |= isLayoutSectioning(style);
799
800                 if (documentHasSections)
801                         break;
802                 bpit += 1;
803         }
804         // Paragraphs before the first section: [ runparams.par_begin ; eppit )
805
806         return make_tuple(documentHasSections, bpit);
807 }
808
809
810 bool hasOnlyNotes(Paragraph const & par)
811 {
812         // Precondition: the paragraph is not empty. Otherwise, the function will always return true...
813         for (int i = 0; i < par.size(); ++i)
814                 // If you find something that is not an inset (like actual text) or an inset that is not a note,
815                 // return false.
816                 if (!par.isInset(i) || par.getInset(i)->lyxCode() != NOTE_CODE)
817                         return false;
818         return true;
819 }
820
821
822 DocBookInfoTag getParagraphsWithInfo(ParagraphList const &paragraphs,
823                                                                          pit_type bpit, pit_type const epit,
824                                                                          // Typically, bpit is the beginning of the document and epit the end *or* the first section.
825                                                                          bool documentHasSections) {
826         set<pit_type> shouldBeInInfo;
827         set<pit_type> mustBeInInfo;
828         set<pit_type> abstractWithLayout;
829         set<pit_type> abstractNoLayout;
830
831         // Find the first non empty paragraph by mutating bpit.
832         while (bpit < epit) {
833                 Paragraph const &par = paragraphs[bpit];
834                 if (par.empty() || hasOnlyNotes(par))
835                         bpit += 1;
836                 else
837                         break;
838         }
839
840         // Traverse everything that might belong to <info>.
841         bool hasAbstractLayout = false;
842         pit_type cpit = bpit;
843         for (; cpit < epit; ++cpit) {
844                 // Skip paragraphs that don't generate anything in DocBook.
845                 Paragraph const & par = paragraphs[cpit];
846                 if (hasOnlyNotes(par))
847                         continue;
848
849                 // There should never be any section here. (Just a sanity check: if this fails, this function could end up
850                 // processing the whole document.)
851                 if (isLayoutSectioning(par.layout())) {
852                         LYXERR0("Assertion failed: section found in potential <info> paragraphs.");
853                         break;
854                 }
855
856                 // If this is marked as an abstract by the layout, put it in the right set.
857                 if (par.layout().docbookabstract()) {
858                         hasAbstractLayout = true;
859                         abstractWithLayout.emplace(cpit);
860                         continue;
861                 }
862
863                 // Based on layout information, store this paragraph in one set: should be in <info>, must be,
864                 // or abstract (either because of layout or of position).
865                 Layout const &style = par.layout();
866
867                 if (style.docbookininfo() == "always")
868                         mustBeInInfo.emplace(cpit);
869                 else if (style.docbookininfo() == "maybe")
870                         shouldBeInInfo.emplace(cpit);
871                 else if (documentHasSections && !hasAbstractLayout)
872                         abstractNoLayout.emplace(cpit);
873                 else // This should definitely not be in <info>.
874                         break;
875         }
876         // Now, cpit points to the first paragraph that no more has things that could go in <info>.
877         // bpit is the beginning of the <info> part.
878
879         return DocBookInfoTag(shouldBeInInfo, mustBeInInfo,
880                                               hasAbstractLayout ? abstractWithLayout : abstractNoLayout,
881                                               hasAbstractLayout, bpit, cpit);
882 }
883
884 } // end anonymous namespace
885
886
887 xml::FontTag docbookStartFontTag(xml::FontTypes type)
888 {
889         return xml::FontTag(from_utf8(fontToDocBookTag(type)), from_utf8(fontToAttribute(type)), type);
890 }
891
892
893 xml::EndFontTag docbookEndFontTag(xml::FontTypes type)
894 {
895         return xml::EndFontTag(from_utf8(fontToDocBookTag(type)), type);
896 }
897
898
899 void outputDocBookInfo(
900                 Text const & text,
901                 Buffer const & buf,
902                 XMLStream & xs,
903                 OutputParams const & runparams,
904                 ParagraphList const & paragraphs,
905                 DocBookInfoTag const & info)
906 {
907         // Perform an additional check on the abstract. Sometimes, there are many paragraphs that should go
908         // into the abstract, but none generates actual content. Thus, first generate to a temporary stream,
909         // then only create the <abstract> tag if these paragraphs generate some content.
910         // This check must be performed *before* a decision on whether or not to output <info> is made.
911         bool hasAbstract = !info.abstract.empty();
912         docstring abstract;
913         if (hasAbstract) {
914                 // Generate the abstract XML into a string before further checks.
915                 odocstringstream os2;
916                 XMLStream xs2(os2);
917                 for (auto const & p : info.abstract)
918                         makeAny(text, buf, xs2, runparams, paragraphs.iterator_at(p));
919
920                 // Actually output the abstract if there is something to do. Don't count line feeds or spaces in this,
921                 // even though they must be properly output if there is some abstract.
922                 abstract = os2.str();
923                 docstring cleaned = abstract;
924                 cleaned.erase(std::remove_if(cleaned.begin(), cleaned.end(), ::isspace), cleaned.end());
925
926                 // Nothing? Then there is no abstract!
927                 if (cleaned.empty())
928                         hasAbstract = false;
929         }
930
931         // The abstract must go in <info>. Otherwise, decide whether to open <info> based on the layouts.
932         bool needInfo = !info.mustBeInInfo.empty() || hasAbstract;
933
934         // Start the <info> tag if required.
935         if (needInfo) {
936                 xs.startDivision(false);
937                 xs << xml::StartTag("info");
938                 xs << xml::CR();
939         }
940
941         // Output the elements that should go in <info>, before and after the abstract.
942         for (auto pit : info.shouldBeInInfo) // Typically, the title: these elements are so important and ubiquitous
943                 // that mandating a wrapper like <info> would repel users. Thus, generate them first.
944                 makeAny(text, buf, xs, runparams, paragraphs.iterator_at(pit));
945         for (auto pit : info.mustBeInInfo)
946                 makeAny(text, buf, xs, runparams, paragraphs.iterator_at(pit));
947
948         // Always output the abstract as the last item of the <info>, as it requires special treatment (especially if
949         // it contains several paragraphs that are empty).
950         if (hasAbstract) {
951                 if (info.abstractLayout) {
952                         xs << XMLStream::ESCAPE_NONE << abstract;
953                         xs << xml::CR();
954                 } else {
955                         string tag = paragraphs[*info.abstract.begin()].layout().docbookforceabstracttag();
956                         if (tag == "NONE")
957                                 tag = "abstract";
958
959                         if (!xs.isLastTagCR())
960                                 xs << xml::CR();
961
962                         xs << xml::StartTag(tag);
963                         xs << xml::CR();
964                         xs << XMLStream::ESCAPE_NONE << abstract;
965                         xs << xml::EndTag(tag);
966                         xs << xml::CR();
967                 }
968         }
969
970         // End the <info> tag if it was started.
971         if (needInfo) {
972                 if (!xs.isLastTagCR())
973                         xs << xml::CR();
974
975                 xs << xml::EndTag("info");
976                 xs << xml::CR();
977                 xs.endDivision();
978         }
979 }
980
981
982 void docbookSimpleAllParagraphs(
983                 Text const & text,
984                 Buffer const & buf,
985                 XMLStream & xs,
986                 OutputParams const & runparams)
987 {
988         // Handle the given text, supposing it has no sections (i.e. a "simple" text). The input may vary in length
989         // between a single paragraph to a whole document.
990         pit_type const bpit = runparams.par_begin;
991         pit_type const epit = runparams.par_end;
992         ParagraphList const &paragraphs = text.paragraphs();
993
994         // First, the <info> tag.
995         DocBookInfoTag info = getParagraphsWithInfo(paragraphs, bpit, epit, false);
996         outputDocBookInfo(text, buf, xs, runparams, paragraphs, info);
997
998         // Then, the content. It starts where the <info> ends.
999         auto par = paragraphs.iterator_at(info.epit);
1000         auto end = paragraphs.iterator_at(epit);
1001         while (par != end) {
1002                 if (!hasOnlyNotes(*par))
1003                         par = makeAny(text, buf, xs, runparams, par);
1004                 else
1005                         ++par;
1006         }
1007 }
1008
1009
1010 void docbookParagraphs(Text const &text,
1011                                            Buffer const &buf,
1012                                            XMLStream &xs,
1013                                            OutputParams const &runparams) {
1014         ParagraphList const &paragraphs = text.paragraphs();
1015         if (runparams.par_begin == runparams.par_end) {
1016                 runparams.par_begin = 0;
1017                 runparams.par_end = paragraphs.size();
1018         }
1019         pit_type bpit = runparams.par_begin;
1020         pit_type const epit = runparams.par_end;
1021         LASSERT(bpit < epit,
1022                         {
1023                                 xs << XMLStream::ESCAPE_NONE << "<!-- DocBook output error! -->\n";
1024                                 return;
1025                         });
1026
1027         std::stack<std::pair<int, string>> headerLevels; // Used to determine when to open/close sections: store the depth
1028         // of the section and the tag that was used to open it.
1029
1030         // Detect whether the document contains sections. If there are no sections, treatment is largely simplified.
1031         // In particular, there can't be an abstract, unless it is manually marked.
1032         bool documentHasSections;
1033         pit_type eppit;
1034         tie(documentHasSections, eppit) = hasDocumentSectioning(paragraphs, bpit, epit);
1035
1036         // Deal with "simple" documents, i.e. those without sections.
1037         if (!documentHasSections) {
1038                 docbookSimpleAllParagraphs(text, buf, xs, runparams);
1039                 return;
1040         }
1041
1042         // Output the first <info> tag (or just the title).
1043         DocBookInfoTag info = getParagraphsWithInfo(paragraphs, bpit, eppit, true);
1044         outputDocBookInfo(text, buf, xs, runparams, paragraphs, info);
1045         bpit = info.epit;
1046
1047         // Then, iterate through the paragraphs of this document.
1048         bool currentlyInAppendix = false;
1049
1050         auto par = text.paragraphs().iterator_at(bpit);
1051         auto end = text.paragraphs().iterator_at(epit);
1052         while (par != end) {
1053                 OutputParams ourparams = runparams;
1054
1055                 if (par->params().startOfAppendix())
1056                         currentlyInAppendix = true;
1057                 if (hasOnlyNotes(*par)) {
1058                         ++par;
1059                         continue;
1060                 }
1061
1062                 Layout const &style = par->layout();
1063
1064                 // Think about adding <section> and/or </section>s.
1065                 if (isLayoutSectioning(style)) {
1066                         int level = style.toclevel;
1067
1068                         // Need to close a previous section if it has the same level or a higher one (close <section> if opening a
1069                         // <h2> after a <h2>, <h3>, <h4>, <h5> or <h6>). More examples:
1070                         //   - current: h2; back: h1; do not close any <section>
1071                         //   - current: h1; back: h2; close two <section> (first the <h2>, then the <h1>, so a new <h1> can come)
1072                         while (!headerLevels.empty() && level <= headerLevels.top().first) {
1073                                 // Output the tag only if it corresponds to a legit section.
1074                                 int stackLevel = headerLevels.top().first;
1075                                 if (stackLevel != Layout::NOT_IN_TOC) {
1076                                         xs << xml::EndTag(headerLevels.top().second);
1077                                         xs << xml::CR();
1078                                 }
1079                                 headerLevels.pop();
1080                         }
1081
1082                         // Open the new section: first push it onto the stack, then output it in DocBook.
1083                         string sectionTag = (currentlyInAppendix && style.docbooksectiontag() == "chapter") ?
1084                                                                 "appendix" : style.docbooksectiontag();
1085                         headerLevels.push(std::make_pair(level, sectionTag));
1086
1087                         // Some sectioning-like elements should not be output (such as FrontMatter).
1088                         if (level != Layout::NOT_IN_TOC) {
1089                                 // Look for a label in the title, i.e. a InsetLabel as a child.
1090                                 docstring id = docstring();
1091                                 for (pos_type i = 0; i < par->size(); ++i) {
1092                                         Inset const *inset = par->getInset(i);
1093                                         if (inset) {
1094                                                 if (auto label = dynamic_cast<InsetLabel const *>(inset)) {
1095                                                         // Generate the attributes for the section if need be.
1096                                                         id += "xml:id=\"" + xml::cleanID(label->screenLabel()) + "\"";
1097
1098                                                         // Don't output the ID as a DocBook <anchor>.
1099                                                         ourparams.docbook_anchors_to_ignore.emplace(label->screenLabel());
1100
1101                                                         // Cannot have multiple IDs per tag.
1102                                                         break;
1103                                                 }
1104                                         }
1105                                 }
1106
1107                                 // Write the open tag for this section.
1108                                 docstring attrs;
1109                                 if (!id.empty())
1110                                         attrs = id;
1111                                 xs << xml::StartTag(sectionTag, attrs);
1112                                 xs << xml::CR();
1113                         }
1114                 }
1115
1116                 // Close all sections before the bibliography.
1117                 // 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)?
1118                 auto insetsLength = distance(par->insetList().begin(), par->insetList().end());
1119                 if (insetsLength > 0) {
1120                         Inset const *firstInset = par->getInset(0);
1121                         if (firstInset && (firstInset->lyxCode() == BIBITEM_CODE || firstInset->lyxCode() == BIBTEX_CODE)) {
1122                                 while (!headerLevels.empty()) {
1123                                         int level = headerLevels.top().first;
1124                                         docstring tag = from_utf8("</" + headerLevels.top().second + ">");
1125                                         headerLevels.pop();
1126
1127                                         // Output the tag only if it corresponds to a legit section.
1128                                         if (level != Layout::NOT_IN_TOC) {
1129                                                 xs << XMLStream::ESCAPE_NONE << tag;
1130                                                 xs << xml::CR();
1131                                         }
1132                                 }
1133                         }
1134                 }
1135
1136                 // Generate this paragraph.
1137                 par = makeAny(text, buf, xs, ourparams, par);
1138
1139                 // Some special sections may require abstracts (mostly parts, in books).
1140                 // TODO: docbookforceabstracttag is a bit contrived here, but it does the job. Having another field just for this would be cleaner, but that's just for <part> and <partintro>, so it's probably not worth the effort.
1141                 if (isLayoutSectioning(style) && style.docbookforceabstracttag() != "NONE") {
1142                         // This abstract may be found between the next paragraph and the next title.
1143                         pit_type cpit = std::distance(text.paragraphs().begin(), par);
1144                         pit_type ppit = std::get<1>(hasDocumentSectioning(paragraphs, cpit, epit));
1145
1146                         // Generate this abstract (this code corresponds to parts of outputDocBookInfo).
1147                         DocBookInfoTag secInfo = getParagraphsWithInfo(paragraphs, cpit, ppit, true);
1148
1149                         if (!secInfo.abstract.empty()) {
1150                                 xs << xml::StartTag(style.docbookforceabstracttag());
1151                                 xs << xml::CR();
1152                                 for (auto const &p : secInfo.abstract)
1153                                         makeAny(text, buf, xs, runparams, paragraphs.iterator_at(p));
1154                                 xs << xml::EndTag(style.docbookforceabstracttag());
1155                                 xs << xml::CR();
1156                         }
1157
1158                         // Skip all the text that just has been generated.
1159                         par = paragraphs.iterator_at(ppit);
1160                 }
1161         }
1162
1163         // If need be, close <section>s, but only at the end of the document (otherwise, dealt with at the beginning
1164         // of the loop).
1165         while (!headerLevels.empty() && headerLevels.top().first > Layout::NOT_IN_TOC) {
1166                 docstring tag = from_utf8("</" + headerLevels.top().second + ">");
1167                 headerLevels.pop();
1168                 xs << XMLStream::ESCAPE_NONE << tag;
1169                 xs << xml::CR();
1170         }
1171 }
1172
1173 } // namespace lyx