KItinerary

uic9183parser.cpp
1 /*
2  SPDX-FileCopyrightText: 2018 Volker Krause <[email protected]>
3 
4  SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6 
7 #include "uic9183parser.h"
8 #include "logging.h"
9 #include "rct2ticket.h"
10 #include "uic9183block.h"
11 #include "uic9183head.h"
12 #include "uic9183header.h"
13 #include "uic9183ticketlayout.h"
14 #include "vendor0080block.h"
15 #include "vendor1154block.h"
16 
17 #include "era/fcbticket.h"
18 #include "era/fcbutil.h"
19 
20 #include <QDateTime>
21 #include <QDebug>
22 #include <QJsonDocument>
23 #include <QJsonObject>
24 #include <QTimeZone>
25 
26 #include <zlib.h>
27 
28 #include <cassert>
29 #include <cstring>
30 
31 using namespace KItinerary;
32 
33 namespace KItinerary {
34 
35 class Uic9183ParserPrivate : public QSharedData
36 {
37 public:
38  QByteArray m_data;
39  QByteArray m_payload;
40 };
41 }
42 
43 Uic9183Parser::Uic9183Parser()
44  : d(new Uic9183ParserPrivate)
45 {
46 }
47 
48 Uic9183Parser::Uic9183Parser(const Uic9183Parser&) = default;
49 Uic9183Parser::~Uic9183Parser() = default;
50 Uic9183Parser& Uic9183Parser::operator=(const Uic9183Parser&) = default;
51 
53 {
54  return Uic9183Block(d->m_payload, 0);
55 }
56 
57 Uic9183Block Uic9183Parser::findBlock(const char name[6]) const
58 {
59  for (auto block = firstBlock(); !block.isNull(); block = block.nextBlock()) {
60  if (block.isA(name)) {
61  return block;
62  }
63  }
64  return {};
65 }
66 
68 {
69  if (name.size() != 6 || d->m_payload.isEmpty()) {
70  return {};
71  }
72 
73 #define BLOCK_FROM_NAME(Type) \
74  if (name == QLatin1String(Type::RecordId)) { \
75  const auto block = findBlock<Type>(); \
76  return block.isValid() ? QVariant::fromValue(block) : QVariant(); \
77  }
78 
79  BLOCK_FROM_NAME(Uic9183Head)
80  BLOCK_FROM_NAME(Uic9183TicketLayout)
81  BLOCK_FROM_NAME(Fcb::UicRailTicketData)
82  BLOCK_FROM_NAME(Vendor0080BLBlock)
83  BLOCK_FROM_NAME(Vendor0080VUBlock)
84  BLOCK_FROM_NAME(Vendor1154UTBlock)
85 
86 #undef BLOCK_FROM_NAME
87 
88  return QVariant::fromValue(findBlock(name.toUtf8().constData()));
89 }
90 
92 {
93 }
94 
95 void Uic9183Parser::parse(const QByteArray &data)
96 {
97  d->m_data.clear();
98  d->m_payload.clear();
99 
100  Uic9183Header header(data);
101  if (!header.isValid()) {
102  return;
103  }
104 
105  // nx zlib payload
106  d->m_data = data;
107  d->m_payload.resize(4096);
108  z_stream stream;
109  stream.zalloc = nullptr;
110  stream.zfree = nullptr;
111  stream.opaque = nullptr;
112  stream.avail_in = data.size() - header.compressedMessageOffset();
113  stream.next_in = reinterpret_cast<unsigned char*>(const_cast<char*>(data.data() + header.compressedMessageOffset()));
114  stream.avail_out = d->m_payload.size();
115  stream.next_out = reinterpret_cast<unsigned char*>(d->m_payload.data());
116 
117  inflateInit(&stream);
118  const auto res = inflate(&stream, Z_NO_FLUSH);
119  switch (res) {
120  case Z_OK:
121  case Z_STREAM_END:
122  break; // all good
123  default:
124  qCWarning(Log) << "UIC 918.3 payload zlib decompression failed" << stream.msg;
125  return;
126  }
127  inflateEnd(&stream);
128  d->m_payload.truncate(d->m_payload.size() - stream.avail_out);
129  //qCDebug(Log) << res << d->m_payload << stream.avail_out;
130 
131  // workaround for Renfe (1071) having various errors...
132  if (d->m_payload.size() > 600 && d->m_payload.startsWith("U_HEAD0100531071") && d->m_payload[54] == 'U' && d->m_payload[36] == ' ') {
133  qCWarning(Log) << "Applying Renfe workaround for broken UIC 913.8 content...";
134  d->m_payload.remove(36, 1); // off by one in U_HEAD
135  const auto idx = d->m_payload.indexOf("U_TLAY00");
136  if (idx < d->m_payload.size() + 400 && std::strncmp(d->m_payload.constData() + idx + 12, "RCT2", 4) != 0) {
137  d->m_payload.insert(idx + 7, "1"); // wrong U_TLAY version
138  d->m_payload.replace(idx + 12, 4, "RCT2"); // wrong layout type
139  d->m_payload.remove(idx + 20, 1); // garbage trailing the layout type?
140  qCDebug(Log) << d->m_payload;
141  }
142  }
143 }
144 
145 bool Uic9183Parser::isValid() const
146 {
147  return !d->m_payload.isEmpty();
148 }
149 
150 template <typename T>
151 static QString fcbReference(const T &data)
152 {
153  if (!data.referenceIA5.isEmpty()) {
154  return QString::fromLatin1(data.referenceIA5);
155  }
156  if (data.referenceNumIsSet()) {
157  return QString::number(data.referenceNum);
158  }
159  return {};
160 }
161 
162 QString Uic9183Parser::pnr() const
163 {
164  if (const auto head = findBlock<Uic9183Head>(); head.isValid()) {
165  const auto key = head.ticketKey().trimmed();
166  const auto issuerId = head.issuerCompanyCodeNumeric();
167 
168  // try to make this match what's printed on the matching tickets...
169  if (issuerId == 80 && (key.size() == 8 || key.size() == 9) && key.at(6) == QLatin1Char('-') && key.at(7).isDigit()) {
170  return key.left(6); // DB domestic
171  }
172  if (issuerId == 80 && key.size() == 13 && key.endsWith(QLatin1String("0101"))) {
173  return key.left(9); // DB domestic part of an international order
174  }
175  if ((issuerId == 1088 || issuerId == 1184) && key.size() == 9 && key.at(7) == QLatin1Char('_') && key.at(8).isDigit()) {
176  return key.left(7); // SNCB and NS
177  }
178 
179  return key;
180  }
181 
182  if (const auto fcb = findBlock<Fcb::UicRailTicketData>(); fcb.isValid()) {
183  if (!fcb.issuingDetail.issuerPNR.isEmpty()) {
184  return QString::fromLatin1(fcb.issuingDetail.issuerPNR);
185  }
186  if (!fcb.transportDocument.isEmpty()) {
187  const auto doc = fcb.transportDocument.at(0);
188  QString pnr;
189  if (doc.ticket.userType() == qMetaTypeId<Fcb::ReservationData>()) {
190  pnr = fcbReference(doc.ticket.value<Fcb::ReservationData>());
191  } else if (doc.ticket.userType() == qMetaTypeId<Fcb::OpenTicketData>()) {
192  pnr = fcbReference(doc.ticket.value<Fcb::OpenTicketData>());
193  } else if (doc.ticket.userType() == qMetaTypeId<Fcb::PassData>()) {
194  pnr = fcbReference(doc.ticket.value<Fcb::PassData>());
195  }
196  if (!pnr.isEmpty()) {
197  return pnr;
198  }
199  }
200  }
201 
202  return {};
203 }
204 
205 template <typename T>
206 static QString fcbTariffName(const T &data)
207 {
208  if (data.tariffs.isEmpty()) {
209  return {};
210  }
211  return data.tariffs.at(0).tariffDesc;
212 }
213 
214 QString Uic9183Parser::name() const
215 {
216  // ERA FCB
217  if (const auto fcb = findBlock<Fcb::UicRailTicketData>(); fcb.isValid() && !fcb.transportDocument.isEmpty()) {
218  const auto doc = fcb.transportDocument.at(0);
219  QString name;
220  if (doc.ticket.userType() == qMetaTypeId<Fcb::ReservationData>()) {
221  name = fcbTariffName(doc.ticket.value<Fcb::ReservationData>());
222  } else if (doc.ticket.userType() == qMetaTypeId<Fcb::OpenTicketData>()) {
223  name = fcbTariffName(doc.ticket.value<Fcb::OpenTicketData>());
224  } else if (doc.ticket.userType() == qMetaTypeId<Fcb::PassData>()) {
225  name = fcbTariffName(doc.ticket.value<Fcb::PassData>());
226  }
227  if (!name.isEmpty()) {
228  return name;
229  }
230  }
231 
232  // DB vendor block
233  if (const auto b = findBlock<Vendor0080BLBlock>(); b.isValid()) {
234  const auto sblock = b.findSubBlock("001");
235  if (!sblock.isNull()) {
236  return QString::fromUtf8(sblock.content(), sblock.contentSize());
237  }
238  }
239 
240  // RCT2
241  if (const auto rct2 = rct2Ticket(); rct2.isValid()) {
242  return rct2.title();
243  }
244 
245  return {};
246 }
247 
248 QString Uic9183Parser::carrierId() const
249 {
250  if (const auto head = findBlock<Uic9183Head>(); head.isValid()) {
251  return head.issuerCompanyCodeString();
252  }
253  if (const auto fcb = findBlock<Fcb::UicRailTicketData>(); fcb.isValid()) {
254  const auto issue = fcb.issuingDetail;
255  if (issue.issuerNumIsSet()) {
256  return QString::number(issue.issuerNum);
257  }
258  if (issue.issuerIA5IsSet()) {
259  return QString::fromLatin1(issue.issuerIA5);
260  }
261  }
262  return header().signerCompanyCode();
263 }
264 
265 Organization Uic9183Parser::issuer() const
266 {
267  Organization issuer;
268  issuer.setIdentifier(QLatin1String("uic:") + carrierId());
269  if (const auto fcb = findBlock<Fcb::UicRailTicketData>(); fcb.isValid() && fcb.issuingDetail.issuerNameIsSet()) {
270  issuer.setName(fcb.issuingDetail.issuerName);
271  }
272  return issuer;
273 }
274 
275 QDateTime Uic9183Parser::validFrom() const
276 {
277  // ERA FCB
278  if (const auto fcb = findBlock<Fcb::UicRailTicketData>(); fcb.isValid() && !fcb.transportDocument.isEmpty()) {
279  const auto issue = fcb.issuingDetail.issueingDateTime();
280  const auto doc = fcb.transportDocument.at(0).ticket;
281  if (doc.userType() == qMetaTypeId<Fcb::ReservationData>()) {
282  return doc.value<Fcb::ReservationData>().departureDateTime(issue);
283  }
284  if (doc.userType() == qMetaTypeId<Fcb::OpenTicketData>()) {
285  return doc.value<Fcb::OpenTicketData>().validFrom(issue);
286  }
287  if (doc.userType() == qMetaTypeId<Fcb::PassData>()) {
288  return doc.value<Fcb::PassData>().validFrom(issue);
289  }
290  }
291 
292  // DB vendor block
293  if (const auto b = findBlock<Vendor0080BLBlock>(); b.isValid() && b.orderBlockCount() == 1) {
294  return QDateTime(b.orderBlock(0).validFrom(), {0, 0, 0});
295  }
296 
297  // ÖBB vender block
298  if (const auto b = findBlock("118199"); !b.isNull()) {
299  const auto obj = QJsonDocument::fromJson(QByteArray::fromRawData(b.content(), b.contentSize())).object();
300  auto dt = QDateTime::fromString(obj.value(QLatin1String("V")).toString(), QStringLiteral("yyMMddhhmm"));
301  if (dt.isValid()) { // ÖBB VorteilsCard barcodes have an empty vendor block
302  if (dt.date().year() < 2000) {
303  dt = dt.addYears(100);
304  }
305  dt.setTimeZone(QTimeZone::utc());
306  return dt;
307  }
308  }
309 
310  // CD vender block
311  if (const auto b = findBlock<Vendor1154UTBlock>(); b.isValid()) {
312  const auto subBlock = b.findSubBlock("OD");
313  qDebug() << subBlock.toString();
314  if (!subBlock.isNull()) {
315  return QDateTime::fromString(subBlock.toString(), QStringLiteral("dd.MM.yyyy hh:mm"));
316  }
317  }
318 
319  // RCT2
320  if (const auto rct2 = rct2Ticket(); rct2.isValid()) {
321  const auto dt = rct2.firstDayOfValidity();
322  if (dt.month() != 1 || dt.day() != 1 || !rct2.outboundDepartureStation().isEmpty()) {
323  return QDateTime(dt, {0, 0, 0});
324  }
325  // firstDayOfValidity is just a year, and we have wildcard station names
326  const auto dep = rct2.outboundDepartureTime();
327  return dep.isValid() ? dep : QDateTime(dt, {0, 0, 0});
328  }
329 
330  return {};
331 }
332 
333 QDateTime Uic9183Parser::validUntil() const
334 {
335  // ERA FCB
336  if (const auto fcb = findBlock<Fcb::UicRailTicketData>(); fcb.isValid() && !fcb.transportDocument.isEmpty()) {
337  const auto issue = fcb.issuingDetail.issueingDateTime();
338  const auto doc = fcb.transportDocument.at(0).ticket;
339  if (doc.userType() == qMetaTypeId<Fcb::ReservationData>()) {
340  return doc.value<Fcb::ReservationData>().arrivalDateTime(issue);
341  }
342  if (doc.userType() == qMetaTypeId<Fcb::OpenTicketData>()) {
343  return doc.value<Fcb::OpenTicketData>().validUntil(issue);
344  }
345  if (doc.userType() == qMetaTypeId<Fcb::PassData>()) {
346  return doc.value<Fcb::PassData>().validUntil(issue);
347  }
348  }
349 
350  // DB vendor block
351  if (const auto b = findBlock<Vendor0080BLBlock>(); b.isValid() && b.orderBlockCount() == 1) {
352  return QDateTime(b.orderBlock(0).validTo(), {23, 59, 59});
353  }
354 
355  // ÖBB vender block
356  if (const auto b = findBlock("118199"); !b.isNull()) {
357  const auto obj = QJsonDocument::fromJson(QByteArray::fromRawData(b.content(), b.contentSize())).object();
358  auto dt = QDateTime::fromString(obj.value(QLatin1String("B")).toString(), QStringLiteral("yyMMddhhmm"));
359  if (dt.isValid()) { // ÖBB VorteilsCard barcodes have an empty vendor block
360  if (dt.date().year() < 2000) {
361  dt = dt.addYears(100);
362  }
363  dt.setTimeZone(QTimeZone::utc());
364  return dt;
365  }
366  }
367 
368  // CD vender block
369  if (const auto b = findBlock<Vendor1154UTBlock>(); b.isValid()) {
370  const auto subBlock = b.findSubBlock("DO");
371  if (!subBlock.isNull()) {
372  return QDateTime::fromString(subBlock.toString(), QStringLiteral("dd.MM.yyyy hh:mm"));
373  }
374  }
375 
376 
377  // RCT2 RPT according to ERA TAP TSI Annex B.6
378  if (const auto rct2 = rct2Ticket(); rct2.isValid()) {
379  const auto validityRange = ticketLayout().text(3, 1, 36, 1).trimmed();
380  const auto idx = std::max(validityRange.lastIndexOf(QLatin1Char(' ')), validityRange.lastIndexOf(QLatin1Char('-')));
381  if (idx > 0) {
382  return QDateTime(QDate::fromString(validityRange.mid(idx + 1), QStringLiteral("dd.MM.yyyy")), {23, 59, 59});
383  }
384  return rct2.outboundArrivalTime();
385  }
386 
387  return {};
388 }
389 
390 Person Uic9183Parser::person() const
391 {
392  // ERA FCB
393  if (const auto fcb = findBlock<Fcb::UicRailTicketData>(); fcb.isValid() && fcb.travelerDetailIsSet() && fcb.travelerDetail.traveler.size() == 1) {
394  const auto traveler = fcb.travelerDetail.traveler.at(0);
395  Person p;
396  p.setGivenName(QString(traveler.firstName + QLatin1Char(' ') + traveler.secondName).trimmed());
397  p.setFamilyName(traveler.lastName);
398  if (traveler.firstNameIsSet() || traveler.lastNameIsSet()) {
399  return p;
400  }
401  }
402 
403  // Deutsche Bahn vendor block
404  if (const auto b = findBlock<Vendor0080BLBlock>(); b.isValid()) {
405  // S028 contains family and given name separated by a '#', UTF-8 encoded
406  auto sblock = b.findSubBlock("028");
407  if (!sblock.isNull()) {
408  const auto endIt = sblock.content() + sblock.contentSize();
409  auto it = std::find(sblock.content(), endIt, '#');
410  if (it != endIt) {
411  Person p;
412  p.setGivenName(QString::fromUtf8(sblock.content(), std::distance(sblock.content(), it)));
413  ++it;
414  p.setFamilyName(QString::fromUtf8(it, std::distance(it, endIt)));
415  return p;
416  }
417  }
418  // S023 contains the full name, UTF-8 encoded
419  sblock = b.findSubBlock("023");
420  if (!sblock.isNull()) {
421  Person p;
422  p.setName(sblock.toString());
423  return p;
424  }
425  }
426  // CD vender block
427  if (const auto b = findBlock<Vendor1154UTBlock>(); b.isValid()) {
428  const auto subBlock = b.findSubBlock("KJ");
429  if (!subBlock.isNull()) {
430  Person p;
431  p.setName(subBlock.toString());
432  return p;
433  }
434  }
435 
436  // RCT2 tickets
437  const auto rct2 = rct2Ticket();
438  if (rct2.isValid()) {
439  const auto name = rct2.passengerName();
440  if (!name.isEmpty()) {
441  Person p;
442  p.setName(name);
443  return p;
444  }
445  }
446 
447  return {};
448 }
449 
450 static void fixFcbStationCode(TrainStation &station)
451 {
452  // UIC codes in Germany are wildly unreliable, there seem to be different
453  // code tables in use by different operators, so we unfortunately have to ignore
454  // those entirely
455  if (station.identifier().startsWith(QLatin1String("uic:80"))) {
456  PostalAddress addr;
457  addr.setAddressCountry(QStringLiteral("DE"));
458  station.setAddress(addr);
459  station.setIdentifier(QString());
460  }
461 }
462 
463 TrainStation Uic9183Parser::outboundDepartureStation() const
464 {
465  TrainStation station;
466 
467  // RTC2 ticket layout
468  if (const auto rtc2 = rct2Ticket(); rtc2.isValid()) {
469  station.setName(rtc2.outboundDepartureStation());
470  }
471 
472  // DB vendor block
473  if (const auto b = findBlock<Vendor0080BLBlock>(); b.isValid()) {
474  if (const auto sblock = b.findSubBlock("015"); !sblock.isNull()) {
475  station.setName(sblock.toString());
476  }
477  // S035 contains the IBNR, possible with leading '80' country code and leading 0 stripped
478  if (const auto sblock = b.findSubBlock("035"); !sblock.isNull() && sblock.contentSize() <= 7) {
479  QString ibnr = QStringLiteral("ibnr:8000000");
480  const auto s = sblock.toString();
481  station.setIdentifier(ibnr.replace(ibnr.size() - s.size(), s.size(), s));
482  }
483  }
484 
485  // ERA FCB
486  if (const auto fcb = findBlock<Fcb::UicRailTicketData>(); fcb.isValid() && !fcb.transportDocument.isEmpty()) {
487  const auto doc = fcb.transportDocument.at(0);
488  if (doc.ticket.userType() == qMetaTypeId<Fcb::ReservationData>()) {
489  const auto irt = doc.ticket.value<Fcb::ReservationData>();
490  station.setName(irt.fromStationNameUTF8);
491  station.setIdentifier(FcbUtil::fromStationIdentifier(irt));
492  } else if (doc.ticket.userType() == qMetaTypeId<Fcb::OpenTicketData>()) {
493  const auto nrt = doc.ticket.value<Fcb::OpenTicketData>();
494  station.setName(nrt.fromStationNameUTF8);
495  station.setIdentifier(FcbUtil::fromStationIdentifier(nrt));
496  }
497  fixFcbStationCode(station);
498  }
499 
500  return station;
501 }
502 
503 TrainStation Uic9183Parser::outboundArrivalStation() const
504 {
505  TrainStation station;
506 
507  // RTC2 ticket layout
508  if (const auto rtc2 = rct2Ticket(); rtc2.isValid()) {
509  station.setName(rtc2.outboundArrivalStation());
510  }
511 
512  if (const auto b = findBlock<Vendor0080BLBlock>(); b.isValid()) {
513  if (const auto sblock = b.findSubBlock("016"); !sblock.isNull()) {
514  station.setName(sblock.toString());
515  }
516  // S036 contains the IBNR, possible with leading '80' country code and leading 0 stripped
517  if (const auto sblock = b.findSubBlock("036"); !sblock.isNull() && sblock.contentSize() <= 7) {
518  QString ibnr = QStringLiteral("ibnr:8000000");
519  const auto s = sblock.toString();
520  station.setIdentifier(ibnr.replace(ibnr.size() - s.size(), s.size(), s));
521  }
522  }
523 
524  // ERA FCB
525  if (const auto fcb = findBlock<Fcb::UicRailTicketData>(); fcb.isValid() && !fcb.transportDocument.isEmpty()) {
526  const auto doc = fcb.transportDocument.at(0);
527  if (doc.ticket.userType() == qMetaTypeId<Fcb::ReservationData>()) {
528  const auto irt = doc.ticket.value<Fcb::ReservationData>();
529  station.setName(irt.toStationNameUTF8);
530  station.setIdentifier(FcbUtil::toStationIdentifier(irt));
531  } else if (doc.ticket.userType() == qMetaTypeId<Fcb::OpenTicketData>()) {
532  const auto nrt = doc.ticket.value<Fcb::OpenTicketData>();
533  station.setName(nrt.toStationNameUTF8);
534  station.setIdentifier(FcbUtil::toStationIdentifier(nrt));
535  }
536  fixFcbStationCode(station);
537  }
538 
539  return station;
540 }
541 
542 TrainStation Uic9183Parser::returnDepartureStation() const
543 {
544  TrainStation station;
545 
546  // RTC2 ticket layout
547  if (const auto rtc2 = rct2Ticket(); rtc2.isValid()) {
548  station.setName(rtc2.returnDepartureStation());
549  }
550 
551  const auto outboundArrival = outboundArrivalStation();
552  // DB vendor block
553  if (const auto b = findBlock<Vendor0080BLBlock>(); b.isValid()) {
554  if (const auto sblock = b.findSubBlock("017"); !sblock.isNull()) {
555  station.setName(sblock.toString());
556  }
557  if (outboundArrival.name() == station.name()) {
558  station.setIdentifier(outboundArrival.identifier());
559  }
560  }
561 
562  // ERA FCB
563  if (const auto fcb = findBlock<Fcb::UicRailTicketData>(); fcb.isValid() && !fcb.transportDocument.isEmpty()) {
564  const auto doc = fcb.transportDocument.at(0);
565  if (doc.ticket.userType() == qMetaTypeId<Fcb::OpenTicketData>()) {
566  const auto nrt = doc.ticket.value<Fcb::OpenTicketData>();
567  if (nrt.returnIncluded && nrt.returnDescriptionIsSet()) {
568  station.setName(nrt.returnDescription.fromStationNameUTF8);
569  station.setIdentifier(FcbUtil::fromStationIdentifier(nrt.stationCodeTable, nrt.returnDescription));
570  } else if (nrt.returnIncluded) {
571  if (outboundArrival.name() == station.name()) {
572  station.setIdentifier(outboundArrival.identifier());
573  }
574  }
575  }
576  fixFcbStationCode(station);
577  }
578 
579  return station;
580 }
581 
582 TrainStation Uic9183Parser::returnArrivalStation() const
583 {
584  TrainStation station;
585 
586  // RTC2 ticket layout
587  if (const auto rtc2 = rct2Ticket(); rtc2.isValid()) {
588  station.setName(rtc2.returnArrivalStation());
589  }
590 
591  const auto outboundDeparture = outboundDepartureStation();
592  // DB vendor block
593  if (const auto b = findBlock<Vendor0080BLBlock>(); b.isValid()) {
594  if (const auto sblock = b.findSubBlock("018"); !sblock.isNull()) {
595  station.setName(sblock.toString());
596  }
597  if (outboundDeparture.name() == station.name()) {
598  station.setIdentifier(outboundDeparture.identifier());
599  }
600  }
601 
602  // ERA FCB
603  if (const auto fcb = findBlock<Fcb::UicRailTicketData>(); fcb.isValid() && !fcb.transportDocument.isEmpty()) {
604  const auto doc = fcb.transportDocument.at(0);
605  if (doc.ticket.userType() == qMetaTypeId<Fcb::OpenTicketData>()) {
606  const auto nrt = doc.ticket.value<Fcb::OpenTicketData>();
607  if (nrt.returnIncluded && nrt.returnDescriptionIsSet()) {
608  station.setName(nrt.returnDescription.toStationNameUTF8);
609  station.setIdentifier(FcbUtil::toStationIdentifier(nrt.stationCodeTable, nrt.returnDescription));
610  } else if (nrt.returnIncluded) {
611  if (outboundDeparture.name() == station.name()) {
612  station.setIdentifier(outboundDeparture.identifier());
613  }
614  }
615  }
616  fixFcbStationCode(station);
617  }
618 
619  return station;
620 }
621 
622 QString Uic9183Parser::seatingType() const
623 {
624  if (const auto fcb = findBlock<Fcb::UicRailTicketData>(); fcb.isValid() && fcb.transportDocument.size() == 1) {
625  const auto doc = fcb.transportDocument.at(0);
626  if (doc.ticket.userType() == qMetaTypeId<Fcb::ReservationData>()) {
627  return FcbUtil::classCodeToString(doc.ticket.value<Fcb::ReservationData>().classCode);
628  }
629  if (doc.ticket.userType() == qMetaTypeId<Fcb::OpenTicketData>()) {
630  return FcbUtil::classCodeToString(doc.ticket.value<Fcb::OpenTicketData>().classCode);
631  }
632  if (doc.ticket.userType() == qMetaTypeId<Fcb::PassData>()) {
633  return FcbUtil::classCodeToString(doc.ticket.value<Fcb::PassData>().classCode);
634  }
635  }
636 
637  if (const auto b = findBlock<Vendor0080BLBlock>(); b.isValid()) {
638  // S014 contains the class, possibly with a leading 'S' for some reason
639  const auto sblock = b.findSubBlock("014");
640  if (!sblock.isNull()) {
641  const auto s = sblock.toString();
642  return s.startsWith(QLatin1Char('S')) ? s.right(1) : s;
643  }
644  }
645 
646  if (const auto rct2 = rct2Ticket(); rct2.isValid()) {
647  return rct2.outboundClass();
648  }
649  return {};
650 }
651 
653 {
654  return findBlock<Uic9183TicketLayout>();
655 }
656 
657 QVariant Uic9183Parser::ticketLayoutVariant() const
658 {
659  const auto layout = ticketLayout();
660  return layout.isValid() ? QVariant::fromValue(layout) : QVariant();
661 }
662 
664 {
665  Rct2Ticket rct2(ticketLayout());
666  const auto u_head = findBlock<Uic9183Head>();
667  rct2.setContextDate(u_head.issuingDateTime());
668  return rct2;
669 }
670 
671 QVariant Uic9183Parser::rct2TicketVariant() const
672 {
673  const auto rct2 = rct2Ticket();
674  if (rct2.isValid()) {
675  return QVariant::fromValue(rct2);
676  }
677  return {};
678 }
679 
681 {
682  return Uic9183Header(d->m_data);
683 }
684 
685 QByteArray Uic9183Parser::rawData() const
686 {
687  return d->m_data;
688 }
689 
691 {
692  Uic9183Header h(data);
693  return h.isValid();
694 }
695 
696 #include "moc_uic9183parser.cpp"
void setContextDate(const QDateTime &)
Date/time this ticket was first encountered.
QTimeZone utc()
bool isNull() const const
Header of an UIC 918.3 ticket.
Definition: uic9183header.h:18
UIC 918.3 1154UT vendor data block.
QJsonObject object() const const
bool isValid() const const
QJsonDocument fromJson(const QByteArray &json, QJsonParseError *error)
QString number(int n, int base)
QVariant ticketLayout
U_TLAY ticket layout block, if present, null otherwise.
Definition: uic9183parser.h:53
QString fromUtf8(const char *str, int size)
int size() const const
QVariant fromValue(const T &value)
QByteArray fromRawData(const char *data, int size)
static QString toStationIdentifier(Fcb::CodeTableType stationCodeTable, const T &doc)
Arrival station identifier for a travel document, in the format needed for output with our JSON-LD fo...
Definition: fcbutil.h:41
A data block from a UIC 918.3 ticket.
Definition: uic9183block.h:18
U_HEAD block of a UIC 918.3 ticket container.
Definition: uic9183head.h:20
A person.
Definition: person.h:19
Uic9183Header header() const
Header found before the compressed payload.
QString() const
Ticket name.
static bool maybeUic9183(const QByteArray &data)
Quickly checks if might be UIC 918.3 content.
Parser for a U_TLAY block in a UIC 918-3 ticket container, such as a ERA TLB ticket.
QVariant rct2Ticket
RCT2 ticket layout block, if present, null otherwise.
Definition: uic9183parser.h:55
bool isEmpty() const const
static QString classCodeToString(Fcb::TravelClassType classCode)
Convert a class code enum value to a string for human representation.
Definition: fcbutil.cpp:30
Reservation document (IRT, RES).
Definition: fcbticket.h:490
QDateTime fromString(const QString &string, Qt::DateFormat format)
Postal address.
Definition: place.h:45
Uic9183Block firstBlock() const
First data block in this ticket.
Train station.
Definition: place.h:125
UIC 918.3 0080BL vendor data block.
QString & replace(int position, int n, QChar after)
QDate fromString(const QString &string, Qt::DateFormat format)
An organization.
Definition: organization.h:30
QString fromLatin1(const char *str, int size)
RCT2 ticket layout payload of an UIC 918.3 ticket token.
Definition: rct2ticket.h:22
const char * name(StandardAction id)
bool isValid() const const
const QChar at(int position) const const
int size() const const
Parser for UIC 918.3 and 918.3* train tickets.
Definition: uic9183parser.h:37
Open ticket document (NRT).
Definition: fcbticket.h:615
static QString fromStationIdentifier(Fcb::CodeTableType stationCodeTable, const T &doc)
Departure station identifier for a travel document, in the format needed for output with our JSON-LD ...
Definition: fcbutil.h:24
Top-level type for the ERA FCB ticket structure.
Definition: fcbticket.h:999
const T * constData() const const
Rail pass document (RPT).
Definition: fcbticket.h:692
Q_INVOKABLE QVariant block(const QString &1) const
Same as the above, but for JS usage.
char * data()
UIC 918.3 0080VU vendor data block (DB local public transport extensions).
char * toString(const EngineQuery &query)
T findBlock() const
Returns the first block of type.
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Wed Nov 29 2023 03:58:20 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.