| /**************************************************************************** |
| ** |
| ** Copyright (C) 2011 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$ |
| ** GNU Lesser General Public License Usage |
| ** This file may be used under the terms of the GNU Lesser General Public |
| ** License version 2.1 as published by the Free Software Foundation and |
| ** appearing in the file LICENSE.LGPL included in the packaging of this |
| ** file. Please review the following information to ensure the GNU Lesser |
| ** General Public License version 2.1 requirements will be met: |
| ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. |
| ** |
| ** In addition, as a special exception, Nokia gives you certain additional |
| ** rights. These rights are described in the Nokia Qt LGPL Exception |
| ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU General |
| ** Public License version 3.0 as published by the Free Software Foundation |
| ** and appearing in the file LICENSE.GPL included in the packaging of this |
| ** file. Please review the following information to ensure the GNU General |
| ** Public License version 3.0 requirements will be met: |
| ** http://www.gnu.org/copyleft/gpl.html. |
| ** |
| ** Other Usage |
| ** Alternatively, this file may be used in accordance with the terms and |
| ** conditions contained in a signed written agreement between you and Nokia. |
| ** |
| ** |
| ** |
| ** |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include <QMetaObject> |
| #include "codemarker.h" |
| #include "config.h" |
| #include "node.h" |
| |
| #include <stdio.h> |
| |
| QT_BEGIN_NAMESPACE |
| |
| QString CodeMarker::defaultLang; |
| QList<CodeMarker *> CodeMarker::markers; |
| |
| /*! |
| When a code marker constructs itself, it puts itself into |
| the static list of code markers. All the code markers in |
| the static list get initialized in initialize(), which is |
| not called until after the qdoc configuration file has |
| been read. |
| */ |
| CodeMarker::CodeMarker() |
| { |
| markers.prepend(this); |
| } |
| |
| /*! |
| When a code marker destroys itself, it removes itself from |
| the static list of code markers. |
| */ |
| CodeMarker::~CodeMarker() |
| { |
| markers.removeAll(this); |
| } |
| |
| /*! |
| A code market performs no initialization by default. Marker-specific |
| initialization is performed in subclasses. |
| */ |
| void CodeMarker::initializeMarker(const Config &config) |
| { |
| } |
| |
| /*! |
| Terminating a code marker is trivial. |
| */ |
| void CodeMarker::terminateMarker() |
| { |
| // nothing. |
| } |
| |
| /*! |
| All the code markers in the static list are initialized |
| here, after the qdoc configuration file has been loaded. |
| */ |
| void CodeMarker::initialize(const Config& config) |
| { |
| defaultLang = config.getString(QLatin1String(CONFIG_LANGUAGE)); |
| QList<CodeMarker *>::ConstIterator m = markers.begin(); |
| while (m != markers.end()) { |
| (*m)->initializeMarker(config); |
| ++m; |
| } |
| } |
| |
| /*! |
| All the code markers in the static list are terminated here. |
| */ |
| void CodeMarker::terminate() |
| { |
| QList<CodeMarker *>::ConstIterator m = markers.begin(); |
| while (m != markers.end()) { |
| (*m)->terminateMarker(); |
| ++m; |
| } |
| } |
| |
| CodeMarker *CodeMarker::markerForCode(const QString& code) |
| { |
| CodeMarker *defaultMarker = markerForLanguage(defaultLang); |
| if (defaultMarker != 0 && defaultMarker->recognizeCode(code)) |
| return defaultMarker; |
| |
| QList<CodeMarker *>::ConstIterator m = markers.begin(); |
| while (m != markers.end()) { |
| if ((*m)->recognizeCode(code)) |
| return *m; |
| ++m; |
| } |
| return defaultMarker; |
| } |
| |
| CodeMarker *CodeMarker::markerForFileName(const QString& fileName) |
| { |
| CodeMarker *defaultMarker = markerForLanguage(defaultLang); |
| int dot = -1; |
| while ((dot = fileName.lastIndexOf(QLatin1Char('.'), dot)) != -1) { |
| QString ext = fileName.mid(dot + 1); |
| if (defaultMarker != 0 && defaultMarker->recognizeExtension(ext)) |
| return defaultMarker; |
| QList<CodeMarker *>::ConstIterator m = markers.begin(); |
| while (m != markers.end()) { |
| if ((*m)->recognizeExtension(ext)) |
| return *m; |
| ++m; |
| } |
| --dot; |
| } |
| return defaultMarker; |
| } |
| |
| CodeMarker *CodeMarker::markerForLanguage(const QString& lang) |
| { |
| QList<CodeMarker *>::ConstIterator m = markers.begin(); |
| while (m != markers.end()) { |
| if ((*m)->recognizeLanguage(lang)) |
| return *m; |
| ++m; |
| } |
| return 0; |
| } |
| |
| const Node *CodeMarker::nodeForString(const QString& string) |
| { |
| if (sizeof(const Node *) == sizeof(uint)) { |
| return reinterpret_cast<const Node *>(string.toUInt()); |
| } |
| else { |
| return reinterpret_cast<const Node *>(string.toULongLong()); |
| } |
| } |
| |
| QString CodeMarker::stringForNode(const Node *node) |
| { |
| if (sizeof(const Node *) == sizeof(ulong)) { |
| return QString::number(reinterpret_cast<quintptr>(node)); |
| } |
| else { |
| return QString::number(reinterpret_cast<qulonglong>(node)); |
| } |
| } |
| |
| static const QString samp = QLatin1String("&"); |
| static const QString slt = QLatin1String("<"); |
| static const QString sgt = QLatin1String(">"); |
| static const QString squot = QLatin1String("""); |
| |
| QString CodeMarker::protect(const QString& str) |
| { |
| int n = str.length(); |
| QString marked; |
| marked.reserve(n * 2 + 30); |
| const QChar *data = str.constData(); |
| for (int i = 0; i != n; ++i) { |
| switch (data[i].unicode()) { |
| case '&': marked += samp; break; |
| case '<': marked += slt; break; |
| case '>': marked += sgt; break; |
| case '"': marked += squot; break; |
| default : marked += data[i]; |
| } |
| } |
| return marked; |
| } |
| |
| QString CodeMarker::typified(const QString &string) |
| { |
| QString result; |
| QString pendingWord; |
| |
| for (int i = 0; i <= string.size(); ++i) { |
| QChar ch; |
| if (i != string.size()) |
| ch = string.at(i); |
| |
| QChar lower = ch.toLower(); |
| if ((lower >= QLatin1Char('a') && lower <= QLatin1Char('z')) |
| || ch.digitValue() >= 0 || ch == QLatin1Char('_') |
| || ch == QLatin1Char(':')) { |
| pendingWord += ch; |
| } |
| else { |
| if (!pendingWord.isEmpty()) { |
| bool isProbablyType = (pendingWord != QLatin1String("const")); |
| if (isProbablyType) |
| result += QLatin1String("<@type>"); |
| result += pendingWord; |
| if (isProbablyType) |
| result += QLatin1String("</@type>"); |
| } |
| pendingWord.clear(); |
| |
| switch (ch.unicode()) { |
| case '\0': |
| break; |
| case '&': |
| result += QLatin1String("&"); |
| break; |
| case '<': |
| result += QLatin1String("<"); |
| break; |
| case '>': |
| result += QLatin1String(">"); |
| break; |
| default: |
| result += ch; |
| } |
| } |
| } |
| return result; |
| } |
| |
| QString CodeMarker::taggedNode(const Node* node) |
| { |
| QString tag; |
| QString name = node->name(); |
| |
| switch (node->type()) { |
| case Node::Namespace: |
| tag = QLatin1String("@namespace"); |
| break; |
| case Node::Class: |
| tag = QLatin1String("@class"); |
| break; |
| case Node::Enum: |
| tag = QLatin1String("@enum"); |
| break; |
| case Node::Typedef: |
| tag = QLatin1String("@typedef"); |
| break; |
| case Node::Function: |
| tag = QLatin1String("@function"); |
| break; |
| case Node::Property: |
| tag = QLatin1String("@property"); |
| break; |
| #ifdef QDOC_QML |
| case Node::Fake: |
| if (node->subType() == Node::QmlClass) { |
| if (node->name().startsWith(QLatin1String("QML:"))) |
| name = name.mid(4); // remove the "QML:" prefix |
| } |
| tag = QLatin1String("@property"); |
| break; |
| #endif |
| default: |
| tag = QLatin1String("@unknown"); |
| break; |
| } |
| return QLatin1Char('<') + tag + QLatin1Char('>') + protect(name) |
| + QLatin1String("</") + tag + QLatin1Char('>'); |
| } |
| |
| #ifdef QDOC_QML |
| QString CodeMarker::taggedQmlNode(const Node* node) |
| { |
| QString tag; |
| switch (node->type()) { |
| case Node::QmlProperty: |
| tag = QLatin1String("@property"); |
| break; |
| case Node::QmlSignal: |
| tag = QLatin1String("@signal"); |
| break; |
| case Node::QmlMethod: |
| tag = QLatin1String("@method"); |
| break; |
| default: |
| tag = QLatin1String("@unknown"); |
| break; |
| } |
| return QLatin1Char('<') + tag + QLatin1Char('>') + protect(node->name()) |
| + QLatin1String("</") + tag + QLatin1Char('>'); |
| } |
| #endif |
| |
| QString CodeMarker::linkTag(const Node *node, const QString& body) |
| { |
| return QLatin1String("<@link node=\"") + stringForNode(node) |
| + QLatin1String("\">") + body + QLatin1String("</@link>"); |
| } |
| |
| QString CodeMarker::sortName(const Node *node) |
| { |
| QString nodeName = node->name(); |
| int numDigits = 0; |
| for (int i = nodeName.size() - 1; i > 0; --i) { |
| if (nodeName.at(i).digitValue() == -1) |
| break; |
| ++numDigits; |
| } |
| |
| // we want 'qint8' to appear before 'qint16' |
| if (numDigits > 0) { |
| for (int i = 0; i < 4 - numDigits; ++i) |
| nodeName.insert(nodeName.size()-numDigits-1, QLatin1String("0")); |
| } |
| |
| if (node->type() == Node::Function) { |
| const FunctionNode *func = static_cast<const FunctionNode *>(node); |
| QString sortNo; |
| if (func->metaness() == FunctionNode::Ctor) { |
| sortNo = QLatin1String("C"); |
| } |
| else if (func->metaness() == FunctionNode::Dtor) { |
| sortNo = QLatin1String("D"); |
| } |
| else { |
| if (nodeName.startsWith(QLatin1String("operator")) |
| && nodeName.length() > 8 |
| && !nodeName[8].isLetterOrNumber()) |
| sortNo = QLatin1String("F"); |
| else |
| sortNo = QLatin1String("E"); |
| } |
| return sortNo + nodeName + QLatin1Char(' ') |
| + QString::number(func->overloadNumber(), 36); |
| } |
| |
| if (node->type() == Node::Class) |
| return QLatin1Char('A') + nodeName; |
| |
| if (node->type() == Node::Property || node->type() == Node::Variable) |
| return QLatin1Char('E') + nodeName; |
| |
| return QLatin1Char('B') + nodeName; |
| } |
| |
| void CodeMarker::insert(FastSection &fastSection, |
| Node *node, |
| SynopsisStyle style, |
| Status status) |
| { |
| bool irrelevant = false; |
| bool inheritedMember = false; |
| if (!node->relates()) { |
| if (node->parent() != (const InnerNode*)fastSection.innerNode) { |
| if (node->type() != Node::QmlProperty) |
| inheritedMember = true; |
| } |
| } |
| |
| if (node->access() == Node::Private) { |
| irrelevant = true; |
| } |
| else if (node->type() == Node::Function) { |
| FunctionNode *func = (FunctionNode *) node; |
| irrelevant = (inheritedMember |
| && (func->metaness() == FunctionNode::Ctor || |
| func->metaness() == FunctionNode::Dtor)); |
| } |
| else if (node->type() == Node::Class || node->type() == Node::Enum |
| || node->type() == Node::Typedef) { |
| irrelevant = (inheritedMember && style != SeparateList); |
| if (!irrelevant && style == Detailed && node->type() == Node::Typedef) { |
| const TypedefNode* typedeffe = static_cast<const TypedefNode*>(node); |
| if (typedeffe->associatedEnum()) |
| irrelevant = true; |
| } |
| } |
| |
| if (!irrelevant) { |
| if (status == Compat) { |
| irrelevant = (node->status() != Node::Compat); |
| } |
| else if (status == Obsolete) { |
| irrelevant = (node->status() != Node::Obsolete); |
| } |
| else { |
| irrelevant = (node->status() == Node::Compat || |
| node->status() == Node::Obsolete); |
| } |
| } |
| |
| if (!irrelevant) { |
| if (!inheritedMember || style == SeparateList) { |
| QString key = sortName(node); |
| if (!fastSection.memberMap.contains(key)) |
| fastSection.memberMap.insert(key, node); |
| } |
| else { |
| if (node->parent()->type() == Node::Class) { |
| if (fastSection.inherited.isEmpty() |
| || fastSection.inherited.last().first != node->parent()) { |
| QPair<ClassNode *, int> p((ClassNode *)node->parent(), 0); |
| fastSection.inherited.append(p); |
| } |
| fastSection.inherited.last().second++; |
| } |
| } |
| } |
| } |
| |
| /*! |
| Returns true if \a node represents a reimplemented member function. |
| If it is, then it is inserted in the reimplemented member map in the |
| section \a fs. And, the test is only performed if \a status is \e OK. |
| Otherwise, false is returned. |
| */ |
| bool CodeMarker::insertReimpFunc(FastSection& fs, Node* node, Status status) |
| { |
| if (node->access() == Node::Private) |
| return false; |
| |
| const FunctionNode* fn = static_cast<const FunctionNode*>(node); |
| if ((fn->reimplementedFrom() != 0) && (status == Okay)) { |
| bool inherited = (!fn->relates() && (fn->parent() != (const InnerNode*)fs.innerNode)); |
| if (!inherited) { |
| QString key = sortName(fn); |
| if (!fs.reimpMemberMap.contains(key)) { |
| fs.reimpMemberMap.insert(key,node); |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| /*! |
| If \a fs is not empty, convert it to a Section and append |
| the new Section to \a sectionList. |
| */ |
| void CodeMarker::append(QList<Section>& sectionList, const FastSection& fs) |
| { |
| if (!fs.isEmpty()) { |
| Section section(fs.name,fs.divClass,fs.singularMember,fs.pluralMember); |
| section.members = fs.memberMap.values(); |
| section.reimpMembers = fs.reimpMemberMap.values(); |
| section.inherited = fs.inherited; |
| sectionList.append(section); |
| } |
| } |
| |
| static QString encode(const QString &string) |
| { |
| #if 0 |
| QString result = string; |
| |
| for (int i = string.size() - 1; i >= 0; --i) { |
| uint ch = string.at(i).unicode(); |
| if (ch > 0xFF) |
| ch = '?'; |
| if ((ch - '0') >= 10 && (ch - 'a') >= 26 && (ch - 'A') >= 26 |
| && ch != '/' && ch != '(' && ch != ')' && ch != ',' && ch != '*' |
| && ch != '&' && ch != '_' && ch != '<' && ch != '>' && ch != ':' |
| && ch != '~') |
| result.replace(i, 1, QString("%") + QString("%1").arg(ch, 2, 16)); |
| } |
| return result; |
| #else |
| return string; |
| #endif |
| } |
| |
| QStringList CodeMarker::macRefsForNode(Node *node) |
| { |
| QString result = QLatin1String("cpp/"); |
| switch (node->type()) { |
| case Node::Class: |
| { |
| const ClassNode *classe = static_cast<const ClassNode *>(node); |
| #if 0 |
| if (!classe->templateStuff().isEmpty()) { |
| result += QLatin1String("tmplt/"); |
| } |
| else |
| #endif |
| { |
| result += QLatin1String("cl/"); |
| } |
| result += macName(classe); // ### Maybe plainName? |
| } |
| break; |
| case Node::Enum: |
| { |
| QStringList stringList; |
| stringList << encode(result + QLatin1String("tag/") + |
| macName(node)); |
| foreach (const QString &enumName, node->doc().enumItemNames()) { |
| // ### Write a plainEnumValue() and use it here |
| stringList << encode(result + QLatin1String("econst/") + |
| macName(node->parent(), enumName)); |
| } |
| return stringList; |
| } |
| case Node::Typedef: |
| result += QLatin1String("tdef/") + macName(node); |
| break; |
| case Node::Function: |
| { |
| bool isMacro = false; |
| const FunctionNode *func = static_cast<const FunctionNode *>(node); |
| |
| // overloads are too clever for the Xcode documentation browser |
| if (func->isOverload()) |
| return QStringList(); |
| |
| if (func->metaness() == FunctionNode::MacroWithParams |
| || func->metaness() == FunctionNode::MacroWithoutParams) { |
| result += QLatin1String("macro/"); |
| isMacro = true; |
| #if 0 |
| } |
| else if (!func->templateStuff().isEmpty()) { |
| result += QLatin1String("ftmplt/"); |
| #endif |
| } |
| else if (func->isStatic()) { |
| result += QLatin1String("clm/"); |
| } |
| else if (!func->parent()->name().isEmpty()) { |
| result += QLatin1String("instm/"); |
| } |
| else { |
| result += QLatin1String("func/"); |
| } |
| |
| result += macName(func); |
| if (result.endsWith(QLatin1String("()"))) |
| result.chop(2); |
| #if 0 |
| // this code is too clever for the Xcode documentation |
| // browser and/or pbhelpindexer |
| if (!isMacro) { |
| result += "/" + QLatin1String(QMetaObject::normalizedSignature(func->returnType().toLatin1().constData())) + "/("; |
| const QList<Parameter> ¶ms = func->parameters(); |
| for (int i = 0; i < params.count(); ++i) { |
| QString type = params.at(i).leftType() + |
| params.at(i).rightType(); |
| type = QLatin1String(QMetaObject::normalizedSignature(type.toLatin1().constData())); |
| if (i != 0) |
| result += ","; |
| result += type; |
| } |
| result += ")"; |
| } |
| #endif |
| } |
| break; |
| case Node::Variable: |
| result += QLatin1String("data/") + macName(node); |
| break; |
| case Node::Property: |
| { |
| NodeList list = static_cast<const PropertyNode*>(node)->functions(); |
| QStringList stringList; |
| foreach (Node* node, list) { |
| stringList += macRefsForNode(node); |
| } |
| return stringList; |
| } |
| case Node::Namespace: |
| case Node::Fake: |
| case Node::Target: |
| default: |
| return QStringList(); |
| } |
| |
| return QStringList(encode(result)); |
| } |
| |
| QString CodeMarker::macName(const Node *node, const QString &name) |
| { |
| QString myName = name; |
| if (myName.isEmpty()) { |
| myName = node->name(); |
| node = node->parent(); |
| } |
| |
| if (node->name().isEmpty()) { |
| return QLatin1Char('/') + protect(myName); |
| } |
| else { |
| return plainFullName(node) + QLatin1Char('/') + protect(myName); |
| } |
| } |
| |
| #ifdef QDOC_QML |
| /*! |
| Get the list of documentation sections for the children of |
| the specified QmlClassNode. |
| */ |
| QList<Section> CodeMarker::qmlSections(const QmlClassNode* , |
| SynopsisStyle , |
| const Tree* ) |
| { |
| return QList<Section>(); |
| } |
| #endif |
| |
| const Node* CodeMarker::resolveTarget(const QString& , |
| const Tree* , |
| const Node* , |
| const Node* ) |
| { |
| return 0; |
| } |
| |
| QT_END_NAMESPACE |