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.
8 * This code is based upon output_docbook.cpp
10 * Full author contact details are available in file CREDITS.
15 #include "output_xhtml.h"
18 #include "buffer_funcs.h"
19 #include "BufferParams.h"
23 #include "OutputParams.h"
24 #include "Paragraph.h"
25 #include "ParagraphList.h"
26 #include "ParagraphParameters.h"
29 #include "TextClass.h"
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"
39 // Uncomment to activate debugging code.
40 // #define XHTML_DEBUG
43 using namespace lyx::support;
48 docstring const fontToHtmlTag(xml::FontTypes 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");
93 string fontToAttribute(xml::FontTypes type)
96 case xml::FontTypes::FT_EMPH:
97 case xml::FontTypes::FT_BOLD:
99 case xml::FontTypes::FT_NOUN:
100 return "class='lyxnoun'";
101 case xml::FontTypes::FT_UBAR:
103 case xml::FontTypes::FT_DBAR:
104 return "class='dline'";
105 case xml::FontTypes::FT_XOUT:
106 case xml::FontTypes::FT_SOUT:
107 return "class='strikeout'";
108 case xml::FontTypes::FT_WAVE:
109 return "class='wline'";
110 case xml::FontTypes::FT_ITALIC:
112 case xml::FontTypes::FT_UPRIGHT:
113 return "style='font-style:normal;'";
114 case xml::FontTypes::FT_SLANTED:
115 return "style='font-style:oblique;'";
116 case xml::FontTypes::FT_SMALLCAPS:
117 return "style='font-variant:small-caps;'";
118 case xml::FontTypes::FT_ROMAN:
119 return "style='font-family:serif;'";
120 case xml::FontTypes::FT_SANS:
121 return "style='font-family:sans-serif;'";
122 case xml::FontTypes::FT_TYPE:
123 return "style='font-family:monospace;'";
124 case xml::FontTypes::FT_SIZE_TINY:
125 case xml::FontTypes::FT_SIZE_SCRIPT:
126 case xml::FontTypes::FT_SIZE_FOOTNOTE:
127 return "style='font-size:x-small;'";
128 case xml::FontTypes::FT_SIZE_SMALL:
129 return "style='font-size:small;'";
130 case xml::FontTypes::FT_SIZE_NORMAL:
131 return "style='font-size:normal;'";
132 case xml::FontTypes::FT_SIZE_LARGE:
133 return "style='font-size:large;'";
134 case xml::FontTypes::FT_SIZE_LARGER:
135 case xml::FontTypes::FT_SIZE_LARGEST:
136 return "style='font-size:x-large;'";
137 case xml::FontTypes::FT_SIZE_HUGE:
138 case xml::FontTypes::FT_SIZE_HUGER:
139 return "style='font-size:xx-large;'";
140 case xml::FontTypes::FT_SIZE_INCREASE:
141 return "style='font-size:larger;'";
142 case xml::FontTypes::FT_SIZE_DECREASE:
143 return "style='font-size:smaller;'";
149 } // end anonymous namespace
152 xml::FontTag xhtmlStartFontTag(xml::FontTypes type)
154 return xml::FontTag(fontToHtmlTag(type), from_utf8(fontToAttribute(type)), type);
158 xml::EndFontTag xhtmlEndFontTag(xml::FontTypes type)
160 return xml::EndFontTag(fontToHtmlTag(type), type);
165 // convenience functions
167 inline void openParTag(XMLStream & xs, Layout const & lay,
168 std::string parlabel)
170 xs << xml::ParTag(lay.htmltag(), lay.htmlattr(), parlabel);
174 void openParTag(XMLStream & xs, Layout const & lay,
175 ParagraphParameters const & params,
176 std::string parlabel)
178 // FIXME Are there other things we should handle here?
179 string const align = alignmentToCSS(params.align());
181 openParTag(xs, lay, parlabel);
184 string attrs = lay.htmlattr() + " style='text-align: " + align + ";'";
185 xs << xml::ParTag(lay.htmltag(), attrs, parlabel);
189 inline void closeTag(XMLStream & xs, Layout const & lay)
191 xs << xml::EndTag(lay.htmltag());
195 inline void openLabelTag(XMLStream & xs, Layout const & lay)
197 xs << xml::StartTag(lay.htmllabeltag(), lay.htmllabelattr());
201 inline void closeLabelTag(XMLStream & xs, Layout const & lay)
203 xs << xml::EndTag(lay.htmllabeltag());
207 inline void openItemTag(XMLStream & xs, Layout const & lay)
209 xs << xml::StartTag(lay.htmlitemtag(), lay.htmlitemattr(), true);
213 void openItemTag(XMLStream & xs, Layout const & lay,
214 ParagraphParameters const & params)
216 // FIXME Are there other things we should handle here?
217 string const align = alignmentToCSS(params.align());
219 openItemTag(xs, lay);
222 string attrs = lay.htmlattr() + " style='text-align: " + align + ";'";
223 xs << xml::StartTag(lay.htmlitemtag(), attrs);
227 inline void closeItemTag(XMLStream & xs, Layout const & lay)
229 xs << xml::EndTag(lay.htmlitemtag());
232 // end of convenience functions
234 ParagraphList::const_iterator findLastParagraph(
235 ParagraphList::const_iterator p,
236 ParagraphList::const_iterator const & pend)
238 for (++p; p != pend && p->layout().latextype == LATEX_PARAGRAPH; ++p)
245 ParagraphList::const_iterator findEndOfEnvironment(
246 ParagraphList::const_iterator const & pstart,
247 ParagraphList::const_iterator const & pend)
249 ParagraphList::const_iterator p = pstart;
250 Layout const & bstyle = p->layout();
251 size_t const depth = p->params().depth();
252 for (++p; p != pend; ++p) {
253 Layout const & style = p->layout();
254 // It shouldn't happen that e.g. a section command occurs inside
255 // a quotation environment, at a higher depth, but as of 6/2009,
256 // it can happen. We pretend that it's just at lowest depth.
257 if (style.latextype == LATEX_COMMAND)
260 // If depth is down, we're done
261 if (p->params().depth() < depth)
264 // If depth is up, we're not done
265 if (p->params().depth() > depth)
268 // FIXME I am not sure about the first check.
269 // Surely we *could* have different layouts that count as
270 // LATEX_PARAGRAPH, right?
271 if (style.latextype == LATEX_PARAGRAPH || style != bstyle)
278 ParagraphList::const_iterator makeParagraphs(Buffer const & buf,
280 OutputParams const & runparams,
282 ParagraphList::const_iterator const & pbegin,
283 ParagraphList::const_iterator const & pend)
285 ParagraphList::const_iterator const begin = text.paragraphs().begin();
286 ParagraphList::const_iterator par = pbegin;
287 for (; par != pend; ++par) {
288 Layout const & lay = par->layout();
289 if (!lay.counter.empty())
290 buf.masterBuffer()->params().
291 documentClass().counters().step(lay.counter, OutputUpdate);
293 // FIXME We should see if there's a label to be output and
294 // do something with it.
298 // We want to open the paragraph tag if:
299 // (i) the current layout permits multiple paragraphs
300 // (ii) we are either not already inside a paragraph (HTMLIsBlock) OR
301 // we are, but this is not the first paragraph
303 // But there is also a special case, and we first see whether we are in it.
304 // We do not want to open the paragraph tag if this paragraph contains
305 // only one item, and that item is "inline", i.e., not HTMLIsBlock (such
306 // as a branch). On the other hand, if that single item has a font change
307 // applied to it, then we still do need to open the paragraph.
309 // Obviously, this is very fragile. The main reason we need to do this is
310 // because of branches, e.g., a branch that contains an entire new section.
311 // We do not really want to wrap that whole thing in a <div>...</div>.
312 bool special_case = false;
313 Inset const * specinset = par->size() == 1 ? par->getInset(0) : nullptr;
314 if (specinset && !specinset->getLayout().htmlisblock()) {
315 Layout const & style = par->layout();
316 FontInfo const first_font = style.labeltype == LABEL_MANUAL ?
317 style.labelfont : style.font;
318 FontInfo const our_font =
319 par->getFont(buf.masterBuffer()->params(), 0,
320 text.outerFont(distance(begin, par))).fontInfo();
321 if (first_font == our_font)
325 bool const open_par = runparams.html_make_pars
326 && (!runparams.html_in_par || par != pbegin)
329 // We want to issue the closing tag if either:
330 // (i) We opened it, and either html_in_par is false,
331 // or we're not in the last paragraph, anyway.
332 // (ii) We didn't open it and html_in_par is true,
333 // but we are in the first par, and there is a next par.
334 ParagraphList::const_iterator nextpar = par;
336 bool const close_par =
337 (open_par && (!runparams.html_in_par || nextpar != pend))
338 || (!open_par && runparams.html_in_par && par == pbegin && nextpar != pend);
341 // We do not issue the paragraph id if we are doing
342 // this for the TOC (or some similar purpose)
343 openParTag(xs, lay, par->params(),
344 runparams.for_toc ? "" : par->magicLabel());
347 docstring const deferred = par->simpleLyXHTMLOnePar(buf, xs,
348 runparams, text.outerFont(distance(begin, par)),
349 open_par, close_par);
356 if (!deferred.empty()) {
357 xs << XMLStream::ESCAPE_NONE << deferred << xml::CR();
364 ParagraphList::const_iterator makeBibliography(Buffer const & buf,
366 OutputParams const & runparams,
368 ParagraphList::const_iterator const & pbegin,
369 ParagraphList::const_iterator const & pend)
372 // Use TextClass::htmlTOCLayout() to figure out how we should look.
373 xs << xml::StartTag("h2", "class='bibliography'")
374 << pbegin->layout().labelstring(false)
377 << xml::StartTag("div", "class='bibliography'")
379 makeParagraphs(buf, xs, runparams, text, pbegin, pend);
380 xs << xml::EndTag("div");
385 bool isNormalEnv(Layout const & lay)
387 return lay.latextype == LATEX_ENVIRONMENT
388 || lay.latextype == LATEX_BIB_ENVIRONMENT;
392 ParagraphList::const_iterator makeEnvironment(Buffer const & buf,
394 OutputParams const & runparams,
396 ParagraphList::const_iterator const & pbegin,
397 ParagraphList::const_iterator const & pend)
399 ParagraphList::const_iterator const begin = text.paragraphs().begin();
400 ParagraphList::const_iterator par = pbegin;
401 Layout const & bstyle = par->layout();
402 depth_type const origdepth = pbegin->params().depth();
404 // open tag for this environment
405 openParTag(xs, bstyle, pbegin->magicLabel());
408 // we will on occasion need to remember a layout from before.
409 Layout const * lastlay = nullptr;
411 while (par != pend) {
412 Layout const & style = par->layout();
413 // the counter only gets stepped if we're in some kind of list,
414 // or if it's the first time through.
415 // note that enum, etc, are handled automatically.
416 // FIXME There may be a bug here about user defined enumeration
417 // types. If so, then we'll need to take the counter and add "i",
418 // "ii", etc, as with enum.
419 Counters & cnts = buf.masterBuffer()->params().documentClass().counters();
420 docstring const & cntr = style.counter;
421 if (!style.counter.empty()
422 && (par == pbegin || !isNormalEnv(style))
423 && cnts.hasCounter(cntr)
425 cnts.step(cntr, OutputUpdate);
426 ParagraphList::const_iterator send;
428 switch (style.latextype) {
429 case LATEX_ENVIRONMENT:
430 case LATEX_LIST_ENVIRONMENT:
431 case LATEX_ITEM_ENVIRONMENT: {
432 // There are two possibilities in this case.
433 // One is that we are still in the environment in which we
434 // started---which we will be if the depth is the same.
435 if (par->params().depth() == origdepth) {
436 LATTEST(bstyle == style);
437 if (lastlay != nullptr) {
438 closeItemTag(xs, *lastlay);
442 // this will be positive, if we want to skip the
443 // initial word (if it's been taken for the label).
445 bool const labelfirst = style.htmllabelfirst();
447 openItemTag(xs, style, par->params());
450 if (style.labeltype != LABEL_NO_LABEL &&
451 style.htmllabeltag() != "NONE") {
452 if (isNormalEnv(style)) {
453 // in this case, we print the label only for the first
454 // paragraph (as in a theorem).
456 docstring const lbl =
457 pbegin->params().labelString();
459 openLabelTag(xs, style);
461 closeLabelTag(xs, style);
465 } else { // some kind of list
466 if (style.labeltype == LABEL_MANUAL) {
467 openLabelTag(xs, style);
468 sep = par->firstWordLyXHTML(xs, runparams);
469 closeLabelTag(xs, style);
473 openLabelTag(xs, style);
474 xs << par->params().labelString();
475 closeLabelTag(xs, style);
479 } // end label output
482 openItemTag(xs, style, par->params());
484 docstring deferred = par->simpleLyXHTMLOnePar(buf, xs, runparams,
485 text.outerFont(distance(begin, par)), true, true, sep);
486 xs << XMLStream::ESCAPE_NONE << deferred;
489 // We may not want to close the tag yet, in particular:
490 // If we're not at the end...
492 // and are doing items...
493 && !isNormalEnv(style)
494 // and if the depth has changed...
495 && par->params().depth() != origdepth) {
496 // then we'll save this layout for later, and close it when
497 // we get another item.
500 closeItemTag(xs, style);
503 // The other possibility is that the depth has increased, in which
504 // case we need to recurse.
506 send = findEndOfEnvironment(par, pend);
507 par = makeEnvironment(buf, xs, runparams, text, par, send);
511 case LATEX_PARAGRAPH:
512 send = findLastParagraph(par, pend);
513 par = makeParagraphs(buf, xs, runparams, text, par, send);
516 case LATEX_BIB_ENVIRONMENT:
519 par = makeParagraphs(buf, xs, runparams, text, par, send);
528 if (lastlay != nullptr)
529 closeItemTag(xs, *lastlay);
530 closeTag(xs, bstyle);
536 void makeCommand(Buffer const & buf,
538 OutputParams const & runparams,
540 ParagraphList::const_iterator const & pbegin)
542 Layout const & style = pbegin->layout();
543 if (!style.counter.empty())
544 buf.masterBuffer()->params().
545 documentClass().counters().step(style.counter, OutputUpdate);
547 bool const make_parid = !runparams.for_toc && runparams.html_make_pars;
549 openParTag(xs, style, pbegin->params(),
550 make_parid ? pbegin->magicLabel() : "");
552 // Label around sectioning number:
553 // FIXME Probably need to account for LABEL_MANUAL
554 // FIXME Probably also need now to account for labels ABOVE and CENTERED.
555 if (style.labeltype != LABEL_NO_LABEL) {
556 openLabelTag(xs, style);
557 xs << pbegin->params().labelString();
558 closeLabelTag(xs, style);
559 // Otherwise the label might run together with the text
560 xs << from_ascii(" ");
563 ParagraphList::const_iterator const begin = text.paragraphs().begin();
564 pbegin->simpleLyXHTMLOnePar(buf, xs, runparams,
565 text.outerFont(distance(begin, pbegin)));
570 } // end anonymous namespace
573 void xhtmlParagraphs(Text const & text,
576 OutputParams const & runparams)
578 ParagraphList const & paragraphs = text.paragraphs();
579 if (runparams.par_begin == runparams.par_end) {
580 runparams.par_begin = 0;
581 runparams.par_end = paragraphs.size();
583 pit_type bpit = runparams.par_begin;
584 pit_type const epit = runparams.par_end;
586 { xs << XMLStream::ESCAPE_NONE << "<!-- XHTML output error! -->\n"; return; });
588 OutputParams ourparams = runparams;
589 ParagraphList::const_iterator const pend =
590 (epit == (int) paragraphs.size()) ?
591 paragraphs.end() : paragraphs.iterator_at(epit);
592 while (bpit < epit) {
593 ParagraphList::const_iterator par = paragraphs.iterator_at(bpit);
594 if (par->params().startOfAppendix()) {
595 // We want to reset the counter corresponding to toplevel sectioning
597 buf.masterBuffer()->params().documentClass().getTOCLayout();
598 docstring const cnt = lay.counter;
601 buf.masterBuffer()->params().documentClass().counters();
605 Layout const & style = par->layout();
606 ParagraphList::const_iterator const lastpar = par;
607 ParagraphList::const_iterator send;
609 switch (style.latextype) {
610 case LATEX_COMMAND: {
611 // The files with which we are working never have more than
612 // one paragraph in a command structure.
614 // if (ourparams.html_in_par)
615 // fix it so we don't get sections inside standard, e.g.
616 // note that we may then need to make runparams not const, so we
617 // can communicate that back.
618 // FIXME Maybe this fix should be in the routines themselves, in case
619 // they are called from elsewhere.
620 makeCommand(buf, xs, ourparams, text, par);
624 case LATEX_ENVIRONMENT:
625 case LATEX_LIST_ENVIRONMENT:
626 case LATEX_ITEM_ENVIRONMENT: {
627 // FIXME Same fix here.
628 send = findEndOfEnvironment(par, pend);
629 par = makeEnvironment(buf, xs, ourparams, text, par, send);
632 case LATEX_BIB_ENVIRONMENT: {
633 // FIXME Same fix here.
634 send = findEndOfEnvironment(par, pend);
635 par = makeBibliography(buf, xs, ourparams, text, par, send);
638 case LATEX_PARAGRAPH:
639 send = findLastParagraph(par, pend);
640 par = makeParagraphs(buf, xs, ourparams, text, par, send);
643 bpit += distance(lastpar, par);
648 string alignmentToCSS(LyXAlignment align)
651 case LYX_ALIGN_BLOCK:
652 // we are NOT going to use text-align: justify!!
655 case LYX_ALIGN_RIGHT:
657 case LYX_ALIGN_CENTER: