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