src/declarative/util/qdeclarativexmllistmodel.cpp
changeset 30 5dc02b23752f
child 33 3e2da88830cd
equal deleted inserted replaced
29:b72c6db6890b 30:5dc02b23752f
       
     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("&lt;"), QLatin1String("<"));
       
   396                     s.replace(QLatin1String("&gt;"), QLatin1String(">"));
       
   397                     s.replace(QLatin1String("&amp;"), 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>