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