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/fcbextractor_p.h"
20#include "era/fcbticket.h"
21#include "era/fcbutil.h"
22
23#include "uic9183/uic9183flex.h"
24#include "uic9183/uic9183head.h"
25#include "uic9183/vendor0080block.h"
26
27#include "variantvisitor_p.h"
28
29#include <KLocalizedString>
30
31#include <QDateTime>
32#include <QJsonArray>
33#include <QJsonObject>
34
35using namespace KItinerary;
36
37Uic9183DocumentProcessor::Uic9183DocumentProcessor()
38{
39 qRegisterMetaType<KItinerary::Uic9183TicketLayoutField>();
40 qRegisterMetaType<KItinerary::Vendor0080BLOrderBlock>();
41}
42
43bool Uic9183DocumentProcessor::canHandleData(const QByteArray &encodedData, [[maybe_unused]] QStringView fileName) const
44{
45 return Uic9183Parser::maybeUic9183(encodedData);
46}
47
49{
51 p.parse(encodedData);
52 if (!p.isValid()) {
53 return {};
54 }
55
57 node.setContent(p);
58 return node;
59}
60
61void Uic9183DocumentProcessor::expandNode(ExtractorDocumentNode &node, [[maybe_unused]] const ExtractorEngine *engine) const
62{
63 // only use the U_HEAD issuing time as context if we have nothing better
64 // while that is usually correct it cannot contain a time zone, unlike the (often) enclosing PDF document´
65 if (!node.contextDateTime().isValid()) {
66 const auto p = node.content<Uic9183Parser>();
67 if (const auto u_flex = p.findBlock<Uic9183Flex>(); u_flex.isValid()) {
68 node.setContextDateTime(std::visit([](auto &&fcb) { return fcb.issuingDetail.issueingDateTime(); }, u_flex.fcb()));
69 } else if (const auto u_head = p.findBlock<Uic9183Head>(); u_head.isValid()) {
70 node.setContextDateTime(u_head.issuingDateTime());
71 }
72 }
73}
74
75template <typename CardReferenceTypeT>
76static ProgramMembership extractCustomerCard(const CardReferenceTypeT &card)
77{
79 p.setProgramName(card.cardName);
80 if (card.cardIdNumIsSet()) {
81 p.setMembershipNumber(QString::number(card.cardIdNum));
82 } else if (card.cardIdIA5IsSet()) {
83 p.setMembershipNumber(QString::fromUtf8(card.cardIdIA5));
84 }
85 return p;
86}
87
88template <typename TariffTypeT>
89static ProgramMembership extractCustomerCard(const QList <TariffTypeT> &tariffs)
90{
91 // TODO what do we do with the (so far theoretical) case of multiple discount cards in use?
92 for (const auto &tariff : tariffs) {
93 for (const auto &card : tariff.reductionCard) {
94 return extractCustomerCard(card);
95 }
96 }
97
98 return {};
99}
100
101void Uic9183DocumentProcessor::preExtract(ExtractorDocumentNode &node, [[maybe_unused]] const ExtractorEngine *engine) const
102{
103 const auto p = node.content<Uic9183Parser>();
104
105 Ticket ticket;
106 ticket.setName(p.name());
107 ticket.setTicketToken(QLatin1StringView("aztecbin:") +
108 QString::fromLatin1(p.rawData().toBase64()));
109 Seat seat;
110 if (const auto seatingType = p.seatingType(); !seatingType.isEmpty()) {
111 seat.setSeatingType(seatingType);
112 }
113
115 res.setReservationNumber(p.pnr());
116 res.setUnderName(p.person());
117
118 ExtractorValidator validator;
119 validator.setAcceptedTypes<TrainTrip>();
120
121 QList<QVariant> results;
122
123 const auto rct2 = p.rct2Ticket();
124 if (rct2.isValid()) {
125 TrainTrip trip, returnTrip;
126 trip.setProvider(p.issuer());
127
128 switch (rct2.type()) {
131 break;
134 {
135 trip.setTrainNumber(rct2.trainNumber());
136 seat.setSeatSection(rct2.coachNumber());
137 seat.setSeatNumber(rct2.seatNumber());
138 [[fallthrough]];
139 }
142 {
143 trip.setDepartureStation(p.outboundDepartureStation());
144 trip.setArrivalStation(p.outboundArrivalStation());
145
146 if (rct2.outboundDepartureTime().isValid()) {
147 trip.setDepartureDay(rct2.outboundDepartureTime().date());
148 } else {
149 trip.setDepartureDay(rct2.firstDayOfValidity());
150 }
151
152 if (rct2.outboundDepartureTime() != rct2.outboundArrivalTime()) {
153 trip.setDepartureTime(rct2.outboundDepartureTime());
154 trip.setArrivalTime(rct2.outboundArrivalTime());
155 }
156
157 if (rct2.type() == Rct2Ticket::Transport && !p.returnDepartureStation().name().isEmpty()) {
158 returnTrip.setProvider(p.issuer());
159 returnTrip.setDepartureStation(p.returnDepartureStation());
160 returnTrip.setArrivalStation(p.returnArrivalStation());
161
162 if (rct2.returnDepartureTime().isValid()) {
163 returnTrip.setDepartureDay(rct2.returnDepartureTime().date());
164 } else {
165 returnTrip.setDepartureDay(rct2.firstDayOfValidity());
166 }
167
168 if (rct2.returnDepartureTime() != rct2.returnArrivalTime()) {
169 returnTrip.setDepartureTime(rct2.returnDepartureTime());
170 returnTrip.setArrivalTime(rct2.returnArrivalTime());
171 }
172 }
173
174 break;
175 }
176 }
177
178 if (const auto currency = rct2.currency(); !currency.isEmpty()) {
179 res.setPriceCurrency(currency);
180 res.setTotalPrice(rct2.price());
181 }
182
183 // provide names for typically "addon" tickets, so we can distinguish them in the UI
184 switch (rct2.type()) {
186 ticket.setName(i18n("Reservation"));
187 break;
189 ticket.setName(i18n("Upgrade"));
190 break;
191 default:
192 break;
193 }
194
195 ticket.setTicketedSeat(seat);
196 if (validator.isValidElement(trip)) {
197 res.setReservationFor(trip);
198 res.setReservedTicket(ticket);
199 results.push_back(res);
200 }
201 if (validator.isValidElement(returnTrip)) {
202 res.setReservationFor(returnTrip);
203 res.setReservedTicket(ticket);
204 results.push_back(res);
205 }
206 }
207
208 if (const auto flex = p.findBlock<Uic9183Flex>(); flex.isValid()) {
209 res.setPriceCurrency(QString::fromUtf8(std::visit([](auto &&fcb) { return fcb.issuingDetail.currency; }, flex.fcb())));
210 for (const auto &doc : flex.transportDocuments()) {
211 ticket.setIssuedBy(p.issuer());
212 ticket.setTicketNumber(p.pnr());
213 FcbExtractor::extractReservation(doc, flex.fcb(), ticket, results);
214 FcbExtractor::extractOpenTicket(doc, flex.fcb(), ticket, results);
215 FcbExtractor::extractCustomerCard(doc, flex.fcb(), ticket, results);
216 }
217
218 FcbExtractor::applyPrice(ticket, flex.fcb());
219 }
220
221 if (!results.isEmpty()) {
222 node.addResult(results);
223 return;
224 }
225
226 // only Ticket
227 ticket.setTicketedSeat(seat);
228 ticket.setIssuedBy(p.issuer());
229 ticket.setTicketNumber(p.pnr());
230 ticket.setUnderName(p.person());
231 ticket.setValidFrom(p.validFrom());
232 ticket.setValidUntil(p.validUntil());
233 node.addResult(QList<QVariant>({ticket}));
234}
235
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.
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.
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:22
bool isValid() const
Returns whether this is a valid U_FLEX layout block.
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
QString fromLatin1(QByteArrayView str)
QString fromUtf8(QByteArrayView str)
QString number(double n, char format, int precision)
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Apr 11 2025 11:58:39 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.