]> git.lyx.org Git - lyx.git/blob - src/output_xhtml.cpp
Merge branch 'master' of git.lyx.org:lyx
[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 "sgml.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 <vector>
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 namespace html {
48
49 docstring escapeChar(char_type c, XHTMLStream::EscapeSettings e)
50 {
51         docstring str;
52         switch (e) {
53         case XHTMLStream::ESCAPE_NONE:
54                 str += c;
55                 break;
56         case XHTMLStream::ESCAPE_ALL:
57                 if (c == '<') {
58                         str += "&lt;";
59                         break;
60                 } else if (c == '>') {
61                         str += "&gt;";
62                         break;
63                 }
64         // fall through
65         case XHTMLStream::ESCAPE_AND:
66                 if (c == '&')
67                         str += "&amp;";
68                 else
69                         str     +=c ;
70                 break;
71         }
72         return str;
73 }
74
75
76 // escape what needs escaping
77 docstring htmlize(docstring const & str, XHTMLStream::EscapeSettings e)
78 {
79         odocstringstream d;
80         docstring::const_iterator it = str.begin();
81         docstring::const_iterator en = str.end();
82         for (; it != en; ++it)
83                 d << escapeChar(*it, e);
84         return d.str();
85 }
86
87
88 docstring escapeChar(char c, XHTMLStream::EscapeSettings e)
89 {
90         LATTEST(static_cast<unsigned char>(c) < 0x80);
91         return escapeChar(static_cast<char_type>(c), e);
92 }
93
94
95 docstring cleanAttr(docstring const & str)
96 {
97         docstring newname;
98         docstring::const_iterator it = str.begin();
99         docstring::const_iterator en = str.end();
100         for (; it != en; ++it) {
101                 char_type const c = *it;
102                 newname += isAlnumASCII(c) ? c : char_type('_');
103         }
104         return newname;
105 }
106
107
108 docstring StartTag::writeTag() const
109 {
110         docstring output = '<' + from_utf8(tag_);
111         if (!attr_.empty())
112                 output += ' ' + html::htmlize(from_utf8(attr_), XHTMLStream::ESCAPE_NONE);
113         output += ">";
114         return output;
115 }
116
117
118 docstring StartTag::writeEndTag() const
119 {
120         string output = "</" + tag_ + ">";
121         return from_utf8(output);
122 }
123
124
125 bool StartTag::operator==(FontTag const & rhs) const
126 {
127         return rhs == *this;
128 }
129
130
131 docstring EndTag::writeEndTag() const
132 {
133         string output = "</" + tag_ + ">";
134         return from_utf8(output);
135 }
136
137
138 docstring ParTag::writeTag() const
139 {
140         docstring output = StartTag::writeTag();
141
142         if (parid_.empty())
143                 return output;
144
145         string const pattr = "id='" + parid_ + "'";
146         output += html::CompTag("a", pattr).writeTag();
147         return output;
148 }
149
150
151 docstring CompTag::writeTag() const
152 {
153         docstring output = '<' + from_utf8(tag_);
154         if (!attr_.empty())
155                 output += ' ' + html::htmlize(from_utf8(attr_), XHTMLStream::ESCAPE_NONE);
156         output += " />";
157         return output;
158 }
159
160
161
162 namespace {
163
164 string fontToTag(html::FontTypes type)
165 {
166         switch(type) {
167         case FT_EMPH:
168                 return "em";
169         case FT_BOLD:
170                 return "b";
171         case FT_NOUN:
172                 return "dfn";
173         case FT_UBAR:
174         case FT_WAVE:
175         case FT_DBAR:
176                 return "u";
177         case FT_SOUT:
178                 return "del";
179         case FT_ITALIC:
180                 return "i";
181         case FT_UPRIGHT:
182         case FT_SLANTED:
183         case FT_SMALLCAPS:
184         case FT_ROMAN:
185         case FT_SANS:
186         case FT_TYPE:
187         case FT_SIZE_TINY:
188         case FT_SIZE_SCRIPT:
189         case FT_SIZE_FOOTNOTE:
190         case FT_SIZE_SMALL:
191         case FT_SIZE_NORMAL:
192         case FT_SIZE_LARGE:
193         case FT_SIZE_LARGER:
194         case FT_SIZE_LARGEST:
195         case FT_SIZE_HUGE:
196         case FT_SIZE_HUGER:
197         case FT_SIZE_INCREASE:
198         case FT_SIZE_DECREASE:
199                 return "span";
200         }
201         // kill warning
202         return "";
203 }
204
205 string fontToAttribute(html::FontTypes type)
206 {
207         switch(type) {
208         case FT_EMPH:
209         case FT_BOLD:
210                 return "";
211         case FT_NOUN:
212                 return "class='lyxnoun'";
213         case FT_UBAR:
214                 return "";
215         case FT_DBAR:
216                 return "class='dline'";
217         case FT_SOUT:
218                 return "class='strikeout'";
219         case FT_WAVE:
220                 return "class='wline'";
221         case FT_ITALIC:
222                 return "";
223         case FT_UPRIGHT:
224                 return "style='font-style:normal;'";
225         case FT_SLANTED:
226                 return "style='font-style:oblique;'";
227         case FT_SMALLCAPS:
228                 return "style='font-variant:small-caps;'";
229         case FT_ROMAN:
230                 return "style='font-family:serif;'";
231         case FT_SANS:
232                 return "style='font-family:sans-serif;'";
233         case FT_TYPE:
234                 return "style='font-family:monospace;'";
235         case FT_SIZE_TINY:
236         case FT_SIZE_SCRIPT:
237         case FT_SIZE_FOOTNOTE:
238                 return "style='font-size:x-small;'";
239         case FT_SIZE_SMALL:
240                 return "style='font-size:small;'";
241         case FT_SIZE_NORMAL:
242                 return "style='font-size:normal;'";
243         case FT_SIZE_LARGE:
244                 return "style='font-size:large;'";
245         case FT_SIZE_LARGER:
246         case FT_SIZE_LARGEST:
247                 return "style='font-size:x-large;'";
248         case FT_SIZE_HUGE:
249         case FT_SIZE_HUGER:
250                 return "style='font-size:xx-large;'";
251         case FT_SIZE_INCREASE:
252                 return "style='font-size:larger;'";
253         case FT_SIZE_DECREASE:
254                 return "style='font-size:smaller;'";
255         }
256         // kill warning
257         return "";
258 }
259
260 } // end anonymous namespace
261
262
263 FontTag::FontTag(FontTypes type)
264   : StartTag(fontToTag(type), fontToAttribute(type)), font_type_(type)
265 {}
266
267
268 bool FontTag::operator==(StartTag const & tag) const
269 {
270         FontTag const * const ftag = tag.asFontTag();
271         if (!ftag)
272                 return false;
273         return (font_type_ == ftag->font_type_);
274 }
275
276
277 EndFontTag::EndFontTag(FontTypes type)
278           : EndTag(fontToTag(type)), font_type_(type)
279 {}
280
281 } // namespace html
282
283
284
285 ////////////////////////////////////////////////////////////////
286 ///
287 /// XHTMLStream
288 ///
289 ////////////////////////////////////////////////////////////////
290
291 XHTMLStream::XHTMLStream(odocstream & os)
292   : os_(os), escape_(ESCAPE_ALL)
293 {}
294
295
296 #ifdef XHTML_DEBUG
297 void XHTMLStream::dumpTagStack(string const & msg) const
298 {
299         writeError(msg + ": Tag Stack");
300         TagStack::const_reverse_iterator it = tag_stack_.rbegin();
301         TagStack::const_reverse_iterator en = tag_stack_.rend();
302         for (; it != en; ++it) {
303                 writeError(it->tag_);
304         }
305         writeError("Pending Tags");
306         it = pending_tags_.rbegin();
307         en = pending_tags_.rend();
308         for (; it != en; ++it) {
309                 writeError(it->tag_);
310         }
311         writeError("End Tag Stack");
312 }
313 #endif
314
315
316 void XHTMLStream::writeError(std::string const & s) const
317 {
318         LYXERR0(s);
319         os_ << from_utf8("<!-- Output Error: " + s + " -->\n");
320 }
321
322
323 namespace {
324         // an illegal tag for internal use
325         static html::StartTag const parsep_tag("&LyX_parsep_tag&");
326 }
327
328
329 bool XHTMLStream::closeFontTags()
330 {
331         if (isTagPending(parsep_tag))
332                 // we haven't had any content
333                 return true;
334
335         // this may be a useless check, since we ought at least to have
336         // the parsep_tag. but it can't hurt too much to be careful.
337         if (tag_stack_.empty())
338                 return true;
339
340         // first, we close any open font tags we can close
341         TagPtr curtag = tag_stack_.back();
342         while (curtag->asFontTag()) {
343                 os_ << curtag->writeEndTag();
344                 tag_stack_.pop_back();
345                 // this shouldn't happen, since then the font tags
346                 // weren't in any other tag.
347                 LBUFERR(!tag_stack_.empty());
348                 curtag = tag_stack_.back();
349         }
350
351         if (*curtag == parsep_tag)
352                 return true;
353
354         // so we've hit a non-font tag.
355         writeError("Tags still open in closeFontTags(). Probably not a problem,\n"
356                    "but you might want to check these tags:");
357         TagDeque::const_reverse_iterator it = tag_stack_.rbegin();
358         TagDeque::const_reverse_iterator const en = tag_stack_.rend();
359         for (; it != en; ++it) {
360                 if (**it == parsep_tag)
361                         break;
362                 writeError((*it)->tag_);
363         }
364         return false;
365 }
366
367
368 void XHTMLStream::startParagraph(bool keep_empty)
369 {
370         pending_tags_.push_back(makeTagPtr(html::StartTag(parsep_tag)));
371         if (keep_empty)
372                 clearTagDeque();
373 }
374
375
376 void XHTMLStream::endParagraph()
377 {
378         if (isTagPending(parsep_tag)) {
379                 // this case is normal. it just means we didn't have content,
380                 // so the parsep_tag never got moved onto the tag stack.
381                 while (!pending_tags_.empty()) {
382                         // clear all pending tags up to and including the parsep tag.
383                         // note that we work from the back, because we want to get rid
384                         // of everything that hasn't been used.
385                         TagPtr const cur_tag = pending_tags_.back();
386                         pending_tags_.pop_back();
387                         if (*cur_tag == parsep_tag)
388                                 break;
389                 }
390                 return;
391         }
392
393         if (!isTagOpen(parsep_tag)) {
394                 writeError("No paragraph separation tag found in endParagraph().");
395                 return;
396         }
397
398         // this case is also normal, if the parsep tag is the last one
399         // on the stack. otherwise, it's an error.
400         while (!tag_stack_.empty()) {
401                 TagPtr const cur_tag = tag_stack_.back();
402                 tag_stack_.pop_back();
403                 if (*cur_tag == parsep_tag)
404                         break;
405                 writeError("Tag `" + cur_tag->tag_ + "' still open at end of paragraph. Closing.");
406                 os_ << cur_tag->writeEndTag();
407         }
408 }
409
410
411 void XHTMLStream::clearTagDeque()
412 {
413         while (!pending_tags_.empty()) {
414                 TagPtr const tag = pending_tags_.front();
415                 if (*tag != parsep_tag)
416                         // tabs?
417                         os_ << tag->writeTag();
418                 tag_stack_.push_back(tag);
419                 pending_tags_.pop_front();
420         }
421 }
422
423
424 XHTMLStream & XHTMLStream::operator<<(docstring const & d)
425 {
426         clearTagDeque();
427         os_ << html::htmlize(d, escape_);
428         escape_ = ESCAPE_ALL;
429         return *this;
430 }
431
432
433 XHTMLStream & XHTMLStream::operator<<(const char * s)
434 {
435         clearTagDeque();
436         docstring const d = from_ascii(s);
437         os_ << html::htmlize(d, escape_);
438         escape_ = ESCAPE_ALL;
439         return *this;
440 }
441
442
443 XHTMLStream & XHTMLStream::operator<<(char_type c)
444 {
445         clearTagDeque();
446         os_ << html::escapeChar(c, escape_);
447         escape_ = ESCAPE_ALL;
448         return *this;
449 }
450
451
452 XHTMLStream & XHTMLStream::operator<<(char c)
453 {
454         clearTagDeque();
455         os_ << html::escapeChar(c, escape_);
456         escape_ = ESCAPE_ALL;
457         return *this;
458 }
459
460
461 XHTMLStream & XHTMLStream::operator<<(int i)
462 {
463         clearTagDeque();
464         os_ << i;
465         escape_ = ESCAPE_ALL;
466         return *this;
467 }
468
469
470 XHTMLStream & XHTMLStream::operator<<(EscapeSettings e)
471 {
472         escape_ = e;
473         return *this;
474 }
475
476
477 XHTMLStream & XHTMLStream::operator<<(html::StartTag const & tag)
478 {
479         if (tag.tag_.empty())
480                 return *this;
481         pending_tags_.push_back(makeTagPtr(tag));
482         if (tag.keepempty_)
483                 clearTagDeque();
484         return *this;
485 }
486
487
488 XHTMLStream & XHTMLStream::operator<<(html::ParTag const & tag)
489 {
490         if (tag.tag_.empty())
491                 return *this;
492         pending_tags_.push_back(makeTagPtr(tag));
493         return *this;
494 }
495
496
497 XHTMLStream & XHTMLStream::operator<<(html::CompTag const & tag)
498 {
499         if (tag.tag_.empty())
500                 return *this;
501         clearTagDeque();
502         os_ << tag.writeTag();
503         *this << html::CR();
504         return *this;
505 }
506
507
508 XHTMLStream & XHTMLStream::operator<<(html::FontTag const & tag)
509 {
510         if (tag.tag_.empty())
511                 return *this;
512         pending_tags_.push_back(makeTagPtr(tag));
513         return *this;
514 }
515
516
517 XHTMLStream & XHTMLStream::operator<<(html::CR const &)
518 {
519         // tabs?
520         os_ << from_ascii("\n");
521         return *this;
522 }
523
524
525 bool XHTMLStream::isTagOpen(html::StartTag const & stag) const
526 {
527         TagDeque::const_iterator sit = tag_stack_.begin();
528         TagDeque::const_iterator const sen = tag_stack_.end();
529         for (; sit != sen; ++sit)
530                 if (**sit == stag)
531                         return true;
532         return false;
533 }
534
535
536 bool XHTMLStream::isTagOpen(html::EndTag const & etag) const
537 {
538         TagDeque::const_iterator sit = tag_stack_.begin();
539         TagDeque::const_iterator const sen = tag_stack_.end();
540         for (; sit != sen; ++sit)
541                 if (etag == **sit)
542                         return true;
543         return false;
544 }
545
546
547 bool XHTMLStream::isTagPending(html::StartTag const & stag) const
548 {
549         TagDeque::const_iterator sit = pending_tags_.begin();
550         TagDeque::const_iterator const sen = pending_tags_.end();
551         for (; sit != sen; ++sit)
552                 if (**sit == stag)
553                         return true;
554         return false;
555 }
556
557
558 // this is complicated, because we want to make sure that
559 // everything is properly nested. the code ought to make
560 // sure of that, but we won't assert (yet) if we run into
561 // a problem. we'll just output error messages and try our
562 // best to make things work.
563 XHTMLStream & XHTMLStream::operator<<(html::EndTag const & etag)
564 {
565         if (etag.tag_.empty())
566                 return *this;
567
568         // if this tag is pending, we can simply discard it.
569         if (!pending_tags_.empty()) {
570
571                 if (etag == *pending_tags_.back()) {
572                         // we have <tag></tag>, so we discard it and remove it
573                         // from the pending_tags_.
574                         pending_tags_.pop_back();
575                         return *this;
576                 }
577
578                 // there is a pending tag that isn't the one we are trying
579                 // to close.
580
581                 // is this tag itself pending?
582                 // non-const iterators because we may call erase().
583                 TagDeque::iterator dit = pending_tags_.begin();
584                 TagDeque::iterator const den = pending_tags_.end();
585                 for (; dit != den; ++dit) {
586                         if (etag == **dit) {
587                                 // it was pending, so we just erase it
588                                 writeError("Tried to close pending tag `" + etag.tag_
589                                         + "' when other tags were pending. Last pending tag is `"
590                                         + to_utf8(pending_tags_.back()->writeTag())
591                                         + "'. Tag discarded.");
592                                 pending_tags_.erase(dit);
593                                 return *this;
594                         }
595                 }
596                 // so etag isn't itself pending. is it even open?
597                 if (!isTagOpen(etag)) {
598                         writeError("Tried to close `" + etag.tag_
599                                  + "' when tag was not open. Tag discarded.");
600                         return *this;
601                 }
602                 // ok, so etag is open.
603                 // our strategy will be as below: we will do what we need to
604                 // do to close this tag.
605                 string estr = "Closing tag `" + etag.tag_
606                         + "' when other tags are pending. Discarded pending tags:\n";
607                 for (dit = pending_tags_.begin(); dit != den; ++dit)
608                         estr += to_utf8(html::htmlize((*dit)->writeTag(), XHTMLStream::ESCAPE_ALL)) + "\n";
609                 writeError(estr);
610                 // clear the pending tags...
611                 pending_tags_.clear();
612                 // ...and then just fall through.
613         }
614
615         // make sure there are tags to be closed
616         if (tag_stack_.empty()) {
617                 writeError("Tried to close `" + etag.tag_
618                          + "' when no tags were open!");
619                 return *this;
620         }
621
622         // is the tag we are closing the last one we opened?
623         if (etag == *tag_stack_.back()) {
624                 // output it...
625                 os_ << etag.writeEndTag();
626                 // ...and forget about it
627                 tag_stack_.pop_back();
628                 return *this;
629         }
630
631         // we are trying to close a tag other than the one last opened.
632         // let's first see if this particular tag is still open somehow.
633         if (!isTagOpen(etag)) {
634                 writeError("Tried to close `" + etag.tag_
635                         + "' when tag was not open. Tag discarded.");
636                 return *this;
637         }
638
639         // so the tag was opened, but other tags have been opened since
640         // and not yet closed.
641         // if it's a font tag, though...
642         if (etag.asFontTag()) {
643                 // it won't be a problem if the other tags open since this one
644                 // are also font tags.
645                 TagDeque::const_reverse_iterator rit = tag_stack_.rbegin();
646                 TagDeque::const_reverse_iterator ren = tag_stack_.rend();
647                 for (; rit != ren; ++rit) {
648                         if (etag == **rit)
649                                 break;
650                         if (!(*rit)->asFontTag()) {
651                                 // we'll just leave it and, presumably, have to close it later.
652                                 writeError("Unable to close font tag `" + etag.tag_
653                                         + "' due to open non-font tag `" + (*rit)->tag_ + "'.");
654                                 return *this;
655                         }
656                 }
657
658                 // so we have e.g.:
659                 //    <em>this is <strong>bold
660                 // and are being asked to closed em. we want:
661                 //    <em>this is <strong>bold</strong></em><strong>
662                 // first, we close the intervening tags...
663                 TagPtr curtag = tag_stack_.back();
664                 // ...remembering them in a stack.
665                 TagDeque fontstack;
666                 while (etag != *curtag) {
667                         os_ << curtag->writeEndTag();
668                         fontstack.push_back(curtag);
669                         tag_stack_.pop_back();
670                         curtag = tag_stack_.back();
671                 }
672                 os_ << etag.writeEndTag();
673                 tag_stack_.pop_back();
674
675                 // ...and restore the other tags.
676                 rit = fontstack.rbegin();
677                 ren = fontstack.rend();
678                 for (; rit != ren; ++rit)
679                         pending_tags_.push_back(*rit);
680                 return *this;
681         }
682
683         // it wasn't a font tag.
684         // so other tags were opened before this one and not properly closed.
685         // so we'll close them, too. that may cause other issues later, but it
686         // at least guarantees proper nesting.
687         writeError("Closing tag `" + etag.tag_
688                 + "' when other tags are open, namely:");
689         TagPtr curtag = tag_stack_.back();
690         while (etag != *curtag) {
691                 writeError(curtag->tag_);
692                 if (*curtag != parsep_tag)
693                         os_ << curtag->writeEndTag();
694                 tag_stack_.pop_back();
695                 curtag = tag_stack_.back();
696         }
697         // curtag is now the one we actually want.
698         os_ << curtag->writeEndTag();
699         tag_stack_.pop_back();
700
701         return *this;
702 }
703
704 // End code for XHTMLStream
705
706 namespace {
707
708 // convenience functions
709
710 inline void openParTag(XHTMLStream & xs, Layout const & lay,
711                        std::string parlabel)
712 {
713         xs << html::ParTag(lay.htmltag(), lay.htmlattr(), parlabel);
714 }
715
716
717 void openParTag(XHTMLStream & xs, Layout const & lay,
718                 ParagraphParameters const & params,
719                 std::string parlabel)
720 {
721         // FIXME Are there other things we should handle here?
722         string const align = alignmentToCSS(params.align());
723         if (align.empty()) {
724                 openParTag(xs, lay, parlabel);
725                 return;
726         }
727         string attrs = lay.htmlattr() + " style='text-align: " + align + ";'";
728         xs << html::ParTag(lay.htmltag(), attrs, parlabel);
729 }
730
731
732 inline void closeTag(XHTMLStream & xs, Layout const & lay)
733 {
734         xs << html::EndTag(lay.htmltag());
735 }
736
737
738 inline void openLabelTag(XHTMLStream & xs, Layout const & lay)
739 {
740         xs << html::StartTag(lay.htmllabeltag(), lay.htmllabelattr());
741 }
742
743
744 inline void closeLabelTag(XHTMLStream & xs, Layout const & lay)
745 {
746         xs << html::EndTag(lay.htmllabeltag());
747 }
748
749
750 inline void openItemTag(XHTMLStream & xs, Layout const & lay)
751 {
752         xs << html::StartTag(lay.htmlitemtag(), lay.htmlitemattr(), true);
753 }
754
755
756 void openItemTag(XHTMLStream & xs, Layout const & lay,
757              ParagraphParameters const & params)
758 {
759         // FIXME Are there other things we should handle here?
760         string const align = alignmentToCSS(params.align());
761         if (align.empty()) {
762                 openItemTag(xs, lay);
763                 return;
764         }
765         string attrs = lay.htmlattr() + " style='text-align: " + align + ";'";
766         xs << html::StartTag(lay.htmlitemtag(), attrs);
767 }
768
769
770 inline void closeItemTag(XHTMLStream & xs, Layout const & lay)
771 {
772         xs << html::EndTag(lay.htmlitemtag());
773 }
774
775 // end of convenience functions
776
777 ParagraphList::const_iterator findLastParagraph(
778         ParagraphList::const_iterator p,
779         ParagraphList::const_iterator const & pend)
780 {
781         for (++p; p != pend && p->layout().latextype == LATEX_PARAGRAPH; ++p)
782                 ;
783
784         return p;
785 }
786
787
788 ParagraphList::const_iterator findEndOfEnvironment(
789                 ParagraphList::const_iterator const & pstart,
790                 ParagraphList::const_iterator const & pend)
791 {
792         ParagraphList::const_iterator p = pstart;
793         Layout const & bstyle = p->layout();
794         size_t const depth = p->params().depth();
795         for (++p; p != pend; ++p) {
796                 Layout const & style = p->layout();
797                 // It shouldn't happen that e.g. a section command occurs inside
798                 // a quotation environment, at a higher depth, but as of 6/2009,
799                 // it can happen. We pretend that it's just at lowest depth.
800                 if (style.latextype == LATEX_COMMAND)
801                         return p;
802
803                 // If depth is down, we're done
804                 if (p->params().depth() < depth)
805                         return p;
806
807                 // If depth is up, we're not done
808                 if (p->params().depth() > depth)
809                         continue;
810
811                 // FIXME I am not sure about the first check.
812                 // Surely we *could* have different layouts that count as
813                 // LATEX_PARAGRAPH, right?
814                 if (style.latextype == LATEX_PARAGRAPH || style != bstyle)
815                         return p;
816         }
817         return pend;
818 }
819
820
821 ParagraphList::const_iterator makeParagraphs(Buffer const & buf,
822                                             XHTMLStream & xs,
823                                             OutputParams const & runparams,
824                                             Text const & text,
825                                             ParagraphList::const_iterator const & pbegin,
826                                             ParagraphList::const_iterator const & pend)
827 {
828         ParagraphList::const_iterator const begin = text.paragraphs().begin();
829         ParagraphList::const_iterator par = pbegin;
830         for (; par != pend; ++par) {
831                 Layout const & lay = par->layout();
832                 if (!lay.counter.empty())
833                         buf.masterBuffer()->params().
834                             documentClass().counters().step(lay.counter, OutputUpdate);
835                 // FIXME We should see if there's a label to be output and
836                 // do something with it.
837                 if (par != pbegin)
838                         xs << html::CR();
839
840                 // If we are already in a paragraph, and this is the first one, then we
841                 // do not want to open the paragraph tag.
842                 // we also do not want to open it if the current layout does not permit
843                 // multiple paragraphs.
844                 bool const opened = runparams.html_make_pars &&
845                         (par != pbegin || !runparams.html_in_par);
846                 bool const make_parid = !runparams.for_toc && runparams.html_make_pars;
847
848                 if (opened)
849                         openParTag(xs, lay, par->params(),
850                                    make_parid ? par->magicLabel() : "");
851
852                 docstring const deferred =
853                         par->simpleLyXHTMLOnePar(buf, xs, runparams, text.outerFont(distance(begin, par)));
854
855                 // We want to issue the closing tag if either:
856                 //   (i)  We opened it, and either html_in_par is false,
857                 //        or we're not in the last paragraph, anyway.
858                 //   (ii) We didn't open it and html_in_par is true,
859                 //        but we are in the first par, and there is a next par.
860                 ParagraphList::const_iterator nextpar = par;
861                 ++nextpar;
862                 bool const needclose =
863                         (opened && (!runparams.html_in_par || nextpar != pend))
864                         || (!opened && runparams.html_in_par && par == pbegin && nextpar != pend);
865                 if (needclose) {
866                         closeTag(xs, lay);
867                         xs << html::CR();
868                 }
869                 if (!deferred.empty()) {
870                         xs << XHTMLStream::ESCAPE_NONE << deferred << html::CR();
871                 }
872         }
873         return pend;
874 }
875
876
877 ParagraphList::const_iterator makeBibliography(Buffer const & buf,
878                                 XHTMLStream & xs,
879                                 OutputParams const & runparams,
880                                 Text const & text,
881                                 ParagraphList::const_iterator const & pbegin,
882                                 ParagraphList::const_iterator const & pend)
883 {
884         // FIXME XHTML
885         // Use TextClass::htmlTOCLayout() to figure out how we should look.
886         xs << html::StartTag("h2", "class='bibliography'")
887            << pbegin->layout().labelstring(false)
888            << html::EndTag("h2")
889            << html::CR()
890            << html::StartTag("div", "class='bibliography'")
891            << html::CR();
892         makeParagraphs(buf, xs, runparams, text, pbegin, pend);
893         xs << html::EndTag("div");
894         return pend;
895 }
896
897
898 bool isNormalEnv(Layout const & lay)
899 {
900         return lay.latextype == LATEX_ENVIRONMENT
901             || lay.latextype == LATEX_BIB_ENVIRONMENT;
902 }
903
904
905 ParagraphList::const_iterator makeEnvironment(Buffer const & buf,
906                                               XHTMLStream & xs,
907                                               OutputParams const & runparams,
908                                               Text const & text,
909                                               ParagraphList::const_iterator const & pbegin,
910                                               ParagraphList::const_iterator const & pend)
911 {
912         ParagraphList::const_iterator const begin = text.paragraphs().begin();
913         ParagraphList::const_iterator par = pbegin;
914         Layout const & bstyle = par->layout();
915         depth_type const origdepth = pbegin->params().depth();
916
917         // open tag for this environment
918         openParTag(xs, bstyle, pbegin->magicLabel());
919         xs << html::CR();
920
921         // we will on occasion need to remember a layout from before.
922         Layout const * lastlay = 0;
923
924         while (par != pend) {
925                 Layout const & style = par->layout();
926                 // the counter only gets stepped if we're in some kind of list,
927                 // or if it's the first time through.
928                 // note that enum, etc, are handled automatically.
929                 // FIXME There may be a bug here about user defined enumeration
930                 // types. If so, then we'll need to take the counter and add "i",
931                 // "ii", etc, as with enum.
932                 Counters & cnts = buf.masterBuffer()->params().documentClass().counters();
933                 docstring const & cntr = style.counter;
934                 if (!style.counter.empty()
935                     && (par == pbegin || !isNormalEnv(style))
936                                 && cnts.hasCounter(cntr)
937                 )
938                         cnts.step(cntr, OutputUpdate);
939                 ParagraphList::const_iterator send;
940
941                 switch (style.latextype) {
942                 case LATEX_ENVIRONMENT:
943                 case LATEX_LIST_ENVIRONMENT:
944                 case LATEX_ITEM_ENVIRONMENT: {
945                         // There are two possiblities in this case.
946                         // One is that we are still in the environment in which we
947                         // started---which we will be if the depth is the same.
948                         if (par->params().depth() == origdepth) {
949                                 LATTEST(bstyle == style);
950                                 if (lastlay != 0) {
951                                         closeItemTag(xs, *lastlay);
952                                         lastlay = 0;
953                                 }
954
955                                 // this will be positive, if we want to skip the
956                                 // initial word (if it's been taken for the label).
957                                 pos_type sep = 0;
958                                 bool const labelfirst = style.htmllabelfirst();
959                                 if (!labelfirst)
960                                         openItemTag(xs, style, par->params());
961
962                                 // label output
963                                 if (style.labeltype != LABEL_NO_LABEL &&
964                                     style.htmllabeltag() != "NONE") {
965                                         if (isNormalEnv(style)) {
966                                                 // in this case, we print the label only for the first
967                                                 // paragraph (as in a theorem).
968                                                 if (par == pbegin) {
969                                                         docstring const lbl =
970                                                                         pbegin->params().labelString();
971                                                         if (!lbl.empty()) {
972                                                                 openLabelTag(xs, style);
973                                                                 xs << lbl;
974                                                                 closeLabelTag(xs, style);
975                                                         }
976                                                         xs << html::CR();
977                                                 }
978                                         } else { // some kind of list
979                                                 if (style.labeltype == LABEL_MANUAL) {
980                                                         openLabelTag(xs, style);
981                                                         sep = par->firstWordLyXHTML(xs, runparams);
982                                                         closeLabelTag(xs, style);
983                                                         xs << html::CR();
984                                                 }
985                                                 else {
986                                                         openLabelTag(xs, style);
987                                                         xs << par->params().labelString();
988                                                         closeLabelTag(xs, style);
989                                                         xs << html::CR();
990                                                 }
991                                         }
992                                 } // end label output
993
994                                 if (labelfirst)
995                                         openItemTag(xs, style, par->params());
996
997                                 par->simpleLyXHTMLOnePar(buf, xs, runparams,
998                                         text.outerFont(distance(begin, par)), sep);
999                                 ++par;
1000
1001                                 // We may not want to close the tag yet, in particular:
1002                                 // If we're not at the end...
1003                                 if (par != pend
1004                                         //  and are doing items...
1005                                          && !isNormalEnv(style)
1006                                          // and if the depth has changed...
1007                                          && par->params().depth() != origdepth) {
1008                                          // then we'll save this layout for later, and close it when
1009                                          // we get another item.
1010                                         lastlay = &style;
1011                                 } else
1012                                         closeItemTag(xs, style);
1013                                 xs << html::CR();
1014                         }
1015                         // The other possibility is that the depth has increased, in which
1016                         // case we need to recurse.
1017                         else {
1018                                 send = findEndOfEnvironment(par, pend);
1019                                 par = makeEnvironment(buf, xs, runparams, text, par, send);
1020                         }
1021                         break;
1022                 }
1023                 case LATEX_PARAGRAPH:
1024                         send = findLastParagraph(par, pend);
1025                         par = makeParagraphs(buf, xs, runparams, text, par, send);
1026                         break;
1027                 // Shouldn't happen
1028                 case LATEX_BIB_ENVIRONMENT:
1029                         send = par;
1030                         ++send;
1031                         par = makeParagraphs(buf, xs, runparams, text, par, send);
1032                         break;
1033                 // Shouldn't happen
1034                 case LATEX_COMMAND:
1035                         ++par;
1036                         break;
1037                 }
1038         }
1039
1040         if (lastlay != 0)
1041                 closeItemTag(xs, *lastlay);
1042         closeTag(xs, bstyle);
1043         xs << html::CR();
1044         return pend;
1045 }
1046
1047
1048 void makeCommand(Buffer const & buf,
1049                  XHTMLStream & xs,
1050                  OutputParams const & runparams,
1051                  Text const & text,
1052                  ParagraphList::const_iterator const & pbegin)
1053 {
1054         Layout const & style = pbegin->layout();
1055         if (!style.counter.empty())
1056                 buf.masterBuffer()->params().
1057                     documentClass().counters().step(style.counter, OutputUpdate);
1058
1059         bool const make_parid = !runparams.for_toc && runparams.html_make_pars;
1060
1061         openParTag(xs, style, pbegin->params(),
1062                    make_parid ? pbegin->magicLabel() : "");
1063
1064         // Label around sectioning number:
1065         // FIXME Probably need to account for LABEL_MANUAL
1066         // FIXME Probably also need now to account for labels ABOVE and CENTERED.
1067         if (style.labeltype != LABEL_NO_LABEL) {
1068                 openLabelTag(xs, style);
1069                 xs << pbegin->params().labelString();
1070                 closeLabelTag(xs, style);
1071                 // Otherwise the label might run together with the text
1072                 xs << from_ascii(" ");
1073         }
1074
1075         ParagraphList::const_iterator const begin = text.paragraphs().begin();
1076         pbegin->simpleLyXHTMLOnePar(buf, xs, runparams,
1077                         text.outerFont(distance(begin, pbegin)));
1078         closeTag(xs, style);
1079         xs << html::CR();
1080 }
1081
1082 } // end anonymous namespace
1083
1084
1085 void xhtmlParagraphs(Text const & text,
1086                        Buffer const & buf,
1087                        XHTMLStream & xs,
1088                        OutputParams const & runparams)
1089 {
1090         ParagraphList const & paragraphs = text.paragraphs();
1091         if (runparams.par_begin == runparams.par_end) {
1092                 runparams.par_begin = 0;
1093                 runparams.par_end = paragraphs.size();
1094         }
1095         pit_type bpit = runparams.par_begin;
1096         pit_type const epit = runparams.par_end;
1097         LASSERT(bpit < epit,
1098                 { xs << XHTMLStream::ESCAPE_NONE << "<!-- XHTML output error! -->\n"; return; });
1099
1100         OutputParams ourparams = runparams;
1101         ParagraphList::const_iterator const pend =
1102                 (epit == (int) paragraphs.size()) ?
1103                         paragraphs.end() : paragraphs.constIterator(epit);
1104         while (bpit < epit) {
1105                 ParagraphList::const_iterator par = paragraphs.constIterator(bpit);
1106                 if (par->params().startOfAppendix()) {
1107                         // We want to reset the counter corresponding to toplevel sectioning
1108                         Layout const & lay =
1109                                 buf.masterBuffer()->params().documentClass().getTOCLayout();
1110                         docstring const cnt = lay.counter;
1111                         if (!cnt.empty()) {
1112                                 Counters & cnts =
1113                                         buf.masterBuffer()->params().documentClass().counters();
1114                                 cnts.reset(cnt);
1115                         }
1116                 }
1117                 Layout const & style = par->layout();
1118                 ParagraphList::const_iterator const lastpar = par;
1119                 ParagraphList::const_iterator send;
1120
1121                 switch (style.latextype) {
1122                 case LATEX_COMMAND: {
1123                         // The files with which we are working never have more than
1124                         // one paragraph in a command structure.
1125                         // FIXME
1126                         // if (ourparams.html_in_par)
1127                         //   fix it so we don't get sections inside standard, e.g.
1128                         // note that we may then need to make runparams not const, so we
1129                         // can communicate that back.
1130                         // FIXME Maybe this fix should be in the routines themselves, in case
1131                         // they are called from elsewhere.
1132                         makeCommand(buf, xs, ourparams, text, par);
1133                         ++par;
1134                         break;
1135                 }
1136                 case LATEX_ENVIRONMENT:
1137                 case LATEX_LIST_ENVIRONMENT:
1138                 case LATEX_ITEM_ENVIRONMENT: {
1139                         // FIXME Same fix here.
1140                         send = findEndOfEnvironment(par, pend);
1141                         par = makeEnvironment(buf, xs, ourparams, text, par, send);
1142                         break;
1143                 }
1144                 case LATEX_BIB_ENVIRONMENT: {
1145                         // FIXME Same fix here.
1146                         send = findEndOfEnvironment(par, pend);
1147                         par = makeBibliography(buf, xs, ourparams, text, par, send);
1148                         break;
1149                 }
1150                 case LATEX_PARAGRAPH:
1151                         send = findLastParagraph(par, pend);
1152                         par = makeParagraphs(buf, xs, ourparams, text, par, send);
1153                         break;
1154                 }
1155                 bpit += distance(lastpar, par);
1156         }
1157 }
1158
1159
1160 string alignmentToCSS(LyXAlignment align)
1161 {
1162         switch (align) {
1163         case LYX_ALIGN_BLOCK:
1164                 // we are NOT going to use text-align: justify!!
1165         case LYX_ALIGN_LEFT:
1166                 return "left";
1167         case LYX_ALIGN_RIGHT:
1168                 return "right";
1169         case LYX_ALIGN_CENTER:
1170                 return "center";
1171         default:
1172                 break;
1173         }
1174         return "";
1175 }
1176
1177 } // namespace lyx