]> git.lyx.org Git - lyx.git/blob - src/output_xhtml.cpp
XHTML: fix generation of many useless </section>.
[lyx.git] / src / output_xhtml.cpp
1 /**
2  * \file output_xhtml.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Richard Heck
7  *
8  * This code is based upon output_docbook.cpp
9  *
10  * Full author contact details are available in file CREDITS.
11  */
12
13 #include <config.h>
14
15 #include "output_xhtml.h"
16
17 #include "Buffer.h"
18 #include "buffer_funcs.h"
19 #include "BufferParams.h"
20 #include "Counters.h"
21 #include "Font.h"
22 #include "Layout.h"
23 #include "OutputParams.h"
24 #include "Paragraph.h"
25 #include "ParagraphList.h"
26 #include "ParagraphParameters.h"
27 #include "xml.h"
28 #include "Text.h"
29 #include "TextClass.h"
30
31 #include "support/convert.h"
32 #include "support/debug.h"
33 #include "support/lassert.h"
34 #include "support/lstrings.h"
35 #include "support/textutils.h"
36
37 #include <stack>
38
39 // Uncomment to activate debugging code.
40 // #define XHTML_DEBUG
41
42 using namespace std;
43 using namespace lyx::support;
44
45 namespace lyx {
46
47
48 docstring fontToHtmlTag(xml::FontTypes type)
49 {
50     switch(type) {
51         case xml::FontTypes::FT_EMPH:
52             return from_utf8("em");
53         case xml::FontTypes::FT_BOLD:
54             return from_utf8("b");
55         case xml::FontTypes::FT_NOUN:
56             return from_utf8("dfn");
57         case xml::FontTypes::FT_UBAR:
58         case xml::FontTypes::FT_WAVE:
59         case xml::FontTypes::FT_DBAR:
60             return from_utf8("u");
61         case xml::FontTypes::FT_SOUT:
62         case xml::FontTypes::FT_XOUT:
63             return from_utf8("del");
64         case xml::FontTypes::FT_ITALIC:
65             return from_utf8("i");
66         case xml::FontTypes::FT_UPRIGHT:
67         case xml::FontTypes::FT_SLANTED:
68         case xml::FontTypes::FT_SMALLCAPS:
69         case xml::FontTypes::FT_ROMAN:
70         case xml::FontTypes::FT_SANS:
71         case xml::FontTypes::FT_TYPE:
72         case xml::FontTypes::FT_SIZE_TINY:
73         case xml::FontTypes::FT_SIZE_SCRIPT:
74         case xml::FontTypes::FT_SIZE_FOOTNOTE:
75         case xml::FontTypes::FT_SIZE_SMALL:
76         case xml::FontTypes::FT_SIZE_NORMAL:
77         case xml::FontTypes::FT_SIZE_LARGE:
78         case xml::FontTypes::FT_SIZE_LARGER:
79         case xml::FontTypes::FT_SIZE_LARGEST:
80         case xml::FontTypes::FT_SIZE_HUGE:
81         case xml::FontTypes::FT_SIZE_HUGER:
82         case xml::FontTypes::FT_SIZE_INCREASE:
83         case xml::FontTypes::FT_SIZE_DECREASE:
84             return from_utf8("span");
85     }
86     // kill warning
87     return docstring();
88 }
89
90
91 docstring fontToHtmlAttribute(xml::FontTypes type)
92 {
93         switch(type) {
94         case xml::FontTypes::FT_EMPH:
95         case xml::FontTypes::FT_BOLD:
96                 return from_ascii("");
97         case xml::FontTypes::FT_NOUN:
98                 return from_ascii("class='lyxnoun'");
99         case xml::FontTypes::FT_UBAR:
100                 return from_ascii("");
101         case xml::FontTypes::FT_DBAR:
102                 return from_ascii("class='dline'");
103         case xml::FontTypes::FT_XOUT:
104         case xml::FontTypes::FT_SOUT:
105                 return from_ascii("class='strikeout'");
106         case xml::FontTypes::FT_WAVE:
107                 return from_ascii("class='wline'");
108         case xml::FontTypes::FT_ITALIC:
109                 return from_ascii("");
110         case xml::FontTypes::FT_UPRIGHT:
111                 return from_ascii("style='font-style:normal;'");
112         case xml::FontTypes::FT_SLANTED:
113                 return from_ascii("style='font-style:oblique;'");
114         case xml::FontTypes::FT_SMALLCAPS:
115                 return from_ascii("style='font-variant:small-caps;'");
116         case xml::FontTypes::FT_ROMAN:
117                 return from_ascii("style='font-family:serif;'");
118         case xml::FontTypes::FT_SANS:
119                 return from_ascii("style='font-family:sans-serif;'");
120         case xml::FontTypes::FT_TYPE:
121                 return from_ascii("style='font-family:monospace;'");
122         case xml::FontTypes::FT_SIZE_TINY:
123         case xml::FontTypes::FT_SIZE_SCRIPT:
124         case xml::FontTypes::FT_SIZE_FOOTNOTE:
125                 return from_ascii("style='font-size:x-small;'");
126         case xml::FontTypes::FT_SIZE_SMALL:
127                 return from_ascii("style='font-size:small;'");
128         case xml::FontTypes::FT_SIZE_NORMAL:
129                 return from_ascii("style='font-size:normal;'");
130         case xml::FontTypes::FT_SIZE_LARGE:
131                 return from_ascii("style='font-size:large;'");
132         case xml::FontTypes::FT_SIZE_LARGER:
133         case xml::FontTypes::FT_SIZE_LARGEST:
134                 return from_ascii("style='font-size:x-large;'");
135         case xml::FontTypes::FT_SIZE_HUGE:
136         case xml::FontTypes::FT_SIZE_HUGER:
137                 return from_ascii("style='font-size:xx-large;'");
138         case xml::FontTypes::FT_SIZE_INCREASE:
139                 return from_ascii("style='font-size:larger;'");
140         case xml::FontTypes::FT_SIZE_DECREASE:
141                 return from_ascii("style='font-size:smaller;'");
142         }
143         // kill warning
144         return from_ascii("");
145 }
146
147
148 xml::FontTag xhtmlStartFontTag(xml::FontTypes type)
149 {
150         return xml::FontTag(fontToHtmlTag(type), fontToHtmlAttribute(type), type);
151 }
152
153
154 xml::EndFontTag xhtmlEndFontTag(xml::FontTypes type)
155 {
156         return xml::EndFontTag(fontToHtmlTag(type), type);
157 }
158
159 namespace {
160
161 // convenience functions
162
163 inline void openParTag(XMLStream & xs, Layout const & lay,
164                        const std::string & parlabel)
165 {
166         string attrs = lay.htmlattr();
167         if (!parlabel.empty())
168                 attrs += " id='" + parlabel + "'";
169         xs << xml::ParTag(lay.htmltag(), attrs);
170 }
171
172
173 void openParTag(XMLStream & xs, Layout const & lay,
174                 ParagraphParameters const & params,
175                 const std::string & parlabel)
176 {
177         // FIXME Are there other things we should handle here?
178         string const align = alignmentToCSS(params.align());
179         if (align.empty()) {
180                 openParTag(xs, lay, parlabel);
181                 return;
182         }
183         string attrs = lay.htmlattr() + " style='text-align: " + align + ";'";
184         if (!parlabel.empty())
185                 attrs += " id='" + parlabel + "'";
186         xs << xml::ParTag(lay.htmltag(), attrs);
187 }
188
189
190 inline void closeTag(XMLStream & xs, Layout const & lay)
191 {
192         xs << xml::EndTag(lay.htmltag());
193 }
194
195
196 inline void openLabelTag(XMLStream & xs, Layout const & lay)
197 {
198         xs << xml::StartTag(lay.htmllabeltag(), lay.htmllabelattr());
199 }
200
201
202 inline void closeLabelTag(XMLStream & xs, Layout const & lay)
203 {
204         xs << xml::EndTag(lay.htmllabeltag());
205 }
206
207
208 inline void openItemTag(XMLStream & xs, Layout const & lay)
209 {
210         xs << xml::StartTag(lay.htmlitemtag(), lay.htmlitemattr(), true);
211 }
212
213
214 void openItemTag(XMLStream & xs, Layout const & lay,
215              ParagraphParameters const & params)
216 {
217         // FIXME Are there other things we should handle here?
218         string const align = alignmentToCSS(params.align());
219         if (align.empty()) {
220                 openItemTag(xs, lay);
221                 return;
222         }
223         string attrs = lay.htmlattr() + " style='text-align: " + align + ";'";
224         xs << xml::StartTag(lay.htmlitemtag(), attrs);
225 }
226
227
228 inline void closeItemTag(XMLStream & xs, Layout const & lay)
229 {
230         xs << xml::EndTag(lay.htmlitemtag());
231 }
232
233 // end of convenience functions
234
235 ParagraphList::const_iterator findLastParagraph(
236         ParagraphList::const_iterator p,
237         ParagraphList::const_iterator const & pend)
238 {
239         for (++p; p != pend && p->layout().latextype == LATEX_PARAGRAPH; ++p)
240                 ;
241
242         return p;
243 }
244
245
246 ParagraphList::const_iterator findEndOfEnvironment(
247                 ParagraphList::const_iterator const & pstart,
248                 ParagraphList::const_iterator const & pend)
249 {
250         ParagraphList::const_iterator p = pstart;
251         Layout const & bstyle = p->layout();
252         size_t const depth = p->params().depth();
253         for (++p; p != pend; ++p) {
254                 Layout const & style = p->layout();
255                 // It shouldn't happen that e.g. a section command occurs inside
256                 // a quotation environment, at a higher depth, but as of 6/2009,
257                 // it can happen. We pretend that it's just at lowest depth.
258                 if (style.latextype == LATEX_COMMAND)
259                         return p;
260
261                 // If depth is down, we're done
262                 if (p->params().depth() < depth)
263                         return p;
264
265                 // If depth is up, we're not done
266                 if (p->params().depth() > depth)
267                         continue;
268
269                 // FIXME I am not sure about the first check.
270                 // Surely we *could* have different layouts that count as
271                 // LATEX_PARAGRAPH, right?
272                 if (style.latextype == LATEX_PARAGRAPH || style != bstyle)
273                         return p;
274         }
275         return pend;
276 }
277
278
279 ParagraphList::const_iterator makeParagraphs(Buffer const & buf,
280                                             XMLStream & xs,
281                                             OutputParams const & runparams,
282                                             Text const & text,
283                                             ParagraphList::const_iterator const & pbegin,
284                                             ParagraphList::const_iterator const & pend)
285 {
286         ParagraphList::const_iterator const begin = text.paragraphs().begin();
287         ParagraphList::const_iterator par = pbegin;
288         for (; par != pend; ++par) {
289                 Layout const & lay = par->layout();
290                 if (!lay.counter.empty())
291                         buf.masterBuffer()->params().
292                             documentClass().counters().step(lay.counter, OutputUpdate);
293
294                 // FIXME We should see if there's a label to be output and
295                 // do something with it.
296                 if (par != pbegin)
297                         xs << xml::CR();
298
299                 // We want to open the paragraph tag if:
300                 //   (i) the current layout permits multiple paragraphs
301                 //  (ii) we are either not already inside a paragraph (HTMLIsBlock) OR
302                 //       we are, but this is not the first paragraph
303                 //
304                 // But there is also a special case, and we first see whether we are in it.
305                 // We do not want to open the paragraph tag if this paragraph contains
306                 // only one item, and that item is "inline", i.e., not HTMLIsBlock (such
307                 // as a branch). On the other hand, if that single item has a font change
308                 // applied to it, then we still do need to open the paragraph.
309                 //
310                 // Obviously, this is very fragile. The main reason we need to do this is
311                 // because of branches, e.g., a branch that contains an entire new section.
312                 // We do not really want to wrap that whole thing in a <div>...</div>.
313                 bool special_case = false;
314                 Inset const * specinset = par->size() == 1 ? par->getInset(0) : nullptr;
315                 if (specinset && !specinset->getLayout().htmlisblock()) {
316                         Layout const & style = par->layout();
317                         FontInfo const first_font = style.labeltype == LABEL_MANUAL ?
318                                                 style.labelfont : style.font;
319                         FontInfo const our_font =
320                                 par->getFont(buf.masterBuffer()->params(), 0,
321                                        text.outerFont(distance(begin, par))).fontInfo();
322                         if (first_font == our_font)
323                                 special_case = true;
324                 }
325
326                 bool const open_par = runparams.html_make_pars
327                         && (!runparams.html_in_par || par != pbegin)
328                         && !special_case;
329
330                 // We want to issue the closing tag if either:
331                 //   (i)  We opened it, and either html_in_par is false,
332                 //        or we're not in the last paragraph, anyway.
333                 //   (ii) We didn't open it and html_in_par is true,
334                 //        but we are in the first par, and there is a next par.
335                 ParagraphList::const_iterator nextpar = par;
336                 ++nextpar;
337                 bool const close_par =
338                         (open_par && (!runparams.html_in_par || nextpar != pend))
339                         || (!open_par && runparams.html_in_par && par == pbegin && nextpar != pend);
340
341                 if (open_par) {
342                         // We do not issue the paragraph id if we are doing
343                         // this for the TOC (or some similar purpose)
344                         openParTag(xs, lay, par->params(),
345                                    runparams.for_toc ? "" : par->magicLabel());
346                 }
347
348                 docstring const deferred = par->simpleLyXHTMLOnePar(buf, xs,
349                         runparams, text.outerFont(distance(begin, par)),
350                         open_par, close_par);
351
352                 if (close_par) {
353                         closeTag(xs, lay);
354                         xs << xml::CR();
355                 }
356
357                 if (!deferred.empty()) {
358                         xs << XMLStream::ESCAPE_NONE << deferred << xml::CR();
359                 }
360         }
361         return pend;
362 }
363
364
365 ParagraphList::const_iterator makeBibliography(Buffer const & buf,
366                                 XMLStream & xs,
367                                 OutputParams const & runparams,
368                                 Text const & text,
369                                 ParagraphList::const_iterator const & pbegin,
370                                 ParagraphList::const_iterator const & pend)
371 {
372         // FIXME XHTML
373         // Use TextClass::htmlTOCLayout() to figure out how we should look.
374         xs << xml::StartTag("h2", "class='bibliography'")
375            << pbegin->layout().labelstring(false)
376            << xml::EndTag("h2")
377            << xml::CR()
378            << xml::StartTag("div", "class='bibliography'")
379            << xml::CR();
380         makeParagraphs(buf, xs, runparams, text, pbegin, pend);
381         xs << xml::EndTag("div");
382         return pend;
383 }
384
385
386 bool isNormalEnv(Layout const & lay)
387 {
388         return lay.latextype == LATEX_ENVIRONMENT
389             || lay.latextype == LATEX_BIB_ENVIRONMENT;
390 }
391
392
393 ParagraphList::const_iterator makeEnvironment(Buffer const & buf,
394                                               XMLStream & xs,
395                                               OutputParams const & runparams,
396                                               Text const & text,
397                                               ParagraphList::const_iterator const & pbegin,
398                                               ParagraphList::const_iterator const & pend)
399 {
400         ParagraphList::const_iterator const begin = text.paragraphs().begin();
401         ParagraphList::const_iterator par = pbegin;
402         Layout const & bstyle = par->layout();
403         depth_type const origdepth = pbegin->params().depth();
404
405         // open tag for this environment
406         openParTag(xs, bstyle, pbegin->magicLabel());
407         xs << xml::CR();
408
409         // we will on occasion need to remember a layout from before.
410         Layout const * lastlay = nullptr;
411
412         while (par != pend) {
413                 Layout const & style = par->layout();
414                 // the counter only gets stepped if we're in some kind of list,
415                 // or if it's the first time through.
416                 // note that enum, etc, are handled automatically.
417                 // FIXME There may be a bug here about user defined enumeration
418                 // types. If so, then we'll need to take the counter and add "i",
419                 // "ii", etc, as with enum.
420                 Counters & cnts = buf.masterBuffer()->params().documentClass().counters();
421                 docstring const & cntr = style.counter;
422                 if (!style.counter.empty()
423                     && (par == pbegin || !isNormalEnv(style))
424                                 && cnts.hasCounter(cntr)
425                 )
426                         cnts.step(cntr, OutputUpdate);
427                 ParagraphList::const_iterator send;
428
429                 switch (style.latextype) {
430                 case LATEX_ENVIRONMENT:
431                 case LATEX_LIST_ENVIRONMENT:
432                 case LATEX_ITEM_ENVIRONMENT: {
433                         // There are two possibilities in this case.
434                         // One is that we are still in the environment in which we
435                         // started---which we will be if the depth is the same.
436                         if (par->params().depth() == origdepth) {
437                                 LATTEST(bstyle == style);
438                                 if (lastlay != nullptr) {
439                                         closeItemTag(xs, *lastlay);
440                                         lastlay = nullptr;
441                                 }
442
443                                 // this will be positive, if we want to skip the
444                                 // initial word (if it's been taken for the label).
445                                 pos_type sep = 0;
446                                 bool const labelfirst = style.htmllabelfirst();
447                                 if (!labelfirst)
448                                         openItemTag(xs, style, par->params());
449
450                                 // label output
451                                 if (style.labeltype != LABEL_NO_LABEL &&
452                                     style.htmllabeltag() != "NONE") {
453                                         if (isNormalEnv(style)) {
454                                                 // in this case, we print the label only for the first
455                                                 // paragraph (as in a theorem).
456                                                 if (par == pbegin) {
457                                                         docstring const lbl =
458                                                                         pbegin->params().labelString();
459                                                         if (!lbl.empty()) {
460                                                                 openLabelTag(xs, style);
461                                                                 xs << lbl;
462                                                                 closeLabelTag(xs, style);
463                                                         }
464                                                         xs << xml::CR();
465                                                 }
466                                         } else { // some kind of list
467                                                 if (style.labeltype == LABEL_MANUAL) {
468                                                         openLabelTag(xs, style);
469                                                         sep = par->firstWordLyXHTML(xs, runparams);
470                                                         closeLabelTag(xs, style);
471                                                         xs << xml::CR();
472                                                 }
473                                                 else {
474                                                         openLabelTag(xs, style);
475                                                         xs << par->params().labelString();
476                                                         closeLabelTag(xs, style);
477                                                         xs << xml::CR();
478                                                 }
479                                         }
480                                 } // end label output
481
482                                 if (labelfirst)
483                                         openItemTag(xs, style, par->params());
484
485                                 docstring deferred = par->simpleLyXHTMLOnePar(buf, xs, runparams,
486                                         text.outerFont(distance(begin, par)), true, true, sep);
487                                 xs << XMLStream::ESCAPE_NONE << deferred;
488                                 ++par;
489
490                                 // We may not want to close the tag yet, in particular:
491                                 // If we're not at the end...
492                                 if (par != pend
493                                         //  and are doing items...
494                                          && !isNormalEnv(style)
495                                          // and if the depth has changed...
496                                          && par->params().depth() != origdepth) {
497                                          // then we'll save this layout for later, and close it when
498                                          // we get another item.
499                                         lastlay = &style;
500                                 } else
501                                         closeItemTag(xs, style);
502                                 xs << xml::CR();
503                         }
504                         // The other possibility is that the depth has increased, in which
505                         // case we need to recurse.
506                         else {
507                                 send = findEndOfEnvironment(par, pend);
508                                 par = makeEnvironment(buf, xs, runparams, text, par, send);
509                         }
510                         break;
511                 }
512                 case LATEX_PARAGRAPH:
513                         send = findLastParagraph(par, pend);
514                         par = makeParagraphs(buf, xs, runparams, text, par, send);
515                         break;
516                 // Shouldn't happen
517                 case LATEX_BIB_ENVIRONMENT:
518                         send = par;
519                         ++send;
520                         par = makeParagraphs(buf, xs, runparams, text, par, send);
521                         break;
522                 // Shouldn't happen
523                 case LATEX_COMMAND:
524                         ++par;
525                         break;
526                 }
527         }
528
529         if (lastlay != nullptr)
530                 closeItemTag(xs, *lastlay);
531         closeTag(xs, bstyle);
532         xs << xml::CR();
533         return pend;
534 }
535
536
537 void makeCommand(Buffer const & buf,
538                  XMLStream & xs,
539                  OutputParams const & runparams,
540                  Text const & text,
541                  ParagraphList::const_iterator const & pbegin)
542 {
543         Layout const & style = pbegin->layout();
544         if (!style.counter.empty())
545                 buf.masterBuffer()->params().
546                     documentClass().counters().step(style.counter, OutputUpdate);
547
548         bool const make_parid = !runparams.for_toc && runparams.html_make_pars;
549
550         openParTag(xs, style, pbegin->params(),
551                    make_parid ? pbegin->magicLabel() : "");
552
553         // Label around sectioning number:
554         // FIXME Probably need to account for LABEL_MANUAL
555         // FIXME Probably also need now to account for labels ABOVE and CENTERED.
556         if (style.labeltype != LABEL_NO_LABEL) {
557                 openLabelTag(xs, style);
558                 xs << pbegin->params().labelString();
559                 closeLabelTag(xs, style);
560                 // Otherwise the label might run together with the text
561                 xs << from_ascii(" ");
562         }
563
564         ParagraphList::const_iterator const begin = text.paragraphs().begin();
565         pbegin->simpleLyXHTMLOnePar(buf, xs, runparams,
566                         text.outerFont(distance(begin, pbegin)));
567         closeTag(xs, style);
568         xs << xml::CR();
569 }
570
571 } // end anonymous namespace
572
573
574 void xhtmlParagraphs(Text const & text,
575                        Buffer const & buf,
576                        XMLStream & xs,
577                        OutputParams const & runparams)
578 {
579         ParagraphList const & paragraphs = text.paragraphs();
580         if (runparams.par_begin == runparams.par_end) {
581                 runparams.par_begin = 0;
582                 runparams.par_end = paragraphs.size();
583         }
584         pit_type bpit = runparams.par_begin;
585         pit_type const epit = runparams.par_end;
586         LASSERT(bpit < epit,
587                 { xs << XMLStream::ESCAPE_NONE << "<!-- XHTML output error! -->\n"; return; });
588
589         OutputParams ourparams = runparams;
590         ParagraphList::const_iterator const pend =
591                 (epit == (int) paragraphs.size()) ?
592                         paragraphs.end() : paragraphs.iterator_at(epit);
593         std::stack<int> headerLevels;
594
595         while (bpit < epit) {
596                 ParagraphList::const_iterator par = paragraphs.iterator_at(bpit);
597                 if (par->params().startOfAppendix()) {
598                         // We want to reset the counter corresponding to toplevel sectioning
599                         Layout const & lay =
600                                 buf.masterBuffer()->params().documentClass().getTOCLayout();
601                         docstring const cnt = lay.counter;
602                         if (!cnt.empty()) {
603                                 Counters & cnts =
604                                         buf.masterBuffer()->params().documentClass().counters();
605                                 cnts.reset(cnt);
606                         }
607                 }
608                 Layout const & style = par->layout();
609                 ParagraphList::const_iterator const lastpar = par;
610                 ParagraphList::const_iterator send;
611
612                 // Think about adding <section> and/or </section>s.
613                 if (style.category() == from_utf8("Sectioning")) {
614                         int level = style.toclevel;
615
616                         // Need to close a previous section if it has the same level or a higher one (close <section> if opening a
617                         // <h2> after a <h2>, <h3>, <h4>, <h5> or <h6>). More examples:
618                         //   - current: h2; back: h1; do not close any <section>
619                         //   - current: h1; back: h2; close two <section> (first the <h2>, then the <h1>, so a new <h1> can come)
620                         while (!headerLevels.empty() && level <= headerLevels.top()) {
621                                 // Output the tag only if it corresponds to a legit section.
622                                 int stackLevel = headerLevels.top();
623                                 if (stackLevel != Layout::NOT_IN_TOC && level > 1) { // <h1> is the document title.
624                                         xs << xml::EndTag("section");
625                                         xs << xml::CR();
626                                 }
627                                 headerLevels.pop();
628                         }
629
630                         // Open the new section: first push it onto the stack, then output it in XHTML.
631                         headerLevels.push(level);
632                         // Some sectioning-like elements should not be output (such as FrontMatter).
633                         if (level != Layout::NOT_IN_TOC && level > 1) { // <h1> is the document title.
634                                 xs << xml::StartTag("section");
635                                 xs << xml::CR();
636                         }
637                 }
638
639                 switch (style.latextype) {
640                 case LATEX_COMMAND: {
641                         // The files with which we are working never have more than
642                         // one paragraph in a command structure.
643                         // FIXME
644                         // if (ourparams.html_in_par)
645                         //   fix it so we don't get sections inside standard, e.g.
646                         // note that we may then need to make runparams not const, so we
647                         // can communicate that back.
648                         // FIXME Maybe this fix should be in the routines themselves, in case
649                         // they are called from elsewhere.
650                         makeCommand(buf, xs, ourparams, text, par);
651                         ++par;
652                         break;
653                 }
654                 case LATEX_ENVIRONMENT:
655                 case LATEX_LIST_ENVIRONMENT:
656                 case LATEX_ITEM_ENVIRONMENT: {
657                         // FIXME Same fix here.
658                         send = findEndOfEnvironment(par, pend);
659                         par = makeEnvironment(buf, xs, ourparams, text, par, send);
660                         break;
661                 }
662                 case LATEX_BIB_ENVIRONMENT: {
663                         // FIXME Same fix here.
664                         send = findEndOfEnvironment(par, pend);
665                         par = makeBibliography(buf, xs, ourparams, text, par, send);
666                         break;
667                 }
668                 case LATEX_PARAGRAPH:
669                         send = findLastParagraph(par, pend);
670                         par = makeParagraphs(buf, xs, ourparams, text, par, send);
671                         break;
672                 }
673                 bpit += distance(lastpar, par);
674         }
675
676         // If need be, close <section>s, but only at the end of the document (otherwise, dealt with at the beginning
677         // of the loop).
678         while (!headerLevels.empty() && headerLevels.top() > Layout::NOT_IN_TOC) {
679                 docstring tag = from_utf8("</section>");
680                 headerLevels.pop();
681                 xs << XMLStream::ESCAPE_NONE << tag;
682                 xs << xml::CR();
683         }
684 }
685
686
687 string alignmentToCSS(LyXAlignment align)
688 {
689         switch (align) {
690         case LYX_ALIGN_BLOCK:
691                 // we are NOT going to use text-align: justify!!
692         case LYX_ALIGN_LEFT:
693                 return "left";
694         case LYX_ALIGN_RIGHT:
695                 return "right";
696         case LYX_ALIGN_CENTER:
697                 return "center";
698         default:
699                 break;
700         }
701         return "";
702 }
703
704 } // namespace lyx