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 p.setGivenName(QString(traveler.firstName + QLatin1Char(' ') + traveler.secondName).trimmed());
402 p.setFamilyName(traveler.lastName);
403 if (traveler.firstNameIsSet() || traveler.lastNameIsSet()) {
404 return p;
405 }
406 }
407
408 // Deutsche Bahn vendor block
409 if (const auto b = findBlock<Vendor0080BLBlock>(); b.isValid()) {
410 // S028 contains family and given name separated by a '#', UTF-8 encoded
411 auto sblock = b.findSubBlock("028");
412 if (!sblock.isNull()) {
413 const auto endIt = sblock.content() + sblock.contentSize();
414 auto it = std::find(sblock.content(), endIt, '#');
415 if (it != endIt) {
416 Person p;
417 p.setGivenName(QString::fromUtf8(sblock.content(), std::distance(sblock.content(), it)));
418 ++it;
419 p.setFamilyName(QString::fromUtf8(it, std::distance(it, endIt)));
420 return p;
421 }
422 }
423 // S023 contains the full name, UTF-8 encoded
424 sblock = b.findSubBlock("023");
425 if (!sblock.isNull()) {
426 Person p;
427 p.setName(sblock.toString());
428 return p;
429 }
430 }
431 // CD vender block
432 if (const auto b = findBlock<Vendor1154UTBlock>(); b.isValid()) {
433 const auto subBlock = b.findSubBlock("KJ");
434 if (!subBlock.isNull()) {
435 Person p;
436 p.setName(subBlock.toString());
437 return p;
438 }
439 }
440
441 // RCT2 tickets
442 const auto rct2 = rct2Ticket();
443 if (rct2.isValid()) {
444 const auto name = rct2.passengerName();
445 if (!name.isEmpty()) {
446 Person p;
447 p.setName(name);
448 return p;
449 }
450 }
451
452 return {};
453}
454
455static void fixFcbStationCode(TrainStation &station)
456{
457 // UIC codes in Germany are wildly unreliable, there seem to be different
458 // code tables in use by different operators, so we unfortunately have to ignore
459 // those entirely
460 if (station.identifier().startsWith(QLatin1StringView("uic:80"))) {
461 PostalAddress addr;
462 addr.setAddressCountry(QStringLiteral("DE"));
463 station.setAddress(addr);
464 station.setIdentifier(QString());
465 }
466}
467
468TrainStation Uic9183Parser::outboundDepartureStation() const
469{
470 TrainStation station;
471
472 // RTC2 ticket layout
473 if (const auto rtc2 = rct2Ticket(); rtc2.isValid()) {
474 station.setName(rtc2.outboundDepartureStation());
475 }
476
477 // DB vendor block
478 if (const auto b = findBlock<Vendor0080BLBlock>(); b.isValid()) {
479 if (const auto sblock = b.findSubBlock("015"); !sblock.isNull()) {
480 station.setName(sblock.toString());
481 }
482 // S035 contains the IBNR, possible with leading '80' country code and leading 0 stripped
483 if (const auto sblock = b.findSubBlock("035"); !sblock.isNull() && sblock.contentSize() <= 7) {
484 QString ibnr = QStringLiteral("ibnr:8000000");
485 const auto s = sblock.toString();
486 station.setIdentifier(ibnr.replace(ibnr.size() - s.size(), s.size(), s));
487 }
488 }
489
490 // ERA FCB
491 if (const auto fcb = findBlock<Fcb::UicRailTicketData>(); fcb.isValid() && !fcb.transportDocument.isEmpty()) {
492 const auto doc = fcb.transportDocument.at(0);
493 if (doc.ticket.userType() == qMetaTypeId<Fcb::ReservationData>()) {
494 const auto irt = doc.ticket.value<Fcb::ReservationData>();
495 station.setName(irt.fromStationNameUTF8);
496 station.setIdentifier(FcbUtil::fromStationIdentifier(irt));
497 } else if (doc.ticket.userType() == qMetaTypeId<Fcb::OpenTicketData>()) {
498 const auto nrt = doc.ticket.value<Fcb::OpenTicketData>();
499 station.setName(nrt.fromStationNameUTF8);
500 station.setIdentifier(FcbUtil::fromStationIdentifier(nrt));
501 }
502 fixFcbStationCode(station);
503 }
504
505 return station;
506}
507
508TrainStation Uic9183Parser::outboundArrivalStation() const
509{
510 TrainStation station;
511
512 // RTC2 ticket layout
513 if (const auto rtc2 = rct2Ticket(); rtc2.isValid()) {
514 station.setName(rtc2.outboundArrivalStation());
515 }
516
517 if (const auto b = findBlock<Vendor0080BLBlock>(); b.isValid()) {
518 if (const auto sblock = b.findSubBlock("016"); !sblock.isNull()) {
519 station.setName(sblock.toString());
520 }
521 // S036 contains the IBNR, possible with leading '80' country code and leading 0 stripped
522 if (const auto sblock = b.findSubBlock("036"); !sblock.isNull() && sblock.contentSize() <= 7) {
523 QString ibnr = QStringLiteral("ibnr:8000000");
524 const auto s = sblock.toString();
525 station.setIdentifier(ibnr.replace(ibnr.size() - s.size(), s.size(), s));
526 }
527 }
528
529 // ERA FCB
530 if (const auto fcb = findBlock<Fcb::UicRailTicketData>(); fcb.isValid() && !fcb.transportDocument.isEmpty()) {
531 const auto doc = fcb.transportDocument.at(0);
532 if (doc.ticket.userType() == qMetaTypeId<Fcb::ReservationData>()) {
533 const auto irt = doc.ticket.value<Fcb::ReservationData>();
534 station.setName(irt.toStationNameUTF8);
535 station.setIdentifier(FcbUtil::toStationIdentifier(irt));
536 } else if (doc.ticket.userType() == qMetaTypeId<Fcb::OpenTicketData>()) {
537 const auto nrt = doc.ticket.value<Fcb::OpenTicketData>();
538 station.setName(nrt.toStationNameUTF8);
539 station.setIdentifier(FcbUtil::toStationIdentifier(nrt));
540 }
541 fixFcbStationCode(station);
542 }
543
544 return station;
545}
546
547TrainStation Uic9183Parser::returnDepartureStation() const
548{
549 TrainStation station;
550
551 // RTC2 ticket layout
552 if (const auto rtc2 = rct2Ticket(); rtc2.isValid()) {
553 station.setName(rtc2.returnDepartureStation());
554 }
555
556 const auto outboundArrival = outboundArrivalStation();
557 // DB vendor block
558 if (const auto b = findBlock<Vendor0080BLBlock>(); b.isValid()) {
559 if (const auto sblock = b.findSubBlock("017"); !sblock.isNull()) {
560 station.setName(sblock.toString());
561 }
562 if (outboundArrival.name() == station.name()) {
563 station.setIdentifier(outboundArrival.identifier());
564 }
565 }
566
567 // ERA FCB
568 if (const auto fcb = findBlock<Fcb::UicRailTicketData>(); fcb.isValid() && !fcb.transportDocument.isEmpty()) {
569 const auto doc = fcb.transportDocument.at(0);
570 if (doc.ticket.userType() == qMetaTypeId<Fcb::OpenTicketData>()) {
571 const auto nrt = doc.ticket.value<Fcb::OpenTicketData>();
572 if (nrt.returnIncluded && nrt.returnDescriptionIsSet()) {
573 station.setName(nrt.returnDescription.fromStationNameUTF8);
574 station.setIdentifier(FcbUtil::fromStationIdentifier(nrt.stationCodeTable, nrt.returnDescription));
575 } else if (nrt.returnIncluded) {
576 if (outboundArrival.name() == station.name()) {
577 station.setIdentifier(outboundArrival.identifier());
578 }
579 }
580 }
581 fixFcbStationCode(station);
582 }
583
584 return station;
585}
586
587TrainStation Uic9183Parser::returnArrivalStation() const
588{
589 TrainStation station;
590
591 // RTC2 ticket layout
592 if (const auto rtc2 = rct2Ticket(); rtc2.isValid()) {
593 station.setName(rtc2.returnArrivalStation());
594 }
595
596 const auto outboundDeparture = outboundDepartureStation();
597 // DB vendor block
598 if (const auto b = findBlock<Vendor0080BLBlock>(); b.isValid()) {
599 if (const auto sblock = b.findSubBlock("018"); !sblock.isNull()) {
600 station.setName(sblock.toString());
601 }
602 if (outboundDeparture.name() == station.name()) {
603 station.setIdentifier(outboundDeparture.identifier());
604 }
605 }
606
607 // ERA FCB
608 if (const auto fcb = findBlock<Fcb::UicRailTicketData>(); fcb.isValid() && !fcb.transportDocument.isEmpty()) {
609 const auto doc = fcb.transportDocument.at(0);
610 if (doc.ticket.userType() == qMetaTypeId<Fcb::OpenTicketData>()) {
611 const auto nrt = doc.ticket.value<Fcb::OpenTicketData>();
612 if (nrt.returnIncluded && nrt.returnDescriptionIsSet()) {
613 station.setName(nrt.returnDescription.toStationNameUTF8);
614 station.setIdentifier(FcbUtil::toStationIdentifier(nrt.stationCodeTable, nrt.returnDescription));
615 } else if (nrt.returnIncluded) {
616 if (outboundDeparture.name() == station.name()) {
617 station.setIdentifier(outboundDeparture.identifier());
618 }
619 }
620 }
621 fixFcbStationCode(station);
622 }
623
624 return station;
625}
626
627QString Uic9183Parser::seatingType() const
628{
629 if (const auto fcb = findBlock<Fcb::UicRailTicketData>(); fcb.isValid() && fcb.transportDocument.size() == 1) {
630 const auto doc = fcb.transportDocument.at(0);
631 if (doc.ticket.userType() == qMetaTypeId<Fcb::ReservationData>()) {
632 return FcbUtil::classCodeToString(doc.ticket.value<Fcb::ReservationData>().classCode);
633 }
634 if (doc.ticket.userType() == qMetaTypeId<Fcb::OpenTicketData>()) {
635 return FcbUtil::classCodeToString(doc.ticket.value<Fcb::OpenTicketData>().classCode);
636 }
637 if (doc.ticket.userType() == qMetaTypeId<Fcb::PassData>()) {
638 return FcbUtil::classCodeToString(doc.ticket.value<Fcb::PassData>().classCode);
639 }
640 }
641
642 if (const auto b = findBlock<Vendor0080BLBlock>(); b.isValid()) {
643 // S014 contains the class, possibly with a leading 'S' for some reason
644 const auto sblock = b.findSubBlock("014");
645 if (!sblock.isNull()) {
646 const auto s = sblock.toString();
647 return s.startsWith(QLatin1Char('S')) ? s.right(1) : s;
648 }
649 }
650
651 if (const auto rct2 = rct2Ticket(); rct2.isValid()) {
652 return rct2.outboundClass();
653 }
654 return {};
655}
656
658{
659 return findBlock<Uic9183TicketLayout>();
660}
661
662QVariant Uic9183Parser::ticketLayoutVariant() const
663{
664 const auto layout = ticketLayout();
665 return layout.isValid() ? QVariant::fromValue(layout) : QVariant();
666}
667
669{
670 Rct2Ticket rct2(ticketLayout());
671 const auto u_head = findBlock<Uic9183Head>();
672 rct2.setContextDate(u_head.issuingDateTime());
673 return rct2;
674}
675
676QVariant Uic9183Parser::rct2TicketVariant() const
677{
678 const auto rct2 = rct2Ticket();
679 if (rct2.isValid()) {
680 return QVariant::fromValue(rct2);
681 }
682 return {};
683}
684
686{
687 return Uic9183Header(d->m_data);
688}
689
690QByteArray Uic9183Parser::rawData() const
691{
692 return d->m_data;
693}
694
696{
697 Uic9183Header h(data);
698 return h.isValid();
699}
700
701#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:999
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, int size)
int size() const const
QDate fromString(const QString &string, Qt::DateFormat format)
QDateTime fromString(const QString &string, Qt::DateFormat format)
bool isValid() const const
QJsonDocument fromJson(const QByteArray &json, QJsonParseError *error)
QJsonObject object() const const
const QChar at(int position) const const
QString fromLatin1(const char *str, int size)
QString fromUtf8(const char *str, int size)
bool isEmpty() const const
QString number(int n, int base)
QString & replace(int position, int n, QChar after)
int size() const const
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
QString trimmed() const const
QTimeZone utc()
QVariant fromValue(const 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 Sun Feb 25 2024 18:40:32 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.