Members of the KDE Community are recommended to subscribe to the kde-community mailing list at https://mail.kde.org/mailman/listinfo/kde-community to allow them to participate in important discussions and receive other important announcements

Commit 3ac7b1d9 authored by Volker Krause's avatar Volker Krause

Move generic pkpass extraction code to its own file

Consistent with the generic PDF extractor, and allows to keep the extractor
engine class a bit more manageable in size.
parent 94a40296
......@@ -39,6 +39,7 @@ set(kitinerary_lib_srcs
extractorrepository.cpp
extractorutil.cpp
genericpdfextractor.cpp
genericpkpassextractor.cpp
htmldocument.cpp
iatabcbpparser.cpp
jsonlddocument.cpp
......
......@@ -22,6 +22,7 @@
#include "extractor.h"
#include "extractorrepository.h"
#include "genericpdfextractor.h"
#include "genericpkpassextractor_p.h"
#include "htmldocument.h"
#include "jsonlddocument.h"
#include "logging.h"
......@@ -38,9 +39,7 @@
#include <KCalCore/ICalFormat>
#endif
#include <KPkPass/Barcode>
#include <KPkPass/BoardingPass>
#include <KPkPass/Location>
#include <KPkPass/Pass>
#include <KMime/Content>
......@@ -74,9 +73,6 @@ public:
void executeScript(const Extractor *extractor);
void processScriptResult(const QJSValue &result);
void extractPass();
void extractBoardingPass(QJsonObject &resFor);
void extractEventTicketPass(QJsonObject &resFor);
std::vector<const Extractor*> m_extractors;
JsApi::Barcode *m_barcodeApi = nullptr;
......@@ -380,7 +376,16 @@ void ExtractorEnginePrivate::extractCustom()
void ExtractorEnginePrivate::extractGeneric()
{
if (m_pass) {
extractPass();
if (m_result.size() > 1) { // a pkpass file contains exactly one boarding pass
return;
}
if (m_result.isEmpty()) {
m_result.push_back(QJsonObject());
}
auto res = m_result.at(0).toObject();
GenericPkPassExtractor::extract(m_pass.get(), res);
m_result[0] = res;
} else if (m_pdfDoc && m_result.isEmpty()) {
QJsonArray genericResult;
m_genericPdfExtractor.extract(m_pdfDoc.get(), genericResult);
......@@ -479,157 +484,3 @@ void ExtractorEnginePrivate::processScriptResult(const QJSValue &result)
qCWarning(Log) << "Invalid result type from script";
}
}
void ExtractorEnginePrivate::extractPass()
{
if (m_result.size() > 1) { // a pkpass file contains exactly one boarding pass
return;
}
if (m_result.isEmpty()) { // no script run, so we need to create the top-level element ourselves
QJsonObject res;
QJsonObject resFor;
if (auto boardingPass = qobject_cast<KPkPass::BoardingPass*>(m_pass.get())) {
switch (boardingPass->transitType()) {
case KPkPass::BoardingPass::Air:
res.insert(QStringLiteral("@type"), QLatin1String("FlightReservation"));
resFor.insert(QStringLiteral("@type"), QLatin1String("Flight"));
break;
// TODO expand once we have test files for train tickets
default:
return;
}
} else {
switch (m_pass->type()) {
case KPkPass::Pass::EventTicket:
res.insert(QStringLiteral("@type"), QLatin1String("EventReservation"));
resFor.insert(QStringLiteral("@type"), QLatin1String("Event"));
break;
default:
return;
}
}
res.insert(QStringLiteral("reservationFor"), resFor);
m_result.push_back(res);
}
// extract structured data from a pkpass, if the extractor script hasn't done so already
auto res = m_result.at(0).toObject();
auto resFor = res.value(QLatin1String("reservationFor")).toObject();
switch (m_pass->type()) {
case KPkPass::Pass::BoardingPass:
extractBoardingPass(resFor);
break;
case KPkPass::Pass::EventTicket:
extractEventTicketPass(resFor);
break;
default:
return;
}
// barcode contains the ticket token
if (!m_pass->barcodes().isEmpty() && !res.contains(QLatin1String("reservedTicket"))) {
const auto barcode = m_pass->barcodes().at(0);
QString token;
switch (barcode.format()) {
case KPkPass::Barcode::QR:
token += QLatin1String("qrCode:");
break;
case KPkPass::Barcode::Aztec:
token += QLatin1String("aztecCode:");
break;
default:
break;
}
token += barcode.message();
QJsonObject ticket;
ticket.insert(QStringLiteral("@type"), QLatin1String("Ticket"));
ticket.insert(QStringLiteral("ticketToken"), token);
res.insert(QStringLiteral("reservedTicket"), ticket);
}
res.insert(QStringLiteral("reservationFor"), resFor);
// associate the pass with the result, so we can find the pass again for display
if (!m_pass->passTypeIdentifier().isEmpty() && !m_pass->serialNumber().isEmpty()) {
res.insert(QStringLiteral("pkpassPassTypeIdentifier"), m_pass->passTypeIdentifier());
res.insert(QStringLiteral("pkpassSerialNumber"), m_pass->serialNumber());
}
m_result[0] = res;
}
void ExtractorEnginePrivate::extractBoardingPass(QJsonObject &resFor)
{
// "relevantDate" is the best guess for the boarding time
if (m_pass->relevantDate().isValid() && !resFor.contains(QLatin1String("boardingTime"))) {
resFor.insert(QStringLiteral("boardingTime"), m_pass->relevantDate().toString(Qt::ISODate));
}
// look for common field names containing the boarding time, if we still have no idea
if (!resFor.contains(QLatin1String("boardingTime"))) {
for (const auto &field : m_pass->fields()) {
if (!field.key().contains(QLatin1String("boarding"), Qt::CaseInsensitive)) {
continue;
}
const auto time = QTime::fromString(field.value().toString());
if (time.isValid()) {
// this misses date, but the postprocessor will fill that in
resFor.insert(QStringLiteral("boardingTime"), QDateTime(QDate(1, 1, 1), time).toString(Qt::ISODate));
break;
}
}
}
// location is the best guess for the departure airport geo coordinates
auto depAirport = resFor.value(QLatin1String("departureAirport")).toObject();
if (depAirport.isEmpty()) {
depAirport.insert(QStringLiteral("@type"), QLatin1String("Airport"));
}
auto depGeo = depAirport.value(QLatin1String("geo")).toObject();
if (m_pass->locations().size() == 1 && depGeo.isEmpty()) {
const auto loc = m_pass->locations().at(0);
depGeo.insert(QStringLiteral("@type"), QLatin1String("GeoCoordinates"));
depGeo.insert(QStringLiteral("latitude"), loc.latitude());
depGeo.insert(QStringLiteral("longitude"), loc.longitude());
depAirport.insert(QStringLiteral("geo"), depGeo);
resFor.insert(QStringLiteral("departureAirport"), depAirport);
}
// organizationName is the best guess for airline name
auto airline = resFor.value(QLatin1String("airline")).toObject();
if (airline.isEmpty()) {
airline.insert(QStringLiteral("@type"), QLatin1String("Airline"));
}
if (!airline.contains(QLatin1String("name"))) {
airline.insert(QStringLiteral("name"), m_pass->organizationName());
}
resFor.insert(QStringLiteral("airline"), airline);
}
void ExtractorEnginePrivate::extractEventTicketPass(QJsonObject &resFor)
{
if (!resFor.contains(QLatin1String("name"))) {
resFor.insert(QStringLiteral("name"), m_pass->description());
}
// "relevantDate" is the best guess for the start time
if (m_pass->relevantDate().isValid() && !resFor.contains(QLatin1String("startDate"))) {
resFor.insert(QStringLiteral("startDate"), m_pass->relevantDate().toString(Qt::ISODate));
}
// location is the best guess for the venue
auto venue = resFor.value(QLatin1String("location")).toObject();
if (venue.isEmpty()) {
venue.insert(QStringLiteral("@type"), QLatin1String("Place"));
}
auto geo = venue.value(QLatin1String("geo")).toObject();
if (!m_pass->locations().isEmpty() && geo.isEmpty()) {
const auto loc = m_pass->locations().at(0);
geo.insert(QStringLiteral("@type"), QLatin1String("GeoCoordinates"));
geo.insert(QStringLiteral("latitude"), loc.latitude());
geo.insert(QStringLiteral("longitude"), loc.longitude());
venue.insert(QStringLiteral("geo"), geo);
venue.insert(QStringLiteral("name"), loc.relevantText());
resFor.insert(QStringLiteral("location"), venue);
}
}
/*
Copyright (c) 2019 Volker Krause <vkrause@kde.org>
This library 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 library 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 Library General Public License
along with this library; see the file COPYING.LIB. If not, write to the
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301, USA.
*/
#include "genericpkpassextractor_p.h"
#include <KPkPass/Barcode>
#include <KPkPass/BoardingPass>
#include <KPkPass/Location>
#include <KPkPass/Field>
#include <QJsonObject>
#include <QTime>
#include <QVariant>
using namespace KItinerary;
static void extractBoardingPass(KPkPass::Pass *pass, QJsonObject &resFor)
{
// "relevantDate" is the best guess for the boarding time
if (pass->relevantDate().isValid() && !resFor.contains(QLatin1String("boardingTime"))) {
resFor.insert(QStringLiteral("boardingTime"), pass->relevantDate().toString(Qt::ISODate));
}
// look for common field names containing the boarding time, if we still have no idea
if (!resFor.contains(QLatin1String("boardingTime"))) {
for (const auto &field : pass->fields()) {
if (!field.key().contains(QLatin1String("boarding"), Qt::CaseInsensitive)) {
continue;
}
const auto time = QTime::fromString(field.value().toString());
if (time.isValid()) {
// this misses date, but the postprocessor will fill that in
resFor.insert(QStringLiteral("boardingTime"), QDateTime(QDate(1, 1, 1), time).toString(Qt::ISODate));
break;
}
}
}
// location is the best guess for the departure airport geo coordinates
auto depAirport = resFor.value(QLatin1String("departureAirport")).toObject();
if (depAirport.isEmpty()) {
depAirport.insert(QStringLiteral("@type"), QLatin1String("Airport"));
}
auto depGeo = depAirport.value(QLatin1String("geo")).toObject();
if (pass->locations().size() == 1 && depGeo.isEmpty()) {
const auto loc = pass->locations().at(0);
depGeo.insert(QStringLiteral("@type"), QLatin1String("GeoCoordinates"));
depGeo.insert(QStringLiteral("latitude"), loc.latitude());
depGeo.insert(QStringLiteral("longitude"), loc.longitude());
depAirport.insert(QStringLiteral("geo"), depGeo);
resFor.insert(QStringLiteral("departureAirport"), depAirport);
}
// organizationName is the best guess for airline name
auto airline = resFor.value(QLatin1String("airline")).toObject();
if (airline.isEmpty()) {
airline.insert(QStringLiteral("@type"), QLatin1String("Airline"));
}
if (!airline.contains(QLatin1String("name"))) {
airline.insert(QStringLiteral("name"), pass->organizationName());
}
resFor.insert(QStringLiteral("airline"), airline);
}
static void extractEventTicketPass(KPkPass::Pass *pass, QJsonObject &resFor)
{
if (!resFor.contains(QLatin1String("name"))) {
resFor.insert(QStringLiteral("name"), pass->description());
}
// "relevantDate" is the best guess for the start time
if (pass->relevantDate().isValid() && !resFor.contains(QLatin1String("startDate"))) {
resFor.insert(QStringLiteral("startDate"), pass->relevantDate().toString(Qt::ISODate));
}
// location is the best guess for the venue
auto venue = resFor.value(QLatin1String("location")).toObject();
if (venue.isEmpty()) {
venue.insert(QStringLiteral("@type"), QLatin1String("Place"));
}
auto geo = venue.value(QLatin1String("geo")).toObject();
if (!pass->locations().isEmpty() && geo.isEmpty()) {
const auto loc = pass->locations().at(0);
geo.insert(QStringLiteral("@type"), QLatin1String("GeoCoordinates"));
geo.insert(QStringLiteral("latitude"), loc.latitude());
geo.insert(QStringLiteral("longitude"), loc.longitude());
venue.insert(QStringLiteral("geo"), geo);
venue.insert(QStringLiteral("name"), loc.relevantText());
resFor.insert(QStringLiteral("location"), venue);
}
}
void GenericPkPassExtractor::extract(KPkPass::Pass *pass, QJsonObject &result)
{
if (result.isEmpty()) { // no previous extractor ran, so we need to create the top-level element ourselves
QJsonObject resFor;
if (auto boardingPass = qobject_cast<KPkPass::BoardingPass*>(pass)) {
switch (boardingPass->transitType()) {
case KPkPass::BoardingPass::Air:
result.insert(QStringLiteral("@type"), QLatin1String("FlightReservation"));
resFor.insert(QStringLiteral("@type"), QLatin1String("Flight"));
break;
// TODO expand once we have test files for train tickets
default:
return;
}
} else {
switch (pass->type()) {
case KPkPass::Pass::EventTicket:
result.insert(QStringLiteral("@type"), QLatin1String("EventReservation"));
resFor.insert(QStringLiteral("@type"), QLatin1String("Event"));
break;
default:
return;
}
}
result.insert(QStringLiteral("reservationFor"), resFor);
}
// extract structured data from a pkpass, if the extractor script hasn't done so already
auto resFor = result.value(QLatin1String("reservationFor")).toObject();
switch (pass->type()) {
case KPkPass::Pass::BoardingPass:
extractBoardingPass(pass, resFor);
break;
case KPkPass::Pass::EventTicket:
extractEventTicketPass(pass, resFor);
break;
default:
return;
}
// barcode contains the ticket token
if (!pass->barcodes().isEmpty() && !result.contains(QLatin1String("reservedTicket"))) {
const auto barcode = pass->barcodes().at(0);
QString token;
switch (barcode.format()) {
case KPkPass::Barcode::QR:
token += QLatin1String("qrCode:");
break;
case KPkPass::Barcode::Aztec:
token += QLatin1String("aztecCode:");
break;
default:
break;
}
token += barcode.message();
QJsonObject ticket;
ticket.insert(QStringLiteral("@type"), QLatin1String("Ticket"));
ticket.insert(QStringLiteral("ticketToken"), token);
result.insert(QStringLiteral("reservedTicket"), ticket);
}
result.insert(QStringLiteral("reservationFor"), resFor);
// associate the pass with the result, so we can find the pass again for display
if (!pass->passTypeIdentifier().isEmpty() && !pass->serialNumber().isEmpty()) {
result.insert(QStringLiteral("pkpassPassTypeIdentifier"), pass->passTypeIdentifier());
result.insert(QStringLiteral("pkpassSerialNumber"), pass->serialNumber());
}
}
/*
Copyright (c) 2019 Volker Krause <vkrause@kde.org>
This library 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 library 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 Library General Public License
along with this library; see the file COPYING.LIB. If not, write to the
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301, USA.
*/
#ifndef KITINERARY_GENERICPKPASSEXTRACTOR_P_H
#define KITINERARY_GENERICPKPASSEXTRACTOR_P_H
namespace KPkPass {
class Pass;
}
class QJsonObject;
namespace KItinerary {
/** Generic extractor for PkPass files. */
namespace GenericPkPassExtractor
{
void extract(KPkPass::Pass *pass, QJsonObject &result);
}
}
#endif // KITINERARY_GENERICPKPASSEXTRACTOR_P_H
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment