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