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