File indexing completed on 2024-05-12 04:42:34

0001 /*
0002     SPDX-FileCopyrightText: 2019 Volker Krause <vkrause@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "efaxmlparser.h"
0008 #include "efamodeoftransport.h"
0009 #include "logging.h"
0010 #include "scopedxmlstreamreader.h"
0011 #include "ifopt/ifoptutil.h"
0012 
0013 #include <KPublicTransport/Journey>
0014 #include <KPublicTransport/Location>
0015 #include <KPublicTransport/Stopover>
0016 
0017 #include <QDateTime>
0018 #include <QDebug>
0019 #include <QXmlStreamReader>
0020 
0021 using namespace KPublicTransport;
0022 
0023 void EfaXmlParser::parseLocationCommon(Location &loc, const ScopedXmlStreamReader &reader) const
0024 {
0025     if (reader.attributes().hasAttribute(QLatin1String("x")) && reader.attributes().hasAttribute(QLatin1String("y"))) {
0026         loc.setLatitude(reader.attributes().value(QLatin1String("y")).toDouble());
0027         loc.setLongitude(reader.attributes().value(QLatin1String("x")).toDouble());
0028     }
0029 
0030     // can be already set on loc, so don't reset it if missing here
0031     const auto locality = reader.attributes().value(QLatin1String("place")).toString();
0032     if (!locality.isEmpty()) {
0033         loc.setLocality(locality);
0034     }
0035 
0036     const auto id = reader.attributes().value(QLatin1String("stopID")).toString();
0037     if (!id.isEmpty()) {
0038         if (!isDummyStopId(id)) {
0039             loc.setIdentifier(m_locationIdentifierType, id);
0040         }
0041     } else {
0042         loc.setIdentifier(m_locationIdentifierType, reader.attributes().value(QLatin1String("stateless")).toString());
0043     }
0044 
0045     // check if IFOPT identifiers are present, in decreasing level of detail
0046     for (const auto &attr : {QLatin1String("pointGid"), QLatin1String("areaGid"), QLatin1String("gid")}) {
0047         const auto id = reader.attributes().value(attr);
0048         if (IfoptUtil::isValid(id)) {
0049             loc.setIdentifier(IfoptUtil::identifierType(), id.toString());
0050             break;
0051         }
0052     }
0053 
0054     const auto type = reader.attributes().value(QLatin1String("anyType"));
0055     if (type == QLatin1String("stop")) {
0056         loc.setType(Location::Stop);
0057     } else if (!type.isEmpty()) {
0058         qCDebug(Log) << "Unhandled EFA location type:" << type;
0059     } else if (!id.isEmpty() && !isDummyStopId(id)) {
0060         loc.setType(Location::Stop);
0061     }
0062 }
0063 
0064 Location EfaXmlParser::parseItdOdvAssignedStop(const ScopedXmlStreamReader &reader) const
0065 {
0066     Location loc;
0067     parseLocationCommon(loc, reader);
0068     loc.setName(reader.attributes().value(QLatin1String("nameWithPlace")).toString());
0069     loc.setType(Location::Stop);
0070     return loc;
0071 }
0072 
0073 Location EfaXmlParser::parseOdvNameElem(ScopedXmlStreamReader &reader) const
0074 {
0075     Location loc;
0076     parseLocationCommon(loc, reader);
0077     loc.setName(reader.readElementText(QXmlStreamReader::SkipChildElements));
0078     return loc;
0079 }
0080 
0081 std::vector<Location> EfaXmlParser::parseStopFinderResponse(const QByteArray &data)
0082 {
0083     std::vector<Location> res;
0084     QXmlStreamReader xsr(data);
0085     ScopedXmlStreamReader reader(xsr);
0086     while (reader.readNextElement()) {
0087         if (reader.name() == QLatin1String("itdOdvAssignedStop") && reader.attributes().hasAttribute(QLatin1String("stopID"))) {
0088             res.push_back(parseItdOdvAssignedStop(reader));
0089         } else if (reader.name() == QLatin1String("odvNameElem")) {
0090             if (reader.attributes().hasAttribute(QLatin1String("stopID"))) {
0091                 res.push_back(parseOdvNameElem(reader));
0092             } else if (reader.attributes().value(QLatin1String("anyType")) == QLatin1String("stop")
0093                 && reader.attributes().hasAttribute(QLatin1String("stateless")))
0094             {
0095                 res.push_back(parseOdvNameElem(reader));
0096             }
0097         }
0098     }
0099     return res;
0100 }
0101 
0102 static QDateTime parseDateTime(ScopedXmlStreamReader &&reader)
0103 {
0104     QDateTime dt;
0105     while (reader.readNextSibling()) {
0106         if (reader.name() == QLatin1String("itdDate")) {
0107             QDate d(
0108                 reader.attributes().value(QLatin1String("year")).toInt(),
0109                 reader.attributes().value(QLatin1String("month")).toInt(),
0110                 reader.attributes().value(QLatin1String("day")).toInt());
0111             dt.setDate(d);
0112         } else if (reader.name() == QLatin1String("itdTime")) {
0113             QTime t(
0114                 reader.attributes().value(QLatin1String("hour")).toInt(),
0115                 reader.attributes().value(QLatin1String("minute")).toInt(), 0);
0116             dt.setTime(t);
0117         }
0118     }
0119 
0120     return dt;
0121 }
0122 
0123 struct {
0124     const char *name;
0125     Load::Category category;
0126 } static constexpr const occupancy_load_map[] = {
0127     { "MANY_SEATS", Load::Low },
0128     { "FEW_SEATS", Load::Medium },
0129     { "STANDING_ONLY", Load::High },
0130 };
0131 
0132 Stopover EfaXmlParser::parseDmDeparture(ScopedXmlStreamReader &&reader) const
0133 {
0134     Stopover dep;
0135     dep.setScheduledPlatform(reader.attributes().value(QLatin1String("platformName")).toString());
0136 
0137     Location stop;
0138     const auto stopId = reader.attributes().value(QLatin1String("stopID")).toString();
0139     const auto stopIt = m_locations.find(stopId);
0140     if (stopIt != m_locations.end()) {
0141         stop = stopIt.value();
0142     }
0143 
0144     parseLocationCommon(stop, reader);
0145     stop.setName(reader.attributes().value(QLatin1String("stopName")).toString());
0146     stop.setType(Location::Stop);
0147     dep.setStopPoint(stop);
0148 
0149     const auto occupancy = reader.attributes().value(QLatin1String("occupancy"));
0150     if (!occupancy.isEmpty()) {
0151         LoadInfo load;
0152         for (const auto &map : occupancy_load_map) {
0153             if (occupancy == QLatin1String(map.name)) {
0154                 load.setLoad(map.category);
0155                 break;
0156             }
0157         }
0158         dep.setLoadInformation({load});
0159     }
0160 
0161     while (reader.readNextSibling()) {
0162         if (reader.name() == QLatin1String("itdServingLine")) {
0163             Line line;
0164             line.setName(reader.attributes().value(QLatin1String("number")).toString());
0165             line.setMode(EfaModeOfTransport::motTypeToLineMode(reader.attributes().value(QLatin1String("motType")).toInt()));
0166             Route route;
0167             route.setDirection(reader.attributes().value(QLatin1String("direction")).toString());
0168             route.setLine(line);
0169             dep.setRoute(route);
0170         } else if (reader.name() == QLatin1String("itdDateTime")) {
0171             dep.setScheduledDepartureTime(parseDateTime(reader.subReader()));
0172         } else if (reader.name() == QLatin1String("itdInfoLinkList")) {
0173             dep.addNotes(parseInfoLink(reader.subReader()));
0174         }
0175     }
0176 
0177     return dep;
0178 }
0179 
0180 std::vector<Stopover> EfaXmlParser::parseDmResponse(const QByteArray &data)
0181 {
0182     std::vector<Stopover> res;
0183     QXmlStreamReader xsr(data);
0184     ScopedXmlStreamReader reader(xsr);
0185     while (reader.readNextElement()) {
0186         if (reader.name() == QLatin1String("itdRequest")) {
0187             m_requestContext.sessionId = reader.attributes().value(QLatin1String("sessionID")).toString();
0188         } else if (reader.name() == QLatin1String("itdDepartureMonitorRequest")) {
0189             m_requestContext.requestId = reader.attributes().value(QLatin1String("requestID")).toString();
0190         } else if (reader.name() == QLatin1String("itdOdvAssignedStop") && reader.attributes().hasAttribute(QLatin1String("stopID"))) {
0191             const auto loc = parseItdOdvAssignedStop(reader);
0192             m_locations[loc.identifier(m_locationIdentifierType)] = loc;
0193         } else if (reader.name() == QLatin1String("itdDeparture")) {
0194             res.push_back(parseDmDeparture(reader.subReader()));
0195         }
0196     }
0197 
0198     return res;
0199 }
0200 
0201 void EfaXmlParser::parseTripDeparture(ScopedXmlStreamReader &&reader, JourneySection &section) const
0202 {
0203     Location loc;
0204     parseLocationCommon(loc, reader);
0205     loc.setName(reader.attributes().value(QLatin1String("name")).toString());
0206 
0207     section.setFrom(loc);
0208     // ### are those the correct ones? there's also just "platform"
0209     section.setScheduledDeparturePlatform(reader.attributes().value(QLatin1String("plannedPlatformName")).toString());
0210     section.setExpectedDeparturePlatform(reader.attributes().value(QLatin1String("platformName")).toString());
0211 
0212     while (reader.readNextSibling()) {
0213         if (reader.name() == QLatin1String("itdDateTime")) {
0214             section.setExpectedDepartureTime(parseDateTime(reader.subReader()));
0215         } else if (reader.name() == QLatin1String("itdDateTimeTarget")) {
0216             section.setScheduledDepartureTime(parseDateTime(reader.subReader()));
0217         }
0218     }
0219 }
0220 
0221 void EfaXmlParser::parseTripArrival(ScopedXmlStreamReader &&reader, JourneySection &section) const
0222 {
0223     Location loc;
0224     parseLocationCommon(loc, reader);
0225     loc.setName(reader.attributes().value(QLatin1String("name")).toString());
0226 
0227     section.setTo(loc);
0228     // ### are those the correct ones? there's also just "platform"
0229     section.setScheduledArrivalPlatform(reader.attributes().value(QLatin1String("plannedPlatformName")).toString());
0230     section.setExpectedArrivalPlatform(reader.attributes().value(QLatin1String("platformName")).toString());
0231 
0232     while (reader.readNextSibling()) {
0233         if (reader.name() == QLatin1String("itdDateTime")) {
0234             section.setExpectedArrivalTime(parseDateTime(reader.subReader()));
0235         } else if (reader.name() == QLatin1String("itdDateTimeTarget")) {
0236             section.setScheduledArrivalTime(parseDateTime(reader.subReader()));
0237         }
0238     }
0239 }
0240 
0241 Stopover EfaXmlParser::parsePartialTripIntermediateStop(ScopedXmlStreamReader &&reader) const
0242 {
0243     Location loc;
0244     parseLocationCommon(loc, reader);
0245     loc.setName(reader.attributes().value(QLatin1String("name")).toString());
0246 
0247     Stopover stop;
0248     stop.setStopPoint(loc);
0249     stop.setScheduledPlatform(reader.attributes().value(QLatin1String("platform")).toString());
0250 
0251     bool result = false;
0252     auto depDelay = reader.attributes().value(QLatin1String("depDelay")).toInt(&result);
0253     if (!result) {
0254         depDelay = -1;
0255     }
0256     // TODO there is also arrDelay - but what's the corresponding date/time for that?
0257 
0258     while (reader.readNextSibling()) {
0259         if (reader.name() == QLatin1String("itdDateTime")) {
0260             // TODO sometimes there are two itdDateTime elements here?
0261             // TODO sometimes there are invalid itdDateTime elements here?
0262             const auto dt = parseDateTime(reader.subReader());
0263             if (dt.isValid()) {
0264                 stop.setScheduledDepartureTime(dt);
0265 
0266                 if (depDelay >= 0) {
0267                     stop.setExpectedDepartureTime(dt.addSecs(60 * depDelay));
0268                 }
0269             }
0270         }
0271     }
0272 
0273     return stop;
0274 }
0275 
0276 std::vector<Stopover> EfaXmlParser::parsePartialTripStopSequence(ScopedXmlStreamReader &&reader) const
0277 {
0278     std::vector<Stopover> stops;
0279     while (reader.readNextSibling()) {
0280         if (reader.name() == QLatin1String("itdPoint")) {
0281             stops.push_back(parsePartialTripIntermediateStop(reader.subReader()));
0282         }
0283     }
0284 
0285     if (stops.size() >= 2) { // includes departure/arrival stops too
0286         stops.erase(std::prev(stops.end()));
0287         stops.erase(stops.begin());
0288     }
0289 
0290     return stops;
0291 }
0292 
0293 struct {
0294     int type;
0295     JourneySection::Mode mode;
0296 } static const journey_section_types[] = {
0297     { 97, JourneySection::Waiting }, // technically: "do not change"?
0298     { 98, JourneySection::Transfer },
0299     { 99, JourneySection::Walking },
0300     { 100, JourneySection::Walking },
0301 };
0302 
0303 std::vector<JourneySection> EfaXmlParser::parseTripPartialRoute(ScopedXmlStreamReader &&reader) const
0304 {
0305     std::vector<JourneySection> result;
0306     JourneySection section;
0307     if (reader.attributes().value(QLatin1String("type")) == QLatin1String("IT")) {
0308         section.setMode(JourneySection::Walking);
0309     }
0310 
0311     QPolygonF sectionPoly, transferPoly;
0312     std::vector<PathDescription> pathDesc;
0313     while (reader.readNextSibling()) {
0314         if (reader.name() == QLatin1String("itdPoint")) {
0315             const auto type = reader.attributes().value(QLatin1String("usage"));
0316             if (type == QLatin1String("departure")) {
0317                 parseTripDeparture(reader.subReader(), section);
0318             } else if (type == QLatin1String("arrival")) {
0319                 parseTripArrival(reader.subReader(), section);
0320             }
0321         } else if (reader.name() == QLatin1String("itdMeansOfTransport")) {
0322             const auto type = reader.attributes().value(QLatin1String("type")).toInt();
0323             for (const auto &m : journey_section_types) {
0324                 if (m.type == type) {
0325                     section.setMode(m.mode);
0326                     break;
0327                 }
0328             }
0329 
0330             if (type < 90) {
0331                 Line line;
0332                 line.setName(reader.attributes().value(QLatin1String("shortname")).toString());
0333                 const auto prodName = reader.attributes().value(QLatin1String("productName"));
0334                 if (prodName == QLatin1String("Fussweg")) {
0335                     section.setMode(JourneySection::Walking);
0336                 } else {
0337                     line.setModeString(prodName.toString());
0338                 }
0339                 line.setMode(EfaModeOfTransport::motTypeToLineMode(reader.attributes().value(QLatin1String("motType")).toInt()));
0340                 Route route;
0341                 route.setDirection(reader.attributes().value(QLatin1String("destination")).toString());
0342                 route.setLine(line);
0343                 section.setRoute(route);
0344                 section.setMode(JourneySection::PublicTransport);
0345             } else if (type > 100) {
0346                 const auto itMode = motTypeToIndividualTransportMode(type);
0347                 section.setIndividualTransport(itMode);
0348                 section.setMode(JourneySection::IndividualTransport);
0349             }
0350         } else if (reader.name() == QLatin1String("infoLink")) {
0351             section.addNotes(parseInfoLink(reader.subReader()));
0352         } else if (reader.name() == QLatin1String("itdInfoTextList")) {
0353             auto subReader = reader.subReader();
0354             while (subReader.readNextSibling()) {
0355                 if (subReader.name() == QLatin1String("infoTextListElem")) {
0356                     section.addNote(subReader.readElementText());
0357                 }
0358             }
0359         } else if (reader.name() == QLatin1String("itdStopSeq")) {
0360             section.setIntermediateStops(parsePartialTripStopSequence(reader.subReader()));
0361         } else if (reader.name() == QLatin1String("itdPathCoordinates")) {
0362             sectionPoly = parsePathCoordinates(reader.subReader());
0363         } else if (reader.name() == QLatin1String("itdInterchangePathCoordinates")) {
0364             auto subreader = reader.subReader();
0365             while (subreader.readNextSibling()) {
0366                 if (subreader.name() == QLatin1String("itdPathCoordinates")) {
0367                     transferPoly = parsePathCoordinates(subreader.subReader());
0368                 }
0369             }
0370         } else if (reader.name() == QLatin1String("itdITPathDescription") && !sectionPoly.isEmpty()) {
0371             auto subreader = reader.subReader();
0372             while (subreader.readNextSibling()) {
0373                 if (subreader.name() == QLatin1String("itdITPathDescriptionList")) {
0374                     pathDesc = parsePathDescriptionList(subreader.subReader());
0375                 }
0376             }
0377         }
0378     }
0379 
0380     if (!pathDesc.empty()) {
0381         resolvePathDescription(pathDesc);
0382         if (!transferPoly.empty() && section.mode() == JourneySection::PublicTransport) {
0383             // path description is actually for a subsequent transfer section
0384             const auto path = assemblePath(pathDesc, transferPoly);
0385             JourneySection transferSection;
0386             transferSection.setMode(JourneySection::Transfer);
0387             transferSection.setScheduledDepartureTime(section.scheduledArrivalTime());
0388             const auto travelTime = std::accumulate(pathDesc.begin(), pathDesc.end(), 0, [](auto accu, const auto &desc) {
0389                 return accu + desc.travelTime;
0390             });
0391             transferSection.setScheduledArrivalTime(section.scheduledArrivalTime().addSecs(travelTime));
0392             Location from;
0393             from.setLatitude(path.startPoint().y());
0394             from.setLongitude(path.startPoint().x());
0395             transferSection.setFrom(std::move(from));
0396             Location to;
0397             to.setLatitude(path.endPoint().y());
0398             to.setLongitude(path.endPoint().x());
0399             transferSection.setTo(std::move(to));
0400             transferSection.setPath(std::move(path));
0401             result.push_back(std::move(transferSection));
0402         } else if (section.mode() == JourneySection::Walking) {
0403             // path description is for this section
0404             section.setPath(assemblePath(pathDesc, sectionPoly));
0405         }
0406     }
0407     if (!sectionPoly.isEmpty() && section.path().isEmpty()) {
0408         section.setPath(polygonToPath(sectionPoly));
0409     }
0410 
0411     result.insert(result.begin(), std::move(section));
0412     return result;
0413 }
0414 
0415 Journey EfaXmlParser::parseTripRoute(ScopedXmlStreamReader &&reader) const
0416 {
0417     Journey journey;
0418     std::vector<JourneySection> sections;
0419 
0420     while (reader.readNextElement()) {
0421         if (reader.name() == QLatin1String("itdPartialRoute")) {
0422             auto sec = parseTripPartialRoute(reader.subReader());
0423             std::move(sec.begin(), sec.end(), std::back_inserter(sections));
0424         }
0425     }
0426 
0427     journey.setSections(std::move(sections));
0428     return journey;
0429 }
0430 
0431 std::vector<Journey> EfaXmlParser::parseTripResponse(const QByteArray &data)
0432 {
0433     //qDebug().noquote() << data;
0434     std::vector<Journey> res;
0435     QXmlStreamReader xsr(data);
0436     ScopedXmlStreamReader reader(xsr);
0437     while (reader.readNextElement()) {
0438         if (reader.name() == QLatin1String("itdRequest")) {
0439             m_requestContext.sessionId = reader.attributes().value(QLatin1String("sessionID")).toString();
0440         } else if (reader.name() == QLatin1String("itdTripRequest")) {
0441             m_requestContext.requestId = reader.attributes().value(QLatin1String("requestID")).toString();
0442         } else if (reader.name() == QLatin1String("itdRoute")) {
0443             res.push_back(parseTripRoute(reader.subReader()));
0444         }
0445     }
0446     return res;
0447 }
0448 
0449 QStringList EfaXmlParser::parseInfoLink(ScopedXmlStreamReader &&reader) const
0450 {
0451     QStringList l;
0452     while (reader.readNextElement()) {
0453         if (reader.name() == QLatin1String("infoLinkText") || reader.name() == QLatin1String("subtitle")
0454             || reader.name() == QLatin1String("wmlText") || reader.name() == QLatin1String("htmlText"))
0455         {
0456             l.push_back(reader.readElementText());
0457         }
0458     }
0459     return l;
0460 }
0461 
0462 QPolygonF EfaXmlParser::parsePathCoordinates(ScopedXmlStreamReader &&reader) const
0463 {
0464     QPolygonF poly;
0465     while (reader.readNextSibling()) {
0466         if (reader.name() == QLatin1String("itdCoordinateString")) {
0467             poly = parsePathCoordinatesElement(reader);
0468         }
0469     }
0470     return poly;
0471 }
0472 
0473 std::vector<EfaXmlParser::PathDescription> EfaXmlParser::parsePathDescriptionList(ScopedXmlStreamReader &&reader) const
0474 {
0475     std::vector<PathDescription> descs;
0476     while (reader.readNextSibling()) {
0477         if (reader.name() == QLatin1String("itdITPathDescriptionElem")) {
0478             PathDescription desc;
0479             auto elemReader = reader.subReader();
0480             while (elemReader.readNextSibling()) {
0481                 if (elemReader.name() == QLatin1String("itdCoord")) {
0482                     desc.point.setX(elemReader.attributes().value(QLatin1String("x")).toDouble());
0483                     desc.point.setY(elemReader.attributes().value(QLatin1String("y")).toDouble());
0484                 } else if (elemReader.name() == QLatin1String("fromPathCoordIdx")) {
0485                     desc.fromIndex = elemReader.readElementText().toInt();
0486                 } else if (elemReader.name() == QLatin1String("toPathCoordIdx")) {
0487                     desc.toIndex = elemReader.readElementText().toInt();
0488                 } else if (elemReader.name() == QLatin1String("streetname")) {
0489                     desc.description = elemReader.readElementText();
0490                 } else if (elemReader.name() == QLatin1String("traveltime")) {
0491                     desc.travelTime = elemReader.readElementText().toInt();
0492                 } else if (elemReader.name() == QLatin1String("niveau")) {
0493                     // seems to match OSM floor level, but is always "0" for elevators/escalators
0494                     desc.niveau = elemReader.readElementText().toInt();
0495                 } else if (elemReader.name() == QLatin1String("genAttrList")) {
0496                     const auto attrs = parseGenericAttributeList(elemReader.subReader());
0497                     const auto indoorType = attrs.value(QStringLiteral("INDOOR_TYPE"));
0498                     if (indoorType == QLatin1String("STAIRS")) {
0499                         desc.maneuver = PathSection::Stairs;
0500                     } else if (indoorType == QLatin1String("LIFT")) {
0501                         desc.maneuver = PathSection::Elevator;
0502                     } else if (indoorType == QLatin1String("ESCALATOR")) {
0503                         desc.maneuver = PathSection::Escalator;
0504                     }
0505 
0506                     bool floorLevelDifferenceValid = false;
0507                     const auto floorLevelDifference = attrs.value(QStringLiteral("FLOOR_LEVEL_DIFFERENCE")).toInt(&floorLevelDifferenceValid);
0508                     if (floorLevelDifferenceValid) {
0509                         desc.niveauDelta = floorLevelDifference;
0510                     }
0511                 }
0512                 // NOTE: skyDirection seems flipped by 180°, ie. pointing to the start point, should we ever need that
0513                 // turnDirection, turningManoeuvre, from/toPathLink??
0514             }
0515             descs.push_back(std::move(desc));
0516         }
0517     }
0518     return descs;
0519 }
0520 
0521 void EfaXmlParser::resolvePathDescription(std::vector<PathDescription> &descs) const
0522 {
0523     if (descs.size() < 3) {
0524         return;
0525     }
0526 
0527     for (auto it = std::next(descs.begin()); it != std::prev(descs.end()); ++it) {
0528         if ((*it).maneuver != PathSection::Stairs && (*it).maneuver != PathSection::Elevator && (*it).maneuver != PathSection::Escalator) {
0529             continue;
0530         }
0531         const auto niveauBefore = (*std::prev(it)).niveau;
0532         const auto niveauAfter = (*std::next(it)).niveau;
0533         if (niveauAfter != niveauBefore && (*it).niveauDelta == 0) {
0534             (*it).niveauDelta = niveauAfter - niveauBefore;
0535         }
0536     }
0537 }
0538 
0539 Path EfaXmlParser::assemblePath(const std::vector<PathDescription> &descs, const QPolygonF &poly) const
0540 {
0541     Path path;
0542     std::vector<PathSection> sections;
0543 
0544     for (const auto &desc : descs) {
0545         if (desc.fromIndex < 0 || desc.toIndex < 0 || desc.fromIndex >= poly.size() || desc.toIndex >= poly.size() || desc.toIndex < desc.fromIndex) {
0546             qCWarning(Log) << "weird polygon indexes?" << desc.fromIndex << desc.toIndex << poly.size();
0547             continue;
0548         }
0549         PathSection section;
0550         QPolygonF subPoly;
0551         subPoly.reserve(desc.toIndex - desc.fromIndex + 1);
0552         std::copy(poly.begin() + desc.fromIndex, poly.begin() + desc.toIndex + 1, std::back_inserter(subPoly));
0553         section.setPath(subPoly);
0554         section.setDescription(desc.description);
0555         section.setManeuver(desc.maneuver);
0556         section.setFloorLevelChange(desc.niveauDelta);
0557         sections.push_back(std::move(section));
0558     }
0559 
0560     path.setSections(std::move(sections));
0561     return path;
0562 }
0563 
0564 QHash<QString, QString> EfaXmlParser::parseGenericAttributeList(ScopedXmlStreamReader &&reader) const
0565 {
0566     return parseKeyValueList(std::move(reader), QLatin1String("genAttrElem"), QLatin1String("name"), QLatin1String("value"));
0567 }