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 "uic9183flex.h"
12#include "uic9183head.h"
13#include "uic9183header.h"
14#include "uic9183ticketlayout.h"
15#include "variantvisitor_p.h"
16#include "vendor0080block.h"
17#include "vendor1154block.h"
18
19#include "era/fcbticket.h"
20#include "era/fcbticket3.h"
21#include "era/fcbutil.h"
22
23#include <QDateTime>
24#include <QDebug>
25#include <QJsonDocument>
26#include <QJsonObject>
27#include <QTimeZone>
28
29#include <zlib.h>
30
31#include <cassert>
32#include <cstring>
33
34using namespace KItinerary;
35
36namespace KItinerary {
37
38class Uic9183ParserPrivate : public QSharedData
39{
40public:
41 QByteArray m_data;
42 QByteArray m_payload;
43};
44}
45
46Uic9183Parser::Uic9183Parser()
47 : d(new Uic9183ParserPrivate)
48{
49}
50
51Uic9183Parser::Uic9183Parser(const Uic9183Parser&) = default;
52Uic9183Parser::~Uic9183Parser() = default;
53Uic9183Parser& Uic9183Parser::operator=(const Uic9183Parser&) = default;
54
56{
57 return Uic9183Block(d->m_payload, 0);
58}
59
60Uic9183Block Uic9183Parser::findBlock(const char name[6]) const
61{
62 for (auto block = firstBlock(); !block.isNull(); block = block.nextBlock()) {
63 if (block.isA(name)) {
64 return block;
65 }
66 }
67 return {};
68}
69
71{
72 if (name.size() != 6 || d->m_payload.isEmpty()) {
73 return {};
74 }
75
76#define BLOCK_FROM_NAME(Type) \
77 if (name == QLatin1StringView(Type::RecordId)) { \
78 const auto block = findBlock<Type>(); \
79 return block.isValid() ? QVariant::fromValue(block) : QVariant(); \
80 }
81
82 BLOCK_FROM_NAME(Uic9183Head)
83 BLOCK_FROM_NAME(Uic9183TicketLayout)
84 BLOCK_FROM_NAME(Uic9183Flex)
85 BLOCK_FROM_NAME(Vendor0080BLBlock)
86 BLOCK_FROM_NAME(Vendor0080VUBlock)
87 BLOCK_FROM_NAME(Vendor1154UTBlock)
88
89#undef BLOCK_FROM_NAME
90
91 return QVariant::fromValue(findBlock(name.toUtf8().constData()));
92}
93
97
98void Uic9183Parser::parse(const QByteArray &data)
99{
100 d->m_data.clear();
101 d->m_payload.clear();
102
103 Uic9183Header header(data);
104 if (!header.isValid()) {
105 return;
106 }
107
108 // nx zlib payload
109 d->m_data = data;
110 d->m_payload.resize(4096);
111 z_stream stream;
112 stream.zalloc = nullptr;
113 stream.zfree = nullptr;
114 stream.opaque = nullptr;
115 stream.avail_in = data.size() - header.compressedMessageOffset();
116 stream.next_in = reinterpret_cast<unsigned char*>(const_cast<char*>(data.data() + header.compressedMessageOffset()));
117 stream.avail_out = d->m_payload.size();
118 stream.next_out = reinterpret_cast<unsigned char*>(d->m_payload.data());
119
120 inflateInit(&stream);
121 const auto res = inflate(&stream, Z_NO_FLUSH);
122 switch (res) {
123 case Z_OK:
124 case Z_STREAM_END:
125 break; // all good
126 default:
127 qCWarning(Log) << "UIC 918.3 payload zlib decompression failed" << stream.msg;
128 return;
129 }
130 inflateEnd(&stream);
131 d->m_payload.truncate(d->m_payload.size() - stream.avail_out);
132 //qCDebug(Log) << res << d->m_payload << stream.avail_out;
133
134 // workaround for Renfe (1071) having various errors...
135 if (d->m_payload.size() > 600 && d->m_payload.startsWith("U_HEAD0100531071") && d->m_payload[54] == 'U' && d->m_payload[36] == ' ') {
136 qCWarning(Log) << "Applying Renfe workaround for broken UIC 913.8 content...";
137 d->m_payload.remove(36, 1); // off by one in U_HEAD
138 const auto idx = d->m_payload.indexOf("U_TLAY00");
139 if (idx < d->m_payload.size() + 400 && std::strncmp(d->m_payload.constData() + idx + 12, "RCT2", 4) != 0) {
140 d->m_payload.insert(idx + 7, "1"); // wrong U_TLAY version
141 d->m_payload.replace(idx + 12, 4, "RCT2"); // wrong layout type
142 d->m_payload.remove(idx + 20, 1); // garbage trailing the layout type?
143 qCDebug(Log) << d->m_payload;
144 }
145 }
146}
147
148bool Uic9183Parser::isValid() const
149{
150 return !d->m_payload.isEmpty();
151}
152
153template <typename T>
154static QString fcbReference(const T &data)
155{
156 if (!data.referenceIA5.isEmpty()) {
157 return QString::fromLatin1(data.referenceIA5);
158 }
159 if (data.referenceNumIsSet()) {
160 return QString::number(data.referenceNum);
161 }
162 return {};
163}
164
165QString Uic9183Parser::pnr() const
166{
167 if (const auto head = findBlock<Uic9183Head>(); head.isValid()) {
168 const auto key = head.ticketKey().trimmed();
169 const auto issuerId = head.issuerCompanyCodeNumeric();
170
171 // try to make this match what's printed on the matching tickets...
172 if (issuerId == 80 && (key.size() == 8 || key.size() == 9) && key.at(6) == QLatin1Char('-') && key.at(7).isDigit()) {
173 return key.left(6); // DB domestic
174 }
175 if (issuerId == 80 && key.size() == 13 &&
176 key.endsWith(QLatin1StringView("0101"))) {
177 return key.left(9); // DB domestic part of an international order
178 }
179 if ((issuerId == 1088 || issuerId == 1184) && key.size() == 9 && key.at(7) == QLatin1Char('_') && key.at(8).isDigit()) {
180 return key.left(7); // SNCB and NS
181 }
182
183 return key;
184 }
185
186 if (const auto flex = findBlock<Uic9183Flex>(); flex.isValid()) {
187 QString pnr = std::visit([](auto &&fcb) { return QString::fromLatin1(fcb.issuingDetail.issuerPNR); }, flex.fcb());
188 if (!pnr.isEmpty()) {
189 return pnr;
190 }
191 if (flex.hasTransportDocument()) {
192 const auto doc = flex.transportDocuments().at(0);
193 QString pnr = VariantVisitor([](auto &&data) {
194 return fcbReference(data);
195 }).visit<Fcb::v13::ReservationData, Fcb::v13::OpenTicketData, Fcb::v13::PassData, Fcb::v3::ReservationData, Fcb::v3::OpenTicketData, Fcb::v3::PassData>(doc);
196 if (!pnr.isEmpty()) {
197 return pnr;
198 }
199 }
200 }
201
202 return {};
203}
204
205QString Uic9183Parser::name() const
206{
207 // ERA FCB
208 if (const auto flex = findBlock<Uic9183Flex>(); flex.hasTransportDocument()) {
209 const auto doc = flex.transportDocuments().at(0);
210 QString name = VariantVisitor([](auto &&data) {
211 return data.tariffs.isEmpty() ? QString() : data.tariffs.at(0).tariffDesc;
212 }).visit<Fcb::v13::ReservationData, Fcb::v13::OpenTicketData, Fcb::v13::PassData, Fcb::v3::ReservationData, Fcb::v3::OpenTicketData, Fcb::v3::PassData>(doc);
213 if (!name.isEmpty()) {
214 return name;
215 }
216 }
217
218 // DB vendor block
219 if (const auto b = findBlock<Vendor0080BLBlock>(); b.isValid()) {
220 const auto sblock = b.findSubBlock("001");
221 if (!sblock.isNull()) {
222 return QString::fromUtf8(sblock.content(), sblock.contentSize());
223 }
224 }
225
226 // RCT2
227 if (const auto rct2 = rct2Ticket(); rct2.isValid()) {
228 return rct2.title();
229 }
230
231 return {};
232}
233
234QString Uic9183Parser::carrierId() const
235{
236 if (const auto head = findBlock<Uic9183Head>(); head.isValid()) {
237 return head.issuerCompanyCodeString();
238 }
239 if (const auto flex = findBlock<Uic9183Flex>(); flex.isValid()) {
240 QString id = std::visit([](auto &&fcb) {
241 if (fcb.issuingDetail.issuerNumIsSet()) {
242 return QString::number(fcb.issuingDetail.issuerNum);
243 }
244 if (fcb.issuingDetail.issuerIA5IsSet()) {
245 return QString::fromLatin1(fcb.issuingDetail.issuerIA5);
246 }
247 return QString();
248 }, flex.fcb());
249 if (!id.isEmpty()) {
250 return id;
251 }
252 }
253 return header().signerCompanyCode();
254}
255
256Organization Uic9183Parser::issuer() const
257{
258 Organization issuer;
259 issuer.setIdentifier(QLatin1StringView("uic:") + carrierId());
260 if (const auto flex = findBlock<Uic9183Flex>(); flex.isValid()) {
261 std::visit([&issuer](auto &&fcb) {
262 if (fcb.issuingDetail.issuerNameIsSet()) {
263 issuer.setName(fcb.issuingDetail.issuerName);
264 }
265 }, flex.fcb());
266 }
267 return issuer;
268}
269
270QDateTime Uic9183Parser::validFrom() const
271{
272 // ERA FCB
273 if (const auto flex = findBlock<Uic9183Flex>(); flex.hasTransportDocument()) {
274 const auto issue = flex.issuingDateTime();
275 const auto doc = flex.transportDocuments().at(0);
276 auto dt = VariantVisitor([issue](auto &&data) {
277 return data.departureDateTime(issue);
278 }).visit<Fcb::v13::ReservationData, Fcb::v3::ReservationData>(doc);
279 if (dt.isValid()) {
280 return dt;
281 }
282 dt = VariantVisitor([issue](auto &&data) {
283 return data.validFrom(issue);
284 }).visit<Fcb::v13::OpenTicketData, Fcb::v13::PassData, Fcb::v3::OpenTicketData, Fcb::v3::PassData>(doc);
285 if (dt.isValid()) {
286 return dt;
287 }
288 }
289
290 // DB vendor block
291 if (const auto b = findBlock<Vendor0080BLBlock>(); b.isValid() && b.orderBlockCount() == 1) {
292 return QDateTime(b.orderBlock(0).validFrom(), {0, 0, 0});
293 }
294
295 // ÖBB vender block
296 if (const auto b = findBlock("118199"); !b.isNull()) {
297 const auto obj = QJsonDocument::fromJson(QByteArray::fromRawData(b.content(), b.contentSize())).object();
298 auto dt =
299 QDateTime::fromString(obj.value(QLatin1StringView("V")).toString(),
300 QStringLiteral("yyMMddhhmm"));
301 if (dt.isValid()) { // ÖBB VorteilsCard barcodes have an empty vendor block
302 if (dt.date().year() < 2000) {
303 dt = dt.addYears(100);
304 }
305 dt.setTimeZone(QTimeZone::utc());
306 return dt;
307 }
308 }
309
310 // CD vender block
311 if (const auto b = findBlock<Vendor1154UTBlock>(); b.isValid()) {
312 const auto subBlock = b.findSubBlock("OD");
313 qDebug() << subBlock.toString();
314 if (!subBlock.isNull()) {
315 return QDateTime::fromString(subBlock.toString(), QStringLiteral("dd.MM.yyyy hh:mm"));
316 }
317 }
318
319 // RCT2
320 if (const auto rct2 = rct2Ticket(); rct2.isValid()) {
321 const auto dt = rct2.firstDayOfValidity();
322 if (dt.month() != 1 || dt.day() != 1 || !rct2.outboundDepartureStation().isEmpty()) {
323 return QDateTime(dt, {0, 0, 0});
324 }
325 // firstDayOfValidity is just a year, and we have wildcard station names
326 const auto dep = rct2.outboundDepartureTime();
327 return dep.isValid() ? dep : QDateTime(dt, {0, 0, 0});
328 }
329
330 return {};
331}
332
333QDateTime Uic9183Parser::validUntil() const
334{
335 // ERA FCB
336 if (const auto flex = findBlock<Uic9183Flex>(); flex.hasTransportDocument()) {
337 const auto issue = flex.issuingDateTime();
338 const auto doc = flex.transportDocuments().at(0);
339 auto dt = VariantVisitor([issue](auto &&data) {
340 return data.arrivalDateTime(issue);
341 }).visit<Fcb::v13::ReservationData, Fcb::v3::ReservationData>(doc);
342 if (dt.isValid()) {
343 return dt;
344 }
345 dt = VariantVisitor([issue](auto &&data) {
346 return data.validUntil(issue);
347 }).visit<Fcb::v13::OpenTicketData, Fcb::v13::PassData, Fcb::v3::OpenTicketData, Fcb::v3::PassData>(doc);
348 if (dt.isValid()) {
349 return dt;
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 flex = findBlock<Uic9183Flex>(); flex.isValid()) {
399 bool travelerFound = false;
400 Person p;
401 std::visit([&p, &travelerFound](auto &&fcb) {
402 if (!fcb.travelerDetailIsSet() || fcb.travelerDetail.traveler.size() != 1) {
403 return;
404 }
405 const auto traveler = fcb.travelerDetail.traveler.at(0);
406 if (traveler.firstNameIsSet() || traveler.secondNameIsSet()) {
407 p.setGivenName(QString(traveler.firstName + QLatin1Char(' ') + traveler.secondName).trimmed());
408 }
409 p.setFamilyName(traveler.lastName);
410 if (traveler.firstNameIsSet() || traveler.lastNameIsSet()) {
411 travelerFound = true;
412 }
413 }, flex.fcb());
414 if (travelerFound) {
415 return p;
416 }
417 }
418
419 // Deutsche Bahn vendor block
420 if (const auto b = findBlock<Vendor0080BLBlock>(); b.isValid()) {
421 // S028 contains family and given name separated by a '#', UTF-8 encoded
422 auto sblock = b.findSubBlock("028");
423 if (!sblock.isNull()) {
424 const auto endIt = sblock.content() + sblock.contentSize();
425 auto it = std::find(sblock.content(), endIt, '#');
426 if (it != endIt) {
427 Person p;
428 p.setGivenName(QString::fromUtf8(sblock.content(), std::distance(sblock.content(), it)));
429 ++it;
430 p.setFamilyName(QString::fromUtf8(it, std::distance(it, endIt)));
431 return p;
432 }
433 }
434 // S023 contains the full name, UTF-8 encoded
435 sblock = b.findSubBlock("023");
436 if (!sblock.isNull()) {
437 Person p;
438 p.setName(sblock.toString());
439 return p;
440 }
441 }
442 // CD vender block
443 if (const auto b = findBlock<Vendor1154UTBlock>(); b.isValid()) {
444 const auto subBlock = b.findSubBlock("KJ");
445 if (!subBlock.isNull()) {
446 Person p;
447 p.setName(subBlock.toString());
448 return p;
449 }
450 }
451
452 // RCT2 tickets
453 const auto rct2 = rct2Ticket();
454 if (rct2.isValid()) {
455 const auto name = rct2.passengerName();
456 if (!name.isEmpty()) {
457 Person p;
458 p.setName(name);
459 return p;
460 }
461 }
462
463 return {};
464}
465
466TrainStation Uic9183Parser::outboundDepartureStation() const
467{
468 TrainStation station;
469
470 // RTC2 ticket layout
471 if (const auto rtc2 = rct2Ticket(); rtc2.isValid()) {
472 station.setName(rtc2.outboundDepartureStation());
473 }
474
475 // DB vendor block
476 if (const auto b = findBlock<Vendor0080BLBlock>(); b.isValid()) {
477 if (const auto sblock = b.findSubBlock("015"); !sblock.isNull()) {
478 station.setName(sblock.toString());
479 }
480 // S035 contains the IBNR, possible with leading '80' country code and leading 0 stripped
481 if (const auto sblock = b.findSubBlock("035"); !sblock.isNull() && sblock.contentSize() <= 7) {
482 QString ibnr = QStringLiteral("ibnr:8000000");
483 const auto s = sblock.toString();
484 station.setIdentifier(ibnr.replace(ibnr.size() - s.size(), s.size(), s));
485 }
486 }
487
488 // ERA FCB
489 if (const auto flex = findBlock<Uic9183Flex>(); flex.hasTransportDocument()) {
490 Uic9183Flex::readDepartureStation(flex.transportDocuments().at(0), station);
491 }
492
493 return station;
494}
495
496TrainStation Uic9183Parser::outboundArrivalStation() const
497{
498 TrainStation station;
499
500 // RTC2 ticket layout
501 if (const auto rtc2 = rct2Ticket(); rtc2.isValid()) {
502 station.setName(rtc2.outboundArrivalStation());
503 }
504
505 if (const auto b = findBlock<Vendor0080BLBlock>(); b.isValid()) {
506 if (const auto sblock = b.findSubBlock("016"); !sblock.isNull()) {
507 station.setName(sblock.toString());
508 }
509 // S036 contains the IBNR, possible with leading '80' country code and leading 0 stripped
510 if (const auto sblock = b.findSubBlock("036"); !sblock.isNull() && sblock.contentSize() <= 7) {
511 QString ibnr = QStringLiteral("ibnr:8000000");
512 const auto s = sblock.toString();
513 station.setIdentifier(ibnr.replace(ibnr.size() - s.size(), s.size(), s));
514 }
515 }
516
517 // ERA FCB
518 if (const auto flex = findBlock<Uic9183Flex>(); flex.hasTransportDocument()) {
519 Uic9183Flex::readArrivalStation(flex.transportDocuments().at(0), station);
520 }
521
522 return station;
523}
524
525TrainStation Uic9183Parser::returnDepartureStation() const
526{
527 TrainStation station;
528
529 // RTC2 ticket layout
530 if (const auto rtc2 = rct2Ticket(); rtc2.isValid()) {
531 station.setName(rtc2.returnDepartureStation());
532 }
533
534 const auto outboundArrival = outboundArrivalStation();
535 // DB vendor block
536 if (const auto b = findBlock<Vendor0080BLBlock>(); b.isValid()) {
537 if (const auto sblock = b.findSubBlock("017"); !sblock.isNull()) {
538 station.setName(sblock.toString());
539 }
540 if (outboundArrival.name() == station.name()) {
541 station.setIdentifier(outboundArrival.identifier());
542 }
543 }
544
545 // ERA FCB
546 if (const auto flex = findBlock<Uic9183Flex>(); flex.hasTransportDocument()) {
547 const auto doc = flex.transportDocuments().at(0);
548 VariantVisitor([&station, outboundArrival](auto &&nrt) {
549 if (nrt.returnIncluded && nrt.returnDescriptionIsSet()) {
550 station.setName(nrt.returnDescription.fromStationNameUTF8);
551 station.setIdentifier(FcbUtil::fromStationIdentifier(nrt.stationCodeTable, nrt.returnDescription));
552 } else if (nrt.returnIncluded) {
553 if (outboundArrival.name() == station.name()) {
554 station.setIdentifier(outboundArrival.identifier());
555 }
556 }
557 }).visit<Fcb::v13::OpenTicketData, Fcb::v3::OpenTicketData>(doc);
559 }
560
561 return station;
562}
563
564TrainStation Uic9183Parser::returnArrivalStation() const
565{
566 TrainStation station;
567
568 // RTC2 ticket layout
569 if (const auto rtc2 = rct2Ticket(); rtc2.isValid()) {
570 station.setName(rtc2.returnArrivalStation());
571 }
572
573 const auto outboundDeparture = outboundDepartureStation();
574 // DB vendor block
575 if (const auto b = findBlock<Vendor0080BLBlock>(); b.isValid()) {
576 if (const auto sblock = b.findSubBlock("018"); !sblock.isNull()) {
577 station.setName(sblock.toString());
578 }
579 if (outboundDeparture.name() == station.name()) {
580 station.setIdentifier(outboundDeparture.identifier());
581 }
582 }
583
584 // ERA FCB
585 if (const auto flex = findBlock<Uic9183Flex>(); flex.hasTransportDocument()) {
586 const auto doc = flex.transportDocuments().at(0);
587 VariantVisitor([&station, outboundDeparture](auto &&nrt) {
588 if (nrt.returnIncluded && nrt.returnDescriptionIsSet()) {
589 station.setName(nrt.returnDescription.toStationNameUTF8);
590 station.setIdentifier(FcbUtil::toStationIdentifier(nrt.stationCodeTable, nrt.returnDescription));
591 } else if (nrt.returnIncluded) {
592 if (outboundDeparture.name() == station.name()) {
593 station.setIdentifier(outboundDeparture.identifier());
594 }
595 }
596 }).visit<Fcb::v13::OpenTicketData, Fcb::v3::OpenTicketData>(doc);
598 }
599
600 return station;
601}
602
603QString Uic9183Parser::seatingType() const
604{
605 if (const auto flex = findBlock<Uic9183Flex>(); flex.transportDocuments().size() == 1) {
606 const auto doc = flex.transportDocuments().at(0);
607 auto c = VariantVisitor([](auto &&data) {
608 return FcbUtil::classCodeToString(data.classCode);
609 }).visit<Fcb::v13::ReservationData, Fcb::v13::OpenTicketData, Fcb::v13::PassData, Fcb::v3::ReservationData, Fcb::v3::OpenTicketData, Fcb::v3::PassData>(doc);
610 if (!c.isEmpty()) {
611 return c;
612 }
613 }
614
615 if (const auto b = findBlock<Vendor0080BLBlock>(); b.isValid()) {
616 // S014 contains the class, possibly with a leading 'S' for some reason
617 const auto sblock = b.findSubBlock("014");
618 if (!sblock.isNull()) {
619 const auto s = sblock.toString();
620 return s.startsWith(QLatin1Char('S')) ? s.right(1) : s;
621 }
622 }
623
624 if (const auto rct2 = rct2Ticket(); rct2.isValid()) {
625 return rct2.outboundClass();
626 }
627 return {};
628}
629
634
635QVariant Uic9183Parser::ticketLayoutVariant() const
636{
637 const auto layout = ticketLayout();
638 return layout.isValid() ? QVariant::fromValue(layout) : QVariant();
639}
640
642{
643 Rct2Ticket rct2(ticketLayout());
644 const auto u_head = findBlock<Uic9183Head>();
645 rct2.setContextDate(u_head.issuingDateTime());
646 return rct2;
647}
648
649QVariant Uic9183Parser::rct2TicketVariant() const
650{
651 const auto rct2 = rct2Ticket();
652 if (rct2.isValid()) {
653 return QVariant::fromValue(rct2);
654 }
655 return {};
656}
657
659{
660 return Uic9183Header(d->m_data);
661}
662
663QByteArray Uic9183Parser::rawData() const
664{
665 return d->m_data;
666}
667
669{
670 Uic9183Header h(data);
671 return h.isValid();
672}
673
674#include "moc_uic9183parser.cpp"
static QString toStationIdentifier(CodeTableTypeT 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:48
static QString classCodeToString(Fcb::v13::TravelClassType classCode)
Convert a class code enum value to a string for human representation.
Definition fcbutil.cpp:30
static QString fromStationIdentifier(CodeTableTypeT stationCodeTable, const T &doc)
Departure station identifier for a travel document, in the format needed for output with our JSON-LD ...
Definition fcbutil.h:34
A person.
Definition person.h:20
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.
Represents a U_FLEX block holding different versions of an FCB payload.
Definition uic9183flex.h:24
static void readArrivalStation(const QVariant &doc, TrainStation &station)
Read arrival station info from the given FCB travel document, if applicable.
static void fixStationCode(TrainStation &station)
Fix known issues with station identifiers.
static void readDepartureStation(const QVariant &doc, TrainStation &station)
Read departure station info from the given FCB travel document, if applicable.
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.
Uic9183Block findBlock(const char name[6]) const
Returns the first block with the given name.
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)
QJsonDocument fromJson(const QByteArray &json, QJsonParseError *error)
QJsonObject object() const const
QString fromLatin1(QByteArrayView str)
QString fromUtf8(QByteArrayView str)
QString number(double n, char format, int precision)
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
qsizetype size() const const
QString trimmed() const const
QTimeZone utc()
QVariant fromValue(T &&value)
bool isValid() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Jan 24 2025 11:52:36 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.