diff --git a/CMakeLists.txt b/CMakeLists.txt --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -63,6 +63,7 @@ find_package(KF5KIO ${KF5_VERSION} CONFIG REQUIRED) find_package(KF5IconThemes ${KF5_VERSION} CONFIG REQUIRED) find_package(KF5Parts ${KF5_VERSION} CONFIG REQUIRED) +find_package(KF5Prison ${KF5_VERSION} CONFIG REQUIRED) set(MAILCOMMON_LIB_VERSION "5.6.43") diff --git a/plugins/messageviewer/bodypartformatter/CMakeLists.txt b/plugins/messageviewer/bodypartformatter/CMakeLists.txt --- a/plugins/messageviewer/bodypartformatter/CMakeLists.txt +++ b/plugins/messageviewer/bodypartformatter/CMakeLists.txt @@ -1,7 +1,8 @@ add_subdirectory(autotests) add_subdirectory(ms-tnef) -add_subdirectory(vcard) +add_subdirectory(vcard) add_subdirectory(highlighter) add_subdirectory(calendar) add_subdirectory(gnupgwks) +add_subdirectory(pkpass) diff --git a/plugins/messageviewer/bodypartformatter/autotests/data/boarding-pass-pkpass.mbox b/plugins/messageviewer/bodypartformatter/autotests/data/boarding-pass-pkpass.mbox new file mode 100644 --- /dev/null +++ b/plugins/messageviewer/bodypartformatter/autotests/data/boarding-pass-pkpass.mbox @@ -0,0 +1,87 @@ +From: KDE Air +Subject: Here is your boarding pass +Date: Sat, 21 Oct 2017 12:18:43 +0200 +Message-ID: <25583002.JFqCOaE6Wz@vkpc5> +MIME-Version: 1.0 +Content-Type: multipart/mixed; boundary="nextPart2470803.6UQdJc0X7t" +Content-Transfer-Encoding: 7Bit + +This is a multi-part message in MIME format. + +--nextPart2470803.6UQdJc0X7t +Content-Transfer-Encoding: 7Bit +Content-Type: text/plain; charset="us-ascii" + +Have a nice flight! +--nextPart2470803.6UQdJc0X7t +Content-Disposition: attachment; filename="boardingpass.pkpass" +Content-Transfer-Encoding: base64 +Content-Type: application/vnd.apple.pkpass; name="boardingpass.pkpass" + +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 + + +--nextPart2470803.6UQdJc0X7t-- diff --git a/plugins/messageviewer/bodypartformatter/autotests/data/boarding-pass-pkpass.mbox.html b/plugins/messageviewer/bodypartformatter/autotests/data/boarding-pass-pkpass.mbox.html new file mode 100644 --- /dev/null +++ b/plugins/messageviewer/bodypartformatter/autotests/data/boarding-pass-pkpass.mbox.html @@ -0,0 +1,105 @@ + + + + +
+ + + + diff --git a/plugins/messageviewer/bodypartformatter/pkpass/CMakeLists.txt b/plugins/messageviewer/bodypartformatter/pkpass/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/plugins/messageviewer/bodypartformatter/pkpass/CMakeLists.txt @@ -0,0 +1,16 @@ +set(pkpass_srcs + pkpassfield.cpp + pkpassfile.cpp + pkpassboardingpass.cpp + pkpass_plugin.cpp +) +qt5_add_resources(pkpass_srcs templates.qrc) + +add_library(messageviewer_bodypartformatter_pkpass MODULE ${pkpass_srcs}) +target_link_libraries(messageviewer_bodypartformatter_pkpass + KF5::Archive + KF5::Prison + KF5::MessageViewer + Grantlee5::Templates +) +install(TARGETS messageviewer_bodypartformatter_pkpass DESTINATION ${KDE_INSTALL_PLUGINDIR}/messageviewer/bodypartformatter) diff --git a/plugins/messageviewer/bodypartformatter/pkpass/pkpass_plugin.cpp b/plugins/messageviewer/bodypartformatter/pkpass/pkpass_plugin.cpp new file mode 100644 --- /dev/null +++ b/plugins/messageviewer/bodypartformatter/pkpass/pkpass_plugin.cpp @@ -0,0 +1,110 @@ +/* + Copyright (c) 2017 Volker Krause + + 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 "pkpassfile.h" +#include "pkpassboardingpass.h" + +#include +#include +#include + +#include +#include + +#include +#include + +#include +#include +#include +#include + +// Grantlee has no Q_GADGET support yet +GRANTLEE_BEGIN_LOOKUP(PkPassField) + const auto idx = PkPassField::staticMetaObject.indexOfProperty(property.toUtf8().constData()); + if (idx < 0) + return {}; + const auto mp = PkPassField::staticMetaObject.property(idx); + return mp.readOnGadget(&object); +GRANTLEE_END_LOOKUP + +namespace { +class Formatter : public MessageViewer::MessagePartRendererBase +{ +public: + bool render(const MimeTreeParser::MessagePartPtr &msgPart, MimeTreeParser::HtmlWriter *htmlWriter, MessageViewer::RenderContext *context) const override + { + Q_UNUSED(context); + auto mp = msgPart.dynamicCast(); + if (!mp || mp->isHidden()) { + return false; + } + + std::unique_ptr pass(PkPassFile::fromData(msgPart->content()->decodedContent())); + if (!qobject_cast(pass.get())) + return false; // only boarding passes implemented so far + + const auto dir = mp->nodeHelper()->createTempDir(QStringLiteral("pkpass")); + const auto logo = pass->logo(); + if (!logo.isNull()) { + const auto fileName = dir + QStringLiteral("/logo.png"); + logo.save(fileName); + pass->setProperty("logoUrl", QUrl::fromLocalFile(fileName)); + mp->nodeHelper()->addTempFile(fileName); + } + + const auto barcode = pass->barcode(); + if (!barcode.isNull()) { + const auto fileName = dir + QStringLiteral("/barcode.png"); + barcode.save(fileName); + pass->setProperty("barcodeUrl", QUrl::fromLocalFile(fileName)); + mp->nodeHelper()->addTempFile(fileName); + } + + auto c = MessageViewer::MessagePartRendererManager::self()->createContext(); + c.insert(QStringLiteral("block"), mp.data()); + c.insert(QStringLiteral("pass"), pass.get()); + auto t = MessageViewer::MessagePartRendererManager::self()->loadByName(QStringLiteral(":/org.kde.messageviewer/pkpass/boardingpass.html")); + Grantlee::OutputStream s(htmlWriter->stream()); + t->render(&s, &c); + return true; + } +}; + +class Plugin : public QObject, public MessageViewer::MessagePartRenderPlugin +{ + Q_OBJECT + Q_INTERFACES(MessageViewer::MessagePartRenderPlugin) + Q_PLUGIN_METADATA(IID "com.kde.messageviewer.bodypartformatter" FILE "pkpass_plugin.json") +public: + explicit Plugin(QObject *parent = nullptr) + : QObject(parent) + { + Grantlee::registerMetaType(); + } + + MessageViewer::MessagePartRendererBase* renderer(int index) override + { + return index == 0 ? new Formatter() : nullptr; + } +}; + +} + +#include "pkpass_plugin.moc" diff --git a/plugins/messageviewer/bodypartformatter/pkpass/pkpass_plugin.json b/plugins/messageviewer/bodypartformatter/pkpass/pkpass_plugin.json new file mode 100644 --- /dev/null +++ b/plugins/messageviewer/bodypartformatter/pkpass/pkpass_plugin.json @@ -0,0 +1,3 @@ +{ + "renderer": [ { "type": "MimeTreeParser::AttachmentMessagePart", "mimetype": "application/vnd.apple.pkpass" } ] +} diff --git a/plugins/messageviewer/bodypartformatter/pkpass/pkpassboardingpass.h b/plugins/messageviewer/bodypartformatter/pkpass/pkpassboardingpass.h new file mode 100644 --- /dev/null +++ b/plugins/messageviewer/bodypartformatter/pkpass/pkpassboardingpass.h @@ -0,0 +1,46 @@ +/* + Copyright (c) 2017 Volker Krause + + 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 PKPASSBOARDINGPASS_H +#define PKPASSBOARDINGPASS_H + +#include "pkpassfile.h" + +/** Boarding pass */ +class PkPassBoardingPass : public PkPassFile +{ + Q_OBJECT + Q_PROPERTY(TransitType transitType READ transitType) +public: + enum TransitType { + Air, + Boat, + Bus, + Train, + Generic + }; + Q_ENUM(TransitType) + + PkPassBoardingPass(QObject *parent = nullptr); + ~PkPassBoardingPass(); + + TransitType transitType() const; +}; + +#endif // PKPASSBOARDINGPASS_H diff --git a/plugins/messageviewer/bodypartformatter/pkpass/pkpassboardingpass.cpp b/plugins/messageviewer/bodypartformatter/pkpass/pkpassboardingpass.cpp new file mode 100644 --- /dev/null +++ b/plugins/messageviewer/bodypartformatter/pkpass/pkpassboardingpass.cpp @@ -0,0 +1,43 @@ +/* + Copyright (c) 2017 Volker Krause + + 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 "pkpassboardingpass.h" + +#include + +PkPassBoardingPass::PkPassBoardingPass(QObject* parent) + : PkPassFile(QStringLiteral("boardingPass"), parent) +{ +} + +PkPassBoardingPass::~PkPassBoardingPass() = default; + +PkPassBoardingPass::TransitType PkPassBoardingPass::transitType() const +{ + const auto t = passData().value(QLatin1String("transitType")).toString(); + if (t == QLatin1String("PKTransitTypeAir")) + return Air; + else if (t == QLatin1String("PKTransitTypeBoat")) + return Boat; + else if (t == QLatin1String("PKTransitTypeBus")) + return Bus; + else if (t == QLatin1String("PKTransitTypeTrain")) + return Train; + return Generic; +} diff --git a/plugins/messageviewer/bodypartformatter/pkpass/pkpassfield.h b/plugins/messageviewer/bodypartformatter/pkpass/pkpassfield.h new file mode 100644 --- /dev/null +++ b/plugins/messageviewer/bodypartformatter/pkpass/pkpassfield.h @@ -0,0 +1,50 @@ +/* + Copyright (c) 2017 Volker Krause + + 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 PKPASSFIELD_H +#define PKPASSFIELD_H + +#include +#include + +class PkPassFile; +class QJsonObject; + +/** Field element in a PkPassFile. */ +class PkPassField +{ + Q_GADGET + Q_PROPERTY(QString label READ label CONSTANT) + Q_PROPERTY(QString value READ value CONSTANT) +public: + PkPassField(); + explicit PkPassField(const QJsonObject &obj, const PkPassFile *file); + ~PkPassField(); + + QString label() const; + QString value() const; + +private: + QString m_label; + QString m_value; +}; + +Q_DECLARE_METATYPE(PkPassField) + +#endif // PKPASSFIELD_H diff --git a/plugins/messageviewer/bodypartformatter/pkpass/pkpassfield.cpp b/plugins/messageviewer/bodypartformatter/pkpass/pkpassfield.cpp new file mode 100644 --- /dev/null +++ b/plugins/messageviewer/bodypartformatter/pkpass/pkpassfield.cpp @@ -0,0 +1,43 @@ +/* + Copyright (c) 2017 Volker Krause + + 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 "pkpassfield.h" +#include "pkpassfile.h" + +#include + +PkPassField::PkPassField() = default; + +PkPassField::PkPassField(const QJsonObject& obj, const PkPassFile *file) +{ + m_label = file->message(obj.value(QLatin1String("label")).toString()); + m_value = file->message(obj.value(QLatin1String("value")).toString()); +} + +PkPassField::~PkPassField() = default; + +QString PkPassField::label() const +{ + return m_label; +} + +QString PkPassField::value() const +{ + return m_value; +} diff --git a/plugins/messageviewer/bodypartformatter/pkpass/pkpassfile.h b/plugins/messageviewer/bodypartformatter/pkpass/pkpassfile.h new file mode 100644 --- /dev/null +++ b/plugins/messageviewer/bodypartformatter/pkpass/pkpassfile.h @@ -0,0 +1,97 @@ +/* + Copyright (c) 2017 Volker Krause + + 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 PKPASSFILE_H +#define PKPASSFILE_H + +#include "pkpassfield.h" + +#include +#include +#include +#include +#include + +#include + +class KZip; +class QBuffer; +class QColor; +class QLatin1String; +class QString; + +/** Base class for a pkpass file. */ +class PkPassFile : public QObject +{ + Q_OBJECT + Q_PROPERTY(QString backgroundColor READ backgroundColor CONSTANT) + Q_PROPERTY(QString foregroundColor READ foregroundColor CONSTANT) + Q_PROPERTY(QString labelColor READ labelColor CONSTANT) + Q_PROPERTY(QString logoText READ logoText CONSTANT) + Q_PROPERTY(QString barcodeAltText READ barcodeAltText CONSTANT) + + Q_PROPERTY(QVector auxiliaryFields READ auxiliaryFields CONSTANT) + Q_PROPERTY(QVector backFields READ backFields CONSTANT) + Q_PROPERTY(QVector headerFields READ headerFields CONSTANT) + Q_PROPERTY(QVector primaryFields READ primaryFields CONSTANT) + Q_PROPERTY(QVector secondaryFields READ secondaryFields CONSTANT) + +public: + ~PkPassFile(); + + /** 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 backgroundColor() const; + QString foregroundColor() const; + QString labelColor() const; + QString logoText() const; + QImage logo() const; + QImage barcode() const; + QString barcodeAltText() const; + + QVector auxiliaryFields() const; + QVector backFields() const; + QVector headerFields() const; + QVector primaryFields() const; + QVector secondaryFields() const; + + /** Create a appropriate sub-class based on the pkpass file type. */ + static PkPassFile* fromData(const QByteArray &data, QObject *parent = nullptr); + +protected: + explicit PkPassFile(const QString &passType, QObject *parent = nullptr); + +private: + void parse(); + bool parseMessages(const QString &lang); + QVector fields(const QLatin1String &fieldType) const; + + std::unique_ptr m_buffer; + std::unique_ptr m_zip; + QJsonObject m_passObj; + QHash m_messages; + QString m_passType; +}; + +#endif // PKPASSFILE_H diff --git a/plugins/messageviewer/bodypartformatter/pkpass/pkpassfile.cpp b/plugins/messageviewer/bodypartformatter/pkpass/pkpassfile.cpp new file mode 100644 --- /dev/null +++ b/plugins/messageviewer/bodypartformatter/pkpass/pkpassfile.cpp @@ -0,0 +1,279 @@ +/* + Copyright (c) 2017 Volker Krause + + 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 "pkpassfile.h" +#include "pkpassboardingpass.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +PkPassFile::PkPassFile(const QString &passType, QObject* parent) + : QObject (parent) + , m_passType(passType) +{ +} + +PkPassFile::~PkPassFile() = default; + +QJsonObject PkPassFile::data() const +{ + return m_passObj; +} + +QJsonObject PkPassFile::passData() const +{ + return m_passObj.value(m_passType).toObject(); +} + +QString PkPassFile::message(const QString& key) const +{ + const auto it = m_messages.constFind(key); + if (it != m_messages.constEnd()) + return it.value(); + return key; +} + +QString PkPassFile::backgroundColor() const +{ + return m_passObj.value(QLatin1String("backgroundColor")).toString(); +} + +QString PkPassFile::foregroundColor() const +{ + return m_passObj.value(QLatin1String("foregroundColor")).toString(); +} + +QString PkPassFile::labelColor() const +{ + const auto c = m_passObj.value(QLatin1String("labelColor")).toString(); + if (!c.isEmpty()) + return c; + return foregroundColor(); +} + +QString PkPassFile::logoText() const +{ + return message(m_passObj.value(QLatin1String("logoText")).toString()); +} + +QImage PkPassFile::logo() const +{ + auto file = m_zip->directory()->file(QStringLiteral("logo.png")); + if (!file) + return {}; + std::unique_ptr dev(file->createDevice()); + return QImage::fromData(dev->readAll()); +} + +QImage PkPassFile::barcode() const +{ + const auto barcodeData = data().value(QLatin1String("barcode")).toObject(); + const auto formatName = barcodeData.value(QLatin1String("format")).toString(); + const auto msg = barcodeData.value(QLatin1String("message")).toString(); + // TODO: consider messageEncoding, once Prison supports that + if (formatName.isEmpty() || msg.isEmpty()) + return {}; + + std::unique_ptr code; + if (formatName == QLatin1String("PKBarcodeFormatQR")) + code.reset(Prison::createBarcode(Prison::QRCode)); + else if (formatName == QLatin1String("PKBarcodeFormatPDF417")) + {} // TODO + else if (formatName == QLatin1String("PKBarcodeFormatAztec")) + code.reset(Prison::createBarcode(Prison::Aztec)); + else if (formatName == QLatin1String("PKBarcodeFormatCode128")) + {} // TODO + + if (!code) + return {}; + code->setData(msg); + return code->toImage(code->minimumSize() * 4); +} + +QString PkPassFile::barcodeAltText() const +{ + return data().value(QLatin1String("barcode")).toObject().value(QLatin1String("altText")).toString(); +} + +QVector PkPassFile::auxiliaryFields() const +{ + return fields(QLatin1String("auxiliaryFields")); +} + +QVector PkPassFile::backFields() const +{ + return fields(QLatin1String("backFields")); +} + +QVector PkPassFile::headerFields() const +{ + return fields(QLatin1String("headerFields")); +} + +QVector PkPassFile::primaryFields() const +{ + return fields(QLatin1String("primaryFields")); +} + +QVector PkPassFile::secondaryFields() const +{ + return fields(QLatin1String("secondaryFields")); +} + +PkPassFile* PkPassFile::fromData(const QByteArray &data, QObject *parent) +{ + std::unique_ptr buffer(new QBuffer); + buffer->setData(data); + buffer->open(QBuffer::ReadOnly); + + std::unique_ptr zip(new KZip(buffer.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 dev(file->createDevice()); + const auto passObj = QJsonDocument::fromJson(dev->readAll()).object(); + + PkPassFile *pass = nullptr; + if (passObj.contains(QLatin1String("boardingPass"))) + pass = new PkPassBoardingPass(parent); + // TODO: coupon, eventTicket, storeCard, generic + else + pass = new PkPassFile(QStringLiteral("generic"), parent); + + pass->m_buffer = std::move(buffer); + pass->m_zip = std::move(zip); + pass->m_passObj = passObj; + pass->parse(); + return pass; +} + +void PkPassFile::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) { + if (catalog.at(i) == c) + return i; + if (catalog.at(i) == 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 PkPassFile::parseMessages(const QString &lang) +{ + auto entry = m_zip->directory()->entry(lang); + if (!entry || !entry->isDirectory()) + return false; + + auto dir = dynamic_cast(entry); + auto file = dir->file(QStringLiteral("pass.strings")); + if (!file) + return false; + + std::unique_ptr dev(file->createDevice()); + auto codec = QTextCodec::codecForName("UTF-16BE"); + const auto catalog = codec->toUnicode(dev->readAll()); + + 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)); + m_messages.insert(key, value); + idx = valueEnd + 1; // there's at least the linebreak and/or a ';' + } + + return !m_messages.isEmpty(); +} + +QVector PkPassFile::fields(const QLatin1String &fieldType) const +{ + const auto a = passData().value(fieldType).toArray(); + QVector f; + f.reserve(a.size()); + foreach (const auto &v, a) + f.push_back(PkPassField{v.toObject(), this}); + return f; +} diff --git a/plugins/messageviewer/bodypartformatter/pkpass/templates.qrc b/plugins/messageviewer/bodypartformatter/pkpass/templates.qrc new file mode 100644 --- /dev/null +++ b/plugins/messageviewer/bodypartformatter/pkpass/templates.qrc @@ -0,0 +1,5 @@ + + + templates/boardingpass.html + + diff --git a/plugins/messageviewer/bodypartformatter/pkpass/templates/boardingpass.html b/plugins/messageviewer/bodypartformatter/pkpass/templates/boardingpass.html new file mode 100644 --- /dev/null +++ b/plugins/messageviewer/bodypartformatter/pkpass/templates/boardingpass.html @@ -0,0 +1,110 @@ + +
+ +
+ +
+ + + + + {% for field in pass.headerFields %} + + {% endfor %} + + {% for field in pass.headerFields %} + + {% endfor %} + +
+ {% if pass.logoUrl %} + + {% endif %} + {{ pass.logoText }}{{ field.label }}
{{ field.value }}
+
+ +
+
+ + + + + + + + + + +
{{ pass.primaryFields.0.label }}{{ pass.primaryFields.1.label }}
{{ pass.primaryFields.0.value }} + {% if pass.transitType == pass.Air %} + ✈ + {% elif pass.transitType == pass.Bus %} + → + {% elif pass.transitType == pass.Boat %} + → + {% elif pass.transitType == pass.Train %} + → + {% else %} + → + {% endif %} + {{ pass.primaryFields.1.value }}
+
+ +
+ + + {% for field in pass.auxiliaryFields %} + + {% endfor %} + + {% for field in pass.auxiliaryFields %} + + {% endfor %} + +
{{ field.label }}
{{ field.value }}
+
+ +
+ + + {% for field in pass.secondaryFields %} + + {% endfor %} + + {% for field in pass.secondaryFields %} + + {% endfor %} + +
{{ field.label }}
{{ field.value }}
+
+ + +
+
+ {% if pass.barcodeUrl %} + + {% else %} + Unsupported barcode format. + {% endif %} +
+
{{ pass.barcodeAltText }}
+
+ +
+{% for field in pass.backFields %} +
{{ field.label }}
+
{{ field.value|safe }}
+{% endfor %} +
+ +