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