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