KItinerary

fcbextractor.cpp
1/*
2 SPDX-FileCopyrightText: 2023 Volker Krause <vkrause@kde.org>
3 SPDX-License-Identifier: LGPL-2.0-or-later
4*/
5
6#include "fcbextractor_p.h"
7
8#include "variantvisitor_p.h"
9
10#include <KItinerary/Organization>
11#include <KItinerary/Person>
12#include <KItinerary/ProgramMembership>
13#include <KItinerary/Ticket>
14
15#include <type_traits>
16
17using namespace Qt::Literals;
18using namespace KItinerary;
19
20[[nodiscard]] static QString ticketNameForDocument(const QVariant &doc)
21{
22 return VariantVisitor([](auto &&doc) {
23 auto n = doc.tariffs.isEmpty() ? QString() : doc.tariffs.at(0).tariffDesc;
24 if (!n.isEmpty()) {
25 return n;
26 }
27 if constexpr (std::is_same_v<std::decay_t<decltype(doc)>, Fcb::v13::PassData> || std::is_same_v<std::decay_t<decltype(doc)>, Fcb::v3::PassData>) {
28 if (!doc.passDescription.isEmpty()) {
29 return doc.passDescription;
30 }
31 }
32
33 return doc.infoText;
34 }).visit<Fcb::v13::ReservationData, Fcb::v13::OpenTicketData, Fcb::v13::PassData, Fcb::v3::ReservationData, Fcb::v3::OpenTicketData, Fcb::v3::PassData>(doc);
35}
36
37QString FcbExtractor::ticketName(const Fcb::UicRailTicketData &fcb)
38{
39 return std::visit([](auto &&fcb) {
40 for (const auto &doc : fcb.transportDocument) {
41 if (auto n = ticketNameForDocument(doc.ticket); !n.isEmpty()) {
42 return n;
43 }
44 }
45 return QString();
46 }, fcb);
47}
48
49template <typename T>
50[[nodiscard]] static QString fcbReference(const T &data)
51{
52 if (!data.referenceIA5.isEmpty()) {
53 return QString::fromLatin1(data.referenceIA5);
54 }
55 if (data.referenceNumIsSet()) {
56 return QString::number(data.referenceNum);
57 }
58 return {};
59}
60
61QString FcbExtractor::pnr(const Fcb::UicRailTicketData &fcb)
62{
63 return std::visit([](auto &&fcb) {
64 if (!fcb.issuingDetail.issuerPNR.isEmpty()) {
65 return QString::fromLatin1(fcb.issuingDetail.issuerPNR);
66 }
67
68 for (const auto &doc : fcb.transportDocument) {
69 auto pnr = VariantVisitor([](auto &&doc) {
70 return fcbReference(doc);
71 }).template visit<Fcb::v13::ReservationData, Fcb::v13::OpenTicketData, Fcb::v13::PassData, Fcb::v3::ReservationData, Fcb::v3::OpenTicketData, Fcb::v3::PassData>(doc.ticket);
72 if (!pnr.isEmpty()) {
73 return pnr;
74 }
75 }
76
77 return QString();
78 }, fcb);
79}
80
81QString FcbExtractor::seatingType(const Fcb::UicRailTicketData &fcb)
82{
83 return std::visit([](auto &&fcb) {
84 for (const auto &doc : fcb.transportDocument) {
85 auto s = VariantVisitor([](auto &&doc) {
86 return FcbUtil::classCodeToString(doc.classCode);
87 }).template visit<Fcb::v13::ReservationData, Fcb::v13::OpenTicketData, Fcb::v13::PassData, Fcb::v3::ReservationData, Fcb::v3::OpenTicketData, Fcb::v3::PassData>(doc.ticket);
88 if (!s.isEmpty()) {
89 return s;
90 }
91 }
92 return QString();
93 }, fcb);
94}
95
96[[nodiscard]] static QString formatIssuerId(int num)
97{
98 auto id = QString::number(num);
99 if (id.size() < 4) {
100 id.insert(0, QString(4 - id.size(), '0'_L1));
101 }
102 return id;
103}
104
105QString FcbExtractor::issuerId(const Fcb::UicRailTicketData &fcb)
106{
107 return std::visit([](auto &&fcb) {
108 if (fcb.issuingDetail.issuerNumIsSet()) {
109 return formatIssuerId(fcb.issuingDetail.issuerNum);
110 }
111 if (fcb.issuingDetail.issuerIA5IsSet()) {
112 return QString::fromLatin1(fcb.issuingDetail.issuerIA5);
113 }
114 if (fcb.issuingDetail.securityProviderNumIsSet()) {
115 return formatIssuerId(fcb.issuingDetail.securityProviderNum);
116 }
117 if (fcb.issuingDetail.securityProviderIA5IsSet()) {
118 return QString::fromLatin1(fcb.issuingDetail.securityProviderIA5);
119 }
120 return QString();
121 }, fcb);
122}
123
124Organization FcbExtractor::issuer(const Fcb::UicRailTicketData &fcb)
125{
126 Organization issuer;
127 if (auto id = issuerId(fcb); !id.isEmpty()) {
128 issuer.setIdentifier("uic:"_L1 + id);
129 }
130 std::visit([&issuer](auto &&fcb) {
131 if (fcb.issuingDetail.issuerNameIsSet()) {
132 issuer.setName(fcb.issuingDetail.issuerName);
133 }
134 }, fcb);
135 return issuer;
136}
137
138Person FcbExtractor::person(const Fcb::UicRailTicketData &fcb)
139{
140 return std::visit([](auto &&fcb) {
141 Person p;
142 if (!fcb.travelerDetailIsSet() || fcb.travelerDetail.traveler.size() != 1) {
143 return p;
144 }
145 const auto traveler = fcb.travelerDetail.traveler.at(0);
146 if (traveler.firstNameIsSet() || traveler.secondNameIsSet()) {
147 p.setGivenName(QString(traveler.firstName + ' '_L1 + traveler.secondName).trimmed());
148 }
149 p.setFamilyName(traveler.lastName);
150 return p;
151 }, fcb);
152}
153
154QDateTime FcbExtractor::validFrom(const Fcb::UicRailTicketData &fcb)
155{
156 return std::visit([](auto &&fcb) {
157 for (const auto &doc : fcb.transportDocument) {
158 auto dt = VariantVisitor([&fcb](auto &&doc) {
159 return doc.departureDateTime(fcb.issuingDetail.issueingDateTime());
160 }).template visit<Fcb::v13::ReservationData, Fcb::v3::ReservationData>(doc.ticket);
161 if (dt.isValid()) {
162 return dt;
163 }
164 dt = VariantVisitor([&fcb](auto &&doc) {
165 return doc.validFrom(fcb.issuingDetail.issueingDateTime());
166 }).template visit<Fcb::v13::OpenTicketData, Fcb::v13::PassData, Fcb::v3::OpenTicketData, Fcb::v3::PassData>(doc.ticket);
167 if (dt.isValid()) {
168 return dt;
169 }
170 }
171 return QDateTime();
172 }, fcb);
173}
174
175QDateTime FcbExtractor::validUntil(const Fcb::UicRailTicketData &fcb)
176{
177 return std::visit([](auto &&fcb) {
178 for (const auto &doc : fcb.transportDocument) {
179 auto dt = VariantVisitor([&fcb](auto &&doc) {
180 return doc.arrivalDateTime(fcb.issuingDetail.issueingDateTime());
181 }).template visit<Fcb::v13::ReservationData, Fcb::v3::ReservationData>(doc.ticket);
182 if (dt.isValid()) {
183 return dt;
184 }
185 dt = VariantVisitor([&fcb](auto &&doc) {
186 return doc.validUntil(fcb.issuingDetail.issueingDateTime());
187 }).template visit<Fcb::v13::OpenTicketData, Fcb::v13::PassData, Fcb::v3::OpenTicketData, Fcb::v3::PassData>(doc.ticket);
188 if (dt.isValid()) {
189 return dt;
190 }
191 }
192 return QDateTime();
193 }, fcb);
194}
195
196FcbExtractor::PriceData FcbExtractor::price(const Fcb::UicRailTicketData &fcb)
197{
198 return std::visit([](auto &&fcb) {
199 PriceData p;
200 p.currency = QString::fromUtf8(fcb.issuingDetail.currency);
201 const auto fract = std::pow(10, fcb.issuingDetail.currencyFract);
202 for (const auto &doc : fcb.transportDocument) {
203 p.price = VariantVisitor([fract](auto &&doc) {
204 return doc.priceIsSet() ? doc.price / fract : NAN;
205 }).template visit<Fcb::v13::ReservationData, Fcb::v3::ReservationData, Fcb::v13::OpenTicketData, Fcb::v13::PassData, Fcb::v3::OpenTicketData, Fcb::v13::PassData, Fcb::v3::PassData>(doc.ticket);
206 if (!std::isnan(p.price)) {
207 continue;
208 }
209 }
210 return p;
211 }, fcb);
212}
213
214void FcbExtractor::extractCustomerCard(const QVariant &ccd, const Fcb::UicRailTicketData &fcb, const Ticket &ticket, QList<QVariant> &result)
215{
216 VariantVisitor([&fcb, &result, ticket](auto &&ccd) {
218 if (ccd.cardIdNumIsSet()) {
219 pm.setMembershipNumber(QString::number(ccd.cardIdNum));
220 } else {
221 pm.setMembershipNumber(QString::fromUtf8(ccd.cardIdIA5));
222 }
223 pm.setProgramName(ccd.cardTypeDescr);
224 pm.setMember(FcbExtractor::person(fcb));
225 pm.setValidFrom(ccd.validFrom().startOfDay());
226 pm.setValidUntil(ccd.validUntil().startOfDay());
227 pm.setToken(ticket.ticketToken());
228 result.push_back(pm);
229 }).visit<Fcb::v13::CustomerCardData, Fcb::v3::CustomerCardData>(ccd);
230}
231
232void FcbExtractor::readDepartureStation(const QVariant &doc, TrainStation &station)
233{
234 VariantVisitor([&station](auto &&data) {
235 FcbExtractor::readDepartureStation(data, station);
236 }).visit<Fcb::v13::ReservationData, Fcb::v13::OpenTicketData, Fcb::v3::ReservationData, Fcb::v3::OpenTicketData>(doc);
237}
238
239void FcbExtractor::readArrivalStation(const QVariant &doc, TrainStation &station)
240{
241 VariantVisitor([&station](auto &&data) {
242 FcbExtractor::readArrivalStation(data, station);
243 }).visit<Fcb::v13::ReservationData, Fcb::v13::OpenTicketData, Fcb::v3::ReservationData, Fcb::v3::OpenTicketData>(doc);
244}
245
246void FcbExtractor::fixStationCode(TrainStation &station)
247{
248 // UIC codes in Germany are wildly unreliable, there seem to be different
249 // code tables in use by different operators, so we unfortunately have to ignore
250 // those entirely
251 if (station.identifier().startsWith("uic:80"_L1)) {
252 PostalAddress addr;
253 addr.setAddressCountry(u"DE"_s);
254 station.setAddress(addr);
255 station.setIdentifier(QString());
256 }
257}
static QString classCodeToString(Fcb::v13::TravelClassType classCode)
Convert a class code enum value to a string for human representation.
Definition fcbutil.cpp:30
Rail pass document (RPT).
Definition fcbticket1.h:694
Rail pass document (RPT).
Definition fcbticket3.h:727
QString identifier
Identifier.
Definition place.h:85
Postal address.
Definition place.h:46
A frequent traveler, bonus points or discount scheme program membership.
A booked ticket.
Definition ticket.h:41
QString ticketToken
The raw ticket token string.
Definition ticket.h:50
Train station.
Definition place.h:126
Classes for reservation/travel data models, data extraction and data augmentation.
Definition berelement.h:17
void push_back(parameter_type value)
QString fromLatin1(QByteArrayView str)
QString fromUtf8(QByteArrayView str)
QString number(double n, char format, int precision)
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
QString trimmed() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Mar 28 2025 11:59:49 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.