| 1 | /**************************************************************************** | 
| 2 | ** | 
| 3 | ** Copyright (C) 2016 The Qt Company Ltd. | 
| 4 | ** Contact: https://www.qt.io/licensing/ | 
| 5 | ** | 
| 6 | ** This file is part of the QtQml module of the Qt Toolkit. | 
| 7 | ** | 
| 8 | ** $QT_BEGIN_LICENSE:LGPL$ | 
| 9 | ** Commercial License Usage | 
| 10 | ** Licensees holding valid commercial Qt licenses may use this file in | 
| 11 | ** accordance with the commercial license agreement provided with the | 
| 12 | ** Software or, alternatively, in accordance with the terms contained in | 
| 13 | ** a written agreement between you and The Qt Company. For licensing terms | 
| 14 | ** and conditions see https://www.qt.io/terms-conditions. For further | 
| 15 | ** information use the contact form at https://www.qt.io/contact-us. | 
| 16 | ** | 
| 17 | ** GNU Lesser General Public License Usage | 
| 18 | ** Alternatively, this file may be used under the terms of the GNU Lesser | 
| 19 | ** General Public License version 3 as published by the Free Software | 
| 20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the | 
| 21 | ** packaging of this file. Please review the following information to | 
| 22 | ** ensure the GNU Lesser General Public License version 3 requirements | 
| 23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. | 
| 24 | ** | 
| 25 | ** GNU General Public License Usage | 
| 26 | ** Alternatively, this file may be used under the terms of the GNU | 
| 27 | ** General Public License version 2.0 or (at your option) the GNU General | 
| 28 | ** Public license version 3 or any later version approved by the KDE Free | 
| 29 | ** Qt Foundation. The licenses are as published by the Free Software | 
| 30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 | 
| 31 | ** included in the packaging of this file. Please review the following | 
| 32 | ** information to ensure the GNU General Public License requirements will | 
| 33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and | 
| 34 | ** https://www.gnu.org/licenses/gpl-3.0.html. | 
| 35 | ** | 
| 36 | ** $QT_END_LICENSE$ | 
| 37 | ** | 
| 38 | ****************************************************************************/ | 
| 39 |  | 
| 40 | #include "qqmlxmllistmodel_p.h" | 
| 41 |  | 
| 42 | #include <qqmlcontext.h> | 
| 43 | #include <qqmlfile.h> | 
| 44 | #include <private/qqmlengine_p.h> | 
| 45 | #include <private/qv4value_p.h> | 
| 46 | #include <private/qv4engine_p.h> | 
| 47 | #include <private/qv4object_p.h> | 
| 48 |  | 
| 49 | #include <QDebug> | 
| 50 | #include <QStringList> | 
| 51 | #include <QMap> | 
| 52 | #include <QThread> | 
| 53 | #include <QXmlQuery> | 
| 54 | #include <QXmlResultItems> | 
| 55 | #include <QXmlNodeModelIndex> | 
| 56 | #include <QBuffer> | 
| 57 | #if QT_CONFIG(qml_network) | 
| 58 | #include <QNetworkRequest> | 
| 59 | #include <QNetworkReply> | 
| 60 | #endif | 
| 61 | #include <QTimer> | 
| 62 | #include <QMutex> | 
| 63 |  | 
| 64 | #include <private/qabstractitemmodel_p.h> | 
| 65 |  | 
| 66 | Q_DECLARE_METATYPE(QQuickXmlQueryResult) | 
| 67 |  | 
| 68 | QT_BEGIN_NAMESPACE | 
| 69 |  | 
| 70 | using namespace QV4; | 
| 71 |  | 
| 72 | typedef QPair<int, int> QQuickXmlListRange; | 
| 73 |  | 
| 74 | #define XMLLISTMODEL_CLEAR_ID 0 | 
| 75 |  | 
| 76 | /*! | 
| 77 |     \qmlmodule QtQuick.XmlListModel 2.\QtMinorVersion | 
| 78 |     \title Qt XML Patterns QML Types | 
| 79 |     \keyword Qt Quick XmlListModel QML Types | 
| 80 |     \ingroup qmlmodules | 
| 81 |     \brief Provides QML types for creating models from XML data | 
| 82 |  | 
| 83 |     This QML module contains types for creating models from XML data. | 
| 84 |  | 
| 85 |     To use the types in this module, import the module with the following line: | 
| 86 |  | 
| 87 |     \qml \QtMinorVersion | 
| 88 |     import QtQuick.XmlListModel 2.\1 | 
| 89 |     \endqml | 
| 90 | */ | 
| 91 |  | 
| 92 | /*! | 
| 93 |     \qmltype XmlRole | 
| 94 |     \inqmlmodule QtQuick.XmlListModel | 
| 95 |     \brief For specifying a role to an XmlListModel. | 
| 96 |     \ingroup qtquick-models | 
| 97 |  | 
| 98 |     \sa {All QML Types}{Qt QML} | 
| 99 | */ | 
| 100 |  | 
| 101 | /*! | 
| 102 |     \qmlproperty string QtQuick.XmlListModel::XmlRole::name | 
| 103 |  | 
| 104 |     The name for the role. This name is used to access the model data for this role. | 
| 105 |  | 
| 106 |     For example, the following model has a role named "title", which can be accessed | 
| 107 |     from the view's delegate: | 
| 108 |  | 
| 109 |     \qml | 
| 110 |     XmlListModel { | 
| 111 |         id: xmlModel | 
| 112 |         // ... | 
| 113 |         XmlRole { | 
| 114 |             name: "title" | 
| 115 |             query: "title/string()" | 
| 116 |         } | 
| 117 |     } | 
| 118 |     \endqml | 
| 119 |  | 
| 120 |     \qml | 
| 121 |     ListView { | 
| 122 |         model: xmlModel | 
| 123 |         delegate: Text { text: title } | 
| 124 |     } | 
| 125 |     \endqml | 
| 126 | */ | 
| 127 |  | 
| 128 | /*! | 
| 129 |     \qmlproperty string QtQuick.XmlListModel::XmlRole::query | 
| 130 |     The relative XPath expression query for this role. The query must be relative; it cannot start | 
| 131 |     with a '/'. | 
| 132 |  | 
| 133 |     For example, if there is an XML document like this: | 
| 134 |  | 
| 135 |     \quotefile qml/xmlrole.xml | 
| 136 |     Here are some valid XPath expressions for XmlRole queries on this document: | 
| 137 |  | 
| 138 |     \snippet qml/xmlrole.qml 0 | 
| 139 |     \dots 4 | 
| 140 |     \snippet qml/xmlrole.qml 1 | 
| 141 |  | 
| 142 |     Accessing the model data for the above roles from a delegate: | 
| 143 |  | 
| 144 |     \snippet qml/xmlrole.qml 2 | 
| 145 |  | 
| 146 |     See the \l{http://www.w3.org/TR/xpath20/}{W3C XPath 2.0 specification} for more information. | 
| 147 | */ | 
| 148 |  | 
| 149 | /*! | 
| 150 |     \qmlproperty bool QtQuick.XmlListModel::XmlRole::isKey | 
| 151 |     Defines whether this is a key role. | 
| 152 |     Key roles are used to determine whether a set of values should | 
| 153 |     be updated or added to the XML list model when XmlListModel::reload() | 
| 154 |     is called. | 
| 155 |  | 
| 156 |     \sa XmlListModel | 
| 157 | */ | 
| 158 |  | 
| 159 | struct XmlQueryJob | 
| 160 | { | 
| 161 |     int queryId; | 
| 162 |     QByteArray data; | 
| 163 |     QString query; | 
| 164 |     QString namespaces; | 
| 165 |     QStringList roleQueries; | 
| 166 |     QList<void*> roleQueryErrorId; // the ptr to send back if there is an error | 
| 167 |     QStringList keyRoleQueries; | 
| 168 |     QStringList keyRoleResultsCache; | 
| 169 |     QString prefix; | 
| 170 | }; | 
| 171 |  | 
| 172 |  | 
| 173 | class QQuickXmlQueryEngine; | 
| 174 | class QQuickXmlQueryThreadObject : public QObject | 
| 175 | { | 
| 176 |     Q_OBJECT | 
| 177 | public: | 
| 178 |     QQuickXmlQueryThreadObject(QQuickXmlQueryEngine *); | 
| 179 |  | 
| 180 |     void processJobs(); | 
| 181 |     bool event(QEvent *e) override; | 
| 182 |  | 
| 183 | private: | 
| 184 |     QQuickXmlQueryEngine *m_queryEngine; | 
| 185 | }; | 
| 186 |  | 
| 187 |  | 
| 188 | class QQuickXmlQueryEngine : public QThread | 
| 189 | { | 
| 190 |     Q_OBJECT | 
| 191 | public: | 
| 192 |     QQuickXmlQueryEngine(QQmlEngine *eng); | 
| 193 |     ~QQuickXmlQueryEngine(); | 
| 194 |  | 
| 195 |     int doQuery(QString query, QString namespaces, QByteArray data, QList<QQuickXmlListModelRole *>* roleObjects, QStringList keyRoleResultsCache); | 
| 196 |     void abort(int id); | 
| 197 |  | 
| 198 |     void processJobs(); | 
| 199 |  | 
| 200 |     static QQuickXmlQueryEngine *instance(QQmlEngine *engine); | 
| 201 |  | 
| 202 | signals: | 
| 203 |     void queryCompleted(const QQuickXmlQueryResult &); | 
| 204 |     void error(void*, const QString&); | 
| 205 |  | 
| 206 | protected: | 
| 207 |     void run() override; | 
| 208 |  | 
| 209 | private: | 
| 210 |     void processQuery(XmlQueryJob *job); | 
| 211 |     void doQueryJob(XmlQueryJob *job, QQuickXmlQueryResult *currentResult); | 
| 212 |     void doSubQueryJob(XmlQueryJob *job, QQuickXmlQueryResult *currentResult); | 
| 213 |     void getValuesOfKeyRoles(const XmlQueryJob& currentJob, QStringList *values, QXmlQuery *query) const; | 
| 214 |     void addIndexToRangeList(QList<QQuickXmlListRange> *ranges, int index) const; | 
| 215 |  | 
| 216 |     QMutex m_mutex; | 
| 217 |     QQuickXmlQueryThreadObject *m_threadObject; | 
| 218 |     QList<XmlQueryJob> m_jobs; | 
| 219 |     QSet<int> m_cancelledJobs; | 
| 220 |     QAtomicInt m_queryIds; | 
| 221 |  | 
| 222 |     QQmlEngine *m_engine; | 
| 223 |     QObject *m_eventLoopQuitHack; | 
| 224 |  | 
| 225 |     static QHash<QQmlEngine *,QQuickXmlQueryEngine*> queryEngines; | 
| 226 |     static QMutex queryEnginesMutex; | 
| 227 | }; | 
| 228 | QHash<QQmlEngine *,QQuickXmlQueryEngine*> QQuickXmlQueryEngine::queryEngines; | 
| 229 | QMutex QQuickXmlQueryEngine::queryEnginesMutex; | 
| 230 |  | 
| 231 |  | 
| 232 | QQuickXmlQueryThreadObject::QQuickXmlQueryThreadObject(QQuickXmlQueryEngine *e) | 
| 233 |     : m_queryEngine(e) | 
| 234 | { | 
| 235 | } | 
| 236 |  | 
| 237 | void QQuickXmlQueryThreadObject::processJobs() | 
| 238 | { | 
| 239 |     QCoreApplication::postEvent(receiver: this, event: new QEvent(QEvent::User)); | 
| 240 | } | 
| 241 |  | 
| 242 | bool QQuickXmlQueryThreadObject::event(QEvent *e) | 
| 243 | { | 
| 244 |     if (e->type() == QEvent::User) { | 
| 245 |         m_queryEngine->processJobs(); | 
| 246 |         return true; | 
| 247 |     } else { | 
| 248 |         return QObject::event(event: e); | 
| 249 |     } | 
| 250 | } | 
| 251 |  | 
| 252 |  | 
| 253 |  | 
| 254 | QQuickXmlQueryEngine::QQuickXmlQueryEngine(QQmlEngine *eng) | 
| 255 | : QThread(eng), m_threadObject(0), m_queryIds(XMLLISTMODEL_CLEAR_ID + 1), m_engine(eng), m_eventLoopQuitHack(0) | 
| 256 | { | 
| 257 |     qRegisterMetaType<QQuickXmlQueryResult>(typeName: "QQuickXmlQueryResult" ); | 
| 258 |  | 
| 259 |     m_eventLoopQuitHack = new QObject; | 
| 260 |     m_eventLoopQuitHack->moveToThread(thread: this); | 
| 261 |     connect(asender: m_eventLoopQuitHack, SIGNAL(destroyed(QObject*)), SLOT(quit()), atype: Qt::DirectConnection); | 
| 262 |     start(QThread::IdlePriority); | 
| 263 | } | 
| 264 |  | 
| 265 | QQuickXmlQueryEngine::~QQuickXmlQueryEngine() | 
| 266 | { | 
| 267 |     queryEnginesMutex.lock(); | 
| 268 |     queryEngines.remove(key: m_engine); | 
| 269 |     queryEnginesMutex.unlock(); | 
| 270 |  | 
| 271 |     m_eventLoopQuitHack->deleteLater(); | 
| 272 |     wait(); | 
| 273 | } | 
| 274 |  | 
| 275 | int QQuickXmlQueryEngine::doQuery(QString query, QString namespaces, QByteArray data, QList<QQuickXmlListModelRole *>* roleObjects, QStringList keyRoleResultsCache) { | 
| 276 |     { | 
| 277 |         QMutexLocker m1(&m_mutex); | 
| 278 |         m_queryIds.ref(); | 
| 279 |         if (m_queryIds.loadRelaxed() <= 0) | 
| 280 |             m_queryIds.storeRelaxed(newValue: 1); | 
| 281 |     } | 
| 282 |  | 
| 283 |     XmlQueryJob job; | 
| 284 |     job.queryId = m_queryIds.loadRelaxed(); | 
| 285 |     job.data = data; | 
| 286 |     job.query = QLatin1String("doc($src)" ) + query; | 
| 287 |     job.namespaces = namespaces; | 
| 288 |     job.keyRoleResultsCache = keyRoleResultsCache; | 
| 289 |  | 
| 290 |     for (int i=0; i<roleObjects->count(); i++) { | 
| 291 |         if (!roleObjects->at(i)->isValid()) { | 
| 292 |             job.roleQueries << QString(); | 
| 293 |             continue; | 
| 294 |         } | 
| 295 |         job.roleQueries << roleObjects->at(i)->query(); | 
| 296 |         job.roleQueryErrorId << static_cast<void*>(roleObjects->at(i)); | 
| 297 |         if (roleObjects->at(i)->isKey()) | 
| 298 |             job.keyRoleQueries << job.roleQueries.last(); | 
| 299 |     } | 
| 300 |  | 
| 301 |     { | 
| 302 |         QMutexLocker ml(&m_mutex); | 
| 303 |         m_jobs.append(t: job); | 
| 304 |         if (m_threadObject) | 
| 305 |             m_threadObject->processJobs(); | 
| 306 |     } | 
| 307 |  | 
| 308 |     return job.queryId; | 
| 309 | } | 
| 310 |  | 
| 311 | void QQuickXmlQueryEngine::abort(int id) | 
| 312 | { | 
| 313 |     QMutexLocker ml(&m_mutex); | 
| 314 |     if (id != -1) | 
| 315 |         m_cancelledJobs.insert(value: id); | 
| 316 | } | 
| 317 |  | 
| 318 | void QQuickXmlQueryEngine::run() | 
| 319 | { | 
| 320 |     m_mutex.lock(); | 
| 321 |     m_threadObject = new QQuickXmlQueryThreadObject(this); | 
| 322 |     m_mutex.unlock(); | 
| 323 |  | 
| 324 |     processJobs(); | 
| 325 |     exec(); | 
| 326 |  | 
| 327 |     delete m_threadObject; | 
| 328 |     m_threadObject = 0; | 
| 329 | } | 
| 330 |  | 
| 331 | void QQuickXmlQueryEngine::processJobs() | 
| 332 | { | 
| 333 |     QMutexLocker locker(&m_mutex); | 
| 334 |  | 
| 335 |     while (true) { | 
| 336 |         if (m_jobs.isEmpty()) | 
| 337 |             return; | 
| 338 |  | 
| 339 |         XmlQueryJob currentJob = m_jobs.takeLast(); | 
| 340 |         while (m_cancelledJobs.remove(value: currentJob.queryId)) { | 
| 341 |             if (m_jobs.isEmpty()) | 
| 342 |               return; | 
| 343 |             currentJob = m_jobs.takeLast(); | 
| 344 |         } | 
| 345 |  | 
| 346 |         locker.unlock(); | 
| 347 |         processQuery(job: ¤tJob); | 
| 348 |         locker.relock(); | 
| 349 |     } | 
| 350 | } | 
| 351 |  | 
| 352 | QQuickXmlQueryEngine *QQuickXmlQueryEngine::instance(QQmlEngine *engine) | 
| 353 | { | 
| 354 |     queryEnginesMutex.lock(); | 
| 355 |     QQuickXmlQueryEngine *queryEng = queryEngines.value(key: engine); | 
| 356 |     if (!queryEng) { | 
| 357 |         queryEng = new QQuickXmlQueryEngine(engine); | 
| 358 |         queryEngines.insert(key: engine, value: queryEng); | 
| 359 |     } | 
| 360 |     queryEnginesMutex.unlock(); | 
| 361 |  | 
| 362 |     return queryEng; | 
| 363 | } | 
| 364 |  | 
| 365 | void QQuickXmlQueryEngine::processQuery(XmlQueryJob *job) | 
| 366 | { | 
| 367 |     QQuickXmlQueryResult result; | 
| 368 |     result.queryId = job->queryId; | 
| 369 |     doQueryJob(job, currentResult: &result); | 
| 370 |     doSubQueryJob(job, currentResult: &result); | 
| 371 |  | 
| 372 |     { | 
| 373 |         QMutexLocker ml(&m_mutex); | 
| 374 |         if (m_cancelledJobs.contains(value: job->queryId)) { | 
| 375 |             m_cancelledJobs.remove(value: job->queryId); | 
| 376 |         } else { | 
| 377 |             emit queryCompleted(result); | 
| 378 |         } | 
| 379 |     } | 
| 380 | } | 
| 381 |  | 
| 382 | void QQuickXmlQueryEngine::doQueryJob(XmlQueryJob *currentJob, QQuickXmlQueryResult *currentResult) | 
| 383 | { | 
| 384 |     Q_ASSERT(currentJob->queryId != -1); | 
| 385 |  | 
| 386 |     QString r; | 
| 387 |     QXmlQuery query; | 
| 388 |     QBuffer buffer(¤tJob->data); | 
| 389 |     buffer.open(openMode: QIODevice::ReadOnly); | 
| 390 |     query.bindVariable(localName: QLatin1String("src" ), &buffer); | 
| 391 |     query.setQuery(sourceCode: currentJob->namespaces + currentJob->query); | 
| 392 |     query.evaluateTo(output: &r); | 
| 393 |  | 
| 394 |     //always need a single root element | 
| 395 |     QByteArray xml = "<dummy:items xmlns:dummy=\"http://qtsotware.com/dummy\">\n"  + r.toUtf8() + "</dummy:items>" ; | 
| 396 |     QBuffer b(&xml); | 
| 397 |     b.open(openMode: QIODevice::ReadOnly); | 
| 398 |  | 
| 399 |     QString namespaces = QLatin1String("declare namespace dummy=\"http://qtsotware.com/dummy\";\n" ) + currentJob->namespaces; | 
| 400 |     QString prefix = QLatin1String("doc($inputDocument)/dummy:items/*" ); | 
| 401 |  | 
| 402 |     //figure out how many items we are dealing with | 
| 403 |     int count = -1; | 
| 404 |     { | 
| 405 |         QXmlResultItems result; | 
| 406 |         QXmlQuery countquery; | 
| 407 |         countquery.bindVariable(localName: QLatin1String("inputDocument" ), &b); | 
| 408 |         countquery.setQuery(sourceCode: namespaces + QLatin1String("count(" ) + prefix + QLatin1Char(')')); | 
| 409 |         countquery.evaluateTo(result: &result); | 
| 410 |         QXmlItem item(result.next()); | 
| 411 |         if (item.isAtomicValue()) | 
| 412 |             count = item.toAtomicValue().toInt(); | 
| 413 |     } | 
| 414 |  | 
| 415 |     currentJob->data = xml; | 
| 416 |     currentJob->prefix = namespaces + prefix + QLatin1Char('/'); | 
| 417 |     currentResult->size = (count > 0 ? count : 0); | 
| 418 | } | 
| 419 |  | 
| 420 | void QQuickXmlQueryEngine::getValuesOfKeyRoles(const XmlQueryJob& currentJob, QStringList *values, QXmlQuery *query) const | 
| 421 | { | 
| 422 |     const QStringList &keysQueries = currentJob.keyRoleQueries; | 
| 423 |     QString keysQuery; | 
| 424 |     if (keysQueries.count() == 1) | 
| 425 |         keysQuery = currentJob.prefix + keysQueries[0]; | 
| 426 |     else if (keysQueries.count() > 1) | 
| 427 |         keysQuery = currentJob.prefix + QLatin1String("concat(" ) + keysQueries.join(sep: QLatin1Char(',')) + QLatin1Char(')'); | 
| 428 |  | 
| 429 |     if (!keysQuery.isEmpty()) { | 
| 430 |         query->setQuery(sourceCode: keysQuery); | 
| 431 |         QXmlResultItems resultItems; | 
| 432 |         query->evaluateTo(result: &resultItems); | 
| 433 |         QXmlItem item(resultItems.next()); | 
| 434 |         while (!item.isNull()) { | 
| 435 |             values->append(t: item.toAtomicValue().toString()); | 
| 436 |             item = resultItems.next(); | 
| 437 |         } | 
| 438 |     } | 
| 439 | } | 
| 440 |  | 
| 441 | void QQuickXmlQueryEngine::addIndexToRangeList(QList<QQuickXmlListRange> *ranges, int index) const { | 
| 442 |     if (ranges->isEmpty()) | 
| 443 |         ranges->append(t: qMakePair(x: index, y: 1)); | 
| 444 |     else if (ranges->last().first + ranges->last().second == index) | 
| 445 |         ranges->last().second += 1; | 
| 446 |     else | 
| 447 |         ranges->append(t: qMakePair(x: index, y: 1)); | 
| 448 | } | 
| 449 |  | 
| 450 | void QQuickXmlQueryEngine::doSubQueryJob(XmlQueryJob *currentJob, QQuickXmlQueryResult *currentResult) | 
| 451 | { | 
| 452 |     Q_ASSERT(currentJob->queryId != -1); | 
| 453 |  | 
| 454 |     QBuffer b(¤tJob->data); | 
| 455 |     b.open(openMode: QIODevice::ReadOnly); | 
| 456 |  | 
| 457 |     QXmlQuery subquery; | 
| 458 |     subquery.bindVariable(localName: QLatin1String("inputDocument" ), &b); | 
| 459 |  | 
| 460 |     QStringList keyRoleResults; | 
| 461 |     getValuesOfKeyRoles(currentJob: *currentJob, values: &keyRoleResults, query: &subquery); | 
| 462 |  | 
| 463 |     // See if any values of key roles have been inserted or removed. | 
| 464 |  | 
| 465 |     if (currentJob->keyRoleResultsCache.isEmpty()) { | 
| 466 |         currentResult->inserted << qMakePair(x: 0, y: currentResult->size); | 
| 467 |     } else { | 
| 468 |         if (keyRoleResults != currentJob->keyRoleResultsCache) { | 
| 469 |             QStringList temp; | 
| 470 |             for (int i=0; i<currentJob->keyRoleResultsCache.count(); i++) { | 
| 471 |                 if (!keyRoleResults.contains(str: currentJob->keyRoleResultsCache[i])) | 
| 472 |                     addIndexToRangeList(ranges: ¤tResult->removed, index: i); | 
| 473 |                 else | 
| 474 |                     temp << currentJob->keyRoleResultsCache[i]; | 
| 475 |             } | 
| 476 |             for (int i=0; i<keyRoleResults.count(); i++) { | 
| 477 |                 if (temp.count() == i || keyRoleResults[i] != temp[i]) { | 
| 478 |                     temp.insert(i, t: keyRoleResults[i]); | 
| 479 |                     addIndexToRangeList(ranges: ¤tResult->inserted, index: i); | 
| 480 |                 } | 
| 481 |             } | 
| 482 |         } | 
| 483 |     } | 
| 484 |     currentResult->keyRoleResultsCache = keyRoleResults; | 
| 485 |  | 
| 486 |     // Get the new values for each role. | 
| 487 |     //### we might be able to condense even further (query for everything in one go) | 
| 488 |     const QStringList &queries = currentJob->roleQueries; | 
| 489 |     for (int i = 0; i < queries.size(); ++i) { | 
| 490 |         QList<QVariant> resultList; | 
| 491 |         if (!queries[i].isEmpty()) { | 
| 492 |             subquery.setQuery(sourceCode: currentJob->prefix + QLatin1String("(let $v := string(" ) + queries[i] + QLatin1String(") return if ($v) then " ) + queries[i] + QLatin1String(" else \"\")" )); | 
| 493 |             if (subquery.isValid()) { | 
| 494 |                 QXmlResultItems resultItems; | 
| 495 |                 subquery.evaluateTo(result: &resultItems); | 
| 496 |                 QXmlItem item(resultItems.next()); | 
| 497 |                 while (!item.isNull()) { | 
| 498 |                     resultList << item.toAtomicValue(); //### we used to trim strings | 
| 499 |                     item = resultItems.next(); | 
| 500 |                 } | 
| 501 |             } else { | 
| 502 |                 emit error(currentJob->roleQueryErrorId.at(i), queries[i]); | 
| 503 |             } | 
| 504 |         } | 
| 505 |         //### should warn here if things have gone wrong. | 
| 506 |         while (resultList.count() < currentResult->size) | 
| 507 |             resultList << QVariant(); | 
| 508 |         currentResult->data << resultList; | 
| 509 |         b.seek(off: 0); | 
| 510 |     } | 
| 511 |  | 
| 512 |     //this method is much slower, but works better for incremental loading | 
| 513 |     /*for (int j = 0; j < m_size; ++j) { | 
| 514 |         QList<QVariant> resultList; | 
| 515 |         for (int i = 0; i < m_roleObjects->size(); ++i) { | 
| 516 |             QQuickXmlListModelRole *role = m_roleObjects->at(i); | 
| 517 |             subquery.setQuery(m_prefix.arg(j+1) + role->query()); | 
| 518 |             if (role->isStringList()) { | 
| 519 |                 QStringList data; | 
| 520 |                 subquery.evaluateTo(&data); | 
| 521 |                 resultList << QVariant(data); | 
| 522 |                 //qDebug() << data; | 
| 523 |             } else { | 
| 524 |                 QString s; | 
| 525 |                 subquery.evaluateTo(&s); | 
| 526 |                 if (role->isCData()) { | 
| 527 |                     //un-escape | 
| 528 |                     s.replace(QLatin1String("<"), QLatin1String("<")); | 
| 529 |                     s.replace(QLatin1String(">"), QLatin1String(">")); | 
| 530 |                     s.replace(QLatin1String("&"), QLatin1String("&")); | 
| 531 |                 } | 
| 532 |                 resultList << s.trimmed(); | 
| 533 |                 //qDebug() << s; | 
| 534 |             } | 
| 535 |             b.seek(0); | 
| 536 |         } | 
| 537 |         m_modelData << resultList; | 
| 538 |     }*/ | 
| 539 | } | 
| 540 |  | 
| 541 | class QQuickXmlListModelPrivate : public QAbstractItemModelPrivate | 
| 542 | { | 
| 543 |     Q_DECLARE_PUBLIC(QQuickXmlListModel) | 
| 544 | public: | 
| 545 |     QQuickXmlListModelPrivate() | 
| 546 |         : isComponentComplete(true), size(0), highestRole(Qt::UserRole) | 
| 547 | #if QT_CONFIG(qml_network) | 
| 548 |         , reply(0) | 
| 549 | #endif | 
| 550 |         , status(QQuickXmlListModel::Null), progress(0.0) | 
| 551 |         , queryId(-1), roleObjects(), redirectCount(0) {} | 
| 552 |  | 
| 553 |  | 
| 554 |     void notifyQueryStarted(bool remoteSource) { | 
| 555 |         Q_Q(QQuickXmlListModel); | 
| 556 |         progress = remoteSource ? 0.0 : 1.0; | 
| 557 |         status = QQuickXmlListModel::Loading; | 
| 558 |         errorString.clear(); | 
| 559 |         emit q->progressChanged(progress); | 
| 560 |         emit q->statusChanged(status); | 
| 561 |     } | 
| 562 |  | 
| 563 | #if QT_CONFIG(qml_network) | 
| 564 |     void deleteReply() { | 
| 565 |         Q_Q(QQuickXmlListModel); | 
| 566 |         if (reply) { | 
| 567 |             QObject::disconnect(sender: reply, signal: 0, receiver: q, member: 0); | 
| 568 |             reply->deleteLater(); | 
| 569 |             reply = 0; | 
| 570 |         } | 
| 571 |     } | 
| 572 | #endif | 
| 573 |  | 
| 574 |     bool isComponentComplete; | 
| 575 |     QUrl src; | 
| 576 |     QString xml; | 
| 577 |     QString query; | 
| 578 |     QString namespaces; | 
| 579 |     int size; | 
| 580 |     QList<int> roles; | 
| 581 |     QStringList roleNames; | 
| 582 |     int highestRole; | 
| 583 |  | 
| 584 | #if QT_CONFIG(qml_network) | 
| 585 |     QNetworkReply *reply; | 
| 586 | #endif | 
| 587 |     QQuickXmlListModel::Status status; | 
| 588 |     QString errorString; | 
| 589 |     qreal progress; | 
| 590 |     int queryId; | 
| 591 |     QStringList keyRoleResultsCache; | 
| 592 |     QList<QQuickXmlListModelRole *> roleObjects; | 
| 593 |  | 
| 594 |     static void append_role(QQmlListProperty<QQuickXmlListModelRole> *list, QQuickXmlListModelRole *role); | 
| 595 |     static void clear_role(QQmlListProperty<QQuickXmlListModelRole> *list); | 
| 596 |     QList<QList<QVariant> > data; | 
| 597 |     int redirectCount; | 
| 598 | }; | 
| 599 |  | 
| 600 |  | 
| 601 | void QQuickXmlListModelPrivate::append_role(QQmlListProperty<QQuickXmlListModelRole> *list, QQuickXmlListModelRole *role) | 
| 602 | { | 
| 603 |     QQuickXmlListModel *_this = qobject_cast<QQuickXmlListModel *>(object: list->object); | 
| 604 |     if (_this && role) { | 
| 605 |         int i = _this->d_func()->roleObjects.count(); | 
| 606 |         _this->d_func()->roleObjects.append(t: role); | 
| 607 |         if (_this->d_func()->roleNames.contains(str: role->name())) { | 
| 608 |             qmlWarning(me: role) << QQuickXmlListModel::tr(s: "\"%1\" duplicates a previous role name and will be disabled." ).arg(a: role->name()); | 
| 609 |             return; | 
| 610 |         } | 
| 611 |         _this->d_func()->roles.insert(i, t: _this->d_func()->highestRole); | 
| 612 |         _this->d_func()->roleNames.insert(i, t: role->name()); | 
| 613 |         ++_this->d_func()->highestRole; | 
| 614 |     } | 
| 615 | } | 
| 616 |  | 
| 617 | //### clear needs to invalidate any cached data (in data table) as well | 
| 618 | //    (and the model should emit the appropriate signals) | 
| 619 | void QQuickXmlListModelPrivate::clear_role(QQmlListProperty<QQuickXmlListModelRole> *list) | 
| 620 | { | 
| 621 |     QQuickXmlListModel *_this = static_cast<QQuickXmlListModel *>(list->object); | 
| 622 |     _this->d_func()->roles.clear(); | 
| 623 |     _this->d_func()->roleNames.clear(); | 
| 624 |     _this->d_func()->roleObjects.clear(); | 
| 625 | } | 
| 626 |  | 
| 627 | /*! | 
| 628 |     \qmltype XmlListModel | 
| 629 |     \inqmlmodule QtQuick.XmlListModel | 
| 630 |     \brief For specifying a read-only model using XPath expressions. | 
| 631 |     \ingroup qtquick-models | 
| 632 |  | 
| 633 |  | 
| 634 |     To use this element, you will need to import the module with the following line: | 
| 635 |     \code | 
| 636 |     import QtQuick.XmlListModel 2.0 | 
| 637 |     \endcode | 
| 638 |  | 
| 639 |     XmlListModel is used to create a read-only model from XML data. It can be used as a data source | 
| 640 |     for view elements (such as ListView, PathView, GridView) and other elements that interact with model | 
| 641 |     data (such as \l [QML]{Repeater}). | 
| 642 |  | 
| 643 |     For example, if there is a XML document at http://www.mysite.com/feed.xml like this: | 
| 644 |  | 
| 645 |     \code | 
| 646 |     <?xml version="1.0" encoding="utf-8"?> | 
| 647 |     <rss version="2.0"> | 
| 648 |         ... | 
| 649 |         <channel> | 
| 650 |             <item> | 
| 651 |                 <title>A blog post</title> | 
| 652 |                 <pubDate>Sat, 07 Sep 2010 10:00:01 GMT</pubDate> | 
| 653 |             </item> | 
| 654 |             <item> | 
| 655 |                 <title>Another blog post</title> | 
| 656 |                 <pubDate>Sat, 07 Sep 2010 15:35:01 GMT</pubDate> | 
| 657 |             </item> | 
| 658 |         </channel> | 
| 659 |     </rss> | 
| 660 |     \endcode | 
| 661 |  | 
| 662 |     A XmlListModel could create a model from this data, like this: | 
| 663 |  | 
| 664 |     \qml | 
| 665 |     import QtQuick 2.0 | 
| 666 |     import QtQuick.XmlListModel 2.0 | 
| 667 |  | 
| 668 |     XmlListModel { | 
| 669 |         id: xmlModel | 
| 670 |         source: "http://www.mysite.com/feed.xml" | 
| 671 |         query: "/rss/channel/item" | 
| 672 |  | 
| 673 |         XmlRole { name: "title"; query: "title/string()" } | 
| 674 |         XmlRole { name: "pubDate"; query: "pubDate/string()" } | 
| 675 |     } | 
| 676 |     \endqml | 
| 677 |  | 
| 678 |     The \l {XmlListModel::query}{query} value of "/rss/channel/item" specifies that the XmlListModel should generate | 
| 679 |     a model item for each \c <item> in the XML document. | 
| 680 |  | 
| 681 |     The XmlRole objects define the | 
| 682 |     model item attributes. Here, each model item will have \c title and \c pubDate | 
| 683 |     attributes that match the \c title and \c pubDate values of its corresponding \c <item>. | 
| 684 |     (See \l XmlRole::query for more examples of valid XPath expressions for XmlRole.) | 
| 685 |  | 
| 686 |     The model could be used in a ListView, like this: | 
| 687 |  | 
| 688 |     \qml | 
| 689 |     ListView { | 
| 690 |         width: 180; height: 300 | 
| 691 |         model: xmlModel | 
| 692 |         delegate: Text { text: title + ": " + pubDate } | 
| 693 |     } | 
| 694 |     \endqml | 
| 695 |  | 
| 696 |     \image qml-xmllistmodel-example.png | 
| 697 |  | 
| 698 |     The XmlListModel data is loaded asynchronously, and \l status | 
| 699 |     is set to \c XmlListModel.Ready when loading is complete. | 
| 700 |     Note this means when XmlListModel is used for a view, the view is not | 
| 701 |     populated until the model is loaded. | 
| 702 |  | 
| 703 |  | 
| 704 |     \section2 Using Key XML Roles | 
| 705 |  | 
| 706 |     You can define certain roles as "keys" so that when reload() is called, | 
| 707 |     the model will only add and refresh data that contains new values for | 
| 708 |     these keys. | 
| 709 |  | 
| 710 |     For example, if above role for "pubDate" was defined like this instead: | 
| 711 |  | 
| 712 |     \qml | 
| 713 |         XmlRole { name: "pubDate"; query: "pubDate/string()"; isKey: true } | 
| 714 |     \endqml | 
| 715 |  | 
| 716 |     Then when reload() is called, the model will only add and reload | 
| 717 |     items with a "pubDate" value that is not already | 
| 718 |     present in the model. | 
| 719 |  | 
| 720 |     This is useful when displaying the contents of XML documents that | 
| 721 |     are incrementally updated (such as RSS feeds) to avoid repainting the | 
| 722 |     entire contents of a model in a view. | 
| 723 |  | 
| 724 |     If multiple key roles are specified, the model only adds and reload items | 
| 725 |     with a combined value of all key roles that is not already present in | 
| 726 |     the model. | 
| 727 |  | 
| 728 |     \sa {Qt Quick Demo - RSS News} | 
| 729 | */ | 
| 730 |  | 
| 731 | QQuickXmlListModel::QQuickXmlListModel(QObject *parent) | 
| 732 |     : QAbstractListModel(*(new QQuickXmlListModelPrivate), parent) | 
| 733 | { | 
| 734 | } | 
| 735 |  | 
| 736 | QQuickXmlListModel::~QQuickXmlListModel() | 
| 737 | { | 
| 738 | } | 
| 739 |  | 
| 740 | /*! | 
| 741 |     \qmlproperty list<XmlRole> QtQuick.XmlListModel::XmlListModel::roles | 
| 742 |  | 
| 743 |     The roles to make available for this model. | 
| 744 | */ | 
| 745 | QQmlListProperty<QQuickXmlListModelRole> QQuickXmlListModel::roleObjects() | 
| 746 | { | 
| 747 |     Q_D(QQuickXmlListModel); | 
| 748 |     QQmlListProperty<QQuickXmlListModelRole> list(this, &d->roleObjects); | 
| 749 |     list.append = &QQuickXmlListModelPrivate::append_role; | 
| 750 |     list.clear = &QQuickXmlListModelPrivate::clear_role; | 
| 751 |     return list; | 
| 752 | } | 
| 753 |  | 
| 754 | QModelIndex QQuickXmlListModel::index(int row, int column, const QModelIndex &parent) const | 
| 755 | { | 
| 756 |     Q_D(const QQuickXmlListModel); | 
| 757 |     return !parent.isValid() && column == 0 && row >= 0 && row < d->size | 
| 758 |             ? createIndex(arow: row, acolumn: column) | 
| 759 |             : QModelIndex(); | 
| 760 | } | 
| 761 |  | 
| 762 | int QQuickXmlListModel::rowCount(const QModelIndex &parent) const | 
| 763 | { | 
| 764 |     Q_D(const QQuickXmlListModel); | 
| 765 |     return !parent.isValid() ? d->size : 0; | 
| 766 | } | 
| 767 |  | 
| 768 | QVariant QQuickXmlListModel::data(const QModelIndex &index, int role) const | 
| 769 | { | 
| 770 |     Q_D(const QQuickXmlListModel); | 
| 771 |     const int roleIndex = d->roles.indexOf(t: role); | 
| 772 |     return (roleIndex == -1 || !index.isValid()) | 
| 773 |             ? QVariant() | 
| 774 |             : d->data.value(i: roleIndex).value(i: index.row()); | 
| 775 | } | 
| 776 |  | 
| 777 | QHash<int, QByteArray> QQuickXmlListModel::roleNames() const | 
| 778 | { | 
| 779 |     Q_D(const QQuickXmlListModel); | 
| 780 |     QHash<int,QByteArray> roleNames; | 
| 781 |     for (int i = 0; i < d->roles.count(); ++i) | 
| 782 |         roleNames.insert(key: d->roles.at(i), value: d->roleNames.at(i).toUtf8()); | 
| 783 |     return roleNames; | 
| 784 | } | 
| 785 |  | 
| 786 | /*! | 
| 787 |     \qmlproperty int QtQuick.XmlListModel::XmlListModel::count | 
| 788 |     The number of data entries in the model. | 
| 789 | */ | 
| 790 | int QQuickXmlListModel::count() const | 
| 791 | { | 
| 792 |     Q_D(const QQuickXmlListModel); | 
| 793 |     return d->size; | 
| 794 | } | 
| 795 |  | 
| 796 | /*! | 
| 797 |     \qmlproperty url QtQuick.XmlListModel::XmlListModel::source | 
| 798 |     The location of the XML data source. | 
| 799 |  | 
| 800 |     If both \c source and \l xml are set, \l xml is used. | 
| 801 | */ | 
| 802 | QUrl QQuickXmlListModel::source() const | 
| 803 | { | 
| 804 |     Q_D(const QQuickXmlListModel); | 
| 805 |     return d->src; | 
| 806 | } | 
| 807 |  | 
| 808 | void QQuickXmlListModel::setSource(const QUrl &src) | 
| 809 | { | 
| 810 |     Q_D(QQuickXmlListModel); | 
| 811 |     if (d->src != src) { | 
| 812 |         d->src = src; | 
| 813 |         if (d->xml.isEmpty())   // src is only used if d->xml is not set | 
| 814 |             reload(); | 
| 815 |         emit sourceChanged(); | 
| 816 |    } | 
| 817 | } | 
| 818 |  | 
| 819 | /*! | 
| 820 |     \qmlproperty string QtQuick.XmlListModel::XmlListModel::xml | 
| 821 |     This property holds the XML data for this model, if set. | 
| 822 |  | 
| 823 |     The text is assumed to be UTF-8 encoded. | 
| 824 |  | 
| 825 |     If both \l source and \c xml are set, \c xml is used. | 
| 826 | */ | 
| 827 | QString QQuickXmlListModel::xml() const | 
| 828 | { | 
| 829 |     Q_D(const QQuickXmlListModel); | 
| 830 |     return d->xml; | 
| 831 | } | 
| 832 |  | 
| 833 | void QQuickXmlListModel::setXml(const QString &xml) | 
| 834 | { | 
| 835 |     Q_D(QQuickXmlListModel); | 
| 836 |     if (d->xml != xml) { | 
| 837 |         d->xml = xml; | 
| 838 |         reload(); | 
| 839 |         emit xmlChanged(); | 
| 840 |     } | 
| 841 | } | 
| 842 |  | 
| 843 | /*! | 
| 844 |     \qmlproperty string QtQuick.XmlListModel::XmlListModel::query | 
| 845 |     An absolute XPath query representing the base query for creating model items | 
| 846 |     from this model's XmlRole objects. The query should start with '/' or '//'. | 
| 847 | */ | 
| 848 | QString QQuickXmlListModel::query() const | 
| 849 | { | 
| 850 |     Q_D(const QQuickXmlListModel); | 
| 851 |     return d->query; | 
| 852 | } | 
| 853 |  | 
| 854 | void QQuickXmlListModel::setQuery(const QString &query) | 
| 855 | { | 
| 856 |     Q_D(QQuickXmlListModel); | 
| 857 |     if (!query.startsWith(c: QLatin1Char('/'))) { | 
| 858 |         qmlWarning(me: this) << QCoreApplication::translate(context: "QQuickXmlRoleList" , key: "An XmlListModel query must start with '/' or \"//\"" ); | 
| 859 |         return; | 
| 860 |     } | 
| 861 |  | 
| 862 |     if (d->query != query) { | 
| 863 |         d->query = query; | 
| 864 |         reload(); | 
| 865 |         emit queryChanged(); | 
| 866 |     } | 
| 867 | } | 
| 868 |  | 
| 869 | /*! | 
| 870 |     \qmlproperty string QtQuick.XmlListModel::XmlListModel::namespaceDeclarations | 
| 871 |     The namespace declarations to be used in the XPath queries. | 
| 872 |  | 
| 873 |     The namespaces should be declared as in XQuery. For example, if a requested document | 
| 874 |     at http://mysite.com/feed.xml uses the namespace "http://www.w3.org/2005/Atom", | 
| 875 |     this can be declared as the default namespace: | 
| 876 |  | 
| 877 |     \qml | 
| 878 |     XmlListModel { | 
| 879 |         source: "http://mysite.com/feed.xml" | 
| 880 |         query: "/feed/entry" | 
| 881 |         namespaceDeclarations: "declare default element namespace 'http://www.w3.org/2005/Atom';" | 
| 882 |  | 
| 883 |         XmlRole { name: "title"; query: "title/string()" } | 
| 884 |     } | 
| 885 |     \endqml | 
| 886 | */ | 
| 887 | QString QQuickXmlListModel::namespaceDeclarations() const | 
| 888 | { | 
| 889 |     Q_D(const QQuickXmlListModel); | 
| 890 |     return d->namespaces; | 
| 891 | } | 
| 892 |  | 
| 893 | void QQuickXmlListModel::setNamespaceDeclarations(const QString &declarations) | 
| 894 | { | 
| 895 |     Q_D(QQuickXmlListModel); | 
| 896 |     if (d->namespaces != declarations) { | 
| 897 |         d->namespaces = declarations; | 
| 898 |         reload(); | 
| 899 |         emit namespaceDeclarationsChanged(); | 
| 900 |     } | 
| 901 | } | 
| 902 |  | 
| 903 | /*! | 
| 904 |     \qmlmethod object QtQuick.XmlListModel::XmlListModel::get(int index) | 
| 905 |  | 
| 906 |     Returns the item at \a index in the model. | 
| 907 |  | 
| 908 |     For example, for a model like this: | 
| 909 |  | 
| 910 |     \qml | 
| 911 |     XmlListModel { | 
| 912 |         id: model | 
| 913 |         source: "http://mysite.com/feed.xml" | 
| 914 |         query: "/feed/entry" | 
| 915 |         XmlRole { name: "title"; query: "title/string()" } | 
| 916 |     } | 
| 917 |     \endqml | 
| 918 |  | 
| 919 |     This will access the \c title value for the first item in the model: | 
| 920 |  | 
| 921 |     \js | 
| 922 |     var title = model.get(0).title; | 
| 923 |     \endjs | 
| 924 | */ | 
| 925 | QJSValue QQuickXmlListModel::get(int index) const | 
| 926 | { | 
| 927 |     // Must be called with a context and handle scope | 
| 928 |     Q_D(const QQuickXmlListModel); | 
| 929 |  | 
| 930 |     if (index < 0 || index >= count()) | 
| 931 |         return QJSValue(QJSValue::UndefinedValue); | 
| 932 |  | 
| 933 |     QQmlEngine *engine = qmlContext(this)->engine(); | 
| 934 |     ExecutionEngine *v4engine = engine->handle(); | 
| 935 |     Scope scope(v4engine); | 
| 936 |     Scoped<Object> o(scope, v4engine->newObject()); | 
| 937 |     ScopedString name(scope); | 
| 938 |     ScopedValue value(scope); | 
| 939 |     for (int ii = 0; ii < d->roleObjects.count(); ++ii) { | 
| 940 |         name = v4engine->newIdentifier(text: d->roleObjects[ii]->name()); | 
| 941 |         value = v4engine->fromVariant(d->data.value(i: ii).value(i: index)); | 
| 942 |         o->insertMember(s: name.getPointer(), v: value); | 
| 943 |     } | 
| 944 |  | 
| 945 |     return QJSValue(v4engine, o->asReturnedValue()); | 
| 946 | } | 
| 947 |  | 
| 948 | /*! | 
| 949 |     \qmlproperty enumeration QtQuick.XmlListModel::XmlListModel::status | 
| 950 |     Specifies the model loading status, which can be one of the following: | 
| 951 |  | 
| 952 |     \list | 
| 953 |     \li XmlListModel.Null - No XML data has been set for this model. | 
| 954 |     \li XmlListModel.Ready - The XML data has been loaded into the model. | 
| 955 |     \li XmlListModel.Loading - The model is in the process of reading and loading XML data. | 
| 956 |     \li XmlListModel.Error - An error occurred while the model was loading. See errorString() for details | 
| 957 |        about the error. | 
| 958 |     \endlist | 
| 959 |  | 
| 960 |     \sa progress | 
| 961 |  | 
| 962 | */ | 
| 963 | QQuickXmlListModel::Status QQuickXmlListModel::status() const | 
| 964 | { | 
| 965 |     Q_D(const QQuickXmlListModel); | 
| 966 |     return d->status; | 
| 967 | } | 
| 968 |  | 
| 969 | /*! | 
| 970 |     \qmlproperty real QtQuick.XmlListModel::XmlListModel::progress | 
| 971 |  | 
| 972 |     This indicates the current progress of the downloading of the XML data | 
| 973 |     source. This value ranges from 0.0 (no data downloaded) to | 
| 974 |     1.0 (all data downloaded). If the XML data is not from a remote source, | 
| 975 |     the progress becomes 1.0 as soon as the data is read. | 
| 976 |  | 
| 977 |     Note that when the progress is 1.0, the XML data has been downloaded, but | 
| 978 |     it is yet to be loaded into the model at this point. Use the status | 
| 979 |     property to find out when the XML data has been read and loaded into | 
| 980 |     the model. | 
| 981 |  | 
| 982 |     \sa status, source | 
| 983 | */ | 
| 984 | qreal QQuickXmlListModel::progress() const | 
| 985 | { | 
| 986 |     Q_D(const QQuickXmlListModel); | 
| 987 |     return d->progress; | 
| 988 | } | 
| 989 |  | 
| 990 | /*! | 
| 991 |     \qmlmethod QtQuick.XmlListModel::XmlListModel::errorString() | 
| 992 |  | 
| 993 |     Returns a string description of the last error that occurred | 
| 994 |     if \l status is XmlListModel::Error. | 
| 995 | */ | 
| 996 | QString QQuickXmlListModel::errorString() const | 
| 997 | { | 
| 998 |     Q_D(const QQuickXmlListModel); | 
| 999 |     return d->errorString; | 
| 1000 | } | 
| 1001 |  | 
| 1002 | void QQuickXmlListModel::classBegin() | 
| 1003 | { | 
| 1004 |     Q_D(QQuickXmlListModel); | 
| 1005 |     d->isComponentComplete = false; | 
| 1006 |  | 
| 1007 |     QQuickXmlQueryEngine *queryEngine = QQuickXmlQueryEngine::instance(engine: qmlEngine(this)); | 
| 1008 |     connect(asender: queryEngine, SIGNAL(queryCompleted(QQuickXmlQueryResult)), | 
| 1009 |             SLOT(queryCompleted(QQuickXmlQueryResult))); | 
| 1010 |     connect(asender: queryEngine, SIGNAL(error(void*,QString)), | 
| 1011 |             SLOT(queryError(void*,QString))); | 
| 1012 | } | 
| 1013 |  | 
| 1014 | void QQuickXmlListModel::componentComplete() | 
| 1015 | { | 
| 1016 |     Q_D(QQuickXmlListModel); | 
| 1017 |     d->isComponentComplete = true; | 
| 1018 |     reload(); | 
| 1019 | } | 
| 1020 |  | 
| 1021 | /*! | 
| 1022 |     \qmlmethod QtQuick.XmlListModel::XmlListModel::reload() | 
| 1023 |  | 
| 1024 |     Reloads the model. | 
| 1025 |  | 
| 1026 |     If no key roles have been specified, all existing model | 
| 1027 |     data is removed, and the model is rebuilt from scratch. | 
| 1028 |  | 
| 1029 |     Otherwise, items are only added if the model does not already | 
| 1030 |     contain items with matching key role values. | 
| 1031 |  | 
| 1032 |     \sa {Using key XML roles}, XmlRole::isKey | 
| 1033 | */ | 
| 1034 | void QQuickXmlListModel::reload() | 
| 1035 | { | 
| 1036 |     Q_D(QQuickXmlListModel); | 
| 1037 |  | 
| 1038 |     if (!d->isComponentComplete) | 
| 1039 |         return; | 
| 1040 |  | 
| 1041 |     QQuickXmlQueryEngine::instance(engine: qmlEngine(this))->abort(id: d->queryId); | 
| 1042 |     d->queryId = -1; | 
| 1043 |  | 
| 1044 |     if (d->size < 0) | 
| 1045 |         d->size = 0; | 
| 1046 |  | 
| 1047 | #if QT_CONFIG(qml_network) | 
| 1048 |     if (d->reply) { | 
| 1049 |         d->reply->abort(); | 
| 1050 |         d->deleteReply(); | 
| 1051 |     } | 
| 1052 | #endif | 
| 1053 |  | 
| 1054 |     if (!d->xml.isEmpty()) { | 
| 1055 |         d->queryId = QQuickXmlQueryEngine::instance(engine: qmlEngine(this))->doQuery(query: d->query, namespaces: d->namespaces, data: d->xml.toUtf8(), roleObjects: &d->roleObjects, keyRoleResultsCache: d->keyRoleResultsCache); | 
| 1056 |         d->notifyQueryStarted(remoteSource: false); | 
| 1057 |  | 
| 1058 |     } else if (d->src.isEmpty()) { | 
| 1059 |         d->queryId = XMLLISTMODEL_CLEAR_ID; | 
| 1060 |         d->notifyQueryStarted(remoteSource: false); | 
| 1061 |         QTimer::singleShot(msec: 0, receiver: this, SLOT(dataCleared())); | 
| 1062 |  | 
| 1063 |     } else if (QQmlFile::isLocalFile(url: d->src)) { | 
| 1064 |         QFile file(QQmlFile::urlToLocalFileOrQrc(d->src)); | 
| 1065 |         QByteArray data = file.open(flags: QIODevice::ReadOnly) ? file.readAll() : QByteArray(); | 
| 1066 |         d->notifyQueryStarted(remoteSource: false); | 
| 1067 |         if (data.isEmpty()) { | 
| 1068 |             d->queryId = XMLLISTMODEL_CLEAR_ID; | 
| 1069 |             QTimer::singleShot(msec: 0, receiver: this, SLOT(dataCleared())); | 
| 1070 |         } else { | 
| 1071 |             d->queryId = QQuickXmlQueryEngine::instance(engine: qmlEngine(this))->doQuery( | 
| 1072 |                         query: d->query, namespaces: d->namespaces, data, roleObjects: &d->roleObjects, keyRoleResultsCache: d->keyRoleResultsCache); | 
| 1073 |         } | 
| 1074 |     } else { | 
| 1075 | #if QT_CONFIG(qml_network) | 
| 1076 |         d->notifyQueryStarted(remoteSource: true); | 
| 1077 |         QNetworkRequest req(d->src); | 
| 1078 |         req.setRawHeader(headerName: "Accept" , value: "application/xml,*/*" ); | 
| 1079 |         d->reply = qmlContext(this)->engine()->networkAccessManager()->get(request: req); | 
| 1080 |         QObject::connect(sender: d->reply, SIGNAL(finished()), receiver: this, SLOT(requestFinished())); | 
| 1081 |         QObject::connect(sender: d->reply, SIGNAL(downloadProgress(qint64,qint64)), | 
| 1082 |                          receiver: this, SLOT(requestProgress(qint64,qint64))); | 
| 1083 | #else | 
| 1084 |         d->queryId = XMLLISTMODEL_CLEAR_ID; | 
| 1085 |         d->notifyQueryStarted(false); | 
| 1086 |         QTimer::singleShot(0, this, SLOT(dataCleared())); | 
| 1087 | #endif | 
| 1088 |     } | 
| 1089 | } | 
| 1090 |  | 
| 1091 | #define XMLLISTMODEL_MAX_REDIRECT 16 | 
| 1092 |  | 
| 1093 | #if QT_CONFIG(qml_network) | 
| 1094 | void QQuickXmlListModel::requestFinished() | 
| 1095 | { | 
| 1096 |     Q_D(QQuickXmlListModel); | 
| 1097 |  | 
| 1098 |     d->redirectCount++; | 
| 1099 |     if (d->redirectCount < XMLLISTMODEL_MAX_REDIRECT) { | 
| 1100 |         QVariant redirect = d->reply->attribute(code: QNetworkRequest::RedirectionTargetAttribute); | 
| 1101 |         if (redirect.isValid()) { | 
| 1102 |             QUrl url = d->reply->url().resolved(relative: redirect.toUrl()); | 
| 1103 |             d->deleteReply(); | 
| 1104 |             setSource(url); | 
| 1105 |             return; | 
| 1106 |         } | 
| 1107 |     } | 
| 1108 |     d->redirectCount = 0; | 
| 1109 |  | 
| 1110 |     if (d->reply->error() != QNetworkReply::NoError) { | 
| 1111 |         d->errorString = d->reply->errorString(); | 
| 1112 |         d->deleteReply(); | 
| 1113 |  | 
| 1114 |         if (d->size > 0) { | 
| 1115 |             beginRemoveRows(parent: QModelIndex(), first: 0, last: d->size - 1); | 
| 1116 |             d->data.clear(); | 
| 1117 |             d->size = 0; | 
| 1118 |             endRemoveRows(); | 
| 1119 |             emit countChanged(); | 
| 1120 |         } | 
| 1121 |  | 
| 1122 |         d->status = Error; | 
| 1123 |         d->queryId = -1; | 
| 1124 |         emit statusChanged(d->status); | 
| 1125 |     } else { | 
| 1126 |         QByteArray data = d->reply->readAll(); | 
| 1127 |         if (data.isEmpty()) { | 
| 1128 |             d->queryId = XMLLISTMODEL_CLEAR_ID; | 
| 1129 |             QTimer::singleShot(msec: 0, receiver: this, SLOT(dataCleared())); | 
| 1130 |         } else { | 
| 1131 |             d->queryId = QQuickXmlQueryEngine::instance(engine: qmlEngine(this))->doQuery(query: d->query, namespaces: d->namespaces, data, roleObjects: &d->roleObjects, keyRoleResultsCache: d->keyRoleResultsCache); | 
| 1132 |         } | 
| 1133 |         d->deleteReply(); | 
| 1134 |  | 
| 1135 |         d->progress = 1.0; | 
| 1136 |         emit progressChanged(progress: d->progress); | 
| 1137 |     } | 
| 1138 | } | 
| 1139 | #endif | 
| 1140 |  | 
| 1141 | void QQuickXmlListModel::requestProgress(qint64 received, qint64 total) | 
| 1142 | { | 
| 1143 |     Q_D(QQuickXmlListModel); | 
| 1144 |     if (d->status == Loading && total > 0) { | 
| 1145 |         d->progress = qreal(received)/total; | 
| 1146 |         emit progressChanged(progress: d->progress); | 
| 1147 |     } | 
| 1148 | } | 
| 1149 |  | 
| 1150 | void QQuickXmlListModel::dataCleared() | 
| 1151 | { | 
| 1152 |     Q_D(QQuickXmlListModel); | 
| 1153 |     QQuickXmlQueryResult r; | 
| 1154 |     r.queryId = XMLLISTMODEL_CLEAR_ID; | 
| 1155 |     r.size = 0; | 
| 1156 |     r.removed << qMakePair(x: 0, y: count()); | 
| 1157 |     r.keyRoleResultsCache = d->keyRoleResultsCache; | 
| 1158 |     queryCompleted(r); | 
| 1159 | } | 
| 1160 |  | 
| 1161 | void QQuickXmlListModel::queryError(void* object, const QString& error) | 
| 1162 | { | 
| 1163 |     // Be extra careful, object may no longer exist, it's just an ID. | 
| 1164 |     Q_D(QQuickXmlListModel); | 
| 1165 |     for (int i=0; i<d->roleObjects.count(); i++) { | 
| 1166 |         if (d->roleObjects.at(i) == static_cast<QQuickXmlListModelRole*>(object)) { | 
| 1167 |             qmlWarning(me: d->roleObjects.at(i)) << QQuickXmlListModel::tr(s: "invalid query: \"%1\"" ).arg(a: error); | 
| 1168 |             return; | 
| 1169 |         } | 
| 1170 |     } | 
| 1171 |     qmlWarning(me: this) << QQuickXmlListModel::tr(s: "invalid query: \"%1\"" ).arg(a: error); | 
| 1172 | } | 
| 1173 |  | 
| 1174 | void QQuickXmlListModel::queryCompleted(const QQuickXmlQueryResult &result) | 
| 1175 | { | 
| 1176 |     Q_D(QQuickXmlListModel); | 
| 1177 |     if (result.queryId != d->queryId) | 
| 1178 |         return; | 
| 1179 |  | 
| 1180 |     int origCount = d->size; | 
| 1181 |     bool sizeChanged = result.size != d->size; | 
| 1182 |  | 
| 1183 |     d->keyRoleResultsCache = result.keyRoleResultsCache; | 
| 1184 |     if (d->src.isEmpty() && d->xml.isEmpty()) | 
| 1185 |         d->status = Null; | 
| 1186 |     else | 
| 1187 |         d->status = Ready; | 
| 1188 |     d->errorString.clear(); | 
| 1189 |     d->queryId = -1; | 
| 1190 |  | 
| 1191 |     bool hasKeys = false; | 
| 1192 |     for (int i=0; i<d->roleObjects.count(); i++) { | 
| 1193 |         if (d->roleObjects[i]->isKey()) { | 
| 1194 |             hasKeys = true; | 
| 1195 |             break; | 
| 1196 |         } | 
| 1197 |     } | 
| 1198 |     if (!hasKeys) { | 
| 1199 |         if (origCount > 0) { | 
| 1200 |             beginRemoveRows(parent: QModelIndex(), first: 0, last: origCount - 1); | 
| 1201 |             endRemoveRows(); | 
| 1202 |         } | 
| 1203 |         d->size = result.size; | 
| 1204 |         d->data = result.data; | 
| 1205 |         if (d->size > 0) { | 
| 1206 |             beginInsertRows(parent: QModelIndex(), first: 0, last: d->size - 1); | 
| 1207 |             endInsertRows(); | 
| 1208 |         } | 
| 1209 |     } else { | 
| 1210 |         for (int i=0; i<result.removed.count(); i++) { | 
| 1211 |             const int index = result.removed[i].first; | 
| 1212 |             const int count = result.removed[i].second; | 
| 1213 |             if (count > 0) { | 
| 1214 |                 beginRemoveRows(parent: QModelIndex(), first: index, last: index + count - 1); | 
| 1215 |                 endRemoveRows(); | 
| 1216 |             } | 
| 1217 |         } | 
| 1218 |         d->size = result.size; | 
| 1219 |         d->data = result.data; | 
| 1220 |         for (int i=0; i<result.inserted.count(); i++) { | 
| 1221 |             const int index = result.inserted[i].first; | 
| 1222 |             const int count = result.inserted[i].second; | 
| 1223 |             if (count > 0) { | 
| 1224 |                 beginInsertRows(parent: QModelIndex(), first: index, last: index + count - 1); | 
| 1225 |                 endInsertRows(); | 
| 1226 |             } | 
| 1227 |         } | 
| 1228 |     } | 
| 1229 |     if (sizeChanged) | 
| 1230 |         emit countChanged(); | 
| 1231 |  | 
| 1232 |     emit statusChanged(d->status); | 
| 1233 | } | 
| 1234 |  | 
| 1235 | QT_END_NAMESPACE | 
| 1236 |  | 
| 1237 | #include <qqmlxmllistmodel.moc> | 
| 1238 |  |