Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qgeoroutexmlparser.cpp
Go to the documentation of this file.
1// Copyright (C) 2015 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
5
6#include <QXmlStreamReader>
7#include <QStringList>
8#include <QString>
9#include <QtCore/QThreadPool>
10#include <QDebug>
11
12#include <QtPositioning/QGeoRectangle>
13#include <QtPositioning/QGeoPath>
14#include <QtLocation/QGeoRoute>
15#include <QtLocation/private/qgeoroutesegment_p.h>
16
18
19QGeoDynamicSpeedInfoContainer::QGeoDynamicSpeedInfoContainer()
20: trafficSpeed(0)
21, baseSpeed(0)
22, trafficTime(0)
23, baseTime(0)
24{}
25
26QGeoRouteXmlParser::QGeoRouteXmlParser(const QGeoRouteRequest &request)
27 : m_request(request)
28{
29}
30
34
35void QGeoRouteXmlParser::parse(const QByteArray &data)
36{
37 m_data = data;
38// QFile file("/tmp/here.xml");
39// file.open(QIODevice::WriteOnly);
40// file.write(data);
41// file.close();
42 QThreadPool::globalInstance()->start(this);
43}
44
46{
47 m_reader = new QXmlStreamReader(m_data);
48
49 if (!parseRootElement())
50 emit errorOccurred(m_reader->errorString());
51 else
52 emit results(m_results);
53
54 delete m_reader;
55 m_reader = 0;
56}
57
58bool QGeoRouteXmlParser::parseRootElement()
59{
60 if (!m_reader->readNextStartElement()) {
61 m_reader->raiseError(QStringLiteral("Expected a root element named \"CalculateRoute\" "
62 "(no root element found)."));
63 return false;
64 }
65
66 if (m_reader->name() == QLatin1String("Error")) {
67 QXmlStreamAttributes attributes = m_reader->attributes();
68 if (attributes.value(QStringLiteral("type")) == QLatin1String("ApplicationError")
69 && attributes.value("subtype") == QLatin1String("NoRouteFound"))
70 return true;
71 }
72
73 bool updateroute = false;
74 if (m_reader->name() != QLatin1String("CalculateRoute")
75 && m_reader->name() != QLatin1String("GetRoute")) {
76 m_reader->raiseError(QStringLiteral("The root element is expected to have the name "
77 "\"CalculateRoute\" or \"GetRoute\" (root element "
78 "was named \"%1\").").arg(m_reader->name().toString()));
79 return false;
80 } else if (m_reader->name() == QLatin1String("GetRoute")) {
81 updateroute = true;
82 }
83
84 if (m_reader->readNextStartElement()) {
85 if (m_reader->name() != QLatin1String("Response")) {
86 m_reader->raiseError(QStringLiteral("Expected a element named \"Response\" (element "
87 "was named \"%1\").").arg(m_reader->name().toString()));
88 return false;
89 }
90 }
91
92 while (m_reader->readNextStartElement() && !m_reader->hasError()) {
93 if (m_reader->name() == QLatin1String("Route")) {
94 QGeoRoute route;
95 route.setRequest(m_request);
96 if (updateroute)
97 route.setTravelMode(QGeoRouteRequest::TravelMode(int(m_request.travelModes())));
98 if (!parseRoute(&route))
99 continue; //route parsing failed move on to the next
100 m_results.append(route);
101 } else if (m_reader->name() == QLatin1String("Progress")) {
102 //TODO: updated route progress
103 m_reader->skipCurrentElement();
104 } else {
105 m_reader->skipCurrentElement();
106 }
107 }
108
109 return !m_reader->hasError();
110}
111
112bool QGeoRouteXmlParser::parseRoute(QGeoRoute *route)
113{
114 Q_ASSERT(m_reader->isStartElement() && m_reader->name() == QLatin1String("Route"));
115 m_maneuvers.clear();
116// m_segments.clear();
117 m_legs.clear();
118 int legIndex = 0;
119 m_reader->readNext();
120 while (!(m_reader->tokenType() == QXmlStreamReader::EndElement
121 && m_reader->name() == QLatin1String("Route")) && !m_reader->hasError()) {
122 if (m_reader->tokenType() == QXmlStreamReader::StartElement) {
123 if (m_reader->name() == QLatin1String("RouteId")) {
124 route->setRouteId(m_reader->readElementText());
125 }
126 //else if (m_reader->name() == "Waypoint") {
127 // succeeded = parseWaypoint(route);
128 //}
129 else if (m_reader->name() == QLatin1String("Mode")) {
130 if (!parseMode(route))
131 return false;
132 } else if (m_reader->name() == QLatin1String("Shape")) {
133 QString elementName = m_reader->name().toString();
134 QList<QGeoCoordinate> path;
135 if (!parseGeoPoints(m_reader->readElementText(), &path, elementName))
136 return false;
137 route->setPath(path);
138 } else if (m_reader->name() == QLatin1String("BoundingBox")) {
139 QGeoRectangle bounds;
140 if (!parseBoundingBox(bounds))
141 return false;
142 route->setBounds(bounds);
143 } else if (m_reader->name() == QLatin1String("Leg")) {
144 if (!parseLeg(legIndex++))
145 return false;
146 } else if (m_reader->name() == QLatin1String("Summary")) {
147 if (!parseSummary(route))
148 return false;
149 } else {
150 m_reader->skipCurrentElement();
151 }
152 }
153 m_reader->readNext();
154 }
155
156 if (m_reader->hasError())
157 return false;
158
159 return postProcessRoute(route);
160}
161
162bool QGeoRouteXmlParser::parseLeg(int legIndex)
163{
164 Q_ASSERT(m_reader->isStartElement() && m_reader->name() == QStringLiteral("Leg"));
165 QGeoRoute leg;
166 leg.setLegIndex(legIndex);
167 m_reader->readNext();
168 QList<QGeoManeuverContainer> maneuvers;
169 QList<QGeoRouteSegmentContainer> links;
170 while (!(m_reader->tokenType() == QXmlStreamReader::EndElement
171 && m_reader->name() == QStringLiteral("Leg")) &&
172 !m_reader->hasError()) {
173 if (m_reader->tokenType() == QXmlStreamReader::StartElement) {
174 if (m_reader->name() == QStringLiteral("Maneuver")) {
175 if (!parseManeuver(maneuvers))
176 return false;
177 }
178// Currently unused, after requesting shape attribute in maneuvers.
179// Links, however, contain additional info, such as speed limits, and might become needed in the future.
180// else if (m_reader->name() == QStringLiteral("Link")) {
181// if (!parseLink(links))
182// return false;
183// }
184 else if (m_reader->name() == QLatin1String("TravelTime")) {
185 leg.setTravelTime(qRound(m_reader->readElementText().toDouble()));
186 } else if (m_reader->name() == QLatin1String("Length")) {
187 leg.setDistance(m_reader->readElementText().toDouble());
188 } else {
189 m_reader->skipCurrentElement();
190 }
191 }
192 m_reader->readNext();
193 }
194
195 if (m_reader->hasError())
196 return false;
197
198 m_legs << leg;
199// m_segments << links;
200 m_maneuvers << maneuvers;
201 return true;
202}
203
204//static bool fuzzyCompare(const QGeoCoordinate &a, const QGeoCoordinate& b)
205//{
206// return qFuzzyCompare(a.latitude(), b.latitude()) && qFuzzyCompare(a.longitude(), b.longitude());
207//}
208
209bool QGeoRouteXmlParser::postProcessRoute(QGeoRoute *route)
210{
211 QList<QList<QGeoRouteSegment>> legSegments;
212 Q_ASSERT(m_maneuvers.size());
213
214
215 // Step 3: populate the linkMap, linkId -> linkContainer
216 for (qsizetype i = 0; i < m_maneuvers.size(); i++) {
217 legSegments << QList<QGeoRouteSegment>();
218 QList<QGeoRouteSegment> &segments = legSegments[i];
219 QList<QGeoManeuverContainer> &maneuvers = m_maneuvers[i];
220 for (qsizetype j = 0; j < m_maneuvers.at(i).size(); j++) {
221 QGeoManeuverContainer &maneuver = maneuvers[j];
222 QGeoRouteSegment segment;
223
224 QVariantMap extendedAttributes;
225 extendedAttributes["first"] = maneuver.first;
226 extendedAttributes["last"] = maneuver.last;
227 extendedAttributes["legIndex"] = i;
228 extendedAttributes["id"] = maneuver.id;
229 extendedAttributes["toLink"] = maneuver.toLink;
230 extendedAttributes["index"] = j;
231 maneuver.maneuver.setExtendedAttributes(extendedAttributes);
232
233 segment.setDistance(maneuver.maneuver.distanceToNextInstruction());
234 segment.setTravelTime(maneuver.maneuver.timeToNextInstruction());
235 segment.setPath(maneuver.path);
236 segment.setManeuver(maneuver.maneuver);
237 segments << segment;
238 }
239 }
240
241 // Step 7: connect all segments.
242 QGeoRouteSegment segment;
243 QGeoRouteSegment firstSegment;
244 for (auto &segments: legSegments) {
245 for (qsizetype j = 0; j < segments.size(); j++) {
246 if (segment.isValid()) {
247 segment.setNextRouteSegment(segments[j]);
248 } else {
249 firstSegment = segments[j];
250 }
251 segment = segments[j];
252 if (j == segments.size() - 1) {
253 QGeoRouteSegmentPrivate *sp = QGeoRouteSegmentPrivate::get(segment);
254 sp->setLegLastSegment(true);
255 }
256 }
257 }
258
259 if (firstSegment.isValid())
260 route->setFirstRouteSegment(firstSegment);
261
262 // Step 8: fill route legs.
263 for (qsizetype i = 0; i < m_legs.size(); i++) {
264 auto &leg = m_legs[i];
265 leg.setTravelMode(route->travelMode());
266 leg.setRequest(route->request());
267 leg.setOverallRoute(*route);
268 leg.setLegIndex(i);
269
270 leg.setFirstRouteSegment(legSegments[i].first());
271
272 // handle path
273 QList<QGeoCoordinate> path;
274 QGeoRouteSegment s = leg.firstRouteSegment();
275 while (s.isValid()) {
276 path.append(s.path());
277 if (s.isLegLastSegment())
278 break;
279 s = s.nextRouteSegment();
280 }
281 leg.setPath(path);
282 leg.setBounds(QGeoPath(path).boundingGeoRectangle());
283 }
284 route->setRouteLegs(m_legs);
285 m_legs.clear();
286// m_segments.clear();
287 m_maneuvers.clear();
288 return true;
289}
290
291/*
292bool QGeoRouteXmlParser::parseWaypoint(QGeoRoute *route)
293{
294 Q_ASSERT(m_reader->isStartElement() && m_reader->name() == "Waypoint");
295 m_reader->readNext();
296 QList<QGeoCoordinate> path(route->pathSummary());
297
298 while (!(m_reader->tokenType() == QXmlStreamReader::EndElement && m_reader->name() == "Waypoint")) {
299 if (m_reader->tokenType() == QXmlStreamReader::StartElement) {
300 if (m_reader->name() == "MappedPosition") {
301 QGeoCoordinate coordinates;
302 if(!parseCoordinates(coordinates))
303 return false;
304 path.append(coordinates);
305 }
306 else {
307 m_reader->skipCurrentElement();
308 }
309 }
310 m_reader->readNext();
311 }
312 route->setPathSummary(path);
313 return true;
314}
315*/
316
317bool QGeoRouteXmlParser::parseMode(QGeoRoute *route)
318{
319 Q_ASSERT(m_reader->isStartElement() && m_reader->name() == QLatin1String("Mode"));
320 m_reader->readNext();
321
322 while (!(m_reader->tokenType() == QXmlStreamReader::EndElement
323 && m_reader->name() == QLatin1String("Mode")) && !m_reader->hasError()) {
324 if (m_reader->tokenType() == QXmlStreamReader::StartElement) {
325 if (m_reader->name() == QLatin1String("TransportModes")) {
326 QString value = m_reader->readElementText();
327 if (value == "car")
328 route->setTravelMode(QGeoRouteRequest::CarTravel);
329 else if (value == "pedestrian")
330 route->setTravelMode(QGeoRouteRequest::PedestrianTravel);
331 else if (value == "publicTransport")
332 route->setTravelMode(QGeoRouteRequest::PublicTransitTravel);
333 else if (value == "bicycle")
334 route->setTravelMode(QGeoRouteRequest::BicycleTravel);
335 else if (value == "truck")
336 route->setTravelMode(QGeoRouteRequest::TruckTravel);
337 else {
338 // unsupported mode
339 m_reader->raiseError(QString("Unsupported travel mode '\"%1\"'").arg(value));
340 return false;
341 }
342 } else {
343 m_reader->skipCurrentElement();
344 }
345 }
346 m_reader->readNext();
347 }
348 return !m_reader->hasError();
349}
350
351bool QGeoRouteXmlParser::parseSummary(QGeoRoute *route)
352{
353 Q_ASSERT(route);
354 Q_ASSERT(m_reader->isStartElement() && m_reader->name() == QLatin1String("Summary"));
355 m_reader->readNext();
356
357 double baseTime = -1, trafficTime = -1;
358
359 while (!(m_reader->tokenType() == QXmlStreamReader::EndElement
360 && m_reader->name() == QLatin1String("Summary")) && !m_reader->hasError()) {
361 if (m_reader->tokenType() == QXmlStreamReader::StartElement) {
362 if (m_reader->name() == QLatin1String("Distance")) {
363 route->setDistance(m_reader->readElementText().toDouble());
364 } else if (m_reader->name() == QLatin1String("TrafficTime")) {
365 trafficTime = m_reader->readElementText().toDouble();
366 } else if (m_reader->name() == QLatin1String("BaseTime")) {
367 baseTime = m_reader->readElementText().toDouble();
368 } else {
369 m_reader->skipCurrentElement();
370 }
371 }
372 m_reader->readNext();
373 }
374
375 if (m_reader->hasError())
376 return false;
377
378 if (trafficTime >= 0)
379 route->setTravelTime(trafficTime);
380 else if (baseTime >= 0)
381 route->setTravelTime(baseTime);
382
383 return true;
384}
385
386bool QGeoRouteXmlParser::parseCoordinates(QGeoCoordinate &coord)
387{
388 QString currentElement = m_reader->name().toString();
389 m_reader->readNext();
390
391 while (!(m_reader->tokenType() == QXmlStreamReader::EndElement
392 && m_reader->name() == currentElement) && !m_reader->hasError()) {
393 if (m_reader->tokenType() == QXmlStreamReader::StartElement) {
394 QString name = m_reader->name().toString();
395 QString value = m_reader->readElementText();
396 if (name == "Latitude")
397 coord.setLatitude(value.toDouble());
398 else if (name == "Longitude")
399 coord.setLongitude(value.toDouble());
400 }
401 m_reader->readNext();
402 }
403
404 return !m_reader->hasError();
405}
406
407bool QGeoRouteXmlParser::parseManeuver(QList<QGeoManeuverContainer> &maneuvers)
408{
409 Q_ASSERT(m_reader->isStartElement() && m_reader->name() == QLatin1String("Maneuver"));
410
411 if (!m_reader->attributes().hasAttribute("id")) {
412 m_reader->raiseError("The element \"Maneuver\" did not have the required attribute \"id\".");
413 return false;
414 }
415 QGeoManeuverContainer maneuverContainter;
416 maneuverContainter.id = m_reader->attributes().value("id").toString();
417
418 m_reader->readNext();
419 while (!(m_reader->tokenType() == QXmlStreamReader::EndElement
420 && m_reader->name() == QLatin1String("Maneuver")) && !m_reader->hasError()) {
421 if (m_reader->tokenType() == QXmlStreamReader::StartElement) {
422 if (m_reader->name() == QLatin1String("Position")) {
423 QGeoCoordinate coordinates;
424 if (parseCoordinates(coordinates))
425 maneuverContainter.maneuver.setPosition(coordinates);
426 } else if (m_reader->name() == QLatin1String("Instruction")) {
427 maneuverContainter.maneuver.setInstructionText(m_reader->readElementText());
428 } else if (m_reader->name() == QLatin1String("Shape")) {
429 QString elementName = m_reader->name().toString();
430 QList<QGeoCoordinate> path;
431 if (!parseGeoPoints(m_reader->readElementText(), &path, elementName))
432 return false;
433 maneuverContainter.path = path;
434 } else if (m_reader->name() == QLatin1String("ToLink")) {
435 maneuverContainter.toLink = m_reader->readElementText();
436 } else if (m_reader->name() == QLatin1String("TravelTime")) {
437 maneuverContainter.maneuver.setTimeToNextInstruction(qRound(m_reader->readElementText().toDouble()));
438 } else if (m_reader->name() == QLatin1String("Length")) {
439 maneuverContainter.maneuver.setDistanceToNextInstruction(m_reader->readElementText().toDouble());
440 } else if (m_reader->name() == QLatin1String("Direction")) {
441 QString value = m_reader->readElementText();
442 if (value == "forward")
443 maneuverContainter.maneuver.setDirection(QGeoManeuver::DirectionForward);
444 else if (value == "bearRight")
445 maneuverContainter.maneuver.setDirection(QGeoManeuver::DirectionBearRight);
446 else if (value == "lightRight")
447 maneuverContainter.maneuver.setDirection(QGeoManeuver::DirectionLightRight);
448 else if (value == "right")
449 maneuverContainter.maneuver.setDirection(QGeoManeuver::DirectionRight);
450 else if (value == "hardRight")
451 maneuverContainter.maneuver.setDirection(QGeoManeuver::DirectionHardRight);
452 else if (value == "uTurnRight")
453 maneuverContainter.maneuver.setDirection(QGeoManeuver::DirectionUTurnRight);
454 else if (value == "uTurnLeft")
455 maneuverContainter.maneuver.setDirection(QGeoManeuver::DirectionUTurnLeft);
456 else if (value == "hardLeft")
457 maneuverContainter.maneuver.setDirection(QGeoManeuver::DirectionHardLeft);
458 else if (value == "left")
459 maneuverContainter.maneuver.setDirection(QGeoManeuver::DirectionLeft);
460 else if (value == "lightLeft")
461 maneuverContainter.maneuver.setDirection(QGeoManeuver::DirectionLightLeft);
462 else if (value == "bearLeft")
463 maneuverContainter.maneuver.setDirection(QGeoManeuver::DirectionBearLeft);
464 else
465 maneuverContainter.maneuver.setDirection(QGeoManeuver::NoDirection);
466 } else {
467 m_reader->skipCurrentElement();
468 }
469 }
470 m_reader->readNext();
471 }
472
473 if (m_reader->hasError())
474 return false;
475
476 maneuvers.append(maneuverContainter);
477 return true;
478}
479
480bool QGeoRouteXmlParser::parseLink(QList<QGeoRouteSegmentContainer> &links)
481{
482 Q_ASSERT(m_reader->isStartElement() && m_reader->name() == QStringLiteral("Link"));
483 m_reader->readNext();
484
485 QGeoRouteSegmentContainer segmentContainer;
486
487 while (!(m_reader->tokenType() == QXmlStreamReader::EndElement
488 && m_reader->name() == QStringLiteral("Link")) && !m_reader->hasError()) {
489 if (m_reader->tokenType() == QXmlStreamReader::StartElement) {
490 if (m_reader->name() == QStringLiteral("LinkId")) {
491 segmentContainer.id = m_reader->readElementText();
492 } else if (m_reader->name() == QStringLiteral("Shape")) {
493 QString elementName = m_reader->name().toString();
494 QList<QGeoCoordinate> path;
495 parseGeoPoints(m_reader->readElementText(), &path, elementName);
496 segmentContainer.segment.setPath(path);
497 } else if (m_reader->name() == QStringLiteral("Length")) {
498 segmentContainer.segment.setDistance(m_reader->readElementText().toDouble());
499 } else if (m_reader->name() == QStringLiteral("Maneuver")) {
500 segmentContainer.maneuverId = m_reader->readElementText();
501 } else if (m_reader->name() == QStringLiteral("DynamicSpeedInfo")) {
503 if (!parseDynamicSpeedInfo(speedInfo))
504 return false;
505 const double time = speedInfo.trafficTime >= 0
506 ? speedInfo.trafficTime : speedInfo.baseTime;
507 if (time >= 0)
508 segmentContainer.segment.setTravelTime(time);
509 } else {
510 m_reader->skipCurrentElement();
511 }
512 }
513 m_reader->readNext();
514 }
515
516 if (m_reader->hasError())
517 return false;
518 links.append(segmentContainer);
519 return true;
520}
521
522bool QGeoRouteXmlParser::parseGeoPoints(const QString &strPoints, QList<QGeoCoordinate> *geoPoints,
523 const QString &elementName)
524{
525 const QStringList rawPoints = strPoints.split(' ');
526
527 for (const auto &rawPoint : rawPoints) {
528 const QStringList coords = rawPoint.split(',');
529
530 if (coords.length() != 2) {
531 m_reader->raiseError(QStringLiteral("Each of the space separated values of \"%1\" "
532 "is expected to be a comma separated pair of "
533 "coordinates (value was \"%2\")")
534 .arg(elementName).arg(rawPoint));
535 return false;
536 }
537
538 bool ok = false;
539 const QString latString = coords[0];
540 double lat = latString.toDouble(&ok);
541
542 if (!ok) {
543 m_reader->raiseError(QStringLiteral("The latitude portions of \"%1\" are expected to "
544 "have a value convertable to a double (value was "
545 "\"%2\")").arg(elementName).arg(latString));
546 return false;
547 }
548
549 const QString lngString = coords[1];
550 double lng = lngString.toDouble(&ok);
551
552 if (!ok) {
553 m_reader->raiseError(QStringLiteral("The longitude portions of \"%1\" are expected to "
554 "have a value convertable to a double (value was "
555 "\"%2\")").arg(elementName).arg(lngString));
556 return false;
557 }
558
559 QGeoCoordinate geoPoint(lat, lng);
560 geoPoints->append(geoPoint);
561 }
562
563 return true;
564}
565
566bool QGeoRouteXmlParser::parseBoundingBox(QGeoRectangle &bounds)
567{
568 Q_ASSERT(m_reader->isStartElement() && m_reader->name() == QLatin1String("BoundingBox"));
569
570 QGeoCoordinate tl;
571 QGeoCoordinate br;
572
573 m_reader->readNext();
574 while (!(m_reader->tokenType() == QXmlStreamReader::EndElement
575 && m_reader->name() == QLatin1String("BoundingBox")) && !m_reader->hasError()) {
576 if (m_reader->tokenType() == QXmlStreamReader::StartElement) {
577 if (m_reader->name() == QLatin1String("TopLeft")) {
578 QGeoCoordinate coordinates;
579 if (parseCoordinates(coordinates))
580 tl = coordinates;
581 } else if (m_reader->name() == QLatin1String("BottomRight")) {
582 QGeoCoordinate coordinates;
583 if (parseCoordinates(coordinates))
584 br = coordinates;
585 } else {
586 m_reader->skipCurrentElement();
587 }
588 }
589 m_reader->readNext();
590 }
591
592 if (m_reader->hasError())
593 return false;
594
595 if (tl.isValid() && br.isValid()) {
596 bounds = QGeoRectangle(tl, br);
597 return true;
598 }
599
600 return false;
601}
602
603bool QGeoRouteXmlParser::parseDynamicSpeedInfo(QGeoDynamicSpeedInfoContainer &speedInfo)
604{
605 Q_ASSERT(m_reader->isStartElement() && m_reader->name() == QStringLiteral("DynamicSpeedInfo"));
606
607 m_reader->readNext();
608 while (!(m_reader->tokenType() == QXmlStreamReader::EndElement && m_reader->name() == QStringLiteral("DynamicSpeedInfo")) &&
609 !m_reader->hasError()) {
610 if (m_reader->tokenType() == QXmlStreamReader::StartElement) {
611 if (m_reader->name() == QStringLiteral("TrafficSpeed")) {
612 speedInfo.trafficSpeed = m_reader->readElementText().toDouble();
613 } else if (m_reader->name() == QStringLiteral("TrafficTime")) {
614 speedInfo.trafficTime = qRound(m_reader->readElementText().toDouble());
615 } else if (m_reader->name() == QStringLiteral("BaseSpeed")) {
616 speedInfo.baseSpeed = m_reader->readElementText().toDouble();
617 } else if (m_reader->name() == QStringLiteral("BaseTime")) {
618 speedInfo.baseTime = qRound(m_reader->readElementText().toDouble());
619 } else {
620 m_reader->skipCurrentElement();
621 }
622 }
623 m_reader->readNext();
624 }
625
626 return !m_reader->hasError();
627}
628
629QT_END_NAMESPACE
void parse(const QByteArray &data)
void run() override
Implement this pure virtual function in your subclass.