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