7#include "uic9183parser.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"
19#include "era/fcbticket.h"
20#include "era/fcbticket3.h"
21#include "era/fcbutil.h"
25#include <QJsonDocument>
38class Uic9183ParserPrivate :
public QSharedData
46Uic9183Parser::Uic9183Parser()
47 : d(new Uic9183ParserPrivate)
52Uic9183Parser::~Uic9183Parser() =
default;
63 if (
block.isA(name)) {
72 if (name.size() != 6 || d->m_payload.isEmpty()) {
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(); \
98void Uic9183Parser::parse(
const QByteArray &data)
101 d->m_payload.clear();
110 d->m_payload.resize(4096);
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());
120 inflateInit(&stream);
121 const auto res = inflate(&stream, Z_NO_FLUSH);
127 qCWarning(
Log) <<
"UIC 918.3 payload zlib decompression failed" << stream.msg;
131 d->m_payload.truncate(d->m_payload.size() - stream.avail_out);
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);
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");
141 d->m_payload.replace(idx + 12, 4,
"RCT2");
142 d->m_payload.remove(idx + 20, 1);
143 qCDebug(Log) << d->m_payload;
148bool Uic9183Parser::isValid()
const
150 return !d->m_payload.isEmpty();
154static QString fcbReference(
const T &data)
156 if (!data.referenceIA5.isEmpty()) {
159 if (data.referenceNumIsSet()) {
168 const auto key = head.ticketKey().trimmed();
169 const auto issuerId = head.issuerCompanyCodeNumeric();
172 if (issuerId == 80 && (key.size() == 8 || key.size() == 9) && key.at(6) ==
QLatin1Char(
'-') && key.at(7).isDigit()) {
175 if (issuerId == 80 && key.size() == 13 &&
179 if ((issuerId == 1088 || issuerId == 1184) && key.size() == 9 && key.at(7) ==
QLatin1Char(
'_') && key.at(8).isDigit()) {
188 if (!pnr.isEmpty()) {
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()) {
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()) {
220 const auto sblock = b.findSubBlock(
"001");
221 if (!sblock.isNull()) {
237 return head.issuerCompanyCodeString();
240 QString id = std::visit([](
auto &&fcb) {
241 if (fcb.issuingDetail.issuerNumIsSet()) {
244 if (fcb.issuingDetail.issuerIA5IsSet()) {
253 return header().signerCompanyCode();
261 std::visit([&issuer](
auto &&fcb) {
262 if (fcb.issuingDetail.issuerNameIsSet()) {
263 issuer.setName(fcb.issuingDetail.issuerName);
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);
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);
292 return QDateTime(b.orderBlock(0).validFrom(), {0, 0, 0});
296 if (
const auto b =
findBlock(
"118199"); !b.isNull()) {
300 QStringLiteral(
"yyMMddhhmm"));
302 if (dt.date().year() < 2000) {
303 dt = dt.addYears(100);
312 const auto subBlock = b.findSubBlock(
"OD");
313 qDebug() << subBlock.toString();
314 if (!subBlock.isNull()) {
321 const auto dt = rct2.firstDayOfValidity();
322 if (dt.month() != 1 || dt.day() != 1 || !rct2.outboundDepartureStation().isEmpty()) {
326 const auto dep = rct2.outboundDepartureTime();
327 return dep.isValid() ? dep :
QDateTime(dt, {0, 0, 0});
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);
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);
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 bool travelerFound =
false;
401 std::visit([&p, &travelerFound](
auto &&fcb) {
402 if (!fcb.travelerDetailIsSet() || fcb.travelerDetail.traveler.size() != 1) {
405 const auto traveler = fcb.travelerDetail.traveler.at(0);
406 if (traveler.firstNameIsSet() || traveler.secondNameIsSet()) {
409 p.setFamilyName(traveler.lastName);
410 if (traveler.firstNameIsSet() || traveler.lastNameIsSet()) {
411 travelerFound =
true;
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,
'#');
428 p.setGivenName(
QString::fromUtf8(sblock.content(), std::distance(sblock.content(), it)));
435 sblock = b.findSubBlock(
"023");
436 if (!sblock.isNull()) {
438 p.setName(sblock.toString());
444 const auto subBlock = b.findSubBlock(
"KJ");
445 if (!subBlock.isNull()) {
447 p.setName(subBlock.toString());
454 if (rct2.isValid()) {
455 const auto name = rct2.passengerName();
456 if (!name.isEmpty()) {
472 station.setName(rtc2.outboundDepartureStation());
477 if (
const auto sblock = b.findSubBlock(
"015"); !sblock.isNull()) {
478 station.setName(sblock.toString());
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));
502 station.setName(rtc2.outboundArrivalStation());
506 if (
const auto sblock = b.findSubBlock(
"016"); !sblock.isNull()) {
507 station.setName(sblock.toString());
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));
531 station.setName(rtc2.returnDepartureStation());
534 const auto outboundArrival = outboundArrivalStation();
537 if (
const auto sblock = b.findSubBlock(
"017"); !sblock.isNull()) {
538 station.setName(sblock.toString());
540 if (outboundArrival.name() == station.name()) {
541 station.setIdentifier(outboundArrival.identifier());
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);
552 }
else if (nrt.returnIncluded) {
553 if (outboundArrival.name() == station.name()) {
554 station.setIdentifier(outboundArrival.identifier());
557 }).visit<Fcb::v13::OpenTicketData, Fcb::v3::OpenTicketData>(doc);
570 station.setName(rtc2.returnArrivalStation());
573 const auto outboundDeparture = outboundDepartureStation();
576 if (
const auto sblock = b.findSubBlock(
"018"); !sblock.isNull()) {
577 station.setName(sblock.toString());
579 if (outboundDeparture.name() == station.name()) {
580 station.setIdentifier(outboundDeparture.identifier());
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);
591 }
else if (nrt.returnIncluded) {
592 if (outboundDeparture.name() == station.name()) {
593 station.setIdentifier(outboundDeparture.identifier());
596 }).visit<Fcb::v13::OpenTicketData, Fcb::v3::OpenTicketData>(doc);
606 const auto doc = flex.transportDocuments().at(0);
607 auto c = VariantVisitor([](
auto &&data) {
609 }).visit<Fcb::v13::ReservationData, Fcb::v13::OpenTicketData, Fcb::v13::PassData, Fcb::v3::ReservationData, Fcb::v3::OpenTicketData, Fcb::v3::PassData>(doc);
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;
625 return rct2.outboundClass();
635QVariant Uic9183Parser::ticketLayoutVariant()
const
649QVariant Uic9183Parser::rct2TicketVariant()
const
652 if (rct2.isValid()) {
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...
static QString classCodeToString(Fcb::v13::TravelClassType classCode)
Convert a class code enum value to a string for human representation.
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 ...
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.
Represents a U_FLEX block holding different versions of an FCB payload.
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.
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.
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
QVariant fromValue(T &&value)
bool isValid() const const