7#include "uic9183parser.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"
17#include "era/fcbticket.h"
18#include "era/fcbutil.h"
22#include <QJsonDocument>
43Uic9183Parser::Uic9183Parser()
44 : d(new Uic9183ParserPrivate)
49Uic9183Parser::~Uic9183Parser() =
default;
60 if (
block.isA(name)) {
69 if (name.size() != 6 || d->m_payload.isEmpty()) {
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(); \
95void Uic9183Parser::parse(
const QByteArray &data)
107 d->m_payload.resize(4096);
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());
117 inflateInit(&stream);
118 const auto res = inflate(&stream, Z_NO_FLUSH);
124 qCWarning(
Log) <<
"UIC 918.3 payload zlib decompression failed" << stream.msg;
128 d->m_payload.truncate(d->m_payload.size() - stream.avail_out);
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);
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");
138 d->m_payload.replace(idx + 12, 4,
"RCT2");
139 d->m_payload.remove(idx + 20, 1);
140 qCDebug(
Log) << d->m_payload;
145bool Uic9183Parser::isValid()
const
147 return !d->m_payload.isEmpty();
151static QString fcbReference(
const T &data)
153 if (!data.referenceIA5.isEmpty()) {
156 if (data.referenceNumIsSet()) {
164 if (
const auto head = findBlock<Uic9183Head>(); head.isValid()) {
165 const auto key = head.ticketKey().trimmed();
166 const auto issuerId = head.issuerCompanyCodeNumeric();
169 if (issuerId == 80 && (key.size() == 8 || key.size() == 9) && key.at(6) ==
QLatin1Char(
'-') && key.at(7).isDigit()) {
172 if (issuerId == 80 && key.size() == 13 &&
173 key.endsWith(QLatin1StringView(
"0101"))) {
176 if ((issuerId == 1088 || issuerId == 1184) && key.size() == 9 && key.at(7) ==
QLatin1Char(
'_') && key.at(8).isDigit()) {
183 if (
const auto fcb = findBlock<Fcb::UicRailTicketData>(); fcb.isValid()) {
184 if (!fcb.issuingDetail.issuerPNR.isEmpty()) {
187 if (!fcb.transportDocument.isEmpty()) {
188 const auto doc = fcb.transportDocument.at(0);
190 if (doc.ticket.userType() == qMetaTypeId<Fcb::ReservationData>()) {
192 }
else if (doc.ticket.userType() == qMetaTypeId<Fcb::OpenTicketData>()) {
194 }
else if (doc.ticket.userType() == qMetaTypeId<Fcb::PassData>()) {
207static QString fcbTariffName(
const T &data)
209 if (data.tariffs.isEmpty()) {
212 return data.tariffs.at(0).tariffDesc;
218 if (
const auto fcb = findBlock<Fcb::UicRailTicketData>(); fcb.isValid() && !fcb.transportDocument.isEmpty()) {
219 const auto doc = fcb.transportDocument.
at(0);
221 if (doc.ticket.userType() == qMetaTypeId<Fcb::ReservationData>()) {
223 }
else if (doc.ticket.userType() == qMetaTypeId<Fcb::OpenTicketData>()) {
225 }
else if (doc.ticket.userType() == qMetaTypeId<Fcb::PassData>()) {
228 if (!name.isEmpty()) {
234 if (
const auto b = findBlock<Vendor0080BLBlock>(); b.isValid()) {
235 const auto sblock = b.findSubBlock(
"001");
236 if (!sblock.isNull()) {
251 if (
const auto head = findBlock<Uic9183Head>(); head.isValid()) {
252 return head.issuerCompanyCodeString();
254 if (
const auto fcb = findBlock<Fcb::UicRailTicketData>(); fcb.isValid()) {
255 const auto issue = fcb.issuingDetail;
256 if (issue.issuerNumIsSet()) {
259 if (issue.issuerIA5IsSet()) {
263 return header().signerCompanyCode();
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);
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>()) {
285 if (doc.userType() == qMetaTypeId<Fcb::OpenTicketData>()) {
288 if (doc.userType() == qMetaTypeId<Fcb::PassData>()) {
294 if (
const auto b = findBlock<Vendor0080BLBlock>(); b.isValid() && b.orderBlockCount() == 1) {
295 return QDateTime(b.orderBlock(0).validFrom(), {0, 0, 0});
299 if (
const auto b =
findBlock(
"118199"); !b.isNull()) {
303 QStringLiteral(
"yyMMddhhmm"));
305 if (dt.date().year() < 2000) {
306 dt = dt.addYears(100);
314 if (
const auto b = findBlock<Vendor1154UTBlock>(); b.isValid()) {
315 const auto subBlock = b.findSubBlock(
"OD");
316 qDebug() << subBlock.toString();
317 if (!subBlock.isNull()) {
324 const auto dt = rct2.firstDayOfValidity();
325 if (dt.month() != 1 || dt.day() != 1 || !rct2.outboundDepartureStation().isEmpty()) {
329 const auto dep = rct2.outboundDepartureTime();
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>()) {
345 if (doc.userType() == qMetaTypeId<Fcb::OpenTicketData>()) {
348 if (doc.userType() == qMetaTypeId<Fcb::PassData>()) {
354 if (
const auto b = findBlock<Vendor0080BLBlock>(); b.isValid() && b.orderBlockCount() == 1) {
355 return QDateTime(b.orderBlock(0).validTo(), {23, 59, 59});
359 if (
const auto b =
findBlock(
"118199"); !b.isNull()) {
363 QStringLiteral(
"yyMMddhhmm"));
365 if (dt.date().year() < 2000) {
366 dt = dt.addYears(100);
374 if (
const auto b = findBlock<Vendor1154UTBlock>(); b.isValid()) {
375 const auto subBlock = b.findSubBlock(
"DO");
376 if (!subBlock.isNull()) {
384 const auto validityRange =
ticketLayout().text(3, 1, 36, 1).trimmed();
385 const auto idx = std::max(validityRange.lastIndexOf(
QLatin1Char(
' ')), validityRange.lastIndexOf(
QLatin1Char(
'-')));
389 return rct2.outboundArrivalTime();
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);
402 p.setFamilyName(traveler.lastName);
403 if (traveler.firstNameIsSet() || traveler.lastNameIsSet()) {
409 if (
const auto b = findBlock<Vendor0080BLBlock>(); b.isValid()) {
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,
'#');
417 p.setGivenName(
QString::fromUtf8(sblock.content(), std::distance(sblock.content(), it)));
424 sblock = b.findSubBlock(
"023");
425 if (!sblock.isNull()) {
427 p.setName(sblock.toString());
432 if (
const auto b = findBlock<Vendor1154UTBlock>(); b.isValid()) {
433 const auto subBlock = b.findSubBlock(
"KJ");
434 if (!subBlock.isNull()) {
436 p.setName(subBlock.toString());
443 if (rct2.isValid()) {
444 const auto name = rct2.passengerName();
462 addr.setAddressCountry(QStringLiteral(
"DE"));
463 station.setAddress(addr);
464 station.setIdentifier(
QString());
474 station.setName(rtc2.outboundDepartureStation());
478 if (
const auto b = findBlock<Vendor0080BLBlock>(); b.isValid()) {
479 if (
const auto sblock = b.findSubBlock(
"015"); !sblock.isNull()) {
480 station.setName(sblock.toString());
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));
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>()) {
495 station.setName(irt.fromStationNameUTF8);
497 }
else if (doc.ticket.userType() == qMetaTypeId<Fcb::OpenTicketData>()) {
499 station.setName(nrt.fromStationNameUTF8);
502 fixFcbStationCode(station);
514 station.setName(rtc2.outboundArrivalStation());
517 if (
const auto b = findBlock<Vendor0080BLBlock>(); b.isValid()) {
518 if (
const auto sblock = b.findSubBlock(
"016"); !sblock.isNull()) {
519 station.setName(sblock.toString());
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));
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>()) {
534 station.setName(irt.toStationNameUTF8);
536 }
else if (doc.ticket.userType() == qMetaTypeId<Fcb::OpenTicketData>()) {
538 station.setName(nrt.toStationNameUTF8);
541 fixFcbStationCode(station);
553 station.setName(rtc2.returnDepartureStation());
556 const auto outboundArrival = outboundArrivalStation();
558 if (
const auto b = findBlock<Vendor0080BLBlock>(); b.isValid()) {
559 if (
const auto sblock = b.findSubBlock(
"017"); !sblock.isNull()) {
560 station.setName(sblock.toString());
562 if (outboundArrival.name() == station.name()) {
563 station.setIdentifier(outboundArrival.identifier());
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>()) {
572 if (nrt.returnIncluded && nrt.returnDescriptionIsSet()) {
573 station.setName(nrt.returnDescription.fromStationNameUTF8);
575 }
else if (nrt.returnIncluded) {
576 if (outboundArrival.name() == station.name()) {
577 station.setIdentifier(outboundArrival.identifier());
581 fixFcbStationCode(station);
593 station.setName(rtc2.returnArrivalStation());
596 const auto outboundDeparture = outboundDepartureStation();
598 if (
const auto b = findBlock<Vendor0080BLBlock>(); b.isValid()) {
599 if (
const auto sblock = b.findSubBlock(
"018"); !sblock.isNull()) {
600 station.setName(sblock.toString());
602 if (outboundDeparture.name() == station.name()) {
603 station.setIdentifier(outboundDeparture.identifier());
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>()) {
612 if (nrt.returnIncluded && nrt.returnDescriptionIsSet()) {
613 station.setName(nrt.returnDescription.toStationNameUTF8);
615 }
else if (nrt.returnIncluded) {
616 if (outboundDeparture.name() == station.name()) {
617 station.setIdentifier(outboundDeparture.identifier());
621 fixFcbStationCode(station);
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>()) {
634 if (doc.ticket.userType() == qMetaTypeId<Fcb::OpenTicketData>()) {
637 if (doc.ticket.userType() == qMetaTypeId<Fcb::PassData>()) {
642 if (
const auto b = findBlock<Vendor0080BLBlock>(); b.isValid()) {
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;
652 return rct2.outboundClass();
659 return findBlock<Uic9183TicketLayout>();
662QVariant Uic9183Parser::ticketLayoutVariant()
const
671 const auto u_head = findBlock<Uic9183Head>();
676QVariant Uic9183Parser::rct2TicketVariant()
const
679 if (rct2.isValid()) {
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 ...
static QString classCodeToString(Fcb::TravelClassType classCode)
Convert a class code enum value to a string for human representation.
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...
Open ticket document (NRT).
Rail pass document (RPT).
Reservation document (IRT, RES).
Top-level type for the ERA FCB ticket structure.
QString identifier
Identifier.
RCT2 ticket layout payload of an UIC 918.3 ticket token.
void setContextDate(const QDateTime &contextDt)
Date/time this ticket was first encountered, to recover possibly missing year numbers.
A data block from a UIC 918.3 ticket.
U_HEAD block of a UIC 918.3 ticket container.
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.
QByteArray fromRawData(const char *data, int size)
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)
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
QString trimmed() const const
QVariant fromValue(const T &value)
bool isNull() const const
bool isValid() const const