/****************************************************************************
**
** Copyright (C) 2019 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the tools applications of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
/*
htmlgenerator.cpp
*/
#include "htmlgenerator.h"
#include "config.h"
#include "codemarker.h"
#include "codeparser.h"
#include "helpprojectwriter.h"
#include "node.h"
#include "qdocdatabase.h"
#include "separator.h"
#include "tree.h"
#include "quoter.h"
#include " << protectEnc(t) << " " << protectEnc(depends) << " This is a list of links from " << defaultModuleName() << " to " << module << ". ";
out() << "Click on a link to go to the location of the link. The link is marked ";
out() << "with red asterisks. ";
out() << "Click on the marked link to see if it goes to the right place. This is a list of broken links in " << defaultModuleName() << ". ";
out() << "Click on a link to go to the broken link. ";
out() << "The link's target could not be found. ";
rewritePropertyBrief(atom, relative);
break;
case Atom::BriefRight:
if (hasBrief(relative))
out() << " ";
in_para = true;
break;
case Atom::CaptionRight:
endLink();
if (in_para) {
out() << " you can rewrite it as For example, if you have code like",
"
" }, // tag is not supported in HTML5
{ ATOM_FORMATTING_UICONTROL, "", "" },
{ ATOM_FORMATTING_UNDERLINE, "", "" },
{ nullptr, nullptr, nullptr } };
Generator::initializeGenerator();
config = &Config::instance();
obsoleteLinks = config->getBool(CONFIG_OBSOLETELINKS);
setImageFileExtensions(QStringList() << "png"
<< "jpg"
<< "jpeg"
<< "gif");
/*
The formatting maps are owned by Generator. They are cleared in
Generator::terminate().
*/
int i = 0;
while (defaults[i].key) {
formattingLeftMap().insert(QLatin1String(defaults[i].key), QLatin1String(defaults[i].left));
formattingRightMap().insert(QLatin1String(defaults[i].key),
QLatin1String(defaults[i].right));
i++;
}
style = config->getString(HtmlGenerator::format() + Config::dot + CONFIG_STYLE);
endHeader = config->getString(HtmlGenerator::format() + Config::dot + CONFIG_ENDHEADER);
postHeader =
config->getString(HtmlGenerator::format() + Config::dot + HTMLGENERATOR_POSTHEADER);
postPostHeader =
config->getString(HtmlGenerator::format() + Config::dot + HTMLGENERATOR_POSTPOSTHEADER);
prologue = config->getString(HtmlGenerator::format() + Config::dot + HTMLGENERATOR_PROLOGUE);
footer = config->getString(HtmlGenerator::format() + Config::dot + HTMLGENERATOR_FOOTER);
address = config->getString(HtmlGenerator::format() + Config::dot + HTMLGENERATOR_ADDRESS);
pleaseGenerateMacRef =
config->getBool(HtmlGenerator::format() + Config::dot + HTMLGENERATOR_GENERATEMACREFS);
noNavigationBar =
config->getBool(HtmlGenerator::format() + Config::dot + HTMLGENERATOR_NONAVIGATIONBAR);
navigationSeparator = config->getString(HtmlGenerator::format() + Config::dot
+ HTMLGENERATOR_NAVIGATIONSEPARATOR);
tocDepth = config->getInt(HtmlGenerator::format() + Config::dot + HTMLGENERATOR_TOCDEPTH);
project = config->getString(CONFIG_PROJECT);
projectDescription = config->getString(CONFIG_DESCRIPTION);
if (projectDescription.isEmpty() && !project.isEmpty())
projectDescription = project + QLatin1String(" Reference Documentation");
projectUrl = config->getString(CONFIG_URL);
tagFile_ = config->getString(CONFIG_TAGFILE);
#ifndef QT_NO_TEXTCODEC
outputEncoding = config->getString(CONFIG_OUTPUTENCODING);
if (outputEncoding.isEmpty())
outputEncoding = QLatin1String("UTF-8");
outputCodec = QTextCodec::codecForName(outputEncoding.toLocal8Bit());
#endif
naturalLanguage = config->getString(CONFIG_NATURALLANGUAGE);
if (naturalLanguage.isEmpty())
naturalLanguage = QLatin1String("en");
codeIndent = config->getInt(CONFIG_CODEINDENT); // QTBUG-27798
codePrefix = config->getString(CONFIG_CODEPREFIX);
codeSuffix = config->getString(CONFIG_CODESUFFIX);
/*
The help file write should be allocated once and only once
per qdoc execution.
*/
if (helpProjectWriter)
helpProjectWriter->reset(project.toLower() + ".qhp", this);
else
helpProjectWriter = new HelpProjectWriter(project.toLower() + ".qhp", this);
// Documentation template handling
headerScripts = config->getString(HtmlGenerator::format() + Config::dot + CONFIG_HEADERSCRIPTS);
headerStyles = config->getString(HtmlGenerator::format() + Config::dot + CONFIG_HEADERSTYLES);
QString prefix = CONFIG_QHP + Config::dot + project + Config::dot;
manifestDir =
QLatin1String("qthelp://") + config->getString(prefix + QLatin1String("namespace"));
manifestDir += QLatin1Char('/') + config->getString(prefix + QLatin1String("virtualFolder"))
+ QLatin1Char('/');
readManifestMetaContent();
examplesPath = config->getString(CONFIG_EXAMPLESINSTALLPATH);
if (!examplesPath.isEmpty())
examplesPath += QLatin1Char('/');
// Retrieve the config for the navigation bar
homepage = config->getString(CONFIG_NAVIGATION + Config::dot + CONFIG_HOMEPAGE);
hometitle = config->getString(CONFIG_NAVIGATION + Config::dot + CONFIG_HOMETITLE, homepage);
landingpage = config->getString(CONFIG_NAVIGATION + Config::dot + CONFIG_LANDINGPAGE);
landingtitle =
config->getString(CONFIG_NAVIGATION + Config::dot + CONFIG_LANDINGTITLE, landingpage);
cppclassespage = config->getString(CONFIG_NAVIGATION + Config::dot + CONFIG_CPPCLASSESPAGE);
cppclassestitle = config->getString(CONFIG_NAVIGATION + Config::dot + CONFIG_CPPCLASSESTITLE,
QLatin1String("C++ Classes"));
qmltypespage = config->getString(CONFIG_NAVIGATION + Config::dot + CONFIG_QMLTYPESPAGE);
qmltypestitle = config->getString(CONFIG_NAVIGATION + Config::dot + CONFIG_QMLTYPESTITLE,
QLatin1String("QML Types"));
buildversion = config->getString(CONFIG_BUILDVERSION);
}
/*!
Gracefully terminates the HTML output generator.
*/
void HtmlGenerator::terminateGenerator()
{
Generator::terminateGenerator();
}
QString HtmlGenerator::format()
{
return "HTML";
}
/*!
Generate targets for any \keyword commands that were seen
in the qdoc comment for the \a node.
*/
void HtmlGenerator::generateKeywordAnchors(const Node *node)
{
Q_UNUSED(node);
// Disabled: keywords always link to the top of the QDoc
// comment they appear in, and do not use a dedicated anchor.
}
/*!
If qdoc is in the \c {-prepare} phase, traverse the primary
tree to generate the index file for the current module.
If qdoc is in the \c {-generate} phase, traverse the primary
tree to generate all the HTML documentation for the current
module. Then generate the help file and the tag file.
*/
void HtmlGenerator::generateDocs()
{
Node *qflags = qdb_->findClassNode(QStringList("QFlags"));
if (qflags)
qflagsHref_ = linkForNode(qflags, nullptr);
if (!config->preparing())
Generator::generateDocs();
if (config->generating() && config->getBool(CONFIG_WRITEQAPAGES))
generateQAPage();
if (!config->generating()) {
QString fileBase =
project.toLower().simplified().replace(QLatin1Char(' '), QLatin1Char('-'));
qdb_->generateIndex(outputDir() + QLatin1Char('/') + fileBase + ".index", projectUrl,
projectDescription, this);
}
if (!config->preparing()) {
helpProjectWriter->generate();
generateManifestFiles();
/*
Generate the XML tag file, if it was requested.
*/
qdb_->generateTagFile(tagFile_, this);
}
}
/*!
Output the module's Quality Assurance page.
*/
void HtmlGenerator::generateQAPage()
{
NamespaceNode *node = qdb_->primaryTreeRoot();
beginSubPage(node, "aaa-" + defaultModuleName().toLower() + "-qa-page.html");
CodeMarker *marker = CodeMarker::markerForFileName(node->location().filePath());
QString title = "Quality Assurance Page for " + defaultModuleName();
QString t = "Quality assurance information for checking the " + defaultModuleName()
+ " documentation.";
generateHeader(title, node, marker);
generateTitle(title, Text() << t, LargeSubTitle, node, marker);
QStringList strings;
QVector" << protectEnc(t) << "
\n";
out() << "
\n";
t = "The Optimal \"depends\" Variable";
out() << " \n";
QString fileName;
for (int i = 0; i < strings.size(); ++i) {
fileName = generateLinksToLinksPage(strings.at(i), marker);
out() << "Destination Module "
<< "Link Count \n";
}
int count = 0;
fileName = generateLinksToBrokenLinksPage(marker, count);
if (count != 0) {
out() << ""
<< "" << strings.at(i) << ""
<< " " << counts.at(i) << " \n";
}
out() << ""
<< ""
<< "Broken Links"
<< ""
<< " " << count << " " << protectEnc(t) << "
\n";
t = "Consider replacing the depends variable in " + defaultModuleName().toLower()
+ ".qdocconf with this one, if the two are not identical:";
out() << "
\n";
}
generateFooter();
endSubPage();
return fileName;
}
/*!
This function writes an html file containing a list of
links to broken links that originate in the current
module and go nowwhere. It returns the name of the file
it generates, and it sets \a count to the number of
broken links that were found. The \a marker is used for
the same thing the marker is always used for.
*/
QString HtmlGenerator::generateLinksToBrokenLinksPage(CodeMarker *marker, int &count)
{
QString fileName;
NamespaceNode *node = qdb_->primaryTreeRoot();
const TargetList *tlist = qdb_->getTargetList("broken");
if (tlist && !tlist->isEmpty()) {
count = tlist->size();
fileName = "aaa-links-to-broken-links.html";
beginSubPage(node, fileName);
QString title = "Broken links in " + defaultModuleName();
generateHeader(title, node, marker);
generateTitle(title, Text(), SmallSubTitle, node, marker);
out() << " \n";
for (const TargetLoc *t : *tlist) {
// e.g.: Layout Management
out() << "Link to link... In file... Somewhere after line number... \n";
}
out() << "";
out() << "fileName_ << "#" << t->target_ << "\">";
out() << t->text_ << " ";
out() << "";
QString f = t->loc_->doc().location().filePath();
out() << f << " ";
out() << "";
out() << t->loc_->doc().location().lineNo() << "
\n";
generateFooter();
endSubPage();
}
return fileName;
}
/*!
Generate html from an instance of Atom.
*/
int HtmlGenerator::generateAtom(const Atom *atom, const Node *relative, CodeMarker *marker)
{
int idx, skipAhead = 0;
static bool in_para = false;
switch (atom->type()) {
case Atom::AutoLink: {
QString name = atom->string();
if (relative && relative->name() == name.replace(QLatin1String("()"), QLatin1String())) {
out() << protectEnc(atom->string());
break;
}
}
Q_FALLTHROUGH();
case Atom::NavAutoLink:
if (!inLink_ && !inContents_ && !inSectionHeading_) {
const Node *node = nullptr;
QString link = getAutoLink(atom, relative, &node);
if (link.isEmpty()) {
if (autolinkErrors())
relative->doc().location().warning(
tr("Can't autolink to '%1'").arg(atom->string()));
} else if (node && node->isObsolete()) {
if ((relative->parent() != node) && !relative->isObsolete())
link.clear();
}
if (link.isEmpty()) {
out() << protectEnc(atom->string());
} else {
if (config->getBool(CONFIG_WRITEQAPAGES)
&& node && (atom->type() != Atom::NavAutoLink)) {
QString text = atom->string();
QString target = qdb_->getNewLinkTarget(relative, node, outFileName(), text);
out() << "";
}
beginLink(link, node, relative);
generateLink(atom, marker);
endLink();
}
} else {
out() << protectEnc(atom->string());
}
break;
case Atom::BaseName:
break;
case Atom::BriefLeft:
if (!hasBrief(relative)) {
skipAhead = skipAtoms(atom, Atom::BriefRight);
break;
}
out() << " \n";
for (const TargetLoc *t : *tlist) {
// e.g.: Layout Management
out() << "Link to broken link... In "
"file... Somewhere after line number... \n";
}
out() << "";
out() << "fileName_ << "#" << t->target_ << "\">";
out() << t->text_ << " ";
out() << "";
QString f = t->loc_->doc().location().filePath();
out() << f << " ";
out() << "";
out() << t->loc_->doc().location().lineNo() << " "
<< trimmedTrailing(highlightedCode(indent(codeIndent, atom->string()), relative,
false, Node::QML),
codePrefix, codeSuffix)
<< "
\n";
break;
case Atom::JavaScript:
out() << ""
<< trimmedTrailing(highlightedCode(indent(codeIndent, atom->string()), relative,
false, Node::JS),
codePrefix, codeSuffix)
<< "
\n";
break;
case Atom::CodeNew:
out() << ""
<< trimmedTrailing(highlightedCode(indent(codeIndent, atom->string()), relative),
codePrefix, codeSuffix)
<< "
\n";
break;
case Atom::CodeOld:
out() << ""
<< trimmedTrailing(protectEnc(plainCode(indent(codeIndent, atom->string()))),
codePrefix, codeSuffix)
<< "
\n";
break;
case Atom::DivLeft:
out() << "
Class "; out() << ""; QStringList pieces = map.key()->fullName().split("::"); out() << protectEnc(pieces.last()); out() << "" << ":
\n"; generateSection(nv, relative, marker); out() << ""; if (fileName.isEmpty()) { relative->location().warning(tr("Missing image: %1").arg(protectEnc(atom->string()))); out() << "[Missing image " << protectEnc(atom->string()) << "]"; } else { QString prefix; out() << ""; helpProjectWriter->addExtraFile(fileName); setImageFileName(relative, fileName); } if (atom->type() == Atom::Image) out() << "
"; } break; case Atom::ImageText: break; case Atom::ImportantLeft: out() << ""; out() << formattingLeftMap()[ATOM_FORMATTING_BOLD]; out() << "Important: "; out() << formattingRightMap()[ATOM_FORMATTING_BOLD]; break; case Atom::ImportantRight: out() << "
"; break; case Atom::NoteLeft: out() << ""; out() << formattingLeftMap()[ATOM_FORMATTING_BOLD]; out() << "Note: "; out() << formattingRightMap()[ATOM_FORMATTING_BOLD]; break; case Atom::NoteRight: out() << "
\n"; break; case Atom::LegaleseLeft: out() << "Constant | "; // If not in \enum topic, skip the value column if (relative->isEnumType()) out() << "Value | "; out() << "Description |
---|---|---|
Constant | Value | |
" << t << " ";
if (relative->isEnumType()) {
out() << " | ";
const EnumNode *enume = static_cast" << protectEnc(itemValue) << " ";
}
}
break;
case Atom::SinceTagRight:
case Atom::ListTagRight:
if (atom->string() == ATOM_LIST_TAG)
out() << "\n";
break;
case Atom::ListItemLeft:
if (atom->string() == ATOM_LIST_TAG) {
out() << " | ";
if (matchAhead(atom, Atom::ListItemRight))
out() << " ";
}
} else {
out() << " |
"; in_para = true; break; case Atom::ParaRight: endLink(); if (in_para) { out() << "
\n"; in_para = false; } // if (!matchAhead(atom, Atom::ListItemRight) && !matchAhead(atom, Atom::TableItemRight)) // out() << "\n"; break; case Atom::QuotationLeft: out() << ""; break; case Atom::QuotationRight: out() << "\n"; break; case Atom::RawString: out() << atom->string(); break; case Atom::SectionLeft: out() << "" << divNavTop << '\n'; break; case Atom::SectionRight: break; case Atom::SectionHeadingLeft: { int unit = atom->string().toInt() + hOffset(relative); out() << "
\\" << protectEnc(atom->string()) << "
";
break;
case Atom::QmlText:
case Atom::EndQmlText:
// don't do anything with these. They are just tags.
break;
case Atom::CodeQuoteArgument:
case Atom::CodeQuoteCommand:
case Atom::SnippetCommand:
case Atom::SnippetIdentifier:
case Atom::SnippetLocation:
// no HTML output (ignore)
break;
default:
unknownAtom(atom);
}
return skipAhead;
}
/*!
Generate a reference page for the C++ class, namespace, or
header file documented in \a node using the code \a marker
provided.
*/
void HtmlGenerator::generateCppReferencePage(Aggregate *aggregate, CodeMarker *marker)
{
QString title;
QString rawTitle;
QString fullTitle;
NamespaceNode *ns = nullptr;
SectionVector *summarySections = nullptr;
SectionVector *detailsSections = nullptr;
Sections sections(aggregate);
QString word = aggregate->typeWord(true);
QString templateDecl = aggregate->templateDecl();
if (aggregate->isNamespace()) {
rawTitle = aggregate->plainName();
fullTitle = aggregate->plainFullName();
title = rawTitle + " Namespace";
ns = static_cast"; generateText(brief, ns, marker); out() << "
\n"; } else generateBrief(aggregate, marker); if (!aggregate->parent()->isClassNode()) generateRequisites(aggregate, marker); generateStatus(aggregate, marker); generateSince(aggregate, marker); out() << ""; generateText(brief, cn, marker); out() << "
\n"; const QList";
} else {
out() << " | \n";
else
out() << "\n";
}
void HtmlGenerator::generateHeader(const QString &title, const Node *node, CodeMarker *marker)
{
out() << "\n";
out() << QString("\n").arg(naturalLanguage);
out() << "\n";
out() << " \n";
if (node && !node->doc().location().isEmpty())
out() << "\n";
// determine the rest of the |||||||||||||||||
" << *it << ":" " | "; if (*it == headerText) out() << requisites.value(*it).toString(); else generateText(requisites.value(*it), aggregate, marker); out() << " |
" << requisite << " | "; if (requisite == importText) out() << requisites.value(requisite).toString(); else generateText(requisites.value(requisite), qcn, marker); out() << " |
"; generateText(brief, node, marker); if (addLink) { if (!relative || node == relative) out() << " More..."; } out() << "
\n"; generateExtractionMark(node, EndMark); } } /*! Revised for the new doc format. Generates a table of contents beginning at \a node. */ void HtmlGenerator::generateTableOfContents(const Node *node, CodeMarker *marker, QVectorThis is the complete list of members for "; generateFullName(aggregate, nullptr); out() << ", including inherited members.
\n"; generateSectionList(section, aggregate, marker); generateFooter(); endSubPage(); return fileName; } /*! This function creates an html page on which are listed all the members of the QML class used to generte the \a sections, including the inherited members. The \a marker is used for formatting stuff. */ QString HtmlGenerator::generateAllQmlMembersFile(const Sections §ions, CodeMarker *marker) { if (sections.allMembersSection().isEmpty()) return QString(); const Aggregate *aggregate = sections.aggregate(); QString fileName = fileBase(aggregate) + "-members." + fileExtension(); beginSubPage(aggregate, fileName); QString title = "List of All Members for " + aggregate->name(); generateHeader(title, aggregate, marker); generateSidebar(); generateTitle(title, Text(), SmallSubTitle, aggregate, marker); out() << "This is the complete list of members for "; generateFullName(aggregate, nullptr); out() << ", including inherited members.
\n"; ClassKeysNodesList &cknl = sections.allMembersSection().classKeysNodesList(); if (!cknl.isEmpty()) { for (int i = 0; i < cknl.size(); i++) { ClassKeysNodes *ckn = cknl[i]; const QmlTypeNode *qcn = ckn->first; KeysAndNodes &kn = ckn->second; QStringList &keys = kn.first; NodeVector &nodes = kn.second; if (nodes.isEmpty()) continue; if (i != 0) { out() << "The following members are inherited from "; generateFullName(qcn, nullptr); out() << ".
\n"; } out() << "The following members of class " << "" << protectEnc(aggregate->name()) << "" << " are obsolete. " << "They are provided to keep old source code working. " << "We strongly advise against using them in new code.
\n"; for (const auto §ion : summary_spv) { out() << "The following members of QML type " << "" << protectEnc(aggregate->name()) << "" << " are obsolete. " << "They are provided to keep old source code working. " << "We strongly advise against using them in new code.
\n"; for (const auto §ion : summary_spv) { QString ref = registerRef(section->title().toLower()); out() << "" << divNavTop << '\n'; out() << ""; generateFullName(node, relative); out() << " | ";
if (!node->isTextPageNode()) {
Text brief = node->doc().trimmedBriefText(node->name());
if (!brief.isEmpty()) {
out() << ""; generateText(brief, node, marker); out() << " | ";
} else if (!node->reconstitutedBrief().isEmpty()) {
out() << ""; out() << node->reconstitutedBrief(); out() << " | ";
}
} else {
out() << ""; if (!node->reconstitutedBrief().isEmpty()) { out() << node->reconstitutedBrief(); } else out() << protectEnc(node->doc().briefText().toString()); out() << " | ";
}
out() << "
"; for (int i = 0; i < 26; i++) { QChar ch('a' + i); if (usedParagraphNames.contains(char('a' + i))) out() << QString("%2 ").arg(ch).arg(ch.toUpper()); } out() << "
\n"; } /* Output a"; for (int i = 0; i < 26; i++) { QChar ch('a' + i); out() << QString("%2 ").arg(ch).arg(ch.toUpper()); } out() << "
\n"; char nextLetter = 'a'; char currentLetter; out() << "";
out() << "
";
} else {
if (twoColumn && i == (nv.count() + 1) / 2)
out() << " |
|
";
out() << "
";
} else {
if (twoColumn && i == (members.count() + 1) / 2)
out() << " |
[see note below]";
} else if (fn->isInvokable()) {
isInvokable = true;
if (alignNames)
out() << " | [see note below]";
}
}
if (alignNames)
out() << " | |
");
marked.replace("@extra>", "
");
}
if (style != Section::Details) {
marked.remove("<@type>");
marked.remove("@type>");
}
out() << highlightedCode(marked, relative, alignNames);
}
QString HtmlGenerator::highlightedCode(const QString &markedCode, const Node *relative,
bool alignNames, Node::Genus genus)
{
QString src = markedCode;
QString html;
html.reserve(src.size());
QStringRef arg;
QStringRef par1;
const QChar charLangle = '<';
const QChar charAt = '@';
static const QString typeTag("type");
static const QString headerTag("headerfile");
static const QString funcTag("func");
static const QString linkTag("link");
// replace all <@link> tags: "(<@link node=\"([^\"]+)\">).*(@link>)"
// replace all <@func> tags: "(<@func target=\"([^\"]*)\">)(.*)(@func>)"
// replace all "(<@(type|headerfile)(?: +[^>]*)?>)(.*)(@\\2>)" tags
bool done = false;
for (int i = 0, srcSize = src.size(); i < srcSize;) {
if (src.at(i) == charLangle && src.at(i + 1) == charAt) {
if (alignNames && !done) {
html += QLatin1String("Access functions:
\n"; generateSectionList(section, node, marker); } Section notifiers(Section::Accessors, Section::Active); notifiers.appendMembers(property->notifiers().toVector()); if (!notifiers.members().isEmpty()) { out() << "Notifier signal:
\n"; generateSectionList(notifiers, node, marker); } } else if (node->isEnumType()) { const EnumNode *etn = static_castThe " << protectEnc(etn->flagsType()->name()) << " type is a typedef for " << "QFlags<" << protectEnc(etn->name()) << ">. It stores an OR combination of " << protectEnc(etn->name()) << " values.
\n"; } } generateAlsoList(node, marker); generateExtractionMark(node, EndMark); } #ifdef GENERATE_MAC_REFS /* No longer valid. */ void HtmlGenerator::generateMacRef(const Node *node, CodeMarker *marker) { if (!pleaseGenerateMacRef || marker == 0) return; const QStringList macRefs = marker->macRefsForNode(node); for (const auto &macRef : macRefs) out() << "\n"; } #endif /*! This version of the function is called when outputting the link to an example file or example image, where the \a link is known to be correct. */ void HtmlGenerator::beginLink(const QString &link) { link_ = link; if (link_.isEmpty()) { if (showBrokenLinks) out() << ""; } out() << ""; inLink_ = true; } void HtmlGenerator::beginLink(const QString &link, const Node *node, const Node *relative) { link_ = link; if (link_.isEmpty()) { if (showBrokenLinks) out() << ""; } else if (node == nullptr || (relative != nullptr && node->status() == relative->status())) out() << ""; else if (node->isObsolete()) out() << ""; else out() << ""; inLink_ = true; } void HtmlGenerator::endLink() { if (inLink_) { if (link_.isEmpty()) { if (showBrokenLinks) out() << ""; } else { if (inObsoleteLink) { out() << "(obsolete)"; } out() << ""; } } inLink_ = false; inObsoleteLink = false; } /*! Generates the summary list for the \a members. Only used for sections of QML element documentation. */ void HtmlGenerator::generateQmlSummary(const NodeVector &members, const Node *relative, CodeMarker *marker) { if (!members.isEmpty()) { out() << ""; out() << ""; out() << "" << scn->name() << " group"; out() << "