/****************************************************************************
**
** 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. ";
if (relative->isProperty() || relative->isVariable()) {
atom = atom->next();
if (atom != nullptr && atom->type() == Atom::String) {
QString firstWord = atom->string().toLower().section(' ', 0, 0, QString::SectionSkipEmpty);
if (firstWord == QLatin1String("the")
|| firstWord == QLatin1String("a")
|| firstWord == QLatin1String("an")
|| firstWord == QLatin1String("whether")
|| firstWord == QLatin1String("which")) {
QString str = "This ";
if (relative->isProperty())
str += "property holds ";
else
str += "variable holds ";
str += atom->string().left(1).toLower() + atom->string().mid(1);
const_cast ";
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");
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 = pmap.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);
if (relative->isExample()) {
const ExampleNode *cen = static_cast
"; 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() << "
"; } if (matchAhead(atom, Atom::ParaLeft)) skipAhead = 1; } break; case Atom::TableItemRight: if (inTableHeader_) 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"; NodeList::ConstIterator m = cn->members().constBegin(); while (m != cn->members().constEnd()) { generateDetailedMember(*m, cn, marker); ++m; } // generateAnnotatedList(cn, marker, cn->members()); generateFooter(cn); } /*! Returns "html" for this subclass of Generator. */ QString HtmlGenerator::fileExtension() const { return "html"; } /*! Output navigation list in the html file. */ void HtmlGenerator::generateNavigationBar(const QString &title, const Node *node, CodeMarker *marker, const QString &buildversion, bool tableItems) { if (noNavigationBar) return; Text navigationbar; // Set list item types based on the navigation bar type Atom::AtomType itemLeft = tableItems ? Atom::TableItemLeft : Atom::ListItemLeft; Atom::AtomType itemRight = tableItems ? Atom::TableItemRight : Atom::ListItemRight; if (hometitle == title) return; if (!homepage.isEmpty()) navigationbar << Atom(itemLeft) << Atom(Atom::NavLink, homepage) << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) << Atom(Atom::String, hometitle) << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK) << Atom(itemRight); if (!landingpage.isEmpty() && landingtitle != title) navigationbar << Atom(itemLeft) << Atom(Atom::NavLink, landingpage) << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) << Atom(Atom::String, landingtitle) << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK) << Atom(itemRight); if (node->isClassNode()) { if (!cppclassespage.isEmpty() && !cppclassestitle.isEmpty()) navigationbar << Atom(itemLeft) << Atom(Atom::NavLink, cppclassespage) << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) << Atom(Atom::String, cppclassestitle) << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK) << Atom(itemRight); if (!node->name().isEmpty()) navigationbar << Atom(itemLeft) << Atom(Atom::String, node->name()) << Atom(itemRight); } else if (node->isQmlType() || node->isQmlBasicType() || node->isJsType() || node->isJsBasicType()) { if (!qmltypespage.isEmpty() && !qmltypestitle.isEmpty()) navigationbar << Atom(itemLeft) << Atom(Atom::NavLink, qmltypespage) << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) << Atom(Atom::String, qmltypestitle) << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK) << Atom(itemRight) << Atom(itemLeft) << Atom(Atom::String, title) << Atom(itemRight); } else { if (node->isAggregate()) { QStringList groups = static_cast";
} 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 ||||||||||||||||
" << *i << ":" " | "; if (*i == headerText) out() << requisites.value(*i).toString(); else generateText(requisites.value(*i), aggregate, marker); out() << " |
" << *i << " | ";
if (*i == importText)
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; iThe 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 (int i = 0; i < summary_spv.size(); ++i) { 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 (int i = 0; i < summary_spv.size(); ++i) { QString ref = registerRef(summary_spv.at(i)->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); out() << QString("%2 ").arg(ch).arg(ch.toUpper()); } out() << "
\n"; char nextLetter = 'a'; char currentLetter; out() << "");
marked.replace("@extra>", "
");
if (summary) {
marked.remove("<@type>");
marked.remove("@type>");
}
out() << highlightedCode(marked, relative, false, Node::QML);
}
/*!
This function generates a simple bullet list for the members
of collection node \a {cn}. The collection node must be a group
and must not be empty. If it is empty, nothing is output, and
false is returned. Otherewise, the list is generated and true is returned.
*/
bool HtmlGenerator::generateGroupList(CollectionNode *cn)
{
qdb_->mergeCollections(cn);
if (cn->members().isEmpty())
return false;
out() << "";
out() << "
";
}
else {
if (twoColumn && i == (int) (nv.count() + 1) / 2)
out() << " |
|
";
out() << "
";
}
else {
if (twoColumn && i == (int) (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"; //out() << "This signal is emitted when the property value is changed.
\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); } int HtmlGenerator::hOffset(const Node *node) { switch (node->nodeType()) { case Node::Namespace: case Node::Class: case Node::Struct: case Node::Union: case Node::Module: return 2; case Node::QmlModule: case Node::QmlBasicType: case Node::QmlType: case Node::Page: return 1; case Node::Enum: case Node::Typedef: case Node::Function: case Node::Property: default: return 3; } } bool HtmlGenerator::isThreeColumnEnumValueTable(const Atom *atom) { while (atom != nullptr && !(atom->type() == Atom::ListRight && atom->string() == ATOM_LIST_VALUE)) { if (atom->type() == Atom::ListItemLeft && !matchAhead(atom, Atom::ListItemRight)) return true; atom = atom->next(); } return false; } const QPairNote: ";
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