KItinerary

uic9183documentprocessor.cpp
1/*
2 SPDX-FileCopyrightText: 2018-2021 Volker Krause <vkrause@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include "uic9183documentprocessor.h"
8
9#include <KItinerary/ExtractorResult>
10#include <KItinerary/ExtractorValidator>
11#include <KItinerary/JsonLdDocument>
12#include <KItinerary/Rct2Ticket>
13#include <KItinerary/Reservation>
14#include <KItinerary/Uic9183Parser>
15#include <KItinerary/Uic9183TicketLayout>
16#include <KItinerary/Ticket>
17#include <KItinerary/TrainTrip>
18
19#include "era/fcbticket.h"
20#include "era/fcbutil.h"
21
22#include "uic9183/uic9183flex.h"
23#include "uic9183/uic9183head.h"
24#include "uic9183/vendor0080block.h"
25
26#include "variantvisitor_p.h"
27
28#include <KLocalizedString>
29
30#include <QDateTime>
31#include <QJsonArray>
32#include <QJsonObject>
33
34using namespace KItinerary;
35
36Uic9183DocumentProcessor::Uic9183DocumentProcessor()
37{
38 qRegisterMetaType<KItinerary::Uic9183TicketLayoutField>();
39 qRegisterMetaType<KItinerary::Vendor0080BLOrderBlock>();
40}
41
42bool Uic9183DocumentProcessor::canHandleData(const QByteArray &encodedData, [[maybe_unused]] QStringView fileName) const
43{
44 return Uic9183Parser::maybeUic9183(encodedData);
45}
46
48{
50 p.parse(encodedData);
51 if (!p.isValid()) {
52 return {};
53 }
54
56 node.setContent(p);
57 return node;
58}
59
60void Uic9183DocumentProcessor::expandNode(ExtractorDocumentNode &node, [[maybe_unused]] const ExtractorEngine *engine) const
61{
62 // only use the U_HEAD issuing time as context if we have nothing better
63 // while that is usually correct it cannot contain a time zone, unlike the (often) enclosing PDF document´
64 if (!node.contextDateTime().isValid()) {
65 const auto p = node.content<Uic9183Parser>();
66 if (const auto u_flex = p.findBlock<Uic9183Flex>(); u_flex.isValid()) {
67 node.setContextDateTime(std::visit([](auto &&fcb) { return fcb.issuingDetail.issueingDateTime(); }, u_flex.fcb()));
68 } else if (const auto u_head = p.findBlock<Uic9183Head>(); u_head.isValid()) {
69 node.setContextDateTime(u_head.issuingDateTime());
70 }
71 }
72}
73
74template <typename CardReferenceTypeT>
75static ProgramMembership extractCustomerCard(const CardReferenceTypeT &card)
76{
78 p.setProgramName(card.cardName);
79 if (card.cardIdNumIsSet()) {
80 p.setMembershipNumber(QString::number(card.cardIdNum));
81 } else if (card.cardIdIA5IsSet()) {
82 p.setMembershipNumber(QString::fromUtf8(card.cardIdIA5));
83 }
84 return p;
85}
86
87template <typename TariffTypeT>
88static ProgramMembership extractCustomerCard(const QList <TariffTypeT> &tariffs)
89{
90 // TODO what do we do with the (so far theoretical) case of multiple discount cards in use?
91 for (const auto &tariff : tariffs) {
92 for (const auto &card : tariff.reductionCard) {
93 return extractCustomerCard(card);
94 }
95 }
96
97 return {};
98}
99
100void Uic9183DocumentProcessor::preExtract(ExtractorDocumentNode &node, [[maybe_unused]] const ExtractorEngine *engine) const
101{
102 const auto p = node.content<Uic9183Parser>();
103
104 Ticket ticket;
105 ticket.setName(p.name());
106 ticket.setTicketToken(QLatin1StringView("aztecbin:") +
107 QString::fromLatin1(p.rawData().toBase64()));
108 Seat seat;
109 if (const auto seatingType = p.seatingType(); !seatingType.isEmpty()) {
110 seat.setSeatingType(seatingType);
111 }
112
114 res.setReservationNumber(p.pnr());
115 res.setUnderName(p.person());
116
117 ExtractorValidator validator;
118 validator.setAcceptedTypes<TrainTrip>();
119
120 QList<QVariant> results;
121
122 const auto rct2 = p.rct2Ticket();
123 if (rct2.isValid()) {
124 TrainTrip trip, returnTrip;
125 trip.setProvider(p.issuer());
126
127 switch (rct2.type()) {
130 break;
133 {
134 trip.setTrainNumber(rct2.trainNumber());
135 seat.setSeatSection(rct2.coachNumber());
136 seat.setSeatNumber(rct2.seatNumber());
137 [[fallthrough]];
138 }
141 {
142 trip.setDepartureStation(p.outboundDepartureStation());
143 trip.setArrivalStation(p.outboundArrivalStation());
144
145 if (rct2.outboundDepartureTime().isValid()) {
146 trip.setDepartureDay(rct2.outboundDepartureTime().date());
147 } else {
148 trip.setDepartureDay(rct2.firstDayOfValidity());
149 }
150
151 if (rct2.outboundDepartureTime() != rct2.outboundArrivalTime()) {
152 trip.setDepartureTime(rct2.outboundDepartureTime());
153 trip.setArrivalTime(rct2.outboundArrivalTime());
154 }
155
156 if (rct2.type() == Rct2Ticket::Transport && !p.returnDepartureStation().name().isEmpty()) {
157 returnTrip.setProvider(p.issuer());
158 returnTrip.setDepartureStation(p.returnDepartureStation());
159 returnTrip.setArrivalStation(p.returnArrivalStation());
160
161 if (rct2.returnDepartureTime().isValid()) {
162 returnTrip.setDepartureDay(rct2.returnDepartureTime().date());
163 } else {
164 returnTrip.setDepartureDay(rct2.firstDayOfValidity());
165 }
166
167 if (rct2.returnDepartureTime() != rct2.returnArrivalTime()) {
168 returnTrip.setDepartureTime(rct2.returnDepartureTime());
169 returnTrip.setArrivalTime(rct2.returnArrivalTime());
170 }
171 }
172
173 break;
174 }
175 }
176
177 if (const auto currency = rct2.currency(); !currency.isEmpty()) {
178 res.setPriceCurrency(currency);
179 res.setTotalPrice(rct2.price());
180 }
181
182 // provide names for typically "addon" tickets, so we can distinguish them in the UI
183 switch (rct2.type()) {
185 ticket.setName(i18n("Reservation"));
186 break;
188 ticket.setName(i18n("Upgrade"));
189 break;
190 default:
191 break;
192 }
193
194 ticket.setTicketedSeat(seat);
195 if (validator.isValidElement(trip)) {
196 res.setReservationFor(trip);
197 res.setReservedTicket(ticket);
198 results.push_back(res);
199 }
200 if (validator.isValidElement(returnTrip)) {
201 res.setReservationFor(returnTrip);
202 res.setReservedTicket(ticket);
203 results.push_back(res);
204 }
205 }
206
207 if (const auto flex = p.findBlock<Uic9183Flex>(); flex.isValid()) {
208 res.setPriceCurrency(QString::fromUtf8(std::visit([](auto &&fcb) { return fcb.issuingDetail.currency; }, flex.fcb())));
209 for (const auto &doc : flex.transportDocuments()) {
210 VariantVisitor([&p, doc, ticket, &res, flex, &validator, &results](auto &&irt) {
211 TrainTrip trip;
212 trip.setProvider(p.issuer());
213
214 TrainStation dep;
216 trip.setDepartureStation(dep);
217
218 TrainStation arr;
220 trip.setArrivalStation(arr);
221
222 trip.setDepartureTime(irt.departureDateTime(flex.issuingDateTime()));
223 trip.setArrivalTime(irt.arrivalDateTime(flex.issuingDateTime()));
224
225 if (irt.trainNumIsSet()) {
226 trip.setTrainNumber(irt.serviceBrandAbrUTF8 + QLatin1Char(' ') + QString::number(irt.trainNum));
227 } else {
228 trip.setTrainNumber(irt.serviceBrandAbrUTF8 + QLatin1Char(' ') + QString::fromUtf8(irt.trainIA5));
229 }
230
231 Seat s;
232 s.setSeatingType(FcbUtil::classCodeToString(irt.classCode));
233 if (irt.placesIsSet()) {
234 s.setSeatSection(QString::fromUtf8(irt.places.coach));
235 QStringList l;
236 for (const auto &b : irt.places.placeIA5)
238 for (auto i : irt.places.placeNum)
240 s.setSeatNumber(l.join(QLatin1StringView(", ")));
241 // TODO other seat encoding variants
242 }
243
244 Ticket t(ticket);
245 t.setTicketedSeat(s);
246 res.setProgramMembershipUsed(extractCustomerCard(irt.tariffs));
247
248 if (irt.priceIsSet()) {
249 res.setTotalPrice(irt.price / std::pow(10, std::visit([](auto &&fcb) { return fcb.issuingDetail.currencyFract; }, flex.fcb())));
250 }
251
252 if (validator.isValidElement(trip)) {
253 res.setReservationFor(trip);
254 res.setReservedTicket(t);
255 results.push_back(res);
256 }
257 }).visit<Fcb::v13::ReservationData, Fcb::v3::ReservationData>(doc);
258
259 VariantVisitor([&p, ticket, flex, &res, &results, &validator](auto &&nrt) {
260 Seat s;
261 s.setSeatingType(FcbUtil::classCodeToString(nrt.classCode));
262 Ticket t(ticket);
263 t.setTicketedSeat(s);
264 res.setProgramMembershipUsed(extractCustomerCard(nrt.tariffs));
265
266 if (nrt.priceIsSet()) {
267 res.setTotalPrice(nrt.price / std::pow(10, std::visit([](auto &&fcb) { return fcb.issuingDetail.currencyFract; }, flex.fcb())));
268 }
269
270 // check for TrainLinkType regional validity constrains
271 bool trainLinkTypeFound = false;
272 for (const auto &regionalValidity : nrt.validRegion) {
273 if (regionalValidity.value.userType() != qMetaTypeId<Fcb::v13::TrainLinkType>()) {
274 continue;
275 }
276 const auto trainLink = regionalValidity.value.template value<Fcb::v13::TrainLinkType>();
277 TrainTrip trip;
278 trip.setProvider(p.issuer());
279
280 // TODO station identifier, use Uic9183Flex::read[Arrival|Departure]Station
281 TrainStation dep;
282 dep.setName(trainLink.fromStationNameUTF8);
284 trip.setDepartureStation(dep);
285
286 TrainStation arr;
287 arr.setName(trainLink.toStationNameUTF8);
289 trip.setArrivalStation(arr);
290
291 trip.setDepartureTime(trainLink.departureDateTime(flex.issuingDateTime()));
292
293 if (trainLink.trainNumIsSet()) {
294 trip.setTrainNumber(QString::number(trainLink.trainNum));
295 } else {
296 trip.setTrainNumber(QString::fromUtf8(trainLink.trainIA5));
297 }
298
299 if (validator.isValidElement(trip)) {
300 res.setReservationFor(trip);
301 res.setReservedTicket(t);
302 results.push_back(res);
303 trainLinkTypeFound = true;
304 }
305 }
306
307 if (!trainLinkTypeFound) {
308 TrainTrip trip;
309 trip.setProvider(p.issuer());
310 trip.setDepartureStation(p.outboundDepartureStation());
311 trip.setArrivalStation(p.outboundArrivalStation());
312 trip.setDepartureDay(nrt.validFrom(flex.issuingDateTime()).date());
313 if (validator.isValidElement(trip)) {
314 res.setReservationFor(trip);
315 res.setReservedTicket(t);
316 results.push_back(res);
317 }
318 // TODO handle nrt.returnIncluded
319 }
320 }).visit<Fcb::v13::OpenTicketData, Fcb::v3::OpenTicketData>(doc);
321
322 VariantVisitor([&p, ticket, &results](auto &&ccd) {
324 if (ccd.cardIdNumIsSet()) {
325 pm.setMembershipNumber(QString::number(ccd.cardIdNum));
326 } else {
327 pm.setMembershipNumber(QString::fromUtf8(ccd.cardIdIA5));
328 }
329 pm.setProgramName(ccd.cardTypeDescr);
330 pm.setMember(p.person());
331 pm.setValidFrom(ccd.validFrom().startOfDay());
332 pm.setValidUntil(ccd.validUntil().startOfDay());
333 pm.setToken(ticket.ticketToken());
334 results.push_back(pm);
335 }).visit<Fcb::v13::CustomerCardData, Fcb::v3::CustomerCardData>(doc);
336 }
337 }
338
339 if (!results.isEmpty()) {
340 node.addResult(results);
341 return;
342 }
343
344 // only Ticket
345 ticket.setTicketedSeat(seat);
346 ticket.setIssuedBy(p.issuer());
347 ticket.setTicketNumber(p.pnr());
348 ticket.setUnderName(p.person());
349 ticket.setValidFrom(p.validFrom());
350 ticket.setValidUntil(p.validUntil());
351 node.addResult(QList<QVariant>({ticket}));
352}
353
A node in the extracted document object tree.
void setContextDateTime(const QDateTime &contextDateTime)
Set the context date/time.
QJSValue content
The decoded content of this node.
void addResult(ExtractorResult &&result)
Add additional results from an extraction step.
QDateTime contextDateTime
The best known context date/time at this point in the document tree.
void setContent(const QVariant &content)
Set decoded content.
Semantic data extraction engine.
Validates extractor results.
bool isValidElement(const QVariant &elem) const
Checks if the given element is valid.
void setAcceptedTypes(std::vector< const QMetaObject * > &&accptedTypes)
Sets the list of supported top-level types that should be accepted.
static QString classCodeToString(Fcb::v13::TravelClassType classCode)
Convert a class code enum value to a string for human representation.
Definition fcbutil.cpp:30
A frequent traveler, bonus points or discount scheme program membership.
QDateTime validFrom
Non-standard extension for ticket validity time ranges.
@ Transport
Non-integrated Reservation Ticket (NRT)
Definition rct2ticket.h:70
@ RailPass
Rail Pass Ticket (RPT)
Definition rct2ticket.h:74
@ Upgrade
Update Document (UPG)
Definition rct2ticket.h:73
@ TransportReservation
Integration Reservation Ticket (IRT)
Definition rct2ticket.h:71
@ Unknown
ticket type could not be detected, or ticket type not supported yet
Definition rct2ticket.h:75
@ Reservation
Reservation Only Document (RES)
Definition rct2ticket.h:72
A reserved seat.
Definition ticket.h:23
A booked ticket.
Definition ticket.h:41
A train reservation.
Train station.
Definition place.h:126
A train trip.
Definition traintrip.h:24
void expandNode(ExtractorDocumentNode &node, const ExtractorEngine *engine) const override
Create child nodes for node, as far as that's necessary for this document type.
ExtractorDocumentNode createNodeFromData(const QByteArray &encodedData) const override
Create a document node from raw data.
void preExtract(ExtractorDocumentNode &node, const ExtractorEngine *engine) const override
Called before extractors are applied to node.
bool canHandleData(const QByteArray &encodedData, QStringView fileName) const override
Fast check whether the given encoded data can possibly be processed by this instance.
Represents a U_FLEX block holding different versions of an FCB payload.
Definition uic9183flex.h:24
bool isValid() const
Returns whether this is a valid U_FLEX layout block.
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.
Definition uic9183head.h:21
bool isValid() const
Returns true if this is a valid U_HEAD block.
Parser for UIC 918.3 and 918.3* train tickets.
static bool maybeUic9183(const QByteArray &data)
Quickly checks if might be UIC 918.3 content.
QString i18n(const char *text, const TYPE &arg...)
Classes for reservation/travel data models, data extraction and data augmentation.
Definition berelement.h:17
bool isValid() const const
void push_back(parameter_type value)
QString fromLatin1(QByteArrayView str)
QString fromUtf8(QByteArrayView str)
QString number(double n, char format, int precision)
QString join(QChar separator) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Jan 24 2025 11:52:36 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.