]> git.lyx.org Git - lyx.git/blob - src/output_docbook.cpp
Move <QTimer> from TocWidget.h
[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         // If there is no title, generate one (required for the document to be valid).
924         // This code is called for the main document, for table cells, etc., so be precise in this condition.
925         if (text.isMainText() && info.mustBeInInfo.empty()) {
926                 xs << xml::StartTag("title");
927                 xs << "Untitled Document";
928                 xs << xml::EndTag("title");
929                 xs << xml::CR();
930         }
931
932         // Always output the abstract as the last item of the <info>, as it requires special treatment (especially if
933         // it contains several paragraphs that are empty).
934         if (hasAbstract) {
935                 if (info.abstractLayout) {
936                         xs << XMLStream::ESCAPE_NONE << abstract;
937                         xs << xml::CR();
938                 } else {
939                         string tag = paragraphs[*info.abstract.begin()].layout().docbookforceabstracttag();
940                         if (tag == "NONE")
941                                 tag = "abstract";
942
943                         if (!xs.isLastTagCR())
944                                 xs << xml::CR();
945
946                         xs << xml::StartTag(tag);
947                         xs << xml::CR();
948                         xs << XMLStream::ESCAPE_NONE << abstract;
949                         xs << xml::EndTag(tag);
950                         xs << xml::CR();
951                 }
952         }
953
954         // End the <info> tag if it was started.
955         if (needInfo) {
956                 if (!xs.isLastTagCR())
957                         xs << xml::CR();
958
959                 xs << xml::EndTag("info");
960                 xs << xml::CR();
961                 xs.endDivision();
962         }
963 }
964
965
966 void docbookSimpleAllParagraphs(
967                 Text const & text,
968                 Buffer const & buf,
969                 XMLStream & xs,
970                 OutputParams const & runparams)
971 {
972         // Handle the given text, supposing it has no sections (i.e. a "simple" text). The input may vary in length
973         // between a single paragraph to a whole document.
974         pit_type const bpit = runparams.par_begin;
975         pit_type const epit = runparams.par_end;
976         ParagraphList const &paragraphs = text.paragraphs();
977
978         // First, the <info> tag.
979         DocBookInfoTag info = getParagraphsWithInfo(paragraphs, bpit, epit, false);
980         outputDocBookInfo(text, buf, xs, runparams, paragraphs, info);
981
982         // Then, the content. It starts where the <info> ends.
983         auto par = paragraphs.iterator_at(info.epit);
984         auto end = paragraphs.iterator_at(epit);
985         while (par != end) {
986                 if (!hasOnlyNotes(*par))
987                         par = makeAny(text, buf, xs, runparams, par);
988                 else
989                         ++par;
990         }
991 }
992
993
994 void docbookParagraphs(Text const &text,
995                                            Buffer const &buf,
996                                            XMLStream &xs,
997                                            OutputParams const &runparams) {
998         ParagraphList const &paragraphs = text.paragraphs();
999         if (runparams.par_begin == runparams.par_end) {
1000                 runparams.par_begin = 0;
1001                 runparams.par_end = paragraphs.size();
1002         }
1003         pit_type bpit = runparams.par_begin;
1004         pit_type const epit = runparams.par_end;
1005         LASSERT(bpit < epit,
1006                         {
1007                                 xs << XMLStream::ESCAPE_NONE << "<!-- DocBook output error! -->\n";
1008                                 return;
1009                         });
1010
1011         std::stack<std::pair<int, string>> headerLevels; // Used to determine when to open/close sections: store the depth
1012         // of the section and the tag that was used to open it.
1013
1014         // Detect whether the document contains sections. If there are no sections, treatment is largely simplified.
1015         // In particular, there can't be an abstract, unless it is manually marked.
1016         bool documentHasSections;
1017         pit_type eppit;
1018         tie(documentHasSections, eppit) = hasDocumentSectioning(paragraphs, bpit, epit);
1019
1020         // Deal with "simple" documents, i.e. those without sections.
1021         if (!documentHasSections) {
1022                 docbookSimpleAllParagraphs(text, buf, xs, runparams);
1023                 return;
1024         }
1025
1026         // Output the first <info> tag (or just the title).
1027         DocBookInfoTag info = getParagraphsWithInfo(paragraphs, bpit, eppit, true);
1028         outputDocBookInfo(text, buf, xs, runparams, paragraphs, info);
1029         bpit = info.epit;
1030
1031         // Then, iterate through the paragraphs of this document.
1032         bool currentlyInAppendix = false;
1033
1034         auto par = text.paragraphs().iterator_at(bpit);
1035         auto end = text.paragraphs().iterator_at(epit);
1036         while (par != end) {
1037                 OutputParams ourparams = runparams;
1038
1039                 if (par->params().startOfAppendix())
1040                         currentlyInAppendix = true;
1041                 if (hasOnlyNotes(*par)) {
1042                         ++par;
1043                         continue;
1044                 }
1045
1046                 Layout const &style = par->layout();
1047
1048                 // Think about adding <section> and/or </section>s.
1049                 if (isLayoutSectioning(style)) {
1050                         int level = style.toclevel;
1051
1052                         // Need to close a previous section if it has the same level or a higher one (close <section> if opening a
1053                         // <h2> after a <h2>, <h3>, <h4>, <h5> or <h6>). More examples:
1054                         //   - current: h2; back: h1; do not close any <section>
1055                         //   - current: h1; back: h2; close two <section> (first the <h2>, then the <h1>, so a new <h1> can come)
1056                         while (!headerLevels.empty() && level <= headerLevels.top().first) {
1057                                 // Output the tag only if it corresponds to a legit section.
1058                                 int stackLevel = headerLevels.top().first;
1059                                 if (stackLevel != Layout::NOT_IN_TOC) {
1060                                         xs << xml::EndTag(headerLevels.top().second);
1061                                         xs << xml::CR();
1062                                 }
1063                                 headerLevels.pop();
1064                         }
1065
1066                         // Open the new section: first push it onto the stack, then output it in DocBook.
1067                         string sectionTag = (currentlyInAppendix && style.docbooksectiontag() == "chapter") ?
1068                                                                 "appendix" : style.docbooksectiontag();
1069                         headerLevels.push(std::make_pair(level, sectionTag));
1070
1071                         // Some sectioning-like elements should not be output (such as FrontMatter).
1072                         if (level != Layout::NOT_IN_TOC) {
1073                                 // Look for a label in the title, i.e. a InsetLabel as a child.
1074                                 docstring id = docstring();
1075                                 for (pos_type i = 0; i < par->size(); ++i) {
1076                                         Inset const *inset = par->getInset(i);
1077                                         if (inset) {
1078                                                 if (auto label = dynamic_cast<InsetLabel const *>(inset)) {
1079                                                         // Generate the attributes for the section if need be.
1080                                                         id += "xml:id=\"" + xml::cleanID(label->screenLabel()) + "\"";
1081
1082                                                         // Don't output the ID as a DocBook <anchor>.
1083                                                         ourparams.docbook_anchors_to_ignore.emplace(label->screenLabel());
1084
1085                                                         // Cannot have multiple IDs per tag.
1086                                                         break;
1087                                                 }
1088                                         }
1089                                 }
1090
1091                                 // Write the open tag for this section.
1092                                 docstring attrs;
1093                                 if (!id.empty())
1094                                         attrs = id;
1095                                 xs << xml::StartTag(sectionTag, attrs);
1096                                 xs << xml::CR();
1097                         }
1098                 }
1099
1100                 // Close all sections before the bibliography.
1101                 // 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)?
1102                 if (!par->insetList().empty()) {
1103                         Inset const *firstInset = par->getInset(0);
1104                         if (firstInset && (firstInset->lyxCode() == BIBITEM_CODE || firstInset->lyxCode() == BIBTEX_CODE)) {
1105                                 while (!headerLevels.empty()) {
1106                                         int level = headerLevels.top().first;
1107                                         docstring tag = from_utf8("</" + headerLevels.top().second + ">");
1108                                         headerLevels.pop();
1109
1110                                         // Output the tag only if it corresponds to a legit section.
1111                                         if (level != Layout::NOT_IN_TOC) {
1112                                                 xs << XMLStream::ESCAPE_NONE << tag;
1113                                                 xs << xml::CR();
1114                                         }
1115                                 }
1116                         }
1117                 }
1118
1119                 // Generate this paragraph.
1120                 par = makeAny(text, buf, xs, ourparams, par);
1121
1122                 // Some special sections may require abstracts (mostly parts, in books).
1123                 // 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.
1124                 if (isLayoutSectioning(style) && style.docbookforceabstracttag() != "NONE") {
1125                         // This abstract may be found between the next paragraph and the next title.
1126                         pit_type cpit = std::distance(text.paragraphs().begin(), par);
1127                         pit_type ppit = std::get<1>(hasDocumentSectioning(paragraphs, cpit, epit));
1128
1129                         // Generate this abstract (this code corresponds to parts of outputDocBookInfo).
1130                         DocBookInfoTag secInfo = getParagraphsWithInfo(paragraphs, cpit, ppit, true);
1131
1132                         if (!secInfo.abstract.empty()) {
1133                                 xs << xml::StartTag(style.docbookforceabstracttag());
1134                                 xs << xml::CR();
1135                                 for (auto const &p : secInfo.abstract)
1136                                         makeAny(text, buf, xs, runparams, paragraphs.iterator_at(p));
1137                                 xs << xml::EndTag(style.docbookforceabstracttag());
1138                                 xs << xml::CR();
1139                         }
1140
1141                         // Skip all the text that just has been generated.
1142                         par = paragraphs.iterator_at(ppit);
1143                 }
1144         }
1145
1146         // If need be, close <section>s, but only at the end of the document (otherwise, dealt with at the beginning
1147         // of the loop).
1148         while (!headerLevels.empty() && headerLevels.top().first > Layout::NOT_IN_TOC) {
1149                 docstring tag = from_utf8("</" + headerLevels.top().second + ">");
1150                 headerLevels.pop();
1151                 xs << XMLStream::ESCAPE_NONE << tag;
1152                 xs << xml::CR();
1153         }
1154 }
1155
1156 } // namespace lyx