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