/****************************************************************************
**
** 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 "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);
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");
const QSet" << 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 (Generator::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, nullptr, 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() << "
"; 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);
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)
{
#ifndef QT_NO_TEXTCODEC
out() << QString("\n").arg(outputEncoding);
#else
out() << QString("\n");
#endif
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->isFunction()) { const FunctionNode *fn = 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() << "\n"); QString qmlItemEnd(" |
"; out() << "" << scn->name() << " group"; out() << "
Note: ";
NodeList &nodes = fn->associatedProperties();
std::sort(nodes.begin(), nodes.end(), Node::nodeNameLessThan);
for (const auto *node : qAsConst(nodes)) {
QString msg;
const PropertyNode *pn = static_cast