9#include "uic9183ticketlayout.h"
11#include "text/pricefinder_p.h"
15#include <QRegularExpression>
25class Rct2TicketPrivate :
public QSharedData
28 QDate firstDayOfValidity()
const;
29 QDateTime parseTime(
const QString &dateStr,
const QString &timeStr)
const;
30 QString reservationPatternCapture(QStringView name)
const;
32 Uic9183TicketLayout layout;
38QDate Rct2TicketPrivate::firstDayOfValidity()
const
40 const auto f = layout.text(3, 1, 48, 1);
41 const auto it = std::find_if(f.begin(), f.end(), [](QChar c) { return c.isDigit(); });
46 const auto dtStr = QStringView(f).mid(std::distance(f.begin(), it));
47 for (
const auto format : {
"dd.MM.yyyy"_L1,
"dd/MM/yyyy"_L1,
"dd.MM.yy"_L1,
"yyyy"_L1 }) {
50 if (dt.year() < 2000) {
51 dt.setDate(dt.year() + 100, dt.month(), dt.day());
59QDateTime Rct2TicketPrivate::parseTime(
const QString &dateStr,
const QString &timeStr)
const
73 const auto validDt = firstDayOfValidity();
74 const auto baseDate = validDt.isValid() ? validDt : contextDt.date();
75 auto dt = QDateTime({baseDate.year(), d.month(), d.day()}, t);
76 if (dt.isValid() && dt.date() < baseDate) {
82static constexpr const char* res_patterns[] = {
83 "ZUG +(?P<train_number>\\d+) +(?P<train_category>[A-Z][A-Z0-9]+) +WAGEN +(?P<coach>\\d+) +PLATZ +(?P<seat>\\d[\\d, ]+)",
84 "ZUG +(?P<train_number>\\d+) +WAGEN +(?P<coach>\\d+) +PLATZ +(?P<seat>\\d[\\d, ]+)",
87QString Rct2TicketPrivate::reservationPatternCapture(QStringView name)
const
89 const auto text = layout.text(8, 0, 72, 1);
90 for (
const auto *pattern : res_patterns) {
91 QRegularExpression re{QLatin1StringView(pattern),
93 Q_ASSERT(re.isValid());
94 const auto match = re.match(text);
95 if (
match.hasMatch()) {
96 return match.captured(name);
109Rct2Ticket::Rct2Ticket()
110 : d(new Rct2TicketPrivate)
115 : d(new Rct2TicketPrivate)
120Rct2Ticket::Rct2Ticket(
const Rct2Ticket&) =
default;
121Rct2Ticket::~Rct2Ticket() =
default;
127 return d->layout.isValid() && (d->layout.type() ==
"RCT2"_L1 || d->layout.type() ==
"RTC2"_L1);
132 d->contextDt = contextDt;
135QDate Rct2Ticket::firstDayOfValidity()
const
137 return d->firstDayOfValidity();
140static constexpr const struct {
143} rct2_ticket_type_map[] = {
170 const auto typeName1 = d->layout.text(0, 14, 38, 1).trimmed().remove(
QLatin1Char(
' ')).toCaseFolded();
171 const auto typeName2 = d->layout.text(1, 14, 38, 1).trimmed().remove(
QLatin1Char(
' ')).toCaseFolded();
174 for (
auto it = std::begin(rct2_ticket_type_map); it != std::end(rct2_ticket_type_map); ++it) {
180 for (
auto it = std::begin(rct2_ticket_type_map); it != std::end(rct2_ticket_type_map); ++it) {
188 for (
const auto &f : d->layout.fields(0, 14, 38, 2)) {
189 for (
auto it = std::begin(rct2_ticket_type_map); it != std::end(rct2_ticket_type_map); ++it) {
203 return d->layout.text(0, 18, 19, 1);
207 if (d->layout.text(0, 15, 3, 1).trimmed().isEmpty()) {
208 const auto s = d->layout.text(0, 18, 33, 1).trimmed();
209 return s.isEmpty() ? d->layout.text(1, 18, 33, 1).trimmed() : s;
213 return d->layout.text(0, 0, 52, 1).
trimmed();
218 const auto name = d->layout.text(0, 52, 19, 1).
trimmed();
220 return std::any_of(name.begin(), name.end(), [](
QChar c) { return c.isDigit(); }) ?
QString() : name;
225 return d->parseTime(d->layout.text(6, 1, 5, 1).trimmed(), d->layout.text(6, 7, 5, 1).trimmed());
230 auto dt = d->parseTime(d->layout.text(6, 52, 5, 1).trimmed(), d->layout.text(6, 58, 5, 1).trimmed());
231 if (dt.isValid() && dt < outboundDepartureTime()) {
240 if (std::all_of(s.
begin(), s.
end(), [](
QChar c) { return c == QLatin1Char(
'*'); })) {
246QString Rct2Ticket::outboundDepartureStation()
const
253 const auto fields = d->layout.containedFields(6, 13, 17, 1);
254 if (fields.size() == 1) {
255 return rct2Clean(fields[0].text().trimmed());
257 return rct2Clean(d->layout.text(6, 12, 18, 1).trimmed());
260QString Rct2Ticket::outboundArrivalStation()
const
262 return type() !=
RailPass ? rct2Clean(d->layout.text(6, 34, 17, 1).trimmed()) :
QString();
267 return rct2Clean(d->layout.text(6, 66, 5, 1).trimmed());
272 return d->parseTime(d->layout.text(7, 1, 5, 1).trimmed(), d->layout.text(7, 7, 5, 1).trimmed());
277 auto dt = d->parseTime(d->layout.text(7, 52, 5, 1).trimmed(), d->layout.text(7, 58, 5, 1).trimmed());
278 if (dt.isValid() && dt < returnDepartureTime()) {
284QString Rct2Ticket::returnDepartureStation()
const
287 return type() !=
RailPass ? rct2Clean(d->layout.text(7, 12, 18, 1).trimmed()) :
QString();
290QString Rct2Ticket::returnArrivalStation()
const
292 return type() !=
RailPass ? rct2Clean(d->layout.text(7, 34, 17, 1).trimmed()) :
QString();
297 return rct2Clean(d->layout.text(7, 66, 5, 1).trimmed());
302 const auto t = type();
304 auto num = d->reservationPatternCapture(u
"train_number");
305 if (!num.isEmpty()) {
306 return d->reservationPatternCapture(u
"train_category") +
QLatin1Char(
' ') + num;
309 const auto cat = d->layout.text(8, 13, 3, 1).trimmed();
310 num = d->layout.text(8, 7, 5, 1).trimmed();
313 if (num.isEmpty() || num.at(0).isDigit()) {
314 const auto numPrefix = d->layout.text(8, 1, 6, 1);
315 for (
int i = numPrefix.size() - 1; i >= 0; --i) {
316 if (numPrefix.at(i).isDigit()) {
317 num.prepend(numPrefix.at(i));
325 if (!cat.isEmpty()) {
335 const auto t = type();
337 const auto coach = d->reservationPatternCapture(u
"coach");
338 return coach.isEmpty() ? d->layout.text(8, 26, 3, 1).trimmed() : coach;
345 const auto t = type();
347 const auto seat = d->reservationPatternCapture(u
"seat");
348 if (!seat.isEmpty()) {
352 const auto row8 = d->layout.text(8, 48, 23, 1).trimmed();
353 if (!row8.isEmpty()) {
357 return d->layout.text(9, 32, 19, 2).simplified();
364 std::vector<PriceFinder::Result> result;
366 finder.findAll(d->layout.text(13, 52, 19, 1).remove(
QLatin1Char(
'*')), result);
367 return result.
size() == 1 ? result[0].currency :
QString();
370double Rct2Ticket::price()
const
372 std::vector<PriceFinder::Result> result;
374 finder.findAll(d->layout.text(13, 52, 19, 1).remove(
QLatin1Char(
'*')), result);
375 return result.size() == 1 ? result[0].value : NAN;
378#include "moc_rct2ticket.cpp"
RCT2 ticket layout payload of an UIC 918.3 ticket token.
@ Transport
Non-integrated Reservation Ticket (NRT)
@ RailPass
Rail Pass Ticket (RPT)
@ Upgrade
Update Document (UPG)
@ TransportReservation
Integration Reservation Ticket (IRT)
@ Unknown
ticket type could not be detected, or ticket type not supported yet
@ Reservation
Reservation Only Document (RES)
void setContextDate(const QDateTime &contextDt)
Date/time this ticket was first encountered, to recover possibly missing year numbers.
bool isValid() const
Returns whether this is a valid RCT2 ticket layout block.
Parser for a U_TLAY block in a UIC 918-3 ticket container, such as a ERA TLB ticket.
KCOREADDONS_EXPORT Result match(QStringView pattern, QStringView str)
Classes for reservation/travel data models, data extraction and data augmentation.
QDate fromString(QStringView string, QStringView format, QCalendar cal)
qsizetype size() const const
QString trimmed() const const
QTime fromString(QStringView string, QStringView format)