6#include <QtQml/qqmlcontext.h>
7#include <QtQml/qqmlengine.h>
8#include <QtQml/qqmlinfo.h>
9#include <QtQml/qqmlfile.h>
11#include <QtCore/qcoreapplication.h>
12#include <QtCore/qfile.h>
13#include <QtCore/qfuturewatcher.h>
14#include <QtCore/qtimer.h>
15#include <QtCore/qxmlstream.h>
17#if QT_CONFIG(qml_network)
18#include <QtNetwork/qnetworkreply.h>
19#include <QtNetwork/qnetworkrequest.h>
22Q_DECLARE_METATYPE(QQmlXmlListModelQueryResult)
27
28
29
30
31
32
33
34
35
36
37
38
39
40
43
44
45
46
47
48
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75QString QQmlXmlListModelRole::name()
const
80void QQmlXmlListModelRole::setName(
const QString &name)
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195QString QQmlXmlListModelRole::elementName()
const
197 return m_elementName;
200void QQmlXmlListModelRole::setElementName(
const QString &name)
202 if (name.startsWith(QLatin1Char(
'/'))) {
203 qmlWarning(
this) << tr(
"An XML element must not start with '/'");
205 }
else if (name.endsWith(QLatin1Char(
'/'))) {
206 qmlWarning(
this) << tr(
"An XML element must not end with '/'");
208 }
else if (name.contains(QStringLiteral(
"//"))) {
209 qmlWarning(
this) << tr(
"An XML element must not contain \"//\"");
213 if (name == m_elementName)
215 m_elementName = name;
216 Q_EMIT elementNameChanged();
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234QString QQmlXmlListModelRole::attributeName()
const
236 return m_attributeName;
239void QQmlXmlListModelRole::setAttributeName(
const QString &attributeName)
241 if (m_attributeName == attributeName)
243 m_attributeName = attributeName;
244 Q_EMIT attributeNameChanged();
247bool QQmlXmlListModelRole::isValid()
const
249 return !m_name.isEmpty();
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
339QQmlXmlListModel::QQmlXmlListModel(QObject *parent) : QAbstractListModel(parent) { }
341QQmlXmlListModel::~QQmlXmlListModel()
344 for (
auto &w : m_watchers.values())
347 while (!m_watchers.isEmpty()) {
348 auto it = m_watchers.begin();
349 it.value()->waitForFinished();
353 m_watchers.erase(it);
357QModelIndex QQmlXmlListModel::index(
int row,
int column,
const QModelIndex &parent)
const
359 return !parent.isValid() && column == 0 && row >= 0 && m_size ? createIndex(row, column)
363int QQmlXmlListModel::rowCount(
const QModelIndex &parent)
const
365 return !parent.isValid() ? m_size : 0;
368QVariant QQmlXmlListModel::data(
const QModelIndex &index,
int role)
const
370 const int roleIndex = m_roles.indexOf(role);
371 return (roleIndex == -1 || !index.isValid()) ? QVariant()
372 : m_data.value(index.row()).value(roleIndex);
375QHash<
int, QByteArray> QQmlXmlListModel::roleNames()
const
377 QHash<
int, QByteArray> roleNames;
378 for (
int i = 0; i < m_roles.size(); ++i)
379 roleNames.insert(m_roles.at(i), m_roleNames.at(i).toUtf8());
384
385
386
387int QQmlXmlListModel::count()
const
393
394
395
396QUrl QQmlXmlListModel::source()
const
401void QQmlXmlListModel::setSource(
const QUrl &src)
403 if (m_source != src) {
406 Q_EMIT sourceChanged();
411
412
413
414
415
416QString QQmlXmlListModel::query()
const
421void QQmlXmlListModel::setQuery(
const QString &query)
423 if (!query.startsWith(QLatin1Char(
'/'))) {
424 qmlWarning(
this) << QCoreApplication::translate(
425 "XmlListModelRoleList",
"An XmlListModel query must start with '/'");
429 if (m_query != query) {
432 Q_EMIT queryChanged();
437
438
439
440
441QQmlListProperty<QQmlXmlListModelRole> QQmlXmlListModel::roleObjects()
443 QQmlListProperty<QQmlXmlListModelRole> list(
this, &m_roleObjects);
444 list.append = &QQmlXmlListModel::appendRole;
445 list.clear = &QQmlXmlListModel::clearRole;
449void QQmlXmlListModel::appendRole(QQmlXmlListModelRole *role)
452 int i = m_roleObjects.size();
453 m_roleObjects.append(role);
454 if (m_roleNames.contains(role->name())) {
456 << QQmlXmlListModel::tr(
457 "\"%1\" duplicates a previous role name and will be disabled.")
461 m_roles.insert(i, m_highestRole);
462 m_roleNames.insert(i, role->name());
467void QQmlXmlListModel::clearRole()
471 m_roleObjects.clear();
474void QQmlXmlListModel::appendRole(QQmlListProperty<QQmlXmlListModelRole> *list,
475 QQmlXmlListModelRole *role)
477 auto object = qobject_cast<QQmlXmlListModel *>(list->object);
479 object->appendRole(role);
482void QQmlXmlListModel::clearRole(QQmlListProperty<QQmlXmlListModelRole> *list)
484 auto object = qobject_cast<QQmlXmlListModel *>(list->object);
489void QQmlXmlListModel::tryExecuteQuery(
const QByteArray &data)
491 auto job = createJob(data);
492 m_queryId = job.queryId;
493 QQmlXmlListModelQueryRunnable *runnable =
new QQmlXmlListModelQueryRunnable(std::move(job));
495 auto future = runnable->future();
496 auto *watcher =
new ResultFutureWatcher();
500 connect(watcher, &ResultFutureWatcher::finished,
this, [id = m_queryId,
this]() {
501 auto *watcher =
static_cast<ResultFutureWatcher *>(sender());
503 if (!watcher->isCanceled()) {
504 QQmlXmlListModelQueryResult result = watcher->result();
506 for (
const auto &errorInfo : result.errors)
507 queryError(errorInfo.first, errorInfo.second);
509 queryCompleted(result);
512 m_watchers.remove(id);
513 watcher->deleteLater();
516 m_watchers[m_queryId] = watcher;
517 watcher->setFuture(future);
518 QThreadPool::globalInstance()->start(runnable);
520 m_errorString = tr(
"Failed to create an instance of QRunnable query object");
521 m_status = QQmlXmlListModel::Error;
523 Q_EMIT statusChanged(m_status);
527QQmlXmlListModelQueryJob QQmlXmlListModel::createJob(
const QByteArray &data)
529 QQmlXmlListModelQueryJob job;
530 job.queryId = nextQueryId();
534 for (
int i = 0; i < m_roleObjects.size(); i++) {
535 if (!m_roleObjects.at(i)->isValid()) {
536 job.roleNames << QString();
537 job.elementNames << QString();
538 job.elementAttributes << QString();
541 job.roleNames << m_roleObjects.at(i)->name();
542 job.elementNames << m_roleObjects.at(i)->elementName();
543 job.elementAttributes << m_roleObjects.at(i)->attributeName();
544 job.roleQueryErrorId <<
static_cast<
void *>(m_roleObjects.at(i));
550int QQmlXmlListModel::nextQueryId()
552 m_nextQueryIdGenerator++;
553 if (m_nextQueryIdGenerator <= 0)
554 m_nextQueryIdGenerator = 1;
555 return m_nextQueryIdGenerator;
559
560
561
562
563
564
565
566
567
568
569
570
571QQmlXmlListModel::Status QQmlXmlListModel::status()
const
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591qreal QQmlXmlListModel::progress()
const
597
598
599
600
601
602QString QQmlXmlListModel::errorString()
const
604 return m_errorString;
607void QQmlXmlListModel::classBegin()
609 m_isComponentComplete =
false;
612void QQmlXmlListModel::componentComplete()
614 m_isComponentComplete =
true;
619
620
621
622
623void QQmlXmlListModel::reload()
625 if (!m_isComponentComplete)
628 if (m_queryId > 0 && m_watchers.contains(m_queryId))
629 m_watchers[m_queryId]->cancel();
636#if QT_CONFIG(qml_network)
643 const QQmlContext *context = qmlContext(
this);
644 const auto resolvedSource = context ? context->resolvedUrl(m_source) : m_source;
646 if (resolvedSource.isEmpty()) {
648 notifyQueryStarted(
false);
649 QTimer::singleShot(0,
this, &QQmlXmlListModel::dataCleared);
650 }
else if (QQmlFile::isLocalFile(resolvedSource)) {
651 QFile file(QQmlFile::urlToLocalFileOrQrc(resolvedSource));
652 const bool opened = file.open(QIODevice::ReadOnly);
654 qWarning(
"Failed to open file %s: %s", qPrintable(file.fileName()),
655 qPrintable(file.errorString()));
656 QByteArray data = opened ? file.readAll() : QByteArray();
657 notifyQueryStarted(
false);
658 if (data.isEmpty()) {
660 QTimer::singleShot(0,
this, &QQmlXmlListModel::dataCleared);
662 tryExecuteQuery(data);
665#if QT_CONFIG(qml_network)
666 notifyQueryStarted(
true);
667 QNetworkRequest req(resolvedSource);
668 req.setRawHeader(
"Accept",
"application/xml,*/*");
669 m_reply = qmlContext(
this)->engine()->networkAccessManager()->get(req);
671 QObject::connect(m_reply, &QNetworkReply::finished,
this,
672 &QQmlXmlListModel::requestFinished);
673 QObject::connect(m_reply, &QNetworkReply::downloadProgress,
this,
674 &QQmlXmlListModel::requestProgress);
677 notifyQueryStarted(
false);
678 QTimer::singleShot(0,
this, &QQmlXmlListModel::dataCleared);
683#if QT_CONFIG(qml_network)
684void QQmlXmlListModel::requestFinished()
686 if (m_reply->error() != QNetworkReply::NoError) {
687 m_errorString = m_reply->errorString();
691 beginRemoveRows(QModelIndex(), 0, m_size - 1);
695 Q_EMIT countChanged();
700 Q_EMIT statusChanged(m_status);
702 QByteArray data = m_reply->readAll();
703 if (data.isEmpty()) {
705 QTimer::singleShot(0,
this, &QQmlXmlListModel::dataCleared);
707 tryExecuteQuery(data);
712 Q_EMIT progressChanged(m_progress);
716void QQmlXmlListModel::deleteReply()
719 QObject::disconnect(m_reply, 0,
this, 0);
720 m_reply->deleteLater();
726void QQmlXmlListModel::requestProgress(qint64 received, qint64 total)
728 if (m_status == Loading && total > 0) {
729 m_progress = qreal(received) / total;
730 Q_EMIT progressChanged(m_progress);
734void QQmlXmlListModel::dataCleared()
736 QQmlXmlListModelQueryResult r;
741void QQmlXmlListModel::queryError(
void *object,
const QString &error)
743 for (
int i = 0; i < m_roleObjects.size(); i++) {
744 if (m_roleObjects.at(i) ==
static_cast<QQmlXmlListModelRole *>(object)) {
745 qmlWarning(m_roleObjects.at(i))
746 << QQmlXmlListModel::tr(
"Query error: \"%1\"").arg(error);
750 qmlWarning(
this) << QQmlXmlListModel::tr(
"Query error: \"%1\"").arg(error);
753void QQmlXmlListModel::queryCompleted(
const QQmlXmlListModelQueryResult &result)
755 if (result.queryId != m_queryId)
758 int origCount = m_size;
759 bool sizeChanged = result.data.size() != m_size;
761 if (m_source.isEmpty())
765 m_errorString.clear();
769 beginRemoveRows(QModelIndex(), 0, origCount - 1);
772 m_size = result.data.size();
773 m_data = result.data;
776 beginInsertRows(QModelIndex(), 0, m_size - 1);
781 Q_EMIT countChanged();
783 Q_EMIT statusChanged(m_status);
786void QQmlXmlListModel::notifyQueryStarted(
bool remoteSource)
788 m_progress = remoteSource ? 0.0 : 1.0;
789 m_status = QQmlXmlListModel::Loading;
790 m_errorString.clear();
791 Q_EMIT progressChanged(m_progress);
792 Q_EMIT statusChanged(m_status);
795static qsizetype findIndexOfName(
const QStringList &elementNames,
const QStringView &name,
796 qsizetype startIndex = 0)
798 for (
auto idx = startIndex; idx < elementNames.size(); ++idx) {
799 if (elementNames[idx].startsWith(name))
805QQmlXmlListModelQueryRunnable::QQmlXmlListModelQueryRunnable(QQmlXmlListModelQueryJob &&job)
806 : m_job(std::move(job))
811void QQmlXmlListModelQueryRunnable::run()
814 if (!m_promise.isCanceled()) {
815 QQmlXmlListModelQueryResult result;
816 result.queryId = m_job.queryId;
818 m_promise.addResult(std::move(result));
823QFuture<QQmlXmlListModelQueryResult> QQmlXmlListModelQueryRunnable::future()
const
825 return m_promise.future();
828void QQmlXmlListModelQueryRunnable::doQueryJob(QQmlXmlListModelQueryResult *currentResult)
830 Q_ASSERT(m_job.queryId != -1);
832 QByteArray data(m_job.data);
833 QXmlStreamReader reader;
834 reader.addData(data);
836 QStringList items = m_job.query.split(QLatin1Char(
'/'), Qt::SkipEmptyParts);
838 while (!reader.atEnd() && !m_promise.isCanceled()) {
840 while (i < items.size()) {
841 if (reader.readNextStartElement()) {
842 if (reader.name() == items.at(i)) {
843 if (i != items.size() - 1) {
847 processElement(currentResult, items.at(i), reader);
850 reader.skipCurrentElement();
853 if (reader.tokenType() == QXmlStreamReader::Invalid) {
856 }
else if (reader.hasError()) {
864void QQmlXmlListModelQueryRunnable::processElement(QQmlXmlListModelQueryResult *currentResult,
865 const QString &element, QXmlStreamReader &reader)
867 if (!reader.isStartElement() || reader.name() != element)
870 const QStringList &elementNames = m_job.elementNames;
871 const QStringList &attributes = m_job.elementAttributes;
872 QFlatMap<
int, QString> results;
876 if (!reader.attributes().isEmpty()) {
877 for (
auto index = 0; index < elementNames.size(); ++index) {
878 if (elementNames.at(index).isEmpty() && !attributes.at(index).isEmpty()) {
879 const QString &attribute = attributes.at(index);
880 if (reader.attributes().hasAttribute(attribute))
881 results[index] = reader.attributes().value(attribute).toString();
889 readSubTree(QString(), reader, results, ¤tResult->errors);
891 if (reader.hasError())
892 currentResult->errors.push_back(std::make_pair(
this, reader.errorString()));
894 currentResult->data << results;
897void QQmlXmlListModelQueryRunnable::readSubTree(
const QString &prefix, QXmlStreamReader &reader,
898 QFlatMap<
int, QString> &results,
899 QList<std::pair<
void *, QString>> *errors)
901 const QStringList &elementNames = m_job.elementNames;
902 const QStringList &attributes = m_job.elementAttributes;
903 while (reader.readNextStartElement()) {
904 const auto name = reader.name();
905 const QString fullName =
906 prefix.isEmpty() ? name.toString() : (prefix + QLatin1Char(
'/') + name.toString());
907 qsizetype index = name.isEmpty() ? -1 : findIndexOfName(elementNames, fullName);
912 const auto elementAttributes = reader.attributes();
917 bool elementTextRead =
false;
921 if (elementNames[index] == fullName) {
923 const QString &attribute = attributes.at(index);
924 if (!attribute.isEmpty()) {
925 if (elementAttributes.hasAttribute(attribute)) {
926 roleResult = elementAttributes.value(attributes.at(index)).toString();
928 errors->push_back(std::make_pair(m_job.roleQueryErrorId.at(index),
929 QLatin1String(
"Attribute %1 not found")
930 .arg(attributes[index])));
932 }
else if (!elementNames.at(index).isEmpty()) {
933 if (!elementTextRead) {
935 reader.readElementText(QXmlStreamReader::IncludeChildElements);
936 elementTextRead =
true;
938 roleResult = elementText;
940 results[index] = roleResult;
943 index = findIndexOfName(elementNames, fullName, index + 1);
945 if (!elementTextRead)
946 readSubTree(fullName, reader, results, errors);
948 reader.skipCurrentElement();
955#include "moc_qqmlxmllistmodel_p.cpp"