diff options
Diffstat (limited to 'src/qdoc/htmlgenerator.cpp')
-rw-r--r-- | src/qdoc/htmlgenerator.cpp | 4898 |
1 files changed, 4898 insertions, 0 deletions
diff --git a/src/qdoc/htmlgenerator.cpp b/src/qdoc/htmlgenerator.cpp new file mode 100644 index 000000000..3c03a0801 --- /dev/null +++ b/src/qdoc/htmlgenerator.cpp @@ -0,0 +1,4898 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/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 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $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 <ctype.h> +#include <qdebug.h> +#include <qlist.h> +#include <qiterator.h> +#include <qtextcodec.h> +#include <quuid.h> +#include <qmap.h> + +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 += QLatin1String("<a href=\""); + *res += linkTarget; + *res += QLatin1String("\">"); + *res += nestedStuff; + *res += QLatin1String("</a>"); + } + 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; + helpProjectWriter = 0; + } +} + +/*! + 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, "<b>", "</b>" }, + { ATOM_FORMATTING_INDEX, "<!--", "-->" }, + { ATOM_FORMATTING_ITALIC, "<i>", "</i>" }, + { ATOM_FORMATTING_PARAMETER, "<i>", "</i>" }, + { ATOM_FORMATTING_SUBSCRIPT, "<sub>", "</sub>" }, + { ATOM_FORMATTING_SUPERSCRIPT, "<sup>", "</sup>" }, + { ATOM_FORMATTING_TELETYPE, "<code>", "</code>" }, // <tt> tag is not supported in HTML5 + { ATOM_FORMATTING_UICONTROL, "<b>", "</b>" }, + { ATOM_FORMATTING_UNDERLINE, "<u>", "</u>" }, + { 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); + 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<QString> editionNames = config.subVars(CONFIG_EDITION); + QSet<QString>::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; + } + + codeIndent = config.getInt(CONFIG_CODEINDENT); // QTBUG-27798 + codePrefix = config.getString(CONFIG_CODEPREFIX); + codeSuffix = config.getString(CONFIG_CODESUFFIX); + + /* + The help file write should be allocated once and only once + per qdoc execution. + */ + if (helpProjectWriter) + helpProjectWriter->reset(config, project.toLower() + ".qhp", this); + else + 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 = QLatin1String("qthelp://") + config.getString(prefix + QLatin1String("namespace")); + manifestDir += QLatin1Char('/') + config.getString(prefix + QLatin1String("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"; +} + +/*! + Generate targets for any \keyword commands that were seen + in the qdoc comment for the \a node. + */ +void HtmlGenerator::generateKeywordAnchors(const Node* node) +{ + Q_UNUSED(node); + // Disabled: keywords always link to the top of the QDoc + // comment they appear in, and do not use a dedicated anchor. +#if 0 + if (!node->doc().isEmpty()) { + const QList<Atom*>& keywords = node->doc().keywords(); + foreach (Atom* a, keywords) { + out() << QLatin1String("<a name=\"") << Doc::canonicalTitle(a->string()) << QLatin1String("\"></a>"); + } + } +#endif +} + +/*! + If qdoc is in the \c {-prepare} phase, traverse the primary + tree to generate the index file for the current module. + + If qdoc is in the \c {-generate} phase, traverse the primary + tree to generate all the HTML documentation for the current + module. Then generate the help file and the tag file. + */ +void HtmlGenerator::generateDocs() +{ + Node* qflags = qdb_->findClassNode(QStringList("QFlags")); + if (qflags) + qflagsHref_ = linkForNode(qflags,0); + if (!preparing()) + Generator::generateDocs(); + if (Generator::generating() && Generator::writeQaPages()) + generateQAPage(); + + if (!generating()) { + QString fileBase = project.toLower().simplified().replace(QLatin1Char(' '), QLatin1Char('-')); + qdb_->generateIndex(outputDir() + QLatin1Char('/') + fileBase + ".index", + projectUrl, + projectDescription, + this, + true); + } + + if (!preparing()) { + helpProjectWriter->generate(); + generateManifestFiles(); + /* + Generate the XML tag file, if it was requested. + */ + qdb_->generateTagFile(tagFile_, this); + } +} + +/*! + Output the module's Quality Assurance page. + */ +void HtmlGenerator::generateQAPage() +{ + NamespaceNode* node = qdb_->primaryTreeRoot(); + beginSubPage(node, "aaa-" + defaultModuleName().toLower() + "-qa-page.html"); + CodeMarker* marker = CodeMarker::markerForFileName(node->location().filePath()); + QString title = "Quality Assurance Page for " + defaultModuleName(); + QString t = "Quality assurance information for checking the " + defaultModuleName() + " documentation."; + generateHeader(title, node, marker); + generateTitle(title, Text() << t, LargeSubTitle, node, marker); + + QStringList strings; + QVector<int> counts; + QString depends = qdb_->getLinkCounts(strings, counts); + if (!strings.isEmpty()) { + t = "Intermodule Link Counts"; + QString ref = registerRef(t); + out() << "<a name=\"" << ref << "\"></a>" << divNavTop << '\n'; + out() << "<h2 id=\"" << ref << "\">" << protectEnc(t) << "</h2>\n"; + out() << "<table class=\"valuelist\"><tr valign=\"top\" " + << "class=\"even\"><th class=\"tblConst\">Destination Module</th>" + << "<th class=\"tblval\">Link Count</th></tr>\n"; + QString fileName; + for (int i = 0; i< strings.size(); ++i) { + fileName = generateLinksToLinksPage(strings.at(i), marker); + out() << "<tr><td class=\"topAlign\"><tt>" + << "<a href=\"" << fileName << "\">" + << strings.at(i) << "</a>" + << "</tt></td><td class=\"topAlign\"><tt>" << counts.at(i) + << "</tt></td></tr>\n"; + } + int count = 0; + fileName = generateLinksToBrokenLinksPage(marker, count); + if (count != 0) { + out() << "<tr><td class=\"topAlign\"><tt>" + << "<a href=\"" << fileName << "\">" + << "Broken Links" << "</a>" + << "</tt></td><td class=\"topAlign\"><tt>" << count + << "</tt></td></tr>\n"; + + } + + out() << "</table>\n"; + t = "The Optimal \"depends\" Variable"; + out() << "<h2>" << protectEnc(t) << "</h2>\n"; + t = "Consider replacing the depends variable in " + defaultModuleName().toLower() + + ".qdocconf with this one, if the two are not identical:"; + out() << "<p>" << protectEnc(t) << "</p>\n"; + out() << "<p>" << protectEnc(depends) << "</p>\n"; + } + generateFooter(); + endSubPage(); +} + +/*! + This function writes an html file containing a list of + links to links that originate in the current module and + go to targets in the specified \a module. The \a marker + is used for the same thing the marker is always used for. + */ +QString HtmlGenerator::generateLinksToLinksPage(const QString& module, CodeMarker* marker) +{ + NamespaceNode* node = qdb_->primaryTreeRoot(); + QString fileName = "aaa-links-to-" + module + ".html"; + beginSubPage(node, fileName); + QString title = "Links from " + defaultModuleName() + " to " + module; + generateHeader(title, node, marker); + generateTitle(title, Text(), SmallSubTitle, node, marker); + out() << "<p>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.</p>\n"; + TargetList* tlist = qdb_->getTargetList(module); + if (tlist) { + out() << "<table class=\"valuelist\"><tr valign=\"top\" class=\"odd\"><th class=\"tblConst\">Link to link...</th><th class=\"tblval\">In file...</th><th class=\"tbldscr\">Somewhere after line number...</th></tr>\n"; + foreach (TargetLoc* t, *tlist) { + // e.g.: <a name="link-8421"></a><a href="layout.html">Layout Management</a> + out() << "<tr><td class=\"topAlign\">"; + out() << "<a href=\"" << t->fileName_ << "#" << t->target_ << "\">"; + out() << t->text_ << "</a></td>"; + out() << "<td class=\"topAlign\">"; + QString f = t->loc_->doc().location().filePath(); + out() << f << "</td>"; + out() << "<td class=\"topAlign\">"; + out() << t->loc_->doc().location().lineNo() << "</td></tr>\n"; + } + out() << "</table>\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() << "<p>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.</p>\n"; + out() << "<table class=\"valuelist\"><tr valign=\"top\" class=\"odd\"><th class=\"tblConst\">Link to broken link...</th><th class=\"tblval\">In file...</th><th class=\"tbldscr\">Somewhere after line number...</th></tr>\n"; + foreach (TargetLoc* t, *tlist) { + // e.g.: <a name="link-8421"></a><a href="layout.html">Layout Management</a> + out() << "<tr><td class=\"topAlign\">"; + out() << "<a href=\"" << t->fileName_ << "#" << t->target_ << "\">"; + out() << t->text_ << "</a></td>"; + out() << "<td class=\"topAlign\">"; + QString f = t->loc_->doc().location().filePath(); + out() << f << "</td>"; + out() << "<td class=\"topAlign\">"; + out() << t->loc_->doc().location().lineNo() << "</td></tr>\n"; + } + out() << "</table>\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() << "<a id=\"" << Doc::canonicalTitle(target) << "\" class=\"qa-mark\"></a>"; + } + beginLink(link, node, relative); + generateLink(atom, marker); + endLink(); + } + } + else { + out() << protectEnc(atom->string()); + } + break; + case Atom::BaseName: + break; + case Atom::BriefLeft: + // Do not output the brief for QML nodes, doc nodes or collections + // (groups, modules, qml module nodes) + if (relative->isQmlType() || + relative->isDocumentNode() || + relative->isCollectionNode() || + relative->isJsType()) { + skipAhead = skipAtoms(atom, Atom::BriefRight); + break; + } + + out() << "<p>"; + if (relative->type() == Node::Property || + relative->type() == Node::Variable) { + QString str; + atom = atom->next(); + while (atom != 0 && atom->type() != Atom::BriefRight) { + if (atom->type() == Atom::String || + atom->type() == Atom::AutoLink) + str += atom->string(); + skipAhead++; + atom = atom->next(); + } + str[0] = str[0].toLower(); + if (str.endsWith(QLatin1Char('.'))) + str.truncate(str.length() - 1); + out() << "This "; + if (relative->type() == Node::Property) + out() << "property"; + else + out() << "variable"; + QStringList words = str.split(QLatin1Char(' ')); + if (!(words.first() == QLatin1String("contains") || words.first() == QLatin1String("specifies") + || words.first() == QLatin1String("describes") || words.first() == QLatin1String("defines") + || words.first() == QLatin1String("holds") || words.first() == QLatin1String("determines"))) + out() << " holds "; + else + out() << ' '; + out() << str << '.'; + } + break; + case Atom::BriefRight: + if (!relative->isDocumentNode()) + out() << "</p>\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() << "<p class=\"figCaption\">"; + in_para = true; + break; + case Atom::CaptionRight: + endLink(); + if (in_para) { + out() << "</p>\n"; + in_para = false; + } + break; + case Atom::Code: + out() << "<pre class=\"cpp\">" + << trimmedTrailing(highlightedCode(indent(codeIndent,atom->string()),relative), codePrefix, codeSuffix) + << "</pre>\n"; + break; + case Atom::Qml: + out() << "<pre class=\"qml\">" + << trimmedTrailing(highlightedCode(indent(codeIndent,atom->string()),relative), codePrefix, codeSuffix) + << "</pre>\n"; + break; + case Atom::JavaScript: + out() << "<pre class=\"js\">" + << trimmedTrailing(highlightedCode(indent(codeIndent,atom->string()),relative), codePrefix, codeSuffix) + << "</pre>\n"; + break; + case Atom::CodeNew: + out() << "<p>you can rewrite it as</p>\n" + << "<pre class=\"cpp\">" + << trimmedTrailing(highlightedCode(indent(codeIndent,atom->string()),relative), codePrefix, codeSuffix) + << "</pre>\n"; + break; + case Atom::CodeOld: + out() << "<p>For example, if you have code like</p>\n"; + // fallthrough + case Atom::CodeBad: + out() << "<pre class=\"cpp\">" + << trimmedTrailing(protectEnc(plainCode(indent(codeIndent,atom->string()))), codePrefix, codeSuffix) + << "</pre>\n"; + break; + case Atom::DivLeft: + out() << "<div"; + if (!atom->string().isEmpty()) + out() << ' ' << atom->string(); + out() << '>'; + break; + case Atom::DivRight: + out() << "</div>"; + break; + case Atom::FootnoteLeft: + // ### For now + if (in_para) { + out() << "</p>\n"; + in_para = false; + } + out() << "<!-- "; + break; + case Atom::FootnoteRight: + // ### For now + out() << "-->"; + break; + case Atom::FormatElse: + case Atom::FormatEndif: + case Atom::FormatIf: + break; + case Atom::FormattingLeft: + if (atom->string().startsWith("span ")) { + out() << '<' + atom->string() << '>'; + } + else + out() << formattingLeftMap()[atom->string()]; + if (atom->string() == ATOM_FORMATTING_PARAMETER) { + if (atom->next() != 0 && atom->next()->type() == Atom::String) { + QRegExp subscriptRegExp("([a-z]+)_([0-9n])"); + if (subscriptRegExp.exactMatch(atom->next()->string())) { + out() << subscriptRegExp.cap(1) << "<sub>" + << subscriptRegExp.cap(2) << "</sub>"; + skipAhead = 1; + } + } + } + break; + case Atom::FormattingRight: + if (atom->string() == ATOM_FORMATTING_LINK) { + endLink(); + } + else if (atom->string().startsWith("span ")) { + out() << "</span>"; + } + else { + out() << formattingRightMap()[atom->string()]; + } + break; + case Atom::AnnotatedList: + { + const CollectionNode* cn = qdb_->getCollectionNode(atom->string(), Node::DOC); + if (cn) + generateList(cn, marker, atom->string()); + } + break; + case Atom::GeneratedList: + if (atom->string() == QLatin1String("annotatedclasses")) { + generateAnnotatedList(relative, marker, qdb_->getCppClasses()); + } + else if (atom->string() == QLatin1String("classes")) { + generateCompactList(Generic, relative, qdb_->getCppClasses(), true, QStringLiteral("")); + } + else if (atom->string().contains("classes ")) { + QString rootName = atom->string().mid(atom->string().indexOf("classes") + 7).trimmed(); + generateCompactList(Generic, relative, qdb_->getCppClasses(), true, rootName); + } + else if (atom->string() == QLatin1String("qmlbasictypes")) { + generateCompactList(Generic, relative, qdb_->getQmlBasicTypes(), true, QStringLiteral("")); + } + else if (atom->string() == QLatin1String("qmltypes")) { + generateCompactList(Generic, relative, qdb_->getQmlTypes(), true, QStringLiteral("")); + } + else if ((idx = atom->string().indexOf(QStringLiteral("bymodule"))) != -1) { + QString moduleName = atom->string().mid(idx + 8).trimmed(); + Node::Genus genus = Node::CPP; + if (atom->string().startsWith(QLatin1String("qml"))) + genus = Node::QML; + else if (atom->string().startsWith(QLatin1String("js"))) + genus = Node::JS; + QDocDatabase* qdb = QDocDatabase::qdocDB(); + const CollectionNode* cn = qdb->getCollectionNode(moduleName, genus); + if (cn) { + if (genus == Node::CPP) { + NodeMap m; + cn->getMemberClasses(m); + if (!m.isEmpty()) { + generateAnnotatedList(relative, marker, m); + } + } + else + generateAnnotatedList(relative, marker, cn->members()); + } + } + else if (atom->string() == QLatin1String("classhierarchy")) { + generateClassHierarchy(relative, qdb_->getCppClasses()); + } + else if (atom->string() == QLatin1String("obsoleteclasses")) { + generateCompactList(Generic, relative, qdb_->getObsoleteClasses(), false, QStringLiteral("Q")); + } + else if (atom->string() == QLatin1String("obsoleteqmltypes")) { + generateCompactList(Generic, relative, qdb_->getObsoleteQmlTypes(), false, QStringLiteral("")); + } + else if (atom->string() == QLatin1String("obsoletecppmembers")) { + generateCompactList(Obsolete, relative, qdb_->getClassesWithObsoleteMembers(), false, QStringLiteral("Q")); + } + else if (atom->string() == QLatin1String("obsoleteqmlmembers")) { + generateCompactList(Obsolete, relative, qdb_->getQmlTypesWithObsoleteMembers(), false, QStringLiteral("")); + } + else if (atom->string() == QLatin1String("functionindex")) { + generateFunctionIndex(relative); + } + else if (atom->string() == QLatin1String("legalese")) { + generateLegaleseList(relative, marker); + } + else if (atom->string() == QLatin1String("overviews")) { + generateList(relative, marker, "overviews"); + } + else if (atom->string() == QLatin1String("cpp-modules")) { + generateList(relative, marker, "cpp-modules"); + } + else if (atom->string() == QLatin1String("qml-modules")) { + generateList(relative, marker, "qml-modules"); + } + else if (atom->string() == QLatin1String("namespaces")) { + generateAnnotatedList(relative, marker, qdb_->getNamespaces()); + } + else if (atom->string() == QLatin1String("related")) { + generateList(relative, marker, "related"); + } +#if 0 + /* + This is not used in Qt5, as of 10/02/2014 + Remove permanently if it is not missed. + */ + else if (atom->string() == QLatin1String("relatedinline")) { + const DocumentNode *dn = static_cast<const DocumentNode *>(relative); + if (dn && !dn->members().isEmpty()) { + // Reverse the list into the original scan order. + // Should be sorted. But on what? It may not be a + // regular class or page definition. + QList<const Node *> list; + foreach (const Node *node, dn->members()) + list.prepend(node); + foreach (const Node *node, list) + generateBody(node, marker); + } + } +#endif + break; + case Atom::SinceList: + { + const NodeMultiMap& nsmap = qdb_->getSinceMap(atom->string()); + const NodeMap& ncmap = qdb_->getClassMap(atom->string()); + const NodeMap& nqcmap = qdb_->getQmlTypeMap(atom->string()); + + if (!nsmap.isEmpty()) { + QList<Section> sections; + QList<Section>::ConstIterator s; + + for (int i=0; i<LastSinceType; ++i) + sections.append(Section(sinceTitle(i),QString(),QString(),QString())); + + NodeMultiMap::const_iterator n = nsmap.constBegin(); + while (n != nsmap.constEnd()) { + Node* node = n.value(); + switch (node->type()) { + case Node::QmlType: + sections[QmlClass].appendMember(node); + break; + case Node::Namespace: + sections[Namespace].appendMember(node); + break; + case Node::Class: + sections[Class].appendMember(node); + break; + case Node::Enum: + sections[Enum].appendMember(node); + break; + case Node::Typedef: + sections[Typedef].appendMember(node); + break; + case Node::Function: { + const FunctionNode* fn = static_cast<const FunctionNode*>(node); + if (fn->isMacro()) + sections[Macro].appendMember(node); + else { + Node* p = fn->parent(); + if (p) { + if (p->type() == Node::Class) + sections[MemberFunction].appendMember(node); + else if (p->type() == Node::Namespace) { + if (p->name().isEmpty()) + sections[GlobalFunction].appendMember(node); + else + sections[NamespaceFunction].appendMember(node); + } + else + sections[GlobalFunction].appendMember(node); + } + else + sections[GlobalFunction].appendMember(node); + } + break; + } + case Node::Property: + sections[Property].appendMember(node); + break; + case Node::Variable: + sections[Variable].appendMember(node); + break; + case Node::QmlProperty: + sections[QmlProperty].appendMember(node); + break; + case Node::QmlSignal: + sections[QmlSignal].appendMember(node); + break; + case Node::QmlSignalHandler: + sections[QmlSignalHandler].appendMember(node); + break; + case Node::QmlMethod: + sections[QmlMethod].appendMember(node); + break; + default: + break; + } + ++n; + } + + out() << "<ul>\n"; + s = sections.constBegin(); + while (s != sections.constEnd()) { + if (!(*s).members.isEmpty()) { + + out() << "<li>" + << "<a href=\"#" + << Doc::canonicalTitle((*s).name) + << "\">" + << (*s).name + << "</a></li>\n"; + } + ++s; + } + out() << "</ul>\n"; + + int idx = 0; + s = sections.constBegin(); + while (s != sections.constEnd()) { + if (!(*s).members.isEmpty()) { + out() << "<a name=\"" + << Doc::canonicalTitle((*s).name) + << "\"></a>\n"; + out() << "<h3>" << protectEnc((*s).name) << "</h3>\n"; + if (idx == Class) + generateCompactList(Generic, 0, ncmap, false, QStringLiteral("Q")); + else if (idx == QmlClass) + generateCompactList(Generic, 0, nqcmap, false, QStringLiteral("")); + else if (idx == MemberFunction) { + ParentMaps parentmaps; + ParentMaps::iterator pmap; + NodeList::const_iterator i = s->members.constBegin(); + while (i != s->members.constEnd()) { + Node* p = (*i)->parent(); + pmap = parentmaps.find(p); + if (pmap == parentmaps.end()) + pmap = parentmaps.insert(p,NodeMultiMap()); + pmap->insert((*i)->name(),(*i)); + ++i; + } + pmap = parentmaps.begin(); + while (pmap != parentmaps.end()) { + NodeList nlist = pmap->values(); + out() << "<p>Class "; + + out() << "<a href=\"" + << linkForNode(pmap.key(), 0) + << "\">"; + QStringList pieces = pmap.key()->fullName().split("::"); + out() << protectEnc(pieces.last()); + out() << "</a>" << ":</p>\n"; + + generateSection(nlist, 0, marker, CodeMarker::Summary); + out() << "<br/>"; + ++pmap; + } + } + else + generateSection(s->members, 0, marker, CodeMarker::Summary); + } + ++idx; + ++s; + } + } + } + break; + case Atom::BR: + out() << "<br />\n"; + break; + case Atom::HR: + out() << "<hr />\n"; + break; + case Atom::Image: + case Atom::InlineImage: + { + QString fileName = imageFileName(relative, atom->string()); + QString text; + if (atom->next() != 0) + text = atom->next()->string(); + if (atom->type() == Atom::Image) + out() << "<p class=\"centerAlign\">"; + if (fileName.isEmpty()) { + relative->location().warning(tr("Missing image: %1").arg(protectEnc(atom->string()))); + out() << "<font color=\"red\">[Missing image " + << protectEnc(atom->string()) << "]</font>"; + } + else { + QString prefix; + out() << "<img src=\"" << protectEnc(prefix + fileName) << '"'; + if (!text.isEmpty()) + out() << " alt=\"" << protectEnc(text) << '"'; + else + out() << " alt=\"\""; + out() << " />"; + helpProjectWriter->addExtraFile(fileName); + if (relative->isExample()) { + const ExampleNode* cen = static_cast<const ExampleNode*>(relative); + if (cen->imageFileName().isEmpty()) { + ExampleNode* en = const_cast<ExampleNode*>(cen); + en->setImageFileName(fileName); + } + } + } + if (atom->type() == Atom::Image) + out() << "</p>"; + } + break; + case Atom::ImageText: + break; + case Atom::ImportantLeft: + out() << "<p>"; + out() << formattingLeftMap()[ATOM_FORMATTING_BOLD]; + out() << "Important: "; + out() << formattingRightMap()[ATOM_FORMATTING_BOLD]; + break; + case Atom::ImportantRight: + out() << "</p>"; + break; + case Atom::NoteLeft: + out() << "<p>"; + out() << formattingLeftMap()[ATOM_FORMATTING_BOLD]; + out() << "Note: "; + out() << formattingRightMap()[ATOM_FORMATTING_BOLD]; + break; + case Atom::NoteRight: + out() << "</p>"; + break; + case Atom::LegaleseLeft: + out() << "<div class=\"LegaleseLeft\">"; + break; + case Atom::LegaleseRight: + out() << "</div>"; + break; + case Atom::LineBreak: + out() << "<br/>"; + break; + case Atom::Link: + case Atom::NavLink: + { + inObsoleteLink = false; + const Node *node = 0; + QString link = getLink(atom, relative, &node); + if (link.isEmpty() && (node != relative) && !noLinkErrors()) { + relative->doc().location().warning(tr("Can't link to '%1'").arg(atom->string())); + if (Generator::writeQaPages() && (atom->type() != Atom::NavAutoLink)) { + QString text = atom->next()->next()->string(); + QString target = qdb_->getNewLinkTarget(relative, node, outFileName(), text, true); + out() << "<a id=\"" << Doc::canonicalTitle(target) << "\" class=\"qa-mark\"></a>"; + } + } + else { + if (Generator::writeQaPages() && node && (atom->type() != Atom::NavLink)) { + QString text = atom->next()->next()->string(); + QString target = qdb_->getNewLinkTarget(relative, node, outFileName(), text); + out() << "<a id=\"" << Doc::canonicalTitle(target) << "\" class=\"qa-mark\"></a>"; + } + /* + mws saw this on 17/10/2014. + Is this correct? Setting node to 0 means the + following test always fails. Did we decide to + no longer warn about linking to obsolete things? + */ + node = 0; + if (node && node->status() == Node::Obsolete) { + if ((relative->parent() != node) && !relative->isObsolete()) { + inObsoleteLink = true; + if (obsoleteLinks) { + relative->doc().location().warning(tr("Link to obsolete item '%1' in %2") + .arg(atom->string()) + .arg(relative->plainFullName())); + } + } + } + } + beginLink(link, node, relative); + skipAhead = 1; + } + break; + case Atom::LinkNode: + { + const Node *node = CodeMarker::nodeForString(atom->string()); + beginLink(linkForNode(node, relative), node, relative); + skipAhead = 1; + } + break; + case Atom::ListLeft: + if (in_para) { + out() << "</p>\n"; + in_para = false; + } + if (atom->string() == ATOM_LIST_BULLET) { + out() << "<ul>\n"; + } + else if (atom->string() == ATOM_LIST_TAG) { + out() << "<dl>\n"; + } + else if (atom->string() == ATOM_LIST_VALUE) { + out() << "<div class=\"table\"><table class=\"valuelist\">"; + threeColumnEnumValueTable_ = isThreeColumnEnumValueTable(atom); + if (threeColumnEnumValueTable_) { + if (++numTableRows_ % 2 == 1) + out() << "<tr valign=\"top\" class=\"odd\">"; + else + out() << "<tr valign=\"top\" class=\"even\">"; + + out() << "<th class=\"tblConst\">Constant</th>"; + + // If not in \enum topic, skip the value column + if (relative->type() == Node::Enum) + out() << "<th class=\"tblval\">Value</th>"; + + out() << "<th class=\"tbldscr\">Description</th></tr>\n"; + } + else { + out() << "<tr><th class=\"tblConst\">Constant</th><th class=\"tblVal\">Value</th></tr>\n"; + } + } + else { + out() << "<ol class="; + if (atom->string() == ATOM_LIST_UPPERALPHA) { + out() << "\"A\""; + } /* why type? changed to */ + else if (atom->string() == ATOM_LIST_LOWERALPHA) { + out() << "\"a\""; + } + else if (atom->string() == ATOM_LIST_UPPERROMAN) { + out() << "\"I\""; + } + else if (atom->string() == ATOM_LIST_LOWERROMAN) { + out() << "\"i\""; + } + else { // (atom->string() == ATOM_LIST_NUMERIC) + out() << "\"1\""; + } + if (atom->next() != 0 && atom->next()->string().toInt() != 1) + out() << " start=\"" << atom->next()->string() << '"'; + out() << ">\n"; + } + break; + case Atom::ListItemNumber: + break; + case Atom::ListTagLeft: + if (atom->string() == ATOM_LIST_TAG) { + out() << "<dt>"; + } + else { // (atom->string() == ATOM_LIST_VALUE) + // ### Trenton + + QString t= protectEnc(plainCode(marker->markedUpEnumValue(atom->next()->string(),relative))); + out() << "<tr><td class=\"topAlign\"><code>" << t << "</code>"; + + if (relative->type() == Node::Enum) { + out() << "</td><td class=\"topAlign\">"; + const EnumNode *enume = static_cast<const EnumNode *>(relative); + QString itemValue = enume->itemValue(atom->next()->string()); + + if (itemValue.isEmpty()) + out() << '?'; + else + out() << "<code>" << protectEnc(itemValue) << "</code>"; + } + skipAhead = 1; + } + break; + case Atom::ListTagRight: + if (atom->string() == ATOM_LIST_TAG) + out() << "</dt>\n"; + break; + case Atom::ListItemLeft: + if (atom->string() == ATOM_LIST_TAG) { + out() << "<dd>"; + } + else if (atom->string() == ATOM_LIST_VALUE) { + if (threeColumnEnumValueTable_) { + out() << "</td><td class=\"topAlign\">"; + if (matchAhead(atom, Atom::ListItemRight)) + out() << " "; + } + } + else { + out() << "<li>"; + } + if (matchAhead(atom, Atom::ParaLeft)) + skipAhead = 1; + break; + case Atom::ListItemRight: + if (atom->string() == ATOM_LIST_TAG) { + out() << "</dd>\n"; + } + else if (atom->string() == ATOM_LIST_VALUE) { + out() << "</td></tr>\n"; + } + else { + out() << "</li>\n"; + } + break; + case Atom::ListRight: + if (atom->string() == ATOM_LIST_BULLET) { + out() << "</ul>\n"; + } + else if (atom->string() == ATOM_LIST_TAG) { + out() << "</dl>\n"; + } + else if (atom->string() == ATOM_LIST_VALUE) { + out() << "</table></div>\n"; + } + else { + out() << "</ol>\n"; + } + break; + case Atom::Nop: + break; + case Atom::ParaLeft: + out() << "<p>"; + in_para = true; + break; + case Atom::ParaRight: + endLink(); + if (in_para) { + out() << "</p>\n"; + in_para = false; + } + //if (!matchAhead(atom, Atom::ListItemRight) && !matchAhead(atom, Atom::TableItemRight)) + // out() << "</p>\n"; + break; + case Atom::QuotationLeft: + out() << "<blockquote>"; + break; + case Atom::QuotationRight: + out() << "</blockquote>\n"; + break; + case Atom::RawString: + out() << atom->string(); + break; + case Atom::SectionLeft: + out() << "<a name=\"" << Doc::canonicalTitle(Text::sectionHeading(atom).toString()) + << "\"></a>" << divNavTop << '\n'; + break; + case Atom::SectionRight: + break; + case Atom::SectionHeadingLeft: { + int unit = atom->string().toInt() + hOffset(relative); + out() << "<h" + QString::number(unit) + QLatin1Char(' '); + if (unit < 3) { + out() << "id=\"" << Doc::canonicalTitle(Text::sectionHeading(atom).toString()) << "\""; + } + out() << '>'; + inSectionHeading_ = true; + break; + } + case Atom::SectionHeadingRight: + out() << "</h" + QString::number(atom->string().toInt() + hOffset(relative)) + ">\n"; + inSectionHeading_ = false; + break; + case Atom::SidebarLeft: + break; + case Atom::SidebarRight: + break; + case Atom::String: + if (inLink_ && !inContents_ && !inSectionHeading_) { + generateLink(atom, marker); + } + else { + out() << protectEnc(atom->string()); + } + break; + case Atom::TableLeft: + { + QString p1, p2; + QString attr = "generic"; + QString width; + if (in_para) { + out() << "</p>\n"; + in_para = false; + } + if (atom->count() > 0) { + p1 = atom->string(0); + if (atom->count() > 1) + p2 = atom->string(1); + } + if (!p1.isEmpty()) { + if (p1 == QLatin1String("borderless")) + attr = p1; + else if (p1.contains(QLatin1Char('%'))) + width = p1; + } + if (!p2.isEmpty()) { + if (p2 == QLatin1String("borderless")) + attr = p2; + else if (p2.contains(QLatin1Char('%'))) + width = p2; + } + out() << "<div class=\"table\"><table class=\"" << attr << '"'; + if (!width.isEmpty()) + out() << " width=\"" << width << '"'; + out() << ">\n "; + numTableRows_ = 0; + } + break; + case Atom::TableRight: + out() << "</table></div>\n"; + break; + case Atom::TableHeaderLeft: + out() << "<thead><tr class=\"qt-style\">"; + inTableHeader_ = true; + break; + case Atom::TableHeaderRight: + out() << "</tr>"; + if (matchAhead(atom, Atom::TableHeaderLeft)) { + skipAhead = 1; + out() << "\n<tr class=\"qt-style\">"; + } + else { + out() << "</thead>\n"; + inTableHeader_ = false; + } + break; + case Atom::TableRowLeft: + if (!atom->string().isEmpty()) + out() << "<tr " << atom->string() << '>'; + else if (++numTableRows_ % 2 == 1) + out() << "<tr valign=\"top\" class=\"odd\">"; + else + out() << "<tr valign=\"top\" class=\"even\">"; + break; + case Atom::TableRowRight: + out() << "</tr>\n"; + break; + case Atom::TableItemLeft: + { + if (inTableHeader_) + out() << "<th "; + else + out() << "<td "; + + for (int i=0; i<atom->count(); ++i) { + if (i > 0) + out() << ' '; + QString p = atom->string(i); + if (p.contains('=')) { + out() << p; + } + else { + QStringList spans = p.split(QLatin1Char(',')); + if (spans.size() == 2) { + if (spans.at(0) != "1") + out() << " colspan=\"" << spans.at(0) << '"'; + if (spans.at(1) != "1") + out() << " rowspan=\"" << spans.at(1) << '"'; + } + } + } + if (inTableHeader_) + out() << '>'; + else { + out() << '>'; + //out() << "><p>"; + } + if (matchAhead(atom, Atom::ParaLeft)) + skipAhead = 1; + } + break; + case Atom::TableItemRight: + if (inTableHeader_) + out() << "</th>"; + else { + out() << "</td>"; + //out() << "</p></td>"; + } + if (matchAhead(atom, Atom::ParaLeft)) + skipAhead = 1; + break; + case Atom::TableOfContents: + break; + case Atom::Keyword: + break; + case Atom::Target: + out() << "<a name=\"" << Doc::canonicalTitle(atom->string()) << "\"></a>"; + break; + case Atom::UnhandledFormat: + out() << "<b class=\"redFont\"><Missing HTML></b>"; + break; + case Atom::UnknownCommand: + out() << "<b class=\"redFont\"><code>\\" << protectEnc(atom->string()) + << "</code></b>"; + break; + case Atom::QmlText: + case Atom::EndQmlText: + // don't do anything with these. They are just tags. + break; + default: + unknownAtom(atom); + } + return skipAhead; +} + +/*! + Generate a reference page for a C++ class or a C++ namespace. + */ +void HtmlGenerator::generateClassLikeNode(Aggregate* inner, CodeMarker* marker) +{ + QList<Section> sections; + QList<Section>::ConstIterator s; + + QString title; + QString rawTitle; + QString fullTitle; + if (inner->type() == Node::Namespace) { + rawTitle = inner->plainName(); + fullTitle = inner->plainFullName(); + title = rawTitle + " Namespace"; + } + else if (inner->type() == Node::Class) { + rawTitle = inner->plainName(); + fullTitle = inner->plainFullName(); + title = rawTitle + " Class"; + } + + Text subtitleText; + if (rawTitle != fullTitle) + subtitleText << "(" << Atom(Atom::AutoLink, fullTitle) << ")" << Atom(Atom::LineBreak); + + generateHeader(title, inner, marker); + + sections = marker->sections(inner, CodeMarker::Summary, CodeMarker::Okay); + generateTableOfContents(inner,marker,§ions); + generateKeywordAnchors(inner); + generateTitle(title, subtitleText, SmallSubTitle, inner, marker); + generateBrief(inner, marker); + generateRequisites(inner, marker); + generateStatus(inner, marker); + generateThreadSafeness(inner, marker); + + out() << "<ul>\n"; + + QString membersLink = generateListOfAllMemberFile(inner, marker); + if (!membersLink.isEmpty()) + out() << "<li><a href=\"" << membersLink << "\">" + << "List of all members, including inherited members</a></li>\n"; + + QString obsoleteLink = generateLowStatusMemberFile(inner, + marker, + CodeMarker::Obsolete); + if (!obsoleteLink.isEmpty()) { + out() << "<li><a href=\"" << obsoleteLink << "\">" + << "Obsolete members</a></li>\n"; + } + + QString compatLink = generateLowStatusMemberFile(inner, + marker, + CodeMarker::Compat); + if (!compatLink.isEmpty()) + out() << "<li><a href=\"" << compatLink << "\">" + << "Compatibility members</a></li>\n"; + + out() << "</ul>\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() << "<hr />\n"; + QString ref = registerRef((*s).name.toLower()); + out() << "<a name=\"" << ref << "\"></a>" << divNavTop << "\n"; + out() << "<h2 id=\"" << ref << "\">" << protectEnc((*s).name) << "</h2>\n"; + generateSection(s->members, inner, marker, CodeMarker::Summary); + } + if (!s->reimpMembers.isEmpty()) { + QString name = QString("Reimplemented ") + (*s).name; + QString ref = registerRef(name.toLower()); + // out() << "<hr />\n"; + out() << "<a name=\"" << ref << "\"></a>" << divNavTop << "\n"; + out() << "<h2 id=\"" << ref << "\">" << protectEnc(name) << "</h2>\n"; + generateSection(s->reimpMembers, inner, marker, CodeMarker::Summary); + } + + if (!s->inherited.isEmpty()) { + out() << "<ul>\n"; + generateSectionInheritedList(*s, inner); + out() << "</ul>\n"; + } + } + ++s; + } + + if (needOtherSection) { + out() << "<h3>Additional Inherited Members</h3>\n" + "<ul>\n"; + + s = sections.constBegin(); + while (s != sections.constEnd()) { + if (s->members.isEmpty() && !s->inherited.isEmpty()) + generateSectionInheritedList(*s, inner); + ++s; + } + out() << "</ul>\n"; + } + + QString detailsRef = registerRef("details"); + out() << "<a name=\"" << detailsRef << "\"></a>" << divNavTop << '\n'; + + if (!inner->doc().isEmpty()) { + generateExtractionMark(inner, DetailedDescriptionMark); + //out() << "<hr />\n" + out() << "<div class=\"descr\">\n" // QTBUG-9504 + << "<h2 id=\"" << detailsRef << "\">" << "Detailed Description" << "</h2>\n"; + generateBody(inner, marker); + out() << "</div>\n"; // QTBUG-9504 + generateAlsoList(inner, marker); + generateMaintainerList(inner, marker); + generateExtractionMark(inner, EndMark); + } + + sections = marker->sections(inner, CodeMarker::Detailed, CodeMarker::Okay); + s = sections.constBegin(); + while (s != sections.constEnd()) { + //out() << "<hr />\n"; + if (!(*s).divClass.isEmpty()) + out() << "<div class=\"" << (*s).divClass << "\">\n"; // QTBUG-9504 + out() << "<h2>" << protectEnc((*s).name) << "</h2>\n"; + + NodeList::ConstIterator m = (*s).members.constBegin(); + while (m != (*s).members.constEnd()) { + if ((*m)->access() != Node::Private) { // ### check necessary? + if ((*m)->type() != Node::Class) + generateDetailedMember(*m, inner, marker); + else { + out() << "<h3> class "; + generateFullName(*m, inner); + out() << "</h3>"; + generateBrief(*m, marker, inner); + } + + QStringList names; + names << (*m)->name(); + if ((*m)->type() == Node::Function) { + const FunctionNode *func = reinterpret_cast<const FunctionNode *>(*m); + if (func->metaness() == FunctionNode::Ctor || + func->metaness() == FunctionNode::Dtor || + func->overloadNumber() != 0) + names.clear(); + } + else if ((*m)->type() == Node::Property) { + const PropertyNode *prop = reinterpret_cast<const PropertyNode *>(*m); + if (!prop->getters().isEmpty() && + !names.contains(prop->getters().first()->name())) + names << prop->getters().first()->name(); + if (!prop->setters().isEmpty()) + names << prop->setters().first()->name(); + if (!prop->resetters().isEmpty()) + names << prop->resetters().first()->name(); + if (!prop->notifiers().isEmpty()) + names << prop->notifiers().first()->name(); + } + else if ((*m)->type() == Node::Enum) { + const EnumNode *enume = reinterpret_cast<const EnumNode*>(*m); + if (enume->flagsType()) + names << enume->flagsType()->name(); + + foreach (const QString &enumName, + enume->doc().enumItemNames().toSet() - + enume->doc().omitEnumItemNames().toSet()) + names << plainCode(marker->markedUpEnumValue(enumName, + enume)); + } + } + ++m; + } + if (!(*s).divClass.isEmpty()) + out() << "</div>\n"; // QTBUG-9504 + ++s; + } + generateFooter(inner); +} + +/*! + Generate the HTML page for a QML type. \qcn is the QML type. + \marker is the code markeup object. + */ +void HtmlGenerator::generateQmlTypePage(QmlTypeNode* qcn, CodeMarker* marker) +{ + Generator::setQmlTypeContext(qcn); + SubTitleSize subTitleSize = LargeSubTitle; + QList<Section>::const_iterator s; + QString htmlTitle = qcn->fullTitle(); + if (qcn->isJsType()) + htmlTitle += " JavaScript Type"; + else + htmlTitle += " QML Type"; + + generateHeader(htmlTitle, qcn, marker); + QList<Section> sections = marker->qmlSections(qcn, CodeMarker::Summary); + generateTableOfContents(qcn, marker, §ions); + marker = CodeMarker::markerForLanguage(QLatin1String("QML")); + generateKeywordAnchors(qcn); + generateTitle(htmlTitle, Text() << qcn->subTitle(), subTitleSize, qcn, marker); + generateBrief(qcn, marker); + generateQmlRequisites(qcn, marker); + + QString allQmlMembersLink = generateAllQmlMembersFile(qcn, marker); + QString obsoleteLink = generateQmlMemberFile(qcn, marker, CodeMarker::Obsolete); + if (!allQmlMembersLink.isEmpty() || !obsoleteLink.isEmpty()) { + out() << "<ul>\n"; + if (!allQmlMembersLink.isEmpty()) { + out() << "<li><a href=\"" << allQmlMembersLink << "\">" + << "List of all members, including inherited members</a></li>\n"; + } + if (!obsoleteLink.isEmpty()) { + out() << "<li><a href=\"" << obsoleteLink << "\">" + << "Obsolete members</a></li>\n"; + } + out() << "</ul>\n"; + } + + s = sections.constBegin(); + while (s != sections.constEnd()) { + QString ref = registerRef((*s).name.toLower()); + out() << "<a name=\"" << ref + << "\"></a>" << divNavTop << '\n'; + out() << "<h2 id=\"" << ref << "\">" << protectEnc((*s).name) << "</h2>\n"; + generateQmlSummary(*s, qcn, marker); + ++s; + } + + generateExtractionMark(qcn, DetailedDescriptionMark); + QString detailsRef = registerRef("details"); + out() << "<a name=\"" << detailsRef << "\"></a>" << divNavTop << '\n'; + out() << "<h2 id=\"" << detailsRef << "\">" << "Detailed Description" << "</h2>\n"; + generateBody(qcn, marker); + ClassNode* cn = qcn->classNode(); + if (cn) + generateQmlText(cn->doc().body(), cn, marker, qcn->name()); + generateAlsoList(qcn, marker); + generateExtractionMark(qcn, EndMark); + //out() << "<hr />\n"; + + sections = marker->qmlSections(qcn,CodeMarker::Detailed); + s = sections.constBegin(); + while (s != sections.constEnd()) { + out() << "<h2>" << protectEnc((*s).name) << "</h2>\n"; + NodeList::ConstIterator m = (*s).members.constBegin(); + while (m != (*s).members.constEnd()) { + generateDetailedQmlMember(*m, qcn, marker); + out() << "<br/>\n"; + ++m; + } + ++s; + } + generateFooter(qcn); + Generator::setQmlTypeContext(0); +} + +/*! + Generate the HTML page for the QML basic type represented + by the QML basic type node \a qbtn. + */ +void HtmlGenerator::generateQmlBasicTypePage(QmlBasicTypeNode* qbtn, CodeMarker* marker) +{ + SubTitleSize subTitleSize = LargeSubTitle; + QList<Section>::const_iterator s; + QString htmlTitle = qbtn->fullTitle(); + if (qbtn->isJsType()) + htmlTitle += " JavaScript Basic Type"; + else + htmlTitle += " QML Basic Type"; + + marker = CodeMarker::markerForLanguage(QLatin1String("QML")); + + generateHeader(htmlTitle, qbtn, marker); + QList<Section> sections = marker->sections(qbtn, CodeMarker::Summary, CodeMarker::Okay); + generateTableOfContents(qbtn,marker,§ions); + generateKeywordAnchors(qbtn); + generateTitle(htmlTitle, + Text() << qbtn->subTitle(), + subTitleSize, + qbtn, + marker); + generateExtractionMark(qbtn, DetailedDescriptionMark); + out() << "<div class=\"descr\"> <a name=\"" << registerRef("details") << "\"></a>\n"; // QTBUG-9504 + + generateBody(qbtn, marker); + out() << "</div>\n"; // QTBUG-9504 + generateAlsoList(qbtn, marker); + generateExtractionMark(qbtn, EndMark); + generateFooter(qbtn); +} + +/*! + Generate the HTML page for an entity that doesn't map + to any underlying parsable C++ class or QML component. + */ +void HtmlGenerator::generateDocumentNode(DocumentNode* 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->docSubtype() == Node::Page && dn->pageType() == Node::DitaMapPage) { + const DitaMapNode* dmn = static_cast<const DitaMapNode*>(dn); + writeDitaMap(dmn); + return; + } + + SubTitleSize subTitleSize = LargeSubTitle; + QList<Section> sections; + QList<Section>::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->name() != QLatin1String("index.html"))) + generateTableOfContents(dn,marker,0); + + generateKeywordAnchors(dn); + generateTitle(fullTitle, + Text() << dn->subTitle(), + subTitleSize, + dn, + marker); + + if (dn->docSubtype() == Node::HeaderFile) { + // Generate brief text and status for modules. + generateBrief(dn, marker); + generateStatus(dn, marker); + generateSince(dn, marker); + + out() << "<ul>\n"; + + QString membersLink = generateListOfAllMemberFile(dn, marker); + if (!membersLink.isEmpty()) + out() << "<li><a href=\"" << membersLink << "\">" + << "List of all members, including inherited members</a></li>\n"; + + QString obsoleteLink = generateLowStatusMemberFile(dn, + marker, + CodeMarker::Obsolete); + if (!obsoleteLink.isEmpty()) { + out() << "<li><a href=\"" << obsoleteLink << "\">" + << "Obsolete members</a></li>\n"; + } + + QString compatLink = generateLowStatusMemberFile(dn, + marker, + CodeMarker::Compat); + if (!compatLink.isEmpty()) + out() << "<li><a href=\"" << compatLink << "\">" + << "Compatibility members</a></li>\n"; + + out() << "</ul>\n"; + } + + sections = marker->sections(dn, CodeMarker::Summary, CodeMarker::Okay); + s = sections.constBegin(); + while (s != sections.constEnd()) { + QString ref = registerRef((*s).name); + out() << "<a name=\"" << ref << "\"></a>" << divNavTop << '\n'; + out() << "<h2 id=\"" << ref << "\">" << protectEnc((*s).name) << "</h2>\n"; + generateSectionList(*s, dn, marker, CodeMarker::Summary); + ++s; + } + + generateExtractionMark(dn, DetailedDescriptionMark); + out() << "<div class=\"descr\"> <a name=\"" << registerRef("details") << "\"></a>\n"; // QTBUG-9504 + + generateBody(dn, marker); + out() << "</div>\n"; // QTBUG-9504 + generateAlsoList(dn, marker); + generateExtractionMark(dn, EndMark); + + sections = marker->sections(dn, CodeMarker::Detailed, CodeMarker::Okay); + s = sections.constBegin(); + while (s != sections.constEnd()) { + //out() << "<hr />\n"; + out() << "<h2>" << protectEnc((*s).name) << "</h2>\n"; + + NodeList::ConstIterator m = (*s).members.constBegin(); + while (m != (*s).members.constEnd()) { + generateDetailedMember(*m, dn, marker); + ++m; + } + ++s; + } + generateFooter(dn); +} + +/*! + Generate the HTML page for a group, module, or QML module. + */ +void HtmlGenerator::generateCollectionNode(CollectionNode* cn, CodeMarker* marker) +{ + SubTitleSize subTitleSize = LargeSubTitle; + QList<Section> sections; + QList<Section>::const_iterator s; + QString fullTitle = cn->fullTitle(); + QString ref; + + generateHeader(fullTitle, cn, marker); + generateTableOfContents(cn,marker,0); + generateKeywordAnchors(cn); + generateTitle(fullTitle, Text() << cn->subTitle(), subTitleSize, cn, marker); + + if (cn->isModule()) { + // Generate brief text and status for modules. + generateBrief(cn, marker); + generateStatus(cn, marker); + generateSince(cn, marker); + + NodeMultiMap nmm; + cn->getMemberNamespaces(nmm); + if (!nmm.isEmpty()) { + ref = registerRef("namespaces"); + out() << "<a name=\"" << ref << "\"></a>" << divNavTop << '\n'; + out() << "<h2 id=\"" << ref << "\">Namespaces</h2>\n"; + generateAnnotatedList(cn, marker, nmm); + } + nmm.clear(); + cn->getMemberClasses(nmm); + if (!nmm.isEmpty()) { + ref = registerRef("classes"); + out() << "<a name=\"" << ref << "\"></a>" << divNavTop << '\n'; + out() << "<h2 id=\"" << ref << "\">Classes</h2>\n"; + generateAnnotatedList(cn, marker, nmm); + } + nmm.clear(); + } + + sections = marker->sections(cn, CodeMarker::Summary, CodeMarker::Okay); + s = sections.constBegin(); + while (s != sections.constEnd()) { + ref = registerRef((*s).name); + out() << "<a name=\"" << ref << "\"></a>" << divNavTop << '\n'; + out() << "<h2 id=\"" << ref << "\">" << protectEnc((*s).name) << "</h2>\n"; + generateSectionList(*s, cn, marker, CodeMarker::Summary); + ++s; + } + + Text brief = cn->doc().briefText(); + if (cn->isModule() && !brief.isEmpty()) { + generateExtractionMark(cn, DetailedDescriptionMark); + ref = registerRef("details"); + out() << "<a name=\"" << ref << "\"></a>" << divNavTop << '\n'; + out() << "<div class=\"descr\">\n"; // QTBUG-9504 + out() << "<h2 id=\"" << ref << "\">" << "Detailed Description" << "</h2>\n"; + } + else { + generateExtractionMark(cn, DetailedDescriptionMark); + out() << "<div class=\"descr\"> <a name=\"" << registerRef("details") << "\"></a>\n"; // QTBUG-9504 + } + + generateBody(cn, marker); + out() << "</div>\n"; // QTBUG-9504 + generateAlsoList(cn, marker); + generateExtractionMark(cn, EndMark); + + if (!cn->noAutoList()) { + if (cn->isGroup()) + generateAnnotatedList(cn, marker, cn->members()); + else if (cn->isQmlModule() || cn->isJsModule()) + generateAnnotatedList(cn, marker, cn->members()); + } + + sections = marker->sections(cn, CodeMarker::Detailed, CodeMarker::Okay); + s = sections.constBegin(); + while (s != sections.constEnd()) { + //out() << "<hr />\n"; + out() << "<h2>" << protectEnc((*s).name) << "</h2>\n"; + + NodeList::ConstIterator m = (*s).members.constBegin(); + while (m != (*s).members.constEnd()) { + generateDetailedMember(*m, cn, marker); + ++m; + } + ++s; + } + 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 (homepage == title) + return; + if (!homepage.isEmpty()) + navigationbar << Atom(itemLeft) + << Atom(Atom::NavAutoLink, homepage) + << Atom(itemRight); + if (!landingpage.isEmpty() && landingpage != title) + navigationbar << Atom(itemLeft) + << Atom(Atom::NavAutoLink, landingpage) + << Atom(itemRight); + + if (node->isClass()) { + if (!cppclassespage.isEmpty()) + navigationbar << Atom(itemLeft) + << Atom(Atom::NavLink, cppclassespage) + << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) + << Atom(Atom::String, QLatin1String("C++ Classes")) + << 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()) + navigationbar << Atom(itemLeft) + << Atom(Atom::NavLink, qmltypespage) + << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) + << Atom(Atom::String, QLatin1String("QML Types")) + << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK) + << Atom(itemRight) + << Atom(itemLeft) + << Atom(Atom::String, title) + << Atom(itemRight); + } + else { + if (node->isExampleFile()) { + navigationbar << Atom(itemLeft) + << Atom(Atom::NavLink, node->parent()->name()) + << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) + << Atom(Atom::String, node->parent()->title()) + << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK) + << Atom(itemRight); + + } + navigationbar << Atom(itemLeft) + << Atom(Atom::String, title) + << Atom(itemRight); + } + + generateText(navigationbar, node, marker); + + if (buildversion.isEmpty()) + return; + + if (tableItems) { + out() << "</tr></table><table class=\"buildversion\"><tr>\n" + << "<td id=\"buildversion\" width=\"100%\" align=\"right\">" + << buildversion << "</td>\n"; + } else { + out() << "<li id=\"buildversion\">" << buildversion << "</li>\n"; + } +} + +void HtmlGenerator::generateHeader(const QString& title, + const Node *node, + CodeMarker *marker) +{ +#ifndef QT_NO_TEXTCODEC + out() << QString("<?xml version=\"1.0\" encoding=\"%1\"?>\n").arg(outputEncoding); +#else + out() << QString("<?xml version=\"1.0\"?>\n"); +#endif + out() << "<!DOCTYPE html>\n"; + out() << QString("<html lang=\"%1\">\n").arg(naturalLanguage); + out() << "<head>\n"; + out() << " <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n"; + if (node && !node->doc().location().isEmpty()) + out() << "<!-- " << node->doc().location().fileName() << " -->\n"; + + QString shortVersion = qdb_->version(); + if (shortVersion.count(QChar('.')) == 2) + shortVersion.truncate(shortVersion.lastIndexOf(QChar('.'))); + + //determine the rest of the <title> element content: "title | titleSuffix version" + QString titleSuffix; + if (!landingpage.isEmpty()) { + //for normal pages: "title | landingpage version" + titleSuffix = landingpage; + } + else if (!homepage.isEmpty()) { + //for pages that set the homepage but not landing page: "title | homepage version" + if (title != homepage) + titleSuffix = homepage; + } + else if (!project.isEmpty()) { + //for projects outside of Qt or Qt 5: "title | project version" + if (title != project) + titleSuffix = project; + } + else + //default: "title | Qt version" + titleSuffix = QLatin1String("Qt "); + + //for pages that duplicate the title and suffix (landing pages, home pages, + // and module landing pages, clear the duplicate + if (title == titleSuffix) + titleSuffix.clear(); + + //for pages that duplicate the version, clear the duplicate + if (title.contains(shortVersion) || titleSuffix.contains(shortVersion)) + shortVersion.clear(); + + QString divider; + if (!titleSuffix.isEmpty() && !title.isEmpty()) + divider = QLatin1String(" | "); + + // Generating page title + out() << " <title>" + << protectEnc(title) + << divider + << titleSuffix + << QLatin1Char(' ') + << shortVersion + << "</title>\n"; + + // Include style sheet and script links. + out() << headerStyles; + out() << headerScripts; + if (endHeader.isEmpty()) + out() << "</head>\n<body>\n"; + else + out() << endHeader; + +#ifdef GENERATE_MAC_REFS + if (mainPage) + generateMacRef(node, marker); +#endif + + out() << QString(postHeader).replace("\\" + COMMAND_VERSION, qdb_->version()); + bool usingTable = postHeader.trimmed().endsWith(QLatin1String("<tr>")); + generateNavigationBar(title, node, marker, buildversion, usingTable); + out() << QString(postPostHeader).replace("\\" + COMMAND_VERSION, qdb_->version()); + + navigationLinks.clear(); + refMap.clear(); + + if (node && !node->links().empty()) { + QPair<QString,QString> linkPair; + QPair<QString,QString> anchorPair; + const Node *linkNode; + + if (node->links().contains(Node::PreviousLink)) { + linkPair = node->links()[Node::PreviousLink]; + linkNode = qdb_->findNodeForTarget(linkPair.first, node); + if (!linkNode) + node->doc().location().warning(tr("Cannot link to '%1'").arg(linkPair.first)); + if (!linkNode || linkNode == node) + anchorPair = linkPair; + else + anchorPair = anchorForNode(linkNode); + + out() << " <link rel=\"prev\" href=\"" + << anchorPair.first << "\" />\n"; + + navigationLinks += "<a class=\"prevPage\" href=\"" + anchorPair.first + "\">"; + if (linkPair.first == linkPair.second && !anchorPair.second.isEmpty()) + navigationLinks += protect(anchorPair.second); + else + navigationLinks += protect(linkPair.second); + navigationLinks += "</a>\n"; + } + if (node->links().contains(Node::NextLink)) { + linkPair = node->links()[Node::NextLink]; + linkNode = qdb_->findNodeForTarget(linkPair.first, node); + if (!linkNode) + node->doc().location().warning(tr("Cannot link to '%1'").arg(linkPair.first)); + if (!linkNode || linkNode == node) + anchorPair = linkPair; + else + anchorPair = anchorForNode(linkNode); + + out() << " <link rel=\"next\" href=\"" + << anchorPair.first << "\" />\n"; + + navigationLinks += "<a class=\"nextPage\" href=\"" + anchorPair.first + "\">"; + if (linkPair.first == linkPair.second && !anchorPair.second.isEmpty()) + navigationLinks += protect(anchorPair.second); + else + navigationLinks += protect(linkPair.second); + navigationLinks += "</a>\n"; + } + if (node->links().contains(Node::StartLink)) { + linkPair = node->links()[Node::StartLink]; + linkNode = qdb_->findNodeForTarget(linkPair.first, node); + if (!linkNode) + node->doc().location().warning(tr("Cannot link to '%1'").arg(linkPair.first)); + if (!linkNode || linkNode == node) + anchorPair = linkPair; + else + anchorPair = anchorForNode(linkNode); + out() << " <link rel=\"start\" href=\"" + << anchorPair.first << "\" />\n"; + } + } + + if (node && !node->links().empty()) + out() << "<p class=\"naviNextPrevious headerNavi\">\n" << navigationLinks << "</p><p/>\n"; +} + +void HtmlGenerator::generateTitle(const QString& title, + const Text &subTitle, + SubTitleSize subTitleSize, + const Node *relative, + CodeMarker *marker) +{ + out() << QString(prologue).replace("\\" + COMMAND_VERSION, qdb_->version()); + if (!title.isEmpty()) + out() << "<h1 class=\"title\">" << protectEnc(title) << "</h1>\n"; + if (!subTitle.isEmpty()) { + out() << "<span"; + if (subTitleSize == SmallSubTitle) + out() << " class=\"small-subtitle\">"; + else + out() << " class=\"subtitle\">"; + generateText(subTitle, relative, marker); + out() << "</span>\n"; + } +} + +void HtmlGenerator::generateFooter(const Node *node) +{ + if (node && !node->links().empty()) + out() << "<p class=\"naviNextPrevious footerNavi\">\n" << navigationLinks << "</p>\n"; + + out() << QString(footer).replace("\\" + COMMAND_VERSION, qdb_->version()) + << QString(address).replace("\\" + COMMAND_VERSION, qdb_->version()); + + out() << "</body>\n"; + out() << "</html>\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(Aggregate *inner, CodeMarker *marker) +{ + QMap<QString, Text> 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 << highlightedCode(indent(codeIndent, + marker->markedUpIncludes(inner->includes())), + inner); + 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 <project> <version> 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->physicalModuleName().isEmpty()) { + const CollectionNode* cn = qdb_->getCollectionNode(inner->physicalModuleName(), Node::CPP); + if (cn && !cn->qtVariable().isEmpty()) { + text.clear(); + text << "QT += " + cn->qtVariable(); + requisites.insert(qtVariableText, text); + } + } + } + + if (inner->type() == Node::Class) { + ClassNode* classe = static_cast<ClassNode*>(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<RelatedClass>::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 << comma(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() << "<div class=\"table\"><table class=\"alignedsummary\">\n"; + + QStringList::ConstIterator i; + for (i = requisiteorder.constBegin(); i != requisiteorder.constEnd(); ++i) { + + if (requisites.contains(*i)) { + out() << "<tr>" + << "<td class=\"memItemLeft rightAlign topAlign\"> " + << *i << ":" + "</td><td class=\"memItemRight bottomAlign\"> "; + + if (*i == headerText) + out() << requisites.value(*i).toString(); + else + generateText(requisites.value(*i), inner, marker); + out() << "</td></tr>"; + } + } + out() << "</table></div>"; + } +} + +/*! +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(QmlTypeNode *qcn, CodeMarker *marker) +{ + if (!qcn) + return; + QMap<QString, Text> 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 logicalModuleVersion; + const CollectionNode* collection = qdb_->getCollectionNode(qcn->logicalModuleName(), qcn->genus()); + if (collection) + logicalModuleVersion = collection->logicalModuleVersion(); + else + logicalModuleVersion = qcn->logicalModuleVersion(); + text.clear(); + text << "import " + qcn->logicalModuleName() + QLatin1Char(' ') + logicalModuleVersion; + 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 <project> <version> 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 + QmlTypeNode* 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; + QmlTypeNode::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() << "<div class=\"table\"><table class=\"alignedsummary\">\n"; + + QStringList::ConstIterator i; + for (i = requisiteorder.constBegin(); i != requisiteorder.constEnd(); ++i) { + + if (requisites.contains(*i)) { + out() << "<tr>" + << "<td class=\"memItemLeft rightAlign topAlign\"> " + << *i + << "</td><td class=\"memItemRight bottomAlign\"> "; + + if (*i == importText) + out()<<requisites.value(*i).toString(); + else + generateText(requisites.value(*i), qcn, marker); + out() << "</td></tr>"; + } + } + out() << "</table></div>"; + } +} + +void HtmlGenerator::generateBrief(const Node *node, CodeMarker *marker, + const Node *relative) +{ + Text brief = node->doc().briefText(); + if (!brief.isEmpty()) { + generateExtractionMark(node, BriefMark); + out() << "<p>"; + generateText(brief, node, marker); + + if (!relative || node == relative) + out() << " <a href=\"#"; + else + out() << " <a href=\"" << linkForNode(node, relative) << '#'; + out() << registerRef("details") << "\">More...</a></p>\n"; + + + generateExtractionMark(node, EndMark); + } +} + +void HtmlGenerator::generateIncludes(const Aggregate *inner, CodeMarker *marker) +{ + if (!inner->includes().isEmpty()) { + out() << "<pre class=\"cpp\">" + << trimmedTrailing(highlightedCode(indent(codeIndent, + marker->markedUpIncludes(inner->includes())), + inner), codePrefix, codeSuffix) + << "</pre>"; + } +} + +/*! + Revised for the new doc format. + Generates a table of contents beginning at \a node. + */ +void HtmlGenerator::generateTableOfContents(const Node *node, + CodeMarker *marker, + QList<Section>* sections) +{ + QList<Atom*> toc; + if (node->doc().hasTableOfContents()) + toc = node->doc().tableOfContents(); + if (tocDepth == 0 || (toc.isEmpty() && !sections && !node->isModule())) { + generateSidebar(); + return; + } + + QStringList sectionNumber; + int detailsBase = 0; + + // disable nested links in table of contents + inContents_ = true; + inLink_ = true; + + out() << "<div class=\"sidebar\">\n"; + out() << "<div class=\"toc\">\n"; + out() << "<h3><a name=\"toc\">Contents</a></h3>\n"; + sectionNumber.append("1"); + out() << "<ul>\n"; + + if (node->isModule()) { + if (node->hasNamespaces()) { + out() << "<li class=\"level" + << sectionNumber.size() + << "\"><a href=\"#" + << registerRef("namespaces") + << "\">Namespaces</a></li>\n"; + } + if (node->hasClasses()) { + out() << "<li class=\"level" + << sectionNumber.size() + << "\"><a href=\"#" + << registerRef("classes") + << "\">Classes</a></li>\n"; + } + out() << "<li class=\"level" + << sectionNumber.size() + << "\"><a href=\"#" + << registerRef("details") + << "\">Detailed Description</a></li>\n"; + for (int i = 0; i < toc.size(); ++i) { + if (toc.at(i)->string().toInt() == 1) { + detailsBase = 1; + break; + } + } + } + else if (sections && (node->isClass() || + node->isNamespace() || + node->isQmlType() || + node->isJsType())) { + QList<Section>::ConstIterator s = sections->constBegin(); + while (s != sections->constEnd()) { + if (!s->members.isEmpty()) { + out() << "<li class=\"level" + << sectionNumber.size() + << "\"><a href=\"#" + << registerRef((*s).pluralMember) + << "\">" << (*s).name + << "</a></li>\n"; + } + if (!s->reimpMembers.isEmpty()) { + QString ref = QString("Reimplemented ") + (*s).pluralMember; + out() << "<li class=\"level" + << sectionNumber.size() + << "\"><a href=\"#" + << registerRef(ref.toLower()) + << "\">" << QString("Reimplemented ") + (*s).name + << "</a></li>\n"; + } + ++s; + } + out() << "<li class=\"level" + << sectionNumber.size() + << "\"><a href=\"#" + << registerRef("details") + << "\">Detailed Description</a></li>\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() << "<li class=\"level" + << sectionNumber.size() + << "\">"; + out() << "<a href=\"" + << '#' + << Doc::canonicalTitle(s) + << "\">"; + generateAtomList(headingText.firstAtom(), node, marker, true, numAtoms); + out() << "</a></li>\n"; + } + } + while (!sectionNumber.isEmpty()) { + sectionNumber.removeLast(); + } + out() << "</ul>\n"; + out() << "</div>\n"; + out() << "<div class=\"sidebar-content\" id=\"sidebar-content\"></div>"; + out() << "</div>\n"; + inContents_ = false; + inLink_ = false; +} + +/*! + Outputs a placeholder div where the style can add customized sidebar content. + */ +void HtmlGenerator::generateSidebar() { + out() << "<div class=\"sidebar\">"; + out() << "<div class=\"sidebar-content\" id=\"sidebar-content\"></div>"; + out() << "</div>\n"; +} + +QString HtmlGenerator::generateListOfAllMemberFile(const Aggregate *inner, + CodeMarker *marker) +{ + QList<Section> sections; + QList<Section>::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); + generateSidebar(); + generateTitle(title, Text(), SmallSubTitle, inner, marker); + out() << "<p>This is the complete list of members for "; + generateFullName(inner, 0); + out() << ", including inherited members.</p>\n"; + + Section section = sections.first(); + generateSectionList(section, inner, 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(QmlTypeNode* qml_cn, CodeMarker* marker) +{ + QList<Section> sections; + QList<Section>::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); + generateSidebar(); + generateTitle(title, Text(), SmallSubTitle, qml_cn, marker); + out() << "<p>This is the complete list of members for "; + generateFullName(qml_cn, 0); + out() << ", including inherited members.</p>\n"; + + ClassKeysNodesList& cknl = sections.first().classKeysNodesList_; + if (!cknl.isEmpty()) { + for (int i=0; i<cknl.size(); i++) { + ClassKeysNodes* ckn = cknl[i]; + const QmlTypeNode* qcn = ckn->first; + KeysAndNodes& kn = ckn->second; + QStringList& keys = kn.first; + NodeList& nodes = kn.second; + if (nodes.isEmpty()) + continue; + if (i != 0) { + out() << "<p>The following members are inherited from "; + generateFullName(qcn,0); + out() << ".</p>\n"; + } + out() << "<ul>\n"; + for (int j=0; j<keys.size(); j++) { + if (nodes[j]->access() == Node::Private || nodes[j]->status() == Node::Internal) { + continue; + } + out() << "<li class=\"fn\">"; + QString prefix; + if (!keys.isEmpty()) { + prefix = keys.at(j).mid(1); + prefix = prefix.left(keys.at(j).indexOf("::")+1); + } + generateQmlItem(nodes[j], qml_cn, marker, true); + if (nodes[j]->isAttached()) + out() << " [attached]"; + //generateSynopsis(nodes[j], qcn, marker, CodeMarker::Subpage, false, &prefix); + out() << "</li>\n"; + } + out() << "</ul>\n"; + } + } + + generateFooter(); + endSubPage(); + return fileName; +} + +QString HtmlGenerator::generateLowStatusMemberFile(Aggregate *inner, + CodeMarker *marker, + CodeMarker::Status status) +{ + QList<Section> sections = marker->sections(inner, + CodeMarker::Summary, + status); + QMutableListIterator<Section> 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); + generateSidebar(); + generateTitle(title, Text(), SmallSubTitle, inner, marker); + + if (status == CodeMarker::Compat) { + out() << "<p><b>The following members of class " + << "<a href=\"" << linkForNode(inner, 0) << "\">" + << protectEnc(inner->name()) << "</a>" + << " are part of the " + "Qt compatibility layer.</b> We advise against " + "using them in new code.</p>\n"; + } + else { + out() << "<p><b>The following members of class " + << "<a href=\"" << linkForNode(inner, 0) << "\">" + << protectEnc(inner->name()) << "</a>" + << " are obsolete.</b> " + << "They are provided to keep old source code working. " + << "We strongly advise against using them in new code.</p>\n"; + } + + for (i = 0; i < sections.size(); ++i) { + out() << "<h2>" << protectEnc(sections.at(i).name) << "</h2>\n"; + generateSectionList(sections.at(i), inner, marker, CodeMarker::Summary); + } + + sections = marker->sections(inner, CodeMarker::Detailed, status); + for (i = 0; i < sections.size(); ++i) { + //out() << "<hr />\n"; + out() << "<h2>" << protectEnc(sections.at(i).name) << "</h2>\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; +} + +/*! + Generates a separate file where certain members of the QML + type \a qcn are listed. The \a marker is used to generate + the section lists, which are then traversed and output here. + + Note that this function currently only handles correctly the + case where \a status is \c {CodeMarker::Obsolete}. + */ +QString HtmlGenerator::generateQmlMemberFile(QmlTypeNode* qcn, + CodeMarker *marker, + CodeMarker::Status status) +{ + QList<Section> sections = marker->qmlSections(qcn, CodeMarker::Summary, status); + QMutableListIterator<Section> j(sections); + while (j.hasNext()) { + if (j.next().members.size() == 0) + j.remove(); + } + if (sections.isEmpty()) + return QString(); + + QString title = "Obsolete Members for " + qcn->name(); + QString fileName = fileBase(qcn) + "-obsolete." + fileExtension(); + + if (status == CodeMarker::Obsolete) { + QString link; + if (useOutputSubdirs() && !Generator::outputSubdir().isEmpty()) + link = QString("../" + Generator::outputSubdir() + QLatin1Char('/')); + link += fileName; + qcn->setObsoleteLink(link); + } + + beginSubPage(qcn, fileName); + generateHeader(title, qcn, marker); + generateSidebar(); + generateTitle(title, Text(), SmallSubTitle, qcn, marker); + + out() << "<p><b>The following members of QML type " + << "<a href=\"" << linkForNode(qcn, 0) << "\">" + << protectEnc(qcn->name()) << "</a>" + << " are obsolete.</b> " + << "They are provided to keep old source code working. " + << "We strongly advise against using them in new code.</p>\n"; + + QList<Section>::const_iterator s = sections.constBegin(); + while (s != sections.constEnd()) { + QString ref = registerRef((*s).name.toLower()); + out() << "<a name=\"" << ref + << "\"></a>" << divNavTop << '\n'; + out() << "<h2 id=\"" << ref << "\">" << protectEnc((*s).name) << "</h2>\n"; + generateQmlSummary(*s, qcn, marker); + ++s; + } + + sections = marker->qmlSections(qcn, CodeMarker::Detailed, status); + QMutableListIterator<Section> k(sections); + while (k.hasNext()) { + if (k.next().members.size() == 0) + k.remove(); + } + if (sections.isEmpty()) + return QString(); + + s = sections.constBegin(); + while (s != sections.constEnd()) { + out() << "<h2>" << protectEnc((*s).name) << "</h2>\n"; + NodeList::ConstIterator m = (*s).members.constBegin(); + while (m != (*s).members.constEnd()) { + generateDetailedQmlMember(*m, qcn, marker); + out() << "<br/>\n"; + ++m; + } + ++s; + } + + 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<ClassNode *>(*c); + if (classe->baseClasses().isEmpty()) + topLevel.insert(classe->name(), classe); + ++c; + } + + QStack<NodeMap > stack; + stack.push(topLevel); + + out() << "<ul>\n"; + while (!stack.isEmpty()) { + if (stack.top().isEmpty()) { + stack.pop(); + out() << "</ul>\n"; + } + else { + ClassNode* child = static_cast<ClassNode*>(*stack.top().begin()); + out() << "<li>"; + generateFullName(child, relative); + out() << "</li>\n"; + stack.top().erase(stack.top().begin()); + + NodeMap newTop; + foreach (const RelatedClass &d, child->derivedClasses()) { + if (d.node_ && !d.isPrivate() && !d.node_->isInternal() && d.node_->hasDoc()) + newTop.insert(d.node_->name(), d.node_); + } + if (!newTop.isEmpty()) { + stack.push(newTop); + out() << "<ul>\n"; + } + } + } +} + +/*! + Output an annotated list of the nodes in \a nodeMap. + A two-column table is output. + */ +void HtmlGenerator::generateAnnotatedList(const Node* relative, + CodeMarker* marker, + const NodeMultiMap& nmm) +{ + if (nmm.isEmpty()) + return; + generateAnnotatedList(relative, marker, nmm.values()); +} + +/*! + */ +void HtmlGenerator::generateAnnotatedList(const Node *relative, + CodeMarker *marker, + const NodeList& unsortedNodes) +{ + NodeMultiMap nmm; + bool allInternal = true; + foreach (Node* node, unsortedNodes) { + if (!node->isInternal() && !node->isObsolete()) { + allInternal = false; + nmm.insert(node->fullName(relative), node); + } + } + if (allInternal) + return; + out() << "<div class=\"table\"><table class=\"annotated\">\n"; + int row = 0; + NodeList nodes = nmm.values(); + foreach (const Node* node, nodes) { + if (++row % 2 == 1) + out() << "<tr class=\"odd topAlign\">"; + else + out() << "<tr class=\"even topAlign\">"; + out() << "<td class=\"tblName\"><p>"; + generateFullName(node, relative); + out() << "</p></td>"; + + if (!node->isDocumentNode()) { + Text brief = node->doc().trimmedBriefText(node->name()); + if (!brief.isEmpty()) { + out() << "<td class=\"tblDescr\"><p>"; + generateText(brief, node, marker); + out() << "</p></td>"; + } + else if (!node->reconstitutedBrief().isEmpty()) { + out() << "<td class=\"tblDescr\"><p>"; + out() << node->reconstitutedBrief(); + out() << "</p></td>"; + } + } + else { + out() << "<td class=\"tblDescr\"><p>"; + if (!node->reconstitutedBrief().isEmpty()) { + out() << node->reconstitutedBrief(); + } + else + out() << protectEnc(node->doc().briefText().toString()); + out() << "</p></td>"; + } + out() << "</tr>\n"; + } + out() << "</table></div>\n"; +} + +/*! + This function finds the common prefix of the names of all + the classes in the class map \a nmm 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 the class map + \a nmm. + */ +void HtmlGenerator::generateCompactList(ListType listType, + const Node *relative, + const NodeMultiMap &nmm, + bool includeAlphabet, + QString commonPrefix) +{ + if (nmm.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 NodeMultiMap. + */ + NodeMultiMap paragraph[NumParagraphs+1]; + QString paragraphName[NumParagraphs+1]; + QSet<char> usedParagraphNames; + + NodeMultiMap::ConstIterator c = nmm.constBegin(); + while (c != nmm.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(c.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<NumParagraphs; i++) // i = 0..36 + paragraphOffset[i+1] = paragraphOffset[i] + paragraph[i].count(); + + /* + Output the alphabet as a row of links. + */ + if (includeAlphabet) { + out() << "<p class=\"centerAlign functionIndex\"><b>"; + for (int i = 0; i < 26; i++) { + QChar ch('a' + i); + if (usedParagraphNames.contains(char('a' + i))) + out() << QString("<a href=\"#%1\">%2</a> ").arg(ch).arg(ch.toUpper()); + } + out() << "</b></p>\n"; + } + + /* + Output a <div> element to contain all the <dl> elements. + */ + out() << "<div class=\"flowListDiv\">\n"; + numTableRows_ = 0; + + int curParNr = 0; + int curParOffset = 0; + QString previousName; + bool multipleOccurrences = false; + + for (int i=0; i<nmm.count(); i++) { + while ((curParNr < NumParagraphs) && + (curParOffset == paragraph[curParNr].count())) { + ++curParNr; + curParOffset = 0; + } + + /* + Starting a new paragraph means starting a new <dl>. + */ + if (curParOffset == 0) { + if (i > 0) + out() << "</dl>\n"; + if (++numTableRows_ % 2 == 1) + out() << "<dl class=\"flowList odd\">"; + else + out() << "<dl class=\"flowList even\">"; + out() << "<dt class=\"alphaChar\">"; + if (includeAlphabet) { + QChar c = paragraphName[curParNr][0].toLower(); + out() << QString("<a name=\"%1\"></a>").arg(c); + } + out() << "<b>" + << paragraphName[curParNr] + << "</b>"; + out() << "</dt>\n"; + } + + /* + Output a <dd> for the current offset in the current paragraph. + */ + out() << "<dd>"; + if ((curParNr < NumParagraphs) && + !paragraphName[curParNr].isEmpty()) { + NodeMultiMap::Iterator it; + NodeMultiMap::Iterator next; + it = paragraph[curParNr].begin(); + for (int i=0; i<curParOffset; i++) + ++it; + + if (listType == Generic) { + /* + Previously, we used generateFullName() for this, but we + require some special formatting. + */ + out() << "<a href=\"" << linkForNode(it.value(), relative) << "\">"; + } + else if (listType == Obsolete) { + QString fileName = fileBase(it.value()) + "-obsolete." + fileExtension(); + QString link; + if (useOutputSubdirs()) { + link = QString("../" + it.value()->outputSubdirectory() + QLatin1Char('/')); + } + link += fileName; + out() << "<a href=\"" << link << "\">"; + } + + QStringList pieces; + if (it.value()->isQmlType() || it.value()->isJsType()) { + QString name = it.value()->name(); + next = it; + ++next; + if (name != previousName) + multipleOccurrences = false; + if ((next != paragraph[curParNr].end()) && (name == next.value()->name())) { + multipleOccurrences = true; + previousName = name; + } + if (multipleOccurrences) + name += ": " + it.value()->tree()->camelCaseModuleName(); + pieces << name; + } + else + pieces = it.value()->fullName(relative).split("::"); + out() << protectEnc(pieces.last()); + out() << "</a>"; + if (pieces.size() > 1) { + out() << " ("; + generateFullName(it.value()->parent(), relative); + out() << ')'; + } + } + out() << "</dd>\n"; + curParOffset++; + } + if (nmm.count() > 0) + out() << "</dl>\n"; + + out() << "</div>\n"; +} + +void HtmlGenerator::generateFunctionIndex(const Node *relative) +{ + out() << "<p class=\"centerAlign functionIndex\"><b>"; + for (int i = 0; i < 26; i++) { + QChar ch('a' + i); + out() << QString("<a href=\"#%1\">%2</a> ").arg(ch).arg(ch.toUpper()); + } + out() << "</b></p>\n"; + + char nextLetter = 'a'; + char currentLetter; + + out() << "<ul>\n"; + NodeMapMap& funcIndex = qdb_->getFunctionIndex(); + QMap<QString, NodeMap >::ConstIterator f = funcIndex.constBegin(); + while (f != funcIndex.constEnd()) { + out() << "<li>"; + out() << protectEnc(f.key()) << ':'; + + currentLetter = f.key()[0].unicode(); + while (islower(currentLetter) && currentLetter >= nextLetter) { + out() << QString("<a name=\"%1\"></a>").arg(nextLetter); + nextLetter++; + } + + NodeMap::ConstIterator s = (*f).constBegin(); + while (s != (*f).constEnd()) { + out() << ' '; + generateFullName((*s)->parent(), relative, *s); + ++s; + } + out() << "</li>"; + out() << '\n'; + ++f; + } + out() << "</ul>\n"; +} + +void HtmlGenerator::generateLegaleseList(const Node *relative, CodeMarker *marker) +{ + TextToNodeMap& legaleseTexts = qdb_->getLegaleseTexts(); + QMap<Text, const Node *>::ConstIterator it = legaleseTexts.constBegin(); + while (it != legaleseTexts.constEnd()) { + Text text = it.key(); + //out() << "<hr />\n"; + generateText(text, relative, marker); + out() << "<ul>\n"; + do { + out() << "<li>"; + generateFullName(it.value(), relative); + out() << "</li>\n"; + ++it; + } while (it != legaleseTexts.constEnd() && it.key() == text); + out() << "</ul>\n"; + } +} + +void HtmlGenerator::generateQmlItem(const Node *node, + const Node *relative, + CodeMarker *marker, + bool summary) +{ + QString marked = marker->markedUpQmlItem(node,summary); + QRegExp templateTag("(<[^@>]*>)"); + if (marked.indexOf(templateTag) != -1) { + QString contents = protectEnc(marked.mid(templateTag.pos(1), + templateTag.cap(1).length())); + marked.replace(templateTag.pos(1), templateTag.cap(1).length(), + contents); + } + marked.replace(QRegExp("<@param>([a-z]+)_([1-9n])</@param>"), + "<i>\\1<sub>\\2</sub></i>"); + marked.replace("<@param>", "<i>"); + marked.replace("</@param>", "</i>"); + + if (summary) + marked.replace("@name>", "b>"); + + marked.replace("<@extra>", "<code>"); + marked.replace("</@extra>", "</code>"); + + if (summary) { + marked.remove("<@type>"); + marked.remove("</@type>"); + } + out() << highlightedCode(marked, relative, false); +} + +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<Node*>(relative); + CollectionNode* cn = static_cast<CollectionNode*>(n); + qdb_->mergeCollections(cn); + generateAnnotatedList(cn, marker, cn->members()); + } +} + +void HtmlGenerator::generateSection(const NodeList& nl, + const Node *relative, + CodeMarker *marker, + CodeMarker::SynopsisStyle style) +{ + bool alignNames = true; + if (!nl.isEmpty()) { + bool twoColumn = false; + if (style == CodeMarker::Subpage) { + alignNames = false; + twoColumn = (nl.count() >= 16); + } + else if (nl.first()->type() == Node::Property) { + twoColumn = (nl.count() >= 5); + alignNames = false; + } + if (alignNames) { + out() << "<div class=\"table\"><table class=\"alignedsummary\">\n"; + } + else { + if (twoColumn) + out() << "<div class=\"table\"><table class=\"propsummary\">\n" + << "<tr><td class=\"topAlign\">"; + out() << "<ul>\n"; + } + + int i = 0; + NodeList::ConstIterator m = nl.constBegin(); + while (m != nl.constEnd()) { + if ((*m)->access() == Node::Private) { + ++m; + continue; + } + + if (alignNames) { + out() << "<tr><td class=\"memItemLeft rightAlign topAlign\"> "; + } + else { + if (twoColumn && i == (int) (nl.count() + 1) / 2) + out() << "</ul></td><td class=\"topAlign\"><ul>\n"; + out() << "<li class=\"fn\">"; + } + + generateSynopsis(*m, relative, marker, style, alignNames); + if (alignNames) + out() << "</td></tr>\n"; + else + out() << "</li>\n"; + i++; + ++m; + } + if (alignNames) + out() << "</table></div>\n"; + else { + out() << "</ul>\n"; + if (twoColumn) + out() << "</td></tr>\n</table></div>\n"; + } + } +} + +void HtmlGenerator::generateSectionList(const Section& section, + const Node *relative, + CodeMarker *marker, + CodeMarker::SynopsisStyle style) +{ + bool alignNames = true; + if (!section.members.isEmpty()) { + bool hasPrivateSignals = false; + bool twoColumn = false; + if (style == CodeMarker::Subpage) { + alignNames = false; + twoColumn = (section.members.count() >= 16); + } + else if (section.members.first()->type() == Node::Property) { + twoColumn = (section.members.count() >= 5); + alignNames = false; + } + if (alignNames) { + out() << "<div class=\"table\"><table class=\"alignedsummary\">\n"; + } + else { + if (twoColumn) + out() << "<div class=\"table\"><table class=\"propsummary\">\n" + << "<tr><td class=\"topAlign\">"; + out() << "<ul>\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() << "<tr><td class=\"memItemLeft topAlign rightAlign\"> "; + } + else { + if (twoColumn && i == (int) (section.members.count() + 1) / 2) + out() << "</ul></td><td class=\"topAlign\"><ul>\n"; + out() << "<li class=\"fn\">"; + } + + QString prefix; + if (!section.keys.isEmpty()) { + prefix = section.keys.at(i).mid(1); + prefix = prefix.left(section.keys.at(i).indexOf("::")+1); + } + generateSynopsis(*m, relative, marker, style, alignNames, &prefix); + if ((*m)->isFunction()) { + const FunctionNode* fn = static_cast<const FunctionNode*>(*m); + if (fn->isPrivateSignal()) { + hasPrivateSignals = true; + if (alignNames) + out() << "</td><td class=\"memItemRight bottomAlign\">[see note below]"; + } + } + if (alignNames) + out() << "</td></tr>\n"; + else + out() << "</li>\n"; + i++; + ++m; + } + if (alignNames) + out() << "</table></div>\n"; + else { + out() << "</ul>\n"; + if (twoColumn) + out() << "</td></tr>\n</table></div>\n"; + } + if (hasPrivateSignals && alignNames) { + generatePrivateSignalNote(relative, marker); + } + } + + if (style == CodeMarker::Summary && !section.inherited.isEmpty()) { + out() << "<ul>\n"; + generateSectionInheritedList(section, relative); + out() << "</ul>\n"; + } +} + +void HtmlGenerator::generateSectionInheritedList(const Section& section, const Node *relative) +{ + QList<QPair<Aggregate *, int> >::ConstIterator p = section.inherited.constBegin(); + while (p != section.inherited.constEnd()) { + out() << "<li class=\"fn\">"; + out() << (*p).second << ' '; + if ((*p).second == 1) { + out() << section.singularMember; + } + else { + out() << section.pluralMember; + } + out() << " inherited from <a href=\"" << fileName((*p).first) + << '#' << Generator::cleanRef(section.name.toLower()) << "\">" + << protectEnc((*p).first->plainFullName(relative)) + << "</a></li>\n"; + ++p; + } +} + +// generateSynopsis(qmn,relative,marker,CodeMarker::Detailed,false); +void HtmlGenerator::generateSynopsis(const Node *node, + const Node *relative, + CodeMarker *marker, + CodeMarker::SynopsisStyle style, + bool alignNames, + const QString* prefix) +{ + QString marked = marker->markedUpSynopsis(node, relative, style); + + if (prefix) + marked.prepend(*prefix); + QRegExp templateTag("(<[^@>]*>)"); + if (marked.indexOf(templateTag) != -1) { + QString contents = protectEnc(marked.mid(templateTag.pos(1), + templateTag.cap(1).length())); + marked.replace(templateTag.pos(1), templateTag.cap(1).length(), + contents); + } + marked.replace(QRegExp("<@param>([a-z]+)_([1-9n])</@param>"), + "<i>\\1<sub>\\2</sub></i>"); + marked.replace("<@param>", "<i>"); + marked.replace("</@param>", "</i>"); + + if (style == CodeMarker::Summary) { + marked.remove("<@name>"); // was "<b>" + marked.remove("</@name>"); // was "</b>" + } + + if (style == CodeMarker::Subpage) { + QRegExp extraRegExp("<@extra>.*</@extra>"); + extraRegExp.setMinimal(true); + marked.remove(extraRegExp); + } else { + marked.replace("<@extra>", "<code>"); + marked.replace("</@extra>", "</code>"); + } + + if (style != CodeMarker::Detailed) { + marked.remove("<@type>"); + marked.remove("</@type>"); + } + + out() << highlightedCode(marked, relative, alignNames); +} + +QString HtmlGenerator::highlightedCode(const QString& markedCode, + const Node* relative, + bool alignNames) +{ + 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("</td><td class=\"memItemRight bottomAlign\">"); + done = true; + } + i += 2; + if (parseArg(src, linkTag, &i, srcSize, &arg, &par1)) { + html += QLatin1String("<b>"); + const Node* n = CodeMarker::nodeForString(par1.toString()); + QString link = linkForNode(n, relative); + addLink(link, arg, &html); + html += QLatin1String("</b>"); + } + else if (parseArg(src, funcTag, &i, srcSize, &arg, &par1)) { + const Node* n = qdb_->findFunctionNode(par1.toString(), relative, Node::DontCare); + QString link = linkForNode(n, relative); + addLink(link, arg, &html); + par1 = QStringRef(); + } + else if (parseArg(src, typeTag, &i, srcSize, &arg, &par1)) { + par1 = QStringRef(); + const Node* n = qdb_->findTypeNode(arg.toString(), relative); + html += QLatin1String("<span class=\"type\">"); + if (n && (n->isQmlBasicType() || n->isJsBasicType())) { + if (relative && (relative->isQmlType() || relative->isJsType())) + addLink(linkForNode(n,relative), arg, &html); + else + html += arg; + } + else + addLink(linkForNode(n,relative), arg, &html); + html += QLatin1String("</span>"); + } + else if (parseArg(src, headerTag, &i, srcSize, &arg, &par1)) { + par1 = QStringRef(); + if (arg.at(0) == QChar('&')) + html += arg; + else { + const Node* n = qdb_->findNodeForInclude(QStringList(arg.toString())); + if (n && n != relative) + addLink(linkForNode(n,relative), arg, &html); + else + html += arg; + } + } + else { + html += charLangle; + html += charAt; + } + } + else { + html += src.at(i++); + } + } + + // replace all + // "<@comment>" -> "<span class=\"comment\">"; + // "<@preprocessor>" -> "<span class=\"preprocessor\">"; + // "<@string>" -> "<span class=\"string\">"; + // "<@char>" -> "<span class=\"char\">"; + // "<@number>" -> "<span class=\"number\">"; + // "<@op>" -> "<span class=\"operator\">"; + // "<@type>" -> "<span class=\"type\">"; + // "<@name>" -> "<span class=\"name\">"; + // "<@keyword>" -> "<span class=\"keyword\">"; + // "</@(?:comment|preprocessor|string|char|number|op|type|name|keyword)>" -> "</span>" + src = html; + html = QString(); + html.reserve(src.size()); + static const QLatin1String spanTags[] = { + QLatin1String("comment>"), QLatin1String("<span class=\"comment\">"), + QLatin1String("preprocessor>"), QLatin1String("<span class=\"preprocessor\">"), + QLatin1String("string>"), QLatin1String("<span class=\"string\">"), + QLatin1String("char>"), QLatin1String("<span class=\"char\">"), + QLatin1String("number>"), QLatin1String("<span class=\"number\">"), + QLatin1String("op>"), QLatin1String("<span class=\"operator\">"), + QLatin1String("type>"), QLatin1String("<span class=\"type\">"), + QLatin1String("name>"), QLatin1String("<span class=\"name\">"), + QLatin1String("keyword>"), QLatin1String("<span class=\"keyword\">") + }; + int nTags = 9; + // 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) == QLatin1Char('<')) { + if (src.at(i + 1) == QLatin1Char('@')) { + i += 2; + bool handled = false; + for (int k = 0; k != nTags; ++k) { + const QLatin1String& tag = spanTags[2 * k]; + if (i + tag.size() <= src.length() && + tag == QStringRef(&src, i, tag.size())) { + html += spanTags[2 * k + 1]; + i += tag.size(); + handled = true; + break; + } + } + if (!handled) { + // drop 'our' unknown tags (the ones still containing '@') + while (i < n && src.at(i) != QLatin1Char('>')) + ++i; + ++i; + } + continue; + } + else if (src.at(i + 1) == QLatin1Char('/') && src.at(i + 2) == QLatin1Char('@')) { + i += 3; + bool handled = false; + for (int k = 0; k != nTags; ++k) { + const QLatin1String& tag = spanTags[2 * k]; + if (i + tag.size() <= src.length() && + tag == QStringRef(&src, i, tag.size())) { + html += QLatin1String("</span>"); + i += tag.size(); + handled = true; + break; + } + } + if (!handled) { + // drop 'our' unknown tags (the ones still containing '@') + while (i < n && src.at(i) != QLatin1Char('>')) + ++i; + ++i; + } + continue; + } + } + 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() << "</i>"; + } else { + out() << "</a>"; + } + inLink_ = false; + out() << protectEnc(atom->string().mid(k)); + } else { + out() << protectEnc(atom->string()); + } +} + +QString HtmlGenerator::registerRef(const QString& ref) +{ + QString clean = Generator::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 == QLatin1String("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("&#x"); + 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->isAggregate()) { + switch (node->status()) { + case Node::Compat: + result += QLatin1String("-compat"); + break; + case Node::Obsolete: + result += QLatin1String("-obsolete"); + break; + default: + ; + } + } + return result; +} + +QString HtmlGenerator::fileName(const Node *node) +{ + if (node->type() == Node::Document) { + if (static_cast<const DocumentNode *>(node)->docSubtype() == Node::ExternalPage) + return node->name(); + if (static_cast<const DocumentNode *>(node)->docSubtype() == 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<const TypedefNode *>(node); + if (typedeffe->associatedEnum()) { + return refForNode(typedeffe->associatedEnum()); + } + else { + ref = node->name() + "-typedef"; + } + break; + case Node::Function: + func = static_cast<const FunctionNode *>(node); + if (func->hasOneAssociatedProperty() && func->doc().isEmpty()) { + return refForNode(func->firstAssociatedProperty()); + } + else { + ref = func->name(); + if (func->overloadNumber() != 0) + ref += QLatin1Char('-') + QString::number(func->overloadNumber()); + } + break; + case Node::Document: + break; + case Node::QmlProperty: + if (node->isAttached()) + ref = node->name() + "-attached-prop"; + else + ref = node->name() + "-prop"; + break; + case Node::QmlPropertyGroup: + 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<const FunctionNode *>(node); + ref = func->name() + "-method"; + if (func->overloadNumber() != 0) + ref += QLatin1Char('-') + QString::number(func->overloadNumber()); + break; + case Node::Variable: + ref = node->name() + "-var"; + break; + } + return registerRef(ref); +} + +/*! + 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; + } + return getAutoLink(atom, relative, node); +} + +/*! + 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; + + *node = qdb_->findNodeForAtom(atom, relative, ref); + if (!(*node)) { + return QString(); + } + + QString link = (*node)->url(); + if (link.isEmpty()) { + link = linkForNode(*node, relative); + if ((*node)->docSubtype() == Node::Image) + link = "images/used-in-examples/" + link; + } + if (!ref.isEmpty()) { + int hashtag = link.lastIndexOf(QChar('#')); + if (hashtag != -1) + link.truncate(hashtag); + 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) + 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 && node->parent() && + (node->parent()->isQmlType() || node->parent()->isJsType()) + && node->parent()->isAbstract()) { + if (Generator::qmlTypeContext()) + fn = fileName(Generator::qmlTypeContext()); + } + QString link = fn; + + if (!node->isAggregate() || node->isQmlPropertyGroup() || node->isJsPropertyGroup()) { + 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()) { + if (link.startsWith(QString(node->outputSubdirectory() + QLatin1Char('/')))) { + link.prepend(QString("../")); + } + else { + 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() << "<a href=\"" << linkForNode(actualNode, relative); + if (true || relative == 0 || relative->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() << "</a>"; +} + +void HtmlGenerator::generateDetailedMember(const Node *node, + const Aggregate *relative, + CodeMarker *marker) +{ + const EnumNode *etn; +#ifdef GENERATE_MAC_REFS + generateMacRef(node, marker); +#endif + generateExtractionMark(node, MemberMark); + generateKeywordAnchors(node); + QString nodeRef = refForNode(node); + if (node->isEnumType() && (etn = static_cast<const EnumNode *>(node))->flagsType()) { +#ifdef GENERATE_MAC_REFS + generateMacRef(etn->flagsType(), marker); +#endif + out() << "<h3 class=\"flags\" id=\"" << nodeRef << "\">"; + out() << "<a name=\"" + nodeRef + "\"></a>"; + generateSynopsis(etn, relative, marker, CodeMarker::Detailed); + out() << "<br/>"; + generateSynopsis(etn->flagsType(), + relative, + marker, + CodeMarker::Detailed); + out() << "</h3>\n"; + } + else { + out() << "<h3 class=\"fn\" id=\"" << nodeRef << "\">"; + out() << "<a name=\"" + nodeRef + "\"></a>"; + generateSynopsis(node, relative, marker, CodeMarker::Detailed); + out() << "</h3>" << divNavTop << '\n'; + } + + generateStatus(node, marker); + generateBody(node, marker); + generateOverloadedSignal(node, marker); + generateThreadSafeness(node, marker); + generateSince(node, marker); + + if (node->isProperty()) { + const PropertyNode *property = static_cast<const PropertyNode *>(node); + Section section; + + section.members += property->getters(); + section.members += property->setters(); + section.members += property->resetters(); + + if (!section.members.isEmpty()) { + out() << "<p><b>Access functions:</b></p>\n"; + generateSectionList(section, node, marker, CodeMarker::Accessors); + } + + Section notifiers; + notifiers.members += property->notifiers(); + + if (!notifiers.members.isEmpty()) { + out() << "<p><b>Notifier signal:</b></p>\n"; + //out() << "<p>This signal is emitted when the property value is changed.</p>\n"; + generateSectionList(notifiers, node, marker, CodeMarker::Accessors); + } + } + else if (node->isFunction()) { + const FunctionNode* fn = static_cast<const FunctionNode*>(node); + if (fn->isPrivateSignal()) + generatePrivateSignalNote(node, marker); + generateAssociatedPropertyNotes(fn); + } + else if (node->isEnumType()) { + const EnumNode *etn = static_cast<const EnumNode *>(node); + if (etn->flagsType()) { + out() << "<p>The " << protectEnc(etn->flagsType()->name()) + << " type is a typedef for " + << "<a href=\"" << qflagsHref_ << "\">QFlags</a><" + << protectEnc(etn->name()) + << ">. It stores an OR combination of " + << protectEnc(etn->name()) + << " values.</p>\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::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 QPair<QString,QString> HtmlGenerator::anchorForNode(const Node *node) +{ + QPair<QString,QString> anchorPair; + + anchorPair.first = Generator::fileName(node); + if (node->type() == Node::Document) { + const DocumentNode *docNode = static_cast<const DocumentNode*>(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->isAggregate()) + Generator::generateStatus(node, marker); + break; + case Node::Compat: + // Porting to Qt 4 no longer supported + 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() << "<a name=\"" << "//apple_ref/" << macRef << "\"></a>\n"; +} +#endif + +void HtmlGenerator::beginLink(const QString &link, const Node *node, const Node *relative) +{ + link_ = link; + if (link_.isEmpty()) { + if (showBrokenLinks) + out() << "<i>"; + } + else if (node == 0 || + (relative != 0 && node->status() == relative->status())) { + out() << "<a href=\"" << link_ << "\">"; + } + else { + switch (node->status()) { + case Node::Obsolete: + out() << "<a href=\"" << link_ << "\" class=\"obsolete\">"; + break; + case Node::Compat: + out() << "<a href=\"" << link_ << "\" class=\"compat\">"; + break; + default: + out() << "<a href=\"" << link_ << "\">"; + } + } + inLink_ = true; +} + +void HtmlGenerator::endLink() +{ + if (inLink_) { + if (link_.isEmpty()) { + if (showBrokenLinks) + out() << "</i>"; + } + else { + if (inObsoleteLink) { + out() << "<sup>(obsolete)</sup>"; + } + out() << "</a>"; + } + } + 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() << "<ul>\n"; + NodeList::ConstIterator m; + m = section.members.constBegin(); + while (m != section.members.constEnd()) { + out() << "<li class=\"fn\">"; + generateQmlItem(*m,relative,marker,true); + if ((*m)->type() == Node::QmlPropertyGroup) { + const QmlPropertyGroupNode* qpgn = static_cast<const QmlPropertyGroupNode*>(*m); + if (!qpgn->childNodes().isEmpty()) { + NodeList::ConstIterator p = qpgn->childNodes().constBegin(); + out() << "<ul>\n"; + while (p != qpgn->childNodes().constEnd()) { + if ((*p)->type() == Node::QmlProperty) { + out() << "<li class=\"fn\">"; + generateQmlItem(*p, relative, marker, true); + out() << "</li>\n"; + } + ++p; + } + out() << "</ul>\n"; + } + } + out() << "</li>\n"; + ++m; + } + out() << "</ul>\n"; + } +} + +/*! + Outputs the html detailed documentation for a section + on a QML element reference page. + */ +void HtmlGenerator::generateDetailedQmlMember(Node *node, + const Aggregate *relative, + CodeMarker *marker) +{ + QmlPropertyNode* qpn = 0; +#ifdef GENERATE_MAC_REFS + generateMacRef(node, marker); +#endif + generateExtractionMark(node, MemberMark); + generateKeywordAnchors(node); + + QString qmlItemHeader("<div class=\"qmlproto\">\n" + "<div class=\"table\"><table class=\"qmlname\">\n" + "<tr valign=\"top\" class=\"odd\" id=\"%1\">\n" + "<td class=\"%2\"><p>\n" + "<a name=\"%3\"></a>"); + + QString qmlItemFooter("</p></td></tr>\n" + "</table></div>\n" + "</div>"); + + out() << "<div class=\"qmlitem\">"; + QString nodeRef = refForNode(node); + if (node->type() == Node::QmlPropertyGroup) { + const QmlPropertyGroupNode* qpgn = static_cast<const QmlPropertyGroupNode*>(node); + NodeList::ConstIterator p = qpgn->childNodes().constBegin(); + out() << "<div class=\"qmlproto\">"; + out() << "<div class=\"table\"><table class=\"qmlname\">"; + + QString heading = qpgn->name() + " group"; + out() << "<tr valign=\"top\" class=\"even\" id=\"" << nodeRef << "\">"; + out() << "<th class=\"centerAlign\"><p>"; + out() << "<a name=\"" + nodeRef + "\"></a>"; + out() << "<b>" << heading << "</b>"; + out() << "</p></th></tr>"; + while (p != qpgn->childNodes().constEnd()) { + if ((*p)->type() == Node::QmlProperty) { + qpn = static_cast<QmlPropertyNode*>(*p); + nodeRef = refForNode(qpn); + out() << "<tr valign=\"top\" class=\"odd\" id=\"" << nodeRef << "\">"; + out() << "<td class=\"tblQmlPropNode\"><p>"; + out() << "<a name=\"" + nodeRef + "\"></a>"; + + if (!qpn->isWritable()) + out() << "<span class=\"qmlreadonly\">read-only</span>"; + if (qpn->isDefault()) + out() << "<span class=\"qmldefault\">default</span>"; + generateQmlItem(qpn, relative, marker, false); + out() << "</p></td></tr>"; + } + ++p; + } + out() << "</table></div>"; + out() << "</div>"; + } + else if (node->type() == Node::QmlProperty) { + qpn = static_cast<QmlPropertyNode*>(node); + out() << qmlItemHeader.arg(nodeRef, "tblQmlPropNode", refForNode(qpn)); + if (!qpn->isReadOnlySet()) { + if (qpn->declarativeCppNode()) + qpn->setReadOnly(!qpn->isWritable()); + } + if (qpn->isReadOnly()) + out() << "<span class=\"qmlreadonly\">read-only</span>"; + if (qpn->isDefault()) + out() << "<span class=\"qmldefault\">default</span>"; + generateQmlItem(qpn, relative, marker, false); + out() << qmlItemFooter; + } + else if (node->type() == Node::QmlSignal || + node->type() == Node::QmlSignalHandler || + node->type() == Node::QmlMethod) { + out() << qmlItemHeader.arg(nodeRef, "tblQmlFuncNode", refForNode(node)); + generateSynopsis(node, relative, marker, CodeMarker::Detailed, false); + out() << qmlItemFooter; + } + out() << "<div class=\"qmldoc\">"; + generateStatus(node, marker); + generateBody(node, marker); + generateThreadSafeness(node, marker); + generateSince(node, marker); + generateAlsoList(node, marker); + out() << "</div>"; + out() << "</div>"; + generateExtractionMark(node, EndMark); +} + +/*! + Output the "Inherits" line for the QML element, + if there should be one. + */ +void HtmlGenerator::generateQmlInherits(QmlTypeNode* qcn, CodeMarker* marker) +{ + if (!qcn) + return; + QmlTypeNode* 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(QmlTypeNode* 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 QmlTypeNode* 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); + if (qcn->isQmlType()) + text << " is instantiated by QML Type "; + else + text << " is instantiated by Javascript 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() << "<!-- $$$" + node->name(); + if (markType == MemberMark) { + if (node->type() == Node::Function) { + const FunctionNode *func = static_cast<const FunctionNode *>(node); + if (!func->hasAssociatedProperties()) { + if (func->overloadNumber() == 0) + out() << "[overload1]"; + out() << "$$$" + func->name() + func->rawParameters().remove(' '); + } + } else if (node->type() == Node::Property) { + out() << "-prop"; + const PropertyNode *prop = static_cast<const PropertyNode *>(node); + const NodeList &list = prop->functions(); + foreach (const Node *propFuncNode, list) { + if (propFuncNode->type() == Node::Function) { + const FunctionNode *func = static_cast<const FunctionNode *>(propFuncNode); + out() << "$$$" + func->name() + func->rawParameters().remove(' '); + } + } + } else if (node->type() == Node::Enum) { + const EnumNode *enumNode = static_cast<const EnumNode *>(node); + foreach (const EnumItem &item, enumNode->items()) + out() << "$$$" + item.name(); + } + } else if (markType == BriefMark) { + out() << "-brief"; + } else if (markType == DetailedDescriptionMark) { + out() << "-description"; + } + out() << " -->\n"; + } else { + out() << "<!-- @@@" + node->name() + " -->\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(const QString &manifest, const QString &element) +{ + ExampleNodeMap& exampleNodeMap = qdb_->exampleNodeMap(); + if (exampleNodeMap.isEmpty()) + return; + QString fileName = manifest +"-manifest.xml"; + QFile file(outputDir() + QLatin1Char('/') + fileName); + bool demos = false; + if (manifest == QLatin1String("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 || !file.open(QFile::WriteOnly | QFile::Text)) + return; + + QXmlStreamWriter writer(&file); + writer.setAutoFormatting(true); + writer.writeStartDocument(); + writer.writeStartElement("instructionals"); + writer.writeAttribute("module", project); + writer.writeStartElement(manifest); + + QStringList usedAttributes; + 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; + } + // attributes that are always written for the element + usedAttributes.clear(); + usedAttributes << "name" << "docUrl" << "projectPath"; + + 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->docSubtype() == 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()); + usedAttributes << "imageUrl"; + } + + QString fullName = project + QLatin1Char('/') + en->title(); + QSet<QString> 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(); + if (!usedAttributes.contains(attrName)) { + writer.writeAttribute(attrName, attrList.join(div)); + usedAttributes << attrName; + } + } + } + } + } + + 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 + // QtOpenGL -> qt,opengl + QRegExp re("([A-Z]+[a-z0-9]*(3D|GL)?)"); + int pos = 0; + while ((pos = re.indexIn(project, pos)) != -1) { + tags << re.cap(1).toLower(); + pos += re.matchedLength(); + } + tags += QSet<QString>::fromList(en->title().toLower().split(QLatin1Char(' '))); + + // Clean up tags, exclude invalid and common words + QSet<QString>::iterator tag_it = tags.begin(); + QSet<QString> modified; + while (tag_it != tags.end()) { + QString s = *tag_it; + if (s.at(0) == '(') + s.remove(0, 1).chop(1); + if (s.endsWith(QLatin1Char(':'))) + s.chop(1); + + if (s.length() < 2 + || s.at(0).isDigit() + || s.at(0) == '-' + || s == QLatin1String("qt") + || s == QLatin1String("the") + || s == QLatin1String("and") + || s.startsWith(QLatin1String("example")) + || s.startsWith(QLatin1String("chapter"))) + tag_it = tags.erase(tag_it); + else if (s != *tag_it) { + modified << s; + tag_it = tags.erase(tag_it); + } + else + ++tag_it; + } + tags += modified; + + if (!tags.isEmpty()) { + writer.writeStartElement("tags"); + bool wrote_one = false; + foreach (const QString &tag, tags) { + if (wrote_one) + writer.writeCharacters(","); + writer.writeCharacters(tag); + wrote_one = true; + } + writer.writeEndElement(); // tags + } + + QString ename = en->name().mid(en->name().lastIndexOf('/')+1); + QMap<int, const Node*> filesToOpen; + foreach (const Node* child, en->childNodes()) { + if (child->docSubtype() == Node::File) { + QFileInfo fileInfo(child->name()); + QString fileName = fileInfo.fileName().toLower(); + // open .qml, .cpp and .h files with a + // basename matching the example (project) name + // QMap key indicates the priority - + // the lowest value will be the top-most file + if ((fileInfo.baseName().compare(ename, Qt::CaseInsensitive) == 0)) { + if (fileName.endsWith(".qml")) + filesToOpen.insert(0, child); + else if (fileName.endsWith(".cpp")) + filesToOpen.insert(1, child); + else if (fileName.endsWith(".h")) + filesToOpen.insert(2, child); + } + // main.qml takes precedence over main.cpp + else if (fileName.endsWith("main.qml")) { + filesToOpen.insert(3, child); + } + else if (fileName.endsWith("main.cpp")) { + filesToOpen.insert(4, child); + } + } + } + + QMap<int, const Node*>::const_iterator it = filesToOpen.constEnd(); + while (it != filesToOpen.constBegin()) { + writer.writeStartElement("fileToOpen"); + if (--it == filesToOpen.constBegin()) { + writer.writeAttribute(QStringLiteral("mainFile"), QStringLiteral("true")); + } + writer.writeCharacters(examplesPath + it.value()->name()); + writer.writeEndElement(); + } + + 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. + + The manifest metacontent map is cleared immediately after + the manifest files have been generated. + */ +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 Aggregate* parent) +{ + const NodeList& children = parent->childNodes(); + if (children.size() == 0) + return; + + bool related; + QString message; + for (int i=0; i<children.size(); ++i) { + Node* child = children[i]; + if (!child || child->isInternal() || 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->docSubtype()) { + case Node::Example: + break; + case Node::HeaderFile: + break; + case Node::File: + break; + case Node::Image: + break; + case Node::Page: + break; + case Node::ExternalPage: + 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<const FunctionNode*>(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 Aggregate* 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 = "<!DOCTYPE map PUBLIC \"-//OASIS//DTD DITA Map//EN\" \"map.dtd\">"; + + xmlWriter().writeDTD(doctype); + xmlWriter().writeStartElement("map"); + xmlWriter().writeStartElement("topicmeta"); + xmlWriter().writeStartElement("shortdesc"); + xmlWriter().writeCharacters(node->title()); + xmlWriter().writeEndElement(); // </shortdesc> + xmlWriter().writeEndElement(); // </topicmeta> + 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 DocumentNode* dn = qdb_->findDocumentNodeByTitle(t->navtitle()); + if (dn) + xmlWriter().writeAttribute("href",fileName(dn)); + } + else + xmlWriter().writeAttribute("href",t->href()); + if (t->subrefs() && !t->subrefs()->isEmpty()) + writeDitaRefs(*(t->subrefs())); + xmlWriter().writeEndElement(); // </topicref> or </mapref> + } +} + +/*! + Generates bold Note lines that explain how function \a fn + is associated with each of its associated properties. + */ +void HtmlGenerator::generateAssociatedPropertyNotes(const FunctionNode* fn) +{ + if (fn->hasAssociatedProperties()) { + out() << "<p><b>Note:</b> "; + foreach (const PropertyNode* pn, fn->associatedProperties()) { + 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 <a href=\"" << link << "\">" << pn->name() << "</a>. "; + } + out() << "</p>"; + } +} + +QT_END_NAMESPACE |