Fix for UBSan build
[platform/upstream/doxygen.git] / src / htmldocvisitor.cpp
1 /******************************************************************************
2  *
3  * $Id: $
4  *
5  *
6  * Copyright (C) 1997-2012 by Dimitri van Heesch.
7  *
8  * Permission to use, copy, modify, and distribute this software and its
9  * documentation under the terms of the GNU General Public License is hereby 
10  * granted. No representations are made about the suitability of this software 
11  * for any purpose. It is provided "as is" without express or implied warranty.
12  * See the GNU General Public License for more details.
13  *
14  * Documents produced by Doxygen are derivative works derived from the
15  * input used in their production; they are not affected by this license.
16  *
17  */
18
19 #include <qdir.h>
20 #include "htmldocvisitor.h"
21 #include "docparser.h"
22 #include "language.h"
23 #include "doxygen.h"
24 #include "outputgen.h"
25 #include "dot.h"
26 #include "message.h"
27 #include "config.h"
28 #include "htmlgen.h"
29 #include "parserintf.h"
30 #include "msc.h"
31 #include "util.h"
32
33
34 static const int NUM_HTML_LIST_TYPES = 4;
35 static const char types[][NUM_HTML_LIST_TYPES] = {"1", "a", "i", "A"};
36
37 static QCString convertIndexWordToAnchor(const QString &word)
38 {
39   static char hex[] = "0123456789abcdef";
40   uint i;
41   QCString result;
42   for (i=0;i<word.length();i++)
43   {
44     int c = word.at(i);
45     if (isId(c))
46     {
47       result+=c;
48     }
49     else if (isspace(c))
50     {
51       result+="_";
52     }
53     else
54     {
55       char cs[3];
56       cs[0]=hex[c>>4];
57       cs[1]=hex[c&0xf];
58       cs[2]=0;
59       result+=cs;
60     }
61   }
62   return result;
63 }
64
65 static bool mustBeOutsideParagraph(DocNode *n)
66 {
67   switch (n->kind())
68   {
69           /* <ul> */
70         case DocNode::Kind_HtmlList:
71         case DocNode::Kind_SimpleList:
72         case DocNode::Kind_AutoList:
73           /* <dl> */
74         case DocNode::Kind_SimpleSect:
75         case DocNode::Kind_ParamSect:
76         case DocNode::Kind_HtmlDescList:
77         case DocNode::Kind_XRefItem:
78           /* <table> */
79         case DocNode::Kind_HtmlTable:
80           /* <h?> */
81         case DocNode::Kind_Section:
82         case DocNode::Kind_HtmlHeader:
83           /* \internal */
84         case DocNode::Kind_Internal:
85           /* <div> */
86         case DocNode::Kind_Include:
87         case DocNode::Kind_Verbatim:
88         case DocNode::Kind_Image:
89         case DocNode::Kind_SecRefList:
90           /* <hr> */
91         case DocNode::Kind_HorRuler:
92           /* CopyDoc gets paragraph markers from the wrapping DocPara node,
93            * but needs to insert them for all documentation being copied to
94            * preserve formatting.
95            */
96         case DocNode::Kind_Copy:
97           /* <blockquote> */
98         case DocNode::Kind_HtmlBlockQuote:
99           return TRUE;
100         case DocNode::Kind_StyleChange:
101           return ((DocStyleChange*)n)->style()==DocStyleChange::Preformatted ||
102                  ((DocStyleChange*)n)->style()==DocStyleChange::Div ||
103                  ((DocStyleChange*)n)->style()==DocStyleChange::Center;
104         case DocNode::Kind_Formula:
105           return !((DocFormula*)n)->isInline();
106         default:
107           break;
108   }
109   return FALSE;
110 }
111
112 static QString htmlAttribsToString(const HtmlAttribList &attribs)
113 {
114   QString result;
115   HtmlAttribListIterator li(attribs);
116   HtmlAttrib *att;
117   for (li.toFirst();(att=li.current());++li)
118   {
119     if (!att->value.isEmpty())  // ignore attribute without values as they
120                                 // are not XHTML compliant
121     {
122       result+=" ";
123       result+=att->name;
124       result+="=\""+convertToXML(att->value)+"\"";
125     }
126   }
127   return result;
128 }
129
130 //-------------------------------------------------------------------------
131
132 HtmlDocVisitor::HtmlDocVisitor(FTextStream &t,CodeOutputInterface &ci,
133                                Definition *ctx,MemberDef *md) 
134   : DocVisitor(DocVisitor_Html), m_t(t), m_ci(ci), m_insidePre(FALSE), 
135                                  m_hide(FALSE), m_ctx(ctx), m_md(md)
136 {
137   if (ctx) m_langExt=ctx->getDefFileExtension();
138 }
139
140   //--------------------------------------
141   // visitor functions for leaf nodes
142   //--------------------------------------
143
144 void HtmlDocVisitor::visit(DocWord *w)
145 {
146   //printf("word: %s\n",w->word().data());
147   if (m_hide) return;
148   filter(w->word());
149 }
150
151 void HtmlDocVisitor::visit(DocLinkedWord *w)
152 {
153   if (m_hide) return;
154   startLink(w->ref(),w->file(),w->relPath(),w->anchor(),w->tooltip());
155   filter(w->word());
156   endLink();
157 }
158
159 void HtmlDocVisitor::visit(DocWhiteSpace *w)
160 {
161   if (m_hide) return;
162   if (m_insidePre)
163   {
164     m_t << w->chars();
165   }
166   else
167   {
168     m_t << " ";
169   }
170 }
171
172 void HtmlDocVisitor::visit(DocSymbol *s)
173 {
174   if (m_hide) return;
175   switch(s->symbol())
176   {
177     case DocSymbol::BSlash:        m_t << "\\"; break;
178     case DocSymbol::At:            m_t << "@"; break;
179     case DocSymbol::Less:          m_t << "&lt;"; break;
180     case DocSymbol::Greater:       m_t << "&gt;"; break;
181     case DocSymbol::Amp:           m_t << "&amp;"; break;
182     case DocSymbol::Dollar:        m_t << "$"; break;
183     case DocSymbol::Hash:          m_t << "#"; break;
184     case DocSymbol::DoubleColon:   m_t << "::"; break;
185     case DocSymbol::Percent:       m_t << "%"; break;
186     case DocSymbol::Pipe:          m_t << "|"; break;
187     case DocSymbol::Copy:          m_t << "&copy;"; break;
188     case DocSymbol::Tm:            m_t << "&trade;"; break;
189     case DocSymbol::Reg:           m_t << "&reg;"; break;
190     case DocSymbol::Apos:          m_t << "'"; break;
191     case DocSymbol::Quot:          m_t << "\""; break;
192     case DocSymbol::Lsquo:         m_t << "&lsquo;"; break;
193     case DocSymbol::Rsquo:         m_t << "&rsquo;"; break;
194     case DocSymbol::Ldquo:         m_t << "&ldquo;"; break;
195     case DocSymbol::Rdquo:         m_t << "&rdquo;"; break;
196     case DocSymbol::Ndash:         m_t << "&ndash;"; break;
197     case DocSymbol::Mdash:         m_t << "&mdash;"; break;
198     case DocSymbol::Uml:           m_t << "&" << s->letter() << "uml;"; break;
199     case DocSymbol::Acute:         m_t << "&" << s->letter() << "acute;"; break;
200     case DocSymbol::Grave:         m_t << "&" << s->letter() << "grave;"; break;
201     case DocSymbol::Circ:          m_t << "&" << s->letter() << "circ;"; break;
202     case DocSymbol::Slash:         m_t << "&" << s->letter() << "slash;"; break;
203     case DocSymbol::Tilde:         m_t << "&" << s->letter() << "tilde;"; break;
204     case DocSymbol::Szlig:         m_t << "&szlig;"; break;
205     case DocSymbol::Cedil:         m_t << "&" << s->letter() << "cedil;"; break;
206     case DocSymbol::Ring:          m_t << "&" << s->letter() << "ring;"; break;
207     case DocSymbol::Nbsp:          m_t << "&#160;"; break;
208     case DocSymbol::AElig:         m_t << "&AElig;"; break;
209     case DocSymbol::Aelig:         m_t << "&aelig;"; break;
210     case DocSymbol::GrkGamma:      m_t << "&Gamma;"; break;
211     case DocSymbol::GrkDelta:      m_t << "&Delta;"; break;
212     case DocSymbol::GrkTheta:      m_t << "&Theta;"; break;
213     case DocSymbol::GrkLambda:     m_t << "&Lambda;"; break;
214     case DocSymbol::GrkXi:         m_t << "&Xi;"; break;
215     case DocSymbol::GrkPi:         m_t << "&Pi;"; break;
216     case DocSymbol::GrkSigma:      m_t << "&Sigma;"; break;
217     case DocSymbol::GrkUpsilon:    m_t << "&Upsilon;"; break;
218     case DocSymbol::GrkPhi:        m_t << "&Phi;"; break;
219     case DocSymbol::GrkPsi:        m_t << "&Psi;"; break;
220     case DocSymbol::GrkOmega:      m_t << "&Omega;"; break;
221     case DocSymbol::Grkalpha:      m_t << "&alpha;"; break;
222     case DocSymbol::Grkbeta:       m_t << "&beta;"; break;
223     case DocSymbol::Grkgamma:      m_t << "&gamma;"; break;
224     case DocSymbol::Grkdelta:      m_t << "&delta;"; break;
225     case DocSymbol::Grkepsilon:    m_t << "&epsilon;"; break;
226     case DocSymbol::Grkzeta:       m_t << "&zeta;"; break;
227     case DocSymbol::Grketa:        m_t << "&eta;"; break;
228     case DocSymbol::Grktheta:      m_t << "&theta;"; break;
229     case DocSymbol::Grkiota:       m_t << "&iota;"; break;
230     case DocSymbol::Grkkappa:      m_t << "&kappa;"; break;
231     case DocSymbol::Grklambda:     m_t << "&lambda;"; break;
232     case DocSymbol::Grkmu:         m_t << "&mu;"; break;
233     case DocSymbol::Grknu:         m_t << "&nu;"; break;
234     case DocSymbol::Grkxi:         m_t << "&xi;"; break;
235     case DocSymbol::Grkpi:         m_t << "&pi;"; break;
236     case DocSymbol::Grkrho:        m_t << "&rho;"; break;
237     case DocSymbol::Grksigma:      m_t << "&sigma;"; break;
238     case DocSymbol::Grktau:        m_t << "&tau;"; break;
239     case DocSymbol::Grkupsilon:    m_t << "&upsilon;"; break;
240     case DocSymbol::Grkphi:        m_t << "&phi;"; break;
241     case DocSymbol::Grkchi:        m_t << "&chi;"; break;
242     case DocSymbol::Grkpsi:        m_t << "&psi;"; break;
243     case DocSymbol::Grkomega:      m_t << "&omega;"; break;
244     case DocSymbol::Grkvarsigma:   m_t << "&sigmaf;"; break;
245     case DocSymbol::Section:       m_t << "&sect;"; break;
246     case DocSymbol::Degree:        m_t << "&deg;"; break;
247     case DocSymbol::Prime:         m_t << "&prime;"; break;
248     case DocSymbol::DoublePrime:   m_t << "&Prime;"; break;
249     case DocSymbol::Infinity:      m_t << "&infin;"; break;
250     case DocSymbol::EmptySet:      m_t << "&empty;"; break;
251     case DocSymbol::PlusMinus:     m_t << "&plusmn;"; break;
252     case DocSymbol::Times:         m_t << "&times;"; break;
253     case DocSymbol::Minus:         m_t << "&minus;"; break;
254     case DocSymbol::CenterDot:     m_t << "&sdot;"; break;
255     case DocSymbol::Partial:       m_t << "&part;"; break;
256     case DocSymbol::Nabla:         m_t << "&nabla;"; break;
257     case DocSymbol::SquareRoot:    m_t << "&radic;"; break;
258     case DocSymbol::Perpendicular: m_t << "&perp;"; break;
259     case DocSymbol::Sum:           m_t << "&sum;"; break;
260     case DocSymbol::Integral:      m_t << "&int;"; break;
261     case DocSymbol::Product:       m_t << "&prod;"; break;
262     case DocSymbol::Similar:       m_t << "&sim;"; break;
263     case DocSymbol::Approx:        m_t << "&asymp;"; break;
264     case DocSymbol::NotEqual:      m_t << "&ne;"; break;
265     case DocSymbol::Equivalent:    m_t << "&equiv;"; break;
266     case DocSymbol::Proportional:  m_t << "&prop;"; break;
267     case DocSymbol::LessEqual:     m_t << "&le;"; break;
268     case DocSymbol::GreaterEqual:  m_t << "&ge;"; break;
269     case DocSymbol::LeftArrow:     m_t << "&larr;"; break;
270     case DocSymbol::RightArrow:    m_t << "&rarr;"; break;
271     case DocSymbol::SetIn:         m_t << "&isin;"; break;
272     case DocSymbol::SetNotIn:      m_t << "&notin;"; break;
273     case DocSymbol::LeftCeil:      m_t << "&lceil;"; break;
274     case DocSymbol::RightCeil:     m_t << "&rceil;"; break;
275     case DocSymbol::LeftFloor:     m_t << "&lfloor;"; break;
276     case DocSymbol::RightFloor:    m_t << "&rfloor;"; break;
277     default:
278        err("error: unknown symbol found\n");
279   }
280 }
281
282 void HtmlDocVisitor::writeObfuscatedMailAddress(const QCString &url)
283 {
284   m_t << "<a href=\"#\" onclick=\"location.href='mai'+'lto:'";
285   uint i;
286   int size=3;
287   for (i=0;i<url.length();)
288   {
289     m_t << "+'" << url.mid(i,size) << "'";
290     i+=size;
291     if (size==3) size=2; else size=3;
292   }
293   m_t << "; return false;\">";
294 }
295
296 void HtmlDocVisitor::visit(DocURL *u)
297 {
298   if (m_hide) return;
299   if (u->isEmail()) // mail address
300   {
301     QCString url = u->url();
302     writeObfuscatedMailAddress(url);
303     uint size=5,i;
304     for (i=0;i<url.length();)
305     {
306       filter(url.mid(i,size));
307       if (i<url.length()-size) m_t << "<span style=\"display: none;\">.nosp@m.</span>";
308       i+=size;
309       if (size==5) size=4; else size=5;
310     }
311     m_t << "</a>";
312   }
313   else // web address
314   {
315     m_t << "<a href=\"";
316     m_t << u->url() << "\">";
317     filter(u->url());
318     m_t << "</a>";
319   }
320 }
321
322 void HtmlDocVisitor::visit(DocLineBreak *)
323 {
324   if (m_hide) return;
325   m_t << "<br/>\n";
326 }
327
328 void HtmlDocVisitor::visit(DocHorRuler *)
329 {
330   if (m_hide) return;
331   m_t << "<hr/>\n";
332 }
333
334 void HtmlDocVisitor::visit(DocStyleChange *s)
335 {
336   if (m_hide) return;
337   switch (s->style())
338   {
339     case DocStyleChange::Bold:
340       if (s->enable()) m_t << "<b" << htmlAttribsToString(s->attribs()) << ">";      else m_t << "</b>";
341       break;
342     case DocStyleChange::Italic:
343       if (s->enable()) m_t << "<em" << htmlAttribsToString(s->attribs()) << ">";     else m_t << "</em>";
344       break;
345     case DocStyleChange::Code:
346       if (s->enable()) m_t << "<code" << htmlAttribsToString(s->attribs()) << ">";   else m_t << "</code>";
347       break;
348     case DocStyleChange::Subscript:
349       if (s->enable()) m_t << "<sub" << htmlAttribsToString(s->attribs()) << ">";    else m_t << "</sub>";
350       break;
351     case DocStyleChange::Superscript:
352       if (s->enable()) m_t << "<sup" << htmlAttribsToString(s->attribs()) << ">";    else m_t << "</sup>";
353       break;
354     case DocStyleChange::Center:
355       if (s->enable()) 
356       {
357         forceEndParagraph(s);
358         m_t << "<center" << htmlAttribsToString(s->attribs()) << ">"; 
359       }
360       else 
361       {
362         m_t << "</center>";
363         forceStartParagraph(s);
364       }
365       break;
366     case DocStyleChange::Small:
367       if (s->enable()) m_t << "<small" << htmlAttribsToString(s->attribs()) << ">";  else m_t << "</small>";
368       break;
369     case DocStyleChange::Preformatted:
370       if (s->enable())
371       {
372         forceEndParagraph(s);
373         m_t << "<pre" << htmlAttribsToString(s->attribs()) << ">";
374         m_insidePre=TRUE;
375       }
376       else
377       {
378         m_insidePre=FALSE;
379         m_t << "</pre>";
380         forceStartParagraph(s);
381       }
382       break;
383     case DocStyleChange::Div:
384       if (s->enable()) 
385       {
386         forceEndParagraph(s);
387         m_t << "<div" << htmlAttribsToString(s->attribs()) << ">";  
388       }
389       else 
390       {
391         m_t << "</div>";
392         forceStartParagraph(s);
393       }
394       break;
395     case DocStyleChange::Span:
396       if (s->enable()) m_t << "<span" << htmlAttribsToString(s->attribs()) << ">";  else m_t << "</span>";
397       break;
398
399   }
400 }
401
402
403 void HtmlDocVisitor::visit(DocVerbatim *s)
404 {
405   if (m_hide) return;
406   QCString lang = m_langExt;
407   if (!s->language().isEmpty()) // explicit language setting
408   {
409     lang = s->language();
410   }
411   switch(s->type())
412   {
413     case DocVerbatim::Code: 
414       forceEndParagraph(s);
415       m_t << PREFRAG_START;
416       Doxygen::parserManager->getParser(lang)
417                             ->parseCode(m_ci,
418                                         s->context(),
419                                         s->text(),
420                                         s->isExample(),
421                                         s->exampleFile(),
422                                         0,     // fileDef
423                                         -1,    // startLine
424                                         -1,    // endLine
425                                         FALSE, // inlineFragment
426                                         0,     // memberDef
427                                         TRUE,  // show line numbers
428                                         m_ctx  // search context
429                                        );
430       m_t << PREFRAG_END;
431       forceStartParagraph(s);
432       break;
433     case DocVerbatim::Verbatim: 
434       forceEndParagraph(s);
435       m_t << /*PREFRAG_START <<*/ "<pre class=\"fragment\">";
436       filter(s->text());
437       m_t << "</pre>" /*<< PREFRAG_END*/;
438       forceStartParagraph(s);
439       break;
440     case DocVerbatim::HtmlOnly: 
441       m_t << s->text(); 
442       break;
443     case DocVerbatim::ManOnly: 
444     case DocVerbatim::LatexOnly: 
445     case DocVerbatim::XmlOnly: 
446     case DocVerbatim::RtfOnly: 
447       /* nothing */ 
448       break;
449
450     case DocVerbatim::Dot:
451       {
452         static int dotindex = 1;
453         QCString fileName(4096);
454
455         fileName.sprintf("%s%d%s", 
456             (Config_getString("HTML_OUTPUT")+"/inline_dotgraph_").data(), 
457             dotindex++,
458             ".dot"
459            );
460         QFile file(fileName);
461         if (!file.open(IO_WriteOnly))
462         {
463           err("Could not open file %s for writing\n",fileName.data());
464         }
465         file.writeBlock( s->text(), s->text().length() );
466         file.close();
467
468         forceEndParagraph(s);
469         m_t << "<div align=\"center\">" << endl;
470         writeDotFile(fileName,s->relPath(),s->context());
471         m_t << "</div>" << endl;
472         forceStartParagraph(s);
473
474         if (Config_getBool("DOT_CLEANUP")) file.remove();
475       }
476       break;
477     case DocVerbatim::Msc:
478       {
479         static int mscindex = 1;
480         QCString baseName(4096);
481
482         baseName.sprintf("%s%d", 
483             (Config_getString("HTML_OUTPUT")+"/inline_mscgraph_").data(), 
484             mscindex++
485            );
486         QFile file(baseName+".msc");
487         if (!file.open(IO_WriteOnly))
488         {
489           err("Could not open file %s.msc for writing\n",baseName.data());
490         }
491         QCString text = "msc {";
492         text+=s->text();
493         text+="}";
494         file.writeBlock( text, text.length() );
495         file.close();
496
497         forceEndParagraph(s);
498         m_t << "<div align=\"center\">" << endl;
499         writeMscFile(baseName+".msc",s->relPath(),s->context());
500         m_t << "</div>" << endl;
501         forceStartParagraph(s);
502
503         if (Config_getBool("DOT_CLEANUP")) file.remove();
504       }
505       break;
506   }
507 }
508
509 void HtmlDocVisitor::visit(DocAnchor *anc)
510 {
511   if (m_hide) return;
512   m_t << "<a class=\"anchor\" id=\"" << anc->anchor() << "\"></a>";
513 }
514
515 void HtmlDocVisitor::visit(DocInclude *inc)
516 {
517   if (m_hide) return;
518   switch(inc->type())
519   {
520     case DocInclude::Include: 
521       forceEndParagraph(inc);
522       m_t << PREFRAG_START;
523       Doxygen::parserManager->getParser(inc->extension())
524                             ->parseCode(m_ci,                 
525                                         inc->context(),
526                                         inc->text(),
527                                         inc->isExample(),
528                                         inc->exampleFile(),
529                                         0,     // fileDef
530                                         -1,    // startLine
531                                         -1,    // endLine
532                                         TRUE,  // inlineFragment
533                                         0,     // memberDef
534                                         TRUE,  // show line numbers
535                                         m_ctx  // search context 
536                                        );
537       m_t << PREFRAG_END;
538       forceStartParagraph(inc);
539       break;
540     case DocInclude::IncWithLines:
541       { 
542          forceEndParagraph(inc);
543          m_t << PREFRAG_START;
544          QFileInfo cfi( inc->file() );
545          FileDef fd( cfi.dirPath().utf8(), cfi.fileName().utf8() );
546          Doxygen::parserManager->getParser(inc->extension())
547                                ->parseCode(m_ci,
548                                            inc->context(),
549                                            inc->text(),
550                                            inc->isExample(),
551                                            inc->exampleFile(), 
552                                            &fd,   // fileDef,
553                                            -1,    // start line
554                                            -1,    // end line
555                                            FALSE, // inline fragment
556                                            0,     // memberDef
557                                            TRUE,  // show line numbers
558                                            m_ctx  // search context
559                                            );
560          m_t << PREFRAG_END;
561          forceStartParagraph(inc);
562       }
563       break;
564     case DocInclude::DontInclude: 
565       break;
566     case DocInclude::HtmlInclude: 
567       m_t << inc->text(); 
568       break;
569     case DocInclude::VerbInclude: 
570       forceEndParagraph(inc);
571       m_t << /*PREFRAG_START <<*/ "<pre class=\"fragment\">";
572       filter(inc->text());
573       m_t << "</pre>" /*<< PREFRAG_END*/;
574       forceStartParagraph(inc);
575       break;
576     case DocInclude::Snippet:
577       {
578          forceEndParagraph(inc);
579          m_t << PREFRAG_START;
580          Doxygen::parserManager->getParser(inc->extension())
581                                ->parseCode(m_ci,
582                                            inc->context(),
583                                            extractBlock(inc->text(),inc->blockId()),
584                                            inc->isExample(),
585                                            inc->exampleFile(), 
586                                            0,
587                                            -1,    // startLine
588                                            -1,    // endLine
589                                            TRUE,  // inlineFragment
590                                            0,     // memberDef
591                                            TRUE,  // show line number
592                                            m_ctx  // search context
593                                           );
594          m_t << PREFRAG_END;
595          forceStartParagraph(inc);
596       }
597       break;
598   }
599 }
600
601 void HtmlDocVisitor::visit(DocIncOperator *op)
602 {
603   //printf("DocIncOperator: type=%d first=%d, last=%d text=`%s'\n",
604   //    op->type(),op->isFirst(),op->isLast(),op->text().data());
605   if (op->isFirst()) 
606   {
607     if (!m_hide) m_t << PREFRAG_START;
608     pushEnabled();
609     m_hide=TRUE;
610   }
611   if (op->type()!=DocIncOperator::Skip) 
612   {
613     popEnabled();
614     if (!m_hide) 
615     {
616       Doxygen::parserManager->getParser(m_langExt)
617                             ->parseCode(
618                                 m_ci,
619                                 op->context(),
620                                 op->text(),
621                                 op->isExample(),
622                                 op->exampleFile(),
623                                 0,     // fileDef
624                                 -1,    // startLine
625                                 -1,    // endLine
626                                 FALSE, // inline fragment
627                                 0,     // memberDef
628                                 TRUE,  // show line numbers
629                                 m_ctx  // search context
630                                );
631     }
632     pushEnabled();
633     m_hide=TRUE;
634   }
635   if (op->isLast())  
636   {
637     popEnabled();
638     if (!m_hide) m_t << PREFRAG_END;
639   }
640   else
641   {
642     if (!m_hide) m_t << endl;
643   }
644 }
645
646 void HtmlDocVisitor::visit(DocFormula *f)
647 {
648   if (m_hide) return;
649   bool bDisplay = !f->isInline();
650   if (bDisplay) 
651   {
652     forceEndParagraph(f);
653     m_t << "<p class=\"formulaDsp\">" << endl;
654   }
655
656   if (Config_getBool("USE_MATHJAX"))
657   {
658     QCString text = f->text();
659     bool closeInline = FALSE;
660     if (!bDisplay && !text.isEmpty() && text.at(0)=='$' && 
661                       text.at(text.length()-1)=='$')
662     {
663       closeInline=TRUE;
664       text = text.mid(1,text.length()-2);
665       m_t << "\\(";
666     }
667     m_t << convertToHtml(text);
668     if (closeInline)
669     {
670       m_t << "\\)";
671     }
672   }
673   else
674   {
675     m_t << "<img class=\"formula" 
676       << (bDisplay ? "Dsp" : "Inl");
677     m_t << "\" alt=\"";
678     filterQuotedCdataAttr(f->text());
679     m_t << "\"";
680     // TODO: cache image dimensions on formula generation and give height/width
681     // for faster preloading and better rendering of the page
682     m_t << " src=\"" << f->relPath() << f->name() << ".png\"/>";
683
684   }
685   if (bDisplay)
686   {
687     m_t << endl << "</p>" << endl;
688     forceStartParagraph(f);
689   }
690 }
691
692 void HtmlDocVisitor::visit(DocIndexEntry *e)
693 {
694   QCString anchor = convertIndexWordToAnchor(e->entry());
695   if (e->member()) 
696   {
697     anchor.prepend(e->member()->anchor()+"_");
698   }
699   m_t << "<a name=\"" << anchor << "\"></a>";
700   //printf("*** DocIndexEntry: word='%s' scope='%s' member='%s'\n",
701   //       e->entry().data(),
702   //       e->scope()  ? e->scope()->name().data()  : "<null>",
703   //       e->member() ? e->member()->name().data() : "<null>"
704   //      );
705   Doxygen::indexList.addIndexItem(e->scope(),e->member(),e->entry());
706 }
707
708 void HtmlDocVisitor::visit(DocSimpleSectSep *)
709 {
710   m_t << "</dd>" << endl;
711   m_t << "<dd>" << endl;
712 }
713
714 void HtmlDocVisitor::visit(DocCite *cite)
715 {
716   if (m_hide) return;
717   if (!cite->file().isEmpty()) 
718   {
719     startLink(cite->ref(),cite->file(),cite->relPath(),cite->anchor());
720   }
721   else
722   {
723     m_t << "<b>[";
724   }
725   filter(cite->text());
726   if (!cite->file().isEmpty()) 
727   {
728     endLink();
729   }
730   else
731   {
732     m_t << "]</b>";
733   }
734 }
735
736
737 //--------------------------------------
738 // visitor functions for compound nodes
739 //--------------------------------------
740
741
742 void HtmlDocVisitor::visitPre(DocAutoList *l)
743 {
744   //printf("DocAutoList::visitPre\n");
745   if (m_hide) return;
746   forceEndParagraph(l);
747   if (l->isEnumList())
748   {
749     //
750     // Do list type based on depth:
751     // 1.
752     //   a.
753     //     i.
754     //       A. 
755     //         1. (repeat)...
756     //
757     m_t << "<ol type=\"" << types[l->depth() % NUM_HTML_LIST_TYPES] << "\">";
758   }
759   else
760   {
761     m_t << "<ul>";
762   }
763   if (!l->isPreformatted()) m_t << "\n";
764 }
765
766 void HtmlDocVisitor::visitPost(DocAutoList *l)
767 {
768   //printf("DocAutoList::visitPost\n");
769   if (m_hide) return;
770   if (l->isEnumList())
771   {
772     m_t << "</ol>";
773   }
774   else
775   {
776     m_t << "</ul>";
777   }
778   if (!l->isPreformatted()) m_t << "\n";
779   forceStartParagraph(l);
780 }
781
782 void HtmlDocVisitor::visitPre(DocAutoListItem *)
783 {
784   if (m_hide) return;
785   m_t << "<li>";
786 }
787
788 void HtmlDocVisitor::visitPost(DocAutoListItem *li) 
789 {
790   if (m_hide) return;
791   m_t << "</li>";
792   if (!li->isPreformatted()) m_t << "\n";
793 }
794
795 template<class T> 
796 bool isFirstChildNode(T *parent, DocNode *node)
797 {
798    return parent->children().getFirst()==node;
799 }
800
801 template<class T> 
802 bool isLastChildNode(T *parent, DocNode *node)
803 {
804    return parent->children().getLast()==node;
805 }
806
807 bool isSeparatedParagraph(DocSimpleSect *parent,DocPara *par)
808 {
809   QList<DocNode> nodes = parent->children();
810   int i = nodes.findRef(par);
811   if (i==-1) return FALSE;
812   int count = parent->children().count();
813   if (count>1 && i==0)
814   {
815     if (nodes.at(i+1)->kind()==DocNode::Kind_SimpleSectSep)
816     {
817       return TRUE;
818     }
819   }
820   else if (count>1 && i==count-1)
821   {
822     if (nodes.at(i-1)->kind()==DocNode::Kind_SimpleSectSep)
823     {
824       return TRUE;
825     }
826   }
827   else if (count>2 && i>0 && i<count-1)
828   {
829     if (nodes.at(i-1)->kind()==DocNode::Kind_SimpleSectSep &&
830         nodes.at(i+1)->kind()==DocNode::Kind_SimpleSectSep)
831     {
832       return TRUE;
833     }
834   }
835   return FALSE;
836 }
837
838 static int getParagraphContext(DocPara *p,bool &isFirst,bool &isLast)
839 {
840   int t=0;
841   isFirst=FALSE;
842   isLast=FALSE;
843   if (p && p->parent())
844   {
845     switch (p->parent()->kind()) 
846     {
847       case DocNode::Kind_AutoListItem:
848         //isFirst=TRUE;
849         //isLast =TRUE;
850         isFirst=isFirstChildNode((DocAutoListItem*)p->parent(),p);
851         isLast =isLastChildNode ((DocAutoListItem*)p->parent(),p);
852         t=1; // not used
853         break;
854       case DocNode::Kind_SimpleListItem:
855         isFirst=TRUE;
856         isLast =TRUE;
857         t=1; // not used
858         break;
859       case DocNode::Kind_ParamList:
860         isFirst=TRUE;
861         isLast =TRUE;
862         t=1; // not used
863         break;
864       case DocNode::Kind_HtmlListItem:
865         isFirst=isFirstChildNode((DocHtmlListItem*)p->parent(),p);
866         isLast =isLastChildNode ((DocHtmlListItem*)p->parent(),p);
867         if (isFirst) t=1;
868         if (isLast)  t=3;
869         break;
870       case DocNode::Kind_SecRefItem:
871         isFirst=isFirstChildNode((DocSecRefItem*)p->parent(),p);
872         isLast =isLastChildNode ((DocSecRefItem*)p->parent(),p);
873         if (isFirst) t=1;
874         if (isLast)  t=3;
875         break;
876       case DocNode::Kind_HtmlDescData:
877         isFirst=isFirstChildNode((DocHtmlDescData*)p->parent(),p);
878         isLast =isLastChildNode ((DocHtmlDescData*)p->parent(),p);
879         if (isFirst) t=2;
880         if (isLast)  t=4;
881         break;
882       case DocNode::Kind_XRefItem:
883         isFirst=isFirstChildNode((DocXRefItem*)p->parent(),p);
884         isLast =isLastChildNode ((DocXRefItem*)p->parent(),p);
885         if (isFirst) t=2;
886         if (isLast)  t=4;
887         break;
888       case DocNode::Kind_HtmlCell:
889         isFirst=isFirstChildNode((DocHtmlCell*)p->parent(),p);
890         isLast =isLastChildNode ((DocHtmlCell*)p->parent(),p);
891         if (isFirst) t=5;
892         if (isLast)  t=6;
893         break;
894       case DocNode::Kind_SimpleSect:
895         isFirst=isFirstChildNode((DocSimpleSect*)p->parent(),p);
896         isLast =isLastChildNode ((DocSimpleSect*)p->parent(),p);
897         if (isFirst) t=2;
898         if (isLast)  t=4;
899         if (isSeparatedParagraph((DocSimpleSect*)p->parent(),p))
900           // if the paragraph is enclosed with separators it will
901           // be included in <dd>..</dd> so avoid addition paragraph
902           // markers
903         {
904           isFirst=isLast=TRUE;
905         }
906         break;
907       default:
908         break;
909     }
910     //printf("para=%p parent()->kind=%d isFirst=%d isLast=%d t=%d\n",
911     //    p,p->parent()->kind(),isFirst,isLast,t);
912   }
913   return t;
914 }
915
916 void HtmlDocVisitor::visitPre(DocPara *p) 
917 {
918   if (m_hide) return;
919
920   //printf("DocPara::visitPre: parent of kind %d ",
921   //       p->parent() ? p->parent()->kind() : -1);
922
923   bool needsTag = FALSE;
924   if (p && p->parent()) 
925   {
926     switch (p->parent()->kind()) 
927     {
928       case DocNode::Kind_Section:
929       case DocNode::Kind_Internal:
930       case DocNode::Kind_HtmlListItem:
931       case DocNode::Kind_HtmlDescData:
932       case DocNode::Kind_HtmlCell:
933       case DocNode::Kind_SimpleListItem:
934       case DocNode::Kind_AutoListItem:
935       case DocNode::Kind_SimpleSect:
936       case DocNode::Kind_XRefItem:
937       case DocNode::Kind_Copy:
938       case DocNode::Kind_HtmlBlockQuote:
939         needsTag = TRUE;
940         break;
941       case DocNode::Kind_Root:
942         needsTag = !((DocRoot*)p->parent())->singleLine();
943         break;
944       default:
945         needsTag = FALSE;
946     }
947   }
948
949   // if the first element of a paragraph is something that should be outside of
950   // the paragraph (<ul>,<dl>,<table>,..) then that will already started the 
951   // paragraph and we don't need to do it here
952   uint nodeIndex = 0;
953   if (p && nodeIndex<p->children().count())
954   {
955     while (nodeIndex<p->children().count() && 
956            p->children().at(nodeIndex)->kind()==DocNode::Kind_WhiteSpace)
957     {
958       nodeIndex++;
959     }
960     if (nodeIndex<p->children().count())
961     {
962       DocNode *n = p->children().at(nodeIndex);
963       if (mustBeOutsideParagraph(n))
964       {
965         needsTag = FALSE;
966       }
967     }
968   }
969
970   // check if this paragraph is the first or last child of a <li> or <dd>.
971   // this allows us to mark the tag with a special class so we can
972   // fix the otherwise ugly spacing.
973   int t;
974   static const char *contexts[7] = 
975   { "",                     // 0
976     " class=\"startli\"",   // 1
977     " class=\"startdd\"",   // 2
978     " class=\"endli\"",     // 3
979     " class=\"enddd\"",     // 4
980     " class=\"starttd\"",   // 5
981     " class=\"endtd\""      // 6
982   };
983   bool isFirst;
984   bool isLast;
985   t = getParagraphContext(p,isFirst,isLast);
986   //printf("startPara first=%d last=%d\n",isFirst,isLast);
987   if (isFirst && isLast) needsTag=FALSE;
988
989   //printf("  needsTag=%d\n",needsTag);
990   // write the paragraph tag (if needed)
991   if (needsTag) m_t << "<p" << contexts[t] << ">";
992 }
993
994 void HtmlDocVisitor::visitPost(DocPara *p)
995 {
996   bool needsTag = FALSE;
997   if (p && p->parent()) 
998   {
999     switch (p->parent()->kind()) 
1000     {
1001       case DocNode::Kind_Section:
1002       case DocNode::Kind_Internal:
1003       case DocNode::Kind_HtmlListItem:
1004       case DocNode::Kind_HtmlDescData:
1005       case DocNode::Kind_HtmlCell:
1006       case DocNode::Kind_SimpleListItem:
1007       case DocNode::Kind_AutoListItem:
1008       case DocNode::Kind_SimpleSect:
1009       case DocNode::Kind_XRefItem:
1010       case DocNode::Kind_Copy:
1011       case DocNode::Kind_HtmlBlockQuote:
1012         needsTag = TRUE;
1013         break;
1014       case DocNode::Kind_Root:
1015         needsTag = !((DocRoot*)p->parent())->singleLine();
1016         break;
1017       default:
1018         needsTag = FALSE;
1019     }
1020   }
1021
1022   QCString context;
1023   // if the last element of a paragraph is something that should be outside of
1024   // the paragraph (<ul>,<dl>,<table>) then that will already have ended the 
1025   // paragraph and we don't need to do it here
1026   int nodeIndex = p->children().count()-1;
1027   if (p && nodeIndex>=0)
1028   {
1029     while (nodeIndex>=0 && p->children().at(nodeIndex)->kind()==DocNode::Kind_WhiteSpace)
1030     {
1031       nodeIndex--;
1032     }
1033     if (nodeIndex>=0)
1034     {
1035       DocNode *n = p->children().at(nodeIndex);
1036       if (mustBeOutsideParagraph(n))
1037       {
1038         needsTag = FALSE;
1039       }
1040     }
1041   }
1042
1043   bool isFirst;
1044   bool isLast;
1045   getParagraphContext(p,isFirst,isLast);
1046   //printf("endPara first=%d last=%d\n",isFirst,isLast);
1047   if (isFirst && isLast) needsTag=FALSE;
1048
1049   //printf("DocPara::visitPost needsTag=%d\n",needsTag);
1050
1051   if (needsTag) m_t << "</p>\n";
1052
1053 }
1054
1055 void HtmlDocVisitor::visitPre(DocRoot *)
1056 {
1057 }
1058
1059 void HtmlDocVisitor::visitPost(DocRoot *)
1060 {
1061 }
1062
1063 void HtmlDocVisitor::visitPre(DocSimpleSect *s)
1064 {
1065   if (m_hide) return;
1066   forceEndParagraph(s);
1067   m_t << "<dl class=\"section " << s->typeString() << "\"><dt>";
1068   switch(s->type())
1069   {
1070     case DocSimpleSect::See: 
1071       m_t << theTranslator->trSeeAlso(); break;
1072     case DocSimpleSect::Return: 
1073       m_t << theTranslator->trReturns(); break;
1074     case DocSimpleSect::Author: 
1075       m_t << theTranslator->trAuthor(TRUE,TRUE); break;
1076     case DocSimpleSect::Authors: 
1077       m_t << theTranslator->trAuthor(TRUE,FALSE); break;
1078     case DocSimpleSect::Version: 
1079       m_t << theTranslator->trVersion(); break;
1080     case DocSimpleSect::Since: 
1081       m_t << theTranslator->trSince(); break;
1082     case DocSimpleSect::Date: 
1083       m_t << theTranslator->trDate(); break;
1084     case DocSimpleSect::Note: 
1085       m_t << theTranslator->trNote(); break;
1086     case DocSimpleSect::Warning:
1087       m_t << theTranslator->trWarning(); break;
1088     case DocSimpleSect::Pre:
1089       m_t << theTranslator->trPrecondition(); break;
1090     case DocSimpleSect::Post:
1091       m_t << theTranslator->trPostcondition(); break;
1092     case DocSimpleSect::Copyright:
1093       m_t << theTranslator->trCopyright(); break;
1094     case DocSimpleSect::Invar:
1095       m_t << theTranslator->trInvariant(); break;
1096     case DocSimpleSect::Remark:
1097       m_t << theTranslator->trRemarks(); break;
1098     case DocSimpleSect::Attention:
1099       m_t << theTranslator->trAttention(); break;
1100     case DocSimpleSect::User: break;
1101     case DocSimpleSect::Rcs: break;
1102     case DocSimpleSect::Unknown:  break;
1103   }
1104
1105   // special case 1: user defined title
1106   if (s->type()!=DocSimpleSect::User && s->type()!=DocSimpleSect::Rcs)
1107   {
1108     m_t << "</dt><dd>";
1109   }
1110 }
1111
1112 void HtmlDocVisitor::visitPost(DocSimpleSect *s)
1113 {
1114   if (m_hide) return;
1115   m_t << "</dd></dl>\n";
1116   forceStartParagraph(s);
1117 }
1118
1119 void HtmlDocVisitor::visitPre(DocTitle *)
1120 {
1121 }
1122
1123 void HtmlDocVisitor::visitPost(DocTitle *)
1124 {
1125   if (m_hide) return;
1126   m_t << "</dt><dd>";
1127 }
1128
1129 void HtmlDocVisitor::visitPre(DocSimpleList *sl)
1130 {
1131   if (m_hide) return;
1132   forceEndParagraph(sl);
1133   m_t << "<ul>";
1134   if (!sl->isPreformatted()) m_t << "\n";
1135
1136 }
1137
1138 void HtmlDocVisitor::visitPost(DocSimpleList *sl)
1139 {
1140   if (m_hide) return;
1141   m_t << "</ul>";
1142   if (!sl->isPreformatted()) m_t << "\n";
1143   forceStartParagraph(sl);
1144 }
1145
1146 void HtmlDocVisitor::visitPre(DocSimpleListItem *)
1147 {
1148   if (m_hide) return;
1149   m_t << "<li>";
1150 }
1151
1152 void HtmlDocVisitor::visitPost(DocSimpleListItem *li) 
1153 {
1154   if (m_hide) return;
1155   m_t << "</li>";
1156   if (!li->isPreformatted()) m_t << "\n";
1157 }
1158
1159 void HtmlDocVisitor::visitPre(DocSection *s)
1160 {
1161   if (m_hide) return;
1162   forceEndParagraph(s);
1163   m_t << "<h" << s->level() << ">";
1164   m_t << "<a class=\"anchor\" id=\"" << s->anchor();
1165   m_t << "\"></a>" << endl;
1166   filter(convertCharEntitiesToUTF8(s->title().data()));
1167   m_t << "</h" << s->level() << ">\n";
1168 }
1169
1170 void HtmlDocVisitor::visitPost(DocSection *s) 
1171 {
1172   forceStartParagraph(s);
1173 }
1174
1175 void HtmlDocVisitor::visitPre(DocHtmlList *s)
1176 {
1177   if (m_hide) return;
1178   forceEndParagraph(s);
1179   if (s->type()==DocHtmlList::Ordered) 
1180   {
1181     m_t << "<ol" << htmlAttribsToString(s->attribs()) << ">\n"; 
1182   }
1183   else 
1184   {
1185     m_t << "<ul" << htmlAttribsToString(s->attribs()) << ">\n";
1186   }
1187 }
1188
1189 void HtmlDocVisitor::visitPost(DocHtmlList *s) 
1190 {
1191   if (m_hide) return;
1192   if (s->type()==DocHtmlList::Ordered) 
1193   {
1194     m_t << "</ol>"; 
1195   }
1196   else
1197   { 
1198     m_t << "</ul>";
1199   }
1200   if (!s->isPreformatted()) m_t << "\n";
1201   forceStartParagraph(s);
1202 }
1203
1204 void HtmlDocVisitor::visitPre(DocHtmlListItem *i)
1205 {
1206   if (m_hide) return;
1207   m_t << "<li" << htmlAttribsToString(i->attribs()) << ">";
1208   if (!i->isPreformatted()) m_t << "\n";
1209 }
1210
1211 void HtmlDocVisitor::visitPost(DocHtmlListItem *) 
1212 {
1213   if (m_hide) return;
1214   m_t << "</li>\n";
1215 }
1216
1217 void HtmlDocVisitor::visitPre(DocHtmlDescList *dl)
1218 {
1219   if (m_hide) return;
1220   forceEndParagraph(dl);
1221   m_t << "<dl" << htmlAttribsToString(dl->attribs()) << ">\n";
1222 }
1223
1224 void HtmlDocVisitor::visitPost(DocHtmlDescList *dl) 
1225 {
1226   if (m_hide) return;
1227   m_t << "</dl>\n";
1228   forceStartParagraph(dl);
1229 }
1230
1231 void HtmlDocVisitor::visitPre(DocHtmlDescTitle *dt)
1232 {
1233   if (m_hide) return;
1234   m_t << "<dt" << htmlAttribsToString(dt->attribs()) << ">";
1235 }
1236
1237 void HtmlDocVisitor::visitPost(DocHtmlDescTitle *) 
1238 {
1239   if (m_hide) return;
1240   m_t << "</dt>\n";
1241 }
1242
1243 void HtmlDocVisitor::visitPre(DocHtmlDescData *dd)
1244 {
1245   if (m_hide) return;
1246   m_t << "<dd" << htmlAttribsToString(dd->attribs()) << ">";
1247 }
1248
1249 void HtmlDocVisitor::visitPost(DocHtmlDescData *) 
1250 {
1251   if (m_hide) return;
1252   m_t << "</dd>\n";
1253 }
1254
1255 void HtmlDocVisitor::visitPre(DocHtmlTable *t)
1256 {
1257   if (m_hide) return;
1258
1259   forceEndParagraph(t);
1260
1261   QString attrs = htmlAttribsToString(t->attribs());
1262   if (attrs.isEmpty())
1263   {
1264     m_t << "<table class=\"doxtable\">\n";
1265   }
1266   else
1267   {
1268     m_t << "<table " << htmlAttribsToString(t->attribs()) << ">\n";
1269   }
1270 }
1271
1272 void HtmlDocVisitor::visitPost(DocHtmlTable *t) 
1273 {
1274   if (m_hide) return;
1275   m_t << "</table>\n";
1276   forceStartParagraph(t);
1277 }
1278
1279 void HtmlDocVisitor::visitPre(DocHtmlRow *tr)
1280 {
1281   if (m_hide) return;
1282   m_t << "<tr" << htmlAttribsToString(tr->attribs()) << ">\n";
1283 }
1284
1285 void HtmlDocVisitor::visitPost(DocHtmlRow *) 
1286 {
1287   if (m_hide) return;
1288   m_t << "</tr>\n";
1289 }
1290
1291 void HtmlDocVisitor::visitPre(DocHtmlCell *c)
1292 {
1293   if (m_hide) return;
1294   if (c->isHeading()) 
1295   {
1296     m_t << "<th" << htmlAttribsToString(c->attribs()) << ">"; 
1297   }
1298   else 
1299   {
1300     m_t << "<td" << htmlAttribsToString(c->attribs()) << ">";
1301   }
1302 }
1303
1304 void HtmlDocVisitor::visitPost(DocHtmlCell *c) 
1305 {
1306   if (m_hide) return;
1307   if (c->isHeading()) m_t << "</th>"; else m_t << "</td>";
1308 }
1309
1310 void HtmlDocVisitor::visitPre(DocHtmlCaption *c)
1311 {
1312   if (m_hide) return;
1313   bool hasAlign      = FALSE;
1314   HtmlAttribListIterator li(c->attribs());
1315   HtmlAttrib *att;
1316   for (li.toFirst();(att=li.current());++li)
1317   {
1318     if (att->name=="align") hasAlign=TRUE;
1319   }
1320   m_t << "<caption" << htmlAttribsToString(c->attribs());
1321   if (!hasAlign) m_t << " align=\"bottom\"";
1322   m_t << ">";
1323 }
1324
1325 void HtmlDocVisitor::visitPost(DocHtmlCaption *) 
1326 {
1327   if (m_hide) return;
1328   m_t << "</caption>\n";
1329 }
1330
1331 void HtmlDocVisitor::visitPre(DocInternal *)
1332 {
1333   if (m_hide) return;
1334   //forceEndParagraph(i);
1335   //m_t << "<p><b>" << theTranslator->trForInternalUseOnly() << "</b></p>" << endl;
1336 }
1337
1338 void HtmlDocVisitor::visitPost(DocInternal *) 
1339 {
1340   if (m_hide) return;
1341   //forceStartParagraph(i);
1342 }
1343
1344 void HtmlDocVisitor::visitPre(DocHRef *href)
1345 {
1346   if (m_hide) return;
1347   if (href->url().left(7)=="mailto:")
1348   {
1349     writeObfuscatedMailAddress(href->url().mid(7));
1350   }
1351   else
1352   {
1353     QCString url = correctURL(href->url(),href->relPath());
1354     m_t << "<a href=\"" << convertToXML(url)  << "\""
1355         << htmlAttribsToString(href->attribs()) << ">";
1356   }
1357 }
1358
1359 void HtmlDocVisitor::visitPost(DocHRef *) 
1360 {
1361   if (m_hide) return;
1362   m_t << "</a>";
1363 }
1364
1365 void HtmlDocVisitor::visitPre(DocHtmlHeader *header)
1366 {
1367   if (m_hide) return;
1368   forceEndParagraph(header);
1369   m_t << "<h" << header->level() 
1370       << htmlAttribsToString(header->attribs()) << ">";
1371 }
1372
1373 void HtmlDocVisitor::visitPost(DocHtmlHeader *header) 
1374 {
1375   if (m_hide) return;
1376   m_t << "</h" << header->level() << ">\n";
1377   forceStartParagraph(header);
1378 }
1379
1380 void HtmlDocVisitor::visitPre(DocImage *img)
1381 {
1382   if (img->type()==DocImage::Html)
1383   {
1384     forceEndParagraph(img);
1385     if (m_hide) return;
1386     QString baseName=img->name();
1387     int i;
1388     if ((i=baseName.findRev('/'))!=-1 || (i=baseName.findRev('\\'))!=-1)
1389     {
1390       baseName=baseName.right(baseName.length()-i-1);
1391     }
1392     m_t << "<div class=\"image\">" << endl;
1393     QCString url = img->url();
1394     if (url.isEmpty())
1395     {
1396       m_t << "<img src=\"" << img->relPath() << img->name() << "\" alt=\"" 
1397           << baseName << "\"" << htmlAttribsToString(img->attribs()) 
1398           << "/>" << endl;
1399     }
1400     else
1401     {
1402       m_t << "<img src=\"" << correctURL(url,img->relPath()) << "\" " 
1403           << htmlAttribsToString(img->attribs())
1404           << "/>" << endl;
1405     }
1406     if (img->hasCaption())
1407     {
1408       m_t << "<div class=\"caption\">" << endl;
1409     }
1410   }
1411   else // other format -> skip
1412   {
1413     pushEnabled();
1414     m_hide=TRUE;
1415   }
1416 }
1417
1418 void HtmlDocVisitor::visitPost(DocImage *img) 
1419 {
1420   if (img->type()==DocImage::Html)
1421   {
1422     if (m_hide) return;
1423     if (img->hasCaption())
1424     {
1425       m_t << "</div>";
1426     }
1427     m_t << "</div>" << endl;
1428     forceStartParagraph(img);
1429   }
1430   else // other format
1431   {
1432     popEnabled();
1433   }
1434 }
1435
1436 void HtmlDocVisitor::visitPre(DocDotFile *df)
1437 {
1438   if (m_hide) return;
1439   m_t << "<div class=\"dotgraph\">" << endl;
1440   writeDotFile(df->file(),df->relPath(),df->context());
1441   if (df->hasCaption())
1442   { 
1443     m_t << "<div class=\"caption\">" << endl;
1444   }
1445 }
1446
1447 void HtmlDocVisitor::visitPost(DocDotFile *df) 
1448 {
1449   if (m_hide) return;
1450   if (df->hasCaption())
1451   {
1452     m_t << "</div>" << endl;
1453   }
1454   m_t << "</div>" << endl;
1455 }
1456
1457 void HtmlDocVisitor::visitPre(DocMscFile *df)
1458 {
1459   if (m_hide) return;
1460   m_t << "<div class=\"mscgraph\">" << endl;
1461   writeMscFile(df->file(),df->relPath(),df->context());
1462   if (df->hasCaption())
1463   { 
1464     m_t << "<div class=\"caption\">" << endl;
1465   }
1466 }
1467 void HtmlDocVisitor::visitPost(DocMscFile *df) 
1468 {
1469   if (m_hide) return;
1470   if (df->hasCaption())
1471   {
1472     m_t << "</div>" << endl;
1473   }
1474   m_t << "</div>" << endl;
1475 }
1476
1477 void HtmlDocVisitor::visitPre(DocLink *lnk)
1478 {
1479   if (m_hide) return;
1480   startLink(lnk->ref(),lnk->file(),lnk->relPath(),lnk->anchor());
1481 }
1482
1483 void HtmlDocVisitor::visitPost(DocLink *) 
1484 {
1485   if (m_hide) return;
1486   endLink();
1487 }
1488
1489 void HtmlDocVisitor::visitPre(DocRef *ref)
1490 {
1491   if (m_hide) return;
1492   if (!ref->file().isEmpty()) 
1493   {
1494     // when ref->isSubPage()==TRUE we use ref->file() for HTML and
1495     // ref->anchor() for LaTeX/RTF
1496     startLink(ref->ref(),ref->file(),ref->relPath(),ref->isSubPage() ? QCString() : ref->anchor());
1497   }
1498   if (!ref->hasLinkText()) filter(ref->targetTitle());
1499 }
1500
1501 void HtmlDocVisitor::visitPost(DocRef *ref) 
1502 {
1503   if (m_hide) return;
1504   if (!ref->file().isEmpty()) endLink();
1505   //m_t << " ";
1506 }
1507
1508 void HtmlDocVisitor::visitPre(DocSecRefItem *ref)
1509 {
1510   if (m_hide) return;
1511   QString refName=ref->file();
1512   if (refName.right(Doxygen::htmlFileExtension.length())!=
1513       QString(Doxygen::htmlFileExtension))
1514   {
1515     refName+=Doxygen::htmlFileExtension;
1516   }
1517   m_t << "<li><a href=\"" << refName << "#" << ref->anchor() << "\">";
1518
1519 }
1520
1521 void HtmlDocVisitor::visitPost(DocSecRefItem *) 
1522 {
1523   if (m_hide) return;
1524   m_t << "</a></li>\n";
1525 }
1526
1527 void HtmlDocVisitor::visitPre(DocSecRefList *s)
1528 {
1529   if (m_hide) return;
1530   forceEndParagraph(s);
1531   m_t << "<div class=\"multicol\">" << endl;
1532   m_t << "<ul>" << endl;
1533 }
1534
1535 void HtmlDocVisitor::visitPost(DocSecRefList *s) 
1536 {
1537   if (m_hide) return;
1538   m_t << "</ul>" << endl;
1539   m_t << "</div>" << endl;
1540   forceStartParagraph(s);
1541 }
1542
1543 //void HtmlDocVisitor::visitPre(DocLanguage *l)
1544 //{
1545 //  QString langId = Config_getEnum("OUTPUT_LANGUAGE");
1546 //  if (l->id().lower()!=langId.lower())
1547 //  {
1548 //    pushEnabled();
1549 //    m_hide = TRUE;
1550 //  }
1551 //}
1552 //
1553 //void HtmlDocVisitor::visitPost(DocLanguage *l) 
1554 //{
1555 //  QString langId = Config_getEnum("OUTPUT_LANGUAGE");
1556 //  if (l->id().lower()!=langId.lower())
1557 //  {
1558 //    popEnabled();
1559 //  }
1560 //}
1561
1562 void HtmlDocVisitor::visitPre(DocParamSect *s)
1563 {
1564   if (m_hide) return;
1565   forceEndParagraph(s);
1566   QCString className;
1567   QCString heading;
1568   switch(s->type())
1569   {
1570     case DocParamSect::Param: 
1571       heading=theTranslator->trParameters(); 
1572       className="params";
1573       break;
1574     case DocParamSect::RetVal: 
1575       heading=theTranslator->trReturnValues(); 
1576       className="retval";
1577       break;
1578     case DocParamSect::Exception: 
1579       heading=theTranslator->trExceptions(); 
1580       className="exception";
1581       break;
1582     case DocParamSect::TemplateParam: 
1583       heading=theTranslator->trTemplateParameters();
1584       className="tparams";
1585       break;
1586     default:
1587       ASSERT(0);
1588   }
1589   m_t << "<dl class=\"" << className << "\"><dt>";
1590   m_t << heading;
1591   m_t << "</dt><dd>" << endl;
1592   m_t << "  <table class=\"" << className << "\">" << endl;
1593 }
1594
1595 void HtmlDocVisitor::visitPost(DocParamSect *s)
1596 {
1597   if (m_hide) return;
1598   m_t << "  </table>" << endl;
1599   m_t << "  </dd>" << endl;
1600   m_t << "</dl>" << endl;
1601   forceStartParagraph(s);
1602 }
1603
1604 void HtmlDocVisitor::visitPre(DocParamList *pl)
1605 {
1606   //printf("DocParamList::visitPre\n");
1607   if (m_hide) return;
1608   m_t << "    <tr>";
1609   DocParamSect *sect = 0;
1610   if (pl->parent()->kind()==DocNode::Kind_ParamSect)
1611   {
1612     sect=(DocParamSect*)pl->parent();
1613   }
1614   if (sect && sect->hasInOutSpecifier())
1615   {
1616     m_t << "<td class=\"paramdir\">";
1617     if (pl->direction()!=DocParamSect::Unspecified)
1618     {
1619       m_t << "[";
1620       if (pl->direction()==DocParamSect::In)
1621       {
1622         m_t << "in";
1623       }
1624       else if (pl->direction()==DocParamSect::Out)
1625       {
1626         m_t << "out";
1627       }
1628       else if (pl->direction()==DocParamSect::InOut)
1629       {
1630         m_t << "in,out";
1631       }
1632       m_t << "]";
1633     }
1634     m_t << "</td>";
1635   }
1636   if (sect && sect->hasTypeSpecifier())
1637   {
1638     m_t << "<td class=\"paramtype\">";
1639     QListIterator<DocNode> li(pl->paramTypes());
1640     DocNode *type;
1641     bool first=TRUE;
1642     for (li.toFirst();(type=li.current());++li)
1643     {
1644       if (!first) m_t << "&#160;|&#160;"; else first=FALSE;
1645       if (type->kind()==DocNode::Kind_Word)
1646       {
1647         visit((DocWord*)type); 
1648       }
1649       else if (type->kind()==DocNode::Kind_LinkedWord)
1650       {
1651         visit((DocLinkedWord*)type); 
1652       }
1653     }
1654     m_t << "</td>";
1655   }
1656   m_t << "<td class=\"paramname\">";
1657   //QStrListIterator li(pl->parameters());
1658   //const char *s;
1659   QListIterator<DocNode> li(pl->parameters());
1660   DocNode *param;
1661   bool first=TRUE;
1662   for (li.toFirst();(param=li.current());++li)
1663   {
1664     if (!first) m_t << ","; else first=FALSE;
1665     if (param->kind()==DocNode::Kind_Word)
1666     {
1667       visit((DocWord*)param); 
1668     }
1669     else if (param->kind()==DocNode::Kind_LinkedWord)
1670     {
1671       visit((DocLinkedWord*)param); 
1672     }
1673   }
1674   m_t << "</td><td>";
1675 }
1676
1677 void HtmlDocVisitor::visitPost(DocParamList *)
1678 {
1679   //printf("DocParamList::visitPost\n");
1680   if (m_hide) return;
1681   m_t << "</td></tr>" << endl;
1682 }
1683
1684 void HtmlDocVisitor::visitPre(DocXRefItem *x)
1685 {
1686   if (m_hide) return;
1687   forceEndParagraph(x);
1688   bool anonymousEnum = x->file()=="@";
1689   if (!anonymousEnum)
1690   {
1691     m_t << "<dl class=\"" << x->key() << "\"><dt><b><a class=\"el\" href=\"" 
1692         << x->relPath() << x->file() << Doxygen::htmlFileExtension 
1693         << "#" << x->anchor() << "\">";
1694   }
1695   else 
1696   {
1697     m_t << "<dl class=\"" << x->key() << "\"><dt><b>";
1698   }
1699   filter(x->title());
1700   m_t << ":";
1701   if (!anonymousEnum) m_t << "</a>";
1702   m_t << "</b></dt><dd>";
1703 }
1704
1705 void HtmlDocVisitor::visitPost(DocXRefItem *x)
1706 {
1707   if (m_hide) return;
1708   m_t << "</dd></dl>" << endl;
1709   forceStartParagraph(x);
1710 }
1711
1712 void HtmlDocVisitor::visitPre(DocInternalRef *ref)
1713 {
1714   if (m_hide) return;
1715   startLink(0,ref->file(),ref->relPath(),ref->anchor());
1716 }
1717
1718 void HtmlDocVisitor::visitPost(DocInternalRef *) 
1719 {
1720   if (m_hide) return;
1721   endLink();
1722   m_t << " ";
1723 }
1724
1725 void HtmlDocVisitor::visitPre(DocCopy *)
1726 {
1727 }
1728
1729 void HtmlDocVisitor::visitPost(DocCopy *)
1730 {
1731 }
1732
1733 void HtmlDocVisitor::visitPre(DocText *)
1734 {
1735 }
1736
1737 void HtmlDocVisitor::visitPost(DocText *)
1738 {
1739 }
1740
1741 void HtmlDocVisitor::visitPre(DocHtmlBlockQuote *b)
1742 {
1743   if (m_hide) return;
1744   forceEndParagraph(b);
1745
1746   QString attrs = htmlAttribsToString(b->attribs());
1747   if (attrs.isEmpty())
1748   {
1749     m_t << "<blockquote class=\"doxtable\">\n";
1750   }
1751   else
1752   {
1753     m_t << "<blockquote " << htmlAttribsToString(b->attribs()) << ">\n";
1754   }
1755 }
1756
1757 void HtmlDocVisitor::visitPost(DocHtmlBlockQuote *b)
1758 {
1759   if (m_hide) return;
1760   m_t << "</blockquote>" << endl;
1761   forceStartParagraph(b);
1762 }
1763
1764 void HtmlDocVisitor::filter(const char *str)
1765
1766   if (str==0) return;
1767   const char *p=str;
1768   char c;
1769   while (*p)
1770   {
1771     c=*p++;
1772     switch(c)
1773     {
1774       case '<':  m_t << "&lt;"; break;
1775       case '>':  m_t << "&gt;"; break;
1776       case '&':  m_t << "&amp;"; break;
1777       default:   m_t << c;
1778     }
1779   }
1780 }
1781
1782 /// Escape basic entities to produce a valid CDATA attribute value,
1783 /// assume that the outer quoting will be using the double quote &quot;
1784 void HtmlDocVisitor::filterQuotedCdataAttr(const char* str)
1785 {
1786   if (str==0) return;
1787   const char *p=str;
1788   char c;
1789   while (*p)
1790   {
1791     c=*p++;
1792     switch(c)
1793     {
1794       case '&':  m_t << "&amp;"; break;
1795       case '"':  m_t << "&quot;"; break;
1796        // For SGML compliance, and given the SGML declaration for HTML syntax,
1797        // it's enough to replace these two, provided that the declaration
1798        // for the HTML version we generate (and as supported by the browser)
1799        // specifies that all the other symbols used in rawVal are
1800        // within the right charachter class (i.e., they're not
1801        // some multinational weird charachters not in the BASESET).
1802        // We assume that 1) the browser will support whatever is remaining
1803        // in the formula and 2) the TeX formulae are generally governed
1804        // by even stricter charachter restrictions so it should be enough.
1805        //
1806        // On some incompliant browsers, additional translation of
1807        // '>' and '<' into "&gt;" and "&lt;", respectively, might be needed;
1808        // but I'm unaware of particular modern (last 4 years) versions
1809        // with such problems, so let's not do it for performance.
1810        // Also, some brousers will (wrongly) not process the entity references
1811        // inside the attribute value and show the &...; form instead,  
1812        // so we won't create entites unless necessary to minimize clutter there.
1813        // --vassilii 
1814       default:   m_t << c;
1815     }
1816   }
1817 }
1818
1819 void HtmlDocVisitor::startLink(const QCString &ref,const QCString &file,
1820                                const QCString &relPath,const QCString &anchor,
1821                                const QCString &tooltip)
1822 {
1823   if (!ref.isEmpty()) // link to entity imported via tag file
1824   {
1825     m_t << "<a class=\"elRef\" ";
1826     m_t << externalLinkTarget() << externalRef(relPath,ref,FALSE);
1827   }
1828   else // local link
1829   {
1830     m_t << "<a class=\"el\" ";
1831   }
1832   m_t << "href=\"";
1833   m_t << externalRef(relPath,ref,TRUE);
1834   if (!file.isEmpty()) m_t << file << Doxygen::htmlFileExtension;
1835   if (!anchor.isEmpty()) m_t << "#" << anchor;
1836   m_t << "\"";
1837   if (!tooltip.isEmpty()) m_t << " title=\"" << substitute(tooltip,"\"","&quot;") << "\"";
1838   m_t << ">";
1839 }
1840
1841 void HtmlDocVisitor::endLink()
1842 {
1843   m_t << "</a>";
1844 }
1845
1846 void HtmlDocVisitor::pushEnabled()
1847 {
1848   m_enabled.push(new bool(m_hide));
1849 }
1850
1851 void HtmlDocVisitor::popEnabled()
1852 {
1853   bool *v=m_enabled.pop();
1854   ASSERT(v!=0);
1855   m_hide = *v;
1856   delete v;
1857 }
1858
1859 void HtmlDocVisitor::writeDotFile(const QCString &fn,const QCString &relPath,
1860                                   const QCString &context)
1861 {
1862   QCString baseName=fn;
1863   int i;
1864   if ((i=baseName.findRev('/'))!=-1)
1865   {
1866     baseName=baseName.right(baseName.length()-i-1);
1867   }
1868   if ((i=baseName.find('.'))!=-1) // strip extension
1869   {
1870     baseName=baseName.left(i);
1871   }
1872   baseName.prepend("dot_");
1873   QCString outDir = Config_getString("HTML_OUTPUT");
1874   writeDotGraphFromFile(fn,outDir,baseName,BITMAP);
1875   writeDotImageMapFromFile(m_t,fn,outDir,relPath,baseName,context);
1876 }
1877
1878 void HtmlDocVisitor::writeMscFile(const QCString &fileName,
1879                                   const QCString &relPath,
1880                                   const QCString &context)
1881 {
1882   QCString baseName=fileName;
1883   int i;
1884   if ((i=baseName.findRev('/'))!=-1) // strip path
1885   {
1886     baseName=baseName.right(baseName.length()-i-1);
1887   }
1888   if ((i=baseName.find('.'))!=-1) // strip extension
1889   {
1890     baseName=baseName.left(i);
1891   }
1892   baseName.prepend("msc_");
1893   QCString outDir = Config_getString("HTML_OUTPUT");
1894   writeMscGraphFromFile(fileName,outDir,baseName,MSC_BITMAP);
1895   writeMscImageMapFromFile(m_t,fileName,outDir,relPath,baseName,context);
1896 }
1897
1898 /** Used for items found inside a paragraph, which due to XHTML restrictions
1899  *  have to be outside of the paragraph. This method will forcefully end
1900  *  the current paragraph and forceStartParagraph() will restart it.
1901  */
1902 void HtmlDocVisitor::forceEndParagraph(DocNode *n)
1903 {
1904   //printf("forceEndParagraph(%p) %d\n",n,n->kind());
1905   if (n->parent() && n->parent()->kind()==DocNode::Kind_Para)
1906   {
1907     DocPara *para = (DocPara*)n->parent();
1908     int nodeIndex = para->children().findRef(n);
1909     nodeIndex--;
1910     if (nodeIndex<0) return; // first node
1911     while (nodeIndex>=0 && 
1912            para->children().at(nodeIndex)->kind()==DocNode::Kind_WhiteSpace
1913           )
1914     {
1915       nodeIndex--;
1916     }
1917     if (nodeIndex>=0)
1918     {
1919       DocNode *n = para->children().at(nodeIndex);
1920       //printf("n=%p kind=%d outside=%d\n",n,n->kind(),mustBeOutsideParagraph(n));
1921       if (mustBeOutsideParagraph(n)) return;
1922     }
1923
1924     bool isFirst;
1925     bool isLast;
1926     getParagraphContext(para,isFirst,isLast);
1927     //printf("forceEnd first=%d last=%d\n",isFirst,isLast);
1928     if (isFirst && isLast) return;
1929
1930     m_t << "</p>" << endl;
1931   }
1932 }
1933
1934 /** Used for items found inside a paragraph, which due to XHTML restrictions
1935  *  have to be outside of the paragraph. This method will forcefully start
1936  *  the paragraph, that was previously ended by forceEndParagraph().
1937  */
1938 void HtmlDocVisitor::forceStartParagraph(DocNode *n)
1939 {
1940   //printf("forceStartParagraph(%p) %d\n",n,n->kind());
1941   if (n->parent() && n->parent()->kind()==DocNode::Kind_Para) // if we are inside a paragraph
1942   {
1943     DocPara *para = (DocPara*)n->parent();
1944     int nodeIndex = para->children().findRef(n);
1945     int numNodes  = para->children().count();
1946     nodeIndex++;
1947     if (nodeIndex==numNodes) return; // last node
1948     while (nodeIndex<numNodes && 
1949            para->children().at(nodeIndex)->kind()==DocNode::Kind_WhiteSpace
1950           )
1951     {
1952       nodeIndex++;
1953     }
1954     if (nodeIndex<numNodes)
1955     {
1956       DocNode *n = para->children().at(nodeIndex);
1957       if (mustBeOutsideParagraph(n)) return;
1958     }
1959     else
1960     {
1961       return; // only whitespace at the end!
1962     }
1963
1964     bool isFirst;
1965     bool isLast;
1966     getParagraphContext(para,isFirst,isLast);
1967     //printf("forceStart first=%d last=%d\n",isFirst,isLast);
1968     if (isFirst && isLast) return;
1969
1970     m_t << "<p>";
1971   }
1972 }
1973