source: trunk/src/declarative/util/qdeclarativexmllistmodel.cpp

Last change on this file was 846, checked in by Dmitry A. Kuminov, 14 years ago

trunk: Merged in qt 4.7.2 sources from branches/vendor/nokia/qt.

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