/**************************************************************************** ** ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). ** All rights reserved. ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** This file is part of the tools applications of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** No Commercial Usage ** This file contains pre-release code and may not be distributed. ** You may use this file in accordance with the terms and conditions ** contained in the Technology Preview License Agreement accompanying ** this package. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain additional ** rights. These rights are described in the Nokia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** If you have questions regarding the use of this file, please contact ** Nokia at qt-info@nokia.com. ** ** ** ** ** ** ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ /* tree.cpp */ #include #include "atom.h" #include "doc.h" #include "htmlgenerator.h" #include "location.h" #include "node.h" #include "text.h" #include "tree.h" #include #include QT_BEGIN_NAMESPACE struct InheritanceBound { Node::Access access; QStringList basePath; QString dataTypeWithTemplateArgs; InnerNode *parent; InheritanceBound() : access(Node::Public) { } InheritanceBound(Node::Access access0, const QStringList& basePath0, const QString &dataTypeWithTemplateArgs0, InnerNode *parent) : access(access0), basePath(basePath0), dataTypeWithTemplateArgs(dataTypeWithTemplateArgs0), parent(parent) { } }; struct Target { Node *node; Atom *atom; int priority; }; typedef QMap RoleMap; typedef QMap PropertyMap; typedef QMultiMap GroupMap; typedef QMultiHash FakeNodeHash; typedef QMultiHash TargetHash; class TreePrivate { public: QMap > unresolvedInheritanceMap; PropertyMap unresolvedPropertyMap; GroupMap groupMap; QMultiMap publicGroupMap; FakeNodeHash fakeNodesByTitle; TargetHash targetHash; QList > basesList; QList > relatedList; }; /*! \class Tree */ /*! The default constructor is the only constructor. */ Tree::Tree() : roo(0, "") { priv = new TreePrivate; } /*! The destructor deletes the internal, private tree. */ Tree::~Tree() { delete priv; } /*! */ Node *Tree::findNode(const QStringList &path, Node *relative, int findFlags, const Node* self) { return const_cast(const_cast(this)->findNode(path, relative, findFlags, self)); } /*! */ const Node* Tree::findNode(const QStringList &path, const Node* start, int findFlags, const Node* self) const { const Node* current = start; if (!current) current = root(); do { const Node *node = current; int i; for (i = 0; i < path.size(); ++i) { if (node == 0 || !node->isInnerNode()) break; const Node *next = static_cast(node)->findNode(path.at(i)); if (!next && (findFlags & SearchEnumValues) && i == path.size()-1) next = static_cast(node)->findEnumNodeForValue(path.at(i)); if (!next && node->type() == Node::Class && (findFlags & SearchBaseClasses)) { NodeList baseClasses = allBaseClasses(static_cast(node)); foreach (const Node *baseClass, baseClasses) { next = static_cast(baseClass)->findNode(path.at(i)); if (!next && (findFlags & SearchEnumValues) && i == path.size() - 1) next = static_cast(baseClass) ->findEnumNodeForValue(path.at(i)); if (next) break; } } node = next; } if (node && i == path.size() && (!(findFlags & NonFunction) || node->type() != Node::Function || ((FunctionNode *)node)->metaness() == FunctionNode::MacroWithoutParams)) { if ((node != self) && (node->subType() != Node::QmlPropertyGroup)) { return node; } } current = current->parent(); } while (current); return 0; } /*! Find the node with the specified \a path name of the specified \a type. */ Node *Tree::findNode(const QStringList &path, Node::Type type, Node *relative, int findFlags) { return const_cast(const_cast(this)->findNode(path, type, relative, findFlags)); } /*! Find the node with the specified \a path name of the specified \a type. */ const Node *Tree::findNode(const QStringList &path, Node::Type type, const Node *relative, int findFlags) const { const Node *node = findNode(path, relative, findFlags); if (node != 0 && node->type() == type) return node; return 0; } /*! */ FunctionNode *Tree::findFunctionNode(const QStringList& path, Node *relative, int findFlags) { return const_cast( const_cast(this)->findFunctionNode(path, relative, findFlags)); } /*! */ const FunctionNode *Tree::findFunctionNode(const QStringList &path, const Node *relative, int findFlags) const { if (!relative) relative = root(); do { const Node *node = relative; int i; for (i = 0; i < path.size(); ++i) { if (node == 0 || !node->isInnerNode()) break; const Node *next; if (i == path.size() - 1) next = ((InnerNode *) node)->findFunctionNode(path.at(i)); else next = ((InnerNode *) node)->findNode(path.at(i)); if (!next && node->type() == Node::Class && (findFlags & SearchBaseClasses)) { NodeList baseClasses = allBaseClasses(static_cast(node)); foreach (const Node *baseClass, baseClasses) { if (i == path.size() - 1) next = static_cast(baseClass)->findFunctionNode(path.at(i)); else next = static_cast(baseClass)->findNode(path.at(i)); if (next) break; } } node = next; } if (node && i == path.size() && node->isFunction()) { // CppCodeParser::processOtherMetaCommand ensures that reimplemented // functions are private. const FunctionNode *func = static_cast(node); while (func->access() == Node::Private) { const FunctionNode *from = func->reimplementedFrom(); if (from != 0) { if (from->access() != Node::Private) return from; else func = from; } else break; } return func; } relative = relative->parent(); } while (relative); return 0; } /*! */ FunctionNode *Tree::findFunctionNode(const QStringList &parentPath, const FunctionNode *clone, Node *relative, int findFlags) { return const_cast( const_cast(this)->findFunctionNode(parentPath, clone, relative, findFlags)); } /*! */ const FunctionNode *Tree::findFunctionNode(const QStringList &parentPath, const FunctionNode *clone, const Node *relative, int findFlags) const { const Node *parent = findNode(parentPath, relative, findFlags); if (parent == 0 || !parent->isInnerNode()) { return 0; } else { return ((InnerNode *)parent)->findFunctionNode(clone); } } static const int NumSuffixes = 3; static const char * const suffixes[NumSuffixes] = { "", "s", "es" }; /*! */ const FakeNode *Tree::findFakeNodeByTitle(const QString &title) const { for (int pass = 0; pass < NumSuffixes; ++pass) { FakeNodeHash::const_iterator i = priv->fakeNodesByTitle.find(Doc::canonicalTitle(title + suffixes[pass])); if (i != priv->fakeNodesByTitle.constEnd()) { FakeNodeHash::const_iterator j = i; ++j; if (j != priv->fakeNodesByTitle.constEnd() && j.key() == i.key()) { QList internalLocations; while (j != priv->fakeNodesByTitle.constEnd()) { if (j.key() == i.key() && j.value()->url().isEmpty()) internalLocations.append(j.value()->doc().location()); ++j; } if (internalLocations.size() > 0) { i.value()->doc().location().warning( tr("Page '%1' defined in more than one location:").arg(title)); foreach (const Location &location, internalLocations) location.warning(tr("(defined here)")); } } return i.value(); } } return 0; } /*! */ const Node* Tree::findUnambiguousTarget(const QString &target, Atom *&atom) const { Target bestTarget = {0, 0, INT_MAX}; int numBestTargets = 0; for (int pass = 0; pass < NumSuffixes; ++pass) { TargetHash::const_iterator i = priv->targetHash.find(Doc::canonicalTitle(target + suffixes[pass])); if (i != priv->targetHash.constEnd()) { TargetHash::const_iterator j = i; do { const Target &candidate = j.value(); if (candidate.priority < bestTarget.priority) { bestTarget = candidate; numBestTargets = 1; } else if (candidate.priority == bestTarget.priority) { ++numBestTargets; } ++j; } while (j != priv->targetHash.constEnd() && j.key() == i.key()); if (numBestTargets == 1) { atom = bestTarget.atom; return bestTarget.node; } } } return 0; } /*! */ Atom *Tree::findTarget(const QString &target, const Node *node) const { for (int pass = 0; pass < NumSuffixes; ++pass) { QString key = Doc::canonicalTitle(target + suffixes[pass]); TargetHash::const_iterator i = priv->targetHash.find(key); if (i != priv->targetHash.constEnd()) { do { if (i.value().node == node) return i.value().atom; ++i; } while (i != priv->targetHash.constEnd() && i.key() == key); } } return 0; } /*! */ void Tree::addBaseClass(ClassNode *subclass, Node::Access access, const QStringList &basePath, const QString &dataTypeWithTemplateArgs, InnerNode *parent) { priv->unresolvedInheritanceMap[subclass].append( InheritanceBound(access, basePath, dataTypeWithTemplateArgs, parent) ); } /*! */ void Tree::addPropertyFunction(PropertyNode *property, const QString &funcName, PropertyNode::FunctionRole funcRole) { priv->unresolvedPropertyMap[property].insert(funcRole, funcName); } /*! This function adds the \a node to the \a group. The group can be listed anywhere using the \e{annotated list} command. */ void Tree::addToGroup(Node *node, const QString &group) { priv->groupMap.insert(group, node); } /*! */ QMultiMap Tree::groups() const { return priv->groupMap; } /*! */ void Tree::addToPublicGroup(Node *node, const QString &group) { priv->publicGroupMap.insert(node->name(), group); addToGroup(node, group); } /*! */ QMultiMap Tree::publicGroups() const { return priv->publicGroupMap; } /*! */ void Tree::resolveInheritance(NamespaceNode *rootNode) { if (!rootNode) rootNode = root(); for (int pass = 0; pass < 2; pass++) { NodeList::ConstIterator c = rootNode->childNodes().begin(); while (c != rootNode->childNodes().end()) { if ((*c)->type() == Node::Class) { resolveInheritance(pass, (ClassNode *) *c); } else if ((*c)->type() == Node::Namespace) { NamespaceNode *ns = static_cast(*c); resolveInheritance(ns); } ++c; } if (rootNode == root()) priv->unresolvedInheritanceMap.clear(); } } /*! */ void Tree::resolveProperties() { PropertyMap::ConstIterator propEntry; propEntry = priv->unresolvedPropertyMap.begin(); while (propEntry != priv->unresolvedPropertyMap.end()) { PropertyNode *property = propEntry.key(); InnerNode *parent = property->parent(); QString getterName = (*propEntry)[PropertyNode::Getter]; QString setterName = (*propEntry)[PropertyNode::Setter]; QString resetterName = (*propEntry)[PropertyNode::Resetter]; QString notifierName = (*propEntry)[PropertyNode::Notifier]; NodeList::ConstIterator c = parent->childNodes().begin(); while (c != parent->childNodes().end()) { if ((*c)->type() == Node::Function) { FunctionNode *function = static_cast(*c); if (function->access() == property->access() && (function->status() == property->status() || function->doc().isEmpty())) { if (function->name() == getterName) { property->addFunction(function, PropertyNode::Getter); } else if (function->name() == setterName) { property->addFunction(function, PropertyNode::Setter); } else if (function->name() == resetterName) { property->addFunction(function, PropertyNode::Resetter); } else if (function->name() == notifierName) { property->addSignal(function, PropertyNode::Notifier); } } } ++c; } ++propEntry; } propEntry = priv->unresolvedPropertyMap.begin(); while (propEntry != priv->unresolvedPropertyMap.end()) { PropertyNode *property = propEntry.key(); // redo it to set the property functions if (property->overriddenFrom()) property->setOverriddenFrom(property->overriddenFrom()); ++propEntry; } priv->unresolvedPropertyMap.clear(); } /*! */ void Tree::resolveInheritance(int pass, ClassNode *classe) { if (pass == 0) { QList bounds = priv->unresolvedInheritanceMap[classe]; QList::ConstIterator b = bounds.begin(); while (b != bounds.end()) { ClassNode *baseClass = (ClassNode*)findNode((*b).basePath, Node::Class); if (!baseClass && (*b).parent) { baseClass = (ClassNode*)findNode((*b).basePath, Node::Class, (*b).parent); } if (baseClass) { classe->addBaseClass((*b).access, baseClass, (*b).dataTypeWithTemplateArgs); } ++b; } } else { NodeList::ConstIterator c = classe->childNodes().begin(); while (c != classe->childNodes().end()) { if ((*c)->type() == Node::Function) { FunctionNode *func = (FunctionNode *) *c; FunctionNode *from = findVirtualFunctionInBaseClasses(classe, func); if (from != 0) { if (func->virtualness() == FunctionNode::NonVirtual) func->setVirtualness(FunctionNode::ImpureVirtual); func->setReimplementedFrom(from); } } else if ((*c)->type() == Node::Property) { fixPropertyUsingBaseClasses(classe, static_cast(*c)); } ++c; } } } /*! */ void Tree::resolveGroups() { GroupMap::const_iterator i; QString prevGroup; for (i = priv->groupMap.constBegin(); i != priv->groupMap.constEnd(); ++i) { if (i.value()->access() == Node::Private) continue; FakeNode *fake = static_cast(findNode(QStringList(i.key()),Node::Fake)); if (fake && fake->subType() == Node::Group) { fake->addGroupMember(i.value()); } prevGroup = i.key(); } //priv->groupMap.clear(); } /*! */ void Tree::resolveTargets() { // need recursion foreach (Node *child, roo.childNodes()) { if (child->type() == Node::Fake) { FakeNode *node = static_cast(child); priv->fakeNodesByTitle.insert(Doc::canonicalTitle(node->title()), node); } if (child->doc().hasTableOfContents()) { const QList &toc = child->doc().tableOfContents(); Target target; target.node = child; target.priority = 3; for (int i = 0; i < toc.size(); ++i) { target.atom = toc.at(i); QString title = Text::sectionHeading(target.atom).toString(); if (!title.isEmpty()) priv->targetHash.insert(Doc::canonicalTitle(title), target); } } if (child->doc().hasKeywords()) { const QList &keywords = child->doc().keywords(); Target target; target.node = child; target.priority = 1; for (int i = 0; i < keywords.size(); ++i) { target.atom = keywords.at(i); priv->targetHash.insert(Doc::canonicalTitle(target.atom->string()), target); } } if (child->doc().hasTargets()) { const QList &toc = child->doc().targets(); Target target; target.node = child; target.priority = 2; for (int i = 0; i < toc.size(); ++i) { target.atom = toc.at(i); priv->targetHash.insert(Doc::canonicalTitle(target.atom->string()), target); } } } } /*! */ void Tree::fixInheritance(NamespaceNode *rootNode) { if (!rootNode) rootNode = root(); NodeList::ConstIterator c = rootNode->childNodes().begin(); while (c != rootNode->childNodes().end()) { if ((*c)->type() == Node::Class) static_cast(*c)->fixBaseClasses(); else if ((*c)->type() == Node::Namespace) { NamespaceNode *ns = static_cast(*c); fixInheritance(ns); } ++c; } } /*! */ FunctionNode *Tree::findVirtualFunctionInBaseClasses(ClassNode *classe, FunctionNode *clone) { QList::ConstIterator r = classe->baseClasses().begin(); while (r != classe->baseClasses().end()) { FunctionNode *func; if (((func = findVirtualFunctionInBaseClasses((*r).node, clone)) != 0 || (func = (*r).node->findFunctionNode(clone)) != 0)) { if (func->virtualness() != FunctionNode::NonVirtual) return func; } ++r; } return 0; } /*! */ void Tree::fixPropertyUsingBaseClasses(ClassNode *classe, PropertyNode *property) { QList::const_iterator r = classe->baseClasses().begin(); while (r != classe->baseClasses().end()) { PropertyNode *baseProperty = static_cast(r->node->findNode(property->name(), Node::Property)); if (baseProperty) { fixPropertyUsingBaseClasses(r->node, baseProperty); property->setOverriddenFrom(baseProperty); } else { fixPropertyUsingBaseClasses(r->node, property); } ++r; } } /*! */ NodeList Tree::allBaseClasses(const ClassNode *classe) const { NodeList result; foreach (const RelatedClass &r, classe->baseClasses()) { result += r.node; result += allBaseClasses(r.node); } return result; } /*! */ void Tree::readIndexes(const QStringList &indexFiles) { foreach (const QString &indexFile, indexFiles) readIndexFile(indexFile); } /*! Read the QDomDocument at \a path and get the index from it. */ void Tree::readIndexFile(const QString &path) { QFile file(path); if (file.open(QFile::ReadOnly)) { QDomDocument document; document.setContent(&file); file.close(); QDomElement indexElement = document.documentElement(); QString indexUrl = indexElement.attribute("url", ""); priv->basesList.clear(); priv->relatedList.clear(); // Scan all elements in the XML file, constructing a map that contains // base classes for each class found. QDomElement child = indexElement.firstChildElement(); while (!child.isNull()) { readIndexSection(child, root(), indexUrl); child = child.nextSiblingElement(); } // Now that all the base classes have been found for this index, // arrange them into an inheritance hierarchy. resolveIndex(); } } /*! */ void Tree::readIndexSection(const QDomElement &element, InnerNode *parent, const QString &indexUrl) { QString name = element.attribute("name"); QString href = element.attribute("href"); Node *section; Location location; if (element.nodeName() == "namespace") { section = new NamespaceNode(parent, name); if (!indexUrl.isEmpty()) location = Location(indexUrl + "/" + name.toLower() + ".html"); else if (!indexUrl.isNull()) location = Location(name.toLower() + ".html"); } else if (element.nodeName() == "class") { section = new ClassNode(parent, name); priv->basesList.append(QPair( static_cast(section), element.attribute("bases"))); if (!indexUrl.isEmpty()) location = Location(indexUrl + "/" + name.toLower() + ".html"); else if (!indexUrl.isNull()) location = Location(name.toLower() + ".html"); } else if (element.nodeName() == "page") { Node::SubType subtype; if (element.attribute("subtype") == "example") subtype = Node::Example; else if (element.attribute("subtype") == "header") subtype = Node::HeaderFile; else if (element.attribute("subtype") == "file") subtype = Node::File; else if (element.attribute("subtype") == "group") subtype = Node::Group; else if (element.attribute("subtype") == "module") subtype = Node::Module; else if (element.attribute("subtype") == "page") subtype = Node::Page; else if (element.attribute("subtype") == "externalpage") subtype = Node::ExternalPage; else return; FakeNode *fakeNode = new FakeNode(parent, name, subtype); fakeNode->setTitle(element.attribute("title")); if (element.hasAttribute("location")) name = element.attribute("location", ""); if (!indexUrl.isEmpty()) location = Location(indexUrl + "/" + name); else if (!indexUrl.isNull()) location = Location(name); section = fakeNode; } else if (element.nodeName() == "enum") { EnumNode *enumNode = new EnumNode(parent, name); if (!indexUrl.isEmpty()) location = Location(indexUrl + "/" + parent->name().toLower() + ".html"); else if (!indexUrl.isNull()) location = Location(parent->name().toLower() + ".html"); QDomElement child = element.firstChildElement("value"); while (!child.isNull()) { EnumItem item(child.attribute("name"), child.attribute("value")); enumNode->addItem(item); child = child.nextSiblingElement("value"); } section = enumNode; } else if (element.nodeName() == "typedef") { section = new TypedefNode(parent, name); if (!indexUrl.isEmpty()) location = Location(indexUrl + "/" + parent->name().toLower() + ".html"); else if (!indexUrl.isNull()) location = Location(parent->name().toLower() + ".html"); } else if (element.nodeName() == "property") { section = new PropertyNode(parent, name); if (!indexUrl.isEmpty()) location = Location(indexUrl + "/" + parent->name().toLower() + ".html"); else if (!indexUrl.isNull()) location = Location(parent->name().toLower() + ".html"); } else if (element.nodeName() == "function") { FunctionNode::Virtualness virt; if (element.attribute("virtual") == "non") virt = FunctionNode::NonVirtual; else if (element.attribute("virtual") == "impure") virt = FunctionNode::ImpureVirtual; else if (element.attribute("virtual") == "pure") virt = FunctionNode::PureVirtual; else return; FunctionNode::Metaness meta; if (element.attribute("meta") == "plain") meta = FunctionNode::Plain; else if (element.attribute("meta") == "signal") meta = FunctionNode::Signal; else if (element.attribute("meta") == "slot") meta = FunctionNode::Slot; else if (element.attribute("meta") == "constructor") meta = FunctionNode::Ctor; else if (element.attribute("meta") == "destructor") meta = FunctionNode::Dtor; else if (element.attribute("meta") == "macro") meta = FunctionNode::MacroWithParams; else if (element.attribute("meta") == "macrowithparams") meta = FunctionNode::MacroWithParams; else if (element.attribute("meta") == "macrowithoutparams") meta = FunctionNode::MacroWithoutParams; else return; FunctionNode *functionNode = new FunctionNode(parent, name); functionNode->setReturnType(element.attribute("return")); functionNode->setVirtualness(virt); functionNode->setMetaness(meta); functionNode->setConst(element.attribute("const") == "true"); functionNode->setStatic(element.attribute("static") == "true"); functionNode->setOverload(element.attribute("overload") == "true"); if (element.hasAttribute("relates") && element.attribute("relates") != parent->name()) { priv->relatedList.append( QPair(functionNode, element.attribute("relates"))); } QDomElement child = element.firstChildElement("parameter"); while (!child.isNull()) { // Do not use the default value for the parameter; it is not // required, and has been known to cause problems. Parameter parameter(child.attribute("left"), child.attribute("right"), child.attribute("name"), ""); // child.attribute("default") functionNode->addParameter(parameter); child = child.nextSiblingElement("parameter"); } section = functionNode; if (!indexUrl.isEmpty()) location = Location(indexUrl + "/" + parent->name().toLower() + ".html"); else if (!indexUrl.isNull()) location = Location(parent->name().toLower() + ".html"); } else if (element.nodeName() == "variable") { section = new VariableNode(parent, name); if (!indexUrl.isEmpty()) location = Location(indexUrl + "/" + parent->name().toLower() + ".html"); else if (!indexUrl.isNull()) location = Location(parent->name().toLower() + ".html"); } else if (element.nodeName() == "keyword") { Target target; target.node = parent; target.priority = 1; target.atom = new Atom(Atom::Target, name); priv->targetHash.insert(name, target); return; } else if (element.nodeName() == "target") { Target target; target.node = parent; target.priority = 2; target.atom = new Atom(Atom::Target, name); priv->targetHash.insert(name, target); return; } else if (element.nodeName() == "contents") { Target target; target.node = parent; target.priority = 3; target.atom = new Atom(Atom::Target, name); priv->targetHash.insert(name, target); return; } else return; QString access = element.attribute("access"); if (access == "public") section->setAccess(Node::Public); else if (access == "protected") section->setAccess(Node::Protected); else if (access == "private") section->setAccess(Node::Private); else section->setAccess(Node::Public); if (element.nodeName() != "page") { QString threadSafety = element.attribute("threadsafety"); if (threadSafety == "non-reentrant") section->setThreadSafeness(Node::NonReentrant); else if (threadSafety == "reentrant") section->setThreadSafeness(Node::Reentrant); else if (threadSafety == "thread safe") section->setThreadSafeness(Node::ThreadSafe); else section->setThreadSafeness(Node::UnspecifiedSafeness); } else section->setThreadSafeness(Node::UnspecifiedSafeness); QString status = element.attribute("status"); if (status == "compat") section->setStatus(Node::Compat); else if (status == "obsolete") section->setStatus(Node::Obsolete); else if (status == "deprecated") section->setStatus(Node::Deprecated); else if (status == "preliminary") section->setStatus(Node::Preliminary); else if (status == "commendable") section->setStatus(Node::Commendable); else if (status == "internal") section->setStatus(Node::Internal); else if (status == "main") section->setStatus(Node::Main); else section->setStatus(Node::Commendable); section->setModuleName(element.attribute("module")); if (!indexUrl.isEmpty()) { if (indexUrl.startsWith(".")) section->setUrl(href); else section->setUrl(indexUrl + "/" + href); } // Create some content for the node. QSet emptySet; Doc doc(location, location, " ", emptySet); // placeholder section->setDoc(doc); if (section->isInnerNode()) { InnerNode *inner = static_cast(section); if (inner) { QDomElement child = element.firstChildElement(); while (!child.isNull()) { if (element.nodeName() == "class") readIndexSection(child, inner, indexUrl); else if (element.nodeName() == "page") readIndexSection(child, inner, indexUrl); else if (element.nodeName() == "namespace" && !name.isEmpty()) // The root node in the index is a namespace with an empty name. readIndexSection(child, inner, indexUrl); else readIndexSection(child, parent, indexUrl); child = child.nextSiblingElement(); } } } } /*! */ QString Tree::readIndexText(const QDomElement &element) { QString text; QDomNode child = element.firstChild(); while (!child.isNull()) { if (child.isText()) text += child.toText().nodeValue(); child = child.nextSibling(); } return text; } /*! */ void Tree::resolveIndex() { QPair pair; foreach (pair, priv->basesList) { foreach (const QString &base, pair.second.split(",")) { Node *baseClass = root()->findNode(base, Node::Class); if (baseClass) { pair.first->addBaseClass(Node::Public, static_cast(baseClass)); } } } QPair relatedPair; foreach (relatedPair, priv->relatedList) { Node *classNode = root()->findNode(relatedPair.second, Node::Class); if (classNode) relatedPair.first->setRelates(static_cast(classNode)); } } /*! Generate the index section with the given \a writer for the \a node specified, returning true if an element was written; otherwise returns false. */ bool Tree::generateIndexSection(QXmlStreamWriter &writer, const Node *node, bool generateInternalNodes) const { if (!node->url().isEmpty()) return false; QString nodeName; switch (node->type()) { case Node::Namespace: nodeName = "namespace"; break; case Node::Class: nodeName = "class"; break; case Node::Fake: nodeName = "page"; break; case Node::Enum: nodeName = "enum"; break; case Node::Typedef: nodeName = "typedef"; break; case Node::Property: nodeName = "property"; break; case Node::Function: nodeName = "function"; break; case Node::Variable: nodeName = "variable"; break; case Node::Target: nodeName = "target"; break; case Node::QmlProperty: nodeName = "qmlproperty"; break; case Node::QmlSignal: nodeName = "qmlsignal"; break; case Node::QmlMethod: nodeName = "qmlmethod"; break; default: return false; } QString access; switch (node->access()) { case Node::Public: access = "public"; break; case Node::Protected: access = "protected"; break; case Node::Private: // Do not include private non-internal nodes in the index. // (Internal public and protected nodes are marked as private // by qdoc. We can check their internal status to determine // whether they were really private to begin with.) if (node->status() == Node::Internal && generateInternalNodes) access = "internal"; else return false; break; default: return false; } QString objName = node->name(); // Special case: only the root node should have an empty name. if (objName.isEmpty() && node != root()) return false; writer.writeStartElement(nodeName); QXmlStreamAttributes attributes; writer.writeAttribute("access", access); if (node->type() != Node::Fake) { QString threadSafety; switch (node->threadSafeness()) { case Node::NonReentrant: threadSafety = "non-reentrant"; break; case Node::Reentrant: threadSafety = "reentrant"; break; case Node::ThreadSafe: threadSafety = "thread safe"; break; case Node::UnspecifiedSafeness: default: threadSafety = "unspecified"; break; } writer.writeAttribute("threadsafety", threadSafety); } QString status; switch (node->status()) { case Node::Compat: status = "compat"; break; case Node::Obsolete: status = "obsolete"; break; case Node::Deprecated: status = "deprecated"; break; case Node::Preliminary: status = "preliminary"; break; case Node::Commendable: status = "commendable"; break; case Node::Internal: status = "internal"; break; case Node::Main: default: status = "main"; break; } writer.writeAttribute("status", status); writer.writeAttribute("name", objName); QString fullName = fullDocumentName(node); if (fullName != objName) writer.writeAttribute("fullname", fullName); writer.writeAttribute("href", fullDocumentLocation(node)); if ((node->type() != Node::Fake) && (!node->isQmlNode())) writer.writeAttribute("location", node->location().fileName()); switch (node->type()) { case Node::Class: { // Classes contain information about their base classes. const ClassNode *classNode = static_cast(node); QList bases = classNode->baseClasses(); QSet baseStrings; foreach (const RelatedClass &related, bases) { ClassNode *baseClassNode = related.node; baseStrings.insert(baseClassNode->name()); } writer.writeAttribute("bases", QStringList(baseStrings.toList()).join(",")); writer.writeAttribute("module", node->moduleName()); } break; case Node::Namespace: writer.writeAttribute("module", node->moduleName()); break; case Node::Fake: { /* Fake nodes (such as manual pages) contain subtypes, titles and other attributes. */ const FakeNode *fakeNode = static_cast(node); switch (fakeNode->subType()) { case Node::Example: writer.writeAttribute("subtype", "example"); break; case Node::HeaderFile: writer.writeAttribute("subtype", "header"); break; case Node::File: writer.writeAttribute("subtype", "file"); break; case Node::Group: writer.writeAttribute("subtype", "group"); break; case Node::Module: writer.writeAttribute("subtype", "module"); break; case Node::Page: writer.writeAttribute("subtype", "page"); break; case Node::ExternalPage: writer.writeAttribute("subtype", "externalpage"); break; case Node::QmlClass: writer.writeAttribute("subtype", "qmlclass"); break; case Node::QmlBasicType: writer.writeAttribute("subtype", "qmlbasictype"); break; default: break; } writer.writeAttribute("title", fakeNode->title()); writer.writeAttribute("fulltitle", fakeNode->fullTitle()); writer.writeAttribute("subtitle", fakeNode->subTitle()); writer.writeAttribute("location", fakeNode->doc().location().fileName()); } break; case Node::Function: { /* Function nodes contain information about the type of function being described. */ const FunctionNode *functionNode = static_cast(node); switch (functionNode->virtualness()) { case FunctionNode::NonVirtual: writer.writeAttribute("virtual", "non"); break; case FunctionNode::ImpureVirtual: writer.writeAttribute("virtual", "impure"); break; case FunctionNode::PureVirtual: writer.writeAttribute("virtual", "pure"); break; default: break; } switch (functionNode->metaness()) { case FunctionNode::Plain: writer.writeAttribute("meta", "plain"); break; case FunctionNode::Signal: writer.writeAttribute("meta", "signal"); break; case FunctionNode::Slot: writer.writeAttribute("meta", "slot"); break; case FunctionNode::Ctor: writer.writeAttribute("meta", "constructor"); break; case FunctionNode::Dtor: writer.writeAttribute("meta", "destructor"); break; case FunctionNode::MacroWithParams: writer.writeAttribute("meta", "macrowithparams"); break; case FunctionNode::MacroWithoutParams: writer.writeAttribute("meta", "macrowithoutparams"); break; default: break; } writer.writeAttribute("const", functionNode->isConst()?"true":"false"); writer.writeAttribute("static", functionNode->isStatic()?"true":"false"); writer.writeAttribute("overload", functionNode->isOverload()?"true":"false"); if (functionNode->isOverload()) writer.writeAttribute("overload-number", QString::number(functionNode->overloadNumber())); if (functionNode->relates()) writer.writeAttribute("relates", functionNode->relates()->name()); const PropertyNode *propertyNode = functionNode->associatedProperty(); if (propertyNode) writer.writeAttribute("associated-property", propertyNode->name()); writer.writeAttribute("type", functionNode->returnType()); } break; case Node::QmlProperty: { const QmlPropertyNode *qpn = static_cast(node); writer.writeAttribute("type", qpn->dataType()); } break; case Node::Property: { const PropertyNode *propertyNode = static_cast(node); writer.writeAttribute("type", propertyNode->dataType()); foreach (const Node *fnNode, propertyNode->getters()) { if (fnNode) { const FunctionNode *functionNode = static_cast(fnNode); writer.writeStartElement("getter"); writer.writeAttribute("name", functionNode->name()); writer.writeEndElement(); // getter } } foreach (const Node *fnNode, propertyNode->setters()) { if (fnNode) { const FunctionNode *functionNode = static_cast(fnNode); writer.writeStartElement("setter"); writer.writeAttribute("name", functionNode->name()); writer.writeEndElement(); // getter } } foreach (const Node *fnNode, propertyNode->resetters()) { if (fnNode) { const FunctionNode *functionNode = static_cast(fnNode); writer.writeStartElement("resetter"); writer.writeAttribute("name", functionNode->name()); writer.writeEndElement(); // getter } } } break; case Node::Variable: { const VariableNode *variableNode = static_cast(node); writer.writeAttribute("type", variableNode->dataType()); writer.writeAttribute("static", variableNode->isStatic() ? "true" : "false"); } break; default: break; } // Inner nodes and function nodes contain child nodes of some sort, either // actual child nodes or function parameters. For these, we close the // opening tag, create child elements, then add a closing tag for the // element. Elements for all other nodes are closed in the opening tag. if (node->isInnerNode()) { const InnerNode *inner = static_cast(node); // For internal pages, we canonicalize the target, keyword and content // item names so that they can be used by qdoc for other sets of // documentation. // The reason we do this here is that we don't want to ruin // externally composed indexes, containing non-qdoc-style target names // when reading in indexes. if (inner->doc().hasTargets()) { bool external = false; if (inner->type() == Node::Fake) { const FakeNode *fakeNode = static_cast(inner); if (fakeNode->subType() == Node::ExternalPage) external = true; } foreach (const Atom *target, inner->doc().targets()) { QString targetName = target->string(); if (!external) targetName = Doc::canonicalTitle(targetName); writer.writeStartElement("target"); writer.writeAttribute("name", targetName); writer.writeEndElement(); // target } } if (inner->doc().hasKeywords()) { foreach (const Atom *keyword, inner->doc().keywords()) { writer.writeStartElement("keyword"); writer.writeAttribute("name", Doc::canonicalTitle(keyword->string())); writer.writeEndElement(); // keyword } } if (inner->doc().hasTableOfContents()) { for (int i = 0; i < inner->doc().tableOfContents().size(); ++i) { Atom *item = inner->doc().tableOfContents()[i]; int level = inner->doc().tableOfContentsLevels()[i]; QString title = Text::sectionHeading(item).toString(); writer.writeStartElement("contents"); writer.writeAttribute("name", Doc::canonicalTitle(title)); writer.writeAttribute("title", title); writer.writeAttribute("level", QString::number(level)); writer.writeEndElement(); // contents } } } else if (node->type() == Node::Function) { const FunctionNode *functionNode = static_cast(node); // Write a signature attribute for convenience. QStringList signatureList; QStringList resolvedParameters; foreach (const Parameter ¶meter, functionNode->parameters()) { QString leftType = parameter.leftType(); const Node *leftNode = const_cast(this)->findNode(parameter.leftType().split("::"), Node::Typedef, 0, SearchBaseClasses|NonFunction); if (!leftNode) { leftNode = const_cast(this)->findNode( parameter.leftType().split("::"), Node::Typedef, node->parent(), SearchBaseClasses|NonFunction); } if (leftNode) { if (leftNode->type() == Node::Typedef) { const TypedefNode *typedefNode = static_cast(leftNode); if (typedefNode->associatedEnum()) { leftType = "QFlags<"+fullDocumentName(typedefNode->associatedEnum())+">"; } } else leftType = fullDocumentName(leftNode); } resolvedParameters.append(leftType); signatureList.append(leftType + " " + parameter.name()); } QString signature = functionNode->name()+"("+signatureList.join(", ")+")"; if (functionNode->isConst()) signature += " const"; writer.writeAttribute("signature", signature); for (int i = 0; i < functionNode->parameters().size(); ++i) { Parameter parameter = functionNode->parameters()[i]; writer.writeStartElement("parameter"); writer.writeAttribute("left", resolvedParameters[i]); writer.writeAttribute("right", parameter.rightType()); writer.writeAttribute("name", parameter.name()); writer.writeAttribute("default", parameter.defaultValue()); writer.writeEndElement(); // parameter } } else if (node->type() == Node::Enum) { const EnumNode *enumNode = static_cast(node); if (enumNode->flagsType()) { writer.writeAttribute("typedef", fullDocumentName(enumNode->flagsType())); } foreach (const EnumItem &item, enumNode->items()) { writer.writeStartElement("value"); writer.writeAttribute("name", item.name()); writer.writeAttribute("value", item.value()); writer.writeEndElement(); // value } } else if (node->type() == Node::Typedef) { const TypedefNode *typedefNode = static_cast(node); if (typedefNode->associatedEnum()) { writer.writeAttribute("enum", fullDocumentName(typedefNode->associatedEnum())); } } return true; } /*! */ void Tree::generateIndexSections(QXmlStreamWriter &writer, const Node *node, bool generateInternalNodes) const { if (generateIndexSection(writer, node, generateInternalNodes)) { if (node->isInnerNode()) { const InnerNode *inner = static_cast(node); foreach (const Node *child, inner->childNodes()) { /* Don't generate anything for a QML property group node. It is just a place holder for a collection of QML property nodes. Recurse to its children, which are the QML property nodes. */ if (child->subType() == Node::QmlPropertyGroup) { const InnerNode *pgn = static_cast(child); foreach (const Node *c, pgn->childNodes()) { generateIndexSections(writer, c, generateInternalNodes); } } else generateIndexSections(writer, child, generateInternalNodes); } /* foreach (const Node *child, inner->relatedNodes()) { QDomElement childElement = generateIndexSections(document, child); element.appendChild(childElement); } */ } writer.writeEndElement(); } } /*! Outputs an index file. */ void Tree::generateIndex(const QString &fileName, const QString &url, const QString &title, bool generateInternalNodes) const { QFile file(fileName); if (!file.open(QFile::WriteOnly | QFile::Text)) return ; QXmlStreamWriter writer(&file); writer.setAutoFormatting(true); writer.writeStartDocument(); writer.writeDTD(""); writer.writeStartElement("INDEX"); writer.writeAttribute("url", url); writer.writeAttribute("title", title); writer.writeAttribute("version", version()); generateIndexSections(writer, root(), generateInternalNodes); writer.writeEndElement(); // INDEX writer.writeEndElement(); // QDOCINDEX writer.writeEndDocument(); file.close(); } /*! Generate the tag file section with the given \a writer for the \a node specified, returning true if an element was written; otherwise returns false. */ void Tree::generateTagFileCompounds(QXmlStreamWriter &writer, const InnerNode *inner) const { foreach (const Node *node, inner->childNodes()) { if (!node->url().isEmpty()) continue; QString kind; switch (node->type()) { case Node::Namespace: kind = "namespace"; break; case Node::Class: kind = "class"; break; case Node::Enum: case Node::Typedef: case Node::Property: case Node::Function: case Node::Variable: case Node::Target: default: continue; } QString access; switch (node->access()) { case Node::Public: access = "public"; break; case Node::Protected: access = "protected"; break; case Node::Private: default: continue; } QString objName = node->name(); // Special case: only the root node should have an empty name. if (objName.isEmpty() && node != root()) continue; // *** Write the starting tag for the element here. *** writer.writeStartElement("compound"); writer.writeAttribute("kind", kind); if (node->type() == Node::Class) { writer.writeTextElement("name", fullDocumentName(node)); writer.writeTextElement("filename", fullDocumentLocation(node)); // Classes contain information about their base classes. const ClassNode *classNode = static_cast(node); QList bases = classNode->baseClasses(); foreach (const RelatedClass &related, bases) { ClassNode *baseClassNode = related.node; writer.writeTextElement("base", baseClassNode->name()); } // Recurse to write all members. generateTagFileMembers(writer, static_cast(node)); writer.writeEndElement(); // Recurse to write all compounds. generateTagFileCompounds(writer, static_cast(node)); } else { writer.writeTextElement("name", fullDocumentName(node)); writer.writeTextElement("filename", fullDocumentLocation(node)); // Recurse to write all members. generateTagFileMembers(writer, static_cast(node)); writer.writeEndElement(); // Recurse to write all compounds. generateTagFileCompounds(writer, static_cast(node)); } } } /*! */ void Tree::generateTagFileMembers(QXmlStreamWriter &writer, const InnerNode *inner) const { foreach (const Node *node, inner->childNodes()) { if (!node->url().isEmpty()) continue; QString nodeName; QString kind; switch (node->type()) { case Node::Enum: nodeName = "member"; kind = "enum"; break; case Node::Typedef: nodeName = "member"; kind = "typedef"; break; case Node::Property: nodeName = "member"; kind = "property"; break; case Node::Function: nodeName = "member"; kind = "function"; break; case Node::Namespace: nodeName = "namespace"; break; case Node::Class: nodeName = "class"; break; case Node::Variable: case Node::Target: default: continue; } QString access; switch (node->access()) { case Node::Public: access = "public"; break; case Node::Protected: access = "protected"; break; case Node::Private: default: continue; } QString objName = node->name(); // Special case: only the root node should have an empty name. if (objName.isEmpty() && node != root()) continue; // *** Write the starting tag for the element here. *** writer.writeStartElement(nodeName); if (!kind.isEmpty()) writer.writeAttribute("kind", kind); switch (node->type()) { case Node::Class: writer.writeCharacters(fullDocumentName(node)); writer.writeEndElement(); break; case Node::Namespace: writer.writeCharacters(fullDocumentName(node)); writer.writeEndElement(); break; case Node::Function: { /* Function nodes contain information about the type of function being described. */ const FunctionNode *functionNode = static_cast(node); writer.writeAttribute("protection", access); switch (functionNode->virtualness()) { case FunctionNode::NonVirtual: writer.writeAttribute("virtualness", "non"); break; case FunctionNode::ImpureVirtual: writer.writeAttribute("virtualness", "virtual"); break; case FunctionNode::PureVirtual: writer.writeAttribute("virtual", "pure"); break; default: break; } writer.writeAttribute("static", functionNode->isStatic() ? "yes" : "no"); if (functionNode->virtualness() == FunctionNode::NonVirtual) writer.writeTextElement("type", functionNode->returnType()); else writer.writeTextElement("type", "virtual " + functionNode->returnType()); writer.writeTextElement("name", objName); QStringList pieces = fullDocumentLocation(node).split("#"); writer.writeTextElement("anchorfile", pieces[0]); writer.writeTextElement("anchor", pieces[1]); // Write a signature attribute for convenience. QStringList signatureList; foreach (const Parameter ¶meter, functionNode->parameters()) { QString leftType = parameter.leftType(); const Node *leftNode = const_cast(this)->findNode(parameter.leftType().split("::"), Node::Typedef, 0, SearchBaseClasses|NonFunction); if (!leftNode) { leftNode = const_cast(this)->findNode( parameter.leftType().split("::"), Node::Typedef, node->parent(), SearchBaseClasses|NonFunction); } if (leftNode) { const TypedefNode *typedefNode = static_cast(leftNode); if (typedefNode->associatedEnum()) { leftType = "QFlags<"+fullDocumentName(typedefNode->associatedEnum())+">"; } } signatureList.append(leftType + " " + parameter.name()); } QString signature = "("+signatureList.join(", ")+")"; if (functionNode->isConst()) signature += " const"; if (functionNode->virtualness() == FunctionNode::PureVirtual) signature += " = 0"; writer.writeTextElement("arglist", signature); } writer.writeEndElement(); // member break; case Node::Property: { const PropertyNode *propertyNode = static_cast(node); writer.writeAttribute("type", propertyNode->dataType()); writer.writeTextElement("name", objName); QStringList pieces = fullDocumentLocation(node).split("#"); writer.writeTextElement("anchorfile", pieces[0]); writer.writeTextElement("anchor", pieces[1]); writer.writeTextElement("arglist", ""); } writer.writeEndElement(); // member break; case Node::Enum: { const EnumNode *enumNode = static_cast(node); writer.writeTextElement("name", objName); QStringList pieces = fullDocumentLocation(node).split("#"); writer.writeTextElement("anchor", pieces[1]); writer.writeTextElement("arglist", ""); writer.writeEndElement(); // member for (int i = 0; i < enumNode->items().size(); ++i) { EnumItem item = enumNode->items().value(i); writer.writeStartElement("member"); writer.writeAttribute("name", item.name()); writer.writeTextElement("anchor", pieces[1]); writer.writeTextElement("arglist", ""); writer.writeEndElement(); // member } } break; case Node::Typedef: { const TypedefNode *typedefNode = static_cast(node); if (typedefNode->associatedEnum()) writer.writeAttribute("type", fullDocumentName(typedefNode->associatedEnum())); else writer.writeAttribute("type", ""); writer.writeTextElement("name", objName); QStringList pieces = fullDocumentLocation(node).split("#"); writer.writeTextElement("anchorfile", pieces[0]); writer.writeTextElement("anchor", pieces[1]); writer.writeTextElement("arglist", ""); } writer.writeEndElement(); // member break; case Node::Variable: case Node::Target: default: break; } } } /*! */ void Tree::generateTagFile(const QString &fileName) const { QFile file(fileName); if (!file.open(QFile::WriteOnly | QFile::Text)) return ; QXmlStreamWriter writer(&file); writer.setAutoFormatting(true); writer.writeStartDocument(); writer.writeStartElement("tagfile"); generateTagFileCompounds(writer, root()); writer.writeEndElement(); // tagfile writer.writeEndDocument(); file.close(); } /*! */ void Tree::addExternalLink(const QString &url, const Node *relative) { FakeNode *fakeNode = new FakeNode(root(), url, Node::ExternalPage); fakeNode->setAccess(Node::Public); // Create some content for the node. QSet emptySet; Location location(relative->doc().location()); Doc doc(location, location, " ", emptySet); // placeholder fakeNode->setDoc(doc); } /*! Returns the full document location for HTML-based documentation. This should be moved into the HTML generator. */ QString Tree::fullDocumentLocation(const Node *node) const { if (!node) return ""; if (!node->url().isEmpty()) return node->url(); QString parentName; QString anchorRef; if (node->type() == Node::Namespace) { // The root namespace has no name - check for this before creating // an attribute containing the location of any documentation. if (!node->fileBase().isEmpty()) parentName = node->fileBase() + ".html"; else return ""; } else if (node->type() == Node::Fake) { #ifdef QDOC_QML if ((node->subType() == Node::QmlClass) || (node->subType() == Node::QmlBasicType)) { QString fb = node->fileBase(); if (fb.startsWith(QLatin1String("qml-"))) return fb + ".html"; else return "qml-" + node->fileBase() + ".html"; } else #endif parentName = node->fileBase() + ".html"; } else if (node->fileBase().isEmpty()) return ""; Node *parentNode = 0; if ((parentNode = node->relates())) parentName = fullDocumentLocation(node->relates()); else if ((parentNode = node->parent())) { if (parentNode->subType() == Node::QmlPropertyGroup) { parentNode = parentNode->parent(); parentName = fullDocumentLocation(parentNode); } else parentName = fullDocumentLocation(node->parent()); } switch (node->type()) { case Node::Class: case Node::Namespace: if (parentNode && !parentNode->name().isEmpty()) parentName = parentName.replace(".html", "") + "-" + node->fileBase().toLower() + ".html"; else parentName = node->fileBase() + ".html"; break; case Node::Function: { /* Functions can be destructors, overloaded, or have associated properties. */ const FunctionNode *functionNode = static_cast(node); if (functionNode->metaness() == FunctionNode::Dtor) anchorRef = "#dtor." + functionNode->name().mid(1); else if (functionNode->associatedProperty()) return fullDocumentLocation(functionNode->associatedProperty()); else if (functionNode->overloadNumber() > 1) anchorRef = "#" + functionNode->name() + "-" + QString::number(functionNode->overloadNumber()); else anchorRef = "#" + functionNode->name(); } /* Use node->name() instead of node->fileBase() as the latter returns the name in lower-case. For HTML anchors, we need to preserve the case. */ break; case Node::Enum: anchorRef = "#" + node->name() + "-enum"; break; case Node::Typedef: anchorRef = "#" + node->name() + "-typedef"; break; case Node::Property: anchorRef = "#" + node->name() + "-prop"; break; case Node::QmlProperty: anchorRef = "#" + node->name() + "-prop"; break; case Node::QmlSignal: anchorRef = "#" + node->name() + "-signal"; break; case Node::QmlMethod: anchorRef = "#" + node->name() + "-method"; break; case Node::Variable: anchorRef = "#" + node->name() + "-var"; break; case Node::Target: anchorRef = "#" + Doc::canonicalTitle(node->name()); break; case Node::Fake: { /* Use node->fileBase() for fake nodes because they are represented by pages whose file names are lower-case. */ parentName = node->fileBase(); parentName.replace("/", "-").replace(".", "-"); parentName += ".html"; } break; default: break; } // Various objects can be compat (deprecated) or obsolete. if (node->type() != Node::Class && node->type() != Node::Namespace) { switch (node->status()) { case Node::Compat: parentName.replace(".html", "-qt3.html"); break; case Node::Obsolete: parentName.replace(".html", "-obsolete.html"); break; default: ; } } return parentName.toLower() + anchorRef; } /*! Construct the full document name for \a node and return the name. */ QString Tree::fullDocumentName(const Node *node) const { if (!node) return ""; QStringList pieces; const Node *n = node; do { if (!n->name().isEmpty() && ((n->type() != Node::Fake) || (n->subType() != Node::QmlPropertyGroup))) pieces.insert(0, n->name()); if ((n->type() == Node::Fake) && (n->subType() != Node::QmlPropertyGroup)) break; // Examine the parent node if one exists. if (n->parent()) n = n->parent(); else break; } while (true); // Create a name based on the type of the ancestor node. if (n->type() == Node::Fake) return pieces.join("#"); else return pieces.join("::"); } QT_END_NAMESPACE