]> git.lyx.org Git - lyx.git/blob - src/output_docbook.cpp
cf76bf4ee87bd86e65db095ba8663525dbaa45e9
[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 "person";
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 // Higher-level convenience functions.
259
260 void openParTag(XMLStream & xs, const Paragraph * par, const Paragraph * prevpar)
261 {
262         Layout const & lay = par->layout();
263
264         if (par == prevpar)
265                 prevpar = nullptr;
266
267         // When should the wrapper be opened here? Only if the previous paragraph has the SAME wrapper tag
268         // (usually, they won't have the same layout) and the CURRENT one allows merging.
269         // The main use case is author information in several paragraphs: if the name of the author is the
270         // first paragraph of an author, then merging with the previous tag does not make sense. Say the
271         // next paragraph is the affiliation, then it should be output in the same <author> tag (different
272         // layout, same wrapper tag).
273         bool openWrapper = lay.docbookwrappertag() != "NONE";
274         if (prevpar != nullptr) {
275                 Layout const & prevlay = prevpar->layout();
276                 if (prevlay.docbookwrappertag() != "NONE") {
277                         if (prevlay.docbookwrappertag() == lay.docbookwrappertag())
278                                 openWrapper = !lay.docbookwrappermergewithprevious();
279                         else
280                                 openWrapper = true;
281                 }
282         }
283
284         // Main logic.
285         if (openWrapper)
286                 openTag(xs, lay.docbookwrappertag(), lay.docbookwrapperattr(), lay.docbookwrappertagtype());
287
288         const string & tag = lay.docbooktag();
289         if (tag != "NONE") {
290                 auto xmltag = xml::ParTag(tag, lay.docbookattr());
291                 if (!xs.isTagOpen(xmltag, 1)) // Don't nest a paragraph directly in a paragraph.
292                         // TODO: required or not?
293                         // TODO: avoid creating a ParTag object just for this query...
294                         openTag(xs, lay.docbooktag(), lay.docbookattr(), lay.docbooktagtype());
295         }
296
297         openTag(xs, lay.docbookitemtag(), lay.docbookitemattr(), lay.docbookitemtagtype());
298         openTag(xs, lay.docbookiteminnertag(), lay.docbookiteminnerattr(), lay.docbookiteminnertagtype());
299 }
300
301
302 void closeParTag(XMLStream & xs, Paragraph const * par, Paragraph const * nextpar)
303 {
304         if (par == nextpar)
305                 nextpar = nullptr;
306
307         // See comment in openParTag.
308         Layout const & lay = par->layout();
309         bool closeWrapper = lay.docbookwrappertag() != "NONE";
310         if (nextpar != nullptr) {
311                 Layout const & nextlay = nextpar->layout();
312                 if (nextlay.docbookwrappertag() != "NONE") {
313                         if (nextlay.docbookwrappertag() == lay.docbookwrappertag())
314                                 closeWrapper = !nextlay.docbookwrappermergewithprevious();
315                         else
316                                 closeWrapper = true;
317                 }
318         }
319
320         // Main logic.
321         closeTag(xs, lay.docbookiteminnertag(), lay.docbookiteminnertagtype());
322         closeTag(xs, lay.docbookitemtag(), lay.docbookitemtagtype());
323         closeTag(xs, lay.docbooktag(), lay.docbooktagtype());
324         if (closeWrapper)
325                 closeTag(xs, lay.docbookwrappertag(), lay.docbookwrappertagtype());
326 }
327
328
329 void openLabelTag(XMLStream & xs, Layout const & lay) // Mostly for definition lists.
330 {
331         openTag(xs, lay.docbookitemlabeltag(), lay.docbookitemlabelattr(), lay.docbookitemlabeltagtype());
332 }
333
334
335 void closeLabelTag(XMLStream & xs, Layout const & lay)
336 {
337         closeTag(xs, lay.docbookitemlabeltag(), lay.docbookitemlabeltagtype());
338 }
339
340
341 void openItemTag(XMLStream & xs, Layout const & lay)
342 {
343         openTag(xs, lay.docbookitemtag(), lay.docbookitemattr(), lay.docbookitemtagtype());
344 }
345
346
347 void closeItemTag(XMLStream & xs, Layout const & lay)
348 {
349         closeTag(xs, lay.docbookitemtag(), lay.docbookitemtagtype());
350 }
351
352
353 ParagraphList::const_iterator makeAny(Text const &,
354                                               Buffer const &,
355                                               XMLStream &,
356                                               OutputParams const &,
357                                               ParagraphList::const_iterator);
358
359
360 void makeBibliography(
361                 Text const & text,
362                 Buffer const & buf,
363                 XMLStream & xs,
364                 OutputParams const & runparams,
365                 ParagraphList::const_iterator const & par)
366 {
367         // If this is the first paragraph in a bibliography, open the bibliography tag.
368         auto pbegin_before = text.paragraphs().getParagraphBefore(par);
369         if (pbegin_before->layout().latextype != LATEX_BIB_ENVIRONMENT) {
370                 xs << xml::StartTag("bibliography");
371                 xs << xml::CR();
372         }
373
374         // Start the precooked bibliography entry. This is very much like opening a paragraph tag.
375         // Don't forget the citation ID!
376         docstring attr;
377         for (auto i = 0; i < par->size(); ++i) {
378                 Inset const *ip = par->getInset(i);
379                 if (!ip)
380                         continue;
381                 if (const auto * bibitem = dynamic_cast<const InsetBibitem*>(ip)) {
382                         attr = from_utf8("xml:id='") + bibitem->getParam("key") + from_utf8("'");
383                         break;
384                 }
385         }
386         xs << xml::StartTag(from_utf8("bibliomixed"), attr);
387
388         // Generate the entry. Concatenate the different parts of the paragraph if any.
389         auto const begin = text.paragraphs().begin();
390         auto pars = par->simpleDocBookOnePar(buf, runparams, text.outerFont(std::distance(begin, par)), 0);
391         for (auto & parXML : pars)
392                 xs << XMLStream::ESCAPE_NONE << parXML;
393
394         // End the precooked bibliography entry.
395         xs << xml::EndTag("bibliomixed");
396         xs << xml::CR();
397
398         // If this is the last paragraph in a bibliography, close the bibliography tag.
399         auto const end = text.paragraphs().end();
400         auto nextpar = par;
401         ++nextpar;
402         bool endBibliography = nextpar == end || nextpar->layout().latextype != LATEX_BIB_ENVIRONMENT;
403
404         if (endBibliography) {
405                 xs << xml::EndTag("bibliography");
406                 xs << xml::CR();
407         }
408 }
409
410
411 void makeParagraph(
412                 Text const & text,
413                 Buffer const & buf,
414                 XMLStream & xs,
415                 OutputParams const & runparams,
416                 ParagraphList::const_iterator const & par)
417 {
418         auto const begin = text.paragraphs().begin();
419         auto const end = text.paragraphs().end();
420         auto prevpar = text.paragraphs().getParagraphBefore(par);
421
422         // We want to open the paragraph tag if:
423         //   (i) the current layout permits multiple paragraphs
424         //  (ii) we are either not already inside a paragraph (HTMLIsBlock) OR
425         //         we are, but this is not the first paragraph
426         //
427         // But there is also a special case, and we first see whether we are in it.
428         // We do not want to open the paragraph tag if this paragraph contains
429         // only one item, and that item is "inline", i.e., not HTMLIsBlock (such
430         // as a branch). On the other hand, if that single item has a font change
431         // applied to it, then we still do need to open the paragraph.
432         //
433         // Obviously, this is very fragile. The main reason we need to do this is
434         // because of branches, e.g., a branch that contains an entire new section.
435         // We do not really want to wrap that whole thing in a <div>...</div>.
436         bool special_case = false;
437         Inset const *specinset = par->size() == 1 ? par->getInset(0) : nullptr;
438         if (specinset && !specinset->getLayout().htmlisblock()) { // TODO: Convert htmlisblock to a DocBook parameter?
439                 Layout const &style = par->layout();
440                 FontInfo const first_font = style.labeltype == LABEL_MANUAL ?
441                                                                         style.labelfont : style.font;
442                 FontInfo const our_font =
443                                 par->getFont(buf.masterBuffer()->params(), 0,
444                                                          text.outerFont(std::distance(begin, par))).fontInfo();
445
446                 if (first_font == our_font)
447                         special_case = true;
448         }
449
450         size_t nInsets = std::distance(par->insetList().begin(), par->insetList().end());
451
452         // Plain layouts must be ignored.
453         special_case |= buf.params().documentClass().isPlainLayout(par->layout()) && !runparams.docbook_force_pars;
454         // Equations do not deserve their own paragraph (DocBook allows them outside paragraphs).
455         special_case |= nInsets == par->size() && std::all_of(par->insetList().begin(), par->insetList().end(), [](InsetList::Element inset) {
456                 return inset.inset && inset.inset->asInsetMath();
457         });
458
459         // 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.
460         if (!special_case && par->size() == 1 && par->getInset(0)) {
461                 Inset const * firstInset = par->getInset(0);
462
463                 // Floats cannot be in paragraphs.
464                 special_case = to_utf8(firstInset->layoutName()).substr(0, 6) == "Float:";
465
466                 // Bibliographies cannot be in paragraphs.
467                 if (!special_case && firstInset->asInsetCommand())
468                         special_case = firstInset->asInsetCommand()->params().getCmdName() == "bibtex";
469
470                 // ERTs are in comments, not paragraphs.
471                 if (!special_case && firstInset->lyxCode() == lyx::ERT_CODE)
472                         special_case = true;
473
474                 // Listings should not get into their own paragraph.
475                 if (!special_case && firstInset->lyxCode() == lyx::LISTINGS_CODE)
476                         special_case = true;
477         }
478
479         bool const open_par = runparams.docbook_make_pars
480                                                   && !runparams.docbook_in_par
481                                                   && !special_case;
482
483         // We want to issue the closing tag if either:
484         //   (i)  We opened it, and either docbook_in_par is false,
485         //              or we're not in the last paragraph, anyway.
486         //   (ii) We didn't open it and docbook_in_par is true,
487         //              but we are in the first par, and there is a next par.
488         auto nextpar = par;
489         ++nextpar;
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 pars = par->simpleDocBookOnePar(buf, runparams, text.outerFont(distance(begin, par)), 0);
498         for (auto & parXML : pars) {
499                 if (!std::all_of(parXML.begin(), parXML.end(), ::isspace)) {
500                         if (open_par)
501                                 openParTag(xs, &*par, prevpar);
502
503                         xs << XMLStream::ESCAPE_NONE << parXML;
504
505                         if (close_par)
506                                 closeParTag(xs, &*par, (nextpar != end) ? &*nextpar : nullptr);
507                 }
508         }
509 }
510
511
512 void makeEnvironment(Text const &text,
513                                          Buffer const &buf,
514                      XMLStream &xs,
515                      OutputParams const &runparams,
516                      ParagraphList::const_iterator const & par)
517 {
518         // TODO: simplify me!
519         auto const end = text.paragraphs().end();
520
521         // Output the opening tag for this environment, but only if it has not been previously opened (condition
522         // implemented in openParTag).
523         auto prevpar = text.paragraphs().getParagraphBefore(par);
524         openParTag(xs, &*par, prevpar); // TODO: switch in layout for par/block?
525
526         // Generate the contents of this environment. There is a special case if this is like some environment.
527         Layout const & style = par->layout();
528         if (style.latextype == LATEX_COMMAND) {
529                 // Nothing to do (otherwise, infinite loops).
530         } else if (style.latextype == LATEX_ENVIRONMENT) {
531                 // Open a wrapper tag if needed.
532                 if (style.docbookitemwrappertag() != "NONE")
533                         openTag(xs, style.docbookitemwrappertag(), style.docbookitemwrapperattr(), style.docbookitemwrappertagtype());
534
535                 // Generate the label, if need be. If it is taken from the text, sep != 0 and corresponds to the first
536                 // character after the label.
537                 pos_type sep = 0;
538                 if (style.labeltype != LABEL_NO_LABEL && style.docbookitemlabeltag() != "NONE") {
539                         // At least one condition must be met:
540                         //  - this environment is not a list
541                         //  - if this is a list, the label must not be manual (i.e. it must be taken from the layout)
542                         if (style.latextype != LATEX_LIST_ENVIRONMENT || style.labeltype != LABEL_MANUAL) {
543                                 // Usual cases: maybe there is something specified at the layout level. Highly unlikely, though.
544                                 docstring const lbl = par->params().labelString();
545
546                                 if (!lbl.empty()) {
547                                         openLabelTag(xs, style);
548                                         xs << lbl;
549                                         closeLabelTag(xs, style);
550                                 }
551                         } else {
552                                 // Only variablelist gets here (or similar items defined as an extension in the layout).
553                                 openLabelTag(xs, style);
554                                 sep = par->firstWordDocBook(xs, runparams);
555                                 closeLabelTag(xs, style);
556                         }
557                 }
558
559                 // Maybe the item is completely empty, i.e. if the first word ends at the end of the current paragraph
560                 // AND if the next paragraph doesn't have the same depth (if there is such a paragraph).
561                 // Common case: there is only the first word on the line, but there is a nested list instead
562                 // of more text.
563                 bool emptyItem = false;
564                 if (sep == par->size()) { // If the separator is already at the end of this paragraph...
565                         auto next_par = par;
566                         ++next_par;
567                         if (next_par == text.paragraphs().end()) // There is no next paragraph.
568                                 emptyItem = true;
569                         else // There is a next paragraph: check depth.
570                                 emptyItem = par->params().depth() >= next_par->params().depth();
571                 }
572
573                 if (emptyItem) {
574                         // Avoid having an empty item, this is not valid DocBook. A single character is enough to force
575                         // generation of a full <para>.
576                         // TODO: this always worked only by magic...
577                         xs << ' ';
578                 } else {
579                         // Generate the rest of the paragraph, if need be. Open as many inner tags as necessary.
580                         auto pars = par->simpleDocBookOnePar(buf, runparams, text.outerFont(std::distance(text.paragraphs().begin(), par)), sep);
581                         auto p = pars.begin();
582                         while (true) {
583                                 xs << XMLStream::ESCAPE_NONE << *p;
584                                 ++p;
585                                 if (p != pars.end()) {
586                                         closeTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnertagtype());
587                                         openTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnerattr(), par->layout().docbookiteminnertagtype());
588                                 } else
589                                         break;
590                         }
591                 }
592         } else {
593                 makeAny(text, buf, xs, runparams, par);
594         }
595
596         // Close the environment.
597         auto nextpar = par;
598         ++nextpar;
599         closeParTag(xs, &*par, (nextpar != end) ? &*nextpar : nullptr); // TODO: switch in layout for par/block?
600 }
601
602
603 ParagraphList::const_iterator findEndOfEnvironment(
604                 ParagraphList::const_iterator const & pstart,
605                 ParagraphList::const_iterator const & pend)
606 {
607         // Copy-paste from XHTML. Should be factored out at some point...
608         ParagraphList::const_iterator p = pstart;
609         Layout const & bstyle = p->layout();
610         size_t const depth = p->params().depth();
611         for (++p; p != pend; ++p) {
612                 Layout const & style = p->layout();
613                 // It shouldn't happen that e.g. a section command occurs inside
614                 // a quotation environment, at a higher depth, but as of 6/2009,
615                 // it can happen. We pretend that it's just at lowest depth.
616                 if (style.latextype == LATEX_COMMAND)
617                         return p;
618
619                 // If depth is down, we're done
620                 if (p->params().depth() < depth)
621                         return p;
622
623                 // If depth is up, we're not done
624                 if (p->params().depth() > depth)
625                         continue;
626
627                 // FIXME I am not sure about the first check.
628                 // Surely we *could* have different layouts that count as
629                 // LATEX_PARAGRAPH, right?
630                 if (style.latextype == LATEX_PARAGRAPH || style != bstyle)
631                         return p;
632         }
633         return pend;
634 }
635
636
637 ParagraphList::const_iterator makeListEnvironment(Text const &text,
638                                                                                                   Buffer const &buf,
639                                                           XMLStream &xs,
640                                                           OutputParams const &runparams,
641                                                           ParagraphList::const_iterator const & begin)
642 {
643         auto par = begin;
644         auto const end = text.paragraphs().end();
645         auto const envend = findEndOfEnvironment(par, end);
646
647         // Output the opening tag for this environment.
648         Layout const & envstyle = par->layout();
649         openTag(xs, envstyle.docbookwrappertag(), envstyle.docbookwrapperattr(), envstyle.docbookwrappertagtype());
650         openTag(xs, envstyle.docbooktag(), envstyle.docbookattr(), envstyle.docbooktagtype());
651
652         // Handle the content of the list environment, item by item.
653         while (par != envend) {
654                 Layout const & style = par->layout();
655
656                 // Open the item wrapper.
657                 openTag(xs, style.docbookitemwrappertag(), style.docbookitemwrapperattr(), style.docbookitemwrappertagtype());
658
659                 // Generate the label, if need be. If it is taken from the text, sep != 0 and corresponds to the first
660                 // character after the label.
661                 pos_type sep = 0;
662                 if (style.labeltype != LABEL_NO_LABEL && style.docbookitemlabeltag() != "NONE") {
663                         if (style.labeltype == LABEL_MANUAL) {
664                                 // Only variablelist gets here (or similar items defined as an extension in the layout).
665                                 openLabelTag(xs, style);
666                                 sep = 1 + par->firstWordDocBook(xs, runparams);
667                                 closeLabelTag(xs, style);
668                         } else {
669                                 // Usual cases: maybe there is something specified at the layout level. Highly unlikely, though.
670                                 docstring const lbl = par->params().labelString();
671
672                                 if (!lbl.empty()) {
673                                         openLabelTag(xs, style);
674                                         xs << lbl;
675                                         closeLabelTag(xs, style);
676                                 }
677                         }
678                 }
679
680                 // Open the item (after the wrapper and the label).
681                 openTag(xs, style.docbookitemtag(), style.docbookitemattr(), style.docbookitemtagtype());
682
683                 // Generate the content of the item.
684                 if (sep < par->size()) {
685                         auto pars = par->simpleDocBookOnePar(buf, runparams,
686                                                              text.outerFont(std::distance(text.paragraphs().begin(), par)), sep);
687                         for (auto &p : pars) {
688                                 openTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnerattr(),
689                                         par->layout().docbookiteminnertagtype());
690                                 xs << XMLStream::ESCAPE_NONE << p;
691                                 closeTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnertagtype());
692                         }
693                 } else {
694                         // DocBook doesn't like emptiness.
695                         openTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnerattr(),
696                                 par->layout().docbookiteminnertagtype());
697                         closeTag(xs, par->layout().docbookiteminnertag(), par->layout().docbookiteminnertagtype());
698                 }
699
700                 // If the next item is deeper, it must go entirely within this item (do it recursively).
701                 // By construction, with findEndOfEnvironment, depth can only stay constant or increase, never decrease.
702                 depth_type currentDepth = par->getDepth();
703                 ++par;
704                 while (par != envend && par->getDepth() != currentDepth)
705                         par = makeAny(text, buf, xs, runparams, par);
706                 // Usually, this loop only makes one iteration, except in complex scenarios, like an item with a paragraph,
707                 // a list, and another paragraph; or an item with two types of list (itemise then enumerate, for instance).
708
709                 // Close the item.
710                 closeTag(xs, style.docbookitemtag(), style.docbookitemtagtype());
711                 closeTag(xs, style.docbookitemwrappertag(), style.docbookitemwrappertagtype());
712         }
713
714         // Close this environment in exactly the same way as it was opened.
715         closeTag(xs, envstyle.docbooktag(), envstyle.docbooktagtype());
716         closeTag(xs, envstyle.docbookwrappertag(), envstyle.docbookwrappertagtype());
717
718         return envend;
719 }
720
721
722 void makeCommand(
723                 Text const & text,
724                 Buffer const & buf,
725                 XMLStream & xs,
726                 OutputParams const & runparams,
727                 ParagraphList::const_iterator const & par)
728 {
729         // Unlike XHTML, no need for labels, as they are handled by DocBook tags.
730         auto const begin = text.paragraphs().begin();
731         auto const end = text.paragraphs().end();
732         auto nextpar = par;
733         ++nextpar;
734
735         // Generate this command.
736         auto prevpar = text.paragraphs().getParagraphBefore(par);
737         openParTag(xs, &*par, prevpar);
738
739         auto pars = par->simpleDocBookOnePar(buf, runparams,text.outerFont(distance(begin, par)));
740         for (auto & parXML : pars)
741                 // TODO: decide what to do with openParTag/closeParTag in new lines.
742                 xs << XMLStream::ESCAPE_NONE << parXML;
743
744         closeParTag(xs, &*par, (nextpar != end) ? &*nextpar : nullptr);
745 }
746
747
748 ParagraphList::const_iterator makeAny(Text const &text,
749                                               Buffer const &buf,
750                                               XMLStream &xs,
751                                               OutputParams const &runparams,
752                                               ParagraphList::const_iterator par)
753 {
754         switch (par->layout().latextype) {
755         case LATEX_COMMAND:
756                 makeCommand(text, buf, xs, runparams, par);
757                 break;
758         case LATEX_ENVIRONMENT:
759                 makeEnvironment(text, buf, xs, runparams, par);
760                 break;
761         case LATEX_LIST_ENVIRONMENT:
762         case LATEX_ITEM_ENVIRONMENT:
763                 // Only case when makeAny() might consume more than one paragraph.
764                 return makeListEnvironment(text, buf, xs, runparams, par);
765         case LATEX_PARAGRAPH:
766                 makeParagraph(text, buf, xs, runparams, par);
767                 break;
768         case LATEX_BIB_ENVIRONMENT:
769                 makeBibliography(text, buf, xs, runparams, par);
770                 break;
771         }
772         ++par;
773         return par;
774 }
775
776
777 bool isLayoutSectioning(Layout const & lay)
778 {
779         return lay.category() == from_utf8("Sectioning");
780 }
781
782
783 using DocBookDocumentSectioning = tuple<bool, pit_type>;
784
785
786 struct DocBookInfoTag
787 {
788         const set<pit_type> shouldBeInInfo;
789         const set<pit_type> mustBeInInfo; // With the notable exception of the abstract!
790         const set<pit_type> abstract;
791         const bool abstractLayout;
792         pit_type bpit;
793         pit_type epit;
794
795         DocBookInfoTag(const set<pit_type> & shouldBeInInfo, const set<pit_type> & mustBeInInfo,
796                                    const set<pit_type> & abstract, bool abstractLayout, pit_type bpit, pit_type epit) :
797                                    shouldBeInInfo(shouldBeInInfo), mustBeInInfo(mustBeInInfo), abstract(abstract),
798                                    abstractLayout(abstractLayout), bpit(bpit), epit(epit) {}
799 };
800
801
802 DocBookDocumentSectioning hasDocumentSectioning(ParagraphList const &paragraphs, pit_type bpit, pit_type const epit) {
803         bool documentHasSections = false;
804
805         while (bpit < epit) {
806                 Layout const &style = paragraphs[bpit].layout();
807                 documentHasSections |= isLayoutSectioning(style);
808
809                 if (documentHasSections)
810                         break;
811                 bpit += 1;
812         }
813         // Paragraphs before the first section: [ runparams.par_begin ; eppit )
814
815         return make_tuple(documentHasSections, bpit);
816 }
817
818
819 bool hasOnlyNotes(Paragraph const & par)
820 {
821         // Precondition: the paragraph is not empty. Otherwise, the function will always return true...
822         for (int i = 0; i < par.size(); ++i)
823                 // If you find something that is not an inset (like actual text) or an inset that is not a note,
824                 // return false.
825                 if (!par.isInset(i) || par.getInset(i)->lyxCode() != NOTE_CODE)
826                         return false;
827         return true;
828 }
829
830
831 DocBookInfoTag getParagraphsWithInfo(ParagraphList const &paragraphs,
832                                                                          pit_type bpit, pit_type const epit,
833                                                                          // Typically, bpit is the beginning of the document and epit the end *or* the first section.
834                                                                          bool documentHasSections) {
835         set<pit_type> shouldBeInInfo;
836         set<pit_type> mustBeInInfo;
837         set<pit_type> abstractWithLayout;
838         set<pit_type> abstractNoLayout;
839
840         // Find the first non empty paragraph by mutating bpit.
841         while (bpit < epit) {
842                 Paragraph const &par = paragraphs[bpit];
843                 if (par.empty() || hasOnlyNotes(par))
844                         bpit += 1;
845                 else
846                         break;
847         }
848
849         // Traverse everything that might belong to <info>.
850         bool hasAbstractLayout = false;
851         pit_type cpit = bpit;
852         for (; cpit < epit; ++cpit) {
853                 // Skip paragraphs that don't generate anything in DocBook.
854                 Paragraph const & par = paragraphs[cpit];
855                 if (hasOnlyNotes(par))
856                         continue;
857
858                 // There should never be any section here. (Just a sanity check: if this fails, this function could end up
859                 // processing the whole document.)
860                 if (isLayoutSectioning(par.layout())) {
861                         LYXERR0("Assertion failed: section found in potential <info> paragraphs.");
862                         break;
863                 }
864
865                 // If this is marked as an abstract by the layout, put it in the right set.
866                 if (par.layout().docbookabstract()) {
867                         hasAbstractLayout = true;
868                         abstractWithLayout.emplace(cpit);
869                         continue;
870                 }
871
872                 // Based on layout information, store this paragraph in one set: should be in <info>, must be,
873                 // or abstract (either because of layout or of position).
874                 Layout const &style = par.layout();
875
876                 if (style.docbookininfo() == "always")
877                         mustBeInInfo.emplace(cpit);
878                 else if (style.docbookininfo() == "maybe")
879                         shouldBeInInfo.emplace(cpit);
880                 else if (documentHasSections && !hasAbstractLayout)
881                         abstractNoLayout.emplace(cpit);
882                 else // This should definitely not be in <info>.
883                         break;
884         }
885         // Now, cpit points to the first paragraph that no more has things that could go in <info>.
886         // bpit is the beginning of the <info> part.
887
888         return DocBookInfoTag(shouldBeInInfo, mustBeInInfo,
889                                               hasAbstractLayout ? abstractWithLayout : abstractNoLayout,
890                                               hasAbstractLayout, bpit, cpit);
891 }
892
893 } // end anonymous namespace
894
895
896 xml::FontTag docbookStartFontTag(xml::FontTypes type)
897 {
898         return xml::FontTag(from_utf8(fontToDocBookTag(type)), from_utf8(fontToAttribute(type)), type);
899 }
900
901
902 xml::EndFontTag docbookEndFontTag(xml::FontTypes type)
903 {
904         return xml::EndFontTag(from_utf8(fontToDocBookTag(type)), type);
905 }
906
907
908 void outputDocBookInfo(
909                 Text const & text,
910                 Buffer const & buf,
911                 XMLStream & xs,
912                 OutputParams const & runparams,
913                 ParagraphList const & paragraphs,
914                 DocBookInfoTag const & info)
915 {
916         // Perform an additional check on the abstract. Sometimes, there are many paragraphs that should go
917         // into the abstract, but none generates actual content. Thus, first generate to a temporary stream,
918         // then only create the <abstract> tag if these paragraphs generate some content.
919         // This check must be performed *before* a decision on whether or not to output <info> is made.
920         bool hasAbstract = !info.abstract.empty();
921         docstring abstract;
922         if (hasAbstract) {
923                 // Generate the abstract XML into a string before further checks.
924                 odocstringstream os2;
925                 XMLStream xs2(os2);
926                 for (auto const & p : info.abstract)
927                         makeAny(text, buf, xs2, runparams, paragraphs.iterator_at(p));
928
929                 // Actually output the abstract if there is something to do. Don't count line feeds or spaces in this,
930                 // even though they must be properly output if there is some abstract.
931                 abstract = os2.str();
932                 docstring cleaned = abstract;
933                 cleaned.erase(std::remove_if(cleaned.begin(), cleaned.end(), ::isspace), cleaned.end());
934
935                 // Nothing? Then there is no abstract!
936                 if (cleaned.empty())
937                         hasAbstract = false;
938         }
939
940         // The abstract must go in <info>. Otherwise, decide whether to open <info> based on the layouts.
941         bool needInfo = !info.mustBeInInfo.empty() || hasAbstract;
942
943         // Start the <info> tag if required.
944         if (needInfo) {
945                 xs.startDivision(false);
946                 xs << xml::StartTag("info");
947                 xs << xml::CR();
948         }
949
950         // Output the elements that should go in <info>, before and after the abstract.
951         for (auto pit : info.shouldBeInInfo) // Typically, the title: these elements are so important and ubiquitous
952                 // that mandating a wrapper like <info> would repel users. Thus, generate them first.
953                 makeAny(text, buf, xs, runparams, paragraphs.iterator_at(pit));
954         for (auto pit : info.mustBeInInfo)
955                 makeAny(text, buf, xs, runparams, paragraphs.iterator_at(pit));
956
957         // Always output the abstract as the last item of the <info>, as it requires special treatment (especially if
958         // it contains several paragraphs that are empty).
959         if (hasAbstract) {
960                 if (info.abstractLayout) {
961                         xs << XMLStream::ESCAPE_NONE << abstract;
962                         xs << xml::CR();
963                 } else {
964                         string tag = paragraphs[*info.abstract.begin()].layout().docbookforceabstracttag();
965                         if (tag == "NONE")
966                                 tag = "abstract";
967
968                         if (!xs.isLastTagCR())
969                                 xs << xml::CR();
970
971                         xs << xml::StartTag(tag);
972                         xs << xml::CR();
973                         xs << XMLStream::ESCAPE_NONE << abstract;
974                         xs << xml::EndTag(tag);
975                         xs << xml::CR();
976                 }
977         }
978
979         // End the <info> tag if it was started.
980         if (needInfo) {
981                 if (!xs.isLastTagCR())
982                         xs << xml::CR();
983
984                 xs << xml::EndTag("info");
985                 xs << xml::CR();
986                 xs.endDivision();
987         }
988 }
989
990
991 void docbookSimpleAllParagraphs(
992                 Text const & text,
993                 Buffer const & buf,
994                 XMLStream & xs,
995                 OutputParams const & runparams)
996 {
997         // Handle the given text, supposing it has no sections (i.e. a "simple" text). The input may vary in length
998         // between a single paragraph to a whole document.
999         pit_type const bpit = runparams.par_begin;
1000         pit_type const epit = runparams.par_end;
1001         ParagraphList const &paragraphs = text.paragraphs();
1002
1003         // First, the <info> tag.
1004         DocBookInfoTag info = getParagraphsWithInfo(paragraphs, bpit, epit, false);
1005         outputDocBookInfo(text, buf, xs, runparams, paragraphs, info);
1006
1007         // Then, the content. It starts where the <info> ends.
1008         auto par = paragraphs.iterator_at(info.epit);
1009         auto end = paragraphs.iterator_at(epit);
1010         while (par != end) {
1011                 if (!hasOnlyNotes(*par))
1012                         par = makeAny(text, buf, xs, runparams, par);
1013                 else
1014                         ++par;
1015         }
1016 }
1017
1018
1019 void docbookParagraphs(Text const &text,
1020                                            Buffer const &buf,
1021                                            XMLStream &xs,
1022                                            OutputParams const &runparams) {
1023         ParagraphList const &paragraphs = text.paragraphs();
1024         if (runparams.par_begin == runparams.par_end) {
1025                 runparams.par_begin = 0;
1026                 runparams.par_end = paragraphs.size();
1027         }
1028         pit_type bpit = runparams.par_begin;
1029         pit_type const epit = runparams.par_end;
1030         LASSERT(bpit < epit,
1031                         {
1032                                 xs << XMLStream::ESCAPE_NONE << "<!-- DocBook output error! -->\n";
1033                                 return;
1034                         });
1035
1036         std::stack<std::pair<int, string>> headerLevels; // Used to determine when to open/close sections: store the depth
1037         // of the section and the tag that was used to open it.
1038
1039         // Detect whether the document contains sections. If there are no sections, treatment is largely simplified.
1040         // In particular, there can't be an abstract, unless it is manually marked.
1041         bool documentHasSections;
1042         pit_type eppit;
1043         tie(documentHasSections, eppit) = hasDocumentSectioning(paragraphs, bpit, epit);
1044
1045         // Deal with "simple" documents, i.e. those without sections.
1046         if (!documentHasSections) {
1047                 docbookSimpleAllParagraphs(text, buf, xs, runparams);
1048                 return;
1049         }
1050
1051         // Output the first <info> tag (or just the title).
1052         DocBookInfoTag info = getParagraphsWithInfo(paragraphs, bpit, eppit, true);
1053         outputDocBookInfo(text, buf, xs, runparams, paragraphs, info);
1054         bpit = info.epit;
1055
1056         // Then, iterate through the paragraphs of this document.
1057         bool currentlyInAppendix = false;
1058
1059         auto par = text.paragraphs().iterator_at(bpit);
1060         auto end = text.paragraphs().iterator_at(epit);
1061         while (par != end) {
1062                 OutputParams ourparams = runparams;
1063
1064                 if (par->params().startOfAppendix())
1065                         currentlyInAppendix = true;
1066                 if (hasOnlyNotes(*par)) {
1067                         ++par;
1068                         continue;
1069                 }
1070
1071                 Layout const &style = par->layout();
1072
1073                 // Think about adding <section> and/or </section>s.
1074                 if (isLayoutSectioning(style)) {
1075                         int level = style.toclevel;
1076
1077                         // Need to close a previous section if it has the same level or a higher one (close <section> if opening a
1078                         // <h2> after a <h2>, <h3>, <h4>, <h5> or <h6>). More examples:
1079                         //   - current: h2; back: h1; do not close any <section>
1080                         //   - current: h1; back: h2; close two <section> (first the <h2>, then the <h1>, so a new <h1> can come)
1081                         while (!headerLevels.empty() && level <= headerLevels.top().first) {
1082                                 // Output the tag only if it corresponds to a legit section.
1083                                 int stackLevel = headerLevels.top().first;
1084                                 if (stackLevel != Layout::NOT_IN_TOC) {
1085                                         xs << xml::EndTag(headerLevels.top().second);
1086                                         xs << xml::CR();
1087                                 }
1088                                 headerLevels.pop();
1089                         }
1090
1091                         // Open the new section: first push it onto the stack, then output it in DocBook.
1092                         string sectionTag = (currentlyInAppendix && style.docbooksectiontag() == "chapter") ?
1093                                                                 "appendix" : style.docbooksectiontag();
1094                         headerLevels.push(std::make_pair(level, sectionTag));
1095
1096                         // Some sectioning-like elements should not be output (such as FrontMatter).
1097                         if (level != Layout::NOT_IN_TOC) {
1098                                 // Look for a label in the title, i.e. a InsetLabel as a child.
1099                                 docstring id = docstring();
1100                                 for (pos_type i = 0; i < par->size(); ++i) {
1101                                         Inset const *inset = par->getInset(i);
1102                                         if (inset) {
1103                                                 if (auto label = dynamic_cast<InsetLabel const *>(inset)) {
1104                                                         // Generate the attributes for the section if need be.
1105                                                         id += "xml:id=\"" + xml::cleanID(label->screenLabel()) + "\"";
1106
1107                                                         // Don't output the ID as a DocBook <anchor>.
1108                                                         ourparams.docbook_anchors_to_ignore.emplace(label->screenLabel());
1109
1110                                                         // Cannot have multiple IDs per tag.
1111                                                         break;
1112                                                 }
1113                                         }
1114                                 }
1115
1116                                 // Write the open tag for this section.
1117                                 docstring attrs;
1118                                 if (!id.empty())
1119                                         attrs = id;
1120                                 xs << xml::StartTag(sectionTag, attrs);
1121                                 xs << xml::CR();
1122                         }
1123                 }
1124
1125                 // Close all sections before the bibliography.
1126                 // 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)?
1127                 auto insetsLength = distance(par->insetList().begin(), par->insetList().end());
1128                 if (insetsLength > 0) {
1129                         Inset const *firstInset = par->getInset(0);
1130                         if (firstInset && (firstInset->lyxCode() == BIBITEM_CODE || firstInset->lyxCode() == BIBTEX_CODE)) {
1131                                 while (!headerLevels.empty()) {
1132                                         int level = headerLevels.top().first;
1133                                         docstring tag = from_utf8("</" + headerLevels.top().second + ">");
1134                                         headerLevels.pop();
1135
1136                                         // Output the tag only if it corresponds to a legit section.
1137                                         if (level != Layout::NOT_IN_TOC) {
1138                                                 xs << XMLStream::ESCAPE_NONE << tag;
1139                                                 xs << xml::CR();
1140                                         }
1141                                 }
1142                         }
1143                 }
1144
1145                 // Generate this paragraph.
1146                 par = makeAny(text, buf, xs, ourparams, par);
1147
1148                 // Some special sections may require abstracts (mostly parts, in books).
1149                 // 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.
1150                 if (isLayoutSectioning(style) && style.docbookforceabstracttag() != "NONE") {
1151                         // This abstract may be found between the next paragraph and the next title.
1152                         pit_type cpit = std::distance(text.paragraphs().begin(), par);
1153                         pit_type ppit = std::get<1>(hasDocumentSectioning(paragraphs, cpit, epit));
1154
1155                         // Generate this abstract (this code corresponds to parts of outputDocBookInfo).
1156                         DocBookInfoTag secInfo = getParagraphsWithInfo(paragraphs, cpit, ppit, true);
1157
1158                         if (!secInfo.abstract.empty()) {
1159                                 xs << xml::StartTag(style.docbookforceabstracttag());
1160                                 xs << xml::CR();
1161                                 for (auto const &p : secInfo.abstract)
1162                                         makeAny(text, buf, xs, runparams, paragraphs.iterator_at(p));
1163                                 xs << xml::EndTag(style.docbookforceabstracttag());
1164                                 xs << xml::CR();
1165                         }
1166
1167                         // Skip all the text that just has been generated.
1168                         par = paragraphs.iterator_at(ppit);
1169                 }
1170         }
1171
1172         // If need be, close <section>s, but only at the end of the document (otherwise, dealt with at the beginning
1173         // of the loop).
1174         while (!headerLevels.empty() && headerLevels.top().first > Layout::NOT_IN_TOC) {
1175                 docstring tag = from_utf8("</" + headerLevels.top().second + ">");
1176                 headerLevels.pop();
1177                 xs << XMLStream::ESCAPE_NONE << tag;
1178                 xs << xml::CR();
1179         }
1180 }
1181
1182 } // namespace lyx