aboutsummaryrefslogtreecommitdiffstats
path: root/src/qml/qml/qqmlcustomparser.cpp
blob: bd632d25a6b248a09595cd41943172134153708f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only

#include "qml/qqmlpropertyvalidator_p.h"
#include "qqmlcustomparser_p.h"

#include <private/qv4compileddata_p.h>
#include <private/qqmlsourcecoordinate_p.h>

#include <QtCore/qdebug.h>

QT_BEGIN_NAMESPACE

/*!
    \class QQmlCustomParser
    \brief The QQmlCustomParser class allows you to add new arbitrary types to QML.
    \internal

    By subclassing QQmlCustomParser, you can add a parser for
    building a particular type.

    The subclass must implement compile() and setCustomData(), and register
    itself in the meta type system by calling the macro:

    \code
    QML_REGISTER_CUSTOM_TYPE(Module, MajorVersion, MinorVersion, Name, TypeClass, ParserClass)
    \endcode
*/

/*
    \fn QByteArray QQmlCustomParser::compile(const QList<QQmlCustomParserProperty> & properties)

    The custom parser processes \a properties, and returns
    a QByteArray containing data meaningful only to the
    custom parser; the type engine will pass this same data to
    setCustomData() when making an instance of the data.

    Errors must be reported via the error() functions.

    The QByteArray may be cached between executions of the system, so
    it must contain correctly-serialized data (not, for example,
    pointers to stack objects).
*/

/*
    \fn void QQmlCustomParser::setCustomData(QObject *object, const QByteArray &data)

    This function sets \a object to have the properties defined
    by \a data, which is a block of data previously returned by a call
    to compile().

    Errors should be reported using qmlWarning(object).

    The \a object will be an instance of the TypeClass specified by QML_REGISTER_CUSTOM_TYPE.
*/

void QQmlCustomParser::clearErrors()
{
    exceptions.clear();
}

/*!
    Reports an error with the given \a description.

    An error is generated referring to the \a location in the source file.
*/
void QQmlCustomParser::error(const QV4::CompiledData::Location &location, const QString &description)
{
    QQmlError error;
    error.setLine(qmlConvertSourceCoordinate<quint32, int>(location.line()));
    error.setColumn(qmlConvertSourceCoordinate<quint32, int>(location.column()));
    error.setDescription(description);

    exceptions << error;
}

/*!
    If \a script is a simple enumeration expression (eg. Text.AlignLeft),
    returns the integer equivalent (eg. 1), and sets \a ok to true.

    Otherwise sets \a ok to false.

    A valid \a ok must be provided, or the function will assert.
*/
int QQmlCustomParser::evaluateEnum(const QString &script, bool *ok) const
{
    Q_ASSERT_X(ok, "QQmlCustomParser::evaluateEnum", "ok must not be a null pointer");
    *ok = false;

    // we support one or two '.' in the enum phrase:
    // * <TypeName>.<EnumValue>
    // * <TypeName>.<ScopedEnumName>.<EnumValue>

    auto nextDot = [&](int dot) {
        const int nextDot = script.indexOf(u'.', dot + 1);
        return (nextDot == script.size() - 1) ? -1 : nextDot;
    };

    int dot = nextDot(-1);
    if (dot == -1)
        return -1;

    const QString scope = script.left(dot);

    if (scope != QLatin1String("Qt")) {
        if (!engine || imports.isNull())
            return -1;
        QQmlType type;

        QQmlTypeLoader *typeLoader = QQmlTypeLoader::get(engine);
        Q_ASSERT(typeLoader);

        if (imports.isT1()) {
            QQmlImportNamespace *ns = nullptr;

            // Pass &recursionDetected to resolveType because that implicitly allows recursion.
            // This way we can find the QQmlType of the document we're currently validating.
            bool recursionDetected = false;

            if (!imports.asT1()->resolveType(
                        typeLoader, scope, &type, nullptr, &ns, nullptr,
                        QQmlType::AnyRegistrationType, &recursionDetected)) {
                return -1;
            }

            if (!type.isValid() && ns != nullptr) {
                dot = nextDot(dot);
                if (dot == -1 || !imports.asT1()->resolveType(
                            typeLoader, script.left(dot), &type, nullptr, nullptr, nullptr,
                            QQmlType::AnyRegistrationType, &recursionDetected)) {
                    return -1;
                }
            }
        } else {
            // Allow recursion so that we can find enums from the same document.
            const QQmlTypeNameCache::Result result
                    = imports.asT2()->query<QQmlImport::AllowRecursion>(scope, typeLoader);
            if (result.isValid()) {
                type = result.type;
            } else if (result.importNamespace) {
                dot = nextDot(dot);
                if (dot != -1) {
                    type = imports.asT2()->query<QQmlImport::AllowRecursion>(
                                                 script.left(dot), typeLoader).type;
                }
            }
        }

        if (!type.isValid())
            return -1;

        const int dot2 = nextDot(dot);
        const bool dot2Valid = (dot2 != -1);
        const QString enumValue = script.mid(dot2Valid ? dot2 + 1 : dot + 1);
        const QString scopedEnumName = dot2Valid ? script.mid(dot + 1, dot2 - dot - 1) : QString();

        // If we're currently validating the same document, we won't be able to find its enums using
        // the QQmlType. However, we do have the property cache already, and that one contains the
        // enums.
        const QUrl documentUrl = validator ? validator->documentSourceUrl() : QUrl();
        if (documentUrl.isValid() && documentUrl == type.sourceUrl()) {
            Q_ASSERT(validator);
            const QQmlPropertyCache::ConstPtr rootCache = validator->rootPropertyCache();
            const int count = rootCache->qmlEnumCount();
            for (int ii = 0; ii < count; ++ii) {
                const QQmlEnumData *enumData = rootCache->qmlEnum(ii);
                if (!scopedEnumName.isEmpty() && scopedEnumName != enumData->name)
                    continue;

                for (int jj = 0; jj < enumData->values.size(); ++jj) {
                    const QQmlEnumValue value = enumData->values.at(jj);
                    if (value.namedValue == enumValue) {
                        *ok = true;
                        return value.value;
                    }
                }
            }
            return -1;
        }

        if (!scopedEnumName.isEmpty())
            return type.scopedEnumValue(engine, scopedEnumName, enumValue, ok);
        else
            return type.enumValue(engine, enumValue, ok);
    }

    const QString enumValue = script.mid(dot + 1);
    const QMetaObject *mo = &Qt::staticMetaObject;
    int i = mo->enumeratorCount();
    while (i--) {
        int v = mo->enumerator(i).keyToValue(enumValue.toUtf8().constData(), ok);
        if (*ok)
            return v;
    }
    return -1;
}

/*!
    Resolves \a name to a type, or 0 if it is not a type. This can be used
    to type-check object nodes.
*/
const QMetaObject *QQmlCustomParser::resolveType(const QString& name) const
{
    if (!engine || !imports.isT1())
        return nullptr;
    QQmlType qmltype;
    if (!imports.asT1()->resolveType(QQmlTypeLoader::get(engine), name, &qmltype, nullptr, nullptr))
        return nullptr;
    return qmltype.metaObject();
}

QT_END_NAMESPACE