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