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
49
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76QString QQmlXmlListModelRole::name()
const
81void QQmlXmlListModelRole::setName(
const QString &name)
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
195
196QString QQmlXmlListModelRole::elementName()
const
198 return m_elementName;
201void QQmlXmlListModelRole::setElementName(
const QString &name)
203 if (name.startsWith(QLatin1Char(
'/'))) {
204 qmlWarning(
this) << tr(
"An XML element must not start with '/'");
206 }
else if (name.endsWith(QLatin1Char(
'/'))) {
207 qmlWarning(
this) << tr(
"An XML element must not end with '/'");
209 }
else if (name.contains(QStringLiteral(
"//"))) {
210 qmlWarning(
this) << tr(
"An XML element must not contain \"//\"");
214 if (name == m_elementName)
216 m_elementName = name;
217 Q_EMIT elementNameChanged();
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235QString QQmlXmlListModelRole::attributeName()
const
237 return m_attributeName;
240void QQmlXmlListModelRole::setAttributeName(
const QString &attributeName)
242 if (m_attributeName == attributeName)
244 m_attributeName = attributeName;
245 Q_EMIT attributeNameChanged();
248bool QQmlXmlListModelRole::isValid()
const
250 return !m_name.isEmpty();
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
338
339
341QQmlXmlListModel::QQmlXmlListModel(QObject *parent) : QAbstractListModel(parent) { }
343QQmlXmlListModel::~QQmlXmlListModel()
346 for (
auto &w : m_watchers.values())
349 while (!m_watchers.isEmpty()) {
350 auto it = m_watchers.begin();
351 it.value()->waitForFinished();
355 m_watchers.erase(it);
359QModelIndex QQmlXmlListModel::index(
int row,
int column,
const QModelIndex &parent)
const
361 return !parent.isValid() && column == 0 && row >= 0 && m_size ? createIndex(row, column)
365int QQmlXmlListModel::rowCount(
const QModelIndex &parent)
const
367 return !parent.isValid() ? m_size : 0;
370QVariant QQmlXmlListModel::data(
const QModelIndex &index,
int role)
const
372 const int roleIndex = m_roles.indexOf(role);
373 return (roleIndex == -1 || !index.isValid()) ? QVariant()
374 : m_data.value(index.row()).value(roleIndex);
377QHash<
int, QByteArray> QQmlXmlListModel::roleNames()
const
379 QHash<
int, QByteArray> roleNames;
380 for (
int i = 0; i < m_roles.size(); ++i)
381 roleNames.insert(m_roles.at(i), m_roleNames.at(i).toUtf8());
386
387
388
389int QQmlXmlListModel::count()
const
395
396
397
398QUrl QQmlXmlListModel::source()
const
403void QQmlXmlListModel::setSource(
const QUrl &src)
405 if (m_source != src) {
408 Q_EMIT sourceChanged();
413
414
415
416
417
418QString QQmlXmlListModel::query()
const
423void QQmlXmlListModel::setQuery(
const QString &query)
425 if (!query.startsWith(QLatin1Char(
'/'))) {
426 qmlWarning(
this) << QCoreApplication::translate(
427 "XmlListModelRoleList",
"An XmlListModel query must start with '/'");
431 if (m_query != query) {
434 Q_EMIT queryChanged();
439
440
441
442
443QQmlListProperty<QQmlXmlListModelRole> QQmlXmlListModel::roleObjects()
445 QQmlListProperty<QQmlXmlListModelRole> list(
this, &m_roleObjects);
446 list.append = &QQmlXmlListModel::appendRole;
447 list.clear = &QQmlXmlListModel::clearRole;
451void QQmlXmlListModel::appendRole(QQmlXmlListModelRole *role)
454 int i = m_roleObjects.size();
455 m_roleObjects.append(role);
456 if (m_roleNames.contains(role->name())) {
458 << QQmlXmlListModel::tr(
459 "\"%1\" duplicates a previous role name and will be disabled.")
463 m_roles.insert(i, m_highestRole);
464 m_roleNames.insert(i, role->name());
469void QQmlXmlListModel::clearRole()
473 m_roleObjects.clear();
476void QQmlXmlListModel::appendRole(QQmlListProperty<QQmlXmlListModelRole> *list,
477 QQmlXmlListModelRole *role)
479 auto object = qobject_cast<QQmlXmlListModel *>(list->object);
481 object->appendRole(role);
484void QQmlXmlListModel::clearRole(QQmlListProperty<QQmlXmlListModelRole> *list)
486 auto object = qobject_cast<QQmlXmlListModel *>(list->object);
491void QQmlXmlListModel::tryExecuteQuery(
const QByteArray &data)
493 auto job = createJob(data);
494 m_queryId = job.queryId;
495 QQmlXmlListModelQueryRunnable *runnable =
new QQmlXmlListModelQueryRunnable(std::move(job));
497 auto future = runnable->future();
498 auto *watcher =
new ResultFutureWatcher();
502 connect(watcher, &ResultFutureWatcher::finished,
this, [id = m_queryId,
this]() {
503 auto *watcher =
static_cast<ResultFutureWatcher *>(sender());
505 if (!watcher->isCanceled()) {
506 QQmlXmlListModelQueryResult result = watcher->result();
508 for (
const auto &errorInfo : result.errors)
509 queryError(errorInfo.first, errorInfo.second);
511 queryCompleted(result);
514 m_watchers.remove(id);
515 watcher->deleteLater();
518 m_watchers[m_queryId] = watcher;
519 watcher->setFuture(future);
520 QThreadPool::globalInstance()->start(runnable);
522 m_errorString = tr(
"Failed to create an instance of QRunnable query object");
523 m_status = QQmlXmlListModel::Error;
525 Q_EMIT statusChanged(m_status);
529QQmlXmlListModelQueryJob QQmlXmlListModel::createJob(
const QByteArray &data)
531 QQmlXmlListModelQueryJob job;
532 job.queryId = nextQueryId();
536 for (
int i = 0; i < m_roleObjects.size(); i++) {
537 if (!m_roleObjects.at(i)->isValid()) {
538 job.roleNames << QString();
539 job.elementNames << QString();
540 job.elementAttributes << QString();
543 job.roleNames << m_roleObjects.at(i)->name();
544 job.elementNames << m_roleObjects.at(i)->elementName();
545 job.elementAttributes << m_roleObjects.at(i)->attributeName();
546 job.roleQueryErrorId <<
static_cast<
void *>(m_roleObjects.at(i));
552int QQmlXmlListModel::nextQueryId()
554 m_nextQueryIdGenerator++;
555 if (m_nextQueryIdGenerator <= 0)
556 m_nextQueryIdGenerator = 1;
557 return m_nextQueryIdGenerator;
561
562
563
564
565
566
567
568
569
570
571
572
573QQmlXmlListModel::Status QQmlXmlListModel::status()
const
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593qreal QQmlXmlListModel::progress()
const
599
600
601
602
603
604QString QQmlXmlListModel::errorString()
const
606 return m_errorString;
609void QQmlXmlListModel::classBegin()
611 m_isComponentComplete =
false;
614void QQmlXmlListModel::componentComplete()
616 m_isComponentComplete =
true;
621
622
623
624
625void QQmlXmlListModel::reload()
627 if (!m_isComponentComplete)
630 if (m_queryId > 0 && m_watchers.contains(m_queryId))
631 m_watchers[m_queryId]->cancel();
638#if QT_CONFIG(qml_network)
645 const QQmlContext *context = qmlContext(
this);
646 const auto resolvedSource = context ? context->resolvedUrl(m_source) : m_source;
648 if (resolvedSource.isEmpty()) {
650 notifyQueryStarted(
false);
651 QTimer::singleShot(0,
this, &QQmlXmlListModel::dataCleared);
652 }
else if (QQmlFile::isLocalFile(resolvedSource)) {
653 QFile file(QQmlFile::urlToLocalFileOrQrc(resolvedSource));
654 const bool opened = file.open(QIODevice::ReadOnly);
656 qWarning(
"Failed to open file %s: %s", qPrintable(file.fileName()),
657 qPrintable(file.errorString()));
658 QByteArray data = opened ? file.readAll() : QByteArray();
659 notifyQueryStarted(
false);
660 if (data.isEmpty()) {
662 QTimer::singleShot(0,
this, &QQmlXmlListModel::dataCleared);
664 tryExecuteQuery(data);
667#if QT_CONFIG(qml_network)
668 notifyQueryStarted(
true);
669 QNetworkRequest req(resolvedSource);
670 req.setRawHeader(
"Accept",
"application/xml,*/*");
671 m_reply = qmlContext(
this)->engine()->networkAccessManager()->get(req);
673 QObject::connect(m_reply, &QNetworkReply::finished,
this,
674 &QQmlXmlListModel::requestFinished);
675 QObject::connect(m_reply, &QNetworkReply::downloadProgress,
this,
676 &QQmlXmlListModel::requestProgress);
679 notifyQueryStarted(
false);
680 QTimer::singleShot(0,
this, &QQmlXmlListModel::dataCleared);
685#if QT_CONFIG(qml_network)
686void QQmlXmlListModel::requestFinished()
688 if (m_reply->error() != QNetworkReply::NoError) {
689 m_errorString = m_reply->errorString();
693 beginRemoveRows(QModelIndex(), 0, m_size - 1);
697 Q_EMIT countChanged();
702 Q_EMIT statusChanged(m_status);
704 QByteArray data = m_reply->readAll();
705 if (data.isEmpty()) {
707 QTimer::singleShot(0,
this, &QQmlXmlListModel::dataCleared);
709 tryExecuteQuery(data);
714 Q_EMIT progressChanged(m_progress);
718void QQmlXmlListModel::deleteReply()
721 QObject::disconnect(m_reply, 0,
this, 0);
722 m_reply->deleteLater();
728void QQmlXmlListModel::requestProgress(qint64 received, qint64 total)
730 if (m_status == Loading && total > 0) {
731 m_progress = qreal(received) / total;
732 Q_EMIT progressChanged(m_progress);
736void QQmlXmlListModel::dataCleared()
738 QQmlXmlListModelQueryResult r;
743void QQmlXmlListModel::queryError(
void *object,
const QString &error)
745 for (
int i = 0; i < m_roleObjects.size(); i++) {
746 if (m_roleObjects.at(i) ==
static_cast<QQmlXmlListModelRole *>(object)) {
747 qmlWarning(m_roleObjects.at(i))
748 << QQmlXmlListModel::tr(
"Query error: \"%1\"").arg(error);
752 qmlWarning(
this) << QQmlXmlListModel::tr(
"Query error: \"%1\"").arg(error);
755void QQmlXmlListModel::queryCompleted(
const QQmlXmlListModelQueryResult &result)
757 if (result.queryId != m_queryId)
760 int origCount = m_size;
761 bool sizeChanged = result.data.size() != m_size;
763 if (m_source.isEmpty())
767 m_errorString.clear();
771 beginRemoveRows(QModelIndex(), 0, origCount - 1);
774 m_size = result.data.size();
775 m_data = result.data;
778 beginInsertRows(QModelIndex(), 0, m_size - 1);
783 Q_EMIT countChanged();
785 Q_EMIT statusChanged(m_status);
788void QQmlXmlListModel::notifyQueryStarted(
bool remoteSource)
790 m_progress = remoteSource ? 0.0 : 1.0;
791 m_status = QQmlXmlListModel::Loading;
792 m_errorString.clear();
793 Q_EMIT progressChanged(m_progress);
794 Q_EMIT statusChanged(m_status);
797static qsizetype findIndexOfName(
const QStringList &elementNames,
const QStringView &name,
798 qsizetype startIndex = 0)
800 for (
auto idx = startIndex; idx < elementNames.size(); ++idx) {
801 if (elementNames[idx].startsWith(name))
807QQmlXmlListModelQueryRunnable::QQmlXmlListModelQueryRunnable(QQmlXmlListModelQueryJob &&job)
808 : m_job(std::move(job))
813void QQmlXmlListModelQueryRunnable::run()
816 if (!m_promise.isCanceled()) {
817 QQmlXmlListModelQueryResult result;
818 result.queryId = m_job.queryId;
820 m_promise.addResult(std::move(result));
825QFuture<QQmlXmlListModelQueryResult> QQmlXmlListModelQueryRunnable::future()
const
827 return m_promise.future();
830void QQmlXmlListModelQueryRunnable::doQueryJob(QQmlXmlListModelQueryResult *currentResult)
832 Q_ASSERT(m_job.queryId != -1);
834 QByteArray data(m_job.data);
835 QXmlStreamReader reader;
836 reader.addData(data);
838 QStringList items = m_job.query.split(QLatin1Char(
'/'), Qt::SkipEmptyParts);
840 while (!reader.atEnd() && !m_promise.isCanceled()) {
842 while (i < items.size()) {
843 if (reader.readNextStartElement()) {
844 if (reader.name() == items.at(i)) {
845 if (i != items.size() - 1) {
849 processElement(currentResult, items.at(i), reader);
852 reader.skipCurrentElement();
855 if (reader.tokenType() == QXmlStreamReader::Invalid) {
858 }
else if (reader.hasError()) {
866void QQmlXmlListModelQueryRunnable::processElement(QQmlXmlListModelQueryResult *currentResult,
867 const QString &element, QXmlStreamReader &reader)
869 if (!reader.isStartElement() || reader.name() != element)
872 const QStringList &elementNames = m_job.elementNames;
873 const QStringList &attributes = m_job.elementAttributes;
874 QFlatMap<
int, QString> results;
878 if (!reader.attributes().isEmpty()) {
879 for (
auto index = 0; index < elementNames.size(); ++index) {
880 if (elementNames.at(index).isEmpty() && !attributes.at(index).isEmpty()) {
881 const QString &attribute = attributes.at(index);
882 if (reader.attributes().hasAttribute(attribute))
883 results[index] = reader.attributes().value(attribute).toString();
891 readSubTree(QString(), reader, results, ¤tResult->errors);
893 if (reader.hasError())
894 currentResult->errors.push_back(std::make_pair(
this, reader.errorString()));
896 currentResult->data << results;
899void QQmlXmlListModelQueryRunnable::readSubTree(
const QString &prefix, QXmlStreamReader &reader,
900 QFlatMap<
int, QString> &results,
901 QList<std::pair<
void *, QString>> *errors)
903 const QStringList &elementNames = m_job.elementNames;
904 const QStringList &attributes = m_job.elementAttributes;
905 while (reader.readNextStartElement()) {
906 const auto name = reader.name();
907 const QString fullName =
908 prefix.isEmpty() ? name.toString() : (prefix + QLatin1Char(
'/') + name.toString());
909 qsizetype index = name.isEmpty() ? -1 : findIndexOfName(elementNames, fullName);
914 const auto elementAttributes = reader.attributes();
919 bool elementTextRead =
false;
923 if (elementNames[index] == fullName) {
925 const QString &attribute = attributes.at(index);
926 if (!attribute.isEmpty()) {
927 if (elementAttributes.hasAttribute(attribute)) {
928 roleResult = elementAttributes.value(attributes.at(index)).toString();
930 errors->push_back(std::make_pair(m_job.roleQueryErrorId.at(index),
931 QLatin1String(
"Attribute %1 not found")
932 .arg(attributes[index])));
934 }
else if (!elementNames.at(index).isEmpty()) {
935 if (!elementTextRead) {
937 reader.readElementText(QXmlStreamReader::IncludeChildElements);
938 elementTextRead =
true;
940 roleResult = elementText;
942 results[index] = roleResult;
945 index = findIndexOfName(elementNames, fullName, index + 1);
947 if (!elementTextRead)
948 readSubTree(fullName, reader, results, errors);
950 reader.skipCurrentElement();
957#include "moc_qqmlxmllistmodel_p.cpp"