diff options
Diffstat (limited to 'src/tools/qdoc/htmlgenerator.cpp')
-rw-r--r-- | src/tools/qdoc/htmlgenerator.cpp | 5115 |
1 files changed, 5115 insertions, 0 deletions
diff --git a/src/tools/qdoc/htmlgenerator.cpp b/src/tools/qdoc/htmlgenerator.cpp new file mode 100644 index 0000000000..dd3a0fcbfc --- /dev/null +++ b/src/tools/qdoc/htmlgenerator.cpp @@ -0,0 +1,5115 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/* + htmlgenerator.cpp +*/ + +#include "codemarker.h" +#include "codeparser.h" +#include "helpprojectwriter.h" +#include "htmlgenerator.h" +#include "node.h" +#include "separator.h" +#include "tree.h" +#include <ctype.h> +#include <qdebug.h> +#include <qlist.h> +#include <qiterator.h> +#include <qtextcodec.h> +#include <QUuid> + +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 += "<a href=\""; + *res += linkTarget; + *res += "\">"; + *res += nestedStuff; + *res += "</a>"; + } + else { + *res += nestedStuff; + } +} + + +HtmlGenerator::HtmlGenerator() + : helpProjectWriter(0), + inLink(false), + inObsoleteLink(false), + inContents(false), + inSectionHeading(false), + inTableHeader(false), + numTableRows(0), + threeColumnEnumValueTable(true), + funcLeftParen("\\S(\\()"), + myTree(0), + obsoleteLinks(false) +{ +} + +/*! + The destructor deletes the instance of HelpProjectWriter. + */ +HtmlGenerator::~HtmlGenerator() +{ + if (helpProjectWriter) + delete helpProjectWriter; +} + +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, "<tt>", "</tt>" }, + { ATOM_FORMATTING_UNDERLINE, "<u>", "</u>" }, + { 0, 0, 0 } + }; + + Generator::initializeGenerator(config); + obsoleteLinks = config.getBool(QLatin1String(CONFIG_OBSOLETELINKS)); + setImageFileExtensions(QStringList() << "png" << "jpg" << "jpeg" << "gif"); + int i = 0; + while (defaults[i].key) { + formattingLeftMap().insert(defaults[i].key, defaults[i].left); + formattingRightMap().insert(defaults[i].key, defaults[i].right); + i++; + } + + style = config.getString(HtmlGenerator::format() + + Config::dot + + CONFIG_STYLE); + endHeader = config.getString(HtmlGenerator::format() + + Config::dot + + CONFIG_ENDHEADER); + postHeader = config.getString(HtmlGenerator::format() + + Config::dot + + HTMLGENERATOR_POSTHEADER); + postPostHeader = config.getString(HtmlGenerator::format() + + Config::dot + + HTMLGENERATOR_POSTPOSTHEADER); + footer = config.getString(HtmlGenerator::format() + + Config::dot + + HTMLGENERATOR_FOOTER); + address = config.getString(HtmlGenerator::format() + + Config::dot + + HTMLGENERATOR_ADDRESS); + pleaseGenerateMacRef = config.getBool(HtmlGenerator::format() + + Config::dot + + HTMLGENERATOR_GENERATEMACREFS); + noBreadCrumbs = config.getBool(HtmlGenerator::format() + + Config::dot + + HTMLGENERATOR_NOBREADCRUMBS); + + project = config.getString(CONFIG_PROJECT); + + projectDescription = config.getString(CONFIG_DESCRIPTION); + if (projectDescription.isEmpty() && !project.isEmpty()) + projectDescription = project + " Reference Documentation"; + + projectUrl = config.getString(CONFIG_URL); + + outputEncoding = config.getString(CONFIG_OUTPUTENCODING); + if (outputEncoding.isEmpty()) + outputEncoding = QLatin1String("ISO-8859-1"); + outputCodec = QTextCodec::codecForName(outputEncoding.toLocal8Bit()); + + naturalLanguage = config.getString(CONFIG_NATURALLANGUAGE); + if (naturalLanguage.isEmpty()) + naturalLanguage = QLatin1String("en"); + + QSet<QString> editionNames = config.subVars(CONFIG_EDITION); + QSet<QString>::ConstIterator edition = editionNames.begin(); + while (edition != editionNames.end()) { + 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); + + helpProjectWriter = new HelpProjectWriter(config, + project.toLower() + + ".qhp"); + + // 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 + "Qt" + Config::dot; + manifestDir = "qthelp://" + config.getString(prefix + "namespace"); + manifestDir += QLatin1Char('/') + config.getString(prefix + "virtualFolder") + QLatin1Char('/'); + + /* + If the output files will be in subdirectores in the output + directory, change the references to files in the style and + scripts subdirectories that appear in the headerscipts and + headerstyles string so that they link to the correct files, + whic means prepending "../" to each + */ + if (!baseDir().isEmpty()) { + headerScripts = headerScripts.replace("style/","../style/"); + headerScripts = headerScripts.replace("scripts/","../scripts/"); + headerStyles = headerStyles.replace("style/","../style/"); + headerStyles = headerStyles.replace("scripts/","../scripts/"); + } +} + +void HtmlGenerator::terminateGenerator() +{ + Generator::terminateGenerator(); +} + +QString HtmlGenerator::format() +{ + return "HTML"; +} + +/*! + This is where the HTML files are written. + \note The HTML file generation is done in the base class, + PageGenerator::generateTree(). + */ +void HtmlGenerator::generateTree(const Tree *tree) +{ + myTree = tree; + nonCompatClasses.clear(); + mainClasses.clear(); + compatClasses.clear(); + obsoleteClasses.clear(); + moduleClassMap.clear(); + moduleNamespaceMap.clear(); + funcIndex.clear(); + legaleseTexts.clear(); + serviceClasses.clear(); + qmlClasses.clear(); + findAllClasses(tree->root()); + findAllFunctions(tree->root()); + findAllLegaleseTexts(tree->root()); + findAllNamespaces(tree->root()); + findAllSince(tree->root()); + + PageGenerator::generateTree(tree); + reportOrphans(tree->root()); + generateDisambiguationPages(); + + QString fileBase = project.toLower().simplified().replace(" ", "-"); + generateIndex(fileBase, projectUrl, projectDescription); + generatePageIndex(outputDir() + QLatin1Char('/') + fileBase + ".pageindex"); + + helpProjectWriter->generate(myTree); + generateManifestFiles(); +} + +void HtmlGenerator::startText(const Node * /* relative */, + CodeMarker * /* marker */) +{ + inLink = false; + inContents = false; + inSectionHeading = false; + inTableHeader = false; + numTableRows = 0; + threeColumnEnumValueTable = true; + link.clear(); + sectionNumber.clear(); +} + +/*! + Generate html from an instance of Atom. + */ +int HtmlGenerator::generateAtom(const Atom *atom, + const Node *relative, + CodeMarker *marker) +{ + int skipAhead = 0; + static bool in_para = false; + + switch (atom->type()) { + case Atom::AbstractLeft: + if (relative) + relative->doc().location().warning(tr("\abstract is not implemented.")); + else + Location::information(tr("\abstract is not implemented.")); + break; + case Atom::AbstractRight: + break; + case Atom::AutoLink: + if (!inLink && !inContents && !inSectionHeading) { + const Node *node = 0; + QString link = getLink(atom, relative, marker, &node); + if (!link.isEmpty()) { + beginLink(link, node, relative, marker); + generateLink(atom, relative, marker); + endLink(); + } + else { + out() << protectEnc(atom->string()); + } + } + else { + out() << protectEnc(atom->string()); + } + break; + case Atom::BaseName: + break; + case Atom::BriefLeft: + if (relative->type() == Node::Fake) { + if (relative->subType() != Node::Example) { + 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() == "contains" || words.first() == "specifies" + || words.first() == "describes" || words.first() == "defines" + || words.first() == "holds" || words.first() == "determines")) + out() << " holds "; + else + out() << ' '; + out() << str << '.'; + } + break; + case Atom::BriefRight: + if (relative->type() != Node::Fake) + 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()), + marker,relative)) + << "</pre>\n"; + break; +#ifdef QDOC_QML + case Atom::Qml: + out() << "<pre class=\"qml\">" + << trimmedTrailing(highlightedCode(indent(codeIndent,atom->string()), + marker,relative)) + << "</pre>\n"; + break; + case Atom::JavaScript: + out() << "<pre class=\"js\">" + << trimmedTrailing(highlightedCode(indent(codeIndent,atom->string()), + marker,relative)) + << "</pre>\n"; + break; +#endif + case Atom::CodeNew: + out() << "<p>you can rewrite it as</p>\n" + << "<pre class=\"cpp\">" + << trimmedTrailing(highlightedCode(indent(codeIndent,atom->string()), + marker,relative)) + << "</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())))) + << "</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: + { + QList<Node*> values = myTree->groups().values(atom->string()); + NodeMap nodeMap; + for (int i = 0; i < values.size(); ++i) { + const Node* n = values.at(i); + if ((n->status() != Node::Internal) && (n->access() != Node::Private)) { + nodeMap.insert(n->nameForLists(),n); + } + } + generateAnnotatedList(relative, marker, nodeMap); + } + break; + case Atom::GeneratedList: + if (atom->string() == "annotatedclasses") { + generateAnnotatedList(relative, marker, nonCompatClasses); + } + else if (atom->string() == "classes") { + generateCompactList(relative, marker, nonCompatClasses, true); + } + else if (atom->string() == "qmlclasses") { + generateCompactList(relative, marker, qmlClasses, true); + } + else if (atom->string().contains("classesbymodule")) { + QString arg = atom->string().trimmed(); + QString moduleName = atom->string().mid(atom->string().indexOf( + "classesbymodule") + 15).trimmed(); + if (moduleClassMap.contains(moduleName)) + generateAnnotatedList(relative, marker, moduleClassMap[moduleName]); + } + else if (atom->string().contains("classesbyedition")) { + + QString arg = atom->string().trimmed(); + QString editionName = atom->string().mid(atom->string().indexOf( + "classesbyedition") + 16).trimmed(); + + if (editionModuleMap.contains(editionName)) { + + // Add all classes in the modules listed for that edition. + NodeMap editionClasses; + foreach (const QString &moduleName, editionModuleMap[editionName]) { + if (moduleClassMap.contains(moduleName)) + editionClasses.unite(moduleClassMap[moduleName]); + } + + // Add additional groups and remove groups of classes that + // should be excluded from the edition. + + QMultiMap <QString, Node *> groups = myTree->groups(); + foreach (const QString &groupName, editionGroupMap[editionName]) { + QList<Node *> groupClasses; + if (groupName.startsWith(QLatin1Char('-'))) { + groupClasses = groups.values(groupName.mid(1)); + foreach (const Node *node, groupClasses) + editionClasses.remove(node->name()); + } + else { + groupClasses = groups.values(groupName); + foreach (const Node *node, groupClasses) + editionClasses.insert(node->name(), node); + } + } + generateAnnotatedList(relative, marker, editionClasses); + } + } + else if (atom->string() == "classhierarchy") { + generateClassHierarchy(relative, marker, nonCompatClasses); + } + else if (atom->string() == "compatclasses") { + generateCompactList(relative, marker, compatClasses, false); + } + else if (atom->string() == "obsoleteclasses") { + generateCompactList(relative, marker, obsoleteClasses, false); + } + else if (atom->string() == "functionindex") { + generateFunctionIndex(relative, marker); + } + else if (atom->string() == "legalese") { + generateLegaleseList(relative, marker); + } + else if (atom->string() == "mainclasses") { + generateCompactList(relative, marker, mainClasses, true); + } + else if (atom->string() == "services") { + generateCompactList(relative, marker, serviceClasses, false); + } + else if (atom->string() == "overviews") { + generateOverviewList(relative, marker); + } + else if (atom->string() == "namespaces") { + generateAnnotatedList(relative, marker, namespaceIndex); + } + else if (atom->string() == "related") { + const FakeNode *fake = static_cast<const FakeNode *>(relative); + if (fake && !fake->groupMembers().isEmpty()) { + NodeMap groupMembersMap; + foreach (const Node *node, fake->groupMembers()) { + if (node->type() == Node::Fake) + groupMembersMap[fullName(node, relative, marker)] = node; + } + generateAnnotatedList(fake, marker, groupMembersMap); + } + } + else if (atom->string() == "relatedinline") { + const FakeNode *fake = static_cast<const FakeNode *>(relative); + if (fake && !fake->groupMembers().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, fake->groupMembers()) + list.prepend(node); + foreach (const Node *node, list) + generateBody(node, marker); + } + } + break; + case Atom::SinceList: + { + NewSinceMaps::const_iterator nsmap; + nsmap = newSinceMaps.find(atom->string()); + NewClassMaps::const_iterator ncmap; + ncmap = newClassMaps.find(atom->string()); + NewClassMaps::const_iterator nqcmap; + nqcmap = newQmlClassMaps.find(atom->string()); + + if ((nsmap != newSinceMaps.constEnd()) && !nsmap.value().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.value().constBegin(); + + while (n != nsmap.value().constEnd()) { + + const Node* node = n.value(); + switch (node->type()) { + case Node::Fake: + if (node->subType() == Node::QmlClass) { + sections[QmlClass].appendMember((Node*)node); + } + break; + case Node::Namespace: + sections[Namespace].appendMember((Node*)node); + break; + case Node::Class: + sections[Class].appendMember((Node*)node); + break; + case Node::Enum: + sections[Enum].appendMember((Node*)node); + break; + case Node::Typedef: + sections[Typedef].appendMember((Node*)node); + break; + case Node::Function: { + const FunctionNode* fn = static_cast<const FunctionNode*>(node); + if (fn->isMacro()) + sections[Macro].appendMember((Node*)node); + else { + Node* p = fn->parent(); + if (p) { + if (p->type() == Node::Class) + sections[MemberFunction].appendMember((Node*)node); + else if (p->type() == Node::Namespace) { + if (p->name().isEmpty()) + sections[GlobalFunction].appendMember((Node*)node); + else + sections[NamespaceFunction].appendMember((Node*)node); + } + else + sections[GlobalFunction].appendMember((Node*)node); + } + else + sections[GlobalFunction].appendMember((Node*)node); + } + break; + } + case Node::Property: + sections[Property].appendMember((Node*)node); + break; + case Node::Variable: + sections[Variable].appendMember((Node*)node); + break; + case Node::QmlProperty: + sections[QmlProperty].appendMember((Node*)node); + break; + case Node::QmlSignal: + sections[QmlSignal].appendMember((Node*)node); + break; + case Node::QmlSignalHandler: + sections[QmlSignalHandler].appendMember((Node*)node); + break; + case Node::QmlMethod: + sections[QmlMethod].appendMember((Node*)node); + break; + default: + break; + } + ++n; + } + + /* + First generate the table of contents. + */ + 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(0, marker, ncmap.value(), false, QString("Q")); + else if (idx == QmlClass) + generateCompactList(0, marker, nqcmap.value(), false, QString("Q")); + 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 = fullName(pmap.key(), 0, marker).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::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()) { + out() << "<font color=\"red\">[Missing image " + << protectEnc(atom->string()) << "]</font>"; + } + else { + QString prefix = ""; + if (!baseDir().isEmpty()) + prefix = "../"; + out() << "<img src=\"" << protectEnc(prefix + fileName) << '"'; + if (!text.isEmpty()) + out() << " alt=\"" << protectEnc(text) << '"'; + else + out() << " alt=\"\""; + out() << " />"; + helpProjectWriter->addExtraFile(fileName); + if ((relative->type() == Node::Fake) && + (relative->subType() == Node::Example)) { + 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: + { + const Node *node = 0; + QString myLink = getLink(atom, relative, marker, &node); + if (myLink.isEmpty()) { + myLink = getDisambiguationLink(atom, marker); + if (myLink.isEmpty()) { + relative->doc().location().warning(tr("Can't create link to '%1'") + .arg(atom->string())); + } + else + node = 0; + } + beginLink(myLink, node, relative, marker); + skipAhead = 1; + } + break; + case Atom::LinkNode: + { + const Node *node = CodeMarker::nodeForString(atom->string()); + beginLink(linkForNode(node, relative), node, relative, marker); + 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) { + threeColumnEnumValueTable = isThreeColumnEnumValueTable(atom); + if (threeColumnEnumValueTable) { + out() << "<table class=\"valuelist\">"; + if (++numTableRows % 2 == 1) + out() << "<tr valign=\"top\" class=\"odd\">"; + else + out() << "<tr valign=\"top\" class=\"even\">"; + + out() << "<th class=\"tblConst\">Constant</th>" + << "<th class=\"tblval\">Value</th>" + << "<th class=\"tbldscr\">Description</th></tr>\n"; + } + else { + out() << "<table class=\"valuelist\">" + << "<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 + + out() << "<tr><td class=\"topAlign\"><tt>" + << protectEnc(plainCode(marker->markedUpEnumValue(atom->next()->string(), + relative))) + << "</tt></td><td class=\"topAlign\">"; + + QString itemValue; + if (relative->type() == Node::Enum) { + const EnumNode *enume = static_cast<const EnumNode *>(relative); + itemValue = enume->itemValue(atom->next()->string()); + } + + if (itemValue.isEmpty()) + out() << '?'; + else + out() << "<tt>" << protectEnc(itemValue) << "</tt>"; + + 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>\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: + out() << "<h" + QString::number(atom->string().toInt() + hOffset(relative)) + QLatin1Char('>'); + 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, relative, 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 == "borderless") + attr = p1; + else if (p1.contains("%")) + width = p1; + } + if (!p2.isEmpty()) { + if (p2 == "borderless") + attr = p2; + else if (p2.contains("%")) + width = p2; + } + out() << "<table class=\"" << attr << "\""; + if (!width.isEmpty()) + out() << " width=\"" << width << "\">"; + out() << "\n "; + numTableRows = 0; + } + break; + case Atom::TableRight: + out() << "</table>\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(","); + 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::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; +#ifdef QDOC_QML + case Atom::QmlText: + case Atom::EndQmlText: + // don't do anything with these. They are just tags. + break; +#endif + default: + unknownAtom(atom); + } + return skipAhead; +} + +/*! + Generate a reference page for a C++ class. + */ +void HtmlGenerator::generateClassLikeNode(const InnerNode *inner, + CodeMarker *marker) +{ + QList<Section> sections; + QList<Section>::ConstIterator s; + + const ClassNode *classe = 0; + + QString title; + QString rawTitle; + QString fullTitle; + if (inner->type() == Node::Namespace) { + rawTitle = marker->plainName(inner); + fullTitle = marker->plainFullName(inner); + title = rawTitle + " Namespace"; + } + else if (inner->type() == Node::Class) { + classe = static_cast<const ClassNode *>(inner); + rawTitle = marker->plainName(inner); + fullTitle = marker->plainFullName(inner); + 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); + generateTitle(title, subtitleText, SmallSubTitle, inner, marker); + generateBrief(inner, marker); + generateIncludes(inner, marker); + generateStatus(inner, marker); + if (classe) { + generateInherits(classe, marker); + generateInheritedBy(classe, marker); + if (classe->qmlElement() != 0) + generateInstantiatedBy(classe,marker); + } + generateThreadSafeness(inner, marker); + generateSince(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 << "\">" + << "Qt 3 support members</a></li>\n"; + + out() << "</ul>\n"; + + bool needOtherSection = false; + + /* + sections is built above for the call to generateTableOfContents(). + */ + s = sections.begin(); + while (s != sections.end()) { + if (s->members.isEmpty() && s->reimpMembers.isEmpty()) { + if (!s->inherited.isEmpty()) + needOtherSection = true; + } + else { + if (!s->members.isEmpty()) { + // out() << "<hr />\n"; + out() << "<a name=\"" + << registerRef((*s).name.toLower()) + << "\"></a>" << divNavTop << "\n"; + out() << "<h2>" << protectEnc((*s).name) << "</h2>\n"; + generateSection(s->members, inner, marker, CodeMarker::Summary); + } + if (!s->reimpMembers.isEmpty()) { + QString name = QString("Reimplemented ") + (*s).name; + // out() << "<hr />\n"; + out() << "<a name=\"" + << registerRef(name.toLower()) + << "\"></a>" << divNavTop << "\n"; + out() << "<h2>" << protectEnc(name) << "</h2>\n"; + generateSection(s->reimpMembers, inner, marker, CodeMarker::Summary); + } + + if (!s->inherited.isEmpty()) { + out() << "<ul>\n"; + generateSectionInheritedList(*s, inner, marker); + out() << "</ul>\n"; + } + } + ++s; + } + + if (needOtherSection) { + out() << "<h3>Additional Inherited Members</h3>\n" + "<ul>\n"; + + s = sections.begin(); + while (s != sections.end()) { + if (s->members.isEmpty() && !s->inherited.isEmpty()) + generateSectionInheritedList(*s, inner, marker); + ++s; + } + out() << "</ul>\n"; + } + + out() << "<a name=\"" << registerRef("details") << "\"></a>" << divNavTop << '\n'; + + if (!inner->doc().isEmpty()) { + generateExtractionMark(inner, DetailedDescriptionMark); + //out() << "<hr />\n" + out() << "<div class=\"descr\">\n" // QTBUG-9504 + << "<h2>" << "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.begin(); + while (s != sections.end()) { + //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.begin(); + while (m != (*s).members.end()) { + if ((*m)->access() != Node::Private) { // ### check necessary? + if ((*m)->type() != Node::Class) + generateDetailedMember(*m, inner, marker); + else { + out() << "<h3> class "; + generateFullName(*m, inner, marker); + 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() != 1) + 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(); + } + 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); +} + +/*! + We delayed generation of the disambiguation pages until now, after + all the other pages have been generated. We do this because we might + encounter a link command that tries to link to a target on a QML + component page, but the link doesn't specify the module identifer + for the component, and the component name without a module + identifier is ambiguous. When such a link is found, qdoc can't find + the target, so it appends the target to the NameCollisionNode. After + the tree has been traversed and all these ambiguous links have been + added to the name collision nodes, this function is called. The list + of collision nodes is traversed here, and the disambiguation page for + each collision is generated. The disambiguation page will not only + disambiguate links to the component pages, but it will also disambiguate + links to properties, section headers, etc. + */ +void HtmlGenerator::generateDisambiguationPages() +{ + if (collisionNodes.isEmpty()) + return; + for (int i=0; i<collisionNodes.size(); ++i) { + NameCollisionNode* ncn = collisionNodes.at(i); + ncn->clearCurrentChild(); + beginSubPage(ncn, PageGenerator::fileName(ncn)); + QString fullTitle = "Name Collision: " + ncn->fullTitle(); + QString htmlTitle = fullTitle; + CodeMarker* marker = CodeMarker::markerForFileName(ncn->location().filePath()); + if (ncn->isQmlNode()) { + // Replace the marker with a QML code marker. + if (ncn->isQmlNode()) + marker = CodeMarker::markerForLanguage(QLatin1String("QML")); + } + + generateHeader(htmlTitle, ncn, marker); + if (!fullTitle.isEmpty()) + out() << "<h1 class=\"title\">" << protectEnc(fullTitle) << "</h1>\n"; + const NodeList& nl = ncn->childNodes(); + NodeMap nm; + NodeList::ConstIterator it = nl.begin(); + while (it != nl.end()) { + QString t = (*it)->qmlModuleIdentifier() + " " + protectEnc(fullTitle); + nm.insertMulti(t,(*it)); + ++it; + } + generateAnnotatedList(ncn, marker, nm, true); + + const QMap<QString,QString>& targets = ncn->linkTargets(); + if (!targets.isEmpty()) { + QMap<QString,QString>::ConstIterator t = targets.begin(); + while (t != targets.end()) { + out() << "<a name=\"" << Doc::canonicalTitle(t.key()) << "\"></a>"; + out() << "<h2 class=\"title\">" << protectEnc(t.key()) << "</h2>\n"; + out() << "<ul>\n"; + it = nl.begin(); + while (it != nl.end()) { + InnerNode* n = static_cast<InnerNode*>(*it); + Node* p = n->findNode(t.key()); + if (p) { + QString link = linkForNode(p,0); + QString label = n->qmlModuleIdentifier() + "::" + n->name() + "::" + p->name(); + out() << "<li>"; + out() << "<a href=\"" << link << "\">"; + out() << protectEnc(label) << "</a>"; + out() << "</li>\n"; + } + ++it; + } + out() << "</ul>\n"; + ++t; + } + } + + generateFooter(ncn); + endSubPage(); + } +} + +/*! + Generate the HTML page for an entity that doesn't map + to any underlying parsable C++ class or QML component. + */ +void HtmlGenerator::generateFakeNode(const FakeNode *fake, CodeMarker *marker) +{ + /* + If the fake 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 (fake->subType() == Node::Page && fake->pageType() == Node::DitaMapPage) { + const DitaMapNode* dmn = static_cast<const DitaMapNode*>(fake); + writeDitaMap(dmn); + return; + } + + SubTitleSize subTitleSize = LargeSubTitle; + QList<Section> sections; + QList<Section>::const_iterator s; + QString fullTitle = fake->fullTitle(); + QString htmlTitle = fullTitle; + + if (fake->subType() == Node::File && !fake->subTitle().isEmpty()) { + subTitleSize = SmallSubTitle; + htmlTitle += " (" + fake->subTitle() + ")"; + } + else if (fake->subType() == Node::QmlBasicType) { + fullTitle = "QML Basic Type: " + fullTitle; + htmlTitle = fullTitle; + + // Replace the marker with a QML code marker. + marker = CodeMarker::markerForLanguage(QLatin1String("QML")); + } + + generateHeader(htmlTitle, fake, marker); + /* + Generate the TOC for the new doc format. + Don't generate a TOC for the home page. + */ + const QmlClassNode* qml_cn = 0; + if (fake->subType() == Node::QmlClass) { + qml_cn = static_cast<const QmlClassNode*>(fake); + sections = marker->qmlSections(qml_cn,CodeMarker::Summary); + generateTableOfContents(fake,marker,§ions); + + // Replace the marker with a QML code marker. + marker = CodeMarker::markerForLanguage(QLatin1String("QML")); + } + else if (fake->subType() != Node::Collision && fake->name() != QString("index.html")) + generateTableOfContents(fake,marker,0); + + generateTitle(fullTitle, + Text() << fake->subTitle(), + subTitleSize, + fake, + marker); + + if (fake->subType() == Node::Module) { + // Generate brief text and status for modules. + generateBrief(fake, marker); + generateStatus(fake, marker); + generateSince(fake, marker); + + if (moduleNamespaceMap.contains(fake->name())) { + out() << "<a name=\"" << registerRef("namespaces") << "\"></a>" << divNavTop << '\n'; + out() << "<h2>Namespaces</h2>\n"; + generateAnnotatedList(fake, marker, moduleNamespaceMap[fake->name()]); + } + if (moduleClassMap.contains(fake->name())) { + out() << "<a name=\"" << registerRef("classes") << "\"></a>" << divNavTop << '\n'; + out() << "<h2>Classes</h2>\n"; + generateAnnotatedList(fake, marker, moduleClassMap[fake->name()]); + } + } + else if (fake->subType() == Node::HeaderFile) { + // Generate brief text and status for modules. + generateBrief(fake, marker); + generateStatus(fake, marker); + generateSince(fake, marker); + + out() << "<ul>\n"; + + QString membersLink = generateListOfAllMemberFile(fake, marker); + if (!membersLink.isEmpty()) + out() << "<li><a href=\"" << membersLink << "\">" + << "List of all members, including inherited members</a></li>\n"; + + QString obsoleteLink = generateLowStatusMemberFile(fake, + marker, + CodeMarker::Obsolete); + if (!obsoleteLink.isEmpty()) + out() << "<li><a href=\"" << obsoleteLink << "\">" + << "Obsolete members</a></li>\n"; + + QString compatLink = generateLowStatusMemberFile(fake, + marker, + CodeMarker::Compat); + if (!compatLink.isEmpty()) + out() << "<li><a href=\"" << compatLink << "\">" + << "Qt 3 support members</a></li>\n"; + + out() << "</ul>\n"; + } + else if (fake->subType() == Node::QmlClass) { + const_cast<FakeNode*>(fake)->setCurrentChild(); + const ClassNode* cn = qml_cn->classNode(); + generateBrief(qml_cn, marker); + generateQmlInherits(qml_cn, marker); + generateQmlInheritedBy(qml_cn, marker); + generateQmlInstantiates(qml_cn, marker); + generateSince(qml_cn, marker); + + QString allQmlMembersLink = generateAllQmlMembersFile(qml_cn, marker); + if (!allQmlMembersLink.isEmpty()) { + out() << "<ul>\n"; + out() << "<li><a href=\"" << allQmlMembersLink << "\">" + << "List of all members, including inherited members</a></li>\n"; + out() << "</ul>\n"; + } + + s = sections.begin(); + while (s != sections.end()) { + out() << "<a name=\"" << registerRef((*s).name.toLower()) + << "\"></a>" << divNavTop << "\n"; + out() << "<h2>" << protectEnc((*s).name) << "</h2>\n"; + generateQmlSummary(*s,fake,marker); + ++s; + } + + generateExtractionMark(fake, DetailedDescriptionMark); + out() << "<a name=\"" << registerRef("details") << "\"></a>" << divNavTop << '\n'; + out() << "<h2>" << "Detailed Description" << "</h2>\n"; + generateBody(fake, marker); + if (cn) + generateQmlText(cn->doc().body(), cn, marker, fake->name()); + generateAlsoList(fake, marker); + generateExtractionMark(fake, EndMark); + //out() << "<hr />\n"; + + sections = marker->qmlSections(qml_cn,CodeMarker::Detailed); + s = sections.begin(); + while (s != sections.end()) { + out() << "<h2>" << protectEnc((*s).name) << "</h2>\n"; + NodeList::ConstIterator m = (*s).members.begin(); + while (m != (*s).members.end()) { + generateDetailedQmlMember(*m, fake, marker); + out() << "<br/>\n"; + ++m; + } + ++s; + } + generateFooter(fake); + const_cast<FakeNode*>(fake)->clearCurrentChild(); + return; + } + + sections = marker->sections(fake, CodeMarker::Summary, CodeMarker::Okay); + s = sections.begin(); + while (s != sections.end()) { + out() << "<a name=\"" << registerRef((*s).name) << "\"></a>" << divNavTop << '\n'; + out() << "<h2>" << protectEnc((*s).name) << "</h2>\n"; + generateSectionList(*s, fake, marker, CodeMarker::Summary); + ++s; + } + + Text brief = fake->doc().briefText(); + if (fake->subType() == Node::Module && !brief.isEmpty()) { + generateExtractionMark(fake, DetailedDescriptionMark); + out() << "<a name=\"" << registerRef("details") << "\"></a>" << divNavTop << '\n'; + out() << "<div class=\"descr\">\n"; // QTBUG-9504 + out() << "<h2>" << "Detailed Description" << "</h2>\n"; + } + else { + generateExtractionMark(fake, DetailedDescriptionMark); + out() << "<div class=\"descr\"> <a name=\"" << registerRef("details") << "\"></a>\n"; // QTBUG-9504 + } + + generateBody(fake, marker); + out() << "</div>\n"; // QTBUG-9504 + generateAlsoList(fake, marker); + generateExtractionMark(fake, EndMark); + + if ((fake->subType() == Node::Group) && !fake->groupMembers().isEmpty()) { + NodeMap groupMembersMap; + foreach (const Node *node, fake->groupMembers()) { + if (node->type() == Node::Class || node->type() == Node::Namespace) + groupMembersMap[node->name()] = node; + } + generateAnnotatedList(fake, marker, groupMembersMap); + } + else if ((fake->subType() == Node::QmlModule) && !fake->qmlModuleMembers().isEmpty()) { + NodeMap qmlModuleMembersMap; + foreach (const Node* node, fake->qmlModuleMembers()) { + if (node->type() == Node::Fake && node->subType() == Node::QmlClass) + qmlModuleMembersMap[node->name()] = node; + } + generateAnnotatedList(fake, marker, qmlModuleMembersMap); + } + + sections = marker->sections(fake, CodeMarker::Detailed, CodeMarker::Okay); + s = sections.begin(); + while (s != sections.end()) { + //out() << "<hr />\n"; + out() << "<h2>" << protectEnc((*s).name) << "</h2>\n"; + + NodeList::ConstIterator m = (*s).members.begin(); + while (m != (*s).members.end()) { + generateDetailedMember(*m, fake, marker); + ++m; + } + ++s; + } + generateFooter(fake); +} + +/*! + Returns "html" for this subclass of Generator. + */ +QString HtmlGenerator::fileExtension(const Node * /* node */) const +{ + return "html"; +} + +/*! + Output breadcrumb list in the html file. + */ +void HtmlGenerator::generateBreadCrumbs(const QString &title, + const Node *node, + CodeMarker *marker) +{ + if (noBreadCrumbs) + return; + + Text breadcrumbs; + if (node->type() == Node::Class) { + const ClassNode *cn = static_cast<const ClassNode *>(node); + QString name = node->moduleName(); + breadcrumbs << Atom(Atom::ListItemLeft) + << Atom(Atom::Link, QLatin1String("All Modules")) + << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) + << Atom(Atom::String, QLatin1String("Modules")) + << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK) + << Atom(Atom::ListItemRight); + if (!name.isEmpty()) + breadcrumbs << Atom(Atom::ListItemLeft) + << Atom(Atom::AutoLink, name) + << Atom(Atom::ListItemRight); + if (!cn->name().isEmpty()) + breadcrumbs << Atom(Atom::ListItemLeft) + << Atom(Atom::String, protectEnc(cn->name())) + << Atom(Atom::ListItemRight); + } + else if (node->type() == Node::Fake) { + const FakeNode* fn = static_cast<const FakeNode*>(node); + if (node->subType() == Node::Module) { + breadcrumbs << Atom(Atom::ListItemLeft) + << Atom(Atom::Link, QLatin1String("All Modules")) + << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) + << Atom(Atom::String, QLatin1String("Modules")) + << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK) + << Atom(Atom::ListItemRight); + QString name = node->name(); + if (!name.isEmpty()) + breadcrumbs << Atom(Atom::ListItemLeft) + << Atom(Atom::String, protectEnc(name)) + << Atom(Atom::ListItemRight); + } + else if (node->subType() == Node::Group) { + if (fn->name() == QString("modules")) + breadcrumbs << Atom(Atom::String, QLatin1String("Modules")); + else + breadcrumbs << Atom(Atom::ListItemLeft) + << Atom(Atom::String, protectEnc(title)) + << Atom(Atom::ListItemRight); + } + else if (node->subType() == Node::Page) { + if (fn->name() == QString("qdeclarativeexamples.html")) { + breadcrumbs << Atom(Atom::ListItemLeft) + << Atom(Atom::Link, QLatin1String("Qt Examples")) + << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) + << Atom(Atom::String, QLatin1String("Examples")) + << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK) + << Atom(Atom::ListItemRight); + breadcrumbs << Atom(Atom::ListItemLeft) + << Atom(Atom::AutoLink, QLatin1String("QML Examples & Demos")) + << Atom(Atom::ListItemRight); + } + else if (fn->name().startsWith("examples-")) { + breadcrumbs << Atom(Atom::ListItemLeft) + << Atom(Atom::Link, QLatin1String("Qt Examples")) + << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) + << Atom(Atom::String, QLatin1String("Examples")) + << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK) + << Atom(Atom::ListItemRight); + breadcrumbs << Atom(Atom::ListItemLeft) + << Atom(Atom::String, protectEnc(title)) + << Atom(Atom::ListItemRight); + } + else if (fn->name() == QString("namespaces.html")) + breadcrumbs << Atom(Atom::String, QLatin1String("Namespaces")); + else + breadcrumbs << Atom(Atom::ListItemLeft) + << Atom(Atom::String, protectEnc(title)) + << Atom(Atom::ListItemRight); + } + else if (node->subType() == Node::QmlClass) { + breadcrumbs << Atom(Atom::ListItemLeft) + << Atom(Atom::AutoLink, QLatin1String("Basic QML Types")) + << Atom(Atom::ListItemRight); + breadcrumbs << Atom(Atom::ListItemLeft) + << Atom(Atom::String, protectEnc(title)) + << Atom(Atom::ListItemRight); + } + else if (node->subType() == Node::Example) { + breadcrumbs << Atom(Atom::ListItemLeft) + << Atom(Atom::Link, QLatin1String("Qt Examples")) + << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) + << Atom(Atom::String, QLatin1String("Examples")) + << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK) + << Atom(Atom::ListItemRight); + QStringList sl = fn->name().split('/'); + if (sl.contains("declarative")) + breadcrumbs << Atom(Atom::ListItemLeft) + << Atom(Atom::AutoLink, QLatin1String("QML Examples & Demos")) + << Atom(Atom::ListItemRight); + else { + QString name = protectEnc("examples-" + sl.at(0) + ".html"); // this generates an empty link + QString t = CodeParser::titleFromName(name); + } + breadcrumbs << Atom(Atom::ListItemLeft) + << Atom(Atom::String, protectEnc(title)) + << Atom(Atom::ListItemRight); + } + } + else if (node->type() == Node::Namespace) { + breadcrumbs << Atom(Atom::ListItemLeft) + << Atom(Atom::Link, QLatin1String("All Namespaces")) + << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) + << Atom(Atom::String, QLatin1String("Namespaces")) + << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK) + << Atom(Atom::ListItemRight); + breadcrumbs << Atom(Atom::ListItemLeft) + << Atom(Atom::String, protectEnc(title)) + << Atom(Atom::ListItemRight); + } + + generateText(breadcrumbs, node, marker); +} + +void HtmlGenerator::generateHeader(const QString& title, + const Node *node, + CodeMarker *marker) +{ + out() << QString("<?xml version=\"1.0\" encoding=\"%1\"?>\n").arg(outputEncoding); + out() << "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n"; + out() << QString("<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"%1\" 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 = myTree->version(); + if (shortVersion.count(QChar('.')) == 2) + shortVersion.truncate(shortVersion.lastIndexOf(QChar('.'))); + if (!project.isEmpty()) + shortVersion = project + QLatin1String(" ") + shortVersion + QLatin1String(": "); + else + shortVersion = QLatin1String("Qt ") + shortVersion + QLatin1String(": "); + + // Generating page title + out() << " <title>" << shortVersion << protectEnc(title) << "</title>\n"; + + // Include style sheet and script links. + out() << headerStyles; + out() << headerScripts; + out() << endHeader; + +#ifdef GENERATE_MAC_REFS + if (mainPage) + generateMacRef(node, marker); +#endif + + out() << QString(postHeader).replace("\\" + COMMAND_VERSION, myTree->version()); + generateBreadCrumbs(title,node,marker); + out() << QString(postPostHeader).replace("\\" + COMMAND_VERSION, myTree->version()); + + navigationLinks.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 = findNodeForTarget(linkPair.first, node, marker); + if (!linkNode || linkNode == node) + anchorPair = linkPair; + else + anchorPair = anchorForNode(linkNode); + + out() << " <link rel=\"prev\" href=\"" + << anchorPair.first << "\" />\n"; + + navigationLinks += "[Previous: <a 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 = findNodeForTarget(linkPair.first, node, marker); + if (!linkNode || linkNode == node) + anchorPair = linkPair; + else + anchorPair = anchorForNode(linkNode); + + out() << " <link rel=\"next\" href=\"" + << anchorPair.first << "\" />\n"; + + navigationLinks += "[Next: <a 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 = findNodeForTarget(linkPair.first, node, marker); + 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) +{ + 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, myTree->version()) + << QString(address).replace("\\" + COMMAND_VERSION, myTree->version()); + + out() << "</body>\n"; + out() << "</html>\n"; +} + +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 InnerNode *inner, CodeMarker *marker) +{ + if (!inner->includes().isEmpty()) { + out() << "<pre class=\"cpp\">" + << trimmedTrailing(highlightedCode(indent(codeIndent, + marker->markedUpIncludes(inner->includes())), + marker,inner)) + << "</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 (toc.isEmpty() && !sections && (node->subType() != Node::Module)) + return; + + QStringList sectionNumber; + int detailsBase = 0; + + // disable nested links in table of contents + inContents = true; + inLink = true; + + out() << "<div class=\"toc\">\n"; + out() << "<h3><a name=\"toc\">Contents</a></h3>\n"; + sectionNumber.append("1"); + out() << "<ul>\n"; + + if (node->subType() == Node::Module) { + if (moduleNamespaceMap.contains(node->name())) { + out() << "<li class=\"level" + << sectionNumber.size() + << "\"><a href=\"#" + << registerRef("namespaces") + << "\">Namespaces</a></li>\n"; + } + if (moduleClassMap.contains(node->name())) { + 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->type() == Node::Class) || + (node->type() == Node::Namespace) || + (node->subType() == Node::QmlClass))) { + QList<Section>::ConstIterator s = sections->begin(); + while (s != sections->end()) { + if (!s->members.isEmpty() || !s->reimpMembers.isEmpty()) { + out() << "<li class=\"level" + << sectionNumber.size() + << "\"><a href=\"#" + << registerRef((*s).pluralMember) + << "\">" << (*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); + } + } + 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"; + inContents = false; + inLink = false; +} + +QString HtmlGenerator::generateListOfAllMemberFile(const InnerNode *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(inner); + beginSubPage(inner, fileName); + QString title = "List of All Members for " + inner->name(); + generateHeader(title, inner, marker); + generateTitle(title, Text(), SmallSubTitle, inner, marker); + out() << "<p>This is the complete list of members for "; + generateFullName(inner, 0, marker); + out() << ", including inherited members.</p>\n"; + + Section section = sections.first(); + generateSectionList(section, 0, marker, CodeMarker::Subpage); + + generateFooter(); + endSubPage(); + return fileName; +} + +/*! + This function creates an html page on which are listed all + the members of QML class \a qml_cn, including the inherited + members. The \a marker is used for formatting stuff. + */ +QString HtmlGenerator::generateAllQmlMembersFile(const QmlClassNode* 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(qml_cn); + beginSubPage(qml_cn, fileName); + QString title = "List of All Members for " + qml_cn->name(); + generateHeader(title, qml_cn, marker); + generateTitle(title, Text(), SmallSubTitle, qml_cn, marker); + out() << "<p>This is the complete list of members for "; + generateFullName(qml_cn, 0, marker); + out() << ", including inherited members.</p>\n"; + + Section section = sections.first(); + generateSectionList(section, 0, marker, CodeMarker::Subpage); + + generateFooter(); + endSubPage(); + return fileName; +} + +QString HtmlGenerator::generateLowStatusMemberFile(const InnerNode *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 = "Qt 3 Support Members for " + inner->name(); + fileName = fileBase(inner) + "-qt3." + fileExtension(inner); + } + else { + title = "Obsolete Members for " + inner->name(); + fileName = fileBase(inner) + "-obsolete." + fileExtension(inner); + } + + beginSubPage(inner, fileName); + generateHeader(title, inner, marker); + generateTitle(title, Text(), SmallSubTitle, inner, marker); + + if (status == CodeMarker::Compat) { + out() << "<p><b>The following class members are part of the " + "<a href=\"qt3support.html\">Qt 3 support layer</a>.</b> " + "They are provided to help you port old code to Qt 4. We advise against " + "using them in new code.</p>\n"; + } + else { + out() << "<p><b>The following class members are obsolete.</b> " + << "They are provided to keep old source code working. " + << "We strongly advise against using them in new code.</p>\n"; + } + + out() << "<p><ul><li><a href=\"" + << linkForNode(inner, 0) << "\">" + << protectEnc(inner->name()) + << " class reference</a></li></ul></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.begin(); + while (m != sections.at(i).members.end()) { + if ((*m)->access() != Node::Private) + generateDetailedMember(*m, inner, marker); + ++m; + } + } + + generateFooter(); + endSubPage(); + return fileName; +} + +void HtmlGenerator::generateClassHierarchy(const Node *relative, + CodeMarker *marker, + const QMap<QString,const Node*> &classMap) +{ + if (classMap.isEmpty()) + return; + + NodeMap topLevel; + NodeMap::ConstIterator c = classMap.begin(); + while (c != classMap.end()) { + const ClassNode *classe = static_cast<const 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 { + const ClassNode *child = + static_cast<const ClassNode *>(*stack.top().begin()); + out() << "<li>"; + generateFullName(child, relative, marker); + out() << "</li>\n"; + stack.top().erase(stack.top().begin()); + + NodeMap newTop; + foreach (const RelatedClass &d, child->derivedClasses()) { + if (d.access != Node::Private && !d.node->doc().isEmpty()) + newTop.insert(d.node->name(), d.node); + } + if (!newTop.isEmpty()) { + stack.push(newTop); + out() << "<ul>\n"; + } + } + } +} + +void HtmlGenerator::generateAnnotatedList(const Node *relative, + CodeMarker *marker, + const NodeMap &nodeMap, + bool allOdd) +{ + out() << "<table class=\"annotated\">\n"; + + int row = 0; + foreach (const QString &name, nodeMap.keys()) { + const Node *node = nodeMap[name]; + + if (node->status() == Node::Obsolete) + continue; + + if (allOdd || (++row % 2 == 1)) + out() << "<tr class=\"odd topAlign\">"; + else + out() << "<tr class=\"even topAlign\">"; + out() << "<td class=\"tblName\"><p>"; + generateFullName(node, relative, marker); + out() << "</p></td>"; + + if (!(node->type() == Node::Fake)) { + Text brief = node->doc().trimmedBriefText(name); + if (!brief.isEmpty()) { + out() << "<td class=\"tblDescr\"><p>"; + generateText(brief, node, marker); + out() << "</p></td>"; + } + } + else { + out() << "<td class=\"tblDescr\"><p>"; + out() << protectEnc(node->doc().briefText().toString()); + out() << "</p></td>"; + } + out() << "</tr>\n"; + } + out() << "</table>\n"; +} + +/*! + This function finds the common prefix of the names of all + the classes in \a classMap and then generates a compact + list of the class names alphabetized on the part of the + name not including the common prefix. You can tell the + function to use \a comonPrefix as the common prefix, but + normally you let it figure it out itself by looking at + the name of the first and last classes in \a classMap. + */ +void HtmlGenerator::generateCompactList(const Node *relative, + CodeMarker *marker, + const NodeMap &classMap, + bool includeAlphabet, + QString commonPrefix) +{ + const int NumParagraphs = 37; // '0' to '9', 'A' to 'Z', '_' + + if (classMap.isEmpty()) + return; + + /* + If commonPrefix is not empty, then the caller knows what + the common prefix is and has passed it in, so just use that + one. But if the commonPrefix is empty (it normally is), then + compute a common prefix using this simple algorithm. Note we + assume the prefix length is 1, i.e. we will have a single + character as the common prefix. + */ + int commonPrefixLen = commonPrefix.length(); + if (commonPrefixLen == 0) { + QVector<int> count(26); + for (int i=0; i<26; ++i) + count[i] = 0; + + NodeMap::const_iterator iter = classMap.begin(); + while (iter != classMap.end()) { + if (!iter.key().contains("::")) { + QChar c = iter.key()[0]; + if ((c >= 'A') && (c <= 'Z')) { + int idx = c.unicode() - QChar('A').unicode(); + ++count[idx]; + } + } + ++iter; + } + int highest = 0; + int idx = -1; + for (int i=0; i<26; ++i) { + if (count[i] > highest) { + highest = count[i]; + idx = i; + } + } + idx += QChar('A').unicode(); + QChar common(idx); + commonPrefix = common; + commonPrefixLen = 1; + +#if 0 + /* + The algorithm below eventually failed, so it was replaced + with the simple (perhaps too simple) algorithm above. + + The caller didn't pass in a common prefix, so get the common + prefix by looking at the class names of the first and last + classes in the class map. Discard any namespace names and + just use the bare class names. For Qt, the prefix is "Q". + + Note that the algorithm used here to derive the common prefix + from the first and last classes in alphabetical order (QAccel + and QXtWidget in Qt 2.1), fails if either class name does not + begin with Q. + */ + QString first; + QString last; + NodeMap::const_iterator iter = classMap.begin(); + while (iter != classMap.end()) { + if (!iter.key().contains("::")) { + first = iter.key(); + break; + } + ++iter; + } + + if (first.isEmpty()) + first = classMap.begin().key(); + + iter = classMap.end(); + while (iter != classMap.begin()) { + --iter; + if (!iter.key().contains("::")) { + last = iter.key(); + break; + } + } + + if (last.isEmpty()) + last = classMap.begin().key(); + + if (classMap.size() > 1) { + while (commonPrefixLen < first.length() + 1 && + commonPrefixLen < last.length() + 1 && + first[commonPrefixLen] == last[commonPrefixLen]) + ++commonPrefixLen; + } + + commonPrefix = first.left(commonPrefixLen); +#endif + } + + /* + Divide the data into 37 paragraphs: 0, ..., 9, A, ..., Z, + underscore (_). QAccel will fall in paragraph 10 (A) and + QXtWidget in paragraph 33 (X). This is the only place where we + assume that NumParagraphs is 37. Each paragraph is a NodeMap. + */ + NodeMap paragraph[NumParagraphs+1]; + QString paragraphName[NumParagraphs+1]; + QSet<char> usedParagraphNames; + + NodeMap::ConstIterator c = classMap.begin(); + while (c != classMap.end()) { + QStringList pieces = c.key().split("::"); + QString key; + int idx = commonPrefixLen; + if (!pieces.last().startsWith(commonPrefix)) + idx = 0; + if (pieces.size() == 1) + key = pieces.last().mid(idx).toLower(); + else + key = pieces.last().toLower(); + + int paragraphNr = NumParagraphs - 1; + + if (key[0].digitValue() != -1) { + paragraphNr = key[0].digitValue(); + } + else if (key[0] >= QLatin1Char('a') && key[0] <= QLatin1Char('z')) { + paragraphNr = 10 + key[0].unicode() - 'a'; + } + + paragraphName[paragraphNr] = key[0].toUpper(); + usedParagraphNames.insert(key[0].toLower().cell()); + paragraph[paragraphNr].insert(key, c.value()); + ++c; + } + + /* + Each paragraph j has a size: paragraph[j].count(). In the + discussion, we will assume paragraphs 0 to 5 will have sizes + 3, 1, 4, 1, 5, 9. + + We now want to compute the paragraph offset. Paragraphs 0 to 6 + start at offsets 0, 3, 4, 8, 9, 14, 23. + */ + int paragraphOffset[NumParagraphs + 1]; // 37 + 1 + paragraphOffset[0] = 0; + for (int i=0; i<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; + + for (int i=0; i<classMap.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()) { + NodeMap::Iterator it; + it = paragraph[curParNr].begin(); + for (int i=0; i<curParOffset; i++) + ++it; + + /* + Previously, we used generateFullName() for this, but we + require some special formatting. + */ + out() << "<a href=\"" << linkForNode(it.value(), relative) << "\">"; + + QStringList pieces; + if (it.value()->subType() == Node::QmlClass) + pieces << it.value()->name(); + else + pieces = fullName(it.value(), relative, marker).split("::"); + out() << protectEnc(pieces.last()); + out() << "</a>"; + if (pieces.size() > 1) { + out() << " ("; + generateFullName(it.value()->parent(), relative, marker); + out() << ')'; + } + } + out() << "</dd>\n"; + curParOffset++; + } + if (classMap.count() > 0) + out() << "</dl>\n"; + + out() << "</div>\n"; +} + +void HtmlGenerator::generateFunctionIndex(const Node *relative, + CodeMarker *marker) +{ + 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; + +#if 1 + out() << "<ul>\n"; +#endif + QMap<QString, NodeMap >::ConstIterator f = funcIndex.begin(); + while (f != funcIndex.end()) { +#if 1 + out() << "<li>"; +#else + out() << "<p>"; +#endif + 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).begin(); + while (s != (*f).end()) { + out() << ' '; + generateFullName((*s)->parent(), relative, marker, *s); + ++s; + } +#if 1 + out() << "</li>"; +#else + out() << "</p>"; +#endif + out() << '\n'; + ++f; + } +#if 1 + out() << "</ul>\n"; +#endif +} + +void HtmlGenerator::generateLegaleseList(const Node *relative, + CodeMarker *marker) +{ + QMap<Text, const Node *>::ConstIterator it = legaleseTexts.begin(); + while (it != legaleseTexts.end()) { + Text text = it.key(); + //out() << "<hr />\n"; + generateText(text, relative, marker); + out() << "<ul>\n"; + do { + out() << "<li>"; + generateFullName(it.value(), relative, marker); + out() << "</li>\n"; + ++it; + } while (it != legaleseTexts.end() && 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>", "<tt>"); + marked.replace("</@extra>", "</tt>"); + + if (summary) { + marked.remove("<@type>"); + marked.remove("</@type>"); + } + out() << highlightedCode(marked, marker, relative, false, node); +} + +void HtmlGenerator::generateOverviewList(const Node *relative, CodeMarker * /* marker */) +{ + QMap<const FakeNode *, QMap<QString, FakeNode *> > fakeNodeMap; + QMap<QString, const FakeNode *> groupTitlesMap; + QMap<QString, FakeNode *> uncategorizedNodeMap; + QRegExp singleDigit("\\b([0-9])\\b"); + + const NodeList children = myTree->root()->childNodes(); + foreach (Node *child, children) { + if (child->type() == Node::Fake && child != relative) { + FakeNode *fakeNode = static_cast<FakeNode *>(child); + + // Check whether the page is part of a group or is the group + // definition page. + QString group; + bool isGroupPage = false; + if (fakeNode->doc().metaCommandsUsed().contains("group")) { + group = fakeNode->doc().metaCommandArgs("group")[0]; + isGroupPage = true; + } + + // there are too many examples; they would clutter the list + if (fakeNode->subType() == Node::Example) + continue; + + // not interested either in individual (Qt Designer etc.) manual chapters + if (fakeNode->links().contains(Node::ContentsLink)) + continue; + + // Discard external nodes. + if (fakeNode->subType() == Node::ExternalPage) + continue; + + QString sortKey = fakeNode->fullTitle().toLower(); + if (sortKey.startsWith("the ")) + sortKey.remove(0, 4); + sortKey.replace(singleDigit, "0\\1"); + + if (!group.isEmpty()) { + if (isGroupPage) { + // If we encounter a group definition page, we add all + // the pages in that group to the list for that group. + foreach (Node *member, fakeNode->groupMembers()) { + if (member->type() != Node::Fake) + continue; + FakeNode *page = static_cast<FakeNode *>(member); + if (page) { + QString sortKey = page->fullTitle().toLower(); + if (sortKey.startsWith("the ")) + sortKey.remove(0, 4); + sortKey.replace(singleDigit, "0\\1"); + fakeNodeMap[const_cast<const FakeNode *>(fakeNode)].insert(sortKey, page); + groupTitlesMap[fakeNode->fullTitle()] = const_cast<const FakeNode *>(fakeNode); + } + } + } + else if (!isGroupPage) { + // If we encounter a page that belongs to a group then + // we add that page to the list for that group. + const FakeNode *groupNode = static_cast<const FakeNode *>(myTree->root()->findNode(group, Node::Fake)); + if (groupNode) + fakeNodeMap[groupNode].insert(sortKey, fakeNode); + //else + // uncategorizedNodeMap.insert(sortKey, fakeNode); + }// else + // uncategorizedNodeMap.insert(sortKey, fakeNode); + }// else + // uncategorizedNodeMap.insert(sortKey, fakeNode); + } + } + + // We now list all the pages found that belong to groups. + // If only certain pages were found for a group, but the definition page + // for that group wasn't listed, the list of pages will be intentionally + // incomplete. However, if the group definition page was listed, all the + // pages in that group are listed for completeness. + + if (!fakeNodeMap.isEmpty()) { + foreach (const QString &groupTitle, groupTitlesMap.keys()) { + const FakeNode *groupNode = groupTitlesMap[groupTitle]; + out() << QString("<h3><a href=\"%1\">%2</a></h3>\n").arg( + linkForNode(groupNode, relative)).arg( + protectEnc(groupNode->fullTitle())); + + if (fakeNodeMap[groupNode].count() == 0) + continue; + + out() << "<ul>\n"; + + foreach (const FakeNode *fakeNode, fakeNodeMap[groupNode]) { + QString title = fakeNode->fullTitle(); + if (title.startsWith("The ")) + title.remove(0, 4); + out() << "<li><a href=\"" << linkForNode(fakeNode, relative) << "\">" + << protectEnc(title) << "</a></li>\n"; + } + out() << "</ul>\n"; + } + } + + if (!uncategorizedNodeMap.isEmpty()) { + out() << QString("<h3>Miscellaneous</h3>\n"); + out() << "<ul>\n"; + foreach (const FakeNode *fakeNode, uncategorizedNodeMap) { + QString title = fakeNode->fullTitle(); + if (title.startsWith("The ")) + title.remove(0, 4); + out() << "<li><a href=\"" << linkForNode(fakeNode, relative) << "\">" + << protectEnc(title) << "</a></li>\n"; + } + out() << "</ul>\n"; + } +} + +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() << "<table class=\"alignedsummary\">\n"; + } + else { + if (twoColumn) + out() << "<table class=\"propsummary\">\n" + << "<tr><td class=\"topAlign\">"; + out() << "<ul>\n"; + } + + int i = 0; + NodeList::ConstIterator m = nl.begin(); + while (m != nl.end()) { + 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>\n"; + else { + out() << "</ul>\n"; + if (twoColumn) + out() << "</td></tr>\n</table>\n"; + } + } +} + +void HtmlGenerator::generateSectionList(const Section& section, + const Node *relative, + CodeMarker *marker, + CodeMarker::SynopsisStyle style) +{ + bool alignNames = true; + if (!section.members.isEmpty()) { + 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() << "<table class=\"alignedsummary\">\n"; + } + else { + if (twoColumn) + out() << "<table class=\"propsummary\">\n" + << "<tr><td class=\"topAlign\">"; + out() << "<ul>\n"; + } + + int i = 0; + NodeList::ConstIterator m = section.members.begin(); + while (m != section.members.end()) { + 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 (alignNames) + out() << "</td></tr>\n"; + else + out() << "</li>\n"; + i++; + ++m; + } + if (alignNames) + out() << "</table>\n"; + else { + out() << "</ul>\n"; + if (twoColumn) + out() << "</td></tr>\n</table>\n"; + } + } + + if (style == CodeMarker::Summary && !section.inherited.isEmpty()) { + out() << "<ul>\n"; + generateSectionInheritedList(section, relative, marker); + out() << "</ul>\n"; + } +} + +void HtmlGenerator::generateSectionInheritedList(const Section& section, + const Node *relative, + CodeMarker *marker) +{ + QList<QPair<InnerNode *, int> >::ConstIterator p = section.inherited.begin(); + while (p != section.inherited.end()) { + 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) + << '#' << HtmlGenerator::cleanRef(section.name.toLower()) << "\">" + << protectEnc(marker->plainFullName((*p).first, relative)) + << "</a></li>\n"; + ++p; + } +} + +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>", "<tt>"); + marked.replace("</@extra>", "</tt>"); + } + + if (style != CodeMarker::Detailed) { + marked.remove("<@type>"); + marked.remove("</@type>"); + } + + out() << highlightedCode(marked, marker, relative, alignNames); +} + +QString HtmlGenerator::highlightedCode(const QString& markedCode, + CodeMarker* marker, + const Node* relative, + bool alignNames, + const Node* self) +{ + QString src = markedCode; + QString html; + 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>)" + 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 += "</td><td class=\"memItemRight bottomAlign\">"; + done = true; + } + i += 2; + if (parseArg(src, linkTag, &i, srcSize, &arg, &par1)) { + html += "<b>"; + const Node* n = CodeMarker::nodeForString(par1.toString()); + QString link = linkForNode(n, relative); + addLink(link, arg, &html); + html += "</b>"; + } + else { + html += charLangle; + html += charAt; + } + } + else { + html += src.at(i++); + } + } + + // replace all <@func> tags: "(<@func target=\"([^\"]*)\">)(.*)(</@func>)" + src = html; + html = QString(); + for (int i = 0, srcSize = src.size(); i < srcSize;) { + if (src.at(i) == charLangle && src.at(i + 1) == charAt) { + i += 2; + if (parseArg(src, funcTag, &i, srcSize, &arg, &par1)) { + + const Node* n = marker->resolveTarget(par1.toString(), + myTree, + relative); + QString link = linkForNode(n, relative); + addLink(link, arg, &html); + par1 = QStringRef(); + } + else { + html += charLangle; + html += charAt; + } + } + else { + html += src.at(i++); + } + } + + // replace all "(<@(type|headerfile|func)(?: +[^>]*)?>)(.*)(</@\\2>)" tags + src = html; + html = QString(); + + for (int i=0, srcSize=src.size(); i<srcSize;) { + if (src.at(i) == charLangle && src.at(i+1) == charAt) { + i += 2; + bool handled = false; + if (parseArg(src, typeTag, &i, srcSize, &arg, &par1)) { + par1 = QStringRef(); + const Node* n = marker->resolveTarget(arg.toString(), myTree, relative, self); + html += QLatin1String("<span class=\"type\">"); + if (n && n->subType() == Node::QmlBasicType) { + if (relative && relative->subType() == Node::QmlClass) + addLink(linkForNode(n,relative), arg, &html); + else + html += arg.toString(); + } + else + addLink(linkForNode(n,relative), arg, &html); + html += QLatin1String("</span>"); + handled = true; + } + else if (parseArg(src, headerTag, &i, srcSize, &arg, &par1)) { + par1 = QStringRef(); + const Node* n = marker->resolveTarget(arg.toString(), myTree, relative); + addLink(linkForNode(n,relative), arg, &html); + handled = true; + } + else if (parseArg(src, funcTag, &i, srcSize, &arg, &par1)) { + par1 = QStringRef(); + const Node* n = marker->resolveTarget(arg.toString(), myTree, relative); + addLink(linkForNode(n,relative), arg, &html); + handled = true; + } + + if (!handled) { + 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(); + static const QString spanTags[] = { + "<@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>", "</span>", + "</@preprocessor>", "</span>", + "</@string>", "</span>", + "</@char>", "</span>", + "</@number>", "</span>", + "</@op>", "</span>", + "</@type>", "</span>", + "</@name>", "</span>", + "</@keyword>", "</span>", + }; + // Update the upper bound of k in the following code to match the length + // of the above array. + for (int i = 0, n = src.size(); i < n;) { + if (src.at(i) == charLangle) { + bool handled = false; + for (int k = 0; k != 18; ++k) { + const QString & tag = spanTags[2 * k]; + if (tag == QStringRef(&src, i, tag.length())) { + html += spanTags[2 * k + 1]; + i += tag.length(); + handled = true; + break; + } + } + if (!handled) { + ++i; + if (src.at(i) == charAt || + (src.at(i) == QLatin1Char('/') && src.at(i + 1) == charAt)) { + // drop 'our' unknown tags (the ones still containing '@') + while (i < n && src.at(i) != QLatin1Char('>')) + ++i; + ++i; + } + else { + // retain all others + html += charLangle; + } + } + } + else { + html += src.at(i); + ++i; + } + } + return html; +} + +void HtmlGenerator::generateLink(const Atom* atom, + const Node* /* relative */, + 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::cleanRef(const QString& ref) +{ + QString clean; + + if (ref.isEmpty()) + return clean; + + clean.reserve(ref.size() + 20); + const QChar c = ref[0]; + const uint u = c.unicode(); + + if ((u >= 'a' && u <= 'z') || + (u >= 'A' && u <= 'Z') || + (u >= '0' && u <= '9')) { + clean += c; + } else if (u == '~') { + clean += "dtor."; + } else if (u == '_') { + clean += "underscore."; + } else { + clean += "A"; + } + + for (int i = 1; i < (int) ref.length(); i++) { + const QChar c = ref[i]; + const uint u = c.unicode(); + if ((u >= 'a' && u <= 'z') || + (u >= 'A' && u <= 'Z') || + (u >= '0' && u <= '9') || u == '-' || + u == '_' || u == ':' || u == '.') { + clean += c; + } else if (c.isSpace()) { + clean += "-"; + } else if (u == '!') { + clean += "-not"; + } else if (u == '&') { + clean += "-and"; + } else if (u == '<') { + clean += "-lt"; + } else if (u == '=') { + clean += "-eq"; + } else if (u == '>') { + clean += "-gt"; + } else if (u == '#') { + clean += QLatin1Char('#'); + } else { + clean += "-"; + clean += QString::number((int)u, 16); + } + } + return clean; +} + +QString HtmlGenerator::registerRef(const QString& ref) +{ + QString clean = HtmlGenerator::cleanRef(ref); + + for (;;) { + QString& prevRef = refMap[clean.toLower()]; + if (prevRef.isEmpty()) { + prevRef = ref; + break; + } else if (prevRef == ref) { + break; + } + clean += "x"; + } + return clean; +} + +QString HtmlGenerator::protectEnc(const QString &string) +{ + return protect(string, outputEncoding); +} + +QString HtmlGenerator::protect(const QString &string, const QString &outputEncoding) +{ +#define APPEND(x) \ + if (html.isEmpty()) { \ + html = string; \ + html.truncate(i); \ +} \ + html += (x); + + QString html; + int n = string.length(); + + for (int i = 0; i < n; ++i) { + QChar ch = string.at(i); + + if (ch == QLatin1Char('&')) { + APPEND("&"); + } else if (ch == QLatin1Char('<')) { + APPEND("<"); + } else if (ch == QLatin1Char('>')) { + APPEND(">"); + } else if (ch == QLatin1Char('"')) { + APPEND("""); + } else if ((outputEncoding == "ISO-8859-1" && ch.unicode() > 0x007F) + || (ch == QLatin1Char('*') && i + 1 < n && string.at(i) == QLatin1Char('/')) + || (ch == QLatin1Char('.') && i > 2 && string.at(i - 2) == QLatin1Char('.'))) { + // we escape '*/' and the last dot in 'e.g.' and 'i.e.' for the Javadoc generator + APPEND("&#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 = PageGenerator::fileBase(node); + + if (!node->isInnerNode()) { + switch (node->status()) { + case Node::Compat: + result += "-qt3"; + break; + case Node::Obsolete: + result += "-obsolete"; + break; + default: + ; + } + } + return result; +} + +QString HtmlGenerator::fileName(const Node *node) +{ + if (node->type() == Node::Fake) { + if (static_cast<const FakeNode *>(node)->subType() == Node::ExternalPage) + return node->name(); + if (static_cast<const FakeNode *>(node)->subType() == Node::Image) + return node->name(); + } + return PageGenerator::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->associatedProperty()) { + return refForNode(func->associatedProperty()); + } + else { + ref = func->name(); + if (func->overloadNumber() != 1) + ref += "-" + QString::number(func->overloadNumber()); + } + break; + case Node::Fake: + if (node->subType() != Node::QmlPropertyGroup) + break; + case Node::QmlProperty: + case Node::Property: + ref = node->name() + "-prop"; + break; + case Node::QmlSignal: + ref = node->name() + "-signal"; + break; + case Node::QmlSignalHandler: + ref = node->name() + "-signal-handler"; + break; + case Node::QmlMethod: + ref = node->name() + "-method"; + break; + case Node::Variable: + ref = node->name() + "-var"; + break; + case Node::Target: + return protectEnc(node->name()); + } + return registerRef(ref); +} + +#define DEBUG_ABSTRACT 0 + +/*! + Construct the link string for the \a node and return it. + The \a relative node is use to decide the link we are + generating is in the same file as the target. Note the + relative node can be 0, which pretty much guarantees + that the link and the target aren't in the same file. + */ +QString HtmlGenerator::linkForNode(const Node *node, const Node *relative) +{ + if (node == 0 || node == relative) + return QString(); + if (!node->url().isEmpty()) + return node->url(); + if (fileBase(node).isEmpty()) + return QString(); + if (node->access() == Node::Private) + return QString(); + + QString fn = fileName(node); + if (node && relative && node->parent() != relative) { + if (node->parent()->subType() == Node::QmlClass && relative->subType() == Node::QmlClass) { + if (node->parent()->isAbstract()) { + /* + This is a bit of a hack. What we discover with + the three 'if' statements immediately above, + is that node's parent is marked \qmlabstract + but the link appears in a qdoc comment for a + subclass of the node's parent. This means the + link should refer to the file for the relative + node, not the file for node. + */ + fn = fileName(relative); +#if DEBUG_ABSTRACT + qDebug() << "ABSTRACT:" << node->parent()->name() + << node->name() << relative->name() + << node->parent()->type() << node->parent()->subType() + << relative->type() << relative->subType() << outFileName(); +#endif + } + } + } + QString link = fn; + + if (!node->isInnerNode() || node->subType() == Node::QmlPropertyGroup) { + QString ref = refForNode(node); + if (relative && fn == fileName(relative) && ref == refForNode(relative)) + return QString(); + + link += QLatin1Char('#'); + link += ref; + } + /* + If the output is going to subdirectories, then if the + two nodes will be output to different directories, then + the link must go up to the parent directory and then + back down into the other subdirectory. + */ + if (node && relative && (node != relative)) { + if (node->outputSubdirectory() != relative->outputSubdirectory()) + link.prepend(QString("../" + node->outputSubdirectory() + QLatin1Char('/'))); + } + return link; +} + +QString HtmlGenerator::refForAtom(Atom *atom, const Node * /* node */) +{ + if (atom->type() == Atom::SectionLeft) { + return Doc::canonicalTitle(Text::sectionHeading(atom).toString()); + } + else if (atom->type() == Atom::Target) { + return Doc::canonicalTitle(atom->string()); + } + else { + return QString(); + } +} + +void HtmlGenerator::generateFullName(const Node *apparentNode, + const Node *relative, + CodeMarker *marker, + 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(fullName(apparentNode, relative, marker)); + out() << "</a>"; +} + +void HtmlGenerator::generateDetailedMember(const Node *node, + const InnerNode *relative, + CodeMarker *marker) +{ + const EnumNode *enume; + +#ifdef GENERATE_MAC_REFS + generateMacRef(node, marker); +#endif + generateExtractionMark(node, MemberMark); + if (node->type() == Node::Enum + && (enume = static_cast<const EnumNode *>(node))->flagsType()) { +#ifdef GENERATE_MAC_REFS + generateMacRef(enume->flagsType(), marker); +#endif + out() << "<h3 class=\"flags\">"; + out() << "<a name=\"" + refForNode(node) + "\"></a>"; + generateSynopsis(enume, relative, marker, CodeMarker::Detailed); + out() << "<br/>"; + generateSynopsis(enume->flagsType(), + relative, + marker, + CodeMarker::Detailed); + out() << "</h3>\n"; + } + else { + out() << "<h3 class=\"fn\">"; + out() << "<a name=\"" + refForNode(node) + "\"></a>"; + generateSynopsis(node, relative, marker, CodeMarker::Detailed); + out() << "</h3>" << divNavTop << '\n'; + } + + generateStatus(node, marker); + generateBody(node, marker); + generateThreadSafeness(node, marker); + generateSince(node, marker); + + if (node->type() == Node::Property) { + 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->type() == Node::Enum) { + const EnumNode *enume = static_cast<const EnumNode *>(node); + if (enume->flagsType()) { + out() << "<p>The " << protectEnc(enume->flagsType()->name()) + << " type is a typedef for " + << "<a href=\"qflags.html\">QFlags</a><" + << protectEnc(enume->name()) + << ">. It stores an OR combination of " + << protectEnc(enume->name()) + << " values.</p>\n"; + } + } + generateAlsoList(node, marker); + generateExtractionMark(node, EndMark); +} + +void HtmlGenerator::findAllClasses(const InnerNode *node) +{ + NodeList::const_iterator c = node->childNodes().constBegin(); + while (c != node->childNodes().constEnd()) { + if ((*c)->access() != Node::Private && (*c)->url().isEmpty()) { + if ((*c)->type() == Node::Class && !(*c)->doc().isEmpty()) { + QString className = (*c)->name(); + if ((*c)->parent() && + (*c)->parent()->type() == Node::Namespace && + !(*c)->parent()->name().isEmpty()) + className = (*c)->parent()->name()+"::"+className; + + if (!(static_cast<const ClassNode *>(*c))->hideFromMainList()) { + if ((*c)->status() == Node::Compat) { + compatClasses.insert(className, *c); + } + else if ((*c)->status() == Node::Obsolete) { + obsoleteClasses.insert(className, *c); + } + else { + nonCompatClasses.insert(className, *c); + if ((*c)->status() == Node::Main) + mainClasses.insert(className, *c); + } + } + + QString moduleName = (*c)->moduleName(); + if (moduleName == "Qt3SupportLight") { + moduleClassMap[moduleName].insert((*c)->name(), *c); + moduleName = "Qt3Support"; + } + if (!moduleName.isEmpty()) + moduleClassMap[moduleName].insert((*c)->name(), *c); + + QString serviceName = + (static_cast<const ClassNode *>(*c))->serviceName(); + if (!serviceName.isEmpty()) + serviceClasses.insert(serviceName, *c); + } + else if ((*c)->type() == Node::Fake && + (*c)->subType() == Node::QmlClass && + !(*c)->doc().isEmpty()) { + QString qmlClassName = (*c)->name(); + /* + Remove the "QML:" prefix, if present. + It shouldn't be present anymore. + */ + if (qmlClassName.startsWith(QLatin1String("QML:"))) + qmlClasses.insert(qmlClassName.mid(4),*c); + else + qmlClasses.insert(qmlClassName,*c); + } + else if ((*c)->isInnerNode()) { + findAllClasses(static_cast<InnerNode *>(*c)); + } + } + ++c; + } +} + +void HtmlGenerator::findAllFunctions(const InnerNode *node) +{ + NodeList::ConstIterator c = node->childNodes().begin(); + while (c != node->childNodes().end()) { + if ((*c)->access() != Node::Private) { + if ((*c)->isInnerNode() && (*c)->url().isEmpty()) { + findAllFunctions(static_cast<const InnerNode *>(*c)); + } + else if ((*c)->type() == Node::Function) { + const FunctionNode *func = static_cast<const FunctionNode *>(*c); + if ((func->status() > Node::Obsolete) && + !func->isInternal() && + (func->metaness() != FunctionNode::Ctor) && + (func->metaness() != FunctionNode::Dtor)) { + funcIndex[(*c)->name()].insert((*c)->parent()->fullDocumentName(), *c); + } + } + } + ++c; + } +} + +void HtmlGenerator::findAllLegaleseTexts(const InnerNode *node) +{ + NodeList::ConstIterator c = node->childNodes().begin(); + while (c != node->childNodes().end()) { + if ((*c)->access() != Node::Private) { + if (!(*c)->doc().legaleseText().isEmpty()) + legaleseTexts.insertMulti((*c)->doc().legaleseText(), *c); + if ((*c)->isInnerNode()) + findAllLegaleseTexts(static_cast<const InnerNode *>(*c)); + } + ++c; + } +} + +void HtmlGenerator::findAllNamespaces(const InnerNode *node) +{ + NodeList::ConstIterator c = node->childNodes().begin(); + while (c != node->childNodes().end()) { + if ((*c)->access() != Node::Private) { + if ((*c)->isInnerNode() && (*c)->url().isEmpty()) { + findAllNamespaces(static_cast<const InnerNode *>(*c)); + if ((*c)->type() == Node::Namespace) { + const NamespaceNode *nspace = static_cast<const NamespaceNode *>(*c); + // Ensure that the namespace's name is not empty (the root + // namespace has no name). + if (!nspace->name().isEmpty()) { + namespaceIndex.insert(nspace->name(), *c); + QString moduleName = (*c)->moduleName(); + if (moduleName == "Qt3SupportLight") { + moduleNamespaceMap[moduleName].insert((*c)->name(), *c); + moduleName = "Qt3Support"; + } + if (!moduleName.isEmpty()) + moduleNamespaceMap[moduleName].insert((*c)->name(), *c); + } + } + } + } + ++c; + } +} + +int HtmlGenerator::hOffset(const Node *node) +{ + switch (node->type()) { + case Node::Namespace: + case Node::Class: + return 2; + case Node::Fake: + 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 Node *HtmlGenerator::findNodeForTarget(const QString &target, + const Node *relative, + CodeMarker *marker, + const Atom *atom) +{ + const Node *node = 0; + + if (target.isEmpty()) { + node = relative; + } + else if (target.endsWith(".html")) { + node = myTree->root()->findNode(target, Node::Fake); + } + else if (marker) { + node = marker->resolveTarget(target, myTree, relative); + if (!node) { + node = myTree->findFakeNodeByTitle(target, relative); + } + if (!node && atom) { + node = myTree->findUnambiguousTarget(target, *const_cast<Atom**>(&atom), relative); + } + } + + if (!node) + relative->doc().location().warning(tr("Cannot link to '%1'").arg(target)); + return node; +} + +const QPair<QString,QString> HtmlGenerator::anchorForNode(const Node *node) +{ + QPair<QString,QString> anchorPair; + + anchorPair.first = PageGenerator::fileName(node); + if (node->type() == Node::Fake) { + const FakeNode *fakeNode = static_cast<const FakeNode*>(node); + anchorPair.second = fakeNode->title(); + } + + return anchorPair; +} + +QString HtmlGenerator::getLink(const Atom *atom, + const Node *relative, + CodeMarker *marker, + const Node** node) +{ + QString link; + *node = 0; + inObsoleteLink = false; + + if (atom->string().contains(":") && + (atom->string().startsWith("file:") + || atom->string().startsWith("http:") + || atom->string().startsWith("https:") + || atom->string().startsWith("ftp:") + || atom->string().startsWith("mailto:"))) { + + link = atom->string(); + } + else { + QStringList path; + if (atom->string().contains('#')) { + path = atom->string().split('#'); + } + else { + path.append(atom->string()); + } + + Atom *targetAtom = 0; + QString first = path.first().trimmed(); + if (first.isEmpty()) { + *node = relative; + } + else if (first.endsWith(".html")) { + *node = myTree->root()->findNode(first, Node::Fake); + } + else { + *node = marker->resolveTarget(first, myTree, relative); + if (!*node) { + *node = myTree->findFakeNodeByTitle(first, relative); + } + if (!*node) { + *node = myTree->findUnambiguousTarget(first, targetAtom, relative); + } + } + if (*node) { + if (!(*node)->url().isEmpty()) { + return (*node)->url(); + } + else { + path.removeFirst(); + } + } + else { + *node = relative; + } + + if (*node) { + if ((*node)->status() == Node::Obsolete) { + if (relative) { + if (relative->parent() != *node) { + if (relative->status() != Node::Obsolete) { + bool porting = false; + if (relative->type() == Node::Fake) { + const FakeNode* fake = static_cast<const FakeNode*>(relative); + if (fake->title().startsWith("Porting")) + porting = true; + } + QString name = marker->plainFullName(relative); + if (!porting && !name.startsWith("Q3")) { + if (obsoleteLinks) { + relative->doc().location().warning(tr("Link to obsolete item '%1' in %2") + .arg(atom->string()) + .arg(name)); + } + inObsoleteLink = true; + } + } + } + } + else { + qDebug() << "Link to Obsolete entity" + << (*node)->name() << "no relative"; + } + } + } + + /* + This loop really only makes sense if *node is not 0. + In that case, The node *node points to represents a + qdoc page, so the link will ultimately point to some + target on that page. This loop finds that target on + the page that *node represents. targetAtom is that + target. + */ + while (!path.isEmpty()) { + targetAtom = myTree->findTarget(path.first(), *node); + if (targetAtom == 0) + break; + path.removeFirst(); + } + + /* + Given that *node is not null, we now cconstruct a link + to the page that *node represents, and then if there is + a target on that page, we connect the target to the link + with '#'. + */ + if (path.isEmpty()) { + link = linkForNode(*node, relative); + if (*node && (*node)->subType() == Node::Image) + link = "images/used-in-examples/" + link; + if (targetAtom) + link += QLatin1Char('#') + refForAtom(targetAtom, *node); + } + /* + 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 (link.startsWith("images/")) { + link.prepend(QString("../")); + } + else if (*node && relative && (*node != relative)) { + if ((*node)->outputSubdirectory() != relative->outputSubdirectory()) { + link.prepend(QString("../" + (*node)->outputSubdirectory() + QLatin1Char('/'))); + } + } + } + return link; +} + +/*! + This function can be called if getLink() returns an empty + string. It tests the \a atom string to see if it is a link + of the form <element> :: <name>, where <element> is a QML + element or component without a module qualifier. If so, it + constructs a link to the <name> clause on the disambiguation + page for <element> and returns that link string. It also + adds the <name> as a target in the NameCollisionNode for + <element>. These clauses are then constructed when the + disambiguation page is actually generated. + */ +QString HtmlGenerator::getDisambiguationLink(const Atom *atom, CodeMarker *) +{ + QString link; + if (!atom->string().contains("::")) + return link; + QStringList path = atom->string().split("::"); + NameCollisionNode* ncn = myTree->findCollisionNode(path[0]); + if (ncn) { + QString label; + if (atom->next() && atom->next()->next()) { + if (atom->next()->type() == Atom::FormattingLeft && + atom->next()->next()->type() == Atom::String) + label = atom->next()->next()->string(); + } + ncn->addLinkTarget(path[1],label); + link = fileName(ncn); + link += QLatin1Char('#'); + link += Doc::canonicalTitle(path[1]); + } + return link; +} + +void HtmlGenerator::generateIndex(const QString &fileBase, + const QString &url, + const QString &title) +{ + myTree->generateIndex(outputDir() + QLatin1Char('/') + fileBase + ".index", url, title); +} + +void HtmlGenerator::generateStatus(const Node *node, CodeMarker *marker) +{ + Text text; + + switch (node->status()) { + case Node::Obsolete: + if (node->isInnerNode()) + Generator::generateStatus(node, marker); + break; + case Node::Compat: + if (node->isInnerNode()) { + text << Atom::ParaLeft + << Atom(Atom::FormattingLeft,ATOM_FORMATTING_BOLD) + << "This " + << typeString(node) + << " is part of the Qt 3 support library." + << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD) + << " It is provided to keep old source code working. " + << "We strongly advise against " + << "using it in new code. See "; + + const FakeNode *fakeNode = myTree->findFakeNodeByTitle("Porting To Qt 4"); + Atom *targetAtom = 0; + if (fakeNode && node->type() == Node::Class) { + QString oldName(node->name()); + oldName.remove(QLatin1Char('3')); + targetAtom = myTree->findTarget(oldName, + fakeNode); + } + + if (targetAtom) { + text << Atom(Atom::Link, linkForNode(fakeNode, node) + QLatin1Char('#') + + refForAtom(targetAtom, fakeNode)); + } + else + text << Atom(Atom::Link, "Porting to Qt 4"); + + text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) + << Atom(Atom::String, "Porting to Qt 4") + << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK) + << " for more information." + << Atom::ParaRight; + } + generateText(text, node, marker); + break; + default: + Generator::generateStatus(node, marker); + } +} + +#ifdef GENERATE_MAC_REFS +/* + No longer valid. + */ +void HtmlGenerator::generateMacRef(const Node *node, CodeMarker *marker) +{ + if (!pleaseGenerateMacRef || marker == 0) + return; + + QStringList macRefs = marker->macRefsForNode(node); + foreach (const QString &macRef, macRefs) + out() << "<a name=\"" << "//apple_ref/" << macRef << "\"></a>\n"; +} +#endif + +void HtmlGenerator::beginLink(const QString &link, + const Node *node, + const Node *relative, + CodeMarker *marker) +{ + Q_UNUSED(marker) + Q_UNUSED(relative) + + this->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. + + Currently handles only the QML property group. + */ +void HtmlGenerator::generateQmlSummary(const Section& section, + const Node *relative, + CodeMarker *marker) +{ + if (!section.members.isEmpty()) { + out() << "<ul>\n"; + NodeList::ConstIterator m; + m = section.members.begin(); + while (m != section.members.end()) { + out() << "<li class=\"fn\">"; + generateQmlItem(*m,relative,marker,true); + out() << "</li>\n"; + ++m; + } + out() << "</ul>\n"; + } +} + +/*! + Outputs the html detailed documentation for a section + on a QML element reference page. + */ +void HtmlGenerator::generateDetailedQmlMember(const Node *node, + const InnerNode *relative, + CodeMarker *marker) +{ + const QmlPropertyNode* qpn = 0; +#ifdef GENERATE_MAC_REFS + generateMacRef(node, marker); +#endif + generateExtractionMark(node, MemberMark); + out() << "<div class=\"qmlitem\">"; + if (node->subType() == Node::QmlPropertyGroup) { + const QmlPropGroupNode* qpgn = static_cast<const QmlPropGroupNode*>(node); + NodeList::ConstIterator p = qpgn->childNodes().begin(); + out() << "<div class=\"qmlproto\">"; + out() << "<table class=\"qmlname\">"; + while (p != qpgn->childNodes().end()) { + if ((*p)->type() == Node::QmlProperty) { + qpn = static_cast<const QmlPropertyNode*>(*p); + out() << "<tr valign=\"top\" class=\"odd\">"; + out() << "<td class=\"tblQmlPropNode\"><p>"; + out() << "<a name=\"" + refForNode(qpn) + "\"></a>"; + + int ro = qpn->getReadOnly(); + if (ro < 0) { + if (!qpn->isWritable(myTree)) { + out() << "<span class=\"qmlreadonly\">read-only</span>"; + } + } + else if (ro > 0) { + out() << "<span class=\"qmlreadonly\">read-only</span>"; + } + if (qpgn->isDefault()) + out() << "<span class=\"qmldefault\">default</span>"; + generateQmlItem(qpn, relative, marker, false); + out() << "</p></td></tr>"; + } + ++p; + } + out() << "</table>"; + out() << "</div>"; + } + else if (node->type() == Node::QmlProperty) { + qpn = static_cast<const QmlPropertyNode*>(node); + /* + If the QML property node has a single subproperty, + override, replace qpn with that override node and + proceed as normal. + */ + if (qpn->qmlPropNodes().size() == 1) { + Node* n = qpn->qmlPropNodes().at(0); + if (n->type() == Node::QmlProperty) + qpn = static_cast<const QmlPropertyNode*>(n); + } + /* + Now qpn either has no overrides, or it has more + than 1. If it has none, proceed to output as nortmal. + */ + if (qpn->qmlPropNodes().isEmpty()) { + out() << "<div class=\"qmlproto\">"; + out() << "<table class=\"qmlname\">"; + out() << "<tr valign=\"top\" class=\"odd\">"; + out() << "<td class=\"tblQmlPropNode\"><p>"; + out() << "<a name=\"" + refForNode(qpn) + "\"></a>"; + int ro = qpn->getReadOnly(); + if (ro < 0) { + const ClassNode* cn = qpn->declarativeCppNode(); + if (cn && !qpn->isWritable(myTree)) { + out() << "<span class=\"qmlreadonly\">read-only</span>"; + } + } + else if (ro > 0) { + 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>"; + out() << "</table>"; + out() << "</div>"; + } + else { + /* + The QML property node has multiple override nodes. + Process the whole list as we would for a QML property + group. + */ + NodeList::ConstIterator p = qpn->qmlPropNodes().begin(); + out() << "<div class=\"qmlproto\">"; + out() << "<table class=\"qmlname\">"; + while (p != qpn->qmlPropNodes().end()) { + if ((*p)->type() == Node::QmlProperty) { + QmlPropertyNode* q = static_cast<QmlPropertyNode*>(*p); + out() << "<tr valign=\"top\" class=\"odd\">"; + out() << "<td class=\"tblQmlPropNode\"><p>"; + out() << "<a name=\"" + refForNode(q) + "\"></a>"; + + int ro = qpn->getReadOnly(); + if (ro < 0) { + if (!qpn->isWritable(myTree)) { + out() << "<span class=\"qmlreadonly\">read-only</span>"; + } + } + else if (ro > 0) { + out() << "<span class=\"qmlreadonly\">read-only</span>"; + } + if (qpn->isDefault()) + out() << "<span class=\"qmldefault\">default</span>"; + generateQmlItem(q, relative, marker, false); + out() << "</p></td></tr>"; + } + ++p; + } + out() << "</table>"; + out() << "</div>"; + } + } + else if (node->type() == Node::QmlSignal) { + const FunctionNode* qsn = static_cast<const FunctionNode*>(node); + out() << "<div class=\"qmlproto\">"; + out() << "<table class=\"qmlname\">"; + out() << "<tr valign=\"top\" class=\"odd\">"; + out() << "<td class=\"tblQmlFuncNode\"><p>"; + out() << "<a name=\"" + refForNode(qsn) + "\"></a>"; + generateSynopsis(qsn,relative,marker,CodeMarker::Detailed,false); + out() << "</p></td></tr>"; + out() << "</table>"; + out() << "</div>"; + } + else if (node->type() == Node::QmlSignalHandler) { + const FunctionNode* qshn = static_cast<const FunctionNode*>(node); + out() << "<div class=\"qmlproto\">"; + out() << "<table class=\"qmlname\">"; + out() << "<tr valign=\"top\" class=\"odd\">"; + out() << "<td class=\"tblQmlFuncNode\"><p>"; + out() << "<a name=\"" + refForNode(qshn) + "\"></a>"; + generateSynopsis(qshn,relative,marker,CodeMarker::Detailed,false); + out() << "</p></td></tr>"; + out() << "</table>"; + out() << "</div>"; + } + else if (node->type() == Node::QmlMethod) { + const FunctionNode* qmn = static_cast<const FunctionNode*>(node); + out() << "<div class=\"qmlproto\">"; + out() << "<table class=\"qmlname\">"; + out() << "<tr valign=\"top\" class=\"odd\">"; + out() << "<td class=\"tblQmlFuncNode\"><p>"; + out() << "<a name=\"" + refForNode(qmn) + "\"></a>"; + generateSynopsis(qmn,relative,marker,CodeMarker::Detailed,false); + out() << "</p></td></tr>"; + out() << "</table>"; + out() << "</div>"; + } + 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(const QmlClassNode* qcn, CodeMarker* marker) +{ + if (!qcn) + return; + const FakeNode* base = qcn->qmlBase(); + 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 "Inherit by" list for the QML element, + if it is inherited by any other elements. + */ +void HtmlGenerator::generateQmlInheritedBy(const QmlClassNode* qcn, CodeMarker* marker) +{ + if (qcn) { + NodeList subs; + QmlClassNode::subclasses(qcn->name(),subs); + if (!subs.isEmpty()) { + Text text; + text << Atom::ParaLeft << "Inherited by "; + appendSortedQmlNames(text,qcn,subs,marker); + 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(const QmlClassNode* qcn, CodeMarker* marker) +{ + const 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 element 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(const ClassNode* cn, CodeMarker* marker) +{ + if (cn && cn->status() != Node::Internal && cn->qmlElement() != 0) { + const QmlClassNode* qcn = cn->qmlElement(); + Text text; + text << Atom::ParaLeft; + text << Atom(Atom::LinkNode,CodeMarker::stringForNode(cn)); + text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK); + text << Atom(Atom::String, cn->name()); + text << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK); + text << " is instantiated by QML element "; + 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); + } +} + +/*! + Generate the <page> element for the given \a node using the \a writer. + Return true if a <page> element was written; otherwise return false. + */ +bool HtmlGenerator::generatePageElement(QXmlStreamWriter& writer, + const Node* node, + CodeMarker* marker) const +{ + if (node->pageType() == Node::NoPageType) + return false; + if (node->name().isEmpty()) + return true; + if (node->access() == Node::Private) + return false; + + QString guid = QUuid::createUuid().toString(); + QString title; + QString rawTitle; + QString fullTitle; + QStringList pageWords; + QXmlStreamAttributes attributes; + + QString url = node->outputSubdirectory(); + if (!url.isEmpty()) + url.append(QLatin1Char('/')); + url.append(PageGenerator::fileName(node)); + + writer.writeStartElement("page"); + + if (node->isInnerNode()) { + const InnerNode* inner = static_cast<const InnerNode*>(node); + if (!inner->pageKeywords().isEmpty()) + pageWords << inner->pageKeywords(); + + switch (node->type()) { + case Node::Fake: + { + const FakeNode* fake = static_cast<const FakeNode*>(node); + title = fake->fullTitle(); + pageWords << title; + break; + } + case Node::Class: + { + title = node->name() + " Class"; + pageWords << node->name() << "class" << "reference"; + break; + } + case Node::Namespace: + { + rawTitle = marker->plainName(inner); + fullTitle = marker->plainFullName(inner); + title = rawTitle + " Namespace"; + pageWords << rawTitle << "namespace" << "reference"; + break; + } + default: + title = node->name(); + pageWords << title; + break; + } + } + else { + switch (node->type()) { + case Node::Enum: + { + title = node->name() + " Enum"; + pageWords << node->name() << "enum" << "type"; + url += QLatin1Char('#') + node->name() + "-enum"; + break; + } + case Node::Function: + { + title = node->name() + " Function"; + pageWords << node->name() << "function"; + url += QLatin1Char('#') + node->name(); + break; + } + case Node::Property: + { + title = node->name() + " Property"; + pageWords << node->name() << "property"; + url += QLatin1Char('#') + node->name() + "-prop"; + break; + } + case Node::Typedef: + { + title = node->name() + " Type"; + pageWords << node->name() << "typedef" << "type"; + url += QLatin1Char('#') + node->name(); + break; + } + default: + title = node->name(); + pageWords << title; + break; + } + + Node* parent = node->parent(); + if (parent && ((parent->type() == Node::Class) || + (parent->type() == Node::Namespace))) { + pageWords << parent->name(); + } + } + + writer.writeAttribute("id",guid); + writer.writeStartElement("pageWords"); + writer.writeCharacters(pageWords.join(" ")); + + writer.writeEndElement(); + writer.writeStartElement("pageTitle"); + writer.writeCharacters(title); + writer.writeEndElement(); + writer.writeStartElement("pageUrl"); + writer.writeCharacters(url); + writer.writeEndElement(); + writer.writeStartElement("pageType"); + QString ptype = "Article"; + switch (node->pageType()) { + case Node::ApiPage: + ptype = "APIPage"; + break; + case Node::ArticlePage: + ptype = "Article"; + break; + case Node::ExamplePage: + ptype = "Example"; + break; + case Node::HowToPage: + ptype = "HowTo"; + break; + case Node::OverviewPage: + ptype = "Overview"; + break; + case Node::TutorialPage: + ptype = "Tutorial"; + break; + case Node::FAQPage: + ptype = "FAQ"; + break; + default: + break; + } + writer.writeCharacters(ptype); + writer.writeEndElement(); + writer.writeEndElement(); + + if (node->type() == Node::Fake && node->doc().hasTableOfContents()) { + QList<Atom*> toc = node->doc().tableOfContents(); + if (!toc.isEmpty()) { + for (int i = 0; i < toc.size(); ++i) { + Text headingText = Text::sectionHeading(toc.at(i)); + QString s = headingText.toString(); + writer.writeStartElement("page"); + guid = QUuid::createUuid().toString(); + QString internalUrl = url + QLatin1Char('#') + Doc::canonicalTitle(s); + writer.writeAttribute("id",guid); + writer.writeStartElement("pageWords"); + writer.writeCharacters(pageWords.join(" ")); + writer.writeCharacters(" "); + writer.writeCharacters(s); + writer.writeEndElement(); + writer.writeStartElement("pageTitle"); + writer.writeCharacters(s); + writer.writeEndElement(); + writer.writeStartElement("pageUrl"); + writer.writeCharacters(internalUrl); + writer.writeEndElement(); + writer.writeStartElement("pageType"); + writer.writeCharacters("Article"); + writer.writeEndElement(); + writer.writeEndElement(); + } + } + } + return true; +} + +/*! + Traverse the tree recursively and generate the <keyword> + elements. + */ +void HtmlGenerator::generatePageElements(QXmlStreamWriter& writer, const Node* node, CodeMarker* marker) const +{ + if (generatePageElement(writer, node, marker)) { + + if (node->isInnerNode()) { + const InnerNode *inner = static_cast<const InnerNode *>(node); + + // Recurse to write an element for this child node and all its children. + foreach (const Node *child, inner->childNodes()) + generatePageElements(writer, child, marker); + } + } +} + +/*! + Outputs the file containing the index used for searching the html docs. + */ +void HtmlGenerator::generatePageIndex(const QString& fileName) const +{ + QFile file(fileName); + if (!file.open(QFile::WriteOnly | QFile::Text)) + return ; + + CodeMarker *marker = CodeMarker::markerForFileName(fileName); + + QXmlStreamWriter writer(&file); + writer.setAutoFormatting(true); + writer.writeStartDocument(); + writer.writeStartElement("qtPageIndex"); + + generatePageElements(writer, myTree->root(), marker); + + writer.writeEndElement(); // qtPageIndex + writer.writeEndDocument(); + file.close(); +} + +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->associatedProperty()) { + if (func->overloadNumber() == 1) + 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"; + } +} + +/*! + Returns the full document location for HTML-based documentation. + */ +QString HtmlGenerator::fullDocumentLocation(const Node *node, bool subdir) +{ + if (!node) + return ""; + if (!node->url().isEmpty()) + return node->url(); + + QString parentName; + QString anchorRef; + QString fdl = ""; + + /* + If the output is being sent to subdirectories of the + output directory, and if the subdir parameter is set, + prepend the subdirectory name + '/' to the result. + */ + if (subdir) { + fdl = node->outputSubdirectory(); + if (!fdl.isEmpty()) + fdl.append(QLatin1Char('/')); + } + if (node->type() == Node::Namespace) { + + // The root namespace has no name - check for this before creating + // an attribute containing the location of any documentation. + + if (!node->fileBase().isEmpty()) + parentName = node->fileBase() + ".html"; + else + return ""; + } + else if (node->type() == Node::Fake) { + if ((node->subType() == Node::QmlClass) || + (node->subType() == Node::QmlBasicType)) { + QString fb = node->fileBase(); + if (fb.startsWith(Generator::outputPrefix(QLatin1String("QML")))) + return fb + ".html"; + else { + QString mq = ""; + if (!node->qmlModuleName().isEmpty()) { + mq = node->qmlModuleIdentifier().replace(QChar('.'),QChar('-')); + mq = mq.toLower() + "-"; + } + return fdl+ Generator::outputPrefix(QLatin1String("QML")) + mq + + node->fileBase() + QLatin1String(".html"); + } + } + else + parentName = node->fileBase() + ".html"; + } + else if (node->fileBase().isEmpty()) + return ""; + + Node *parentNode = 0; + + if ((parentNode = node->relates())) { + parentName = fullDocumentLocation(node->relates()); + } + else if ((parentNode = node->parent())) { + if (parentNode->subType() == Node::QmlPropertyGroup) { + parentNode = parentNode->parent(); + parentName = fullDocumentLocation(parentNode); + } + else + parentName = fullDocumentLocation(node->parent()); + } + + switch (node->type()) { + case Node::Class: + case Node::Namespace: + if (parentNode && !parentNode->name().isEmpty()) { + parentName.remove(".html"); + parentName += QLatin1Char('-') + + node->fileBase().toLower() + ".html"; + } else { + parentName = node->fileBase() + ".html"; + } + break; + case Node::Function: + { + /* + Functions can be destructors, overloaded, or + have associated properties. + */ + const FunctionNode *functionNode = + static_cast<const FunctionNode *>(node); + + if (functionNode->metaness() == FunctionNode::Dtor) + anchorRef = "#dtor." + functionNode->name().mid(1); + + else if (functionNode->associatedProperty()) + return fullDocumentLocation(functionNode->associatedProperty()); + + else if (functionNode->overloadNumber() > 1) + anchorRef = QLatin1Char('#') + functionNode->name() + + "-" + QString::number(functionNode->overloadNumber()); + else + anchorRef = QLatin1Char('#') + functionNode->name(); + } + + /* + Use node->name() instead of node->fileBase() as + the latter returns the name in lower-case. For + HTML anchors, we need to preserve the case. + */ + break; + case Node::Enum: + anchorRef = QLatin1Char('#') + node->name() + "-enum"; + break; + case Node::Typedef: + anchorRef = QLatin1Char('#') + node->name() + "-typedef"; + break; + case Node::Property: + anchorRef = QLatin1Char('#') + node->name() + "-prop"; + break; + case Node::QmlProperty: + anchorRef = QLatin1Char('#') + node->name() + "-prop"; + break; + case Node::QmlSignal: + anchorRef = QLatin1Char('#') + node->name() + "-signal"; + break; + case Node::QmlSignalHandler: + anchorRef = QLatin1Char('#') + node->name() + "-signal-handler"; + break; + case Node::QmlMethod: + anchorRef = QLatin1Char('#') + node->name() + "-method"; + break; + case Node::Variable: + anchorRef = QLatin1Char('#') + node->name() + "-var"; + break; + case Node::Target: + anchorRef = QLatin1Char('#') + Doc::canonicalTitle(node->name()); + break; + case Node::Fake: + { + /* + Use node->fileBase() for fake nodes because they are represented + by pages whose file names are lower-case. + */ + parentName = node->fileBase(); + parentName.replace(QLatin1Char('/'), "-").replace(".", "-"); + parentName += ".html"; + } + break; + default: + break; + } + + // Various objects can be compat (deprecated) or obsolete. + if (node->type() != Node::Class && node->type() != Node::Namespace) { + switch (node->status()) { + case Node::Compat: + parentName.replace(".html", "-qt3.html"); + break; + case Node::Obsolete: + parentName.replace(".html", "-obsolete.html"); + break; + default: + ; + } + } + + return fdl + parentName.toLower() + anchorRef; +} + +/*! + This function outputs one or more manifest files in XML. + They are used by Creator. + */ +void HtmlGenerator::generateManifestFiles() +{ + generateManifestFile("examples", "example"); + generateManifestFile("demos", "demo"); + ExampleNode::exampleNodeMap.clear(); +} + +/*! + This function is called by generaqteManiferstFile(), once + for each manifest file to be generated. \a manifest is the + type of manifest file. + */ +void HtmlGenerator::generateManifestFile(QString manifest, QString element) +{ + if (ExampleNode::exampleNodeMap.isEmpty()) + return; + QString fileName = manifest +"-manifest.xml"; + QFile file(outputDir() + QLatin1Char('/') + fileName); + if (!file.open(QFile::WriteOnly | QFile::Text)) + return ; + bool demos = false; + if (manifest == "demos") + demos = true; + + bool proceed = false; + ExampleNodeMap::Iterator i = ExampleNode::exampleNodeMap.begin(); + while (i != ExampleNode::exampleNodeMap.end()) { + const ExampleNode* en = i.value(); + if (demos) { + if (en->name().startsWith("demos")) { + proceed = true; + break; + } + } + else if (!en->name().startsWith("demos")) { + proceed = true; + break; + } + ++i; + } + if (!proceed) + return; + + QXmlStreamWriter writer(&file); + writer.setAutoFormatting(true); + writer.writeStartDocument(); + writer.writeStartElement("instructionals"); + writer.writeAttribute("module", project); + writer.writeStartElement(manifest); + + i = ExampleNode::exampleNodeMap.begin(); + while (i != ExampleNode::exampleNodeMap.end()) { + const ExampleNode* en = i.value(); + if (demos) { + if (!en->name().startsWith("demos")) { + ++i; + continue; + } + } + else if (en->name().startsWith("demos")) { + ++i; + continue; + } + writer.writeStartElement(element); + writer.writeAttribute("name", en->title()); + QString docUrl = manifestDir + en->fileBase() + ".html"; + writer.writeAttribute("docUrl", docUrl); + foreach (const Node* child, en->childNodes()) { + if (child->subType() == Node::File) { + QString file = child->name(); + if (file.endsWith(".pro") || file.endsWith(".qmlproject")) { + if (file.startsWith("demos/")) + file = file.mid(6); + writer.writeAttribute("projectPath", file); + break; + } + } + } + if (!en->imageFileName().isEmpty()) + writer.writeAttribute("imageUrl", manifestDir + en->imageFileName()); + 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 + QStringList tags = en->title().toLower().split(QLatin1Char(' ')); + if (!tags.isEmpty()) { + writer.writeStartElement("tags"); + bool wrote_one = false; + for (int n=0; n<tags.size(); ++n) { + QString tag = tags.at(n); + if (tag.at(0).isDigit()) + continue; + if (tag.at(0) == '-') + continue; + if (tag.startsWith("example")) + continue; + if (tag.startsWith("chapter")) + continue; + if (tag.endsWith(QLatin1Char(':'))) + tag.chop(1); + if (n>0 && wrote_one) + writer.writeCharacters(","); + writer.writeCharacters(tag); + wrote_one = true; + } + writer.writeEndElement(); // tags + } + + QString ename = en->name().mid(en->name().lastIndexOf('/')+1); + QSet<QString> usedNames; + foreach (const Node* child, en->childNodes()) { + if (child->subType() == Node::File) { + QString file = child->name(); + QString fileName = file.mid(file.lastIndexOf('/')+1); + QString baseName = fileName; + if ((fileName.count(QChar('.')) > 0) && + (fileName.endsWith(".cpp") || + fileName.endsWith(".h") || + fileName.endsWith(".qml"))) + baseName.truncate(baseName.lastIndexOf(QChar('.'))); + if (baseName.toLower() == ename) { + if (!usedNames.contains(fileName)) { + writer.writeStartElement("fileToOpen"); + if (file.startsWith("demos/")) + file = file.mid(6); + writer.writeCharacters(file); + writer.writeEndElement(); // fileToOpen + usedNames.insert(fileName); + } + } + else if (fileName.toLower().endsWith("main.cpp") || + fileName.toLower().endsWith("main.qml")) { + if (!usedNames.contains(fileName)) { + writer.writeStartElement("fileToOpen"); + if (file.startsWith("demos/")) + file = file.mid(6); + writer.writeCharacters(file); + writer.writeEndElement(); // fileToOpen + usedNames.insert(fileName); + } + } + } + } + writer.writeEndElement(); // example + ++i; + } + + writer.writeEndElement(); // examples + writer.writeEndElement(); // instructionals + writer.writeEndDocument(); + file.close(); +} + +/*! + Find global entities that have documentation but no + \e{relates} comand. Report these as errors if they + are not also marked \e {internal}. + + type: Class + type: Namespace + + subtype: Example + subtype: External page + subtype: Group + subtype: Header file + subtype: Module + subtype: Page + subtype: QML basic type + subtype: QML class + subtype: QML module + */ +void HtmlGenerator::reportOrphans(const InnerNode* parent) +{ + const NodeList& children = parent->childNodes(); + if (children.size() == 0) + return; + + bool related; + QString message; + for (int i=0; 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::Fake: + switch (child->subType()) { + case Node::Example: + break; + case Node::HeaderFile: + break; + case Node::File: + break; + case Node::Image: + break; + case Node::Group: + break; + case Node::Module: + break; + case Node::Page: + break; + case Node::ExternalPage: + break; + case Node::QmlClass: + break; + case Node::QmlPropertyGroup: + break; + case Node::QmlBasicType: + break; + case Node::QmlModule: + break; + case Node::Collision: + break; + default: + break; + } + break; + case Node::Enum: + if (!related) + child->location().warning(tr("Global enum, %1, %2").arg(child->name()).arg(message)); + break; + case Node::Typedef: + if (!related) + child->location().warning(tr("Global typedef, %1, %2").arg(child->name()).arg(message)); + break; + case Node::Function: + if (!related) { + const FunctionNode* fn = static_cast<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::Target: + break; + case Node::QmlProperty: + if (!related) + child->location().warning(tr("Global QML property, %1, %2").arg(child->name()).arg(message)); + break; + case Node::QmlSignal: + if (!related) + child->location().warning(tr("Global QML, signal, %1 %2").arg(child->name()).arg(message)); + break; + case Node::QmlSignalHandler: + if (!related) + child->location().warning(tr("Global QML signal handler, %1, %2").arg(child->name()).arg(message)); + break; + case Node::QmlMethod: + if (!related) + child->location().warning(tr("Global QML method, %1, %2").arg(child->name()).arg(message)); + break; + default: + break; + } + } +} + +/*! + Returns a reference to the XML stream writer currently in use. + There is one XML stream writer open for each XML file being + written, and they are kept on a stack. The one on top of the + stack is the one being written to at the moment. In the HTML + output generator, it is perhaps impossible for there to ever + be more than one writer open. + */ +QXmlStreamWriter& HtmlGenerator::xmlWriter() +{ + return *xmlWriterStack.top(); +} + +/*! + This function is only called for writing ditamaps. + + Calls beginSubPage() in the base class to open the file. + Then creates a new XML stream writer using the IO device + from opened file and pushes the XML writer onto a stackj. + Creates the file named \a fileName in the output directory. + Attaches a QTextStream to the created file, which is written + to all over the place using out(). Finally, it sets some + parameters in the XML writer and calls writeStartDocument(). + + It also ensures that a GUID map is created for the output file. + */ +void HtmlGenerator::beginDitamapPage(const InnerNode* node, const QString& fileName) +{ + PageGenerator::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(); + PageGenerator::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 FakeNode* fn = myTree->findFakeNodeByTitle(t->navtitle()); + if (fn) + xmlWriter().writeAttribute("href",fileName(fn)); + } + else + xmlWriter().writeAttribute("href",t->href()); + if (t->subrefs() && !t->subrefs()->isEmpty()) + writeDitaRefs(*(t->subrefs())); + xmlWriter().writeEndElement(); // </topicref> or </mapref> + } +} + +QT_END_NAMESPACE |