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(&currentJob);
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(&currentJob->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(&currentJob->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(&currentResult->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(&currentResult->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("&lt;"), QLatin1String("<"));
509                     s.replace(QLatin1String("&gt;"), QLatin1String(">"));
510                     s.replace(QLatin1String("&amp;"), 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