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