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