Commit 3c1ebbfc authored by Volker Krause's avatar Volker Krause
Browse files

Move private API of KPkPass::Pass to private class

parent 735722a8
......@@ -17,6 +17,7 @@
#include "barcode.h"
#include "pass.h"
#include "pass_p.h"
#include <QJsonObject>
......@@ -28,7 +29,7 @@ Barcode::Barcode()
Barcode::Barcode(const QJsonObject &obj, const Pass *pass)
{
m_altText = pass->message(obj.value(QLatin1String("altText")).toString());
m_altText = pass->d->message(obj.value(QLatin1String("altText")).toString());
const auto format = obj.value(QLatin1String("format")).toString();
if (format == QLatin1String("PKBarcodeFormatQR"))
m_format = QR;
......
......@@ -18,6 +18,7 @@
*/
#include "boardingpass.h"
#include "pass_p.h"
using namespace KPkPass;
......@@ -28,7 +29,7 @@ BoardingPass::BoardingPass(QObject *parent)
BoardingPass::TransitType BoardingPass::transitType() const
{
const auto t = passData().value(QLatin1String("transitType")).toString();
const auto t = d->passData().value(QLatin1String("transitType")).toString();
if (t == QLatin1String("PKTransitTypeAir")) {
return Air;
} else if (t == QLatin1String("PKTransitTypeBoat")) {
......
......@@ -19,6 +19,7 @@
#include "field.h"
#include "pass.h"
#include "pass_p.h"
#include <QJsonObject>
......@@ -57,7 +58,7 @@ QString Field::key() const
QString Field::label() const
{
if (d->pass) {
return d->pass->message(d->obj.value(QLatin1String("label")).toString());
return d->pass->d->message(d->obj.value(QLatin1String("label")).toString());
}
return {};
}
......@@ -67,9 +68,9 @@ QVariant Field::value() const
if (!d->pass) {
return {};
}
auto v = d->pass->message(d->obj.value(QLatin1String("attributedValue")).toString());
auto v = d->pass->d->message(d->obj.value(QLatin1String("attributedValue")).toString());
if (v.isEmpty()) {
v = d->pass->message(d->obj.value(QLatin1String("value")).toString());
v = d->pass->d->message(d->obj.value(QLatin1String("value")).toString());
}
// TODO number and date/time detection
return v;
......@@ -86,7 +87,7 @@ QString Field::changeMessage() const
if (!d->pass) {
return {};
}
auto msg = d->pass->message(d->obj.value(QLatin1String("changeMessage")).toString());
auto msg = d->pass->d->message(d->obj.value(QLatin1String("changeMessage")).toString());
msg = msg.replace(QLatin1String("%@"), valueDisplayString());
return msg;
}
......@@ -32,6 +32,7 @@ class QJsonObject;
namespace KPkPass {
class Pass;
class PassPrivate;
class FieldPrivate;
/** Field element in a KPkPass::Pass.
......@@ -73,7 +74,7 @@ public:
// TODO add textAlignment property
private:
friend class Pass;
friend class PassPrivate;
explicit Field(const QJsonObject &obj, const Pass *pass);
std::shared_ptr<FieldPrivate> d;
......
......@@ -18,6 +18,7 @@
*/
#include "pass.h"
#include "pass_p.h"
#include "boardingpass.h"
#include "logging.h"
......@@ -36,45 +37,189 @@
using namespace KPkPass;
namespace KPkPass {
class PassPrivate {
public:
std::unique_ptr<QIODevice> buffer;
std::unique_ptr<KZip> zip;
QJsonObject passObj;
QHash<QString, QString> messages;
QString passType;
};
QJsonObject PassPrivate::data() const
{
return passObj;
}
Pass::Pass (const QString &passType, QObject *parent)
: QObject(parent)
, d(new PassPrivate)
QJsonObject PassPrivate::passData() const
{
d->passType = passType;
return passObj.value(passType).toObject();
}
Pass::~Pass() = default;
QString PassPrivate::message(const QString &key) const
{
const auto it = messages.constFind(key);
if (it != messages.constEnd()) {
return it.value();
}
return key;
}
QJsonObject Pass::data() const
void PassPrivate::parse()
{
return d->passObj;
// find the message catalog
auto lang = QLocale().name();
auto idx = lang.indexOf(QLatin1Char('_'));
if (idx > 0) {
lang = lang.left(idx);
}
lang += QLatin1String(".lproj");
if (!parseMessages(lang)) {
parseMessages(QStringLiteral("en.lproj"));
}
}
QJsonObject Pass::passData() const
static int indexOfUnquoted(const QString &catalog, QLatin1Char c, int start)
{
return d->passObj.value(d->passType).toObject();
for (int i = start; i < catalog.size(); ++i) {
const QChar catalogChar = catalog.at(i);
if (catalogChar == c) {
return i;
}
if (catalogChar == QLatin1Char('\\')) {
++i;
}
}
return -1;
}
QString Pass::message(const QString &key) const
static QString unquote(const QStringRef &str)
{
const auto it = d->messages.constFind(key);
if (it != d->messages.constEnd()) {
return it.value();
QString res;
res.reserve(str.size());
for (int i = 0; i < str.size(); ++i) {
const auto c1 = str.at(i);
if (c1 == QLatin1Char('\\') && i < str.size() - 1) {
const auto c2 = str.at(i + 1);
if (c2 == QLatin1Char('r')) {
res.push_back(QLatin1Char('\r'));
} else if (c2 == QLatin1Char('n')) {
res.push_back(QLatin1Char('\n'));
} else if (c2 == QLatin1Char('\\')) {
res.push_back(c2);
} else {
res.push_back(c1);
res.push_back(c2);
}
++i;
} else {
res.push_back(c1);
}
}
return key;
return res;
}
bool PassPrivate::parseMessages(const QString &lang)
{
auto entry = zip->directory()->entry(lang);
if (!entry || !entry->isDirectory()) {
return false;
}
auto dir = static_cast<const KArchiveDirectory *>(entry);
auto file = dir->file(QStringLiteral("pass.strings"));
if (!file) {
return false;
}
std::unique_ptr<QIODevice> dev(file->createDevice());
const auto rawData = dev->readAll();
// this should be UTF-16BE, but that doesn't stop Eurowings from using UTF-8,
// so do a primitive auto-detection here. UTF-16's first byte would either be the BOM
// or \0.
QString catalog;
if (rawData.at(0) == '"') {
catalog = QString::fromUtf8(rawData);
} else {
auto codec = QTextCodec::codecForName("UTF-16BE");
catalog = codec->toUnicode(rawData);
}
int idx = 0;
while (idx < catalog.size()) {
// key
const auto keyBegin = indexOfUnquoted(catalog, QLatin1Char('"'), idx) + 1;
if (keyBegin < 1) {
break;
}
const auto keyEnd = indexOfUnquoted(catalog, QLatin1Char('"'), keyBegin);
if (keyEnd <= keyBegin) {
break;
}
// value
const auto valueBegin = indexOfUnquoted(catalog, QLatin1Char('"'), keyEnd + 2) + 1; // there's at least also the '='
if (valueBegin <= keyEnd) {
break;
}
const auto valueEnd = indexOfUnquoted(catalog, QLatin1Char('"'), valueBegin);
if (valueEnd <= valueBegin) {
break;
}
const auto key = catalog.mid(keyBegin, keyEnd - keyBegin);
const auto value = unquote(catalog.midRef(valueBegin, valueEnd - valueBegin));
messages.insert(key, value);
idx = valueEnd + 1; // there's at least the linebreak and/or a ';'
}
return !messages.isEmpty();
}
QVector<Field> PassPrivate::fields(const QLatin1String &fieldType, const Pass *q) const
{
const auto a = passData().value(fieldType).toArray();
QVector<Field> f;
f.reserve(a.size());
foreach (const auto &v, a) {
f.push_back(Field{v.toObject(), q});
}
return f;
}
Pass *PassPrivate::fromData(std::unique_ptr<QIODevice> device, QObject *parent)
{
std::unique_ptr<KZip> zip(new KZip(device.get()));
if (!zip->open(QIODevice::ReadOnly)) {
return nullptr;
}
// extract pass.json
auto file = zip->directory()->file(QStringLiteral("pass.json"));
if (!file) {
return nullptr;
}
std::unique_ptr<QIODevice> dev(file->createDevice());
const auto passObj = QJsonDocument::fromJson(dev->readAll()).object();
Pass *pass = nullptr;
if (passObj.contains(QLatin1String("boardingPass"))) {
pass = new KPkPass::BoardingPass(parent);
}
// TODO: coupon, eventTicket, storeCard, generic
else {
pass = new Pass (QStringLiteral("generic"), parent);
}
pass->d->buffer = std::move(device);
pass->d->zip = std::move(zip);
pass->d->passObj = passObj;
pass->d->parse();
return pass;
}
Pass::Pass (const QString &passType, QObject *parent)
: QObject(parent)
, d(new PassPrivate)
{
d->passType = passType;
}
Pass::~Pass() = default;
QString Pass::passTypeIdentifier() const
{
return d->passObj.value(QLatin1String("passTypeIdentifier")).toString();
......@@ -91,7 +236,7 @@ static const auto passTypesCount = sizeof(passTypes) / sizeof(passTypes[0]);
Pass::Type KPkPass::Pass::type() const
{
for (unsigned int i = 0; i < passTypesCount; ++i) {
if (data().contains(QLatin1String(passTypes[i]))) {
if (d->data().contains(QLatin1String(passTypes[i]))) {
return static_cast<Type>(i);
}
}
......@@ -131,7 +276,7 @@ QColor Pass::labelColor() const
QString Pass::logoText() const
{
return message(d->passObj.value(QLatin1String("logoText")).toString());
return d->message(d->passObj.value(QLatin1String("logoText")).toString());
}
QImage Pass::logo(unsigned int devicePixelRatio) const
......@@ -162,14 +307,14 @@ QVector<Barcode> Pass::barcodes() const
QVector<Barcode> codes;
// barcodes array
const auto a = data().value(QLatin1String("barcodes")).toArray();
const auto a = d->data().value(QLatin1String("barcodes")).toArray();
codes.reserve(a.size());
for (const auto &bc : a)
codes.push_back(Barcode(bc.toObject(), this));
// just a single barcode
if (codes.isEmpty()) {
const auto bc = data().value(QLatin1String("barcode")).toObject();
const auto bc = d->data().value(QLatin1String("barcode")).toObject();
if (!bc.isEmpty())
codes.push_back(Barcode(bc, this));
}
......@@ -179,27 +324,27 @@ QVector<Barcode> Pass::barcodes() const
QVector<Field> Pass::auxiliaryFields() const
{
return fields(QLatin1String("auxiliaryFields"));
return d->fields(QLatin1String("auxiliaryFields"), this);
}
QVector<Field> Pass::backFields() const
{
return fields(QLatin1String("backFields"));
return d->fields(QLatin1String("backFields"), this);
}
QVector<Field> Pass::headerFields() const
{
return fields(QLatin1String("headerFields"));
return d->fields(QLatin1String("headerFields"), this);
}
QVector<Field> Pass::primaryFields() const
{
return fields(QLatin1String("primaryFields"));
return d->fields(QLatin1String("primaryFields"), this);
}
QVector<Field> Pass::secondaryFields() const
{
return fields(QLatin1String("secondaryFields"));
return d->fields(QLatin1String("secondaryFields"), this);
}
Pass *Pass::fromData(const QByteArray &data, QObject *parent)
......@@ -207,173 +352,19 @@ Pass *Pass::fromData(const QByteArray &data, QObject *parent)
std::unique_ptr<QBuffer> buffer(new QBuffer);
buffer->setData(data);
buffer->open(QBuffer::ReadOnly);
return fromData(std::move(buffer), parent);
}
Pass *Pass::fromData(std::unique_ptr<QIODevice> device, QObject *parent)
{
std::unique_ptr<KZip> zip(new KZip(device.get()));
if (!zip->open(QIODevice::ReadOnly)) {
return nullptr;
}
// extract pass.json
auto file = zip->directory()->file(QStringLiteral("pass.json"));
if (!file) {
return nullptr;
}
std::unique_ptr<QIODevice> dev(file->createDevice());
const auto passObj = QJsonDocument::fromJson(dev->readAll()).object();
Pass *pass = nullptr;
if (passObj.contains(QLatin1String("boardingPass"))) {
pass = new KPkPass::BoardingPass(parent);
}
// TODO: coupon, eventTicket, storeCard, generic
else {
pass = new Pass (QStringLiteral("generic"), parent);
}
pass->d->buffer = std::move(device);
pass->d->zip = std::move(zip);
pass->d->passObj = passObj;
pass->parse();
return pass;
return PassPrivate::fromData(std::move(buffer), parent);
}
Pass *Pass::fromFile(const QString &fileName, QObject *parent)
{
std::unique_ptr<QFile> file(new QFile(fileName));
if (file->open(QFile::ReadOnly)) {
return fromData(std::move(file), parent);
return PassPrivate::fromData(std::move(file), parent);
}
qCWarning(Log) << "Failed to open" << fileName << ":" << file->errorString();
return nullptr;
}
void Pass::parse()
{
// find the message catalog
auto lang = QLocale().name();
auto idx = lang.indexOf(QLatin1Char('_'));
if (idx > 0) {
lang = lang.left(idx);
}
lang += QLatin1String(".lproj");
if (!parseMessages(lang)) {
parseMessages(QStringLiteral("en.lproj"));
}
}
static int indexOfUnquoted(const QString &catalog, QLatin1Char c, int start)
{
for (int i = start; i < catalog.size(); ++i) {
const QChar catalogChar = catalog.at(i);
if (catalogChar == c) {
return i;
}
if (catalogChar == QLatin1Char('\\')) {
++i;
}
}
return -1;
}
static QString unquote(const QStringRef &str)
{
QString res;
res.reserve(str.size());
for (int i = 0; i < str.size(); ++i) {
const auto c1 = str.at(i);
if (c1 == QLatin1Char('\\') && i < str.size() - 1) {
const auto c2 = str.at(i + 1);
if (c2 == QLatin1Char('r')) {
res.push_back(QLatin1Char('\r'));
} else if (c2 == QLatin1Char('n')) {
res.push_back(QLatin1Char('\n'));
} else if (c2 == QLatin1Char('\\')) {
res.push_back(c2);
} else {
res.push_back(c1);
res.push_back(c2);
}
++i;
} else {
res.push_back(c1);
}
}
return res;
}
bool Pass::parseMessages(const QString &lang)
{
auto entry = d->zip->directory()->entry(lang);
if (!entry || !entry->isDirectory()) {
return false;
}
auto dir = static_cast<const KArchiveDirectory *>(entry);
auto file = dir->file(QStringLiteral("pass.strings"));
if (!file) {
return false;
}
std::unique_ptr<QIODevice> dev(file->createDevice());
const auto rawData = dev->readAll();
// this should be UTF-16BE, but that doesn't stop Eurowings from using UTF-8,
// so do a primitive auto-detection here. UTF-16's first byte would either be the BOM
// or \0.
QString catalog;
if (rawData.at(0) == '"') {
catalog = QString::fromUtf8(rawData);
} else {
auto codec = QTextCodec::codecForName("UTF-16BE");
catalog = codec->toUnicode(rawData);
}
int idx = 0;
while (idx < catalog.size()) {
// key
const auto keyBegin = indexOfUnquoted(catalog, QLatin1Char('"'), idx) + 1;
if (keyBegin < 1) {
break;
}
const auto keyEnd = indexOfUnquoted(catalog, QLatin1Char('"'), keyBegin);
if (keyEnd <= keyBegin) {
break;
}
// value
const auto valueBegin = indexOfUnquoted(catalog, QLatin1Char('"'), keyEnd + 2) + 1; // there's at least also the '='
if (valueBegin <= keyEnd) {
break;
}
const auto valueEnd = indexOfUnquoted(catalog, QLatin1Char('"'), valueBegin);
if (valueEnd <= valueBegin) {
break;
}
const auto key = catalog.mid(keyBegin, keyEnd - keyBegin);
const auto value = unquote(catalog.midRef(valueBegin, valueEnd - valueBegin));
d->messages.insert(key, value);
idx = valueEnd + 1; // there's at least the linebreak and/or a ';'
}
return !d->messages.isEmpty();
}
QVector<Field> Pass::fields(const QLatin1String &fieldType) const
{
const auto a = passData().value(fieldType).toArray();
QVector<Field> f;
f.reserve(a.size());
foreach (const auto &v, a) {
f.push_back(Field{v.toObject(), this});
}
return f;
}
template <typename T>
static QVariantList toVariantList(const QVector<T> &elems)
{
......
......@@ -84,13 +84,6 @@ public:
Q_ENUM(Type)
Type type() const;
/** Content of the pass.json file. */
QJsonObject data() const;
/** The pass data structure of the pass.json file. */
QJsonObject passData() const;
/** Localized message for the given key. */
QString message(const QString &key) const;
QString passTypeIdentifier() const;
QString serialNumber() const;
......@@ -117,15 +110,14 @@ public:
protected:
///@cond internal
friend class Barcode;
friend class Field;
friend class PassPrivate;
explicit Pass (const QString &passType, QObject *parent = nullptr);
std::unique_ptr<PassPrivate> d;
///@endcond
private:
static Pass *fromData(std::unique_ptr<QIODevice> device, QObject *parent);
void parse();
bool parseMessages(const QString &lang);
QVector<Field> fields(const QLatin1String &fieldType) const;
QVariantList auxiliaryFieldsVariant() const;
QVariantList backFieldsVariant() const;
QVariantList headerFieldsVariant() const;
......@@ -133,7 +125,6 @@ private:
QVariantList secondaryFieldsVariant() const;
QVariantList barcodesVariant() const;
std::unique_ptr<PassPrivate> d;
};
}
......
/*
Copyright (c) 2018 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 KPKPASS_PASS_P_H
#define KPKPASS_PASS_P_H