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