1 /******************************************************************************
6 * Copyright (C) 1997-2012 by Dimitri van Heesch.
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.
14 * Documents produced by Doxygen are derivative works derived from the
15 * input used in their production; they are not affected by this license.
20 #include "htmldocvisitor.h"
21 #include "docparser.h"
24 #include "outputgen.h"
29 #include "parserintf.h"
34 static const int NUM_HTML_LIST_TYPES = 4;
35 static const char types[][NUM_HTML_LIST_TYPES] = {"1", "a", "i", "A"};
37 static QCString convertIndexWordToAnchor(const QString &word)
39 static char hex[] = "0123456789abcdef";
42 for (i=0;i<word.length();i++)
65 static bool mustBeOutsideParagraph(DocNode *n)
70 case DocNode::Kind_HtmlList:
71 case DocNode::Kind_SimpleList:
72 case DocNode::Kind_AutoList:
74 case DocNode::Kind_SimpleSect:
75 case DocNode::Kind_ParamSect:
76 case DocNode::Kind_HtmlDescList:
77 case DocNode::Kind_XRefItem:
79 case DocNode::Kind_HtmlTable:
81 case DocNode::Kind_Section:
82 case DocNode::Kind_HtmlHeader:
84 case DocNode::Kind_Internal:
86 case DocNode::Kind_Include:
87 case DocNode::Kind_Verbatim:
88 case DocNode::Kind_Image:
89 case DocNode::Kind_SecRefList:
91 case DocNode::Kind_HorRuler:
92 /* CopyDoc gets paragraph markers from the wrapping DocPara node,
93 * but needs to insert them for all documentation being copied to
94 * preserve formatting.
96 case DocNode::Kind_Copy:
98 case DocNode::Kind_HtmlBlockQuote:
100 case DocNode::Kind_StyleChange:
101 return ((DocStyleChange*)n)->style()==DocStyleChange::Preformatted ||
102 ((DocStyleChange*)n)->style()==DocStyleChange::Div ||
103 ((DocStyleChange*)n)->style()==DocStyleChange::Center;
104 case DocNode::Kind_Formula:
105 return !((DocFormula*)n)->isInline();
112 static QString htmlAttribsToString(const HtmlAttribList &attribs)
115 HtmlAttribListIterator li(attribs);
117 for (li.toFirst();(att=li.current());++li)
119 if (!att->value.isEmpty()) // ignore attribute without values as they
120 // are not XHTML compliant
124 result+="=\""+convertToXML(att->value)+"\"";
130 //-------------------------------------------------------------------------
132 HtmlDocVisitor::HtmlDocVisitor(FTextStream &t,CodeOutputInterface &ci,
133 Definition *ctx,MemberDef *md)
134 : DocVisitor(DocVisitor_Html), m_t(t), m_ci(ci), m_insidePre(FALSE),
135 m_hide(FALSE), m_ctx(ctx), m_md(md)
137 if (ctx) m_langExt=ctx->getDefFileExtension();
140 //--------------------------------------
141 // visitor functions for leaf nodes
142 //--------------------------------------
144 void HtmlDocVisitor::visit(DocWord *w)
146 //printf("word: %s\n",w->word().data());
151 void HtmlDocVisitor::visit(DocLinkedWord *w)
154 startLink(w->ref(),w->file(),w->relPath(),w->anchor(),w->tooltip());
159 void HtmlDocVisitor::visit(DocWhiteSpace *w)
172 void HtmlDocVisitor::visit(DocSymbol *s)
177 case DocSymbol::BSlash: m_t << "\\"; break;
178 case DocSymbol::At: m_t << "@"; break;
179 case DocSymbol::Less: m_t << "<"; break;
180 case DocSymbol::Greater: m_t << ">"; break;
181 case DocSymbol::Amp: m_t << "&"; break;
182 case DocSymbol::Dollar: m_t << "$"; break;
183 case DocSymbol::Hash: m_t << "#"; break;
184 case DocSymbol::DoubleColon: m_t << "::"; break;
185 case DocSymbol::Percent: m_t << "%"; break;
186 case DocSymbol::Pipe: m_t << "|"; break;
187 case DocSymbol::Copy: m_t << "©"; break;
188 case DocSymbol::Tm: m_t << "™"; break;
189 case DocSymbol::Reg: m_t << "®"; break;
190 case DocSymbol::Apos: m_t << "'"; break;
191 case DocSymbol::Quot: m_t << "\""; break;
192 case DocSymbol::Lsquo: m_t << "‘"; break;
193 case DocSymbol::Rsquo: m_t << "’"; break;
194 case DocSymbol::Ldquo: m_t << "“"; break;
195 case DocSymbol::Rdquo: m_t << "”"; break;
196 case DocSymbol::Ndash: m_t << "–"; break;
197 case DocSymbol::Mdash: m_t << "—"; break;
198 case DocSymbol::Uml: m_t << "&" << s->letter() << "uml;"; break;
199 case DocSymbol::Acute: m_t << "&" << s->letter() << "acute;"; break;
200 case DocSymbol::Grave: m_t << "&" << s->letter() << "grave;"; break;
201 case DocSymbol::Circ: m_t << "&" << s->letter() << "circ;"; break;
202 case DocSymbol::Slash: m_t << "&" << s->letter() << "slash;"; break;
203 case DocSymbol::Tilde: m_t << "&" << s->letter() << "tilde;"; break;
204 case DocSymbol::Szlig: m_t << "ß"; break;
205 case DocSymbol::Cedil: m_t << "&" << s->letter() << "cedil;"; break;
206 case DocSymbol::Ring: m_t << "&" << s->letter() << "ring;"; break;
207 case DocSymbol::Nbsp: m_t << " "; break;
208 case DocSymbol::AElig: m_t << "Æ"; break;
209 case DocSymbol::Aelig: m_t << "æ"; break;
210 case DocSymbol::GrkGamma: m_t << "Γ"; break;
211 case DocSymbol::GrkDelta: m_t << "Δ"; break;
212 case DocSymbol::GrkTheta: m_t << "Θ"; break;
213 case DocSymbol::GrkLambda: m_t << "Λ"; break;
214 case DocSymbol::GrkXi: m_t << "Ξ"; break;
215 case DocSymbol::GrkPi: m_t << "Π"; break;
216 case DocSymbol::GrkSigma: m_t << "Σ"; break;
217 case DocSymbol::GrkUpsilon: m_t << "Υ"; break;
218 case DocSymbol::GrkPhi: m_t << "Φ"; break;
219 case DocSymbol::GrkPsi: m_t << "Ψ"; break;
220 case DocSymbol::GrkOmega: m_t << "Ω"; break;
221 case DocSymbol::Grkalpha: m_t << "α"; break;
222 case DocSymbol::Grkbeta: m_t << "β"; break;
223 case DocSymbol::Grkgamma: m_t << "γ"; break;
224 case DocSymbol::Grkdelta: m_t << "δ"; break;
225 case DocSymbol::Grkepsilon: m_t << "ε"; break;
226 case DocSymbol::Grkzeta: m_t << "ζ"; break;
227 case DocSymbol::Grketa: m_t << "η"; break;
228 case DocSymbol::Grktheta: m_t << "θ"; break;
229 case DocSymbol::Grkiota: m_t << "ι"; break;
230 case DocSymbol::Grkkappa: m_t << "κ"; break;
231 case DocSymbol::Grklambda: m_t << "λ"; break;
232 case DocSymbol::Grkmu: m_t << "μ"; break;
233 case DocSymbol::Grknu: m_t << "ν"; break;
234 case DocSymbol::Grkxi: m_t << "ξ"; break;
235 case DocSymbol::Grkpi: m_t << "π"; break;
236 case DocSymbol::Grkrho: m_t << "ρ"; break;
237 case DocSymbol::Grksigma: m_t << "σ"; break;
238 case DocSymbol::Grktau: m_t << "τ"; break;
239 case DocSymbol::Grkupsilon: m_t << "υ"; break;
240 case DocSymbol::Grkphi: m_t << "φ"; break;
241 case DocSymbol::Grkchi: m_t << "χ"; break;
242 case DocSymbol::Grkpsi: m_t << "ψ"; break;
243 case DocSymbol::Grkomega: m_t << "ω"; break;
244 case DocSymbol::Grkvarsigma: m_t << "ς"; break;
245 case DocSymbol::Section: m_t << "§"; break;
246 case DocSymbol::Degree: m_t << "°"; break;
247 case DocSymbol::Prime: m_t << "′"; break;
248 case DocSymbol::DoublePrime: m_t << "″"; break;
249 case DocSymbol::Infinity: m_t << "∞"; break;
250 case DocSymbol::EmptySet: m_t << "∅"; break;
251 case DocSymbol::PlusMinus: m_t << "±"; break;
252 case DocSymbol::Times: m_t << "×"; break;
253 case DocSymbol::Minus: m_t << "−"; break;
254 case DocSymbol::CenterDot: m_t << "⋅"; break;
255 case DocSymbol::Partial: m_t << "∂"; break;
256 case DocSymbol::Nabla: m_t << "∇"; break;
257 case DocSymbol::SquareRoot: m_t << "√"; break;
258 case DocSymbol::Perpendicular: m_t << "⊥"; break;
259 case DocSymbol::Sum: m_t << "∑"; break;
260 case DocSymbol::Integral: m_t << "∫"; break;
261 case DocSymbol::Product: m_t << "∏"; break;
262 case DocSymbol::Similar: m_t << "∼"; break;
263 case DocSymbol::Approx: m_t << "≈"; break;
264 case DocSymbol::NotEqual: m_t << "≠"; break;
265 case DocSymbol::Equivalent: m_t << "≡"; break;
266 case DocSymbol::Proportional: m_t << "∝"; break;
267 case DocSymbol::LessEqual: m_t << "≤"; break;
268 case DocSymbol::GreaterEqual: m_t << "≥"; break;
269 case DocSymbol::LeftArrow: m_t << "←"; break;
270 case DocSymbol::RightArrow: m_t << "→"; break;
271 case DocSymbol::SetIn: m_t << "∈"; break;
272 case DocSymbol::SetNotIn: m_t << "∉"; break;
273 case DocSymbol::LeftCeil: m_t << "⌈"; break;
274 case DocSymbol::RightCeil: m_t << "⌉"; break;
275 case DocSymbol::LeftFloor: m_t << "⌊"; break;
276 case DocSymbol::RightFloor: m_t << "⌋"; break;
278 err("error: unknown symbol found\n");
282 void HtmlDocVisitor::writeObfuscatedMailAddress(const QCString &url)
284 m_t << "<a href=\"#\" onclick=\"location.href='mai'+'lto:'";
287 for (i=0;i<url.length();)
289 m_t << "+'" << url.mid(i,size) << "'";
291 if (size==3) size=2; else size=3;
293 m_t << "; return false;\">";
296 void HtmlDocVisitor::visit(DocURL *u)
299 if (u->isEmail()) // mail address
301 QCString url = u->url();
302 writeObfuscatedMailAddress(url);
304 for (i=0;i<url.length();)
306 filter(url.mid(i,size));
307 if (i<url.length()-size) m_t << "<span style=\"display: none;\">.nosp@m.</span>";
309 if (size==5) size=4; else size=5;
316 m_t << u->url() << "\">";
322 void HtmlDocVisitor::visit(DocLineBreak *)
328 void HtmlDocVisitor::visit(DocHorRuler *)
334 void HtmlDocVisitor::visit(DocStyleChange *s)
339 case DocStyleChange::Bold:
340 if (s->enable()) m_t << "<b" << htmlAttribsToString(s->attribs()) << ">"; else m_t << "</b>";
342 case DocStyleChange::Italic:
343 if (s->enable()) m_t << "<em" << htmlAttribsToString(s->attribs()) << ">"; else m_t << "</em>";
345 case DocStyleChange::Code:
346 if (s->enable()) m_t << "<code" << htmlAttribsToString(s->attribs()) << ">"; else m_t << "</code>";
348 case DocStyleChange::Subscript:
349 if (s->enable()) m_t << "<sub" << htmlAttribsToString(s->attribs()) << ">"; else m_t << "</sub>";
351 case DocStyleChange::Superscript:
352 if (s->enable()) m_t << "<sup" << htmlAttribsToString(s->attribs()) << ">"; else m_t << "</sup>";
354 case DocStyleChange::Center:
357 forceEndParagraph(s);
358 m_t << "<center" << htmlAttribsToString(s->attribs()) << ">";
363 forceStartParagraph(s);
366 case DocStyleChange::Small:
367 if (s->enable()) m_t << "<small" << htmlAttribsToString(s->attribs()) << ">"; else m_t << "</small>";
369 case DocStyleChange::Preformatted:
372 forceEndParagraph(s);
373 m_t << "<pre" << htmlAttribsToString(s->attribs()) << ">";
380 forceStartParagraph(s);
383 case DocStyleChange::Div:
386 forceEndParagraph(s);
387 m_t << "<div" << htmlAttribsToString(s->attribs()) << ">";
392 forceStartParagraph(s);
395 case DocStyleChange::Span:
396 if (s->enable()) m_t << "<span" << htmlAttribsToString(s->attribs()) << ">"; else m_t << "</span>";
403 void HtmlDocVisitor::visit(DocVerbatim *s)
406 QCString lang = m_langExt;
407 if (!s->language().isEmpty()) // explicit language setting
409 lang = s->language();
413 case DocVerbatim::Code:
414 forceEndParagraph(s);
415 m_t << PREFRAG_START;
416 Doxygen::parserManager->getParser(lang)
425 FALSE, // inlineFragment
427 TRUE, // show line numbers
428 m_ctx // search context
431 forceStartParagraph(s);
433 case DocVerbatim::Verbatim:
434 forceEndParagraph(s);
435 m_t << /*PREFRAG_START <<*/ "<pre class=\"fragment\">";
437 m_t << "</pre>" /*<< PREFRAG_END*/;
438 forceStartParagraph(s);
440 case DocVerbatim::HtmlOnly:
443 case DocVerbatim::ManOnly:
444 case DocVerbatim::LatexOnly:
445 case DocVerbatim::XmlOnly:
446 case DocVerbatim::RtfOnly:
450 case DocVerbatim::Dot:
452 static int dotindex = 1;
453 QCString fileName(4096);
455 fileName.sprintf("%s%d%s",
456 (Config_getString("HTML_OUTPUT")+"/inline_dotgraph_").data(),
460 QFile file(fileName);
461 if (!file.open(IO_WriteOnly))
463 err("Could not open file %s for writing\n",fileName.data());
465 file.writeBlock( s->text(), s->text().length() );
468 forceEndParagraph(s);
469 m_t << "<div align=\"center\">" << endl;
470 writeDotFile(fileName,s->relPath(),s->context());
471 m_t << "</div>" << endl;
472 forceStartParagraph(s);
474 if (Config_getBool("DOT_CLEANUP")) file.remove();
477 case DocVerbatim::Msc:
479 static int mscindex = 1;
480 QCString baseName(4096);
482 baseName.sprintf("%s%d",
483 (Config_getString("HTML_OUTPUT")+"/inline_mscgraph_").data(),
486 QFile file(baseName+".msc");
487 if (!file.open(IO_WriteOnly))
489 err("Could not open file %s.msc for writing\n",baseName.data());
491 QCString text = "msc {";
494 file.writeBlock( text, text.length() );
497 forceEndParagraph(s);
498 m_t << "<div align=\"center\">" << endl;
499 writeMscFile(baseName+".msc",s->relPath(),s->context());
500 m_t << "</div>" << endl;
501 forceStartParagraph(s);
503 if (Config_getBool("DOT_CLEANUP")) file.remove();
509 void HtmlDocVisitor::visit(DocAnchor *anc)
512 m_t << "<a class=\"anchor\" id=\"" << anc->anchor() << "\"></a>";
515 void HtmlDocVisitor::visit(DocInclude *inc)
520 case DocInclude::Include:
521 forceEndParagraph(inc);
522 m_t << PREFRAG_START;
523 Doxygen::parserManager->getParser(inc->extension())
532 TRUE, // inlineFragment
534 TRUE, // show line numbers
535 m_ctx // search context
538 forceStartParagraph(inc);
540 case DocInclude::IncWithLines:
542 forceEndParagraph(inc);
543 m_t << PREFRAG_START;
544 QFileInfo cfi( inc->file() );
545 FileDef fd( cfi.dirPath().utf8(), cfi.fileName().utf8() );
546 Doxygen::parserManager->getParser(inc->extension())
555 FALSE, // inline fragment
557 TRUE, // show line numbers
558 m_ctx // search context
561 forceStartParagraph(inc);
564 case DocInclude::DontInclude:
566 case DocInclude::HtmlInclude:
569 case DocInclude::VerbInclude:
570 forceEndParagraph(inc);
571 m_t << /*PREFRAG_START <<*/ "<pre class=\"fragment\">";
573 m_t << "</pre>" /*<< PREFRAG_END*/;
574 forceStartParagraph(inc);
576 case DocInclude::Snippet:
578 forceEndParagraph(inc);
579 m_t << PREFRAG_START;
580 Doxygen::parserManager->getParser(inc->extension())
583 extractBlock(inc->text(),inc->blockId()),
589 TRUE, // inlineFragment
591 TRUE, // show line number
592 m_ctx // search context
595 forceStartParagraph(inc);
601 void HtmlDocVisitor::visit(DocIncOperator *op)
603 //printf("DocIncOperator: type=%d first=%d, last=%d text=`%s'\n",
604 // op->type(),op->isFirst(),op->isLast(),op->text().data());
607 if (!m_hide) m_t << PREFRAG_START;
611 if (op->type()!=DocIncOperator::Skip)
616 Doxygen::parserManager->getParser(m_langExt)
626 FALSE, // inline fragment
628 TRUE, // show line numbers
629 m_ctx // search context
638 if (!m_hide) m_t << PREFRAG_END;
642 if (!m_hide) m_t << endl;
646 void HtmlDocVisitor::visit(DocFormula *f)
649 bool bDisplay = !f->isInline();
652 forceEndParagraph(f);
653 m_t << "<p class=\"formulaDsp\">" << endl;
656 if (Config_getBool("USE_MATHJAX"))
658 QCString text = f->text();
659 bool closeInline = FALSE;
660 if (!bDisplay && !text.isEmpty() && text.at(0)=='$' &&
661 text.at(text.length()-1)=='$')
664 text = text.mid(1,text.length()-2);
667 m_t << convertToHtml(text);
675 m_t << "<img class=\"formula"
676 << (bDisplay ? "Dsp" : "Inl");
678 filterQuotedCdataAttr(f->text());
680 // TODO: cache image dimensions on formula generation and give height/width
681 // for faster preloading and better rendering of the page
682 m_t << " src=\"" << f->relPath() << f->name() << ".png\"/>";
687 m_t << endl << "</p>" << endl;
688 forceStartParagraph(f);
692 void HtmlDocVisitor::visit(DocIndexEntry *e)
694 QCString anchor = convertIndexWordToAnchor(e->entry());
697 anchor.prepend(e->member()->anchor()+"_");
699 m_t << "<a name=\"" << anchor << "\"></a>";
700 //printf("*** DocIndexEntry: word='%s' scope='%s' member='%s'\n",
701 // e->entry().data(),
702 // e->scope() ? e->scope()->name().data() : "<null>",
703 // e->member() ? e->member()->name().data() : "<null>"
705 Doxygen::indexList.addIndexItem(e->scope(),e->member(),e->entry());
708 void HtmlDocVisitor::visit(DocSimpleSectSep *)
710 m_t << "</dd>" << endl;
711 m_t << "<dd>" << endl;
714 void HtmlDocVisitor::visit(DocCite *cite)
717 if (!cite->file().isEmpty())
719 startLink(cite->ref(),cite->file(),cite->relPath(),cite->anchor());
725 filter(cite->text());
726 if (!cite->file().isEmpty())
737 //--------------------------------------
738 // visitor functions for compound nodes
739 //--------------------------------------
742 void HtmlDocVisitor::visitPre(DocAutoList *l)
744 //printf("DocAutoList::visitPre\n");
746 forceEndParagraph(l);
750 // Do list type based on depth:
757 m_t << "<ol type=\"" << types[l->depth() % NUM_HTML_LIST_TYPES] << "\">";
763 if (!l->isPreformatted()) m_t << "\n";
766 void HtmlDocVisitor::visitPost(DocAutoList *l)
768 //printf("DocAutoList::visitPost\n");
778 if (!l->isPreformatted()) m_t << "\n";
779 forceStartParagraph(l);
782 void HtmlDocVisitor::visitPre(DocAutoListItem *)
788 void HtmlDocVisitor::visitPost(DocAutoListItem *li)
792 if (!li->isPreformatted()) m_t << "\n";
796 bool isFirstChildNode(T *parent, DocNode *node)
798 return parent->children().getFirst()==node;
802 bool isLastChildNode(T *parent, DocNode *node)
804 return parent->children().getLast()==node;
807 bool isSeparatedParagraph(DocSimpleSect *parent,DocPara *par)
809 QList<DocNode> nodes = parent->children();
810 int i = nodes.findRef(par);
811 if (i==-1) return FALSE;
812 int count = parent->children().count();
815 if (nodes.at(i+1)->kind()==DocNode::Kind_SimpleSectSep)
820 else if (count>1 && i==count-1)
822 if (nodes.at(i-1)->kind()==DocNode::Kind_SimpleSectSep)
827 else if (count>2 && i>0 && i<count-1)
829 if (nodes.at(i-1)->kind()==DocNode::Kind_SimpleSectSep &&
830 nodes.at(i+1)->kind()==DocNode::Kind_SimpleSectSep)
838 static int getParagraphContext(DocPara *p,bool &isFirst,bool &isLast)
843 if (p && p->parent())
845 switch (p->parent()->kind())
847 case DocNode::Kind_AutoListItem:
850 isFirst=isFirstChildNode((DocAutoListItem*)p->parent(),p);
851 isLast =isLastChildNode ((DocAutoListItem*)p->parent(),p);
854 case DocNode::Kind_SimpleListItem:
859 case DocNode::Kind_ParamList:
864 case DocNode::Kind_HtmlListItem:
865 isFirst=isFirstChildNode((DocHtmlListItem*)p->parent(),p);
866 isLast =isLastChildNode ((DocHtmlListItem*)p->parent(),p);
870 case DocNode::Kind_SecRefItem:
871 isFirst=isFirstChildNode((DocSecRefItem*)p->parent(),p);
872 isLast =isLastChildNode ((DocSecRefItem*)p->parent(),p);
876 case DocNode::Kind_HtmlDescData:
877 isFirst=isFirstChildNode((DocHtmlDescData*)p->parent(),p);
878 isLast =isLastChildNode ((DocHtmlDescData*)p->parent(),p);
882 case DocNode::Kind_XRefItem:
883 isFirst=isFirstChildNode((DocXRefItem*)p->parent(),p);
884 isLast =isLastChildNode ((DocXRefItem*)p->parent(),p);
888 case DocNode::Kind_HtmlCell:
889 isFirst=isFirstChildNode((DocHtmlCell*)p->parent(),p);
890 isLast =isLastChildNode ((DocHtmlCell*)p->parent(),p);
894 case DocNode::Kind_SimpleSect:
895 isFirst=isFirstChildNode((DocSimpleSect*)p->parent(),p);
896 isLast =isLastChildNode ((DocSimpleSect*)p->parent(),p);
899 if (isSeparatedParagraph((DocSimpleSect*)p->parent(),p))
900 // if the paragraph is enclosed with separators it will
901 // be included in <dd>..</dd> so avoid addition paragraph
910 //printf("para=%p parent()->kind=%d isFirst=%d isLast=%d t=%d\n",
911 // p,p->parent()->kind(),isFirst,isLast,t);
916 void HtmlDocVisitor::visitPre(DocPara *p)
920 //printf("DocPara::visitPre: parent of kind %d ",
921 // p->parent() ? p->parent()->kind() : -1);
923 bool needsTag = FALSE;
924 if (p && p->parent())
926 switch (p->parent()->kind())
928 case DocNode::Kind_Section:
929 case DocNode::Kind_Internal:
930 case DocNode::Kind_HtmlListItem:
931 case DocNode::Kind_HtmlDescData:
932 case DocNode::Kind_HtmlCell:
933 case DocNode::Kind_SimpleListItem:
934 case DocNode::Kind_AutoListItem:
935 case DocNode::Kind_SimpleSect:
936 case DocNode::Kind_XRefItem:
937 case DocNode::Kind_Copy:
938 case DocNode::Kind_HtmlBlockQuote:
941 case DocNode::Kind_Root:
942 needsTag = !((DocRoot*)p->parent())->singleLine();
949 // if the first element of a paragraph is something that should be outside of
950 // the paragraph (<ul>,<dl>,<table>,..) then that will already started the
951 // paragraph and we don't need to do it here
953 if (p && nodeIndex<p->children().count())
955 while (nodeIndex<p->children().count() &&
956 p->children().at(nodeIndex)->kind()==DocNode::Kind_WhiteSpace)
960 if (nodeIndex<p->children().count())
962 DocNode *n = p->children().at(nodeIndex);
963 if (mustBeOutsideParagraph(n))
970 // check if this paragraph is the first or last child of a <li> or <dd>.
971 // this allows us to mark the tag with a special class so we can
972 // fix the otherwise ugly spacing.
974 static const char *contexts[7] =
976 " class=\"startli\"", // 1
977 " class=\"startdd\"", // 2
978 " class=\"endli\"", // 3
979 " class=\"enddd\"", // 4
980 " class=\"starttd\"", // 5
981 " class=\"endtd\"" // 6
985 t = getParagraphContext(p,isFirst,isLast);
986 //printf("startPara first=%d last=%d\n",isFirst,isLast);
987 if (isFirst && isLast) needsTag=FALSE;
989 //printf(" needsTag=%d\n",needsTag);
990 // write the paragraph tag (if needed)
991 if (needsTag) m_t << "<p" << contexts[t] << ">";
994 void HtmlDocVisitor::visitPost(DocPara *p)
996 bool needsTag = FALSE;
997 if (p && p->parent())
999 switch (p->parent()->kind())
1001 case DocNode::Kind_Section:
1002 case DocNode::Kind_Internal:
1003 case DocNode::Kind_HtmlListItem:
1004 case DocNode::Kind_HtmlDescData:
1005 case DocNode::Kind_HtmlCell:
1006 case DocNode::Kind_SimpleListItem:
1007 case DocNode::Kind_AutoListItem:
1008 case DocNode::Kind_SimpleSect:
1009 case DocNode::Kind_XRefItem:
1010 case DocNode::Kind_Copy:
1011 case DocNode::Kind_HtmlBlockQuote:
1014 case DocNode::Kind_Root:
1015 needsTag = !((DocRoot*)p->parent())->singleLine();
1023 // if the last element of a paragraph is something that should be outside of
1024 // the paragraph (<ul>,<dl>,<table>) then that will already have ended the
1025 // paragraph and we don't need to do it here
1026 int nodeIndex = p->children().count()-1;
1027 if (p && nodeIndex>=0)
1029 while (nodeIndex>=0 && p->children().at(nodeIndex)->kind()==DocNode::Kind_WhiteSpace)
1035 DocNode *n = p->children().at(nodeIndex);
1036 if (mustBeOutsideParagraph(n))
1045 getParagraphContext(p,isFirst,isLast);
1046 //printf("endPara first=%d last=%d\n",isFirst,isLast);
1047 if (isFirst && isLast) needsTag=FALSE;
1049 //printf("DocPara::visitPost needsTag=%d\n",needsTag);
1051 if (needsTag) m_t << "</p>\n";
1055 void HtmlDocVisitor::visitPre(DocRoot *)
1059 void HtmlDocVisitor::visitPost(DocRoot *)
1063 void HtmlDocVisitor::visitPre(DocSimpleSect *s)
1066 forceEndParagraph(s);
1067 m_t << "<dl class=\"section " << s->typeString() << "\"><dt>";
1070 case DocSimpleSect::See:
1071 m_t << theTranslator->trSeeAlso(); break;
1072 case DocSimpleSect::Return:
1073 m_t << theTranslator->trReturns(); break;
1074 case DocSimpleSect::Author:
1075 m_t << theTranslator->trAuthor(TRUE,TRUE); break;
1076 case DocSimpleSect::Authors:
1077 m_t << theTranslator->trAuthor(TRUE,FALSE); break;
1078 case DocSimpleSect::Version:
1079 m_t << theTranslator->trVersion(); break;
1080 case DocSimpleSect::Since:
1081 m_t << theTranslator->trSince(); break;
1082 case DocSimpleSect::Date:
1083 m_t << theTranslator->trDate(); break;
1084 case DocSimpleSect::Note:
1085 m_t << theTranslator->trNote(); break;
1086 case DocSimpleSect::Warning:
1087 m_t << theTranslator->trWarning(); break;
1088 case DocSimpleSect::Pre:
1089 m_t << theTranslator->trPrecondition(); break;
1090 case DocSimpleSect::Post:
1091 m_t << theTranslator->trPostcondition(); break;
1092 case DocSimpleSect::Copyright:
1093 m_t << theTranslator->trCopyright(); break;
1094 case DocSimpleSect::Invar:
1095 m_t << theTranslator->trInvariant(); break;
1096 case DocSimpleSect::Remark:
1097 m_t << theTranslator->trRemarks(); break;
1098 case DocSimpleSect::Attention:
1099 m_t << theTranslator->trAttention(); break;
1100 case DocSimpleSect::User: break;
1101 case DocSimpleSect::Rcs: break;
1102 case DocSimpleSect::Unknown: break;
1105 // special case 1: user defined title
1106 if (s->type()!=DocSimpleSect::User && s->type()!=DocSimpleSect::Rcs)
1112 void HtmlDocVisitor::visitPost(DocSimpleSect *s)
1115 m_t << "</dd></dl>\n";
1116 forceStartParagraph(s);
1119 void HtmlDocVisitor::visitPre(DocTitle *)
1123 void HtmlDocVisitor::visitPost(DocTitle *)
1129 void HtmlDocVisitor::visitPre(DocSimpleList *sl)
1132 forceEndParagraph(sl);
1134 if (!sl->isPreformatted()) m_t << "\n";
1138 void HtmlDocVisitor::visitPost(DocSimpleList *sl)
1142 if (!sl->isPreformatted()) m_t << "\n";
1143 forceStartParagraph(sl);
1146 void HtmlDocVisitor::visitPre(DocSimpleListItem *)
1152 void HtmlDocVisitor::visitPost(DocSimpleListItem *li)
1156 if (!li->isPreformatted()) m_t << "\n";
1159 void HtmlDocVisitor::visitPre(DocSection *s)
1162 forceEndParagraph(s);
1163 m_t << "<h" << s->level() << ">";
1164 m_t << "<a class=\"anchor\" id=\"" << s->anchor();
1165 m_t << "\"></a>" << endl;
1166 filter(convertCharEntitiesToUTF8(s->title().data()));
1167 m_t << "</h" << s->level() << ">\n";
1170 void HtmlDocVisitor::visitPost(DocSection *s)
1172 forceStartParagraph(s);
1175 void HtmlDocVisitor::visitPre(DocHtmlList *s)
1178 forceEndParagraph(s);
1179 if (s->type()==DocHtmlList::Ordered)
1181 m_t << "<ol" << htmlAttribsToString(s->attribs()) << ">\n";
1185 m_t << "<ul" << htmlAttribsToString(s->attribs()) << ">\n";
1189 void HtmlDocVisitor::visitPost(DocHtmlList *s)
1192 if (s->type()==DocHtmlList::Ordered)
1200 if (!s->isPreformatted()) m_t << "\n";
1201 forceStartParagraph(s);
1204 void HtmlDocVisitor::visitPre(DocHtmlListItem *i)
1207 m_t << "<li" << htmlAttribsToString(i->attribs()) << ">";
1208 if (!i->isPreformatted()) m_t << "\n";
1211 void HtmlDocVisitor::visitPost(DocHtmlListItem *)
1217 void HtmlDocVisitor::visitPre(DocHtmlDescList *dl)
1220 forceEndParagraph(dl);
1221 m_t << "<dl" << htmlAttribsToString(dl->attribs()) << ">\n";
1224 void HtmlDocVisitor::visitPost(DocHtmlDescList *dl)
1228 forceStartParagraph(dl);
1231 void HtmlDocVisitor::visitPre(DocHtmlDescTitle *dt)
1234 m_t << "<dt" << htmlAttribsToString(dt->attribs()) << ">";
1237 void HtmlDocVisitor::visitPost(DocHtmlDescTitle *)
1243 void HtmlDocVisitor::visitPre(DocHtmlDescData *dd)
1246 m_t << "<dd" << htmlAttribsToString(dd->attribs()) << ">";
1249 void HtmlDocVisitor::visitPost(DocHtmlDescData *)
1255 void HtmlDocVisitor::visitPre(DocHtmlTable *t)
1259 forceEndParagraph(t);
1261 QString attrs = htmlAttribsToString(t->attribs());
1262 if (attrs.isEmpty())
1264 m_t << "<table class=\"doxtable\">\n";
1268 m_t << "<table " << htmlAttribsToString(t->attribs()) << ">\n";
1272 void HtmlDocVisitor::visitPost(DocHtmlTable *t)
1275 m_t << "</table>\n";
1276 forceStartParagraph(t);
1279 void HtmlDocVisitor::visitPre(DocHtmlRow *tr)
1282 m_t << "<tr" << htmlAttribsToString(tr->attribs()) << ">\n";
1285 void HtmlDocVisitor::visitPost(DocHtmlRow *)
1291 void HtmlDocVisitor::visitPre(DocHtmlCell *c)
1296 m_t << "<th" << htmlAttribsToString(c->attribs()) << ">";
1300 m_t << "<td" << htmlAttribsToString(c->attribs()) << ">";
1304 void HtmlDocVisitor::visitPost(DocHtmlCell *c)
1307 if (c->isHeading()) m_t << "</th>"; else m_t << "</td>";
1310 void HtmlDocVisitor::visitPre(DocHtmlCaption *c)
1313 bool hasAlign = FALSE;
1314 HtmlAttribListIterator li(c->attribs());
1316 for (li.toFirst();(att=li.current());++li)
1318 if (att->name=="align") hasAlign=TRUE;
1320 m_t << "<caption" << htmlAttribsToString(c->attribs());
1321 if (!hasAlign) m_t << " align=\"bottom\"";
1325 void HtmlDocVisitor::visitPost(DocHtmlCaption *)
1328 m_t << "</caption>\n";
1331 void HtmlDocVisitor::visitPre(DocInternal *)
1334 //forceEndParagraph(i);
1335 //m_t << "<p><b>" << theTranslator->trForInternalUseOnly() << "</b></p>" << endl;
1338 void HtmlDocVisitor::visitPost(DocInternal *)
1341 //forceStartParagraph(i);
1344 void HtmlDocVisitor::visitPre(DocHRef *href)
1347 if (href->url().left(7)=="mailto:")
1349 writeObfuscatedMailAddress(href->url().mid(7));
1353 QCString url = correctURL(href->url(),href->relPath());
1354 m_t << "<a href=\"" << convertToXML(url) << "\""
1355 << htmlAttribsToString(href->attribs()) << ">";
1359 void HtmlDocVisitor::visitPost(DocHRef *)
1365 void HtmlDocVisitor::visitPre(DocHtmlHeader *header)
1368 forceEndParagraph(header);
1369 m_t << "<h" << header->level()
1370 << htmlAttribsToString(header->attribs()) << ">";
1373 void HtmlDocVisitor::visitPost(DocHtmlHeader *header)
1376 m_t << "</h" << header->level() << ">\n";
1377 forceStartParagraph(header);
1380 void HtmlDocVisitor::visitPre(DocImage *img)
1382 if (img->type()==DocImage::Html)
1384 forceEndParagraph(img);
1386 QString baseName=img->name();
1388 if ((i=baseName.findRev('/'))!=-1 || (i=baseName.findRev('\\'))!=-1)
1390 baseName=baseName.right(baseName.length()-i-1);
1392 m_t << "<div class=\"image\">" << endl;
1393 QCString url = img->url();
1396 m_t << "<img src=\"" << img->relPath() << img->name() << "\" alt=\""
1397 << baseName << "\"" << htmlAttribsToString(img->attribs())
1402 m_t << "<img src=\"" << correctURL(url,img->relPath()) << "\" "
1403 << htmlAttribsToString(img->attribs())
1406 if (img->hasCaption())
1408 m_t << "<div class=\"caption\">" << endl;
1411 else // other format -> skip
1418 void HtmlDocVisitor::visitPost(DocImage *img)
1420 if (img->type()==DocImage::Html)
1423 if (img->hasCaption())
1427 m_t << "</div>" << endl;
1428 forceStartParagraph(img);
1430 else // other format
1436 void HtmlDocVisitor::visitPre(DocDotFile *df)
1439 m_t << "<div class=\"dotgraph\">" << endl;
1440 writeDotFile(df->file(),df->relPath(),df->context());
1441 if (df->hasCaption())
1443 m_t << "<div class=\"caption\">" << endl;
1447 void HtmlDocVisitor::visitPost(DocDotFile *df)
1450 if (df->hasCaption())
1452 m_t << "</div>" << endl;
1454 m_t << "</div>" << endl;
1457 void HtmlDocVisitor::visitPre(DocMscFile *df)
1460 m_t << "<div class=\"mscgraph\">" << endl;
1461 writeMscFile(df->file(),df->relPath(),df->context());
1462 if (df->hasCaption())
1464 m_t << "<div class=\"caption\">" << endl;
1467 void HtmlDocVisitor::visitPost(DocMscFile *df)
1470 if (df->hasCaption())
1472 m_t << "</div>" << endl;
1474 m_t << "</div>" << endl;
1477 void HtmlDocVisitor::visitPre(DocLink *lnk)
1480 startLink(lnk->ref(),lnk->file(),lnk->relPath(),lnk->anchor());
1483 void HtmlDocVisitor::visitPost(DocLink *)
1489 void HtmlDocVisitor::visitPre(DocRef *ref)
1492 if (!ref->file().isEmpty())
1494 // when ref->isSubPage()==TRUE we use ref->file() for HTML and
1495 // ref->anchor() for LaTeX/RTF
1496 startLink(ref->ref(),ref->file(),ref->relPath(),ref->isSubPage() ? QCString() : ref->anchor());
1498 if (!ref->hasLinkText()) filter(ref->targetTitle());
1501 void HtmlDocVisitor::visitPost(DocRef *ref)
1504 if (!ref->file().isEmpty()) endLink();
1508 void HtmlDocVisitor::visitPre(DocSecRefItem *ref)
1511 QString refName=ref->file();
1512 if (refName.right(Doxygen::htmlFileExtension.length())!=
1513 QString(Doxygen::htmlFileExtension))
1515 refName+=Doxygen::htmlFileExtension;
1517 m_t << "<li><a href=\"" << refName << "#" << ref->anchor() << "\">";
1521 void HtmlDocVisitor::visitPost(DocSecRefItem *)
1524 m_t << "</a></li>\n";
1527 void HtmlDocVisitor::visitPre(DocSecRefList *s)
1530 forceEndParagraph(s);
1531 m_t << "<div class=\"multicol\">" << endl;
1532 m_t << "<ul>" << endl;
1535 void HtmlDocVisitor::visitPost(DocSecRefList *s)
1538 m_t << "</ul>" << endl;
1539 m_t << "</div>" << endl;
1540 forceStartParagraph(s);
1543 //void HtmlDocVisitor::visitPre(DocLanguage *l)
1545 // QString langId = Config_getEnum("OUTPUT_LANGUAGE");
1546 // if (l->id().lower()!=langId.lower())
1553 //void HtmlDocVisitor::visitPost(DocLanguage *l)
1555 // QString langId = Config_getEnum("OUTPUT_LANGUAGE");
1556 // if (l->id().lower()!=langId.lower())
1562 void HtmlDocVisitor::visitPre(DocParamSect *s)
1565 forceEndParagraph(s);
1570 case DocParamSect::Param:
1571 heading=theTranslator->trParameters();
1574 case DocParamSect::RetVal:
1575 heading=theTranslator->trReturnValues();
1578 case DocParamSect::Exception:
1579 heading=theTranslator->trExceptions();
1580 className="exception";
1582 case DocParamSect::TemplateParam:
1583 heading=theTranslator->trTemplateParameters();
1584 className="tparams";
1589 m_t << "<dl class=\"" << className << "\"><dt>";
1591 m_t << "</dt><dd>" << endl;
1592 m_t << " <table class=\"" << className << "\">" << endl;
1595 void HtmlDocVisitor::visitPost(DocParamSect *s)
1598 m_t << " </table>" << endl;
1599 m_t << " </dd>" << endl;
1600 m_t << "</dl>" << endl;
1601 forceStartParagraph(s);
1604 void HtmlDocVisitor::visitPre(DocParamList *pl)
1606 //printf("DocParamList::visitPre\n");
1609 DocParamSect *sect = 0;
1610 if (pl->parent()->kind()==DocNode::Kind_ParamSect)
1612 sect=(DocParamSect*)pl->parent();
1614 if (sect && sect->hasInOutSpecifier())
1616 m_t << "<td class=\"paramdir\">";
1617 if (pl->direction()!=DocParamSect::Unspecified)
1620 if (pl->direction()==DocParamSect::In)
1624 else if (pl->direction()==DocParamSect::Out)
1628 else if (pl->direction()==DocParamSect::InOut)
1636 if (sect && sect->hasTypeSpecifier())
1638 m_t << "<td class=\"paramtype\">";
1639 QListIterator<DocNode> li(pl->paramTypes());
1642 for (li.toFirst();(type=li.current());++li)
1644 if (!first) m_t << " | "; else first=FALSE;
1645 if (type->kind()==DocNode::Kind_Word)
1647 visit((DocWord*)type);
1649 else if (type->kind()==DocNode::Kind_LinkedWord)
1651 visit((DocLinkedWord*)type);
1656 m_t << "<td class=\"paramname\">";
1657 //QStrListIterator li(pl->parameters());
1659 QListIterator<DocNode> li(pl->parameters());
1662 for (li.toFirst();(param=li.current());++li)
1664 if (!first) m_t << ","; else first=FALSE;
1665 if (param->kind()==DocNode::Kind_Word)
1667 visit((DocWord*)param);
1669 else if (param->kind()==DocNode::Kind_LinkedWord)
1671 visit((DocLinkedWord*)param);
1677 void HtmlDocVisitor::visitPost(DocParamList *)
1679 //printf("DocParamList::visitPost\n");
1681 m_t << "</td></tr>" << endl;
1684 void HtmlDocVisitor::visitPre(DocXRefItem *x)
1687 forceEndParagraph(x);
1688 bool anonymousEnum = x->file()=="@";
1691 m_t << "<dl class=\"" << x->key() << "\"><dt><b><a class=\"el\" href=\""
1692 << x->relPath() << x->file() << Doxygen::htmlFileExtension
1693 << "#" << x->anchor() << "\">";
1697 m_t << "<dl class=\"" << x->key() << "\"><dt><b>";
1701 if (!anonymousEnum) m_t << "</a>";
1702 m_t << "</b></dt><dd>";
1705 void HtmlDocVisitor::visitPost(DocXRefItem *x)
1708 m_t << "</dd></dl>" << endl;
1709 forceStartParagraph(x);
1712 void HtmlDocVisitor::visitPre(DocInternalRef *ref)
1715 startLink(0,ref->file(),ref->relPath(),ref->anchor());
1718 void HtmlDocVisitor::visitPost(DocInternalRef *)
1725 void HtmlDocVisitor::visitPre(DocCopy *)
1729 void HtmlDocVisitor::visitPost(DocCopy *)
1733 void HtmlDocVisitor::visitPre(DocText *)
1737 void HtmlDocVisitor::visitPost(DocText *)
1741 void HtmlDocVisitor::visitPre(DocHtmlBlockQuote *b)
1744 forceEndParagraph(b);
1746 QString attrs = htmlAttribsToString(b->attribs());
1747 if (attrs.isEmpty())
1749 m_t << "<blockquote class=\"doxtable\">\n";
1753 m_t << "<blockquote " << htmlAttribsToString(b->attribs()) << ">\n";
1757 void HtmlDocVisitor::visitPost(DocHtmlBlockQuote *b)
1760 m_t << "</blockquote>" << endl;
1761 forceStartParagraph(b);
1764 void HtmlDocVisitor::filter(const char *str)
1774 case '<': m_t << "<"; break;
1775 case '>': m_t << ">"; break;
1776 case '&': m_t << "&"; break;
1782 /// Escape basic entities to produce a valid CDATA attribute value,
1783 /// assume that the outer quoting will be using the double quote "
1784 void HtmlDocVisitor::filterQuotedCdataAttr(const char* str)
1794 case '&': m_t << "&"; break;
1795 case '"': m_t << """; break;
1796 // For SGML compliance, and given the SGML declaration for HTML syntax,
1797 // it's enough to replace these two, provided that the declaration
1798 // for the HTML version we generate (and as supported by the browser)
1799 // specifies that all the other symbols used in rawVal are
1800 // within the right charachter class (i.e., they're not
1801 // some multinational weird charachters not in the BASESET).
1802 // We assume that 1) the browser will support whatever is remaining
1803 // in the formula and 2) the TeX formulae are generally governed
1804 // by even stricter charachter restrictions so it should be enough.
1806 // On some incompliant browsers, additional translation of
1807 // '>' and '<' into ">" and "<", respectively, might be needed;
1808 // but I'm unaware of particular modern (last 4 years) versions
1809 // with such problems, so let's not do it for performance.
1810 // Also, some brousers will (wrongly) not process the entity references
1811 // inside the attribute value and show the &...; form instead,
1812 // so we won't create entites unless necessary to minimize clutter there.
1819 void HtmlDocVisitor::startLink(const QCString &ref,const QCString &file,
1820 const QCString &relPath,const QCString &anchor,
1821 const QCString &tooltip)
1823 if (!ref.isEmpty()) // link to entity imported via tag file
1825 m_t << "<a class=\"elRef\" ";
1826 m_t << externalLinkTarget() << externalRef(relPath,ref,FALSE);
1830 m_t << "<a class=\"el\" ";
1833 m_t << externalRef(relPath,ref,TRUE);
1834 if (!file.isEmpty()) m_t << file << Doxygen::htmlFileExtension;
1835 if (!anchor.isEmpty()) m_t << "#" << anchor;
1837 if (!tooltip.isEmpty()) m_t << " title=\"" << substitute(tooltip,"\"",""") << "\"";
1841 void HtmlDocVisitor::endLink()
1846 void HtmlDocVisitor::pushEnabled()
1848 m_enabled.push(new bool(m_hide));
1851 void HtmlDocVisitor::popEnabled()
1853 bool *v=m_enabled.pop();
1859 void HtmlDocVisitor::writeDotFile(const QCString &fn,const QCString &relPath,
1860 const QCString &context)
1862 QCString baseName=fn;
1864 if ((i=baseName.findRev('/'))!=-1)
1866 baseName=baseName.right(baseName.length()-i-1);
1868 if ((i=baseName.find('.'))!=-1) // strip extension
1870 baseName=baseName.left(i);
1872 baseName.prepend("dot_");
1873 QCString outDir = Config_getString("HTML_OUTPUT");
1874 writeDotGraphFromFile(fn,outDir,baseName,BITMAP);
1875 writeDotImageMapFromFile(m_t,fn,outDir,relPath,baseName,context);
1878 void HtmlDocVisitor::writeMscFile(const QCString &fileName,
1879 const QCString &relPath,
1880 const QCString &context)
1882 QCString baseName=fileName;
1884 if ((i=baseName.findRev('/'))!=-1) // strip path
1886 baseName=baseName.right(baseName.length()-i-1);
1888 if ((i=baseName.find('.'))!=-1) // strip extension
1890 baseName=baseName.left(i);
1892 baseName.prepend("msc_");
1893 QCString outDir = Config_getString("HTML_OUTPUT");
1894 writeMscGraphFromFile(fileName,outDir,baseName,MSC_BITMAP);
1895 writeMscImageMapFromFile(m_t,fileName,outDir,relPath,baseName,context);
1898 /** Used for items found inside a paragraph, which due to XHTML restrictions
1899 * have to be outside of the paragraph. This method will forcefully end
1900 * the current paragraph and forceStartParagraph() will restart it.
1902 void HtmlDocVisitor::forceEndParagraph(DocNode *n)
1904 //printf("forceEndParagraph(%p) %d\n",n,n->kind());
1905 if (n->parent() && n->parent()->kind()==DocNode::Kind_Para)
1907 DocPara *para = (DocPara*)n->parent();
1908 int nodeIndex = para->children().findRef(n);
1910 if (nodeIndex<0) return; // first node
1911 while (nodeIndex>=0 &&
1912 para->children().at(nodeIndex)->kind()==DocNode::Kind_WhiteSpace
1919 DocNode *n = para->children().at(nodeIndex);
1920 //printf("n=%p kind=%d outside=%d\n",n,n->kind(),mustBeOutsideParagraph(n));
1921 if (mustBeOutsideParagraph(n)) return;
1926 getParagraphContext(para,isFirst,isLast);
1927 //printf("forceEnd first=%d last=%d\n",isFirst,isLast);
1928 if (isFirst && isLast) return;
1930 m_t << "</p>" << endl;
1934 /** Used for items found inside a paragraph, which due to XHTML restrictions
1935 * have to be outside of the paragraph. This method will forcefully start
1936 * the paragraph, that was previously ended by forceEndParagraph().
1938 void HtmlDocVisitor::forceStartParagraph(DocNode *n)
1940 //printf("forceStartParagraph(%p) %d\n",n,n->kind());
1941 if (n->parent() && n->parent()->kind()==DocNode::Kind_Para) // if we are inside a paragraph
1943 DocPara *para = (DocPara*)n->parent();
1944 int nodeIndex = para->children().findRef(n);
1945 int numNodes = para->children().count();
1947 if (nodeIndex==numNodes) return; // last node
1948 while (nodeIndex<numNodes &&
1949 para->children().at(nodeIndex)->kind()==DocNode::Kind_WhiteSpace
1954 if (nodeIndex<numNodes)
1956 DocNode *n = para->children().at(nodeIndex);
1957 if (mustBeOutsideParagraph(n)) return;
1961 return; // only whitespace at the end!
1966 getParagraphContext(para,isFirst,isLast);
1967 //printf("forceStart first=%d last=%d\n",isFirst,isLast);
1968 if (isFirst && isLast) return;