/****************************************************************************
**
** Copyright (C) 2016 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 "codemarker.h"
#include "codeparser.h"
#include "helpprojectwriter.h"
#include "htmlgenerator.h"
#include "node.h"
#include "qdocdatabase.h"
#include "separator.h"
#include "tree.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->type() == Node::Property ||
relative->type() == Node::Variable) {
atom = atom->next();
if (atom != 0 && 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->type() == Node::Property)
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, "", "" },
{ 0, 0, 0 }
};
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();
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";
foreach (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:
case Atom::NavAutoLink:
if (!inLink_ && !inContents_ && !inSectionHeading_) {
const Node *node = 0;
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->status() == Node::Obsolete) {
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";
foreach (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, 0, 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->type() == Node::Enum) out() << "Value | "; out() << "Description |
---|---|---|
Constant | Value | |
" << t << " ";
if (relative->type() == Node::Enum) {
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 or C++ namespace
documented in \a node using the \a marker.
*/
void HtmlGenerator::generateCppReferencePage(Node* node, CodeMarker* marker)
{
Q_ASSERT(node->isAggregate());
Aggregate* aggregate = static_cast"; generateText(brief, ns, marker); out() << "
\n"; } else generateBrief(aggregate, marker); generateRequisites(aggregate, marker); generateStatus(aggregate, marker); out() << "";
} 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";
QString projectVersion = qdb_->version();
//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); } } void HtmlGenerator::generateIncludes(const Aggregate *aggregate, CodeMarker *marker) { if (!aggregate->includes().isEmpty()) { out() << "" << trimmedTrailing(highlightedCode(indent(codeIndent, marker->markedUpIncludes(aggregate->includes())), aggregate), codePrefix, codeSuffix) << ""; } } /*! Revised for the new doc format. Generates a table of contents beginning at \a node. */ void HtmlGenerator::generateTableOfContents(const Node *node, CodeMarker *marker, QVector
This is the complete list of members for "; generateFullName(aggregate, 0); 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, 0); 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,0); 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->isDocumentNode()) {
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);
}
void HtmlGenerator::generateList(const Node* relative, CodeMarker* marker, const QString& selector)
{
CNMap cnm;
Node::Genus genus = Node::DontCare;
if (selector == QLatin1String("overviews"))
genus = Node::DOC;
else if (selector == QLatin1String("cpp-modules"))
genus = Node::CPP;
else if (selector == QLatin1String("qml-modules"))
genus = Node::QML;
else if (selector == QLatin1String("js-modules"))
genus = Node::JS;
if (genus != Node::DontCare) {
NodeList nl;
qdb_->mergeCollections(genus, cnm, relative);
CollectionList cl = cnm.values();
foreach (CollectionNode* cn, cl)
nl.append(cn);
generateAnnotatedList(relative, marker, nl);
}
else {
/*
\generatelist {selector} is only allowed in a
comment where the topic is \group, \module,
\qmlmodule, or \jsmodule
*/
if (relative && !relative->isCollectionNode()) {
relative->doc().location().warning(tr("\\generatelist {%1} is only allowed in \\group, "
"\\module, \\qmlmodule, and \\jsmodule comments.").arg(selector));
return;
}
Node* n = const_cast";
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->type()) { case Node::Namespace: case Node::Class: case Node::Module: return 2; case Node::QmlModule: case Node::QmlBasicType: case Node::QmlType: case Node::Document: 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 != 0 && !(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: "; PropNodeList propertyNodes = fn->associatedProperties(); std::sort(propertyNodes.begin(), propertyNodes.end(), Node::nodeNameLessThan); foreach (const PropertyNode* pn, propertyNodes) { QString msg; switch (pn->role(fn)) { case PropertyNode::Getter: msg = QStringLiteral("Getter function "); break; case PropertyNode::Setter: msg = QStringLiteral("Setter function "); break; case PropertyNode::Resetter: msg = QStringLiteral("Resetter function "); break; case PropertyNode::Notifier: msg = QStringLiteral("Notifier signal "); break; default: break; } QString link = linkForNode(pn, 0); out() << msg << "for property " << pn->name() << ". "; } out() << "
"; } } QT_END_NAMESPACE