summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorQt Forward Merge Bot <qt_forward_merge_bot@qt-project.org>2018-08-30 17:00:11 +0200
committerQt Forward Merge Bot <qt_forward_merge_bot@qt-project.org>2018-08-30 17:00:11 +0200
commitec37df9608ba674c3200e9f23030478018c50236 (patch)
tree5900084d1c682616e35842b2f5dd61a06f87b4af
parentb9b941945dc63a48652576c806e10dfeb15005e9 (diff)
parent8c6e24329ecd65f364654b1ca2b6a273f0826a8b (diff)
downloadqtxmlpatterns-ec37df9608ba674c3200e9f23030478018c50236.tar.gz
Merge remote-tracking branch 'origin/5.12' into dev
Change-Id: I21cb38be0abdf8998dcefb1f74f813a37fd76906
-rw-r--r--src/imports/imports.pro3
-rw-r--r--src/imports/xmllistmodel/plugin.cpp67
-rw-r--r--src/imports/xmllistmodel/plugins.qmltypes59
-rw-r--r--src/imports/xmllistmodel/qmldir5
-rw-r--r--src/imports/xmllistmodel/qqmlxmllistmodel.cpp1238
-rw-r--r--src/imports/xmllistmodel/qqmlxmllistmodel_p.h219
-rw-r--r--src/imports/xmllistmodel/xmllistmodel.pro12
-rw-r--r--src/src.pro6
-rw-r--r--tests/auto/auto.pro3
-rw-r--r--tests/auto/qquickxmllistmodel/data/empty.xml0
-rw-r--r--tests/auto/qquickxmllistmodel/data/get.qml61
-rw-r--r--tests/auto/qquickxmllistmodel/data/groups.qml10
-rw-r--r--tests/auto/qquickxmllistmodel/data/groups.xml18
-rw-r--r--tests/auto/qquickxmllistmodel/data/model.qml11
-rw-r--r--tests/auto/qquickxmllistmodel/data/model.xml54
-rw-r--r--tests/auto/qquickxmllistmodel/data/model2.xml14
-rw-r--r--tests/auto/qquickxmllistmodel/data/propertychanges.qml11
-rw-r--r--tests/auto/qquickxmllistmodel/data/proxyCrash.qml9
-rw-r--r--tests/auto/qquickxmllistmodel/data/recipes.qml11
-rw-r--r--tests/auto/qquickxmllistmodel/data/recipes.xml90
-rw-r--r--tests/auto/qquickxmllistmodel/data/roleCrash.qml8
-rw-r--r--tests/auto/qquickxmllistmodel/data/roleErrors.qml11
-rw-r--r--tests/auto/qquickxmllistmodel/data/roleKeys.qml13
-rw-r--r--tests/auto/qquickxmllistmodel/data/testtypes.qml8
-rw-r--r--tests/auto/qquickxmllistmodel/data/unique.qml9
-rw-r--r--tests/auto/qquickxmllistmodel/qquickxmllistmodel.pro16
-rw-r--r--tests/auto/qquickxmllistmodel/tst_qquickxmllistmodel.cpp1011
27 files changed, 2977 insertions, 0 deletions
diff --git a/src/imports/imports.pro b/src/imports/imports.pro
new file mode 100644
index 0000000..72046db
--- /dev/null
+++ b/src/imports/imports.pro
@@ -0,0 +1,3 @@
+TEMPLATE = subdirs
+
+qtHaveModule(qml): SUBDIRS += xmllistmodel
diff --git a/src/imports/xmllistmodel/plugin.cpp b/src/imports/xmllistmodel/plugin.cpp
new file mode 100644
index 0000000..c5356b8
--- /dev/null
+++ b/src/imports/xmllistmodel/plugin.cpp
@@ -0,0 +1,67 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** 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-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QtQml/qqmlextensionplugin.h>
+#include <QtQml/qqml.h>
+
+#include "qqmlxmllistmodel_p.h"
+
+QT_BEGIN_NAMESPACE
+
+class QmlXmlListModelPlugin : public QQmlExtensionPlugin
+{
+ Q_OBJECT
+ Q_PLUGIN_METADATA(IID QQmlExtensionInterface_iid)
+
+public:
+ QmlXmlListModelPlugin(QObject *parent = 0) : QQmlExtensionPlugin(parent) { }
+ void registerTypes(const char *uri) override
+ {
+ Q_ASSERT(QLatin1String(uri) == QLatin1String("QtQuick.XmlListModel"));
+ qmlRegisterType<QQuickXmlListModel>(uri,2,0,"XmlListModel");
+ qmlRegisterType<QQuickXmlListModelRole>(uri,2,0,"XmlRole");
+
+ // Auto-increment the import to stay in sync with ALL future QtQuick minor versions from 5.11 onward
+ qmlRegisterModule(uri, 2, QT_VERSION_MINOR);
+ }
+};
+
+QT_END_NAMESPACE
+
+#include "plugin.moc"
diff --git a/src/imports/xmllistmodel/plugins.qmltypes b/src/imports/xmllistmodel/plugins.qmltypes
new file mode 100644
index 0000000..cc675d5
--- /dev/null
+++ b/src/imports/xmllistmodel/plugins.qmltypes
@@ -0,0 +1,59 @@
+import QtQuick.tooling 1.2
+
+// This file describes the plugin-supplied types contained in the library.
+// It is used for QML tooling purposes only.
+//
+// This file was auto-generated by:
+// 'qmlplugindump -nonrelocatable QtQuick.XmlListModel 2.0'
+
+Module {
+ dependencies: ["QtQuick 2.8"]
+ Component {
+ name: "QQuickXmlListModel"
+ defaultProperty: "roles"
+ prototype: "QAbstractListModel"
+ exports: ["QtQuick.XmlListModel/XmlListModel 2.0"]
+ exportMetaObjectRevisions: [0]
+ Enum {
+ name: "Status"
+ values: {
+ "Null": 0,
+ "Ready": 1,
+ "Loading": 2,
+ "Error": 3
+ }
+ }
+ Property { name: "status"; type: "Status"; isReadonly: true }
+ Property { name: "progress"; type: "double"; isReadonly: true }
+ Property { name: "source"; type: "QUrl" }
+ Property { name: "xml"; type: "string" }
+ Property { name: "query"; type: "string" }
+ Property { name: "namespaceDeclarations"; type: "string" }
+ Property { name: "roles"; type: "QQuickXmlListModelRole"; isList: true; isReadonly: true }
+ Property { name: "count"; type: "int"; isReadonly: true }
+ Signal {
+ name: "statusChanged"
+ Parameter { type: "QQuickXmlListModel::Status" }
+ }
+ Signal {
+ name: "progressChanged"
+ Parameter { name: "progress"; type: "double" }
+ }
+ Method { name: "reload" }
+ Method {
+ name: "get"
+ type: "QQmlV4Handle"
+ Parameter { name: "index"; type: "int" }
+ }
+ Method { name: "errorString"; type: "string" }
+ }
+ Component {
+ name: "QQuickXmlListModelRole"
+ prototype: "QObject"
+ exports: ["QtQuick.XmlListModel/XmlRole 2.0"]
+ exportMetaObjectRevisions: [0]
+ Property { name: "name"; type: "string" }
+ Property { name: "query"; type: "string" }
+ Property { name: "isKey"; type: "bool" }
+ }
+}
diff --git a/src/imports/xmllistmodel/qmldir b/src/imports/xmllistmodel/qmldir
new file mode 100644
index 0000000..1f17dbb
--- /dev/null
+++ b/src/imports/xmllistmodel/qmldir
@@ -0,0 +1,5 @@
+module QtQuick.XmlListModel
+plugin qmlxmllistmodelplugin
+classname QmlXmlListModelPlugin
+typeinfo plugins.qmltypes
+designersupported
diff --git a/src/imports/xmllistmodel/qqmlxmllistmodel.cpp b/src/imports/xmllistmodel/qqmlxmllistmodel.cpp
new file mode 100644
index 0000000..dbfdd5c
--- /dev/null
+++ b/src/imports/xmllistmodel/qqmlxmllistmodel.cpp
@@ -0,0 +1,1238 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** 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-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qqmlxmllistmodel_p.h"
+
+#include <qqmlcontext.h>
+#include <private/qqmlengine_p.h>
+#include <private/qv8engine_p.h>
+#include <private/qv4value_p.h>
+#include <private/qv4engine_p.h>
+#include <private/qv4object_p.h>
+
+#include <QDebug>
+#include <QStringList>
+#include <QMap>
+#include <QThread>
+#include <QXmlQuery>
+#include <QXmlResultItems>
+#include <QXmlNodeModelIndex>
+#include <QBuffer>
+#if QT_CONFIG(qml_network)
+#include <QNetworkRequest>
+#include <QNetworkReply>
+#endif
+#include <QTimer>
+#include <QMutex>
+
+#include <private/qabstractitemmodel_p.h>
+
+Q_DECLARE_METATYPE(QQuickXmlQueryResult)
+
+QT_BEGIN_NAMESPACE
+
+using namespace QV4;
+
+typedef QPair<int, int> QQuickXmlListRange;
+
+#define XMLLISTMODEL_CLEAR_ID 0
+
+/*!
+ \qmlmodule QtQuick.XmlListModel 2.11
+ \title Qt Quick XmlListModel QML Types
+ \ingroup qmlmodules
+ \brief Provides QML types for creating models from XML data
+
+ This QML module contains types for creating models from XML data.
+
+ To use the types in this module, import the module with the following line:
+
+ \code
+ import QtQuick.XmlListModel 2.11
+ \endcode
+*/
+
+/*!
+ \qmltype XmlRole
+ \instantiates QQuickXmlListModelRole
+ \inqmlmodule QtQuick.XmlListModel
+ \brief For specifying a role to an XmlListModel
+ \ingroup qtquick-models
+
+ \sa {Qt QML}
+*/
+
+/*!
+ \qmlproperty string QtQuick.XmlListModel::XmlRole::name
+
+ The name for the role. This name is used to access the model data for this role.
+
+ For example, the following model has a role named "title", which can be accessed
+ from the view's delegate:
+
+ \qml
+ XmlListModel {
+ id: xmlModel
+ // ...
+ XmlRole {
+ name: "title"
+ query: "title/string()"
+ }
+ }
+ \endqml
+
+ \qml
+ ListView {
+ model: xmlModel
+ delegate: Text { text: title }
+ }
+ \endqml
+*/
+
+/*!
+ \qmlproperty string QtQuick.XmlListModel::XmlRole::query
+ The relative XPath expression query for this role. The query must be relative; it cannot start
+ with a '/'.
+
+ For example, if there is an XML document like this:
+
+ \quotefile qml/xmlrole.xml
+ Here are some valid XPath expressions for XmlRole queries on this document:
+
+ \snippet qml/xmlrole.qml 0
+ \dots 4
+ \snippet qml/xmlrole.qml 1
+
+ Accessing the model data for the above roles from a delegate:
+
+ \snippet qml/xmlrole.qml 2
+
+ See the \l{http://www.w3.org/TR/xpath20/}{W3C XPath 2.0 specification} for more information.
+*/
+
+/*!
+ \qmlproperty bool QtQuick.XmlListModel::XmlRole::isKey
+ Defines whether this is a key role.
+ Key roles are used to determine whether a set of values should
+ be updated or added to the XML list model when XmlListModel::reload()
+ is called.
+
+ \sa XmlListModel
+*/
+
+struct XmlQueryJob
+{
+ int queryId;
+ QByteArray data;
+ QString query;
+ QString namespaces;
+ QStringList roleQueries;
+ QList<void*> roleQueryErrorId; // the ptr to send back if there is an error
+ QStringList keyRoleQueries;
+ QStringList keyRoleResultsCache;
+ QString prefix;
+};
+
+
+class QQuickXmlQueryEngine;
+class QQuickXmlQueryThreadObject : public QObject
+{
+ Q_OBJECT
+public:
+ QQuickXmlQueryThreadObject(QQuickXmlQueryEngine *);
+
+ void processJobs();
+ bool event(QEvent *e) override;
+
+private:
+ QQuickXmlQueryEngine *m_queryEngine;
+};
+
+
+class QQuickXmlQueryEngine : public QThread
+{
+ Q_OBJECT
+public:
+ QQuickXmlQueryEngine(QQmlEngine *eng);
+ ~QQuickXmlQueryEngine();
+
+ int doQuery(QString query, QString namespaces, QByteArray data, QList<QQuickXmlListModelRole *>* roleObjects, QStringList keyRoleResultsCache);
+ void abort(int id);
+
+ void processJobs();
+
+ static QQuickXmlQueryEngine *instance(QQmlEngine *engine);
+
+signals:
+ void queryCompleted(const QQuickXmlQueryResult &);
+ void error(void*, const QString&);
+
+protected:
+ void run() override;
+
+private:
+ void processQuery(XmlQueryJob *job);
+ void doQueryJob(XmlQueryJob *job, QQuickXmlQueryResult *currentResult);
+ void doSubQueryJob(XmlQueryJob *job, QQuickXmlQueryResult *currentResult);
+ void getValuesOfKeyRoles(const XmlQueryJob& currentJob, QStringList *values, QXmlQuery *query) const;
+ void addIndexToRangeList(QList<QQuickXmlListRange> *ranges, int index) const;
+
+ QMutex m_mutex;
+ QQuickXmlQueryThreadObject *m_threadObject;
+ QList<XmlQueryJob> m_jobs;
+ QSet<int> m_cancelledJobs;
+ QAtomicInt m_queryIds;
+
+ QQmlEngine *m_engine;
+ QObject *m_eventLoopQuitHack;
+
+ static QHash<QQmlEngine *,QQuickXmlQueryEngine*> queryEngines;
+ static QMutex queryEnginesMutex;
+};
+QHash<QQmlEngine *,QQuickXmlQueryEngine*> QQuickXmlQueryEngine::queryEngines;
+QMutex QQuickXmlQueryEngine::queryEnginesMutex;
+
+
+QQuickXmlQueryThreadObject::QQuickXmlQueryThreadObject(QQuickXmlQueryEngine *e)
+ : m_queryEngine(e)
+{
+}
+
+void QQuickXmlQueryThreadObject::processJobs()
+{
+ QCoreApplication::postEvent(this, new QEvent(QEvent::User));
+}
+
+bool QQuickXmlQueryThreadObject::event(QEvent *e)
+{
+ if (e->type() == QEvent::User) {
+ m_queryEngine->processJobs();
+ return true;
+ } else {
+ return QObject::event(e);
+ }
+}
+
+
+
+QQuickXmlQueryEngine::QQuickXmlQueryEngine(QQmlEngine *eng)
+: QThread(eng), m_threadObject(0), m_queryIds(XMLLISTMODEL_CLEAR_ID + 1), m_engine(eng), m_eventLoopQuitHack(0)
+{
+ qRegisterMetaType<QQuickXmlQueryResult>("QQuickXmlQueryResult");
+
+ m_eventLoopQuitHack = new QObject;
+ m_eventLoopQuitHack->moveToThread(this);
+ connect(m_eventLoopQuitHack, SIGNAL(destroyed(QObject*)), SLOT(quit()), Qt::DirectConnection);
+ start(QThread::IdlePriority);
+}
+
+QQuickXmlQueryEngine::~QQuickXmlQueryEngine()
+{
+ queryEnginesMutex.lock();
+ queryEngines.remove(m_engine);
+ queryEnginesMutex.unlock();
+
+ m_eventLoopQuitHack->deleteLater();
+ wait();
+}
+
+int QQuickXmlQueryEngine::doQuery(QString query, QString namespaces, QByteArray data, QList<QQuickXmlListModelRole *>* roleObjects, QStringList keyRoleResultsCache) {
+ {
+ QMutexLocker m1(&m_mutex);
+ m_queryIds.ref();
+ if (m_queryIds.load() <= 0)
+ m_queryIds.store(1);
+ }
+
+ XmlQueryJob job;
+ job.queryId = m_queryIds.load();
+ job.data = data;
+ job.query = QLatin1String("doc($src)") + query;
+ job.namespaces = namespaces;
+ job.keyRoleResultsCache = keyRoleResultsCache;
+
+ for (int i=0; i<roleObjects->count(); i++) {
+ if (!roleObjects->at(i)->isValid()) {
+ job.roleQueries << QString();
+ continue;
+ }
+ job.roleQueries << roleObjects->at(i)->query();
+ job.roleQueryErrorId << static_cast<void*>(roleObjects->at(i));
+ if (roleObjects->at(i)->isKey())
+ job.keyRoleQueries << job.roleQueries.last();
+ }
+
+ {
+ QMutexLocker ml(&m_mutex);
+ m_jobs.append(job);
+ if (m_threadObject)
+ m_threadObject->processJobs();
+ }
+
+ return job.queryId;
+}
+
+void QQuickXmlQueryEngine::abort(int id)
+{
+ QMutexLocker ml(&m_mutex);
+ if (id != -1)
+ m_cancelledJobs.insert(id);
+}
+
+void QQuickXmlQueryEngine::run()
+{
+ m_mutex.lock();
+ m_threadObject = new QQuickXmlQueryThreadObject(this);
+ m_mutex.unlock();
+
+ processJobs();
+ exec();
+
+ delete m_threadObject;
+ m_threadObject = 0;
+}
+
+void QQuickXmlQueryEngine::processJobs()
+{
+ QMutexLocker locker(&m_mutex);
+
+ while (true) {
+ if (m_jobs.isEmpty())
+ return;
+
+ XmlQueryJob currentJob = m_jobs.takeLast();
+ while (m_cancelledJobs.remove(currentJob.queryId)) {
+ if (m_jobs.isEmpty())
+ return;
+ currentJob = m_jobs.takeLast();
+ }
+
+ locker.unlock();
+ processQuery(&currentJob);
+ locker.relock();
+ }
+}
+
+QQuickXmlQueryEngine *QQuickXmlQueryEngine::instance(QQmlEngine *engine)
+{
+ queryEnginesMutex.lock();
+ QQuickXmlQueryEngine *queryEng = queryEngines.value(engine);
+ if (!queryEng) {
+ queryEng = new QQuickXmlQueryEngine(engine);
+ queryEngines.insert(engine, queryEng);
+ }
+ queryEnginesMutex.unlock();
+
+ return queryEng;
+}
+
+void QQuickXmlQueryEngine::processQuery(XmlQueryJob *job)
+{
+ QQuickXmlQueryResult result;
+ result.queryId = job->queryId;
+ doQueryJob(job, &result);
+ doSubQueryJob(job, &result);
+
+ {
+ QMutexLocker ml(&m_mutex);
+ if (m_cancelledJobs.contains(job->queryId)) {
+ m_cancelledJobs.remove(job->queryId);
+ } else {
+ emit queryCompleted(result);
+ }
+ }
+}
+
+void QQuickXmlQueryEngine::doQueryJob(XmlQueryJob *currentJob, QQuickXmlQueryResult *currentResult)
+{
+ Q_ASSERT(currentJob->queryId != -1);
+
+ QString r;
+ QXmlQuery query;
+ QBuffer buffer(&currentJob->data);
+ buffer.open(QIODevice::ReadOnly);
+ query.bindVariable(QLatin1String("src"), &buffer);
+ query.setQuery(currentJob->namespaces + currentJob->query);
+ query.evaluateTo(&r);
+
+ //always need a single root element
+ QByteArray xml = "<dummy:items xmlns:dummy=\"http://qtsotware.com/dummy\">\n" + r.toUtf8() + "</dummy:items>";
+ QBuffer b(&xml);
+ b.open(QIODevice::ReadOnly);
+
+ QString namespaces = QLatin1String("declare namespace dummy=\"http://qtsotware.com/dummy\";\n") + currentJob->namespaces;
+ QString prefix = QLatin1String("doc($inputDocument)/dummy:items/*");
+
+ //figure out how many items we are dealing with
+ int count = -1;
+ {
+ QXmlResultItems result;
+ QXmlQuery countquery;
+ countquery.bindVariable(QLatin1String("inputDocument"), &b);
+ countquery.setQuery(namespaces + QLatin1String("count(") + prefix + QLatin1Char(')'));
+ countquery.evaluateTo(&result);
+ QXmlItem item(result.next());
+ if (item.isAtomicValue())
+ count = item.toAtomicValue().toInt();
+ }
+
+ currentJob->data = xml;
+ currentJob->prefix = namespaces + prefix + QLatin1Char('/');
+ currentResult->size = (count > 0 ? count : 0);
+}
+
+void QQuickXmlQueryEngine::getValuesOfKeyRoles(const XmlQueryJob& currentJob, QStringList *values, QXmlQuery *query) const
+{
+ const QStringList &keysQueries = currentJob.keyRoleQueries;
+ QString keysQuery;
+ if (keysQueries.count() == 1)
+ keysQuery = currentJob.prefix + keysQueries[0];
+ else if (keysQueries.count() > 1)
+ keysQuery = currentJob.prefix + QLatin1String("concat(") + keysQueries.join(QLatin1Char(',')) + QLatin1Char(')');
+
+ if (!keysQuery.isEmpty()) {
+ query->setQuery(keysQuery);
+ QXmlResultItems resultItems;
+ query->evaluateTo(&resultItems);
+ QXmlItem item(resultItems.next());
+ while (!item.isNull()) {
+ values->append(item.toAtomicValue().toString());
+ item = resultItems.next();
+ }
+ }
+}
+
+void QQuickXmlQueryEngine::addIndexToRangeList(QList<QQuickXmlListRange> *ranges, int index) const {
+ if (ranges->isEmpty())
+ ranges->append(qMakePair(index, 1));
+ else if (ranges->last().first + ranges->last().second == index)
+ ranges->last().second += 1;
+ else
+ ranges->append(qMakePair(index, 1));
+}
+
+void QQuickXmlQueryEngine::doSubQueryJob(XmlQueryJob *currentJob, QQuickXmlQueryResult *currentResult)
+{
+ Q_ASSERT(currentJob->queryId != -1);
+
+ QBuffer b(&currentJob->data);
+ b.open(QIODevice::ReadOnly);
+
+ QXmlQuery subquery;
+ subquery.bindVariable(QLatin1String("inputDocument"), &b);
+
+ QStringList keyRoleResults;
+ getValuesOfKeyRoles(*currentJob, &keyRoleResults, &subquery);
+
+ // See if any values of key roles have been inserted or removed.
+
+ if (currentJob->keyRoleResultsCache.isEmpty()) {
+ currentResult->inserted << qMakePair(0, currentResult->size);
+ } else {
+ if (keyRoleResults != currentJob->keyRoleResultsCache) {
+ QStringList temp;
+ for (int i=0; i<currentJob->keyRoleResultsCache.count(); i++) {
+ if (!keyRoleResults.contains(currentJob->keyRoleResultsCache[i]))
+ addIndexToRangeList(&currentResult->removed, i);
+ else
+ temp << currentJob->keyRoleResultsCache[i];
+ }
+ for (int i=0; i<keyRoleResults.count(); i++) {
+ if (temp.count() == i || keyRoleResults[i] != temp[i]) {
+ temp.insert(i, keyRoleResults[i]);
+ addIndexToRangeList(&currentResult->inserted, i);
+ }
+ }
+ }
+ }
+ currentResult->keyRoleResultsCache = keyRoleResults;
+
+ // Get the new values for each role.
+ //### we might be able to condense even further (query for everything in one go)
+ const QStringList &queries = currentJob->roleQueries;
+ for (int i = 0; i < queries.size(); ++i) {
+ QList<QVariant> resultList;
+ if (!queries[i].isEmpty()) {
+ subquery.setQuery(currentJob->prefix + QLatin1String("(let $v := string(") + queries[i] + QLatin1String(") return if ($v) then ") + queries[i] + QLatin1String(" else \"\")"));
+ if (subquery.isValid()) {
+ QXmlResultItems resultItems;
+ subquery.evaluateTo(&resultItems);
+ QXmlItem item(resultItems.next());
+ while (!item.isNull()) {
+ resultList << item.toAtomicValue(); //### we used to trim strings
+ item = resultItems.next();
+ }
+ } else {
+ emit error(currentJob->roleQueryErrorId.at(i), queries[i]);
+ }
+ }
+ //### should warn here if things have gone wrong.
+ while (resultList.count() < currentResult->size)
+ resultList << QVariant();
+ currentResult->data << resultList;
+ b.seek(0);
+ }
+
+ //this method is much slower, but works better for incremental loading
+ /*for (int j = 0; j < m_size; ++j) {
+ QList<QVariant> resultList;
+ for (int i = 0; i < m_roleObjects->size(); ++i) {
+ QQuickXmlListModelRole *role = m_roleObjects->at(i);
+ subquery.setQuery(m_prefix.arg(j+1) + role->query());
+ if (role->isStringList()) {
+ QStringList data;
+ subquery.evaluateTo(&data);
+ resultList << QVariant(data);
+ //qDebug() << data;
+ } else {
+ QString s;
+ subquery.evaluateTo(&s);
+ if (role->isCData()) {
+ //un-escape
+ s.replace(QLatin1String("&lt;"), QLatin1String("<"));
+ s.replace(QLatin1String("&gt;"), QLatin1String(">"));
+ s.replace(QLatin1String("&amp;"), QLatin1String("&"));
+ }
+ resultList << s.trimmed();
+ //qDebug() << s;
+ }
+ b.seek(0);
+ }
+ m_modelData << resultList;
+ }*/
+}
+
+class QQuickXmlListModelPrivate : public QAbstractItemModelPrivate
+{
+ Q_DECLARE_PUBLIC(QQuickXmlListModel)
+public:
+ QQuickXmlListModelPrivate()
+ : isComponentComplete(true), size(0), highestRole(Qt::UserRole)
+#if QT_CONFIG(qml_network)
+ , reply(0)
+#endif
+ , status(QQuickXmlListModel::Null), progress(0.0)
+ , queryId(-1), roleObjects(), redirectCount(0) {}
+
+
+ void notifyQueryStarted(bool remoteSource) {
+ Q_Q(QQuickXmlListModel);
+ progress = remoteSource ? 0.0 : 1.0;
+ status = QQuickXmlListModel::Loading;
+ errorString.clear();
+ emit q->progressChanged(progress);
+ emit q->statusChanged(status);
+ }
+
+#if QT_CONFIG(qml_network)
+ void deleteReply() {
+ Q_Q(QQuickXmlListModel);
+ if (reply) {
+ QObject::disconnect(reply, 0, q, 0);
+ reply->deleteLater();
+ reply = 0;
+ }
+ }
+#endif
+
+ bool isComponentComplete;
+ QUrl src;
+ QString xml;
+ QString query;
+ QString namespaces;
+ int size;
+ QList<int> roles;
+ QStringList roleNames;
+ int highestRole;
+
+#if QT_CONFIG(qml_network)
+ QNetworkReply *reply;
+#endif
+ QQuickXmlListModel::Status status;
+ QString errorString;
+ qreal progress;
+ int queryId;
+ QStringList keyRoleResultsCache;
+ QList<QQuickXmlListModelRole *> roleObjects;
+
+ static void append_role(QQmlListProperty<QQuickXmlListModelRole> *list, QQuickXmlListModelRole *role);
+ static void clear_role(QQmlListProperty<QQuickXmlListModelRole> *list);
+ QList<QList<QVariant> > data;
+ int redirectCount;
+};
+
+
+void QQuickXmlListModelPrivate::append_role(QQmlListProperty<QQuickXmlListModelRole> *list, QQuickXmlListModelRole *role)
+{
+ QQuickXmlListModel *_this = qobject_cast<QQuickXmlListModel *>(list->object);
+ if (_this && role) {
+ int i = _this->d_func()->roleObjects.count();
+ _this->d_func()->roleObjects.append(role);
+ if (_this->d_func()->roleNames.contains(role->name())) {
+ qmlWarning(role) << QQuickXmlListModel::tr("\"%1\" duplicates a previous role name and will be disabled.").arg(role->name());
+ return;
+ }
+ _this->d_func()->roles.insert(i, _this->d_func()->highestRole);
+ _this->d_func()->roleNames.insert(i, role->name());
+ ++_this->d_func()->highestRole;
+ }
+}
+
+//### clear needs to invalidate any cached data (in data table) as well
+// (and the model should emit the appropriate signals)
+void QQuickXmlListModelPrivate::clear_role(QQmlListProperty<QQuickXmlListModelRole> *list)
+{
+ QQuickXmlListModel *_this = static_cast<QQuickXmlListModel *>(list->object);
+ _this->d_func()->roles.clear();
+ _this->d_func()->roleNames.clear();
+ _this->d_func()->roleObjects.clear();
+}
+
+/*!
+ \qmltype XmlListModel
+ \instantiates QQuickXmlListModel
+ \inqmlmodule QtQuick.XmlListModel
+ \brief For specifying a read-only model using XPath expressions
+ \ingroup qtquick-models
+
+
+ To use this element, you will need to import the module with the following line:
+ \code
+ import QtQuick.XmlListModel 2.0
+ \endcode
+
+ XmlListModel is used to create a read-only model from XML data. It can be used as a data source
+ for view elements (such as ListView, PathView, GridView) and other elements that interact with model
+ data (such as \l Repeater).
+
+ For example, if there is a XML document at http://www.mysite.com/feed.xml like this:
+
+ \code
+ <?xml version="1.0" encoding="utf-8"?>
+ <rss version="2.0">
+ ...
+ <channel>
+ <item>
+ <title>A blog post</title>
+ <pubDate>Sat, 07 Sep 2010 10:00:01 GMT</pubDate>
+ </item>
+ <item>
+ <title>Another blog post</title>
+ <pubDate>Sat, 07 Sep 2010 15:35:01 GMT</pubDate>
+ </item>
+ </channel>
+ </rss>
+ \endcode
+
+ A XmlListModel could create a model from this data, like this:
+
+ \qml
+ import QtQuick 2.0
+ import QtQuick.XmlListModel 2.0
+
+ XmlListModel {
+ id: xmlModel
+ source: "http://www.mysite.com/feed.xml"
+ query: "/rss/channel/item"
+
+ XmlRole { name: "title"; query: "title/string()" }
+ XmlRole { name: "pubDate"; query: "pubDate/string()" }
+ }
+ \endqml
+
+ The \l {XmlListModel::query}{query} value of "/rss/channel/item" specifies that the XmlListModel should generate
+ a model item for each \c <item> in the XML document.
+
+ The XmlRole objects define the
+ model item attributes. Here, each model item will have \c title and \c pubDate
+ attributes that match the \c title and \c pubDate values of its corresponding \c <item>.
+ (See \l XmlRole::query for more examples of valid XPath expressions for XmlRole.)
+
+ The model could be used in a ListView, like this:
+
+ \qml
+ ListView {
+ width: 180; height: 300
+ model: xmlModel
+ delegate: Text { text: title + ": " + pubDate }
+ }
+ \endqml
+
+ \image qml-xmllistmodel-example.png
+
+ The XmlListModel data is loaded asynchronously, and \l status
+ is set to \c XmlListModel.Ready when loading is complete.
+ Note this means when XmlListModel is used for a view, the view is not
+ populated until the model is loaded.
+
+
+ \section2 Using Key XML Roles
+
+ You can define certain roles as "keys" so that when reload() is called,
+ the model will only add and refresh data that contains new values for
+ these keys.
+
+ For example, if above role for "pubDate" was defined like this instead:
+
+ \qml
+ XmlRole { name: "pubDate"; query: "pubDate/string()"; isKey: true }
+ \endqml
+
+ Then when reload() is called, the model will only add and reload
+ items with a "pubDate" value that is not already
+ present in the model.
+
+ This is useful when displaying the contents of XML documents that
+ are incrementally updated (such as RSS feeds) to avoid repainting the
+ entire contents of a model in a view.
+
+ If multiple key roles are specified, the model only adds and reload items
+ with a combined value of all key roles that is not already present in
+ the model.
+
+ \sa {Qt Quick Demo - RSS News}
+*/
+
+QQuickXmlListModel::QQuickXmlListModel(QObject *parent)
+ : QAbstractListModel(*(new QQuickXmlListModelPrivate), parent)
+{
+}
+
+QQuickXmlListModel::~QQuickXmlListModel()
+{
+}
+
+/*!
+ \qmlproperty list<XmlRole> QtQuick.XmlListModel::XmlListModel::roles
+
+ The roles to make available for this model.
+*/
+QQmlListProperty<QQuickXmlListModelRole> QQuickXmlListModel::roleObjects()
+{
+ Q_D(QQuickXmlListModel);
+ QQmlListProperty<QQuickXmlListModelRole> list(this, d->roleObjects);
+ list.append = &QQuickXmlListModelPrivate::append_role;
+ list.clear = &QQuickXmlListModelPrivate::clear_role;
+ return list;
+}
+
+QModelIndex QQuickXmlListModel::index(int row, int column, const QModelIndex &parent) const
+{
+ Q_D(const QQuickXmlListModel);
+ return !parent.isValid() && column == 0 && row >= 0 && row < d->size
+ ? createIndex(row, column)
+ : QModelIndex();
+}
+
+int QQuickXmlListModel::rowCount(const QModelIndex &parent) const
+{
+ Q_D(const QQuickXmlListModel);
+ return !parent.isValid() ? d->size : 0;
+}
+
+QVariant QQuickXmlListModel::data(const QModelIndex &index, int role) const
+{
+ Q_D(const QQuickXmlListModel);
+ const int roleIndex = d->roles.indexOf(role);
+ return (roleIndex == -1 || !index.isValid())
+ ? QVariant()
+ : d->data.value(roleIndex).value(index.row());
+}
+
+QHash<int, QByteArray> QQuickXmlListModel::roleNames() const
+{
+ Q_D(const QQuickXmlListModel);
+ QHash<int,QByteArray> roleNames;
+ for (int i = 0; i < d->roles.count(); ++i)
+ roleNames.insert(d->roles.at(i), d->roleNames.at(i).toUtf8());
+ return roleNames;
+}
+
+/*!
+ \qmlproperty int QtQuick.XmlListModel::XmlListModel::count
+ The number of data entries in the model.
+*/
+int QQuickXmlListModel::count() const
+{
+ Q_D(const QQuickXmlListModel);
+ return d->size;
+}
+
+/*!
+ \qmlproperty url QtQuick.XmlListModel::XmlListModel::source
+ The location of the XML data source.
+
+ If both \c source and \l xml are set, \l xml is used.
+*/
+QUrl QQuickXmlListModel::source() const
+{
+ Q_D(const QQuickXmlListModel);
+ return d->src;
+}
+
+void QQuickXmlListModel::setSource(const QUrl &src)
+{
+ Q_D(QQuickXmlListModel);
+ if (d->src != src) {
+ d->src = src;
+ if (d->xml.isEmpty()) // src is only used if d->xml is not set
+ reload();
+ emit sourceChanged();
+ }
+}
+
+/*!
+ \qmlproperty string QtQuick.XmlListModel::XmlListModel::xml
+ This property holds the XML data for this model, if set.
+
+ The text is assumed to be UTF-8 encoded.
+
+ If both \l source and \c xml are set, \c xml is used.
+*/
+QString QQuickXmlListModel::xml() const
+{
+ Q_D(const QQuickXmlListModel);
+ return d->xml;
+}
+
+void QQuickXmlListModel::setXml(const QString &xml)
+{
+ Q_D(QQuickXmlListModel);
+ if (d->xml != xml) {
+ d->xml = xml;
+ reload();
+ emit xmlChanged();
+ }
+}
+
+/*!
+ \qmlproperty string QtQuick.XmlListModel::XmlListModel::query
+ An absolute XPath query representing the base query for creating model items
+ from this model's XmlRole objects. The query should start with '/' or '//'.
+*/
+QString QQuickXmlListModel::query() const
+{
+ Q_D(const QQuickXmlListModel);
+ return d->query;
+}
+
+void QQuickXmlListModel::setQuery(const QString &query)
+{
+ Q_D(QQuickXmlListModel);
+ if (!query.startsWith(QLatin1Char('/'))) {
+ qmlWarning(this) << QCoreApplication::translate("QQuickXmlRoleList", "An XmlListModel query must start with '/' or \"//\"");
+ return;
+ }
+
+ if (d->query != query) {
+ d->query = query;
+ reload();
+ emit queryChanged();
+ }
+}
+
+/*!
+ \qmlproperty string QtQuick.XmlListModel::XmlListModel::namespaceDeclarations
+ The namespace declarations to be used in the XPath queries.
+
+ The namespaces should be declared as in XQuery. For example, if a requested document
+ at http://mysite.com/feed.xml uses the namespace "http://www.w3.org/2005/Atom",
+ this can be declared as the default namespace:
+
+ \qml
+ XmlListModel {
+ source: "http://mysite.com/feed.xml"
+ query: "/feed/entry"
+ namespaceDeclarations: "declare default element namespace 'http://www.w3.org/2005/Atom';"
+
+ XmlRole { name: "title"; query: "title/string()" }
+ }
+ \endqml
+*/
+QString QQuickXmlListModel::namespaceDeclarations() const
+{
+ Q_D(const QQuickXmlListModel);
+ return d->namespaces;
+}
+
+void QQuickXmlListModel::setNamespaceDeclarations(const QString &declarations)
+{
+ Q_D(QQuickXmlListModel);
+ if (d->namespaces != declarations) {
+ d->namespaces = declarations;
+ reload();
+ emit namespaceDeclarationsChanged();
+ }
+}
+
+/*!
+ \qmlmethod object QtQuick.XmlListModel::XmlListModel::get(int index)
+
+ Returns the item at \a index in the model.
+
+ For example, for a model like this:
+
+ \qml
+ XmlListModel {
+ id: model
+ source: "http://mysite.com/feed.xml"
+ query: "/feed/entry"
+ XmlRole { name: "title"; query: "title/string()" }
+ }
+ \endqml
+
+ This will access the \c title value for the first item in the model:
+
+ \js
+ var title = model.get(0).title;
+ \endjs
+*/
+QQmlV4Handle QQuickXmlListModel::get(int index) const
+{
+ // Must be called with a context and handle scope
+ Q_D(const QQuickXmlListModel);
+
+ if (index < 0 || index >= count())
+ return QQmlV4Handle(Encode::undefined());
+
+ QQmlEngine *engine = qmlContext(this)->engine();
+ ExecutionEngine *v4engine = engine->handle();
+ Scope scope(v4engine);
+ Scoped<Object> o(scope, v4engine->newObject());
+ ScopedString name(scope);
+ ScopedValue value(scope);
+ for (int ii = 0; ii < d->roleObjects.count(); ++ii) {
+ name = v4engine->newIdentifier(d->roleObjects[ii]->name());
+ value = v4engine->fromVariant(d->data.value(ii).value(index));
+ o->insertMember(name.getPointer(), value);
+ }
+
+ return QQmlV4Handle(o);
+}
+
+/*!
+ \qmlproperty enumeration QtQuick.XmlListModel::XmlListModel::status
+ Specifies the model loading status, which can be one of the following:
+
+ \list
+ \li XmlListModel.Null - No XML data has been set for this model.
+ \li XmlListModel.Ready - The XML data has been loaded into the model.
+ \li XmlListModel.Loading - The model is in the process of reading and loading XML data.
+ \li XmlListModel.Error - An error occurred while the model was loading. See errorString() for details
+ about the error.
+ \endlist
+
+ \sa progress
+
+*/
+QQuickXmlListModel::Status QQuickXmlListModel::status() const
+{
+ Q_D(const QQuickXmlListModel);
+ return d->status;
+}
+
+/*!
+ \qmlproperty real QtQuick.XmlListModel::XmlListModel::progress
+
+ This indicates the current progress of the downloading of the XML data
+ source. This value ranges from 0.0 (no data downloaded) to
+ 1.0 (all data downloaded). If the XML data is not from a remote source,
+ the progress becomes 1.0 as soon as the data is read.
+
+ Note that when the progress is 1.0, the XML data has been downloaded, but
+ it is yet to be loaded into the model at this point. Use the status
+ property to find out when the XML data has been read and loaded into
+ the model.
+
+ \sa status, source
+*/
+qreal QQuickXmlListModel::progress() const
+{
+ Q_D(const QQuickXmlListModel);
+ return d->progress;
+}
+
+/*!
+ \qmlmethod QtQuick.XmlListModel::XmlListModel::errorString()
+
+ Returns a string description of the last error that occurred
+ if \l status is XmlListModel::Error.
+*/
+QString QQuickXmlListModel::errorString() const
+{
+ Q_D(const QQuickXmlListModel);
+ return d->errorString;
+}
+
+void QQuickXmlListModel::classBegin()
+{
+ Q_D(QQuickXmlListModel);
+ d->isComponentComplete = false;
+
+ QQuickXmlQueryEngine *queryEngine = QQuickXmlQueryEngine::instance(qmlEngine(this));
+ connect(queryEngine, SIGNAL(queryCompleted(QQuickXmlQueryResult)),
+ SLOT(queryCompleted(QQuickXmlQueryResult)));
+ connect(queryEngine, SIGNAL(error(void*,QString)),
+ SLOT(queryError(void*,QString)));
+}
+
+void QQuickXmlListModel::componentComplete()
+{
+ Q_D(QQuickXmlListModel);
+ d->isComponentComplete = true;
+ reload();
+}
+
+/*!
+ \qmlmethod QtQuick.XmlListModel::XmlListModel::reload()
+
+ Reloads the model.
+
+ If no key roles have been specified, all existing model
+ data is removed, and the model is rebuilt from scratch.
+
+ Otherwise, items are only added if the model does not already
+ contain items with matching key role values.
+
+ \sa {Using key XML roles}, XmlRole::isKey
+*/
+void QQuickXmlListModel::reload()
+{
+ Q_D(QQuickXmlListModel);
+
+ if (!d->isComponentComplete)
+ return;
+
+ QQuickXmlQueryEngine::instance(qmlEngine(this))->abort(d->queryId);
+ d->queryId = -1;
+
+ if (d->size < 0)
+ d->size = 0;
+
+#if QT_CONFIG(qml_network)
+ if (d->reply) {
+ d->reply->abort();
+ d->deleteReply();
+ }
+#endif
+
+ if (!d->xml.isEmpty()) {
+ d->queryId = QQuickXmlQueryEngine::instance(qmlEngine(this))->doQuery(d->query, d->namespaces, d->xml.toUtf8(), &d->roleObjects, d->keyRoleResultsCache);
+ d->notifyQueryStarted(false);
+
+ } else if (d->src.isEmpty()) {
+ d->queryId = XMLLISTMODEL_CLEAR_ID;
+ d->notifyQueryStarted(false);
+ QTimer::singleShot(0, this, SLOT(dataCleared()));
+
+ } else if (QQmlFile::isLocalFile(d->src)) {
+ QFile file(QQmlFile::urlToLocalFileOrQrc(d->src));
+ QByteArray data = file.open(QIODevice::ReadOnly) ? file.readAll() : QByteArray();
+ d->notifyQueryStarted(false);
+ if (data.isEmpty()) {
+ d->queryId = XMLLISTMODEL_CLEAR_ID;
+ QTimer::singleShot(0, this, SLOT(dataCleared()));
+ } else {
+ d->queryId = QQuickXmlQueryEngine::instance(qmlEngine(this))->doQuery(
+ d->query, d->namespaces, data, &d->roleObjects, d->keyRoleResultsCache);
+ }
+ } else {
+#if QT_CONFIG(qml_network)
+ d->notifyQueryStarted(true);
+ QNetworkRequest req(d->src);
+ req.setRawHeader("Accept", "application/xml,*/*");
+ d->reply = qmlContext(this)->engine()->networkAccessManager()->get(req);
+ QObject::connect(d->reply, SIGNAL(finished()), this, SLOT(requestFinished()));
+ QObject::connect(d->reply, SIGNAL(downloadProgress(qint64,qint64)),
+ this, SLOT(requestProgress(qint64,qint64)));
+#else
+ d->queryId = XMLLISTMODEL_CLEAR_ID;
+ d->notifyQueryStarted(false);
+ QTimer::singleShot(0, this, SLOT(dataCleared()));
+#endif
+ }
+}
+
+#define XMLLISTMODEL_MAX_REDIRECT 16
+
+#if QT_CONFIG(qml_network)
+void QQuickXmlListModel::requestFinished()
+{
+ Q_D(QQuickXmlListModel);
+
+ d->redirectCount++;
+ if (d->redirectCount < XMLLISTMODEL_MAX_REDIRECT) {
+ QVariant redirect = d->reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
+ if (redirect.isValid()) {
+ QUrl url = d->reply->url().resolved(redirect.toUrl());
+ d->deleteReply();
+ setSource(url);
+ return;
+ }
+ }
+ d->redirectCount = 0;
+
+ if (d->reply->error() != QNetworkReply::NoError) {
+ d->errorString = d->reply->errorString();
+ d->deleteReply();
+
+ if (d->size > 0) {
+ beginRemoveRows(QModelIndex(), 0, d->size - 1);
+ d->data.clear();
+ d->size = 0;
+ endRemoveRows();
+ emit countChanged();
+ }
+
+ d->status = Error;
+ d->queryId = -1;
+ emit statusChanged(d->status);
+ } else {
+ QByteArray data = d->reply->readAll();
+ if (data.isEmpty()) {
+ d->queryId = XMLLISTMODEL_CLEAR_ID;
+ QTimer::singleShot(0, this, SLOT(dataCleared()));
+ } else {
+ d->queryId = QQuickXmlQueryEngine::instance(qmlEngine(this))->doQuery(d->query, d->namespaces, data, &d->roleObjects, d->keyRoleResultsCache);
+ }
+ d->deleteReply();
+
+ d->progress = 1.0;
+ emit progressChanged(d->progress);
+ }
+}
+#endif
+
+void QQuickXmlListModel::requestProgress(qint64 received, qint64 total)
+{
+ Q_D(QQuickXmlListModel);
+ if (d->status == Loading && total > 0) {
+ d->progress = qreal(received)/total;
+ emit progressChanged(d->progress);
+ }
+}
+
+void QQuickXmlListModel::dataCleared()
+{
+ Q_D(QQuickXmlListModel);
+ QQuickXmlQueryResult r;
+ r.queryId = XMLLISTMODEL_CLEAR_ID;
+ r.size = 0;
+ r.removed << qMakePair(0, count());
+ r.keyRoleResultsCache = d->keyRoleResultsCache;
+ queryCompleted(r);
+}
+
+void QQuickXmlListModel::queryError(void* object, const QString& error)
+{
+ // Be extra careful, object may no longer exist, it's just an ID.
+ Q_D(QQuickXmlListModel);
+ for (int i=0; i<d->roleObjects.count(); i++) {
+ if (d->roleObjects.at(i) == static_cast<QQuickXmlListModelRole*>(object)) {
+ qmlWarning(d->roleObjects.at(i)) << QQuickXmlListModel::tr("invalid query: \"%1\"").arg(error);
+ return;
+ }
+ }
+ qmlWarning(this) << QQuickXmlListModel::tr("invalid query: \"%1\"").arg(error);
+}
+
+void QQuickXmlListModel::queryCompleted(const QQuickXmlQueryResult &result)
+{
+ Q_D(QQuickXmlListModel);
+ if (result.queryId != d->queryId)
+ return;
+
+ int origCount = d->size;
+ bool sizeChanged = result.size != d->size;
+
+ d->keyRoleResultsCache = result.keyRoleResultsCache;
+ if (d->src.isEmpty() && d->xml.isEmpty())
+ d->status = Null;
+ else
+ d->status = Ready;
+ d->errorString.clear();
+ d->queryId = -1;
+
+ bool hasKeys = false;
+ for (int i=0; i<d->roleObjects.count(); i++) {
+ if (d->roleObjects[i]->isKey()) {
+ hasKeys = true;
+ break;
+ }
+ }
+ if (!hasKeys) {
+ if (origCount > 0) {
+ beginRemoveRows(QModelIndex(), 0, origCount - 1);
+ endRemoveRows();
+ }
+ d->size = result.size;
+ d->data = result.data;
+ if (d->size > 0) {
+ beginInsertRows(QModelIndex(), 0, d->size - 1);
+ endInsertRows();
+ }
+ } else {
+ for (int i=0; i<result.removed.count(); i++) {
+ const int index = result.removed[i].first;
+ const int count = result.removed[i].second;
+ if (count > 0) {
+ beginRemoveRows(QModelIndex(), index, index + count - 1);
+ endRemoveRows();
+ }
+ }
+ d->size = result.size;
+ d->data = result.data;
+ for (int i=0; i<result.inserted.count(); i++) {
+ const int index = result.inserted[i].first;
+ const int count = result.inserted[i].second;
+ if (count > 0) {
+ beginInsertRows(QModelIndex(), index, index + count - 1);
+ endInsertRows();
+ }
+ }
+ }
+ if (sizeChanged)
+ emit countChanged();
+
+ emit statusChanged(d->status);
+}
+
+QT_END_NAMESPACE
+
+#include <qqmlxmllistmodel.moc>
diff --git a/src/imports/xmllistmodel/qqmlxmllistmodel_p.h b/src/imports/xmllistmodel/qqmlxmllistmodel_p.h
new file mode 100644
index 0000000..65f1299
--- /dev/null
+++ b/src/imports/xmllistmodel/qqmlxmllistmodel_p.h
@@ -0,0 +1,219 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** 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-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QQUICKXMLLISTMODEL_H
+#define QQUICKXMLLISTMODEL_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <qqml.h>
+#include <qqmlinfo.h>
+
+#include <QtCore/qurl.h>
+#include <QtCore/qstringlist.h>
+#include <QtCore/qabstractitemmodel.h>
+#include <private/qv8engine_p.h>
+
+QT_BEGIN_NAMESPACE
+
+
+class QQmlContext;
+class QQuickXmlListModelRole;
+class QQuickXmlListModelPrivate;
+
+struct QQuickXmlQueryResult {
+ int queryId;
+ int size;
+ QList<QList<QVariant> > data;
+ QList<QPair<int, int> > inserted;
+ QList<QPair<int, int> > removed;
+ QStringList keyRoleResultsCache;
+};
+
+class QQuickXmlListModel : public QAbstractListModel, public QQmlParserStatus
+{
+ Q_OBJECT
+ Q_INTERFACES(QQmlParserStatus)
+
+ Q_PROPERTY(Status status READ status NOTIFY statusChanged)
+ Q_PROPERTY(qreal progress READ progress NOTIFY progressChanged)
+ Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged)
+ Q_PROPERTY(QString xml READ xml WRITE setXml NOTIFY xmlChanged)
+ Q_PROPERTY(QString query READ query WRITE setQuery NOTIFY queryChanged)
+ Q_PROPERTY(QString namespaceDeclarations READ namespaceDeclarations WRITE setNamespaceDeclarations NOTIFY namespaceDeclarationsChanged)
+ Q_PROPERTY(QQmlListProperty<QQuickXmlListModelRole> roles READ roleObjects)
+ Q_PROPERTY(int count READ count NOTIFY countChanged)
+ Q_CLASSINFO("DefaultProperty", "roles")
+
+public:
+ QQuickXmlListModel(QObject *parent = 0);
+ ~QQuickXmlListModel();
+
+ QModelIndex index(int row, int column, const QModelIndex &parent) const override;
+ int rowCount(const QModelIndex &parent) const override;
+ QVariant data(const QModelIndex &index, int role) const override;
+ QHash<int, QByteArray> roleNames() const override;
+
+ int count() const;
+
+ QQmlListProperty<QQuickXmlListModelRole> roleObjects();
+
+ QUrl source() const;
+ void setSource(const QUrl&);
+
+ QString xml() const;
+ void setXml(const QString&);
+
+ QString query() const;
+ void setQuery(const QString&);
+
+ QString namespaceDeclarations() const;
+ void setNamespaceDeclarations(const QString&);
+
+ Q_INVOKABLE QQmlV4Handle get(int index) const;
+
+ enum Status { Null, Ready, Loading, Error };
+ Q_ENUM(Status)
+ Status status() const;
+ qreal progress() const;
+
+ Q_INVOKABLE QString errorString() const;
+
+ void classBegin() override;
+ void componentComplete() override;
+
+Q_SIGNALS:
+ void statusChanged(QQuickXmlListModel::Status);
+ void progressChanged(qreal progress);
+ void countChanged();
+ void sourceChanged();
+ void xmlChanged();
+ void queryChanged();
+ void namespaceDeclarationsChanged();
+
+public Q_SLOTS:
+ // ### need to use/expose Expiry to guess when to call this?
+ // ### property to auto-call this on reasonable Expiry?
+ // ### LastModified/Age also useful to guess.
+ // ### Probably also applies to other network-requesting types.
+ void reload();
+
+private Q_SLOTS:
+#if QT_CONFIG(qml_network)
+ void requestFinished();
+#endif
+ void requestProgress(qint64,qint64);
+ void dataCleared();
+ void queryCompleted(const QQuickXmlQueryResult &);
+ void queryError(void* object, const QString& error);
+
+private:
+ Q_DECLARE_PRIVATE(QQuickXmlListModel)
+ Q_DISABLE_COPY(QQuickXmlListModel)
+};
+
+class QQuickXmlListModelRole : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
+ Q_PROPERTY(QString query READ query WRITE setQuery NOTIFY queryChanged)
+ Q_PROPERTY(bool isKey READ isKey WRITE setIsKey NOTIFY isKeyChanged)
+public:
+ QQuickXmlListModelRole() : m_isKey(false) {}
+ ~QQuickXmlListModelRole() {}
+
+ QString name() const { return m_name; }
+ void setName(const QString &name) {
+ if (name == m_name)
+ return;
+ m_name = name;
+ Q_EMIT nameChanged();
+ }
+
+ QString query() const { return m_query; }
+ void setQuery(const QString &query)
+ {
+ if (query.startsWith(QLatin1Char('/'))) {
+ qmlWarning(this) << tr("An XmlRole query must not start with '/'");
+ return;
+ }
+ if (m_query == query)
+ return;
+ m_query = query;
+ Q_EMIT queryChanged();
+ }
+
+ bool isKey() const { return m_isKey; }
+ void setIsKey(bool b) {
+ if (m_isKey == b)
+ return;
+ m_isKey = b;
+ Q_EMIT isKeyChanged();
+ }
+
+ bool isValid() const {
+ return !m_name.isEmpty() && !m_query.isEmpty();
+ }
+
+Q_SIGNALS:
+ void nameChanged();
+ void queryChanged();
+ void isKeyChanged();
+
+private:
+ QString m_name;
+ QString m_query;
+ bool m_isKey;
+};
+
+QT_END_NAMESPACE
+
+QML_DECLARE_TYPE(QQuickXmlListModel)
+QML_DECLARE_TYPE(QQuickXmlListModelRole)
+
+#endif // QQUICKXMLLISTMODEL_H
diff --git a/src/imports/xmllistmodel/xmllistmodel.pro b/src/imports/xmllistmodel/xmllistmodel.pro
new file mode 100644
index 0000000..8ee9ea3
--- /dev/null
+++ b/src/imports/xmllistmodel/xmllistmodel.pro
@@ -0,0 +1,12 @@
+CXX_MODULE = qml
+TARGET = qmlxmllistmodelplugin
+TARGETPATH = QtQuick/XmlListModel
+IMPORT_VERSION = 2.$$QT_MINOR_VERSION
+
+QT = xmlpatterns qml-private core-private
+qtConfig(qml-network): QT += network
+
+SOURCES += qqmlxmllistmodel.cpp plugin.cpp
+HEADERS += qqmlxmllistmodel_p.h
+
+load(qml_plugin)
diff --git a/src/src.pro b/src/src.pro
index cd32a1b..5083270 100644
--- a/src/src.pro
+++ b/src/src.pro
@@ -1,2 +1,8 @@
TEMPLATE = subdirs
SUBDIRS += xmlpatterns
+
+qtHaveModule(qml){
+ SUBDIRS += imports
+ imports.depends = xmlpatterns
+}
+
diff --git a/tests/auto/auto.pro b/tests/auto/auto.pro
index aba49b6..40f4c5c 100644
--- a/tests/auto/auto.pro
+++ b/tests/auto/auto.pro
@@ -53,4 +53,7 @@ xmlpatternsxqts.depends = xmlpatternssdk
xmlpatternsxqts \
xmlpatternsxslts \
+qtHaveModule(quick): SUBIDIRS += qquickxmllistmodel
+
!cross_compile: SUBDIRS += host.pro
+
diff --git a/tests/auto/qquickxmllistmodel/data/empty.xml b/tests/auto/qquickxmllistmodel/data/empty.xml
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/auto/qquickxmllistmodel/data/empty.xml
diff --git a/tests/auto/qquickxmllistmodel/data/get.qml b/tests/auto/qquickxmllistmodel/data/get.qml
new file mode 100644
index 0000000..509da71
--- /dev/null
+++ b/tests/auto/qquickxmllistmodel/data/get.qml
@@ -0,0 +1,61 @@
+import QtQuick 2.0
+import QtQuick.XmlListModel 2.0
+
+XmlListModel {
+ source: "model.xml"
+ query: "/Pets/Pet"
+ XmlRole { name: "name"; query: "name/string()" }
+ XmlRole { name: "type"; query: "type/string()" }
+ XmlRole { name: "age"; query: "age/number()" }
+ XmlRole { name: "size"; query: "size/string()" }
+
+ id: root
+
+ property bool preTest: false
+ property bool postTest: false
+
+ function runPreTest() {
+ if (root.get(0) != undefined)
+ return;
+
+ preTest = true;
+ }
+
+ function runPostTest() {
+ if (root.get(-1) != undefined)
+ return;
+
+ var row = root.get(0);
+ if (row.name != "Polly" ||
+ row.type != "Parrot" ||
+ row.age != 12 ||
+ row.size != "Small")
+ return;
+
+ row = root.get(1);
+ if (row.name != "Penny" ||
+ row.type != "Turtle" ||
+ row.age != 4 ||
+ row.size != "Small")
+ return;
+
+ row = root.get(7);
+ if (row.name != "Rover" ||
+ row.type != "Dog" ||
+ row.age != 0 ||
+ row.size != "Large")
+ return;
+
+ row = root.get(8);
+ if (row.name != "Tiny" ||
+ row.type != "Elephant" ||
+ row.age != 15 ||
+ row.size != "Large")
+ return;
+
+ if (root.get(9) != undefined)
+ return;
+
+ postTest = true;
+ }
+}
diff --git a/tests/auto/qquickxmllistmodel/data/groups.qml b/tests/auto/qquickxmllistmodel/data/groups.qml
new file mode 100644
index 0000000..c1b574a
--- /dev/null
+++ b/tests/auto/qquickxmllistmodel/data/groups.qml
@@ -0,0 +1,10 @@
+import QtQuick 2.0
+import QtQuick.XmlListModel 2.0
+
+XmlListModel {
+ source: "groups.xml"
+ query: "//animal[@name='Garfield']/parent::group"
+
+ XmlRole { name: "id"; query: "@id/string()" }
+ XmlRole { name: "name"; query: "@name/string()" }
+}
diff --git a/tests/auto/qquickxmllistmodel/data/groups.xml b/tests/auto/qquickxmllistmodel/data/groups.xml
new file mode 100644
index 0000000..5de4d2e
--- /dev/null
+++ b/tests/auto/qquickxmllistmodel/data/groups.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<groups version="2.0">
+ <group id="1" name="Animals" type="root">
+ <group id="11" name="dogs">
+ <animal id="111" name="Lassie"/>
+ <animal id="112" name="Laika"/>
+ <animal id="113" name="Wile E. Coyote" type="fictional"/>
+ </group>
+ <group id="12" name="cats">
+ <animal id="121" name="Garfield" type="fictional"/>
+ <animal id="122" name="Sylvester" type="fictional"/>
+ </group>
+ <group id="13" name="birds">
+ <animal id="131" name="Donald Duck" type="fictional"/>
+ <animal id="132" name="Phoenix" type="fictional"/>
+ </group>
+ </group>
+</groups>
diff --git a/tests/auto/qquickxmllistmodel/data/model.qml b/tests/auto/qquickxmllistmodel/data/model.qml
new file mode 100644
index 0000000..2df3927
--- /dev/null
+++ b/tests/auto/qquickxmllistmodel/data/model.qml
@@ -0,0 +1,11 @@
+import QtQuick 2.0
+import QtQuick.XmlListModel 2.0
+
+XmlListModel {
+ source: "model.xml"
+ query: "/Pets/Pet"
+ XmlRole { name: "name"; query: "name/string()" }
+ XmlRole { name: "type"; query: "type/string()" }
+ XmlRole { name: "age"; query: "age/number()" }
+ XmlRole { name: "size"; query: "size/string()" }
+}
diff --git a/tests/auto/qquickxmllistmodel/data/model.xml b/tests/auto/qquickxmllistmodel/data/model.xml
new file mode 100644
index 0000000..40cd6d0
--- /dev/null
+++ b/tests/auto/qquickxmllistmodel/data/model.xml
@@ -0,0 +1,54 @@
+<Pets>
+ <Pet>
+ <name>Polly</name>
+ <type>Parrot</type>
+ <age>12</age>
+ <size>Small</size>
+ </Pet>
+ <Pet>
+ <name>Penny</name>
+ <type>Turtle</type>
+ <age>4</age>
+ <size>Small</size>
+ </Pet>
+ <Pet>
+ <name>Warren</name>
+ <type>Rabbit</type>
+ <age>2</age>
+ <size>Small</size>
+ </Pet>
+ <Pet>
+ <name>Spot</name>
+ <type>Dog</type>
+ <age>9</age>
+ <size>Medium</size>
+ </Pet>
+ <Pet>
+ <name>Whiskers</name>
+ <type>Cat</type>
+ <age>2</age>
+ <size>Medium</size>
+ </Pet>
+ <Pet>
+ <name>Joey</name>
+ <type>Kangaroo</type>
+ <age>1</age>
+ </Pet>
+ <Pet>
+ <name>Kimba</name>
+ <type>Bunny</type>
+ <age>65</age>
+ <size>Large</size>
+ </Pet>
+ <Pet>
+ <name>Rover</name>
+ <type>Dog</type>
+ <size>Large</size>
+ </Pet>
+ <Pet>
+ <name>Tiny</name>
+ <type>Elephant</type>
+ <age>15</age>
+ <size>Large</size>
+ </Pet>
+</Pets>
diff --git a/tests/auto/qquickxmllistmodel/data/model2.xml b/tests/auto/qquickxmllistmodel/data/model2.xml
new file mode 100644
index 0000000..dab2ec6
--- /dev/null
+++ b/tests/auto/qquickxmllistmodel/data/model2.xml
@@ -0,0 +1,14 @@
+<Pets>
+ <Pet>
+ <name>Polly</name>
+ <type>Parrot</type>
+ <age>12</age>
+ <size>Small</size>
+ </Pet>
+ <Pet>
+ <name>Penny</name>
+ <type>Turtle</type>
+ <age>4</age>
+ <size>Small</size>
+ </Pet>
+</Pets>
diff --git a/tests/auto/qquickxmllistmodel/data/propertychanges.qml b/tests/auto/qquickxmllistmodel/data/propertychanges.qml
new file mode 100644
index 0000000..f8a97bf
--- /dev/null
+++ b/tests/auto/qquickxmllistmodel/data/propertychanges.qml
@@ -0,0 +1,11 @@
+import QtQuick 2.0
+import QtQuick.XmlListModel 2.0
+
+XmlListModel {
+ source: "model.xml"
+ query: "/Pets/Pet"
+ XmlRole { objectName: "role"; name: "name"; query: "name/string()" }
+ XmlRole { name: "type"; query: "type/string()" }
+ XmlRole { name: "age"; query: "age/number()" }
+ XmlRole { name: "size"; query: "size/string()" }
+}
diff --git a/tests/auto/qquickxmllistmodel/data/proxyCrash.qml b/tests/auto/qquickxmllistmodel/data/proxyCrash.qml
new file mode 100644
index 0000000..c0c5a25
--- /dev/null
+++ b/tests/auto/qquickxmllistmodel/data/proxyCrash.qml
@@ -0,0 +1,9 @@
+import QtQuick 2.0
+import QtQuick.XmlListModel 2.0
+import SortFilterProxyModel 1.0
+
+SortFilterProxyModel {
+ source: XmlListModel {
+ XmlRole { }
+ }
+}
diff --git a/tests/auto/qquickxmllistmodel/data/recipes.qml b/tests/auto/qquickxmllistmodel/data/recipes.qml
new file mode 100644
index 0000000..dc609e9
--- /dev/null
+++ b/tests/auto/qquickxmllistmodel/data/recipes.qml
@@ -0,0 +1,11 @@
+import QtQuick 2.0
+import QtQuick.XmlListModel 2.0
+
+XmlListModel {
+ source: "recipes.xml"
+ query: "/recipes/recipe"
+ XmlRole { name: "title"; query: "@title/string()" }
+ XmlRole { name: "picture"; query: "picture/string()" }
+ XmlRole { name: "ingredients"; query: "ingredients/string()" }
+ XmlRole { name: "preparation"; query: "method/string()" }
+}
diff --git a/tests/auto/qquickxmllistmodel/data/recipes.xml b/tests/auto/qquickxmllistmodel/data/recipes.xml
new file mode 100644
index 0000000..d71de60
--- /dev/null
+++ b/tests/auto/qquickxmllistmodel/data/recipes.xml
@@ -0,0 +1,90 @@
+<recipes>
+ <recipe title="Pancakes">
+ <picture>content/pics/pancakes.jpg</picture>
+ <ingredients><![CDATA[<html>
+ <ul>
+ <li> 1 cup (150g) self-raising flour
+ <li> 1 tbs caster sugar
+ <li> 3/4 cup (185ml) milk
+ <li> 1 egg
+ </ul>
+ </html>
+ ]]></ingredients>
+ <method><![CDATA[<html>
+ <ol>
+ <li> Sift flour and sugar together into a bowl. Add a pinch of salt.
+ <li> Beat milk and egg together, then add to dry ingredients. Beat until smooth.
+ <li> Pour mixture into a pan on medium heat and cook until bubbles appear on the surface.
+ <li> Turn over and cook other side until golden.
+ </ol>
+ </html>
+ ]]></method>
+ </recipe>
+ <recipe title="Fruit Salad">
+ <picture>content/pics/fruit-salad.jpg</picture>
+ <ingredients><![CDATA[* Seasonal Fruit]]></ingredients>
+ <method><![CDATA[* Chop fruit and place in a bowl.]]></method>
+ </recipe>
+ <recipe title="Vegetable Soup">
+ <picture>content/pics/vegetable-soup.jpg</picture>
+ <ingredients><![CDATA[<html>
+ <ul>
+ <li> 1 onion
+ <li> 1 turnip
+ <li> 1 potato
+ <li> 1 carrot
+ <li> 1 head of celery
+ <li> 1 1/2 litres of water
+ </ul>
+ </html>
+ ]]></ingredients>
+ <method><![CDATA[<html>
+ <ol>
+ <li> Chop vegetables.
+ <li> Boil in water until vegetables soften.
+ <li> Season with salt and pepper to taste.
+ </ol>
+ </html>
+ ]]></method>
+ </recipe>
+ <recipe title="Hamburger">
+ <picture>content/pics/hamburger.jpg</picture>
+ <ingredients><![CDATA[<html>
+ <ul>
+ <li> 500g minced beef
+ <li> Seasoning
+ <li> lettuce, tomato, onion, cheese
+ <li> 1 hamburger bun for each burger
+ </ul>
+ </html>
+ ]]></ingredients>
+ <method><![CDATA[<html>
+ <ol>
+ <li> Mix the beef, together with seasoning, in a food processor.
+ <li> Shape the beef into burgers.
+ <li> Grill the burgers for about 5 mins on each side (until cooked through)
+ <li> Serve each burger on a bun with ketchup, cheese, lettuce, tomato and onion.
+ </ol>
+ </html>
+ ]]></method>
+ </recipe>
+ <recipe title="Lemonade">
+ <picture>content/pics/lemonade.jpg</picture>
+ <ingredients><![CDATA[<html>
+ <ul>
+ <li> 1 cup Lemon Juice
+ <li> 1 cup Sugar
+ <li> 6 Cups of Water (2 cups warm water, 4 cups cold water)
+ </ul>
+ </html>
+ ]]></ingredients>
+ <method><![CDATA[<html>
+ <ol>
+ <li> Pour 2 cups of warm water into a pitcher and stir in sugar until it dissolves.
+ <li> Pour in lemon juice, stir again, and add 4 cups of cold water.
+ <li> Chill or serve over ice cubes.
+ </ol>
+ </html>
+ ]]></method>
+ </recipe>
+</recipes>
diff --git a/tests/auto/qquickxmllistmodel/data/roleCrash.qml b/tests/auto/qquickxmllistmodel/data/roleCrash.qml
new file mode 100644
index 0000000..6a7059b
--- /dev/null
+++ b/tests/auto/qquickxmllistmodel/data/roleCrash.qml
@@ -0,0 +1,8 @@
+import QtQuick 2.0
+import QtQuick.XmlListModel 2.0
+
+XmlListModel {
+ id: model
+ XmlRole {}
+ Component.onCompleted: model.roles = 0
+}
diff --git a/tests/auto/qquickxmllistmodel/data/roleErrors.qml b/tests/auto/qquickxmllistmodel/data/roleErrors.qml
new file mode 100644
index 0000000..91664b6
--- /dev/null
+++ b/tests/auto/qquickxmllistmodel/data/roleErrors.qml
@@ -0,0 +1,11 @@
+import QtQuick 2.0
+import QtQuick.XmlListModel 2.0
+
+XmlListModel {
+ source: "model.xml"
+ query: "/Pets/Pet"
+ XmlRole { name: "name"; query: "/name/string()" } //starts with '/'
+ XmlRole { name: "type"; query: "type" } //no type
+ XmlRole { name: "age"; query: "age/" } //ends with '/'
+ XmlRole { name: "size"; query: "size/number()" } //wrong type
+}
diff --git a/tests/auto/qquickxmllistmodel/data/roleKeys.qml b/tests/auto/qquickxmllistmodel/data/roleKeys.qml
new file mode 100644
index 0000000..9f667d8
--- /dev/null
+++ b/tests/auto/qquickxmllistmodel/data/roleKeys.qml
@@ -0,0 +1,13 @@
+import QtQuick 2.0
+import QtQuick.XmlListModel 2.0
+
+XmlListModel {
+ query: "/data/item"
+ XmlRole { id: nameRole; name: "name"; query: "name/string()"; isKey: true }
+ XmlRole { name: "age"; query: "age/number()"; isKey: true }
+ XmlRole { name: "sport"; query: "sport/string()" }
+
+ function disableNameKey() {
+ nameRole.isKey = false;
+ }
+}
diff --git a/tests/auto/qquickxmllistmodel/data/testtypes.qml b/tests/auto/qquickxmllistmodel/data/testtypes.qml
new file mode 100644
index 0000000..5ec1ffa
--- /dev/null
+++ b/tests/auto/qquickxmllistmodel/data/testtypes.qml
@@ -0,0 +1,8 @@
+import QtQuick 2.0
+import QtQuick.XmlListModel 2.0
+
+XmlListModel {
+ query: "/data"
+ XmlRole { name: "stringValue"; query: "a-string/string()" }
+ XmlRole { name: "numberValue"; query: "a-number/number()" }
+}
diff --git a/tests/auto/qquickxmllistmodel/data/unique.qml b/tests/auto/qquickxmllistmodel/data/unique.qml
new file mode 100644
index 0000000..322a2e4
--- /dev/null
+++ b/tests/auto/qquickxmllistmodel/data/unique.qml
@@ -0,0 +1,9 @@
+import QtQuick 2.0
+import QtQuick.XmlListModel 2.0
+
+XmlListModel {
+ source: "model.xml"
+ query: "/Pets/Pet"
+ XmlRole { name: "name"; query: "name/string()" }
+ XmlRole { name: "name"; query: "type/string()" }
+}
diff --git a/tests/auto/qquickxmllistmodel/qquickxmllistmodel.pro b/tests/auto/qquickxmllistmodel/qquickxmllistmodel.pro
new file mode 100644
index 0000000..e2d873c
--- /dev/null
+++ b/tests/auto/qquickxmllistmodel/qquickxmllistmodel.pro
@@ -0,0 +1,16 @@
+CONFIG += testcase
+TARGET = tst_qquickxmllistmodel
+macos:CONFIG -= app_bundle
+
+SOURCES += tst_qquickxmllistmodel.cpp \
+ ../../../../src/imports/xmllistmodel/qqmlxmllistmodel.cpp
+HEADERS += ../../../../src/imports/xmllistmodel/qqmlxmllistmodel_p.h
+
+include (../../shared/util.pri)
+
+TESTDATA = data/*
+
+QT += core-private gui-private qml-private network testlib xmlpatterns
+
+OTHER_FILES += \
+ data/groups.qml
diff --git a/tests/auto/qquickxmllistmodel/tst_qquickxmllistmodel.cpp b/tests/auto/qquickxmllistmodel/tst_qquickxmllistmodel.cpp
new file mode 100644
index 0000000..b2e5703
--- /dev/null
+++ b/tests/auto/qquickxmllistmodel/tst_qquickxmllistmodel.cpp
@@ -0,0 +1,1011 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the test suite 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 <QtTest/QtTest>
+#include <QtGlobal>
+#include <math.h>
+#include <QMetaObject>
+#include <qtest.h>
+#include <QtTest/qsignalspy.h>
+#include <QtQml/qqmlnetworkaccessmanagerfactory.h>
+#include <QtNetwork/qnetworkaccessmanager.h>
+#include <QtNetwork/qnetworkrequest.h>
+#include <QtCore/qtimer.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qtemporaryfile.h>
+#include <QtCore/qsortfilterproxymodel.h>
+#include "../../shared/util.h"
+#include <private/qqmlengine_p.h>
+
+#include <QtQml/qqmlengine.h>
+#include <QtQml/qqmlcomponent.h>
+#include "../../../../src/imports/xmllistmodel/qqmlxmllistmodel_p.h"
+
+#include <algorithm>
+
+typedef QPair<int, int> QQuickXmlListRange;
+typedef QList<QVariantList> QQmlXmlModelData;
+
+Q_DECLARE_METATYPE(QList<QQuickXmlListRange>)
+Q_DECLARE_METATYPE(QQmlXmlModelData)
+Q_DECLARE_METATYPE(QQuickXmlListModel::Status)
+
+class tst_qquickxmllistmodel : public QQmlDataTest
+
+{
+ Q_OBJECT
+public:
+ tst_qquickxmllistmodel() {}
+
+private slots:
+ void initTestCase() {
+ QQmlDataTest::initTestCase();
+ qRegisterMetaType<QQuickXmlListModel::Status>();
+ }
+
+ void buildModel();
+ void testTypes();
+ void testTypes_data();
+ void cdata();
+ void attributes();
+ void roles();
+ void roleErrors();
+ void uniqueRoleNames();
+ void headers();
+ void xml();
+ void xml_data();
+ void source();
+ void source_data();
+ void data();
+ void get();
+ void reload();
+ void useKeys();
+ void useKeys_data();
+ void noKeysValueChanges();
+ void keysChanged();
+ void threading();
+ void threading_data();
+ void propertyChanges();
+ void selectAncestor();
+
+ void roleCrash();
+ void proxyCrash();
+
+private:
+ QString errorString(QAbstractItemModel *model) {
+ QString ret;
+ QMetaObject::invokeMethod(model, "errorString", Q_RETURN_ARG(QString, ret));
+ return ret;
+ }
+
+ QString makeItemXmlAndData(const QString &data, QQmlXmlModelData *modelData = 0) const
+ {
+ if (modelData)
+ modelData->clear();
+ QString xml;
+
+ if (!data.isEmpty()) {
+ QStringList items = data.split(QLatin1Char(';'));
+ foreach (const QString &item, items) {
+ if (item.isEmpty())
+ continue;
+ QVariantList variants;
+ xml += QLatin1String("<item>");
+ QStringList fields = item.split(QLatin1Char(','));
+ foreach (const QString &field, fields) {
+ QStringList values = field.split(QLatin1Char('='));
+ if (values.count() != 2) {
+ qWarning() << "makeItemXmlAndData: invalid field:" << field;
+ continue;
+ }
+ xml += QString("<%1>%2</%1>").arg(values[0], values[1]);
+ if (!modelData)
+ continue;
+ bool isNum = false;
+ int number = values[1].toInt(&isNum);
+ if (isNum)
+ variants << number;
+ else
+ variants << values[1];
+ }
+ xml += QLatin1String("</item>");
+ if (modelData)
+ modelData->append(variants);
+ }
+ }
+
+ QString decl = "<?xml version=\"1.0\" encoding=\"iso-8859-1\" ?>";
+ return decl + QLatin1String("<data>") + xml + QLatin1String("</data>");
+ }
+
+ QQmlEngine engine;
+};
+
+class CustomNetworkAccessManagerFactory : public QObject, public QQmlNetworkAccessManagerFactory
+{
+ Q_OBJECT
+public:
+ QVariantMap lastSentHeaders;
+
+protected:
+ QNetworkAccessManager *create(QObject *parent);
+};
+
+class CustomNetworkAccessManager : public QNetworkAccessManager
+{
+ Q_OBJECT
+public:
+ CustomNetworkAccessManager(CustomNetworkAccessManagerFactory *factory, QObject *parent)
+ : QNetworkAccessManager(parent), m_factory(factory) {}
+
+protected:
+ QNetworkReply *createRequest(Operation op, const QNetworkRequest &req, QIODevice * outgoingData = 0)
+ {
+ if (m_factory) {
+ QVariantMap map;
+ foreach (const QString &header, req.rawHeaderList())
+ map[header] = req.rawHeader(header.toUtf8());
+ m_factory->lastSentHeaders = map;
+ }
+ return QNetworkAccessManager::createRequest(op, req, outgoingData);
+ }
+
+ QPointer<CustomNetworkAccessManagerFactory> m_factory;
+};
+
+QNetworkAccessManager *CustomNetworkAccessManagerFactory::create(QObject *parent)
+{
+ return new CustomNetworkAccessManager(this, parent);
+}
+
+
+void tst_qquickxmllistmodel::buildModel()
+{
+ QQmlComponent component(&engine, testFileUrl("model.qml"));
+ QAbstractItemModel *model = qobject_cast<QAbstractItemModel *>(component.create());
+ QVERIFY(model != 0);
+ QTRY_COMPARE(model->rowCount(), 9);
+
+ QModelIndex index = model->index(3, 0);
+ QCOMPARE(model->data(index, Qt::UserRole).toString(), QLatin1String("Spot"));
+ QCOMPARE(model->data(index, Qt::UserRole+1).toString(), QLatin1String("Dog"));
+ QCOMPARE(model->data(index, Qt::UserRole+2).toInt(), 9);
+ QCOMPARE(model->data(index, Qt::UserRole+3).toString(), QLatin1String("Medium"));
+
+ delete model;
+}
+
+void tst_qquickxmllistmodel::testTypes()
+{
+ QFETCH(QString, xml);
+ QFETCH(QString, roleName);
+ QFETCH(QVariant, expectedValue);
+
+ QQmlComponent component(&engine, testFileUrl("testtypes.qml"));
+ QAbstractItemModel *model = qobject_cast<QAbstractItemModel *>(component.create());
+ QVERIFY(model != 0);
+ model->setProperty("xml",xml.toUtf8());
+ QMetaObject::invokeMethod(model, "reload");
+ QTRY_COMPARE(model->rowCount(), 1);
+
+ int role = model->roleNames().key(roleName.toUtf8(), -1);
+ QVERIFY(role >= 0);
+
+ QModelIndex index = model->index(0, 0);
+ if (expectedValue.toString() == "nan")
+ QVERIFY(qIsNaN(model->data(index, role).toDouble()));
+ else
+ QCOMPARE(model->data(index, role), expectedValue);
+
+ delete model;
+}
+
+void tst_qquickxmllistmodel::testTypes_data()
+{
+ QTest::addColumn<QString>("xml");
+ QTest::addColumn<QString>("roleName");
+ QTest::addColumn<QVariant>("expectedValue");
+
+ QTest::newRow("missing string field") << "<data></data>"
+ << "stringValue" << QVariant("");
+ QTest::newRow("empty string") << "<data><a-string></a-string></data>"
+ << "stringValue" << QVariant("");
+ QTest::newRow("1-char string") << "<data><a-string>5</a-string></data>"
+ << "stringValue" << QVariant("5");
+ QTest::newRow("string ok") << "<data><a-string>abc def g</a-string></data>"
+ << "stringValue" << QVariant("abc def g");
+
+ QTest::newRow("missing number field") << "<data></data>"
+ << "numberValue" << QVariant("");
+ double nan = qQNaN();
+ QTest::newRow("empty number field") << "<data><a-number></a-number></data>"
+ << "numberValue" << QVariant(nan);
+ QTest::newRow("number field with string") << "<data><a-number>a string</a-number></data>"
+ << "numberValue" << QVariant(nan);
+ QTest::newRow("-1") << "<data><a-number>-1</a-number></data>"
+ << "numberValue" << QVariant("-1");
+ QTest::newRow("-1.5") << "<data><a-number>-1.5</a-number></data>"
+ << "numberValue" << QVariant("-1.5");
+ QTest::newRow("0") << "<data><a-number>0</a-number></data>"
+ << "numberValue" << QVariant("0");
+ QTest::newRow("+1") << "<data><a-number>1</a-number></data>"
+ << "numberValue" << QVariant("1");
+ QTest::newRow("+1.5") << "<data><a-number>1.5</a-number></data>"
+ << "numberValue" << QVariant("1.5");
+}
+
+void tst_qquickxmllistmodel::cdata()
+{
+ QQmlComponent component(&engine, testFileUrl("recipes.qml"));
+ QAbstractItemModel *model = qobject_cast<QAbstractItemModel *>(component.create());
+ QVERIFY(model != 0);
+ QTRY_COMPARE(model->rowCount(), 5);
+
+ QVERIFY(model->data(model->index(2, 0), Qt::UserRole+2).toString().startsWith(QLatin1String("<html>")));
+
+ delete model;
+}
+
+void tst_qquickxmllistmodel::attributes()
+{
+ QQmlComponent component(&engine, testFileUrl("recipes.qml"));
+ QAbstractItemModel *model = qobject_cast<QAbstractItemModel *>(component.create());
+ QVERIFY(model != 0);
+ QTRY_COMPARE(model->rowCount(), 5);
+ QCOMPARE(model->data(model->index(2, 0), Qt::UserRole).toString(), QLatin1String("Vegetable Soup"));
+
+ delete model;
+}
+
+void tst_qquickxmllistmodel::roles()
+{
+ QQmlComponent component(&engine, testFileUrl("model.qml"));
+ QAbstractItemModel *model = qobject_cast<QAbstractItemModel *>(component.create());
+ QVERIFY(model != 0);
+ QTRY_COMPARE(model->rowCount(), 9);
+
+ QHash<int, QByteArray> roleNames = model->roleNames();
+ QCOMPARE(roleNames.count(), 4);
+ QVERIFY(roleNames.key("name", -1) >= 0);
+ QVERIFY(roleNames.key("type", -1) >= 0);
+ QVERIFY(roleNames.key("age", -1) >= 0);
+ QVERIFY(roleNames.key("size", -1) >= 0);
+
+ QSet<int> roles;
+ roles.insert(roleNames.key("name"));
+ roles.insert(roleNames.key("type"));
+ roles.insert(roleNames.key("age"));
+ roles.insert(roleNames.key("size"));
+ QCOMPARE(roles.count(), 4);
+
+ delete model;
+}
+
+void tst_qquickxmllistmodel::roleErrors()
+{
+ QQmlComponent component(&engine, testFileUrl("roleErrors.qml"));
+ QTest::ignoreMessage(QtWarningMsg, (testFileUrl("roleErrors.qml").toString() + ":7:5: QML XmlRole: An XmlRole query must not start with '/'").toUtf8().constData());
+ QTest::ignoreMessage(QtWarningMsg, (testFileUrl("roleErrors.qml").toString() + ":10:5: QML XmlRole: invalid query: \"age/\"").toUtf8().constData());
+
+ //### make sure we receive all expected warning messages.
+ QAbstractItemModel *model = qobject_cast<QAbstractItemModel *>(component.create());
+ QVERIFY(model != 0);
+ QTRY_COMPARE(model->rowCount(), 9);
+
+ QModelIndex index = model->index(3, 0);
+ //### should any of these return valid values?
+ QCOMPARE(model->data(index, Qt::UserRole), QVariant());
+ QCOMPARE(model->data(index, Qt::UserRole+1), QVariant());
+ QCOMPARE(model->data(index, Qt::UserRole+2), QVariant());
+
+ QEXPECT_FAIL("", "QTBUG-10797", Continue);
+ QCOMPARE(model->data(index, Qt::UserRole+3), QVariant());
+
+ delete model;
+}
+
+void tst_qquickxmllistmodel::uniqueRoleNames()
+{
+ QQmlComponent component(&engine, testFileUrl("unique.qml"));
+ QTest::ignoreMessage(QtWarningMsg, (testFileUrl("unique.qml").toString() + ":8:5: QML XmlRole: \"name\" duplicates a previous role name and will be disabled.").toUtf8().constData());
+ QAbstractItemModel *model = qobject_cast<QAbstractItemModel *>(component.create());
+ QVERIFY(model != 0);
+ QTRY_COMPARE(model->rowCount(), 9);
+
+ QHash<int, QByteArray> roleNames = model->roleNames();
+ QCOMPARE(roleNames.count(), 1);
+
+ delete model;
+}
+
+
+void tst_qquickxmllistmodel::xml()
+{
+ QFETCH(QString, xml);
+ QFETCH(int, count);
+
+ QQmlComponent component(&engine, testFileUrl("model.qml"));
+ QAbstractItemModel *model = qobject_cast<QAbstractItemModel *>(component.create());
+
+ QSignalSpy spy(model, SIGNAL(statusChanged(QQuickXmlListModel::Status)));
+ QVERIFY(errorString(model).isEmpty());
+ QCOMPARE(model->property("progress").toDouble(), qreal(1.0));
+ QCOMPARE(qvariant_cast<QQuickXmlListModel::Status>(model->property("status")),
+ QQuickXmlListModel::Loading);
+ QTRY_COMPARE(spy.count(), 1); spy.clear();
+ QTest::qWait(50);
+ QCOMPARE(qvariant_cast<QQuickXmlListModel::Status>(model->property("status")),
+ QQuickXmlListModel::Ready);
+ QVERIFY(errorString(model).isEmpty());
+ QCOMPARE(model->property("progress").toDouble(), qreal(1.0));
+ QCOMPARE(model->rowCount(), 9);
+
+ // if xml is empty (i.e. clearing) it won't have any effect if a source is set
+ if (xml.isEmpty())
+ model->setProperty("source",QUrl());
+ model->setProperty("xml",xml);
+ QCOMPARE(model->property("progress").toDouble(), qreal(1.0)); // immediately goes to 1.0 if using setXml()
+ QTRY_COMPARE(spy.count(), 1); spy.clear();
+ QCOMPARE(qvariant_cast<QQuickXmlListModel::Status>(model->property("status")),
+ QQuickXmlListModel::Loading);
+ QTRY_COMPARE(spy.count(), 1); spy.clear();
+ if (xml.isEmpty())
+ QCOMPARE(qvariant_cast<QQuickXmlListModel::Status>(model->property("status")),
+ QQuickXmlListModel::Null);
+ else
+ QCOMPARE(qvariant_cast<QQuickXmlListModel::Status>(model->property("status")),
+ QQuickXmlListModel::Ready);
+ QVERIFY(errorString(model).isEmpty());
+ QCOMPARE(model->rowCount(), count);
+
+ delete model;
+}
+
+void tst_qquickxmllistmodel::xml_data()
+{
+ QTest::addColumn<QString>("xml");
+ QTest::addColumn<int>("count");
+
+ QTest::newRow("xml with no items") << "<Pets></Pets>" << 0;
+ QTest::newRow("empty xml") << "" << 0;
+ QTest::newRow("one item") << "<Pets><Pet><name>Hobbes</name><type>Tiger</type><age>7</age><size>Large</size></Pet></Pets>" << 1;
+}
+
+void tst_qquickxmllistmodel::headers()
+{
+ // ensure the QNetworkAccessManagers created for this test are immediately deleted
+ QQmlEngine qmlEng;
+
+ CustomNetworkAccessManagerFactory factory;
+ qmlEng.setNetworkAccessManagerFactory(&factory);
+
+ QQmlComponent component(&qmlEng, testFileUrl("model.qml"));
+ QAbstractItemModel *model = qobject_cast<QAbstractItemModel *>(component.create());
+ QVERIFY(model != 0);
+ QTRY_COMPARE(qvariant_cast<QQuickXmlListModel::Status>(model->property("status")),
+ QQuickXmlListModel::Ready);
+
+ // It doesn't do a network request for a local file
+ QCOMPARE(factory.lastSentHeaders.count(), 0);
+
+ model->setProperty("source", QUrl("http://localhost/filethatdoesnotexist.xml"));
+ QTRY_COMPARE(qvariant_cast<QQuickXmlListModel::Status>(model->property("status")),
+ QQuickXmlListModel::Error);
+
+ QVariantMap expectedHeaders;
+ expectedHeaders["Accept"] = "application/xml,*/*";
+
+ QCOMPARE(factory.lastSentHeaders.count(), expectedHeaders.count());
+ foreach (const QString &header, expectedHeaders.keys()) {
+ QVERIFY(factory.lastSentHeaders.contains(header));
+ QCOMPARE(factory.lastSentHeaders[header].toString(), expectedHeaders[header].toString());
+ }
+
+ delete model;
+}
+
+void tst_qquickxmllistmodel::source()
+{
+ QFETCH(QUrl, source);
+ QFETCH(int, count);
+ QFETCH(QQuickXmlListModel::Status, status);
+
+ QQmlComponent component(&engine, testFileUrl("model.qml"));
+ QAbstractItemModel *model = qobject_cast<QAbstractItemModel *>(component.create());
+ QSignalSpy spy(model, SIGNAL(statusChanged(QQuickXmlListModel::Status)));
+
+ QVERIFY(errorString(model).isEmpty());
+ QCOMPARE(model->property("progress").toDouble(), qreal(1.0));
+ QCOMPARE(qvariant_cast<QQuickXmlListModel::Status>(model->property("status")),
+ QQuickXmlListModel::Loading);
+ QTRY_COMPARE(spy.count(), 1); spy.clear();
+ QCOMPARE(qvariant_cast<QQuickXmlListModel::Status>(model->property("status")),
+ QQuickXmlListModel::Ready);
+ QVERIFY(errorString(model).isEmpty());
+ QCOMPARE(model->property("progress").toDouble(), qreal(1.0));
+ QCOMPARE(model->rowCount(), 9);
+
+ model->setProperty("source",source);
+ if (model->property("source").toString().isEmpty())
+ QCOMPARE(qvariant_cast<QQuickXmlListModel::Status>(model->property("status")),
+ QQuickXmlListModel::Null);
+ QCOMPARE(model->property("progress").toDouble(), qreal(source.isLocalFile() ? 1.0 : 0.0));
+ QTRY_COMPARE(spy.count(), 1); spy.clear();
+ QCOMPARE(qvariant_cast<QQuickXmlListModel::Status>(model->property("status")),
+ QQuickXmlListModel::Loading);
+ QVERIFY(errorString(model).isEmpty());
+
+ QEventLoop loop;
+ QTimer timer;
+ timer.setSingleShot(true);
+ connect(model, SIGNAL(statusChanged(QQuickXmlListModel::Status)), &loop, SLOT(quit()));
+ connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
+ timer.start(20000);
+ loop.exec();
+
+ if (spy.count() == 0 && status != QQuickXmlListModel::Ready) {
+ qWarning("QQuickXmlListModel invalid source test timed out");
+ } else {
+ QCOMPARE(spy.count(), 1); spy.clear();
+ }
+
+ QCOMPARE(qvariant_cast<QQuickXmlListModel::Status>(model->property("status")), status);
+ QCOMPARE(model->rowCount(), count);
+
+ if (status == QQuickXmlListModel::Ready)
+ QCOMPARE(model->property("progress").toDouble(), qreal(1.0));
+
+ QCOMPARE(errorString(model).isEmpty(), status == QQuickXmlListModel::Ready);
+
+ delete model;
+}
+
+void tst_qquickxmllistmodel::source_data()
+{
+ QTest::addColumn<QUrl>("source");
+ QTest::addColumn<int>("count");
+ QTest::addColumn<QQuickXmlListModel::Status>("status");
+
+ QTest::newRow("valid") << testFileUrl("model2.xml") << 2
+ << QQuickXmlListModel::Ready;
+ QTest::newRow("invalid") << QUrl("http://blah.blah/blah.xml") << 0
+ << QQuickXmlListModel::Error;
+
+ // empty file
+ QTemporaryFile *temp = new QTemporaryFile(this);
+ if (temp->open())
+ QTest::newRow("empty file") << QUrl::fromLocalFile(temp->fileName()) << 0
+ << QQuickXmlListModel::Ready;
+ temp->close();
+}
+
+void tst_qquickxmllistmodel::data()
+{
+ QQmlComponent component(&engine, testFileUrl("model.qml"));
+ QAbstractItemModel *model = qobject_cast<QAbstractItemModel *>(component.create());
+ QVERIFY(model != 0);
+
+ for (int i=0; i<9; i++) {
+ QModelIndex index = model->index(i, 0);
+ for (int j=0; j<model->roleNames().count(); j++) {
+ QCOMPARE(model->data(index, j), QVariant());
+ }
+ }
+ QTRY_COMPARE(model->rowCount(), 9);
+
+ delete model;
+}
+
+void tst_qquickxmllistmodel::get()
+{
+ QQmlComponent component(&engine, testFileUrl("get.qml"));
+ QAbstractItemModel *model = qobject_cast<QAbstractItemModel *>(component.create());
+
+ QVERIFY(model != 0);
+
+ QVERIFY(QMetaObject::invokeMethod(model, "runPreTest"));
+ QCOMPARE(model->property("preTest").toBool(), true);
+
+ QTRY_COMPARE(model->rowCount(), 9);
+
+ QVERIFY(QMetaObject::invokeMethod(model, "runPostTest"));
+ QCOMPARE(model->property("postTest").toBool(), true);
+
+ delete model;
+}
+
+void tst_qquickxmllistmodel::reload()
+{
+ // If no keys are used, the model should be rebuilt from scratch when
+ // reload() is called.
+
+ QQmlComponent component(&engine, testFileUrl("model.qml"));
+ QAbstractItemModel *model = qobject_cast<QAbstractItemModel *>(component.create());
+ QVERIFY(model != 0);
+ QTRY_COMPARE(model->rowCount(), 9);
+
+ QSignalSpy spyInsert(model, SIGNAL(rowsInserted(QModelIndex,int,int)));
+ QSignalSpy spyRemove(model, SIGNAL(rowsRemoved(QModelIndex,int,int)));
+ QSignalSpy spyCount(model, SIGNAL(countChanged()));
+ //reload multiple times to test the xml query aborting
+ QMetaObject::invokeMethod(model, "reload");
+ QMetaObject::invokeMethod(model, "reload");
+ QCoreApplication::processEvents();
+ QMetaObject::invokeMethod(model, "reload");
+ QMetaObject::invokeMethod(model, "reload");
+ QTRY_COMPARE(spyCount.count(), 0);
+ QTRY_COMPARE(spyInsert.count(), 1);
+ QTRY_COMPARE(spyRemove.count(), 1);
+
+ QCOMPARE(spyInsert[0][1].toInt(), 0);
+ QCOMPARE(spyInsert[0][2].toInt(), 8);
+
+ QCOMPARE(spyRemove[0][1].toInt(), 0);
+ QCOMPARE(spyRemove[0][2].toInt(), 8);
+
+ delete model;
+}
+
+void tst_qquickxmllistmodel::useKeys()
+{
+ // If using incremental updates through keys, the model should only
+ // insert & remove some of the items, instead of throwing everything
+ // away and causing the view to repaint the whole view.
+
+ QFETCH(QString, oldXml);
+ QFETCH(int, oldCount);
+ QFETCH(QString, newXml);
+ QFETCH(QQmlXmlModelData, newData);
+ QFETCH(QList<QQuickXmlListRange>, insertRanges);
+ QFETCH(QList<QQuickXmlListRange>, removeRanges);
+
+ QQmlComponent component(&engine, testFileUrl("roleKeys.qml"));
+ QAbstractItemModel *model = qobject_cast<QAbstractItemModel *>(component.create());
+ QVERIFY(model != 0);
+
+ model->setProperty("xml",oldXml);
+ QTRY_COMPARE(model->rowCount(), oldCount);
+
+ QSignalSpy spyInsert(model, SIGNAL(rowsInserted(QModelIndex,int,int)));
+ QSignalSpy spyRemove(model, SIGNAL(rowsRemoved(QModelIndex,int,int)));
+ QSignalSpy spyCount(model, SIGNAL(countChanged()));
+
+ model->setProperty("xml",newXml);
+
+ if (oldCount != newData.count()) {
+ QTRY_COMPARE(model->rowCount(), newData.count());
+ QCOMPARE(spyCount.count(), 1);
+ } else {
+ QTRY_VERIFY(spyInsert.count() > 0 || spyRemove.count() > 0);
+ QCOMPARE(spyCount.count(), 0);
+ }
+
+ QList<int> roles = model->roleNames().keys();
+ std::sort(roles.begin(), roles.end());
+ for (int i=0; i<model->rowCount(); i++) {
+ QModelIndex index = model->index(i, 0);
+ for (int j=0; j<roles.count(); j++)
+ QCOMPARE(model->data(index, roles.at(j)), newData[i][j]);
+ }
+
+ QCOMPARE(spyInsert.count(), insertRanges.count());
+ for (int i=0; i<spyInsert.count(); i++) {
+ QCOMPARE(spyInsert[i][1].toInt(), insertRanges[i].first);
+ QCOMPARE(spyInsert[i][2].toInt(), insertRanges[i].first + insertRanges[i].second - 1);
+ }
+
+ QCOMPARE(spyRemove.count(), removeRanges.count());
+ for (int i=0; i<spyRemove.count(); i++) {
+ QCOMPARE(spyRemove[i][1].toInt(), removeRanges[i].first);
+ QCOMPARE(spyRemove[i][2].toInt(), removeRanges[i].first + removeRanges[i].second - 1);
+ }
+
+ delete model;
+}
+
+void tst_qquickxmllistmodel::useKeys_data()
+{
+ QTest::addColumn<QString>("oldXml");
+ QTest::addColumn<int>("oldCount");
+ QTest::addColumn<QString>("newXml");
+ QTest::addColumn<QQmlXmlModelData>("newData");
+ QTest::addColumn<QList<QQuickXmlListRange> >("insertRanges");
+ QTest::addColumn<QList<QQuickXmlListRange> >("removeRanges");
+
+ QQmlXmlModelData modelData;
+
+ QTest::newRow("append 1")
+ << makeItemXmlAndData("name=A,age=25,sport=Football") << 1
+ << makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics", &modelData)
+ << modelData
+ << (QList<QQuickXmlListRange>() << qMakePair(1, 1))
+ << QList<QQuickXmlListRange>();
+
+ QTest::newRow("append multiple")
+ << makeItemXmlAndData("name=A,age=25,sport=Football") << 1
+ << makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics;name=C,age=45,sport=Curling", &modelData)
+ << modelData
+ << (QList<QQuickXmlListRange>() << qMakePair(1, 2))
+ << QList<QQuickXmlListRange>();
+
+ QTest::newRow("insert in different spots")
+ << makeItemXmlAndData("name=B,age=35,sport=Athletics") << 1
+ << makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics;name=C,age=45,sport=Curling;name=D,age=55,sport=Golf", &modelData)
+ << modelData
+ << (QList<QQuickXmlListRange>() << qMakePair(0, 1) << qMakePair(2,2))
+ << QList<QQuickXmlListRange>();
+
+ QTest::newRow("insert in middle")
+ << makeItemXmlAndData("name=A,age=25,sport=Football;name=D,age=55,sport=Golf") << 2
+ << makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics;name=C,age=45,sport=Curling;name=D,age=55,sport=Golf", &modelData)
+ << modelData
+ << (QList<QQuickXmlListRange>() << qMakePair(1, 2))
+ << QList<QQuickXmlListRange>();
+
+ QTest::newRow("remove first")
+ << makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics") << 2
+ << makeItemXmlAndData("name=B,age=35,sport=Athletics", &modelData)
+ << modelData
+ << QList<QQuickXmlListRange>()
+ << (QList<QQuickXmlListRange>() << qMakePair(0, 1));
+
+ QTest::newRow("remove last")
+ << makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics") << 2
+ << makeItemXmlAndData("name=A,age=25,sport=Football", &modelData)
+ << modelData
+ << QList<QQuickXmlListRange>()
+ << (QList<QQuickXmlListRange>() << qMakePair(1, 1));
+
+ QTest::newRow("remove from multiple spots")
+ << makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics;name=C,age=45,sport=Curling;name=D,age=55,sport=Golf;name=E,age=65,sport=Fencing") << 5
+ << makeItemXmlAndData("name=A,age=25,sport=Football;name=C,age=45,sport=Curling", &modelData)
+ << modelData
+ << QList<QQuickXmlListRange>()
+ << (QList<QQuickXmlListRange>() << qMakePair(1, 1) << qMakePair(3,2));
+
+ QTest::newRow("remove all")
+ << makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics;name=C,age=45,sport=Curling") << 3
+ << makeItemXmlAndData("", &modelData)
+ << modelData
+ << QList<QQuickXmlListRange>()
+ << (QList<QQuickXmlListRange>() << qMakePair(0, 3));
+
+ QTest::newRow("replace item")
+ << makeItemXmlAndData("name=A,age=25,sport=Football") << 1
+ << makeItemXmlAndData("name=ZZZ,age=25,sport=Football", &modelData)
+ << modelData
+ << (QList<QQuickXmlListRange>() << qMakePair(0, 1))
+ << (QList<QQuickXmlListRange>() << qMakePair(0, 1));
+
+ QTest::newRow("add and remove simultaneously, in different spots")
+ << makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics;name=C,age=45,sport=Curling;name=D,age=55,sport=Golf") << 4
+ << makeItemXmlAndData("name=B,age=35,sport=Athletics;name=E,age=65,sport=Fencing", &modelData)
+ << modelData
+ << (QList<QQuickXmlListRange>() << qMakePair(1, 1))
+ << (QList<QQuickXmlListRange>() << qMakePair(0, 1) << qMakePair(2,2));
+
+ QTest::newRow("insert at start, remove at end i.e. rss feed")
+ << makeItemXmlAndData("name=C,age=45,sport=Curling;name=D,age=55,sport=Golf;name=E,age=65,sport=Fencing") << 3
+ << makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics;name=C,age=45,sport=Curling", &modelData)
+ << modelData
+ << (QList<QQuickXmlListRange>() << qMakePair(0, 2))
+ << (QList<QQuickXmlListRange>() << qMakePair(1, 2));
+
+ QTest::newRow("remove at start, insert at end")
+ << makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics;name=C,age=45,sport=Curling") << 3
+ << makeItemXmlAndData("name=C,age=45,sport=Curling;name=D,age=55,sport=Golf;name=E,age=65,sport=Fencing", &modelData)
+ << modelData
+ << (QList<QQuickXmlListRange>() << qMakePair(1, 2))
+ << (QList<QQuickXmlListRange>() << qMakePair(0, 2));
+
+ QTest::newRow("all data has changed")
+ << makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35") << 2
+ << makeItemXmlAndData("name=C,age=45,sport=Curling;name=D,age=55,sport=Golf", &modelData)
+ << modelData
+ << (QList<QQuickXmlListRange>() << qMakePair(0, 2))
+ << (QList<QQuickXmlListRange>() << qMakePair(0, 2));
+}
+
+void tst_qquickxmllistmodel::noKeysValueChanges()
+{
+ // The 'key' roles are 'name' and 'age', as defined in roleKeys.qml.
+ // If a 'sport' value is changed, the model should not be reloaded,
+ // since 'sport' is not marked as a key.
+
+ QQmlComponent component(&engine, testFileUrl("roleKeys.qml"));
+ QAbstractItemModel *model = qobject_cast<QAbstractItemModel *>(component.create());
+ QVERIFY(model != 0);
+
+ QString xml;
+
+ xml = makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics");
+ model->setProperty("xml",xml);
+ QTRY_COMPARE(model->rowCount(), 2);
+
+ model->setProperty("xml","");
+
+ QSignalSpy spyInsert(model, SIGNAL(rowsInserted(QModelIndex,int,int)));
+ QSignalSpy spyRemove(model, SIGNAL(rowsRemoved(QModelIndex,int,int)));
+ QSignalSpy spyCount(model, SIGNAL(countChanged()));
+
+ xml = makeItemXmlAndData("name=A,age=25,sport=AussieRules;name=B,age=35,sport=Athletics");
+ model->setProperty("xml",xml);
+
+ QList<int> roles = model->roleNames().keys();
+ std::sort(roles.begin(), roles.end());
+ // wait for the new xml data to be set, and verify no signals were emitted
+ QTRY_VERIFY(model->data(model->index(0, 0), roles.at(2)).toString() != QLatin1String("Football"));
+ QCOMPARE(model->data(model->index(0, 0), roles.at(2)).toString(), QLatin1String("AussieRules"));
+
+ QCOMPARE(spyInsert.count(), 0);
+ QCOMPARE(spyRemove.count(), 0);
+ QCOMPARE(spyCount.count(), 0);
+
+ QCOMPARE(model->rowCount(), 2);
+
+ delete model;
+}
+
+void tst_qquickxmllistmodel::keysChanged()
+{
+ // If the key roles change, the next time the data is reloaded, it should
+ // delete all its data and build a clean model (i.e. same behavior as
+ // if no keys are set).
+
+ QQmlComponent component(&engine, testFileUrl("roleKeys.qml"));
+ QAbstractItemModel *model = qobject_cast<QAbstractItemModel *>(component.create());
+ QVERIFY(model != 0);
+
+ QString xml = makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics");
+ model->setProperty("xml",xml);
+ QTRY_COMPARE(model->rowCount(), 2);
+
+ model->setProperty("xml","");
+
+ QSignalSpy spyInsert(model, SIGNAL(rowsInserted(QModelIndex,int,int)));
+ QSignalSpy spyRemove(model, SIGNAL(rowsRemoved(QModelIndex,int,int)));
+ QSignalSpy spyCount(model, SIGNAL(countChanged()));
+
+ QVERIFY(QMetaObject::invokeMethod(model, "disableNameKey"));
+ model->setProperty("xml",xml);
+
+ QTRY_VERIFY(spyInsert.count() > 0 && spyRemove.count() > 0);
+
+ QCOMPARE(spyInsert.count(), 1);
+ QCOMPARE(spyInsert[0][1].toInt(), 0);
+ QCOMPARE(spyInsert[0][2].toInt(), 1);
+
+ QCOMPARE(spyRemove.count(), 1);
+ QCOMPARE(spyRemove[0][1].toInt(), 0);
+ QCOMPARE(spyRemove[0][2].toInt(), 1);
+
+ QCOMPARE(spyCount.count(), 0);
+
+ delete model;
+}
+
+void tst_qquickxmllistmodel::threading()
+{
+ QFETCH(int, xmlDataCount);
+
+ QQmlComponent component(&engine, testFileUrl("roleKeys.qml"));
+
+ QAbstractItemModel *m1 = qobject_cast<QAbstractItemModel *>(component.create());
+ QVERIFY(m1 != 0);
+ QAbstractItemModel *m2 = qobject_cast<QAbstractItemModel *>(component.create());
+ QVERIFY(m2 != 0);
+ QAbstractItemModel *m3 = qobject_cast<QAbstractItemModel *>(component.create());
+ QVERIFY(m3 != 0);
+
+ for (int dataCount=0; dataCount<xmlDataCount; dataCount++) {
+
+ QString data1, data2, data3;
+ for (int i=0; i<dataCount; i++) {
+ data1 += "name=A" + QString::number(i) + ",age=1" + QString::number(i) + ",sport=Football;";
+ data2 += "name=B" + QString::number(i) + ",age=2" + QString::number(i) + ",sport=Athletics;";
+ data3 += "name=C" + QString::number(i) + ",age=3" + QString::number(i) + ",sport=Curling;";
+ }
+
+ //Set the xml data multiple times with randomized order and mixed with multiple event loops
+ //to test the xml query reloading/aborting, the result should be stable.
+ m1->setProperty("xml",makeItemXmlAndData(data1));
+ m2->setProperty("xml",makeItemXmlAndData(data2));
+ m3->setProperty("xml",makeItemXmlAndData(data3));
+ QCoreApplication::processEvents();
+ m2->setProperty("xml",makeItemXmlAndData(data2));
+ m1->setProperty("xml",makeItemXmlAndData(data1));
+ m2->setProperty("xml",makeItemXmlAndData(data2));
+ QCoreApplication::processEvents();
+ m3->setProperty("xml",makeItemXmlAndData(data3));
+ QCoreApplication::processEvents();
+ m2->setProperty("xml",makeItemXmlAndData(data2));
+ m1->setProperty("xml",makeItemXmlAndData(data1));
+ m2->setProperty("xml",makeItemXmlAndData(data2));
+ m3->setProperty("xml",makeItemXmlAndData(data3));
+ QCoreApplication::processEvents();
+ m2->setProperty("xml",makeItemXmlAndData(data2));
+ m3->setProperty("xml",makeItemXmlAndData(data3));
+ m3->setProperty("xml",makeItemXmlAndData(data3));
+ QCoreApplication::processEvents();
+
+ QTRY_VERIFY(m1->rowCount() == dataCount && m2->rowCount() == dataCount && m3->rowCount() == dataCount);
+
+ for (int i=0; i<dataCount; i++) {
+ QModelIndex index = m1->index(i, 0);
+ QList<int> roles = m1->roleNames().keys();
+ std::sort(roles.begin(), roles.end());
+ QCOMPARE(m1->data(index, roles.at(0)).toString(), QLatin1Char('A') + QString::number(i));
+ QCOMPARE(m1->data(index, roles.at(1)).toString(), QLatin1Char('1') + QString::number(i));
+ QCOMPARE(m1->data(index, roles.at(2)).toString(), QString("Football"));
+
+ index = m2->index(i, 0);
+ roles = m2->roleNames().keys();
+ std::sort(roles.begin(), roles.end());
+ QCOMPARE(m2->data(index, roles.at(0)).toString(), QLatin1Char('B') + QString::number(i));
+ QCOMPARE(m2->data(index, roles.at(1)).toString(), QLatin1Char('2') + QString::number(i));
+ QCOMPARE(m2->data(index, roles.at(2)).toString(), QString("Athletics"));
+
+ index = m3->index(i, 0);
+ roles = m3->roleNames().keys();
+ std::sort(roles.begin(), roles.end());
+ QCOMPARE(m3->data(index, roles.at(0)).toString(), QLatin1Char('C') + QString::number(i));
+ QCOMPARE(m3->data(index, roles.at(1)).toString(), QLatin1Char('3') + QString::number(i));
+ QCOMPARE(m3->data(index, roles.at(2)).toString(), QString("Curling"));
+ }
+ }
+
+ delete m1;
+ delete m2;
+ delete m3;
+}
+
+void tst_qquickxmllistmodel::threading_data()
+{
+ QTest::addColumn<int>("xmlDataCount");
+
+ QTest::newRow("1") << 1;
+ QTest::newRow("2") << 2;
+ QTest::newRow("10") << 10;
+}
+
+void tst_qquickxmllistmodel::propertyChanges()
+{
+ QQmlComponent component(&engine, testFileUrl("propertychanges.qml"));
+ QAbstractItemModel *model = qobject_cast<QAbstractItemModel*>(component.create());
+ QVERIFY(model != 0);
+ QTRY_COMPARE(model->rowCount(), 9);
+
+ QObject *role = model->findChild<QObject*>("role");
+ QVERIFY(role);
+
+ QSignalSpy nameSpy(role, SIGNAL(nameChanged()));
+ QSignalSpy querySpy(role, SIGNAL(queryChanged()));
+ QSignalSpy isKeySpy(role, SIGNAL(isKeyChanged()));
+
+ role->setProperty("name","size");
+ role->setProperty("query","size/string()");
+ role->setProperty("isKey",true);
+
+ QCOMPARE(role->property("name").toString(), QString("size"));
+ QCOMPARE(role->property("query").toString(), QString("size/string()"));
+ QVERIFY(role->property("isKey").toBool());
+
+ QCOMPARE(nameSpy.count(),1);
+ QCOMPARE(querySpy.count(),1);
+ QCOMPARE(isKeySpy.count(),1);
+
+ role->setProperty("name","size");
+ role->setProperty("query","size/string()");
+ role->setProperty("isKey",true);
+
+ QCOMPARE(nameSpy.count(),1);
+ QCOMPARE(querySpy.count(),1);
+ QCOMPARE(isKeySpy.count(),1);
+
+ QSignalSpy sourceSpy(model, SIGNAL(sourceChanged()));
+ QSignalSpy xmlSpy(model, SIGNAL(xmlChanged()));
+ QSignalSpy modelQuerySpy(model, SIGNAL(queryChanged()));
+ QSignalSpy namespaceDeclarationsSpy(model, SIGNAL(namespaceDeclarationsChanged()));
+
+ model->setProperty("source",QUrl(""));
+ model->setProperty("xml","<Pets><Pet><name>Polly</name><type>Parrot</type><age>12</age><size>Small</size></Pet></Pets>");
+ model->setProperty("query","/Pets");
+ model->setProperty("namespaceDeclarations","declare namespace media=\"http://search.yahoo.com/mrss/\";");
+
+ QCOMPARE(model->property("source").toUrl(), QUrl(""));
+ QCOMPARE(model->property("xml").toString(), QString("<Pets><Pet><name>Polly</name><type>Parrot</type><age>12</age><size>Small</size></Pet></Pets>"));
+ QCOMPARE(model->property("query").toString(), QString("/Pets"));
+ QCOMPARE(model->property("namespaceDeclarations").toString(), QString("declare namespace media=\"http://search.yahoo.com/mrss/\";"));
+
+ QTRY_COMPARE(model->rowCount(), 1);
+
+ QCOMPARE(sourceSpy.count(),1);
+ QCOMPARE(xmlSpy.count(),1);
+ QCOMPARE(modelQuerySpy.count(),1);
+ QCOMPARE(namespaceDeclarationsSpy.count(),1);
+
+ model->setProperty("source",QUrl(""));
+ model->setProperty("xml","<Pets><Pet><name>Polly</name><type>Parrot</type><age>12</age><size>Small</size></Pet></Pets>");
+ model->setProperty("query","/Pets");
+ model->setProperty("namespaceDeclarations","declare namespace media=\"http://search.yahoo.com/mrss/\";");
+
+ QCOMPARE(sourceSpy.count(),1);
+ QCOMPARE(xmlSpy.count(),1);
+ QCOMPARE(modelQuerySpy.count(),1);
+ QCOMPARE(namespaceDeclarationsSpy.count(),1);
+
+ QTRY_COMPARE(model->rowCount(), 1);
+ delete model;
+}
+
+void tst_qquickxmllistmodel::selectAncestor()
+{
+ QQmlComponent component(&engine, testFileUrl("groups.qml"));
+ QAbstractItemModel *model = qobject_cast<QAbstractItemModel *>(component.create());
+ QVERIFY(model != 0);
+ QTRY_COMPARE(model->rowCount(), 1);
+
+ QModelIndex index = model->index(0, 0);
+ QCOMPARE(model->data(index, Qt::UserRole).toInt(), 12);
+ QCOMPARE(model->data(index, Qt::UserRole+1).toString(), QLatin1String("cats"));
+}
+
+void tst_qquickxmllistmodel::roleCrash()
+{
+ // don't crash
+ QQmlComponent component(&engine, testFileUrl("roleCrash.qml"));
+ QAbstractItemModel *model = qobject_cast<QAbstractItemModel *>(component.create());
+ QVERIFY(model != 0);
+ delete model;
+}
+
+class SortFilterProxyModel : public QSortFilterProxyModel
+{
+ Q_OBJECT
+ Q_PROPERTY(QObject *source READ source WRITE setSource)
+
+public:
+ SortFilterProxyModel(QObject *parent = 0) : QSortFilterProxyModel(parent) { sort(0); }
+ QObject *source() const { return sourceModel(); }
+ void setSource(QObject *source) { setSourceModel(qobject_cast<QAbstractItemModel *>(source)); }
+};
+
+void tst_qquickxmllistmodel::proxyCrash()
+{
+ qmlRegisterType<SortFilterProxyModel>("SortFilterProxyModel", 1, 0, "SortFilterProxyModel");
+
+ // don't crash
+ QQmlComponent component(&engine, testFileUrl("proxyCrash.qml"));
+ QAbstractItemModel *model = qobject_cast<QAbstractItemModel *>(component.create());
+ QVERIFY(model != 0);
+ delete model;
+}
+
+QTEST_MAIN(tst_qquickxmllistmodel)
+
+#include "tst_qquickxmllistmodel.moc"