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 §ion) 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 }