Initial import from the monolithic Qt.
[profile/ivi/qtdeclarative.git] / src / declarative / util / qdeclarativexmllistmodel.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
6 **
7 ** This file is part of the QtDeclarative module of the Qt Toolkit.
8 **
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** No Commercial Usage
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
14 ** this package.
15 **
16 ** GNU Lesser General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU Lesser
18 ** General Public License version 2.1 as published by the Free Software
19 ** Foundation and appearing in the file LICENSE.LGPL included in the
20 ** packaging of this file.  Please review the following information to
21 ** ensure the GNU Lesser General Public License version 2.1 requirements
22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23 **
24 ** In addition, as a special exception, Nokia gives you certain additional
25 ** rights.  These rights are described in the Nokia Qt LGPL Exception
26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27 **
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
30 **
31 **
32 **
33 **
34 **
35 **
36 **
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 class QDeclarativeXmlQuery : public QObject
151 {
152     Q_OBJECT
153 public:
154     QDeclarativeXmlQuery(QObject *parent=0)
155         : QObject(parent), m_queryIds(XMLLISTMODEL_CLEAR_ID + 1) {
156         qRegisterMetaType<QDeclarativeXmlQueryResult>("QDeclarativeXmlQueryResult");
157         moveToThread(&m_thread);
158         m_thread.start(QThread::IdlePriority);
159     }
160
161     ~QDeclarativeXmlQuery() {
162         if(m_thread.isRunning()) {
163             m_thread.quit();
164             m_thread.wait();
165         }
166     }
167
168     void abort(int id) {
169         QMutexLocker ml(&m_mutex);
170         if (id != -1) {
171             m_jobs.remove(id);
172         }
173     }
174
175     int doQuery(QString query, QString namespaces, QByteArray data, QList<QDeclarativeXmlListModelRole *>* roleObjects, QStringList keyRoleResultsCache) {
176         {
177             QMutexLocker m1(&m_mutex);
178             m_queryIds.ref();
179             if (m_queryIds <= 0)
180                 m_queryIds = 1;
181         }
182
183         XmlQueryJob job;
184         job.queryId = m_queryIds;
185         job.data = data;
186         job.query = QLatin1String("doc($src)") + query;
187         job.namespaces = namespaces;
188         job.keyRoleResultsCache = keyRoleResultsCache;
189
190         for (int i=0; i<roleObjects->count(); i++) {
191             if (!roleObjects->at(i)->isValid()) {
192                 job.roleQueries << QString();
193                 continue;
194             }
195             job.roleQueries << roleObjects->at(i)->query();
196             job.roleQueryErrorId << static_cast<void*>(roleObjects->at(i));
197             if (roleObjects->at(i)->isKey())
198                 job.keyRoleQueries << job.roleQueries.last();
199         }
200
201         {
202             QMutexLocker ml(&m_mutex);
203             m_jobs.insert(m_queryIds, job);
204         }
205
206         QMetaObject::invokeMethod(this, "processQuery", Qt::QueuedConnection, Q_ARG(int, job.queryId));
207         return job.queryId;
208     }
209
210 private slots:
211     void processQuery(int queryId) {
212         XmlQueryJob job;
213
214         {
215             QMutexLocker ml(&m_mutex);
216             if (!m_jobs.contains(queryId))
217                 return;
218             job = m_jobs.value(queryId);
219         }
220
221         QDeclarativeXmlQueryResult result;
222         result.queryId = job.queryId;
223         doQueryJob(&job, &result);
224         doSubQueryJob(&job, &result);
225
226         {
227             QMutexLocker ml(&m_mutex);
228             if (m_jobs.contains(queryId)) {
229                 emit queryCompleted(result);
230                 m_jobs.remove(queryId);
231             }
232         }
233     }
234
235 Q_SIGNALS:
236     void queryCompleted(const QDeclarativeXmlQueryResult &);
237     void error(void*, const QString&);
238
239 protected:
240
241
242 private:
243     void doQueryJob(XmlQueryJob *job, QDeclarativeXmlQueryResult *currentResult);
244     void doSubQueryJob(XmlQueryJob *job, QDeclarativeXmlQueryResult *currentResult);
245     void getValuesOfKeyRoles(const XmlQueryJob& currentJob, QStringList *values, QXmlQuery *query) const;
246     void addIndexToRangeList(QList<QDeclarativeXmlListRange> *ranges, int index) const;
247
248 private:
249     QMutex m_mutex;
250     QThread m_thread;
251     QMap<int, XmlQueryJob> m_jobs;
252     QAtomicInt m_queryIds;
253 };
254
255 Q_GLOBAL_STATIC(QDeclarativeXmlQuery, globalXmlQuery)
256
257 void QDeclarativeXmlQuery::doQueryJob(XmlQueryJob *currentJob, QDeclarativeXmlQueryResult *currentResult)
258 {
259     Q_ASSERT(currentJob->queryId != -1);
260
261     QString r;
262     QXmlQuery query;
263     QBuffer buffer(&currentJob->data);
264     buffer.open(QIODevice::ReadOnly);
265     query.bindVariable(QLatin1String("src"), &buffer);
266     query.setQuery(currentJob->namespaces + currentJob->query);
267     query.evaluateTo(&r);
268
269     //always need a single root element
270     QByteArray xml = "<dummy:items xmlns:dummy=\"http://qtsotware.com/dummy\">\n" + r.toUtf8() + "</dummy:items>";
271     QBuffer b(&xml);
272     b.open(QIODevice::ReadOnly);
273
274     QString namespaces = QLatin1String("declare namespace dummy=\"http://qtsotware.com/dummy\";\n") + currentJob->namespaces;
275     QString prefix = QLatin1String("doc($inputDocument)/dummy:items") +
276                      currentJob->query.mid(currentJob->query.lastIndexOf(QLatin1Char('/')));
277
278     //figure out how many items we are dealing with
279     int count = -1;
280     {
281         QXmlResultItems result;
282         QXmlQuery countquery;
283         countquery.bindVariable(QLatin1String("inputDocument"), &b);
284         countquery.setQuery(namespaces + QLatin1String("count(") + prefix + QLatin1Char(')'));
285         countquery.evaluateTo(&result);
286         QXmlItem item(result.next());
287         if (item.isAtomicValue())
288             count = item.toAtomicValue().toInt();
289     }
290
291     currentJob->data = xml;
292     currentJob->prefix = namespaces + prefix + QLatin1Char('/');
293     currentResult->size = (count > 0 ? count : 0);
294 }
295
296 void QDeclarativeXmlQuery::getValuesOfKeyRoles(const XmlQueryJob& currentJob, QStringList *values, QXmlQuery *query) const
297 {
298     const QStringList &keysQueries = currentJob.keyRoleQueries;
299     QString keysQuery;
300     if (keysQueries.count() == 1)
301         keysQuery = currentJob.prefix + keysQueries[0];
302     else if (keysQueries.count() > 1)
303         keysQuery = currentJob.prefix + QLatin1String("concat(") + keysQueries.join(QLatin1String(",")) + QLatin1String(")");
304
305     if (!keysQuery.isEmpty()) {
306         query->setQuery(keysQuery);
307         QXmlResultItems resultItems;
308         query->evaluateTo(&resultItems);
309         QXmlItem item(resultItems.next());
310         while (!item.isNull()) {
311             values->append(item.toAtomicValue().toString());
312             item = resultItems.next();
313         }
314     }
315 }
316
317 void QDeclarativeXmlQuery::addIndexToRangeList(QList<QDeclarativeXmlListRange> *ranges, int index) const {
318     if (ranges->isEmpty())
319         ranges->append(qMakePair(index, 1));
320     else if (ranges->last().first + ranges->last().second == index)
321         ranges->last().second += 1;
322     else
323         ranges->append(qMakePair(index, 1));
324 }
325
326 void QDeclarativeXmlQuery::doSubQueryJob(XmlQueryJob *currentJob, QDeclarativeXmlQueryResult *currentResult)
327 {
328     Q_ASSERT(currentJob->queryId != -1);
329
330     QBuffer b(&currentJob->data);
331     b.open(QIODevice::ReadOnly);
332
333     QXmlQuery subquery;
334     subquery.bindVariable(QLatin1String("inputDocument"), &b);
335
336     QStringList keyRoleResults;
337     getValuesOfKeyRoles(*currentJob, &keyRoleResults, &subquery);
338
339     // See if any values of key roles have been inserted or removed.
340
341     if (currentJob->keyRoleResultsCache.isEmpty()) {
342         currentResult->inserted << qMakePair(0, currentResult->size);
343     } else {
344         if (keyRoleResults != currentJob->keyRoleResultsCache) {
345             QStringList temp;
346             for (int i=0; i<currentJob->keyRoleResultsCache.count(); i++) {
347                 if (!keyRoleResults.contains(currentJob->keyRoleResultsCache[i]))
348                     addIndexToRangeList(&currentResult->removed, i);
349                 else 
350                     temp << currentJob->keyRoleResultsCache[i];
351             }
352
353             for (int i=0; i<keyRoleResults.count(); i++) {
354                 if (temp.count() == i || keyRoleResults[i] != temp[i]) {
355                     temp.insert(i, keyRoleResults[i]);
356                     addIndexToRangeList(&currentResult->inserted, i);
357                 }
358             }
359         }
360     }
361     currentResult->keyRoleResultsCache = keyRoleResults;
362
363     // Get the new values for each role.
364     //### we might be able to condense even further (query for everything in one go)
365     const QStringList &queries = currentJob->roleQueries;
366     for (int i = 0; i < queries.size(); ++i) {
367         QList<QVariant> resultList;
368         if (!queries[i].isEmpty()) {
369             subquery.setQuery(currentJob->prefix + QLatin1String("(let $v := string(") + queries[i] + QLatin1String(") return if ($v) then ") + queries[i] + QLatin1String(" else \"\")"));
370             if (subquery.isValid()) {
371                 QXmlResultItems resultItems;
372                 subquery.evaluateTo(&resultItems);
373                 QXmlItem item(resultItems.next());
374                 while (!item.isNull()) {
375                     resultList << item.toAtomicValue(); //### we used to trim strings
376                     item = resultItems.next();
377                 }
378             } else {
379                 emit error(currentJob->roleQueryErrorId.at(i), queries[i]);
380             }
381         }
382         //### should warn here if things have gone wrong.
383         while (resultList.count() < currentResult->size)
384             resultList << QVariant();
385         currentResult->data << resultList;
386         b.seek(0);
387     }
388
389     //this method is much slower, but works better for incremental loading
390     /*for (int j = 0; j < m_size; ++j) {
391         QList<QVariant> resultList;
392         for (int i = 0; i < m_roleObjects->size(); ++i) {
393             QDeclarativeXmlListModelRole *role = m_roleObjects->at(i);
394             subquery.setQuery(m_prefix.arg(j+1) + role->query());
395             if (role->isStringList()) {
396                 QStringList data;
397                 subquery.evaluateTo(&data);
398                 resultList << QVariant(data);
399                 //qDebug() << data;
400             } else {
401                 QString s;
402                 subquery.evaluateTo(&s);
403                 if (role->isCData()) {
404                     //un-escape
405                     s.replace(QLatin1String("&lt;"), QLatin1String("<"));
406                     s.replace(QLatin1String("&gt;"), QLatin1String(">"));
407                     s.replace(QLatin1String("&amp;"), QLatin1String("&"));
408                 }
409                 resultList << s.trimmed();
410                 //qDebug() << s;
411             }
412             b.seek(0);
413         }
414         m_modelData << resultList;
415     }*/
416 }
417
418 class QDeclarativeXmlListModelPrivate : public QObjectPrivate
419 {
420     Q_DECLARE_PUBLIC(QDeclarativeXmlListModel)
421 public:
422     QDeclarativeXmlListModelPrivate()
423         : isComponentComplete(true), size(-1), highestRole(Qt::UserRole)
424         , reply(0), status(QDeclarativeXmlListModel::Null), progress(0.0)
425         , queryId(-1), roleObjects(), redirectCount(0) {}
426
427
428     void notifyQueryStarted(bool remoteSource) {
429         Q_Q(QDeclarativeXmlListModel);
430         progress = remoteSource ? 0.0 : 1.0;
431         status = QDeclarativeXmlListModel::Loading;
432         errorString.clear();
433         emit q->progressChanged(progress);
434         emit q->statusChanged(status);
435     }
436
437     bool isComponentComplete;
438     QUrl src;
439     QString xml;
440     QString query;
441     QString namespaces;
442     int size;
443     QList<int> roles;
444     QStringList roleNames;
445     int highestRole;
446     QNetworkReply *reply;
447     QDeclarativeXmlListModel::Status status;
448     QString errorString;
449     qreal progress;
450     int queryId;
451     QStringList keyRoleResultsCache;
452     QList<QDeclarativeXmlListModelRole *> roleObjects;
453     static void append_role(QDeclarativeListProperty<QDeclarativeXmlListModelRole> *list, QDeclarativeXmlListModelRole *role);
454     static void clear_role(QDeclarativeListProperty<QDeclarativeXmlListModelRole> *list);
455     QList<QList<QVariant> > data;
456     int redirectCount;
457 };
458
459
460 void QDeclarativeXmlListModelPrivate::append_role(QDeclarativeListProperty<QDeclarativeXmlListModelRole> *list, QDeclarativeXmlListModelRole *role)
461 {
462     QDeclarativeXmlListModel *_this = qobject_cast<QDeclarativeXmlListModel *>(list->object);
463     if (_this && role) {
464         int i = _this->d_func()->roleObjects.count();
465         _this->d_func()->roleObjects.append(role);
466         if (_this->d_func()->roleNames.contains(role->name())) {
467             qmlInfo(role) << QObject::tr("\"%1\" duplicates a previous role name and will be disabled.").arg(role->name());
468             return;
469         }
470         _this->d_func()->roles.insert(i, _this->d_func()->highestRole);
471         _this->d_func()->roleNames.insert(i, role->name());
472         ++_this->d_func()->highestRole;
473     }
474 }
475
476 //### clear needs to invalidate any cached data (in data table) as well
477 //    (and the model should emit the appropriate signals)
478 void QDeclarativeXmlListModelPrivate::clear_role(QDeclarativeListProperty<QDeclarativeXmlListModelRole> *list)
479 {
480     QDeclarativeXmlListModel *_this = static_cast<QDeclarativeXmlListModel *>(list->object);
481     _this->d_func()->roles.clear();
482     _this->d_func()->roleNames.clear();
483     _this->d_func()->roleObjects.clear();
484 }
485
486 /*!
487     \qmlclass XmlListModel QDeclarativeXmlListModel
488     \ingroup qml-working-with-data
489   \since 4.7
490     \brief The XmlListModel element is used to specify a read-only model using XPath expressions.
491
492     XmlListModel is used to create a read-only model from XML data. It can be used as a data source
493     for view elements (such as ListView, PathView, GridView) and other elements that interact with model
494     data (such as \l Repeater).
495
496     For example, if there is a XML document at http://www.mysite.com/feed.xml like this:
497
498     \code
499     <?xml version="1.0" encoding="utf-8"?>
500     <rss version="2.0">
501         ...
502         <channel>
503             <item>
504                 <title>A blog post</title>
505                 <pubDate>Sat, 07 Sep 2010 10:00:01 GMT</pubDate>
506             </item>
507             <item>
508                 <title>Another blog post</title>
509                 <pubDate>Sat, 07 Sep 2010 15:35:01 GMT</pubDate>
510             </item>
511         </channel>
512     </rss>
513     \endcode
514
515     A XmlListModel could create a model from this data, like this:
516
517     \qml
518     import QtQuick 1.0
519
520     XmlListModel {
521         id: xmlModel
522         source: "http://www.mysite.com/feed.xml"
523         query: "/rss/channel/item"
524
525         XmlRole { name: "title"; query: "title/string()" }
526         XmlRole { name: "pubDate"; query: "pubDate/string()" }
527     }
528     \endqml
529
530     The \l {XmlListModel::query}{query} value of "/rss/channel/item" specifies that the XmlListModel should generate
531     a model item for each \c <item> in the XML document. 
532     
533     The XmlRole objects define the
534     model item attributes. Here, each model item will have \c title and \c pubDate 
535     attributes that match the \c title and \c pubDate values of its corresponding \c <item>.
536     (See \l XmlRole::query for more examples of valid XPath expressions for XmlRole.)
537
538     The model could be used in a ListView, like this:
539
540     \qml
541     ListView {
542         width: 180; height: 300
543         model: xmlModel
544         delegate: Text { text: title + ": " + pubDate }
545     }
546     \endqml
547
548     \image qml-xmllistmodel-example.png
549
550     The XmlListModel data is loaded asynchronously, and \l status
551     is set to \c XmlListModel.Ready when loading is complete.
552     Note this means when XmlListModel is used for a view, the view is not
553     populated until the model is loaded.
554
555
556     \section2 Using key XML roles
557
558     You can define certain roles as "keys" so that when reload() is called,
559     the model will only add and refresh data that contains new values for
560     these keys.
561
562     For example, if above role for "pubDate" was defined like this instead:
563
564     \qml
565         XmlRole { name: "pubDate"; query: "pubDate/string()"; isKey: true }
566     \endqml
567
568     Then when reload() is called, the model will only add and reload
569     items with a "pubDate" value that is not already 
570     present in the model. 
571
572     This is useful when displaying the contents of XML documents that
573     are incrementally updated (such as RSS feeds) to avoid repainting the
574     entire contents of a model in a view.
575
576     If multiple key roles are specified, the model only adds and reload items
577     with a combined value of all key roles that is not already present in
578     the model.
579
580     \sa {RSS News}
581 */
582
583 QDeclarativeXmlListModel::QDeclarativeXmlListModel(QObject *parent)
584     : QListModelInterface(*(new QDeclarativeXmlListModelPrivate), parent)
585 {
586     connect(globalXmlQuery(), SIGNAL(queryCompleted(QDeclarativeXmlQueryResult)),
587             this, SLOT(queryCompleted(QDeclarativeXmlQueryResult)));
588     connect(globalXmlQuery(), SIGNAL(error(void*,QString)),
589             this, SLOT(queryError(void*,QString)));
590 }
591
592 QDeclarativeXmlListModel::~QDeclarativeXmlListModel()
593 {
594 }
595
596 /*!
597     \qmlproperty list<XmlRole> XmlListModel::roles
598
599     The roles to make available for this model.
600 */
601 QDeclarativeListProperty<QDeclarativeXmlListModelRole> QDeclarativeXmlListModel::roleObjects()
602 {
603     Q_D(QDeclarativeXmlListModel);
604     QDeclarativeListProperty<QDeclarativeXmlListModelRole> list(this, d->roleObjects);
605     list.append = &QDeclarativeXmlListModelPrivate::append_role;
606     list.clear = &QDeclarativeXmlListModelPrivate::clear_role;
607     return list;
608 }
609
610 QHash<int,QVariant> QDeclarativeXmlListModel::data(int index, const QList<int> &roles) const
611 {
612     Q_D(const QDeclarativeXmlListModel);
613     QHash<int, QVariant> rv;
614     for (int i = 0; i < roles.size(); ++i) {
615         int role = roles.at(i);
616         int roleIndex = d->roles.indexOf(role);
617         rv.insert(role, roleIndex == -1 ? QVariant() : d->data.value(roleIndex).value(index));
618     }
619     return rv;
620 }
621
622 QVariant QDeclarativeXmlListModel::data(int index, int role) const
623 {
624     Q_D(const QDeclarativeXmlListModel);
625     int roleIndex = d->roles.indexOf(role);
626     return (roleIndex == -1) ? QVariant() : d->data.value(roleIndex).value(index);
627 }
628
629 /*!
630     \qmlproperty int XmlListModel::count
631     The number of data entries in the model.
632 */
633 int QDeclarativeXmlListModel::count() const
634 {
635     Q_D(const QDeclarativeXmlListModel);
636     return d->size;
637 }
638
639 QList<int> QDeclarativeXmlListModel::roles() const
640 {
641     Q_D(const QDeclarativeXmlListModel);
642     return d->roles;
643 }
644
645 QString QDeclarativeXmlListModel::toString(int role) const
646 {
647     Q_D(const QDeclarativeXmlListModel);
648     int index = d->roles.indexOf(role);
649     if (index == -1)
650         return QString();
651     return d->roleNames.at(index);
652 }
653
654 /*!
655     \qmlproperty url XmlListModel::source
656     The location of the XML data source.
657
658     If both \c source and \l xml are set, \l xml is used.
659 */
660 QUrl QDeclarativeXmlListModel::source() const
661 {
662     Q_D(const QDeclarativeXmlListModel);
663     return d->src;
664 }
665
666 void QDeclarativeXmlListModel::setSource(const QUrl &src)
667 {
668     Q_D(QDeclarativeXmlListModel);
669     if (d->src != src) {
670         d->src = src;
671         if (d->xml.isEmpty())   // src is only used if d->xml is not set
672             reload();
673         emit sourceChanged();
674    }
675 }
676
677 /*!
678     \qmlproperty string XmlListModel::xml
679     This property holds the XML data for this model, if set.
680
681     The text is assumed to be UTF-8 encoded.
682
683     If both \l source and \c xml are set, \c xml is used.
684 */
685 QString QDeclarativeXmlListModel::xml() const
686 {
687     Q_D(const QDeclarativeXmlListModel);
688     return d->xml;
689 }
690
691 void QDeclarativeXmlListModel::setXml(const QString &xml)
692 {
693     Q_D(QDeclarativeXmlListModel);
694     if (d->xml != xml) {
695         d->xml = xml;
696         reload();
697         emit xmlChanged();
698     }
699 }
700
701 /*!
702     \qmlproperty string XmlListModel::query
703     An absolute XPath query representing the base query for creating model items
704     from this model's XmlRole objects. The query should start with '/' or '//'.
705 */
706 QString QDeclarativeXmlListModel::query() const
707 {
708     Q_D(const QDeclarativeXmlListModel);
709     return d->query;
710 }
711
712 void QDeclarativeXmlListModel::setQuery(const QString &query)
713 {
714     Q_D(QDeclarativeXmlListModel);
715     if (!query.startsWith(QLatin1Char('/'))) {
716         qmlInfo(this) << QCoreApplication::translate("QDeclarativeXmlRoleList", "An XmlListModel query must start with '/' or \"//\"");
717         return;
718     }
719
720     if (d->query != query) {
721         d->query = query;
722         reload();
723         emit queryChanged();
724     }
725 }
726
727 /*!
728     \qmlproperty string XmlListModel::namespaceDeclarations
729     The namespace declarations to be used in the XPath queries.
730
731     The namespaces should be declared as in XQuery. For example, if a requested document
732     at http://mysite.com/feed.xml uses the namespace "http://www.w3.org/2005/Atom",
733     this can be declared as the default namespace:
734
735     \qml
736     XmlListModel {
737         source: "http://mysite.com/feed.xml"
738         query: "/feed/entry"
739         namespaceDeclarations: "declare default element namespace 'http://www.w3.org/2005/Atom';"
740
741         XmlRole { name: "title"; query: "title/string()" }
742     }
743     \endqml
744 */
745 QString QDeclarativeXmlListModel::namespaceDeclarations() const
746 {
747     Q_D(const QDeclarativeXmlListModel);
748     return d->namespaces;
749 }
750
751 void QDeclarativeXmlListModel::setNamespaceDeclarations(const QString &declarations)
752 {
753     Q_D(QDeclarativeXmlListModel);
754     if (d->namespaces != declarations) {
755         d->namespaces = declarations;
756         reload();
757         emit namespaceDeclarationsChanged();
758     }
759 }
760
761 /*!
762     \qmlmethod object XmlListModel::get(int index)
763
764     Returns the item at \a index in the model.
765
766     For example, for a model like this:
767
768     \qml
769     XmlListModel {
770         id: model
771         source: "http://mysite.com/feed.xml"
772         query: "/feed/entry"
773         XmlRole { name: "title"; query: "title/string()" }
774     }
775     \endqml
776
777     This will access the \c title value for the first item in the model:
778
779     \js
780     var title = model.get(0).title;
781     \endjs
782 */
783 QScriptValue QDeclarativeXmlListModel::get(int index) const
784 {
785     Q_D(const QDeclarativeXmlListModel);
786
787     QScriptEngine *sengine = QDeclarativeEnginePrivate::getScriptEngine(qmlContext(this)->engine());
788     if (index < 0 || index >= count())
789         return sengine->undefinedValue();
790
791     QScriptValue sv = sengine->newObject();
792     for (int i=0; i<d->roleObjects.count(); i++) 
793         sv.setProperty(d->roleObjects[i]->name(), sengine->toScriptValue(d->data.value(i).value(index)));
794     return sv;    
795 }
796
797 /*!
798     \qmlproperty enumeration XmlListModel::status
799     Specifies the model loading status, which can be one of the following:
800
801     \list
802     \o XmlListModel.Null - No XML data has been set for this model.
803     \o XmlListModel.Ready - The XML data has been loaded into the model.
804     \o XmlListModel.Loading - The model is in the process of reading and loading XML data.
805     \o XmlListModel.Error - An error occurred while the model was loading. See errorString() for details
806        about the error.
807     \endlist
808
809     \sa progress
810
811 */
812 QDeclarativeXmlListModel::Status QDeclarativeXmlListModel::status() const
813 {
814     Q_D(const QDeclarativeXmlListModel);
815     return d->status;
816 }
817
818 /*!
819     \qmlproperty real XmlListModel::progress
820
821     This indicates the current progress of the downloading of the XML data
822     source. This value ranges from 0.0 (no data downloaded) to
823     1.0 (all data downloaded). If the XML data is not from a remote source,
824     the progress becomes 1.0 as soon as the data is read.
825
826     Note that when the progress is 1.0, the XML data has been downloaded, but 
827     it is yet to be loaded into the model at this point. Use the status
828     property to find out when the XML data has been read and loaded into
829     the model.
830
831     \sa status, source
832 */
833 qreal QDeclarativeXmlListModel::progress() const
834 {
835     Q_D(const QDeclarativeXmlListModel);
836     return d->progress;
837 }
838
839 /*!
840     \qmlmethod void XmlListModel::errorString()
841
842     Returns a string description of the last error that occurred
843     if \l status is XmlListModel::Error.
844 */
845 QString QDeclarativeXmlListModel::errorString() const
846 {
847     Q_D(const QDeclarativeXmlListModel);
848     return d->errorString;
849 }
850
851 void QDeclarativeXmlListModel::classBegin()
852 {
853     Q_D(QDeclarativeXmlListModel);
854     d->isComponentComplete = false;
855 }
856
857 void QDeclarativeXmlListModel::componentComplete()
858 {
859     Q_D(QDeclarativeXmlListModel);
860     d->isComponentComplete = true;
861     reload();
862 }
863
864 /*!
865     \qmlmethod XmlListModel::reload()
866
867     Reloads the model.
868     
869     If no key roles have been specified, all existing model
870     data is removed, and the model is rebuilt from scratch.
871
872     Otherwise, items are only added if the model does not already
873     contain items with matching key role values.
874     
875     \sa {Using key XML roles}, XmlRole::isKey
876 */
877 void QDeclarativeXmlListModel::reload()
878 {
879     Q_D(QDeclarativeXmlListModel);
880
881     if (!d->isComponentComplete)
882         return;
883
884     globalXmlQuery()->abort(d->queryId);
885     d->queryId = -1;
886
887     if (d->size < 0)
888         d->size = 0;
889
890     if (d->reply) {
891         d->reply->abort();
892         if (d->reply) {
893             // abort will generally have already done this (and more)
894             d->reply->deleteLater();
895             d->reply = 0;
896         }
897     }
898
899     if (!d->xml.isEmpty()) {
900         d->queryId = globalXmlQuery()->doQuery(d->query, d->namespaces, d->xml.toUtf8(), &d->roleObjects, d->keyRoleResultsCache);
901         d->notifyQueryStarted(false);
902
903     } else if (d->src.isEmpty()) {
904         d->queryId = XMLLISTMODEL_CLEAR_ID;
905         d->notifyQueryStarted(false);
906         QTimer::singleShot(0, this, SLOT(dataCleared()));
907
908     } else {
909         d->notifyQueryStarted(true);
910         QNetworkRequest req(d->src);
911         req.setRawHeader("Accept", "application/xml,*/*");
912         d->reply = qmlContext(this)->engine()->networkAccessManager()->get(req);
913         QObject::connect(d->reply, SIGNAL(finished()), this, SLOT(requestFinished()));
914         QObject::connect(d->reply, SIGNAL(downloadProgress(qint64,qint64)),
915                          this, SLOT(requestProgress(qint64,qint64)));
916     }
917 }
918
919 #define XMLLISTMODEL_MAX_REDIRECT 16
920
921 void QDeclarativeXmlListModel::requestFinished()
922 {
923     Q_D(QDeclarativeXmlListModel);
924
925     d->redirectCount++;
926     if (d->redirectCount < XMLLISTMODEL_MAX_REDIRECT) {
927         QVariant redirect = d->reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
928         if (redirect.isValid()) {
929             QUrl url = d->reply->url().resolved(redirect.toUrl());
930             d->reply->deleteLater();
931             d->reply = 0;
932             setSource(url);
933             return;
934         }
935     }
936     d->redirectCount = 0;
937
938     if (d->reply->error() != QNetworkReply::NoError) {
939         d->errorString = d->reply->errorString();
940         disconnect(d->reply, 0, this, 0);
941         d->reply->deleteLater();
942         d->reply = 0;
943
944         int count = this->count();
945         d->data.clear();
946         d->size = 0;
947         if (count > 0) {
948             emit itemsRemoved(0, count);
949             emit countChanged();
950         }
951
952         d->status = Error;
953         d->queryId = -1;
954         emit statusChanged(d->status);
955     } else {
956         QByteArray data = d->reply->readAll();
957         if (data.isEmpty()) {
958             d->queryId = XMLLISTMODEL_CLEAR_ID;
959             QTimer::singleShot(0, this, SLOT(dataCleared()));
960         } else {
961             d->queryId = globalXmlQuery()->doQuery(d->query, d->namespaces, data, &d->roleObjects, d->keyRoleResultsCache);
962         }
963         disconnect(d->reply, 0, this, 0);
964         d->reply->deleteLater();
965         d->reply = 0;
966
967         d->progress = 1.0;
968         emit progressChanged(d->progress);
969     }
970 }
971
972 void QDeclarativeXmlListModel::requestProgress(qint64 received, qint64 total)
973 {
974     Q_D(QDeclarativeXmlListModel);
975     if (d->status == Loading && total > 0) {
976         d->progress = qreal(received)/total;
977         emit progressChanged(d->progress);
978     }
979 }
980
981 void QDeclarativeXmlListModel::dataCleared()
982 {
983     Q_D(QDeclarativeXmlListModel);
984     QDeclarativeXmlQueryResult r;
985     r.queryId = XMLLISTMODEL_CLEAR_ID;
986     r.size = 0;
987     r.removed << qMakePair(0, count());
988     r.keyRoleResultsCache = d->keyRoleResultsCache;
989     queryCompleted(r);
990 }
991
992 void QDeclarativeXmlListModel::queryError(void* object, const QString& error)
993 {
994     // Be extra careful, object may no longer exist, it's just an ID.
995     Q_D(QDeclarativeXmlListModel);
996     for (int i=0; i<d->roleObjects.count(); i++) {
997         if (d->roleObjects.at(i) == static_cast<QDeclarativeXmlListModelRole*>(object)) {
998             qmlInfo(d->roleObjects.at(i)) << QObject::tr("invalid query: \"%1\"").arg(error);
999             return;
1000         }
1001     }
1002     qmlInfo(this) << QObject::tr("invalid query: \"%1\"").arg(error);
1003 }
1004
1005 void QDeclarativeXmlListModel::queryCompleted(const QDeclarativeXmlQueryResult &result)
1006 {
1007     Q_D(QDeclarativeXmlListModel);
1008     if (result.queryId != d->queryId)
1009         return;
1010
1011     int origCount = d->size;
1012     bool sizeChanged = result.size != d->size;
1013
1014     d->size = result.size;
1015     d->data = result.data;
1016     d->keyRoleResultsCache = result.keyRoleResultsCache;
1017     d->status = Ready;
1018     d->errorString.clear();
1019     d->queryId = -1;
1020
1021     bool hasKeys = false;
1022     for (int i=0; i<d->roleObjects.count(); i++) {
1023         if (d->roleObjects[i]->isKey()) {
1024             hasKeys = true;
1025             break;
1026         }
1027     }
1028     if (!hasKeys) {
1029         if (!(origCount == 0 && d->size == 0)) {
1030             emit itemsRemoved(0, origCount);
1031             emit itemsInserted(0, d->size);
1032             emit countChanged();
1033         }
1034
1035     } else {
1036         for (int i=0; i<result.removed.count(); i++)
1037             emit itemsRemoved(result.removed[i].first, result.removed[i].second);
1038         for (int i=0; i<result.inserted.count(); i++)
1039             emit itemsInserted(result.inserted[i].first, result.inserted[i].second);
1040
1041         if (sizeChanged)
1042             emit countChanged();
1043     }
1044
1045     emit statusChanged(d->status);
1046 }
1047
1048 QT_END_NAMESPACE
1049
1050 #include <qdeclarativexmllistmodel.moc>