| 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 | |