| /**************************************************************************** |
| ** |
| ** Copyright (C) 2019 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the tools applications of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
| ** 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 https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "codemarker.h" |
| |
| #include "config.h" |
| #include "node.h" |
| |
| #include <QtCore/qdebug.h> |
| #include <QtCore/qobjectdefs.h> |
| |
| #include <stdio.h> |
| |
| QT_BEGIN_NAMESPACE |
| |
| QString CodeMarker::defaultLang; |
| QVector<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() {} |
| |
| /*! |
| 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() |
| { |
| defaultLang = Config::instance().getString(CONFIG_LANGUAGE); |
| for (const auto &marker : qAsConst(markers)) |
| marker->initializeMarker(); |
| } |
| |
| /*! |
| All the code markers in the static list are terminated here. |
| */ |
| void CodeMarker::terminate() |
| { |
| for (const auto &marker : qAsConst(markers)) |
| marker->terminateMarker(); |
| } |
| |
| CodeMarker *CodeMarker::markerForCode(const QString &code) |
| { |
| CodeMarker *defaultMarker = markerForLanguage(defaultLang); |
| if (defaultMarker != nullptr && defaultMarker->recognizeCode(code)) |
| return defaultMarker; |
| |
| for (const auto &marker : qAsConst(markers)) { |
| if (marker->recognizeCode(code)) |
| return marker; |
| } |
| |
| 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 != nullptr && defaultMarker->recognizeExtension(ext)) |
| return defaultMarker; |
| for (const auto &marker : qAsConst(markers)) { |
| if (marker->recognizeExtension(ext)) |
| return marker; |
| } |
| --dot; |
| } |
| return defaultMarker; |
| } |
| |
| CodeMarker *CodeMarker::markerForLanguage(const QString &lang) |
| { |
| for (const auto &marker : qAsConst(markers)) { |
| if (marker->recognizeLanguage(lang)) |
| return marker; |
| } |
| return nullptr; |
| } |
| |
| const Node *CodeMarker::nodeForString(const QString &string) |
| { |
| #if QT_POINTER_SIZE == 4 |
| const quintptr n = string.toUInt(); |
| #else |
| const quintptr n = string.toULongLong(); |
| #endif |
| return reinterpret_cast<const Node *>(n); |
| } |
| |
| 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; |
| } |
| |
| void CodeMarker::appendProtectedString(QString *output, const QStringRef &str) |
| { |
| int n = str.length(); |
| output->reserve(output->size() + n * 2 + 30); |
| const QChar *data = str.constData(); |
| for (int i = 0; i != n; ++i) { |
| switch (data[i].unicode()) { |
| case '&': |
| *output += samp; |
| break; |
| case '<': |
| *output += slt; |
| break; |
| case '>': |
| *output += sgt; |
| break; |
| case '"': |
| *output += squot; |
| break; |
| default: |
| *output += data[i]; |
| } |
| } |
| } |
| |
| QString CodeMarker::typified(const QString &string, bool trailingSpace) |
| { |
| 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; |
| } |
| } |
| } |
| if (trailingSpace && string.size()) { |
| if (!string.endsWith(QLatin1Char('*')) && !string.endsWith(QLatin1Char('&'))) |
| result += QLatin1Char(' '); |
| } |
| return result; |
| } |
| |
| QString CodeMarker::taggedNode(const Node *node) |
| { |
| QString tag; |
| QString name = node->name(); |
| |
| switch (node->nodeType()) { |
| case Node::Namespace: |
| tag = QLatin1String("@namespace"); |
| break; |
| case Node::Class: |
| case Node::Struct: |
| case Node::Union: |
| 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; |
| case Node::QmlType: |
| /* |
| Remove the "QML:" prefix, if present. |
| There shouldn't be any of these "QML:" |
| prefixes in the documentation sources |
| after the switch to using QML module |
| qualifiers, but this code is kept to |
| be backward compatible. |
| */ |
| if (node->name().startsWith(QLatin1String("QML:"))) |
| name = name.mid(4); |
| tag = QLatin1String("@property"); |
| break; |
| case Node::Page: |
| tag = QLatin1String("@property"); |
| break; |
| default: |
| tag = QLatin1String("@unknown"); |
| break; |
| } |
| return (QLatin1Char('<') + tag + QLatin1Char('>') + protect(name) + QLatin1String("</") + tag |
| + QLatin1Char('>')); |
| } |
| |
| QString CodeMarker::taggedQmlNode(const Node *node) |
| { |
| QString tag; |
| if (node->isFunction()) { |
| const FunctionNode *fn = static_cast<const FunctionNode *>(node); |
| switch (fn->metaness()) { |
| case FunctionNode::JsSignal: |
| case FunctionNode::QmlSignal: |
| tag = QLatin1String("@signal"); |
| break; |
| case FunctionNode::JsSignalHandler: |
| case FunctionNode::QmlSignalHandler: |
| tag = QLatin1String("@signalhandler"); |
| break; |
| case FunctionNode::JsMethod: |
| case FunctionNode::QmlMethod: |
| tag = QLatin1String("@method"); |
| break; |
| default: |
| tag = QLatin1String("@unknown"); |
| break; |
| } |
| } else if (node->isQmlProperty() || node->isJsProperty()) { |
| tag = QLatin1String("@property"); |
| } else { |
| tag = QLatin1String("@unknown"); |
| } |
| return QLatin1Char('<') + tag + QLatin1Char('>') + protect(node->name()) + QLatin1String("</") |
| + tag + QLatin1Char('>'); |
| } |
| |
| QString CodeMarker::linkTag(const Node *node, const QString &body) |
| { |
| return QLatin1String("<@link node=\"") + stringForNode(node) + QLatin1String("\">") + body |
| + QLatin1String("</@link>"); |
| } |
| |
| static QString encode(const QString &string) |
| { |
| return string; |
| } |
| |
| QStringList CodeMarker::macRefsForNode(Node *node) |
| { |
| QString result = QLatin1String("cpp/"); |
| switch (node->nodeType()) { |
| case Node::Class: { |
| const ClassNode *classe = static_cast<const ClassNode *>(node); |
| { |
| result += QLatin1String("cl/"); |
| } |
| result += macName(classe); // ### Maybe plainName? |
| } break; |
| case Node::Enum: { |
| QStringList stringList; |
| stringList << encode(result + QLatin1String("tag/") + macName(node)); |
| const auto enumItemNames = node->doc().enumItemNames(); |
| for (const auto &name : enumItemNames) { |
| // ### Write a plainEnumValue() and use it here |
| stringList << encode(result + QLatin1String("econst/") + macName(node->parent(), name)); |
| } |
| return stringList; |
| } |
| case Node::Typedef: |
| result += QLatin1String("tdef/") + macName(node); |
| break; |
| case Node::Function: { |
| bool isMacro = false; |
| Q_UNUSED(isMacro); |
| const FunctionNode *func = static_cast<const FunctionNode *>(node); |
| |
| // overloads are too clever for the Xcode documentation browser |
| if (func->isOverload()) |
| return QStringList(); |
| |
| if (func->isMacro()) { |
| result += QLatin1String("macro/"); |
| } 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); |
| } break; |
| case Node::Variable: |
| result += QLatin1String("data/") + macName(node); |
| break; |
| case Node::Property: { |
| const NodeList list = static_cast<const PropertyNode *>(node)->functions(); |
| QStringList stringList; |
| for (auto *node : list) { |
| stringList += macRefsForNode(node); |
| } |
| return stringList; |
| } |
| 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 node->plainFullName() + QLatin1Char('/') + protect(myName); |
| } |
| |
| QT_END_NAMESPACE |