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
4#include "qml/qqmlpropertyvalidator_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>(location.line()));
71 error.setColumn(qmlConvertSourceCoordinate<quint32, int>(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(u'.', 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(dot);
104
105 if (scope != QLatin1String("Qt")) {
106 if (imports.isNull())
107 return -1;
108
109 QQmlTypeLoader *loader = typeLoader();
110 if (!loader)
111 return -1;
112
113 QQmlType type;
114 if (imports.isT1()) {
115 QQmlImportNamespace *ns = nullptr;
116
117 // Pass &recursionDetected to resolveType because that implicitly allows recursion.
118 // This way we can find the QQmlType of the document we're currently validating.
119 bool recursionDetected = false;
120
121 if (!imports.asT1()->resolveType(
122 loader, scope, &type, nullptr, &ns, nullptr,
123 QQmlType::AnyRegistrationType, &recursionDetected)) {
124 return -1;
125 }
126
127 if (!type.isValid() && ns != nullptr) {
128 dot = nextDot(dot);
129 if (dot == -1 || !imports.asT1()->resolveType(
130 loader, script.left(dot), &type, nullptr, nullptr, nullptr,
131 QQmlType::AnyRegistrationType, &recursionDetected)) {
132 return -1;
133 }
134 }
135 } else {
136 // Allow recursion so that we can find enums from the same document.
137 const QQmlTypeNameCache::Result result
138 = imports.asT2()->query<QQmlImport::AllowRecursion>(scope, loader);
139 if (result.type.isValid()) {
140 type = result.type;
141 } else if (result.importNamespace) {
142 dot = nextDot(dot);
143 if (dot != -1) {
144 type = imports.asT2()->query<QQmlImport::AllowRecursion>(
145 script.left(dot), loader).type;
146 }
147 }
148 }
149
150 if (!type.isValid())
151 return -1;
152
153 const int dot2 = nextDot(dot);
154 const bool dot2Valid = (dot2 != -1);
155 const QString enumValue = script.mid(dot2Valid ? dot2 + 1 : dot + 1);
156 const QString scopedEnumName = dot2Valid ? script.mid(dot + 1, dot2 - dot - 1) : QString();
157
158 // If we're currently validating the same document, we won't be able to find its enums using
159 // the QQmlType. However, we do have the property cache already, and that one contains the
160 // enums.
161 const QUrl documentUrl = validator ? validator->documentSourceUrl() : QUrl();
162 if (documentUrl.isValid() && documentUrl == type.sourceUrl()) {
163 Q_ASSERT(validator);
164 const QQmlPropertyCache::ConstPtr rootCache = validator->rootPropertyCache();
165 const int count = rootCache->qmlEnumCount();
166 for (int ii = 0; ii < count; ++ii) {
167 const QQmlEnumData *enumData = rootCache->qmlEnum(ii);
168 if (!scopedEnumName.isEmpty() && scopedEnumName != enumData->name)
169 continue;
170
171 for (int jj = 0; jj < enumData->values.size(); ++jj) {
172 const QQmlEnumValue value = enumData->values.at(jj);
173 if (value.namedValue == enumValue) {
174 *ok = true;
175 return value.value;
176 }
177 }
178 }
179 return -1;
180 }
181
182 if (!scopedEnumName.isEmpty())
183 return type.scopedEnumValue(loader, scopedEnumName, enumValue, ok);
184 else
185 return type.enumValue(loader, enumValue, ok);
186 }
187
188 const QString enumValue = script.mid(dot + 1);
189 const QMetaObject *mo = &Qt::staticMetaObject;
190 int i = mo->enumeratorCount();
191 while (i--) {
192 int v = mo->enumerator(i).keyToValue(enumValue.toUtf8().constData(), ok);
193 if (*ok)
194 return v;
195 }
196 return -1;
197}
198
199/*!
200 Resolves \a name to a type, or 0 if it is not a type. This can be used
201 to type-check object nodes.
202*/
203const QMetaObject *QQmlCustomParser::resolveType(const QString& name) const
204{
205 if (!imports.isT1())
206 return nullptr;
207
208 QQmlTypeLoader *loader = typeLoader();
209 if (!loader)
210 return nullptr;
211
212 QQmlType qmltype;
213 if (!imports.asT1()->resolveType(loader, name, &qmltype, nullptr, nullptr))
214 return nullptr;
215
216 return qmltype.metaObject();
217}
218
219QQmlTypeLoader *QQmlCustomParser::typeLoader() const
220{
221 if (!engine && !validator)
222 return nullptr;
223
224 return validator ? validator->typeLoader() : QQmlTypeLoader::get(engine);
225}
226
227QT_END_NAMESPACE