diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 454802a..7d39719 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,242 +1,243 @@ set(KDE_INSTALL_INCLUDEDIR_PIM ${KDE_INSTALL_INCLUDEDIR}/KPim) add_subdirectory(cli) if (TARGET Qt5::Network) add_subdirectory(knowledgedb-generator) endif() add_subdirectory(vdv/certs) configure_file(config-kitinerary.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-kitinerary.h) set(kitinerary_lib_srcs datatypes/action.cpp datatypes/brand.cpp datatypes/bustrip.cpp datatypes/creativework.cpp datatypes/event.cpp datatypes/flight.cpp datatypes/organization.cpp datatypes/person.cpp datatypes/place.cpp datatypes/reservation.cpp datatypes/taxi.cpp datatypes/ticket.cpp datatypes/traintrip.cpp datatypes/rentalcar.cpp datatypes/visit.cpp generic/genericpdfextractor.cpp generic/genericpkpassextractor.cpp generic/genericuic918extractor.cpp + generic/genericvdvextractor.cpp generic/structureddataextractor.cpp jsapi/barcode.cpp jsapi/bitarray.cpp jsapi/context.cpp jsapi/jsonld.cpp knowledgedb/alphaid.cpp knowledgedb/airportdb.cpp knowledgedb/countrydb.cpp knowledgedb/iatacode.cpp knowledgedb/knowledgedb.cpp knowledgedb/timezonedb.cpp knowledgedb/trainstationdb.cpp pdf/pdfdocument.cpp pdf/pdfextractoroutputdevice.cpp pdf/pdfimage.cpp pdf/pdfvectorpicture.cpp pdf/popplerglobalparams.cpp pdf/popplerutils.cpp uic9183/rct2ticket.cpp uic9183/uic9183block.cpp uic9183/uic9183parser.cpp uic9183/uic9183ticketlayout.cpp uic9183/vendor0080block.cpp vdv/iso9796_2decoder.cpp vdv/vdvcertificate.cpp vdv/vdvticket.cpp vdv/vdvticketparser.cpp vdv/certs/vdv-certs.qrc barcodedecoder.cpp calendarhandler.cpp documentutil.cpp extractor.cpp extractorengine.cpp extractorfilter.cpp extractorinput.cpp extractorpostprocessor.cpp extractorrepository.cpp extractorutil.cpp extractorvalidator.cpp file.cpp flightpostprocessor.cpp htmldocument.cpp iatabcbpparser.cpp jsonlddocument.cpp jsonldimportfilter.cpp locationutil.cpp mergeutil.cpp qimagepurebinarizer.cpp sortutil.cpp stringutil.cpp ) qt5_add_resources(kitinerary_lib_srcs extractors/extractors.qrc) ecm_qt_declare_logging_category(kitinerary_lib_srcs HEADER logging.h IDENTIFIER KItinerary::Log CATEGORY_NAME org.kde.kitinerary) ecm_qt_declare_logging_category(kitinerary_lib_srcs HEADER compare-logging.h IDENTIFIER KItinerary::CompareLog CATEGORY_NAME org.kde.kitinerary.comparator) ecm_qt_declare_logging_category(kitinerary_lib_srcs HEADER validator-logging.h IDENTIFIER KItinerary::ValidatorLog CATEGORY_NAME org.kde.kitinerary.extractorValidator) add_library(KPimItinerary ${kitinerary_lib_srcs}) add_library(KPim::Itinerary ALIAS KPimItinerary) generate_export_header(KPimItinerary BASE_NAME KItinerary) set_target_properties(KPimItinerary PROPERTIES VERSION ${KITINERARY_VERSION_STRING} SOVERSION ${KITINERARY_SOVERSION} EXPORT_NAME Itinerary ) target_include_directories(KPimItinerary INTERFACE "$") target_include_directories(KPimItinerary PUBLIC "$") target_link_libraries(KPimItinerary PUBLIC Qt5::Core KF5::Mime PRIVATE Qt5::Qml KF5::Archive KF5::I18n KF5::Contacts KPim::PkPass ${ZLIB_LIBRARIES} ) if (HAVE_POPPLER) target_link_libraries(KPimItinerary PRIVATE Poppler::Core) endif() if (HAVE_ZXING) target_link_libraries(KPimItinerary PRIVATE ZXing::Core) endif() if (HAVE_KCAL) target_link_libraries(KPimItinerary PUBLIC KF5::CalendarCore) endif() if (HAVE_LIBXML2) target_compile_definitions(KPimItinerary PRIVATE ${LIBXML2_DEFINITIONS}) target_include_directories(KPimItinerary PRIVATE ${LIBXML2_INCLUDE_DIR}) target_link_libraries(KPimItinerary PRIVATE ${LIBXML2_LIBRARIES}) endif() if (HAVE_PHONENUMBER) target_link_libraries(KPimItinerary PRIVATE PhoneNumber::PhoneNumber) endif() if (HAVE_OPENSSL_RSA) target_link_libraries(KPimItinerary PRIVATE OpenSSL::Crypto) endif() ecm_generate_headers(KItinerary_FORWARDING_HEADERS HEADER_NAMES BarcodeDecoder CalendarHandler DocumentUtil Extractor ExtractorEngine ExtractorFilter ExtractorInput ExtractorPostprocessor ExtractorRepository File HtmlDocument IataBcbpParser JsonLdDocument LocationUtil MergeUtil SortUtil PREFIX KItinerary REQUIRED_HEADERS KItinerary_HEADERS ) ecm_generate_headers(KItinerary_KnowledgeDb_FORWARDING_HEADERS HEADER_NAMES AlphaId CountryDb KnowledgeDb PREFIX KItinerary REQUIRED_HEADERS KItinerary_KnowledgeDb_HEADERS RELATIVE knowledgedb ) ecm_generate_headers(KItinerary_Datatypes_FORWARDING_HEADERS HEADER_NAMES Action Brand BusTrip CreativeWork Datatypes Event Flight Organization Reservation RentalCar Person Place Taxi Ticket TrainTrip Visit PREFIX KItinerary REQUIRED_HEADERS KItinerary_Datatypes_HEADERS RELATIVE datatypes ) ecm_generate_headers(KItinerary_Pdf_FORWARDING_HEADERS HEADER_NAMES PdfDocument PdfImage PREFIX KItinerary REQUIRED_HEADERS KItinerary_Pdf_HEADERS RELATIVE pdf ) ecm_generate_headers(KItinerary_Uic9183_FORWARDING_HEADERS HEADER_NAMES Rct2Ticket Uic9183Block Uic9183Parser Uic9183TicketLayout Vendor0080Block PREFIX KItinerary REQUIRED_HEADERS KItinerary_Uic9183_HEADERS RELATIVE uic9183 ) ecm_generate_headers(KItinerary_Vdv_FORWARDING_HEADERS HEADER_NAMES VdvTicket VdvTicketParser PREFIX KItinerary REQUIRED_HEADERS KItinerary_Vdv_HEADERS RELATIVE vdv ) install(TARGETS KPimItinerary EXPORT KPimItineraryTargets ${INSTALL_TARGETS_DEFAULT_ARGS}) install(FILES ${KItinerary_FORWARDING_HEADERS} ${KItinerary_KnowledgeDb_FORWARDING_HEADERS} ${KItinerary_Datatypes_FORWARDING_HEADERS} ${KItinerary_Pdf_FORWARDING_HEADERS} ${KItinerary_Uic9183_FORWARDING_HEADERS} ${KItinerary_Vdv_FORWARDING_HEADERS} DESTINATION ${KDE_INSTALL_INCLUDEDIR_PIM}/KItinerary ) install(FILES ${KItinerary_HEADERS} ${KItinerary_AirportDb_HEADERS} ${KItinerary_Datatypes_HEADERS} ${KItinerary_KnowledgeDb_HEADERS} ${KItinerary_Pdf_HEADERS} ${KItinerary_Uic9183_HEADERS} ${KItinerary_Vdv_HEADERS} ${CMAKE_CURRENT_BINARY_DIR}/kitinerary_export.h DESTINATION ${KDE_INSTALL_INCLUDEDIR_PIM}/kitinerary ) if (NOT ANDROID) install(FILES application-vnd-kde-itinerary.xml DESTINATION ${XDG_MIME_INSTALL_DIR}) update_xdg_mimetypes(${XDG_MIME_INSTALL_DIR}) endif() diff --git a/src/generic/genericpdfextractor.cpp b/src/generic/genericpdfextractor.cpp index b104c76..5e74e4f 100644 --- a/src/generic/genericpdfextractor.cpp +++ b/src/generic/genericpdfextractor.cpp @@ -1,161 +1,170 @@ /* Copyright (C) 2018 Volker Krause This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "genericpdfextractor_p.h" #include "genericuic918extractor_p.h" +#include "genericvdvextractor_p.h" #include #include #include #include #include +#include #include #include #include #include #include using namespace KItinerary; enum { MaxPageCount = 10, // maximum in the current test set is 6 MaxFileSize = 4000000, // maximum in the current test set is 980kB // unit is 1/72 inch, assuming landscape orientation MinTargetImageHeight = 28, MinTargetImageWidth = 36, MaxTargetImageHeight = 252, MaxTargetImageWidth = 252, }; GenericPdfExtractor::GenericPdfExtractor() = default; GenericPdfExtractor::~GenericPdfExtractor() = default; void GenericPdfExtractor::setBarcodeDecoder(BarcodeDecoder *decoder) { m_barcodeDecoder = decoder; } void GenericPdfExtractor::setContextDate(const QDateTime &dt) { m_contextDate = dt; } std::vector GenericPdfExtractor::extract(PdfDocument *doc) { std::vector result; // stay away from documents that are atypically large for what we are looking for // that's just unnecessarily eating up resources if (doc->pageCount() > MaxPageCount || doc->fileSize() > MaxFileSize) { return result; } m_imageIds.clear(); for (int i = 0; i < doc->pageCount(); ++i) { const auto page = doc->page(i); for (int j = 0; j < page.imageCount(); ++j) { auto img = page.image(j); img.setLoadingHints(PdfImage::AbortOnColorHint | PdfImage::ConvertToGrayscaleHint); // we only care about b/w-ish images for barcode detection if (img.hasObjectId() && m_imageIds.find(img.objectId()) != m_imageIds.end()) { continue; } if (!maybeBarcode(img)) { continue; } auto r = extractImage(img, result); if (!r.barcode.isNull() || !r.result.isEmpty()) { r.pageNum = i; result.push_back(r); } if (img.hasObjectId()) { m_imageIds.insert(img.objectId()); } } } return result; } static bool containsBarcodeResult(const std::vector &results, const QVariant &barcode) { const auto it = std::find_if(results.begin(), results.end(), [barcode](const auto &result) { return result.barcode == barcode; }); return it != results.end(); } GenericExtractor::Result GenericPdfExtractor::extractImage(const PdfImage &img, const std::vector &existingResults) { const auto imgData = img.image(); if (imgData.isNull()) { // can happen due to AbortOnColorHint return {}; } // binary barcode content const auto b = m_barcodeDecoder->decodeBinary(imgData); if (!b.isEmpty()) { if (containsBarcodeResult(existingResults, b)) { return {}; } if (Uic9183Parser::maybeUic9183(b)) { QJsonArray result; GenericUic918Extractor::extract(b, result, m_contextDate); if (!result.isEmpty()) { return GenericExtractor::Result{result, b, -1}; } return {}; } + + if (VdvTicketParser::maybeVdvTicket(b)) { + const auto result = GenericVdvExtractor::extract(b); + if (!result.isEmpty()) { + return GenericExtractor::Result{result, b, -1}; + } + } } // string barcode content const auto s = m_barcodeDecoder->decodeString(imgData); if (!s.isEmpty()) { if (containsBarcodeResult(existingResults, s)) { return {}; } if (IataBcbpParser::maybeIataBcbp(s)) { const auto res = IataBcbpParser::parse(s, m_contextDate.date()); const auto jsonLd = JsonLdDocument::toJson(res); return {jsonLd, s, -1}; } } return {{}, s.isEmpty() ? b.isEmpty() ? QVariant() : QVariant(b) : QVariant(s), -1}; } bool GenericPdfExtractor::maybeBarcode(const PdfImage &img, BarcodeDecoder::BarcodeTypes hint) { const auto w = img.width(); const auto h = img.height(); if (!BarcodeDecoder::isPlausibleSize(img.sourceWidth(), img.sourceHeight()) || !BarcodeDecoder::isPlausibleAspectRatio(w, h, hint)) { return false; } // image target size checks if (std::min(w, h) < MinTargetImageHeight || std::max(w, h) < MinTargetImageWidth || h > MaxTargetImageHeight || w > MaxTargetImageWidth) { return false; } return true; } diff --git a/src/generic/genericvdvextractor.cpp b/src/generic/genericvdvextractor.cpp new file mode 100644 index 0000000..3d905af --- /dev/null +++ b/src/generic/genericvdvextractor.cpp @@ -0,0 +1,65 @@ +/* + Copyright (C) 2019 Volker Krause + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU Library General Public License as published by + the Free Software Foundation; either version 2 of the License, or (at your + option) any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public + License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "genericvdvextractor_p.h" + +#include +#include + +#include +#include +#include + +using namespace KItinerary; + +QJsonArray GenericVdvExtractor::extract(const QByteArray &data) +{ + VdvTicketParser p; + if (!p.parse(data)) { + return {}; + } + + const auto vdv = p.ticket(); + + QJsonObject org; + org.insert(QStringLiteral("@type"), QLatin1String("Organization")); + org.insert(QStringLiteral("identifier"), QString(QLatin1String("vdv:") + QString::number(vdv.issuerId()))); + QJsonObject trip; + trip.insert(QStringLiteral("@type"), QLatin1String("TrainTrip")); + trip.insert(QStringLiteral("provider"), org); + QJsonObject seat; + seat.insert(QStringLiteral("@type"), QLatin1String("Seat")); +// seat.insert(QStringLiteral("seatingType"), vdv.serviceClass()); + + QJsonObject ticket; + ticket.insert(QStringLiteral("@type"), QLatin1String("Ticket")); + ticket.insert(QStringLiteral("ticketToken"), QString(QLatin1String("aztecbin:") + QString::fromLatin1(data.toBase64()))); + ticket.insert(QStringLiteral("ticketedSeat"), seat); + + QJsonObject person; + person.insert(QStringLiteral("@type"), QLatin1String("Person")); +// person.insert(QStringLiteral("name"), vdv.passengerName()); + + QJsonObject res; + res.insert(QStringLiteral("@type"), QLatin1String("TrainReservation")); + res.insert(QStringLiteral("reservationFor"), trip); +// res.insert(QStringLiteral("reservationNumber"), vdv.ticketNumber()); + res.insert(QStringLiteral("reservedTicket"), ticket); + res.insert(QStringLiteral("underName"), person); + + return {res}; +} diff --git a/src/generic/genericvdvextractor_p.h b/src/generic/genericvdvextractor_p.h new file mode 100644 index 0000000..158cc05 --- /dev/null +++ b/src/generic/genericvdvextractor_p.h @@ -0,0 +1,34 @@ +/* + Copyright (C) 2019 Volker Krause + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU Library General Public License as published by + the Free Software Foundation; either version 2 of the License, or (at your + option) any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public + License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef KITINERARY_GENERICVDVEXTRACTOR_P_H +#define KITINERARY_GENERICVDVEXTRACTOR_P_H + +class QByteArray; +class QJsonArray; + +namespace KItinerary { + +/** Generic extractor for VDV tickets. */ +namespace GenericVdvExtractor +{ + QJsonArray extract(const QByteArray &data); +} + +} + +#endif // KITINERARY_GENERICVDVEXTRACTOR_P_H