for SVG images,
// which do not support the alt attribute
{
*pAltValue = att.value;
}
else
{
result+=" ";
result+=att.name;
result+="=\""+convertToXML(att.value)+"\"";
}
}
else if (att.name=="open")
{
// The open attribute is a boolean attribute.
// Specifies that the details should be visible (open) to the user
// As it is a boolean attribute the initialisation value is of no interest
result+=" ";
result+=att.name;
result+="=\"true\"";
}
else if (att.name=="nowrap") // In XHTML, attribute minimization is forbidden, and the nowrap attribute must be defined as .
{
result+=" ";
result+=att.name;
result+="=\"nowrap\"";
}
}
return result;
}
//-------------------------------------------------------------------------
HtmlDocVisitor::HtmlDocVisitor(TextStream &t,CodeOutputInterface &ci,
const Definition *ctx)
: m_t(t), m_ci(ci), m_ctx(ctx)
{
if (ctx) m_langExt=ctx->getDefFileExtension();
}
template
void HtmlDocVisitor::visitCaption(TextStream &t,const T &n)
{
if (n.hasCaption())
{
t << "\n";
for (const auto &child : n.children())
{
std::visit(*this, child);
}
t << "
\n";
}
}
//--------------------------------------
// visitor functions for leaf nodes
//--------------------------------------
void HtmlDocVisitor::operator()(const DocWord &w)
{
if (m_hide) return;
filter(w.word());
}
void HtmlDocVisitor::operator()(const DocLinkedWord &w)
{
if (m_hide) return;
//printf("linked word: %s\n",qPrint(w.word()));
startLink(w.ref(),w.file(),w.relPath(),w.anchor(),w.tooltip());
filter(w.word());
endLink();
}
void HtmlDocVisitor::operator()(const DocWhiteSpace &w)
{
if (m_hide) return;
if (m_insidePre)
{
m_t << w.chars();
}
else
{
m_t << " ";
}
}
void HtmlDocVisitor::operator()(const DocSymbol &s)
{
if (m_hide) return;
if (m_insideTitle &&
(s.symbol()==HtmlEntityMapper::Sym_Quot || s.symbol()==HtmlEntityMapper::Sym_quot)) // escape "'s inside title="..."
{
m_t << """;
}
else
{
const char *res = HtmlEntityMapper::instance()->html(s.symbol());
if (res)
{
m_t << res;
}
else
{
err("HTML: non supported HTML-entity found: %s\n",
HtmlEntityMapper::instance()->html(s.symbol(),TRUE));
}
}
}
void HtmlDocVisitor::operator()(const DocEmoji &s)
{
if (m_hide) return;
const char *res = EmojiEntityMapper::instance()->unicode(s.index());
if (res)
{
m_t << "" << res << " ";
}
else
{
m_t << s.name();
}
}
void HtmlDocVisitor::writeObfuscatedMailAddress(const QCString &url)
{
if (!Config_getBool(OBFUSCATE_EMAILS))
{
m_t << "";
}
else
{
m_t << " ";
}
}
void HtmlDocVisitor::operator()(const DocURL &u)
{
if (m_hide) return;
if (u.isEmail()) // mail address
{
QCString url = u.url();
// obfuscate the mail address link
writeObfuscatedMailAddress(url);
if (!Config_getBool(OBFUSCATE_EMAILS))
{
m_t << url;
}
else
{
const char *p = url.data();
// also obfuscate the address as shown on the web page
uint size=5;
while (*p)
{
for (uint j=0;j.nosp@m.";
if (size==5) size=4; else size=5;
}
}
m_t << " ";
}
else // web address
{
m_t << "";
filter(u.url());
m_t << " ";
}
}
void HtmlDocVisitor::operator()(const DocLineBreak &br)
{
if (m_hide) return;
m_t << " \n";
}
void HtmlDocVisitor::operator()(const DocHorRuler &hr)
{
if (m_hide) return;
forceEndParagraph(hr);
m_t << " \n";
forceStartParagraph(hr);
}
void HtmlDocVisitor::operator()(const DocStyleChange &s)
{
if (m_hide) return;
switch (s.style())
{
case DocStyleChange::Bold:
if (s.enable()) m_t << ""; else m_t << " ";
break;
case DocStyleChange::S:
if (s.enable()) m_t << ""; else m_t << " ";
break;
case DocStyleChange::Strike:
if (s.enable()) m_t << ""; else m_t << " ";
break;
case DocStyleChange::Del:
if (s.enable()) m_t << ""; else m_t << "";
break;
case DocStyleChange::Underline:
if (s.enable()) m_t << ""; else m_t << " ";
break;
case DocStyleChange::Ins:
if (s.enable()) m_t << ""; else m_t << " ";
break;
case DocStyleChange::Italic:
if (s.enable()) m_t << ""; else m_t << " ";
break;
case DocStyleChange::Code:
if (s.enable()) m_t << ""; else m_t << "
";
break;
case DocStyleChange::Subscript:
if (s.enable()) m_t << ""; else m_t << " ";
break;
case DocStyleChange::Superscript:
if (s.enable()) m_t << ""; else m_t << " ";
break;
case DocStyleChange::Center:
if (s.enable())
{
forceEndParagraph(s);
m_t << "";
}
else
{
m_t << " ";
forceStartParagraph(s);
}
break;
case DocStyleChange::Small:
if (s.enable()) m_t << ""; else m_t << " ";
break;
case DocStyleChange::Cite:
if (s.enable()) m_t << ""; else m_t << " ";
break;
case DocStyleChange::Preformatted:
if (s.enable())
{
forceEndParagraph(s);
m_t << "";
m_insidePre=TRUE;
}
else
{
m_insidePre=FALSE;
m_t << " ";
forceStartParagraph(s);
}
break;
case DocStyleChange::Div:
if (s.enable())
{
forceEndParagraph(s);
m_t << "";
}
else
{
m_t << "
";
forceStartParagraph(s);
}
break;
case DocStyleChange::Span:
if (s.enable()) m_t << ""; else m_t << " ";
break;
case DocStyleChange::Summary:
if (s.enable()) m_t << ""; else m_t << " ";
break;
}
}
///--------------------------------------------------------------------------------------
void HtmlDocVisitor::operator()(const DocVerbatim &s)
{
if (m_hide) return;
QCString lang = m_langExt;
if (!s.language().isEmpty()) // explicit language setting
{
lang = s.language();
}
SrcLangExt langExt = getLanguageFromCodeLang(lang);
switch(s.type())
{
case DocVerbatim::Code:
forceEndParagraph(s);
m_ci.startCodeFragment("DoxyCode");
getCodeParser(lang).parseCode(m_ci,
s.context(),
s.text(),
langExt,
s.isExample(),
s.exampleFile(),
0, // fileDef
-1, // startLine
-1, // endLine
FALSE, // inlineFragment
0, // memberDef
TRUE, // show line numbers
m_ctx // search context
);
m_ci.endCodeFragment("DoxyCode");
forceStartParagraph(s);
break;
case DocVerbatim::Verbatim:
forceEndParagraph(s);
m_t << "";
filter(s.text());
m_t << " ";
forceStartParagraph(s);
break;
case DocVerbatim::JavaDocLiteral:
filter(s.text(), true);
break;
case DocVerbatim::JavaDocCode:
m_t << "";
filter(s.text(), true);
m_t << "
";
break;
case DocVerbatim::HtmlOnly:
{
if (s.isBlock()) forceEndParagraph(s);
m_t << s.text();
if (s.isBlock()) forceStartParagraph(s);
}
break;
case DocVerbatim::ManOnly:
case DocVerbatim::LatexOnly:
case DocVerbatim::XmlOnly:
case DocVerbatim::RtfOnly:
case DocVerbatim::DocbookOnly:
/* nothing */
break;
case DocVerbatim::Dot:
{
static int dotindex = 1;
QCString fileName(4096);
forceEndParagraph(s);
fileName.sprintf("%s%d%s",
qPrint(Config_getString(HTML_OUTPUT)+"/inline_dotgraph_"),
dotindex++,
".dot"
);
std::ofstream file(fileName.str(),std::ofstream::out | std::ofstream::binary);
if (!file.is_open())
{
err("Could not open file %s for writing\n",qPrint(fileName));
}
else
{
QCString stext = s.text();
file.write( stext.data(), stext.length() );
file.close();
m_t << "\n";
writeDotFile(fileName,s.relPath(),s.context(),s.srcFile(),s.srcLine());
visitCaption(m_t, s);
m_t << "
\n";
if (Config_getBool(DOT_CLEANUP)) Dir().remove(fileName.str());
}
forceStartParagraph(s);
}
break;
case DocVerbatim::Msc:
{
forceEndParagraph(s);
static int mscindex = 1;
QCString baseName(4096);
baseName.sprintf("%s%d",
qPrint(Config_getString(HTML_OUTPUT)+"/inline_mscgraph_"),
mscindex++
);
std::ofstream file(baseName.str()+".msc",std::ofstream::out | std::ofstream::binary);
if (!file.is_open())
{
err("Could not open file %s.msc for writing\n",qPrint(baseName));
}
else
{
QCString text = "msc {";
text+=s.text();
text+="}";
file.write( text.data(), text.length() );
file.close();
m_t << "\n";
writeMscFile(baseName+".msc",s.relPath(),s.context(),s.srcFile(),s.srcLine());
visitCaption(m_t, s);
m_t << "
\n";
if (Config_getBool(DOT_CLEANUP)) Dir().remove(baseName.str()+".msc");
}
forceStartParagraph(s);
}
break;
case DocVerbatim::PlantUML:
{
forceEndParagraph(s);
QCString htmlOutput = Config_getString(HTML_OUTPUT);
QCString imgExt = getDotImageExtension();
PlantumlManager::OutputFormat format = PlantumlManager::PUML_BITMAP; // default : PUML_BITMAP
if (imgExt=="svg")
{
format = PlantumlManager::PUML_SVG;
}
QCString baseName = PlantumlManager::instance().writePlantUMLSource(
htmlOutput,s.exampleFile(),
s.text(),format,s.engine(),s.srcFile(),s.srcLine());
m_t << "\n";
writePlantUMLFile(baseName,s.relPath(),s.context(),s.srcFile(),s.srcLine());
visitCaption(m_t, s);
m_t << "
\n";
forceStartParagraph(s);
}
break;
}
}
void HtmlDocVisitor::operator()(const DocAnchor &anc)
{
if (m_hide) return;
m_t << " ";
}
void HtmlDocVisitor::operator()(const DocInclude &inc)
{
if (m_hide) return;
SrcLangExt langExt = getLanguageFromFileName(inc.extension());
switch(inc.type())
{
case DocInclude::Include:
forceEndParagraph(inc);
m_ci.startCodeFragment("DoxyCode");
getCodeParser(inc.extension()).parseCode(m_ci,
inc.context(),
inc.text(),
langExt,
inc.isExample(),
inc.exampleFile(),
0, // fileDef
-1, // startLine
-1, // endLine
TRUE, // inlineFragment
0, // memberDef
FALSE, // show line numbers
m_ctx // search context
);
m_ci.endCodeFragment("DoxyCode");
forceStartParagraph(inc);
break;
case DocInclude::IncWithLines:
{
forceEndParagraph(inc);
m_ci.startCodeFragment("DoxyCode");
FileInfo cfi( inc.file().str() );
FileDef *fd = createFileDef( cfi.dirPath(), cfi.fileName() );
getCodeParser(inc.extension()).parseCode(m_ci,
inc.context(),
inc.text(),
langExt,
inc.isExample(),
inc.exampleFile(),
fd, // fileDef,
-1, // start line
-1, // end line
FALSE, // inline fragment
0, // memberDef
TRUE, // show line numbers
m_ctx // search context
);
delete fd;
m_ci.endCodeFragment("DoxyCode");
forceStartParagraph(inc);
}
break;
case DocInclude::DontInclude:
case DocInclude::LatexInclude:
case DocInclude::RtfInclude:
case DocInclude::ManInclude:
case DocInclude::XmlInclude:
case DocInclude::DocbookInclude:
case DocInclude::DontIncWithLines:
break;
case DocInclude::HtmlInclude:
{
if (inc.isBlock()) forceEndParagraph(inc);
m_t << inc.text();
if (inc.isBlock()) forceStartParagraph(inc);
}
break;
case DocInclude::VerbInclude:
forceEndParagraph(inc);
m_t << "";
filter(inc.text());
m_t << " ";
forceStartParagraph(inc);
break;
case DocInclude::Snippet:
{
forceEndParagraph(inc);
m_ci.startCodeFragment("DoxyCode");
getCodeParser(inc.extension()).parseCode(m_ci,
inc.context(),
extractBlock(inc.text(),inc.blockId()),
langExt,
inc.isExample(),
inc.exampleFile(),
0,
-1, // startLine
-1, // endLine
TRUE, // inlineFragment
0, // memberDef
FALSE, // show line number
m_ctx // search context
);
m_ci.endCodeFragment("DoxyCode");
forceStartParagraph(inc);
}
break;
case DocInclude::SnipWithLines:
{
forceEndParagraph(inc);
m_ci.startCodeFragment("DoxyCode");
FileInfo cfi( inc.file().str() );
FileDef *fd = createFileDef( cfi.dirPath(), cfi.fileName() );
getCodeParser(inc.extension()).parseCode(m_ci,
inc.context(),
extractBlock(inc.text(),inc.blockId()),
langExt,
inc.isExample(),
inc.exampleFile(),
fd,
lineBlock(inc.text(),inc.blockId()),
-1, // endLine
FALSE, // inlineFragment
0, // memberDef
TRUE, // show line number
m_ctx // search context
);
delete fd;
m_ci.endCodeFragment("DoxyCode");
forceStartParagraph(inc);
}
break;
case DocInclude::SnippetDoc:
case DocInclude::IncludeDoc:
err("Internal inconsistency: found switch SnippetDoc / IncludeDoc in file: %s"
"Please create a bug report\n",__FILE__);
break;
}
}
void HtmlDocVisitor::operator()(const DocIncOperator &op)
{
//printf("DocIncOperator: type=%d first=%d, last=%d text='%s'\n",
// op.type(),op.isFirst(),op.isLast(),qPrint(op.text()));
if (op.isFirst())
{
forceEndParagraph(op);
if (!m_hide) m_ci.startCodeFragment("DoxyCode");
pushHidden(m_hide);
m_hide=TRUE;
}
QCString locLangExt = getFileNameExtension(op.includeFileName());
if (locLangExt.isEmpty()) locLangExt = m_langExt;
SrcLangExt langExt = getLanguageFromFileName(locLangExt);
if (op.type()!=DocIncOperator::Skip)
{
m_hide = popHidden();
if (!m_hide)
{
FileDef *fd = 0;
if (!op.includeFileName().isEmpty())
{
FileInfo cfi( op.includeFileName().str() );
fd = createFileDef( cfi.dirPath(), cfi.fileName() );
}
getCodeParser(locLangExt).parseCode(
m_ci,
op.context(),
op.text(),
langExt,
op.isExample(),
op.exampleFile(),
fd, // fileDef
op.line(), // startLine
-1, // endLine
FALSE, // inline fragment
0, // memberDef
op.showLineNo(), // show line numbers
m_ctx // search context
);
if (fd) delete fd;
}
pushHidden(m_hide);
m_hide=TRUE;
}
if (op.isLast())
{
m_hide = popHidden();
if (!m_hide) m_ci.endCodeFragment("DoxyCode");
forceStartParagraph(op);
}
else
{
if (!m_hide) m_t << "\n";
}
}
void HtmlDocVisitor::operator()(const DocFormula &f)
{
if (m_hide) return;
bool bDisplay = !f.isInline();
if (bDisplay)
{
forceEndParagraph(f);
m_t << "\n";
}
if (Config_getBool(USE_MATHJAX))
{
QCString text = f.text();
bool closeInline = FALSE;
if (!bDisplay && !text.isEmpty() && text.at(0)=='$' &&
text.at(text.length()-1)=='$')
{
closeInline=TRUE;
text = text.mid(1,text.length()-2);
m_t << "\\(";
}
else if (!bDisplay && !text.isEmpty())
{
closeInline=TRUE;
m_t << "\\(";
}
m_t << convertToHtml(text);
if (closeInline)
{
m_t << "\\)";
}
}
else
{
const Formula *formula = FormulaManager::instance().findFormula(f.id());
enum class ImageType { Light, Dark };
enum class Visibility { Always, Dark, Light, AutoDark, AutoLight };
auto writeFormula = [&](ImageType imgType,Visibility visibility) -> QCString {
// see https://chipcullen.com/how-to-have-dark-mode-image-that-works-with-user-choice for the design idea
TextStream t;
QCString extension = Config_getEnum(HTML_FORMULA_FORMAT)==HTML_FORMULA_FORMAT_t::svg ? ".svg":".png" ;
if (visibility==Visibility::AutoDark || visibility==Visibility::AutoLight)
{
t << "";
t << " ";
}
t << " width()!=-1)
{
t << "\" width=\"";
t << formula->width();
}
if (formula && formula->height()!=-1)
{
t << "\" height=\"";
t << formula->height();
}
t << "\"/>";
if (visibility==Visibility::AutoDark || visibility==Visibility::AutoLight)
{
t << " ";
}
return QCString(t.str());
};
auto colorStyle = Config_getEnum(HTML_COLORSTYLE);
switch(colorStyle)
{
case HTML_COLORSTYLE_t::LIGHT:
m_t << writeFormula(ImageType::Light,Visibility::Always);
break;
case HTML_COLORSTYLE_t::DARK:
m_t << writeFormula(ImageType::Dark, Visibility::Always);
break;
case HTML_COLORSTYLE_t::AUTO_LIGHT:
m_t << writeFormula(ImageType::Light, Visibility::AutoLight);
break;
case HTML_COLORSTYLE_t::AUTO_DARK:
m_t << writeFormula(ImageType::Dark, Visibility::AutoDark);
break;
case HTML_COLORSTYLE_t::TOGGLE:
// write the image twice and use javascript (darkmode_toggle.js) to show only one of them
m_t << writeFormula(ImageType::Light,Visibility::Light);
m_t << writeFormula(ImageType::Dark, Visibility::Dark);
break;
}
}
if (bDisplay)
{
m_t << "\n
\n";
forceStartParagraph(f);
}
}
void HtmlDocVisitor::operator()(const DocIndexEntry &e)
{
QCString anchor = convertIndexWordToAnchor(e.entry());
if (e.member())
{
anchor.prepend(e.member()->anchor()+"_");
}
m_t << " ";
//printf("*** DocIndexEntry: word='%s' scope='%s' member='%s'\n",
// qPrint(e.entry()),
// e.scope() ? qPrint(e.scope()->name()) : "",
// e.member() ? qPrint(e.member()->name()) : ""
// );
Doxygen::indexList->addIndexItem(e.scope(),e.member(),anchor,e.entry());
}
void HtmlDocVisitor::operator()(const DocSimpleSectSep &)
{
m_t << "\n";
m_t << "\n";
}
void HtmlDocVisitor::operator()(const DocCite &cite)
{
if (m_hide) return;
if (!cite.file().isEmpty())
{
startLink(cite.ref(),cite.file(),cite.relPath(),cite.anchor());
}
else
{
m_t << "[";
}
filter(cite.text());
if (!cite.file().isEmpty())
{
endLink();
}
else
{
m_t << "] ";
}
}
//--------------------------------------
// visitor functions for compound nodes
//--------------------------------------
void HtmlDocVisitor::operator()(const DocAutoList &l)
{
//printf("DocAutoList::visitPre\n");
if (m_hide) return;
forceEndParagraph(l);
if (l.isEnumList())
{
//
// Do list type based on depth:
// 1.
// a.
// i.
// A.
// 1. (repeat)...
//
m_t << "";
}
else
{
m_t << "";
}
if (!l.isPreformatted()) m_t << "\n";
visitChildren(l);
if (l.isEnumList())
{
m_t << " ";
}
else
{
m_t << "";
}
if (!l.isPreformatted()) m_t << "\n";
forceStartParagraph(l);
}
void HtmlDocVisitor::operator()(const DocAutoListItem &li)
{
if (m_hide) return;
m_t << "";
visitChildren(li);
m_t << " ";
if (!li.isPreformatted()) m_t << "\n";
}
template
static bool holds_value(const Node *val,const DocNodeVariant &v)
{
bool b = std::visit([&](auto &&x) {
//printf("holds_value val=%s (%p) v=%s (%p)\n",
// docNodeName(*val),(void*)val,docNodeName(v),(void *)&x);
return val==static_cast(&x);
}, v);
return b;
}
template
bool isFirstChildNode(const T *parent, const DocPara &node)
{
return !parent->children().empty() && holds_value(&node,parent->children().front());
}
template
bool isLastChildNode(const T *parent, const DocPara &node)
{
return !parent->children().empty() && holds_value(&node,parent->children().back());
}
bool isSeparatedParagraph(const DocSimpleSect &parent,const DocPara &par)
{
const DocNodeList &nodes = parent.children();
auto it = std::find_if(std::begin(nodes),std::end(nodes),[&par](const auto &n) { return holds_value(&par,n); });
if (it==std::end(nodes)) return FALSE;
size_t count = parent.children().size();
auto isSeparator = [](auto &&it_) { return std::get_if(&(*it_))!=0; };
if (count>1 && it==std::begin(nodes)) // it points to first node
{
return isSeparator(std::next(it));
}
else if (count>1 && it==std::prev(std::end(nodes))) // it points to last node
{
return isSeparator(std::prev(it));
}
else if (count>2 && it!=std::begin(nodes) && it!=std::prev(std::end(nodes))) // it points to intermediate node
{
return isSeparator(std::prev(it)) && isSeparator(std::next(it));
}
return false;
}
static int getParagraphContext(const DocPara &p,bool &isFirst,bool &isLast)
{
int t=0;
isFirst=FALSE;
isLast=FALSE;
if (p.parent())
{
const auto parBlock = std::get_if(p.parent());
if (parBlock)
{
// hierarchy: node N -> para -> parblock -> para
// adapt return value to kind of N
const DocNodeVariant *p3 = 0;
if (::parent(p.parent()) && ::parent(::parent(p.parent())) )
{
p3 = ::parent(::parent(p.parent()));
}
isFirst=isFirstChildNode(parBlock,p);
isLast =isLastChildNode (parBlock,p);
bool isLI = p3!=0 && holds_one_of_alternatives(*p3);
bool isDD = p3!=0 && holds_one_of_alternatives(*p3);
bool isTD = p3!=0 && holds_one_of_alternatives(*p3);
t=NONE;
if (isFirst)
{
if (isLI) t=STARTLI; else if (isDD) t=STARTDD; else if (isTD) t=STARTTD;
}
if (isLast)
{
if (isLI) t=ENDLI; else if (isDD) t=ENDDD; else if (isTD) t=ENDTD;
}
if (!isFirst && !isLast)
{
if (isLI) t=INTERLI; else if (isDD) t=INTERDD; else if (isTD) t=INTERTD;
}
return t;
}
const auto docAutoListItem = std::get_if(p.parent());
if (docAutoListItem)
{
isFirst=isFirstChildNode(docAutoListItem,p);
isLast =isLastChildNode (docAutoListItem,p);
t=STARTLI; // not used
return t;
}
const auto docSimpleListItem = std::get_if(p.parent());
if (docSimpleListItem)
{
isFirst=TRUE;
isLast =TRUE;
t=STARTLI; // not used
return t;
}
const auto docParamList = std::get_if(p.parent());
if (docParamList)
{
isFirst=TRUE;
isLast =TRUE;
t=STARTLI; // not used
return t;
}
const auto docHtmlListItem = std::get_if(p.parent());
if (docHtmlListItem)
{
isFirst=isFirstChildNode(docHtmlListItem,p);
isLast =isLastChildNode (docHtmlListItem,p);
if (isFirst) t=STARTLI;
if (isLast) t=ENDLI;
if (!isFirst && !isLast) t = INTERLI;
return t;
}
const auto docSecRefItem = std::get_if(p.parent());
if (docSecRefItem)
{
isFirst=isFirstChildNode(docSecRefItem,p);
isLast =isLastChildNode (docSecRefItem,p);
if (isFirst) t=STARTLI;
if (isLast) t=ENDLI;
if (!isFirst && !isLast) t = INTERLI;
return t;
}
const auto docHtmlDescData = std::get_if(p.parent());
if (docHtmlDescData)
{
isFirst=isFirstChildNode(docHtmlDescData,p);
isLast =isLastChildNode (docHtmlDescData,p);
if (isFirst) t=STARTDD;
if (isLast) t=ENDDD;
if (!isFirst && !isLast) t = INTERDD;
return t;
}
const auto docXRefItem = std::get_if(p.parent());
if (docXRefItem)
{
isFirst=isFirstChildNode(docXRefItem,p);
isLast =isLastChildNode (docXRefItem,p);
if (isFirst) t=STARTDD;
if (isLast) t=ENDDD;
if (!isFirst && !isLast) t = INTERDD;
return t;
}
const auto docSimpleSect = std::get_if(p.parent());
if (docSimpleSect)
{
isFirst=isFirstChildNode(docSimpleSect,p);
isLast =isLastChildNode (docSimpleSect,p);
if (isFirst) t=STARTDD;
if (isLast) t=ENDDD;
if (isSeparatedParagraph(*docSimpleSect,p))
// if the paragraph is enclosed with separators it will
// be included in .. so avoid addition paragraph
// markers
{
isFirst=isLast=TRUE;
}
if (!isFirst && !isLast) t = INTERDD;
return t;
}
const auto docHtmlCell = std::get_if(p.parent());
if (docHtmlCell)
{
isFirst=isFirstChildNode(docHtmlCell,p);
isLast =isLastChildNode (docHtmlCell,p);
if (isFirst) t=STARTTD;
if (isLast) t=ENDTD;
if (!isFirst && !isLast) t = INTERTD;
return t;
}
}
return t;
}
static bool determineIfNeedsTag(const DocPara &p)
{
bool needsTag = FALSE;
if (p.parent())
{
if (holds_one_of_alternatives(*p.parent()))
{
needsTag = TRUE;
}
else if (std::get_if(p.parent()))
{
needsTag = !std::get(*p.parent()).singleLine();
}
}
return needsTag;
}
void HtmlDocVisitor::operator()(const DocPara &p)
{
if (m_hide) return;
//printf("> DocPara\n");
//dumpDocNodeList(p.children());
bool needsTag = determineIfNeedsTag(p);
//printf(" needsTag=%d\n",needsTag);
bool needsTagBefore = needsTag;
bool needsTagAfter = needsTag;
// if the first element of a paragraph is something that should be outside of
// the paragraph (,,,..) then that will already started the
// paragraph and we don't need to do it here
if (!p.children().empty())
{
auto it = std::find_if(std::begin(p.children()),std::end(p.children()),
[](const auto &node) { return !isInvisibleNode(node); });
if (it!=std::end(p.children()))
{
const DocNodeVariant &n = *it;
if (mustBeOutsideParagraph(n))
{
needsTagBefore = FALSE;
}
}
}
// check if this paragraph is the first or last or intermediate child of a or .
// this allows us to mark the tag with a special class so we can
// fix the otherwise ugly spacing.
int t;
bool isFirst;
bool isLast;
t = getParagraphContext(p,isFirst,isLast);
//printf("startPara first=%d last=%d\n",isFirst,isLast);
if (isFirst && isLast) needsTagBefore=FALSE;
//printf(" needsTagBefore=%d\n",needsTagBefore);
// write the paragraph tag (if needed)
if (needsTagBefore)
{
if (strlen(contexts[t]))
m_t << "";
else
m_t << "
";
}
visitChildren(p);
// if the last element of a paragraph is something that should be outside of
// the paragraph (
,,) then that will already have ended the
// paragraph and we don't need to do it here
if (!p.children().empty())
{
auto it = std::prev(std::end(p.children()));
for (;;)
{
const DocNodeVariant &n = *it;
if (!isInvisibleNode(n))
{
if (mustBeOutsideParagraph(n))
{
needsTagAfter = FALSE;
}
// stop searching if we found a node that is visible
break;
}
if (it==std::begin(p.children()))
{
// stop searching if we are at the beginning of the list
break;
}
else
{
--it;
}
}
}
//printf("endPara first=%d last=%d\n",isFirst,isLast);
if (isFirst && isLast) needsTagAfter=FALSE;
//printf(" needsTagAfter=%d\n",needsTagAfter);
if (needsTagAfter) m_t << "\n";
//printf("< DocPara\n");
}
void HtmlDocVisitor::operator()(const DocRoot &r)
{
//printf("> DocRoot\n");
//dumpDocNodeList(r.children());
visitChildren(r);
//printf("< DocRoot\n");
}
void HtmlDocVisitor::operator()(const DocSimpleSect &s)
{
if (m_hide) return;
forceEndParagraph(s);
m_t << "";
switch(s.type())
{
case DocSimpleSect::See:
m_t << theTranslator->trSeeAlso(); break;
case DocSimpleSect::Return:
m_t << theTranslator->trReturns(); break;
case DocSimpleSect::Author:
m_t << theTranslator->trAuthor(TRUE,TRUE); break;
case DocSimpleSect::Authors:
m_t << theTranslator->trAuthor(TRUE,FALSE); break;
case DocSimpleSect::Version:
m_t << theTranslator->trVersion(); break;
case DocSimpleSect::Since:
m_t << theTranslator->trSince(); break;
case DocSimpleSect::Date:
m_t << theTranslator->trDate(); break;
case DocSimpleSect::Note:
m_t << theTranslator->trNote(); break;
case DocSimpleSect::Warning:
m_t << theTranslator->trWarning(); break;
case DocSimpleSect::Pre:
m_t << theTranslator->trPrecondition(); break;
case DocSimpleSect::Post:
m_t << theTranslator->trPostcondition(); break;
case DocSimpleSect::Copyright:
m_t << theTranslator->trCopyright(); break;
case DocSimpleSect::Invar:
m_t << theTranslator->trInvariant(); break;
case DocSimpleSect::Remark:
m_t << theTranslator->trRemarks(); break;
case DocSimpleSect::Attention:
m_t << theTranslator->trAttention(); break;
case DocSimpleSect::User: break;
case DocSimpleSect::Rcs: break;
case DocSimpleSect::Unknown: break;
}
if (s.title())
{
std::visit(*this,*s.title());
}
m_t << " ";
visitChildren(s);
m_t << " \n";
forceStartParagraph(s);
}
void HtmlDocVisitor::operator()(const DocTitle &t)
{
if (m_hide) return;
visitChildren(t);
}
void HtmlDocVisitor::operator()(const DocSimpleList &sl)
{
if (m_hide) return;
forceEndParagraph(sl);
m_t << "";
if (!sl.isPreformatted()) m_t << "\n";
visitChildren(sl);
m_t << " ";
if (!sl.isPreformatted()) m_t << "\n";
forceStartParagraph(sl);
}
void HtmlDocVisitor::operator()(const DocSimpleListItem &li)
{
if (m_hide) return;
m_t << "";
if (li.paragraph())
{
visit(*this,*li.paragraph());
}
m_t << " ";
if (!li.isPreformatted()) m_t << "\n";
}
void HtmlDocVisitor::operator()(const DocSection &s)
{
if (m_hide) return;
forceEndParagraph(s);
m_t << "";
m_t << " \n";
filter(convertCharEntitiesToUTF8(s.title()));
m_t << " \n";
visitChildren(s);
forceStartParagraph(s);
}
void HtmlDocVisitor::operator()(const DocHtmlList &s)
{
if (m_hide) return;
forceEndParagraph(s);
if (s.type()==DocHtmlList::Ordered)
{
m_t << "\n";
visitChildren(s);
if (s.type()==DocHtmlList::Ordered)
{
m_t << " ";
}
else
{
m_t << "";
}
if (!s.isPreformatted()) m_t << "\n";
forceStartParagraph(s);
}
void HtmlDocVisitor::operator()(const DocHtmlListItem &i)
{
if (m_hide) return;
m_t << "";
if (!i.isPreformatted()) m_t << "\n";
visitChildren(i);
m_t << " \n";
}
void HtmlDocVisitor::operator()(const DocHtmlDescList &dl)
{
if (m_hide) return;
forceEndParagraph(dl);
m_t << "\n";
visitChildren(dl);
m_t << " \n";
forceStartParagraph(dl);
}
void HtmlDocVisitor::operator()(const DocHtmlDescTitle &dt)
{
if (m_hide) return;
m_t << "";
visitChildren(dt);
m_t << " \n";
}
void HtmlDocVisitor::operator()(const DocHtmlDescData &dd)
{
if (m_hide) return;
m_t << "";
visitChildren(dd);
m_t << " \n";
}
void HtmlDocVisitor::operator()(const DocHtmlTable &t)
{
if (m_hide) return;
forceEndParagraph(t);
if (t.caption())
{
QCString anc = std::get(*t.caption()).anchor();
if (!anc.isEmpty())
{
m_t << " \n";
}
}
QCString attrs = htmlAttribsToString(t.attribs());
if (attrs.isEmpty())
{
m_t << "\n";
}
else
{
m_t << "\n";
}
if (t.caption())
{
std::visit(*this,*t.caption());
}
visitChildren(t);
m_t << "
\n";
forceStartParagraph(t);
}
void HtmlDocVisitor::operator()(const DocHtmlRow &tr)
{
if (m_hide) return;
m_t << "\n";
visitChildren(tr);
m_t << " \n";
}
void HtmlDocVisitor::operator()(const DocHtmlCell &c)
{
if (m_hide) return;
if (c.isHeading())
{
m_t << "";
}
else
{
m_t << " ";
}
visitChildren(c);
if (c.isHeading()) m_t << ""; else m_t << " ";
}
void HtmlDocVisitor::operator()(const DocHtmlCaption &c)
{
if (m_hide) return;
m_t << "";
visitChildren(c);
m_t << " \n";
}
void HtmlDocVisitor::operator()(const DocInternal &i)
{
if (m_hide) return;
visitChildren(i);
}
void HtmlDocVisitor::operator()(const DocHRef &href)
{
if (m_hide) return;
if (href.url().startsWith("mailto:"))
{
writeObfuscatedMailAddress(href.url().mid(7));
}
else
{
QCString url = correctURL(href.url(),href.relPath());
m_t << "";
}
visitChildren(href);
m_t << " ";
}
void HtmlDocVisitor::operator()(const DocHtmlDetails &d)
{
if (m_hide) return;
forceEndParagraph(d);
m_t << "\n";
visitChildren(d);
m_t << " \n";
forceStartParagraph(d);
}
void HtmlDocVisitor::operator()(const DocHtmlHeader &header)
{
if (m_hide) return;
forceEndParagraph(header);
m_t << "";
visitChildren(header);
m_t << " \n";
forceStartParagraph(header);
}
void HtmlDocVisitor::operator()(const DocImage &img)
{
if (img.type()==DocImage::Html)
{
bool inlineImage = img.isInlineImage();
bool typeSVG = img.isSVG();
QCString url = img.url();
if (!inlineImage)
{
forceEndParagraph(img);
}
if (m_hide) return;
QCString baseName=img.name();
int i;
if ((i=baseName.findRev('/'))!=-1 || (i=baseName.findRev('\\'))!=-1)
{
baseName=baseName.right(baseName.length()-i-1);
}
if (!inlineImage) m_t << "\n";
QCString sizeAttribs;
if (!img.width().isEmpty())
{
sizeAttribs+=" width=\""+img.width()+"\"";
}
if (!img.height().isEmpty()) // link to local file
{
sizeAttribs+=" height=\""+img.height()+"\"";
}
// 16 cases: url.isEmpty() | typeSVG | inlineImage | img.hasCaption()
HtmlAttribList extraAttribs;
if (typeSVG)
{
HtmlAttrib opt;
opt.name = "style";
opt.value = "pointer-events: none;";
extraAttribs.push_back(opt);
}
QCString alt;
mergeHtmlAttributes(img.attribs(),extraAttribs);
QCString attrs = htmlAttribsToString(extraAttribs,&alt);
QCString src;
if (url.isEmpty())
{
src = img.relPath()+img.name();
}
else
{
src = correctURL(url,img.relPath());
}
if (typeSVG && !inlineImage)
{
m_t << "
" << alt << " \n";
}
}
else
{
m_t << "
\n";
}
}
if (img.hasCaption())
{
if (inlineImage)
{
m_t << " title=\"";
m_insideTitle=true;
}
else
{
m_t << "
\n";
}
}
else if (inlineImage)
{
m_t << "/>";
}
visitChildren(img);
if (img.hasCaption())
{
if (inlineImage)
{
m_t << "\"/>";
m_insideTitle=false;
}
else // end
{
m_t << "
";
}
}
if (!inlineImage) // end
{
m_t << "
\n";
forceStartParagraph(img);
}
}
else // other format -> skip
{
}
}
void HtmlDocVisitor::operator()(const DocDotFile &df)
{
if (m_hide) return;
if (!Config_getBool(DOT_CLEANUP)) copyFile(df.file(),Config_getString(HTML_OUTPUT)+"/"+stripPath(df.file()));
m_t << "
\n";
writeDotFile(df.file(),df.relPath(),df.context(),df.srcFile(),df.srcLine());
if (df.hasCaption())
{
m_t << "
\n";
}
visitChildren(df);
if (df.hasCaption())
{
m_t << "
\n";
}
m_t << "
\n";
}
void HtmlDocVisitor::operator()(const DocMscFile &df)
{
if (m_hide) return;
if (!Config_getBool(DOT_CLEANUP)) copyFile(df.file(),Config_getString(HTML_OUTPUT)+"/"+stripPath(df.file()));
m_t << "
\n";
writeMscFile(df.file(),df.relPath(),df.context(),df.srcFile(),df.srcLine());
if (df.hasCaption())
{
m_t << "
\n";
}
visitChildren(df);
if (df.hasCaption())
{
m_t << "
\n";
}
m_t << "
\n";
}
void HtmlDocVisitor::operator()(const DocDiaFile &df)
{
if (m_hide) return;
if (!Config_getBool(DOT_CLEANUP)) copyFile(df.file(),Config_getString(HTML_OUTPUT)+"/"+stripPath(df.file()));
m_t << "
\n";
writeDiaFile(df.file(),df.relPath(),df.context(),df.srcFile(),df.srcLine());
if (df.hasCaption())
{
m_t << "
\n";
}
visitChildren(df);
if (df.hasCaption())
{
m_t << "
\n";
}
m_t << "
\n";
}
void HtmlDocVisitor::operator()(const DocLink &lnk)
{
if (m_hide) return;
startLink(lnk.ref(),lnk.file(),lnk.relPath(),lnk.anchor());
visitChildren(lnk);
endLink();
}
void HtmlDocVisitor::operator()(const DocRef &ref)
{
if (m_hide) return;
if (!ref.file().isEmpty())
{
// when ref.isSubPage()==TRUE we use ref.file() for HTML and
// ref.anchor() for LaTeX/RTF
startLink(ref.ref(),ref.file(),ref.relPath(),ref.isSubPage() ? QCString() : ref.anchor());
}
if (!ref.hasLinkText()) filter(ref.targetTitle());
visitChildren(ref);
if (!ref.file().isEmpty()) endLink();
//m_t << " ";
}
void HtmlDocVisitor::operator()(const DocSecRefItem &ref)
{
if (m_hide) return;
if (!ref.file().isEmpty())
{
m_t << "
";
startLink(ref.ref(),ref.file(),ref.relPath(),ref.isSubPage() ? QCString() : ref.anchor());
}
visitChildren(ref);
if (!ref.file().isEmpty())
{
endLink();
m_t << " \n";
}
}
void HtmlDocVisitor::operator()(const DocSecRefList &s)
{
if (m_hide) return;
forceEndParagraph(s);
m_t << "
\n";
m_t << "
\n";
visitChildren(s);
m_t << " \n";
m_t << "
\n";
forceStartParagraph(s);
}
void HtmlDocVisitor::operator()(const DocParamSect &s)
{
if (m_hide) return;
forceEndParagraph(s);
QCString className;
QCString heading;
switch(s.type())
{
case DocParamSect::Param:
heading=theTranslator->trParameters();
className="params";
break;
case DocParamSect::RetVal:
heading=theTranslator->trReturnValues();
className="retval";
break;
case DocParamSect::Exception:
heading=theTranslator->trExceptions();
className="exception";
break;
case DocParamSect::TemplateParam:
heading=theTranslator->trTemplateParameters();
className="tparams";
break;
default:
ASSERT(0);
}
m_t << "
";
m_t << heading;
m_t << " \n";
m_t << " \n";
visitChildren(s);
m_t << "
\n";
m_t << " \n";
m_t << "\n";
forceStartParagraph(s);
}
void HtmlDocVisitor::operator()(const DocSeparator &s)
{
if (m_hide) return;
m_t << " " << s.chars() << " ";
}
void HtmlDocVisitor::operator()(const DocParamList &pl)
{
//printf("DocParamList::visitPre\n");
if (m_hide) return;
m_t << "
";
const DocParamSect *sect = std::get_if(pl.parent());
if (sect && sect->hasInOutSpecifier())
{
m_t << "";
if (pl.direction()!=DocParamSect::Unspecified)
{
m_t << "[";
if (pl.direction()==DocParamSect::In)
{
m_t << "in";
}
else if (pl.direction()==DocParamSect::Out)
{
m_t << "out";
}
else if (pl.direction()==DocParamSect::InOut)
{
m_t << "in,out";
}
m_t << "]";
}
m_t << " ";
}
if (sect && sect->hasTypeSpecifier())
{
m_t << "";
for (const auto &type : pl.paramTypes())
{
std::visit(*this,type);
}
m_t << " ";
}
m_t << "";
bool first=TRUE;
for (const auto ¶m : pl.parameters())
{
if (!first) m_t << ","; else first=FALSE;
std::visit(*this,param);
}
m_t << " ";
for (const auto &par : pl.paragraphs())
{
std::visit(*this,par);
}
m_t << " \n";
}
void HtmlDocVisitor::operator()(const DocXRefItem &x)
{
if (m_hide) return;
if (x.title().isEmpty()) return;
forceEndParagraph(x);
bool anonymousEnum = x.file()=="@";
if (!anonymousEnum)
{
m_t << "
";
}
else
{
m_t << "";
}
filter(x.title());
m_t << ":";
if (!anonymousEnum) m_t << " ";
m_t << "";
visitChildren(x);
if (x.title().isEmpty()) return;
m_t << " \n";
forceStartParagraph(x);
}
void HtmlDocVisitor::operator()(const DocInternalRef &ref)
{
if (m_hide) return;
startLink(QCString(),ref.file(),ref.relPath(),ref.anchor());
visitChildren(ref);
endLink();
m_t << " ";
}
void HtmlDocVisitor::operator()(const DocText &t)
{
visitChildren(t);
}
void HtmlDocVisitor::operator()(const DocHtmlBlockQuote &b)
{
if (m_hide) return;
forceEndParagraph(b);
m_t << "
\n";
visitChildren(b);
m_t << " \n";
forceStartParagraph(b);
}
void HtmlDocVisitor::operator()(const DocVhdlFlow &vf)
{
if (m_hide) return;
if (VhdlDocGen::getFlowMember()) // use VHDL flow chart creator
{
forceEndParagraph(vf);
QCString fname=FlowChart::convertNameToFileName();
m_t << "
";
m_t << "flowchart: " ; // TODO: translate me
m_t << "";
m_t << VhdlDocGen::getFlowMember()->name();
m_t << " ";
if (vf.hasCaption())
{
m_t << " ";
}
}
visitChildren(vf);
if (VhdlDocGen::getFlowMember()) // use VHDL flow chart creator
{
m_t << "
";
forceStartParagraph(vf);
}
}
void HtmlDocVisitor::operator()(const DocParBlock &pb)
{
if (m_hide) return;
visitChildren(pb);
}
void HtmlDocVisitor::filter(const QCString &str, const bool retainNewline)
{
if (str.isEmpty()) return;
const char *p=str.data();
char c;
while (*p)
{
c=*p++;
switch(c)
{
case '\n': if(retainNewline) m_t << "
"; m_t << c; break;
case '<': m_t << "<"; break;
case '>': m_t << ">"; break;
case '&': m_t << "&"; break;
case '\\': if ((*p == '(') || (*p == ')'))
m_t << "\\" << *p++;
else
m_t << c;
break;
default:
{
uchar uc = static_cast
(c);
if (uc<32 && !isspace(c)) // non-printable control characters
{
m_t << "$" << hex[uc>>4] << hex[uc&0xF] << ";";
}
else
{
m_t << c;
}
}
break;
}
}
}
/// Escape basic entities to produce a valid CDATA attribute value,
/// assume that the outer quoting will be using the double quote "
QCString HtmlDocVisitor::filterQuotedCdataAttr(const QCString &str)
{
GrowBuf growBuf;
if (str.isEmpty()) return str;
const char *p=str.data();
char c;
while (*p)
{
c=*p++;
switch(c)
{
case '&': growBuf.addStr("&"); break;
case '"': growBuf.addStr("""); break;
case '<': growBuf.addStr("<"); break;
case '>': growBuf.addStr(">"); break;
case '\\':
if ((*p == '(') || (*p == ')'))
{
growBuf.addStr("\\");
growBuf.addChar(*p++);
}
else
{
growBuf.addChar(c);
}
break;
default:
{
uchar uc = static_cast(c);
if (uc<32 && !isspace(c)) // non-printable control characters
{
growBuf.addStr("$");
growBuf.addChar(hex[uc>>4]);
growBuf.addChar(hex[uc&0xF]);
growBuf.addStr(";");
}
else
{
growBuf.addChar(c);
}
}
break;
}
}
growBuf.addChar(0);
return growBuf.get();
}
void HtmlDocVisitor::startLink(const QCString &ref,const QCString &file,
const QCString &relPath,const QCString &anchor,
const QCString &tooltip)
{
//printf("HtmlDocVisitor: file=%s anchor=%s\n",qPrint(file),qPrint(anchor));
if (!ref.isEmpty()) // link to entity imported via tag file
{
m_t << "";
}
void HtmlDocVisitor::endLink()
{
m_t << " ";
}
void HtmlDocVisitor::writeDotFile(const QCString &fn,const QCString &relPath,
const QCString &context,const QCString &srcFile,int srcLine)
{
QCString baseName=fn;
int i;
if ((i=baseName.findRev('/'))!=-1)
{
baseName=baseName.right(baseName.length()-i-1);
}
if ((i=baseName.find('.'))!=-1) // strip extension
{
baseName=baseName.left(i);
}
baseName.prepend("dot_");
QCString outDir = Config_getString(HTML_OUTPUT);
writeDotGraphFromFile(fn,outDir,baseName,GOF_BITMAP,srcFile,srcLine);
writeDotImageMapFromFile(m_t,fn,outDir,relPath,baseName,context,-1,srcFile,srcLine);
}
void HtmlDocVisitor::writeMscFile(const QCString &fileName,const QCString &relPath,
const QCString &context,const QCString &srcFile,int srcLine)
{
QCString baseName=fileName;
int i;
if ((i=baseName.findRev('/'))!=-1) // strip path
{
baseName=baseName.right(baseName.length()-i-1);
}
if ((i=baseName.find('.'))!=-1) // strip extension
{
baseName=baseName.left(i);
}
baseName.prepend("msc_");
QCString outDir = Config_getString(HTML_OUTPUT);
QCString imgExt = getDotImageExtension();
MscOutputFormat mscFormat = MSC_BITMAP;
if ("svg" == imgExt)
mscFormat = MSC_SVG;
writeMscGraphFromFile(fileName,outDir,baseName,mscFormat,srcFile,srcLine);
writeMscImageMapFromFile(m_t,fileName,outDir,relPath,baseName,context,mscFormat,srcFile,srcLine);
}
void HtmlDocVisitor::writeDiaFile(const QCString &fileName, const QCString &relPath,
const QCString &,const QCString &srcFile,int srcLine)
{
QCString baseName=fileName;
int i;
if ((i=baseName.findRev('/'))!=-1) // strip path
{
baseName=baseName.right(baseName.length()-i-1);
}
if ((i=baseName.find('.'))!=-1) // strip extension
{
baseName=baseName.left(i);
}
baseName.prepend("dia_");
QCString outDir = Config_getString(HTML_OUTPUT);
writeDiaGraphFromFile(fileName,outDir,baseName,DIA_BITMAP,srcFile,srcLine);
m_t << " \n";
}
void HtmlDocVisitor::writePlantUMLFile(const QCString &fileName, const QCString &relPath,
const QCString &,const QCString &srcFile,int srcLine)
{
QCString baseName=fileName;
int i;
if ((i=baseName.findRev('/'))!=-1) // strip path
{
baseName=baseName.right(baseName.length()-i-1);
}
if ((i=baseName.findRev('.'))!=-1) // strip extension
{
baseName=baseName.left(i);
}
QCString outDir = Config_getString(HTML_OUTPUT);
QCString imgExt = getDotImageExtension();
if (imgExt=="svg")
{
PlantumlManager::instance().generatePlantUMLOutput(fileName,outDir,PlantumlManager::PUML_SVG);
//m_t << "\n";
//m_t << "This browser is not able to show SVG: try Firefox, Chrome, Safari, or Opera instead.
";
//m_t << "\n";
m_t << " \n";
}
else
{
PlantumlManager::instance().generatePlantUMLOutput(fileName,outDir,PlantumlManager::PUML_BITMAP);
m_t << " \n";
}
}
/** Returns TRUE if the child nodes in paragraph \a para until \a nodeIndex
contain a style change node that is still active and that style change is one that
must be located outside of a paragraph, i.e. it is a center, div, or pre tag.
See also bug746162.
*/
static bool insideStyleChangeThatIsOutsideParagraph(const DocPara *para,
DocNodeList::const_iterator it)
{
//printf("insideStyleChangeThatIsOutputParagraph(index=%d)\n",nodeIndex);
int styleMask=0;
bool styleOutsideParagraph=FALSE;
while (!styleOutsideParagraph)
{
const DocNodeVariant *n = &(*it);
const DocStyleChange *sc = std::get_if(n);
if (sc)
{
if (!sc->enable()) // remember styles that has been closed already
{
styleMask|=static_cast(sc->style());
}
bool paraStyle = sc->style()==DocStyleChange::Center ||
sc->style()==DocStyleChange::Div ||
sc->style()==DocStyleChange::Preformatted;
//printf("Found style change %s enabled=%d\n",sc->styleString(),sc->enable());
if (sc->enable() && (styleMask&static_cast(sc->style()))==0 && // style change that is still active
paraStyle
)
{
styleOutsideParagraph=TRUE;
}
}
if (it!=std::begin(para->children()))
{
--it;
}
else
{
break;
}
}
return styleOutsideParagraph;
}
/** Used for items found inside a paragraph, which due to XHTML restrictions
* have to be outside of the paragraph. This method will forcefully end
* the current paragraph and forceStartParagraph() will restart it.
*/
template
void HtmlDocVisitor::forceEndParagraph(const Node &n)
{
const DocPara *para=std::get_if(n.parent());
if (para)
{
const DocNodeList &children = para->children();
//printf("forceEndParagraph\n");
//dumpDocNodeList(children);
auto it = std::find_if(std::begin(children),std::end(children),
[&n](const auto &np) { return holds_value(&n,np); });
if (it==std::end(children)) return;
if (it==std::begin(children)) return; // first node in paragraph
it = std::prev(it);
bool found=false;
while (!found)
{
found = !isInvisibleNode(*it);
if (found) break;
if (it!=std::begin(children))
{
--it;
}
else
{
break;
}
}
if (!found) return; // first visible node in paragraph
const DocNodeVariant &v = *it;
if (mustBeOutsideParagraph(v)) return; // previous node already outside paragraph context
bool styleOutsideParagraph=false;
if (it!=std::begin(children))
{
it = std::prev(it);
styleOutsideParagraph=insideStyleChangeThatIsOutsideParagraph(para,it);
}
bool isFirst;
bool isLast;
getParagraphContext(*para,isFirst,isLast);
//printf("forceEnd first=%d last=%d styleOutsideParagraph=%d\n",isFirst,isLast,styleOutsideParagraph);
if (isFirst && isLast) return;
if (styleOutsideParagraph) return;
//printf("adding \n");
m_t << "";
}
}
/** Used for items found inside a paragraph, which due to XHTML restrictions
* have to be outside of the paragraph. This method will forcefully start
* the paragraph, that was previously ended by forceEndParagraph().
*/
template
void HtmlDocVisitor::forceStartParagraph(const Node &n)
{
//printf("> forceStartParagraph(%s)\n",docNodeName(n));
const DocPara *para=0;
if (n.parent() && (para = std::get_if(n.parent()))) // if we are inside a paragraph
{
const DocNodeList &children = para->children();
auto it = std::find_if(std::begin(children),
std::end(children),
[&n](const auto &np)
{ return holds_value(&n,np); });
if (it==std::end(children)) return;
bool styleOutsideParagraph=insideStyleChangeThatIsOutsideParagraph(para,it);
//printf("it=%s (%p) styleOutsideParagraph=%d\n",
// docNodeName(*it), (void *)&*it, styleOutsideParagraph);
if (styleOutsideParagraph) return;
it = std::next(it);
while (it!=std::end(children) && isInvisibleNode(*it))
{
++it;
}
if (it!=std::end(children))
{
const DocNodeVariant &v = *it;
if (mustBeOutsideParagraph(v)) return; // next element also outside paragraph
}
else
{
return; // only whitespace at the end!
}
bool needsTag = TRUE;
bool isFirst;
bool isLast;
getParagraphContext(*para,isFirst,isLast);
if (isFirst && isLast) needsTag = FALSE;
//printf("forceStart first=%d last=%d needsTag=%d\n",isFirst,isLast,needsTag);
if (needsTag) m_t << "";
}
}