KItinerary

uic9183parser.cpp
1/*
2 SPDX-FileCopyrightText: 2018 Volker Krause <vkrause@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include "uic9183parser.h"
8#include "logging.h"
9#include "rct2ticket.h"
10#include "uic9183block.h"
11#include "uic9183head.h"
12#include "uic9183header.h"
13#include "uic9183ticketlayout.h"
14#include "vendor0080block.h"
15#include "vendor1154block.h"
16
17#include "era/fcbticket.h"
18#include "era/fcbutil.h"
19
20#include <QDateTime>
21#include <QDebug>
22#include <QJsonDocument>
23#include <QJsonObject>
24#include <QTimeZone>
25
26#include <zlib.h>
27
28#include <cassert>
29#include <cstring>
30
31using namespace KItinerary;
32
33namespace KItinerary {
34
35class Uic9183ParserPrivate : public QSharedData
36{
37public:
38 QByteArray m_data;
39 QByteArray m_payload;
40};
41}
42
43Uic9183Parser::Uic9183Parser()
44 : d(new Uic9183ParserPrivate)
45{
46}
47
48Uic9183Parser::Uic9183Parser(const Uic9183Parser&) = default;
49Uic9183Parser::~Uic9183Parser() = default;
50Uic9183Parser& Uic9183Parser::operator=(const Uic9183Parser&) = default;
51
53{
54 return Uic9183Block(d->m_payload, 0);
55}
56
57Uic9183Block Uic9183Parser::findBlock(const char name[6]) const
58{
59 for (auto block = firstBlock(); !block.isNull(); block = block.nextBlock()) {
60 if (block.isA(name)) {
61 return block;
62 }
63 }
64 return {};
65}
66
68{
69 if (name.size() != 6 || d->m_payload.isEmpty()) {
70 return {};
71 }
72
73#define BLOCK_FROM_NAME(Type) \
74 if (name == QLatin1StringView(Type::RecordId)) { \
75 const auto block = findBlock<Type>(); \
76 return block.isValid() ? QVariant::fromValue(block) : QVariant(); \
77 }
78
79 BLOCK_FROM_NAME(Uic9183Head)
80 BLOCK_FROM_NAME(Uic9183TicketLayout)
81 BLOCK_FROM_NAME(Fcb::UicRailTicketData)
82 BLOCK_FROM_NAME(Vendor0080BLBlock)
83 BLOCK_FROM_NAME(Vendor0080VUBlock)
84 BLOCK_FROM_NAME(Vendor1154UTBlock)
85
86#undef BLOCK_FROM_NAME
87
88 return QVariant::fromValue(findBlock(name.toUtf8().constData()));
89}
90
94
95void Uic9183Parser::parse(const QByteArray &data)
96{
97 d->m_data.clear();
98 d->m_payload.clear();
99
100 Uic9183Header header(data);
101 if (!header.isValid()) {
102 return;
103 }
104
105 // nx zlib payload
106 d->m_data = data;
107 d->m_payload.resize(4096);
108 z_stream stream;
109 stream.zalloc = nullptr;
110 stream.zfree = nullptr;
111 stream.opaque = nullptr;
112 stream.avail_in = data.size() - header.compressedMessageOffset();
113 stream.next_in = reinterpret_cast<unsigned char*>(const_cast<char*>(data.data() + header.compressedMessageOffset()));
114 stream.avail_out = d->m_payload.size();
115 stream.next_out = reinterpret_cast<unsigned char*>(d->m_payload.data());
116
117 inflateInit(&stream);
118 const auto res = inflate(&stream, Z_NO_FLUSH);
119 switch (res) {
120 case Z_OK:
121 case Z_STREAM_END:
122 break; // all good
123 default:
124 qCWarning(Log) << "UIC 918.3 payload zlib decompression failed" << stream.msg;
125 return;
126 }
127 inflateEnd(&stream);
128 d->m_payload.truncate(d->m_payload.size() - stream.avail_out);
129 //qCDebug(Log) << res << d->m_payload << stream.avail_out;
130
131 // workaround for Renfe (1071) having various errors...
132 if (d->m_payload.size() > 600 && d->m_payload.startsWith("U_HEAD0100531071") && d->m_payload[54] == 'U' && d->m_payload[36] == ' ') {
133 qCWarning(Log) << "Applying Renfe workaround for broken UIC 913.8 content...";
134 d->m_payload.remove(36, 1); // off by one in U_HEAD
135 const auto idx = d->m_payload.indexOf("U_TLAY00");
136 if (idx < d->m_payload.size() + 400 && std::strncmp(d->m_payload.constData() + idx + 12, "RCT2", 4) != 0) {
137 d->m_payload.insert(idx + 7, "1"); // wrong U_TLAY version
138 d->m_payload.replace(idx + 12, 4, "RCT2"); // wrong layout type
139 d->m_payload.remove(idx + 20, 1); // garbage trailing the layout type?
140 qCDebug(Log) << d->m_payload;
141 }
142 }
143}
144
145bool Uic9183Parser::isValid() const
146{
147 return !d->m_payload.isEmpty();
148}
149
150template <typename T>
151static QString fcbReference(const T &data)
152{
153 if (!data.referenceIA5.isEmpty()) {
154 return QString::fromLatin1(data.referenceIA5);
155 }
156 if (data.referenceNumIsSet()) {
157 return QString::number(data.referenceNum);
158 }
159 return {};
160}
161
162QString Uic9183Parser::pnr() const
163{
164 if (const auto head = findBlock<Uic9183Head>(); head.isValid()) {
165 const auto key = head.ticketKey().trimmed();
166 const auto issuerId = head.issuerCompanyCodeNumeric();
167
168 // try to make this match what's printed on the matching tickets...
169 if (issuerId == 80 && (key.size() == 8 || key.size() == 9) && key.at(6) == QLatin1Char('-') && key.at(7).isDigit()) {
170 return key.left(6); // DB domestic
171 }
172 if (issuerId == 80 && key.size() == 13 &&
173 key.endsWith(QLatin1StringView("0101"))) {
174 return key.left(9); // DB domestic part of an international order
175 }
176 if ((issuerId == 1088 || issuerId == 1184) && key.size() == 9 && key.at(7) == QLatin1Char('_') && key.at(8).isDigit()) {
177 return key.left(7); // SNCB and NS
178 }
179
180 return key;
181 }
182
183 if (const auto fcb = findBlock<Fcb::UicRailTicketData>(); fcb.isValid()) {
184 if (!fcb.issuingDetail.issuerPNR.isEmpty()) {
185 return QString::fromLatin1(fcb.issuingDetail.issuerPNR);
186 }
187 if (!fcb.transportDocument.isEmpty()) {
188 const auto doc = fcb.transportDocument.at(0);
189 QString pnr;
190 if (doc.ticket.userType() == qMetaTypeId<Fcb::ReservationData>()) {
191 pnr = fcbReference(doc.ticket.value<Fcb::ReservationData>());
192 } else if (doc.ticket.userType() == qMetaTypeId<Fcb::OpenTicketData>()) {
193 pnr = fcbReference(doc.ticket.value<Fcb::OpenTicketData>());
194 } else if (doc.ticket.userType() == qMetaTypeId<Fcb::PassData>()) {
195 pnr = fcbReference(doc.ticket.value<Fcb::PassData>());
196 }
197 if (!pnr.isEmpty()) {
198 return pnr;
199 }
200 }
201 }
202
203 return {};
204}
205
206template <typename T>
207static QString fcbTariffName(const T &data)
208{
209 if (data.tariffs.isEmpty()) {
210 return {};
211 }
212 return data.tariffs.at(0).tariffDesc;
213}
214
215QString Uic9183Parser::name() const
216{
217 // ERA FCB
218 if (const auto fcb = findBlock<Fcb::UicRailTicketData>(); fcb.isValid() && !fcb.transportDocument.isEmpty()) {
219 const auto doc = fcb.transportDocument.at(0);
220 QString name;
221 if (doc.ticket.userType() == qMetaTypeId<Fcb::ReservationData>()) {
222 name = fcbTariffName(doc.ticket.value<Fcb::ReservationData>());
223 } else if (doc.ticket.userType() == qMetaTypeId<Fcb::OpenTicketData>()) {
224 name = fcbTariffName(doc.ticket.value<Fcb::OpenTicketData>());
225 } else if (doc.ticket.userType() == qMetaTypeId<Fcb::PassData>()) {
226 name = fcbTariffName(doc.ticket.value<Fcb::PassData>());
227 }
228 if (!name.isEmpty()) {
229 return name;
230 }
231 }
232
233 // DB vendor block
234 if (const auto b = findBlock<Vendor0080BLBlock>(); b.isValid()) {
235 const auto sblock = b.findSubBlock("001");
236 if (!sblock.isNull()) {
237 return QString::fromUtf8(sblock.content(), sblock.contentSize());
238 }
239 }
240
241 // RCT2
242 if (const auto rct2 = rct2Ticket(); rct2.isValid()) {
243 return rct2.title();
244 }
245
246 return {};
247}
248
249QString Uic9183Parser::carrierId() const
250{
251 if (const auto head = findBlock<Uic9183Head>(); head.isValid()) {
252 return head.issuerCompanyCodeString();
253 }
254 if (const auto fcb = findBlock<Fcb::UicRailTicketData>(); fcb.isValid()) {
255 const auto issue = fcb.issuingDetail;
256 if (issue.issuerNumIsSet()) {
257 return QString::number(issue.issuerNum);
258 }
259 if (issue.issuerIA5IsSet()) {
260 return QString::fromLatin1(issue.issuerIA5);
261 }
262 }
263 return header().signerCompanyCode();
264}
265
266Organization Uic9183Parser::issuer() const
267{
268 Organization issuer;
269 issuer.setIdentifier(QLatin1StringView("uic:") + carrierId());
270 if (const auto fcb = findBlock<Fcb::UicRailTicketData>(); fcb.isValid() && fcb.issuingDetail.issuerNameIsSet()) {
271 issuer.setName(fcb.issuingDetail.issuerName);
272 }
273 return issuer;
274}
275
276QDateTime Uic9183Parser::validFrom() const
277{
278 // ERA FCB
279 if (const auto fcb = findBlock<Fcb::UicRailTicketData>(); fcb.isValid() && !fcb.transportDocument.isEmpty()) {
280 const auto issue = fcb.issuingDetail.issueingDateTime();
281 const auto doc = fcb.transportDocument.at(0).ticket;
282 if (doc.userType() == qMetaTypeId<Fcb::ReservationData>()) {
283 return doc.value<Fcb::ReservationData>().departureDateTime(issue);
284 }
285 if (doc.userType() == qMetaTypeId<Fcb::OpenTicketData>()) {
286 return doc.value<Fcb::OpenTicketData>().validFrom(issue);
287 }
288 if (doc.userType() == qMetaTypeId<Fcb::PassData>()) {
289 return doc.value<Fcb::PassData>().validFrom(issue);
290 }
291 }
292
293 // DB vendor block
294 if (const auto b = findBlock<Vendor0080BLBlock>(); b.isValid() && b.orderBlockCount() == 1) {
295 return QDateTime(b.orderBlock(0).validFrom(), {0, 0, 0});
296 }
297
298 // ÖBB vender block
299 if (const auto b = findBlock("118199"); !b.isNull()) {
300 const auto obj = QJsonDocument::fromJson(QByteArray::fromRawData(b.content(), b.contentSize())).object();
301 auto dt =
302 QDateTime::fromString(obj.value(QLatin1StringView("V")).toString(),
303 QStringLiteral("yyMMddhhmm"));
304 if (dt.isValid()) { // ÖBB VorteilsCard barcodes have an empty vendor block
305 if (dt.date().year() < 2000) {
306 dt = dt.addYears(100);
307 }
308 dt.setTimeZone(QTimeZone::utc());
309 return dt;
310 }
311 }
312
313 // CD vender block
314 if (const auto b = findBlock<Vendor1154UTBlock>(); b.isValid()) {
315 const auto subBlock = b.findSubBlock("OD");
316 qDebug() << subBlock.toString();
317 if (!subBlock.isNull()) {
318 return QDateTime::fromString(subBlock.toString(), QStringLiteral("dd.MM.yyyy hh:mm"));
319 }
320 }
321
322 // RCT2
323 if (const auto rct2 = rct2Ticket(); rct2.isValid()) {
324 const auto dt = rct2.firstDayOfValidity();
325 if (dt.month() != 1 || dt.day() != 1 || !rct2.outboundDepartureStation().isEmpty()) {
326 return QDateTime(dt, {0, 0, 0});
327 }
328 // firstDayOfValidity is just a year, and we have wildcard station names
329 const auto dep = rct2.outboundDepartureTime();
330 return dep.isValid() ? dep : QDateTime(dt, {0, 0, 0});
331 }
332
333 return {};
334}
335
336QDateTime Uic9183Parser::validUntil() const
337{
338 // ERA FCB
339 if (const auto fcb = findBlock<Fcb::UicRailTicketData>(); fcb.isValid() && !fcb.transportDocument.isEmpty()) {
340 const auto issue = fcb.issuingDetail.issueingDateTime();
341 const auto doc = fcb.transportDocument.at(0).ticket;
342 if (doc.userType() == qMetaTypeId<Fcb::ReservationData>()) {
343 return doc.value<Fcb::ReservationData>().arrivalDateTime(issue);
344 }
345 if (doc.userType() == qMetaTypeId<Fcb::OpenTicketData>()) {
346 return doc.value<Fcb::OpenTicketData>().validUntil(issue);
347 }
348 if (doc.userType() == qMetaTypeId<Fcb::PassData>()) {
349 return doc.value<Fcb::PassData>().validUntil(issue);
350 }
351 }
352
353 // DB vendor block
354 if (const auto b = findBlock<Vendor0080BLBlock>(); b.isValid() && b.orderBlockCount() == 1) {
355 return QDateTime(b.orderBlock(0).validTo(), {23, 59, 59});
356 }
357
358 // ÖBB vender block
359 if (const auto b = findBlock("118199"); !b.isNull()) {
360 const auto obj = QJsonDocument::fromJson(QByteArray::fromRawData(b.content(), b.contentSize())).object();
361 auto dt =
362 QDateTime::fromString(obj.value(QLatin1StringView("B")).toString(),
363 QStringLiteral("yyMMddhhmm"));
364 if (dt.isValid()) { // ÖBB VorteilsCard barcodes have an empty vendor block
365 if (dt.date().year() < 2000) {
366 dt = dt.addYears(100);
367 }
368 dt.setTimeZone(QTimeZone::utc());
369 return dt;
370 }
371 }
372
373 // CD vender block
374 if (const auto b = findBlock<Vendor1154UTBlock>(); b.isValid()) {
375 const auto subBlock = b.findSubBlock("DO");
376 if (!subBlock.isNull()) {
377 return QDateTime::fromString(subBlock.toString(), QStringLiteral("dd.MM.yyyy hh:mm"));
378 }
379 }
380
381
382 // RCT2 RPT according to ERA TAP TSI Annex B.6
383 if (const auto rct2 = rct2Ticket(); rct2.isValid()) {
384 const auto validityRange = ticketLayout().text(3, 1, 36, 1).trimmed();
385 const auto idx = std::max(validityRange.lastIndexOf(QLatin1Char(' ')), validityRange.lastIndexOf(QLatin1Char('-')));
386 if (idx > 0) {
387 return QDateTime(QDate::fromString(validityRange.mid(idx + 1), QStringLiteral("dd.MM.yyyy")), {23, 59, 59});
388 }
389 return rct2.outboundArrivalTime();
390 }
391
392 return {};
393}
394
395Person Uic9183Parser::person() const
396{
397 // ERA FCB
398 if (const auto fcb = findBlock<Fcb::UicRailTicketData>(); fcb.isValid() && fcb.travelerDetailIsSet() && fcb.travelerDetail.traveler.size() == 1) {
399 const auto traveler = fcb.travelerDetail.traveler.at(0);
400 Person p;
401 if (traveler.firstNameIsSet() || traveler.secondNameIsSet()) {
402 p.setGivenName(QString(traveler.firstName + QLatin1Char(' ') + traveler.secondName).trimmed());
403 }
404 p.setFamilyName(traveler.lastName);
405 if (traveler.firstNameIsSet() || traveler.lastNameIsSet()) {
406 return p;
407 }
408 }
409
410 // Deutsche Bahn vendor block
411 if (const auto b = findBlock<Vendor0080BLBlock>(); b.isValid()) {
412 // S028 contains family and given name separated by a '#', UTF-8 encoded
413 auto sblock = b.findSubBlock("028");
414 if (!sblock.isNull()) {
415 const auto endIt = sblock.content() + sblock.contentSize();
416 auto it = std::find(sblock.content(), endIt, '#');
417 if (it != endIt) {
418 Person p;
419 p.setGivenName(QString::fromUtf8(sblock.content(), std::distance(sblock.content(), it)));
420 ++it;
421 p.setFamilyName(QString::fromUtf8(it, std::distance(it, endIt)));
422 return p;
423 }
424 }
425 // S023 contains the full name, UTF-8 encoded
426 sblock = b.findSubBlock("023");
427 if (!sblock.isNull()) {
428 Person p;
429 p.setName(sblock.toString());
430 return p;
431 }
432 }
433 // CD vender block
434 if (const auto b = findBlock<Vendor1154UTBlock>(); b.isValid()) {
435 const auto subBlock = b.findSubBlock("KJ");
436 if (!subBlock.isNull()) {
437 Person p;
438 p.setName(subBlock.toString());
439 return p;
440 }
441 }
442
443 // RCT2 tickets
444 const auto rct2 = rct2Ticket();
445 if (rct2.isValid()) {
446 const auto name = rct2.passengerName();
447 if (!name.isEmpty()) {
448 Person p;
449 p.setName(name);
450 return p;
451 }
452 }
453
454 return {};
455}
456
457static void fixFcbStationCode(TrainStation &station)
458{
459 // UIC codes in Germany are wildly unreliable, there seem to be different
460 // code tables in use by different operators, so we unfortunately have to ignore
461 // those entirely
462 if (station.identifier().startsWith(QLatin1StringView("uic:80"))) {
463 PostalAddress addr;
464 addr.setAddressCountry(QStringLiteral("DE"));
465 station.setAddress(addr);
466 station.setIdentifier(QString());
467 }
468}
469
470TrainStation Uic9183Parser::outboundDepartureStation() const
471{
472 TrainStation station;
473
474 // RTC2 ticket layout
475 if (const auto rtc2 = rct2Ticket(); rtc2.isValid()) {
476 station.setName(rtc2.outboundDepartureStation());
477 }
478
479 // DB vendor block
480 if (const auto b = findBlock<Vendor0080BLBlock>(); b.isValid()) {
481 if (const auto sblock = b.findSubBlock("015"); !sblock.isNull()) {
482 station.setName(sblock.toString());
483 }
484 // S035 contains the IBNR, possible with leading '80' country code and leading 0 stripped
485 if (const auto sblock = b.findSubBlock("035"); !sblock.isNull() && sblock.contentSize() <= 7) {
486 QString ibnr = QStringLiteral("ibnr:8000000");
487 const auto s = sblock.toString();
488 station.setIdentifier(ibnr.replace(ibnr.size() - s.size(), s.size(), s));
489 }
490 }
491
492 // ERA FCB
493 if (const auto fcb = findBlock<Fcb::UicRailTicketData>(); fcb.isValid() && !fcb.transportDocument.isEmpty()) {
494 const auto doc = fcb.transportDocument.at(0);
495 if (doc.ticket.userType() == qMetaTypeId<Fcb::ReservationData>()) {
496 const auto irt = doc.ticket.value<Fcb::ReservationData>();
497 station.setName(irt.fromStationNameUTF8);
498 station.setIdentifier(FcbUtil::fromStationIdentifier(irt));
499 } else if (doc.ticket.userType() == qMetaTypeId<Fcb::OpenTicketData>()) {
500 const auto nrt = doc.ticket.value<Fcb::OpenTicketData>();
501 station.setName(nrt.fromStationNameUTF8);
502 station.setIdentifier(FcbUtil::fromStationIdentifier(nrt));
503 }
504 fixFcbStationCode(station);
505 }
506
507 return station;
508}
509
510TrainStation Uic9183Parser::outboundArrivalStation() const
511{
512 TrainStation station;
513
514 // RTC2 ticket layout
515 if (const auto rtc2 = rct2Ticket(); rtc2.isValid()) {
516 station.setName(rtc2.outboundArrivalStation());
517 }
518
519 if (const auto b = findBlock<Vendor0080BLBlock>(); b.isValid()) {
520 if (const auto sblock = b.findSubBlock("016"); !sblock.isNull()) {
521 station.setName(sblock.toString());
522 }
523 // S036 contains the IBNR, possible with leading '80' country code and leading 0 stripped
524 if (const auto sblock = b.findSubBlock("036"); !sblock.isNull() && sblock.contentSize() <= 7) {
525 QString ibnr = QStringLiteral("ibnr:8000000");
526 const auto s = sblock.toString();
527 station.setIdentifier(ibnr.replace(ibnr.size() - s.size(), s.size(), s));
528 }
529 }
530
531 // ERA FCB
532 if (const auto fcb = findBlock<Fcb::UicRailTicketData>(); fcb.isValid() && !fcb.transportDocument.isEmpty()) {
533 const auto doc = fcb.transportDocument.at(0);
534 if (doc.ticket.userType() == qMetaTypeId<Fcb::ReservationData>()) {
535 const auto irt = doc.ticket.value<Fcb::ReservationData>();
536 station.setName(irt.toStationNameUTF8);
537 station.setIdentifier(FcbUtil::toStationIdentifier(irt));
538 } else if (doc.ticket.userType() == qMetaTypeId<Fcb::OpenTicketData>()) {
539 const auto nrt = doc.ticket.value<Fcb::OpenTicketData>();
540 station.setName(nrt.toStationNameUTF8);
541 station.setIdentifier(FcbUtil::toStationIdentifier(nrt));
542 }
543 fixFcbStationCode(station);
544 }
545
546 return station;
547}
548
549TrainStation Uic9183Parser::returnDepartureStation() const
550{
551 TrainStation station;
552
553 // RTC2 ticket layout
554 if (const auto rtc2 = rct2Ticket(); rtc2.isValid()) {
555 station.setName(rtc2.returnDepartureStation());
556 }
557
558 const auto outboundArrival = outboundArrivalStation();
559 // DB vendor block
560 if (const auto b = findBlock<Vendor0080BLBlock>(); b.isValid()) {
561 if (const auto sblock = b.findSubBlock("017"); !sblock.isNull()) {
562 station.setName(sblock.toString());
563 }
564 if (outboundArrival.name() == station.name()) {
565 station.setIdentifier(outboundArrival.identifier());
566 }
567 }
568
569 // ERA FCB
570 if (const auto fcb = findBlock<Fcb::UicRailTicketData>(); fcb.isValid() && !fcb.transportDocument.isEmpty()) {
571 const auto doc = fcb.transportDocument.at(0);
572 if (doc.ticket.userType() == qMetaTypeId<Fcb::OpenTicketData>()) {
573 const auto nrt = doc.ticket.value<Fcb::OpenTicketData>();
574 if (nrt.returnIncluded && nrt.returnDescriptionIsSet()) {
575 station.setName(nrt.returnDescription.fromStationNameUTF8);
576 station.setIdentifier(FcbUtil::fromStationIdentifier(nrt.stationCodeTable, nrt.returnDescription));
577 } else if (nrt.returnIncluded) {
578 if (outboundArrival.name() == station.name()) {
579 station.setIdentifier(outboundArrival.identifier());
580 }
581 }
582 }
583 fixFcbStationCode(station);
584 }
585
586 return station;
587}
588
589TrainStation Uic9183Parser::returnArrivalStation() const
590{
591 TrainStation station;
592
593 // RTC2 ticket layout
594 if (const auto rtc2 = rct2Ticket(); rtc2.isValid()) {
595 station.setName(rtc2.returnArrivalStation());
596 }
597
598 const auto outboundDeparture = outboundDepartureStation();
599 // DB vendor block
600 if (const auto b = findBlock<Vendor0080BLBlock>(); b.isValid()) {
601 if (const auto sblock = b.findSubBlock("018"); !sblock.isNull()) {
602 station.setName(sblock.toString());
603 }
604 if (outboundDeparture.name() == station.name()) {
605 station.setIdentifier(outboundDeparture.identifier());
606 }
607 }
608
609 // ERA FCB
610 if (const auto fcb = findBlock<Fcb::UicRailTicketData>(); fcb.isValid() && !fcb.transportDocument.isEmpty()) {
611 const auto doc = fcb.transportDocument.at(0);
612 if (doc.ticket.userType() == qMetaTypeId<Fcb::OpenTicketData>()) {
613 const auto nrt = doc.ticket.value<Fcb::OpenTicketData>();
614 if (nrt.returnIncluded && nrt.returnDescriptionIsSet()) {
615 station.setName(nrt.returnDescription.toStationNameUTF8);
616 station.setIdentifier(FcbUtil::toStationIdentifier(nrt.stationCodeTable, nrt.returnDescription));
617 } else if (nrt.returnIncluded) {
618 if (outboundDeparture.name() == station.name()) {
619 station.setIdentifier(outboundDeparture.identifier());
620 }
621 }
622 }
623 fixFcbStationCode(station);
624 }
625
626 return station;
627}
628
629QString Uic9183Parser::seatingType() const
630{
631 if (const auto fcb = findBlock<Fcb::UicRailTicketData>(); fcb.isValid() && fcb.transportDocument.size() == 1) {
632 const auto doc = fcb.transportDocument.at(0);
633 if (doc.ticket.userType() == qMetaTypeId<Fcb::ReservationData>()) {
634 return FcbUtil::classCodeToString(doc.ticket.value<Fcb::ReservationData>().classCode);
635 }
636 if (doc.ticket.userType() == qMetaTypeId<Fcb::OpenTicketData>()) {
637 return FcbUtil::classCodeToString(doc.ticket.value<Fcb::OpenTicketData>().classCode);
638 }
639 if (doc.ticket.userType() == qMetaTypeId<Fcb::PassData>()) {
640 return FcbUtil::classCodeToString(doc.ticket.value<Fcb::PassData>().classCode);
641 }
642 }
643
644 if (const auto b = findBlock<Vendor0080BLBlock>(); b.isValid()) {
645 // S014 contains the class, possibly with a leading 'S' for some reason
646 const auto sblock = b.findSubBlock("014");
647 if (!sblock.isNull()) {
648 const auto s = sblock.toString();
649 return s.startsWith(QLatin1Char('S')) ? s.right(1) : s;
650 }
651 }
652
653 if (const auto rct2 = rct2Ticket(); rct2.isValid()) {
654 return rct2.outboundClass();
655 }
656 return {};
657}
658
660{
661 return findBlock<Uic9183TicketLayout>();
662}
663
664QVariant Uic9183Parser::ticketLayoutVariant() const
665{
666 const auto layout = ticketLayout();
667 return layout.isValid() ? QVariant::fromValue(layout) : QVariant();
668}
669
671{
672 Rct2Ticket rct2(ticketLayout());
673 const auto u_head = findBlock<Uic9183Head>();
674 rct2.setContextDate(u_head.issuingDateTime());
675 return rct2;
676}
677
678QVariant Uic9183Parser::rct2TicketVariant() const
679{
680 const auto rct2 = rct2Ticket();
681 if (rct2.isValid()) {
682 return QVariant::fromValue(rct2);
683 }
684 return {};
685}
686
688{
689 return Uic9183Header(d->m_data);
690}
691
692QByteArray Uic9183Parser::rawData() const
693{
694 return d->m_data;
695}
696
698{
699 Uic9183Header h(data);
700 return h.isValid();
701}
702
703#include "moc_uic9183parser.cpp"
static QString fromStationIdentifier(Fcb::CodeTableType stationCodeTable, const T &doc)
Departure station identifier for a travel document, in the format needed for output with our JSON-LD ...
Definition fcbutil.h:24
static QString classCodeToString(Fcb::TravelClassType classCode)
Convert a class code enum value to a string for human representation.
Definition fcbutil.cpp:30
static QString toStationIdentifier(Fcb::CodeTableType stationCodeTable, const T &doc)
Arrival station identifier for a travel document, in the format needed for output with our JSON-LD fo...
Definition fcbutil.h:41
Open ticket document (NRT).
Definition fcbticket.h:615
Rail pass document (RPT).
Definition fcbticket.h:692
Reservation document (IRT, RES).
Definition fcbticket.h:490
Top-level type for the ERA FCB ticket structure.
Definition fcbticket.h:1005
A person.
Definition person.h:20
QString identifier
Identifier.
Definition place.h:85
Postal address.
Definition place.h:46
RCT2 ticket layout payload of an UIC 918.3 ticket token.
Definition rct2ticket.h:23
void setContextDate(const QDateTime &contextDt)
Date/time this ticket was first encountered, to recover possibly missing year numbers.
Train station.
Definition place.h:126
A data block from a UIC 918.3 ticket.
U_HEAD block of a UIC 918.3 ticket container.
Definition uic9183head.h:21
Header of an UIC 918.3 ticket.
Parser for UIC 918.3 and 918.3* train tickets.
Q_INVOKABLE QVariant block(const QString &name) const
Same as the above, but for JS usage.
T findBlock() const
Returns the first block of type.
QVariant ticketLayout
U_TLAY ticket layout block, if present, null otherwise.
QVariant rct2Ticket
RCT2 ticket layout block, if present, null otherwise.
void setContextDate(const QDateTime &)
Date/time this ticket was first encountered.
static bool maybeUic9183(const QByteArray &data)
Quickly checks if might be UIC 918.3 content.
Uic9183Block firstBlock() const
First data block in this ticket.
Uic9183Header header() const
Header found before the compressed payload.
Parser for a U_TLAY block in a UIC 918-3 ticket container, such as a ERA TLB ticket.
UIC 918.3 0080BL vendor data block.
UIC 918.3 0080VU vendor data block (DB local public transport extensions).
UIC 918.3 1154UT vendor data block.
Classes for reservation/travel data models, data extraction and data augmentation.
Definition berelement.h:17
char * data()
QByteArray fromRawData(const char *data, qsizetype size)
qsizetype size() const const
QDate fromString(QStringView string, QStringView format, QCalendar cal)
QDateTime fromString(QStringView string, QStringView format, QCalendar cal)
bool isValid() const const
QJsonDocument fromJson(const QByteArray &json, QJsonParseError *error)
QJsonObject object() const const
const QChar at(qsizetype position) const const
QString fromLatin1(QByteArrayView str)
QString fromUtf8(QByteArrayView str)
bool isEmpty() const const
QString number(double n, char format, int precision)
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
qsizetype size() const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
QString trimmed() const const
QTimeZone utc()
QVariant fromValue(T &&value)
bool isNull() const const
bool isValid() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Jun 14 2024 11:54:40 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.