Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package kitinerary for openSUSE:Factory checked in at 2023-10-12 23:40:06 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/kitinerary (Old) and /work/SRC/openSUSE:Factory/.kitinerary.new.1807 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "kitinerary" Thu Oct 12 23:40:06 2023 rev:66 rq:1117312 version:23.08.2 Changes: -------- --- /work/SRC/openSUSE:Factory/kitinerary/kitinerary.changes 2023-09-15 22:03:00.823854852 +0200 +++ /work/SRC/openSUSE:Factory/.kitinerary.new.1807/kitinerary.changes 2023-10-12 23:41:02.642584285 +0200 @@ -1,0 +2,32 @@ +Tue Oct 10 18:27:34 UTC 2023 - Christophe Marin <[email protected]> + +- Update to 23.08.2 + * New bugfix release + * For more details please see: + * https://kde.org/announcements/gear/23.08.2/ +- Changes since 23.08.1: + * Fix several copy/paste errors in SortUtil::hasStart/EndTime() + * Add bookingkit PDF extractor script + * Handle departure/arrival/duration triples in generic extraction as well + * Update clang-tidy settings to something more reasonable + * Make the Amadeus PDF timestamp workaround a bit less specific + * Correctly compare times with and without timezones when merging + * Fix parsing newer UK railway PDF tickets + * Deal with an alternative way to mark non-reservation RSP-6 tickets + * Fix parsing GWR iCal attachments for multi-leg train trips + * Manually merge international Renfe results + * Unify reservation/ticket number extraction for Renfe/Ouigo ES barcodes + * Move Renfe barcode documentation to the wiki + * Add a workaround for fixing broken UIC 918.3 payloads in Renfe tickets + * Sanity-check the RCT2 traveler name + * Handle one more data format in RCT2 tickets + * Try barcode decoding both on transformed and untranformed source images + * Remove some excessive debug output + * Extract un-styled Pretix PDF tickets + * Extract multi-leg Renfe tickets correctly + * Make train number matching slightly less strict + * Merge the two ÃBB UIC 918.3 extractor scripts + * Extract multi-leg ÃBB PDF tickets + * Fix online import of unidirectional SNCF bookings (kde#474197) + +------------------------------------------------------------------- Old: ---- kitinerary-23.08.1.tar.xz kitinerary-23.08.1.tar.xz.sig New: ---- kitinerary-23.08.2.tar.xz kitinerary-23.08.2.tar.xz.sig ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ kitinerary.spec ++++++ --- /var/tmp/diff_new_pack.cVuX6H/_old 2023-10-12 23:41:03.894629607 +0200 +++ /var/tmp/diff_new_pack.cVuX6H/_new 2023-10-12 23:41:03.898629752 +0200 @@ -19,7 +19,7 @@ %define libname libKPimItinerary5 %bcond_without released Name: kitinerary -Version: 23.08.1 +Version: 23.08.2 Release: 0 Summary: Data model and extraction system for travel reservations License: LGPL-2.1-or-later ++++++ kitinerary-23.08.1.tar.xz -> kitinerary-23.08.2.tar.xz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kitinerary-23.08.1/.clang-tidy new/kitinerary-23.08.2/.clang-tidy --- old/kitinerary-23.08.1/.clang-tidy 2023-09-11 17:49:23.000000000 +0200 +++ new/kitinerary-23.08.2/.clang-tidy 2023-10-09 17:36:11.000000000 +0200 @@ -2,11 +2,39 @@ # SPDX-FileCopyrightText: none --- -Checks: 'clang-diagnostic-*,-clang-diagnostic-gnu-zero-variadic-macro-arguments,-clang-diagnostic-unknown-warning-option,clang-analyzer-*,bugprone-*,misc-*,modernize-*,performance-*,readability-*,-readability-implicit-bool-conversion,-modernize-use-trailing-return-type' +Checks: > + clang-diagnostic-*, + -clang-diagnostic-gnu-zero-variadic-macro-arguments, + -clang-diagnostic-unknown-warning-option, + clang-analyzer-*, + bugprone-*, + -bugprone-easily-swappable-parameters, + -bugprone-suspicious-include, + misc-*, + -misc-use-anonymous-namespace, + modernize-*, + -modernize-avoid-c-arrays, + -modernize-concat-nested-namespaces, + -modernize-use-trailing-return-type, + performance-*, + readability-*, + -readability-function-cognitive-complexity, + -readability-identifier-length, + -readability-implicit-bool-conversion, + -readability-isolate-declaration, + -readability-magic-numbers, + -readability-named-parameter, + -readability-qualified-auto, + -readability-uppercase-literal-suffix + FormatStyle: none -CheckOptions: +CheckOptions: - key: readability-implicit-bool-conversion.AllowIntegerConditions value: '1' - key: readability-implicit-bool-conversion.AllowPointerConditions value: '1' + - key: misc-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic + value: '1' + - key: modernize-use-override.IgnoreDestructors + value: '1' ... diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kitinerary-23.08.1/CMakeLists.txt new/kitinerary-23.08.2/CMakeLists.txt --- old/kitinerary-23.08.1/CMakeLists.txt 2023-09-11 17:49:23.000000000 +0200 +++ new/kitinerary-23.08.2/CMakeLists.txt 2023-10-09 17:36:11.000000000 +0200 @@ -3,7 +3,7 @@ # SPDX-License-Identifier: BSD-3-Clause cmake_minimum_required(VERSION 3.16 FATAL_ERROR) -set(PIM_VERSION "5.24.1") +set(PIM_VERSION "5.24.2") project(KItinerary VERSION ${PIM_VERSION}) set(KF_MIN_VERSION "5.91.0") @@ -48,8 +48,8 @@ find_package(SharedMimeInfo 1.3 REQUIRED) endif() -set(KMIME_VERSION "5.24.1") -set(PIM_PKPASS "5.24.1") +set(KMIME_VERSION "5.24.2") +set(PIM_PKPASS "5.24.2") find_package(KPim${KF_MAJOR_VERSION}Mime ${KMIME_VERSION} CONFIG REQUIRED) find_package(KPimPkPass ${PIM_PKPASS} CONFIG REQUIRED) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kitinerary-23.08.1/autotests/extractortest.cpp new/kitinerary-23.08.2/autotests/extractortest.cpp --- old/kitinerary-23.08.1/autotests/extractortest.cpp 2023-09-11 17:49:23.000000000 +0200 +++ new/kitinerary-23.08.2/autotests/extractortest.cpp 2023-10-09 17:36:11.000000000 +0200 @@ -172,7 +172,6 @@ continue; } const auto tokenData = ticket.ticketTokenData(); - qDebug() << tokenData << ticket.ticketToken(); if (tokenData.userType() == QMetaType::QString) { QVERIFY(tokenData.toString() != ticket.ticketToken()); } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kitinerary-23.08.1/po/zh_CN/kitinerary.po new/kitinerary-23.08.2/po/zh_CN/kitinerary.po --- old/kitinerary-23.08.1/po/zh_CN/kitinerary.po 2023-09-11 17:49:23.000000000 +0200 +++ new/kitinerary-23.08.2/po/zh_CN/kitinerary.po 2023-10-09 17:36:11.000000000 +0200 @@ -3,7 +3,7 @@ "Project-Id-Version: kdeorg\n" "Report-Msgid-Bugs-To: https://bugs.kde.org\n" "POT-Creation-Date: 2022-07-20 00:46+0000\n" -"PO-Revision-Date: 2023-09-02 02:59\n" +"PO-Revision-Date: 2023-09-16 10:11\n" "Last-Translator: \n" "Language-Team: Chinese Simplified\n" "Language: zh_CN\n" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kitinerary-23.08.1/src/cli/org.kde.kitinerary-extractor.appdata.xml new/kitinerary-23.08.2/src/cli/org.kde.kitinerary-extractor.appdata.xml --- old/kitinerary-23.08.1/src/cli/org.kde.kitinerary-extractor.appdata.xml 2023-09-11 17:49:23.000000000 +0200 +++ new/kitinerary-23.08.2/src/cli/org.kde.kitinerary-extractor.appdata.xml 2023-10-09 17:36:11.000000000 +0200 @@ -119,9 +119,9 @@ <binary>kitinerary-extractor</binary> </provides> <releases> + <release version="5.24.2" date="2023-10-12"/> <release version="5.24.1" date="2023-09-14"/> <release version="5.24.0" date="2023-08-24"/> <release version="5.23.3" date="2023-07-06"/> - <release version="5.23.2" date="2023-06-08"/> </releases> </component> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kitinerary-23.08.1/src/lib/extractors/genericboardingpassextractor.cpp new/kitinerary-23.08.2/src/lib/extractors/genericboardingpassextractor.cpp --- old/kitinerary-23.08.1/src/lib/extractors/genericboardingpassextractor.cpp 2023-09-11 17:49:23.000000000 +0200 +++ new/kitinerary-23.08.2/src/lib/extractors/genericboardingpassextractor.cpp 2023-10-09 17:36:11.000000000 +0200 @@ -89,6 +89,16 @@ return fromDt < toDt && FlightUtil::isPlausibleDistanceForDuration(distance, flightDuration); } +[[nodiscard]] static qint64 flightDuration(const QDateTime &fromTime, const QDateTime &toTime, KnowledgeDb::IataCode from, KnowledgeDb::IataCode to) +{ + // times are local, so convert them to the right timezone first + auto fromDt = fromTime; + fromDt.setTimeZone(KnowledgeDb::timezoneForAirport(from)); + auto toDt = toTime; + toDt.setTimeZone(KnowledgeDb::timezoneForAirport(to)); + return fromDt.secsTo(toDt) / 60; +} + static bool conflictIfSet(const QDateTime &lhs, const QDateTime &rhs) { return lhs.isValid() && rhs.isValid() && lhs != rhs; @@ -244,8 +254,11 @@ } else if (isPlausibleBoardingTime(times[1], times[2]) && isPlausibleFlightTime(times[2], times[0].addDays(1), from, to)) { applyFlightTimes(result, times[1], times[2], times[0].addDays(1)); } - // TODO handle boarding before midnight + // departure/arrival/duration + else if (isPlausibleFlightTime(times[1], times[2], from, to) && flightDuration(times[1], times[2], from, to) == (times[0].time().hour() * 60 + times[0].time().minute())) { + applyFlightTimes(result, {}, times[1], times[2]); + } } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kitinerary-23.08.1/src/lib/mergeutil.cpp new/kitinerary-23.08.2/src/lib/mergeutil.cpp --- old/kitinerary-23.08.1/src/lib/mergeutil.cpp 2023-09-11 17:49:23.000000000 +0200 +++ new/kitinerary-23.08.2/src/lib/mergeutil.cpp 2023-10-09 17:36:11.000000000 +0200 @@ -54,6 +54,27 @@ using namespace KItinerary; +/* Compare times without assuming times without a timezone are in the current time zone + * (they might be local to the destination instead). + */ +static bool dateTimeCompare(const QDateTime &lhs, const QDateTime &rhs) +{ + if (lhs == rhs) { + return true; + } + if (lhs.timeSpec() == Qt::LocalTime && rhs.timeSpec() != Qt::LocalTime) { + QDateTime dt(rhs); + dt.setTimeSpec(Qt::LocalTime); + return lhs == dt; + } + if (lhs.timeSpec() != Qt::LocalTime && rhs.timeSpec() == Qt::LocalTime) { + QDateTime dt(lhs); + dt.setTimeSpec(Qt::LocalTime); + return dt == rhs; + } + return false; +} + /* Checks that @p lhs and @p rhs are non-empty and equal. */ static bool equalAndPresent(QStringView lhs, QStringView rhs, Qt::CaseSensitivity caseSensitive = Qt::CaseSensitive) { @@ -64,17 +85,25 @@ { return lhs.isValid() && lhs == rhs; } +static bool equalAndPresent(const QDateTime &lhs, const QDateTime &rhs) +{ + return lhs.isValid() && dateTimeCompare(lhs, rhs); +} /* Checks that @p lhs and @p rhs are not non-equal if both values are set. */ -static bool conflictIfPresent(const QString &lhs, const QString &rhs, Qt::CaseSensitivity caseSensitive = Qt::CaseSensitive) +static bool conflictIfPresent(QStringView lhs, QStringView rhs, Qt::CaseSensitivity caseSensitive = Qt::CaseSensitive) { return !lhs.isEmpty() && !rhs.isEmpty() && lhs.compare(rhs, caseSensitive) != 0; } template <typename T> -static bool conflictIfPresent(const T &lhs, const T &rhs) +static typename std::enable_if<!std::is_same_v<T, QString>, bool>::type conflictIfPresent(const T &lhs, const T &rhs) { return lhs.isValid() && rhs.isValid() && lhs != rhs; } +static bool conflictIfPresent(const QDateTime &lhs, const QDateTime &rhs) +{ + return lhs.isValid() && rhs.isValid() && !dateTimeCompare(lhs, rhs); +} static bool conflictIfPresent(const Person &lhs, const Person &rhs) { return !lhs.name().isEmpty() && !rhs.name().isEmpty() && !MergeUtil::isSamePerson(lhs, rhs); @@ -404,6 +433,14 @@ return (lIt != lEnd && (*lIt).isSpace()) || (rIt != rEnd && (*rIt).isSpace()); } +static bool isSameTrainName(QStringView lhs, QStringView rhs) +{ + if (lhs.isEmpty() || rhs.isEmpty()) { + return false; + } + return lhs.startsWith(rhs) || rhs.startsWith(lhs); +} + static bool isSameLineName(const QString &lhs, const QString &rhs) { if (isSameLineName(lhs.begin(), lhs.end(), rhs.begin(), rhs.end()) @@ -419,8 +456,8 @@ if (!lhsMatch.hasMatch() || !rhsMatch.hasMatch()) { return false; } - return equalAndPresent(lhsMatch.capturedView(u"type"), rhsMatch.capturedView(u"type")) && ( - (equalAndPresent(lhsMatch.capturedView(u"line"), rhsMatch.capturedView(u"line")) && equalAndPresent(lhsMatch.capturedView(u"route"), rhsMatch.capturedView(u"route"))) + return isSameTrainName(lhsMatch.capturedView(u"type"), rhsMatch.capturedView(u"type")) && ( + (equalAndPresent(lhsMatch.capturedView(u"line"), rhsMatch.capturedView(u"line")) && !conflictIfPresent(lhsMatch.capturedView(u"route"), rhsMatch.capturedView(u"route"))) || (equalAndPresent(lhsMatch.capturedView(u"line"), rhsMatch.capturedView(u"route")) && lhsMatch.capturedView(u"route").isEmpty()) || (equalAndPresent(lhsMatch.capturedView(u"route"), rhsMatch.capturedView(u"line")) && rhsMatch.capturedView(u"route").isEmpty()) ); @@ -440,6 +477,7 @@ && LocationUtil::isSameLocation(lhs.arrivalStation(), rhs.arrivalStation(), LocationUtil::Exact); } else if (lhs.departureTime().isValid() && rhs.departureTime().isValid()) { // if we have both departure times, they have to match + qCDebug(CompareLog) << "departure time" << lhs.departureTime() << rhs.departureTime() <<equalAndPresent(lhs.departureTime(), rhs.departureTime()); if (!equalAndPresent(lhs.departureTime(), rhs.departureTime())) { return false; } @@ -454,6 +492,7 @@ // arrival times (when present) should either match exactly, or be almost the same at a matching arrival location // (tickets even for the same connection booked on the same day sometimes have slight variation in the arrival time...) if (conflictIfPresent(lhs.arrivalTime(), rhs.arrivalTime())) { + qCDebug(CompareLog) << "arrival time" << lhs.arrivalTime() << rhs.arrivalTime(); if (std::abs(lhs.arrivalTime().secsTo(rhs.arrivalTime())) > 180) { return false; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kitinerary-23.08.1/src/lib/pdf/pdfimage.cpp new/kitinerary-23.08.2/src/lib/pdf/pdfimage.cpp --- old/kitinerary-23.08.1/src/lib/pdf/pdfimage.cpp 2023-09-11 17:49:23.000000000 +0200 +++ new/kitinerary-23.08.2/src/lib/pdf/pdfimage.cpp 2023-10-09 17:36:11.000000000 +0200 @@ -231,12 +231,7 @@ if (d->m_format == QImage::Format_Invalid) { return d->m_vectorPicture.renderToImage(); } - - const auto img = d->load(); - if (!img.isNull() && (d->m_width != d->m_sourceWidth || d->m_height != d->m_sourceHeight)) { - return img.scaled(d->m_width, d->m_height); - } - return img; + return d->load(); } bool PdfImage::hasObjectId() const @@ -258,3 +253,13 @@ { return d->m_vectorPicture.pathElementsCount(); } + +bool PdfImage::hasAspectRatioTransform() const +{ + return d->m_format != QImage::Format_Invalid && (d->m_width != d->m_sourceWidth || d->m_height != d->m_sourceHeight); +} + +QImage PdfImage::applyAspectRatioTransform(const QImage &image) const +{ + return image.scaled(d->m_width, d->m_height); +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kitinerary-23.08.1/src/lib/pdf/pdfimage.h new/kitinerary-23.08.2/src/lib/pdf/pdfimage.h --- old/kitinerary-23.08.1/src/lib/pdf/pdfimage.h 2023-09-11 17:49:23.000000000 +0200 +++ new/kitinerary-23.08.2/src/lib/pdf/pdfimage.h 2023-10-09 17:36:11.000000000 +0200 @@ -106,7 +106,7 @@ /** Sets image loading hints. */ void setLoadingHints(LoadingHints hints); - /** The source image with display transformations applied. */ + /** The source image without display transformations applied. */ QImage image() const; /** Returns whether this image has an object id. @@ -128,6 +128,17 @@ */ int pathElementsCount() const; + /** Returns @c true if this image has an aspect-ratio changing transform. + * That might need to be applied before doing barcode decoding for example. + */ + [[nodiscard]] bool hasAspectRatioTransform() const; + + /** Applies the aspect ratio changing part of the transform + * to the given image (which typically should be the one returned + * by image()). + */ + [[nodiscard]] QImage applyAspectRatioTransform(const QImage &image) const; + private: friend class PdfExtractorOutputDevice; friend class PdfPagePrivate; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kitinerary-23.08.1/src/lib/processors/barcodedocumentprocessorhelper.cpp new/kitinerary-23.08.2/src/lib/processors/barcodedocumentprocessorhelper.cpp --- old/kitinerary-23.08.1/src/lib/processors/barcodedocumentprocessorhelper.cpp 2023-09-11 17:49:23.000000000 +0200 +++ new/kitinerary-23.08.2/src/lib/processors/barcodedocumentprocessorhelper.cpp 2023-10-09 17:36:11.000000000 +0200 @@ -11,7 +11,7 @@ using namespace KItinerary; -void BarcodeDocumentProcessorHelper::expandNode(const QImage &img, BarcodeDecoder::BarcodeTypes barcodeHints, ExtractorDocumentNode &parent, const ExtractorEngine* engine) +bool BarcodeDocumentProcessorHelper::expandNode(const QImage &img, BarcodeDecoder::BarcodeTypes barcodeHints, ExtractorDocumentNode &parent, const ExtractorEngine* engine) { // in case the barcode raw data (string or bytearray) gets detected as a type we handle, // we nevertheless inject a raw data node in between. This is useful in cases where the @@ -22,12 +22,12 @@ auto c = engine->documentNodeFactory()->createNode(b); if (c.isA<QByteArray>() || c.isA<QString>()) { parent.appendChild(c); - return; + return true; } auto rawNode = engine->documentNodeFactory()->createNode(QVariant::fromValue(b), u"application/octet-stream"); rawNode.appendChild(c); parent.appendChild(rawNode); - return; + return true; } const auto s = engine->barcodeDecoder()->decodeString(img, barcodeHints); @@ -35,11 +35,13 @@ auto c = engine->documentNodeFactory()->createNode(s.toUtf8()); if (c.isA<QByteArray>() || c.isA<QString>()) { parent.appendChild(c); - return; + return true; } auto rawNode = engine->documentNodeFactory()->createNode(QVariant::fromValue(s), u"text/plain"); rawNode.appendChild(c); parent.appendChild(rawNode); - return; + return true; } + + return false; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kitinerary-23.08.1/src/lib/processors/barcodedocumentprocessorhelper.h new/kitinerary-23.08.2/src/lib/processors/barcodedocumentprocessorhelper.h --- old/kitinerary-23.08.1/src/lib/processors/barcodedocumentprocessorhelper.h 2023-09-11 17:49:23.000000000 +0200 +++ new/kitinerary-23.08.2/src/lib/processors/barcodedocumentprocessorhelper.h 2023-10-09 17:36:11.000000000 +0200 @@ -16,7 +16,7 @@ /** Barcode-related document processor implementation details. */ namespace BarcodeDocumentProcessorHelper { -void expandNode(const QImage &img, BarcodeDecoder::BarcodeTypes barcodeHints, ExtractorDocumentNode &parent, const ExtractorEngine *engine); +bool expandNode(const QImage &img, BarcodeDecoder::BarcodeTypes barcodeHints, ExtractorDocumentNode &parent, const ExtractorEngine *engine); } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kitinerary-23.08.1/src/lib/processors/pdfdocumentprocessor.cpp new/kitinerary-23.08.2/src/lib/processors/pdfdocumentprocessor.cpp --- old/kitinerary-23.08.1/src/lib/processors/pdfdocumentprocessor.cpp 2023-09-11 17:49:23.000000000 +0200 +++ new/kitinerary-23.08.2/src/lib/processors/pdfdocumentprocessor.cpp 2023-10-09 17:36:11.000000000 +0200 @@ -39,7 +39,7 @@ static void applyContextDateTime(PdfDocument *pdf, ExtractorDocumentNode &node) { // ignore broken PDF times for Amadeus documents - if (pdf->producer() == QLatin1String("Amadeus") && pdf->creationTime() == pdf->modificationTime() && pdf->creationTime().date() == QDate(2011, 5, 10)) { + if (pdf->producer() == QLatin1String("Amadeus") && pdf->creationTime() == pdf->modificationTime() && pdf->creationTime().date().year() <= 2013) { return; } @@ -117,7 +117,14 @@ // technically not our job to do this here rather than letting the image node processor handle this // but we have the output aspect ratio of the barcode only here, which gives better decoding hints - BarcodeDocumentProcessorHelper::expandNode(imgData, barcodeHints, childNode, engine); + if (BarcodeDocumentProcessorHelper::expandNode(imgData, barcodeHints, childNode, engine)) { + continue; + } + + // if this failed, check if the image as a aspect-ratio distorting scale and try again with that + if (img.hasAspectRatioTransform()) { + BarcodeDocumentProcessorHelper::expandNode(img.applyAspectRatioTransform(imgData), barcodeHints, childNode, engine); + } } // handle full page raster images diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kitinerary-23.08.1/src/lib/scripts/bookingkit.js new/kitinerary-23.08.2/src/lib/scripts/bookingkit.js --- old/kitinerary-23.08.1/src/lib/scripts/bookingkit.js 2023-09-11 17:49:23.000000000 +0200 +++ new/kitinerary-23.08.2/src/lib/scripts/bookingkit.js 2023-10-09 17:36:11.000000000 +0200 @@ -27,3 +27,30 @@ return res; } + +function parsePdf(pdf, node, barcode) { + const page = pdf.pages[barcode.location]; + const text = page.textInRect(0.0, 0.0, 0.9, 0.3); + console.log(text); + let res = JsonLd.newEventReservation(); + res.reservationFor.name = text.match(/^ *(\S.*)\n/)[1]; + res.reservedTicket.ticketToken = 'qrCode:' + barcode.content; + res.reservationNumber = barcode.content; + + const dts = text.match(/(\d\d\.\d\d\.\d{4}).*\n *(\d\d\.\d\d\.\d{4})\n.*\n *(\d\d:\d\d).*(\d\d:\d\d)/); + if (dts) { + res.reservationFor.startDate = JsonLd.toDateTime(dts[1] + dts[3], 'dd.MM.yyyyhh:mm', 'en'); + res.reservationFor.endDate = JsonLd.toDateTime(dts[2] + dts[4], 'dd.MM.yyyyhh:mm', 'en'); + } else { + const dt = text.match(/(\d\d\.\d\d\.\d{4}).*\n *(\d\d:\d\d)/); + res.reservationFor.startDate = JsonLd.toDateTime(dt[1] + dt[2], 'dd.MM.yyyyhh:mm', 'en'); + } + + const loc = text.match(/(\S.*), (.*), (.*)\n *(.*)\n *(.*)\n.*##/); + res.reservationFor.location.address.streetAddress = loc[1]; + res.reservationFor.location.address.addressLocality = loc[2]; + res.reservationFor.location.address.addressCountry = loc[3]; + res.reservedTicket.name = loc[4]; + res.underName.name = loc[5]; + return res; +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kitinerary-23.08.1/src/lib/scripts/bookingkit.json new/kitinerary-23.08.2/src/lib/scripts/bookingkit.json --- old/kitinerary-23.08.1/src/lib/scripts/bookingkit.json 2023-09-11 17:49:23.000000000 +0200 +++ new/kitinerary-23.08.2/src/lib/scripts/bookingkit.json 2023-10-09 17:36:11.000000000 +0200 @@ -7,4 +7,14 @@ "scope": "Current" } ], "script": "bookingkit.js" +}, +{ + "mimeType": "application/pdf", + "filter": [ { + "match": "^[A-Z0-9]{3}-[A-z0-9]{6}-[A-Z0-9]{3}$", + "mimeType": "text/plain", + "scope": "Descendants" + } ], + "script": "bookingkit.js", + "function": "parsePdf" }] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kitinerary-23.08.1/src/lib/scripts/chaos-communication-congress.js new/kitinerary-23.08.2/src/lib/scripts/chaos-communication-congress.js --- old/kitinerary-23.08.1/src/lib/scripts/chaos-communication-congress.js 2023-09-11 17:49:23.000000000 +0200 +++ new/kitinerary-23.08.2/src/lib/scripts/chaos-communication-congress.js 2023-10-09 17:36:11.000000000 +0200 @@ -12,7 +12,7 @@ var ptTicket = triggerNode.content var res = JsonLd.newEventReservation(); - res.reservationFor.name = ptTicket.person.name; + res.reservationFor.name = ptTicket.ticketLayout.text(0, 52, 19, 1).trim(); res.reservationFor.location.name = "Congress Center Leipzig"; res.reservationFor.location.address.streetAddress = "Messeallee"; res.reservationFor.location.address.postalCode = "04356"; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kitinerary-23.08.1/src/lib/scripts/elron.js new/kitinerary-23.08.2/src/lib/scripts/elron.js --- old/kitinerary-23.08.1/src/lib/scripts/elron.js 2023-09-11 17:49:23.000000000 +0200 +++ new/kitinerary-23.08.2/src/lib/scripts/elron.js 2023-10-09 17:36:11.000000000 +0200 @@ -5,7 +5,6 @@ function parsePdfTicket(pdf, node, triggerNode) { const text = pdf.pages[triggerNode.location].text; - console.log(text); let res = JsonLd.newTrainReservation(); const trip = text.match(/(\d\d:\d\d) (\S.*) - (\d\d:\d\d) (\S.*) +(\d\d\.\d\d\.\d{4})\n *(\S.*?) /); res.reservationFor.departureTime = JsonLd.toDateTime(trip[5] + ' ' + trip[1], 'dd.MM.yyyy hh:mm', 'ee'); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kitinerary-23.08.1/src/lib/scripts/fjordline.js new/kitinerary-23.08.2/src/lib/scripts/fjordline.js --- old/kitinerary-23.08.1/src/lib/scripts/fjordline.js 2023-09-11 17:49:23.000000000 +0200 +++ new/kitinerary-23.08.2/src/lib/scripts/fjordline.js 2023-10-09 17:36:11.000000000 +0200 @@ -7,7 +7,6 @@ const text = pdf.pages[triggerNode.location].text; if (!text.match(/Fjord Line/)) return; - console.log(text); let res = JsonLd.newBoatReservation(); const trip = text.match(/(\d{2}.\d{2}.\d{4}) +(.*?) +(\d{2}\.\d{2}) +(.*) +(\d{2}\.\d{2}) +(.*?) +(.*)/); res.reservationFor.departureBoatTerminal.name = trip[2]; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kitinerary-23.08.1/src/lib/scripts/gwr.js new/kitinerary-23.08.2/src/lib/scripts/gwr.js --- old/kitinerary-23.08.1/src/lib/scripts/gwr.js 2023-09-11 17:49:23.000000000 +0200 +++ new/kitinerary-23.08.2/src/lib/scripts/gwr.js 2023-10-09 17:36:11.000000000 +0200 @@ -6,7 +6,6 @@ function parseEvent(event) { let reservations = []; let idx = 0; - const dtStart = JsonLd.readQDateTime(event, 'dtStart'); while (true) { const trip = event.description.substr(idx).match(/Journey Details: (.*) \(([A-Z]{3})\) to (.*) \(([A-Z]{3})\), *dep *(\d\d):(\d\d), *arr *(\d\d):(\d\d)/); if (!trip) { @@ -15,11 +14,11 @@ idx += trip.index + trip[0].length; let res = JsonLd.newTrainReservation(); - let depDt = new Date(dtStart); + let depDt = new Date(event.dtStart); depDt.setHours(trip[5]); depDt.setMinutes(trip[6]); res.reservationFor.departureTime = depDt; - let arrDt = new Date(dtStart); + let arrDt = new Date(event.dtStart); arrDt.setHours(trip[7]); arrDt.setMinutes(trip[8]); res.reservationFor.arrivalTime = arrDt; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kitinerary-23.08.1/src/lib/scripts/nationalrail.js new/kitinerary-23.08.2/src/lib/scripts/nationalrail.js --- old/kitinerary-23.08.1/src/lib/scripts/nationalrail.js 2023-09-11 17:49:23.000000000 +0200 +++ new/kitinerary-23.08.2/src/lib/scripts/nationalrail.js 2023-10-09 17:36:11.000000000 +0200 @@ -82,7 +82,7 @@ } const seatLetter = readRSP6String(rsp6, 422 + offset, 1); const seatNum = rsp6.readNumberMSB(428 + offset, 7); - if (seatLetter || seatNum) { + if ((seatLetter && seatLetter != '*') || seatNum) { res.reservedTicket.ticketedSeat.seatNumber = (seatLetter ? seatLetter : "") + (seatNum ? seatNum : ""); } offset += 45; @@ -99,7 +99,7 @@ function parseTicket(pdf, node, triggerNode) { const text = pdf.pages[triggerNode.location].text; var res = triggerNode.result[0]; - const header = text.match(/= +(\d{2}[ -][A-Z][a-z]{2}[ -]\d{4}) +(?:Out: |Ret: )?([A-Z]{3}) ?- ?([A-Z]{3})\n(.*) +(.*)/); + const header = text.match(/ +(\d{2}[ -][A-Z][a-z]{2}[ -]\d{4}) +(?:Out: |Ret: )?([A-Z]{3}) ?- ?([A-Z]{3})\n(.*) +(.*)/); const date = header[1].replace(/-/g, ' '); const itinerary = text.match(/Itinerary.*\n +(.*)\n +(\d{2}:\d{2})\n +([\S\s]*?)\n +(\d{2}:\d{2})\n +(.*)/); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kitinerary-23.08.1/src/lib/scripts/oebb.js new/kitinerary-23.08.2/src/lib/scripts/oebb.js --- old/kitinerary-23.08.1/src/lib/scripts/oebb.js 2023-09-11 17:49:23.000000000 +0200 +++ new/kitinerary-23.08.2/src/lib/scripts/oebb.js 2023-10-09 17:36:11.000000000 +0200 @@ -3,14 +3,24 @@ SPDX-License-Identifier: LGPL-2.0-or-later */ -function parseTicket(ticket, node) { - if (node.result.length > 1) // not sure this can happen - return; - - var res = node.result[0]; +function parseTicket(code, node) { + // VorteilsCard + if (code.ticketLayout && code.ticketLayout.type == "RCT2" && code.ticketLayout.text(0, 0, 50, 1).match(/VORTEILSCARD/i)) { + let card = JsonLd.newObject("ProgramMembership"); + card.programName = code.ticketLayout.text(0, 0, 50, 1); + card.membershipNumber = code.ticketLayout.text(1, 1, 16, 1); + card.member = JsonLd.newObject("Person"); + card.member.givenName = code.ticketLayout.text(1, 52, 19 ,1); + card.member.familyName = code.ticketLayout.text(2, 52, 19 ,1); + card.token = 'aztecbin:' + ByteArray.toBase64(code.rawData); + card.validFrom = JsonLd.readQDateTime(code, 'validFrom'); + card.validUntil = JsonLd.readQDateTime(code, 'validUntil'); + return card.programName != undefined ? card : undefined; + } // decode 118199 vendor block - const block = ticket.block("118199"); + let res = node.result[0]; + const block = code.block("118199"); const json = JSON.parse(block.contentText); if (!res.reservationFor.trainNumber) res.reservationFor.trainNumber = json["Z"]; @@ -19,15 +29,33 @@ } function parsePage(pdf, node, triggerNode) { - if (triggerNode.parent.result.length > 2) // not sure this can happen - return; + const details = pdf.pages[triggerNode.location].textInRect(0, 0.35, 0.65, 1); + let reservations = []; + let idx = 0; + while (true) { + const leg = details.substr(idx).match(/(\S.*\S) +(\d\d\.\d\d\.\d{4}) +(\d\d:\d\d) +(.*)\n *(\S.*\S) +(\d\d\.\d\d\.\d{4}) +(\d\d:\d\d)/); + if (!leg) + break; + idx += leg.index + leg[0].length; + + let res = JsonLd.newTrainReservation(); + res.reservationFor.departureStation.name = leg[1]; + res.reservationFor.departureTime = JsonLd.toDateTime(leg[2] + ' ' + leg[3], 'dd.MM.yyyy hh:mm', 'de'); + res.reservationFor.trainNumber = leg[4]; + res.reservationFor.arrivalStation.name = leg[5]; + res.reservationFor.arrivalTime = JsonLd.toDateTime(leg[6] + ' ' + leg[7], 'dd.MM.yyyy hh:mm', 'de'); + res = JsonLd.apply(triggerNode.result[0], res); + reservations.push(res); + } + + if (reservations.length > 0) + return reservations; // look for non-elided station names const text = pdf.pages[triggerNode.location].text; const legs = text.match(/VON +-> NACH.*\n.*? ([A-Z].*) +-> (.*?) .*\n.*? ([\w\*].*) +-> (.*?) /); if (!legs) { return triggerNode.result; } - var reservations = []; var res = triggerNode.parent.result[0]; res.reservationFor.departureStation.name = legs[1]; res.reservationFor.arrivalStation.name = legs[2]; @@ -41,19 +69,3 @@ return reservations; } - -function parseUic9183(code, node) { - // VorteilsCard - if (code.ticketLayout && code.ticketLayout.type == "RCT2" && code.ticketLayout.text(0, 0, 50, 1).match(/VORTEILSCARD/i)) { - var card = JsonLd.newObject("ProgramMembership"); - card.programName = code.ticketLayout.text(0, 0, 50, 1); - card.membershipNumber = code.ticketLayout.text(1, 1, 16, 1); - card.member = JsonLd.newObject("Person"); - card.member.givenName = code.ticketLayout.text(1, 52, 19 ,1); - card.member.familyName = code.ticketLayout.text(2, 52, 19 ,1); - card.token = 'aztecbin:' + ByteArray.toBase64(code.rawData); - card.validFrom = JsonLd.readQDateTime(code, 'validFrom'); - card.validUntil = JsonLd.readQDateTime(code, 'validUntil'); - return card.programName != undefined ? card : undefined; - } -} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kitinerary-23.08.1/src/lib/scripts/oebb.json new/kitinerary-23.08.2/src/lib/scripts/oebb.json --- old/kitinerary-23.08.1/src/lib/scripts/oebb.json 2023-09-11 17:49:23.000000000 +0200 +++ new/kitinerary-23.08.2/src/lib/scripts/oebb.json 2023-10-09 17:36:11.000000000 +0200 @@ -24,18 +24,5 @@ "function": "parsePage", "mimeType": "application/pdf", "script": "oebb.js" - }, - { - "filter": [ - { - "field": "carrierId", - "match": "1181", - "mimeType": "internal/uic9183", - "scope": "Current" - } - ], - "function": "parseUic9183", - "mimeType": "internal/uic9183", - "script": "oebb.js" } ] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kitinerary-23.08.1/src/lib/scripts/ouigo-es.js new/kitinerary-23.08.2/src/lib/scripts/ouigo-es.js --- old/kitinerary-23.08.1/src/lib/scripts/ouigo-es.js 2023-09-11 17:49:23.000000000 +0200 +++ new/kitinerary-23.08.2/src/lib/scripts/ouigo-es.js 2023-10-09 17:36:11.000000000 +0200 @@ -3,20 +3,12 @@ SPDX-License-Identifier: LGPL-2.0-or-later */ -// barcode format -// 13x ticket number -// 5x ? -// 5x train number -// 15x departure time (dd/MM/yyyyhh:mm) -// 7x departure UIC station code -// 7x arrival UIC station code -// 3x coach number -// 3x seat number -// more stuff - signature? +// see https://community.kde.org/KDE_PIM/KItinerary/Renfe_Barcodes#Common_Spanish_Ticket_Barcode function parseBarcode(content) { let res = JsonLd.newTrainReservation(); res.reservedTicket.ticketToken = 'azteccode:' + content; res.reservedTicket.ticketNumber = content.substr(0, 13); + res.reservationFor.provider.identifier = 'uic:' + content.substr(13, 5); res.reservationFor.trainNumber = content.substr(18, 5); res.reservationFor.departureTime = JsonLd.toDateTime(content.substr(23, 15), 'dd/MM/yyyyhh:mm', 'es'); res.reservationFor.departureStation.identifier = 'uic:71' + content.substr(40, 5); @@ -25,6 +17,10 @@ res.reservationFor.arrivalStation.name = content.substr(45, 7); res.reservedTicket.ticketedSeat.seatSection = content.substr(52, 3); res.reservedTicket.ticketedSeat.seatNumber = content.substr(55, 3); + + if (content.substr(13, 5) == "01071") { + res.reservationNumber = content.substr(143, 6); + } return res; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kitinerary-23.08.1/src/lib/scripts/pretix.js new/kitinerary-23.08.2/src/lib/scripts/pretix.js --- old/kitinerary-23.08.1/src/lib/scripts/pretix.js 2023-09-11 17:49:23.000000000 +0200 +++ new/kitinerary-23.08.2/src/lib/scripts/pretix.js 2023-10-09 17:36:11.000000000 +0200 @@ -15,3 +15,17 @@ res.reservedTicket.name = content.field['ticket'].value; return res; } + +// only works for unstyled PDFs common for smaller events +function parsePdf(pdf, node, barcode) { + let res = JsonLd.newEventReservation(); + const text = pdf.pages[barcode.location].textInRect(0.0, 0.0, 1.0, 0.4); + const data = text.trim().split(/\n/); + res.reservationFor.location.name = data[data.length - 2]; + res.reservationNumber = data[data.length - 1].match(/(\S+) /)[1]; + res.reservationFor.startDate = JsonLd.toDateTime(data[data.length - 3], ['dd.MM.yyyy hh:mm', 'yyyy-MM-dd hh:mm'], 'en'); + res.underName.name = data[data.length - 4]; + res.reservationFor.name = data.slice(0, data.length - 5).join(' '); + res.reservedTicket.ticketToken = 'qrcode:' + barcode.content; + return res; +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kitinerary-23.08.1/src/lib/scripts/pretix.json new/kitinerary-23.08.2/src/lib/scripts/pretix.json --- old/kitinerary-23.08.1/src/lib/scripts/pretix.json 2023-09-11 17:49:23.000000000 +0200 +++ new/kitinerary-23.08.2/src/lib/scripts/pretix.json 2023-10-09 17:36:11.000000000 +0200 @@ -1,13 +1,27 @@ -{ - "filter": [ - { - "field": "passTypeIdentifier", - "match": "pass.eu.pretix.ticket", - "mimeType": "application/vnd.apple.pkpass", - "scope": "Current" - } - ], - "function": "parsePass", - "mimeType": "application/vnd.apple.pkpass", - "script": "pretix.js" -} +[ + { + "filter": [ + { + "field": "passTypeIdentifier", + "match": "pass.eu.pretix.ticket", + "mimeType": "application/vnd.apple.pkpass", + "scope": "Current" + } + ], + "function": "parsePass", + "mimeType": "application/vnd.apple.pkpass", + "script": "pretix.js" + }, + { + "filter": [ + { + "match": "^[0-9a-z]{32}$", + "mimeType": "text/plain", + "scope": "Descendants" + } + ], + "function": "parsePdf", + "mimeType": "application/pdf", + "script": "pretix.js" + } +] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kitinerary-23.08.1/src/lib/scripts/qatar-airways.js new/kitinerary-23.08.2/src/lib/scripts/qatar-airways.js --- old/kitinerary-23.08.1/src/lib/scripts/qatar-airways.js 2023-09-11 17:49:23.000000000 +0200 +++ new/kitinerary-23.08.2/src/lib/scripts/qatar-airways.js 2023-10-09 17:36:11.000000000 +0200 @@ -9,7 +9,6 @@ let reservations = []; for(let idx = 0;;) { const leg = leftCol.substr(idx).match(/([A-Z0-9]{2})(\d{1,4}) +... \(([A-Z]{3})\), (.*)\n(.*?) +\S*, (.*)\n.*\n *... \(([A-Z]{3})\), (.*)\n.*, (.*)\n/); - console.log(leg); if (!leg) break; idx += leg.index + leg[0].length; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kitinerary-23.08.1/src/lib/scripts/renfe.js new/kitinerary-23.08.2/src/lib/scripts/renfe.js --- old/kitinerary-23.08.1/src/lib/scripts/renfe.js 2023-09-11 17:49:23.000000000 +0200 +++ new/kitinerary-23.08.2/src/lib/scripts/renfe.js 2023-10-09 17:36:11.000000000 +0200 @@ -4,22 +4,9 @@ SPDX-License-Identifier: LGPL-2.0-or-later */ +// see https://community.kde.org/KDE_PIM/KItinerary/Renfe_Barcodes#Renfe_Barcode function parseBarcode(barcode) { - // barcode content: - // 13x ticket number - // 5x Renfe departure station id - // 5x Renfe arrival station id - // 6x departure(?) date: ddMMyy - // 5x train number - // 3x coach number - // 3x seat number - // 2x unknown number - // 1x unknown number - // 6x "localizador" ~ PNR? - // ".." (optional) - // 5x "CombinadoCercanias" (optional) - if (barcode.trim().length > 56) return null; @@ -28,7 +15,8 @@ barcode = barcode.substr(0, 49); var res = JsonLd.newTrainReservation(); - res.reservedTicket.ticketToken = "qrCode:" + barcode; + res.reservedTicket.ticketToken = "qrCode:" + barcode.trim(); + res.reservedTicket.ticketNumber = barcode.substr(0, 13); res.reservationFor.trainNumber = barcode.substr(29, 5); res.reservedTicket.ticketedSeat.seatSection = barcode.substr(34, 3); res.reservedTicket.ticketedSeat.seatNumber = barcode.substr(37, 3); @@ -42,25 +30,50 @@ return res; } -function parsePdf(pdf, node, triggerNode) +function parseLeg(text, baseRes) { - if (!triggerNode.content) - return; + let res = JsonLd.newTrainReservation(); - var res = parseBarcode(triggerNode.content.trim()); - var text = pdf.pages[triggerNode.location].text; - var dep = text.match(/(?:Salida|Origen:) +(.*?) {2,}([\d\/]+) +(\d\d:\d\d)/); + const dep = text.match(/(?:Salida|Origen:) +(.*?) {2,}([\d\/]+) +(\d\d[:\.]\d\d)/); res.reservationFor.departureStation.name = dep[1]; - res.reservationFor.departureTime = JsonLd.toDateTime(dep[2] + dep[3], "dd/MM/yyyyhh:mm", "es"); + res.reservationFor.departureTime = JsonLd.toDateTime(dep[2] + dep[3], ["dd/MM/yyyyhh:mm", "dd/MM/yyyyhh.mm"], "es"); - var arr = text.match(/(?:Llegada|Destino:)\s+(.*?) {2,}([\d\/]+) +(\d\d:\d\d)\n(?:.* )?([A-Z]+) +(\d+)/); + const arr = text.match(/(?:Llegada|Destino:)\s+(.*?) {2,}([\d\/]+) +(\d\d[:\.]\d\d)\n(?:.* )?([A-Z]+) +(\d+)/); res.reservationFor.arrivalStation.name = arr[1]; - res.reservationFor.arrivalTime = JsonLd.toDateTime(arr[2] + arr[3], "dd/MM/yyyyhh:mm", "es"); + res.reservationFor.arrivalTime = JsonLd.toDateTime(arr[2] + arr[3], ["dd/MM/yyyyhh:mm", "dd/MM/yyyyhh.mm"], "es"); res.reservationFor.trainName = arr[4]; + const train = text.match(/Coche:\s+(\S+)\s+Plaza:\s+(\S+)\s+(\S.*) +(\d{1,5})/); + if (train) { + res.reservedTicket.ticketedSeat.seatSection = train[1]; + res.reservedTicket.ticketedSeat.seatNumber = train[2]; + res.reservationFor.trainName = train[3]; + res.reservationFor.trainNumber = train[4]; + } + + res.reservationNumber = baseRes.reservationNumber; + res.reservationFor.provider = baseRes.reservationFor.provider; + res.reservedTicket.ticketToken = baseRes.reservedTicket.ticketToken; return res; } +function parsePdf(pdf, node, triggerNode) +{ + const res = triggerNode.result[0]; + const page = pdf.pages[triggerNode.location] + const text = page.text; + if (!text.match(/TRAYECTO/)) { + return JsonLd.apply(res, parseLeg(text, res)); + } + + // multi-leg ticket + let res1 = parseLeg(page.textInRect(0.0, 0.0, 0.5, 1.0), res); + let res2 = parseLeg(page.textInRect(0.5, 0.0, 1.0, 1.0), res); + res1.reservationFor.departureStation = JsonLd.apply(res.reservationFor.departureStation, res1.reservationFor.departureStation); + res2.reservationFor.arrivalStation = JsonLd.apply(res.reservationFor.arrivalStation, res2.reservationFor.arrivalStation); + return [res1, res2]; +} + function parsePkPass(content) { var res = parseBarcode(content.barcodes[0].message); @@ -84,3 +97,17 @@ return res; } + +function parseInternationalPdf(pdf, node, uicNode) +{ + const renfeNode = node.findChildNodes({ scope: "Descendants", mimeType: "text/plain", match: "^\\d{25}/\\d{2}/\\d{6}:\\d{16}" }).find(n => n.location === uicNode.location); + + let res = JsonLd.apply(renfeNode.result[0], uicNode.result[0]); + res.reservationFor.departureStation.identifier = undefined; + res.reservationFor.arrivalStation.identifier = undefined; + res.reservationNumber = renfeNode.result[0].reservationNumber; + let renfeRes = JsonLd.clone(res); + renfeRes.reservedTicket.ticketToken = renfeNode.result[0].reservedTicket.ticketToken; + renfeRes.reservedTicket.name = renfeNode.result[0].reservedTicket.name; + return [renfeRes, res]; +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kitinerary-23.08.1/src/lib/scripts/renfe.json new/kitinerary-23.08.2/src/lib/scripts/renfe.json --- old/kitinerary-23.08.1/src/lib/scripts/renfe.json 2023-09-11 17:49:23.000000000 +0200 +++ new/kitinerary-23.08.2/src/lib/scripts/renfe.json 2023-10-09 17:36:11.000000000 +0200 @@ -33,4 +33,17 @@ "function": "parseBarcode", "script": "renfe.js", "mimeType": "text/plain" +}, +{ + "filter": [ + { + "field": "carrierId", + "match": "1071", + "mimeType": "internal/uic9183", + "scope": "Descendants" + } + ], + "function": "parseInternationalPdf", + "script": "renfe.js", + "mimeType": "application/pdf" }] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kitinerary-23.08.1/src/lib/scripts/sncf-online-ticket.js new/kitinerary-23.08.2/src/lib/scripts/sncf-online-ticket.js --- old/kitinerary-23.08.1/src/lib/scripts/sncf-online-ticket.js 2023-09-11 17:49:23.000000000 +0200 +++ new/kitinerary-23.08.2/src/lib/scripts/sncf-online-ticket.js 2023-10-09 17:36:11.000000000 +0200 @@ -10,8 +10,11 @@ // TODO mark cancelled trips as such for (const trips of [response.cancelledTrips, response.passedTrips, response.trips, response.preReservedTrips]) { for (const trip of trips) { - for (const jny of [trip.trip.tripDetails.inwardJourney, trip.trip.tripDetails.outwardJourney]) { + for (const jny of [trip.trip.tripDetails.inwardJourney, trip.trip.tripDetails.outwardJourney, trip.trip.tripDetails]) { let jnyResult = []; + if (!jny || !jny.timeline) { + continue; + } for (const step of jny.timeline.steps) { if (!step['train']) { continue; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kitinerary-23.08.1/src/lib/sortutil.cpp new/kitinerary-23.08.2/src/lib/sortutil.cpp --- old/kitinerary-23.08.1/src/lib/sortutil.cpp 2023-09-11 17:49:23.000000000 +0200 +++ new/kitinerary-23.08.2/src/lib/sortutil.cpp 2023-10-09 17:36:11.000000000 +0200 @@ -186,7 +186,7 @@ return elem.value<TrainTrip>().departureTime().isValid(); } if (JsonLd::isA<Flight>(elem)) { - return elem.value<TrainTrip>().departureTime().isValid(); + return elem.value<Flight>().departureTime().isValid(); } return SortUtil::startDateTime(elem).isValid(); @@ -195,14 +195,14 @@ bool SortUtil::hasEndTime(const QVariant &elem) { if (JsonLd::canConvert<Reservation>(elem)) { - return hasStartTime(JsonLd::convert<Reservation>(elem).reservationFor()); + return hasEndTime(JsonLd::convert<Reservation>(elem).reservationFor()); } if (JsonLd::isA<TrainTrip>(elem)) { return elem.value<TrainTrip>().arrivalTime().isValid(); } if (JsonLd::isA<Flight>(elem)) { - return elem.value<TrainTrip>().arrivalTime().isValid(); + return elem.value<Flight>().arrivalTime().isValid(); } - return SortUtil::startDateTime(elem).isValid(); + return SortUtil::endDateTime(elem).isValid(); } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kitinerary-23.08.1/src/lib/uic9183/rct2ticket.cpp new/kitinerary-23.08.2/src/lib/uic9183/rct2ticket.cpp --- old/kitinerary-23.08.1/src/lib/uic9183/rct2ticket.cpp 2023-09-11 17:49:23.000000000 +0200 +++ new/kitinerary-23.08.2/src/lib/uic9183/rct2ticket.cpp 2023-10-09 17:36:11.000000000 +0200 @@ -60,6 +60,9 @@ if (!d.isValid()) { d = QDate::fromString(dateStr, QStringLiteral("dd/MM")); } + if (!d.isValid()) { + d = QDate::fromString(dateStr, QStringLiteral("dd-MM")); + } auto t = QTime::fromString(timeStr, QStringLiteral("hh:mm")); if (!t.isValid()) { t = QTime::fromString(timeStr, QStringLiteral("hh.mm")); @@ -203,7 +206,9 @@ QString Rct2Ticket::passengerName() const { - return d->layout.text(0, 52, 19, 1).trimmed(); + const auto name = d->layout.text(0, 52, 19, 1).trimmed(); + // sanity-check if this is a plausible name, e.g. Renfe has random other stuff here + return std::any_of(name.begin(), name.end(), [](QChar c) { return c.isDigit(); }) ? QString() : name; } QDateTime Rct2Ticket::outboundDepartureTime() const diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kitinerary-23.08.1/src/lib/uic9183/uic9183parser.cpp new/kitinerary-23.08.2/src/lib/uic9183/uic9183parser.cpp --- old/kitinerary-23.08.1/src/lib/uic9183/uic9183parser.cpp 2023-09-11 17:49:23.000000000 +0200 +++ new/kitinerary-23.08.2/src/lib/uic9183/uic9183parser.cpp 2023-10-09 17:36:11.000000000 +0200 @@ -126,6 +126,19 @@ inflateEnd(&stream); d->m_payload.truncate(d->m_payload.size() - stream.avail_out); //qCDebug(Log) << res << d->m_payload << stream.avail_out; + + // workaround for Renfe (1071) having various errors... + if (d->m_payload.size() > 600 && d->m_payload.startsWith("U_HEAD0100531071") && d->m_payload[54] == 'U' && d->m_payload[36] == ' ') { + qCWarning(Log) << "Applying Renfe workaround for broken UIC 913.8 content..."; + d->m_payload.remove(36, 1); // off by one in U_HEAD + const auto idx = d->m_payload.indexOf("U_TLAY00"); + if (idx < d->m_payload.size() + 400 && std::strncmp(d->m_payload.constData() + idx + 12, "RCT2", 4) != 0) { + d->m_payload.insert(idx + 7, "1"); // wrong U_TLAY version + d->m_payload.replace(idx + 12, 4, "RCT2"); // wrong layout type + d->m_payload.remove(idx + 20, 1); // garbage trailing the layout type? + qCDebug(Log) << d->m_payload; + } + } } bool Uic9183Parser::isValid() const
