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()) {
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 &&
176 if ((issuerId == 1088 || issuerId == 1184) && key.size() == 9 && key.at(7) ==
QLatin1Char(
'_') && key.at(8).isDigit()) {
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;
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()) {
235 const auto sblock = b.findSubBlock(
"001");
236 if (!sblock.isNull()) {
252 return head.issuerCompanyCodeString();
255 const auto issue = fcb.issuingDetail;
256 if (issue.issuerNumIsSet()) {
259 if (issue.issuerIA5IsSet()) {
263 return header().signerCompanyCode();
271 issuer.setName(fcb.issuingDetail.issuerName);
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>()) {
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);
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();
330 return dep.isValid() ? dep :
QDateTime(dt, {0, 0, 0});
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>()) {
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);
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();
399 const auto traveler = fcb.travelerDetail.traveler.at(0);
401 if (traveler.firstNameIsSet() || traveler.secondNameIsSet()) {
404 p.setFamilyName(traveler.lastName);
405 if (traveler.firstNameIsSet() || traveler.lastNameIsSet()) {
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,
'#');
419 p.setGivenName(
QString::fromUtf8(sblock.content(), std::distance(sblock.content(), it)));
426 sblock = b.findSubBlock(
"023");
427 if (!sblock.isNull()) {
429 p.setName(sblock.toString());
435 const auto subBlock = b.findSubBlock(
"KJ");
436 if (!subBlock.isNull()) {
438 p.setName(subBlock.toString());
445 if (rct2.isValid()) {
446 const auto name = rct2.passengerName();
464 addr.setAddressCountry(QStringLiteral(
"DE"));
465 station.setAddress(addr);
466 station.setIdentifier(
QString());
476 station.setName(rtc2.outboundDepartureStation());
481 if (
const auto sblock = b.findSubBlock(
"015"); !sblock.isNull()) {
482 station.setName(sblock.toString());
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));
494 const auto doc = fcb.transportDocument.at(0);
495 if (doc.ticket.userType() == qMetaTypeId<Fcb::ReservationData>()) {
497 station.setName(irt.fromStationNameUTF8);
499 }
else if (doc.ticket.userType() == qMetaTypeId<Fcb::OpenTicketData>()) {
501 station.setName(nrt.fromStationNameUTF8);
504 fixFcbStationCode(station);
516 station.setName(rtc2.outboundArrivalStation());
520 if (
const auto sblock = b.findSubBlock(
"016"); !sblock.isNull()) {
521 station.setName(sblock.toString());
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));
533 const auto doc = fcb.transportDocument.at(0);
534 if (doc.ticket.userType() == qMetaTypeId<Fcb::ReservationData>()) {
536 station.setName(irt.toStationNameUTF8);
538 }
else if (doc.ticket.userType() == qMetaTypeId<Fcb::OpenTicketData>()) {
540 station.setName(nrt.toStationNameUTF8);
543 fixFcbStationCode(station);
555 station.setName(rtc2.returnDepartureStation());
558 const auto outboundArrival = outboundArrivalStation();
561 if (
const auto sblock = b.findSubBlock(
"017"); !sblock.isNull()) {
562 station.setName(sblock.toString());
564 if (outboundArrival.name() == station.name()) {
565 station.setIdentifier(outboundArrival.identifier());
571 const auto doc = fcb.transportDocument.at(0);
572 if (doc.ticket.userType() == qMetaTypeId<Fcb::OpenTicketData>()) {
574 if (nrt.returnIncluded && nrt.returnDescriptionIsSet()) {
575 station.setName(nrt.returnDescription.fromStationNameUTF8);
577 }
else if (nrt.returnIncluded) {
578 if (outboundArrival.name() == station.name()) {
579 station.setIdentifier(outboundArrival.identifier());
583 fixFcbStationCode(station);
595 station.setName(rtc2.returnArrivalStation());
598 const auto outboundDeparture = outboundDepartureStation();
601 if (
const auto sblock = b.findSubBlock(
"018"); !sblock.isNull()) {
602 station.setName(sblock.toString());
604 if (outboundDeparture.name() == station.name()) {
605 station.setIdentifier(outboundDeparture.identifier());
611 const auto doc = fcb.transportDocument.at(0);
612 if (doc.ticket.userType() == qMetaTypeId<Fcb::OpenTicketData>()) {
614 if (nrt.returnIncluded && nrt.returnDescriptionIsSet()) {
615 station.setName(nrt.returnDescription.toStationNameUTF8);
617 }
else if (nrt.returnIncluded) {
618 if (outboundDeparture.name() == station.name()) {
619 station.setIdentifier(outboundDeparture.identifier());
623 fixFcbStationCode(station);
632 const auto doc = fcb.transportDocument.at(0);
633 if (doc.ticket.userType() == qMetaTypeId<Fcb::ReservationData>()) {
636 if (doc.ticket.userType() == qMetaTypeId<Fcb::OpenTicketData>()) {
639 if (doc.ticket.userType() == qMetaTypeId<Fcb::PassData>()) {
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;
654 return rct2.outboundClass();
664QVariant Uic9183Parser::ticketLayoutVariant()
const
678QVariant Uic9183Parser::rct2TicketVariant()
const
681 if (rct2.isValid()) {
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 ...
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, 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)
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
QVariant fromValue(T &&value)
bool isNull() const const
bool isValid() const const