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