/**************************************************************************** ** ** Copyright (C) 2015 The Qt Company Ltd. ** Contact: http://www.qt.io/licensing/ ** ** This file is part of the tools applications of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL21$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see http://www.qt.io/terms-conditions. For further ** information use the contact form at http://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 or version 3 as published by the Free ** Software Foundation and appearing in the file LICENSE.LGPLv21 and ** LICENSE.LGPLv3 included in the packaging of this file. Please review the ** following information to ensure the GNU Lesser General Public License ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** As a special exception, The Qt Company gives you certain additional ** rights. These rights are described in The Qt Company LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ /* htmlgenerator.cpp */ #include "codemarker.h" #include "codeparser.h" #include "helpprojectwriter.h" #include "htmlgenerator.h" #include "node.h" #include "qdocdatabase.h" #include "separator.h" #include "tree.h" #include #include #include #include #include #include QT_BEGIN_NAMESPACE #define COMMAND_VERSION Doc::alias("version") int HtmlGenerator::id = 0; bool HtmlGenerator::debugging_on = false; QString HtmlGenerator::divNavTop; static bool showBrokenLinks = false; static QRegExp linkTag("(<@link node=\"([^\"]+)\">).*()"); static QRegExp funcTag("(<@func target=\"([^\"]*)\">)(.*)()"); static QRegExp typeTag("(<@(type|headerfile|func)(?: +[^>]*)?>)(.*)()"); static QRegExp spanTag(""); static QRegExp unknownTag("]*>"); static void addLink(const QString &linkTarget, const QStringRef &nestedStuff, QString *res) { if (!linkTarget.isEmpty()) { *res += ""; *res += nestedStuff; *res += ""; } else { *res += nestedStuff; } } /*! Constructs the HTML output generator. */ HtmlGenerator::HtmlGenerator() : codeIndent(0), helpProjectWriter(0), inObsoleteLink(false), funcLeftParen("\\S(\\()"), obsoleteLinks(false) { } /*! Destroys the HTML output generator. Deletes the singleton instance of HelpProjectWriter. */ HtmlGenerator::~HtmlGenerator() { if (helpProjectWriter) { delete helpProjectWriter; helpProjectWriter = 0; } } /*! Initializes the HTML output generator's data structures from the configuration class \a config. */ void HtmlGenerator::initializeGenerator(const Config &config) { static const struct { const char *key; const char *left; const char *right; } defaults[] = { { ATOM_FORMATTING_BOLD, "", "" }, { ATOM_FORMATTING_INDEX, "" }, { ATOM_FORMATTING_ITALIC, "", "" }, { ATOM_FORMATTING_PARAMETER, "", "" }, { ATOM_FORMATTING_SUBSCRIPT, "", "" }, { ATOM_FORMATTING_SUPERSCRIPT, "", "" }, { ATOM_FORMATTING_TELETYPE, "", "" }, // tag is not supported in HTML5 { ATOM_FORMATTING_UICONTROL, "", "" }, { ATOM_FORMATTING_UNDERLINE, "", "" }, { 0, 0, 0 } }; Generator::initializeGenerator(config); obsoleteLinks = config.getBool(CONFIG_OBSOLETELINKS); setImageFileExtensions(QStringList() << "png" << "jpg" << "jpeg" << "gif"); /* The formatting maps are owned by Generator. They are cleared in Generator::terminate(). */ int i = 0; while (defaults[i].key) { formattingLeftMap().insert(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); prologue = config.getString(HtmlGenerator::format() + Config::dot + HTMLGENERATOR_PROLOGUE); footer = config.getString(HtmlGenerator::format() + Config::dot + HTMLGENERATOR_FOOTER); address = config.getString(HtmlGenerator::format() + Config::dot + HTMLGENERATOR_ADDRESS); pleaseGenerateMacRef = config.getBool(HtmlGenerator::format() + Config::dot + HTMLGENERATOR_GENERATEMACREFS); noNavigationBar = config.getBool(HtmlGenerator::format() + Config::dot + HTMLGENERATOR_NONAVIGATIONBAR); tocDepth = config.getInt(HtmlGenerator::format() + Config::dot + HTMLGENERATOR_TOCDEPTH); project = config.getString(CONFIG_PROJECT); projectDescription = config.getString(CONFIG_DESCRIPTION); if (projectDescription.isEmpty() && !project.isEmpty()) projectDescription = project + " Reference Documentation"; projectUrl = config.getString(CONFIG_URL); tagFile_ = config.getString(CONFIG_TAGFILE); #ifndef QT_NO_TEXTCODEC outputEncoding = config.getString(CONFIG_OUTPUTENCODING); if (outputEncoding.isEmpty()) outputEncoding = QLatin1String("UTF-8"); outputCodec = QTextCodec::codecForName(outputEncoding.toLocal8Bit()); #endif naturalLanguage = config.getString(CONFIG_NATURALLANGUAGE); if (naturalLanguage.isEmpty()) naturalLanguage = QLatin1String("en"); QSet editionNames = config.subVars(CONFIG_EDITION); QSet::ConstIterator edition = editionNames.constBegin(); while (edition != editionNames.constEnd()) { QString editionName = *edition; QStringList editionModules = config.getStringList(CONFIG_EDITION + Config::dot + editionName + Config::dot + "modules"); QStringList editionGroups = config.getStringList(CONFIG_EDITION + Config::dot + editionName + Config::dot + "groups"); if (!editionModules.isEmpty()) editionModuleMap[editionName] = editionModules; if (!editionGroups.isEmpty()) editionGroupMap[editionName] = editionGroups; ++edition; } // The following line was changed to fix QTBUG-27798 //codeIndent = config.getInt(CONFIG_CODEINDENT); /* The help file write should be allocated once and only once per qdoc execution. */ if (helpProjectWriter) helpProjectWriter->reset(config, project.toLower() + ".qhp", this); else helpProjectWriter = new HelpProjectWriter(config, project.toLower() + ".qhp", this); // Documentation template handling headerScripts = config.getString(HtmlGenerator::format() + Config::dot + CONFIG_HEADERSCRIPTS); headerStyles = config.getString(HtmlGenerator::format() + Config::dot + CONFIG_HEADERSTYLES); QString prefix = CONFIG_QHP + Config::dot + project + Config::dot; manifestDir = "qthelp://" + config.getString(prefix + "namespace"); manifestDir += QLatin1Char('/') + config.getString(prefix + "virtualFolder") + QLatin1Char('/'); readManifestMetaContent(config); examplesPath = config.getString(CONFIG_EXAMPLESINSTALLPATH); if (!examplesPath.isEmpty()) examplesPath += QLatin1Char('/'); //retrieve the config for the navigation bar homepage = config.getString(CONFIG_NAVIGATION + Config::dot + CONFIG_HOMEPAGE); landingpage = config.getString(CONFIG_NAVIGATION + Config::dot + CONFIG_LANDINGPAGE); cppclassespage = config.getString(CONFIG_NAVIGATION + Config::dot + CONFIG_CPPCLASSESPAGE); qmltypespage = config.getString(CONFIG_NAVIGATION + Config::dot + CONFIG_QMLTYPESPAGE); buildversion = config.getString(CONFIG_BUILDVERSION); } /*! Gracefully terminates the HTML output generator. */ void HtmlGenerator::terminateGenerator() { Generator::terminateGenerator(); } QString HtmlGenerator::format() { return "HTML"; } /*! Generate targets for any \keyword commands that were seen in the qdoc comment for the \a node. */ void HtmlGenerator::generateKeywordAnchors(const Node* node) { if (!node->doc().isEmpty()) { const QList& keywords = node->doc().keywords(); foreach (Atom* a, keywords) { out() << "string()) << "\">"; } } } /*! If qdoc is in the \c {-prepare} phase, traverse the primary tree to generate the index file for the current module. If qdoc is in the \c {-generate} phase, traverse the primary tree to generate all the HTML documentation for the current module. Then generate the help file and the tag file. */ void HtmlGenerator::generateDocs() { Node* qflags = qdb_->findClassNode(QStringList("QFlags")); if (qflags) qflagsHref_ = linkForNode(qflags,0); if (!preparing()) Generator::generateDocs(); if (Generator::generating() && Generator::writeQaPages()) generateQAPage(); if (!generating()) { QString fileBase = project.toLower().simplified().replace(QLatin1Char(' '), QLatin1Char('-')); qdb_->generateIndex(outputDir() + QLatin1Char('/') + fileBase + ".index", projectUrl, projectDescription, this, true); } if (!preparing()) { helpProjectWriter->generate(); generateManifestFiles(); /* Generate the XML tag file, if it was requested. */ qdb_->generateTagFile(tagFile_, this); } } /*! Output the module's Quality Assurance page. */ void HtmlGenerator::generateQAPage() { NamespaceNode* node = qdb_->primaryTreeRoot(); beginSubPage(node, "aaa-" + defaultModuleName().toLower() + "-qa-page.html"); CodeMarker* marker = CodeMarker::markerForFileName(node->location().filePath()); QString title = "Quality Assurance Page for " + defaultModuleName(); QString t = "Quality assurance information for checking the " + defaultModuleName() + " documentation."; generateHeader(title, node, marker); generateTitle(title, Text() << t, LargeSubTitle, node, marker); QStringList strings; QVector counts; QString depends = qdb_->getLinkCounts(strings, counts); if (!strings.isEmpty()) { t = "Intermodule Link Counts"; QString ref = registerRef(t); out() << "" << divNavTop << '\n'; out() << "

" << protectEnc(t) << "

\n"; out() << "" << "\n"; QString fileName; for (int i = 0; i< strings.size(); ++i) { fileName = generateLinksToLinksPage(strings.at(i), marker); out() << "\n"; } int count = 0; fileName = generateLinksToBrokenLinksPage(marker, count); if (count != 0) { out() << "\n"; } out() << "
Destination ModuleLink Count
" << "" << strings.at(i) << "" << "" << counts.at(i) << "
" << "" << "Broken Links" << "" << "" << count << "
\n"; t = "The Optimal \"depends\" Variable"; out() << "

" << protectEnc(t) << "

\n"; t = "Consider replacing the depends variable in " + defaultModuleName().toLower() + ".qdocconf with this one, if the two are not identical:"; out() << "

" << protectEnc(t) << "

\n"; out() << "

" << protectEnc(depends) << "

\n"; } generateFooter(); endSubPage(); } /*! This function writes an html file containing a list of links to links that originate in the current module and go to targets in the specified \a module. The \a marker is used for the same thing the marker is always used for. */ QString HtmlGenerator::generateLinksToLinksPage(const QString& module, CodeMarker* marker) { NamespaceNode* node = qdb_->primaryTreeRoot(); QString fileName = "aaa-links-to-" + module + ".html"; beginSubPage(node, fileName); QString title = "Links from " + defaultModuleName() + " to " + module; generateHeader(title, node, marker); generateTitle(title, Text(), SmallSubTitle, node, marker); out() << "

This is a list of links from " << defaultModuleName() << " to " << module << ". "; out() << "Click on a link to go to the location of the link. The link is marked "; out() << "with red asterisks. "; out() << "Click on the marked link to see if it goes to the right place.

\n"; TargetList* tlist = qdb_->getTargetList(module); if (tlist) { out() << "\n"; foreach (TargetLoc* t, *tlist) { // e.g.: Layout Management out() << ""; out() << ""; out() << "\n"; } out() << "
Link to link...In file...Somewhere after line number...
"; out() << "fileName_ << "#" << t->target_ << "\">"; out() << t->text_ << ""; QString f = t->loc_->doc().location().filePath(); out() << f << ""; out() << t->loc_->doc().location().lineNo() << "
\n"; } generateFooter(); endSubPage(); return fileName; } /*! This function writes an html file containing a list of links to broken links that originate in the current module and go nowwhere. It returns the name of the file it generates, and it sets \a count to the number of broken links that were found. The \a marker is used for the same thing the marker is always used for. */ QString HtmlGenerator::generateLinksToBrokenLinksPage(CodeMarker* marker, int& count) { QString fileName; NamespaceNode* node = qdb_->primaryTreeRoot(); TargetList* tlist = qdb_->getTargetList("broken"); if (tlist && !tlist->isEmpty()) { count = tlist->size(); fileName = "aaa-links-to-broken-links.html"; beginSubPage(node, fileName); QString title = "Broken links in " + defaultModuleName(); generateHeader(title, node, marker); generateTitle(title, Text(), SmallSubTitle, node, marker); out() << "

This is a list of broken links in " << defaultModuleName() << ". "; out() << "Click on a link to go to the broken link. "; out() << "The link's target could not be found.

\n"; out() << "\n"; foreach (TargetLoc* t, *tlist) { // e.g.: Layout Management out() << ""; out() << ""; out() << "\n"; } out() << "
Link to broken link...In file...Somewhere after line number...
"; out() << "fileName_ << "#" << t->target_ << "\">"; out() << t->text_ << ""; QString f = t->loc_->doc().location().filePath(); out() << f << ""; out() << t->loc_->doc().location().lineNo() << "
\n"; generateFooter(); endSubPage(); } return fileName; } /*! Generate html from an instance of Atom. */ int HtmlGenerator::generateAtom(const Atom *atom, const Node *relative, CodeMarker *marker) { int 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: case Atom::NavAutoLink: if (!inLink_ && !inContents_ && !inSectionHeading_) { const Node *node = 0; QString link = getAutoLink(atom, relative, &node); if (link.isEmpty()) { if (autolinkErrors()) relative->doc().location().warning(tr("Can't autolink to '%1'").arg(atom->string())); } else if (node && node->status() == Node::Obsolete) { if ((relative->parent() != node) && !relative->isObsolete()) link.clear(); } if (link.isEmpty()) { out() << protectEnc(atom->string()); } else { if (Generator::writeQaPages() && node && (atom->type() != Atom::NavAutoLink)) { QString text = atom->string(); QString target = qdb_->getNewLinkTarget(relative, node, outFileName(), text); out() << ""; } beginLink(link, node, relative); generateLink(atom, marker); endLink(); } } else { out() << protectEnc(atom->string()); } break; case Atom::BaseName: break; case Atom::BriefLeft: // Do not output the brief for QML nodes, doc nodes or collections // (groups, modules, qml module nodes) if (relative->isQmlType() || relative->isDocumentNode() || relative->isCollectionNode() || relative->isJsType()) { skipAhead = skipAtoms(atom, Atom::BriefRight); break; } out() << "

"; 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->isDocumentNode()) out() << "

\n"; break; case Atom::C: // This may at one time have been used to mark up C++ code but it is // now widely used to write teletype text. As a result, text marked // with the \c command is not passed to a code marker. out() << formattingLeftMap()[ATOM_FORMATTING_TELETYPE]; if (inLink_) { out() << protectEnc(plainCode(atom->string())); } else { out() << protectEnc(plainCode(atom->string())); } out() << formattingRightMap()[ATOM_FORMATTING_TELETYPE]; break; case Atom::CaptionLeft: out() << "

"; in_para = true; break; case Atom::CaptionRight: endLink(); if (in_para) { out() << "

\n"; in_para = false; } break; case Atom::Code: out() << "
"
              << trimmedTrailing(highlightedCode(indent(codeIndent,atom->string()),relative))
              << "
\n"; break; case Atom::Qml: out() << "
"
              << trimmedTrailing(highlightedCode(indent(codeIndent,atom->string()),relative))
              << "
\n"; break; case Atom::JavaScript: out() << "
"
              << trimmedTrailing(highlightedCode(indent(codeIndent,atom->string()),relative))
              << "
\n"; break; case Atom::CodeNew: out() << "

you can rewrite it as

\n" << "
"
              << trimmedTrailing(highlightedCode(indent(codeIndent,atom->string()),relative))
              << "
\n"; break; case Atom::CodeOld: out() << "

For example, if you have code like

\n"; // fallthrough case Atom::CodeBad: out() << "
"
              << trimmedTrailing(protectEnc(plainCode(indent(codeIndent,atom->string()))))
              << "
\n"; break; case Atom::DivLeft: out() << "string().isEmpty()) out() << ' ' << atom->string(); out() << '>'; break; case Atom::DivRight: out() << ""; break; case Atom::FootnoteLeft: // ### For now if (in_para) { out() << "

\n"; in_para = false; } 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) << "" << subscriptRegExp.cap(2) << ""; skipAhead = 1; } } } break; case Atom::FormattingRight: if (atom->string() == ATOM_FORMATTING_LINK) { endLink(); } else if (atom->string().startsWith("span ")) { out() << ""; } else { out() << formattingRightMap()[atom->string()]; } break; case Atom::AnnotatedList: { CollectionNode* cn = qdb_->getCollection(atom->string(), Node::DOC); if (cn) generateList(cn, marker, atom->string()); } break; case Atom::GeneratedList: if (atom->string() == "annotatedclasses") { generateAnnotatedList(relative, marker, qdb_->getCppClasses()); } else if (atom->string() == "classes") { generateCompactList(Generic, relative, qdb_->getCppClasses(), true, QStringLiteral("")); } else if (atom->string().contains("classes ")) { QString rootName = atom->string().mid(atom->string().indexOf("classes") + 7).trimmed(); generateCompactList(Generic, relative, qdb_->getCppClasses(), true, rootName); } else if (atom->string() == "qmlbasictypes") { generateCompactList(Generic, relative, qdb_->getQmlBasicTypes(), true, QStringLiteral("")); } else if (atom->string() == "qmltypes") { generateCompactList(Generic, relative, qdb_->getQmlTypes(), true, QStringLiteral("")); } else if (atom->string().contains("classesbymodule")) { QString physicalModuleName = atom->string().mid(atom->string().indexOf("classesbymodule") + 15).trimmed(); QDocDatabase* qdb = QDocDatabase::qdocDB(); CollectionNode* cn = qdb->findModule(physicalModuleName); if (cn) { NodeMap m; cn->getMemberClasses(m); if (!m.isEmpty()) { generateAnnotatedList(relative, marker, m); } } } else if (atom->string() == "classhierarchy") { generateClassHierarchy(relative, qdb_->getCppClasses()); } else if (atom->string() == "obsoleteclasses") { generateCompactList(Generic, relative, qdb_->getObsoleteClasses(), false, QStringLiteral("Q")); } else if (atom->string() == "obsoleteqmltypes") { generateCompactList(Generic, relative, qdb_->getObsoleteQmlTypes(), false, QStringLiteral("")); } else if (atom->string() == "obsoletecppmembers") { generateCompactList(Obsolete, relative, qdb_->getClassesWithObsoleteMembers(), false, QStringLiteral("Q")); } else if (atom->string() == "obsoleteqmlmembers") { generateCompactList(Obsolete, relative, qdb_->getQmlTypesWithObsoleteMembers(), false, QStringLiteral("")); } else if (atom->string() == "functionindex") { generateFunctionIndex(relative); } else if (atom->string() == "legalese") { generateLegaleseList(relative, marker); } else if (atom->string() == "overviews") { generateList(relative, marker, "overviews"); } else if (atom->string() == "cpp-modules") { generateList(relative, marker, "cpp-modules"); } else if (atom->string() == "qml-modules") { generateList(relative, marker, "qml-modules"); } else if (atom->string() == "namespaces") { generateAnnotatedList(relative, marker, qdb_->getNamespaces()); } else if (atom->string() == "related") { generateList(relative, marker, "related"); } #if 0 /* This is not used in Qt5, as of 10/02/2014 Remove permanently if it is not missed. */ else if (atom->string() == "relatedinline") { const DocumentNode *dn = static_cast(relative); if (dn && !dn->members().isEmpty()) { // Reverse the list into the original scan order. // Should be sorted. But on what? It may not be a // regular class or page definition. QList list; foreach (const Node *node, dn->members()) list.prepend(node); foreach (const Node *node, list) generateBody(node, marker); } } #endif break; case Atom::SinceList: { const NodeMultiMap& nsmap = qdb_->getSinceMap(atom->string()); const NodeMap& ncmap = qdb_->getClassMap(atom->string()); const NodeMap& nqcmap = qdb_->getQmlTypeMap(atom->string()); if (!nsmap.isEmpty()) { QList
sections; QList
::ConstIterator s; for (int i=0; itype()) { case Node::QmlType: sections[QmlClass].appendMember(node); break; case Node::Namespace: sections[Namespace].appendMember(node); break; case Node::Class: sections[Class].appendMember(node); break; case Node::Enum: sections[Enum].appendMember(node); break; case Node::Typedef: sections[Typedef].appendMember(node); break; case Node::Function: { const FunctionNode* fn = static_cast(node); if (fn->isMacro()) sections[Macro].appendMember(node); else { Node* p = fn->parent(); if (p) { if (p->type() == Node::Class) sections[MemberFunction].appendMember(node); else if (p->type() == Node::Namespace) { if (p->name().isEmpty()) sections[GlobalFunction].appendMember(node); else sections[NamespaceFunction].appendMember(node); } else sections[GlobalFunction].appendMember(node); } else sections[GlobalFunction].appendMember(node); } break; } case Node::Property: sections[Property].appendMember(node); break; case Node::Variable: sections[Variable].appendMember(node); break; case Node::QmlProperty: sections[QmlProperty].appendMember(node); break; case Node::QmlSignal: sections[QmlSignal].appendMember(node); break; case Node::QmlSignalHandler: sections[QmlSignalHandler].appendMember(node); break; case Node::QmlMethod: sections[QmlMethod].appendMember(node); break; default: break; } ++n; } out() << "
    \n"; s = sections.constBegin(); while (s != sections.constEnd()) { if (!(*s).members.isEmpty()) { out() << "
  • " << "" << (*s).name << "
  • \n"; } ++s; } out() << "
\n"; int idx = 0; s = sections.constBegin(); while (s != sections.constEnd()) { if (!(*s).members.isEmpty()) { out() << "\n"; out() << "

" << protectEnc((*s).name) << "

\n"; if (idx == Class) generateCompactList(Generic, 0, ncmap, false, QStringLiteral("Q")); else if (idx == QmlClass) generateCompactList(Generic, 0, nqcmap, false, QStringLiteral("")); else if (idx == MemberFunction) { ParentMaps parentmaps; ParentMaps::iterator pmap; NodeList::const_iterator i = s->members.constBegin(); while (i != s->members.constEnd()) { Node* p = (*i)->parent(); pmap = parentmaps.find(p); if (pmap == parentmaps.end()) pmap = parentmaps.insert(p,NodeMultiMap()); pmap->insert((*i)->name(),(*i)); ++i; } pmap = parentmaps.begin(); while (pmap != parentmaps.end()) { NodeList nlist = pmap->values(); out() << "

Class "; out() << ""; QStringList pieces = pmap.key()->fullName().split("::"); out() << protectEnc(pieces.last()); out() << "" << ":

\n"; generateSection(nlist, 0, marker, CodeMarker::Summary); out() << "
"; ++pmap; } } else generateSection(s->members, 0, marker, CodeMarker::Summary); } ++idx; ++s; } } } break; case Atom::BR: out() << "
\n"; break; case Atom::HR: out() << "
\n"; break; case Atom::Image: case Atom::InlineImage: { QString fileName = imageFileName(relative, atom->string()); QString text; if (atom->next() != 0) text = atom->next()->string(); if (atom->type() == Atom::Image) out() << "

"; if (fileName.isEmpty()) { relative->location().warning(tr("Missing image: %1").arg(protectEnc(atom->string()))); out() << "[Missing image " << protectEnc(atom->string()) << "]"; } else { QString prefix; out() << "\"""; helpProjectWriter->addExtraFile(fileName); if (relative->isExample()) { const ExampleNode* cen = static_cast(relative); if (cen->imageFileName().isEmpty()) { ExampleNode* en = const_cast(cen); en->setImageFileName(fileName); } } } if (atom->type() == Atom::Image) out() << "

"; } break; case Atom::ImageText: break; case Atom::ImportantLeft: out() << "

"; out() << formattingLeftMap()[ATOM_FORMATTING_BOLD]; out() << "Important: "; out() << formattingRightMap()[ATOM_FORMATTING_BOLD]; break; case Atom::ImportantRight: out() << "

"; break; case Atom::NoteLeft: out() << "

"; out() << formattingLeftMap()[ATOM_FORMATTING_BOLD]; out() << "Note: "; out() << formattingRightMap()[ATOM_FORMATTING_BOLD]; break; case Atom::NoteRight: out() << "

"; break; case Atom::LegaleseLeft: out() << "
"; break; case Atom::LegaleseRight: out() << "
"; break; case Atom::LineBreak: out() << "
"; break; case Atom::Link: case Atom::NavLink: { inObsoleteLink = false; const Node *node = 0; QString link = getLink(atom, relative, &node); if (link.isEmpty() && (node != relative) && !noLinkErrors()) { relative->doc().location().warning(tr("Can't link to '%1'").arg(atom->string())); if (Generator::writeQaPages() && (atom->type() != Atom::NavAutoLink)) { QString text = atom->next()->next()->string(); QString target = qdb_->getNewLinkTarget(relative, node, outFileName(), text, true); out() << ""; } } else { if (Generator::writeQaPages() && node && (atom->type() != Atom::NavLink)) { QString text = atom->next()->next()->string(); QString target = qdb_->getNewLinkTarget(relative, node, outFileName(), text); out() << ""; } /* mws saw this on 17/10/2014. Is this correct? Setting node to 0 means the following test always fails. Did we decide to no longer warn about linking to obsolete things? */ node = 0; if (node && node->status() == Node::Obsolete) { if ((relative->parent() != node) && !relative->isObsolete()) { inObsoleteLink = true; if (obsoleteLinks) { relative->doc().location().warning(tr("Link to obsolete item '%1' in %2") .arg(atom->string()) .arg(relative->plainFullName())); } } } } beginLink(link, node, relative); skipAhead = 1; } break; case Atom::LinkNode: { const Node *node = CodeMarker::nodeForString(atom->string()); beginLink(linkForNode(node, relative), node, relative); skipAhead = 1; } break; case Atom::ListLeft: if (in_para) { out() << "

\n"; in_para = false; } if (atom->string() == ATOM_LIST_BULLET) { out() << "
    \n"; } else if (atom->string() == ATOM_LIST_TAG) { out() << "
    \n"; } else if (atom->string() == ATOM_LIST_VALUE) { out() << "
    "; threeColumnEnumValueTable_ = isThreeColumnEnumValueTable(atom); if (threeColumnEnumValueTable_) { if (++numTableRows_ % 2 == 1) out() << ""; else out() << ""; out() << ""; // If not in \enum topic, skip the value column if (relative->type() == Node::Enum) out() << ""; out() << "\n"; } else { out() << "\n"; } } else { out() << "
      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() << "
      "; } else { // (atom->string() == ATOM_LIST_VALUE) // ### Trenton QString t= protectEnc(plainCode(marker->markedUpEnumValue(atom->next()->string(),relative))); out() << "
    \n"; } else { out() << "\n"; } break; case Atom::ListRight: if (atom->string() == ATOM_LIST_BULLET) { out() << "\n"; } else if (atom->string() == ATOM_LIST_TAG) { out() << "\n"; } else if (atom->string() == ATOM_LIST_VALUE) { out() << "
    ConstantValueDescription
    ConstantValue
    " << t << ""; if (relative->type() == Node::Enum) { out() << ""; const EnumNode *enume = static_cast(relative); QString itemValue = enume->itemValue(atom->next()->string()); if (itemValue.isEmpty()) out() << '?'; else out() << "" << protectEnc(itemValue) << ""; } skipAhead = 1; } break; case Atom::ListTagRight: if (atom->string() == ATOM_LIST_TAG) out() << "\n"; break; case Atom::ListItemLeft: if (atom->string() == ATOM_LIST_TAG) { out() << "
    "; } else if (atom->string() == ATOM_LIST_VALUE) { if (threeColumnEnumValueTable_) { out() << "
    "; if (matchAhead(atom, Atom::ListItemRight)) out() << " "; } } else { out() << "
  • "; } if (matchAhead(atom, Atom::ParaLeft)) skipAhead = 1; break; case Atom::ListItemRight: if (atom->string() == ATOM_LIST_TAG) { out() << "\n"; } else if (atom->string() == ATOM_LIST_VALUE) { out() << "
  • \n"; } else { out() << "\n"; } break; case Atom::Nop: break; case Atom::ParaLeft: out() << "

    "; in_para = true; break; case Atom::ParaRight: endLink(); if (in_para) { out() << "

    \n"; in_para = false; } //if (!matchAhead(atom, Atom::ListItemRight) && !matchAhead(atom, Atom::TableItemRight)) // out() << "

    \n"; break; case Atom::QuotationLeft: out() << "
    "; break; case Atom::QuotationRight: out() << "
    \n"; break; case Atom::RawString: out() << atom->string(); break; case Atom::SectionLeft: out() << "" << divNavTop << '\n'; break; case Atom::SectionRight: break; case Atom::SectionHeadingLeft: { int unit = atom->string().toInt() + hOffset(relative); out() << ""; inSectionHeading_ = true; break; } case Atom::SectionHeadingRight: out() << "string().toInt() + hOffset(relative)) + ">\n"; inSectionHeading_ = false; break; case Atom::SidebarLeft: break; case Atom::SidebarRight: break; case Atom::String: if (inLink_ && !inContents_ && !inSectionHeading_) { generateLink(atom, marker); } else { out() << protectEnc(atom->string()); } break; case Atom::TableLeft: { QString p1, p2; QString attr = "generic"; QString width; if (in_para) { out() << "

    \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() << "
    \n "; numTableRows_ = 0; } break; case Atom::TableRight: out() << "
    \n"; break; case Atom::TableHeaderLeft: out() << ""; inTableHeader_ = true; break; case Atom::TableHeaderRight: out() << ""; if (matchAhead(atom, Atom::TableHeaderLeft)) { skipAhead = 1; out() << "\n"; } else { out() << "\n"; inTableHeader_ = false; } break; case Atom::TableRowLeft: if (!atom->string().isEmpty()) out() << "string() << '>'; else if (++numTableRows_ % 2 == 1) out() << ""; else out() << ""; break; case Atom::TableRowRight: out() << "\n"; break; case Atom::TableItemLeft: { if (inTableHeader_) out() << "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() << ">

    "; } if (matchAhead(atom, Atom::ParaLeft)) skipAhead = 1; } break; case Atom::TableItemRight: if (inTableHeader_) out() << ""; else { out() << ""; //out() << "

    "; } if (matchAhead(atom, Atom::ParaLeft)) skipAhead = 1; break; case Atom::TableOfContents: break; case Atom::Keyword: break; case Atom::Target: out() << "string()) << "\">"; break; case Atom::UnhandledFormat: out() << "<Missing HTML>"; break; case Atom::UnknownCommand: out() << "\\" << protectEnc(atom->string()) << ""; break; case Atom::QmlText: case Atom::EndQmlText: // don't do anything with these. They are just tags. break; default: unknownAtom(atom); } return skipAhead; } /*! Generate a reference page for a C++ class or a C++ namespace. */ void HtmlGenerator::generateClassLikeNode(InnerNode* inner, CodeMarker* marker) { QList
    sections; QList
    ::ConstIterator s; QString title; QString rawTitle; QString fullTitle; if (inner->type() == Node::Namespace) { rawTitle = inner->plainName(); fullTitle = inner->plainFullName(); title = rawTitle + " Namespace"; } else if (inner->type() == Node::Class) { rawTitle = inner->plainName(); fullTitle = inner->plainFullName(); title = rawTitle + " Class"; } Text subtitleText; if (rawTitle != fullTitle) subtitleText << "(" << Atom(Atom::AutoLink, fullTitle) << ")" << Atom(Atom::LineBreak); generateHeader(title, inner, marker); sections = marker->sections(inner, CodeMarker::Summary, CodeMarker::Okay); generateTableOfContents(inner,marker,§ions); generateKeywordAnchors(inner); generateTitle(title, subtitleText, SmallSubTitle, inner, marker); generateBrief(inner, marker); generateRequisites(inner, marker); generateStatus(inner, marker); generateThreadSafeness(inner, marker); out() << "\n"; bool needOtherSection = false; /* sections is built above for the call to generateTableOfContents(). */ s = sections.constBegin(); while (s != sections.constEnd()) { if (s->members.isEmpty() && s->reimpMembers.isEmpty()) { if (!s->inherited.isEmpty()) needOtherSection = true; } else { if (!s->members.isEmpty()) { // out() << "
    \n"; QString ref = registerRef((*s).name.toLower()); out() << "" << divNavTop << "\n"; out() << "

    " << protectEnc((*s).name) << "

    \n"; generateSection(s->members, inner, marker, CodeMarker::Summary); } if (!s->reimpMembers.isEmpty()) { QString name = QString("Reimplemented ") + (*s).name; QString ref = registerRef(name.toLower()); // out() << "
    \n"; out() << "" << divNavTop << "\n"; out() << "

    " << protectEnc(name) << "

    \n"; generateSection(s->reimpMembers, inner, marker, CodeMarker::Summary); } if (!s->inherited.isEmpty()) { out() << "
      \n"; generateSectionInheritedList(*s, inner); out() << "
    \n"; } } ++s; } if (needOtherSection) { out() << "

    Additional Inherited Members

    \n" "
      \n"; s = sections.constBegin(); while (s != sections.constEnd()) { if (s->members.isEmpty() && !s->inherited.isEmpty()) generateSectionInheritedList(*s, inner); ++s; } out() << "
    \n"; } QString detailsRef = registerRef("details"); out() << "" << divNavTop << '\n'; if (!inner->doc().isEmpty()) { generateExtractionMark(inner, DetailedDescriptionMark); //out() << "
    \n" out() << "
    \n" // QTBUG-9504 << "

    " << "Detailed Description" << "

    \n"; generateBody(inner, marker); out() << "
    \n"; // QTBUG-9504 generateAlsoList(inner, marker); generateMaintainerList(inner, marker); generateExtractionMark(inner, EndMark); } sections = marker->sections(inner, CodeMarker::Detailed, CodeMarker::Okay); s = sections.constBegin(); while (s != sections.constEnd()) { //out() << "
    \n"; if (!(*s).divClass.isEmpty()) out() << "
    \n"; // QTBUG-9504 out() << "

    " << protectEnc((*s).name) << "

    \n"; NodeList::ConstIterator m = (*s).members.constBegin(); while (m != (*s).members.constEnd()) { if ((*m)->access() != Node::Private) { // ### check necessary? if ((*m)->type() != Node::Class) generateDetailedMember(*m, inner, marker); else { out() << "

    class "; generateFullName(*m, inner); out() << "

    "; generateBrief(*m, marker, inner); } QStringList names; names << (*m)->name(); if ((*m)->type() == Node::Function) { const FunctionNode *func = reinterpret_cast(*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(*m); if (!prop->getters().isEmpty() && !names.contains(prop->getters().first()->name())) names << prop->getters().first()->name(); if (!prop->setters().isEmpty()) names << prop->setters().first()->name(); if (!prop->resetters().isEmpty()) names << prop->resetters().first()->name(); if (!prop->notifiers().isEmpty()) names << prop->notifiers().first()->name(); } else if ((*m)->type() == Node::Enum) { const EnumNode *enume = reinterpret_cast(*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() << "
    \n"; // QTBUG-9504 ++s; } generateFooter(inner); } /*! Generate the HTML page for a QML type. \qcn is the QML type. \marker is the code markeup object. */ void HtmlGenerator::generateQmlTypePage(QmlTypeNode* qcn, CodeMarker* marker) { Generator::setQmlTypeContext(qcn); SubTitleSize subTitleSize = LargeSubTitle; QList
    ::const_iterator s; QString htmlTitle = qcn->fullTitle(); if (qcn->isJsType()) htmlTitle += " JavaScript Type"; else htmlTitle += " QML Type"; generateHeader(htmlTitle, qcn, marker); QList
    sections = marker->qmlSections(qcn, CodeMarker::Summary); generateTableOfContents(qcn, marker, §ions); marker = CodeMarker::markerForLanguage(QLatin1String("QML")); generateKeywordAnchors(qcn); generateTitle(htmlTitle, Text() << qcn->subTitle(), subTitleSize, qcn, marker); generateBrief(qcn, marker); generateQmlRequisites(qcn, marker); QString allQmlMembersLink = generateAllQmlMembersFile(qcn, marker); QString obsoleteLink = generateQmlMemberFile(qcn, marker, CodeMarker::Obsolete); if (!allQmlMembersLink.isEmpty() || !obsoleteLink.isEmpty()) { out() << "\n"; } s = sections.constBegin(); while (s != sections.constEnd()) { QString ref = registerRef((*s).name.toLower()); out() << "" << divNavTop << '\n'; out() << "

    " << protectEnc((*s).name) << "

    \n"; generateQmlSummary(*s, qcn, marker); ++s; } generateExtractionMark(qcn, DetailedDescriptionMark); QString detailsRef = registerRef("details"); out() << "" << divNavTop << '\n'; out() << "

    " << "Detailed Description" << "

    \n"; generateBody(qcn, marker); ClassNode* cn = qcn->classNode(); if (cn) generateQmlText(cn->doc().body(), cn, marker, qcn->name()); generateAlsoList(qcn, marker); generateExtractionMark(qcn, EndMark); //out() << "
    \n"; sections = marker->qmlSections(qcn,CodeMarker::Detailed); s = sections.constBegin(); while (s != sections.constEnd()) { out() << "

    " << protectEnc((*s).name) << "

    \n"; NodeList::ConstIterator m = (*s).members.constBegin(); while (m != (*s).members.constEnd()) { generateDetailedQmlMember(*m, qcn, marker); out() << "
    \n"; ++m; } ++s; } generateFooter(qcn); Generator::setQmlTypeContext(0); } /*! Generate the HTML page for the QML basic type represented by the QML basic type node \a qbtn. */ void HtmlGenerator::generateQmlBasicTypePage(QmlBasicTypeNode* qbtn, CodeMarker* marker) { SubTitleSize subTitleSize = LargeSubTitle; QList
    ::const_iterator s; QString htmlTitle = qbtn->fullTitle(); if (qbtn->isJsType()) htmlTitle += " JavaScript Basic Type"; else htmlTitle += " QML Basic Type"; marker = CodeMarker::markerForLanguage(QLatin1String("QML")); generateHeader(htmlTitle, qbtn, marker); QList
    sections = marker->sections(qbtn, CodeMarker::Summary, CodeMarker::Okay); generateTableOfContents(qbtn,marker,§ions); generateKeywordAnchors(qbtn); generateTitle(htmlTitle, Text() << qbtn->subTitle(), subTitleSize, qbtn, marker); generateExtractionMark(qbtn, DetailedDescriptionMark); out() << "
    \n"; // QTBUG-9504 generateBody(qbtn, marker); out() << "
    \n"; // QTBUG-9504 generateAlsoList(qbtn, marker); generateExtractionMark(qbtn, EndMark); generateFooter(qbtn); } /*! Generate the HTML page for an entity that doesn't map to any underlying parsable C++ class or QML component. */ void HtmlGenerator::generateDocumentNode(DocumentNode* dn, CodeMarker* marker) { /* If the document node is a page node, and if the page type is DITA map page, write the node's contents as a dita map and return without doing anything else. */ if (dn->subType() == Node::Page && dn->pageType() == Node::DitaMapPage) { const DitaMapNode* dmn = static_cast(dn); writeDitaMap(dmn); return; } SubTitleSize subTitleSize = LargeSubTitle; QList
    sections; QList
    ::const_iterator s; QString fullTitle = dn->fullTitle(); generateHeader(fullTitle, dn, marker); /* Generate the TOC for the new doc format. Don't generate a TOC for the home page. */ if ((dn->name() != QStringLiteral("index.html"))) generateTableOfContents(dn,marker,0); generateKeywordAnchors(dn); generateTitle(fullTitle, Text() << dn->subTitle(), subTitleSize, dn, marker); if (dn->subType() == Node::HeaderFile) { // Generate brief text and status for modules. generateBrief(dn, marker); generateStatus(dn, marker); generateSince(dn, marker); out() << "\n"; } sections = marker->sections(dn, CodeMarker::Summary, CodeMarker::Okay); s = sections.constBegin(); while (s != sections.constEnd()) { QString ref = registerRef((*s).name); out() << "" << divNavTop << '\n'; out() << "

    " << protectEnc((*s).name) << "

    \n"; generateSectionList(*s, dn, marker, CodeMarker::Summary); ++s; } generateExtractionMark(dn, DetailedDescriptionMark); out() << "
    \n"; // QTBUG-9504 generateBody(dn, marker); out() << "
    \n"; // QTBUG-9504 generateAlsoList(dn, marker); generateExtractionMark(dn, EndMark); sections = marker->sections(dn, CodeMarker::Detailed, CodeMarker::Okay); s = sections.constBegin(); while (s != sections.constEnd()) { //out() << "
    \n"; out() << "

    " << protectEnc((*s).name) << "

    \n"; NodeList::ConstIterator m = (*s).members.constBegin(); while (m != (*s).members.constEnd()) { generateDetailedMember(*m, dn, marker); ++m; } ++s; } generateFooter(dn); } /*! Generate the HTML page for a group, module, or QML module. */ void HtmlGenerator::generateCollectionNode(CollectionNode* cn, CodeMarker* marker) { SubTitleSize subTitleSize = LargeSubTitle; QList
    sections; QList
    ::const_iterator s; QString fullTitle = cn->fullTitle(); QString ref; generateHeader(fullTitle, cn, marker); generateTableOfContents(cn,marker,0); generateKeywordAnchors(cn); generateTitle(fullTitle, Text() << cn->subTitle(), subTitleSize, cn, marker); if (cn->isModule()) { // Generate brief text and status for modules. generateBrief(cn, marker); generateStatus(cn, marker); generateSince(cn, marker); NodeMultiMap nmm; cn->getMemberNamespaces(nmm); if (!nmm.isEmpty()) { ref = registerRef("namespaces"); out() << "" << divNavTop << '\n'; out() << "

    Namespaces

    \n"; generateAnnotatedList(cn, marker, nmm); } nmm.clear(); cn->getMemberClasses(nmm); if (!nmm.isEmpty()) { ref = registerRef("classes"); out() << "" << divNavTop << '\n'; out() << "

    Classes

    \n"; generateAnnotatedList(cn, marker, nmm); } nmm.clear(); } sections = marker->sections(cn, CodeMarker::Summary, CodeMarker::Okay); s = sections.constBegin(); while (s != sections.constEnd()) { ref = registerRef((*s).name); out() << "" << divNavTop << '\n'; out() << "

    " << protectEnc((*s).name) << "

    \n"; generateSectionList(*s, cn, marker, CodeMarker::Summary); ++s; } Text brief = cn->doc().briefText(); if (cn->isModule() && !brief.isEmpty()) { generateExtractionMark(cn, DetailedDescriptionMark); ref = registerRef("details"); out() << "" << divNavTop << '\n'; out() << "
    \n"; // QTBUG-9504 out() << "

    " << "Detailed Description" << "

    \n"; } else { generateExtractionMark(cn, DetailedDescriptionMark); out() << "
    \n"; // QTBUG-9504 } generateBody(cn, marker); out() << "
    \n"; // QTBUG-9504 generateAlsoList(cn, marker); generateExtractionMark(cn, EndMark); if (cn->isGroup()) generateAnnotatedList(cn, marker, cn->members()); else if (cn->isQmlModule() || cn->isJsModule()) generateAnnotatedList(cn, marker, cn->members()); sections = marker->sections(cn, CodeMarker::Detailed, CodeMarker::Okay); s = sections.constBegin(); while (s != sections.constEnd()) { //out() << "
    \n"; out() << "

    " << protectEnc((*s).name) << "

    \n"; NodeList::ConstIterator m = (*s).members.constBegin(); while (m != (*s).members.constEnd()) { generateDetailedMember(*m, cn, marker); ++m; } ++s; } generateFooter(cn); } /*! Returns "html" for this subclass of Generator. */ QString HtmlGenerator::fileExtension() const { return "html"; } /*! Output navigation list in the html file. */ void HtmlGenerator::generateNavigationBar(const QString &title, const Node *node, CodeMarker *marker) { if (noNavigationBar) return; Text navigationbar; if (homepage == title) return; if (!homepage.isEmpty()) navigationbar << Atom(Atom::ListItemLeft) << Atom(Atom::NavAutoLink, homepage) << Atom(Atom::ListItemRight); if (!landingpage.isEmpty() && landingpage != title) navigationbar << Atom(Atom::ListItemLeft) << Atom(Atom::NavAutoLink, landingpage) << Atom(Atom::ListItemRight); if (node->isClass()) { const ClassNode *cn = static_cast(node); QString name = node->physicalModuleName(); if (!cppclassespage.isEmpty()) navigationbar << Atom(Atom::ListItemLeft) << Atom(Atom::NavLink, cppclassespage) << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) << Atom(Atom::String, QLatin1String("C++ Classes")) << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK) << Atom(Atom::ListItemRight); if (!cn->name().isEmpty()) navigationbar << Atom(Atom::ListItemLeft) << Atom(Atom::String, cn->name()) << Atom(Atom::ListItemRight); } else if (node->isQmlType() || node->isQmlBasicType() || node->isJsType() || node->isJsBasicType()) { if (!qmltypespage.isEmpty()) navigationbar << Atom(Atom::ListItemLeft) << Atom(Atom::NavLink, qmltypespage) << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) << Atom(Atom::String, QLatin1String("QML Types")) << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK) << Atom(Atom::ListItemRight) << Atom(Atom::ListItemLeft) << Atom(Atom::String, title) << Atom(Atom::ListItemRight); } else { if (node->isExampleFile()) { navigationbar << Atom(Atom::ListItemLeft) << Atom(Atom::NavLink, node->parent()->name()) << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) << Atom(Atom::String, node->parent()->title()) << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK) << Atom(Atom::ListItemRight); } navigationbar << Atom(Atom::ListItemLeft) << Atom(Atom::String, title) << Atom(Atom::ListItemRight); } generateText(navigationbar, node, marker); } void HtmlGenerator::generateHeader(const QString& title, const Node *node, CodeMarker *marker) { #ifndef QT_NO_TEXTCODEC out() << QString("\n").arg(outputEncoding); #else out() << QString("\n"); #endif out() << "\n"; out() << QString("\n").arg(naturalLanguage); out() << "\n"; out() << " \n"; if (node && !node->doc().location().isEmpty()) out() << "\n"; QString shortVersion = qdb_->version(); if (shortVersion.count(QChar('.')) == 2) shortVersion.truncate(shortVersion.lastIndexOf(QChar('.'))); //determine the rest of the element content: "title | titleSuffix version" QString titleSuffix; if (!landingpage.isEmpty()) { //for normal pages: "title | landingpage version" titleSuffix = landingpage; } else if (!homepage.isEmpty()) { //for pages that set the homepage but not landing page: "title | homepage version" if (title != homepage) titleSuffix = homepage; } else if (!project.isEmpty()) { //for projects outside of Qt or Qt 5: "title | project version" if (title != project) titleSuffix = project; } else //default: "title | Qt version" titleSuffix = QLatin1String("Qt "); //for pages that duplicate the title and suffix (landing pages, home pages, // and module landing pages, clear the duplicate if (title == titleSuffix) titleSuffix.clear(); //for pages that duplicate the version, clear the duplicate if (title.contains(shortVersion) || titleSuffix.contains(shortVersion)) shortVersion.clear(); QString divider; if (!titleSuffix.isEmpty() && !title.isEmpty()) divider = QLatin1String(" | "); // Generating page title out() << " <title>" << protectEnc(title) << divider << titleSuffix << QLatin1Char(' ') << shortVersion << "\n"; // Include style sheet and script links. out() << headerStyles; out() << headerScripts; if (endHeader.isEmpty()) out() << "\n\n"; else out() << endHeader; #ifdef GENERATE_MAC_REFS if (mainPage) generateMacRef(node, marker); #endif out() << QString(postHeader).replace("\\" + COMMAND_VERSION, qdb_->version()); generateNavigationBar(title,node,marker); if (!buildversion.isEmpty()) out() << "
  • " << buildversion << "
  • \n"; out() << QString(postPostHeader).replace("\\" + COMMAND_VERSION, qdb_->version()); navigationLinks.clear(); refMap.clear(); if (node && !node->links().empty()) { QPair linkPair; QPair anchorPair; const Node *linkNode; if (node->links().contains(Node::PreviousLink)) { linkPair = node->links()[Node::PreviousLink]; linkNode = qdb_->findNodeForTarget(linkPair.first, node); if (!linkNode) node->doc().location().warning(tr("Cannot link to '%1'").arg(linkPair.first)); if (!linkNode || linkNode == node) anchorPair = linkPair; else anchorPair = anchorForNode(linkNode); out() << " \n"; navigationLinks += ""; if (linkPair.first == linkPair.second && !anchorPair.second.isEmpty()) navigationLinks += protect(anchorPair.second); else navigationLinks += protect(linkPair.second); navigationLinks += "\n"; } if (node->links().contains(Node::NextLink)) { linkPair = node->links()[Node::NextLink]; linkNode = qdb_->findNodeForTarget(linkPair.first, node); if (!linkNode) node->doc().location().warning(tr("Cannot link to '%1'").arg(linkPair.first)); if (!linkNode || linkNode == node) anchorPair = linkPair; else anchorPair = anchorForNode(linkNode); out() << " \n"; navigationLinks += ""; if (linkPair.first == linkPair.second && !anchorPair.second.isEmpty()) navigationLinks += protect(anchorPair.second); else navigationLinks += protect(linkPair.second); navigationLinks += "\n"; } if (node->links().contains(Node::StartLink)) { linkPair = node->links()[Node::StartLink]; linkNode = qdb_->findNodeForTarget(linkPair.first, node); if (!linkNode) node->doc().location().warning(tr("Cannot link to '%1'").arg(linkPair.first)); if (!linkNode || linkNode == node) anchorPair = linkPair; else anchorPair = anchorForNode(linkNode); out() << " \n"; } } if (node && !node->links().empty()) out() << "

    \n" << navigationLinks << "

    \n"; } void HtmlGenerator::generateTitle(const QString& title, const Text &subTitle, SubTitleSize subTitleSize, const Node *relative, CodeMarker *marker) { out() << QString(prologue).replace("\\" + COMMAND_VERSION, qdb_->version()); if (!title.isEmpty()) out() << "

    " << protectEnc(title) << "

    \n"; if (!subTitle.isEmpty()) { out() << ""; else out() << " class=\"subtitle\">"; generateText(subTitle, relative, marker); out() << "\n"; } } void HtmlGenerator::generateFooter(const Node *node) { if (node && !node->links().empty()) out() << "

    \n" << navigationLinks << "

    \n"; out() << QString(footer).replace("\\" + COMMAND_VERSION, qdb_->version()) << QString(address).replace("\\" + COMMAND_VERSION, qdb_->version()); out() << "\n"; out() << "\n"; } /*! Lists the required imports and includes in a table. The number of rows is known, so this path is simpler than the generateSection() path. */ void HtmlGenerator::generateRequisites(InnerNode *inner, CodeMarker *marker) { QMap requisites; Text text; const QString headerText = "Header"; const QString sinceText = "Since"; const QString inheritedBytext = "Inherited By"; const QString inheritsText = "Inherits"; const QString instantiatedByText = "Instantiated By"; const QString qtVariableText = "qmake"; //add the includes to the map if (!inner->includes().isEmpty()) { text.clear(); text << highlightedCode(indent(codeIndent, marker->markedUpIncludes(inner->includes())), inner); requisites.insert(headerText, text); } //The order of the requisites matter QStringList requisiteorder; requisiteorder << headerText << qtVariableText << sinceText << instantiatedByText << inheritsText << inheritedBytext; //add the since and project into the map if (!inner->since().isEmpty()) { text.clear(); QStringList since = inner->since().split(QLatin1Char(' ')); if (since.count() == 1) { // If there is only one argument, assume it is the Qt version number. text << " Qt " << since[0]; } else { //Otherwise, reconstruct the string. text << " " << since.join(' '); } text << Atom::ParaRight; requisites.insert(sinceText, text); } if (inner->type() == Node::Class || inner->type() == Node::Namespace) { //add the QT variable to the map if (!inner->physicalModuleName().isEmpty()) { CollectionNode* cn = qdb_->findModule(inner->physicalModuleName()); if (cn && !cn->qtVariable().isEmpty()) { text.clear(); text << "QT += " + cn->qtVariable(); requisites.insert(qtVariableText, text); } } } if (inner->type() == Node::Class) { ClassNode* classe = static_cast(inner); if (classe->qmlElement() != 0 && classe->status() != Node::Internal) { text.clear(); text << Atom(Atom::LinkNode, CodeMarker::stringForNode(classe->qmlElement())) << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) << Atom(Atom::String, classe->qmlElement()->name()) << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK); requisites.insert(instantiatedByText, text); } //add the inherits to the map QList::ConstIterator r; int index; if (!classe->baseClasses().isEmpty()) { text.clear(); r = classe->baseClasses().constBegin(); index = 0; while (r != classe->baseClasses().constEnd()) { if ((*r).node_) { text << Atom(Atom::LinkNode, CodeMarker::stringForNode((*r).node_)) << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) << Atom(Atom::String, (*r).signature_) << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK); if ((*r).access_ == Node::Protected) { text << " (protected)"; } else if ((*r).access_ == Node::Private) { text << " (private)"; } text << comma(index++, classe->baseClasses().count()); } ++r; } text << Atom::ParaRight; requisites.insert(inheritsText, text); } //add the inherited-by to the map if (!classe->derivedClasses().isEmpty()) { text.clear(); text << Atom::ParaLeft; appendSortedNames(text, classe, classe->derivedClasses()); text << Atom::ParaRight; requisites.insert(inheritedBytext, text); } } if (!requisites.isEmpty()) { //generate the table out() << "
    \n"; QStringList::ConstIterator i; for (i = requisiteorder.constBegin(); i != requisiteorder.constEnd(); ++i) { if (requisites.contains(*i)) { out() << "" << ""; } } out() << "
    " << *i << ":" << " "; if (*i == headerText) out() << requisites.value(*i).toString(); else generateText(requisites.value(*i), inner, marker); out() << "
    "; } } /*! Lists the required imports and includes in a table. The number of rows is known, so this path is simpler than the generateSection() path. */ void HtmlGenerator::generateQmlRequisites(QmlTypeNode *qcn, CodeMarker *marker) { if (!qcn) return; QMap requisites; Text text; const QString importText = "Import Statement:"; const QString sinceText = "Since:"; const QString inheritedBytext = "Inherited By:"; const QString inheritsText = "Inherits:"; const QString instantiatesText = "Instantiates:"; //The order of the requisites matter QStringList requisiteorder; requisiteorder << importText << sinceText << instantiatesText << inheritsText << inheritedBytext; //add the module name and version to the map QString logicalModuleVersion; CollectionNode* collection = 0; if (qcn->isJsNode()) collection = qdb_->findJsModule(qcn->logicalModuleName()); else collection = qdb_->findQmlModule(qcn->logicalModuleName()); if (collection) logicalModuleVersion = collection->logicalModuleVersion(); else logicalModuleVersion = qcn->logicalModuleVersion(); text.clear(); text << "import " + qcn->logicalModuleName() + " " + logicalModuleVersion; requisites.insert(importText, text); //add the since and project into the map if (!qcn->since().isEmpty()) { text.clear(); QStringList since = qcn->since().split(QLatin1Char(' ')); if (since.count() == 1) { // If there is only one argument, assume it is the Qt version number. text << " Qt " << since[0]; } else { //Otherwise, reconstruct the string. text << " " << since.join(' '); } text << Atom::ParaRight; requisites.insert(sinceText, text); } //add the instantiates to the map ClassNode* cn = qcn->classNode(); if (cn && (cn->status() != Node::Internal)) { text.clear(); text << Atom(Atom::LinkNode,CodeMarker::stringForNode(qcn)); text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK); text << Atom(Atom::LinkNode,CodeMarker::stringForNode(cn)); text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK); text << Atom(Atom::String, cn->name()); text << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK); requisites.insert(instantiatesText, text); } //add the inherits to the map QmlTypeNode* base = qcn->qmlBaseNode(); while (base && base->isInternal()) { base = base->qmlBaseNode(); } if (base) { text.clear(); text << Atom::ParaLeft << Atom(Atom::LinkNode,CodeMarker::stringForNode(base)) << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) << Atom(Atom::String, base->name()) << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK) << Atom::ParaRight; requisites.insert(inheritsText, text); } //add the inherited-by to the map NodeList subs; QmlTypeNode::subclasses(qcn->name(), subs); if (!subs.isEmpty()) { text.clear(); text << Atom::ParaLeft; appendSortedQmlNames(text, qcn, subs); text << Atom::ParaRight; requisites.insert(inheritedBytext, text); } if (!requisites.isEmpty()) { //generate the table out() << "
    \n"; QStringList::ConstIterator i; for (i = requisiteorder.constBegin(); i != requisiteorder.constEnd(); ++i) { if (requisites.contains(*i)) { out() << "" << ""; } } out() << "
    " << *i << " "; if (*i == importText) out()<
    "; } } void HtmlGenerator::generateBrief(const Node *node, CodeMarker *marker, const Node *relative) { Text brief = node->doc().briefText(); if (!brief.isEmpty()) { generateExtractionMark(node, BriefMark); out() << "

    "; generateText(brief, node, marker); if (!relative || node == relative) out() << " More...

    \n"; generateExtractionMark(node, EndMark); } } void HtmlGenerator::generateIncludes(const InnerNode *inner, CodeMarker *marker) { if (!inner->includes().isEmpty()) { out() << "
    "
                  << trimmedTrailing(highlightedCode(indent(codeIndent,
                                                            marker->markedUpIncludes(inner->includes())),
                                                     inner))
                  << "
    "; } } /*! Revised for the new doc format. Generates a table of contents beginning at \a node. */ void HtmlGenerator::generateTableOfContents(const Node *node, CodeMarker *marker, QList
    * sections) { QList toc; if (node->doc().hasTableOfContents()) toc = node->doc().tableOfContents(); if (tocDepth == 0 || (toc.isEmpty() && !sections && !node->isModule())) { generateSidebar(); return; } QStringList sectionNumber; int detailsBase = 0; // disable nested links in table of contents inContents_ = true; inLink_ = true; out() << "
    \n"; out() << "
    \n"; out() << "

    Contents

    \n"; sectionNumber.append("1"); out() << "
      \n"; if (node->isModule()) { if (node->hasNamespaces()) { out() << "
    • Namespaces
    • \n"; } if (node->hasClasses()) { out() << "
    • Classes
    • \n"; } out() << "
    • Detailed Description
    • \n"; for (int i = 0; i < toc.size(); ++i) { if (toc.at(i)->string().toInt() == 1) { detailsBase = 1; break; } } } else if (sections && (node->isClass() || node->isNamespace() || node->isQmlType() || node->isJsType())) { QList
      ::ConstIterator s = sections->constBegin(); while (s != sections->constEnd()) { if (!s->members.isEmpty()) { out() << "
    • " << (*s).name << "
    • \n"; } if (!s->reimpMembers.isEmpty()) { QString ref = QString("Reimplemented ") + (*s).pluralMember; out() << "
    • " << QString("Reimplemented ") + (*s).name << "
    • \n"; } ++s; } out() << "
    • Detailed Description
    • \n"; for (int i = 0; i < toc.size(); ++i) { if (toc.at(i)->string().toInt() == 1) { detailsBase = 1; break; } } } for (int i = 0; i < toc.size(); ++i) { Atom *atom = toc.at(i); int nextLevel = atom->string().toInt() + detailsBase; if (nextLevel >= 0) { if (sectionNumber.size() < nextLevel) { do { sectionNumber.append("1"); } while (sectionNumber.size() < nextLevel); } else { while (sectionNumber.size() > nextLevel) { sectionNumber.removeLast(); } sectionNumber.last() = QString::number(sectionNumber.last().toInt() + 1); } } //restrict the ToC depth to the one set by the HTML.tocdepth variable or //print all levels if tocDepth is not set. if (sectionNumber.size() <= tocDepth || tocDepth < 0) { int numAtoms; Text headingText = Text::sectionHeading(atom); QString s = headingText.toString(); out() << "
    • "; out() << ""; generateAtomList(headingText.firstAtom(), node, marker, true, numAtoms); out() << "
    • \n"; } } while (!sectionNumber.isEmpty()) { sectionNumber.removeLast(); } out() << "
    \n"; out() << "
    \n"; out() << "
    "; out() << "
    \n"; inContents_ = false; inLink_ = false; } /*! Outputs a placeholder div where the style can add customized sidebar content. */ void HtmlGenerator::generateSidebar() { out() << "
    "; out() << "
    "; out() << "
    \n"; } QString HtmlGenerator::generateListOfAllMemberFile(const InnerNode *inner, CodeMarker *marker) { QList
    sections; QList
    ::ConstIterator s; sections = marker->sections(inner, CodeMarker::Subpage, CodeMarker::Okay); if (sections.isEmpty()) return QString(); QString fileName = fileBase(inner) + "-members." + fileExtension(); beginSubPage(inner, fileName); QString title = "List of All Members for " + inner->name(); generateHeader(title, inner, marker); generateSidebar(); generateTitle(title, Text(), SmallSubTitle, inner, marker); out() << "

    This is the complete list of members for "; generateFullName(inner, 0); out() << ", including inherited members.

    \n"; Section section = sections.first(); generateSectionList(section, inner, marker, CodeMarker::Subpage); generateFooter(); endSubPage(); return fileName; } /*! This function creates an html page on which are listed all the members of QML class \a qml_cn, including the inherited members. The \a marker is used for formatting stuff. */ QString HtmlGenerator::generateAllQmlMembersFile(QmlTypeNode* qml_cn, CodeMarker* marker) { QList
    sections; QList
    ::ConstIterator s; sections = marker->qmlSections(qml_cn,CodeMarker::Subpage); if (sections.isEmpty()) return QString(); QString fileName = fileBase(qml_cn) + "-members." + fileExtension(); beginSubPage(qml_cn, fileName); QString title = "List of All Members for " + qml_cn->name(); generateHeader(title, qml_cn, marker); generateSidebar(); generateTitle(title, Text(), SmallSubTitle, qml_cn, marker); out() << "

    This is the complete list of members for "; generateFullName(qml_cn, 0); out() << ", including inherited members.

    \n"; ClassKeysNodesList& cknl = sections.first().classKeysNodesList_; if (!cknl.isEmpty()) { for (int i=0; ifirst; KeysAndNodes& kn = ckn->second; QStringList& keys = kn.first; NodeList& nodes = kn.second; if (nodes.isEmpty()) continue; if (i != 0) { out() << "

    The following members are inherited from "; generateFullName(qcn,0); out() << ".

    \n"; } out() << "
      \n"; for (int j=0; jaccess() == Node::Private || nodes[j]->status() == Node::Internal) { continue; } out() << "
    • "; QString prefix; if (!keys.isEmpty()) { prefix = keys.at(j).mid(1); prefix = prefix.left(keys.at(j).indexOf("::")+1); } generateQmlItem(nodes[j], qml_cn, marker, true); if (nodes[j]->isAttached()) out() << " [attached]"; //generateSynopsis(nodes[j], qcn, marker, CodeMarker::Subpage, false, &prefix); out() << "
    • \n"; } out() << "
    \n"; } } generateFooter(); endSubPage(); return fileName; } QString HtmlGenerator::generateLowStatusMemberFile(InnerNode *inner, CodeMarker *marker, CodeMarker::Status status) { QList
    sections = marker->sections(inner, CodeMarker::Summary, status); QMutableListIterator
    j(sections); while (j.hasNext()) { if (j.next().members.size() == 0) j.remove(); } if (sections.isEmpty()) return QString(); int i; QString title; QString fileName; if (status == CodeMarker::Compat) { title = "Compatibility Members for " + inner->name(); fileName = fileBase(inner) + "-compat." + fileExtension(); } else { title = "Obsolete Members for " + inner->name(); fileName = fileBase(inner) + "-obsolete." + fileExtension(); } if (status == CodeMarker::Obsolete) { QString link; if (useOutputSubdirs() && !Generator::outputSubdir().isEmpty()) link = QString("../" + Generator::outputSubdir() + QLatin1Char('/')); link += fileName; inner->setObsoleteLink(link); } beginSubPage(inner, fileName); generateHeader(title, inner, marker); generateSidebar(); generateTitle(title, Text(), SmallSubTitle, inner, marker); if (status == CodeMarker::Compat) { out() << "

    The following members of class " << "" << protectEnc(inner->name()) << "" << " are part of the " "Qt compatibility layer. We advise against " "using them in new code.

    \n"; } else { out() << "

    The following members of class " << "" << protectEnc(inner->name()) << "" << " are obsolete. " << "They are provided to keep old source code working. " << "We strongly advise against using them in new code.

    \n"; } for (i = 0; i < sections.size(); ++i) { out() << "

    " << protectEnc(sections.at(i).name) << "

    \n"; generateSectionList(sections.at(i), inner, marker, CodeMarker::Summary); } sections = marker->sections(inner, CodeMarker::Detailed, status); for (i = 0; i < sections.size(); ++i) { //out() << "
    \n"; out() << "

    " << protectEnc(sections.at(i).name) << "

    \n"; NodeList::ConstIterator m = sections.at(i).members.constBegin(); while (m != sections.at(i).members.constEnd()) { if ((*m)->access() != Node::Private) generateDetailedMember(*m, inner, marker); ++m; } } generateFooter(); endSubPage(); return fileName; } /*! Generates a separate file where certain members of the QML type \a qcn are listed. The \a marker is used to generate the section lists, which are then traversed and output here. Note that this function currently only handles correctly the case where \a status is \c {CodeMarker::Obsolete}. */ QString HtmlGenerator::generateQmlMemberFile(QmlTypeNode* qcn, CodeMarker *marker, CodeMarker::Status status) { QList
    sections = marker->qmlSections(qcn, CodeMarker::Summary, status); QMutableListIterator
    j(sections); while (j.hasNext()) { if (j.next().members.size() == 0) j.remove(); } if (sections.isEmpty()) return QString(); QString title = "Obsolete Members for " + qcn->name(); QString fileName = fileBase(qcn) + "-obsolete." + fileExtension(); if (status == CodeMarker::Obsolete) { QString link; if (useOutputSubdirs() && !Generator::outputSubdir().isEmpty()) link = QString("../" + Generator::outputSubdir() + QLatin1Char('/')); link += fileName; qcn->setObsoleteLink(link); } beginSubPage(qcn, fileName); generateHeader(title, qcn, marker); generateSidebar(); generateTitle(title, Text(), SmallSubTitle, qcn, marker); out() << "

    The following members of QML type " << "" << protectEnc(qcn->name()) << "" << " are obsolete. " << "They are provided to keep old source code working. " << "We strongly advise against using them in new code.

    \n"; QList
    ::const_iterator s = sections.constBegin(); while (s != sections.constEnd()) { QString ref = registerRef((*s).name.toLower()); out() << "" << divNavTop << '\n'; out() << "

    " << protectEnc((*s).name) << "

    \n"; generateQmlSummary(*s, qcn, marker); ++s; } sections = marker->qmlSections(qcn, CodeMarker::Detailed, status); QMutableListIterator
    k(sections); while (k.hasNext()) { if (k.next().members.size() == 0) k.remove(); } if (sections.isEmpty()) return QString(); s = sections.constBegin(); while (s != sections.constEnd()) { out() << "

    " << protectEnc((*s).name) << "

    \n"; NodeList::ConstIterator m = (*s).members.constBegin(); while (m != (*s).members.constEnd()) { generateDetailedQmlMember(*m, qcn, marker); out() << "
    \n"; ++m; } ++s; } generateFooter(); endSubPage(); return fileName; } void HtmlGenerator::generateClassHierarchy(const Node *relative, NodeMap& classMap) { if (classMap.isEmpty()) return; NodeMap topLevel; NodeMap::Iterator c = classMap.begin(); while (c != classMap.end()) { ClassNode *classe = static_cast(*c); if (classe->baseClasses().isEmpty()) topLevel.insert(classe->name(), classe); ++c; } QStack stack; stack.push(topLevel); out() << "
      \n"; while (!stack.isEmpty()) { if (stack.top().isEmpty()) { stack.pop(); out() << "
    \n"; } else { ClassNode* child = static_cast(*stack.top().begin()); out() << "
  • "; generateFullName(child, relative); out() << "
  • \n"; stack.top().erase(stack.top().begin()); NodeMap newTop; foreach (const RelatedClass &d, child->derivedClasses()) { if (d.node_ && !d.isPrivate() && !d.node_->isInternal() && d.node_->hasDoc()) newTop.insert(d.node_->name(), d.node_); } if (!newTop.isEmpty()) { stack.push(newTop); out() << "
      \n"; } } } } /*! Output an annotated list of the nodes in \a nodeMap. A two-column table is output. */ void HtmlGenerator::generateAnnotatedList(const Node* relative, CodeMarker* marker, const NodeMultiMap& nmm) { if (nmm.isEmpty()) return; generateAnnotatedList(relative, marker, nmm.values()); } /*! */ void HtmlGenerator::generateAnnotatedList(const Node *relative, CodeMarker *marker, const NodeList& unsortedNodes) { NodeMultiMap nmm; bool allInternal = true; foreach (Node* node, unsortedNodes) { if (!node->isInternal() && !node->isObsolete()) { allInternal = false; nmm.insert(node->fullName(relative), node); } } if (allInternal) return; out() << "
      \n"; int row = 0; NodeList nodes = nmm.values(); foreach (const Node* node, nodes) { if (++row % 2 == 1) out() << ""; else out() << ""; out() << ""; if (!node->isDocumentNode()) { Text brief = node->doc().trimmedBriefText(node->name()); if (!brief.isEmpty()) { out() << ""; } else if (!node->reconstitutedBrief().isEmpty()) { out() << ""; } } else { out() << ""; } out() << "\n"; } out() << "

      "; generateFullName(node, relative); out() << "

      "; generateText(brief, node, marker); out() << "

      "; out() << node->reconstitutedBrief(); out() << "

      "; if (!node->reconstitutedBrief().isEmpty()) { out() << node->reconstitutedBrief(); } else out() << protectEnc(node->doc().briefText().toString()); out() << "

      \n"; } /*! This function finds the common prefix of the names of all the classes in the class map \a nmm and then generates a compact list of the class names alphabetized on the part of the name not including the common prefix. You can tell the function to use \a comonPrefix as the common prefix, but normally you let it figure it out itself by looking at the name of the first and last classes in the class map \a nmm. */ void HtmlGenerator::generateCompactList(ListType listType, const Node *relative, const NodeMultiMap &nmm, bool includeAlphabet, QString commonPrefix) { if (nmm.isEmpty()) return; const int NumParagraphs = 37; // '0' to '9', 'A' to 'Z', '_' int commonPrefixLen = commonPrefix.length(); /* Divide the data into 37 paragraphs: 0, ..., 9, A, ..., Z, underscore (_). QAccel will fall in paragraph 10 (A) and QXtWidget in paragraph 33 (X). This is the only place where we assume that NumParagraphs is 37. Each paragraph is a NodeMultiMap. */ NodeMultiMap paragraph[NumParagraphs+1]; QString paragraphName[NumParagraphs+1]; QSet usedParagraphNames; NodeMultiMap::ConstIterator c = nmm.constBegin(); while (c != nmm.constEnd()) { QStringList pieces = c.key().split("::"); QString key; int idx = commonPrefixLen; if (idx > 0 && !pieces.last().startsWith(commonPrefix)) idx = 0; if (pieces.size() == 1) key = pieces.last().mid(idx).toLower(); else key = pieces.last().toLower(); int paragraphNr = NumParagraphs - 1; if (key[0].digitValue() != -1) { paragraphNr = key[0].digitValue(); } else if (key[0] >= QLatin1Char('a') && key[0] <= QLatin1Char('z')) { paragraphNr = 10 + key[0].unicode() - 'a'; } paragraphName[paragraphNr] = key[0].toUpper(); usedParagraphNames.insert(key[0].toLower().cell()); paragraph[paragraphNr].insert(c.key(), c.value()); ++c; } /* Each paragraph j has a size: paragraph[j].count(). In the discussion, we will assume paragraphs 0 to 5 will have sizes 3, 1, 4, 1, 5, 9. We now want to compute the paragraph offset. Paragraphs 0 to 6 start at offsets 0, 3, 4, 8, 9, 14, 23. */ int paragraphOffset[NumParagraphs + 1]; // 37 + 1 paragraphOffset[0] = 0; for (int i=0; i"; for (int i = 0; i < 26; i++) { QChar ch('a' + i); if (usedParagraphNames.contains(char('a' + i))) out() << QString("%2 ").arg(ch).arg(ch.toUpper()); } out() << "

      \n"; } /* Output a
      element to contain all the
      elements. */ out() << "
      \n"; numTableRows_ = 0; int curParNr = 0; int curParOffset = 0; QString previousName; bool multipleOccurrences = false; for (int i=0; i. */ if (curParOffset == 0) { if (i > 0) out() << "
      \n"; if (++numTableRows_ % 2 == 1) out() << "
      "; else out() << "
      "; out() << "
      "; if (includeAlphabet) { QChar c = paragraphName[curParNr][0].toLower(); out() << QString("").arg(c); } out() << "" << paragraphName[curParNr] << ""; out() << "
      \n"; } /* Output a
      for the current offset in the current paragraph. */ out() << "
      "; if ((curParNr < NumParagraphs) && !paragraphName[curParNr].isEmpty()) { NodeMultiMap::Iterator it; NodeMultiMap::Iterator next; it = paragraph[curParNr].begin(); for (int i=0; i"; } else if (listType == Obsolete) { QString fileName = fileBase(it.value()) + "-obsolete." + fileExtension(); QString link; if (useOutputSubdirs()) { link = QString("../" + it.value()->outputSubdirectory() + QLatin1Char('/')); } link += fileName; out() << ""; } QStringList pieces; if (it.value()->isQmlType() || it.value()->isJsType()) { QString name = it.value()->name(); next = it; ++next; if (name != previousName) multipleOccurrences = false; if ((next != paragraph[curParNr].end()) && (name == next.value()->name())) { multipleOccurrences = true; previousName = name; } if (multipleOccurrences) name += ": " + it.value()->tree()->camelCaseModuleName(); pieces << name; } else pieces = it.value()->fullName(relative).split("::"); out() << protectEnc(pieces.last()); out() << ""; if (pieces.size() > 1) { out() << " ("; generateFullName(it.value()->parent(), relative); out() << ')'; } } out() << "
      \n"; curParOffset++; } if (nmm.count() > 0) out() << "
      \n"; out() << "
      \n"; } void HtmlGenerator::generateFunctionIndex(const Node *relative) { out() << "

      "; for (int i = 0; i < 26; i++) { QChar ch('a' + i); out() << QString("%2 ").arg(ch).arg(ch.toUpper()); } out() << "

      \n"; char nextLetter = 'a'; char currentLetter; out() << "
        \n"; NodeMapMap& funcIndex = qdb_->getFunctionIndex(); QMap::ConstIterator f = funcIndex.constBegin(); while (f != funcIndex.constEnd()) { out() << "
      • "; out() << protectEnc(f.key()) << ':'; currentLetter = f.key()[0].unicode(); while (islower(currentLetter) && currentLetter >= nextLetter) { out() << QString("").arg(nextLetter); nextLetter++; } NodeMap::ConstIterator s = (*f).constBegin(); while (s != (*f).constEnd()) { out() << ' '; generateFullName((*s)->parent(), relative, *s); ++s; } out() << "
      • "; out() << '\n'; ++f; } out() << "
      \n"; } void HtmlGenerator::generateLegaleseList(const Node *relative, CodeMarker *marker) { TextToNodeMap& legaleseTexts = qdb_->getLegaleseTexts(); QMap::ConstIterator it = legaleseTexts.constBegin(); while (it != legaleseTexts.constEnd()) { Text text = it.key(); //out() << "
      \n"; generateText(text, relative, marker); out() << "
        \n"; do { out() << "
      • "; generateFullName(it.value(), relative); out() << "
      • \n"; ++it; } while (it != legaleseTexts.constEnd() && it.key() == text); out() << "
      \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])"), "\\1\\2"); marked.replace("<@param>", " "); marked.replace("", ""); if (summary) marked.replace("@name>", "b>"); marked.replace("<@extra>", ""); marked.replace("", ""); if (summary) { marked.remove("<@type>"); marked.remove(""); } out() << highlightedCode(marked, relative, false); } void HtmlGenerator::generateList(const Node* relative, CodeMarker* marker, const QString& selector) { CNMap cnm; Node::Genus genus = Node::DontCare; if (selector == "overviews") genus = Node::DOC; else if (selector == "cpp-modules") genus = Node::CPP; else if (selector == "qml-modules") genus = Node::QML; else if (selector == "js-modules") genus = Node::JS; if (genus != Node::DontCare) { NodeList nl; qdb_->mergeCollections(genus, cnm, relative); CollectionList cl = cnm.values(); foreach (CollectionNode* cn, cl) nl.append(cn); generateAnnotatedList(relative, marker, nl); } else { /* \generatelist {selector} is only allowed in a comment where the topic is \group, \module, \qmlmodule, or \jsmodule */ if (!relative || !relative->isCollectionNode()) { relative->doc().location().warning(tr("\\generatelist {%1} is only allowed in \\group, " "\\module, \\qmlmodule, and \\jsmodule comments.").arg(selector)); return; } Node* n = const_cast(relative); CollectionNode* cn = static_cast(n); qdb_->mergeCollections(cn); generateAnnotatedList(cn, marker, cn->members()); } } void HtmlGenerator::generateSection(const NodeList& nl, const Node *relative, CodeMarker *marker, CodeMarker::SynopsisStyle style) { bool alignNames = true; if (!nl.isEmpty()) { bool twoColumn = false; if (style == CodeMarker::Subpage) { alignNames = false; twoColumn = (nl.count() >= 16); } else if (nl.first()->type() == Node::Property) { twoColumn = (nl.count() >= 5); alignNames = false; } if (alignNames) { out() << "
      \n"; } else { if (twoColumn) out() << "
      \n" << "\n"; else out() << "\n"; i++; ++m; } if (alignNames) out() << "
      "; out() << "
        \n"; } int i = 0; NodeList::ConstIterator m = nl.constBegin(); while (m != nl.constEnd()) { if ((*m)->access() == Node::Private) { ++m; continue; } if (alignNames) { out() << "
      "; } else { if (twoColumn && i == (int) (nl.count() + 1) / 2) out() << "
        \n"; out() << "
      • "; } generateSynopsis(*m, relative, marker, style, alignNames); if (alignNames) out() << "
      \n"; else { out() << "
    \n"; if (twoColumn) out() << "\n
    \n"; } } } void HtmlGenerator::generateSectionList(const Section& section, const Node *relative, CodeMarker *marker, CodeMarker::SynopsisStyle style) { bool alignNames = true; if (!section.members.isEmpty()) { bool hasPrivateSignals = false; bool twoColumn = false; if (style == CodeMarker::Subpage) { alignNames = false; twoColumn = (section.members.count() >= 16); } else if (section.members.first()->type() == Node::Property) { twoColumn = (section.members.count() >= 5); alignNames = false; } if (alignNames) { out() << "
    \n"; } else { if (twoColumn) out() << "
    \n" << "\n"; else out() << "\n"; i++; ++m; } if (alignNames) out() << "
    "; out() << "
      \n"; } int i = 0; NodeList::ConstIterator m = section.members.constBegin(); while (m != section.members.constEnd()) { if ((*m)->access() == Node::Private) { ++m; continue; } if (alignNames) { out() << "
    "; } else { if (twoColumn && i == (int) (section.members.count() + 1) / 2) out() << "
      \n"; out() << "
    • "; } QString prefix; if (!section.keys.isEmpty()) { prefix = section.keys.at(i).mid(1); prefix = prefix.left(section.keys.at(i).indexOf("::")+1); } generateSynopsis(*m, relative, marker, style, alignNames, &prefix); if ((*m)->isFunction()) { const FunctionNode* fn = static_cast(*m); if (fn->isPrivateSignal()) { hasPrivateSignals = true; if (alignNames) out() << "
    [see note below]"; } } if (alignNames) out() << "
    \n"; else { out() << "
\n"; if (twoColumn) out() << "\n\n"; } if (hasPrivateSignals && alignNames) { generatePrivateSignalNote(relative, marker); } } if (style == CodeMarker::Summary && !section.inherited.isEmpty()) { out() << "
    \n"; generateSectionInheritedList(section, relative); out() << "
\n"; } } void HtmlGenerator::generateSectionInheritedList(const Section& section, const Node *relative) { QList >::ConstIterator p = section.inherited.constBegin(); while (p != section.inherited.constEnd()) { out() << "
  • "; out() << (*p).second << ' '; if ((*p).second == 1) { out() << section.singularMember; } else { out() << section.pluralMember; } out() << " inherited from " << protectEnc((*p).first->plainFullName(relative)) << "
  • \n"; ++p; } } // generateSynopsis(qmn,relative,marker,CodeMarker::Detailed,false); void HtmlGenerator::generateSynopsis(const Node *node, const Node *relative, CodeMarker *marker, CodeMarker::SynopsisStyle style, bool alignNames, const QString* prefix) { QString marked = marker->markedUpSynopsis(node, relative, style); if (prefix) marked.prepend(*prefix); QRegExp templateTag("(<[^@>]*>)"); if (marked.indexOf(templateTag) != -1) { QString contents = protectEnc(marked.mid(templateTag.pos(1), templateTag.cap(1).length())); marked.replace(templateTag.pos(1), templateTag.cap(1).length(), contents); } marked.replace(QRegExp("<@param>([a-z]+)_([1-9n])"), "\\1\\2"); marked.replace("<@param>", " "); marked.replace("", ""); if (style == CodeMarker::Summary) { marked.remove("<@name>"); // was "" marked.remove(""); // was "" } if (style == CodeMarker::Subpage) { QRegExp extraRegExp("<@extra>.*"); extraRegExp.setMinimal(true); marked.remove(extraRegExp); } else { marked.replace("<@extra>", ""); marked.replace("", ""); } if (style != CodeMarker::Detailed) { marked.remove("<@type>"); marked.remove(""); } out() << highlightedCode(marked, relative, alignNames); } QString HtmlGenerator::highlightedCode(const QString& markedCode, const Node* relative, bool alignNames) { 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=\"([^\"]+)\">).*()" 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 += ""; done = true; } i += 2; if (parseArg(src, linkTag, &i, srcSize, &arg, &par1)) { html += ""; const Node* n = CodeMarker::nodeForString(par1.toString()); QString link = linkForNode(n, relative); addLink(link, arg, &html); html += ""; } else { html += charLangle; html += charAt; } } else { html += src.at(i++); } } // replace all <@func> tags: "(<@func target=\"([^\"]*)\">)(.*)()" src = html; html = QString(); for (int i = 0, srcSize = src.size(); i < srcSize;) { if (src.at(i) == charLangle && src.at(i + 1) == charAt) { i += 2; if (parseArg(src, funcTag, &i, srcSize, &arg, &par1)) { const Node* n = qdb_->findFunctionNode(par1.toString(), relative, Node::DontCare); 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)(?: +[^>]*)?>)(.*)()" tags src = html; html = QString(); for (int i=0, srcSize=src.size(); ifindTypeNode(arg.toString(), relative); html += QLatin1String(""); if (n && (n->isQmlBasicType() || n->isJsBasicType())) { if (relative && (relative->isQmlType() || relative->isJsType())) addLink(linkForNode(n,relative), arg, &html); else html += arg; } else addLink(linkForNode(n,relative), arg, &html); html += QLatin1String(""); handled = true; } else if (parseArg(src, headerTag, &i, srcSize, &arg, &par1)) { par1 = QStringRef(); if (arg.at(0) == QChar('&')) html += arg; else { const Node* n = qdb_->findNodeForInclude(QStringList(arg.toString())); if (n && n != relative) addLink(linkForNode(n,relative), arg, &html); else html += arg; } handled = true; } if (!handled) { html += charLangle; html += charAt; } } else { html += src.at(i++); } } // replace all // "<@comment>" -> ""; // "<@preprocessor>" -> ""; // "<@string>" -> ""; // "<@char>" -> ""; // "<@number>" -> ""; // "<@op>" -> ""; // "<@type>" -> ""; // "<@name>" -> ""; // "<@keyword>" -> ""; // "" -> "" src = html; html = QString(); static const QString spanTags[] = { "<@comment>", "", "<@preprocessor>", "", "<@string>", "", "<@char>", "", "<@number>", "", "<@op>", "", "<@type>", "", "<@name>", "", "<@keyword>", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", }; // Update the upper bound of k in the following code to match the length // of the above array. for (int i = 0, n = src.size(); i < n;) { if (src.at(i) == charLangle) { bool handled = false; for (int k = 0; k != 18; ++k) { const QString & tag = spanTags[2 * k]; if (tag == QStringRef(&src, i, tag.length())) { html += spanTags[2 * k + 1]; i += tag.length(); handled = true; break; } } if (!handled) { ++i; if (src.at(i) == charAt || (src.at(i) == QLatin1Char('/') && src.at(i + 1) == charAt)) { // drop 'our' unknown tags (the ones still containing '@') while (i < n && src.at(i) != QLatin1Char('>')) ++i; ++i; } else { // retain all others html += charLangle; } } } else { html += src.at(i); ++i; } } return html; } void HtmlGenerator::generateLink(const Atom* atom, CodeMarker* marker) { static QRegExp camelCase("[A-Z][A-Z][a-z]|[a-z][A-Z0-9]|_"); if (funcLeftParen.indexIn(atom->string()) != -1 && marker->recognizeLanguage("Cpp")) { // hack for C++: move () outside of link int k = funcLeftParen.pos(1); out() << protectEnc(atom->string().left(k)); if (link_.isEmpty()) { if (showBrokenLinks) out() << ""; } else { out() << ""; } inLink_ = false; out() << protectEnc(atom->string().mid(k)); } else { out() << protectEnc(atom->string()); } } QString HtmlGenerator::registerRef(const QString& ref) { QString clean = Generator::cleanRef(ref); for (;;) { QString& prevRef = refMap[clean.toLower()]; if (prevRef.isEmpty()) { prevRef = ref; break; } else if (prevRef == ref) { break; } clean += QLatin1Char('x'); } return clean; } QString HtmlGenerator::protectEnc(const QString &string) { #ifndef QT_NO_TEXTCODEC return protect(string, outputEncoding); #else return protect(string); #endif } QString HtmlGenerator::protect(const QString &string, const QString &outputEncoding) { #define APPEND(x) \ if (html.isEmpty()) { \ html = string; \ html.truncate(i); \ } \ html += (x); QString html; int n = string.length(); for (int i = 0; i < n; ++i) { QChar ch = string.at(i); if (ch == QLatin1Char('&')) { APPEND("&"); } else if (ch == QLatin1Char('<')) { APPEND("<"); } else if (ch == QLatin1Char('>')) { APPEND(">"); } else if (ch == QLatin1Char('"')) { APPEND("""); } else if ((outputEncoding == "ISO-8859-1" && ch.unicode() > 0x007F) || (ch == QLatin1Char('*') && i + 1 < n && string.at(i) == QLatin1Char('/')) || (ch == QLatin1Char('.') && i > 2 && string.at(i - 2) == QLatin1Char('.'))) { // we escape '*/' and the last dot in 'e.g.' and 'i.e.' for the Javadoc generator APPEND("&#x"); html += QString::number(ch.unicode(), 16); html += QLatin1Char(';'); } else { if (!html.isEmpty()) html += ch; } } if (!html.isEmpty()) return html; return string; #undef APPEND } QString HtmlGenerator::fileBase(const Node *node) const { QString result; result = Generator::fileBase(node); if (!node->isInnerNode()) { switch (node->status()) { case Node::Compat: result += "-compat"; break; case Node::Obsolete: result += "-obsolete"; break; default: ; } } return result; } QString HtmlGenerator::fileName(const Node *node) { if (node->type() == Node::Document) { if (static_cast(node)->subType() == Node::ExternalPage) return node->name(); if (static_cast(node)->subType() == Node::Image) return node->name(); } return Generator::fileName(node); } QString HtmlGenerator::refForNode(const Node *node) { const FunctionNode *func; const TypedefNode *typedeffe; QString ref; switch (node->type()) { case Node::Namespace: case Node::Class: default: break; case Node::Enum: ref = node->name() + "-enum"; break; case Node::Typedef: typedeffe = static_cast(node); if (typedeffe->associatedEnum()) { return refForNode(typedeffe->associatedEnum()); } else { ref = node->name() + "-typedef"; } break; case Node::Function: func = static_cast(node); if (func->associatedProperty()) { return refForNode(func->associatedProperty()); } else { ref = func->name(); if (func->overloadNumber() != 1) ref += QLatin1Char('-') + QString::number(func->overloadNumber()); } break; case Node::Document: break; case Node::QmlProperty: if (node->isAttached()) ref = node->name() + "-attached-prop"; else ref = node->name() + "-prop"; break; case Node::QmlPropertyGroup: case Node::Property: ref = node->name() + "-prop"; break; case Node::QmlSignal: ref = node->name() + "-signal"; break; case Node::QmlSignalHandler: ref = node->name() + "-signal-handler"; break; case Node::QmlMethod: func = static_cast(node); ref = func->name() + "-method"; if (func->overloadNumber() != 1) ref += QLatin1Char('-') + QString::number(func->overloadNumber()); break; case Node::Variable: ref = node->name() + "-var"; break; } return registerRef(ref); } /*! This function is called for links, i.e. for words that are marked with the qdoc link command. For autolinks that are not marked with the qdoc link command, the getAutoLink() function is called It returns the string for a link found by using the data in the \a atom to search the database. It also sets \a node to point to the target node for that link. \a relative points to the node holding the qdoc comment where the link command was found. */ QString HtmlGenerator::getLink(const Atom *atom, const Node *relative, const Node** node) { const QString& t = atom->string(); if (t.at(0) == QChar('h')) { if (t.startsWith("http:") || t.startsWith("https:")) return t; } else if (t.at(0) == QChar('f')) { if (t.startsWith("file:") || t.startsWith("ftp:")) return t; } else if (t.at(0) == QChar('m')) { if (t.startsWith("mailto:")) return t; } return getAutoLink(atom, relative, node); } /*! This function is called for autolinks, i.e. for words that are not marked with the qdoc link command that qdoc has reason to believe should be links. For links marked with the qdoc link command, the getLink() function is called. It returns the string for a link found by using the data in the \a atom to search the database. It also sets \a node to point to the target node for that link. \a relative points to the node holding the qdoc comment where the link command was found. */ QString HtmlGenerator::getAutoLink(const Atom *atom, const Node *relative, const Node** node) { QString ref; *node = qdb_->findNodeForAtom(atom, relative, ref); if (!(*node)) { return QString(); } QString link = (*node)->url(); if (link.isEmpty()) { link = linkForNode(*node, relative); if ((*node)->subType() == Node::Image) link = "images/used-in-examples/" + link; if (!ref.isEmpty()) link += QLatin1Char('#') + ref; } else if (!ref.isEmpty()) { int hashtag = link.lastIndexOf(QChar('#')); if (hashtag != -1) link.truncate(hashtag); link += "#" + ref; } return link; } /*! Construct the link string for the \a node and return it. The \a relative node is use to decide the link we are generating is in the same file as the target. Note the relative node can be 0, which pretty much guarantees that the link and the target aren't in the same file. */ QString HtmlGenerator::linkForNode(const Node *node, const Node *relative) { if (node == 0) return QString(); if (!node->url().isEmpty()) return node->url(); if (fileBase(node).isEmpty()) return QString(); if (node->access() == Node::Private) return QString(); QString fn = fileName(node); if (node && node->parent() && (node->parent()->isQmlType() || node->parent()->isJsType()) && node->parent()->isAbstract()) { if (Generator::qmlTypeContext()) fn = fileName(Generator::qmlTypeContext()); } QString link = fn; if (!node->isInnerNode() || node->isQmlPropertyGroup() || node->isJsPropertyGroup()) { QString ref = refForNode(node); if (relative && fn == fileName(relative) && ref == refForNode(relative)) return QString(); link += QLatin1Char('#'); link += ref; } /* If the output is going to subdirectories, then if the two nodes will be output to different directories, then the link must go up to the parent directory and then back down into the other subdirectory. */ if (node && relative && (node != relative)) { if (useOutputSubdirs() && !node->isExternalPage() && node->outputSubdirectory() != relative->outputSubdirectory()) { if (link.startsWith(QString(node->outputSubdirectory() + QLatin1Char('/')))) { link.prepend(QString("../")); } else { link.prepend(QString("../" + node->outputSubdirectory() + QLatin1Char('/'))); } } } return link; } void HtmlGenerator::generateFullName(const Node *apparentNode, const Node *relative, const Node *actualNode) { if (actualNode == 0) actualNode = apparentNode; out() << "status() != actualNode->status()) { switch (actualNode->status()) { case Node::Obsolete: out() << "\" class=\"obsolete"; break; case Node::Compat: out() << "\" class=\"compat"; break; default: ; } } out() << "\">"; out() << protectEnc(apparentNode->fullName(relative)); out() << ""; } void HtmlGenerator::generateDetailedMember(const Node *node, const InnerNode *relative, CodeMarker *marker) { const EnumNode *enume; #ifdef GENERATE_MAC_REFS generateMacRef(node, marker); #endif generateExtractionMark(node, MemberMark); generateKeywordAnchors(node); QString nodeRef = refForNode(node); if (node->type() == Node::Enum && (enume = static_cast(node))->flagsType()) { #ifdef GENERATE_MAC_REFS generateMacRef(enume->flagsType(), marker); #endif out() << "

    "; out() << ""; generateSynopsis(enume, relative, marker, CodeMarker::Detailed); out() << "
    "; generateSynopsis(enume->flagsType(), relative, marker, CodeMarker::Detailed); out() << "

    \n"; } else { out() << "

    "; out() << ""; generateSynopsis(node, relative, marker, CodeMarker::Detailed); out() << "

    " << divNavTop << '\n'; } generateStatus(node, marker); generateBody(node, marker); generateThreadSafeness(node, marker); generateSince(node, marker); if (node->type() == Node::Property) { const PropertyNode *property = static_cast(node); Section section; section.members += property->getters(); section.members += property->setters(); section.members += property->resetters(); if (!section.members.isEmpty()) { out() << "

    Access functions:

    \n"; generateSectionList(section, node, marker, CodeMarker::Accessors); } Section notifiers; notifiers.members += property->notifiers(); if (!notifiers.members.isEmpty()) { out() << "

    Notifier signal:

    \n"; //out() << "

    This signal is emitted when the property value is changed.

    \n"; generateSectionList(notifiers, node, marker, CodeMarker::Accessors); } } else if (node->isFunction()) { const FunctionNode* fn = static_cast(node); if (fn->isPrivateSignal()) generatePrivateSignalNote(node, marker); } else if (node->type() == Node::Enum) { const EnumNode *enume = static_cast(node); if (enume->flagsType()) { out() << "

    The " << protectEnc(enume->flagsType()->name()) << " type is a typedef for " << "QFlags<" << protectEnc(enume->name()) << ">. It stores an OR combination of " << protectEnc(enume->name()) << " values.

    \n"; } } generateAlsoList(node, marker); generateExtractionMark(node, EndMark); } int HtmlGenerator::hOffset(const Node *node) { switch (node->type()) { case Node::Namespace: case Node::Class: return 2; case Node::QmlBasicType: case Node::QmlType: case Node::Document: return 1; case Node::Enum: case Node::Typedef: case Node::Function: case Node::Property: default: return 3; } } bool HtmlGenerator::isThreeColumnEnumValueTable(const Atom *atom) { while (atom != 0 && !(atom->type() == Atom::ListRight && atom->string() == ATOM_LIST_VALUE)) { if (atom->type() == Atom::ListItemLeft && !matchAhead(atom, Atom::ListItemRight)) return true; atom = atom->next(); } return false; } const QPair HtmlGenerator::anchorForNode(const Node *node) { QPair anchorPair; anchorPair.first = Generator::fileName(node); if (node->type() == Node::Document) { const DocumentNode *docNode = static_cast(node); anchorPair.second = docNode->title(); } return anchorPair; } void HtmlGenerator::generateStatus(const Node *node, CodeMarker *marker) { Text text; switch (node->status()) { case Node::Obsolete: if (node->isInnerNode()) Generator::generateStatus(node, marker); break; case Node::Compat: // Porting to Qt 4 no longer supported break; default: Generator::generateStatus(node, marker); } } #ifdef GENERATE_MAC_REFS /* No longer valid. */ void HtmlGenerator::generateMacRef(const Node *node, CodeMarker *marker) { if (!pleaseGenerateMacRef || marker == 0) return; QStringList macRefs = marker->macRefsForNode(node); foreach (const QString &macRef, macRefs) out() << "\n"; } #endif void HtmlGenerator::beginLink(const QString &link, const Node *node, const Node *relative) { link_ = link; if (link_.isEmpty()) { if (showBrokenLinks) out() << ""; } else if (node == 0 || (relative != 0 && node->status() == relative->status())) { out() << ""; } else { switch (node->status()) { case Node::Obsolete: out() << ""; break; case Node::Compat: out() << ""; break; default: out() << ""; } } inLink_ = true; } void HtmlGenerator::endLink() { if (inLink_) { if (link_.isEmpty()) { if (showBrokenLinks) out() << ""; } else { if (inObsoleteLink) { out() << "(obsolete)"; } out() << ""; } } inLink_ = false; inObsoleteLink = false; } /*! Generates the summary for the \a section. Only used for sections of QML element documentation. */ void HtmlGenerator::generateQmlSummary(const Section& section, const Node *relative, CodeMarker *marker) { if (!section.members.isEmpty()) { out() << "
      \n"; NodeList::ConstIterator m; m = section.members.constBegin(); while (m != section.members.constEnd()) { out() << "
    • "; generateQmlItem(*m,relative,marker,true); if ((*m)->type() == Node::QmlPropertyGroup) { const QmlPropertyGroupNode* qpgn = static_cast(*m); if (!qpgn->childNodes().isEmpty()) { NodeList::ConstIterator p = qpgn->childNodes().constBegin(); out() << "
        \n"; while (p != qpgn->childNodes().constEnd()) { if ((*p)->type() == Node::QmlProperty) { out() << "
      • "; generateQmlItem(*p, relative, marker, true); out() << "
      • \n"; } ++p; } out() << "
      \n"; } } out() << "
    • \n"; ++m; } out() << "
    \n"; } } /*! Outputs the html detailed documentation for a section on a QML element reference page. */ void HtmlGenerator::generateDetailedQmlMember(Node *node, const InnerNode *relative, CodeMarker *marker) { QmlPropertyNode* qpn = 0; #ifdef GENERATE_MAC_REFS generateMacRef(node, marker); #endif generateExtractionMark(node, MemberMark); generateKeywordAnchors(node); out() << "
    "; QString nodeRef = refForNode(node); if (node->type() == Node::QmlPropertyGroup) { const QmlPropertyGroupNode* qpgn = static_cast(node); NodeList::ConstIterator p = qpgn->childNodes().constBegin(); out() << "
    "; out() << "
    "; QString heading = qpgn->name() + " group"; out() << ""; out() << ""; while (p != qpgn->childNodes().constEnd()) { if ((*p)->type() == Node::QmlProperty) { qpn = static_cast(*p); nodeRef = refForNode(qpn); out() << ""; out() << ""; } ++p; } out() << "

    "; out() << ""; out() << "" << heading << ""; out() << "

    "; out() << ""; if (!qpn->isWritable()) out() << "read-only"; if (qpn->isDefault()) out() << "default"; generateQmlItem(qpn, relative, marker, false); out() << "

    "; out() << "
    "; } else if (node->type() == Node::QmlProperty) { qpn = static_cast(node); out() << "
    "; out() << "
    "; out() << ""; out() << ""; out() << "

    "; out() << ""; if (!qpn->isReadOnlySet()) { if (qpn->declarativeCppNode()) qpn->setReadOnly(!qpn->isWritable()); } if (qpn->isReadOnly()) out() << "read-only"; if (qpn->isDefault()) out() << "default"; generateQmlItem(qpn, relative, marker, false); out() << "

    "; out() << "
    "; } else if (node->type() == Node::QmlSignal) { const FunctionNode* qsn = static_cast(node); out() << "
    "; out() << "
    "; out() << ""; out() << ""; out() << "

    "; out() << ""; generateSynopsis(qsn,relative,marker,CodeMarker::Detailed,false); out() << "

    "; out() << "
    "; } else if (node->type() == Node::QmlSignalHandler) { const FunctionNode* qshn = static_cast(node); out() << "
    "; out() << "
    "; out() << ""; out() << ""; out() << "

    "; out() << ""; generateSynopsis(qshn,relative,marker,CodeMarker::Detailed,false); out() << "

    "; out() << "
    "; } else if (node->type() == Node::QmlMethod) { const FunctionNode* qmn = static_cast(node); out() << "
    "; out() << "
    "; out() << ""; out() << ""; out() << "

    "; out() << ""; generateSynopsis(qmn,relative,marker,CodeMarker::Detailed,false); out() << "

    "; out() << "
    "; } out() << "
    "; generateStatus(node, marker); generateBody(node, marker); generateThreadSafeness(node, marker); generateSince(node, marker); generateAlsoList(node, marker); out() << "
    "; out() << "
    "; generateExtractionMark(node, EndMark); } /*! Output the "Inherits" line for the QML element, if there should be one. */ void HtmlGenerator::generateQmlInherits(QmlTypeNode* qcn, CodeMarker* marker) { if (!qcn) return; QmlTypeNode* base = qcn->qmlBaseNode(); while (base && base->isInternal()) { base = base->qmlBaseNode(); } if (base) { Text text; text << Atom::ParaLeft << "Inherits "; text << Atom(Atom::LinkNode,CodeMarker::stringForNode(base)); text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK); text << Atom(Atom::String, base->name()); text << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK); text << Atom::ParaRight; generateText(text, qcn, marker); } } /*! Output the "[Xxx instantiates the C++ class QmlGraphicsXxx]" line for the QML element, if there should be one. If there is no class node, or if the class node status is set to Node::Internal, do nothing. */ void HtmlGenerator::generateQmlInstantiates(QmlTypeNode* qcn, CodeMarker* marker) { ClassNode* cn = qcn->classNode(); if (cn && (cn->status() != Node::Internal)) { Text text; text << Atom::ParaLeft; text << Atom(Atom::LinkNode,CodeMarker::stringForNode(qcn)); text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK); QString name = qcn->name(); /* Remove the "QML:" prefix, if present. It shouldn't be present anymore. */ if (name.startsWith(QLatin1String("QML:"))) name = name.mid(4); // remove the "QML:" prefix text << Atom(Atom::String, name); text << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK); text << " instantiates the C++ class "; text << Atom(Atom::LinkNode,CodeMarker::stringForNode(cn)); text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK); text << Atom(Atom::String, cn->name()); text << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK); text << Atom::ParaRight; generateText(text, qcn, marker); } } /*! Output the "[QmlGraphicsXxx is instantiated by QML Type Xxx]" line for the class, if there should be one. If there is no QML element, or if the class node status is set to Node::Internal, do nothing. */ void HtmlGenerator::generateInstantiatedBy(ClassNode* cn, CodeMarker* marker) { if (cn && cn->status() != Node::Internal && cn->qmlElement() != 0) { const QmlTypeNode* qcn = cn->qmlElement(); Text text; text << Atom::ParaLeft; text << Atom(Atom::LinkNode,CodeMarker::stringForNode(cn)); text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK); text << Atom(Atom::String, cn->name()); text << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK); if (qcn->isQmlType()) text << " is instantiated by QML Type "; else text << " is instantiated by Javascript Type "; text << Atom(Atom::LinkNode,CodeMarker::stringForNode(qcn)); text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK); text << Atom(Atom::String, qcn->name()); text << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK); text << Atom::ParaRight; generateText(text, cn, marker); } } void HtmlGenerator::generateExtractionMark(const Node *node, ExtractionMarkType markType) { if (markType != EndMark) { out() << "\n"; } else { out() << "\n"; } } /*! This function outputs one or more manifest files in XML. They are used by Creator. */ void HtmlGenerator::generateManifestFiles() { generateManifestFile("examples", "example"); generateManifestFile("demos", "demo"); qdb_->exampleNodeMap().clear(); manifestMetaContent.clear(); } /*! This function is called by generateManifestFiles(), once for each manifest file to be generated. \a manifest is the type of manifest file. */ void HtmlGenerator::generateManifestFile(const QString &manifest, const QString &element) { ExampleNodeMap& exampleNodeMap = qdb_->exampleNodeMap(); if (exampleNodeMap.isEmpty()) return; QString fileName = manifest +"-manifest.xml"; QFile file(outputDir() + QLatin1Char('/') + fileName); bool demos = false; if (manifest == "demos") demos = true; bool proceed = false; ExampleNodeMap::Iterator i = exampleNodeMap.begin(); while (i != exampleNodeMap.end()) { const ExampleNode* en = i.value(); if (demos) { if (en->name().startsWith("demos")) { proceed = true; break; } } else if (!en->name().startsWith("demos")) { proceed = true; break; } ++i; } if (!proceed || !file.open(QFile::WriteOnly | QFile::Text)) return; QXmlStreamWriter writer(&file); writer.setAutoFormatting(true); writer.writeStartDocument(); writer.writeStartElement("instructionals"); writer.writeAttribute("module", project); writer.writeStartElement(manifest); QStringList usedAttributes; i = exampleNodeMap.begin(); while (i != exampleNodeMap.end()) { const ExampleNode* en = i.value(); if (demos) { if (!en->name().startsWith("demos")) { ++i; continue; } } else if (en->name().startsWith("demos")) { ++i; continue; } // attributes that are always written for the element usedAttributes.clear(); usedAttributes << "name" << "docUrl" << "projectPath"; writer.writeStartElement(element); writer.writeAttribute("name", en->title()); QString docUrl = manifestDir + fileBase(en) + ".html"; writer.writeAttribute("docUrl", docUrl); QStringList proFiles; foreach (const Node* child, en->childNodes()) { if (child->subType() == Node::File) { QString file = child->name(); if (file.endsWith(".pro") || file.endsWith(".qmlproject")) { proFiles << file; } } } if (!proFiles.isEmpty()) { if (proFiles.size() == 1) { writer.writeAttribute("projectPath", examplesPath + proFiles[0]); } else { QString exampleName = en->name().split('/').last(); bool proWithExampleNameFound = false; for (int j = 0; j < proFiles.size(); j++) { if (proFiles[j].endsWith(QStringLiteral("%1/%1.pro").arg(exampleName)) || proFiles[j].endsWith(QStringLiteral("%1/%1.qmlproject").arg(exampleName))) { writer.writeAttribute("projectPath", examplesPath + proFiles[j]); proWithExampleNameFound = true; break; } } if (!proWithExampleNameFound) writer.writeAttribute("projectPath", examplesPath + proFiles[0]); } } if (!en->imageFileName().isEmpty()) { writer.writeAttribute("imageUrl", manifestDir + en->imageFileName()); usedAttributes << "imageUrl"; } QString fullName = project + QLatin1Char('/') + en->title(); QSet tags; for (int idx=0; idx < manifestMetaContent.size(); ++idx) { foreach (const QString &name, manifestMetaContent[idx].names) { bool match = false; int wildcard = name.indexOf(QChar('*')); switch (wildcard) { case -1: // no wildcard, exact match match = (fullName == name); break; case 0: // '*' matches all match = true; break; default: // match with wildcard at the end match = fullName.startsWith(name.left(wildcard)); } if (match) { tags += manifestMetaContent[idx].tags; foreach (const QString &attr, manifestMetaContent[idx].attributes) { QLatin1Char div(':'); QStringList attrList = attr.split(div); if (attrList.count() == 1) attrList.append(QStringLiteral("true")); QString attrName = attrList.takeFirst(); if (!usedAttributes.contains(attrName)) { writer.writeAttribute(attrName, attrList.join(div)); usedAttributes << attrName; } } } } } writer.writeStartElement("description"); Text brief = en->doc().briefText(); if (!brief.isEmpty()) writer.writeCDATA(brief.toString()); else writer.writeCDATA(QString("No description available")); writer.writeEndElement(); // description // Add words from module name as tags // QtQuickControls -> qt,quick,controls // QtOpenGL -> qt,opengl QRegExp re("([A-Z]+[a-z0-9]*(3D|GL)?)"); int pos = 0; while ((pos = re.indexIn(project, pos)) != -1) { tags << re.cap(1).toLower(); pos += re.matchedLength(); } tags += QSet::fromList(en->title().toLower().split(QLatin1Char(' '))); // Clean up tags, exclude invalid and common words QSet::iterator tag_it = tags.begin(); QSet modified; while (tag_it != tags.end()) { QString s = *tag_it; if (s.at(0) == '(') s.remove(0, 1).chop(1); if (s.endsWith(QLatin1Char(':'))) s.chop(1); if (s.length() < 2 || s.at(0).isDigit() || s.at(0) == '-' || s == QStringLiteral("qt") || s == QStringLiteral("the") || s == QStringLiteral("and") || s.startsWith(QStringLiteral("example")) || s.startsWith(QStringLiteral("chapter"))) tag_it = tags.erase(tag_it); else if (s != *tag_it) { modified << s; tag_it = tags.erase(tag_it); } else ++tag_it; } tags += modified; if (!tags.isEmpty()) { writer.writeStartElement("tags"); bool wrote_one = false; foreach (QString tag, tags) { if (wrote_one) writer.writeCharacters(","); writer.writeCharacters(tag); wrote_one = true; } writer.writeEndElement(); // tags } QString ename = en->name().mid(en->name().lastIndexOf('/')+1); QMap filesToOpen; foreach (const Node* child, en->childNodes()) { if (child->subType() == Node::File) { QFileInfo fileInfo(child->name()); QString fileName = fileInfo.fileName().toLower(); // open .qml, .cpp and .h files with a // basename matching the example (project) name // QMap key indicates the priority - // the lowest value will be the top-most file if ((fileInfo.baseName().compare(ename, Qt::CaseInsensitive) == 0)) { if (fileName.endsWith(".qml")) filesToOpen.insert(0, child); else if (fileName.endsWith(".cpp")) filesToOpen.insert(1, child); else if (fileName.endsWith(".h")) filesToOpen.insert(2, child); } // main.qml takes precedence over main.cpp else if (fileName.endsWith("main.qml")) { filesToOpen.insert(3, child); } else if (fileName.endsWith("main.cpp")) { filesToOpen.insert(4, child); } } } QMap::const_iterator it = filesToOpen.constEnd(); while (it != filesToOpen.constBegin()) { writer.writeStartElement("fileToOpen"); if (--it == filesToOpen.constBegin()) { writer.writeAttribute(QStringLiteral("mainFile"), QStringLiteral("true")); } writer.writeCharacters(examplesPath + it.value()->name()); writer.writeEndElement(); } writer.writeEndElement(); // example ++i; } writer.writeEndElement(); // examples writer.writeEndElement(); // instructionals writer.writeEndDocument(); file.close(); } /*! Reads metacontent - additional attributes and tags to apply when generating manifest files, read from config. Takes the configuration class \a config as a parameter. The manifest metacontent map is cleared immediately after the manifest files have been generated. */ void HtmlGenerator::readManifestMetaContent(const Config &config) { QStringList names = config.getStringList(CONFIG_MANIFESTMETA + Config::dot + QStringLiteral("filters")); foreach (const QString &manifest, names) { ManifestMetaFilter filter; QString prefix = CONFIG_MANIFESTMETA + Config::dot + manifest + Config::dot; filter.names = config.getStringSet(prefix + QStringLiteral("names")); filter.attributes = config.getStringSet(prefix + QStringLiteral("attributes")); filter.tags = config.getStringSet(prefix + QStringLiteral("tags")); manifestMetaContent.append(filter); } } /*! Find global entities that have documentation but no \e{relates} comand. Report these as errors if they are not also marked \e {internal}. type: Class type: Namespace subtype: Example subtype: External page subtype: Group subtype: Header file subtype: Module subtype: Page subtype: QML basic type subtype: QML class subtype: QML module */ void HtmlGenerator::reportOrphans(const InnerNode* parent) { const NodeList& children = parent->childNodes(); if (children.size() == 0) return; bool related; QString message; for (int i=0; iisInternal() || child->doc().isEmpty()) continue; if (child->relates()) { related = true; message = child->relates()->name(); } else { related = false; message = "has documentation but no \\relates command"; } switch (child->type()) { case Node::Namespace: break; case Node::Class: break; case Node::QmlType: break; case Node::QmlBasicType: break; case Node::Group: break; case Node::Module: break; case Node::QmlModule: break; case Node::Document: switch (child->subType()) { case Node::Example: break; case Node::HeaderFile: break; case Node::File: break; case Node::Image: break; case Node::Page: break; case Node::ExternalPage: break; default: break; } break; case Node::Enum: if (!related) child->location().warning(tr("Global enum, %1, %2").arg(child->name()).arg(message)); break; case Node::Typedef: if (!related) child->location().warning(tr("Global typedef, %1, %2").arg(child->name()).arg(message)); break; case Node::Function: if (!related) { const FunctionNode* fn = static_cast(child); if (fn->isMacro()) child->location().warning(tr("Global macro, %1, %2").arg(child->name()).arg(message)); else child->location().warning(tr("Global function, %1(), %2").arg(child->name()).arg(message)); } break; case Node::Property: break; case Node::Variable: if (!related) child->location().warning(tr("Global variable, %1, %2").arg(child->name()).arg(message)); break; case Node::QmlPropertyGroup: break; case Node::QmlProperty: if (!related) child->location().warning(tr("Global QML property, %1, %2").arg(child->name()).arg(message)); break; case Node::QmlSignal: if (!related) child->location().warning(tr("Global QML, signal, %1 %2").arg(child->name()).arg(message)); break; case Node::QmlSignalHandler: if (!related) child->location().warning(tr("Global QML signal handler, %1, %2").arg(child->name()).arg(message)); break; case Node::QmlMethod: if (!related) child->location().warning(tr("Global QML method, %1, %2").arg(child->name()).arg(message)); break; default: break; } } } /*! Returns a reference to the XML stream writer currently in use. There is one XML stream writer open for each XML file being written, and they are kept on a stack. The one on top of the stack is the one being written to at the moment. In the HTML output generator, it is perhaps impossible for there to ever be more than one writer open. */ QXmlStreamWriter& HtmlGenerator::xmlWriter() { return *xmlWriterStack.top(); } /*! This function is only called for writing ditamaps. Calls beginSubPage() in the base class to open the file. Then creates a new XML stream writer using the IO device from opened file and pushes the XML writer onto a stackj. Creates the file named \a fileName in the output directory. Attaches a QTextStream to the created file, which is written to all over the place using out(). Finally, it sets some parameters in the XML writer and calls writeStartDocument(). It also ensures that a GUID map is created for the output file. */ void HtmlGenerator::beginDitamapPage(const InnerNode* node, const QString& fileName) { Generator::beginSubPage(node,fileName); QXmlStreamWriter* writer = new QXmlStreamWriter(out().device()); xmlWriterStack.push(writer); writer->setAutoFormatting(true); writer->setAutoFormattingIndent(4); writer->writeStartDocument(); } /*! This function is only called for writing ditamaps. Calls writeEndDocument() and then pops the XML stream writer off the stack and deletes it. Then it calls endSubPage() in the base class to close the device. */ void HtmlGenerator::endDitamapPage() { xmlWriter().writeEndDocument(); delete xmlWriterStack.pop(); Generator::endSubPage(); } /*! This function is only called for writing ditamaps. Creates the DITA map from the topicrefs in \a node, which is a DitaMapNode. */ void HtmlGenerator::writeDitaMap(const DitaMapNode* node) { beginDitamapPage(node,node->name()); QString doctype = ""; xmlWriter().writeDTD(doctype); xmlWriter().writeStartElement("map"); xmlWriter().writeStartElement("topicmeta"); xmlWriter().writeStartElement("shortdesc"); xmlWriter().writeCharacters(node->title()); xmlWriter().writeEndElement(); // xmlWriter().writeEndElement(); // DitaRefList map = node->map(); writeDitaRefs(map); endDitamapPage(); } /*! Write the \a ditarefs to the current output file. */ void HtmlGenerator::writeDitaRefs(const DitaRefList& ditarefs) { foreach (DitaRef* t, ditarefs) { if (t->isMapRef()) xmlWriter().writeStartElement("mapref"); else xmlWriter().writeStartElement("topicref"); xmlWriter().writeAttribute("navtitle",t->navtitle()); if (t->href().isEmpty()) { const DocumentNode* dn = qdb_->findDocumentNodeByTitle(t->navtitle()); if (dn) xmlWriter().writeAttribute("href",fileName(dn)); } else xmlWriter().writeAttribute("href",t->href()); if (t->subrefs() && !t->subrefs()->isEmpty()) writeDitaRefs(*(t->subrefs())); xmlWriter().writeEndElement(); // or } } QT_END_NAMESPACE