/****************************************************************************
**
** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the tools applications of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** 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 Digia. For licensing terms and
** conditions see http://qt.digia.com/licensing. For further information
** use the contact form at http://qt.digia.com/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Digia gives you certain additional
** rights. These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3.0 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU General Public License version 3.0 requirements will be
** met: http://www.gnu.org/copyleft/gpl.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
#include
#include
#include
#include
#include
QT_BEGIN_NAMESPACE
#define COMMAND_VERSION Doc::alias("version")
int HtmlGenerator::id = 0;
bool HtmlGenerator::debugging_on = false;
QString HtmlGenerator::divNavTop;
static bool showBrokenLinks = false;
static QRegExp linkTag("(<@link node=\"([^\"]+)\">).*(@link>)");
static QRegExp funcTag("(<@func target=\"([^\"]*)\">)(.*)(@func>)");
static QRegExp typeTag("(<@(type|headerfile|func)(?: +[^>]*)?>)(.*)(@\\2>)");
static QRegExp spanTag("@(?:comment|preprocessor|string|char|number|op|type|name|keyword)>");
static QRegExp unknownTag("?@[^>]*>");
static void addLink(const QString &linkTarget,
const QStringRef &nestedStuff,
QString *res)
{
if (!linkTarget.isEmpty()) {
*res += "";
*res += nestedStuff;
*res += "";
}
else {
*res += nestedStuff;
}
}
/*!
Constructs the HTML output generator.
*/
HtmlGenerator::HtmlGenerator()
: codeIndent(0),
helpProjectWriter(0),
inObsoleteLink(false),
funcLeftParen("\\S(\\()"),
obsoleteLinks(false)
{
}
/*!
Destroys the HTML output generator. Deletes the singleton
instance of HelpProjectWriter.
*/
HtmlGenerator::~HtmlGenerator()
{
if (helpProjectWriter)
delete helpProjectWriter;
}
/*!
Initializes the HTML output generator's data structures
from the configuration class \a config.
*/
void HtmlGenerator::initializeGenerator(const Config &config)
{
static const struct {
const char *key;
const char *left;
const char *right;
} defaults[] = {
{ ATOM_FORMATTING_BOLD, "", "" },
{ ATOM_FORMATTING_INDEX, "" },
{ ATOM_FORMATTING_ITALIC, "", "" },
{ ATOM_FORMATTING_PARAMETER, "", "" },
{ ATOM_FORMATTING_SUBSCRIPT, "", "" },
{ ATOM_FORMATTING_SUPERSCRIPT, "", "" },
{ ATOM_FORMATTING_TELETYPE, "", "" },
{ ATOM_FORMATTING_UICONTROL, "", "" },
{ ATOM_FORMATTING_UNDERLINE, "", "" },
{ 0, 0, 0 }
};
Generator::initializeGenerator(config);
obsoleteLinks = config.getBool(CONFIG_OBSOLETELINKS);
setImageFileExtensions(QStringList() << "png" << "jpg" << "jpeg" << "gif");
int i = 0;
while (defaults[i].key) {
formattingLeftMap().insert(defaults[i].key, defaults[i].left);
formattingRightMap().insert(defaults[i].key, 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);
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);
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 + " 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 editionNames = config.subVars(CONFIG_EDITION);
QSet::ConstIterator edition = editionNames.constBegin();
while (edition != editionNames.constEnd()) {
QString editionName = *edition;
QStringList editionModules = config.getStringList(CONFIG_EDITION +
Config::dot +
editionName +
Config::dot +
"modules");
QStringList editionGroups = config.getStringList(CONFIG_EDITION +
Config::dot +
editionName +
Config::dot +
"groups");
if (!editionModules.isEmpty())
editionModuleMap[editionName] = editionModules;
if (!editionGroups.isEmpty())
editionGroupMap[editionName] = editionGroups;
++edition;
}
// The following line was changed to fix QTBUG-27798
//codeIndent = config.getInt(CONFIG_CODEINDENT);
helpProjectWriter = new HelpProjectWriter(config, 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 = "qthelp://" + config.getString(prefix + "namespace");
manifestDir += QLatin1Char('/') + config.getString(prefix + "virtualFolder") + QLatin1Char('/');
readManifestMetaContent(config);
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);
landingpage = config.getString(CONFIG_NAVIGATION
+ Config::dot
+ CONFIG_LANDINGPAGE);
cppclassespage = config.getString(CONFIG_NAVIGATION
+ Config::dot
+ CONFIG_CPPCLASSESPAGE);
qmltypespage = config.getString(CONFIG_NAVIGATION
+ Config::dot
+ CONFIG_QMLTYPESPAGE);
buildversion = config.getString(CONFIG_BUILDVERSION);
}
/*!
Gracefully terminates the HTML output generator.
*/
void HtmlGenerator::terminateGenerator()
{
Generator::terminateGenerator();
}
QString HtmlGenerator::format()
{
return "HTML";
}
/*!
Traverses the current tree generating all the HTML documentation.
*/
void HtmlGenerator::generateDocs()
{
Node* qflags = qdb_->findClassNode(QStringList("QFlags"));
if (qflags)
qflagsHref_ = linkForNode(qflags,0);
if (!runPrepareOnly()) {
Generator::generateDocs();
generateCollisionPages();
}
if (!runGenerateOnly()) {
QString fileBase = project.toLower().simplified().replace(QLatin1Char(' '), QLatin1Char('-'));
qdb_->generateIndex(outputDir() + QLatin1Char('/') + fileBase + ".index",
projectUrl,
projectDescription,
this,
true);
}
if (!runPrepareOnly()) {
helpProjectWriter->generate();
generateManifestFiles();
/*
Generate the XML tag file, if it was requested.
*/
qdb_->generateTagFile(tagFile_, this);
}
}
/*!
Generate html from an instance of Atom.
*/
int HtmlGenerator::generateAtom(const Atom *atom, const Node *relative, CodeMarker *marker)
{
int skipAhead = 0;
static bool in_para = false;
switch (atom->type()) {
case Atom::AbstractLeft:
if (relative)
relative->doc().location().warning(tr("\abstract is not implemented."));
else
Location::information(tr("\abstract is not implemented."));
break;
case Atom::AbstractRight:
break;
case Atom::AutoLink:
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 {
beginLink(link, node, relative);
generateLink(atom, marker);
endLink();
}
}
else {
out() << protectEnc(atom->string());
}
break;
case Atom::BaseName:
break;
case Atom::BriefLeft:
if (relative->isQmlBasicType() || (relative->isDocNode() && !relative->isExample())) {
skipAhead = skipAtoms(atom, Atom::BriefRight);
break;
}
out() << "
\n";
break;
case Atom::C:
// This may at one time have been used to mark up C++ code but it is
// now widely used to write teletype text. As a result, text marked
// with the \c command is not passed to a code marker.
out() << formattingLeftMap()[ATOM_FORMATTING_TELETYPE];
if (inLink_) {
out() << protectEnc(plainCode(atom->string()));
}
else {
out() << protectEnc(plainCode(atom->string()));
}
out() << formattingRightMap()[ATOM_FORMATTING_TELETYPE];
break;
case Atom::CaptionLeft:
out() << "
";
in_para = true;
break;
case Atom::CaptionRight:
endLink();
if (in_para) {
out() << "
\n";
bool needOtherSection = false;
/*
sections is built above for the call to generateTableOfContents().
*/
s = sections.constBegin();
while (s != sections.constEnd()) {
if (s->members.isEmpty() && s->reimpMembers.isEmpty()) {
if (!s->inherited.isEmpty())
needOtherSection = true;
}
else {
if (!s->members.isEmpty()) {
// out() << "\n";
out() << "" << divNavTop << "\n";
out() << "
\n";
s = sections.constBegin();
while (s != sections.constEnd()) {
if (s->members.isEmpty() && !s->inherited.isEmpty())
generateSectionInheritedList(*s, inner);
++s;
}
out() << "
\n"; // QTBUG-9504
generateAlsoList(qbtn, marker);
generateExtractionMark(qbtn, EndMark);
generateFooter(qbtn);
}
/*!
We delayed generation of the disambiguation pages until now, after
all the other pages have been generated. We do this because we might
encounter a link command that tries to link to a target on a QML
component page, but the link doesn't specify the module identifer
for the component, and the component name without a module
identifier is ambiguous. When such a link is found, qdoc can't find
the target, so it appends the target to the NameCollisionNode. After
the tree has been traversed and all these ambiguous links have been
added to the name collision nodes, this function is called. The list
of collision nodes is traversed here, and the disambiguation page for
each collision is generated. The disambiguation page will not only
disambiguate links to the component pages, but it will also disambiguate
links to properties, section headers, etc.
*/
void HtmlGenerator::generateCollisionPages()
{
if (collisionNodes.isEmpty())
return;
for (int i=0; ichildNodes();
if (!nl.isEmpty()) {
NodeList::ConstIterator it = nl.constBegin();
while (it != nl.constEnd()) {
if (!(*it)->isInternal())
collisions.append(*it);
++it;
}
}
if (collisions.size() <= 1)
continue;
beginSubPage(ncn, Generator::fileName(ncn));
QString fullTitle = ncn->fullTitle();
CodeMarker* marker = CodeMarker::markerForFileName(ncn->location().filePath());
if (ncn->isQmlNode()) {
// Replace the marker with a QML code marker.
if (ncn->isQmlNode())
marker = CodeMarker::markerForLanguage(QLatin1String("QML"));
}
generateHeader(fullTitle, ncn, marker);
if (!fullTitle.isEmpty())
out() << "
" << protectEnc(fullTitle) << "
\n";
NodeMap nm;
for (int i=0; iqmlModuleName().isEmpty())
t = n->qmlModuleName() + "::";
t += protectEnc(fullTitle);
nm.insertMulti(t,n);
}
generateAnnotatedList(ncn, marker, nm);
QList targets;
if (!ncn->linkTargets().isEmpty()) {
QMap::ConstIterator t = ncn->linkTargets().constBegin();
while (t != ncn->linkTargets().constEnd()) {
int count = 0;
for (int i=0; i(collisions.at(i));
if (n->findChildNode(t.key())) {
++count;
if (count > 1) {
targets.append(t.key());
break;
}
}
}
++t;
}
}
if (!targets.isEmpty()) {
QList::ConstIterator t = targets.constBegin();
while (t != targets.constEnd()) {
out() << "";
out() << "
" << protectEnc(*t) << "
\n";
out() << "
\n";
for (int i=0; i(collisions.at(i));
Node* p = n->findChildNode(*t);
if (p) {
QString link = linkForNode(p,0);
QString label;
if (!n->qmlModuleName().isEmpty())
label = n->qmlModuleName() + "::";
label += n->name() + "::" + p->name();
out() << "
\n";
++t;
}
}
generateFooter(ncn);
endSubPage();
}
}
/*!
Generate the HTML page for an entity that doesn't map
to any underlying parsable C++ class or QML component.
*/
void HtmlGenerator::generateDocNode(DocNode* dn, CodeMarker* marker)
{
/*
If the document node is a page node, and if the page type
is DITA map page, write the node's contents as a dita
map and return without doing anything else.
*/
if (dn->subType() == Node::Page && dn->pageType() == Node::DitaMapPage) {
const DitaMapNode* dmn = static_cast(dn);
writeDitaMap(dmn);
return;
}
SubTitleSize subTitleSize = LargeSubTitle;
QList sections;
QList::const_iterator s;
QString fullTitle = dn->fullTitle();
generateHeader(fullTitle, dn, marker);
/*
Generate the TOC for the new doc format.
Don't generate a TOC for the home page.
*/
if ((dn->subType() != Node::Collision) &&
(dn->name() != QString("index.html")) &&
(dn->name() != QString("qtexamplesandtutorials.html")))
generateTableOfContents(dn,marker,0);
generateTitle(fullTitle,
Text() << dn->subTitle(),
subTitleSize,
dn,
marker);
if (dn->subType() == Node::HeaderFile) {
// Generate brief text and status for modules.
generateBrief(dn, marker);
generateStatus(dn, marker);
generateSince(dn, marker);
out() << "
\n";
out() << QString(footer).replace("\\" + COMMAND_VERSION, qdb_->version())
<< QString(address).replace("\\" + COMMAND_VERSION, qdb_->version());
out() << "\n";
out() << "\n";
}
/*!
Lists the required imports and includes in a table.
The number of rows is known, so this path is simpler than the generateSection() path.
*/
void HtmlGenerator::generateRequisites(InnerNode *inner, CodeMarker *marker)
{
QMap requisites;
Text text;
const QString headerText = "Header";
const QString sinceText = "Since";
const QString inheritedBytext = "Inherited By";
const QString inheritsText = "Inherits";
const QString instantiatedByText = "Instantiated By";
const QString qtVariableText = "qmake";
//add the includes to the map
if (!inner->includes().isEmpty()) {
text.clear();
text << formattingRightMap()[ATOM_FORMATTING_BOLD]
<< formattingLeftMap()[ATOM_FORMATTING_TELETYPE]
<< highlightedCode(indent(codeIndent,
marker->markedUpIncludes(inner->includes())),
inner)
<< formattingRightMap()[ATOM_FORMATTING_TELETYPE];
requisites.insert(headerText, text);
}
//The order of the requisites matter
QStringList requisiteorder;
requisiteorder << headerText
<< qtVariableText
<< sinceText
<< instantiatedByText
<< inheritsText
<< inheritedBytext;
//add the since and project into the map
if (!inner->since().isEmpty()) {
text.clear();
QStringList since = inner->since().split(QLatin1Char(' '));
if (since.count() == 1) {
// If there is only one argument, assume it is the Qt version number.
text << " Qt " << since[0];
}
else {
//Otherwise, reconstruct the string.
text << " " << since.join(' ');
}
text << Atom::ParaRight;
requisites.insert(sinceText, text);
}
if (inner->type() == Node::Class || inner->type() == Node::Namespace) {
//add the QT variable to the map
if (!inner->moduleName().isEmpty()) {
ModuleNode* moduleNode = qdb_->findModule(inner->moduleName());
if (moduleNode && !moduleNode->qtVariable().isEmpty()) {
text.clear();
text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_TELETYPE)
<< "QT += " + moduleNode->qtVariable()
<< Atom(Atom::FormattingRight, ATOM_FORMATTING_TELETYPE);
requisites.insert(qtVariableText, text);
}
}
}
if (inner->type() == Node::Class) {
ClassNode* classe = static_cast(inner);
if (classe->qmlElement() != 0 && classe->status() != Node::Internal) {
text.clear();
text << Atom(Atom::LinkNode, CodeMarker::stringForNode(classe->qmlElement()))
<< Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
<< Atom(Atom::String, classe->qmlElement()->name())
<< Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
requisites.insert(instantiatedByText, text);
}
//add the inherits to the map
QList::ConstIterator r;
int index;
if (!classe->baseClasses().isEmpty()) {
text.clear();
r = classe->baseClasses().constBegin();
index = 0;
while (r != classe->baseClasses().constEnd()) {
if ((*r).node_) {
text << Atom(Atom::LinkNode, CodeMarker::stringForNode((*r).node_))
<< Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
<< Atom(Atom::String, (*r).signature_)
<< Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
if ((*r).access_ == Node::Protected) {
text << " (protected)";
}
else if ((*r).access_ == Node::Private) {
text << " (private)";
}
text << separator(index++, classe->baseClasses().count());
}
++r;
}
text << Atom::ParaRight;
requisites.insert(inheritsText, text);
}
//add the inherited-by to the map
if (!classe->derivedClasses().isEmpty()) {
text.clear();
text << Atom::ParaLeft;
appendSortedNames(text, classe, classe->derivedClasses());
text << Atom::ParaRight;
requisites.insert(inheritedBytext, text);
}
}
if (!requisites.isEmpty()) {
//generate the table
out() << "
\n";
QStringList::ConstIterator i;
for (i = requisiteorder.begin(); i != requisiteorder.constEnd(); ++i) {
if (requisites.contains(*i)) {
out() << "
";
}
}
/*!
Lists the required imports and includes in a table.
The number of rows is known, so this path is simpler than the generateSection() path.
*/
void HtmlGenerator::generateQmlRequisites(QmlClassNode *qcn, CodeMarker *marker)
{
if (!qcn)
return;
QMap requisites;
Text text;
const QString importText = "Import Statement:";
const QString sinceText = "Since:";
const QString inheritedBytext = "Inherited By:";
const QString inheritsText = "Inherits:";
const QString instantiatesText = "Instantiates:";
//The order of the requisites matter
QStringList requisiteorder;
requisiteorder << importText
<< sinceText
<< instantiatesText
<< inheritsText
<< inheritedBytext;
//add the module name and version to the map
QString qmlModuleVersion;
QmlModuleNode* qmn = qdb_->findQmlModule(qcn->qmlModuleName());
if (qmn)
qmlModuleVersion = qmn->qmlModuleVersion();
else
qmlModuleVersion = qcn->qmlModuleVersion();
text.clear();
text << formattingRightMap()[ATOM_FORMATTING_BOLD]
<< formattingLeftMap()[ATOM_FORMATTING_TELETYPE]
<< "import " + qcn->qmlModuleName() + " " + qmlModuleVersion
<< formattingRightMap()[ATOM_FORMATTING_TELETYPE];
requisites.insert(importText, text);
//add the since and project into the map
if (!qcn->since().isEmpty()) {
text.clear();
QStringList since = qcn->since().split(QLatin1Char(' '));
if (since.count() == 1) {
// If there is only one argument, assume it is the Qt version number.
text << " Qt " << since[0];
}
else {
//Otherwise, reconstruct the string.
text << " " << since.join(' ');
}
text << Atom::ParaRight;
requisites.insert(sinceText, text);
}
//add the instantiates to the map
ClassNode* cn = qcn->classNode();
if (cn && (cn->status() != Node::Internal)) {
text.clear();
text << Atom(Atom::LinkNode,CodeMarker::stringForNode(qcn));
text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK);
text << Atom(Atom::LinkNode,CodeMarker::stringForNode(cn));
text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK);
text << Atom(Atom::String, cn->name());
text << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
requisites.insert(instantiatesText, text);
}
//add the inherits to the map
QmlClassNode* base = qcn->qmlBaseNode();
while (base && base->isInternal()) {
base = base->qmlBaseNode();
}
if (base) {
text.clear();
text << Atom::ParaLeft
<< Atom(Atom::LinkNode,CodeMarker::stringForNode(base))
<< Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
<< Atom(Atom::String, base->name())
<< Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK)
<< Atom::ParaRight;
requisites.insert(inheritsText, text);
}
//add the inherited-by to the map
NodeList subs;
QmlClassNode::subclasses(qcn->name(), subs);
if (!subs.isEmpty()) {
text.clear();
text << Atom::ParaLeft;
appendSortedQmlNames(text, qcn, subs);
text << Atom::ParaRight;
requisites.insert(inheritedBytext, text);
}
if (!requisites.isEmpty()) {
//generate the table
out() << "
\n";
QStringList::ConstIterator i;
for (i = requisiteorder.begin(); i != requisiteorder.constEnd(); ++i) {
if (requisites.contains(*i)) {
out() << "
";
}
}
/*!
Revised for the new doc format.
Generates a table of contents beginning at \a node.
*/
void HtmlGenerator::generateTableOfContents(const Node *node,
CodeMarker *marker,
QList* sections)
{
QList toc;
if (node->doc().hasTableOfContents())
toc = node->doc().tableOfContents();
if (toc.isEmpty() && !sections && !node->isModule())
return;
//turn off table of contents if HTML.tocdepth is set to 0
if (tocDepth == 0)
return;
QStringList sectionNumber;
int detailsBase = 0;
// disable nested links in table of contents
inContents_ = true;
inLink_ = true;
out() << "
\n";
for (int i = 0; i < toc.size(); ++i) {
if (toc.at(i)->string().toInt() == 1) {
detailsBase = 1;
break;
}
}
}
for (int i = 0; i < toc.size(); ++i) {
Atom *atom = toc.at(i);
int nextLevel = atom->string().toInt() + detailsBase;
if (nextLevel >= 0) {
if (sectionNumber.size() < nextLevel) {
do {
sectionNumber.append("1");
} while (sectionNumber.size() < nextLevel);
}
else {
while (sectionNumber.size() > nextLevel) {
sectionNumber.removeLast();
}
sectionNumber.last() = QString::number(sectionNumber.last().toInt() + 1);
}
}
//restrict the ToC depth to the one set by the HTML.tocdepth variable or
//print all levels if tocDepth is not set.
if (sectionNumber.size() <= tocDepth || tocDepth < 0) {
int numAtoms;
Text headingText = Text::sectionHeading(atom);
QString s = headingText.toString();
out() << "
\n";
inContents_ = false;
inLink_ = false;
}
QString HtmlGenerator::generateListOfAllMemberFile(const InnerNode *inner,
CodeMarker *marker)
{
QList sections;
QList::ConstIterator s;
sections = marker->sections(inner,
CodeMarker::Subpage,
CodeMarker::Okay);
if (sections.isEmpty())
return QString();
QString fileName = fileBase(inner) + "-members." + fileExtension();
beginSubPage(inner, fileName);
QString title = "List of All Members for " + inner->name();
generateHeader(title, inner, marker);
generateTitle(title, Text(), SmallSubTitle, inner, marker);
out() << "
This is the complete list of members for ";
generateFullName(inner, 0);
out() << ", including inherited members.
\n";
Section section = sections.first();
generateSectionList(section, 0, marker, CodeMarker::Subpage);
generateFooter();
endSubPage();
return fileName;
}
/*!
This function creates an html page on which are listed all
the members of QML class \a qml_cn, including the inherited
members. The \a marker is used for formatting stuff.
*/
QString HtmlGenerator::generateAllQmlMembersFile(QmlClassNode* qml_cn, CodeMarker* marker)
{
QList sections;
QList::ConstIterator s;
sections = marker->qmlSections(qml_cn,CodeMarker::Subpage);
if (sections.isEmpty())
return QString();
QString fileName = fileBase(qml_cn) + "-members." + fileExtension();
beginSubPage(qml_cn, fileName);
QString title = "List of All Members for " + qml_cn->name();
generateHeader(title, qml_cn, marker);
generateTitle(title, Text(), SmallSubTitle, qml_cn, marker);
out() << "
This is the complete list of members for ";
generateFullName(qml_cn, 0);
out() << ", including inherited members.
\n";
ClassKeysNodesList& cknl = sections.first().classKeysNodesList_;
if (!cknl.isEmpty()) {
for (int i=0; ifirst;
KeysAndNodes& kn = ckn->second;
QStringList& keys = kn.first;
NodeList& nodes = kn.second;
if (nodes.isEmpty())
continue;
if (i != 0) {
out() << "
The following members are inherited from ";
generateFullName(qcn,0);
out() << ".
\n";
}
}
generateFooter();
endSubPage();
return fileName;
}
QString HtmlGenerator::generateLowStatusMemberFile(InnerNode *inner,
CodeMarker *marker,
CodeMarker::Status status)
{
QList sections = marker->sections(inner,
CodeMarker::Summary,
status);
QMutableListIterator j(sections);
while (j.hasNext()) {
if (j.next().members.size() == 0)
j.remove();
}
if (sections.isEmpty())
return QString();
int i;
QString title;
QString fileName;
if (status == CodeMarker::Compat) {
title = "Compatibility Members for " + inner->name();
fileName = fileBase(inner) + "-compat." + fileExtension();
}
else {
title = "Obsolete Members for " + inner->name();
fileName = fileBase(inner) + "-obsolete." + fileExtension();
}
if (status == CodeMarker::Obsolete) {
QString link;
if (useOutputSubdirs() && !Generator::outputSubdir().isEmpty())
link = QString("../" + Generator::outputSubdir() + QLatin1Char('/'));
link += fileName;
inner->setObsoleteLink(link);
}
beginSubPage(inner, fileName);
generateHeader(title, inner, marker);
generateTitle(title, Text(), SmallSubTitle, inner, marker);
if (status == CodeMarker::Compat) {
out() << "
The following members of class "
<< ""
<< protectEnc(inner->name()) << ""
<< "are part of the "
"Qt compatibility layer. We advise against "
"using them in new code.
\n";
}
else {
out() << "
The following members of class "
<< ""
<< protectEnc(inner->name()) << ""
<< " are obsolete. "
<< "They are provided to keep old source code working. "
<< "We strongly advise against using them in new code.
\n";
}
for (i = 0; i < sections.size(); ++i) {
out() << "
" << protectEnc(sections.at(i).name) << "
\n";
generateSectionList(sections.at(i), inner, marker, CodeMarker::Summary);
}
sections = marker->sections(inner, CodeMarker::Detailed, status);
for (i = 0; i < sections.size(); ++i) {
//out() << "\n";
out() << "
" << protectEnc(sections.at(i).name) << "
\n";
NodeList::ConstIterator m = sections.at(i).members.constBegin();
while (m != sections.at(i).members.constEnd()) {
if ((*m)->access() != Node::Private)
generateDetailedMember(*m, inner, marker);
++m;
}
}
generateFooter();
endSubPage();
return fileName;
}
void HtmlGenerator::generateClassHierarchy(const Node *relative, NodeMap& classMap)
{
if (classMap.isEmpty())
return;
NodeMap topLevel;
NodeMap::Iterator c = classMap.begin();
while (c != classMap.end()) {
ClassNode *classe = static_cast(*c);
if (classe->baseClasses().isEmpty())
topLevel.insert(classe->name(), classe);
++c;
}
QStack stack;
stack.push(topLevel);
out() << "
\n";
while (!stack.isEmpty()) {
if (stack.top().isEmpty()) {
stack.pop();
out() << "
\n";
}
/*!
This function finds the common prefix of the names of all
the classes in \a classMap and then generates a compact
list of the class names alphabetized on the part of the
name not including the common prefix. You can tell the
function to use \a comonPrefix as the common prefix, but
normally you let it figure it out itself by looking at
the name of the first and last classes in \a classMap.
*/
void HtmlGenerator::generateCompactList(ListType listType,
const Node *relative,
const NodeMap &classMap,
bool includeAlphabet,
QString commonPrefix)
{
if (classMap.isEmpty())
return;
const int NumParagraphs = 37; // '0' to '9', 'A' to 'Z', '_'
int commonPrefixLen = commonPrefix.length();
/*
Divide the data into 37 paragraphs: 0, ..., 9, A, ..., Z,
underscore (_). QAccel will fall in paragraph 10 (A) and
QXtWidget in paragraph 33 (X). This is the only place where we
assume that NumParagraphs is 37. Each paragraph is a NodeMap.
*/
NodeMap paragraph[NumParagraphs+1];
QString paragraphName[NumParagraphs+1];
QSet usedParagraphNames;
NodeMap::ConstIterator c = classMap.constBegin();
while (c != classMap.constEnd()) {
QStringList pieces = c.key().split("::");
QString key;
int idx = commonPrefixLen;
if (idx > 0 && !pieces.last().startsWith(commonPrefix))
idx = 0;
if (pieces.size() == 1)
key = pieces.last().mid(idx).toLower();
else
key = pieces.last().toLower();
int paragraphNr = NumParagraphs - 1;
if (key[0].digitValue() != -1) {
paragraphNr = key[0].digitValue();
}
else if (key[0] >= QLatin1Char('a') && key[0] <= QLatin1Char('z')) {
paragraphNr = 10 + key[0].unicode() - 'a';
}
paragraphName[paragraphNr] = key[0].toUpper();
usedParagraphNames.insert(key[0].toLower().cell());
paragraph[paragraphNr].insert(key, c.value());
++c;
}
/*
Each paragraph j has a size: paragraph[j].count(). In the
discussion, we will assume paragraphs 0 to 5 will have sizes
3, 1, 4, 1, 5, 9.
We now want to compute the paragraph offset. Paragraphs 0 to 6
start at offsets 0, 3, 4, 8, 9, 14, 23.
*/
int paragraphOffset[NumParagraphs + 1]; // 37 + 1
paragraphOffset[0] = 0;
for (int i=0; i";
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
element to contain all the
elements.
*/
out() << "
\n";
numTableRows_ = 0;
int curParNr = 0;
int curParOffset = 0;
for (int i=0; i.
*/
if (curParOffset == 0) {
if (i > 0)
out() << "\n";
if (++numTableRows_ % 2 == 1)
out() << "
\n";
}
int i = 0;
NodeList::ConstIterator m = nl.constBegin();
while (m != nl.constEnd()) {
if ((*m)->access() == Node::Private) {
++m;
continue;
}
if (alignNames) {
out() << "
";
}
else {
if (twoColumn && i == (int) (nl.count() + 1) / 2)
out() << "
\n";
}
int i = 0;
NodeList::ConstIterator m = section.members.constBegin();
while (m != section.members.constEnd()) {
if ((*m)->access() == Node::Private) {
++m;
continue;
}
if (alignNames) {
out() << "
";
}
else {
if (twoColumn && i == (int) (section.members.count() + 1) / 2)
out() << "
";
done = true;
}
i += 2;
if (parseArg(src, linkTag, &i, srcSize, &arg, &par1)) {
html += "";
const Node* n = CodeMarker::nodeForString(par1.toString());
QString link = linkForNode(n, relative);
addLink(link, arg, &html);
html += "";
}
else {
html += charLangle;
html += charAt;
}
}
else {
html += src.at(i++);
}
}
// replace all <@func> tags: "(<@func target=\"([^\"]*)\">)(.*)(@func>)"
src = html;
html = QString();
for (int i = 0, srcSize = src.size(); i < srcSize;) {
if (src.at(i) == charLangle && src.at(i + 1) == charAt) {
i += 2;
if (parseArg(src, funcTag, &i, srcSize, &arg, &par1)) {
const Node* n = qdb_->resolveFunctionTarget(par1.toString(), relative);
QString link = linkForNode(n, relative);
addLink(link, arg, &html);
par1 = QStringRef();
}
else {
html += charLangle;
html += charAt;
}
}
else {
html += src.at(i++);
}
}
// replace all "(<@(type|headerfile)(?: +[^>]*)?>)(.*)(@\\2>)" tags
src = html;
html = QString();
for (int i=0, srcSize=src.size(); iresolveType(arg.toString(), relative);
html += QLatin1String("");
if (n && n->isQmlBasicType()) {
if (relative && relative->isQmlType())
addLink(linkForNode(n,relative), arg, &html);
else
html += arg.toString();
}
else
addLink(linkForNode(n,relative), arg, &html);
html += QLatin1String("");
handled = true;
}
else if (parseArg(src, headerTag, &i, srcSize, &arg, &par1)) {
par1 = QStringRef();
if (arg.at(0) == QChar('&'))
html += arg.toString();
else {
// zzz resolveClassTarget()
const Node* n = qdb_->resolveTarget(arg.toString(), relative);
if (n)
addLink(linkForNode(n,relative), arg, &html);
else
html += arg.toString();
}
handled = true;
}
if (!handled) {
html += charLangle;
html += charAt;
}
}
else {
html += src.at(i++);
}
}
// replace all
// "<@comment>" -> "";
// "<@preprocessor>" -> "";
// "<@string>" -> "";
// "<@char>" -> "";
// "<@number>" -> "";
// "<@op>" -> "";
// "<@type>" -> "";
// "<@name>" -> "";
// "<@keyword>" -> "";
// "@(?:comment|preprocessor|string|char|number|op|type|name|keyword)>" -> ""
src = html;
html = QString();
static const QString spanTags[] = {
"<@comment>", "",
"<@preprocessor>", "",
"<@string>", "",
"<@char>", "",
"<@number>", "",
"<@op>", "",
"<@type>", "",
"<@name>", "",
"<@keyword>", "",
"@comment>", "",
"@preprocessor>", "",
"@string>", "",
"@char>", "",
"@number>", "",
"@op>", "",
"@type>", "",
"@name>", "",
"@keyword>", "",
};
// Update the upper bound of k in the following code to match the length
// of the above array.
for (int i = 0, n = src.size(); i < n;) {
if (src.at(i) == charLangle) {
bool handled = false;
for (int k = 0; k != 18; ++k) {
const QString & tag = spanTags[2 * k];
if (tag == QStringRef(&src, i, tag.length())) {
html += spanTags[2 * k + 1];
i += tag.length();
handled = true;
break;
}
}
if (!handled) {
++i;
if (src.at(i) == charAt ||
(src.at(i) == QLatin1Char('/') && src.at(i + 1) == charAt)) {
// drop 'our' unknown tags (the ones still containing '@')
while (i < n && src.at(i) != QLatin1Char('>'))
++i;
++i;
}
else {
// retain all others
html += charLangle;
}
}
}
else {
html += src.at(i);
++i;
}
}
return html;
}
void HtmlGenerator::generateLink(const Atom* atom, CodeMarker* marker)
{
static QRegExp camelCase("[A-Z][A-Z][a-z]|[a-z][A-Z0-9]|_");
if (funcLeftParen.indexIn(atom->string()) != -1 && marker->recognizeLanguage("Cpp")) {
// hack for C++: move () outside of link
int k = funcLeftParen.pos(1);
out() << protectEnc(atom->string().left(k));
if (link_.isEmpty()) {
if (showBrokenLinks)
out() << "";
} else {
out() << "";
}
inLink_ = false;
out() << protectEnc(atom->string().mid(k));
} else {
out() << protectEnc(atom->string());
}
}
QString HtmlGenerator::cleanRef(const QString& ref)
{
QString clean;
if (ref.isEmpty())
return clean;
clean.reserve(ref.size() + 20);
const QChar c = ref[0];
const uint u = c.unicode();
if ((u >= 'a' && u <= 'z') ||
(u >= 'A' && u <= 'Z') ||
(u >= '0' && u <= '9')) {
clean += c;
} else if (u == '~') {
clean += "dtor.";
} else if (u == '_') {
clean += "underscore.";
} else {
clean += QLatin1Char('A');
}
for (int i = 1; i < (int) ref.length(); i++) {
const QChar c = ref[i];
const uint u = c.unicode();
if ((u >= 'a' && u <= 'z') ||
(u >= 'A' && u <= 'Z') ||
(u >= '0' && u <= '9') || u == '-' ||
u == '_' || u == ':' || u == '.') {
clean += c;
} else if (c.isSpace()) {
clean += QLatin1Char('-');
} else if (u == '!') {
clean += "-not";
} else if (u == '&') {
clean += "-and";
} else if (u == '<') {
clean += "-lt";
} else if (u == '=') {
clean += "-eq";
} else if (u == '>') {
clean += "-gt";
} else if (u == '#') {
clean += QLatin1Char('#');
} else {
clean += QLatin1Char('-');
clean += QString::number((int)u, 16);
}
}
return clean;
}
QString HtmlGenerator::registerRef(const QString& ref)
{
QString clean = HtmlGenerator::cleanRef(ref);
for (;;) {
QString& prevRef = refMap[clean.toLower()];
if (prevRef.isEmpty()) {
prevRef = ref;
break;
} else if (prevRef == ref) {
break;
}
clean += QLatin1Char('x');
}
return clean;
}
QString HtmlGenerator::protectEnc(const QString &string)
{
#ifndef QT_NO_TEXTCODEC
return protect(string, outputEncoding);
#else
return protect(string);
#endif
}
QString HtmlGenerator::protect(const QString &string, const QString &outputEncoding)
{
#define APPEND(x) \
if (html.isEmpty()) { \
html = string; \
html.truncate(i); \
} \
html += (x);
QString html;
int n = string.length();
for (int i = 0; i < n; ++i) {
QChar ch = string.at(i);
if (ch == QLatin1Char('&')) {
APPEND("&");
} else if (ch == QLatin1Char('<')) {
APPEND("<");
} else if (ch == QLatin1Char('>')) {
APPEND(">");
} else if (ch == QLatin1Char('"')) {
APPEND(""");
} else if ((outputEncoding == "ISO-8859-1" && ch.unicode() > 0x007F)
|| (ch == QLatin1Char('*') && i + 1 < n && string.at(i) == QLatin1Char('/'))
|| (ch == QLatin1Char('.') && i > 2 && string.at(i - 2) == QLatin1Char('.'))) {
// we escape '*/' and the last dot in 'e.g.' and 'i.e.' for the Javadoc generator
APPEND("");
html += QString::number(ch.unicode(), 16);
html += QLatin1Char(';');
} else {
if (!html.isEmpty())
html += ch;
}
}
if (!html.isEmpty())
return html;
return string;
#undef APPEND
}
QString HtmlGenerator::fileBase(const Node *node) const
{
QString result;
result = Generator::fileBase(node);
if (!node->isInnerNode()) {
switch (node->status()) {
case Node::Compat:
result += "-compat";
break;
case Node::Obsolete:
result += "-obsolete";
break;
default:
;
}
}
return result;
}
QString HtmlGenerator::fileName(const Node *node)
{
if (node->type() == Node::Document) {
if (static_cast(node)->subType() == Node::ExternalPage)
return node->name();
if (static_cast(node)->subType() == Node::Image)
return node->name();
}
return Generator::fileName(node);
}
QString HtmlGenerator::refForNode(const Node *node)
{
const FunctionNode *func;
const TypedefNode *typedeffe;
QString ref;
switch (node->type()) {
case Node::Namespace:
case Node::Class:
default:
break;
case Node::Enum:
ref = node->name() + "-enum";
break;
case Node::Typedef:
typedeffe = static_cast(node);
if (typedeffe->associatedEnum()) {
return refForNode(typedeffe->associatedEnum());
}
else {
ref = node->name() + "-typedef";
}
break;
case Node::Function:
func = static_cast(node);
if (func->associatedProperty()) {
return refForNode(func->associatedProperty());
}
else {
ref = func->name();
if (func->overloadNumber() != 1)
ref += QLatin1Char('-') + QString::number(func->overloadNumber());
}
break;
case Node::Document:
break;
case Node::QmlPropertyGroup:
case Node::QmlProperty:
case Node::Property:
ref = node->name() + "-prop";
break;
case Node::QmlSignal:
ref = node->name() + "-signal";
break;
case Node::QmlSignalHandler:
ref = node->name() + "-signal-handler";
break;
case Node::QmlMethod:
func = static_cast(node);
ref = func->name() + "-method";
if (func->overloadNumber() != 1)
ref += QLatin1Char('-') + QString::number(func->overloadNumber());
break;
case Node::Variable:
ref = node->name() + "-var";
break;
}
return registerRef(ref);
}
#define DEBUG_ABSTRACT 0
/*!
This function is called for links, i.e. for words that
are marked with the qdoc link command. For autolinks
that are not marked with the qdoc link command, the
getAutoLink() function is called
It returns the string for a link found by using the data
in the \a atom to search the database. It also sets \a node
to point to the target node for that link. \a relative points
to the node holding the qdoc comment where the link command
was found.
*/
QString HtmlGenerator::getLink(const Atom *atom, const Node *relative, const Node** node)
{
const QString& t = atom->string();
if (t.at(0) == QChar('h')) {
if (t.startsWith("http:") || t.startsWith("https:"))
return t;
}
else if (t.at(0) == QChar('f')) {
if (t.startsWith("file:") || t.startsWith("ftp:"))
return t;
}
else if (t.at(0) == QChar('m')) {
if (t.startsWith("mailto:"))
return t;
}
QString ref;
*node = qdb_->findNode(atom, relative, ref);
if (!(*node))
return QString();
QString url = (*node)->url();
if (!url.isEmpty()) {
if (ref.isEmpty())
return url;
int hashtag = url.lastIndexOf(QChar('#'));
if (hashtag != -1)
url.truncate(hashtag);
return url + "#" + ref;
}
/*
Given that *node is not null, we now cconstruct a link
to the page that *node represents, and then if we found
a target on that page, we connect the target to the link
with '#'.
*/
QString link = linkForNode(*node, relative);
if (*node && (*node)->subType() == Node::Image)
link = "images/used-in-examples/" + link;
if (!ref.isEmpty())
link += QLatin1Char('#') + ref;
return link;
}
/*!
This function is called for autolinks, i.e. for words that
are not marked with the qdoc link command that qdoc has
reason to believe should be links. For links marked with
the qdoc link command, the getLink() function is called.
It returns the string for a link found by using the data
in the \a atom to search the database. It also sets \a node
to point to the target node for that link. \a relative points
to the node holding the qdoc comment where the link command
was found.
*/
QString HtmlGenerator::getAutoLink(const Atom *atom, const Node *relative, const Node** node)
{
QString ref;
QString link;
QString target = atom->string().trimmed();
*node = 0;
if (target.endsWith("()")) { // The target is a C++ function or QML method.
*node = qdb_->resolveFunctionTarget(target, relative);
}
else {
*node = qdb_->resolveTarget(target, relative);
if (!(*node)) {
*node = qdb_->findDocNodeByTitle(target);
}
if (!(*node)) {
*node = qdb_->findUnambiguousTarget(target, ref);
if (*node && !(*node)->url().isEmpty() && !ref.isEmpty()) {
QString final = (*node)->url() + "#" + ref;
return final;
}
}
}
if (!(*node))
return link; // empty
if (!(*node)->url().isEmpty())
return (*node)->url();
link = linkForNode(*node, relative);
if (!ref.isEmpty())
link += QLatin1Char('#') + ref;
return link;
}
/*!
Construct the link string for the \a node and return it.
The \a relative node is use to decide the link we are
generating is in the same file as the target. Note the
relative node can be 0, which pretty much guarantees
that the link and the target aren't in the same file.
*/
QString HtmlGenerator::linkForNode(const Node *node, const Node *relative)
{
if (node == 0 || node == relative)
return QString();
if (!node->url().isEmpty())
return node->url();
if (fileBase(node).isEmpty())
return QString();
if (node->access() == Node::Private)
return QString();
QString fn = fileName(node);
if (node && relative && node->parent() != relative) {
if (node->parent()->isQmlType() && relative->isQmlType()) {
if (node->parent()->isAbstract()) {
/*
This is a bit of a hack. What we discover with
the three 'if' statements immediately above,
is that node's parent is marked \qmlabstract
but the link appears in a qdoc comment for a
subclass of the node's parent. This means the
link should refer to the file for the relative
node, not the file for node.
*/
fn = fileName(relative);
}
}
}
QString link = fn;
if (!node->isInnerNode() || node->type() == Node::QmlPropertyGroup) {
QString ref = refForNode(node);
if (relative && fn == fileName(relative) && ref == refForNode(relative))
return QString();
link += QLatin1Char('#');
link += ref;
}
/*
If the output is going to subdirectories, then if the
two nodes will be output to different directories, then
the link must go up to the parent directory and then
back down into the other subdirectory.
*/
if (node && relative && (node != relative)) {
if (useOutputSubdirs() && !node->isExternalPage() &&
node->outputSubdirectory() != relative->outputSubdirectory()) {
link.prepend(QString("../" + node->outputSubdirectory() + QLatin1Char('/')));
}
}
return link;
}
void HtmlGenerator::generateFullName(const Node *apparentNode, const Node *relative, const Node *actualNode)
{
if (actualNode == 0)
actualNode = apparentNode;
out() << "status() != actualNode->status()) {
switch (actualNode->status()) {
case Node::Obsolete:
out() << "\" class=\"obsolete";
break;
case Node::Compat:
out() << "\" class=\"compat";
break;
default:
;
}
}
out() << "\">";
out() << protectEnc(apparentNode->fullName(relative));
out() << "";
}
void HtmlGenerator::generateDetailedMember(const Node *node,
const InnerNode *relative,
CodeMarker *marker)
{
const EnumNode *enume;
#ifdef GENERATE_MAC_REFS
generateMacRef(node, marker);
#endif
generateExtractionMark(node, MemberMark);
if (node->type() == Node::Enum
&& (enume = static_cast(node))->flagsType()) {
#ifdef GENERATE_MAC_REFS
generateMacRef(enume->flagsType(), marker);
#endif
out() << "
The " << protectEnc(enume->flagsType()->name())
<< " type is a typedef for "
<< "QFlags<"
<< protectEnc(enume->name())
<< ">. It stores an OR combination of "
<< protectEnc(enume->name())
<< " values.
\n";
}
}
generateAlsoList(node, marker);
generateExtractionMark(node, EndMark);
}
int HtmlGenerator::hOffset(const Node *node)
{
switch (node->type()) {
case Node::Namespace:
case Node::Class:
return 2;
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 QPair HtmlGenerator::anchorForNode(const Node *node)
{
QPair anchorPair;
anchorPair.first = Generator::fileName(node);
if (node->type() == Node::Document) {
const DocNode *docNode = static_cast(node);
anchorPair.second = docNode->title();
}
return anchorPair;
}
void HtmlGenerator::generateStatus(const Node *node, CodeMarker *marker)
{
Text text;
switch (node->status()) {
case Node::Obsolete:
if (node->isInnerNode())
Generator::generateStatus(node, marker);
break;
case Node::Compat:
if (node->isInnerNode()) {
text << Atom::ParaLeft
<< Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD)
<< "This "
<< typeString(node)
<< " is part of the Qt 3 support library."
<< Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD)
<< " It is provided to keep old source code working. "
<< "We strongly advise against "
<< "using it in new code. See ";
text << Atom(Atom::Link, "Porting to Qt 4");
text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
<< Atom(Atom::String, "Porting to Qt 4")
<< Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK)
<< " for more information."
<< Atom::ParaRight;
}
generateText(text, node, marker);
break;
default:
Generator::generateStatus(node, marker);
}
}
#ifdef GENERATE_MAC_REFS
/*
No longer valid.
*/
void HtmlGenerator::generateMacRef(const Node *node, CodeMarker *marker)
{
if (!pleaseGenerateMacRef || marker == 0)
return;
QStringList macRefs = marker->macRefsForNode(node);
foreach (const QString &macRef, macRefs)
out() << "\n";
}
#endif
void HtmlGenerator::beginLink(const QString &link, const Node *node, const Node *relative)
{
link_ = link;
if (link_.isEmpty()) {
if (showBrokenLinks)
out() << "";
}
else if (node == 0 ||
(relative != 0 && node->status() == relative->status())) {
out() << "";
}
else {
switch (node->status()) {
case Node::Obsolete:
out() << "";
break;
case Node::Compat:
out() << "";
break;
default:
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 for the \a section. Only used for
sections of QML element documentation.
*/
void HtmlGenerator::generateQmlSummary(const Section& section,
const Node *relative,
CodeMarker *marker)
{
if (!section.members.isEmpty()) {
out() << "
\n";
NodeList::ConstIterator m;
m = section.members.constBegin();
while (m != section.members.constEnd()) {
out() << "
";
generateQmlItem(*m,relative,marker,true);
if ((*m)->type() == Node::QmlPropertyGroup) {
const QmlPropertyGroupNode* qpgn = static_cast(*m);
if (!qpgn->childNodes().isEmpty()) {
NodeList::ConstIterator p = qpgn->childNodes().constBegin();
out() << "
\n";
while (p != qpgn->childNodes().constEnd()) {
if ((*p)->type() == Node::QmlProperty) {
out() << "
";
generateExtractionMark(node, EndMark);
}
/*!
Output the "Inherits" line for the QML element,
if there should be one.
*/
void HtmlGenerator::generateQmlInherits(QmlClassNode* qcn, CodeMarker* marker)
{
if (!qcn)
return;
QmlClassNode* base = qcn->qmlBaseNode();
while (base && base->isInternal()) {
base = base->qmlBaseNode();
}
if (base) {
Text text;
text << Atom::ParaLeft << "Inherits ";
text << Atom(Atom::LinkNode,CodeMarker::stringForNode(base));
text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK);
text << Atom(Atom::String, base->name());
text << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
text << Atom::ParaRight;
generateText(text, qcn, marker);
}
}
/*!
Output the "[Xxx instantiates the C++ class QmlGraphicsXxx]"
line for the QML element, if there should be one.
If there is no class node, or if the class node status
is set to Node::Internal, do nothing.
*/
void HtmlGenerator::generateQmlInstantiates(QmlClassNode* qcn, CodeMarker* marker)
{
ClassNode* cn = qcn->classNode();
if (cn && (cn->status() != Node::Internal)) {
Text text;
text << Atom::ParaLeft;
text << Atom(Atom::LinkNode,CodeMarker::stringForNode(qcn));
text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK);
QString name = qcn->name();
/*
Remove the "QML:" prefix, if present.
It shouldn't be present anymore.
*/
if (name.startsWith(QLatin1String("QML:")))
name = name.mid(4); // remove the "QML:" prefix
text << Atom(Atom::String, name);
text << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
text << " instantiates the C++ class ";
text << Atom(Atom::LinkNode,CodeMarker::stringForNode(cn));
text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK);
text << Atom(Atom::String, cn->name());
text << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
text << Atom::ParaRight;
generateText(text, qcn, marker);
}
}
/*!
Output the "[QmlGraphicsXxx is instantiated by QML Type Xxx]"
line for the class, if there should be one.
If there is no QML element, or if the class node status
is set to Node::Internal, do nothing.
*/
void HtmlGenerator::generateInstantiatedBy(ClassNode* cn, CodeMarker* marker)
{
if (cn && cn->status() != Node::Internal && cn->qmlElement() != 0) {
const QmlClassNode* qcn = cn->qmlElement();
Text text;
text << Atom::ParaLeft;
text << Atom(Atom::LinkNode,CodeMarker::stringForNode(cn));
text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK);
text << Atom(Atom::String, cn->name());
text << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
text << " is instantiated by QML Type ";
text << Atom(Atom::LinkNode,CodeMarker::stringForNode(qcn));
text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK);
text << Atom(Atom::String, qcn->name());
text << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
text << Atom::ParaRight;
generateText(text, cn, marker);
}
}
void HtmlGenerator::generateExtractionMark(const Node *node, ExtractionMarkType markType)
{
if (markType != EndMark) {
out() << "\n";
} else {
out() << "\n";
}
}
/*!
This function outputs one or more manifest files in XML.
They are used by Creator.
*/
void HtmlGenerator::generateManifestFiles()
{
generateManifestFile("examples", "example");
generateManifestFile("demos", "demo");
qdb_->exampleNodeMap().clear();
manifestMetaContent.clear();
}
/*!
This function is called by generateManifestFiles(), once
for each manifest file to be generated. \a manifest is the
type of manifest file.
*/
void HtmlGenerator::generateManifestFile(QString manifest, QString element)
{
ExampleNodeMap& exampleNodeMap = qdb_->exampleNodeMap();
if (exampleNodeMap.isEmpty())
return;
QString fileName = manifest +"-manifest.xml";
QFile file(outputDir() + QLatin1Char('/') + fileName);
if (!file.open(QFile::WriteOnly | QFile::Text))
return ;
bool demos = false;
if (manifest == "demos")
demos = true;
bool proceed = false;
ExampleNodeMap::Iterator i = exampleNodeMap.begin();
while (i != exampleNodeMap.end()) {
const ExampleNode* en = i.value();
if (demos) {
if (en->name().startsWith("demos")) {
proceed = true;
break;
}
}
else if (!en->name().startsWith("demos")) {
proceed = true;
break;
}
++i;
}
if (!proceed)
return;
QXmlStreamWriter writer(&file);
writer.setAutoFormatting(true);
writer.writeStartDocument();
writer.writeStartElement("instructionals");
writer.writeAttribute("module", project);
writer.writeStartElement(manifest);
i = exampleNodeMap.begin();
while (i != exampleNodeMap.end()) {
const ExampleNode* en = i.value();
if (demos) {
if (!en->name().startsWith("demos")) {
++i;
continue;
}
}
else if (en->name().startsWith("demos")) {
++i;
continue;
}
writer.writeStartElement(element);
writer.writeAttribute("name", en->title());
QString docUrl = manifestDir + fileBase(en) + ".html";
writer.writeAttribute("docUrl", docUrl);
QStringList proFiles;
foreach (const Node* child, en->childNodes()) {
if (child->subType() == Node::File) {
QString file = child->name();
if (file.endsWith(".pro") || file.endsWith(".qmlproject")) {
proFiles << file;
}
}
}
if (!proFiles.isEmpty()) {
if (proFiles.size() == 1) {
writer.writeAttribute("projectPath", examplesPath + proFiles[0]);
}
else {
QString exampleName = en->name().split('/').last();
bool proWithExampleNameFound = false;
for (int j = 0; j < proFiles.size(); j++)
{
if (proFiles[j].endsWith(QStringLiteral("%1/%1.pro").arg(exampleName))
|| proFiles[j].endsWith(QStringLiteral("%1/%1.qmlproject").arg(exampleName))) {
writer.writeAttribute("projectPath", examplesPath + proFiles[j]);
proWithExampleNameFound = true;
break;
}
}
if (!proWithExampleNameFound)
writer.writeAttribute("projectPath", examplesPath + proFiles[0]);
}
}
if (!en->imageFileName().isEmpty())
writer.writeAttribute("imageUrl", manifestDir + en->imageFileName());
QString fullName = project + QLatin1Char('/') + en->title();
QSet tags;
for (int idx=0; idx < manifestMetaContent.size(); ++idx) {
foreach (const QString &name, manifestMetaContent[idx].names) {
bool match = false;
int wildcard = name.indexOf(QChar('*'));
switch (wildcard) {
case -1: // no wildcard, exact match
match = (fullName == name);
break;
case 0: // '*' matches all
match = true;
break;
default: // match with wildcard at the end
match = fullName.startsWith(name.left(wildcard));
}
if (match) {
tags += manifestMetaContent[idx].tags;
foreach (const QString &attr, manifestMetaContent[idx].attributes) {
QLatin1Char div(':');
QStringList attrList = attr.split(div);
if (attrList.count() == 1)
attrList.append(QStringLiteral("true"));
QString attrName = attrList.takeFirst();
writer.writeAttribute(attrName, attrList.join(div));
}
}
}
}
writer.writeStartElement("description");
Text brief = en->doc().briefText();
if (!brief.isEmpty())
writer.writeCDATA(brief.toString());
else
writer.writeCDATA(QString("No description available"));
writer.writeEndElement(); // description
// Add words from module name as tags (QtQuickControls -> qt,quick,controls)
QRegExp re("([A-Z]+[a-z0-9]*)");
int pos = 0;
while ((pos = re.indexIn(project, pos)) != -1) {
tags << re.cap(1).toLower();
pos += re.matchedLength();
}
tags += QSet::fromList(en->title().toLower().split(QLatin1Char(' ')));
if (!tags.isEmpty()) {
writer.writeStartElement("tags");
bool wrote_one = false;
// Exclude invalid and common words
foreach (QString tag, tags) {
if (tag.length() < 2)
continue;
if (tag.at(0).isDigit())
continue;
if (tag.at(0) == '-')
continue;
if (tag == QStringLiteral("qt"))
continue;
if (tag.startsWith("example"))
continue;
if (tag.startsWith("chapter"))
continue;
if (tag.endsWith(QLatin1Char(':')))
tag.chop(1);
if (wrote_one)
writer.writeCharacters(",");
writer.writeCharacters(tag);
wrote_one = true;
}
writer.writeEndElement(); // tags
}
QString ename = en->name().mid(en->name().lastIndexOf('/')+1);
QSet usedNames;
foreach (const Node* child, en->childNodes()) {
if (child->subType() == Node::File) {
QString file = child->name();
QString fileName = file.mid(file.lastIndexOf('/')+1);
QString baseName = fileName;
if ((fileName.count(QChar('.')) > 0) &&
(fileName.endsWith(".cpp") ||
fileName.endsWith(".h") ||
fileName.endsWith(".qml")))
baseName.truncate(baseName.lastIndexOf(QChar('.')));
if (baseName.compare(ename, Qt::CaseInsensitive) == 0) {
if (!usedNames.contains(fileName)) {
writer.writeStartElement("fileToOpen");
writer.writeCharacters(examplesPath + file);
writer.writeEndElement(); // fileToOpen
usedNames.insert(fileName);
}
}
else if (fileName.toLower().endsWith("main.cpp") ||
fileName.toLower().endsWith("main.qml")) {
if (!usedNames.contains(fileName)) {
writer.writeStartElement("fileToOpen");
writer.writeCharacters(examplesPath + file);
writer.writeEndElement(); // fileToOpen
usedNames.insert(fileName);
}
}
}
}
writer.writeEndElement(); // example
++i;
}
writer.writeEndElement(); // examples
writer.writeEndElement(); // instructionals
writer.writeEndDocument();
file.close();
}
/*!
Reads metacontent - additional attributes and tags to apply
when generating manifest files, read from config. Takes the
configuration class \a config as a parameter.
*/
void HtmlGenerator::readManifestMetaContent(const Config &config)
{
QStringList names = config.getStringList(CONFIG_MANIFESTMETA + Config::dot + QStringLiteral("filters"));
foreach (const QString &manifest, names) {
ManifestMetaFilter filter;
QString prefix = CONFIG_MANIFESTMETA + Config::dot + manifest + Config::dot;
filter.names = config.getStringSet(prefix + QStringLiteral("names"));
filter.attributes = config.getStringSet(prefix + QStringLiteral("attributes"));
filter.tags = config.getStringSet(prefix + QStringLiteral("tags"));
manifestMetaContent.append(filter);
}
}
/*!
Find global entities that have documentation but no
\e{relates} comand. Report these as errors if they
are not also marked \e {internal}.
type: Class
type: Namespace
subtype: Example
subtype: External page
subtype: Group
subtype: Header file
subtype: Module
subtype: Page
subtype: QML basic type
subtype: QML class
subtype: QML module
*/
void HtmlGenerator::reportOrphans(const InnerNode* parent)
{
const NodeList& children = parent->childNodes();
if (children.size() == 0)
return;
bool related;
QString message;
for (int i=0; iisInternal() || child->doc().isEmpty())
continue;
if (child->relates()) {
related = true;
message = child->relates()->name();
}
else {
related = false;
message = "has documentation but no \\relates command";
}
switch (child->type()) {
case Node::Namespace:
break;
case Node::Class:
break;
case Node::QmlType:
break;
case Node::QmlBasicType:
break;
case Node::Group:
break;
case Node::Module:
break;
case Node::QmlModule:
break;
case Node::Document:
switch (child->subType()) {
case Node::Example:
break;
case Node::HeaderFile:
break;
case Node::File:
break;
case Node::Image:
break;
case Node::Page:
break;
case Node::ExternalPage:
break;
case Node::Collision:
break;
default:
break;
}
break;
case Node::Enum:
if (!related)
child->location().warning(tr("Global enum, %1, %2").arg(child->name()).arg(message));
break;
case Node::Typedef:
if (!related)
child->location().warning(tr("Global typedef, %1, %2").arg(child->name()).arg(message));
break;
case Node::Function:
if (!related) {
const FunctionNode* fn = static_cast(child);
if (fn->isMacro())
child->location().warning(tr("Global macro, %1, %2").arg(child->name()).arg(message));
else
child->location().warning(tr("Global function, %1(), %2").arg(child->name()).arg(message));
}
break;
case Node::Property:
break;
case Node::Variable:
if (!related)
child->location().warning(tr("Global variable, %1, %2").arg(child->name()).arg(message));
break;
case Node::QmlPropertyGroup:
break;
case Node::QmlProperty:
if (!related)
child->location().warning(tr("Global QML property, %1, %2").arg(child->name()).arg(message));
break;
case Node::QmlSignal:
if (!related)
child->location().warning(tr("Global QML, signal, %1 %2").arg(child->name()).arg(message));
break;
case Node::QmlSignalHandler:
if (!related)
child->location().warning(tr("Global QML signal handler, %1, %2").arg(child->name()).arg(message));
break;
case Node::QmlMethod:
if (!related)
child->location().warning(tr("Global QML method, %1, %2").arg(child->name()).arg(message));
break;
default:
break;
}
}
}
/*!
Returns a reference to the XML stream writer currently in use.
There is one XML stream writer open for each XML file being
written, and they are kept on a stack. The one on top of the
stack is the one being written to at the moment. In the HTML
output generator, it is perhaps impossible for there to ever
be more than one writer open.
*/
QXmlStreamWriter& HtmlGenerator::xmlWriter()
{
return *xmlWriterStack.top();
}
/*!
This function is only called for writing ditamaps.
Calls beginSubPage() in the base class to open the file.
Then creates a new XML stream writer using the IO device
from opened file and pushes the XML writer onto a stackj.
Creates the file named \a fileName in the output directory.
Attaches a QTextStream to the created file, which is written
to all over the place using out(). Finally, it sets some
parameters in the XML writer and calls writeStartDocument().
It also ensures that a GUID map is created for the output file.
*/
void HtmlGenerator::beginDitamapPage(const InnerNode* node, const QString& fileName)
{
Generator::beginSubPage(node,fileName);
QXmlStreamWriter* writer = new QXmlStreamWriter(out().device());
xmlWriterStack.push(writer);
writer->setAutoFormatting(true);
writer->setAutoFormattingIndent(4);
writer->writeStartDocument();
}
/*!
This function is only called for writing ditamaps.
Calls writeEndDocument() and then pops the XML stream writer
off the stack and deletes it. Then it calls endSubPage() in
the base class to close the device.
*/
void HtmlGenerator::endDitamapPage()
{
xmlWriter().writeEndDocument();
delete xmlWriterStack.pop();
Generator::endSubPage();
}
/*!
This function is only called for writing ditamaps.
Creates the DITA map from the topicrefs in \a node,
which is a DitaMapNode.
*/
void HtmlGenerator::writeDitaMap(const DitaMapNode* node)
{
beginDitamapPage(node,node->name());
QString doctype = "";
xmlWriter().writeDTD(doctype);
xmlWriter().writeStartElement("map");
xmlWriter().writeStartElement("topicmeta");
xmlWriter().writeStartElement("shortdesc");
xmlWriter().writeCharacters(node->title());
xmlWriter().writeEndElement(); //
xmlWriter().writeEndElement(); //
DitaRefList map = node->map();
writeDitaRefs(map);
endDitamapPage();
}
/*!
Write the \a ditarefs to the current output file.
*/
void HtmlGenerator::writeDitaRefs(const DitaRefList& ditarefs)
{
foreach (DitaRef* t, ditarefs) {
if (t->isMapRef())
xmlWriter().writeStartElement("mapref");
else
xmlWriter().writeStartElement("topicref");
xmlWriter().writeAttribute("navtitle",t->navtitle());
if (t->href().isEmpty()) {
const DocNode* fn = qdb_->findDocNodeByTitle(t->navtitle());
if (fn)
xmlWriter().writeAttribute("href",fileName(fn));
}
else
xmlWriter().writeAttribute("href",t->href());
if (t->subrefs() && !t->subrefs()->isEmpty())
writeDitaRefs(*(t->subrefs()));
xmlWriter().writeEndElement(); // or
}
}
QT_END_NAMESPACE