1 /******************************************************************************
2  *
3  * Copyright (C) 1997-2020 by Dimitri van Heesch.
4  *
5  * Permission to use, copy, modify, and distribute this software and its
6  * documentation under the terms of the GNU General Public License is hereby
7  * granted. No representations are made about the suitability of this software
8  * for any purpose. It is provided "as is" without express or implied warranty.
9  * See the GNU General Public License for more details.
10  *
11  * Documents produced by Doxygen are derivative works derived from the
12  * input used in their production; they are not affected by this license.
13  *
14  */
15 
16 #include "htmldocvisitor.h"
17 #include "docparser.h"
18 #include "language.h"
19 #include "doxygen.h"
20 #include "outputgen.h"
21 #include "dot.h"
22 #include "message.h"
23 #include "config.h"
24 #include "htmlgen.h"
25 #include "parserintf.h"
26 #include "msc.h"
27 #include "dia.h"
28 #include "util.h"
29 #include "vhdldocgen.h"
30 #include "filedef.h"
31 #include "memberdef.h"
32 #include "htmlentity.h"
33 #include "emoji.h"
34 #include "plantuml.h"
35 #include "formula.h"
36 #include "fileinfo.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 enum contexts_t
41 {
42     NONE,      // 0
43     STARTLI,   // 1
44     STARTDD,   // 2
45     ENDLI,     // 3
46     ENDDD,     // 4
47     STARTTD,   // 5
48     ENDTD,     // 6
49     INTERLI,   // 7
50     INTERDD,   // 8
51     INTERTD    // 9
52 };
53 static const char *contexts[10] =
54 { "",          // 0
55   "startli",   // 1
56   "startdd",   // 2
57   "endli",     // 3
58   "enddd",     // 4
59   "starttd",   // 5
60   "endtd",     // 6
61   "interli",   // 7
62   "interdd",   // 8
63   "intertd"    // 9
64 };
65 static const char *hex="0123456789ABCDEF";
66 
convertIndexWordToAnchor(const QCString & word)67 static QCString convertIndexWordToAnchor(const QCString &word)
68 {
69   static int cnt = 0;
70   QCString result="a";
71   QCString cntStr;
72   result += cntStr.setNum(cnt);
73   result += "_";
74   cnt++;
75   const char *str = word.data();
76   unsigned char c;
77   if (str)
78   {
79     while ((c = *str++))
80     {
81       if ((c >= 'a' && c <= 'z') || // ALPHA
82           (c >= 'A' && c <= 'Z') || // ALPHA
83           (c >= '0' && c <= '9') || // DIGIT
84           c == '-' ||
85           c == '.' ||
86           c == '_'
87          )
88       {
89         result += c;
90       }
91       else
92       {
93         char enc[4];
94         enc[0] = ':';
95         enc[1] = hex[(c & 0xf0) >> 4];
96         enc[2] = hex[c & 0xf];
97         enc[3] = 0;
98         result += enc;
99       }
100     }
101   }
102   return result;
103 }
104 
mustBeOutsideParagraph(const DocNode * n)105 static bool mustBeOutsideParagraph(const DocNode *n)
106 {
107   switch (n->kind())
108   {
109           /* <ul> */
110         case DocNode::Kind_HtmlList:
111         case DocNode::Kind_SimpleList:
112         case DocNode::Kind_AutoList:
113           /* <dl> */
114         case DocNode::Kind_SimpleSect:
115         case DocNode::Kind_ParamSect:
116         case DocNode::Kind_HtmlDescList:
117         case DocNode::Kind_XRefItem:
118           /* <table> */
119         case DocNode::Kind_HtmlTable:
120           /* <h?> */
121         case DocNode::Kind_Section:
122         case DocNode::Kind_HtmlHeader:
123           /* \internal */
124         case DocNode::Kind_Internal:
125           /* <div> */
126         case DocNode::Kind_Include:
127         case DocNode::Kind_SecRefList:
128           /* <hr> */
129         case DocNode::Kind_HorRuler:
130           /* CopyDoc gets paragraph markers from the wrapping DocPara node,
131            * but needs to insert them for all documentation being copied to
132            * preserve formatting.
133            */
134         case DocNode::Kind_Copy:
135           /* <blockquote> */
136         case DocNode::Kind_HtmlBlockQuote:
137           /* \parblock */
138         case DocNode::Kind_ParBlock:
139         case DocNode::Kind_IncOperator:
140           return TRUE;
141         case DocNode::Kind_Verbatim:
142           {
143             DocVerbatim *dv = (DocVerbatim*)n;
144             DocVerbatim::Type t = dv->type();
145             if (t == DocVerbatim::JavaDocCode || t == DocVerbatim::JavaDocLiteral) return FALSE;
146             return t!=DocVerbatim::HtmlOnly || dv->isBlock();
147           }
148         case DocNode::Kind_StyleChange:
149           return ((DocStyleChange*)n)->style()==DocStyleChange::Preformatted ||
150                  ((DocStyleChange*)n)->style()==DocStyleChange::Div ||
151                  ((DocStyleChange*)n)->style()==DocStyleChange::Center;
152         case DocNode::Kind_Formula:
153           return !((DocFormula*)n)->isInline();
154         case DocNode::Kind_Image:
155           return !((DocImage*)n)->isInlineImage();
156         default:
157           break;
158   }
159   return FALSE;
160 }
161 
isDocVerbatimVisible(const DocVerbatim * s)162 static bool isDocVerbatimVisible(const DocVerbatim *s)
163 {
164   switch(s->type())
165   {
166     case DocVerbatim::ManOnly:
167     case DocVerbatim::LatexOnly:
168     case DocVerbatim::XmlOnly:
169     case DocVerbatim::RtfOnly:
170     case DocVerbatim::DocbookOnly:
171       return FALSE;
172     default:
173       return TRUE;
174   }
175 }
176 
isDocIncludeVisible(const DocInclude * s)177 static bool isDocIncludeVisible(const DocInclude *s)
178 {
179   switch (s->type())
180   {
181     case DocInclude::DontInclude:
182     case DocInclude::LatexInclude:
183     case DocInclude::RtfInclude:
184     case DocInclude::ManInclude:
185     case DocInclude::XmlInclude:
186     case DocInclude::DocbookInclude:
187       return FALSE;
188     default:
189       return TRUE;
190   }
191 }
192 
isDocIncOperatorVisible(const DocIncOperator * s)193 static bool isDocIncOperatorVisible(const DocIncOperator *s)
194 {
195   switch (s->type())
196   {
197     case DocIncOperator::Skip:
198       return FALSE;
199     default:
200       return TRUE;
201   }
202 }
203 
isInvisibleNode(const DocNode * node)204 static bool isInvisibleNode(const DocNode *node)
205 {
206   return (node->kind()==DocNode::Kind_WhiteSpace)
207       || // skip over image nodes that are not for HTML output
208          (node->kind()==DocNode::Kind_Image && ((DocImage*)node)->type()!=DocImage::Html)
209       || // skip over verbatim nodes that are not visible in the HTML output
210          (node->kind()==DocNode::Kind_Verbatim && !isDocVerbatimVisible((DocVerbatim*)node))
211       || // skip over include nodes that are not visible in the HTML output
212          (node->kind()==DocNode::Kind_Include && !isDocIncludeVisible((DocInclude*)node))
213       || // skip over include operator nodes that are not visible in the HTML output
214          (node->kind()==DocNode::Kind_IncOperator && !isDocIncOperatorVisible((DocIncOperator*)node))
215       ;
216 }
217 
mergeHtmlAttributes(const HtmlAttribList & attribs,HtmlAttribList & mergeInto)218 static void mergeHtmlAttributes(const HtmlAttribList &attribs, HtmlAttribList &mergeInto)
219 {
220   for (const auto &att : attribs)
221   {
222     auto it = std::find_if(mergeInto.begin(),mergeInto.end(),
223                            [&att](const auto &opt) { return opt.name==att.name; });
224     if (it!=mergeInto.end()) // attribute name already in mergeInto
225     {
226        it->value = it->value + " " + att.value;
227     }
228     else // attribute name not yet in mergeInto
229     {
230       mergeInto.push_back(att);
231     }
232   }
233 }
234 
htmlAttribsToString(const HtmlAttribList & attribs,QCString * pAltValue=0)235 static QCString htmlAttribsToString(const HtmlAttribList &attribs, QCString *pAltValue = 0)
236 {
237   QCString result;
238   for (const auto &att : attribs)
239   {
240     if (!att.value.isEmpty())  // ignore attribute without values as they
241                                 // are not XHTML compliant, with the exception
242 				// of the alt attribute with the img tag
243     {
244       if (att.name=="alt" && pAltValue) // optionally return the value of alt separately
245                                          // need to convert <img> to <object> for SVG images,
246                                          // which do not support the alt attribute
247       {
248         *pAltValue = att.value;
249       }
250       else
251       {
252         result+=" ";
253         result+=att.name;
254         result+="=\""+convertToXML(att.value)+"\"";
255       }
256     }
257     else if (att.name=="open")
258     {
259         // The open attribute is a boolean attribute.
260         // Specifies that the details should be visible (open) to the user
261         // As it is a boolean attribute the initialisation value is of no interest
262         result+=" ";
263         result+=att.name;
264         result+="=\"true\"";
265     }
266     else if (att.name=="nowrap") // In XHTML, attribute minimization is forbidden, and the nowrap attribute must be defined as <td nowrap="nowrap">.
267     {
268         result+=" ";
269         result+=att.name;
270         result+="=\"nowrap\"";
271     }
272   }
273   return result;
274 }
275 
276 //-------------------------------------------------------------------------
277 
HtmlDocVisitor(TextStream & t,CodeOutputInterface & ci,const Definition * ctx)278 HtmlDocVisitor::HtmlDocVisitor(TextStream &t,CodeOutputInterface &ci,
279                                const Definition *ctx)
280   : DocVisitor(DocVisitor_Html), m_t(t), m_ci(ci), m_ctx(ctx)
281 {
282   if (ctx) m_langExt=ctx->getDefFileExtension();
283 }
284 
285   //--------------------------------------
286   // visitor functions for leaf nodes
287   //--------------------------------------
288 
visit(DocWord * w)289 void HtmlDocVisitor::visit(DocWord *w)
290 {
291   //printf("word: %s\n",qPrint(w->word()));
292   if (m_hide) return;
293   filter(w->word());
294 }
295 
visit(DocLinkedWord * w)296 void HtmlDocVisitor::visit(DocLinkedWord *w)
297 {
298   if (m_hide) return;
299   //printf("linked word: %s\n",qPrint(w->word()));
300   startLink(w->ref(),w->file(),w->relPath(),w->anchor(),w->tooltip());
301   filter(w->word());
302   endLink();
303 }
304 
visit(DocWhiteSpace * w)305 void HtmlDocVisitor::visit(DocWhiteSpace *w)
306 {
307   if (m_hide) return;
308   if (m_insidePre)
309   {
310     m_t << w->chars();
311   }
312   else
313   {
314     m_t << " ";
315   }
316 }
317 
visit(DocSymbol * s)318 void HtmlDocVisitor::visit(DocSymbol *s)
319 {
320   if (m_hide) return;
321   if (m_insideTitle &&
322       (s->symbol()==DocSymbol::Sym_Quot || s->symbol()==DocSymbol::Sym_quot)) // escape "'s inside title="..."
323   {
324     m_t << "&quot;";
325   }
326   else
327   {
328     const char *res = HtmlEntityMapper::instance()->html(s->symbol());
329     if (res)
330     {
331       m_t << res;
332     }
333     else
334     {
335       err("HTML: non supported HTML-entity found: %s\n",HtmlEntityMapper::instance()->html(s->symbol(),TRUE));
336     }
337   }
338 }
339 
visit(DocEmoji * s)340 void HtmlDocVisitor::visit(DocEmoji *s)
341 {
342   if (m_hide) return;
343   const char *res = EmojiEntityMapper::instance()->unicode(s->index());
344   if (res)
345   {
346     m_t << "<span class=\"emoji\">"<<res<<"</span>";
347   }
348   else
349   {
350     m_t << s->name();
351   }
352 }
353 
writeObfuscatedMailAddress(const QCString & url)354 void HtmlDocVisitor::writeObfuscatedMailAddress(const QCString &url)
355 {
356   if (!Config_getBool(OBFUSCATE_EMAILS))
357   {
358     m_t << "<a href=\"mailto:" << url << "\">";
359   }
360   else
361   {
362     m_t << "<a href=\"#\" onclick=\"location.href='mai'+'lto:'";
363     if (!url.isEmpty())
364     {
365       const char *p = url.data();
366       uint size=3;
367       while (*p)
368       {
369         m_t << "+'";
370         for (uint j=0;j<size && *p;j++)
371         {
372           p = writeUTF8Char(m_t,p);
373         }
374         m_t << "'";
375         if (size==3) size=2; else size=3;
376       }
377     }
378     m_t << "; return false;\">";
379   }
380 }
381 
visit(DocURL * u)382 void HtmlDocVisitor::visit(DocURL *u)
383 {
384   if (m_hide) return;
385   if (u->isEmail()) // mail address
386   {
387     QCString url = u->url();
388     // obfuscate the mail address link
389     writeObfuscatedMailAddress(url);
390     if (!Config_getBool(OBFUSCATE_EMAILS))
391     {
392       m_t << url;
393     }
394     else
395     {
396       const char *p = url.data();
397       // also obfuscate the address as shown on the web page
398       uint size=5;
399       while (*p)
400       {
401         for (uint j=0;j<size && *p;j++)
402         {
403           p = writeUTF8Char(m_t,p);
404         }
405         if (*p) m_t << "<span class=\"obfuscator\">.nosp@m.</span>";
406         if (size==5) size=4; else size=5;
407       }
408     }
409     m_t << "</a>";
410   }
411   else // web address
412   {
413     m_t << "<a href=\"";
414     m_t << u->url() << "\">";
415     filter(u->url());
416     m_t << "</a>";
417   }
418 }
419 
visit(DocLineBreak * br)420 void HtmlDocVisitor::visit(DocLineBreak *br)
421 {
422   if (m_hide) return;
423   m_t << "<br "<< htmlAttribsToString(br->attribs()) << " />\n";
424 }
425 
visit(DocHorRuler * hr)426 void HtmlDocVisitor::visit(DocHorRuler *hr)
427 {
428   if (m_hide) return;
429   forceEndParagraph(hr);
430   m_t << "<hr "<< htmlAttribsToString(hr->attribs()) << " />\n";
431   forceStartParagraph(hr);
432 }
433 
visit(DocStyleChange * s)434 void HtmlDocVisitor::visit(DocStyleChange *s)
435 {
436   if (m_hide) return;
437   switch (s->style())
438   {
439     case DocStyleChange::Bold:
440       if (s->enable()) m_t << "<b" << htmlAttribsToString(s->attribs()) << ">";      else m_t << "</b>";
441       break;
442     case DocStyleChange::S:
443       if (s->enable()) m_t << "<s" << htmlAttribsToString(s->attribs()) << ">";      else m_t << "</s>";
444       break;
445     case DocStyleChange::Strike:
446       if (s->enable()) m_t << "<strike" << htmlAttribsToString(s->attribs()) << ">";      else m_t << "</strike>";
447       break;
448     case DocStyleChange::Del:
449       if (s->enable()) m_t << "<del" << htmlAttribsToString(s->attribs()) << ">";      else m_t << "</del>";
450       break;
451     case DocStyleChange::Underline:
452       if (s->enable()) m_t << "<u" << htmlAttribsToString(s->attribs()) << ">";      else m_t << "</u>";
453       break;
454     case DocStyleChange::Ins:
455       if (s->enable()) m_t << "<ins" << htmlAttribsToString(s->attribs()) << ">";      else m_t << "</ins>";
456       break;
457     case DocStyleChange::Italic:
458       if (s->enable()) m_t << "<em" << htmlAttribsToString(s->attribs()) << ">";     else m_t << "</em>";
459       break;
460     case DocStyleChange::Code:
461       if (s->enable()) m_t << "<code" << htmlAttribsToString(s->attribs()) << ">";   else m_t << "</code>";
462       break;
463     case DocStyleChange::Subscript:
464       if (s->enable()) m_t << "<sub" << htmlAttribsToString(s->attribs()) << ">";    else m_t << "</sub>";
465       break;
466     case DocStyleChange::Superscript:
467       if (s->enable()) m_t << "<sup" << htmlAttribsToString(s->attribs()) << ">";    else m_t << "</sup>";
468       break;
469     case DocStyleChange::Center:
470       if (s->enable())
471       {
472         forceEndParagraph(s);
473         m_t << "<center" << htmlAttribsToString(s->attribs()) << ">";
474       }
475       else
476       {
477         m_t << "</center>";
478         forceStartParagraph(s);
479       }
480       break;
481     case DocStyleChange::Small:
482       if (s->enable()) m_t << "<small" << htmlAttribsToString(s->attribs()) << ">";  else m_t << "</small>";
483       break;
484     case DocStyleChange::Cite:
485       if (s->enable()) m_t << "<cite" << htmlAttribsToString(s->attribs()) << ">";  else m_t << "</cite>";
486       break;
487     case DocStyleChange::Preformatted:
488       if (s->enable())
489       {
490         forceEndParagraph(s);
491         m_t << "<pre" << htmlAttribsToString(s->attribs()) << ">";
492         m_insidePre=TRUE;
493       }
494       else
495       {
496         m_insidePre=FALSE;
497         m_t << "</pre>";
498         forceStartParagraph(s);
499       }
500       break;
501     case DocStyleChange::Div:
502       if (s->enable())
503       {
504         forceEndParagraph(s);
505         m_t << "<div" << htmlAttribsToString(s->attribs()) << ">";
506       }
507       else
508       {
509         m_t << "</div>";
510         forceStartParagraph(s);
511       }
512       break;
513     case DocStyleChange::Span:
514       if (s->enable()) m_t << "<span" << htmlAttribsToString(s->attribs()) << ">";  else m_t << "</span>";
515       break;
516     case DocStyleChange::Details:
517       if (s->enable()) m_t << "<details" << htmlAttribsToString(s->attribs()) << ">\n"; else m_t << "</details>\n";
518       break;
519     case DocStyleChange::Summary:
520       if (s->enable()) m_t << "<summary" << htmlAttribsToString(s->attribs()) << ">";  else m_t << "</summary>";
521       break;
522   }
523 }
524 
525 
visitPreCaption(TextStream & t,DocVerbatim * s)526 static void visitPreCaption(TextStream &t, DocVerbatim *s)
527 {
528   if (s->hasCaption())
529   {
530     t << "<div class=\"caption\">\n";
531   }
532 }
533 
534 
visitPostCaption(TextStream & t,DocVerbatim * s)535 static void visitPostCaption(TextStream &t, DocVerbatim *s)
536 {
537   if (s->hasCaption())
538   {
539     t << "</div>\n";
540   }
541 }
542 
543 
visitCaption(HtmlDocVisitor * parent,DocNodeList & children)544 static void visitCaption(HtmlDocVisitor *parent, DocNodeList &children)
545 {
546   for (const auto &n : children) n->accept(parent);
547 }
548 
visit(DocVerbatim * s)549 void HtmlDocVisitor::visit(DocVerbatim *s)
550 {
551   if (m_hide) return;
552   QCString lang = m_langExt;
553   if (!s->language().isEmpty()) // explicit language setting
554   {
555     lang = s->language();
556   }
557   SrcLangExt langExt = getLanguageFromCodeLang(lang);
558   switch(s->type())
559   {
560     case DocVerbatim::Code:
561       forceEndParagraph(s);
562       m_ci.startCodeFragment("DoxyCode");
563       getCodeParser(lang).parseCode(m_ci,
564                                         s->context(),
565                                         s->text(),
566                                         langExt,
567                                         s->isExample(),
568                                         s->exampleFile(),
569                                         0,     // fileDef
570                                         -1,    // startLine
571                                         -1,    // endLine
572                                         FALSE, // inlineFragment
573                                         0,     // memberDef
574                                         TRUE,  // show line numbers
575                                         m_ctx  // search context
576                                        );
577       m_ci.endCodeFragment("DoxyCode");
578       forceStartParagraph(s);
579       break;
580     case DocVerbatim::Verbatim:
581       forceEndParagraph(s);
582       m_t << "<pre class=\"fragment\">";
583       filter(s->text());
584       m_t << "</pre>";
585       forceStartParagraph(s);
586       break;
587     case DocVerbatim::JavaDocLiteral:
588       filter(s->text(), true);
589       break;
590     case DocVerbatim::JavaDocCode:
591       m_t << "<code class=\"JavaDocCode\">";
592       filter(s->text(), true);
593       m_t << "</code>";
594       break;
595     case DocVerbatim::HtmlOnly:
596       {
597         if (s->isBlock()) forceEndParagraph(s);
598         m_t << s->text();
599         if (s->isBlock()) forceStartParagraph(s);
600       }
601       break;
602     case DocVerbatim::ManOnly:
603     case DocVerbatim::LatexOnly:
604     case DocVerbatim::XmlOnly:
605     case DocVerbatim::RtfOnly:
606     case DocVerbatim::DocbookOnly:
607       /* nothing */
608       break;
609 
610     case DocVerbatim::Dot:
611       {
612         static int dotindex = 1;
613         QCString fileName(4096);
614 
615         forceEndParagraph(s);
616         fileName.sprintf("%s%d%s",
617             qPrint(Config_getString(HTML_OUTPUT)+"/inline_dotgraph_"),
618             dotindex++,
619             ".dot"
620            );
621         std::ofstream file(fileName.str(),std::ofstream::out | std::ofstream::binary);
622         if (!file.is_open())
623         {
624           err("Could not open file %s for writing\n",qPrint(fileName));
625         }
626         else
627         {
628           QCString stext = s->text();
629           file.write( stext.data(), stext.length() );
630           file.close();
631 
632           m_t << "<div class=\"dotgraph\">\n";
633           writeDotFile(fileName,s->relPath(),s->context(),s->srcFile(),s->srcLine());
634           visitPreCaption(m_t, s);
635           visitCaption(this, s->children());
636           visitPostCaption(m_t, s);
637           m_t << "</div>\n";
638 
639           if (Config_getBool(DOT_CLEANUP)) Dir().remove(fileName.str());
640         }
641         forceStartParagraph(s);
642       }
643       break;
644     case DocVerbatim::Msc:
645       {
646         forceEndParagraph(s);
647 
648         static int mscindex = 1;
649         QCString baseName(4096);
650 
651         baseName.sprintf("%s%d",
652             qPrint(Config_getString(HTML_OUTPUT)+"/inline_mscgraph_"),
653             mscindex++
654             );
655         std::ofstream file(baseName.str()+".msc",std::ofstream::out | std::ofstream::binary);
656         if (!file.is_open())
657         {
658           err("Could not open file %s.msc for writing\n",qPrint(baseName));
659         }
660         else
661         {
662           QCString text = "msc {";
663           text+=s->text();
664           text+="}";
665 
666           file.write( text.data(), text.length() );
667           file.close();
668 
669           m_t << "<div class=\"mscgraph\">\n";
670           writeMscFile(baseName+".msc",s->relPath(),s->context(),s->srcFile(),s->srcLine());
671           visitPreCaption(m_t, s);
672           visitCaption(this, s->children());
673           visitPostCaption(m_t, s);
674           m_t << "</div>\n";
675 
676           if (Config_getBool(DOT_CLEANUP)) Dir().remove(baseName.str()+".msc");
677         }
678         forceStartParagraph(s);
679       }
680       break;
681     case DocVerbatim::PlantUML:
682       {
683         forceEndParagraph(s);
684         static QCString htmlOutput = Config_getString(HTML_OUTPUT);
685         QCString imgExt = getDotImageExtension();
686         PlantumlManager::OutputFormat format = PlantumlManager::PUML_BITMAP;	// default : PUML_BITMAP
687         if (imgExt=="svg")
688         {
689           format = PlantumlManager::PUML_SVG;
690         }
691         QCString baseName = PlantumlManager::instance().writePlantUMLSource(
692                                     htmlOutput,s->exampleFile(),
693                                     s->text(),format,s->engine(),s->srcFile(),s->srcLine());
694         m_t << "<div class=\"plantumlgraph\">\n";
695         writePlantUMLFile(baseName,s->relPath(),s->context(),s->srcFile(),s->srcLine());
696         visitPreCaption(m_t, s);
697         visitCaption(this, s->children());
698         visitPostCaption(m_t, s);
699         m_t << "</div>\n";
700         forceStartParagraph(s);
701       }
702       break;
703   }
704 }
705 
visit(DocAnchor * anc)706 void HtmlDocVisitor::visit(DocAnchor *anc)
707 {
708   if (m_hide) return;
709   m_t << "<a class=\"anchor\" id=\"" << anc->anchor() << "\"" << htmlAttribsToString(anc->attribs()) << "></a>";
710 }
711 
visit(DocInclude * inc)712 void HtmlDocVisitor::visit(DocInclude *inc)
713 {
714   if (m_hide) return;
715   SrcLangExt langExt = getLanguageFromFileName(inc->extension());
716   switch(inc->type())
717   {
718     case DocInclude::Include:
719       forceEndParagraph(inc);
720       m_ci.startCodeFragment("DoxyCode");
721       getCodeParser(inc->extension()).parseCode(m_ci,
722                                         inc->context(),
723                                         inc->text(),
724                                         langExt,
725                                         inc->isExample(),
726                                         inc->exampleFile(),
727                                         0,     // fileDef
728                                         -1,    // startLine
729                                         -1,    // endLine
730                                         TRUE,  // inlineFragment
731                                         0,     // memberDef
732                                         FALSE, // show line numbers
733                                         m_ctx  // search context
734                                        );
735       m_ci.endCodeFragment("DoxyCode");
736       forceStartParagraph(inc);
737       break;
738     case DocInclude::IncWithLines:
739       {
740          forceEndParagraph(inc);
741          m_ci.startCodeFragment("DoxyCode");
742          FileInfo cfi( inc->file().str() );
743          FileDef *fd = createFileDef( cfi.dirPath(), cfi.fileName() );
744          getCodeParser(inc->extension()).parseCode(m_ci,
745                                            inc->context(),
746                                            inc->text(),
747                                            langExt,
748                                            inc->isExample(),
749                                            inc->exampleFile(),
750                                            fd,   // fileDef,
751                                            -1,    // start line
752                                            -1,    // end line
753                                            FALSE, // inline fragment
754                                            0,     // memberDef
755                                            TRUE,  // show line numbers
756                                            m_ctx  // search context
757                                            );
758          delete fd;
759          m_ci.endCodeFragment("DoxyCode");
760          forceStartParagraph(inc);
761       }
762       break;
763     case DocInclude::DontInclude:
764     case DocInclude::LatexInclude:
765     case DocInclude::RtfInclude:
766     case DocInclude::ManInclude:
767     case DocInclude::XmlInclude:
768     case DocInclude::DocbookInclude:
769     case DocInclude::DontIncWithLines:
770       break;
771     case DocInclude::HtmlInclude:
772       {
773         if (inc->isBlock()) forceEndParagraph(inc);
774         m_t << inc->text();
775         if (inc->isBlock()) forceStartParagraph(inc);
776       }
777       break;
778     case DocInclude::VerbInclude:
779       forceEndParagraph(inc);
780       m_t << "<pre class=\"fragment\">";
781       filter(inc->text());
782       m_t << "</pre>";
783       forceStartParagraph(inc);
784       break;
785     case DocInclude::Snippet:
786       {
787          forceEndParagraph(inc);
788          m_ci.startCodeFragment("DoxyCode");
789          getCodeParser(inc->extension()).parseCode(m_ci,
790                                            inc->context(),
791                                            extractBlock(inc->text(),inc->blockId()),
792                                            langExt,
793                                            inc->isExample(),
794                                            inc->exampleFile(),
795                                            0,
796                                            -1,    // startLine
797                                            -1,    // endLine
798                                            TRUE,  // inlineFragment
799                                            0,     // memberDef
800                                            FALSE, // show line number
801                                            m_ctx  // search context
802                                           );
803          m_ci.endCodeFragment("DoxyCode");
804          forceStartParagraph(inc);
805       }
806       break;
807     case DocInclude::SnipWithLines:
808       {
809          forceEndParagraph(inc);
810          m_ci.startCodeFragment("DoxyCode");
811          FileInfo cfi( inc->file().str() );
812          FileDef *fd = createFileDef( cfi.dirPath(), cfi.fileName() );
813          getCodeParser(inc->extension()).parseCode(m_ci,
814                                            inc->context(),
815                                            extractBlock(inc->text(),inc->blockId()),
816                                            langExt,
817                                            inc->isExample(),
818                                            inc->exampleFile(),
819                                            fd,
820                                            lineBlock(inc->text(),inc->blockId()),
821                                            -1,    // endLine
822                                            FALSE, // inlineFragment
823                                            0,     // memberDef
824                                            TRUE,  // show line number
825                                            m_ctx  // search context
826                                           );
827          delete fd;
828          m_ci.endCodeFragment("DoxyCode");
829          forceStartParagraph(inc);
830       }
831       break;
832     case DocInclude::SnippetDoc:
833     case DocInclude::IncludeDoc:
834       err("Internal inconsistency: found switch SnippetDoc / IncludeDoc in file: %s"
835           "Please create a bug report\n",__FILE__);
836       break;
837   }
838 }
839 
visit(DocIncOperator * op)840 void HtmlDocVisitor::visit(DocIncOperator *op)
841 {
842   //printf("DocIncOperator: type=%d first=%d, last=%d text='%s'\n",
843   //    op->type(),op->isFirst(),op->isLast(),qPrint(op->text()));
844   if (op->isFirst())
845   {
846     forceEndParagraph(op);
847     if (!m_hide) m_ci.startCodeFragment("DoxyCode");
848     pushHidden(m_hide);
849     m_hide=TRUE;
850   }
851   QCString locLangExt = getFileNameExtension(op->includeFileName());
852   if (locLangExt.isEmpty()) locLangExt = m_langExt;
853   SrcLangExt langExt = getLanguageFromFileName(locLangExt);
854   if (op->type()!=DocIncOperator::Skip)
855   {
856     m_hide = popHidden();
857     if (!m_hide)
858     {
859       FileDef *fd = 0;
860       if (!op->includeFileName().isEmpty())
861       {
862         FileInfo cfi( op->includeFileName().str() );
863         fd = createFileDef( cfi.dirPath(), cfi.fileName() );
864       }
865       getCodeParser(locLangExt).parseCode(
866                                 m_ci,
867                                 op->context(),
868                                 op->text(),
869                                 langExt,
870                                 op->isExample(),
871                                 op->exampleFile(),
872                                 fd,     // fileDef
873                                 op->line(),    // startLine
874                                 -1,    // endLine
875                                 FALSE, // inline fragment
876                                 0,     // memberDef
877                                 op->showLineNo(),  // show line numbers
878                                 m_ctx  // search context
879                                );
880       if (fd) delete fd;
881     }
882     pushHidden(m_hide);
883     m_hide=TRUE;
884   }
885   if (op->isLast())
886   {
887     m_hide = popHidden();
888     if (!m_hide) m_ci.endCodeFragment("DoxyCode");
889     forceStartParagraph(op);
890   }
891   else
892   {
893     if (!m_hide) m_t << "\n";
894   }
895 }
896 
visit(DocFormula * f)897 void HtmlDocVisitor::visit(DocFormula *f)
898 {
899   if (m_hide) return;
900   bool bDisplay = !f->isInline();
901   if (bDisplay)
902   {
903     forceEndParagraph(f);
904     m_t << "<p class=\"formulaDsp\">\n";
905   }
906 
907   if (Config_getBool(USE_MATHJAX))
908   {
909     QCString text = f->text();
910     bool closeInline = FALSE;
911     if (!bDisplay && !text.isEmpty() && text.at(0)=='$' &&
912                       text.at(text.length()-1)=='$')
913     {
914       closeInline=TRUE;
915       text = text.mid(1,text.length()-2);
916       m_t << "\\(";
917     }
918     else if (!bDisplay && !text.isEmpty())
919     {
920       closeInline=TRUE;
921       m_t << "\\(";
922     }
923     m_t << convertToHtml(text);
924     if (closeInline)
925     {
926       m_t << "\\)";
927     }
928   }
929   else
930   {
931     m_t << "<img class=\"formula"
932       << (bDisplay ? "Dsp" : "Inl");
933     m_t << "\" alt=\"";
934     filterQuotedCdataAttr(f->text());
935     m_t << "\"";
936     m_t << " src=\"" << f->relPath() << f->name();
937     if (Config_getEnum(HTML_FORMULA_FORMAT)==HTML_FORMULA_FORMAT_t::svg)
938     {
939       m_t << ".svg";
940     }
941     else
942     {
943       m_t << ".png";
944     }
945     FormulaManager::DisplaySize size = FormulaManager::instance().displaySize(f->id());
946     if (size.width!=-1)
947     {
948       m_t << "\" width=\"" << size.width;
949     }
950     if (size.height!=-1)
951     {
952       m_t << "\" height=\"" << size.height;
953     }
954     m_t << "\"/>";
955   }
956   if (bDisplay)
957   {
958     m_t << "\n</p>\n";
959     forceStartParagraph(f);
960   }
961 }
962 
visit(DocIndexEntry * e)963 void HtmlDocVisitor::visit(DocIndexEntry *e)
964 {
965   QCString anchor = convertIndexWordToAnchor(e->entry());
966   if (e->member())
967   {
968     anchor.prepend(e->member()->anchor()+"_");
969   }
970   m_t << "<a id=\"" << anchor << "\" name=\"" << anchor << "\"></a>";
971   //printf("*** DocIndexEntry: word='%s' scope='%s' member='%s'\n",
972   //       qPrint(e->entry()),
973   //       e->scope()  ? qPrint(e->scope()->name())  : "<null>",
974   //       e->member() ? qPrint(e->member()->name()) : "<null>"
975   //      );
976   Doxygen::indexList->addIndexItem(e->scope(),e->member(),anchor,e->entry());
977 }
978 
visit(DocSimpleSectSep *)979 void HtmlDocVisitor::visit(DocSimpleSectSep *)
980 {
981   m_t << "</dd>\n";
982   m_t << "<dd>\n";
983 }
984 
visit(DocCite * cite)985 void HtmlDocVisitor::visit(DocCite *cite)
986 {
987   if (m_hide) return;
988   if (!cite->file().isEmpty())
989   {
990     startLink(cite->ref(),cite->file(),cite->relPath(),cite->anchor());
991   }
992   else
993   {
994     m_t << "<b>[";
995   }
996   filter(cite->text());
997   if (!cite->file().isEmpty())
998   {
999     endLink();
1000   }
1001   else
1002   {
1003     m_t << "]</b>";
1004   }
1005 }
1006 
1007 
1008 //--------------------------------------
1009 // visitor functions for compound nodes
1010 //--------------------------------------
1011 
1012 
visitPre(DocAutoList * l)1013 void HtmlDocVisitor::visitPre(DocAutoList *l)
1014 {
1015   //printf("DocAutoList::visitPre\n");
1016   if (m_hide) return;
1017   forceEndParagraph(l);
1018   if (l->isEnumList())
1019   {
1020     //
1021     // Do list type based on depth:
1022     // 1.
1023     //   a.
1024     //     i.
1025     //       A.
1026     //         1. (repeat)...
1027     //
1028     m_t << "<ol type=\"" << types[l->depth() % NUM_HTML_LIST_TYPES] << "\">";
1029   }
1030   else
1031   {
1032     m_t << "<ul>";
1033   }
1034   if (!l->isPreformatted()) m_t << "\n";
1035 }
1036 
visitPost(DocAutoList * l)1037 void HtmlDocVisitor::visitPost(DocAutoList *l)
1038 {
1039   //printf("DocAutoList::visitPost\n");
1040   if (m_hide) return;
1041   if (l->isEnumList())
1042   {
1043     m_t << "</ol>";
1044   }
1045   else
1046   {
1047     m_t << "</ul>";
1048   }
1049   if (!l->isPreformatted()) m_t << "\n";
1050   forceStartParagraph(l);
1051 }
1052 
visitPre(DocAutoListItem *)1053 void HtmlDocVisitor::visitPre(DocAutoListItem *)
1054 {
1055   if (m_hide) return;
1056   m_t << "<li>";
1057 }
1058 
visitPost(DocAutoListItem * li)1059 void HtmlDocVisitor::visitPost(DocAutoListItem *li)
1060 {
1061   if (m_hide) return;
1062   m_t << "</li>";
1063   if (!li->isPreformatted()) m_t << "\n";
1064 }
1065 
1066 template<class T>
isFirstChildNode(T * parent,DocNode * node)1067 bool isFirstChildNode(T *parent, DocNode *node)
1068 {
1069    return !parent->children().empty() && parent->children().front().get()==node;
1070 }
1071 
1072 template<class T>
isLastChildNode(T * parent,DocNode * node)1073 bool isLastChildNode(T *parent, DocNode *node)
1074 {
1075    return !parent->children().empty() && parent->children().back().get()==node;
1076 }
1077 
isSeparatedParagraph(DocSimpleSect * parent,DocPara * par)1078 bool isSeparatedParagraph(DocSimpleSect *parent,DocPara *par)
1079 {
1080   const DocNodeList &nodes = parent->children();
1081   auto it = std::find_if(nodes.begin(),nodes.end(),[par](const auto &n) { return n.get()==par; });
1082   if (it==nodes.end()) return FALSE;
1083   size_t i = it - nodes.begin();
1084   size_t count = parent->children().size();
1085   if (count>1 && i==0) // first node
1086   {
1087     if (nodes.at(i+1)->kind()==DocNode::Kind_SimpleSectSep)
1088     {
1089       return TRUE;
1090     }
1091   }
1092   else if (count>1 && i==count-1) // last node
1093   {
1094     if (nodes.at(i-1)->kind()==DocNode::Kind_SimpleSectSep)
1095     {
1096       return TRUE;
1097     }
1098   }
1099   else if (count>2 && i>0 && i<count-1) // intermediate node
1100   {
1101     if (nodes.at(i-1)->kind()==DocNode::Kind_SimpleSectSep &&
1102         nodes.at(i+1)->kind()==DocNode::Kind_SimpleSectSep)
1103     {
1104       return TRUE;
1105     }
1106   }
1107   return FALSE;
1108 }
1109 
getParagraphContext(DocPara * p,bool & isFirst,bool & isLast)1110 static int getParagraphContext(DocPara *p,bool &isFirst,bool &isLast)
1111 {
1112   int t=0;
1113   isFirst=FALSE;
1114   isLast=FALSE;
1115   if (p && p->parent())
1116   {
1117     switch (p->parent()->kind())
1118     {
1119       case DocNode::Kind_ParBlock:
1120         { // hierarchy: node N -> para -> parblock -> para
1121           // adapt return value to kind of N
1122           DocNode::Kind kind = DocNode::Kind_Para;
1123           if ( p->parent()->parent() && p->parent()->parent()->parent() )
1124           {
1125             kind = p->parent()->parent()->parent()->kind();
1126           }
1127           isFirst=isFirstChildNode((DocParBlock*)p->parent(),p);
1128           isLast =isLastChildNode ((DocParBlock*)p->parent(),p);
1129           t=NONE;
1130           if (isFirst)
1131           {
1132             if (kind==DocNode::Kind_HtmlListItem ||
1133                 kind==DocNode::Kind_SecRefItem)
1134             {
1135               t=STARTLI;
1136             }
1137             else if (kind==DocNode::Kind_HtmlDescData ||
1138                      kind==DocNode::Kind_XRefItem ||
1139                      kind==DocNode::Kind_SimpleSect)
1140             {
1141               t=STARTDD;
1142             }
1143             else if (kind==DocNode::Kind_HtmlCell ||
1144                      kind==DocNode::Kind_ParamList)
1145             {
1146               t=STARTTD;
1147             }
1148           }
1149           if (isLast)
1150           {
1151             if (kind==DocNode::Kind_HtmlListItem ||
1152                 kind==DocNode::Kind_SecRefItem)
1153             {
1154               t=ENDLI;
1155             }
1156             else if (kind==DocNode::Kind_HtmlDescData ||
1157                      kind==DocNode::Kind_XRefItem ||
1158                      kind==DocNode::Kind_SimpleSect)
1159             {
1160               t=ENDDD;
1161             }
1162             else if (kind==DocNode::Kind_HtmlCell ||
1163                      kind==DocNode::Kind_ParamList)
1164             {
1165               t=ENDTD;
1166             }
1167           }
1168           if (!isFirst && !isLast)
1169           {
1170             if (kind==DocNode::Kind_HtmlListItem ||
1171                 kind==DocNode::Kind_SecRefItem)
1172             {
1173               t=INTERLI;
1174             }
1175             else if (kind==DocNode::Kind_HtmlDescData ||
1176                      kind==DocNode::Kind_XRefItem ||
1177                      kind==DocNode::Kind_SimpleSect)
1178             {
1179               t=INTERDD;
1180             }
1181             else if (kind==DocNode::Kind_HtmlCell ||
1182                      kind==DocNode::Kind_ParamList)
1183             {
1184               t=INTERTD;
1185             }
1186           }
1187           break;
1188         }
1189       case DocNode::Kind_AutoListItem:
1190         isFirst=isFirstChildNode((DocAutoListItem*)p->parent(),p);
1191         isLast =isLastChildNode ((DocAutoListItem*)p->parent(),p);
1192         t=STARTLI; // not used
1193         break;
1194       case DocNode::Kind_SimpleListItem:
1195         isFirst=TRUE;
1196         isLast =TRUE;
1197         t=STARTLI; // not used
1198         break;
1199       case DocNode::Kind_ParamList:
1200         isFirst=TRUE;
1201         isLast =TRUE;
1202         t=STARTLI; // not used
1203         break;
1204       case DocNode::Kind_HtmlListItem:
1205         isFirst=isFirstChildNode((DocHtmlListItem*)p->parent(),p);
1206         isLast =isLastChildNode ((DocHtmlListItem*)p->parent(),p);
1207         if (isFirst) t=STARTLI;
1208         if (isLast)  t=ENDLI;
1209         if (!isFirst && !isLast) t = INTERLI;
1210         break;
1211       case DocNode::Kind_SecRefItem:
1212         isFirst=isFirstChildNode((DocSecRefItem*)p->parent(),p);
1213         isLast =isLastChildNode ((DocSecRefItem*)p->parent(),p);
1214         if (isFirst) t=STARTLI;
1215         if (isLast)  t=ENDLI;
1216         if (!isFirst && !isLast) t = INTERLI;
1217         break;
1218       case DocNode::Kind_HtmlDescData:
1219         isFirst=isFirstChildNode((DocHtmlDescData*)p->parent(),p);
1220         isLast =isLastChildNode ((DocHtmlDescData*)p->parent(),p);
1221         if (isFirst) t=STARTDD;
1222         if (isLast)  t=ENDDD;
1223         if (!isFirst && !isLast) t = INTERDD;
1224         break;
1225       case DocNode::Kind_XRefItem:
1226         isFirst=isFirstChildNode((DocXRefItem*)p->parent(),p);
1227         isLast =isLastChildNode ((DocXRefItem*)p->parent(),p);
1228         if (isFirst) t=STARTDD;
1229         if (isLast)  t=ENDDD;
1230         if (!isFirst && !isLast) t = INTERDD;
1231         break;
1232       case DocNode::Kind_SimpleSect:
1233         isFirst=isFirstChildNode((DocSimpleSect*)p->parent(),p);
1234         isLast =isLastChildNode ((DocSimpleSect*)p->parent(),p);
1235         if (isFirst) t=STARTDD;
1236         if (isLast)  t=ENDDD;
1237         if (isSeparatedParagraph((DocSimpleSect*)p->parent(),p))
1238           // if the paragraph is enclosed with separators it will
1239           // be included in <dd>..</dd> so avoid addition paragraph
1240           // markers
1241         {
1242           isFirst=isLast=TRUE;
1243         }
1244         if (!isFirst && !isLast) t = INTERDD;
1245         break;
1246       case DocNode::Kind_HtmlCell:
1247         isFirst=isFirstChildNode((DocHtmlCell*)p->parent(),p);
1248         isLast =isLastChildNode ((DocHtmlCell*)p->parent(),p);
1249         if (isFirst) t=STARTTD;
1250         if (isLast)  t=ENDTD;
1251         if (!isFirst && !isLast) t = INTERTD;
1252         break;
1253       default:
1254         break;
1255     }
1256     //printf("para=%p parent()->kind=%d isFirst=%d isLast=%d t=%d\n",
1257     //    p,p->parent()->kind(),isFirst,isLast,t);
1258   }
1259   return t;
1260 }
1261 
visitPre(DocPara * p)1262 void HtmlDocVisitor::visitPre(DocPara *p)
1263 {
1264   if (m_hide) return;
1265 
1266   //printf("DocPara::visitPre: parent of kind %d ",
1267   //       p->parent() ? p->parent()->kind() : -1);
1268 
1269   bool needsTag = FALSE;
1270   if (p && p->parent())
1271   {
1272     switch (p->parent()->kind())
1273     {
1274       case DocNode::Kind_Section:
1275       case DocNode::Kind_Internal:
1276       case DocNode::Kind_HtmlListItem:
1277       case DocNode::Kind_HtmlDescData:
1278       case DocNode::Kind_HtmlCell:
1279       case DocNode::Kind_SimpleListItem:
1280       case DocNode::Kind_AutoListItem:
1281       case DocNode::Kind_SimpleSect:
1282       case DocNode::Kind_XRefItem:
1283       case DocNode::Kind_Copy:
1284       case DocNode::Kind_HtmlBlockQuote:
1285       case DocNode::Kind_ParBlock:
1286         needsTag = TRUE;
1287         break;
1288       case DocNode::Kind_Root:
1289         needsTag = !((DocRoot*)p->parent())->singleLine();
1290         break;
1291       default:
1292         needsTag = FALSE;
1293     }
1294   }
1295 
1296   // if the first element of a paragraph is something that should be outside of
1297   // the paragraph (<ul>,<dl>,<table>,..) then that will already started the
1298   // paragraph and we don't need to do it here
1299   size_t nodeIndex = 0;
1300   if (p && nodeIndex<p->children().size())
1301   {
1302     while (nodeIndex<p->children().size() && isInvisibleNode(p->children().at(nodeIndex).get()))
1303     {
1304       nodeIndex++;
1305     }
1306     if (nodeIndex<p->children().size())
1307     {
1308       const DocNode *n = p->children().at(nodeIndex).get();
1309       if (mustBeOutsideParagraph(n))
1310       {
1311         needsTag = FALSE;
1312       }
1313     }
1314   }
1315 
1316   // check if this paragraph is the first or last or intermediate child of a <li> or <dd>.
1317   // this allows us to mark the tag with a special class so we can
1318   // fix the otherwise ugly spacing.
1319   int t;
1320   bool isFirst;
1321   bool isLast;
1322   t = getParagraphContext(p,isFirst,isLast);
1323   //printf("startPara first=%d last=%d\n",isFirst,isLast);
1324   if (isFirst && isLast) needsTag=FALSE;
1325 
1326   //printf("  needsTag=%d\n",needsTag);
1327   // write the paragraph tag (if needed)
1328   if (needsTag)
1329   {
1330     if (strlen(contexts[t]))
1331       m_t << "<p class=\"" << contexts[t] << "\"" << htmlAttribsToString(p->attribs()) << ">";
1332     else
1333       m_t << "<p " << htmlAttribsToString(p->attribs()) << ">";
1334   }
1335 }
1336 
visitPost(DocPara * p)1337 void HtmlDocVisitor::visitPost(DocPara *p)
1338 {
1339 
1340   //printf("DocPara::visitPost: parent of kind %d ",
1341   //       p->parent() ? p->parent()->kind() : -1);
1342 
1343   bool needsTag = FALSE;
1344   if (p->parent())
1345   {
1346     switch (p->parent()->kind())
1347     {
1348       case DocNode::Kind_Section:
1349       case DocNode::Kind_Internal:
1350       case DocNode::Kind_HtmlListItem:
1351       case DocNode::Kind_HtmlDescData:
1352       case DocNode::Kind_HtmlCell:
1353       case DocNode::Kind_SimpleListItem:
1354       case DocNode::Kind_AutoListItem:
1355       case DocNode::Kind_SimpleSect:
1356       case DocNode::Kind_XRefItem:
1357       case DocNode::Kind_Copy:
1358       case DocNode::Kind_HtmlBlockQuote:
1359       case DocNode::Kind_ParBlock:
1360         needsTag = TRUE;
1361         break;
1362       case DocNode::Kind_Root:
1363         needsTag = !((DocRoot*)p->parent())->singleLine();
1364         break;
1365       default:
1366         needsTag = FALSE;
1367     }
1368   }
1369 
1370   // if the last element of a paragraph is something that should be outside of
1371   // the paragraph (<ul>,<dl>,<table>) then that will already have ended the
1372   // paragraph and we don't need to do it here
1373   if (!p->children().empty())
1374   {
1375     int nodeIndex = static_cast<int>(p->children().size()-1);
1376     while (nodeIndex>=0 && isInvisibleNode(p->children().at(nodeIndex).get()))
1377     {
1378       nodeIndex--;
1379     }
1380     if (nodeIndex>=0)
1381     {
1382       const DocNode *n = p->children().at(nodeIndex).get();
1383       if (mustBeOutsideParagraph(n))
1384       {
1385         needsTag = FALSE;
1386       }
1387     }
1388   }
1389 
1390   bool isFirst;
1391   bool isLast;
1392   getParagraphContext(p,isFirst,isLast);
1393   //printf("endPara first=%d last=%d\n",isFirst,isLast);
1394   if (isFirst && isLast) needsTag=FALSE;
1395 
1396   //printf("DocPara::visitPost needsTag=%d\n",needsTag);
1397 
1398   if (needsTag) m_t << "</p>\n";
1399 
1400 }
1401 
visitPre(DocRoot *)1402 void HtmlDocVisitor::visitPre(DocRoot *)
1403 {
1404 }
1405 
visitPost(DocRoot *)1406 void HtmlDocVisitor::visitPost(DocRoot *)
1407 {
1408 }
1409 
visitPre(DocSimpleSect * s)1410 void HtmlDocVisitor::visitPre(DocSimpleSect *s)
1411 {
1412   if (m_hide) return;
1413   forceEndParagraph(s);
1414   m_t << "<dl class=\"section " << s->typeString() << "\"><dt>";
1415   switch(s->type())
1416   {
1417     case DocSimpleSect::See:
1418       m_t << theTranslator->trSeeAlso(); break;
1419     case DocSimpleSect::Return:
1420       m_t << theTranslator->trReturns(); break;
1421     case DocSimpleSect::Author:
1422       m_t << theTranslator->trAuthor(TRUE,TRUE); break;
1423     case DocSimpleSect::Authors:
1424       m_t << theTranslator->trAuthor(TRUE,FALSE); break;
1425     case DocSimpleSect::Version:
1426       m_t << theTranslator->trVersion(); break;
1427     case DocSimpleSect::Since:
1428       m_t << theTranslator->trSince(); break;
1429     case DocSimpleSect::Date:
1430       m_t << theTranslator->trDate(); break;
1431     case DocSimpleSect::Note:
1432       m_t << theTranslator->trNote(); break;
1433     case DocSimpleSect::Warning:
1434       m_t << theTranslator->trWarning(); break;
1435     case DocSimpleSect::Pre:
1436       m_t << theTranslator->trPrecondition(); break;
1437     case DocSimpleSect::Post:
1438       m_t << theTranslator->trPostcondition(); break;
1439     case DocSimpleSect::Copyright:
1440       m_t << theTranslator->trCopyright(); break;
1441     case DocSimpleSect::Invar:
1442       m_t << theTranslator->trInvariant(); break;
1443     case DocSimpleSect::Remark:
1444       m_t << theTranslator->trRemarks(); break;
1445     case DocSimpleSect::Attention:
1446       m_t << theTranslator->trAttention(); break;
1447     case DocSimpleSect::User: break;
1448     case DocSimpleSect::Rcs: break;
1449     case DocSimpleSect::Unknown:  break;
1450   }
1451 
1452   // special case 1: user defined title
1453   if (s->type()!=DocSimpleSect::User && s->type()!=DocSimpleSect::Rcs)
1454   {
1455     m_t << "</dt><dd>";
1456   }
1457 }
1458 
visitPost(DocSimpleSect * s)1459 void HtmlDocVisitor::visitPost(DocSimpleSect *s)
1460 {
1461   if (m_hide) return;
1462   m_t << "</dd></dl>\n";
1463   forceStartParagraph(s);
1464 }
1465 
visitPre(DocTitle *)1466 void HtmlDocVisitor::visitPre(DocTitle *)
1467 {
1468 }
1469 
visitPost(DocTitle *)1470 void HtmlDocVisitor::visitPost(DocTitle *)
1471 {
1472   if (m_hide) return;
1473   m_t << "</dt><dd>";
1474 }
1475 
visitPre(DocSimpleList * sl)1476 void HtmlDocVisitor::visitPre(DocSimpleList *sl)
1477 {
1478   if (m_hide) return;
1479   forceEndParagraph(sl);
1480   m_t << "<ul>";
1481   if (!sl->isPreformatted()) m_t << "\n";
1482 
1483 }
1484 
visitPost(DocSimpleList * sl)1485 void HtmlDocVisitor::visitPost(DocSimpleList *sl)
1486 {
1487   if (m_hide) return;
1488   m_t << "</ul>";
1489   if (!sl->isPreformatted()) m_t << "\n";
1490   forceStartParagraph(sl);
1491 }
1492 
visitPre(DocSimpleListItem *)1493 void HtmlDocVisitor::visitPre(DocSimpleListItem *)
1494 {
1495   if (m_hide) return;
1496   m_t << "<li>";
1497 }
1498 
visitPost(DocSimpleListItem * li)1499 void HtmlDocVisitor::visitPost(DocSimpleListItem *li)
1500 {
1501   if (m_hide) return;
1502   m_t << "</li>";
1503   if (!li->isPreformatted()) m_t << "\n";
1504 }
1505 
visitPre(DocSection * s)1506 void HtmlDocVisitor::visitPre(DocSection *s)
1507 {
1508   if (m_hide) return;
1509   forceEndParagraph(s);
1510   m_t << "<h" << s->level() << ">";
1511   m_t << "<a class=\"anchor\" id=\"" << s->anchor();
1512   m_t << "\"></a>\n";
1513   filter(convertCharEntitiesToUTF8(s->title()));
1514   m_t << "</h" << s->level() << ">\n";
1515 }
1516 
visitPost(DocSection * s)1517 void HtmlDocVisitor::visitPost(DocSection *s)
1518 {
1519   forceStartParagraph(s);
1520 }
1521 
visitPre(DocHtmlList * s)1522 void HtmlDocVisitor::visitPre(DocHtmlList *s)
1523 {
1524   if (m_hide) return;
1525   forceEndParagraph(s);
1526   if (s->type()==DocHtmlList::Ordered)
1527   {
1528     m_t << "<ol" << htmlAttribsToString(s->attribs());
1529   }
1530   else
1531   {
1532     m_t << "<ul" << htmlAttribsToString(s->attribs());
1533   }
1534   m_t << ">\n";
1535 }
1536 
visitPost(DocHtmlList * s)1537 void HtmlDocVisitor::visitPost(DocHtmlList *s)
1538 {
1539   if (m_hide) return;
1540   if (s->type()==DocHtmlList::Ordered)
1541   {
1542     m_t << "</ol>";
1543   }
1544   else
1545   {
1546     m_t << "</ul>";
1547   }
1548   if (!s->isPreformatted()) m_t << "\n";
1549   forceStartParagraph(s);
1550 }
1551 
visitPre(DocHtmlListItem * i)1552 void HtmlDocVisitor::visitPre(DocHtmlListItem *i)
1553 {
1554   if (m_hide) return;
1555   m_t << "<li" << htmlAttribsToString(i->attribs()) << ">";
1556   if (!i->isPreformatted()) m_t << "\n";
1557 }
1558 
visitPost(DocHtmlListItem *)1559 void HtmlDocVisitor::visitPost(DocHtmlListItem *)
1560 {
1561   if (m_hide) return;
1562   m_t << "</li>\n";
1563 }
1564 
visitPre(DocHtmlDescList * dl)1565 void HtmlDocVisitor::visitPre(DocHtmlDescList *dl)
1566 {
1567   if (m_hide) return;
1568   forceEndParagraph(dl);
1569   m_t << "<dl" << htmlAttribsToString(dl->attribs()) << ">\n";
1570 }
1571 
visitPost(DocHtmlDescList * dl)1572 void HtmlDocVisitor::visitPost(DocHtmlDescList *dl)
1573 {
1574   if (m_hide) return;
1575   m_t << "</dl>\n";
1576   forceStartParagraph(dl);
1577 }
1578 
visitPre(DocHtmlDescTitle * dt)1579 void HtmlDocVisitor::visitPre(DocHtmlDescTitle *dt)
1580 {
1581   if (m_hide) return;
1582   m_t << "<dt" << htmlAttribsToString(dt->attribs()) << ">";
1583 }
1584 
visitPost(DocHtmlDescTitle *)1585 void HtmlDocVisitor::visitPost(DocHtmlDescTitle *)
1586 {
1587   if (m_hide) return;
1588   m_t << "</dt>\n";
1589 }
1590 
visitPre(DocHtmlDescData * dd)1591 void HtmlDocVisitor::visitPre(DocHtmlDescData *dd)
1592 {
1593   if (m_hide) return;
1594   m_t << "<dd" << htmlAttribsToString(dd->attribs()) << ">";
1595 }
1596 
visitPost(DocHtmlDescData *)1597 void HtmlDocVisitor::visitPost(DocHtmlDescData *)
1598 {
1599   if (m_hide) return;
1600   m_t << "</dd>\n";
1601 }
1602 
visitPre(DocHtmlTable * t)1603 void HtmlDocVisitor::visitPre(DocHtmlTable *t)
1604 {
1605   if (m_hide) return;
1606 
1607   forceEndParagraph(t);
1608 
1609   if (t->hasCaption())
1610   {
1611     QCString anc =  t->caption()->anchor();
1612     if (!anc.isEmpty())
1613     {
1614       m_t << "<a class=\"anchor\" id=\"" << anc << "\"></a>\n";
1615     }
1616   }
1617 
1618   QCString attrs = htmlAttribsToString(t->attribs());
1619   if (attrs.isEmpty())
1620   {
1621     m_t << "<table class=\"doxtable\">\n";
1622   }
1623   else
1624   {
1625     m_t << "<table" << htmlAttribsToString(t->attribs()) << ">\n";
1626   }
1627 }
1628 
visitPost(DocHtmlTable * t)1629 void HtmlDocVisitor::visitPost(DocHtmlTable *t)
1630 {
1631   if (m_hide) return;
1632   m_t << "</table>\n";
1633   forceStartParagraph(t);
1634 }
1635 
visitPre(DocHtmlRow * tr)1636 void HtmlDocVisitor::visitPre(DocHtmlRow *tr)
1637 {
1638   if (m_hide) return;
1639   m_t << "<tr" << htmlAttribsToString(tr->attribs()) << ">\n";
1640 }
1641 
visitPost(DocHtmlRow *)1642 void HtmlDocVisitor::visitPost(DocHtmlRow *)
1643 {
1644   if (m_hide) return;
1645   m_t << "</tr>\n";
1646 }
1647 
visitPre(DocHtmlCell * c)1648 void HtmlDocVisitor::visitPre(DocHtmlCell *c)
1649 {
1650   if (m_hide) return;
1651   if (c->isHeading())
1652   {
1653     m_t << "<th" << htmlAttribsToString(c->attribs()) << ">";
1654   }
1655   else
1656   {
1657     m_t << "<td" << htmlAttribsToString(c->attribs()) << ">";
1658   }
1659 }
1660 
visitPost(DocHtmlCell * c)1661 void HtmlDocVisitor::visitPost(DocHtmlCell *c)
1662 {
1663   if (m_hide) return;
1664   if (c->isHeading()) m_t << "</th>"; else m_t << "</td>";
1665 }
1666 
visitPre(DocHtmlCaption * c)1667 void HtmlDocVisitor::visitPre(DocHtmlCaption *c)
1668 {
1669   if (m_hide) return;
1670   m_t << "<caption" << htmlAttribsToString(c->attribs()) << ">";
1671 }
1672 
visitPost(DocHtmlCaption *)1673 void HtmlDocVisitor::visitPost(DocHtmlCaption *)
1674 {
1675   if (m_hide) return;
1676   m_t << "</caption>\n";
1677 }
1678 
visitPre(DocInternal *)1679 void HtmlDocVisitor::visitPre(DocInternal *)
1680 {
1681   if (m_hide) return;
1682   //forceEndParagraph(i);
1683   //m_t << "<p><b>" << theTranslator->trForInternalUseOnly() << "</b></p>\n";
1684 }
1685 
visitPost(DocInternal *)1686 void HtmlDocVisitor::visitPost(DocInternal *)
1687 {
1688   if (m_hide) return;
1689   //forceStartParagraph(i);
1690 }
1691 
visitPre(DocHRef * href)1692 void HtmlDocVisitor::visitPre(DocHRef *href)
1693 {
1694   if (m_hide) return;
1695   if (href->url().left(7)=="mailto:")
1696   {
1697     writeObfuscatedMailAddress(href->url().mid(7));
1698   }
1699   else
1700   {
1701     QCString url = correctURL(href->url(),href->relPath());
1702     m_t << "<a href=\"" << convertToHtml(url)  << "\""
1703         << htmlAttribsToString(href->attribs()) << ">";
1704   }
1705 }
1706 
visitPost(DocHRef *)1707 void HtmlDocVisitor::visitPost(DocHRef *)
1708 {
1709   if (m_hide) return;
1710   m_t << "</a>";
1711 }
1712 
visitPre(DocHtmlHeader * header)1713 void HtmlDocVisitor::visitPre(DocHtmlHeader *header)
1714 {
1715   if (m_hide) return;
1716   forceEndParagraph(header);
1717   m_t << "<h" << header->level() << htmlAttribsToString(header->attribs()) << ">";
1718 }
1719 
visitPost(DocHtmlHeader * header)1720 void HtmlDocVisitor::visitPost(DocHtmlHeader *header)
1721 {
1722   if (m_hide) return;
1723   m_t << "</h" << header->level() << ">\n";
1724   forceStartParagraph(header);
1725 }
1726 
visitPre(DocImage * img)1727 void HtmlDocVisitor::visitPre(DocImage *img)
1728 {
1729   if (img->type()==DocImage::Html)
1730   {
1731     bool inlineImage = img->isInlineImage();
1732     bool typeSVG = img->isSVG();
1733     QCString url = img->url();
1734 
1735     if (!inlineImage)
1736     {
1737       forceEndParagraph(img);
1738     }
1739     if (m_hide) return;
1740     QCString baseName=img->name();
1741     int i;
1742     if ((i=baseName.findRev('/'))!=-1 || (i=baseName.findRev('\\'))!=-1)
1743     {
1744       baseName=baseName.right(baseName.length()-i-1);
1745     }
1746     if (!inlineImage) m_t << "<div class=\"image\">\n";
1747     QCString sizeAttribs;
1748     if (!img->width().isEmpty())
1749     {
1750       sizeAttribs+=" width=\""+img->width()+"\"";
1751     }
1752     if (!img->height().isEmpty()) // link to local file
1753     {
1754       sizeAttribs+=" height=\""+img->height()+"\"";
1755     }
1756     // 16 cases: url.isEmpty() | typeSVG | inlineImage | img->hasCaption()
1757 
1758     HtmlAttribList extraAttribs;
1759     if (typeSVG)
1760     {
1761       HtmlAttrib opt;
1762       opt.name  = "style";
1763       opt.value = "pointer-events: none;";
1764       extraAttribs.push_back(opt);
1765     }
1766     QCString alt;
1767     mergeHtmlAttributes(img->attribs(),extraAttribs);
1768     QCString attrs = htmlAttribsToString(extraAttribs,&alt);
1769     QCString src;
1770     if (url.isEmpty())
1771     {
1772       src = img->relPath()+img->name();
1773     }
1774     else
1775     {
1776       src = correctURL(url,img->relPath());
1777     }
1778     if (typeSVG && !inlineImage)
1779     {
1780       m_t << "<object type=\"image/svg+xml\" data=\"" << convertToHtml(src)
1781         << "\"" << sizeAttribs << attrs;
1782       if (inlineImage)
1783       {
1784         // skip closing tag
1785       }
1786       else
1787       {
1788         m_t << ">" << alt << "</object>\n";
1789       }
1790     }
1791     else
1792     {
1793       m_t << "<img src=\"" << convertToHtml(src) << "\" alt=\"" << alt << "\"" << sizeAttribs << attrs;
1794       if (inlineImage)
1795       {
1796         m_t << " class=\"inline\"";
1797       }
1798       else
1799       {
1800         m_t << "/>\n";
1801       }
1802     }
1803     if (img->hasCaption())
1804     {
1805       if (inlineImage)
1806       {
1807         m_t << " title=\"";
1808         m_insideTitle=true;
1809       }
1810       else
1811       {
1812         m_t << "<div class=\"caption\">\n";
1813       }
1814     }
1815     else if (inlineImage)
1816     {
1817       m_t << "/>";
1818     }
1819   }
1820   else // other format -> skip
1821   {
1822     pushHidden(m_hide);
1823     m_hide=TRUE;
1824   }
1825 }
1826 
visitPost(DocImage * img)1827 void HtmlDocVisitor::visitPost(DocImage *img)
1828 {
1829   if (img->type()==DocImage::Html)
1830   {
1831     if (m_hide) return;
1832     bool inlineImage = img->isInlineImage();
1833     if (img->hasCaption())
1834     {
1835       if (inlineImage)
1836       {
1837         m_t << "\"/>";
1838         m_insideTitle=false;
1839       }
1840       else // end <div class="caption">
1841       {
1842         m_t << "</div>";
1843       }
1844     }
1845     if (!inlineImage) // end <div class="image">
1846     {
1847       m_t << "</div>\n";
1848       forceStartParagraph(img);
1849     }
1850   }
1851   else // other format
1852   {
1853     m_hide = popHidden();
1854   }
1855 }
1856 
visitPre(DocDotFile * df)1857 void HtmlDocVisitor::visitPre(DocDotFile *df)
1858 {
1859   if (m_hide) return;
1860   if (!Config_getBool(DOT_CLEANUP)) copyFile(df->file(),Config_getString(HTML_OUTPUT)+"/"+stripPath(df->file()));
1861   m_t << "<div class=\"dotgraph\">\n";
1862   writeDotFile(df->file(),df->relPath(),df->context(),df->srcFile(),df->srcLine());
1863   if (df->hasCaption())
1864   {
1865     m_t << "<div class=\"caption\">\n";
1866   }
1867 }
1868 
visitPost(DocDotFile * df)1869 void HtmlDocVisitor::visitPost(DocDotFile *df)
1870 {
1871   if (m_hide) return;
1872   if (df->hasCaption())
1873   {
1874     m_t << "</div>\n";
1875   }
1876   m_t << "</div>\n";
1877 }
1878 
visitPre(DocMscFile * df)1879 void HtmlDocVisitor::visitPre(DocMscFile *df)
1880 {
1881   if (m_hide) return;
1882   if (!Config_getBool(DOT_CLEANUP)) copyFile(df->file(),Config_getString(HTML_OUTPUT)+"/"+stripPath(df->file()));
1883   m_t << "<div class=\"mscgraph\">\n";
1884   writeMscFile(df->file(),df->relPath(),df->context(),df->srcFile(),df->srcLine());
1885   if (df->hasCaption())
1886   {
1887     m_t << "<div class=\"caption\">\n";
1888   }
1889 }
visitPost(DocMscFile * df)1890 void HtmlDocVisitor::visitPost(DocMscFile *df)
1891 {
1892   if (m_hide) return;
1893   if (df->hasCaption())
1894   {
1895     m_t << "</div>\n";
1896   }
1897   m_t << "</div>\n";
1898 }
1899 
visitPre(DocDiaFile * df)1900 void HtmlDocVisitor::visitPre(DocDiaFile *df)
1901 {
1902   if (m_hide) return;
1903   if (!Config_getBool(DOT_CLEANUP)) copyFile(df->file(),Config_getString(HTML_OUTPUT)+"/"+stripPath(df->file()));
1904   m_t << "<div class=\"diagraph\">\n";
1905   writeDiaFile(df->file(),df->relPath(),df->context(),df->srcFile(),df->srcLine());
1906   if (df->hasCaption())
1907   {
1908     m_t << "<div class=\"caption\">\n";
1909   }
1910 }
visitPost(DocDiaFile * df)1911 void HtmlDocVisitor::visitPost(DocDiaFile *df)
1912 {
1913   if (m_hide) return;
1914   if (df->hasCaption())
1915   {
1916     m_t << "</div>\n";
1917   }
1918   m_t << "</div>\n";
1919 }
1920 
visitPre(DocLink * lnk)1921 void HtmlDocVisitor::visitPre(DocLink *lnk)
1922 {
1923   if (m_hide) return;
1924   startLink(lnk->ref(),lnk->file(),lnk->relPath(),lnk->anchor());
1925 }
1926 
visitPost(DocLink *)1927 void HtmlDocVisitor::visitPost(DocLink *)
1928 {
1929   if (m_hide) return;
1930   endLink();
1931 }
1932 
visitPre(DocRef * ref)1933 void HtmlDocVisitor::visitPre(DocRef *ref)
1934 {
1935   if (m_hide) return;
1936   if (!ref->file().isEmpty())
1937   {
1938     // when ref->isSubPage()==TRUE we use ref->file() for HTML and
1939     // ref->anchor() for LaTeX/RTF
1940     startLink(ref->ref(),ref->file(),ref->relPath(),ref->isSubPage() ? QCString() : ref->anchor());
1941   }
1942   if (!ref->hasLinkText()) filter(ref->targetTitle());
1943 }
1944 
visitPost(DocRef * ref)1945 void HtmlDocVisitor::visitPost(DocRef *ref)
1946 {
1947   if (m_hide) return;
1948   if (!ref->file().isEmpty()) endLink();
1949   //m_t << " ";
1950 }
1951 
visitPre(DocSecRefItem * ref)1952 void HtmlDocVisitor::visitPre(DocSecRefItem *ref)
1953 {
1954   if (m_hide) return;
1955   if (!ref->file().isEmpty())
1956   {
1957     m_t << "<li>";
1958     startLink(ref->ref(),ref->file(),ref->relPath(),ref->isSubPage() ? QCString() : ref->anchor());
1959   }
1960 }
1961 
visitPost(DocSecRefItem * ref)1962 void HtmlDocVisitor::visitPost(DocSecRefItem *ref)
1963 {
1964   if (m_hide) return;
1965   if (!ref->file().isEmpty())
1966   {
1967     endLink();
1968     m_t << "</li>\n";
1969   }
1970 }
1971 
visitPre(DocSecRefList * s)1972 void HtmlDocVisitor::visitPre(DocSecRefList *s)
1973 {
1974   if (m_hide) return;
1975   forceEndParagraph(s);
1976   m_t << "<div>\n";
1977   m_t << "<ul class=\"multicol\">\n";
1978 }
1979 
visitPost(DocSecRefList * s)1980 void HtmlDocVisitor::visitPost(DocSecRefList *s)
1981 {
1982   if (m_hide) return;
1983   m_t << "</ul>\n";
1984   m_t << "</div>\n";
1985   forceStartParagraph(s);
1986 }
1987 
visitPre(DocParamSect * s)1988 void HtmlDocVisitor::visitPre(DocParamSect *s)
1989 {
1990   if (m_hide) return;
1991   forceEndParagraph(s);
1992   QCString className;
1993   QCString heading;
1994   switch(s->type())
1995   {
1996     case DocParamSect::Param:
1997       heading=theTranslator->trParameters();
1998       className="params";
1999       break;
2000     case DocParamSect::RetVal:
2001       heading=theTranslator->trReturnValues();
2002       className="retval";
2003       break;
2004     case DocParamSect::Exception:
2005       heading=theTranslator->trExceptions();
2006       className="exception";
2007       break;
2008     case DocParamSect::TemplateParam:
2009       heading=theTranslator->trTemplateParameters();
2010       className="tparams";
2011       break;
2012     default:
2013       ASSERT(0);
2014   }
2015   m_t << "<dl class=\"" << className << "\"><dt>";
2016   m_t << heading;
2017   m_t << "</dt><dd>\n";
2018   m_t << "  <table class=\"" << className << "\">\n";
2019 }
2020 
visitPost(DocParamSect * s)2021 void HtmlDocVisitor::visitPost(DocParamSect *s)
2022 {
2023   if (m_hide) return;
2024   m_t << "  </table>\n";
2025   m_t << "  </dd>\n";
2026   m_t << "</dl>\n";
2027   forceStartParagraph(s);
2028 }
2029 
visitPre(DocParamList * pl)2030 void HtmlDocVisitor::visitPre(DocParamList *pl)
2031 {
2032   //printf("DocParamList::visitPre\n");
2033   if (m_hide) return;
2034   m_t << "    <tr>";
2035   DocParamSect *sect = 0;
2036   if (pl->parent()->kind()==DocNode::Kind_ParamSect)
2037   {
2038     sect=(DocParamSect*)pl->parent();
2039   }
2040   if (sect && sect->hasInOutSpecifier())
2041   {
2042     m_t << "<td class=\"paramdir\">";
2043     if (pl->direction()!=DocParamSect::Unspecified)
2044     {
2045       m_t << "[";
2046       if (pl->direction()==DocParamSect::In)
2047       {
2048         m_t << "in";
2049       }
2050       else if (pl->direction()==DocParamSect::Out)
2051       {
2052         m_t << "out";
2053       }
2054       else if (pl->direction()==DocParamSect::InOut)
2055       {
2056         m_t << "in,out";
2057       }
2058       m_t << "]";
2059     }
2060     m_t << "</td>";
2061   }
2062   if (sect && sect->hasTypeSpecifier())
2063   {
2064     m_t << "<td class=\"paramtype\">";
2065     for (const auto &type : pl->paramTypes())
2066     {
2067       if (type->kind()==DocNode::Kind_Word)
2068       {
2069         visit((DocWord*)type.get());
2070       }
2071       else if (type->kind()==DocNode::Kind_LinkedWord)
2072       {
2073         visit((DocLinkedWord*)type.get());
2074       }
2075       else if (type->kind()==DocNode::Kind_Sep)
2076       {
2077         m_t << "&#160;" << ((DocSeparator *)type.get())->chars() << "&#160;";
2078       }
2079     }
2080     m_t << "</td>";
2081   }
2082   m_t << "<td class=\"paramname\">";
2083   bool first=TRUE;
2084   for (const auto &param : pl->parameters())
2085   {
2086     if (!first) m_t << ","; else first=FALSE;
2087     if (param->kind()==DocNode::Kind_Word)
2088     {
2089       visit((DocWord*)param.get());
2090     }
2091     else if (param->kind()==DocNode::Kind_LinkedWord)
2092     {
2093       visit((DocLinkedWord*)param.get());
2094     }
2095   }
2096   m_t << "</td><td>";
2097 }
2098 
visitPost(DocParamList *)2099 void HtmlDocVisitor::visitPost(DocParamList *)
2100 {
2101   //printf("DocParamList::visitPost\n");
2102   if (m_hide) return;
2103   m_t << "</td></tr>\n";
2104 }
2105 
visitPre(DocXRefItem * x)2106 void HtmlDocVisitor::visitPre(DocXRefItem *x)
2107 {
2108   if (m_hide) return;
2109   if (x->title().isEmpty()) return;
2110 
2111   forceEndParagraph(x);
2112   bool anonymousEnum = x->file()=="@";
2113   if (!anonymousEnum)
2114   {
2115     m_t << "<dl class=\"" << x->key() << "\"><dt><b><a class=\"el\" href=\""
2116         << x->relPath() << addHtmlExtensionIfMissing(x->file())
2117         << "#" << x->anchor() << "\">";
2118   }
2119   else
2120   {
2121     m_t << "<dl class=\"" << x->key() << "\"><dt><b>";
2122   }
2123   filter(x->title());
2124   m_t << ":";
2125   if (!anonymousEnum) m_t << "</a>";
2126   m_t << "</b></dt><dd>";
2127 }
2128 
visitPost(DocXRefItem * x)2129 void HtmlDocVisitor::visitPost(DocXRefItem *x)
2130 {
2131   if (m_hide) return;
2132   if (x->title().isEmpty()) return;
2133   m_t << "</dd></dl>\n";
2134   forceStartParagraph(x);
2135 }
2136 
visitPre(DocInternalRef * ref)2137 void HtmlDocVisitor::visitPre(DocInternalRef *ref)
2138 {
2139   if (m_hide) return;
2140   startLink(QCString(),ref->file(),ref->relPath(),ref->anchor());
2141 }
2142 
visitPost(DocInternalRef *)2143 void HtmlDocVisitor::visitPost(DocInternalRef *)
2144 {
2145   if (m_hide) return;
2146   endLink();
2147   m_t << " ";
2148 }
2149 
visitPre(DocText *)2150 void HtmlDocVisitor::visitPre(DocText *)
2151 {
2152 }
2153 
visitPost(DocText *)2154 void HtmlDocVisitor::visitPost(DocText *)
2155 {
2156 }
2157 
visitPre(DocHtmlBlockQuote * b)2158 void HtmlDocVisitor::visitPre(DocHtmlBlockQuote *b)
2159 {
2160   if (m_hide) return;
2161   forceEndParagraph(b);
2162   QCString attrs = htmlAttribsToString(b->attribs());
2163   m_t << "<blockquote class=\"doxtable\"" << htmlAttribsToString(b->attribs()) << ">\n";
2164 }
2165 
visitPost(DocHtmlBlockQuote * b)2166 void HtmlDocVisitor::visitPost(DocHtmlBlockQuote *b)
2167 {
2168   if (m_hide) return;
2169   m_t << "</blockquote>\n";
2170   forceStartParagraph(b);
2171 }
2172 
visitPre(DocVhdlFlow * vf)2173 void HtmlDocVisitor::visitPre(DocVhdlFlow *vf)
2174 {
2175   if (m_hide) return;
2176   if (VhdlDocGen::getFlowMember()) // use VHDL flow chart creator
2177   {
2178     forceEndParagraph(vf);
2179     QCString fname=FlowChart::convertNameToFileName();
2180     m_t << "<p>";
2181     m_t << "flowchart: " ; // TODO: translate me
2182     m_t << "<a href=\"";
2183     m_t << fname;
2184     m_t << ".svg\">";
2185     m_t << VhdlDocGen::getFlowMember()->name();
2186     m_t << "</a>";
2187     if (vf->hasCaption())
2188     {
2189       m_t << "<br />";
2190     }
2191   }
2192 }
2193 
visitPost(DocVhdlFlow * vf)2194 void HtmlDocVisitor::visitPost(DocVhdlFlow *vf)
2195 {
2196   if (m_hide) return;
2197   if (VhdlDocGen::getFlowMember()) // use VHDL flow chart creator
2198   {
2199     m_t << "</p>";
2200     forceStartParagraph(vf);
2201   }
2202 }
2203 
visitPre(DocParBlock *)2204 void HtmlDocVisitor::visitPre(DocParBlock *)
2205 {
2206   if (m_hide) return;
2207 }
2208 
visitPost(DocParBlock *)2209 void HtmlDocVisitor::visitPost(DocParBlock *)
2210 {
2211   if (m_hide) return;
2212 }
2213 
2214 
2215 
filter(const QCString & str,const bool retainNewline)2216 void HtmlDocVisitor::filter(const QCString &str, const bool retainNewline)
2217 {
2218   if (str.isEmpty()) return;
2219   const char *p=str.data();
2220   char c;
2221   while (*p)
2222   {
2223     c=*p++;
2224     switch(c)
2225     {
2226       case '\n': if(retainNewline) m_t << "<br/>"; m_t << c; break;
2227       case '<':  m_t << "&lt;"; break;
2228       case '>':  m_t << "&gt;"; break;
2229       case '&':  m_t << "&amp;"; break;
2230       case '\\': if ((*p == '(') || (*p == ')'))
2231           m_t << "\\&zwj;" << *p++;
2232         else
2233           m_t << c;
2234         break;
2235       default:
2236         {
2237           uchar uc = static_cast<uchar>(c);
2238           if (uc<32 && !isspace(c)) // non-printable control characters
2239           {
2240             m_t << "&#x24" << hex[uc>>4] << hex[uc&0xF] << ";";
2241           }
2242           else
2243           {
2244             m_t << c;
2245           }
2246         }
2247         break;
2248     }
2249   }
2250 }
2251 
2252 /// Escape basic entities to produce a valid CDATA attribute value,
2253 /// assume that the outer quoting will be using the double quote &quot;
filterQuotedCdataAttr(const QCString & str)2254 void HtmlDocVisitor::filterQuotedCdataAttr(const QCString &str)
2255 {
2256   if (str.isEmpty()) return;
2257   const char *p=str.data();
2258   char c;
2259   while (*p)
2260   {
2261     c=*p++;
2262     switch(c)
2263     {
2264       case '&':  m_t << "&amp;"; break;
2265       case '"':  m_t << "&quot;"; break;
2266       case '<':  m_t << "&lt;"; break;
2267       case '>':  m_t << "&gt;"; break;
2268       case '\\': if ((*p == '(') || (*p == ')'))
2269           m_t << "\\&zwj;" << *p++;
2270         else
2271           m_t << c;
2272         break;
2273       default:
2274         {
2275           uchar uc = static_cast<uchar>(c);
2276           if (uc<32 && !isspace(c)) // non-printable control characters
2277           {
2278             m_t << "&#x24" << hex[uc>>4] << hex[uc&0xF] << ";";
2279           }
2280           else
2281           {
2282             m_t << c;
2283           }
2284         }
2285         break;
2286     }
2287   }
2288 }
2289 
startLink(const QCString & ref,const QCString & file,const QCString & relPath,const QCString & anchor,const QCString & tooltip)2290 void HtmlDocVisitor::startLink(const QCString &ref,const QCString &file,
2291                                const QCString &relPath,const QCString &anchor,
2292                                const QCString &tooltip)
2293 {
2294   //printf("HtmlDocVisitor: file=%s anchor=%s\n",qPrint(file),qPrint(anchor));
2295   if (!ref.isEmpty()) // link to entity imported via tag file
2296   {
2297     m_t << "<a class=\"elRef\" ";
2298     m_t << externalLinkTarget();
2299   }
2300   else // local link
2301   {
2302     m_t << "<a class=\"el\" ";
2303   }
2304   m_t << "href=\"";
2305   m_t << externalRef(relPath,ref,TRUE);
2306   if (!file.isEmpty())
2307   {
2308     m_t << addHtmlExtensionIfMissing(file);
2309   }
2310   if (!anchor.isEmpty()) m_t << "#" << anchor;
2311   m_t << "\"";
2312   if (!tooltip.isEmpty()) m_t << " title=\"" << convertToHtml(tooltip) << "\"";
2313   m_t << ">";
2314 }
2315 
endLink()2316 void HtmlDocVisitor::endLink()
2317 {
2318   m_t << "</a>";
2319 }
2320 
writeDotFile(const QCString & fn,const QCString & relPath,const QCString & context,const QCString & srcFile,int srcLine)2321 void HtmlDocVisitor::writeDotFile(const QCString &fn,const QCString &relPath,
2322                                   const QCString &context,const QCString &srcFile,int srcLine)
2323 {
2324   QCString baseName=fn;
2325   int i;
2326   if ((i=baseName.findRev('/'))!=-1)
2327   {
2328     baseName=baseName.right(baseName.length()-i-1);
2329   }
2330   if ((i=baseName.find('.'))!=-1) // strip extension
2331   {
2332     baseName=baseName.left(i);
2333   }
2334   baseName.prepend("dot_");
2335   QCString outDir = Config_getString(HTML_OUTPUT);
2336   writeDotGraphFromFile(fn,outDir,baseName,GOF_BITMAP,srcFile,srcLine);
2337   writeDotImageMapFromFile(m_t,fn,outDir,relPath,baseName,context,-1,srcFile,srcLine);
2338 }
2339 
writeMscFile(const QCString & fileName,const QCString & relPath,const QCString & context,const QCString & srcFile,int srcLine)2340 void HtmlDocVisitor::writeMscFile(const QCString &fileName,const QCString &relPath,
2341                                   const QCString &context,const QCString &srcFile,int srcLine)
2342 {
2343   QCString baseName=fileName;
2344   int i;
2345   if ((i=baseName.findRev('/'))!=-1) // strip path
2346   {
2347     baseName=baseName.right(baseName.length()-i-1);
2348   }
2349   if ((i=baseName.find('.'))!=-1) // strip extension
2350   {
2351     baseName=baseName.left(i);
2352   }
2353   baseName.prepend("msc_");
2354   QCString outDir = Config_getString(HTML_OUTPUT);
2355   QCString imgExt = getDotImageExtension();
2356   MscOutputFormat mscFormat = MSC_BITMAP;
2357   if ("svg" == imgExt)
2358     mscFormat = MSC_SVG;
2359   writeMscGraphFromFile(fileName,outDir,baseName,mscFormat,srcFile,srcLine);
2360   writeMscImageMapFromFile(m_t,fileName,outDir,relPath,baseName,context,mscFormat,srcFile,srcLine);
2361 }
2362 
writeDiaFile(const QCString & fileName,const QCString & relPath,const QCString &,const QCString & srcFile,int srcLine)2363 void HtmlDocVisitor::writeDiaFile(const QCString &fileName, const QCString &relPath,
2364                                   const QCString &,const QCString &srcFile,int srcLine)
2365 {
2366   QCString baseName=fileName;
2367   int i;
2368   if ((i=baseName.findRev('/'))!=-1) // strip path
2369   {
2370     baseName=baseName.right(baseName.length()-i-1);
2371   }
2372   if ((i=baseName.find('.'))!=-1) // strip extension
2373   {
2374     baseName=baseName.left(i);
2375   }
2376   baseName.prepend("dia_");
2377   QCString outDir = Config_getString(HTML_OUTPUT);
2378   writeDiaGraphFromFile(fileName,outDir,baseName,DIA_BITMAP,srcFile,srcLine);
2379 
2380   m_t << "<img src=\"" << relPath << baseName << ".png" << "\" />\n";
2381 }
2382 
writePlantUMLFile(const QCString & fileName,const QCString & relPath,const QCString &,const QCString & srcFile,int srcLine)2383 void HtmlDocVisitor::writePlantUMLFile(const QCString &fileName, const QCString &relPath,
2384                                        const QCString &,const QCString &srcFile,int srcLine)
2385 {
2386   QCString baseName=fileName;
2387   int i;
2388   if ((i=baseName.findRev('/'))!=-1) // strip path
2389   {
2390     baseName=baseName.right(baseName.length()-i-1);
2391   }
2392   if ((i=baseName.findRev('.'))!=-1) // strip extension
2393   {
2394     baseName=baseName.left(i);
2395   }
2396   static QCString outDir = Config_getString(HTML_OUTPUT);
2397   QCString imgExt = getDotImageExtension();
2398   if (imgExt=="svg")
2399   {
2400     PlantumlManager::instance().generatePlantUMLOutput(fileName,outDir,PlantumlManager::PUML_SVG);
2401     //m_t << "<iframe scrolling=\"no\" frameborder=\"0\" src=\"" << relPath << baseName << ".svg" << "\" />\n";
2402     //m_t << "<p><b>This browser is not able to show SVG: try Firefox, Chrome, Safari, or Opera instead.</b></p>";
2403     //m_t << "</iframe>\n";
2404     m_t << "<object type=\"image/svg+xml\" data=\"" << relPath << baseName << ".svg\"></object>\n";
2405   }
2406   else
2407   {
2408     PlantumlManager::instance().generatePlantUMLOutput(fileName,outDir,PlantumlManager::PUML_BITMAP);
2409     m_t << "<img src=\"" << relPath << baseName << ".png" << "\" />\n";
2410   }
2411 }
2412 
2413 /** Returns TRUE if the child nodes in paragraph \a para until \a nodeIndex
2414     contain a style change node that is still active and that style change is one that
2415     must be located outside of a paragraph, i.e. it is a center, div, or pre tag.
2416     See also bug746162.
2417  */
insideStyleChangeThatIsOutsideParagraph(DocPara * para,int nodeIndex)2418 static bool insideStyleChangeThatIsOutsideParagraph(DocPara *para,int nodeIndex)
2419 {
2420   //printf("insideStyleChangeThatIsOutputParagraph(index=%d)\n",nodeIndex);
2421   int styleMask=0;
2422   bool styleOutsideParagraph=FALSE;
2423   while (nodeIndex>=0 && !styleOutsideParagraph)
2424   {
2425     DocNode *n = para->children().at(nodeIndex).get();
2426     if (n->kind()==DocNode::Kind_StyleChange)
2427     {
2428       DocStyleChange *sc = (DocStyleChange*)n;
2429       if (!sc->enable()) // remember styles that has been closed already
2430       {
2431         styleMask|=(int)sc->style();
2432       }
2433       bool paraStyle = sc->style()==DocStyleChange::Center ||
2434                        sc->style()==DocStyleChange::Div    ||
2435                        sc->style()==DocStyleChange::Preformatted;
2436       //printf("Found style change %s enabled=%d\n",sc->styleString(),sc->enable());
2437       if (sc->enable() && (styleMask&(int)sc->style())==0 && // style change that is still active
2438           paraStyle
2439          )
2440       {
2441         styleOutsideParagraph=TRUE;
2442       }
2443     }
2444     nodeIndex--;
2445   }
2446   return styleOutsideParagraph;
2447 }
2448 
2449 /** Used for items found inside a paragraph, which due to XHTML restrictions
2450  *  have to be outside of the paragraph. This method will forcefully end
2451  *  the current paragraph and forceStartParagraph() will restart it.
2452  */
forceEndParagraph(DocNode * n)2453 void HtmlDocVisitor::forceEndParagraph(DocNode *n)
2454 {
2455   //printf("forceEndParagraph(%p) %d\n",n,n->kind());
2456   if (n->parent() && n->parent()->kind()==DocNode::Kind_Para)
2457   {
2458     DocPara *para = (DocPara*)n->parent();
2459     const DocNodeList &children = para->children();
2460     auto it = std::find_if(children.begin(),children.end(),[n](const auto &np) { return np.get()==n; });
2461     if (it==children.end()) return;
2462     int nodeIndex = static_cast<int>(it - children.begin());
2463     nodeIndex--;
2464     if (nodeIndex<0) return; // first node in paragraph
2465     while (nodeIndex>=0 && isInvisibleNode(children.at(nodeIndex).get()))
2466     {
2467       nodeIndex--;
2468     }
2469     if (nodeIndex<0) return; // first visible node in paragraph
2470     n = children.at(nodeIndex).get();
2471     if (mustBeOutsideParagraph(n)) return; // previous node already outside paragraph context
2472     nodeIndex--;
2473     bool styleOutsideParagraph=insideStyleChangeThatIsOutsideParagraph(para,nodeIndex);
2474     bool isFirst;
2475     bool isLast;
2476     getParagraphContext(para,isFirst,isLast);
2477     //printf("forceEnd first=%d last=%d styleOutsideParagraph=%d\n",isFirst,isLast,styleOutsideParagraph);
2478     if (isFirst && isLast) return;
2479     if (styleOutsideParagraph) return;
2480 
2481     m_t << "</p>";
2482   }
2483 }
2484 
2485 /** Used for items found inside a paragraph, which due to XHTML restrictions
2486  *  have to be outside of the paragraph. This method will forcefully start
2487  *  the paragraph, that was previously ended by forceEndParagraph().
2488  */
forceStartParagraph(DocNode * n)2489 void HtmlDocVisitor::forceStartParagraph(DocNode *n)
2490 {
2491   //printf("forceStartParagraph(%p) %d\n",n,n->kind());
2492   if (n->parent() && n->parent()->kind()==DocNode::Kind_Para) // if we are inside a paragraph
2493   {
2494     DocPara *para = (DocPara*)n->parent();
2495     const DocNodeList &children = para->children();
2496     auto it = std::find_if(children.begin(),children.end(),[n](const auto &np) { return np.get()==n; });
2497     if (it==children.end()) return;
2498     int nodeIndex = static_cast<int>(it - children.begin());
2499     int numNodes  = static_cast<int>(para->children().size());
2500     bool styleOutsideParagraph=insideStyleChangeThatIsOutsideParagraph(para,nodeIndex);
2501     if (styleOutsideParagraph) return;
2502     nodeIndex++;
2503     if (nodeIndex==numNodes) return; // last node
2504     while (nodeIndex<numNodes && isInvisibleNode(para->children().at(nodeIndex).get()))
2505     {
2506       nodeIndex++;
2507     }
2508     if (nodeIndex<numNodes)
2509     {
2510       n = para->children().at(nodeIndex).get();
2511       if (mustBeOutsideParagraph(n)) return; // next element also outside paragraph
2512     }
2513     else
2514     {
2515       return; // only whitespace at the end!
2516     }
2517 
2518     bool needsTag = TRUE;
2519     bool isFirst;
2520     bool isLast;
2521     getParagraphContext(para,isFirst,isLast);
2522     if (isFirst && isLast) needsTag = FALSE;
2523     //printf("forceStart first=%d last=%d needsTag=%d\n",isFirst,isLast,needsTag);
2524 
2525     if (needsTag) m_t << "<p>";
2526   }
2527 }
2528 
2529