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/fcbextractor_p.h"
20#include "era/fcbticket1.h"
21#include "era/fcbticket3.h"
22#include "era/fcbutil.h"
26#include <QJsonDocument>
39class Uic9183ParserPrivate :
public QSharedData
47Uic9183Parser::Uic9183Parser()
48 : d(new Uic9183ParserPrivate)
53Uic9183Parser::~Uic9183Parser() =
default;
64 if (
block.isA(name)) {
73 if (name.size() != 6 || d->m_payload.isEmpty()) {
77#define BLOCK_FROM_NAME(Type) \
78 if (name == QLatin1StringView(Type::RecordId)) { \
79 const auto block = findBlock<Type>(); \
80 return block.isValid() ? QVariant::fromValue(block) : QVariant(); \
99void Uic9183Parser::parse(
const QByteArray &data)
102 d->m_payload.clear();
111 d->m_payload.resize(4096);
113 stream.zalloc =
nullptr;
114 stream.zfree =
nullptr;
115 stream.opaque =
nullptr;
116 stream.avail_in = data.
size() -
header.compressedMessageOffset();
117 stream.next_in =
reinterpret_cast<unsigned char*
>(
const_cast<char*
>(data.
data() +
header.compressedMessageOffset()));
118 stream.avail_out = d->m_payload.size();
119 stream.next_out =
reinterpret_cast<unsigned char*
>(d->m_payload.data());
121 inflateInit(&stream);
122 const auto res = inflate(&stream, Z_NO_FLUSH);
128 qCWarning(
Log) <<
"UIC 918.3 payload zlib decompression failed" << stream.msg;
132 d->m_payload.truncate(d->m_payload.size() - stream.avail_out);
136 if (d->m_payload.size() > 600 && d->m_payload.startsWith(
"U_HEAD0100531071") && d->m_payload[54] ==
'U' && d->m_payload[36] ==
' ') {
137 qCWarning(Log) <<
"Applying Renfe workaround for broken UIC 913.8 content...";
138 d->m_payload.remove(36, 1);
139 const auto idx = d->m_payload.indexOf(
"U_TLAY00");
140 if (idx < d->m_payload.size() + 400 && std::strncmp(d->m_payload.constData() + idx + 12,
"RCT2", 4) != 0) {
141 d->m_payload.insert(idx + 7,
"1");
142 d->m_payload.replace(idx + 12, 4,
"RCT2");
143 d->m_payload.remove(idx + 20, 1);
144 qCDebug(Log) << d->m_payload;
149bool Uic9183Parser::isValid()
const
151 return !d->m_payload.isEmpty();
157 auto key = head.ticketKey().trimmed();
158 const auto issuerId = head.issuerCompanyCodeNumeric();
161 if (issuerId == 80 && (key.size() == 8 || key.size() == 9) && key.at(6) ==
QLatin1Char(
'-') && key.at(7).isDigit()) {
164 if (issuerId == 80 && key.size() == 13 &&
168 if ((issuerId == 1088 || issuerId == 1184) && key.size() == 9 && key.at(7) ==
QLatin1Char(
'_') && key.at(8).isDigit()) {
176 if (
auto pnr = FcbExtractor::pnr(flex.fcb()); !pnr.isEmpty()) {
188 auto name = FcbExtractor::ticketName(flex.fcb());
189 if (!name.isEmpty()) {
196 const auto sblock = b.findSubBlock(
"001");
197 if (!sblock.isNull()) {
213 return head.issuerCompanyCodeString();
216 if (
const auto id = FcbExtractor::issuerId(flex.fcb()); !
id.isEmpty()) {
220 return header().signerCompanyCode();
227 issuer = FcbExtractor::issuer(flex.fcb());
237 const auto dt = FcbExtractor::validFrom(flex.fcb());
245 return QDateTime(b.orderBlock(0).validFrom(), {0, 0, 0});
249 if (
const auto b =
findBlock(
"118199"); !b.isNull()) {
253 QStringLiteral(
"yyMMddhhmm"));
255 if (dt.date().year() < 2000) {
256 dt = dt.addYears(100);
265 const auto subBlock = b.findSubBlock(
"OD");
266 qDebug() << subBlock.toString();
267 if (!subBlock.isNull()) {
274 const auto dt = rct2.firstDayOfValidity();
275 if (dt.month() != 1 || dt.day() != 1 || !rct2.outboundDepartureStation().isEmpty()) {
279 const auto dep = rct2.outboundDepartureTime();
280 return dep.isValid() ? dep :
QDateTime(dt, {0, 0, 0});
290 const auto dt = FcbExtractor::validUntil(flex.fcb());
298 return QDateTime(b.orderBlock(0).validTo(), {23, 59, 59});
302 if (
const auto b =
findBlock(
"118199"); !b.isNull()) {
306 QStringLiteral(
"yyMMddhhmm"));
308 if (dt.date().year() < 2000) {
309 dt = dt.addYears(100);
318 const auto subBlock = b.findSubBlock(
"DO");
319 if (!subBlock.isNull()) {
327 const auto validityRange =
ticketLayout().text(3, 1, 36, 1).trimmed();
328 const auto idx = std::max(validityRange.lastIndexOf(
QLatin1Char(
' ')), validityRange.lastIndexOf(
QLatin1Char(
'-')));
332 return rct2.outboundArrivalTime();
342 Person p = FcbExtractor::person(flex.fcb());
351 auto sblock = b.findSubBlock(
"028");
352 if (!sblock.isNull()) {
353 const auto endIt = sblock.content() + sblock.contentSize();
354 auto it = std::find(sblock.content(), endIt,
'#');
357 p.setGivenName(
QString::fromUtf8(sblock.content(), std::distance(sblock.content(), it)));
364 sblock = b.findSubBlock(
"023");
365 if (!sblock.isNull()) {
367 p.setName(sblock.toString());
373 const auto subBlock = b.findSubBlock(
"KJ");
374 if (!subBlock.isNull()) {
376 p.setName(subBlock.toString());
383 if (rct2.isValid()) {
384 const auto name = rct2.passengerName();
385 if (!name.isEmpty()) {
401 station.setName(rtc2.outboundDepartureStation());
406 if (
const auto sblock = b.findSubBlock(
"015"); !sblock.isNull()) {
407 station.setName(sblock.toString());
410 if (
const auto sblock = b.findSubBlock(
"035"); !sblock.isNull() && sblock.contentSize() <= 7) {
411 QString ibnr = QStringLiteral(
"ibnr:8000000");
412 const auto s = sblock.toString();
413 station.setIdentifier(ibnr.
replace(ibnr.
size() - s.size(), s.size(), s));
419 FcbExtractor::readDepartureStation(flex.transportDocuments().at(0), station);
431 station.setName(rtc2.outboundArrivalStation());
435 if (
const auto sblock = b.findSubBlock(
"016"); !sblock.isNull()) {
436 station.setName(sblock.toString());
439 if (
const auto sblock = b.findSubBlock(
"036"); !sblock.isNull() && sblock.contentSize() <= 7) {
440 QString ibnr = QStringLiteral(
"ibnr:8000000");
441 const auto s = sblock.toString();
442 station.setIdentifier(ibnr.
replace(ibnr.
size() - s.size(), s.size(), s));
448 FcbExtractor::readArrivalStation(flex.transportDocuments().at(0), station);
460 station.setName(rtc2.returnDepartureStation());
463 const auto outboundArrival = outboundArrivalStation();
466 if (
const auto sblock = b.findSubBlock(
"017"); !sblock.isNull()) {
467 station.setName(sblock.toString());
469 if (outboundArrival.name() == station.name()) {
470 station.setIdentifier(outboundArrival.identifier());
476 const auto doc = flex.transportDocuments().at(0);
477 VariantVisitor([&station, outboundArrival](
auto &&nrt) {
478 if (nrt.returnIncluded && nrt.returnDescriptionIsSet()) {
479 station.setName(nrt.returnDescription.fromStationNameUTF8);
481 }
else if (nrt.returnIncluded) {
482 if (outboundArrival.name() == station.name()) {
483 station.setIdentifier(outboundArrival.identifier());
486 }).visit<Fcb::v13::OpenTicketData, Fcb::v3::OpenTicketData>(doc);
487 FcbExtractor::fixStationCode(station);
499 station.setName(rtc2.returnArrivalStation());
502 const auto outboundDeparture = outboundDepartureStation();
505 if (
const auto sblock = b.findSubBlock(
"018"); !sblock.isNull()) {
506 station.setName(sblock.toString());
508 if (outboundDeparture.name() == station.name()) {
509 station.setIdentifier(outboundDeparture.identifier());
515 const auto doc = flex.transportDocuments().at(0);
516 VariantVisitor([&station, outboundDeparture](
auto &&nrt) {
517 if (nrt.returnIncluded && nrt.returnDescriptionIsSet()) {
518 station.setName(nrt.returnDescription.toStationNameUTF8);
520 }
else if (nrt.returnIncluded) {
521 if (outboundDeparture.name() == station.name()) {
522 station.setIdentifier(outboundDeparture.identifier());
525 }).visit<Fcb::v13::OpenTicketData, Fcb::v3::OpenTicketData>(doc);
526 FcbExtractor::fixStationCode(station);
535 if (
auto c = FcbExtractor::seatingType(flex.fcb()); !c.isEmpty()) {
542 const auto sblock = b.findSubBlock(
"014");
543 if (!sblock.isNull()) {
544 const auto s = sblock.toString();
545 return s.startsWith(
QLatin1Char(
'S')) ? s.right(1) : s;
550 return rct2.outboundClass();
560QVariant Uic9183Parser::ticketLayoutVariant()
const
574QVariant Uic9183Parser::rct2TicketVariant()
const
577 if (rct2.isValid()) {
599#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 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.
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 fromUtf8(QByteArrayView str)
bool isEmpty() const const
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
qsizetype size() const const
QVariant fromValue(T &&value)
bool isValid() const const