1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qml/qqmlpropertyvalidator_p.h"
5#include "qqmlcustomparser_p.h"
6
7#include <private/qv4compileddata_p.h>
8#include <private/qqmlsourcecoordinate_p.h>
9
10#include <QtCore/qdebug.h>
11
12QT_BEGIN_NAMESPACE
13
14/*!
15 \class QQmlCustomParser
16 \brief The QQmlCustomParser class allows you to add new arbitrary types to QML.
17 \internal
18
19 By subclassing QQmlCustomParser, you can add a parser for
20 building a particular type.
21
22 The subclass must implement compile() and setCustomData(), and register
23 itself in the meta type system by calling the macro:
24
25 \code
26 QML_REGISTER_CUSTOM_TYPE(Module, MajorVersion, MinorVersion, Name, TypeClass, ParserClass)
27 \endcode
28*/
29
30/*
31 \fn QByteArray QQmlCustomParser::compile(const QList<QQmlCustomParserProperty> & properties)
32
33 The custom parser processes \a properties, and returns
34 a QByteArray containing data meaningful only to the
35 custom parser; the type engine will pass this same data to
36 setCustomData() when making an instance of the data.
37
38 Errors must be reported via the error() functions.
39
40 The QByteArray may be cached between executions of the system, so
41 it must contain correctly-serialized data (not, for example,
42 pointers to stack objects).
43*/
44
45/*
46 \fn void QQmlCustomParser::setCustomData(QObject *object, const QByteArray &data)
47
48 This function sets \a object to have the properties defined
49 by \a data, which is a block of data previously returned by a call
50 to compile().
51
52 Errors should be reported using qmlWarning(object).
53
54 The \a object will be an instance of the TypeClass specified by QML_REGISTER_CUSTOM_TYPE.
55*/
56
57void QQmlCustomParser::clearErrors()
58{
59 exceptions.clear();
60}
61
62/*!
63 Reports an error with the given \a description.
64
65 An error is generated referring to the \a location in the source file.
66*/
67void QQmlCustomParser::error(const QV4::CompiledData::Location &location, const QString &description)
68{
69 QQmlError error;
70 error.setLine(qmlConvertSourceCoordinate<quint32, int>(n: location.line()));
71 error.setColumn(qmlConvertSourceCoordinate<quint32, int>(n: location.column()));
72 error.setDescription(description);
73
74 exceptions << error;
75}
76
77/*!
78 If \a script is a simple enumeration expression (eg. Text.AlignLeft),
79 returns the integer equivalent (eg. 1), and sets \a ok to true.
80
81 Otherwise sets \a ok to false.
82
83 A valid \a ok must be provided, or the function will assert.
84*/
85int QQmlCustomParser::evaluateEnum(const QString &script, bool *ok) const
86{
87 Q_ASSERT_X(ok, "QQmlCustomParser::evaluateEnum", "ok must not be a null pointer");
88 *ok = false;
89
90 // we support one or two '.' in the enum phrase:
91 // * <TypeName>.<EnumValue>
92 // * <TypeName>.<ScopedEnumName>.<EnumValue>
93
94 auto nextDot = [&](int dot) {
95 const int nextDot = script.indexOf(c: u'.', from: dot + 1);
96 return (nextDot == script.size() - 1) ? -1 : nextDot;
97 };
98
99 int dot = nextDot(-1);
100 if (dot == -1)
101 return -1;
102
103 const QString scope = script.left(n: dot);
104
105 if (scope != QLatin1String("Qt")) {
106 if (imports.isNull())
107 return -1;
108 QQmlType type;
109
110 if (imports.isT1()) {
111 QQmlImportNamespace *ns = nullptr;
112
113 // Pass &recursionDetected to resolveType because that implicitly allows recursion.
114 // This way we can find the QQmlType of the document we're currently validating.
115 bool recursionDetected = false;
116
117 if (!imports.asT1()->resolveType(
118 type: scope, type_return: &type, version_return: nullptr, ns_return: &ns, errors: nullptr,
119 registrationType: QQmlType::AnyRegistrationType, typeRecursionDetected: &recursionDetected)) {
120 return -1;
121 }
122
123 if (!type.isValid() && ns != nullptr) {
124 dot = nextDot(dot);
125 if (dot == -1 || !imports.asT1()->resolveType(
126 type: script.left(n: dot), type_return: &type, version_return: nullptr, ns_return: nullptr, errors: nullptr,
127 registrationType: QQmlType::AnyRegistrationType, typeRecursionDetected: &recursionDetected)) {
128 return -1;
129 }
130 }
131 } else {
132 // Allow recursion so that we can find enums from the same document.
133 const QQmlTypeNameCache::Result result
134 = imports.asT2()->query<QQmlImport::AllowRecursion>(key: scope);
135 if (result.isValid()) {
136 type = result.type;
137 } else if (result.importNamespace) {
138 dot = nextDot(dot);
139 if (dot != -1)
140 type = imports.asT2()->query<QQmlImport::AllowRecursion>(key: script.left(n: dot)).type;
141 }
142 }
143
144 if (!type.isValid())
145 return -1;
146
147 const int dot2 = nextDot(dot);
148 const bool dot2Valid = (dot2 != -1);
149 const QString enumValue = script.mid(position: dot2Valid ? dot2 + 1 : dot + 1);
150 const QString scopedEnumName = dot2Valid ? script.mid(position: dot + 1, n: dot2 - dot - 1) : QString();
151
152 // If we're currently validating the same document, we won't be able to find its enums using
153 // the QQmlType. However, we do have the property cache already, and that one contains the
154 // enums.
155 const QUrl documentUrl = validator ? validator->documentSourceUrl() : QUrl();
156 if (documentUrl.isValid() && documentUrl == type.sourceUrl()) {
157 Q_ASSERT(validator);
158 const QQmlPropertyCache::ConstPtr rootCache = validator->rootPropertyCache();
159 const int count = rootCache->qmlEnumCount();
160 for (int ii = 0; ii < count; ++ii) {
161 const QQmlEnumData *enumData = rootCache->qmlEnum(index: ii);
162 if (!scopedEnumName.isEmpty() && scopedEnumName != enumData->name)
163 continue;
164
165 for (int jj = 0; jj < enumData->values.size(); ++jj) {
166 const QQmlEnumValue value = enumData->values.at(i: jj);
167 if (value.namedValue == enumValue) {
168 *ok = true;
169 return value.value;
170 }
171 }
172 }
173 return -1;
174 }
175
176 if (!scopedEnumName.isEmpty())
177 return type.scopedEnumValue(engine, scopedEnumName, enumValue, ok);
178 else
179 return type.enumValue(engine, enumValue, ok);
180 }
181
182 const QString enumValue = script.mid(position: dot + 1);
183 const QMetaObject *mo = &Qt::staticMetaObject;
184 int i = mo->enumeratorCount();
185 while (i--) {
186 int v = mo->enumerator(index: i).keyToValue(key: enumValue.toUtf8().constData(), ok);
187 if (*ok)
188 return v;
189 }
190 return -1;
191}
192
193/*!
194 Resolves \a name to a type, or 0 if it is not a type. This can be used
195 to type-check object nodes.
196*/
197const QMetaObject *QQmlCustomParser::resolveType(const QString& name) const
198{
199 if (!imports.isT1())
200 return nullptr;
201 QQmlType qmltype;
202 if (!imports.asT1()->resolveType(type: name, type_return: &qmltype, version_return: nullptr, ns_return: nullptr, errors: nullptr))
203 return nullptr;
204 return qmltype.metaObject();
205}
206
207QT_END_NAMESPACE
208

source code of qtdeclarative/src/qml/qml/qqmlcustomparser.cpp