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

0001 /*
0002     SPDX-FileCopyrightText: 2019 Volker Krause <vkrause@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "efacompactparser.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 static void parseCompactCoordinate(const QString &text, Location &loc)
0024 {
0025     const auto coords = text.split(QLatin1Char(','));
0026     if (coords.size() == 2) {
0027         loc.setLatitude(coords[1].toFloat());
0028         loc.setLongitude(coords[0].toFloat());
0029     }
0030 }
0031 
0032 static void parseCompactIfopt(ScopedXmlStreamReader &reader, Location &loc)
0033 {
0034     if (reader.name() == QLatin1String("pgid")) {
0035         const auto id = reader.readElementText();
0036         if (IfoptUtil::isValid(id)) {
0037             loc.setIdentifier(IfoptUtil::identifierType(), id);
0038         }
0039     } else if (reader.name() == QLatin1String("gid")) {
0040         const auto id = reader.readElementText();
0041         if (IfoptUtil::isValid(id) && loc.identifier(IfoptUtil::identifierType()).isEmpty()) {
0042             loc.setIdentifier(IfoptUtil::identifierType(), id);
0043         }
0044     }
0045 }
0046 
0047 Location EfaCompactParser::parseCompactSf(ScopedXmlStreamReader &&reader) const
0048 {
0049     Location loc;
0050 
0051     while (reader.readNextSibling()) {
0052         if (reader.name() == QLatin1String("n")) {
0053             loc.setName(reader.readElementText());
0054         } else if (reader.name() == QLatin1String("ty")) {
0055             const auto type = reader.readElementText();
0056             if (type == QLatin1String("stop")) {
0057                 loc.setType(Location::Stop);
0058             } else {
0059                 qCDebug(Log) << "unhandled location type:" << type;
0060             }
0061         } else if (reader.name() == QLatin1String("r")) {
0062             auto sub = reader.subReader();
0063             while (sub.readNextSibling()) {
0064                 if (sub.name() == QLatin1String("c")) {
0065                     parseCompactCoordinate(sub.readElementText(), loc);
0066                 } else if (sub.name() == QLatin1String("id")) {
0067                     const auto id = sub.readElementText();
0068                     if (!id.isEmpty() && !isDummyStopId(id)) {
0069                         loc.setIdentifier(m_locationIdentifierType, id);
0070                     }
0071                 } else if (sub.name() == QLatin1String("pc")) {
0072                     loc.setLocality(sub.readElementText());
0073                 } else {
0074                     parseCompactIfopt(sub, loc);
0075                 }
0076             }
0077         }
0078     }
0079 
0080     return loc;
0081 }
0082 
0083 std::vector<Location> EfaCompactParser::parseStopFinderResponse(const QByteArray &data)
0084 {
0085     std::vector<Location> res;
0086     QXmlStreamReader xsr(data);
0087     ScopedXmlStreamReader reader(xsr);
0088     while (reader.readNextElement()) {
0089         if (reader.name() == QLatin1String("p")) {
0090             const auto l = parseCompactSf(reader.subReader());
0091             if (!l.isEmpty()) {
0092                 res.push_back(l);
0093             }
0094         }
0095     }
0096     return res;
0097 }
0098 
0099 static std::pair<QDateTime, QDateTime> parseCompactTimePair(ScopedXmlStreamReader &&reader)
0100 {
0101     QDateTime scheduledDt, expectedDt;
0102 
0103     while (reader.readNextSibling()) {
0104         if (reader.name() == QLatin1String("da")) {
0105             scheduledDt.setDate(QDate::fromString(reader.readElementText(), QStringLiteral("yyyyMMdd")));
0106         } else if (reader.name() == QLatin1String("t")) {
0107             scheduledDt.setTime(QTime::fromString(reader.readElementText(), QStringLiteral("hhmm")));
0108         } else if (reader.name() == QLatin1String("rda")) {
0109             expectedDt.setDate(QDate::fromString(reader.readElementText(), QStringLiteral("yyyyMMdd")));
0110         } else if (reader.name() == QLatin1String("rt")) {
0111             expectedDt.setTime(QTime::fromString(reader.readElementText(), QStringLiteral("hhmm")));
0112         }
0113     }
0114 
0115     return std::make_pair(scheduledDt, expectedDt);
0116 }
0117 
0118 QStringList EfaCompactParser::parseNotes(ScopedXmlStreamReader &&reader) const
0119 {
0120     QStringList ns;
0121     while (reader.readNextElement()) {
0122         if (reader.name() == QLatin1String("tx")) {
0123             ns.push_back(reader.readElementText());
0124         }
0125     }
0126     return ns;
0127 }
0128 
0129 Stopover EfaCompactParser::parseCompactDp(ScopedXmlStreamReader &&reader) const
0130 {
0131     Stopover dep;
0132     Location loc;
0133     bool clearRealtime = false;
0134     while (reader.readNextSibling()) {
0135         if (reader.name() == QLatin1String("n")) {
0136             loc.setName(reader.readElementText());
0137         } else if (reader.name() == QLatin1String("realtime")) {
0138             clearRealtime = reader.readElementText() == QLatin1String("0");
0139         } else if (reader.name() == QLatin1String("rts")) {
0140             const auto rts = reader.readElementText();
0141             if (rts == QLatin1String("TRIP_CANCELLED")) {
0142                 dep.setDisruptionEffect(Disruption::NoService);
0143             }
0144         } else if (reader.name() == QLatin1String("st")) {
0145             const auto st = parseCompactTimePair(reader.subReader());
0146             dep.setScheduledDepartureTime(st.first);
0147             dep.setExpectedDepartureTime(st.second);
0148         } else if (reader.name() == QLatin1String("m")) {
0149             Route route;
0150             Line line;
0151             auto subReader = reader.subReader();
0152             while (subReader.readNextSibling()) {
0153                 if (subReader.name() == QLatin1String("des")) {
0154                     route.setDirection(subReader.readElementText());
0155                 } else if (subReader.name() == QLatin1String("nu")) {
0156                     line.setName(subReader.readElementText());
0157                 } else if (subReader.name() == QLatin1String("n")) {
0158                     line.setModeString(subReader.readElementText());
0159                 } else if (subReader.name() == QLatin1String("ty")) {
0160                     line.setMode(EfaModeOfTransport::motTypeToLineMode(subReader.readElementText().toInt()));
0161                 }
0162             }
0163             route.setLine(line);
0164             dep.setRoute(route);
0165         } else if (reader.name() == QLatin1String("r")) {
0166             auto subReader = reader.subReader();
0167             while (subReader.readNextSibling()) {
0168                 if (subReader.name() == QLatin1String("id")) {
0169                     const auto id = subReader.readElementText();
0170                     if (!id.isEmpty() && !isDummyStopId(id)) {
0171                         loc.setIdentifier(m_locationIdentifierType, id);
0172                         loc.setType(Location::Stop);
0173                     }
0174                 } else if (subReader.name() == QLatin1String("pl")) {
0175                     dep.setScheduledPlatform(subReader.readElementText());
0176                 }
0177             }
0178         } else if (reader.name() == QLatin1String("c")) {
0179             parseCompactCoordinate(reader.readElementText(), loc);
0180         } else if (reader.name() == QLatin1String("ns")) {
0181             dep.setNotes(parseNotes(reader.subReader()));
0182         } else {
0183             parseCompactIfopt(reader, loc);
0184         }
0185     }
0186 
0187     dep.setStopPoint(loc);
0188     if (clearRealtime) {
0189         dep.setExpectedDepartureTime({});
0190     }
0191     return dep;
0192 }
0193 
0194 std::vector<Stopover> EfaCompactParser::parseDmResponse(const QByteArray &data)
0195 {
0196     std::vector<Stopover> res;
0197     QXmlStreamReader xsr(data);
0198     ScopedXmlStreamReader reader(xsr);
0199     while (reader.readNextElement()) {
0200         if (reader.name() == QLatin1String("pas")) {
0201             const auto pas = parseKeyValueList(reader.subReader(), QLatin1String("pa"), QLatin1String("n"), QLatin1String("v"));
0202             m_requestContext.sessionId = pas.value(QStringLiteral("sessionID"));
0203             m_requestContext.requestId = pas.value(QStringLiteral("requestID"));
0204         } else if (reader.name() == QLatin1String("dp")) {
0205             res.push_back(parseCompactDp(reader.subReader()));
0206         }
0207     }
0208 
0209     return res;
0210 }
0211 
0212 void EfaCompactParser::parseTripSectionHalf(ScopedXmlStreamReader &&reader, JourneySection &section) const
0213 {
0214     Location loc;
0215     std::pair<QDateTime, QDateTime> dts;
0216     QString platform;
0217 
0218     bool isArr = true;
0219     while (reader.readNextSibling()) {
0220         if (reader.name() == QLatin1String("n") && loc.name().isEmpty()) {
0221             loc.setName(reader.readElementText());
0222         } else if (reader.name() == QLatin1String("de")) {
0223             loc.setName(reader.readElementText());
0224         } else if (reader.name() == QLatin1String("u")) {
0225             isArr = reader.readElementText() == QLatin1String("arrival");
0226         } else if (reader.name() == QLatin1String("st")) {
0227             dts = parseCompactTimePair(reader.subReader());
0228         } else if (reader.name() == QLatin1String("r")) {
0229             ScopedXmlStreamReader subReader(reader.subReader());
0230             while (subReader.readNextSibling()) {
0231                 if (subReader.name() == QLatin1String("id")) {
0232                     const auto id = subReader.readElementText();
0233                     if (!id.isEmpty() && !isDummyStopId(id)) {
0234                         loc.setIdentifier(m_locationIdentifierType, id);
0235                         loc.setType(Location::Stop);
0236                     }
0237                 } else if (subReader.name() == QLatin1String("pc")) {
0238                     loc.setLocality(subReader.readElementText());
0239                 } else if (subReader.name() == QLatin1String("pl") || subReader.name() == QLatin1String("divaPl")) {
0240                     platform = subReader.readElementText();
0241                 } else if (subReader.name() == QLatin1String("c")) {
0242                     parseCompactCoordinate(subReader.readElementText(), loc);
0243                 }
0244             }
0245         } else {
0246             parseCompactIfopt(reader, loc);
0247         }
0248     }
0249 
0250     if (isArr) {
0251         section.setTo(loc);
0252         section.setScheduledArrivalTime(dts.first);
0253         section.setExpectedArrivalTime(dts.second);
0254         section.setScheduledArrivalPlatform(platform);
0255     } else {
0256         section.setFrom(loc);
0257         section.setScheduledDepartureTime(dts.first);
0258         section.setExpectedDepartureTime(dts.second);
0259         section.setScheduledDeparturePlatform(platform);
0260     }
0261 }
0262 
0263 JourneySection EfaCompactParser::parseTripSection(ScopedXmlStreamReader &&reader) const
0264 {
0265     JourneySection section;
0266     section.setMode(JourneySection::PublicTransport);
0267 
0268     while (reader.readNextElement()) {
0269         if (reader.name() == QLatin1String("p")) {
0270             parseTripSectionHalf(reader.subReader(), section);
0271         } else if (reader.name() == QLatin1String("m")) {
0272             Route route;
0273             Line line;
0274             auto subReader = reader.subReader();
0275             while (subReader.readNextSibling()) {
0276                 if (subReader.name() == QLatin1String("des")) {
0277                     route.setDirection(subReader.readElementText());
0278                 } else if (subReader.name() == QLatin1String("nu")) {
0279                     line.setName(subReader.readElementText());
0280                 } else if (subReader.name() == QLatin1String("n")) {
0281                     line.setModeString(subReader.readElementText());
0282                 } else if (subReader.name() == QLatin1String("ty")) {
0283                     const auto secType = subReader.readElementText().toInt();
0284                     switch (secType) {
0285                         case 99:
0286                         case 100:
0287                             section.setMode(JourneySection::Walking);
0288                             break;
0289                         case 98:
0290                         case 105:
0291                             section.setMode(JourneySection::Transfer);
0292                             break;
0293                     }
0294                 } else if (subReader.name() == QLatin1String("co")) {
0295                     line.setMode(EfaModeOfTransport::motTypeToLineMode(subReader.readElementText().toInt()));
0296                 }
0297             }
0298 
0299             // fix messed up mode name values
0300             if (line.modeString().startsWith(line.name() + QLatin1Char(' '))) {
0301                 line.setModeString(line.modeString().mid(line.name().size() + 1));
0302             } else if (line.modeString().endsWith(QLatin1Char(' ') + line.name())) {
0303                 line.setModeString(line.modeString().left(line.modeString().size() - line.name().size() - 1));
0304             }
0305 
0306             if (section.mode() == JourneySection::PublicTransport) {
0307                 route.setLine(line);
0308                 section.setRoute(route);
0309             }
0310         } else if (reader.name() == QLatin1String("ns")) {
0311             section.setNotes(parseNotes(reader.subReader()));
0312         } else if (reader.name() == QLatin1String("realtime")) {
0313             if (reader.readElementText() == QLatin1String("0")) { // expected time fields are set in all cases
0314                 section.setExpectedArrivalTime({});
0315                 section.setExpectedDepartureTime({});
0316             }
0317         } else if (reader.name() == QLatin1String("pss")) {
0318             std::vector<Stopover> stops;
0319             auto subReader = reader.subReader();
0320             while (subReader.readNextSibling()) {
0321                 if (subReader.name() == QLatin1String("s")) {
0322                     const auto stopParams = subReader.readElementText().split(QLatin1Char(';'));
0323                     if (stopParams.size() <= 13) {
0324                         continue;
0325                     }
0326 
0327                     // semicolon separated list with the following content
0328                     // 0: station id
0329                     // 1: station name
0330                     // 2: date as yyyyMMdd
0331                     // 3: time as hhmm
0332                     // 4: location coordinate, colon separated as <lon>:<lat>:WGS84[DD.ddddd]
0333                     // 5: delay in minutes, presumably for fields 2/3
0334                     // 6: integer of unknown meaning - same as <a>
0335                     // 7: platform - same as <divaPl>
0336                     // 8: date as yyyyMMdd - only set on departure?
0337                     // 9: time as hhmm - only set on departure?
0338                     // 10: delay in minutes, presumably for fields 8/9
0339                     // 11: integer of unknown meaning
0340                     // 12: IFOPT station id (see also <gid>)
0341                     // 13: IFOPT stop/platform id (see also <pgid>)
0342 
0343                     Location loc;
0344                     loc.setIdentifier(m_locationIdentifierType, stopParams[0]);
0345                     loc.setName(stopParams[1]);
0346 
0347                     const auto coord = stopParams[4].split(QLatin1Char(':'));
0348                     if (coord.size() < 2) {
0349                         continue;
0350                     }
0351                     loc.setCoordinate(coord[1].toFloat(), coord[0].toFloat());
0352 
0353                     if (IfoptUtil::isValid(stopParams[13])) {
0354                         loc.setIdentifier(IfoptUtil::identifierType(), stopParams[13]);
0355                     } else if (IfoptUtil::isValid(stopParams[12])) {
0356                         loc.setIdentifier(IfoptUtil::identifierType(), stopParams[12]);
0357                     }
0358 
0359                     const auto dt = QDateTime::fromString(stopParams[2] + stopParams[3], QStringLiteral("yyyyMMddhhmm"));
0360                     if (!dt.isValid()) {
0361                         continue;
0362                     }
0363                     bool result = false;
0364                     auto delay = stopParams[5].toInt(&result);
0365                     if (!result) {
0366                         delay = -1;
0367                     }
0368 
0369                     Stopover stop;
0370                     stop.setStopPoint(loc);
0371                     stop.setScheduledPlatform(stopParams[7]);
0372                     stop.setScheduledDepartureTime(dt);
0373                     if (delay >= 0) {
0374                         stop.setExpectedDepartureTime(dt.addSecs(delay * 60));
0375                     }
0376 
0377                     stops.push_back(stop);
0378                 }
0379             }
0380 
0381             if (stops.size() >= 2) { // exclude departure/arrival stops
0382                 stops.erase(std::prev(stops.end()));
0383                 stops.erase(stops.begin());
0384             }
0385             section.setIntermediateStops(std::move(stops));
0386         } else if (reader.name() == QLatin1String("pt")) {
0387             section.setPath(polygonToPath(parsePathCoordinatesElement(reader)));
0388         }
0389         // TODO interchange tag - should we turn this into transfer sections?
0390     }
0391     return section;
0392 }
0393 
0394 Journey EfaCompactParser::parseCompactTp(ScopedXmlStreamReader &&reader) const
0395 {
0396     Journey jny;
0397     std::vector<JourneySection> sections;
0398 
0399     while (reader.readNextElement()) {
0400         if (reader.name() == QLatin1String("l")) {
0401             sections.push_back(parseTripSection(reader.subReader()));
0402         }
0403     }
0404 
0405     jny.setSections(std::move(sections));
0406     return jny;
0407 }
0408 
0409 std::vector<Journey> EfaCompactParser::parseTripResponse(const QByteArray &data)
0410 {
0411     //qDebug().noquote() << data;
0412     std::vector<Journey> res;
0413     QXmlStreamReader xsr(data);
0414     ScopedXmlStreamReader reader(xsr);
0415     while (reader.readNextElement()) {
0416         if (reader.name() == QLatin1String("pas")) {
0417             const auto pas = parseKeyValueList(reader.subReader(), QLatin1String("pa"), QLatin1String("n"), QLatin1String("v"));
0418             m_requestContext.sessionId = pas.value(QStringLiteral("sessionID"));
0419             m_requestContext.requestId = pas.value(QStringLiteral("requestID"));
0420         } else if (reader.name() == QLatin1String("tp")) {
0421             res.push_back(parseCompactTp(reader.subReader()));
0422         }
0423     }
0424 
0425     return res;
0426 }