diff --git a/add_license.sh b/add_license.sh index 36e33a1..ebbd253 100755 --- a/add_license.sh +++ b/add_license.sh @@ -1,31 +1,31 @@ #!/bin/bash find "$@" -name '*.h' -o -name '*.cpp' -o -name '*.qml' -o -name '*.java'| grep -v /3rdparty/ | while read FILE; do if grep -qiE "Licensed under CC0." "$FILE" ; then continue; fi if grep -qiE "Copyright \(C\) [0-9, -]{4,} " "$FILE" ; then continue; fi thisfile=`basename $FILE` authorName=`git config user.name` authorEmail=`git config user.email` thisYear=`date +%Y` cat < "$FILE".tmp /* Copyright (C) $thisYear $authorName <$authorEmail> 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 . + along with this program. If not, see . */ EOF cat "$FILE" >> "$FILE".tmp mv "$FILE".tmp "$FILE" done diff --git a/autotests/barcodedecodertest.cpp b/autotests/barcodedecodertest.cpp index 0678804..fcedefd 100644 --- a/autotests/barcodedecodertest.cpp +++ b/autotests/barcodedecodertest.cpp @@ -1,102 +1,102 @@ /* 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 . + along with this program. If not, see . */ #include #include #include #include #include #include using namespace KItinerary; class BarcodeDecoderTest : public QObject { Q_OBJECT private Q_SLOTS: void testPDF417_data() { QTest::addColumn("fileName"); QTest::newRow("1bit") << QStringLiteral("pdf417_1bit.png"); QTest::newRow("8bit") << QStringLiteral("pdf417_8bit.png"); QTest::newRow("24bit") << QStringLiteral("pdf417_24bit.png"); QTest::newRow("32bit") << QStringLiteral("pdf417_32bit.png"); QTest::newRow("cropped") << QStringLiteral("pdf417_cropped.png"); QTest::newRow("rot90") << QStringLiteral("pdf417_rot90.png"); QTest::newRow("rot180") << QStringLiteral("pdf417_rot180.png"); QTest::newRow("rot270") << QStringLiteral("pdf417_rot270.png"); QTest::newRow("flipped") << QStringLiteral("pdf417_flipped.png"); } void testPDF417() { QFETCH(QString, fileName); QImage img(QStringLiteral(SOURCE_DIR "/barcodes/") + fileName); QVERIFY(!img.isNull()); #ifdef HAVE_ZXING_ANY QCOMPARE(BarcodeDecoder::decodePdf417(img), QStringLiteral("PDF417 is a stacked linear barcode symbol format used in a variety of applications, primarily transport, identification cards, and inventory management.")); #endif } void testAztec() { QImage img(QStringLiteral(SOURCE_DIR "/barcodes/aztec.png")); QVERIFY(!img.isNull()); #ifdef HAVE_ZXING_ANY QCOMPARE(BarcodeDecoder::decodeAztec(img), QStringLiteral("This is an example Aztec symbol for Wikipedia.")); #endif img.load(QStringLiteral(SOURCE_DIR "/barcodes/uic918-3star.png")); QVERIFY(!img.isNull()); #ifdef HAVE_ZXING_ANY const auto b = BarcodeDecoder::decodeAztecBinary(img); QCOMPARE(b.size(), 351); QVERIFY(b.startsWith("OTI010080000020")); #endif } void testQRCode_data() { QTest::addColumn("fileName"); QTest::addColumn("result"); QTest::newRow("1") << QStringLiteral("qrcode1.png") << QStringLiteral("M$K0YGV0G"); QTest::newRow("2") << QStringLiteral("qrcode2.png") << QStringLiteral("KZEXO4HRE"); } void testQRCode() { QFETCH(QString, fileName); QFETCH(QString, result); QImage img(QStringLiteral(SOURCE_DIR "/barcodes/") + fileName); QVERIFY(!img.isNull()); #ifdef HAVE_ZXING_ANY QCOMPARE(BarcodeDecoder::decodeQRCode(img), result); #endif } }; QTEST_APPLESS_MAIN(BarcodeDecoderTest) #include "barcodedecodertest.moc" diff --git a/autotests/bcbpparsertest.cpp b/autotests/bcbpparsertest.cpp index 13cd4a9..0aeea61 100644 --- a/autotests/bcbpparsertest.cpp +++ b/autotests/bcbpparsertest.cpp @@ -1,105 +1,105 @@ /* 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 . + along with this program. If not, see . */ #include "iatabcbpparser.h" #include #include #include #include #include #include #include #include #include using namespace KItinerary; class BcbpParserTest : public QObject { Q_OBJECT private Q_SLOTS: void testParserValid_data() { QTest::addColumn("message"); QTest::addColumn("refFile"); // example data from IATA resolution 792 version 5 Attachment B (with security sections shortended or omitted) QTest::newRow("single leg, mandatory only") << QStringLiteral("M1DESMARAIS/LUC EABC123 YULFRAAC 0834 326J001A0025 100") << QStringLiteral("iata-resolution792-example1.json"); QTest::newRow("single leg, all fields") << QStringLiteral("M1DESMARAIS/LUC EAB12C3 YULFRAAC 0834 326J003A0027 167>5321WW1325BAC 0014123456002001412346700100141234789012A0141234567890 1AC AC 1234567890123 4PCYLX58Z^108ABCDEFGH") << QStringLiteral("iata-resolution792-example2.json"); QTest::newRow("single leg, partial") << QStringLiteral("M1GRANDMAIRE/MELANIE EABC123 GVACDGAF 0123 339C002F0025 130>5002A0571234567890 AF AF 1234567890123456 Y^18ABCDEFGH") << QStringLiteral("iata-resolution792-example3.json"); QTest::newRow("multi leg, all fields") << QStringLiteral("M2DESMARAIS/LUC EAB12C3 YULFRAAC 0834 326J003A0027 167>5321WW1325BAC 0014123456002001412346700100141234789012A0141234567890 1AC AC 1234567890123 4PCYLX58ZDEF456 FRAGVALH 3664 327C012C0002 12E2A0140987654321 1AC AC 1234567890123 3PCNWQ^108ABCDEFGH") << QStringLiteral("iata-resolution792-example4.json"); QTest::newRow("multi leg, partial") << QStringLiteral("M2GRANDMAIRE/MELANIE EABC123 GVACDGAF 0123 339C002F0025 130>5002A0571234567890 AF AF 1234567890123456 YDEF456 CDGDTWNW 0049 339F001A0002 12C2A012098765432101 2PC ^18ABCDEFGH") << QStringLiteral("iata-resolution792-example5.json"); // EW misses the 'E' eticket marker (BCBP item 253) QTest::newRow("missing eticket indicator") << QStringLiteral("M1DOE/JOHN XXX007 BRUTXLEW 8103 035Y012C0030 147>1181W 8033BEW 0000000000000291040000000000 0 LH 123456789012345 ") << QStringLiteral("missing-eticket-indicator.json"); // boarding pass issue date (BCBP item 22) QTest::newRow("issue date") << QStringLiteral("M1DOE/JOHN EXXX007 TXLBRUSN 2588 034Y023D0999 35D>5181WM7034BSN 2A08200000000000 SN LH 123456789012345 *30600000K0902 ") << QStringLiteral("issue-date.json"); // EasyJet being easy on the standard interpretation QTest::newRow("easyjet") << QStringLiteral("M1DOE/JOHN EABCDEFGMRSLGWEZY8724 99 3C 506 10Axxxxxxxxxx") << QStringLiteral("easyjet.json"); // Brussels Airlines short codes on booking confirmation QTest::newRow("minimal1") << QStringLiteral("M1DOE/JOHN EXXX007 TXLBRUSN 2592 110Y") << QStringLiteral("minimal.json"); QTest::newRow("minimal2") << QStringLiteral("M1DOE/JOHN EXXX007 TXLBRUSN 2592 110") << QStringLiteral("minimal2.json"); // TAP missing boarding pass issue date QTest::newRow("no issue date") << QStringLiteral("M1DOE/JOHN EXXX007 LISLCGTP 1080 204Y002D0003 35C>2180 B1A 2904712345678900 *306 09 BRND") << QStringLiteral("tap-missing-issue-date.json"); } void testParserValid() { QFETCH(QString, message); QFETCH(QString, refFile); QFile f(QStringLiteral(SOURCE_DIR "/bcbpdata/") + refFile); QVERIFY(f.open(QFile::ReadOnly)); const auto refArray = QJsonDocument::fromJson(f.readAll()).array(); QVERIFY(!refArray.isEmpty()); const auto res = IataBcbpParser::parse(message, QDate(2018, 4, 2)); const auto resJson = JsonLdDocument::toJson(res); if (refArray != resJson) { qWarning().noquote() << QJsonDocument(resJson).toJson(); } QCOMPARE(resJson, refArray); } void testParserInvalid_data() { QTest::addColumn("message"); QTest::newRow("empty") << QString(); QTest::newRow("too short") << QStringLiteral("M1DESMARAIS/LUC "); QTest::newRow("wrong leg count") << QStringLiteral("M2DESMARAIS/LUC EABC123 YULFRAAC 0834 326J001A0025 100"); QTest::newRow("too short repeated mandatory section") << QStringLiteral("M1DOE/JOHN EXXX007 TXLBRUSN 2592 11"); } void testParserInvalid() { QFETCH(QString, message); const auto res = IataBcbpParser::parse(message); QVERIFY(res.isEmpty()); } }; QTEST_APPLESS_MAIN(BcbpParserTest) #include "bcbpparsertest.moc" diff --git a/autotests/extractortest.cpp b/autotests/extractortest.cpp index c5323e5..a25ad61 100644 --- a/autotests/extractortest.cpp +++ b/autotests/extractortest.cpp @@ -1,189 +1,189 @@ /* 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 . + along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KItinerary; /** Note: this test requires external test data that is not publicly available, * ie. real-world unmodified booking documents. * This data cannot be shared for containing privacy-sensitive data and copyrighted * material (e.g. airline logos). */ class ExtractorTest : public QObject { Q_OBJECT private: ExtractorEngine m_engine; private Q_SLOTS: void initTestCase() { // use some exotic locale to ensure the date/time parsing doesn't just work by luck QLocale::setDefault(QLocale(QStringLiteral("fr_FR"))); } void testExtract_data() { QTest::addColumn("contextFile"); QTest::addColumn("inputFile"); for (const QDir &baseDir : {QStringLiteral(SOURCE_DIR "/extractordata"), QStringLiteral(SOURCE_DIR "/../../kitinerary-tests")}) { if (!baseDir.exists()) { continue; } QDirIterator it(baseDir.path(), {QStringLiteral("*.txt"), QStringLiteral("*.html"), QStringLiteral("*.pdf"), QStringLiteral("*.pkpass"), QStringLiteral("*.ics"), QStringLiteral("*.eml"), QStringLiteral("*.mbox")}, QDir::Files | QDir::Readable | QDir::NoSymLinks, QDirIterator::Subdirectories); while (it.hasNext()) { it.next(); // ignore context files if (it.fileName() == QLatin1String("context.eml")) { continue; } QFileInfo contextFi(it.fileInfo().absolutePath() + QLatin1String("/context.eml")); QTest::newRow((contextFi.dir().dirName() + QLatin1Char('-') + it.fileName()).toLatin1().constData()) << contextFi.absoluteFilePath() << it.fileInfo().absoluteFilePath(); } } } void testExtract() { QFETCH(QString, contextFile); QFETCH(QString, inputFile); if (contextFile.isEmpty()) { return; } m_engine.clear(); QFile cf(contextFile); KMime::Message contextMsg; if (cf.open(QFile::ReadOnly)) { contextMsg.setContent(cf.readAll()); contextMsg.parse(); m_engine.setContext(&contextMsg); } QFile inFile(inputFile); QVERIFY(inFile.open(QFile::ReadOnly)); std::unique_ptr pass; std::unique_ptr htmlDoc; std::unique_ptr pdfDoc; KCalCore::Calendar::Ptr calendar; std::unique_ptr mimeMsg; QJsonArray jsonResult; if (inputFile.endsWith(QLatin1String(".pkpass"))) { pass.reset(KPkPass::Pass::fromData(inFile.readAll())); m_engine.setPass(pass.get()); } else if (inputFile.endsWith(QLatin1String(".pdf"))) { pdfDoc.reset(PdfDocument::fromData(inFile.readAll())); QVERIFY(pdfDoc); m_engine.setPdfDocument(pdfDoc.get()); } else if (inputFile.endsWith(QLatin1String(".html"))) { htmlDoc.reset(HtmlDocument::fromData(inFile.readAll())); QVERIFY(htmlDoc); m_engine.setHtmlDocument(htmlDoc.get()); } else if (inputFile.endsWith(QLatin1String(".txt"))) { m_engine.setText(QString::fromUtf8(inFile.readAll())); } else if (inputFile.endsWith(QLatin1String(".ics"))) { calendar.reset(new KCalCore::MemoryCalendar(QTimeZone())); KCalCore::ICalFormat format; QVERIFY(format.fromRawString(calendar, inFile.readAll())); m_engine.setCalendar(calendar); } else if (inputFile.endsWith(QLatin1String(".eml")) || inputFile.endsWith(QLatin1String(".mbox"))) { mimeMsg.reset(new KMime::Message); mimeMsg->setContent(inFile.readAll()); mimeMsg->parse(); m_engine.setContent(mimeMsg.get()); } jsonResult = m_engine.extract(); const auto expectedSkip = QFile::exists(inputFile + QLatin1String(".skip")); if (jsonResult.isEmpty() && expectedSkip) { QSKIP("nothing extracted"); return; } QVERIFY(!jsonResult.isEmpty()); const auto result = JsonLdDocument::fromJson(jsonResult); ExtractorPostprocessor postproc; postproc.setContextDate(contextMsg.date()->dateTime()); postproc.process(result); const auto postProcResult = JsonLdDocument::toJson(postproc.result()); if (postProcResult.isEmpty()) { qDebug() << "Result discared in post processing:"; qDebug().noquote() << QJsonDocument(jsonResult).toJson(); } QVERIFY(!postProcResult.isEmpty()); const QString refFile = inputFile + QLatin1String(".json"); if (!QFile::exists(refFile) && !expectedSkip) { QFile f(refFile); QVERIFY(f.open(QFile::WriteOnly)); f.write(QJsonDocument(postProcResult).toJson()); return; } QFile f(refFile); QVERIFY(f.open(QFile::ReadOnly)); const auto refDoc = QJsonDocument::fromJson(f.readAll()); if (refDoc.array() != postProcResult) { QFile failFile(refFile + QLatin1String(".fail")); QVERIFY(failFile.open(QFile::WriteOnly)); failFile.write(QJsonDocument(postProcResult).toJson()); failFile.close(); QProcess proc; proc.setProcessChannelMode(QProcess::ForwardedChannels); proc.start(QStringLiteral("diff"), {QStringLiteral("-u"), refFile, failFile.fileName()}); QVERIFY(proc.waitForFinished()); } QCOMPARE(refDoc.array(), postProcResult); } }; QTEST_MAIN(ExtractorTest) #include "extractortest.moc" diff --git a/autotests/htmldocumenttest.cpp b/autotests/htmldocumenttest.cpp index f77a438..d94e990 100644 --- a/autotests/htmldocumenttest.cpp +++ b/autotests/htmldocumenttest.cpp @@ -1,148 +1,148 @@ /* 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 . + along with this program. If not, see . */ #include #include #include #include #include using namespace KItinerary; class HtmlDocumentTest : public QObject { Q_OBJECT private Q_SLOTS: void testElementWalking() { QFile f(QStringLiteral(SOURCE_DIR "/structureddata/os-two-leg-invalid-microdata.html")); QVERIFY(f.open(QFile::ReadOnly)); #ifdef HAVE_LIBXML2 std::unique_ptr doc(HtmlDocument::fromData(f.readAll())); QVERIFY(doc); auto elem = doc->root(); QVERIFY(!elem.isNull()); QCOMPARE(elem.name(), QLatin1String("html")); QCOMPARE(elem.attribute(QLatin1String("lang")), QLatin1String("de")); QVERIFY(elem.nextSibling().isNull()); QVERIFY(elem.parent().isNull()); elem = elem.firstChild(); QVERIFY(!elem.isNull()); QCOMPARE(elem.name(), QLatin1String("head")); elem = elem.nextSibling(); QVERIFY(!elem.isNull()); QCOMPARE(elem.name(), QLatin1String("body")); QCOMPARE(elem.parent().name(), QLatin1String("html")); auto res = doc->eval(QLatin1String("/html")); auto nodes = res.toList(); QCOMPARE(nodes.size(), 1); QCOMPARE(nodes.at(0).value().name(), QLatin1String("html")); nodes = doc->eval(QLatin1String("//body")).toList(); QCOMPARE(nodes.size(), 1); nodes = doc->eval(QLatin1String("//link")).toList(); QCOMPARE(nodes.size(), 6); nodes = doc->eval(QLatin1String("/html/@lang")).toList(); QCOMPARE(nodes.at(0).value().content(), QLatin1String("de")); nodes = doc->eval(QLatin1String("//div[@itemtype=\"http://schema.org/FlightReservation\"]")).toList(); QCOMPARE(nodes.size(), 2); elem = nodes.at(0).value(); QCOMPARE(elem.attributes().size(), 2); QVERIFY(elem.attributes().contains(QLatin1String("itemscope"))); QVERIFY(elem.attributes().contains(QLatin1String("itemtype"))); nodes = elem.eval(QLatin1String("./link")).toList(); QCOMPARE(nodes.size(), 3); #endif } void testContentAccess() { QFile f(QStringLiteral(SOURCE_DIR "/structureddata/hotel-json-ld-fallback.html")); QVERIFY(f.open(QFile::ReadOnly)); #ifdef HAVE_LIBXML2 std::unique_ptr doc(HtmlDocument::fromData(f.readAll())); QVERIFY(doc); auto elem = doc->root(); QVERIFY(!elem.isNull()); QVERIFY(elem.content().isEmpty()); elem = elem.firstChild().firstChild().nextSibling(); QCOMPARE(elem.name(), QLatin1String("script")); QCOMPARE(elem.attribute(QLatin1String("type")), QLatin1String("application/ld+json")); QCOMPARE(elem.attributes().size(), 1); QCOMPARE(elem.attributes().at(0), QLatin1String("type")); const auto s = elem.content(); QVERIFY(s.contains(QLatin1String("checkoutDate"))); elem = doc->root().firstChild().nextSibling().firstChild(); QCOMPARE(elem.name(), QLatin1String("p")); QCOMPARE(elem.content(), QLatin1String("random content\ncan be invalid")); #endif } void testContentProcessing() { QFile f(QStringLiteral(SOURCE_DIR "/misc/test.html")); QVERIFY(f.open(QFile::ReadOnly)); #ifdef HAVE_LIBXML2 std::unique_ptr doc(HtmlDocument::fromData(f.readAll())); QVERIFY(doc); auto elem = doc->root(); QVERIFY(!elem.isNull()); QVERIFY(elem.content().isEmpty()); QVERIFY(elem.recursiveContent().contains(QLatin1String("spaces"))); elem = elem.firstChild().firstChild(); QCOMPARE(elem.name(), QLatin1String("p")); QCOMPARE(elem.content(), QLatin1String("word1\nword2")); QCOMPARE(elem.recursiveContent(), QLatin1String("word1\nword2")); elem = elem.nextSibling(); QCOMPARE(elem.name(), QLatin1String("p")); QCOMPARE(elem.content(), QLatin1String("lots of spaces")); QCOMPARE(elem.recursiveContent(), QLatin1String("lots of spaces")); auto elems = doc->eval(QLatin1String("//*[text()[normalize-space(.)='lots of spaces']]")).toList(); QCOMPARE(elems.size(), 1); QCOMPARE(elems.at(0).value().name(), QLatin1String("p")); elems = doc->eval(QLatin1String("//*[text()='lots of spaces']")).toList(); QCOMPARE(elems.size(), 0); elem = elem.nextSibling(); QCOMPARE(elem.content(), QString::fromUtf8("인천공항")); QCOMPARE(elem.recursiveContent(), QString::fromUtf8("인천공항")); elem = elem.nextSibling(); QCOMPARE(elem.content(), QLatin1String("a b")); QCOMPARE(elem.recursiveContent(), QLatin1String("a b")); elem = elem.nextSibling(); QCOMPARE(elem.content(), QLatin1String("a&b")); QCOMPARE(elem.recursiveContent(), QLatin1String("a&b")); elem = elem.nextSibling(); QCOMPARE(elem.content(), QLatin1String("a&b")); QCOMPARE(elem.recursiveContent(), QLatin1String("a&b")); #endif } }; QTEST_GUILESS_MAIN(HtmlDocumentTest) #include "htmldocumenttest.moc" diff --git a/autotests/locationutiltest.cpp b/autotests/locationutiltest.cpp index fc2fb08..99500ea 100644 --- a/autotests/locationutiltest.cpp +++ b/autotests/locationutiltest.cpp @@ -1,100 +1,100 @@ /* 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 . + along with this program. If not, see . */ #include #include #include #include #include #define _(x) QStringLiteral(x) using namespace KItinerary; class LocationUtilTest : public QObject { Q_OBJECT private Q_SLOTS: void testLocationCompare() { Airport txlCoord; txlCoord.setGeo({52.5f, 13.28f}); Airport sxfCoord; sxfCoord.setGeo({52.3f, 13.52f}); QVERIFY(LocationUtil::isSameLocation(txlCoord, sxfCoord, LocationUtil::CityLevel)); QVERIFY(LocationUtil::isSameLocation(sxfCoord, txlCoord, LocationUtil::CityLevel)); QVERIFY(LocationUtil::isSameLocation(txlCoord, txlCoord, LocationUtil::CityLevel)); QVERIFY(!LocationUtil::isSameLocation(txlCoord, {}, LocationUtil::CityLevel)); QVERIFY(!LocationUtil::isSameLocation({}, sxfCoord, LocationUtil::CityLevel)); QVERIFY(!LocationUtil::isSameLocation(sxfCoord, txlCoord, LocationUtil::Exact)); QVERIFY(LocationUtil::isSameLocation(txlCoord, txlCoord, LocationUtil::Exact)); QVERIFY(!LocationUtil::isSameLocation(txlCoord, {}, LocationUtil::Exact)); QVERIFY(!LocationUtil::isSameLocation({}, sxfCoord, LocationUtil::Exact)); PostalAddress addr; addr.setAddressCountry(_("DE")); addr.setAddressLocality(_("Berlin")); Airport txlAddress; txlAddress.setAddress(addr); Airport sxfAddress; sxfAddress.setAddress(addr); QVERIFY(LocationUtil::isSameLocation(txlAddress, sxfAddress, LocationUtil::CityLevel)); QVERIFY(LocationUtil::isSameLocation(sxfAddress, txlAddress, LocationUtil::CityLevel)); QVERIFY(LocationUtil::isSameLocation(sxfAddress, sxfAddress, LocationUtil::CityLevel)); QVERIFY(!LocationUtil::isSameLocation(txlAddress, {}, LocationUtil::CityLevel)); QVERIFY(!LocationUtil::isSameLocation({}, sxfAddress, LocationUtil::CityLevel)); QVERIFY(!LocationUtil::isSameLocation(txlAddress, sxfAddress, LocationUtil::Exact)); QVERIFY(!LocationUtil::isSameLocation(txlAddress, {}, LocationUtil::Exact)); QVERIFY(!LocationUtil::isSameLocation({}, sxfAddress, LocationUtil::Exact)); Airport txlIata; txlIata.setIataCode(_("TXL")); QVERIFY(LocationUtil::isSameLocation(txlIata, txlIata, LocationUtil::CityLevel)); QVERIFY(LocationUtil::isSameLocation(txlIata, txlIata, LocationUtil::Exact)); QVERIFY(!LocationUtil::isSameLocation({}, txlIata, LocationUtil::CityLevel)); QVERIFY(!LocationUtil::isSameLocation(txlIata, {}, LocationUtil::Exact)); Place txlName; txlName.setName(_("Berlin Tegel Airpot")); QVERIFY(LocationUtil::isSameLocation(txlName, txlName, LocationUtil::CityLevel)); QVERIFY(LocationUtil::isSameLocation(txlName, txlName, LocationUtil::Exact)); QVERIFY(!LocationUtil::isSameLocation({}, txlName, LocationUtil::CityLevel)); QVERIFY(!LocationUtil::isSameLocation(txlName, {}, LocationUtil::Exact)); Airport txl; txl.setGeo(txlCoord.geo()); txl.setIataCode(_("TXL")); txl.setName(_("Berlin Tegel Airport")); Airport sxf; sxf.setGeo(sxfCoord.geo()); sxf.setIataCode(_("SXF")); sxf.setName(_("Berlin Schönefeld Airport")); QVERIFY(LocationUtil::isSameLocation(txl, sxf, LocationUtil::CityLevel)); QVERIFY(!LocationUtil::isSameLocation(txl, sxf, LocationUtil::Exact)); QVERIFY(!LocationUtil::isSameLocation(txl, {}, LocationUtil::CityLevel)); QVERIFY(!LocationUtil::isSameLocation({}, sxf, LocationUtil::Exact)); } }; QTEST_APPLESS_MAIN(LocationUtilTest) #include "locationutiltest.moc" diff --git a/autotests/mergeutiltest.cpp b/autotests/mergeutiltest.cpp index cca94de..3c1cefd 100644 --- a/autotests/mergeutiltest.cpp +++ b/autotests/mergeutiltest.cpp @@ -1,234 +1,234 @@ /* 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 . + along with this program. If not, see . */ #include #include #include #include #include #include #include #include #define _(x) QStringLiteral(x) using namespace KItinerary; class MergeUtilTest : public QObject { Q_OBJECT private Q_SLOTS: void testIsSameReservation() { QVERIFY(!MergeUtil::isSame({}, {})); FlightReservation res1; QVERIFY(!MergeUtil::isSame(res1, {})); QVERIFY(!MergeUtil::isSame({}, res1)); res1.setReservationNumber(QLatin1String("XXX007")); Flight flight1; flight1.setFlightNumber(QLatin1String("1234")); flight1.setDepartureDay(QDate(2018, 4, 21)); res1.setReservationFor(flight1); FlightReservation res2; res2.setReservationNumber(QLatin1String("YYY008")); Flight flight2; flight2.setFlightNumber(QLatin1String("1234")); res2.setReservationFor(flight2); QVERIFY(!MergeUtil::isSame(res1, res2)); flight2.setDepartureDay(QDate(2018, 4, 21)); res2.setReservationFor(flight2); QVERIFY(!MergeUtil::isSame(res1, res2)); res2.setReservationNumber(QLatin1String("XXX007")); QVERIFY(MergeUtil::isSame(res1, res2)); } void testIsSameFlight() { Airline airline1; airline1.setIataCode(QLatin1String("KL")); Flight f1; f1.setAirline(airline1); f1.setFlightNumber(QLatin1String("8457")); f1.setDepartureTime(QDateTime(QDate(2018, 4, 2), QTime(17, 51, 0))); Flight f2; QVERIFY(!MergeUtil::isSame(f1, f2)); f2.setFlightNumber(QLatin1String("8457")); QVERIFY(!MergeUtil::isSame(f1, f2)); Airline airline2; airline2.setIataCode(QLatin1String("AF")); f2.setAirline(airline2); QVERIFY(!MergeUtil::isSame(f1, f2)); airline2.setIataCode(QLatin1String("KL")); f2.setAirline(airline2); QVERIFY(!MergeUtil::isSame(f1, f2)); f2.setDepartureDay(QDate(2018, 4, 2)); QVERIFY(MergeUtil::isSame(f1, f2)); } void testCodeShareFlight() { Airline a1; a1.setIataCode(QLatin1String("4U")); Flight f1; f1.setAirline(a1); f1.setFlightNumber(QLatin1String("42")); f1.setDepartureDay(QDate(2018, 04, 21)); Airline a2; a2.setIataCode(QLatin1String("EW")); Flight f2(f1); f2.setAirline(a2); QVERIFY(MergeUtil::isSame(f1, f2)); } void testIsSamePerson_data() { // we do not need to consider cases here that ExtractorPostprocessor eliminates for us // such as filling the full name or honoric prefixes QTest::addColumn>("data"); QTest::newRow("simple name") << QVector { {_("Volker Krause"), {}, {}}, {_("VOLKER KRAUSE"), {}, {}}, {_("VOLKER KRAUSE"), _("Volker"), _("Krause")}, {_("VOLKER KRAUSE"), {}, _("Krause")}, {_("VOLKER KRAUSE"), _("Volker"), {}}, // IATA BCBP artifacts {_("VOLKERMR KRAUSE"), _("VOLKERMR"), _("KRAUSE")}, {_("VOLKER MR KRAUSE"), _("VOLKER MR"), _("KRAUSE")} }; QTest::newRow("double family name") << QVector { {_("Andreas Cord-Landwehr"), {}, {}}, {_("ANDREAS CORD-LANDWEHR"), {}, {}}, {_("ANDREAS CORD-LANDWEHR"), _("Andreas"), _("Cord-Landwehr")}, // IATA BCBP artifacts {_("Andreas Cordlandwehr"), {}, {}}, {_("ANDREAS CORDLANDWEHR"), _("ANDREAS"), _("CORDLANDWEHR")}, {_("ANDREAS CORD LANDWEHR"), _("ANDREAS"), _("CORD LANDWEHR")}, {_("ANDREAS CORD LANDWEHR"), {}, {}} }; QTest::newRow("diacritic") << QVector { {_("Daniel Vrátil"), {}, {} }, {_("Daniel Vrátil"), _("Daniel"), _("Vrátil") }, {_("DANIEL VRATIL"), {}, {} }, {_("DANIEL VRATIL"), _("DANIEL"), _("VRATIL") } }; } void testIsSamePerson() { QFETCH(QVector, data); for(int i = 0; i < data.size(); ++i) { Person lhs; lhs.setName(data[i][0]); lhs.setGivenName(data[i][1]); lhs.setFamilyName(data[i][2]); for (int j = 0; j < data.size(); ++j) { Person rhs; rhs.setName(data[j][0]); rhs.setGivenName(data[j][1]); rhs.setFamilyName(data[j][2]); QVERIFY(!MergeUtil::isSamePerson(lhs, {})); QVERIFY(!MergeUtil::isSamePerson({}, lhs)); if (!MergeUtil::isSamePerson(lhs, rhs)) { qDebug() << "Left: " << lhs.name() << lhs.givenName() << lhs.familyName(); qDebug() << "Right: " << rhs.name() << rhs.givenName() << rhs.familyName(); } QVERIFY(MergeUtil::isSamePerson(lhs, rhs)); } } } void testIsNotSamePerson() { QVector data { { _("Volker Krause"), {}, {} }, { _("Andreas Cord-Landwehr"), _("Andread"), _("Cord-Landwehr") }, { _("GIVEN1 GIVEN2 FAMILY1"), {}, {} }, { _("V K"), {}, {} }, {_("Daniel Vrátil"), _("Daniel"), _("Vrátil") }, }; for(int i = 0; i < data.size(); ++i) { Person lhs; lhs.setName(data[i][0]); lhs.setGivenName(data[i][1]); lhs.setFamilyName(data[i][2]); for (int j = 0; j < data.size(); ++j) { if (i == j) { continue; } Person rhs; rhs.setName(data[j][0]); rhs.setGivenName(data[j][1]); rhs.setFamilyName(data[j][2]); QVERIFY(!MergeUtil::isSamePerson(lhs, {})); QVERIFY(!MergeUtil::isSamePerson({}, lhs)); if (MergeUtil::isSamePerson(lhs, rhs)) { qDebug() << "Left: " << lhs.name() << lhs.givenName() << lhs.familyName(); qDebug() << "Right: " << rhs.name() << rhs.givenName() << rhs.familyName(); } QVERIFY(!MergeUtil::isSamePerson(lhs, rhs)); } } } void testIsSameLodingReservation() { LodgingReservation res1; LodgingBusiness hotel1; hotel1.setName(QLatin1String("Haus Randa")); res1.setReservationFor(hotel1); res1.setCheckinTime(QDateTime(QDate(2018, 4, 9), QTime(10, 0))); res1.setReservationNumber(QLatin1String("1234")); LodgingReservation res2; QVERIFY(!MergeUtil::isSame(res1, res2)); res2.setReservationNumber(QLatin1String("1234")); QVERIFY(!MergeUtil::isSame(res1, res2)); res2.setCheckinTime(QDateTime(QDate(2018, 4, 9), QTime(15, 0))); QVERIFY(!MergeUtil::isSame(res1, res2)); LodgingBusiness hotel2; hotel2.setName(QLatin1String("Haus Randa")); res2.setReservationFor(hotel2); QVERIFY(MergeUtil::isSame(res1, res2)); } }; QTEST_APPLESS_MAIN(MergeUtilTest) #include "mergeutiltest.moc" diff --git a/autotests/pdfdocumenttest.cpp b/autotests/pdfdocumenttest.cpp index 21f141d..d781c07 100644 --- a/autotests/pdfdocumenttest.cpp +++ b/autotests/pdfdocumenttest.cpp @@ -1,83 +1,83 @@ /* 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 . + along with this program. If not, see . */ #include #include #include #include #include #include #include using namespace KItinerary; class PdfDocumentTest : public QObject { Q_OBJECT private Q_SLOTS: void testPdfDocument() { QFile f(QStringLiteral(SOURCE_DIR "/misc/test.pdf")); QVERIFY(f.open(QFile::ReadOnly)); #ifdef HAVE_POPPLER std::unique_ptr doc(PdfDocument::fromData(f.readAll())); QVERIFY(doc); QCOMPARE(doc->text(), QStringLiteral("This is the first page.\nIt contains a PDF 417 barcode.\nThis is the second page.\nIt contains an Aztec code.\n")); QCOMPARE(doc->pageCount(), 2); QCOMPARE(doc->property("pages").toList().size(), 2); auto page = doc->page(0); QCOMPARE(page.text(), QStringLiteral("This is the first page.\nIt contains a PDF 417 barcode.\n")); QCOMPARE(page.imageCount(), 1); QCOMPARE(PdfPage::staticMetaObject.property(1).readOnGadget(&page).toList().size(), 1); QCOMPARE(page.textInRect(0, 0, 1, 0.5), QStringLiteral("This is the first page.\nIt contains a PDF 417 barcode.\n")); QCOMPARE(page.textInRect(0, 0.5, 1, 1), QString()); auto img = page.image(0); QCOMPARE(img.width(), 350); QCOMPARE(img.height(), 152); QCOMPARE(img.image().width(), img.width()); QCOMPARE(img.image().height(), img.height()); page = doc->page(1); QCOMPARE(page.text(), QStringLiteral("This is the second page.\nIt contains an Aztec code.\n")); QCOMPARE(page.imageCount(), 1); img = page.image(0); QCOMPARE(img.width(), 276); QCOMPARE(img.height(), 276); QVERIFY(page.imagesInRect(0, 0, 0.5, 1).isEmpty()); QCOMPARE(page.imagesInRect(0, 0.5, 1, 1).size(), 1); #endif } void testInvalidPdfDocument() { QVERIFY(!PdfDocument::fromData(QByteArray())); QVERIFY(!PdfDocument::fromData(QByteArray("HELLO"))); QFile f(QStringLiteral(SOURCE_DIR "/misc/test.pdf")); QVERIFY(f.open(QFile::ReadOnly)); QVERIFY(!PdfDocument::fromData(f.readAll().left(f.size() / 2))); } }; QTEST_GUILESS_MAIN(PdfDocumentTest) #include "pdfdocumenttest.moc" diff --git a/autotests/pkpassextractortest.cpp b/autotests/pkpassextractortest.cpp index 1befafe..e72f9d1 100644 --- a/autotests/pkpassextractortest.cpp +++ b/autotests/pkpassextractortest.cpp @@ -1,100 +1,100 @@ /* 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 . + along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KItinerary; class PkPassExtractorTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase() { // use some exotic locale to ensure the date/time parsing doesn't just work by luck QLocale::setDefault(QLocale(QStringLiteral("fr_FR"))); qputenv("TZ", "EST"); } void testExtractText_data() { QTest::addColumn("inputFile"); QTest::addColumn("refFile"); QDir dir(QStringLiteral(SOURCE_DIR "/pkpassdata")); const auto lst = dir.entryList(QStringList(QStringLiteral("*.pkpass")), QDir::Files | QDir::Readable | QDir::NoSymLinks); for (const auto &file : lst) { const QString refFile = dir.path() + QLatin1Char('/') + file.left(file.size() - 7) + QStringLiteral(".json"); if (!QFile::exists(refFile)) { qDebug() << "reference file" << refFile << "does not exist, skipping test file" << file; continue; } QTest::newRow(file.toLatin1().constData()) << QString(dir.path() + QLatin1Char('/') + file) << refFile; } } void testExtractText() { QFETCH(QString, inputFile); QFETCH(QString, refFile); const auto pass = KPkPass::Pass::fromFile(inputFile, this); QVERIFY(pass); ExtractorEngine engine; engine.setContextDate(QDateTime(QDate(2017, 12, 29), QTime(18, 46, 2))); engine.setPass(pass); auto result = JsonLdDocument::fromJson(engine.extract()); QCOMPARE(result.size(), 1); ExtractorPostprocessor postproc; postproc.setContextDate(QDateTime(QDate(2017, 12, 29), QTime(18, 46, 2))); postproc.process(result); result = postproc.result(); QCOMPARE(result.size(), 1); const auto resJson = JsonLdDocument::toJson(result); QFile ref(refFile); QVERIFY(ref.open(QFile::ReadOnly)); const auto doc = QJsonDocument::fromJson(ref.readAll()); QVERIFY(doc.isArray()); if (resJson != doc.array()) { qDebug().noquote() << QJsonDocument(resJson).toJson(); } QCOMPARE(resJson, doc.array()); } }; QTEST_GUILESS_MAIN(PkPassExtractorTest) #include "pkpassextractortest.moc" diff --git a/autotests/stringutiltest.cpp b/autotests/stringutiltest.cpp index 2f131c9..1d6ee54 100644 --- a/autotests/stringutiltest.cpp +++ b/autotests/stringutiltest.cpp @@ -1,53 +1,53 @@ /* 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 . + along with this program. If not, see . */ #include "stringutil.h" #include #include #include #define _(x) QStringLiteral(x) using namespace KItinerary; class StringUtilTest : public QObject { Q_OBJECT private Q_SLOTS: void testNormalize_data() { QTest::addColumn("in"); QTest::addColumn("out"); QTest::newRow("empty") << QString() << QString(); QTest::newRow("normalized") << _("normal") << _("normal"); QTest::newRow("case-folding") << _("NORMAL") << _("normal"); QTest::newRow("umlaut") << _("NöRMÄl") << _("normal"); } void testNormalize() { QFETCH(QString, in); QFETCH(QString, out); QCOMPARE(StringUtil::normalize(in), out); } }; QTEST_APPLESS_MAIN(StringUtilTest) #include "stringutiltest.moc" diff --git a/autotests/uic9183parsertest.cpp b/autotests/uic9183parsertest.cpp index 30c88db..4ec437c 100644 --- a/autotests/uic9183parsertest.cpp +++ b/autotests/uic9183parsertest.cpp @@ -1,100 +1,100 @@ /* 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 . + along with this program. If not, see . */ #include #include #include #include #include #include #include #include using namespace KItinerary; class Uic9183ParserTest : public QObject { Q_OBJECT private Q_SLOTS: void testParserValid_data() { QTest::addColumn("inFile"); QTest::addColumn("refFile"); QDir dir(QStringLiteral(SOURCE_DIR "/uic918-3/valid")); const auto lst = dir.entryList(QStringList(QStringLiteral("*.bin")), QDir::Files | QDir::Readable | QDir::NoSymLinks); for (const auto &file : lst) { const QString refFile = dir.path() + QLatin1Char('/') + file.left(file.size() - 4) + QStringLiteral(".json"); if (!QFile::exists(refFile)) { qDebug() << "reference file" << refFile << "does not exist, skipping test file" << file; continue; } QTest::newRow(file.toUtf8().constData()) << QString(dir.path() + QLatin1Char('/') + file) << refFile; } } void testParserValid() { QFETCH(QString, inFile); QFETCH(QString, refFile); QFile f(inFile); QVERIFY(f.open(QFile::ReadOnly)); Uic9183Parser p; p.parse(f.readAll()); QVERIFY(p.isValid()); QFile ref(refFile); QVERIFY(ref.open(QFile::ReadOnly)); const auto refArray = QJsonDocument::fromJson(ref.readAll()).array(); QVERIFY(!refArray.isEmpty()); const auto resJson = JsonLdDocument::toJson({QVariant::fromValue(p)}); if (refArray != resJson) { qWarning().noquote() << QJsonDocument(resJson).toJson(); } QCOMPARE(resJson, refArray); } void testParserInvalid_data() { QTest::addColumn("fileName"); QDir dir(QStringLiteral(SOURCE_DIR "/uic918-3/invalid")); const auto lst = dir.entryList(QStringList(QStringLiteral("*.bin")), QDir::Files | QDir::Readable | QDir::NoSymLinks); for (const auto &file : lst) { QTest::newRow(file.toLatin1().constData()) << QString(dir.path() + QLatin1Char('/') + file); } } void testParserInvalid() { QFETCH(QString, fileName); QFile f(fileName); QVERIFY(f.open(QFile::ReadOnly)); Uic9183Parser p; p.parse(f.readAll()); QVERIFY(!p.isValid()); } }; QTEST_APPLESS_MAIN(Uic9183ParserTest) #include "uic9183parsertest.moc" diff --git a/src/barcodedecoder.cpp b/src/barcodedecoder.cpp index 121fcd6..4dda6b5 100644 --- a/src/barcodedecoder.cpp +++ b/src/barcodedecoder.cpp @@ -1,165 +1,165 @@ /* 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 . + along with this program. If not, see . */ #include "config-kitinerary.h" #include "barcodedecoder.h" #include "logging.h" #include "qimageluminancesource.h" #include "qimagepurebinarizer.h" #include #include #include #ifdef HAVE_ZXING #include #include #include #elif defined(HAVE_ZXING_OLD) #include #include #include #include #include #endif #include using namespace KItinerary; #ifdef HAVE_ZXING using ZXing::BarcodeFormat; static QString decodeString(const QImage &img, ZXing::BarcodeFormat format) { QImagePureBinarizer binarizer(img); ZXing::DecodeHints hints; hints.setPossibleFormats({format}); ZXing::MultiFormatReader reader(hints); const auto result = reader.read(binarizer); if (result.isValid()) { return QString::fromStdWString(result.text()); } return {}; } static QByteArray decodeBinary(const QImage &img, ZXing::BarcodeFormat format) { QImagePureBinarizer binarizer(img); ZXing::DecodeHints hints; hints.setPossibleFormats({format}); ZXing::MultiFormatReader reader(hints); auto result = reader.read(binarizer); if (result.isValid()) { QByteArray b; b.resize(result.text().size()); std::copy(result.text().begin(), result.text().end(), b.begin()); return b; } return {}; } #elif defined(HAVE_ZXING_OLD) using zxing::BarcodeFormat; static QString decodeString(const QImage &img, zxing::BarcodeFormat format) { try { const zxing::Ref source(new QImageLuminanceSource(img)); const zxing::Ref binarizer(new zxing::HybridBinarizer(source)); const zxing::Ref binary(new zxing::BinaryBitmap(binarizer)); const zxing::DecodeHints hints(1 << format); zxing::MultiFormatReader reader; const auto result = reader.decode(binary, hints); return QString::fromStdString(result->getText()->getText()); } catch (const std::exception &e) { //qCDebug(Log) << e.what(); } return {}; } static QByteArray decodeBinary(const QImage &img, zxing::BarcodeFormat format) { try { const zxing::Ref source(new QImageLuminanceSource(img)); const zxing::Ref binarizer(new zxing::HybridBinarizer(source)); const zxing::Ref binary(new zxing::BinaryBitmap(binarizer)); const zxing::DecodeHints hints(1 << format); zxing::MultiFormatReader reader; const auto result = reader.decode(binary, hints); return QByteArray(result->getText()->getText().c_str(), result->getText()->getText().size()); } catch (const std::exception &e) { //qCDebug(Log) << e.what(); } return {}; } #endif QString BarcodeDecoder::decodePdf417(const QImage &img) { #ifdef HAVE_ZXING_ANY auto normalizedImg = img; if (normalizedImg.width() < normalizedImg.height()) { QTransform tf; tf.rotate(-90); normalizedImg = normalizedImg.transformed(tf); } const auto result = decodeString(normalizedImg, BarcodeFormat::PDF_417); if (!result.isEmpty()) { return result; } // try flipped around the x axis, zxing doesn't detect that, but it's e.g. encountered in SAS passes return decodeString(normalizedImg.transformed(QTransform{1, 0, 0, -1, 0, 0}), BarcodeFormat::PDF_417); #else Q_UNUSED(img); return {}; #endif } QString BarcodeDecoder::decodeAztec(const QImage &img) { #ifdef HAVE_ZXING_ANY return decodeString(img, BarcodeFormat::AZTEC); #else Q_UNUSED(img); return {}; #endif } QByteArray BarcodeDecoder::decodeAztecBinary(const QImage &img) { #ifdef HAVE_ZXING_ANY return decodeBinary(img, BarcodeFormat::AZTEC); #else Q_UNUSED(img); #endif return {}; } QString BarcodeDecoder::decodeQRCode(const QImage &img) { #ifdef HAVE_ZXING_ANY return decodeString(img, BarcodeFormat::QR_CODE); #else Q_UNUSED(img); return {}; #endif } diff --git a/src/barcodedecoder.h b/src/barcodedecoder.h index 671d5ee..2009305 100644 --- a/src/barcodedecoder.h +++ b/src/barcodedecoder.h @@ -1,49 +1,49 @@ /* 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 . + along with this program. If not, see . */ #ifndef KITINERARY_BARCODEDECODER_H #define KITINERARY_BARCODEDECODER_H #include "kitinerary_export.h" class QByteArray; class QImage; class QString; namespace KItinerary { /** Barcode decoding functions. * @note These functions are only functional if zxing is available. */ namespace BarcodeDecoder { /** Decode a PDF417 barcode. */ KITINERARY_EXPORT QString decodePdf417(const QImage &img); /** Decode an Aztec barcode containing text data. */ KITINERARY_EXPORT QString decodeAztec(const QImage &img); /** Decode an Aztec barcode containing binary data (such as UIC 918.3 codes). */ KITINERARY_EXPORT QByteArray decodeAztecBinary(const QImage &img); /** Decode an QRCode barcode containing text data. */ KITINERARY_EXPORT QString decodeQRCode(const QImage &img); } } #endif // KITINERARY_BARCODEDECODER_H diff --git a/src/datatypes/action.cpp b/src/datatypes/action.cpp index 867f11d..700c6e1 100644 --- a/src/datatypes/action.cpp +++ b/src/datatypes/action.cpp @@ -1,81 +1,81 @@ /* 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 . + along with this program. If not, see . */ #include "action.h" #include "datatypes_p.h" #include using namespace KItinerary; namespace KItinerary { class ActionPrivate : public QSharedData { KITINERARY_PRIVATE_BASE_GADGET(Action) public: QUrl target; }; KITINERARY_MAKE_BASE_CLASS(Action) KITINERARY_MAKE_PROPERTY(Action, QUrl, target, setTarget) KITINERARY_MAKE_OPERATOR(Action) class CancelActionPrivate : public ActionPrivate { KITINERARY_PRIVATE_GADGET(CancelAction) }; KITINERARY_MAKE_SUB_CLASS(CancelAction, Action) KITINERARY_MAKE_OPERATOR(CancelAction) class CheckInActionPrivate : public ActionPrivate { KITINERARY_PRIVATE_GADGET(CheckInAction) }; KITINERARY_MAKE_SUB_CLASS(CheckInAction, Action) KITINERARY_MAKE_OPERATOR(CheckInAction) class DownloadActionPrivate : public ActionPrivate { KITINERARY_PRIVATE_GADGET(DownloadAction) }; KITINERARY_MAKE_SUB_CLASS(DownloadAction, Action) KITINERARY_MAKE_OPERATOR(DownloadAction) class UpdateActionPrivate : public ActionPrivate { KITINERARY_PRIVATE_GADGET(UpdateAction) }; KITINERARY_MAKE_SUB_CLASS(UpdateAction, Action) KITINERARY_MAKE_OPERATOR(UpdateAction) class ViewActionPrivate : public ActionPrivate { KITINERARY_PRIVATE_GADGET(ViewAction) }; KITINERARY_MAKE_SUB_CLASS(ViewAction, Action) KITINERARY_MAKE_OPERATOR(ViewAction) } template <> KItinerary::ActionPrivate *QExplicitlySharedDataPointer::clone() { return d->clone(); } #include "moc_action.cpp" diff --git a/src/datatypes/action.h b/src/datatypes/action.h index 07b96dc..7895336 100644 --- a/src/datatypes/action.h +++ b/src/datatypes/action.h @@ -1,85 +1,85 @@ /* 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 . + along with this program. If not, see . */ #ifndef KITINERARY_ACTION_H #define KITINERARY_ACTION_H #include "kitinerary_export.h" #include "datatypes.h" class QUrl; namespace KItinerary { class ActionPrivate; /** Base class for actions. * @see https://schema.org/Action */ class KITINERARY_EXPORT Action { KITINERARY_BASE_GADGET(Action) KITINERARY_PROPERTY(QUrl, target, setTarget) protected: ///@cond internal QExplicitlySharedDataPointer d; ///@endcond }; /** Cancel action. * @see https://schema.org/CancelAction */ class KITINERARY_EXPORT CancelAction : public Action { KITINERARY_GADGET(CancelAction) }; /** Check-in action. * @see https://schema.org/CheckInAction */ class KITINERARY_EXPORT CheckInAction : public Action { KITINERARY_GADGET(CheckInAction) }; /** Download action. * @see https://schema.org/DownloadAction */ class KITINERARY_EXPORT DownloadAction : public Action { KITINERARY_GADGET(DownloadAction) }; /** Edit/update action. * @see https://schema.org/UpdateAction */ class KITINERARY_EXPORT UpdateAction : public Action { KITINERARY_GADGET(UpdateAction) }; /** View action. * @see https://schema.org/ViewAction */ class KITINERARY_EXPORT ViewAction : public Action { KITINERARY_GADGET(ViewAction) }; } #endif // KITINERARY_ACTION_H diff --git a/src/datatypes/brand.cpp b/src/datatypes/brand.cpp index 441c904..6e216bf 100644 --- a/src/datatypes/brand.cpp +++ b/src/datatypes/brand.cpp @@ -1,37 +1,37 @@ /* Copyright (C) 2018 Benjamin Port 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 . + along with this program. If not, see . */ #include "brand.h" #include "datatypes_p.h" using namespace KItinerary; namespace KItinerary { class BrandPrivate : public QSharedData { public: QString name; }; KITINERARY_MAKE_SIMPLE_CLASS(Brand) KITINERARY_MAKE_PROPERTY(Brand, QString, name, setName) KITINERARY_MAKE_OPERATOR(Brand) } #include "moc_brand.cpp" diff --git a/src/datatypes/brand.h b/src/datatypes/brand.h index 583f6aa..b5d0868 100644 --- a/src/datatypes/brand.h +++ b/src/datatypes/brand.h @@ -1,44 +1,44 @@ /* Copyright (C) 2018 Benjamin Port 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 . + along with this program. If not, see . */ #ifndef KITINERARY_BRAND_H #define KITINERARY_BRAND_H #include "kitinerary_export.h" #include "datatypes.h" namespace KItinerary { class BrandPrivate; /** A brand * @see https://schema.org/Brand */ class KITINERARY_EXPORT Brand { KITINERARY_GADGET(Brand) KITINERARY_PROPERTY(QString, name, setName) private: QExplicitlySharedDataPointer d; }; } Q_DECLARE_METATYPE(KItinerary::Brand) #endif // KITINERARY_BRAND_H diff --git a/src/datatypes/bustrip.cpp b/src/datatypes/bustrip.cpp index e3db050..36c62a3 100644 --- a/src/datatypes/bustrip.cpp +++ b/src/datatypes/bustrip.cpp @@ -1,55 +1,55 @@ /* 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 . + along with this program. If not, see . */ #include "bustrip.h" #include "datatypes_p.h" #include using namespace KItinerary; namespace KItinerary { class BusTripPrivate : public QSharedData { public: QString arrivalPlatform; BusStation arrivalBusStop; QDateTime arrivalTime; QString departurePlatform; BusStation departureBusStop; QDateTime departureTime; QString busName; QString busNumber; Organization provider; }; KITINERARY_MAKE_SIMPLE_CLASS(BusTrip) KITINERARY_MAKE_PROPERTY(BusTrip, QString, arrivalPlatform, setArrivalPlatform) KITINERARY_MAKE_PROPERTY(BusTrip, BusStation, arrivalBusStop, setArrivalBusStop) KITINERARY_MAKE_PROPERTY(BusTrip, QDateTime, arrivalTime, setArrivalTime) KITINERARY_MAKE_PROPERTY(BusTrip, QString, departurePlatform, setDeparturePlatform) KITINERARY_MAKE_PROPERTY(BusTrip, BusStation, departureBusStop, setDepartureBusStop) KITINERARY_MAKE_PROPERTY(BusTrip, QDateTime, departureTime, setDepartureTime) KITINERARY_MAKE_PROPERTY(BusTrip, QString, busName, setBusName) KITINERARY_MAKE_PROPERTY(BusTrip, QString, busNumber, setBusNumber) KITINERARY_MAKE_PROPERTY(BusTrip, Organization, provider, setProvider) KITINERARY_MAKE_OPERATOR(BusTrip) } #include "moc_bustrip.cpp" diff --git a/src/datatypes/bustrip.h b/src/datatypes/bustrip.h index 7237ac3..fec71c5 100644 --- a/src/datatypes/bustrip.h +++ b/src/datatypes/bustrip.h @@ -1,54 +1,54 @@ /* 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 . + along with this program. If not, see . */ #ifndef KITINERARY_BUSTRIP_H #define KITINERARY_BUSTRIP_H #include "kitinerary_export.h" #include "datatypes.h" #include "organization.h" #include "place.h" namespace KItinerary { class BusTripPrivate; /** A bus trip. * @see https://schema.org/BusTrip */ class KITINERARY_EXPORT BusTrip { KITINERARY_GADGET(BusTrip) KITINERARY_PROPERTY(QString, arrivalPlatform, setArrivalPlatform) // ### is this used? it's not in the schema KITINERARY_PROPERTY(KItinerary::BusStation, arrivalBusStop, setArrivalBusStop) KITINERARY_PROPERTY(QDateTime, arrivalTime, setArrivalTime) KITINERARY_PROPERTY(QString, departurePlatform, setDeparturePlatform) // ### not in the schema KITINERARY_PROPERTY(KItinerary::BusStation, departureBusStop, setDepartureBusStop) KITINERARY_PROPERTY(QDateTime, departureTime, setDepartureTime) KITINERARY_PROPERTY(QString, busName, setBusName) KITINERARY_PROPERTY(QString, busNumber, setBusNumber) KITINERARY_PROPERTY(KItinerary::Organization, provider, setProvider) private: QExplicitlySharedDataPointer d; }; } Q_DECLARE_METATYPE(KItinerary::BusTrip) #endif // KITINERARY_BUSTRIP_H diff --git a/src/datatypes/datatypes.h b/src/datatypes/datatypes.h index 04b041d..ef43463 100644 --- a/src/datatypes/datatypes.h +++ b/src/datatypes/datatypes.h @@ -1,102 +1,102 @@ /* 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 . + along with this program. If not, see . */ #ifndef KITINERARY_DATATYPES_H #define KITINERARY_DATATYPES_H #include #include #include #include class QString; namespace KItinerary { /** JSON-LD data type helper functions. */ namespace JsonLd { /** Returns @c true if @p value is of type @p T. */ template inline bool isA(const QVariant &value) { return value.userType() == qMetaTypeId(); } /** Checks if the given value can be up-cast to @p T */ template inline bool canConvert(const QVariant &value) { const auto mo = QMetaType(value.userType()).metaObject(); if (!mo) { return false; } return mo->inherits(&T::staticMetaObject); } /** Up-cast @p value to @p T. * @note This does not perform any safety checks! * @see canConvert */ template inline T convert(const QVariant &value) { return T(*static_cast(value.constData())); } } namespace detail { template struct parameter_type { using type = typename std::conditional::value, T, const T&>::type; }; } } #define KITINERARY_GADGET(Class) \ Q_GADGET \ Q_PROPERTY(QString className READ className STORED false CONSTANT) \ QString className() const; \ public: \ Class(); \ Class(const Class &other); \ ~Class(); \ Class& operator=(const Class &other); \ bool operator==(const Class &other) const; \ inline bool operator!=(const Class &other) const { return !(*this == other); } \ operator QVariant () const; \ private: #define KITINERARY_BASE_GADGET(Class) \ KITINERARY_GADGET(Class) \ protected: \ Class(Class ## Private *dd); \ private: #define KITINERARY_PROPERTY(Type, Name, SetName) \ Q_PROPERTY(Type Name READ Name WRITE SetName STORED true) \ public: \ Type Name() const; \ void SetName(KItinerary::detail::parameter_type::type value); \ private: #endif diff --git a/src/datatypes/datatypes_p.h b/src/datatypes/datatypes_p.h index e794e7f..f7a397d 100644 --- a/src/datatypes/datatypes_p.h +++ b/src/datatypes/datatypes_p.h @@ -1,159 +1,159 @@ /* 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 . + along with this program. If not, see . */ #ifndef KITINERARY_DATATYPES_P_H #define KITINERARY_DATATYPES_P_H #include namespace KItinerary { namespace detail { // Helper types for the auto-generated operator== // This is based on the approach described here https://woboq.com/blog/verdigris-implementation-tricks.html // numerical index of properties, done in a way that we can daisy-chain overloads with it template struct num : public num { static constexpr int value = N; static constexpr num prev() { return {}; } }; template <> struct num<0> { static constexpr int value = 0; }; // type tag, to avoid unwanted overload resolution on arguments other than num<> template struct tag {}; // SFINAE helper to determine if we have a polimorphic or a simple value type template struct base_type { template static typename U::super_type test(typename U::super_type*); template static T test(...); typedef decltype(test(nullptr)) type; static constexpr const bool is_valid = !std::is_same::value; }; // customization hook for comparisson for certain types template inline bool equals(typename parameter_type::type lhs, typename parameter_type::type rhs) { return lhs == rhs; } // QDateTime::operator== is true for two instances referring to the same point in time // we however want to know if two instances contain exactly the same information template <> inline bool equals(const QDateTime &lhs, const QDateTime &rhs) { return lhs.timeSpec() == rhs.timeSpec() && lhs == rhs; } // QString::operator== ignores null vs empty // we probably don't care either, but until that's decided this makes the existing tests pass template <> inline bool equals(const QString &lhs, const QString &rhs) { if (lhs.isEmpty() && rhs.isEmpty()) { return lhs.isNull() == rhs.isNull(); } return lhs == rhs; } } #define KITINERARY_PRIVATE_BASE_GADGET(Class) \ public: \ virtual ~ Class ## Private() = default; \ virtual Class ## Private * clone() const { \ return new Class ##Private(*this); \ } \ typedef Class ## Private base_type; \ typedef Class ## Private this_type; \ private: \ #define KITINERARY_PRIVATE_GADGET(Class) \ public: \ inline base_type* clone() const override { \ return new Class ## Private(*this); \ } \ typedef this_type super_type; \ typedef Class ## Private this_type; \ private: #define KITINERARY_MAKE_CLASS_IMPL(Class) \ Q_GLOBAL_STATIC_WITH_ARGS(QExplicitlySharedDataPointer, s_ ## Class ## _shared_null, (new Class ## Private)) \ Class::Class(const Class&) = default; \ Class::~Class() = default; \ Class& Class::operator=(const Class &other) { d = other.d; return *this; } \ QString Class::className() const { return QStringLiteral(#Class); } \ Class::operator QVariant() const { return QVariant::fromValue(*this); } \ static_assert(sizeof(Class) == sizeof(void*), "dptr must be the only member!"); \ namespace detail { \ static constexpr int property_counter(num<0>, tag) { return 1; } \ static constexpr bool property_equals(num<0>, tag, const Class ## Private *, const Class ## Private *) { return true; } \ } #define KITINERARY_MAKE_SIMPLE_CLASS(Class) \ KITINERARY_MAKE_CLASS_IMPL(Class) \ Class::Class() : d(*s_ ## Class ## _shared_null()) {} #define KITINERARY_MAKE_BASE_CLASS(Class) \ KITINERARY_MAKE_CLASS_IMPL(Class) \ Class::Class() : d(*s_ ## Class ## _shared_null()) {} \ Class::Class(Class ## Private *dd) : d(dd) {} #define KITINERARY_MAKE_SUB_CLASS(Class, Base) \ KITINERARY_MAKE_CLASS_IMPL(Class) \ Class::Class() : Base(s_ ## Class ## _shared_null()->data()) {} #define K_D(Class) auto d = static_cast(this->d.data()) #define KITINERARY_MAKE_PROPERTY(Class, Type, Name, SetName) \ Type Class::Name() const { return static_cast(d.data())->Name; } \ void Class::SetName(detail::parameter_type::type value) { \ if (detail::equals(static_cast(d.data())->Name, value)) { return; } \ d.detach(); \ static_cast(d.data())->Name = value; \ } \ namespace detail { \ static constexpr int property_counter(num(), tag())> n, tag) { return decltype(n)::value + 1; } \ static inline bool property_equals(num(), tag())> n, tag, const Class ## Private *lhs, const Class ## Private *rhs) \ { \ if (equals(lhs->Name, rhs->Name)) { return property_equals(n.prev(), tag(), lhs, rhs); } \ return false; \ } \ } #define KITINERARY_MAKE_OPERATOR(Class) \ bool Class::operator==(const Class &other) const \ { \ static_assert(detail::property_counter(detail::num<0>(), detail::tag()) == 1, "silence unused function warnings"); \ typedef Class ## Private this_type; \ const auto lhs = static_cast(d.data()); \ const auto rhs = static_cast(other.d.data()); \ if (lhs == rhs) { \ return true; \ } \ if (!detail::property_equals(detail::num<>(), detail::tag(), lhs, rhs)) { \ return false; \ } \ if (detail::base_type::is_valid) { \ typedef typename detail::base_type::type super_type; \ return detail::property_equals(detail::num<>(), detail::tag(), static_cast(lhs), static_cast(rhs)); \ } \ return true; \ } } #endif diff --git a/src/datatypes/event.cpp b/src/datatypes/event.cpp index 48cd319..920dab4 100644 --- a/src/datatypes/event.cpp +++ b/src/datatypes/event.cpp @@ -1,50 +1,50 @@ /* 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 . + along with this program. If not, see . */ #include "event.h" #include "datatypes_p.h" #include #include using namespace KItinerary; namespace KItinerary { class EventPrivate: public QSharedData { public: QString name; QUrl url; QDateTime startDate; QDateTime endDate; QDateTime doorTime; QVariant location; }; KITINERARY_MAKE_SIMPLE_CLASS(Event) KITINERARY_MAKE_PROPERTY(Event, QString, name, setName) KITINERARY_MAKE_PROPERTY(Event, QUrl, url, setUrl) KITINERARY_MAKE_PROPERTY(Event, QDateTime, startDate, setStartDate) KITINERARY_MAKE_PROPERTY(Event, QDateTime, endDate, setEndDate) KITINERARY_MAKE_PROPERTY(Event, QDateTime, doorTime, setDoorTime) KITINERARY_MAKE_PROPERTY(Event, QVariant, location, setLocation) KITINERARY_MAKE_OPERATOR(Event) } #include "moc_event.cpp" diff --git a/src/datatypes/event.h b/src/datatypes/event.h index 83ba259..d42e1ce 100644 --- a/src/datatypes/event.h +++ b/src/datatypes/event.h @@ -1,50 +1,50 @@ /* 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 . + along with this program. If not, see . */ #ifndef KITINERARY_EVENT_H #define KITINERARY_EVENT_H #include "kitinerary_export.h" #include "datatypes.h" namespace KItinerary { class EventPrivate; /** An event. * @see https://schema.org/Event * @see https://developers.google.com/gmail/markup/reference/event-reservation */ class KITINERARY_EXPORT Event { KITINERARY_GADGET(Event) KITINERARY_PROPERTY(QString, name, setName) KITINERARY_PROPERTY(QUrl, url, setUrl) KITINERARY_PROPERTY(QDateTime, startDate, setStartDate) KITINERARY_PROPERTY(QDateTime, endDate, setEndDate) KITINERARY_PROPERTY(QDateTime, doorTime, setDoorTime) KITINERARY_PROPERTY(QVariant, location, setLocation) private: QExplicitlySharedDataPointer d; }; } Q_DECLARE_METATYPE(KItinerary::Event) #endif // KITINERARY_EVENT_H diff --git a/src/datatypes/flight.cpp b/src/datatypes/flight.cpp index cfb4da8..3a2ecb0 100644 --- a/src/datatypes/flight.cpp +++ b/src/datatypes/flight.cpp @@ -1,84 +1,84 @@ /* 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 . + along with this program. If not, see . */ #include "flight.h" #include "place.h" #include "organization.h" #include "datatypes_p.h" #include #include using namespace KItinerary; namespace KItinerary { class FlightPrivate : public QSharedData { public: QString flightNumber; Airline airline; Airport departureAirport; QString departureGate; QString departureTerminal; QDateTime departureTime; Airport arrivalAirport; QString arrivalTerminal; QDateTime arrivalTime; QDateTime boardingTime; QDate departureDay; Organization provider; }; KITINERARY_MAKE_SIMPLE_CLASS(Flight) KITINERARY_MAKE_PROPERTY(Flight, QString, flightNumber, setFlightNumber) KITINERARY_MAKE_PROPERTY(Flight, Airline, airline, setAirline) KITINERARY_MAKE_PROPERTY(Flight, Airport, departureAirport, setDepartureAirport) KITINERARY_MAKE_PROPERTY(Flight, QString, departureGate, setDepartureGate) KITINERARY_MAKE_PROPERTY(Flight, QString, departureTerminal, setDepartureTerminal) KITINERARY_MAKE_PROPERTY(Flight, QDateTime, departureTime, setDepartureTime) KITINERARY_MAKE_PROPERTY(Flight, Airport, arrivalAirport, setArrivalAirport) KITINERARY_MAKE_PROPERTY(Flight, QDateTime, arrivalTime, setArrivalTime) KITINERARY_MAKE_PROPERTY(Flight, QString, arrivalTerminal, setArrivalTerminal) KITINERARY_MAKE_PROPERTY(Flight, QDateTime, boardingTime, setBoardingTime) KITINERARY_MAKE_PROPERTY(Flight, Organization, provider, setProvider) KITINERARY_MAKE_OPERATOR(Flight) QDate Flight::departureDay() const { if (d->departureDay.isValid()) { return d->departureDay; } // pre-1970 dates are used as transient state when we only know the time if (d->departureTime.isValid() && d->departureTime.date().year() > 1970) { return d->departureTime.date(); } if (d->boardingTime.isValid() && d->boardingTime.date().year() > 1970) { return d->boardingTime.date(); } return {}; } void Flight::setDepartureDay(const QDate &value) { d.detach(); d->departureDay = value; } } #include "moc_flight.cpp" diff --git a/src/datatypes/flight.h b/src/datatypes/flight.h index 3dd8628..882efdc 100644 --- a/src/datatypes/flight.h +++ b/src/datatypes/flight.h @@ -1,69 +1,69 @@ /* 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 . + along with this program. If not, see . */ #ifndef KITINERARY_FLIGHT_H #define KITINERARY_FLIGHT_H #include "kitinerary_export.h" #include "datatypes.h" #include "organization.h" #include "place.h" class QDateTime; namespace KItinerary { class FlightPrivate; /** A flight. * @see https://schema.org/Flight * @see https://developers.google.com/gmail/markup/reference/flight-reservation */ class KITINERARY_EXPORT Flight { KITINERARY_GADGET(Flight) KITINERARY_PROPERTY(QString, flightNumber, setFlightNumber) KITINERARY_PROPERTY(KItinerary::Airline, airline, setAirline) KITINERARY_PROPERTY(KItinerary::Airport, departureAirport, setDepartureAirport) KITINERARY_PROPERTY(QString, departureGate, setDepartureGate) KITINERARY_PROPERTY(QString, departureTerminal, setDepartureTerminal) KITINERARY_PROPERTY(QDateTime, departureTime, setDepartureTime) KITINERARY_PROPERTY(KItinerary::Airport, arrivalAirport, setArrivalAirport) KITINERARY_PROPERTY(QString, arrivalTerminal, setArrivalTerminal) KITINERARY_PROPERTY(QDateTime, arrivalTime, setArrivalTime) KITINERARY_PROPERTY(KItinerary::Organization, provider, setProvider) // Google extension for boarding pass data KITINERARY_PROPERTY(QDateTime, boardingTime, setBoardingTime) // KDE extensions /** The scheduled day of departure. * This is part of the unique identification of a flight and part of the IATA BCBP data. * This might be different from departureTime, which reflects the actual time of departure * and thus can in case of delays even move to a following day. */ KITINERARY_PROPERTY(QDate, departureDay, setDepartureDay) private: QExplicitlySharedDataPointer d; }; } Q_DECLARE_METATYPE(KItinerary::Flight) #endif // KITINERARY_FLIGHT_H diff --git a/src/datatypes/organization.cpp b/src/datatypes/organization.cpp index f316ab9..7a570ab 100644 --- a/src/datatypes/organization.cpp +++ b/src/datatypes/organization.cpp @@ -1,81 +1,81 @@ /* Copyright (C) 2018 Luca Beltrame 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 . + along with this program. If not, see . */ #include "organization.h" #include "datatypes_p.h" #include using namespace KItinerary; namespace KItinerary { class OrganizationPrivate: public QSharedData { KITINERARY_PRIVATE_BASE_GADGET(Organization) public: QString name; QString email; QString telephone; QUrl url; PostalAddress address; GeoCoordinates geo; }; KITINERARY_MAKE_BASE_CLASS(Organization) KITINERARY_MAKE_PROPERTY(Organization, QString, name, setName) KITINERARY_MAKE_PROPERTY(Organization, QString, email, setEmail) KITINERARY_MAKE_PROPERTY(Organization, QString, telephone, setTelephone) KITINERARY_MAKE_PROPERTY(Organization, QUrl, url, setUrl) KITINERARY_MAKE_PROPERTY(Organization, PostalAddress, address, setAddress) KITINERARY_MAKE_PROPERTY(Organization, KItinerary::GeoCoordinates, geo, setGeo) KITINERARY_MAKE_OPERATOR(Organization) class AirlinePrivate : public OrganizationPrivate { KITINERARY_PRIVATE_GADGET(Airline) public: QString iataCode; }; KITINERARY_MAKE_SUB_CLASS(Airline, Organization) KITINERARY_MAKE_PROPERTY(Airline, QString, iataCode, setIataCode) KITINERARY_MAKE_OPERATOR(Airline) class FoodEstablishmentPrivate: public OrganizationPrivate { KITINERARY_PRIVATE_GADGET(FoodEstablishment) }; KITINERARY_MAKE_SUB_CLASS(FoodEstablishment, Organization) KITINERARY_MAKE_OPERATOR(FoodEstablishment) class LodgingBusinessPrivate : public OrganizationPrivate { KITINERARY_PRIVATE_GADGET(LodgingBusiness) }; KITINERARY_MAKE_SUB_CLASS(LodgingBusiness, Organization) KITINERARY_MAKE_OPERATOR(LodgingBusiness) } template <> KItinerary::OrganizationPrivate *QExplicitlySharedDataPointer::clone() { return d->clone(); } #include "moc_organization.cpp" diff --git a/src/datatypes/organization.h b/src/datatypes/organization.h index aee260e..c44fe02 100644 --- a/src/datatypes/organization.h +++ b/src/datatypes/organization.h @@ -1,91 +1,91 @@ /* Copyright (C) 2018 Luca Beltrame 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 . + along with this program. If not, see . */ #ifndef KITINERARY_ORGANIZATION_H #define KITINERARY_ORGANIZATION_H #include "kitinerary_export.h" #include "datatypes.h" #include "place.h" class QUrl; namespace KItinerary { class OrganizationPrivate; /** An organization. * * This slightly deviates from the schema.org definition and also includes * properties of Place that its sub-classes need. This is a simplification * to avoid having to use multi-inheritance. * * @see https://schema.org/Organization */ class KITINERARY_EXPORT Organization { KITINERARY_BASE_GADGET(Organization) KITINERARY_PROPERTY(QString, name, setName) KITINERARY_PROPERTY(QString, email, setEmail) KITINERARY_PROPERTY(QString, telephone, setTelephone) KITINERARY_PROPERTY(QUrl, url, setUrl) KITINERARY_PROPERTY(KItinerary::PostalAddress, address, setAddress) KITINERARY_PROPERTY(KItinerary::GeoCoordinates, geo, setGeo) protected: ///@cond internal QExplicitlySharedDataPointer d; ///@endcond }; class AirlinePrivate; /** An airline. * @see https://schema.org/Airline */ class KITINERARY_EXPORT Airline : public Organization { KITINERARY_GADGET(Airline) KITINERARY_PROPERTY(QString, iataCode, setIataCode) }; /** Hotel. * @see https://schema.org/LodgingBusiness */ class KITINERARY_EXPORT LodgingBusiness: public Organization { KITINERARY_GADGET(LodgingBusiness) }; /** Food-related business (such as a restaurant, or a bakery). * @see https://schema.org/FoodEstablishment */ class KITINERARY_EXPORT FoodEstablishment: public Organization { KITINERARY_GADGET(FoodEstablishment) }; } // namespace KItinerary Q_DECLARE_METATYPE(KItinerary::Organization) Q_DECLARE_METATYPE(KItinerary::Airline) Q_DECLARE_METATYPE(KItinerary::FoodEstablishment) Q_DECLARE_METATYPE(KItinerary::LodgingBusiness) #endif // KITINERARY_ORGANIZATION_H diff --git a/src/datatypes/person.cpp b/src/datatypes/person.cpp index 524578b..900d064 100644 --- a/src/datatypes/person.cpp +++ b/src/datatypes/person.cpp @@ -1,43 +1,43 @@ /* 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 . + along with this program. If not, see . */ #include "person.h" #include "datatypes_p.h" using namespace KItinerary; namespace KItinerary { class PersonPrivate : public QSharedData { public: QString name; QString email; QString familyName; QString givenName; }; KITINERARY_MAKE_SIMPLE_CLASS(Person) KITINERARY_MAKE_PROPERTY(Person, QString, name, setName) KITINERARY_MAKE_PROPERTY(Person, QString, email, setEmail) KITINERARY_MAKE_PROPERTY(Person, QString, familyName, setFamilyName) KITINERARY_MAKE_PROPERTY(Person, QString, givenName, setGivenName) KITINERARY_MAKE_OPERATOR(Person) } #include "moc_person.cpp" diff --git a/src/datatypes/person.h b/src/datatypes/person.h index 8d96fe0..302d1b3 100644 --- a/src/datatypes/person.h +++ b/src/datatypes/person.h @@ -1,46 +1,46 @@ /* 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 . + along with this program. If not, see . */ #ifndef KITINERARY_PERSON_H #define KITINERARY_PERSON_H #include "kitinerary_export.h" #include "datatypes.h" namespace KItinerary { class PersonPrivate; /** A person * @see https://schema.org/Person */ class KITINERARY_EXPORT Person { KITINERARY_GADGET(Person) KITINERARY_PROPERTY(QString, name, setName) KITINERARY_PROPERTY(QString, email, setEmail) KITINERARY_PROPERTY(QString, familyName, setFamilyName) KITINERARY_PROPERTY(QString, givenName, setGivenName) private: QExplicitlySharedDataPointer d; }; } Q_DECLARE_METATYPE(KItinerary::Person) #endif // KITINERARY_PERSON_H diff --git a/src/datatypes/place.cpp b/src/datatypes/place.cpp index 2bd20d0..ce3b33a 100644 --- a/src/datatypes/place.cpp +++ b/src/datatypes/place.cpp @@ -1,146 +1,146 @@ /* 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 . + along with this program. If not, see . */ #include "place.h" #include "datatypes_p.h" #include using namespace KItinerary; namespace KItinerary { class GeoCoordinatesPrivate : public QSharedData { public: float latitude = NAN; float longitude = NAN; }; KITINERARY_MAKE_SIMPLE_CLASS(GeoCoordinates) KITINERARY_MAKE_PROPERTY(GeoCoordinates, float, latitude, setLatitude) KITINERARY_MAKE_PROPERTY(GeoCoordinates, float, longitude, setLongitude) GeoCoordinates::GeoCoordinates(float latitude, float longitude) : d(*s_GeoCoordinates_shared_null()) { d.detach(); d->latitude = latitude; d->longitude = longitude; } bool GeoCoordinates::isValid() const { return !std::isnan(d->latitude) && !std::isnan(d->longitude); } // implemented manually, as NAN != NAN bool GeoCoordinates::operator==(const GeoCoordinates &other) const { if (!isValid() && !other.isValid()) { return true; } return qFuzzyCompare(d->latitude, other.d->latitude) && qFuzzyCompare(d->longitude, other.d->longitude); } class PostalAddressPrivate : public QSharedData { public: QString streetAddress; QString addressLocality; QString postalCode; QString addressRegion; QString addressCountry; }; KITINERARY_MAKE_SIMPLE_CLASS(PostalAddress) KITINERARY_MAKE_PROPERTY(PostalAddress, QString, streetAddress, setStreetAddress) KITINERARY_MAKE_PROPERTY(PostalAddress, QString, addressLocality, setAddressLocality) KITINERARY_MAKE_PROPERTY(PostalAddress, QString, postalCode, setPostalCode) KITINERARY_MAKE_PROPERTY(PostalAddress, QString, addressRegion, setAddressRegion) KITINERARY_MAKE_PROPERTY(PostalAddress, QString, addressCountry, setAddressCountry) KITINERARY_MAKE_OPERATOR(PostalAddress) bool PostalAddress::isEmpty() const { return d->streetAddress.isEmpty() && d->addressLocality.isEmpty() && d->postalCode.isEmpty() && d->addressRegion.isEmpty() && d->addressCountry.isEmpty(); } class PlacePrivate : public QSharedData { KITINERARY_PRIVATE_BASE_GADGET(Place) public: QString name; PostalAddress address; GeoCoordinates geo; QString telephone; QString identifier; }; KITINERARY_MAKE_BASE_CLASS(Place) KITINERARY_MAKE_PROPERTY(Place, QString, name, setName) KITINERARY_MAKE_PROPERTY(Place, PostalAddress, address, setAddress) KITINERARY_MAKE_PROPERTY(Place, GeoCoordinates, geo, setGeo) KITINERARY_MAKE_PROPERTY(Place, QString, telephone, setTelephone) KITINERARY_MAKE_PROPERTY(Place, QString, identifier, setIdentifier) KITINERARY_MAKE_OPERATOR(Place) class AirportPrivate : public PlacePrivate { KITINERARY_PRIVATE_GADGET(Airport) public: QString iataCode; }; KITINERARY_MAKE_SUB_CLASS(Airport, Place) KITINERARY_MAKE_PROPERTY(Airport, QString, iataCode, setIataCode) KITINERARY_MAKE_OPERATOR(Airport) class TrainStationPrivate : public PlacePrivate { KITINERARY_PRIVATE_GADGET(TrainStation) }; KITINERARY_MAKE_SUB_CLASS(TrainStation, Place) KITINERARY_MAKE_OPERATOR(TrainStation) class BusStationPrivate : public PlacePrivate { KITINERARY_PRIVATE_GADGET(BusStation) }; KITINERARY_MAKE_SUB_CLASS(BusStation, Place) KITINERARY_MAKE_OPERATOR(BusStation) class TouristAttractionPrivate: public PlacePrivate { KITINERARY_PRIVATE_GADGET(TouristAttraction) }; KITINERARY_MAKE_SUB_CLASS(TouristAttraction, Place) KITINERARY_MAKE_OPERATOR(TouristAttraction) } template <> KItinerary::PlacePrivate *QExplicitlySharedDataPointer::clone() { return d->clone(); } #include "moc_place.cpp" diff --git a/src/datatypes/place.h b/src/datatypes/place.h index eb62a61..e9e41f0 100644 --- a/src/datatypes/place.h +++ b/src/datatypes/place.h @@ -1,145 +1,145 @@ /* 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 . + along with this program. If not, see . */ #ifndef KITINERARY_PLACE_H #define KITINERARY_PLACE_H #include "kitinerary_export.h" #include "datatypes.h" class QVariant; namespace KItinerary { class GeoCoordinatesPrivate; /** Geographic coordinates. * @see https://schema.org/GeoCoordinates */ class KITINERARY_EXPORT GeoCoordinates { KITINERARY_GADGET(GeoCoordinates) KITINERARY_PROPERTY(float, latitude, setLatitude) KITINERARY_PROPERTY(float, longitude, setLongitude) Q_PROPERTY(bool isValid READ isValid STORED false) public: GeoCoordinates(float latitude, float longitude); /** Returns @c true if both latitude and longitude are set and within * the valid range. */ bool isValid() const; private: QExplicitlySharedDataPointer d; }; class PostalAddressPrivate; /** Postal address. * @see https://schema.org/PostalAddress */ class KITINERARY_EXPORT PostalAddress { KITINERARY_GADGET(PostalAddress) KITINERARY_PROPERTY(QString, streetAddress, setStreetAddress) KITINERARY_PROPERTY(QString, addressLocality, setAddressLocality) KITINERARY_PROPERTY(QString, postalCode, setPostalCode) KITINERARY_PROPERTY(QString, addressRegion, setAddressRegion) /** The country this address is in, as ISO 3166-1 alpha 2 code. */ KITINERARY_PROPERTY(QString, addressCountry, setAddressCountry) Q_PROPERTY(bool isEmpty READ isEmpty STORED false) public: /** Returns @c true if there is no property set in this object. */ bool isEmpty() const; private: QExplicitlySharedDataPointer d; }; class PlacePrivate; /** Base class for places. * @see https://schema.org/Place */ class KITINERARY_EXPORT Place { KITINERARY_BASE_GADGET(Place) KITINERARY_PROPERTY(QString, name, setName) KITINERARY_PROPERTY(KItinerary::PostalAddress, address, setAddress) KITINERARY_PROPERTY(KItinerary::GeoCoordinates, geo, setGeo) KITINERARY_PROPERTY(QString, telephone, setTelephone) /** Identifier. * We use the following schemas currently: * - 'uic:', UIC station code (see https://www.wikidata.org/wiki/Property:P722) * - 'sncf:', Gares & Connextions ID, (see https://www.wikidata.org/wiki/Property:P3104), French train station identifier. * - 'ibnr:', Internationale Bahnhofsnummer, (see https://www.wikidata.org/wiki/Property:P954), German train station identifier. * @see http://schema.org/docs/datamodel.html#identifierBg */ KITINERARY_PROPERTY(QString, identifier, setIdentifier) protected: ///@cond internal QExplicitlySharedDataPointer d; ///@endcond }; /** Airport. * @see https://schema.org/Airport. */ class KITINERARY_EXPORT Airport : public Place { KITINERARY_GADGET(Airport) KITINERARY_PROPERTY(QString, iataCode, setIataCode) }; /** Train station. * @see https://schema.org/TrainStation */ class KITINERARY_EXPORT TrainStation : public Place { KITINERARY_GADGET(TrainStation) }; /** Bus station. * @see https://schema.org/BusStation */ class KITINERARY_EXPORT BusStation : public Place { KITINERARY_GADGET(BusStation) }; /** Tourist attraction (e.g. Museum, sight, etc.). * @see https://schema.org/TouristAttraction */ class KITINERARY_EXPORT TouristAttraction : public Place { KITINERARY_GADGET(TouristAttraction) }; } Q_DECLARE_METATYPE(KItinerary::Place) Q_DECLARE_METATYPE(KItinerary::GeoCoordinates) Q_DECLARE_METATYPE(KItinerary::PostalAddress) Q_DECLARE_METATYPE(KItinerary::Airport) Q_DECLARE_METATYPE(KItinerary::TrainStation) Q_DECLARE_METATYPE(KItinerary::BusStation) Q_DECLARE_METATYPE(KItinerary::TouristAttraction) #endif // KITINERARY_PLACE_H diff --git a/src/datatypes/rentalcar.cpp b/src/datatypes/rentalcar.cpp index 316474d..8c8935d 100644 --- a/src/datatypes/rentalcar.cpp +++ b/src/datatypes/rentalcar.cpp @@ -1,45 +1,45 @@ /* Copyright (C) 2018 Laurent Montel 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 . + along with this program. If not, see . */ #include "rentalcar.h" #include "datatypes_p.h" #include #include using namespace KItinerary; namespace KItinerary { class RentalCarPrivate: public QSharedData { public: QString name; QString model; Organization rentalCompany; Brand brand; }; KITINERARY_MAKE_SIMPLE_CLASS(RentalCar) KITINERARY_MAKE_PROPERTY(RentalCar, QString, name, setName) KITINERARY_MAKE_PROPERTY(RentalCar, QString, model, setModel) KITINERARY_MAKE_PROPERTY(RentalCar, Organization, rentalCompany, setRentalCompany) KITINERARY_MAKE_PROPERTY(RentalCar, Brand, brand, setBrand) KITINERARY_MAKE_OPERATOR(RentalCar) } #include "moc_rentalcar.cpp" diff --git a/src/datatypes/rentalcar.h b/src/datatypes/rentalcar.h index 1b72af4..39edf35 100644 --- a/src/datatypes/rentalcar.h +++ b/src/datatypes/rentalcar.h @@ -1,49 +1,49 @@ /* Copyright (C) 2018 Laurent Montel 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 . + along with this program. If not, see . */ #ifndef KITINERARY_RENTALCAR_H #define KITINERARY_RENTALCAR_H #include "kitinerary_export.h" #include "datatypes.h" #include "organization.h" #include "brand.h" namespace KItinerary { class RentalCarPrivate; /** A car rental. * @see https://developers.google.com/gmail/markup/reference/rental-car */ class KITINERARY_EXPORT RentalCar { KITINERARY_GADGET(RentalCar) KITINERARY_PROPERTY(QString, name, setName) KITINERARY_PROPERTY(QString, model, setModel) KITINERARY_PROPERTY(KItinerary::Organization, rentalCompany, setRentalCompany) KITINERARY_PROPERTY(KItinerary::Brand, brand, setBrand) private: QExplicitlySharedDataPointer d; }; } Q_DECLARE_METATYPE(KItinerary::RentalCar) #endif // KITINERARY_RENTALCAR_H diff --git a/src/datatypes/reservation.cpp b/src/datatypes/reservation.cpp index 56f0298..3770bfd 100644 --- a/src/datatypes/reservation.cpp +++ b/src/datatypes/reservation.cpp @@ -1,161 +1,161 @@ /* 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 . + along with this program. If not, see . */ #include "reservation.h" #include "organization.h" #include "datatypes_p.h" #include #include #include #include using namespace KItinerary; namespace KItinerary { class ReservationPrivate : public QSharedData { KITINERARY_PRIVATE_BASE_GADGET(Reservation) public: QString reservationNumber; QVariant reservationFor; QVariant reservedTicket; QVariant underName; QUrl url; QString pkpassPassTypeIdentifier; QString pkpassSerialNumber; Organization provider; QVariantList potentialAction; QDateTime modifiedTime; }; KITINERARY_MAKE_BASE_CLASS(Reservation) KITINERARY_MAKE_PROPERTY(Reservation, QString, reservationNumber, setReservationNumber) KITINERARY_MAKE_PROPERTY(Reservation, QVariant, reservationFor, setReservationFor) KITINERARY_MAKE_PROPERTY(Reservation, QVariant, reservedTicket, setReservedTicket) KITINERARY_MAKE_PROPERTY(Reservation, QVariant, underName, setUnderName) KITINERARY_MAKE_PROPERTY(Reservation, QUrl, url, setUrl) KITINERARY_MAKE_PROPERTY(Reservation, QString, pkpassPassTypeIdentifier, setPkpassPassTypeIdentifier) KITINERARY_MAKE_PROPERTY(Reservation, QString, pkpassSerialNumber, setPkpassSerialNumber) KITINERARY_MAKE_PROPERTY(Reservation, Organization, provider, setProvider) KITINERARY_MAKE_PROPERTY(Reservation, QVariantList, potentialAction, setPotentialAction) KITINERARY_MAKE_PROPERTY(Reservation, QDateTime, modifiedTime, setModifiedTime) KITINERARY_MAKE_OPERATOR(Reservation) class LodgingReservationPrivate : public ReservationPrivate { KITINERARY_PRIVATE_GADGET(LodgingReservation) public: QDateTime checkinTime; QDateTime checkoutTime; }; KITINERARY_MAKE_SUB_CLASS(LodgingReservation, Reservation) KITINERARY_MAKE_PROPERTY(LodgingReservation, QDateTime, checkinTime, setCheckinTime) KITINERARY_MAKE_PROPERTY(LodgingReservation, QDateTime, checkoutTime, setCheckoutTime) KITINERARY_MAKE_OPERATOR(LodgingReservation) class FlightReservationPrivate : public ReservationPrivate { KITINERARY_PRIVATE_GADGET(FlightReservation) public: QString passengerSequenceNumber; QString airplaneSeat; QString boardingGroup; }; KITINERARY_MAKE_SUB_CLASS(FlightReservation, Reservation) KITINERARY_MAKE_PROPERTY(FlightReservation, QString, passengerSequenceNumber, setPassengerSequenceNumber) KITINERARY_MAKE_PROPERTY(FlightReservation, QString, airplaneSeat, setAirplaneSeat) KITINERARY_MAKE_PROPERTY(FlightReservation, QString, boardingGroup, setBoardingGroup) KITINERARY_MAKE_OPERATOR(FlightReservation) class TrainReservationPrivate : public ReservationPrivate { KITINERARY_PRIVATE_GADGET(TrainReservation) }; KITINERARY_MAKE_SUB_CLASS(TrainReservation, Reservation) KITINERARY_MAKE_OPERATOR(TrainReservation) class BusReservationPrivate : public ReservationPrivate { KITINERARY_PRIVATE_GADGET(BusReservation) }; KITINERARY_MAKE_SUB_CLASS(BusReservation, Reservation) KITINERARY_MAKE_OPERATOR(BusReservation) class FoodEstablishmentReservationPrivate : public ReservationPrivate { KITINERARY_PRIVATE_GADGET(FoodEstablishmentReservation) public: QDateTime endTime; QDateTime startTime; int partySize = 0; }; KITINERARY_MAKE_SUB_CLASS(FoodEstablishmentReservation, Reservation) KITINERARY_MAKE_PROPERTY(FoodEstablishmentReservation, QDateTime, endTime, setEndTime) KITINERARY_MAKE_PROPERTY(FoodEstablishmentReservation, int, partySize, setPartySize) KITINERARY_MAKE_PROPERTY(FoodEstablishmentReservation, QDateTime, startTime, setStartTime) KITINERARY_MAKE_OPERATOR(FoodEstablishmentReservation) class EventReservationPrivate : public ReservationPrivate { KITINERARY_PRIVATE_GADGET(EventReservation) }; KITINERARY_MAKE_SUB_CLASS(EventReservation, Reservation) KITINERARY_MAKE_OPERATOR(EventReservation) class RentalCarReservationPrivate : public ReservationPrivate { KITINERARY_PRIVATE_GADGET(RentalCarReservation) public: QDateTime dropoffTime; QDateTime pickupTime; Place pickupLocation; Place dropoffLocation; }; KITINERARY_MAKE_SUB_CLASS(RentalCarReservation, Reservation) KITINERARY_MAKE_PROPERTY(RentalCarReservation, QDateTime, dropoffTime, setDropoffTime) KITINERARY_MAKE_PROPERTY(RentalCarReservation, QDateTime, pickupTime, setPickupTime) KITINERARY_MAKE_PROPERTY(RentalCarReservation, Place, pickupLocation, setPickupLocation) KITINERARY_MAKE_PROPERTY(RentalCarReservation, Place, dropoffLocation, setDropoffLocation) KITINERARY_MAKE_OPERATOR(RentalCarReservation) class TaxiReservationPrivate : public ReservationPrivate { KITINERARY_PRIVATE_GADGET(TaxiReservation) public: QDateTime pickupTime; Place pickupLocation; }; KITINERARY_MAKE_SUB_CLASS(TaxiReservation, Reservation) KITINERARY_MAKE_PROPERTY(TaxiReservation, QDateTime, pickupTime, setPickupTime) KITINERARY_MAKE_PROPERTY(TaxiReservation, Place, pickupLocation, setPickupLocation) KITINERARY_MAKE_OPERATOR(TaxiReservation) } template <> KItinerary::ReservationPrivate *QExplicitlySharedDataPointer::clone() { return d->clone(); } #include "moc_reservation.cpp" diff --git a/src/datatypes/reservation.h b/src/datatypes/reservation.h index 1595d81..780ef61 100644 --- a/src/datatypes/reservation.h +++ b/src/datatypes/reservation.h @@ -1,163 +1,163 @@ /* 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 . + along with this program. If not, see . */ #ifndef KITINERARY_RESERVATION_H #define KITINERARY_RESERVATION_H #include "kitinerary_export.h" #include "datatypes.h" #include "organization.h" #include "place.h" class QUrl; namespace KItinerary { class ReservationPrivate; /** Abstract base class for reservations. * @see https://schema.org/Reservation */ class KITINERARY_EXPORT Reservation { KITINERARY_BASE_GADGET(Reservation) KITINERARY_PROPERTY(QString, reservationNumber, setReservationNumber) KITINERARY_PROPERTY(QVariant, reservationFor, setReservationFor) KITINERARY_PROPERTY(QVariant, reservedTicket, setReservedTicket) KITINERARY_PROPERTY(QVariant, underName, setUnderName) KITINERARY_PROPERTY(KItinerary::Organization, provider, setProvider) KITINERARY_PROPERTY(QUrl, url, setUrl) KITINERARY_PROPERTY(QVariantList, potentialAction, setPotentialAction) KITINERARY_PROPERTY(QDateTime, modifiedTime, setModifiedTime) // KDE extensions /** Pass type identifier of an associated Apple Wallet boarding pass. * @see KPkPass::Pass::passTypeIdentifier */ KITINERARY_PROPERTY(QString, pkpassPassTypeIdentifier, setPkpassPassTypeIdentifier) /** Serial number of an associated Apple Wallet boarding pass. * @see KPkPass::Pass::serialNumber */ KITINERARY_PROPERTY(QString, pkpassSerialNumber, setPkpassSerialNumber) protected: ///@cond internal QExplicitlySharedDataPointer d; ///@endcond }; /** A hotel reservation. * @see https://schema.org/LodgingReservation * @see https://developers.google.com/gmail/markup/reference/hotel-reservation */ class KITINERARY_EXPORT LodgingReservation : public Reservation { KITINERARY_GADGET(LodgingReservation) KITINERARY_PROPERTY(QDateTime, checkinTime, setCheckinTime) KITINERARY_PROPERTY(QDateTime, checkoutTime, setCheckoutTime) }; /** A flight reservation. * @see https://schema.org/FlightReservation * @see https://developers.google.com/gmail/markup/reference/flight-reservation */ class KITINERARY_EXPORT FlightReservation : public Reservation { KITINERARY_GADGET(FlightReservation) /** Passenger sequnce number * Despite the name, do not expect this to be a number, infants without * their own seat get vendor-defined codes here for example. * @see https://schema.org/passengerSequenceNumber */ KITINERARY_PROPERTY(QString, passengerSequenceNumber, setPassengerSequenceNumber) // Google extensions KITINERARY_PROPERTY(QString, airplaneSeat, setAirplaneSeat) KITINERARY_PROPERTY(QString, boardingGroup, setBoardingGroup) }; /** A train reservation. * @see https://schema.org/TrainReservation */ class KITINERARY_EXPORT TrainReservation : public Reservation { KITINERARY_GADGET(TrainReservation) }; /** A bus reservation. * @see https://schema.org/BusReservation */ class KITINERARY_EXPORT BusReservation : public Reservation { KITINERARY_GADGET(BusReservation) }; /** A restaurant reservation. * @see https://schema.org/FoodEstablishmentReservation * @see https://developers.google.com/gmail/markup/reference/restaurant-reservation */ class KITINERARY_EXPORT FoodEstablishmentReservation : public Reservation { KITINERARY_GADGET(FoodEstablishmentReservation) KITINERARY_PROPERTY(QDateTime, endTime, setEndTime) KITINERARY_PROPERTY(int, partySize, setPartySize) KITINERARY_PROPERTY(QDateTime, startTime, setStartTime) }; /** An event reservation. * @see https://schema.org/EventReservation * @see https://developers.google.com/gmail/markup/reference/event-reservation */ class KITINERARY_EXPORT EventReservation : public Reservation { KITINERARY_GADGET(EventReservation) }; /** A Rental Car reservation. * @see https://developers.google.com/gmail/markup/reference/rental-car */ class KITINERARY_EXPORT RentalCarReservation : public Reservation { KITINERARY_GADGET(RentalCarReservation) KITINERARY_PROPERTY(QDateTime, dropoffTime, setDropoffTime) KITINERARY_PROPERTY(QDateTime, pickupTime, setPickupTime) KITINERARY_PROPERTY(KItinerary::Place, pickupLocation, setPickupLocation) KITINERARY_PROPERTY(KItinerary::Place, dropoffLocation, setDropoffLocation) }; /** A Taxi reservation. * @see https://schema.org/TaxiReservation */ class KITINERARY_EXPORT TaxiReservation : public Reservation { KITINERARY_GADGET(TaxiReservation) KITINERARY_PROPERTY(QDateTime, pickupTime, setPickupTime) KITINERARY_PROPERTY(KItinerary::Place, pickupLocation, setPickupLocation) }; } Q_DECLARE_METATYPE(KItinerary::FlightReservation) Q_DECLARE_METATYPE(KItinerary::LodgingReservation) Q_DECLARE_METATYPE(KItinerary::TrainReservation) Q_DECLARE_METATYPE(KItinerary::BusReservation) Q_DECLARE_METATYPE(KItinerary::FoodEstablishmentReservation) Q_DECLARE_METATYPE(KItinerary::EventReservation) Q_DECLARE_METATYPE(KItinerary::RentalCarReservation) #endif // KITINERARY_RESERVATION_H diff --git a/src/datatypes/taxi.cpp b/src/datatypes/taxi.cpp index 9f534eb..04fdd64 100644 --- a/src/datatypes/taxi.cpp +++ b/src/datatypes/taxi.cpp @@ -1,39 +1,39 @@ /* Copyright (C) 2018 Laurent Montel 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 . + along with this program. If not, see . */ #include "taxi.h" #include "datatypes_p.h" #include #include using namespace KItinerary; namespace KItinerary { class TaxiPrivate: public QSharedData { public: QString name; }; KITINERARY_MAKE_SIMPLE_CLASS(Taxi) KITINERARY_MAKE_PROPERTY(Taxi, QString, name, setName) KITINERARY_MAKE_OPERATOR(Taxi) } #include "moc_taxi.cpp" diff --git a/src/datatypes/taxi.h b/src/datatypes/taxi.h index f2cdc91..9f579cc 100644 --- a/src/datatypes/taxi.h +++ b/src/datatypes/taxi.h @@ -1,40 +1,40 @@ /* Copyright (C) 2018 Laurent Montel 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 . + along with this program. If not, see . */ #ifndef KITINERARY_TAXI_H #define KITINERARY_TAXI_H #include "kitinerary_export.h" #include "datatypes.h" namespace KItinerary { class TaxiPrivate; class KITINERARY_EXPORT Taxi { KITINERARY_GADGET(Taxi) KITINERARY_PROPERTY(QString, name, setName) private: QExplicitlySharedDataPointer d; }; } Q_DECLARE_METATYPE(KItinerary::Taxi) #endif // KITINERARY_TAXI_H diff --git a/src/datatypes/ticket.cpp b/src/datatypes/ticket.cpp index 5b45965..55fc35f 100644 --- a/src/datatypes/ticket.cpp +++ b/src/datatypes/ticket.cpp @@ -1,84 +1,84 @@ /* 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 . + along with this program. If not, see . */ #include "ticket.h" #include "datatypes_p.h" #include using namespace KItinerary; namespace KItinerary { class SeatPrivate : public QSharedData { public: QString seatNumber; QString seatRow; QString seatSection; }; KITINERARY_MAKE_SIMPLE_CLASS(Seat) KITINERARY_MAKE_PROPERTY(Seat, QString, seatNumber, setSeatNumber) KITINERARY_MAKE_PROPERTY(Seat, QString, seatRow, setSeatRow) KITINERARY_MAKE_PROPERTY(Seat, QString, seatSection, setSeatSection) KITINERARY_MAKE_OPERATOR(Seat) class TicketPrivate : public QSharedData { public: Seat ticketedSeat; QString ticketToken; }; KITINERARY_MAKE_SIMPLE_CLASS(Ticket) KITINERARY_MAKE_PROPERTY(Ticket, Seat, ticketedSeat, setTicketedSeat) KITINERARY_MAKE_PROPERTY(Ticket, QString, ticketToken, setTicketToken) KITINERARY_MAKE_OPERATOR(Ticket) Ticket::TicketTokenType Ticket::ticketTokenType() const { if (d->ticketToken.startsWith(QLatin1Literal("qrcode:"), Qt::CaseInsensitive)) { return QRCode; } else if (d->ticketToken.startsWith(QLatin1String("aztec"), Qt::CaseInsensitive)) { return AztecCode; } else if (d->ticketToken.startsWith(QLatin1String("barcode128:"), Qt::CaseInsensitive)) { return Code128; } else if (d->ticketToken.startsWith(QLatin1String("http"), Qt::CaseInsensitive)) { return Url; } return Unknown; } QString Ticket::ticketTokenData() const { if (d->ticketToken.startsWith(QLatin1Literal("qrcode:"), Qt::CaseInsensitive)) { return ticketToken().mid(7); } else if (d->ticketToken.startsWith(QLatin1String("azteccode:"), Qt::CaseInsensitive)) { return ticketToken().mid(10); } else if (d->ticketToken.startsWith(QLatin1String("aztecbin:"), Qt::CaseInsensitive)) { const auto b = QByteArray::fromBase64(d->ticketToken.midRef(9).toLatin1()); return QString::fromLatin1(b.constData(), b.size()); } else if (d->ticketToken.startsWith(QLatin1String("barcode128:"), Qt::CaseInsensitive)) { return ticketToken().mid(11); } return ticketToken(); } } #include "moc_ticket.cpp" diff --git a/src/datatypes/ticket.h b/src/datatypes/ticket.h index 89e8c59..bc10598 100644 --- a/src/datatypes/ticket.h +++ b/src/datatypes/ticket.h @@ -1,82 +1,82 @@ /* 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 . + along with this program. If not, see . */ #ifndef KITINERARY_TICKET_H #define KITINERARY_TICKET_H #include "kitinerary_export.h" #include "datatypes.h" namespace KItinerary { class SeatPrivate; /** A reserved seat. * @see https://schema.org/Seat */ class KITINERARY_EXPORT Seat { KITINERARY_GADGET(Seat) KITINERARY_PROPERTY(QString, seatNumber, setSeatNumber) KITINERARY_PROPERTY(QString, seatRow, setSeatRow) KITINERARY_PROPERTY(QString, seatSection, setSeatSection) private: QExplicitlySharedDataPointer d; }; class TicketPrivate; /** A booked ticket. * @see https://schema.org/Ticket */ class KITINERARY_EXPORT Ticket { KITINERARY_GADGET(Ticket) KITINERARY_PROPERTY(KItinerary::Seat, ticketedSeat, setTicketedSeat) /** The raw ticket token string. * @see ticketTokenType, ticketTokenData */ KITINERARY_PROPERTY(QString, ticketToken, setTicketToken) /** The type of the content in ticketToken. */ Q_PROPERTY(TicketTokenType ticketTokenType READ ticketTokenType STORED false) /** The ticket token payload for barcodes, otherwise the same as ticketToken. */ Q_PROPERTY(QString ticketTokenData READ ticketTokenData STORED false) public: /** The type of content in the ticketToken property. */ enum TicketTokenType { QRCode, ///< QR code AztecCode, ///< Aztec code Code128, ///< Code 128 barcode Url, ///< A download URL Unknown ///< Unknown or empty ticket token }; Q_ENUM(TicketTokenType) TicketTokenType ticketTokenType() const; QString ticketTokenData() const; private: QExplicitlySharedDataPointer d; }; } Q_DECLARE_METATYPE(KItinerary::Seat) Q_DECLARE_METATYPE(KItinerary::Ticket) #endif // KITINERARY_TICKET_H diff --git a/src/datatypes/traintrip.cpp b/src/datatypes/traintrip.cpp index 1f9a1f7..681c2db 100644 --- a/src/datatypes/traintrip.cpp +++ b/src/datatypes/traintrip.cpp @@ -1,57 +1,57 @@ /* 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 . + along with this program. If not, see . */ #include "traintrip.h" #include "organization.h" #include "place.h" #include "datatypes_p.h" #include using namespace KItinerary; namespace KItinerary { class TrainTripPrivate : public QSharedData { public: QString arrivalPlatform; TrainStation arrivalStation; QDateTime arrivalTime; QString departurePlatform; TrainStation departureStation; Organization provider; QDateTime departureTime; QString trainName; QString trainNumber; }; KITINERARY_MAKE_SIMPLE_CLASS(TrainTrip) KITINERARY_MAKE_PROPERTY(TrainTrip, QString, arrivalPlatform, setArrivalPlatform) KITINERARY_MAKE_PROPERTY(TrainTrip, TrainStation, arrivalStation, setArrivalStation) KITINERARY_MAKE_PROPERTY(TrainTrip, QDateTime, arrivalTime, setArrivalTime) KITINERARY_MAKE_PROPERTY(TrainTrip, QString, departurePlatform, setDeparturePlatform) KITINERARY_MAKE_PROPERTY(TrainTrip, TrainStation, departureStation, setDeparatureStation) KITINERARY_MAKE_PROPERTY(TrainTrip, QDateTime, departureTime, setDepartureTime) KITINERARY_MAKE_PROPERTY(TrainTrip, Organization, provider, setProvider) KITINERARY_MAKE_PROPERTY(TrainTrip, QString, trainName, setTrainName) KITINERARY_MAKE_PROPERTY(TrainTrip, QString, trainNumber, setTrainNumber) KITINERARY_MAKE_OPERATOR(TrainTrip) } #include "moc_traintrip.cpp" diff --git a/src/datatypes/traintrip.h b/src/datatypes/traintrip.h index ab3152b..6bd129c 100644 --- a/src/datatypes/traintrip.h +++ b/src/datatypes/traintrip.h @@ -1,56 +1,56 @@ /* 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 . + along with this program. If not, see . */ #ifndef KITINERARY_TRAINTRIP_H #define KITINERARY_TRAINTRIP_H #include "kitinerary_export.h" #include "datatypes.h" #include "organization.h" #include "place.h" class QDateTime; namespace KItinerary { class TrainTripPrivate; /** A train trip. * @see https://schema.org/TrainTrip */ class KITINERARY_EXPORT TrainTrip { KITINERARY_GADGET(TrainTrip) KITINERARY_PROPERTY(QString, arrivalPlatform, setArrivalPlatform) KITINERARY_PROPERTY(KItinerary::TrainStation, arrivalStation, setArrivalStation) KITINERARY_PROPERTY(QDateTime, arrivalTime, setArrivalTime) KITINERARY_PROPERTY(QString, departurePlatform, setDeparturePlatform) KITINERARY_PROPERTY(KItinerary::TrainStation, departureStation, setDeparatureStation) KITINERARY_PROPERTY(QDateTime, departureTime, setDepartureTime) KITINERARY_PROPERTY(QString, trainName, setTrainName) KITINERARY_PROPERTY(QString, trainNumber, setTrainNumber) KITINERARY_PROPERTY(KItinerary::Organization, provider, setProvider) private: QExplicitlySharedDataPointer d; }; } Q_DECLARE_METATYPE(KItinerary::TrainTrip) #endif // KITINERARY_TRAINTRIP_H diff --git a/src/datatypes/visit.cpp b/src/datatypes/visit.cpp index e7d5f55..36ebff9 100644 --- a/src/datatypes/visit.cpp +++ b/src/datatypes/visit.cpp @@ -1,44 +1,44 @@ /* Copyright (C) 2018 Luca Beltrame 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 . + along with this program. If not, see . */ #include "visit.h" #include "place.h" #include "datatypes_p.h" #include using namespace KItinerary; namespace KItinerary { class TouristAttractionVisitPrivate: public QSharedData { public: TouristAttraction touristAttraction; QDateTime arrivalTime; QDateTime departureTime; }; KITINERARY_MAKE_SIMPLE_CLASS(TouristAttractionVisit) KITINERARY_MAKE_PROPERTY(TouristAttractionVisit, TouristAttraction, touristAttraction, setTouristAttraction) KITINERARY_MAKE_PROPERTY(TouristAttractionVisit, QDateTime, arrivalTime, setArrivalTime) KITINERARY_MAKE_PROPERTY(TouristAttractionVisit, QDateTime, departureTime, setDepartureTime) KITINERARY_MAKE_OPERATOR(TouristAttractionVisit) } #include "moc_visit.cpp" diff --git a/src/datatypes/visit.h b/src/datatypes/visit.h index 6f15db5..fa31895 100644 --- a/src/datatypes/visit.h +++ b/src/datatypes/visit.h @@ -1,45 +1,45 @@ /* Copyright (C) 2018 Luca Beltrame 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 . + along with this program. If not, see . */ #ifndef KITINERARY_VISIT_H #define KITINERARY_VISIT_H #include "kitinerary_export.h" #include "datatypes.h" #include "place.h" namespace KItinerary { class TouristAttractionVisitPrivate; class KITINERARY_EXPORT TouristAttractionVisit { KITINERARY_GADGET(TouristAttractionVisit) KITINERARY_PROPERTY(KItinerary::TouristAttraction, touristAttraction, setTouristAttraction) KITINERARY_PROPERTY(QDateTime, arrivalTime, setArrivalTime) KITINERARY_PROPERTY(QDateTime, departureTime, setDepartureTime) private: QExplicitlySharedDataPointer d; }; } // namespace KItinerary Q_DECLARE_METATYPE(KItinerary::TouristAttractionVisit) #endif // KITINERARY_VISIT_H diff --git a/src/genericpdfextractor.cpp b/src/genericpdfextractor.cpp index 8e2fcf3..1cd1ddc 100644 --- a/src/genericpdfextractor.cpp +++ b/src/genericpdfextractor.cpp @@ -1,195 +1,195 @@ /* 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 . + along with this program. If not, see . */ #include "genericpdfextractor.h" #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 pixels, assuming landscape orientation MinSourceImageHeight = 10, MinSourceImageWidth = 30, MaxSourceImageHeight = 1000, // TODO what's a realisitic value here? MaxSourceImageWidth = 2000, // unit is 1/72 inch, assuming landscape orientation MinTargetImageHeight = 30, MinTargetImageWidth = 72, MaxTargetImageHeight = 252, MaxTargetImageWidth = 252, }; GenericPdfExtractor::GenericPdfExtractor() = default; GenericPdfExtractor::~GenericPdfExtractor() = default; void GenericPdfExtractor::setContextDate(const QDateTime &dt) { m_contextDate = dt; } void GenericPdfExtractor::extract(PdfDocument *doc, QJsonArray &result) { m_unrecognizedBarcodes.clear(); // stay away from documents that are atypically large for what we are looking for // that's just unecessarily eating up resources if (doc->pageCount() > MaxPageCount || doc->fileSize() > MaxFileSize) { return; } 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) { const auto img = page.image(j); if (m_imageIds.find(img.objectId()) != m_imageIds.end()) { continue; } // image source size sanity checks if (std::min(img.sourceWidth(), img.sourceHeight()) < MinSourceImageHeight || std::max(img.sourceWidth(), img.sourceHeight()) < MinSourceImageWidth || img.sourceHeight() > MaxSourceImageHeight || img.sourceWidth() > MaxSourceImageWidth) { continue; } // image target size checks const auto targetRect = img.transform().map(QRectF(0, 0, 1, -1)).boundingRect(); if (std::min(targetRect.width(), targetRect.height()) < MinTargetImageHeight || std::max(targetRect.width(), targetRect.height()) < MinTargetImageWidth || targetRect.height() > MaxTargetImageHeight || targetRect.width() > MaxTargetImageWidth) { continue; } extractImage(img, result); m_imageIds.insert(img.objectId()); } } } QStringList GenericPdfExtractor::unrecognizedBarcodes() const { return m_unrecognizedBarcodes; } void GenericPdfExtractor::extractImage(const PdfImage &img, QJsonArray &result) { const auto aspectRatio = img.width() < img.height() ? (float)img.height() / (float)img.width() : (float)img.width() / (float)img.height(); // almost square, assume Aztec (or QR, which we don't handle here yet) if (aspectRatio < 1.2f) { const auto b = BarcodeDecoder::decodeAztecBinary(img.image()); if (Uic9183Parser::maybeUic9183(b)) { extractUic9183(b, result); } else { extractBarcode(QString::fromUtf8(b), result); } } // rectangular with medium aspect ratio, assume PDF 417 if (aspectRatio > 1.5 && aspectRatio < 6) { const auto s = BarcodeDecoder::decodePdf417(img.image()); extractBarcode(s, result); } } void GenericPdfExtractor::extractBarcode(const QString &code, QJsonArray &result) { if (IataBcbpParser::maybeIataBcbp(code)) { const auto res = IataBcbpParser::parse(code, m_contextDate.date()); const auto jsonLd = JsonLdDocument::toJson(res); std::copy(jsonLd.begin(), jsonLd.end(), std::back_inserter(result)); } m_unrecognizedBarcodes.push_back(code); } void GenericPdfExtractor::extractUic9183(const QByteArray &data, QJsonArray &result) { Uic9183Parser p; p.setContextDate(m_contextDate); p.parse(data); if (!p.isValid()) { return; } QJsonObject org; org.insert(QLatin1String("@type"), QLatin1String("Organization")); org.insert(QLatin1String("identifier"), QString(QLatin1String("uic:") + p.carrierId())); QJsonObject trip; trip.insert(QLatin1String("@type"), QLatin1String("TrainTrip")); trip.insert(QLatin1String("provider"), org); QJsonObject seat; seat.insert(QLatin1String("@type"), QLatin1String("Seat")); const auto rct2 = p.rct2Ticket(); if (rct2.isValid()) { switch (rct2.type()) { case Rct2Ticket::Reservation: { QJsonObject dep; dep.insert(QLatin1String("@type"), QLatin1String("TrainStation")); dep.insert(QLatin1String("name"), rct2.outboundDepartureStation()); trip.insert(QLatin1String("departureStation"), dep); trip.insert(QLatin1String("departureTime"), rct2.outboundDepartureTime().toString(Qt::ISODate)); QJsonObject arr; arr.insert(QLatin1String("@type"), QLatin1String("TrainStation")); arr.insert(QLatin1String("name"), rct2.outboundArrivalStation()); trip.insert(QLatin1String("arrivalStation"), arr); trip.insert(QLatin1String("arrivalTime"), rct2.outboundArrivalTime().toString(Qt::ISODate)); trip.insert(QLatin1String("trainNumber"), rct2.trainNumber()); seat.insert(QLatin1String("seatSection"), rct2.coachNumber()); seat.insert(QLatin1String("seatNumber"), rct2.seatNumber()); break; } default: break; } } QJsonObject ticket; ticket.insert(QLatin1String("@type"), QLatin1String("Ticket")); ticket.insert(QLatin1String("ticketedSeat"), seat); QJsonObject res; res.insert(QLatin1String("@type"), QLatin1String("TrainReservation")); res.insert(QLatin1String("reservationFor"), trip); res.insert(QLatin1String("reservationNumber"), p.pnr()); res.insert(QLatin1String("reservedTicket"), ticket); result.push_back(res); } diff --git a/src/genericpdfextractor.h b/src/genericpdfextractor.h index 3a5f48c..792034d 100644 --- a/src/genericpdfextractor.h +++ b/src/genericpdfextractor.h @@ -1,68 +1,68 @@ /* 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 . + along with this program. If not, see . */ #ifndef KITINERARY_GENERICPDFEXTRACTOR_H #define KITINERARY_GENERICPDFEXTRACTOR_H #include #include #include class QJsonArray; class QString; namespace KItinerary { class PdfDocument; class PdfImage; /** Generic extractor for PDF documents. * This is applied to all PDF documents and searches for * barcodes we can recognize. * * @internal */ class GenericPdfExtractor { public: GenericPdfExtractor(); ~GenericPdfExtractor(); GenericPdfExtractor(const GenericPdfExtractor&) = delete; /** Set the context date used for extraction. */ void setContextDate(const QDateTime &dt); /** Try to extract the given document. */ void extract(PdfDocument *doc, QJsonArray &result); /** Barcodes that we couldn't recognize, for use with custom extractors. */ QStringList unrecognizedBarcodes() const; private: void extractImage(const PdfImage &img, QJsonArray &result); void extractBarcode(const QString &code, QJsonArray &result); void extractUic9183(const QByteArray &data, QJsonArray &result); QDateTime m_contextDate; QStringList m_unrecognizedBarcodes; std::unordered_set m_imageIds; }; } #endif // KITINERARY_GENERICPDFEXTRACTOR_H diff --git a/src/htmldocument.cpp b/src/htmldocument.cpp index 74c34ea..750f053 100644 --- a/src/htmldocument.cpp +++ b/src/htmldocument.cpp @@ -1,342 +1,342 @@ /* 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 . + along with this program. If not, see . */ #include "config-kitinerary.h" #include "htmldocument.h" #include #include #ifdef HAVE_LIBXML2 #include #include #endif using namespace KItinerary; namespace KItinerary { class HtmlDocumentPrivate { public: #ifdef HAVE_LIBXML2 ~HtmlDocumentPrivate() { xmlFreeDoc(m_doc); } xmlDocPtr m_doc; #endif }; } HtmlElement::HtmlElement() : d(nullptr) { } HtmlElement::~HtmlElement() = default; #ifdef HAVE_LIBXML2 HtmlElement::HtmlElement(xmlNode *dd) : d(dd) { } #endif HtmlDocument::HtmlDocument(QObject *parent) : QObject(parent) , d(new HtmlDocumentPrivate) { } HtmlDocument::~HtmlDocument() = default; bool HtmlElement::isNull() const { return d == nullptr; } QString HtmlElement::name() const { #ifdef HAVE_LIBXML2 if (d) { return QString::fromUtf8(reinterpret_cast(d->name)); } #endif return {}; } QString HtmlElement::attribute(const QString &attr) const { #ifdef HAVE_LIBXML2 if (d) { const auto val = std::unique_ptr(xmlGetProp(d, reinterpret_cast(attr.toUtf8().constData())), xmlFree); return QString::fromUtf8(reinterpret_cast(val.get())); } #else Q_UNUSED(attr); #endif return {}; } HtmlElement HtmlElement::parent() const { #ifdef HAVE_LIBXML2 if (d && d->parent && d->parent->type == XML_ELEMENT_NODE) { return HtmlElement(d->parent); } #endif return {}; } HtmlElement HtmlElement::firstChild() const { #ifdef HAVE_LIBXML2 if (d) { return HtmlElement(xmlFirstElementChild(d)); } #endif return {}; } HtmlElement HtmlElement::nextSibling() const { #ifdef HAVE_LIBXML2 if (d) { return HtmlElement(xmlNextElementSibling(d)); } #endif return {}; } QString HtmlElement::content() const { #ifdef HAVE_LIBXML2 if (!d) { return {}; } QString s; auto node = d->children; while (node) { switch (node->type) { case XML_TEXT_NODE: case XML_CDATA_SECTION_NODE: s += QString::fromUtf8(reinterpret_cast(node->content)); break; case XML_ENTITY_REF_NODE: { const auto val = std::unique_ptr(xmlNodeGetContent(node), xmlFree); s += QString::fromUtf8(reinterpret_cast(val.get())); break; } case XML_ELEMENT_NODE: if (qstricmp(reinterpret_cast(node->name), "br") == 0) { s += QLatin1Char('\n'); } break; default: break; } node = node->next; } // convert non-breaking spaces to normal ones, technically not correct // but way too often this confuses our regular expressions s.replace(QString::fromUtf8(" "), QLatin1String(" ")); return s.trimmed(); #endif return {}; } #ifdef HAVE_LIBXML2 static void recursiveContent(_xmlNode *node, QString &s) { switch (node->type) { case XML_TEXT_NODE: case XML_CDATA_SECTION_NODE: s += QString::fromUtf8(reinterpret_cast(node->content)); return; case XML_ENTITY_REF_NODE: { const auto val = std::unique_ptr(xmlNodeGetContent(node), xmlFree); s += QString::fromUtf8(reinterpret_cast(val.get())); break; } case XML_ELEMENT_NODE: { if (qstricmp(reinterpret_cast(node->name), "br") == 0) { s += QLatin1Char('\n'); } else { s += QLatin1Char(' '); } break; } case XML_ATTRIBUTE_NODE: case XML_COMMENT_NODE: return; default: break; } auto child = node->children; while (child) { recursiveContent(child, s); child = child->next; } } #endif QString HtmlElement::recursiveContent() const { #ifdef HAVE_LIBXML2 if (!d) { return {}; } QString s; ::recursiveContent(d, s); // convert non-breaking spaces to normal ones, technically not correct // but way too often this confuses our regular expressions s.replace(QString::fromUtf8(" "), QLatin1String(" ")); return s.trimmed(); #else return {}; #endif } QVariant HtmlElement::eval(const QString &xpath) const { #ifdef HAVE_LIBXML2 if (!d) { return {}; } const auto ctx = std::unique_ptr(xmlXPathNewContext(d->doc), &xmlXPathFreeContext); if (!ctx) { return {}; } xmlXPathSetContextNode(d, ctx.get()); const auto xpathObj = std::unique_ptr(xmlXPathEvalExpression(reinterpret_cast(xpath.toUtf8().constData()), ctx.get()), &xmlXPathFreeObject); if (!xpathObj) { return {}; } switch (xpathObj->type) { case XPATH_NODESET: { QVariantList l; if (!xpathObj->nodesetval) { return l; } l.reserve(xpathObj->nodesetval->nodeNr); for (int i = 0; i < xpathObj->nodesetval->nodeNr; ++i) { l.push_back(QVariant::fromValue(xpathObj->nodesetval->nodeTab[i])); } return l; } case XPATH_BOOLEAN: return QVariant::fromValue(xpathObj->boolval); case XPATH_NUMBER: return xpathObj->floatval; case XPATH_STRING: return QString::fromUtf8(reinterpret_cast(xpathObj->stringval)); default: return {}; } #else Q_UNUSED(xpath); #endif return {}; } bool HtmlElement::hasAttribute(const QString& attr) const { #ifdef HAVE_LIBXML2 if (!d) { return false; } auto attribute = d->properties; while(attribute) { if (qstricmp(attr.toUtf8().constData(), reinterpret_cast(attribute->name)) == 0) { return true; } attribute = attribute->next; } #else Q_UNUSED(attr); #endif return false; } QStringList HtmlElement::attributes() const { QStringList l; #ifdef HAVE_LIBXML2 if (!d) { return l; } auto attribute = d->properties; while(attribute) { l.push_back(QString::fromUtf8(reinterpret_cast(attribute->name))); attribute = attribute->next; } #endif return l; } HtmlElement HtmlDocument::root() const { #ifdef HAVE_LIBXML2 if (!d->m_doc) { return {}; } return HtmlElement(xmlDocGetRootElement(d->m_doc)); #else return {}; #endif } QVariant HtmlDocument::eval(const QString &xpath) const { return root().eval(xpath); } HtmlDocument* HtmlDocument::fromData(const QByteArray &data, QObject *parent) { #ifdef HAVE_LIBXML2 auto tree = htmlReadMemory(data.constData(), data.size(), nullptr, nullptr, HTML_PARSE_RECOVER | HTML_PARSE_NOERROR | HTML_PARSE_NOWARNING | HTML_PARSE_NOBLANKS | HTML_PARSE_NONET | HTML_PARSE_COMPACT); if (!tree) { return nullptr; } auto doc = new HtmlDocument(parent); doc->d->m_doc = tree; return doc; #else Q_UNUSED(data); Q_UNUSED(parent); return nullptr; #endif } #include "moc_htmldocument.cpp" diff --git a/src/htmldocument.h b/src/htmldocument.h index 477e5fa..f36876b 100644 --- a/src/htmldocument.h +++ b/src/htmldocument.h @@ -1,114 +1,114 @@ /* 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 . + along with this program. If not, see . */ #ifndef KITINERARY_HTMLDOCUMENT_H #define KITINERARY_HTMLDOCUMENT_H #include "kitinerary_export.h" #include #include struct _xmlNode; namespace KItinerary { class HtmlDocument; class HtmlDocumentPrivate; /** HTML document element. */ class KITINERARY_EXPORT HtmlElement { Q_GADGET Q_PROPERTY(bool isNull READ isNull) Q_PROPERTY(QString name READ name) Q_PROPERTY(KItinerary::HtmlElement parent READ parent) Q_PROPERTY(KItinerary::HtmlElement firstChild READ firstChild) Q_PROPERTY(KItinerary::HtmlElement nextSibling READ nextSibling) Q_PROPERTY(QString content READ content) Q_PROPERTY(QString recursiveContent READ recursiveContent) public: HtmlElement(); ~HtmlElement(); /** Check if the element is null. */ bool isNull() const; /** The element name. */ QString name() const; /** Value of the attribute @p attr. */ Q_INVOKABLE QString attribute(const QString &attr) const; /** Returns the parent element of this node. */ HtmlElement parent() const; /** Returns the first child element of this node. */ HtmlElement firstChild() const; /** Returns the next sibling element of this node. */ HtmlElement nextSibling() const; /** Returns the content of this element. * That is, all text nodes that are immediate children of this element. * The content is trimmed from leading or trailing whitespaces. */ QString content() const; /** Returns the content of this element and all its children. */ QString recursiveContent() const; /** Checks whether an attribute with name @p attr exists. */ bool hasAttribute(const QString &attr) const; /** Returns the list of all attributes of this node. */ QStringList attributes() const; /** Evaluate an XPath expression relative to this node. */ Q_INVOKABLE QVariant eval(const QString &xpath) const; private: friend class HtmlDocument; HtmlElement(_xmlNode *dd); _xmlNode *d; }; /** HTML document for extraction. * This is used as input for ExtractorEngine and the JS extractor scripts. * @note This class is only functional if libxml is available as a dependency, * otherwise all methods return empty values. */ class KITINERARY_EXPORT HtmlDocument : public QObject { Q_OBJECT Q_PROPERTY(KItinerary::HtmlElement root READ root) public: ~HtmlDocument(); /** Creates a HtmlDocument from the given raw data. * @returns @c nullptr if loading fails or libxml was not found. */ static HtmlDocument* fromData(const QByteArray &data, QObject *parent = nullptr); /** Returns the root element of the document. */ HtmlElement root() const; /** Evaluate an XPath expression relative to the document root. */ Q_INVOKABLE QVariant eval(const QString &xpath) const; private: explicit HtmlDocument(QObject *parent = nullptr); std::unique_ptr d; }; } Q_DECLARE_METATYPE(KItinerary::HtmlElement) #endif // KITINERARY_HTMLDOCUMENT_H diff --git a/src/iatabcbpparser.cpp b/src/iatabcbpparser.cpp index 8993a4a..92b5f59 100644 --- a/src/iatabcbpparser.cpp +++ b/src/iatabcbpparser.cpp @@ -1,247 +1,247 @@ /* 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 . + along with this program. If not, see . */ #include "iatabcbpparser.h" #include "logging.h" #include #include #include #include #include #include #include #include using namespace KItinerary; namespace KItinerary { enum Constants { UniqueMandatorySize = 23, RepeatedMandatorySize = 37, FormatCode = 'M', BeginOfVersionNumber = '>' }; static QStringRef stripLeadingZeros(const QStringRef &s) { const auto it = std::find_if(s.begin(), s.end(), [](const QChar &c) { return c != QLatin1Char('0'); }); const auto d = std::distance(s.begin(), it); return s.mid(d); } static int readHexValue(const QStringRef &s, int width) { return s.mid(0, width).toInt(nullptr, 16); } static int parseRepeatedMandatorySection(const QStringRef& msg, FlightReservation& res) { if (msg.size() < 24) { // pre-checking data, technically incomplete, but we can make use of this nevertheless qCWarning(Log) << "IATA BCBP repeated mandatory section too short"; return -1; // error } res.setReservationNumber(msg.mid(0, 7).trimmed().toString()); Flight flight; Airport airport; airport.setIataCode(msg.mid(7, 3).toString()); flight.setDepartureAirport(airport); airport.setIataCode(msg.mid(10, 3).toString()); flight.setArrivalAirport(airport); Airline airline; airline.setIataCode(msg.mid(13, 3).trimmed().toString()); flight.setAirline(airline); flight.setFlightNumber(stripLeadingZeros(msg.mid(16, 5).trimmed()).toString()); // 3x Date of flight, as days since Jan 1st // we don't know the year here, so use 1970, will be filled up or discarded by the caller const auto days = msg.mid(21, 3).toInt() - 1; flight.setDepartureDay(QDate(1970, 1, 1).addDays(days)); res.setReservationFor(flight); if (msg.size() < RepeatedMandatorySize) { return 0; } // 1x Compartment code res.setAirplaneSeat(stripLeadingZeros(msg.mid(25, 4)).trimmed().toString()); res.setPassengerSequenceNumber(stripLeadingZeros(msg.mid(29, 5)).trimmed().toString()); // 1x Passenger status // field size of conditional section + airline use section return readHexValue(msg.mid(35), 2); } } QVector IataBcbpParser::parse(const QString& message, const QDate &externalIssueDate) { if (!IataBcbpParser::maybeIataBcbp(message)) { qCWarning(Log) << "IATA BCBP code too short for unique mandatory section, or invalid mandatory section format"; return {}; } // parse unique mandatory section const auto legCount = message.at(1).toLatin1() - '0'; QVector result; result.reserve(legCount); FlightReservation res1; { Person person; const auto fullName = message.midRef(2, 20).trimmed(); const auto idx = fullName.indexOf(QLatin1Char('/')); if (idx > 0 && idx < fullName.size() - 1) { person.setFamilyName(fullName.left(idx).toString()); person.setGivenName(fullName.mid(idx + 1).toString()); } else { person.setName(fullName.toString()); } res1.setUnderName(person); } { Ticket ticket; ticket.setTicketToken(QStringLiteral("aztecCode:") + message); res1.setReservedTicket(ticket); } const auto varSize = parseRepeatedMandatorySection(message.midRef(UniqueMandatorySize), res1); if (varSize < 0) { // parser error return {}; } int index = UniqueMandatorySize + RepeatedMandatorySize; auto issueDate = externalIssueDate; if (varSize > 0) { if (message.size() < (index + varSize)) { qCWarning(Log) << "IATA BCBP code too short for conditional section in first leg" << varSize << message.size(); return {}; } // parse unique conditional section, if there is one, otherwise we skip all of this assuming "for airline use" if (message.at(index) == QLatin1Char(BeginOfVersionNumber)) { // 1x version number // 2x field size of unique conditional section const auto uniqCondSize = readHexValue(message.midRef(index + 2), 2); if (uniqCondSize + 4 > varSize) { qCWarning(Log) << "IATA BCBP unique conditional section has invalid size" << varSize << uniqCondSize; return {}; } // 1x passenger description // 1x source of checking // 1x source of boarding pass issuance // 4x date of issue of boarding pass // this only contains the last digit of the year (sic), but we assume it to be in the past // so this still gives us a 10 year range of correctly determined times if (uniqCondSize >= 11 && externalIssueDate.isValid() && message.at(index + 7).isDigit() && message.at(index + 10).isDigit()) { const auto year = message.at(index + 7).toLatin1() - '0'; const auto days = message.midRef(index + 8, 3).toInt() - 1; if (year < 0 || year > 9 || days < 0 || days > 365) { qCWarning(Log) << "IATA BCBP invalid boarding pass issue date format" << message.midRef(index + 7, 4); return {}; } auto currentYear = externalIssueDate.year() - externalIssueDate.year() % 10 + year; if (currentYear > externalIssueDate.year()) { currentYear -= 10; } issueDate = QDate(currentYear, 1, 1).addDays(days); } // 1x document type // 3x airline code of boarding pass issuer // 3x 13x baggage tag numbers // skip repeated conditional section, containing mainly bonus program data } // skip for airline use section index += varSize; } result.push_back(res1); // all following legs only contain repeated sections, copy content from the unique ones from the first leg for (int i = 1; i < legCount; ++i) { if (message.size() < (index + RepeatedMandatorySize)) { qCWarning(Log) << "IATA BCBP repeated mandatory section too short" << i; return {}; } FlightReservation res = res1; const auto varSize = parseRepeatedMandatorySection(message.midRef(index), res); if (varSize < 0) { // parser error return {}; } index += RepeatedMandatorySize; if (message.size() < (index + varSize)) { qCWarning(Log) << "IATA BCBP repeated conditional section too short" << i; return {}; } // skip repeated conditional section // skip for airline use section index += varSize; result.push_back(res); } // optional security section at the end, not interesting for us // complete departure dates with the now (hopefully known issue date) for (auto it = result.begin(); it != result.end(); ++it) { auto res = (*it).value(); auto flight = res.reservationFor().value(); if (issueDate.isValid()) { const auto days = flight.departureDay().dayOfYear() - 1; QDate date(issueDate.year(), 1, 1); date = date.addDays(days); if (date >= issueDate) { flight.setDepartureDay(date); } else { flight.setDepartureDay(QDate(issueDate.year() + 1, 1, 1).addDays(days)); } } else { flight.setDepartureDay(QDate()); } res.setReservationFor(flight); *it = res; } return result; } bool IataBcbpParser::maybeIataBcbp(const QString &message) { if (message.size() < UniqueMandatorySize) { return false; } if (message.at(0) != QLatin1Char(FormatCode) || !message.at(1).isDigit()) { return false; } return true; } diff --git a/src/iatabcbpparser.h b/src/iatabcbpparser.h index 52ba26b..f8a884e 100644 --- a/src/iatabcbpparser.h +++ b/src/iatabcbpparser.h @@ -1,55 +1,55 @@ /* 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 . + along with this program. If not, see . */ #ifndef KITINERARY_IATABCBPPARSER_H #define KITINERARY_IATABCBPPARSER_H #include "kitinerary_export.h" #include #include #include class QDate; class QString; class QVariant; namespace KItinerary { /** * Parser for IATA Bar Coded Boarding Pass messages. * @see https://www.iata.org/whatwedo/stb/Documents/BCBP-Implementation-Guide-5th-Edition-June-2016.pdf */ namespace IataBcbpParser { /** Parses the bar coded boarding pass message @p message into * a list of FlightReservation instances. * @param externalIssueDate The date the boarding pass was issued (or a sufficiently close approximation). * This is necessary as by default the BCBP data only contains day and month of the flight, not the year. */ KITINERARY_EXPORT QVector parse(const QString &message, const QDate &externalIssueDate = QDate()); /** Quickly test if @p message could be a IATA BCBP code. * This optimizes for speed rather than correctness, for use in barcode content auto-detection. */ bool maybeIataBcbp(const QString &message); } } #endif // KITINERARY_IATABCBPPARSER_H diff --git a/src/jsapi/barcode.cpp b/src/jsapi/barcode.cpp index 51fd4b0..a24543e 100644 --- a/src/jsapi/barcode.cpp +++ b/src/jsapi/barcode.cpp @@ -1,77 +1,77 @@ /* 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 . + along with this program. If not, see . */ #include "barcode.h" #include #include #include #include #include using namespace KItinerary; QString JsApi::Barcode::decodePdf417(const QVariant &img) const { if (img.userType() == qMetaTypeId()) { return BarcodeDecoder::decodePdf417(img.value().image()); } return {}; } QString JsApi::Barcode::decodeAztec(const QVariant &img) const { if (img.userType() == qMetaTypeId()) { return BarcodeDecoder::decodeAztec(img.value().image()); } return {}; } QVariant JsApi::Barcode::decodeAztecBinary(const QVariant &img) const { if (img.userType() == qMetaTypeId()) { const auto b = BarcodeDecoder::decodeAztecBinary(img.value().image()); return QVariant::fromValue(b); } return {}; } QVariant JsApi::Barcode::decodeUic9183(const QVariant &s) const { Uic9183Parser p; p.setContextDate(m_contextDate); p.parse(s.toByteArray()); return QVariant::fromValue(p); } QVariant JsApi::Barcode::decodeIataBcbp(const QString &s) const { return QVariant::fromValue(IataBcbpParser::parse(s, m_contextDate.date())); } QString JsApi::Barcode::toBase64(const QVariant &b) const { return QString::fromUtf8(b.toByteArray().toBase64()); } void JsApi::Barcode::setContextDate(const QDateTime &dt) { m_contextDate = dt; } #include "moc_barcode.cpp" diff --git a/src/jsapi/barcode.h b/src/jsapi/barcode.h index 23f3ae3..dd53c64 100644 --- a/src/jsapi/barcode.h +++ b/src/jsapi/barcode.h @@ -1,68 +1,68 @@ /* 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 . + along with this program. If not, see . */ #ifndef KITINERARY_JSAPI_BARCODE_H #define KITINERARY_JSAPI_BARCODE_H #include #include namespace KItinerary { namespace JsApi { /** Barcode decoding functions. */ class Barcode : public QObject { Q_OBJECT public: /** Decode a PDF417 barcode image. * @param img An image containing the barcode, e.g. a PdfImage instance. */ Q_INVOKABLE QString decodePdf417(const QVariant &img) const; /** Decode a Aztec barcode image. * @param img An image containing the barcode, e.g. a PdfImage instance. */ Q_INVOKABLE QString decodeAztec(const QVariant &img) const; /** Decode a Aztec barcode image containing binary data. * @param img An image containing the barcode, e.g. a PdfImage instance. * @return a QByteArray, which from the JS perspective is essentially an opque handle. */ Q_INVOKABLE QVariant decodeAztecBinary(const QVariant &img) const; /** Decode an UIC 918.3 message from a train ticket Aztec code. * @param s A QByteArray containing the raw data from the barcode. * @returns An instance of Uic9183Parser. */ Q_INVOKABLE QVariant decodeUic9183(const QVariant &s) const; /** Decode an IATA BCBP message from a flight boarding pass barcode. * @returns A JSON-LD structure representing the boarding pass. */ Q_INVOKABLE QVariant decodeIataBcbp(const QString &s) const; /** Converts the given QByteArray into an base64 encoded string. */ Q_INVOKABLE QString toBase64(const QVariant &b) const; ///@cond internal void setContextDate(const QDateTime &dt); ///@endcond private: QDateTime m_contextDate; }; } } #endif // KITINERARY_JSAPI_BARCODE_H diff --git a/src/jsapi/context.cpp b/src/jsapi/context.cpp index 55d27ae..82f2029 100644 --- a/src/jsapi/context.cpp +++ b/src/jsapi/context.cpp @@ -1,18 +1,18 @@ /* 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 . + along with this program. If not, see . */ #include "context.h" diff --git a/src/jsapi/context.h b/src/jsapi/context.h index 4d84ef0..2ab082f 100644 --- a/src/jsapi/context.h +++ b/src/jsapi/context.h @@ -1,53 +1,53 @@ /* 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 . + along with this program. If not, see . */ #ifndef KITINERARY_JSAPI_CONTEXT_H #define KITINERARY_JSAPI_CONTEXT_H #include #include namespace KItinerary { /** JavaScript API exposed to extractor scripts. * @see ExtractorEngine */ namespace JsApi { /** The extraction context. * This object contains information about what is being extracted, * or where the extracted information is coming from. */ class Context : public QObject { Q_OBJECT /** The time the email containing the extracted data was sent. * This can be useful if the extracted data only contains dates without * specifying a year. The year can then be infered out of this context. */ Q_PROPERTY(QDateTime senderDate MEMBER m_senderDate) public: ///@cond internal QDateTime m_senderDate; ///@endcond }; } } #endif // KITINERARY_JSAPI_CONTEXT_H diff --git a/src/jsapi/jsonld.cpp b/src/jsapi/jsonld.cpp index 9834967..981fb9d 100644 --- a/src/jsapi/jsonld.cpp +++ b/src/jsapi/jsonld.cpp @@ -1,140 +1,140 @@ /* 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 . + along with this program. If not, see . */ #include "jsonld.h" #include #include #include #include #include #include #include #include using namespace KItinerary; JsApi::JsonLd::JsonLd(QJSEngine* engine) : QObject(engine) , m_engine(engine) { } JsApi::JsonLd::~JsonLd() = default; QJSValue JsApi::JsonLd::newObject(const QString &typeName) const { auto v = m_engine->newObject(); v.setProperty(QStringLiteral("@type"), typeName); return v; } QDateTime JsApi::JsonLd::toDateTime(const QString &dtStr, const QString &format, const QString &localeName) const { QLocale locale(localeName); auto dt = locale.toDateTime(dtStr, format); // try harder for the "MMM" month format // QLocale expects the exact string in QLocale::shortMonthName(), while we often encounter a three // letter month identifier. For en_US that's the same, for Swedish it isn't though for example. So // let's try to fix up the month identifiers to the full short name. if (!dt.isValid() && format.contains(QLatin1String("MMM"))) { auto dtStrFixed = dtStr; for (int i = 1; i <= 12; ++i) { const auto monthName = locale.monthName(i, QLocale::ShortFormat); dtStrFixed = dtStrFixed.replace(monthName.left(3), monthName, Qt::CaseInsensitive); } dt = locale.toDateTime(dtStrFixed, format); } if (!dt.isValid()) { return dt; } const bool hasFullYear = format.contains(QLatin1String("yyyy")); const bool hasYear = hasFullYear || format.contains(QLatin1String("yy")); const bool hasMonth = format.contains(QLatin1String("M")); const bool hasDay = format.contains(QLatin1String("d")); // time only, set a default date if (!hasDay && !hasMonth && !hasYear) { dt.setDate({1970, 1, 1}); } // if the date does not contain a year number, determine that based on the context date, if set else if (!hasYear && m_contextDate.isValid()) { dt.setDate({m_contextDate.date().year(), dt.date().month(), dt.date().day()}); if (dt < m_contextDate) { dt = dt.addYears(1); } } // fix two-digit years ending up in the wrong century else if (!hasFullYear && dt.date().year() / 100 == 19) { dt = dt.addYears(100); } return dt; } QJSValue JsApi::JsonLd::toJson(const QVariant &v) const { if (v.canConvert>()) { return m_engine->toScriptValue(JsonLdDocument::toJson(v.value>())); } const auto json = JsonLdDocument::toJson({v}); if (json.isEmpty()) { return {}; } return m_engine->toScriptValue(json.at(0)); } QJSValue JsApi::JsonLd::clone(const QJSValue& v) const { return m_engine->toScriptValue(v.toVariant()); } QJSValue JsApi::JsonLd::toGeoCoordinates(const QString &mapUrl) { QUrl url(mapUrl); if (url.host().contains(QLatin1String("google"))) { QRegularExpression regExp(QStringLiteral("[/=](-?\\d+\\.\\d+),(-?\\d+\\.\\d+)")); auto match = regExp.match(url.path()); if (!match.hasMatch()) { match = regExp.match(url.query()); } if (match.hasMatch()) { auto geo = m_engine->newObject(); geo.setProperty(QStringLiteral("@type"), QStringLiteral("GeoCoordinates")); geo.setProperty(QStringLiteral("latitude"), match.capturedRef(1).toDouble()); geo.setProperty(QStringLiteral("longitude"), match.capturedRef(2).toDouble()); return geo; } } return {}; } void JsApi::JsonLd::setContextDate(const QDateTime& dt) { m_contextDate = dt; } #include "moc_jsonld.cpp" diff --git a/src/jsapi/jsonld.h b/src/jsapi/jsonld.h index f9fcd31..d944171 100644 --- a/src/jsapi/jsonld.h +++ b/src/jsapi/jsonld.h @@ -1,80 +1,80 @@ /* 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 . + along with this program. If not, see . */ #ifndef KITINERARY_JSAPI_JSONLD_H #define KITINERARY_JSAPI_JSONLD_H #include #include class QJSEngine; class QJSValue; namespace KItinerary { namespace JsApi { /** Methods to create JSON-LD objects. */ class JsonLd : public QObject { Q_OBJECT public: ///@cond internal explicit JsonLd(QJSEngine *engine); ~JsonLd(); ///@endcond /** Create a new JSON-LD object of type @p typeName. */ Q_INVOKABLE QJSValue newObject(const QString &typeName) const; /** Convert a date/time string to a date/time value. * @param dtStr The input string containing a date/time value. * @param format The format of the input string. Same format specification as * used by QLocale and QDateTime. If the year is not part of the date * it is attempted to be recovered from the context date set on the * ExtractorEngine (that is, the returned date will be after the context * date). * @param localeName The locale in which the string is formatted. This is * relevant when the input contains for example localized month names or * month abbreviations. */ Q_INVOKABLE QDateTime toDateTime(const QString &dtStr, const QString &format, const QString &localeName) const; /** Convert object @p v to a JSON-LD object. * This is useful when interacting with API returning regular data types, * such as Uic9183Parser. */ Q_INVOKABLE QJSValue toJson(const QVariant &v) const; /** Clones the given JS object. * That is, create a deep copy of @p v. */ Q_INVOKABLE QJSValue clone(const QJSValue &v) const; /** Parses geo coordinates from a given mapping service URLs. * This consumes for example Google Maps links and returns a JSON-LD * GeoCoordinates object. */ Q_INVOKABLE QJSValue toGeoCoordinates(const QString &mapUrl); ///@cond internal void setContextDate(const QDateTime &dt); ///@endcond private: QJSEngine *m_engine; QDateTime m_contextDate; }; } } #endif // KITINERARY_JSAPI_JSONLD_H diff --git a/src/jsonldimportfilter.cpp b/src/jsonldimportfilter.cpp index e7e724d..0f17d73 100644 --- a/src/jsonldimportfilter.cpp +++ b/src/jsonldimportfilter.cpp @@ -1,233 +1,233 @@ /* 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 . + along with this program. If not, see . */ #include "jsonldimportfilter.h" #include #include #include #include using namespace KItinerary; static void renameProperty(QJsonObject &obj, const char *oldName, const char *newName) { const auto value = obj.value(QLatin1String(oldName)); if (!value.isNull() && !obj.contains(QLatin1String(newName))) { obj.insert(QLatin1String(newName), value); obj.remove(QLatin1String(oldName)); } } static void renameType(QJsonObject &obj, const char *oldType, const char *newType) { if (obj.value(QLatin1String("@type")) == QLatin1String(oldType)) { obj.insert(QLatin1String("@type"), QLatin1String(newType)); } } static void migrateToAction(QJsonObject &obj, const char *propName, const char *typeName, bool remove) { const auto value = obj.value(QLatin1String(propName)); if (value.isNull() || value.isUndefined()) { return; } auto actions = obj.value(QLatin1String("potentialAction")).toArray(); for (const auto &act : actions) { if (act.toObject().value(QLatin1String("@type")).toString() == QLatin1String(typeName)) { return; } } QJsonObject action; action.insert(QStringLiteral("@type"), QLatin1String(typeName)); action.insert(QStringLiteral("target"), value); actions.push_back(action); obj.insert(QLatin1String("potentialAction"), actions); if (remove) { obj.remove(QLatin1String("propName")); } } static void filterTrainTrip(QJsonObject &trip) { if (trip.value(QLatin1String("@type")).toString() != QLatin1String("TrainTrip")) { return; } // move TrainTrip::trainCompany to TrainTrip::provider (as defined by schema.org) renameProperty(trip, "trainCompany", "provider"); } static void filterLodgingBusiness(QJsonObject &hotel) { // convert LodgingBusiness sub-types we don't handle renameType(hotel, "Hotel", "LodgingBusiness"); } static void filterLodgingReservation(QJsonObject &res) { // check[in|out]Date -> check[in|out]Time (legacy Google format) renameProperty(res, "checkinDate", "checkinTime"); renameProperty(res, "checkoutDate", "checkoutTime"); QJsonObject hotel = res.value(QLatin1String("reservationFor")).toObject(); filterLodgingBusiness(hotel); res.insert(QLatin1String("reservationFor"), hotel); } static void filterTaxiReservation(QJsonObject &res) { renameProperty(res, "reservationId", "reservationNumber"); } static void filterFlight(QJsonObject &res) { // move incomplete departureTime (ie. just ISO date, no time) to departureDay if (res.value(QLatin1String("departureTime")).toString().size() == 10) { renameProperty(res, "departureTime", "departureDay"); } } static void filterReservation(QJsonObject &res) { // move ticketToken to Ticket (Google vs. schema.org difference) const auto token = res.value(QLatin1String("ticketToken")).toString(); if (!token.isEmpty()) { auto ticket = res.value(QLatin1String("reservedTicket")).toObject(); if (ticket.isEmpty()) { ticket.insert(QLatin1String("@type"), QLatin1String("Ticket")); } if (!ticket.contains(QLatin1String("ticketToken"))) { ticket.insert(QLatin1String("ticketToken"), token); res.insert(QLatin1String("reservedTicket"), ticket); res.remove(QLatin1String("ticketToken")); } } // legacy potentialAction property renameProperty(res, "action", "potentialAction"); // move Google xxxUrl properties to Action instances migrateToAction(res, "cancelReservationUrl", "CancelAction", true); migrateToAction(res, "checkinUrl", "CheckInAction", true); migrateToAction(res, "modifyReservationUrl", "UpdateAction", true); migrateToAction(res, "ticketDownloadUrl", "DownloadAction", true); migrateToAction(res, "url", "ViewAction", false); // "typos" renameProperty(res, "Url", "url"); } static void filterEvent(QJsonObject &hotel) { // convert Event sub-types we don't handle renameType(hotel, "MusicEvent", "Event"); } static void filterEventReservation(QJsonObject &res) { QJsonObject event = res.value(QLatin1String("reservationFor")).toObject(); filterEvent(event); res.insert(QLatin1String("reservationFor"), event); } static void filterBusStop(QJsonObject &station) { renameType(station, "BusStop", "BusStation"); } static void filterBusTrip(QJsonObject &trip) { renameProperty(trip, "arrivalStation", "arrivalBusStop"); renameProperty(trip, "departureStation", "departureBusStop"); renameProperty(trip, "busCompany", "provider"); auto station = trip.value(QLatin1String("arrivalBusStop")).toObject(); filterBusStop(station); trip.insert(QLatin1String("arrivalBusStop"), station); station = trip.value(QLatin1String("departureBusStop")).toObject(); filterBusStop(station); trip.insert(QLatin1String("departureBusStop"), station); } static void filterBusReservation(QJsonObject &res) { QJsonObject trip = res.value(QLatin1String("reservationFor")).toObject(); filterBusTrip(trip); res.insert(QLatin1String("reservationFor"), trip); } static QJsonArray filterActions(const QJsonValue &v) { QJsonArray actions; if (v.isArray()) { actions = v.toArray(); } else { actions.push_back(v); } for (auto it = actions.begin(); it != actions.end(); ++it) { auto action = (*it).toObject(); renameType(action, "EditAction", "UpdateAction"); renameProperty(action, "url", "target"); *it = action; } return actions; } QJsonObject JsonLdImportFilter::filterObject(const QJsonObject& obj) { QJsonObject res(obj); const auto type = obj.value(QLatin1String("@type")).toString(); if (type.endsWith(QLatin1String("Reservation"))) { filterReservation(res); } if (type == QLatin1String("TrainReservation")) { auto train = obj.value(QLatin1String("reservationFor")).toObject(); filterTrainTrip(train); if (!train.isEmpty()) { res.insert(QLatin1String("reservationFor"), train); } } else if (type == QLatin1String("LodgingReservation")) { filterLodgingReservation(res); } else if (type == QLatin1String("FlightReservation")) { auto flight = obj.value(QLatin1String("reservationFor")).toObject(); filterFlight(flight); if (!flight.isEmpty()) { res.insert(QLatin1String("reservationFor"), flight); } } else if (type == QLatin1String("TaxiReservation")) { filterTaxiReservation(res); } else if (type == QLatin1String("EventReservation")) { filterEventReservation(res); } else if (type == QLatin1String("BusReservation")) { filterBusReservation(res); } auto actions = res.value(QLatin1String("potentialAction")); if (!actions.isUndefined()) { res.insert(QLatin1String("potentialAction"), filterActions(actions)); } return res; } diff --git a/src/jsonldimportfilter.h b/src/jsonldimportfilter.h index 122db64..8e4a92f 100644 --- a/src/jsonldimportfilter.h +++ b/src/jsonldimportfilter.h @@ -1,36 +1,36 @@ /* 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 . + along with this program. If not, see . */ #ifndef KITINERARY_JSONLDIMPORTFILTER_H #define KITINERARY_JSONLDIMPORTFILTER_H class QJsonObject; namespace KItinerary { /** Filter input JSON for loading with JsonLdDocument, to deal with obsolete * or renamed elements. */ namespace JsonLdImportFilter { /** Filter the top-level object @p obj for loading with JsonLdDocument. */ QJsonObject filterObject(const QJsonObject &obj); } } #endif // KITINERARY_JSONLDIMPORTFILTER_H diff --git a/src/knowledgedb-generator/airportdbgenerator.cpp b/src/knowledgedb-generator/airportdbgenerator.cpp index b25f5ec..b1af726 100644 --- a/src/knowledgedb-generator/airportdbgenerator.cpp +++ b/src/knowledgedb-generator/airportdbgenerator.cpp @@ -1,420 +1,420 @@ /* 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 . + along with this program. If not, see . */ #include "airportdbgenerator.h" #include "codegen.h" #include "wikidata.h" #include "../stringutil.h" #include #include #include #include #include #include #include using namespace KItinerary; using namespace KItinerary::KnowledgeDb; using namespace KItinerary::Generator; static bool soundsMilitaryish(const QString &s) { return s.contains(QLatin1String("Airbase"), Qt::CaseInsensitive) || s.contains(QLatin1String("Air Base"), Qt::CaseInsensitive) || s.contains(QLatin1String("Air Force"), Qt::CaseInsensitive) || s.contains(QLatin1String("Air National Guard Base"), Qt::CaseInsensitive) || s.contains(QLatin1String("Air Reserve Base"), Qt::CaseInsensitive) || s.contains(QLatin1String("Air Station"), Qt::CaseInsensitive) || s.contains(QLatin1String("Army Airfield"), Qt::CaseInsensitive) || s.contains(QLatin1String("Army Airport"), Qt::CaseInsensitive) || s.contains(QLatin1String("Army Heliport"), Qt::CaseInsensitive) || s.contains(QLatin1String("Canadian Forces Base"), Qt::CaseInsensitive) || s.contains(QLatin1String("Joint Base"), Qt::CaseInsensitive) || s.contains(QLatin1String("Marine Corps"), Qt::CaseInsensitive) || s.contains(QLatin1String("Military ")) || s.contains(QLatin1String("Naval ")) || s.contains(QLatin1String("RAF ")) || s.contains(QLatin1String("RAAF ")) || s.contains(QLatin1String("RNAS ")) || s.contains(QLatin1String("CFB ")) || s.contains(QLatin1String("PAF ")) || s.contains(QLatin1String("NAF ")) ; } static void stripAirportAllLanguages(QStringList &s) { // only languages used in the English (sic!) wikidata labels and description matter here s.removeAll(QLatin1String("aeroport")); s.removeAll(QLatin1String("aeroporto")); s.removeAll(QLatin1String("aeropuerto")); s.removeAll(QLatin1String("air")); s.removeAll(QLatin1String("airfield")); s.removeAll(QLatin1String("airpark")); s.removeAll(QLatin1String("airport")); s.removeAll(QLatin1String("airstrip")); s.removeAll(QLatin1String("flughafen")); s.removeAll(QLatin1String("lufthavn")); s.removeAll(QLatin1String("terminal")); } void AirportDbGenerator::merge(Airport &lhs, const Airport &rhs) { if (lhs.iataCode != rhs.iataCode) { qWarning() << "Multiple IATA codes on" << lhs.uri; // this can actually be valid, see BSL/MLH/EAP } // we don't really care about multiple ICAO codes // if (lhs.icaoCode != rhs.icaoCode) // qWarning() << "Multiple ICAO codes on" << lhs.uri; QString extraLabel; if (lhs.label != rhs.label) { extraLabel += QLatin1Char(' ') + rhs.label; } if (lhs.alias != rhs.alias) { extraLabel += QLatin1Char(' ') + rhs.alias; } lhs.alias += extraLabel; if (!lhs.coord.isValid()) { lhs.coord = rhs.coord; } else if (rhs.coord.isValid()) { if (std::abs(lhs.coord.latitude - rhs.coord.latitude) > 0.2f || std::abs(lhs.coord.longitude - rhs.coord.longitude) > 0.2f) { ++m_coordinateConflicts; qDebug() << lhs.label << lhs.iataCode << lhs.uri << "has multiple conflicting coordinates"; } // pick always the same independent of the input order, so stabilize generated output lhs.coord.latitude = std::min(lhs.coord.latitude, rhs.coord.latitude); lhs.coord.longitude = std::min(lhs.coord.longitude, rhs.coord.longitude); } } bool AirportDbGenerator::fetchAirports() { // sorted by URI to stabilize the result in case of conflicts const auto airportArray = WikiData::query(R"( SELECT DISTINCT ?airport ?airportLabel ?airportAltLabel ?iataCode ?icaoCode ?coord ?endDate ?demolished ?openingDate ?iataEndDate WHERE { ?airport (wdt:P31/wdt:P279*) wd:Q1248784. ?airport p:P238 ?iataStmt. ?iataStmt ps:P238 ?iataCode. OPTIONAL { ?airport wdt:P239 ?icaoCode. } OPTIONAL { ?airport wdt:P625 ?coord. } OPTIONAL { ?airport wdt:P582 ?endDate. } OPTIONAL { ?airport wdt:P576 ?demolished. } OPTIONAL { ?airport wdt:P1619 ?openingDate. } OPTIONAL { ?iataStmt pq:P582 ?iataEndDate. } SERVICE wikibase:label { bd:serviceParam wikibase:language "en". } } ORDER BY (?airport))", "wikidata_airports.json"); if (airportArray.isEmpty()) { qWarning() << "No results in SPARQL query found."; return false; } for (const auto &data: airportArray) { const auto obj = data.toObject(); if (obj.isEmpty()) { continue; } if (obj.contains(QLatin1String("endDate")) || obj.contains(QLatin1String("demolished")) || obj.contains(QLatin1String("iataEndDate"))) { // skip closed airports or those with expired IATA codes continue; } const auto openingdDt = QDateTime::fromString(obj.value(QLatin1String("openingDate")).toObject().value(QLatin1String("value")).toString(), Qt::ISODate); if (openingdDt.isValid() && openingdDt > QDateTime::currentDateTime().addDays(120)) { // skip future airports continue; } Airport a; a.uri = QUrl(obj.value(QLatin1String("airport")).toObject().value(QLatin1String("value")).toString()); a.iataCode = obj.value(QLatin1String("iataCode")).toObject().value(QLatin1String("value")).toString(); if (a.iataCode.size() != 3 || !a.iataCode.at(0).isUpper() || !a.iataCode.at(1).isUpper() || !a.iataCode.at(2).isUpper()) { // invalid IATA code continue; } a.icaoCode = obj.value(QLatin1String("icaoCode")).toObject().value(QLatin1String("value")).toString(); a.label = obj.value(QLatin1String("airportLabel")).toObject().value(QLatin1String("value")).toString(); a.alias = obj.value(QLatin1String("airportAltLabel")).toObject().value(QLatin1String("value")).toString(); // primitive military airport filter, turns out to be more reliable than querying for the military airport types if (soundsMilitaryish(a.label) || soundsMilitaryish(a.alias)) { continue; } a.coord = WikiData::parseCoordinate(obj.value(QLatin1String("coord")).toObject().value(QLatin1String("value")).toString()); // merge multiple records for the same airport auto it = m_airportMap.find(a.uri); if (it != m_airportMap.end()) { merge(*it, a); // continue nevertheless, to deal with multiple IATA codes per airport (e.g. BSL/MLH/EAP) } else { m_airportMap.insert(a.uri, a); } // TODO deal with IATA code duplications if (m_iataMap.contains(a.iataCode) && m_iataMap.value(a.iataCode) != a.uri) { ++m_iataCollisions; qDebug() << "duplicate iata code:" << a.iataCode << a.label << a.uri << m_airportMap.value(m_iataMap.value(a.iataCode)).label << m_airportMap.value(m_iataMap.value(a.iataCode)).uri; } m_iataMap.insert(a.iataCode, a.uri); } return true; } bool AirportDbGenerator::fetchCountries() { const auto array = WikiData::query(R"( SELECT DISTINCT ?airport ?isoCode WHERE { ?airport (wdt:P31/wdt:P279*) wd:Q1248784. ?airport wdt:P17 ?country. ?country wdt:P297 ?isoCode. } ORDER BY (?airport))", "wikidata_airport_country.json"); if (array.isEmpty()) { qWarning() << "Empty query result!"; return false; } for (const auto &airportData: array) { const auto obj = airportData.toObject(); const auto uri = QUrl(obj.value(QLatin1String("airport")).toObject().value(QLatin1String("value")).toString()); const auto isoCode = obj.value(QLatin1String("isoCode")).toObject().value(QLatin1String("value")).toString(); const auto it = m_airportMap.find(uri); if (it != m_airportMap.end()) { if ((*it).country != isoCode && !(*it).country.isEmpty()) { ++m_countryConflicts; qWarning() << "Country code conflict on" << (*it).label << (*it).uri << (*it).country << isoCode; continue; } (*it).country = isoCode; } } return true; } void AirportDbGenerator::lookupTimezones() { for (auto it = m_airportMap.begin(); it != m_airportMap.end(); ++it) { if (!(*it).coord.isValid()) { qDebug() << "Airport has no geo coordinate:" << (*it).label << (*it).iataCode << (*it).uri; } (*it).tz = m_tzDb.timezoneForLocation((*it).country, (*it).coord); if ((*it).tz.isEmpty()) { qDebug() << "Failed to find timezone for" << (*it).iataCode << (*it).label << (*it).country << (*it).coord.latitude << (*it).coord.longitude << (*it).uri; ++m_timezoneLoopupFails; continue; } } } void KItinerary::Generator::AirportDbGenerator::indexNames() { for (auto it = m_airportMap.begin(); it != m_airportMap.end(); ++it) { auto l = StringUtil::normalize(it.value().label + QLatin1Char(' ') + it.value().alias) .split(QRegularExpression(QStringLiteral("[ 0-9/'\"\\(\\)&\\,.–„-]")), QString::SkipEmptyParts); std::for_each(l.begin(), l.end(), [](QString &s) { s = s.toCaseFolded(); }); l.removeAll(it.value().iataCode.toCaseFolded()); l.removeAll(it.value().icaoCode.toCaseFolded()); stripAirportAllLanguages(l); l.removeDuplicates(); for (const auto &s : l) { if (s.size() <= 2) { continue; } m_labelMap[s].push_back(it.value().iataCode); } } for (auto it = m_labelMap.begin(); it != m_labelMap.end(); ++it) { std::sort(it.value().begin(), it.value().end()); } } bool AirportDbGenerator::generate(QIODevice* out) { // step 1 query wikidata for all airports if (!fetchAirports() || !fetchCountries()) { return false; } // step 2 augment the data with timezones lookupTimezones(); // step 3 index the names for reverse lookup indexNames(); // step 4 generate code CodeGen::writeLicenseHeader(out); out->write(R"( #include "airportdb.h" #include "airportdb_p.h" #include "knowledgedb.h" #include "timezonedb.h" #include "timezonedb_data_p.h" #include using namespace KItinerary::KnowledgeDb; namespace KItinerary { namespace KnowledgeDb { // airport data sorted by IATA code // the corresponding index is used to access data the following tables static constexpr Airport airport_table[] = { )"); // IATA to airport data index for (auto it = m_iataMap.constBegin(); it != m_iataMap.constEnd(); ++it) { out->write(" Airport{IataCode{\""); out->write(it.key().toUtf8()); out->write("\"}, "); CodeGen::writeCountryIsoCode(out, m_airportMap.value(it.value()).country); out->write(", "); CodeGen::writeTimezone(out, m_airportMap.value(it.value()).tz); out->write("}, // "); out->write(m_airportMap.value(it.value()).label.toUtf8()); out->write("\n"); } out->write(R"(}; // airport coordinates in latitude/longitude pairs // stored out of line of the airport_table to avoid alignment padding static constexpr Coordinate coordinate_table[] = { )"); // airport data tables - coordinates // TODO: should be possible to squeeze into 48 bit per coordinate, as 10m resolution is good enough for us for (auto it = m_iataMap.constBegin(); it != m_iataMap.constEnd(); ++it) { const auto &airport = m_airportMap.value(it.value()); out->write(" "); CodeGen::writeCoordinate(out, airport.coord); out->write(", // "); out->write(airport.iataCode.toUtf8()); out->write("\n"); } out->write(R"(}; // reverse name lookup string table for unique strings static const char name1_string_table[] = )"); // TODO prefix compression std::vector string_offsets; string_offsets.reserve(m_labelMap.size()); uint32_t label_offset = 0; for (auto it = m_labelMap.begin(); it != m_labelMap.end(); ++it) { if (it.value().size() > 1) { continue; } out->write(" \""); out->write(it.key().toUtf8()); out->write("\" // "); out->write(it.value().at(0).toUtf8()); out->write("\n"); string_offsets.emplace_back(Name1Index{label_offset, (uint8_t)it.key().toUtf8().size(), (uint16_t)std::distance(m_iataMap.begin(), m_iataMap.find(it.value().at(0)))}); label_offset += it.key().toUtf8().size(); } out->write(R"(; // string table indices into name_string_table static const Name1Index name1_string_index[] = { )"); for (const auto &offset : string_offsets) { out->write(" Name1Index{"); out->write(QByteArray::number(offset.offset())); out->write(", "); out->write(QByteArray::number(offset.length)); out->write(", "); out->write(QByteArray::number(offset.iataIndex)); out->write("},\n"); } out->write(R"(}; // reverse name lookup string table for non-unique strings static const char nameN_string_table[] = )"); // TODO prefix compression? struct stringN_index_t { QByteArray str; uint16_t strOffset; uint16_t iataMapOffset; QVector iataList; }; std::vector stringN_offsets; stringN_offsets.reserve(m_labelMap.size() - string_offsets.size()); uint16_t string_offset = 0; uint16_t iata_map_offset = 0; for (auto it = m_labelMap.begin(); it != m_labelMap.end(); ++it) { if (it.value().size() == 1) { continue; } out->write(" \""); out->write(it.key().toUtf8()); out->write("\"\n"); stringN_offsets.emplace_back(stringN_index_t{it.key().toUtf8(), string_offset, iata_map_offset, it.value()}); string_offset += it.key().toUtf8().size(); iata_map_offset += it.value().size(); } out->write(R"(; // string table index to iata code mapping static const uint16_t nameN_iata_table[] = { )"); for (const auto &offset : stringN_offsets) { out->write(" "); for (const auto &iataCode : offset.iataList) { out->write(QByteArray::number(std::distance(m_iataMap.begin(), m_iataMap.find(iataCode)))); out->write(", "); } out->write(" // "); out->write(offset.str); out->write("\n"); } out->write(R"(}; // index into the above string and iata index tables static const NameNIndex nameN_string_index[] = { )"); for (const auto &offset : stringN_offsets) { out->write(" NameNIndex{"); out->write(QByteArray::number(offset.strOffset)); out->write(", "); out->write(QByteArray::number(offset.str.length())); out->write(", "); out->write(QByteArray::number(offset.iataMapOffset)); out->write(", "); out->write(QByteArray::number(offset.iataList.size())); out->write("},\n"); } out->write(R"(}; } } )"); qDebug() << "Generated database containing" << m_iataMap.size() << "airports"; qDebug() << "Name fragment index:" << string_offsets.size() << "unique keys," << m_labelMap.size() - string_offsets.size() << "non-unique keys"; qDebug() << "IATA code collisions:" << m_iataCollisions; qDebug() << "Coordinate conflicts:" << m_coordinateConflicts; qDebug() << "Country conflicts:" << m_countryConflicts; qDebug() << "Failed timezone lookups:" << m_timezoneLoopupFails; return true; } diff --git a/src/knowledgedb-generator/airportdbgenerator.h b/src/knowledgedb-generator/airportdbgenerator.h index 983754b..d942393 100644 --- a/src/knowledgedb-generator/airportdbgenerator.h +++ b/src/knowledgedb-generator/airportdbgenerator.h @@ -1,73 +1,73 @@ /* 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 . + along with this program. If not, see . */ #ifndef KITINERARY_AIRPORTDBGENERATOR_H #define KITINERARY_AIRPORTDBGENERATOR_H #include #include "timezones.h" #include #include #include class QIODevice; namespace KItinerary { namespace Generator { /** Generate airport database from Wikidata. */ class AirportDbGenerator { public: bool generate(QIODevice *out); struct Airport { QUrl uri; QString iataCode; QString icaoCode; QString label; QString alias; QString country; QByteArray tz; int tzOffset; KnowledgeDb::Coordinate coord; }; private: bool fetchAirports(); bool fetchCountries(); void merge(Airport &lhs, const Airport &rhs); void lookupTimezones(); void indexNames(); QHash m_airportMap; QMap m_iataMap; QMap> m_labelMap; Timezones m_tzDb; int m_iataCollisions = 0; int m_coordinateConflicts = 0; int m_countryConflicts = 0; int m_timezoneLoopupFails = 0; }; } } #endif // KITINERARY_AIRPORTDBGENERATOR_H diff --git a/src/knowledgedb-generator/codegen.cpp b/src/knowledgedb-generator/codegen.cpp index e2a6076..3a98d46 100644 --- a/src/knowledgedb-generator/codegen.cpp +++ b/src/knowledgedb-generator/codegen.cpp @@ -1,69 +1,69 @@ /* 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 . + along with this program. If not, see . */ #include "codegen.h" #include "timezones.h" #include using namespace KItinerary::Generator; void CodeGen::writeLicenseHeader(QIODevice *out) { out->write(R"(/* * This code is auto-generated from Wikidata data. Licensed under CC0. */ )"); } void CodeGen::writeCoordinate(QIODevice* out, const KnowledgeDb::Coordinate& coord) { out->write("Coordinate{"); if (coord.isValid()) { out->write(QByteArray::number(coord.longitude)); out->write(", "); out->write(QByteArray::number(coord.latitude)); } out->write("}"); } void CodeGen::writeCountryIsoCode(QIODevice *out, const QString &isoCode) { out->write("CountryId{"); if (!isoCode.isEmpty()) { out->write("\""); out->write(isoCode.toUtf8()); out->write("\""); } out->write("}"); } void CodeGen::writeTimezone(QIODevice *out, const QByteArray &tzName) { if (tzName.isEmpty()) { out->write("Timezone{}"); } else { out->write("Tz::"); writeTimezoneEnum(out, tzName); } } void CodeGen::writeTimezoneEnum(QIODevice* out, const QByteArray& tzName) { auto enumName(tzName); out->write(enumName.replace("/", "_").replace("-", "_")); } diff --git a/src/knowledgedb-generator/codegen.h b/src/knowledgedb-generator/codegen.h index 001983f..93ea995 100644 --- a/src/knowledgedb-generator/codegen.h +++ b/src/knowledgedb-generator/codegen.h @@ -1,45 +1,45 @@ /* 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 . + along with this program. If not, see . */ #ifndef KITINERARY_GENERATOR_CODEGEN_H #define KITINERARY_GENERATOR_CODEGEN_H #include "knowledgedb.h" class QByteArray; class QIODevice; class QString; namespace KItinerary { namespace Generator { class Timezones; /** Code generation utilities. */ namespace CodeGen { void writeLicenseHeader(QIODevice *out); void writeCoordinate(QIODevice *out, const KnowledgeDb::Coordinate &coord); void writeCountryIsoCode(QIODevice *out, const QString &isoCode); void writeTimezone(QIODevice *out, const QByteArray &tzName); void writeTimezoneEnum(QIODevice *out, const QByteArray &tzName); } } } #endif // KITINERARY_GENERATOR_CODEGEN_H diff --git a/src/knowledgedb-generator/countrydbgenerator.cpp b/src/knowledgedb-generator/countrydbgenerator.cpp index bef7f65..4d6d7cb 100644 --- a/src/knowledgedb-generator/countrydbgenerator.cpp +++ b/src/knowledgedb-generator/countrydbgenerator.cpp @@ -1,254 +1,254 @@ /* 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 . + along with this program. If not, see . */ #include "countrydbgenerator.h" #include "codegen.h" #include "util.h" #include "wikidata.h" #include #include #include #include using namespace KItinerary; using namespace KItinerary::Generator; namespace KItinerary { namespace Generator { static bool operator<(const CountryDbGenerator::Country &lhs, const CountryDbGenerator::Country &rhs) { return lhs.uri < rhs.uri; } static bool operator<(const CountryDbGenerator::Country &lhs, const QUrl &rhs) { return lhs.uri < rhs; } } } bool CountryDbGenerator::generate(QIODevice* out) { if (!fetchCountryList() || !fetchDrivingDirections() || !fetchPowerPlugTypes()) { return false; } CodeGen::writeLicenseHeader(out); out->write(R"( #include "knowledgedb.h" #include "countrydb.h" namespace KItinerary { namespace KnowledgeDb { )"); writeCountryTable(out); out->write(R"( } } )"); printSummary(); return true; } bool CountryDbGenerator::fetchCountryList() { const auto countryArray = WikiData::query(R"( SELECT DISTINCT ?country ?countryLabel ?isoCode ?demolished WHERE { ?country (wdt:P31/wdt:P279*) wd:Q6256. ?country wdt:P297 ?isoCode. OPTIONAL { ?country wdt:P576 ?demolished. } SERVICE wikibase:label { bd:serviceParam wikibase:language "en". } } ORDER BY (?country))", "wikidata_country.json"); if (countryArray.isEmpty()) { qWarning() << "Empty query result!"; return false; } for (const auto &countryData : countryArray) { const auto countryObj = countryData.toObject(); if (countryObj.contains(QLatin1String("demolished"))) { continue; } const auto uri = insertOrMerge(countryObj); const auto isoCode = countryObj.value(QLatin1String("isoCode")).toObject().value(QLatin1String("value")).toString().toUpper(); if (isoCode.size() != 2 || !Util::containsOnlyLetters(isoCode)) { qWarning() << "ISO 3166-1 alpha 2 format violation" << isoCode << uri; continue; } const auto it = m_isoCodeMap.find(isoCode); if (it != m_isoCodeMap.end() && (*it).second != uri) { ++m_isoCodeConflicts; qWarning() << "Conflict on ISO 3166-1 alpha 2 id" << isoCode << uri << m_isoCodeMap[isoCode]; } else { m_isoCodeMap[isoCode] = uri; } } return true; } bool CountryDbGenerator::fetchDrivingDirections() { const auto countryArray = WikiData::query(R"( SELECT DISTINCT ?country ?drivingSide ?drivingSideEndTime WHERE { ?country (wdt:P31/wdt:P279*) wd:Q6256. ?country p:P1622 ?drivingSideStmt. ?drivingSideStmt ps:P1622 ?drivingSide. OPTIONAL { ?drivingSideStmt pq:P582 ?drivingSideEndTime. } } ORDER BY (?country))", "wikidata_country_driving_side.json"); if (countryArray.isEmpty()) { qWarning() << "Empty query result!"; return false; } for (const auto &countryData : countryArray) { const auto countryObj = countryData.toObject(); insertOrMerge(countryObj); } return true; } bool CountryDbGenerator::fetchPowerPlugTypes() { const auto countryArray = WikiData::query(R"( SELECT DISTINCT ?country ?plugType ?plugTypeEndTime WHERE { ?country (wdt:P31/wdt:P279*) wd:Q6256. ?country p:P2853 ?plugTypeStmt. ?plugTypeStmt ps:P2853 ?plugType. OPTIONAL { ?plugTypeStmt pq:P582 ?plugTypeEndTime. } } ORDER BY (?country))", "wikidata_country_power_plug_type.json"); if (countryArray.isEmpty()) { qWarning() << "Empty query result!"; return false; } for (const auto &countryData : countryArray) { const auto countryObj = countryData.toObject(); insertOrMerge(countryObj); } return true; } QUrl CountryDbGenerator::insertOrMerge(const QJsonObject& obj) { if (obj.isEmpty()) { return {}; } Country c; c.uri = QUrl(obj.value(QLatin1String("country")).toObject().value(QLatin1String("value")).toString()); c.name = obj.value(QLatin1String("countryLabel")).toObject().value(QLatin1String("value")).toString(); if (!obj.contains(QLatin1String("drivingSideEndTime"))) { c.drivingSide = obj.value(QLatin1String("drivingSide")).toObject().value(QLatin1String("value")).toString(); } if (!obj.contains(QLatin1String("plugTypeEndTime")) && obj.contains(QLatin1String("plugType"))) { c.powerPlugTypes.insert(QUrl(obj.value(QLatin1String("plugType")).toObject().value(QLatin1String("value")).toString()).fileName()); } const auto it = std::lower_bound(m_countries.begin(), m_countries.end(), c); if (it != m_countries.end() && (*it).uri == c.uri) { if (!(*it).drivingSide.isEmpty() && !c.drivingSide.isEmpty() && (*it).drivingSide != c.drivingSide) { qWarning() << "Conflicting driving side information for" << c.name << c.uri; } else if (!c.drivingSide.isEmpty()) { (*it).drivingSide = c.drivingSide; } (*it).powerPlugTypes += c.powerPlugTypes; return c.uri; } m_countries.insert(it, c); return c.uri; } struct plug_type_mapping { const char *wikidataId; const char *enumName; }; static const plug_type_mapping plug_type_table[] = { { "Q24288454", "TypeA" }, { "Q24288456", "TypeB" }, { "Q1378312", "TypeC" }, { "Q2335524", "TypeD" }, { "Q2335536", "TypeE" }, { "Q1123613", "TypeF" }, { "Q1528507", "TypeG" }, { "Q1266396", "TypeH" }, { "Q2335539", "TypeI" }, { "Q2335530", "TypeJ" }, { "Q1502017", "TypeK" }, { "Q1520890", "TypeL" }, { "Q1383497", "TypeM" }, { "Q1653438", "TypeN" } }; static const auto plug_type_table_size = sizeof(plug_type_table) / sizeof(plug_type_mapping); void CountryDbGenerator::writeCountryTable(QIODevice *out) { const auto plug_type_table_end = plug_type_table + plug_type_table_size; out->write("static const Country country_table[] = {\n"); for (const auto &kv : m_isoCodeMap) { const auto countryIt = std::lower_bound(m_countries.begin(), m_countries.end(), kv.second); out->write(" {CountryId{\""); out->write(kv.first.toLatin1()); out->write("\"}, "); if ((*countryIt).drivingSide.endsWith(QLatin1String("/Q14565199"))) { out->write("DrivingSide::Right"); } else if ((*countryIt).drivingSide.endsWith(QLatin1String("/Q13196750"))) { out->write("DrivingSide::Left"); } else { out->write("DrivingSide::Unknown"); } out->write(", {"); QStringList plugTypes; plugTypes.reserve((*countryIt).powerPlugTypes.size()); for (const auto &plugType : (*countryIt).powerPlugTypes) { const auto it = std::find_if(plug_type_table, plug_type_table_end, [plugType](const plug_type_mapping &elem) { return QLatin1String(elem.wikidataId) == plugType; }); if (it != plug_type_table_end) { plugTypes.push_back(QLatin1String((*it).enumName)); } else { qWarning() << "Unknown plug type" << plugType << (*countryIt).name; } } std::sort(plugTypes.begin(), plugTypes.end()); out->write(plugTypes.join(QLatin1Char('|')).toUtf8()); out->write("}"); out->write("}, // "); out->write((*countryIt).name.toUtf8()); out->write("\n"); } out->write("};\n\n"); } void CountryDbGenerator::printSummary() { qDebug() << "Generated database containing" << m_isoCodeMap.size() << "countries."; qDebug() << "ISO 3166-1 alpha 2 code collisions:" << m_isoCodeConflicts; } diff --git a/src/knowledgedb-generator/countrydbgenerator.h b/src/knowledgedb-generator/countrydbgenerator.h index 4041d79..12157cc 100644 --- a/src/knowledgedb-generator/countrydbgenerator.h +++ b/src/knowledgedb-generator/countrydbgenerator.h @@ -1,63 +1,63 @@ /* 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 . + along with this program. If not, see . */ #ifndef KITINERARY_GENERATOR_COUNTRYDBGENERATOR_H #define KITINERARY_GENERATOR_COUNTRYDBGENERATOR_H #include #include #include #include class QIODevice; class QJsonObject; namespace KItinerary { namespace Generator { /** Generate country data tables. */ class CountryDbGenerator { public: bool generate(QIODevice *out); struct Country { QUrl uri; QString name; QString drivingSide; QSet powerPlugTypes; }; private: bool fetchCountryList(); bool fetchDrivingDirections(); bool fetchPowerPlugTypes(); QUrl insertOrMerge(const QJsonObject &obj); void writeCountryTable(QIODevice *out); void printSummary(); std::vector m_countries; std::map m_isoCodeMap; int m_isoCodeConflicts = 0; }; }} #endif // KITINERARY_GENERATOR_COUNTRYDBGENERATOR_H diff --git a/src/knowledgedb-generator/main.cpp b/src/knowledgedb-generator/main.cpp index a6deb9c..5106f8e 100644 --- a/src/knowledgedb-generator/main.cpp +++ b/src/knowledgedb-generator/main.cpp @@ -1,63 +1,63 @@ /* 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 . + along with this program. If not, see . */ #include "airportdbgenerator.h" #include "countrydbgenerator.h" #include "timezonedbgenerator.h" #include "trainstationdbgenerator.h" #include #include #include #include using namespace KItinerary::Generator; int main(int argc, char **argv) { QCoreApplication app(argc, argv); QCommandLineParser parser; QCommandLineOption dbOpt({QStringLiteral("d"), QStringLiteral("database")}, QStringLiteral("The database to generate."), QStringLiteral("database type")); parser.addOption(dbOpt); QCommandLineOption outputOpt({QStringLiteral("o"), QStringLiteral("output")}, QStringLiteral("Output file."), QStringLiteral("output file")); parser.addOption(outputOpt); parser.addHelpOption(); parser.process(app); QFile out(parser.value(outputOpt)); if (!out.open(QFile::WriteOnly)) { qWarning() << out.errorString(); return 1; } if (parser.value(dbOpt) == QLatin1String("airport")) { AirportDbGenerator gen; return gen.generate(&out) ? 0 : 1; } else if (parser.value(dbOpt) == QLatin1String("country")) { CountryDbGenerator gen; return gen.generate(&out) ? 0 : 1; } else if (parser.value(dbOpt) == QLatin1String("timezone")) { TimezoneDbGenerator gen; gen.generate(&out); } else if (parser.value(dbOpt) == QLatin1String("timezoneheader")) { TimezoneDbGenerator gen; gen.generateHeader(&out); } else if (parser.value(dbOpt) == QLatin1String("trainstation")) { TrainStationDbGenerator gen; return gen.generate(&out) ? 0 : 1; } } diff --git a/src/knowledgedb-generator/timezonedbgenerator.cpp b/src/knowledgedb-generator/timezonedbgenerator.cpp index 22166a2..5ec1e9d 100644 --- a/src/knowledgedb-generator/timezonedbgenerator.cpp +++ b/src/knowledgedb-generator/timezonedbgenerator.cpp @@ -1,105 +1,105 @@ /* 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 . + along with this program. If not, see . */ #include "timezonedbgenerator.h" #include "codegen.h" #include "timezones.h" #include #include using namespace KItinerary::Generator; void TimezoneDbGenerator::generate(QIODevice *out) { CodeGen::writeLicenseHeader(out); Timezones tzDb; out->write(R"( #include "timezonedb_p.h" #include "timezonedb_data_p.h" namespace KItinerary { namespace KnowledgeDb { // timezone name strings static const char timezone_names[] = )"); // timezone string tables for (const auto &tz : tzDb.m_zones) { out->write(" "); out->write("\""); out->write(tz); out->write("\\0\"\n"); } out->write(R"(; static constexpr const CountryTimezoneMap country_timezone_map[] = { )"); for (const auto &map : tzDb.m_countryZones) { if (map.second.size() != 1) { continue; } out->write(" { "); CodeGen::writeCountryIsoCode(out, map.first); out->write(", "); CodeGen::writeTimezone(out, map.second.at(0)); out->write(" },\n"); } out->write(R"(}; } } )"); } void TimezoneDbGenerator::generateHeader(QIODevice *out) { CodeGen::writeLicenseHeader(out); Timezones tzDb; out->write(R"( #ifndef KITINERARY_KNOWLEDGEDB_TIMEZONEDB_DATA_P_H #define KITINERARY_KNOWLEDGEDB_TIMEZONEDB_DATA_P_H #include namespace KItinerary { namespace KnowledgeDb { /** Enum representing all timezones, values match the offsets into the timezone name string table. */ enum class Tz : uint16_t { )"); for (const auto &tz : tzDb.m_zones) { out->write(" "); CodeGen::writeTimezoneEnum(out, tz); out->write(" = "); out->write(QByteArray::number(tzDb.offset(tz))); out->write(",\n"); } out->write(R"(}; } } #endif )"); } diff --git a/src/knowledgedb-generator/timezonedbgenerator.h b/src/knowledgedb-generator/timezonedbgenerator.h index 3fc2e85..f522cce 100644 --- a/src/knowledgedb-generator/timezonedbgenerator.h +++ b/src/knowledgedb-generator/timezonedbgenerator.h @@ -1,37 +1,37 @@ /* 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 . + along with this program. If not, see . */ #ifndef KITINERARY_TIMEZONEDBGENERATOR_H #define KITINERARY_TIMEZONEDBGENERATOR_H class QIODevice; namespace KItinerary { namespace Generator { /** Generate IANA timezone lookup table. */ class TimezoneDbGenerator { public: void generate(QIODevice *out); void generateHeader(QIODevice *out); }; } } #endif // KITINERARY_TIMEZONEDBGENERATOR_H diff --git a/src/knowledgedb-generator/trainstationdbgenerator.cpp b/src/knowledgedb-generator/trainstationdbgenerator.cpp index d49759d..23d0779 100644 --- a/src/knowledgedb-generator/trainstationdbgenerator.cpp +++ b/src/knowledgedb-generator/trainstationdbgenerator.cpp @@ -1,332 +1,332 @@ /* 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 . + along with this program. If not, see . */ #include "trainstationdbgenerator.h" #include "codegen.h" #include "util.h" #include "wikidata.h" #include #include #include #include #include using namespace KItinerary::Generator; namespace KItinerary { namespace Generator { static bool operator<(const TrainStationDbGenerator::Station &lhs, const TrainStationDbGenerator::Station &rhs) { return lhs.uri < rhs.uri; } static bool operator<(const TrainStationDbGenerator::Station &lhs, const QUrl &rhs) { return lhs.uri < rhs; } } } bool TrainStationDbGenerator::generate(QIODevice *out) { // retrieve content from Wikidata if (!fetchIBNR() || !fetchGaresConnexions() || !fetchCountryInformation()) { return false; } // timezone lookup and filtering processStations(); // code generation CodeGen::writeLicenseHeader(out); out->write(R"( #include "knowledgedb.h" #include "timezonedb.h" #include "trainstationdb.h" #include "timezonedb_data_p.h" namespace KItinerary { namespace KnowledgeDb { )"); writeStationData(out); writeIBNRMap(out); writeGareConnexionMap(out); out->write(R"( } } )"); printSummary(); return true; } bool TrainStationDbGenerator::fetchIBNR() { const auto stationArray = WikiData::query(R"( SELECT DISTINCT ?station ?stationLabel ?ibnr ?coord WHERE { ?station (wdt:P31/wdt:P279*) wd:Q55488. ?station wdt:P954 ?ibnr. OPTIONAL { ?station wdt:P625 ?coord. } SERVICE wikibase:label { bd:serviceParam wikibase:language "en". } } ORDER BY (?station))", "wikidata_trainstation_ibnr.json"); if (stationArray.isEmpty()) { qWarning() << "Empty query result!"; return false; } for (const auto &stationData : stationArray) { const auto stationObj = stationData.toObject(); const auto uri = insertOrMerge(stationObj); const auto id = stationObj.value(QLatin1String("ibnr")).toObject().value(QLatin1String("value")).toString().toUInt(); if (id < 1000000 || id > 9999999) { ++m_idFormatViolations; qWarning() << "IBNR format violation" << id << uri; continue; } const auto it = m_ibnrMap.find(id); if (it != m_ibnrMap.end() && (*it).second != uri) { ++m_idConflicts; qWarning() << "Conflict on IBNR" << id << uri << m_ibnrMap[id]; } else { m_ibnrMap[id] = uri; } } return true; } bool TrainStationDbGenerator::fetchGaresConnexions() { const auto stationArray = WikiData::query(R"( SELECT DISTINCT ?station ?stationLabel ?gareConnexionId ?coord WHERE { ?station (wdt:P31/wdt:P279*) wd:Q55488. ?station wdt:P3104 ?gareConnexionId. OPTIONAL { ?station wdt:P625 ?coord. } SERVICE wikibase:label { bd:serviceParam wikibase:language "en". } } ORDER BY (?station))", "wikidata_trainstation_gare_connexion.json"); if (stationArray.isEmpty()) { qWarning() << "Empty query result!"; return false; } for (const auto &stationData : stationArray) { const auto stationObj = stationData.toObject(); const auto uri = insertOrMerge(stationObj); const auto id = stationObj.value(QLatin1String("gareConnexionId")).toObject().value(QLatin1String("value")).toString().toUpper(); if (id.size() != 5 || !Util::containsOnlyLetters(id)) { ++m_idFormatViolations; qWarning() << "Gares & Connexions ID format violation" << id << uri; continue; } const auto it = m_garesConnexionsIdMap.find(id); if (it != m_garesConnexionsIdMap.end() && (*it).second != uri) { ++m_idConflicts; qWarning() << "Conflict on Gares & Connexions ID" << id << uri << m_garesConnexionsIdMap[id]; } else { m_garesConnexionsIdMap[id] = uri; } } return true; } bool TrainStationDbGenerator::fetchCountryInformation() { const auto stationArray = WikiData::query(R"( SELECT DISTINCT ?station ?isoCode WHERE { ?station (wdt:P31/wdt:P279*) wd:Q55488. ?station wdt:P17 ?country. ?country wdt:P297 ?isoCode. } ORDER BY (?station))", "wikidata_trainstation_country.json"); if (stationArray.isEmpty()) { qWarning() << "Empty query result!"; return false; } for (const auto &stationData : stationArray) { const auto uri = insertOrMerge(stationData.toObject(), true); } return true; } QUrl TrainStationDbGenerator::insertOrMerge(const QJsonObject &obj, bool mergeOnly) { if (obj.isEmpty()) { return {}; } Station s; s.uri = QUrl(obj.value(QLatin1String("station")).toObject().value(QLatin1String("value")).toString()); s.name = obj.value(QLatin1String("stationLabel")).toObject().value(QLatin1String("value")).toString(); s.coord = WikiData::parseCoordinate(obj.value(QLatin1String("coord")).toObject().value(QLatin1String("value")).toString()); s.isoCode = obj.value(QLatin1String("isoCode")).toObject().value(QLatin1String("value")).toString(); const auto it = std::lower_bound(m_stations.begin(), m_stations.end(), s); if (it != m_stations.end() && (*it).uri == s.uri) { if ((*it).name.isEmpty()) { (*it).name = s.name; } // check for coordinate conflicts if (s.coord.isValid() && (*it).coord.isValid()) { if (std::abs(s.coord.latitude - (*it).coord.latitude) > 0.2f || std::abs(s.coord.longitude - (*it).coord.longitude) > 0.2f) { ++m_coordinateConflicts; qWarning() << s.uri << "has multiple conflicting coordinates"; } // pick always the same independent of the input order, so stabilize generated output (*it).coord.latitude = std::min((*it).coord.latitude, s.coord.latitude); (*it).coord.longitude = std::min((*it).coord.longitude, s.coord.longitude); } if ((*it).isoCode != s.isoCode && !s.isoCode.isEmpty()) { if (!(*it).isoCode.isEmpty()) { ++m_countryConflicts; qWarning() << s.uri << (*it).name << "has multiple country codes"; } else { (*it).isoCode = s.isoCode; } } return s.uri; } if (!mergeOnly) { m_stations.insert(it, s); } return s.uri; } void TrainStationDbGenerator::processStations() { for (auto it = m_stations.begin(); it != m_stations.end();) { if (!(*it).coord.isValid()) { qDebug() << "Station has no geo coordinates:" << (*it).name << (*it).uri; } (*it).tz = m_tzDb.timezoneForLocation((*it).isoCode, (*it).coord); if ((*it).tz.isEmpty() && ((*it).coord.isValid() || !(*it).isoCode.isEmpty())) { ++m_timezoneLookupFailure; qWarning() << "Timezone lookup failure:" << (*it).name << (*it).uri; } if (!(*it).coord.isValid() && (*it).tz.isEmpty() && (*it).isoCode.isEmpty()) { // no useful information it = m_stations.erase(it); } else { ++it; } } } void TrainStationDbGenerator::writeStationData(QIODevice *out) { out->write("static const TrainStation trainstation_table[] = {\n"); for (const auto &station : m_stations) { out->write(" {"); CodeGen::writeCoordinate(out, station.coord); out->write(", "); CodeGen::writeTimezone(out, station.tz); out->write(", "); CodeGen::writeCountryIsoCode(out, station.isoCode); out->write("}, // "); out->write(station.name.toUtf8()); out->write("\n"); } out->write("};\n\n"); } void TrainStationDbGenerator::writeIBNRMap(QIODevice *out) { out->write("static const IBNR ibnr_table[] = {\n"); for (const auto &it : m_ibnrMap) { const auto station = std::lower_bound(m_stations.begin(), m_stations.end(), it.second); if (station == m_stations.end() || (*station).uri != it.second) { continue; } out->write(" IBNR{"); out->write(QByteArray::number(it.first)); out->write("}, // "); out->write((*station).name.toUtf8()); out->write("\n"); } out->write("};\n\n"); out->write("static const TrainStationIndex ibnr_index[] = {\n"); for (const auto &it : m_ibnrMap) { const auto station = std::lower_bound(m_stations.begin(), m_stations.end(), it.second); if (station == m_stations.end() || (*station).uri != it.second) { continue; } out->write(" "); out->write(QByteArray::number((int)std::distance(m_stations.begin(), station))); out->write(", // "); out->write(QByteArray::number(it.first)); out->write(" -> "); out->write((*station).name.toUtf8()); out->write("\n"); } out->write("};\n\n"); } void TrainStationDbGenerator::writeGareConnexionMap(QIODevice *out) { out->write("static const GaresConnexionsId garesConnexionsId_table[] = {\n"); for (const auto &it : m_garesConnexionsIdMap) { const auto station = std::lower_bound(m_stations.begin(), m_stations.end(), it.second); if (station == m_stations.end() || (*station).uri != it.second) { continue; } out->write(" GaresConnexionsId{\""); out->write(it.first.toUtf8()); out->write("\"}, // "); out->write((*station).name.toUtf8()); out->write("\n"); } out->write("};\n\n"); out->write("static const TrainStationIndex garesConnexionsId_index[] = {\n"); for (const auto &it : m_garesConnexionsIdMap) { const auto station = std::lower_bound(m_stations.begin(), m_stations.end(), it.second); if (station == m_stations.end() || (*station).uri != it.second) { continue; } out->write(" "); out->write(QByteArray::number((int)std::distance(m_stations.begin(), station))); out->write(", // "); out->write(it.first.toUtf8()); out->write(" -> "); out->write((*station).name.toUtf8()); out->write("\n"); } out->write("};\n\n"); } void TrainStationDbGenerator::printSummary() { qDebug() << "Generated database containing" << m_stations.size() << "train stations"; qDebug() << "IBNR index:" << m_ibnrMap.size() << "elements"; qDebug() << "Gares & Connexions ID index:" << m_garesConnexionsIdMap.size() << "elements"; qDebug() << "Identifier collisions:" << m_idConflicts; qDebug() << "Identifier format violations:" << m_idFormatViolations; qDebug() << "Coordinate conflicts:" << m_coordinateConflicts; qDebug() << "Country ISO code conflicts: " << m_countryConflicts; qDebug() << "Failed timezone lookups:" << m_timezoneLookupFailure; } diff --git a/src/knowledgedb-generator/trainstationdbgenerator.h b/src/knowledgedb-generator/trainstationdbgenerator.h index 0970088..34910e9 100644 --- a/src/knowledgedb-generator/trainstationdbgenerator.h +++ b/src/knowledgedb-generator/trainstationdbgenerator.h @@ -1,78 +1,78 @@ /* 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 . + along with this program. If not, see . */ #ifndef KITINERARY_GENERATOR_TRAINSTATIONDBGENERATOR_H #define KITINERARY_GENERATOR_TRAINSTATIONDBGENERATOR_H #include #include #include #include #include #include #include class QIODevice; class QJsonObject; namespace KItinerary { namespace Generator { /** Generate train station data tables. */ class TrainStationDbGenerator { public: bool generate(QIODevice *out); struct Station { QUrl uri; QString name; KnowledgeDb::Coordinate coord; QByteArray tz; QString isoCode; }; private: bool fetchIBNR(); bool fetchGaresConnexions(); bool fetchCountryInformation(); QUrl insertOrMerge(const QJsonObject &obj, bool mergeOnly = false); void processStations(); void writeStationData(QIODevice *out); void writeIBNRMap(QIODevice *out); void writeGareConnexionMap(QIODevice *out); void printSummary(); std::vector m_stations; std::map m_ibnrMap; std::map m_garesConnexionsIdMap; Timezones m_tzDb; int m_idConflicts = 0; int m_idFormatViolations = 0; int m_timezoneLookupFailure = 0; int m_coordinateConflicts = 0; int m_countryConflicts = 0; }; } } #endif // KITINERARY_GENERATOR_TRAINSTATIONDBGENERATOR_H diff --git a/src/knowledgedb-generator/util.cpp b/src/knowledgedb-generator/util.cpp index 0f48426..8497c12 100644 --- a/src/knowledgedb-generator/util.cpp +++ b/src/knowledgedb-generator/util.cpp @@ -1,32 +1,32 @@ /* 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 . + along with this program. If not, see . */ #include "util.h" #include using namespace KItinerary::Generator; bool Util::containsOnlyLetters(const QString& s) { for (const auto c : s) { if (c < QLatin1Char('A') || c > QLatin1Char('Z')) { return false; } } return true; } diff --git a/src/knowledgedb-generator/util.h b/src/knowledgedb-generator/util.h index 8437ab0..b25fad8 100644 --- a/src/knowledgedb-generator/util.h +++ b/src/knowledgedb-generator/util.h @@ -1,35 +1,35 @@ /* 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 . + along with this program. If not, see . */ #ifndef KITINERARY_GENERATOR_UTIL_H #define KITINERARY_GENERATOR_UTIL_H class QString; namespace KItinerary { namespace Generator { /** Misc ustils for the static data code generator. */ namespace Util { bool containsOnlyLetters(const QString &s); } } } #endif // KITINERARY_GENERATOR_UTIL_H diff --git a/src/knowledgedb-generator/wikidata.cpp b/src/knowledgedb-generator/wikidata.cpp index 1e65d47..aec4fd8 100644 --- a/src/knowledgedb-generator/wikidata.cpp +++ b/src/knowledgedb-generator/wikidata.cpp @@ -1,84 +1,84 @@ /* 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 . + along with this program. If not, see . */ #include "wikidata.h" #include #include #include #include #include #include #include #include #include #include #include using namespace KItinerary; using namespace KItinerary::Generator; KnowledgeDb::Coordinate WikiData::parseCoordinate(const QString& value) { const auto idx = value.indexOf(QLatin1Char(' ')); bool latOk = false, longOk = false; KnowledgeDb::Coordinate c; c.longitude = value.midRef(6, idx - 6).toFloat(&latOk); c.latitude = value.midRef(idx + 1, value.size() - idx - 2).toFloat(&longOk); if (!latOk || !longOk) { c.longitude = NAN; c.latitude = NAN; } return c; } QJsonArray WikiData::query(const char *sparqlQuery, const char *cacheFileName) { QDir().mkdir(QLatin1String("data")); QFile cacheFile(QLatin1String("data/") + QString::fromUtf8(cacheFileName)); QByteArray data; if (cacheFile.exists() && qEnvironmentVariableIsSet("KITINERARY_USE_WIKIDATA_CACHE")) { cacheFile.open(QFile::ReadOnly); data = cacheFile.readAll(); cacheFile.close(); } if (data.isEmpty()) { QUrl url(QStringLiteral("https://query.wikidata.org/sparql")); QUrlQuery query; query.addQueryItem(QStringLiteral("query"), QString::fromUtf8(sparqlQuery).trimmed().simplified()); query.addQueryItem(QStringLiteral("format"), QStringLiteral("json")); url.setQuery(query); QNetworkAccessManager nam; auto reply = nam.get(QNetworkRequest(url)); QObject::connect(reply, &QNetworkReply::finished, QCoreApplication::instance(), &QCoreApplication::quit); QCoreApplication::exec(); if (reply->error() != QNetworkReply::NoError) { qWarning() << reply->errorString(); return {}; } data = reply->readAll(); cacheFile.open(QFile::WriteOnly); cacheFile.write(data); } const auto doc = QJsonDocument::fromJson(data); const auto resultArray = doc.object().value(QLatin1String("results")).toObject().value(QLatin1String("bindings")).toArray(); return resultArray; } diff --git a/src/knowledgedb-generator/wikidata.h b/src/knowledgedb-generator/wikidata.h index 2e19d5e..4edf80d 100644 --- a/src/knowledgedb-generator/wikidata.h +++ b/src/knowledgedb-generator/wikidata.h @@ -1,49 +1,49 @@ /* 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 . + along with this program. If not, see . */ #ifndef KITINERARY_WIKIDATA_H #define KITINERARY_WIKIDATA_H #include "knowledgedb.h" class QJsonArray; class QString; namespace KItinerary { namespace Generator { /** Utilities for interaction with Wikidata. */ namespace WikiData { /** Parse a geo coordinate from a Wikidata JSON-LD response. */ KnowledgeDb::Coordinate parseCoordinate(const QString &value); /** Retrieve the result of a SPARQL query from Wikidata. * This has the ability to use an already retrieved local cache * instead, for reducing query load and bandwidth use while working * on the generator code. * @warning This uses the QCoreApplication event loop to emulated * synchronous calls, do never ever use this outside of a simple * CLI tool! */ QJsonArray query(const char *sparqlQuery, const char *cacheFileName); } } } #endif // KITINERARY_WIKIDATA_H diff --git a/src/knowledgedb/countrydb.cpp b/src/knowledgedb/countrydb.cpp index 9afcf11..3540ddf 100644 --- a/src/knowledgedb/countrydb.cpp +++ b/src/knowledgedb/countrydb.cpp @@ -1,126 +1,126 @@ /* 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 . + along with this program. If not, see . */ #include "countrydb.h" #include "countrydb_data.cpp" #include #include #include using namespace KItinerary; using namespace KItinerary::KnowledgeDb; static_assert(sizeof(CountryId) <= 2, "CountryId too large"); CountryId::CountryId(const QString& id) { m_id1 = m_id2 = 0; m_unused = 0; if (id.size() != 2) { return; } if (!id.at(0).isUpper() || !id.at(1).isUpper()) { return; } m_id1 = id.at(0).toLatin1() - '@'; m_id2 = id.at(1).toLatin1() - '@'; } Country KnowledgeDb::countryForId(CountryId id) { const auto it = std::lower_bound(std::begin(country_table), std::end(country_table), id, [](const Country &lhs, CountryId rhs) { return lhs.id < rhs; }); if (it == std::end(country_table) || (*it).id != id) { return {CountryId{}, DrivingSide::Unknown, Unknown}; } return (*it); } const Country* KnowledgeDb::countriesBegin() { return std::begin(country_table); } const Country* KnowledgeDb::countriesEnd() { return std::end(country_table); } QString KnowledgeDb::CountryId::toString() const { QString s; s.resize(2); s[0] = QLatin1Char('@' + m_id1); s[1] = QLatin1Char('@' + m_id2); return s; } struct PowerPlugCompatMap { PowerPlugType plug; PowerPlugTypes sockets; }; static const PowerPlugCompatMap power_plug_compat_map[] = { { TypeA, TypeA | TypeB }, { TypeB, TypeB }, { TypeC, TypeC | TypeE | TypeF | TypeH | TypeJ | TypeK | TypeL | TypeN }, { TypeD, TypeD }, { TypeE, TypeE | TypeF | TypeK }, // TypeE <-> TypeF not strictly correct, but in practise almost always compatible { TypeF, TypeE | TypeF | TypeK }, { TypeG, TypeG }, { TypeH, TypeH }, { TypeI, TypeI }, { TypeJ, TypeJ }, { TypeK, TypeK }, { TypeL, TypeL }, { TypeM, TypeM }, { TypeN, TypeN } }; PowerPlugTypes KnowledgeDb::incompatiblePowerPlugs(PowerPlugTypes plugs, PowerPlugTypes sockets) { PowerPlugTypes failPlugs{}; for (const auto map : power_plug_compat_map) { if ((plugs & map.plug) == 0) { continue; } if ((map.sockets & sockets) == 0) { failPlugs |= map.plug; } } return failPlugs; } PowerPlugTypes KnowledgeDb::incompatiblePowerSockets(PowerPlugTypes plugs, PowerPlugTypes sockets) { PowerPlugTypes failSockets{}; for (const auto map : power_plug_compat_map) { if ((plugs & map.plug) == 0) { continue; } if ((map.sockets & ~sockets) != 0) { failSockets |= (map.sockets ^ sockets) & sockets; } } return failSockets & ~plugs; } #include "moc_countrydb.cpp" diff --git a/src/knowledgedb/countrydb.h b/src/knowledgedb/countrydb.h index edda85a..9e9293c 100644 --- a/src/knowledgedb/countrydb.h +++ b/src/knowledgedb/countrydb.h @@ -1,144 +1,144 @@ /* 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 . + along with this program. If not, see . */ #ifndef KITINERARY_COUNTRYDB_H #define KITINERARY_COUNTRYDB_H #include "kitinerary_export.h" #include #include class QString; namespace KItinerary { namespace KnowledgeDb { KITINERARY_EXPORT Q_NAMESPACE /** ISO 3166-1 alpha 2 country identifier. */ class CountryId { public: inline constexpr CountryId() : m_id1(0) , m_id2(0) , m_unused(0) {} inline constexpr CountryId(const char id[2]) : m_id1(id[0] - '@') , m_id2(id[1] - '@') , m_unused(0) {} KITINERARY_EXPORT explicit CountryId(const QString &id); inline constexpr bool operator<(CountryId other) const { return (m_id1 << 5 | m_id2) < (other.m_id1 << 5 | other.m_id2); } inline constexpr bool operator==(CountryId other) const { return m_id1 == other.m_id1 && m_id2 == other.m_id2; } inline constexpr bool operator!=(CountryId other) const { return m_id1 != other.m_id1 || m_id2 != other.m_id2; } inline constexpr bool isValid() const { return m_id1 != 0 || m_id2 != 0; } KITINERARY_EXPORT QString toString() const; private: uint16_t m_id1 : 5; uint16_t m_id2 : 5; uint16_t m_unused : 6; }; /** Driving side. */ enum class DrivingSide : uint8_t { Unknown, Left, Right, }; Q_ENUM_NS(DrivingSide) /** Power plug types. * @note This cannot be an enum class due to QTBUG-47652. */ enum PowerPlugType : uint16_t { Unknown = 0, TypeA = 1 << 0, ///< US two-pin plugs TypeB = 1 << 1, ///< US three-pin plugs TypeC = 1 << 2, ///< Europlug TypeD = 1 << 3, ///< Type D TypeE = 1 << 4, ///< French plug TypeF = 1 << 5, ///< Schuko plug TypeG = 1 << 6, ///< UK plug TypeH = 1 << 7, ///< Israel plug TypeI = 1 << 8, ///< Australian plug TypeJ = 1 << 9, ///< Swiss plug TypeK = 1 << 10, ///< Danish plug TypeL = 1 << 11, ///< Type L TypeM = 1 << 12, ///< Type M TypeN = 1 << 13, ///< Type N (Brasilian) }; Q_DECLARE_FLAGS(PowerPlugTypes, PowerPlugType) Q_FLAG_NS(PowerPlugTypes) /** Returns the power plugs out of @p plugs that wont fit into @p sockets. */ KITINERARY_EXPORT PowerPlugTypes incompatiblePowerPlugs(PowerPlugTypes plugs, PowerPlugTypes sockets); /** Returns the power sockets out pf @p sockets that are unable to receive plugs * out of @p plugs, excluding those in @p plugs. */ KITINERARY_EXPORT PowerPlugTypes incompatiblePowerSockets(PowerPlugTypes plugs, PowerPlugTypes sockets); /** Country information. */ struct Country { CountryId id; DrivingSide drivingSide; PowerPlugTypes powerPlugTypes; // TODO voltage/frequency // TODO currency }; /** Look up contry infromation by id. */ KITINERARY_EXPORT Country countryForId(CountryId id); /** Iterator access for the country information table. */ KITINERARY_EXPORT const Country* countriesBegin(); /** Iterator access for the country information table. */ KITINERARY_EXPORT const Country* countriesEnd(); } } Q_DECLARE_OPERATORS_FOR_FLAGS(KItinerary::KnowledgeDb::PowerPlugTypes) Q_DECLARE_METATYPE(KItinerary::KnowledgeDb::DrivingSide) Q_DECLARE_METATYPE(KItinerary::KnowledgeDb::PowerPlugTypes) #endif // KITINERARY_COUNTRYDB_H diff --git a/src/knowledgedb/knowledgedb.cpp b/src/knowledgedb/knowledgedb.cpp index 22fcab0..818be3b 100644 --- a/src/knowledgedb/knowledgedb.cpp +++ b/src/knowledgedb/knowledgedb.cpp @@ -1,20 +1,20 @@ /* 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 . + along with this program. If not, see . */ #include "knowledgedb.h" using namespace KItinerary; diff --git a/src/knowledgedb/knowledgedb.h b/src/knowledgedb/knowledgedb.h index acc34d6..559dd31 100644 --- a/src/knowledgedb/knowledgedb.h +++ b/src/knowledgedb/knowledgedb.h @@ -1,69 +1,69 @@ /* 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 . + along with this program. If not, see . */ #ifndef KITINERARY_KNOWLEDGEDB_H #define KITINERARY_KNOWLEDGEDB_H #include namespace KItinerary { /** Lookup functions, utilities and data types for the static knowledge database. * The content accessible by this functions is extracted from Wikidata and compiled * into this library. * @note The types in this namespace match the binary storage structure and thus * are not intented for use in binary compatible APIs. */ namespace KnowledgeDb { /** Geographical coordinate. * This matches the binary data layout on disk, it's not intended * for use in API. */ struct Coordinate { inline constexpr Coordinate() : longitude(NAN) , latitude(NAN) { } inline explicit constexpr Coordinate(float lng, float lat) : longitude(lng) , latitude(lat) { } inline bool isValid() const { return !std::isnan(latitude) && !std::isnan(longitude); } inline constexpr bool operator==(const Coordinate &other) const { return latitude == other.latitude && longitude == other.longitude; } float longitude; float latitude; }; } } #endif // KITINERARY_KNOWLEDGEDB_H diff --git a/src/knowledgedb/timezonedb.cpp b/src/knowledgedb/timezonedb.cpp index 7c11a9d..e9447b0 100644 --- a/src/knowledgedb/timezonedb.cpp +++ b/src/knowledgedb/timezonedb.cpp @@ -1,42 +1,42 @@ /* 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 . + along with this program. If not, see . */ #include "timezonedb.h" #include "timezonedb_p.h" #include "timezonedb_data.cpp" #include using namespace KItinerary::KnowledgeDb; QTimeZone Timezone::toQTimeZone() const { if (offset > sizeof(timezone_names)) { return {}; } return QTimeZone(timezone_names + offset); } Timezone KItinerary::KnowledgeDb::timezoneForCountry(CountryId country) { const auto it = std::lower_bound(std::begin(country_timezone_map), std::end(country_timezone_map), country); if (it != std::end(country_timezone_map) && (*it).country == country) { return (*it).timezone; } return {}; } diff --git a/src/knowledgedb/timezonedb.h b/src/knowledgedb/timezonedb.h index f4f62b1..7cd8862 100644 --- a/src/knowledgedb/timezonedb.h +++ b/src/knowledgedb/timezonedb.h @@ -1,56 +1,56 @@ /* 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 . + along with this program. If not, see . */ #ifndef KITINERARY_TIMEZONEDB_H #define KITINERARY_TIMEZONEDB_H #include #include #include class QTimeZone; namespace KItinerary { namespace KnowledgeDb { class CountryId; enum class Tz : uint16_t; /** Timezone type as used in the database. * @note Do not use in API/ABI. */ struct Timezone { inline constexpr Timezone() = default; inline constexpr Timezone(Tz tz) : offset(static_cast(tz)) {} /** Returns the corresponding QTimeZone. */ KITINERARY_EXPORT QTimeZone toQTimeZone() const; // offset into timezone name string table uint16_t offset = std::numeric_limits::max(); }; /** Returns the timezone for the given country, as long as there is exactly * one timezone used in that country. */ KITINERARY_EXPORT Timezone timezoneForCountry(CountryId country); } } #endif diff --git a/src/knowledgedb/trainstationdb.cpp b/src/knowledgedb/trainstationdb.cpp index 5c54d44..4cf5368 100644 --- a/src/knowledgedb/trainstationdb.cpp +++ b/src/knowledgedb/trainstationdb.cpp @@ -1,63 +1,63 @@ /* 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 . + along with this program. If not, see . */ #include "trainstationdb.h" #include "trainstationdb_data.cpp" #include #include using namespace KItinerary; using namespace KItinerary::KnowledgeDb; namespace KItinerary { namespace KnowledgeDb { static const auto trainstation_table_size = sizeof(trainstation_table) / sizeof(TrainStation); static_assert(trainstation_table_size < (1 << (sizeof(TrainStationIndex) * 8)), "TrainStationIndex data type too small!"); } } GaresConnexionsId::GaresConnexionsId(const QString& id) { if (id.size() != 5) { return; } m_id = fromChars(id.toUpper().toLatin1().constData()); } TrainStation KnowledgeDb::stationForIbnr(IBNR ibnr) { const auto ibnrIt = std::lower_bound(std::begin(ibnr_table), std::end(ibnr_table), ibnr); if (ibnrIt == std::end(ibnr_table) || *ibnrIt != ibnr) { return {Coordinate{}, Timezone{}, CountryId{}}; } return trainstation_table[ibnr_index[std::distance(std::begin(ibnr_table), ibnrIt)]]; } TrainStation KnowledgeDb::stationForGaresConnexionsId(GaresConnexionsId garesConnexionsId) { const auto gcIt = std::lower_bound(std::begin(garesConnexionsId_table), std::end(garesConnexionsId_table), garesConnexionsId); if (gcIt == std::end(garesConnexionsId_table) || *gcIt != garesConnexionsId) { return {Coordinate{}, Timezone{}, CountryId{}}; } return trainstation_table[garesConnexionsId_index[std::distance(std::begin(garesConnexionsId_table), gcIt)]]; } diff --git a/src/knowledgedb/trainstationdb.h b/src/knowledgedb/trainstationdb.h index af5185a..e0e88ca 100644 --- a/src/knowledgedb/trainstationdb.h +++ b/src/knowledgedb/trainstationdb.h @@ -1,109 +1,109 @@ /* 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 . + along with this program. If not, see . */ #ifndef KITINERARY_TRAINSTATIONDB_H #define KITINERARY_TRAINSTATIONDB_H #include "kitinerary_export.h" #include "countrydb.h" #include "knowledgedb.h" #include "timezonedb.h" #include class QString; namespace KItinerary { namespace KnowledgeDb { /** Position in the train station database. */ typedef uint16_t TrainStationIndex; /** Train station entry in the station table. * @note This is not for use in APIs/ABIs, but matches the binary layout of the database. */ struct TrainStation { Coordinate coordinate; Timezone timezone; CountryId country; }; /** IBNR station id. * 2 digits UIC country code, 5 digits station id */ struct IBNR { inline constexpr IBNR() = default; inline explicit constexpr IBNR(uint32_t ibnr) : id(ibnr) {} inline constexpr bool operator!=(IBNR other) const { return id != other.id; } inline constexpr bool operator<(IBNR other) const { return id < other.id; } uint32_t id = 0; // TODO 24bit would be enough }; /** Gares & Connexion ID. * 2 letters ISO country code, 5 letters station id, expected to be in upper case. */ class GaresConnexionsId { public: inline constexpr GaresConnexionsId() = default; inline explicit constexpr GaresConnexionsId(const char s[5]) : m_id(fromChars(s)) { } KITINERARY_EXPORT explicit GaresConnexionsId(const QString &id); inline constexpr bool operator!=(GaresConnexionsId other) const { return m_id != other.m_id; } inline constexpr bool operator<(GaresConnexionsId other) const { return m_id < other.m_id; } private: static inline constexpr uint32_t fromChars(const char s[5]) { return (s[4] - '@') + 26 * ((s[3] - '@') + 26 * ((s[2] - '@') + 26 * ((s[1] - '@') + 26 * (s[0] - '@')))); } // TODO 24bit would be enough uint32_t m_id = 0; }; /** Lookup train station data by IBNR. */ KITINERARY_EXPORT TrainStation stationForIbnr(IBNR ibnr); /** Lookup train station data by Gares & Connexions ID. */ KITINERARY_EXPORT TrainStation stationForGaresConnexionsId(GaresConnexionsId garesConnexionsId); } } #endif // KITINERARY_TRAINSTATIONDB_H diff --git a/src/locationutil.cpp b/src/locationutil.cpp index 60414e5..78f912f 100644 --- a/src/locationutil.cpp +++ b/src/locationutil.cpp @@ -1,181 +1,181 @@ /* 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 . + along with this program. If not, see . */ #include "locationutil.h" #include #include #include #include #include #include #include #include #include using namespace KItinerary; bool LocationUtil::isLocationChange(const QVariant &res) { // TODO rental car if pickup != dropoff return JsonLd::isA(res) || JsonLd::isA(res) || JsonLd::isA(res); } QVariant LocationUtil::arrivalLocation(const QVariant &res) { if (JsonLd::isA(res)) { return res.value().reservationFor().value().arrivalAirport(); } if (JsonLd::isA(res)) { return res.value().reservationFor().value().arrivalStation(); } if (JsonLd::isA(res)) { return res.value().reservationFor().value().arrivalBusStop(); } if (JsonLd::isA(res)) { return res.value().dropoffLocation(); } return {}; } QVariant LocationUtil::departureLocation(const QVariant &res) { if (JsonLd::isA(res)) { return res.value().reservationFor().value().departureAirport(); } if (JsonLd::isA(res)) { return res.value().reservationFor().value().departureStation(); } if (JsonLd::isA(res)) { return res.value().reservationFor().value().departureBusStop(); } if (JsonLd::isA(res)) { return res.value().pickupLocation(); } return {}; } QVariant LocationUtil::location(const QVariant &res) { if (JsonLd::isA(res)) { return res.value().reservationFor(); } if (JsonLd::isA(res)) { return res.value().reservationFor(); } if (JsonLd::isA(res)) { return res.value().touristAttraction(); } if (JsonLd::isA(res)) { return res.value().reservationFor().value().location(); } if (JsonLd::isA(res)) { return res.value().pickupLocation(); } return {}; } GeoCoordinates LocationUtil::geo(const QVariant &location) { if (JsonLd::canConvert(location)) { return JsonLd::convert(location).geo(); } if (JsonLd::canConvert(location)) { return JsonLd::convert(location).geo(); } return {}; } PostalAddress LocationUtil::address(const QVariant &location) { if (JsonLd::canConvert(location)) { return JsonLd::convert(location).address(); } if (JsonLd::canConvert(location)) { return JsonLd::convert(location).address(); } return {}; } QString LocationUtil::name(const QVariant &location) { if (JsonLd::isA(location)) { const auto airport = location.value(); return airport.name().isEmpty() ? airport.iataCode() : airport.name(); } if (JsonLd::canConvert(location)) { return JsonLd::convert(location).name(); } if (JsonLd::canConvert(location)) { return JsonLd::convert(location).name(); } return {}; } // see https://en.wikipedia.org/wiki/Haversine_formula int LocationUtil::distance(const GeoCoordinates &coord1, const GeoCoordinates &coord2) { const auto degToRad = M_PI / 180.0; const auto earthRadius = 6371000.0; // in meters const auto d_lat = (coord1.latitude() - coord2.latitude()) * degToRad; const auto d_lon = (coord1.longitude() - coord2.longitude()) * degToRad; const auto a = pow(sin(d_lat / 2.0), 2) + cos(coord1.latitude() * degToRad) * cos(coord2.latitude() * degToRad) * pow(sin(d_lon / 2.0), 2); return 2.0 * earthRadius * atan2(sqrt(a), sqrt(1.0 - a)); } bool LocationUtil::isSameLocation(const QVariant &lhs, const QVariant &rhs, LocationUtil::Accuracy accuracy) { const auto lhsGeo = geo(lhs); const auto rhsGeo = geo(rhs); if (lhsGeo.isValid() && rhsGeo.isValid()) { const auto d = distance(lhsGeo, rhsGeo); switch (accuracy) { case Exact: return d < 100; case CityLevel: return d < 50000; break; } return false; } const auto lhsAddr = address(lhs); const auto rhsAddr = address(rhs); switch (accuracy) { case Exact: if (!lhsAddr.streetAddress().isEmpty() && !lhsAddr.addressLocality().isEmpty()) { return lhsAddr.streetAddress() == rhsAddr.streetAddress() && lhsAddr.addressLocality() == rhsAddr.addressLocality(); } break; case CityLevel: if (!lhsAddr.addressLocality().isEmpty()) { return lhsAddr.addressLocality() == rhsAddr.addressLocality(); } break; } const auto lhsName = name(lhs); return !lhsName.isEmpty() && lhsName == name(rhs); } diff --git a/src/locationutil.h b/src/locationutil.h index f60e248..98243b7 100644 --- a/src/locationutil.h +++ b/src/locationutil.h @@ -1,83 +1,83 @@ /* 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 . + along with this program. If not, see . */ #ifndef KITINERARY_LOCATIONUTIL_H #define KITINERARY_LOCATIONUTIL_H #include "kitinerary_export.h" class QString; class QVariant; namespace KItinerary { class GeoCoordinates; class PostalAddress; /** Utility functions related to location information. */ namespace LocationUtil { /** Returns @c true if the given reservation is a location change. * That is, some form of transport reservation with different departure * and arrival locations. */ bool KITINERARY_EXPORT isLocationChange(const QVariant &res); /** Returns the departure location of the given reservation. * This assumes isLocationChange(res) == true. */ QVariant KITINERARY_EXPORT departureLocation(const QVariant &res); /** Returns the arrival location of the given reservation. * This assumes isLocationChange(res) == true. */ QVariant KITINERARY_EXPORT arrivalLocation(const QVariant &res); /** Returns the location of a non-transport reservation. * This assumes isLocationChange(res) == false. */ QVariant KITINERARY_EXPORT location(const QVariant &res); /** Returns the geo coordinates of a given location. */ GeoCoordinates KITINERARY_EXPORT geo(const QVariant &location); /** Returns the address of the given location. */ PostalAddress KITINERARY_EXPORT address(const QVariant &location); /** Returns a description of the location. */ QString KITINERARY_EXPORT name(const QVariant &location); /** Computes the distance between to geo coordinates in meters. */ int KITINERARY_EXPORT distance(const GeoCoordinates &coord1, const GeoCoordinates &coord2); /** Location comparison accuracy. */ enum Accuracy { Exact, ///< Locations match exactly CityLevel ///< Locations are in the same city }; /** Returns @c true if the given locations are the same. * @param accuracy Defines how closely the locations have to match. */ bool KITINERARY_EXPORT isSameLocation(const QVariant &lhs, const QVariant &rhs, Accuracy accuracy = Exact); } } #endif // KITINERARY_LOCATIONUTIL_H diff --git a/src/mergeutil.cpp b/src/mergeutil.cpp index 0fc40d7..e6ca93b 100644 --- a/src/mergeutil.cpp +++ b/src/mergeutil.cpp @@ -1,428 +1,428 @@ /* 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 . + along with this program. If not, see . */ #include "mergeutil.h" #include "logging.h" #include "stringutil.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KItinerary; /* Checks that @p lhs and @p rhs are non-empty and equal. */ static bool equalAndPresent(const QString &lhs, const QString &rhs, Qt::CaseSensitivity caseSensitive = Qt::CaseSensitive) { return !lhs.isEmpty() && (lhs.compare(rhs, caseSensitive) == 0); } static bool equalAndPresent(const QDate &lhs, const QDate &rhs) { return lhs.isValid() && lhs == rhs; } static bool equalAndPresent(const QDateTime &lhs, const QDateTime &rhs) { return lhs.isValid() && 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) { return !lhs.isEmpty() && !rhs.isEmpty() && lhs.compare(rhs, caseSensitive) != 0; } static bool conflictIfPresent(const QDateTime &lhs, const QDateTime &rhs) { return lhs.isValid() && rhs.isValid() && lhs != rhs; } static bool isSameFlight(const Flight &lhs, const Flight &rhs); static bool isSameTrainTrip(const TrainTrip &lhs, const TrainTrip &rhs); static bool isSameBusTrip(const BusTrip &lhs, const BusTrip &rhs); static bool isSameLodingBusiness(const LodgingBusiness &lhs, const LodgingBusiness &rhs); static bool isSameFoodEstablishment(const FoodEstablishment &lhs, const FoodEstablishment &rhs); static bool isSameTouristAttractionVisit(const TouristAttractionVisit &lhs, const TouristAttractionVisit &rhs); static bool isSameTouristAttraction(const TouristAttraction &lhs, const TouristAttraction &rhs); static bool isSameEvent(const Event &lhs, const Event &rhs); static bool isSameRentalCar(const RentalCar &lhs, const RentalCar &rhs); static bool isSameTaxiTrip(const Taxi &lhs, const Taxi &rhs); bool MergeUtil::isSame(const QVariant& lhs, const QVariant& rhs) { if (lhs.isNull() || rhs.isNull()) { return false; } if (lhs.userType() != rhs.userType()) { return false; } // for all reservations check underName if (JsonLd::canConvert(lhs)) { // for all: underName either matches or is not set const auto lhsUN = JsonLd::convert(lhs).underName().value(); const auto rhsUN = JsonLd::convert(rhs).underName().value(); if (!lhsUN.name().isEmpty() && !rhsUN.name().isEmpty() && !isSamePerson(lhsUN, rhsUN)) { return false; } } // flight: booking ref, flight number and departure day match if (JsonLd::isA(lhs)) { const auto lhsRes = lhs.value(); const auto rhsRes = rhs.value(); if (conflictIfPresent(lhsRes.reservationNumber(), rhsRes.reservationNumber()) || conflictIfPresent(lhsRes.passengerSequenceNumber(), rhsRes.passengerSequenceNumber())) { return false; } return isSame(lhsRes.reservationFor(), rhsRes.reservationFor()); } if (JsonLd::isA(lhs)) { const auto lhsFlight = lhs.value(); const auto rhsFlight = rhs.value(); return isSameFlight(lhsFlight, rhsFlight); } // train: booking ref, train number and depature day match if (JsonLd::isA(lhs)) { const auto lhsRes = lhs.value(); const auto rhsRes = rhs.value(); if (lhsRes.reservationNumber() != rhsRes.reservationNumber()) { return false; } return isSame(lhsRes.reservationFor(), rhsRes.reservationFor()); } if (JsonLd::isA(lhs)) { const auto lhsTrip = lhs.value(); const auto rhsTrip = rhs.value(); return isSameTrainTrip(lhsTrip, rhsTrip); } // bus: booking ref, number and depature time match if (JsonLd::isA(lhs)) { const auto lhsRes = lhs.value(); const auto rhsRes = rhs.value(); if (lhsRes.reservationNumber() != rhsRes.reservationNumber()) { return false; } return isSame(lhsRes.reservationFor(), rhsRes.reservationFor()); } if (JsonLd::isA(lhs)) { const auto lhsTrip = lhs.value(); const auto rhsTrip = rhs.value(); return isSameBusTrip(lhsTrip, rhsTrip); } // hotel: booking ref, checkin day, name match if (JsonLd::isA(lhs)) { const auto lhsRes = lhs.value(); const auto rhsRes = rhs.value(); if (lhsRes.reservationNumber() != rhsRes.reservationNumber()) { return false; } return isSame(lhsRes.reservationFor(), rhsRes.reservationFor()) && lhsRes.checkinTime().date() == rhsRes.checkinTime().date(); } if (JsonLd::isA(lhs)) { const auto lhsHotel = lhs.value(); const auto rhsHotel = rhs.value(); return isSameLodingBusiness(lhsHotel, rhsHotel); } // Rental Car if (JsonLd::isA(lhs)) { const auto lhsRes = lhs.value(); const auto rhsRes = rhs.value(); if (lhsRes.reservationNumber() != rhsRes.reservationNumber()) { return false; } return isSame(lhsRes.reservationFor(), rhsRes.reservationFor()) && lhsRes.pickupTime().date() == rhsRes.pickupTime().date(); } if (JsonLd::isA(lhs)) { const auto lhsEv = lhs.value(); const auto rhsEv = rhs.value(); return isSameRentalCar(lhsEv, rhsEv); } // Taxi if (JsonLd::isA(lhs)) { const auto lhsRes = lhs.value(); const auto rhsRes = rhs.value(); if (lhsRes.reservationNumber() != rhsRes.reservationNumber()) { return false; } return isSame(lhsRes.reservationFor(), rhsRes.reservationFor()) && lhsRes.pickupTime().date() == rhsRes.pickupTime().date(); } if (JsonLd::isA(lhs)) { const auto lhsEv = lhs.value(); const auto rhsEv = rhs.value(); return isSameTaxiTrip(lhsEv, rhsEv); } // restaurant reservation: same restaurant, same booking ref, same day if (JsonLd::isA(lhs)) { const auto lhsRes = lhs.value(); const auto rhsRes = rhs.value(); if (lhsRes.reservationNumber() != rhsRes.reservationNumber()) { return false; } auto endTime = rhsRes.endTime(); if (!endTime.isValid()) { endTime = QDateTime(rhsRes.startTime().date(), QTime(23, 59, 59)); } return isSame(lhsRes.reservationFor(), rhsRes.reservationFor()) && lhsRes.startTime().date() == endTime.date(); } if (JsonLd::isA(lhs)) { const auto lhsRestaurant = lhs.value(); const auto rhsRestaurant = rhs.value(); return isSameFoodEstablishment(lhsRestaurant, rhsRestaurant); } // event reservation if (JsonLd::isA(lhs)) { const auto lhsRes = lhs.value(); const auto rhsRes = rhs.value(); if (lhsRes.reservationNumber() != rhsRes.reservationNumber()) { return false; } return isSame(lhsRes.reservationFor(), rhsRes.reservationFor()); } if (JsonLd::isA(lhs)) { const auto lhsEv = lhs.value(); const auto rhsEv = rhs.value(); return isSameEvent(lhsEv, rhsEv); } // tourist attraction visit if (JsonLd::isA(lhs)) { const auto l = lhs.value(); const auto r = rhs.value(); return isSameTouristAttractionVisit(l, r); } return true; } static bool isSameFlight(const Flight& lhs, const Flight& rhs) { // if there is a conflict on where this is going, or when, this is obviously not the same flight if (conflictIfPresent(lhs.departureAirport().iataCode(), rhs.departureAirport().iataCode()) || conflictIfPresent(lhs.arrivalAirport().iataCode(), rhs.arrivalAirport().iataCode()) || !equalAndPresent(lhs.departureDay(), rhs.departureDay())) { return false; } // same flight number and airline (on the same day) -> we assume same flight if (equalAndPresent(lhs.flightNumber(), rhs.flightNumber()) && equalAndPresent(lhs.airline().iataCode(), rhs.airline().iataCode())) { return true; } // we get here if we have matching origin/destination on the same day, but mismatching flight numbers // so this might be a codeshare flight // our caller checks for matching booking ref, so just look for a few counter-indicators here // (that is, if this is ever made available as standalone API, the last return should not be true) if (conflictIfPresent(lhs.departureTime(), rhs.departureTime())) { return false; } return true; } static bool isSameTrainTrip(const TrainTrip &lhs, const TrainTrip &rhs) { if (lhs.trainNumber().isEmpty() || rhs.trainNumber().isEmpty()) { return false; } return lhs.trainName() == rhs.trainName() && lhs.trainNumber() == rhs.trainNumber() && lhs.departureTime().date() == rhs.departureTime().date(); } static bool isSameBusTrip(const BusTrip &lhs, const BusTrip &rhs) { if (lhs.busNumber().isEmpty() || rhs.busNumber().isEmpty()) { return false; } return lhs.busName() == rhs.busName() && lhs.busNumber() == rhs.busNumber() && lhs.departureTime() == rhs.departureTime(); } static bool isSameLodingBusiness(const LodgingBusiness &lhs, const LodgingBusiness &rhs) { if (lhs.name().isEmpty() || rhs.name().isEmpty()) { return false; } return lhs.name() == rhs.name(); } static bool isSameFoodEstablishment(const FoodEstablishment &lhs, const FoodEstablishment &rhs) { if (lhs.name().isEmpty() || rhs.name().isEmpty()) { return false; } return lhs.name() == rhs.name(); } static bool isSameTouristAttractionVisit(const TouristAttractionVisit &lhs, const TouristAttractionVisit &rhs) { return lhs.arrivalTime() == rhs.arrivalTime() && isSameTouristAttraction(lhs.touristAttraction(), rhs.touristAttraction()); } static bool isSameTouristAttraction(const TouristAttraction &lhs, const TouristAttraction &rhs) { return lhs.name() == rhs.name(); } // compute the "difference" between @p lhs and @p rhs static QString diffString(const QString &lhs, const QString &rhs) { QString diff; // this is just a basic linear-time heuristic, this would need to be more something like // the Levenstein Distance algorithm for (int i = 0, j = 0; i < lhs.size() || j < rhs.size();) { if (i < lhs.size() && j < rhs.size() && StringUtil::normalize(lhs[i]) == StringUtil::normalize(rhs[j])) { ++i; ++j; continue; } if ((j < rhs.size() && (lhs.size() < rhs.size() || (lhs.size() == rhs.size() && j < i))) || i == lhs.size()) { diff += rhs[j]; ++j; } else { diff += lhs[i]; ++i; } } return diff.trimmed(); } static bool isNameEqualish(const QString &lhs, const QString &rhs) { if (lhs.isEmpty() || rhs.isEmpty()) { return false; } auto diff = diffString(lhs, rhs).toUpper(); // remove honoric prefixes from the diff, in case the previous check didn't catch that diff.remove(QLatin1String("MRS")); diff.remove(QLatin1String("MR")); diff.remove(QLatin1String("MS")); // if there's letters in the diff, we assume this is different for (const auto c : diff) { if (c.isLetter()) { return false; } } return true; } bool MergeUtil::isSamePerson(const Person& lhs, const Person& rhs) { return isNameEqualish(lhs.name(), rhs.name()) || (isNameEqualish(lhs.givenName(), rhs.givenName()) && isNameEqualish(lhs.familyName(), rhs.familyName())); } static bool isSameEvent(const Event &lhs, const Event &rhs) { return equalAndPresent(lhs.name(), rhs.name()) && equalAndPresent(lhs.startDate(), rhs.startDate()); } static bool isSameRentalCar(const RentalCar &lhs, const RentalCar &rhs) { return lhs.name() == rhs.name(); } static bool isSameTaxiTrip(const Taxi &lhs, const Taxi &rhs) { //TODO verify return lhs.name() == rhs.name(); } static Airline mergeValue(const Airline &lhs, const Airline &rhs) { auto a = JsonLdDocument::apply(lhs, rhs).value(); // prefer the more detailed name if (a.name().size() < lhs.name().size()) { a.setName(lhs.name()); } return a; } static QDateTime mergeValue(const QDateTime &lhs, const QDateTime &rhs) { // prefer value with timezone return lhs.isValid() && lhs.timeSpec() == Qt::TimeZone && rhs.timeSpec() != Qt::TimeZone ? lhs : rhs; } QVariant MergeUtil::merge(const QVariant &lhs, const QVariant &rhs) { if (rhs.isNull()) { return lhs; } if (lhs.isNull()) { return rhs; } if (lhs.userType() != rhs.userType()) { qCWarning(Log) << "type mismatch during merging:" << lhs << rhs; return {}; } auto res = lhs; const auto mo = QMetaType(res.userType()).metaObject(); for (int i = 0; i < mo->propertyCount(); ++i) { const auto prop = mo->property(i); if (!prop.isStored()) { continue; } auto lv = prop.readOnGadget(lhs.constData()); auto rv = prop.readOnGadget(rhs.constData()); auto mt = rv.userType(); if (mt == qMetaTypeId()) { rv = mergeValue(lv.value(), rv.value()); } else if (mt == qMetaTypeId()) { rv = mergeValue(lv.toDateTime(), rv.toDateTime()); } else if (QMetaType(mt).metaObject()) { rv = merge(prop.readOnGadget(lhs.constData()), rv); } if (!rv.isNull()) { prop.writeOnGadget(res.data(), rv); } } return res; } diff --git a/src/mergeutil.h b/src/mergeutil.h index 8fbddb3..39d0b51 100644 --- a/src/mergeutil.h +++ b/src/mergeutil.h @@ -1,65 +1,65 @@ /* 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 . + along with this program. If not, see . */ #ifndef KITINERARY_MERGEUTIL_H #define KITINERARY_MERGEUTIL_H #include "kitinerary_export.h" class QVariant; namespace KItinerary { class Flight; class Person; class TrainTrip; /** Utilities for merging reservations or elements of them. */ namespace MergeUtil { /** * Checks if two Reservation or Trip values refer to the same booking element. * * This does not mean being exactly equal, but having matching identifying properties. * What this means exactly depends on the data type: * - Flights: booking reference, flight number and departure day match * - Train trip: booking reference, train number and departure day match * - Bus trip: booking ref and/or number and departure time match * - Hotel booking: hotel name, booking reference and checkin day match * * For all reservation types, the Reservation::underName property is * checked and either needs to be equal or absent in one of the values. */ KITINERARY_EXPORT bool isSame(const QVariant &lhs, const QVariant &rhs); /** * Checks if two Person objects refer to the same person. * * Essentially a case-insensitive comparisson of the name components. */ KITINERARY_EXPORT bool isSamePerson(const Person &lhs, const Person &rhs); /** * Merge the two given objects. * This is the same as JsonLdDocument::apply in most cases, but if one side * can be determined to be "better", that one is preferred. */ KITINERARY_EXPORT QVariant merge(const QVariant &lhs, const QVariant &rhs); } } #endif // KITINERARY_MERGEUTIL_H diff --git a/src/pdfdocument.cpp b/src/pdfdocument.cpp index 2fd18a4..4538f26 100644 --- a/src/pdfdocument.cpp +++ b/src/pdfdocument.cpp @@ -1,523 +1,523 @@ /* 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 . + along with this program. If not, see . */ #include "config-kitinerary.h" #include "pdfdocument.h" #include #include #include #ifdef HAVE_POPPLER #include #include #include #include #endif #include #include using namespace KItinerary; namespace KItinerary { class PdfImagePrivate : public QSharedData { public: #ifdef HAVE_POPPLER QImage load(Stream *str, GfxImageColorMap *colorMap); std::unique_ptr m_colorMap; #endif int m_refNum = -1; int m_refGen = -1; PdfPagePrivate *m_page = nullptr; QTransform m_transform; int m_width = 0; int m_height = 0; int m_sourceWidth = 0; int m_sourceHeight = 0; QImage::Format m_format = QImage::Format_Invalid; }; class PdfPagePrivate : public QSharedData { public: void load(); int m_pageNum = -1; bool m_loaded = false; QString m_text; std::vector m_images; PdfDocumentPrivate *m_doc; }; class PdfDocumentPrivate { public: // needs to be kept alive as long as the Poppler::PdfDoc instance lives QByteArray m_pdfData; // this contains the actually loaded/decoded image data // and is referenced by the object id from PdfImage to avoid // expensive loading/decoding of multiple occurrences of the same image // image data in here is stored in its source form, without applied transformations std::unordered_map m_imageData; std::vector m_pages; #ifdef HAVE_POPPLER std::unique_ptr m_popplerDoc; #endif }; #ifdef HAVE_POPPLER static std::unique_ptr s_globalParams; static GlobalParams* popplerGlobalParams() { if (!s_globalParams) { s_globalParams.reset(new GlobalParams); } return s_globalParams.get(); } class ExtractorOutputDevice : public TextOutputDev { public: ExtractorOutputDevice(); bool needNonText() override { return true; } void drawImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, bool interpolate, int *maskColors, bool inlineImg) override; std::vector m_images; private: QTransform m_transform; }; #ifndef HAVE_POPPLER_0_69 class ImageLoaderOutputDevice : public OutputDev { public: ImageLoaderOutputDevice(PdfImagePrivate *dd); bool interpretType3Chars() override { return false; } bool needNonText() override { return true; } bool upsideDown() override { return false; } bool useDrawChar() override { return false; } void drawImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, bool interpolate, int *maskColors, bool inlineImg) override; QImage image() const { return m_image; } private: PdfImagePrivate *d; QImage m_image; }; #endif #endif } #ifdef HAVE_POPPLER QImage PdfImagePrivate::load(Stream* str, GfxImageColorMap* colorMap) { auto img = QImage(m_sourceWidth, m_sourceHeight, m_format); const auto bytesPerPixel = colorMap->getNumPixelComps(); std::unique_ptr imgStream(new ImageStream(str, m_sourceWidth, bytesPerPixel, colorMap->getBits())); imgStream->reset(); switch (m_format) { case QImage::Format_RGB888: for (int i = 0; i < m_sourceHeight; ++i) { const auto row = imgStream->getLine(); auto imgData = img.scanLine(i); GfxRGB rgb; for (int j = 0; j < m_sourceWidth; ++j) { colorMap->getRGB(row + (j * bytesPerPixel), &rgb); *imgData++ = colToByte(rgb.r); *imgData++ = colToByte(rgb.g); *imgData++ = colToByte(rgb.b); } } break; case QImage::Format_Grayscale8: for (int i = 0; i < m_sourceHeight; ++i) { const auto row = imgStream->getLine(); auto imgData = img.scanLine(i); GfxGray gray; for (int j = 0; j < m_sourceWidth; ++j) { colorMap->getGray(row + j, &gray); *imgData++ = colToByte(gray); } } break; default: break; } imgStream->close(); m_page->m_doc->m_imageData[m_refNum] = img; return img; } ExtractorOutputDevice::ExtractorOutputDevice() : TextOutputDev(nullptr, false, 0, false, false) { } void ExtractorOutputDevice::drawImage(GfxState* state, Object* ref, Stream* str, int width, int height, GfxImageColorMap* colorMap, bool interpolate, int* maskColors, bool inlineImg) { Q_UNUSED(str); Q_UNUSED(interpolate); Q_UNUSED(maskColors); Q_UNUSED(inlineImg); if (!colorMap || !colorMap->isOk() || !ref || !ref->isRef()) { return; } QImage::Format format; if (colorMap->getColorSpace()->getMode() == csIndexed) { format = QImage::Format_RGB888; } else if (colorMap->getNumPixelComps() == 1 && (colorMap->getBits() >= 1 && colorMap->getBits() <= 8)) { format = QImage::Format_Grayscale8; } else if (colorMap->getNumPixelComps() == 3 && colorMap->getBits() == 8) { format = QImage::Format_RGB888; } else { return; } PdfImage pdfImg; pdfImg.d->m_refNum = ref->getRef().num; pdfImg.d->m_refGen = ref->getRef().gen; #ifdef HAVE_POPPLER_0_69 pdfImg.d->m_colorMap.reset(colorMap->copy()); #endif pdfImg.d->m_sourceHeight = height; pdfImg.d->m_sourceWidth = width; pdfImg.d->m_width = width; pdfImg.d->m_height = height; // deal with aspect-ratio changing scaling const auto sourceAspectRatio = (double)width / (double)height; const auto targetAspectRatio = state->getCTM()[0] / -state->getCTM()[3]; if (!qFuzzyCompare(sourceAspectRatio, targetAspectRatio) && qFuzzyIsNull(state->getCTM()[1]) && qFuzzyIsNull(state->getCTM()[2])) { if (targetAspectRatio > sourceAspectRatio) { pdfImg.d->m_width = width * targetAspectRatio / sourceAspectRatio; } else { pdfImg.d->m_height = height * sourceAspectRatio / targetAspectRatio; } } const auto ctm = state->getCTM(); pdfImg.d->m_transform = QTransform(ctm[0], ctm[1], ctm[2], ctm[3], ctm[4], ctm[5]); pdfImg.d->m_format = format; m_images.push_back(pdfImg); } #ifndef HAVE_POPPLER_0_69 ImageLoaderOutputDevice::ImageLoaderOutputDevice(PdfImagePrivate* dd) : d(dd) { } void ImageLoaderOutputDevice::drawImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, bool interpolate, int *maskColors, bool inlineImg) { Q_UNUSED(state); Q_UNUSED(height); Q_UNUSED(width); Q_UNUSED(interpolate); Q_UNUSED(maskColors); Q_UNUSED(inlineImg); if (!colorMap || !colorMap->isOk() || !ref) { return; } if (ref->isRef() && d->m_refNum != ref->getRef().num) { return; } m_image = d->load(str, colorMap); } #endif #endif PdfImage::PdfImage() : d(new PdfImagePrivate) { } PdfImage::PdfImage(const PdfImage&) = default; PdfImage::~PdfImage() = default; PdfImage& PdfImage::operator=(const PdfImage&) = default; int PdfImage::height() const { return d->m_height; } int PdfImage::width() const { return d->m_width; } int PdfImage::sourceHeight() const { return d->m_sourceHeight; } int PdfImage::sourceWidth() const { return d->m_sourceWidth; } QTransform PdfImage::transform() const { return d->m_transform; } QImage PdfImage::sourceImage() const { const auto it = d->m_page->m_doc->m_imageData.find(d->m_refNum); if (it != d->m_page->m_doc->m_imageData.end()) { return (*it).second; } #ifdef HAVE_POPPLER QScopedValueRollback globalParamResetter(globalParams, popplerGlobalParams()); #ifdef HAVE_POPPLER_0_69 const auto xref = d->m_page->m_doc->m_popplerDoc->getXRef(); const auto obj = xref->fetch(d->m_refNum, d->m_refGen); return d->load(obj.getStream(), d->m_colorMap.get()); #else std::unique_ptr device(new ImageLoaderOutputDevice(d.data())); d->m_page->m_doc->m_popplerDoc->displayPageSlice(device.get(), d->m_page->m_pageNum + 1, 72, 72, 0, false, true, false, -1, -1, -1, -1); return device->image(); #endif #else return {}; #endif } QImage PdfImage::image() const { const auto img = sourceImage(); if (d->m_width != d->m_sourceWidth || d->m_height != d->m_sourceHeight) { return img.scaled(d->m_width, d->m_height); } return img; } int PdfImage::objectId() const { return d->m_refNum; } void PdfPagePrivate::load() { if (m_loaded) { return; } #ifdef HAVE_POPPLER QScopedValueRollback globalParamResetter(globalParams, popplerGlobalParams()); ExtractorOutputDevice device; m_doc->m_popplerDoc->displayPageSlice(&device, m_pageNum + 1, 72, 72, 0, false, true, false, -1, -1, -1, -1); const auto pageRect = m_doc->m_popplerDoc->getPage(m_pageNum + 1)->getCropBox(); std::unique_ptr s(device.getText(pageRect->x1, pageRect->y1, pageRect->x2, pageRect->y2)); #ifdef HAVE_POPPLER_0_72 m_text = QString::fromUtf8(s->c_str()); #else m_text = QString::fromUtf8(s->getCString()); #endif m_images = std::move(device.m_images); for (auto it = m_images.begin(); it != m_images.end(); ++it) { (*it).d->m_page = this; } #endif m_loaded = true; } PdfPage::PdfPage() : d(new PdfPagePrivate) { } PdfPage::PdfPage(const PdfPage&) = default; PdfPage::~PdfPage() = default; PdfPage& PdfPage::operator=(const PdfPage&) = default; QString PdfPage::text() const { d->load(); return d->m_text; } #ifdef HAVE_POPPLER static double ratio(double begin, double end, double ratio) { return begin + (end - begin) * ratio; } #endif QString PdfPage::textInRect(double left, double top, double right, double bottom) const { #ifdef HAVE_POPPLER QScopedValueRollback globalParamResetter(globalParams, popplerGlobalParams()); ExtractorOutputDevice device; d->m_doc->m_popplerDoc->displayPageSlice(&device, d->m_pageNum + 1, 72, 72, 0, false, true, false, -1, -1, -1, -1); const auto pageRect = d->m_doc->m_popplerDoc->getPage(d->m_pageNum + 1)->getCropBox(); std::unique_ptr s(device.getText(ratio(pageRect->x1, pageRect->x2, left), ratio(pageRect->y1, pageRect->y2, top), ratio(pageRect->x1, pageRect->x2, right), ratio(pageRect->y1, pageRect->y2, bottom))); #ifdef HAVE_POPPLER_0_72 return QString::fromUtf8(s->c_str()); #else return QString::fromUtf8(s->getCString()); #endif #else Q_UNUSED(left); Q_UNUSED(top); Q_UNUSED(right); Q_UNUSED(bottom); return {}; #endif } int PdfPage::imageCount() const { d->load(); return d->m_images.size(); } PdfImage PdfPage::image(int index) const { d->load(); return d->m_images[index]; } QVariantList PdfPage::imagesVariant() const { d->load(); QVariantList l; l.reserve(imageCount()); std::for_each(d->m_images.begin(), d->m_images.end(), [&l](const PdfImage& img) { l.push_back(QVariant::fromValue(img)); }); return l; } QVariantList PdfPage::imagesInRect(double left, double top, double right, double bottom) const { d->load(); QVariantList l; #ifdef HAVE_POPPLER QScopedValueRollback globalParamResetter(globalParams, popplerGlobalParams()); const auto pageRect = d->m_doc->m_popplerDoc->getPage(d->m_pageNum + 1)->getCropBox(); for (const auto &img : d->m_images) { if ((img.d->m_transform.dx() >= ratio(pageRect->x1, pageRect->x2, left) && img.d->m_transform.dx() <= ratio(pageRect->x1, pageRect->x2, right)) && (img.d->m_transform.dy() >= ratio(pageRect->y1, pageRect->y2, top) && img.d->m_transform.dy() <= ratio(pageRect->y1, pageRect->y2, bottom))) { l.push_back(QVariant::fromValue(img)); } } #else Q_UNUSED(left); Q_UNUSED(top); Q_UNUSED(right); Q_UNUSED(bottom); #endif return l; } PdfDocument::PdfDocument(QObject *parent) : QObject(parent) , d(new PdfDocumentPrivate) { } PdfDocument::~PdfDocument() = default; QString PdfDocument::text() const { QString text; std::for_each(d->m_pages.begin(), d->m_pages.end(), [&text](const PdfPage &p) { text += p.text(); }); return text; } int PdfDocument::pageCount() const { #ifdef HAVE_POPPLER return d->m_popplerDoc->getNumPages(); #else return 0; #endif } PdfPage PdfDocument::page(int index) const { return d->m_pages[index]; } int PdfDocument::fileSize() const { return d->m_pdfData.size(); } QVariantList PdfDocument::pagesVariant() const { QVariantList l; l.reserve(pageCount()); std::for_each(d->m_pages.begin(), d->m_pages.end(), [&l](const PdfPage& p) { l.push_back(QVariant::fromValue(p)); }); return l; } PdfDocument* PdfDocument::fromData(const QByteArray &data, QObject *parent) { #ifdef HAVE_POPPLER QScopedValueRollback globalParamResetter(globalParams, popplerGlobalParams()); std::unique_ptr doc(new PdfDocument(parent)); doc->d->m_pdfData = data; // PDFDoc takes ownership of stream #ifdef HAVE_POPPLER_0_58 auto stream = new MemStream(const_cast(doc->d->m_pdfData.constData()), 0, doc->d->m_pdfData.size(), Object()); #else Object obj; obj.initNull(); auto stream = new MemStream(const_cast(doc->d->m_pdfData.constData()), 0, doc->d->m_pdfData.size(), &obj); #endif std::unique_ptr popplerDoc(new PDFDoc(stream, nullptr, nullptr)); if (!popplerDoc->isOk()) { return nullptr; } doc->d->m_pages.reserve(popplerDoc->getNumPages()); for (int i = 0; i < popplerDoc->getNumPages(); ++i) { PdfPage page; page.d->m_pageNum = i; page.d->m_doc = doc->d.get(); doc->d->m_pages.push_back(page); } doc->d->m_popplerDoc = std::move(popplerDoc); return doc.release(); #else Q_UNUSED(data); Q_UNUSED(parent); return nullptr; #endif } diff --git a/src/pdfdocument.h b/src/pdfdocument.h index 6e812ea..691d75a 100644 --- a/src/pdfdocument.h +++ b/src/pdfdocument.h @@ -1,167 +1,167 @@ /* 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 . + along with this program. If not, see . */ #ifndef KITINERARY_PDFDOCUMENT_H #define KITINERARY_PDFDOCUMENT_H #include "kitinerary_export.h" #include #include #include #include class QImage; class QTransform; namespace KItinerary { class PdfImagePrivate; class PdfPagePrivate; /** An image in a PDF document. */ class KITINERARY_EXPORT PdfImage { Q_GADGET Q_PROPERTY(int width READ width) Q_PROPERTY(int height READ height) public: PdfImage(); PdfImage(const PdfImage&); ~PdfImage(); PdfImage& operator=(const PdfImage&); int width() const; int height() const; /** Height of the source image. */ int sourceHeight() const; /** Width of the source image. */ int sourceWidth() const; /** Transformation from source image to final size/position on the page. * Values are 1/72 inch. */ QTransform transform() const; /** The image as loaded from PDF, without transformations applied. */ QImage sourceImage() const; /** The source image with display transformations applied. */ QImage image() const; /** PDF-internal unique identifier of this image. * Use this to detect multiple occurrences of the same image in different * places, if that reduces e.g. computation cost. */ int objectId() const; private: friend class ExtractorOutputDevice; friend class PdfPagePrivate; friend class PdfPage; QExplicitlySharedDataPointer d; }; /** A page in a PDF document. */ class KITINERARY_EXPORT PdfPage { Q_GADGET Q_PROPERTY(QString text READ text) Q_PROPERTY(QVariantList images READ imagesVariant) public: PdfPage(); PdfPage(const PdfPage&); ~PdfPage(); PdfPage& operator=(const PdfPage&); /** The entire text on this page. */ QString text() const; /** Returns the text in the specified sub-rect of this page. * All parameters are relative values between @c 0 and @c 1 of the entire page size. */ Q_INVOKABLE QString textInRect(double left, double top, double right, double bottom) const; /** The number of images found in this document. */ int imageCount() const; /** The n-th image found in this document. */ PdfImage image(int index) const; /** Returns the images in the specified sub-rect of this page. * All parameters are relative values between @c 0 and @c 1 of the entire page size. */ Q_INVOKABLE QVariantList imagesInRect(double left, double top, double right, double bottom) const; private: QVariantList imagesVariant() const; friend class PdfDocument; QExplicitlySharedDataPointer d; }; class PdfDocumentPrivate; /** PDF document for extraction. * This is used as input for ExtractorEngine and the JS extractor scripts. * @note This class is only functional if Poppler is available as a dependency, * otherwise all methods return empty values. */ class KITINERARY_EXPORT PdfDocument : public QObject { Q_OBJECT Q_PROPERTY(QString text READ text CONSTANT) Q_PROPERTY(int pageCount READ pageCount CONSTANT) Q_PROPERTY(QVariantList pages READ pagesVariant CONSTANT) public: explicit PdfDocument(QObject *parent = nullptr); ~PdfDocument(); /** The entire text extracted from the PDF document. */ QString text() const; /** The number of pages in this document. */ int pageCount() const; /** The n-thj page in this document. */ PdfPage page(int index) const; /** File size of the entire document in bytes. */ int fileSize() const; /** Creates a PdfDocument from the given raw data. * @returns @c nullptr if loading fails or Poppler was not found. */ static PdfDocument* fromData(const QByteArray &data, QObject *parent = nullptr); private: QVariantList pagesVariant() const; std::unique_ptr d; }; } Q_DECLARE_METATYPE(KItinerary::PdfImage) Q_DECLARE_METATYPE(KItinerary::PdfPage) #endif // KITINERARY_PDFDOCUMENT_H diff --git a/src/qimageluminancesource.cpp b/src/qimageluminancesource.cpp index 4940b8d..4f7c625 100644 --- a/src/qimageluminancesource.cpp +++ b/src/qimageluminancesource.cpp @@ -1,73 +1,73 @@ /* 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 . + along with this program. If not, see . */ #include "qimageluminancesource.h" #ifdef HAVE_ZXING_OLD using namespace KItinerary; QImageLuminanceSource::QImageLuminanceSource(const QImage &img) : zxing::LuminanceSource(img.width() + 2, img.height() + 2) , m_img(img) { } QImageLuminanceSource::~QImageLuminanceSource() = default; zxing::ArrayRef QImageLuminanceSource::getRow(int y, zxing::ArrayRef row) const { if (!row) { row = zxing::ArrayRef(getWidth()); } if (y == 0 || y == getHeight() - 1) { memset(&row[0], 0xff, getWidth()); return row; } row[0] = (char)0xff; for (int i = 1; i < getWidth() - 1; ++i) { row[i] = luminance(i - 1, y); } row[getWidth() - 1] = (char)0xff; return row; } zxing::ArrayRef QImageLuminanceSource::getMatrix() const { zxing::ArrayRef matrix(getWidth() * getHeight()); memset(&matrix[0], 0xff, getWidth()); for (int i = 1; i < getHeight() - 1; ++i) { matrix[i * getWidth()] = (char)0xff; for (int j = 1; j < getWidth() - 1; ++j) { matrix[i * getWidth() + j] = luminance(j - 1, i - 1); } matrix[(i + 1) * getWidth() - 1] = (char)0xff; } memset(&matrix[(getHeight() - 1) * getWidth()], 0xff, getWidth()); return matrix; } char QImageLuminanceSource::luminance(int x, int y) const { return qGray(m_img.pixel(x, y)); } #endif diff --git a/src/qimageluminancesource.h b/src/qimageluminancesource.h index 2cc7c71..e087d82 100644 --- a/src/qimageluminancesource.h +++ b/src/qimageluminancesource.h @@ -1,52 +1,52 @@ /* 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 . + along with this program. If not, see . */ #ifndef KITINERARY_QIMAGELUMINANCESOURCE_H #define KITINERARY_QIMAGELUMINANCESOURCE_H #include "config-kitinerary.h" #ifdef HAVE_ZXING_OLD #include #include namespace KItinerary { /** QImage-based LuminanceSource. * This automatically adds a 1px quiet zone around the content. */ class QImageLuminanceSource : public zxing::LuminanceSource { public: explicit QImageLuminanceSource(const QImage &img); ~QImageLuminanceSource(); zxing::ArrayRef getRow(int y, zxing::ArrayRef row) const override; zxing::ArrayRef getMatrix() const override; private: char luminance(int x, int y) const; QImage m_img; }; } #endif #endif // KITINERARY_QIMAGELUMINANCESOURCE_H diff --git a/src/qimagepurebinarizer.cpp b/src/qimagepurebinarizer.cpp index bf340e6..aea67f9 100644 --- a/src/qimagepurebinarizer.cpp +++ b/src/qimagepurebinarizer.cpp @@ -1,116 +1,116 @@ /* 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 . + along with this program. If not, see . */ #include "qimagepurebinarizer.h" #ifdef HAVE_ZXING #include using namespace KItinerary; QImagePureBinarizer::QImagePureBinarizer(const QImage &img) : m_img(img) { } QImagePureBinarizer::~QImagePureBinarizer() = default; int QImagePureBinarizer::height() const { return m_img.height() + 2; } int QImagePureBinarizer::width() const { return m_img.width() + 2; } bool QImagePureBinarizer::isPureBarcode() const { return true; } bool QImagePureBinarizer::getBlackRow(int y, ZXing::BitArray &row) const { using namespace ZXing; const auto w = width(); if (row.size() != w) { row = BitArray(w); } else { row.clearBits(); } if (y == 0 || y == height() - 1) { return true; } for (auto i = 1; i < w - 1; ++i) { if (qGray(m_img.pixel(i - 1, y - 1)) < 127) row.set(i); } return true; } std::shared_ptr QImagePureBinarizer::getBlackMatrix() const { using namespace ZXing; if (!m_bitmap) { const auto w = width(); const auto h = height(); auto bitmap = std::make_shared(w, h); for (int x = 1; x < w - 1; ++x) { for (int y = 1; y < h - 1; ++y) { if (qGray(m_img.pixel(x - 1, y - 1)) < 127) bitmap->set(x, y); } } m_bitmap = std::move(bitmap); } return m_bitmap; } bool QImagePureBinarizer::canCrop() const { return false; // unused by ZXing } std::shared_ptr QImagePureBinarizer::cropped(int left, int top, int width, int height) const { Q_UNUSED(left); Q_UNUSED(top); Q_UNUSED(width); Q_UNUSED(height); return {}; } bool QImagePureBinarizer::canRotate() const { return false; } std::shared_ptr QImagePureBinarizer::rotated(int degreeCW) const { Q_UNUSED(degreeCW); return {}; } #endif diff --git a/src/qimagepurebinarizer.h b/src/qimagepurebinarizer.h index 9c2043e..052073b 100644 --- a/src/qimagepurebinarizer.h +++ b/src/qimagepurebinarizer.h @@ -1,62 +1,62 @@ /* 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 . + along with this program. If not, see . */ #ifndef KITINERARY_QIMAGEPUREBINARIZER_H #define KITINERARY_QIMAGEPUREBINARIZER_H #include "config-kitinerary.h" #ifdef HAVE_ZXING #include #include #include namespace KItinerary { /** Binarizer of pure black/white barcode images. * This bypasses the usually applied LuminanceSource and HybridBinarizer steps * for input data from PDFs that don't need this. */ class QImagePureBinarizer : public ZXing::BinaryBitmap { public: explicit QImagePureBinarizer(const QImage &img); ~QImagePureBinarizer(); int height() const override; int width() const override; bool isPureBarcode() const override; bool getBlackRow(int y, ZXing::BitArray &row) const override; std::shared_ptr getBlackMatrix() const override; bool canCrop() const override; bool canRotate() const override; std::shared_ptr cropped(int left, int top, int width, int height) const override; std::shared_ptr rotated(int degreeCW) const override; private: QImage m_img; mutable std::shared_ptr m_bitmap; }; } #endif #endif // KITINERARY_QIMAGEPUREBINARIZER_H diff --git a/src/sortutil.cpp b/src/sortutil.cpp index 6b8f373..d47c7bb 100644 --- a/src/sortutil.cpp +++ b/src/sortutil.cpp @@ -1,131 +1,131 @@ /* 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 . + along with this program. If not, see . */ #include "sortutil.h" #include #include #include #include #include #include #include #include #include #include #include using namespace KItinerary; QDateTime SortUtil::startDateTime(const QVariant &res) { if (JsonLd::isA(res)) { const auto flight = res.value().reservationFor().value(); if (flight.departureTime().isValid()) { return flight.departureTime(); } if (flight.boardingTime().isValid()) { return flight.boardingTime(); } return QDateTime(flight.departureDay(), QTime(23, 59, 59)); } if (JsonLd::isA(res)) { return res.value().reservationFor().value().departureTime(); } if (JsonLd::isA(res)) { return res.value().reservationFor().value().departureTime(); } if (JsonLd::isA(res)) { return res.value().startTime(); } if (JsonLd::isA(res)) { return res.value().pickupTime(); } if (JsonLd::isA(res)) { const auto hotel = res.value(); // hotel checkin/checkout is always considered the first/last thing of the day return QDateTime(hotel.checkinTime().date(), QTime(23, 59, 59)); } if (JsonLd::isA(res)) { return res.value().arrivalTime(); } if (JsonLd::isA(res)) { return res.value().reservationFor().value().startDate(); } if (JsonLd::isA(res)) { return res.value().pickupTime(); } return {}; } QDateTime SortUtil::endtDateTime(const QVariant &res) { if (JsonLd::isA(res)) { const auto flight = res.value().reservationFor().value(); if (flight.arrivalTime().isValid()) { return flight.arrivalTime(); } return QDateTime(flight.departureDay(), QTime(23, 59, 59)); } if (JsonLd::isA(res)) { return res.value().reservationFor().value().arrivalTime(); } if (JsonLd::isA(res)) { return res.value().reservationFor().value().arrivalTime(); } if (JsonLd::isA(res)) { auto endTime = res.value().endTime(); if (!endTime.isValid()) { endTime = QDateTime(res.value().startTime().date(), QTime(23, 59, 59)); } return endTime; } if (JsonLd::isA(res)) { return res.value().dropoffTime(); } if (JsonLd::isA(res)) { const auto hotel = res.value(); // hotel checkin/checkout is always considered the first/last thing of the day return QDateTime(hotel.checkoutTime().date(), QTime(0, 0, 0)); } if (JsonLd::isA(res)) { return res.value().departureTime(); } if (JsonLd::isA(res)) { return res.value().reservationFor().value().endDate(); } return {}; } bool SortUtil::isBefore(const QVariant &lhs, const QVariant &rhs) { if (startDateTime(lhs) == startDateTime(rhs) && lhs.userType() == rhs.userType() && JsonLd::canConvert(lhs)) { // for multi-traveler reservations, sort by traveler name to achieve a stable result const auto lhsRes = JsonLd::convert(lhs); const auto rhsRes = JsonLd::convert(rhs); if (!lhsRes.underName().isNull() && !rhsRes.underName().isNull() && MergeUtil::isSame(lhsRes.reservationFor(), rhsRes.reservationFor())) { const auto lhsUN = lhsRes.underName().value(); const auto rhsUN = rhsRes.underName().value(); return lhsUN.name() < rhsUN.name(); } } return startDateTime(lhs) < startDateTime(rhs); } diff --git a/src/sortutil.h b/src/sortutil.h index ebf9c50..735be7f 100644 --- a/src/sortutil.h +++ b/src/sortutil.h @@ -1,43 +1,43 @@ /* 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 . + along with this program. If not, see . */ #ifndef KITINERARY_SORTUTIL_H #define KITINERARY_SORTUTIL_H #include "kitinerary_export.h" class QDateTime; class QVariant; namespace KItinerary { /** Utility function for sorting reservations/visits/events. */ namespace SortUtil { /** Returns the (start) time associated with the given reservation. */ KITINERARY_EXPORT QDateTime startDateTime(const QVariant &res); /** Returns the (end) time associated with the given reservation. */ KITINERARY_EXPORT QDateTime endtDateTime(const QVariant &res); /** Sorting function for top-level reservation/visit/event elements. */ KITINERARY_EXPORT bool isBefore(const QVariant &lhs, const QVariant &rhs); } } #endif // KITINERARY_SORTUTIL_H diff --git a/src/stringutil.cpp b/src/stringutil.cpp index bbbdd26..34c4e2d 100644 --- a/src/stringutil.cpp +++ b/src/stringutil.cpp @@ -1,49 +1,49 @@ /* 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 . + along with this program. If not, see . */ #include "stringutil.h" #include #include using namespace KItinerary; QChar StringUtil::normalize(QChar c) { // case folding const auto n = c.toCaseFolded(); // if the character has a canonical decomposition use that and skip the // combining diacritic markers following it // see https://en.wikipedia.org/wiki/Unicode_equivalence // see https://en.wikipedia.org/wiki/Combining_character if (n.decompositionTag() == QChar::Canonical) { return n.decomposition().at(0); } return n; } QString StringUtil::normalize(const QString &str) { QString out; out.reserve(str.size()); for (const auto c : str) { out.push_back(normalize(c)); } return out; } diff --git a/src/stringutil.h b/src/stringutil.h index 1ee57b2..a601944 100644 --- a/src/stringutil.h +++ b/src/stringutil.h @@ -1,42 +1,42 @@ /* 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 . + along with this program. If not, see . */ #ifndef KITINERARY_STRINGUTIL_H #define KITINERARY_STRINGUTIL_H #include "kitinerary_export.h" class QChar; class QString; namespace KItinerary { /** String normalization and comparison utilities. */ namespace StringUtil { /** Convert @p c to case-folded form and remove diacritic marks. */ QChar normalize(QChar c); /** Strips out diacritics and converts to case-folded form. * @internal only exported for unit tests */ KITINERARY_EXPORT QString normalize(const QString &str); } } #endif // KITINERARY_STRINGUTIL_H diff --git a/src/uic9183parser.cpp b/src/uic9183parser.cpp index 7ad0496..b3941c0 100644 --- a/src/uic9183parser.cpp +++ b/src/uic9183parser.cpp @@ -1,726 +1,726 @@ /* 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 . + along with this program. If not, see . */ #include "uic9183parser.h" #include "logging.h" #include #include #include #include #include using namespace KItinerary; static int asciiToInt(const char *s, int size) { if (!s) { return 0; } int v = 0; for (int i = 0; i < size; ++i) { v *= 10; v += (*(s + i)) - '0'; } return v; } namespace KItinerary { class Uic9183Block { public: Uic9183Block() = default; Uic9183Block(const char *data, int size); const char *data() const { return m_data; } int version() const; int size() const { return m_size; } bool isNull() const { return m_size <= 12; } private: const char *m_data = nullptr; int m_size = 0; }; // 0080BL vendor block sub-block ("S block") // 1x 'S' // 3x field type // 4x field value length // nx field value class Vendor0080BLSubBlock { public: Vendor0080BLSubBlock() = default; Vendor0080BLSubBlock(const char *data, int size); bool isNull() const { return m_size <= 0 || !m_data; } int size() const { return m_size; } const char *id() const { return m_data + 1; } const char *data() const { return m_data + 8; } QString toString() const { return QString::fromUtf8(data(), size()); } private: const char *m_data = nullptr; int m_size = 0; }; // 0080BL vendor block (DB) (version 2/3, dynamic size) // 2x stuff // 1x number of certificate blocks // 22+8+8+8x (v2) or 8+8+10x (v3) certificate block // 2x number of sub blocks class Vendor0080BLBlock { public: Vendor0080BLBlock(const Uic9183Block &block); bool isValid() const; Vendor0080BLSubBlock findSubBlock(const char id[3]) const; private: static int subblockOffset(const Uic9183Block &block); Uic9183Block m_block; }; class Uic9183ParserPrivate : public QSharedData { public: // name is either "U_" + 4 letter type or a 4 digit vendor id + 2 char type Uic9183Block findBlock(const char name[6]) const; QByteArray m_payload; QDateTime m_contextDt; }; // 2x field line, number as ascii text // 2x field column // 2x field height // 2x field width // 1x field format // 4x text length // Nx text content class Rct2TicketField { public: Rct2TicketField() = default; /** Create a new RCT2 field starting at @p data. * @param size The size of the remaining RCT2 field array (not just this field!). */ Rct2TicketField(const char *data, int size); bool isNull() const; // size of the field data, not size of the text content int size() const; int row() const; int column() const; int height() const; int width() const; QString text() const; Rct2TicketField next() const; private: const char *m_data = nullptr; int m_size = 0; }; class Rct2TicketPrivate : public QSharedData { public: QString fieldText(int row, int column, int width, int height = 1) const; QDate firstDayOfValidity() const; QDateTime parseTime(const QString &dateStr, const QString &timeStr) const; Rct2TicketField firstField() const; Uic9183Block block; QDateTime contextDt; }; } Uic9183Block::Uic9183Block(const char* data, int size) : m_data(data) , m_size(size) { } int Uic9183Block::version() const { return QByteArray(m_data + 6, 2).toInt(); } Vendor0080BLSubBlock::Vendor0080BLSubBlock(const char *data, int size) : m_data(data) , m_size(size) { } Vendor0080BLBlock::Vendor0080BLBlock(const Uic9183Block &block) { if (block.isNull()) { return; } if (block.version() != 2 && block.version() != 3) { qCWarning(Log) << "Unsupported version of 0080BL vendor block." << block.version(); return; } if (block.isNull() || block.size() < 15 || subblockOffset(block) > block.size()) { return; } m_block = block; } bool Vendor0080BLBlock::isValid() const { return !m_block.isNull(); } Vendor0080BLSubBlock Vendor0080BLBlock::findSubBlock(const char id[3]) const { for (int i = subblockOffset(m_block); i < m_block.size();) { if (*(m_block.data() + i) != 'S') { qCWarning(Log) << "0080BL invalid S-block format."; return {}; } const int subblockSize = QByteArray(m_block.data() + i + 4, 4).toInt(); if (subblockSize + i > m_block.size()) { qCWarning(Log) << "0080BL S-block size exceeds block size."; return {}; } Vendor0080BLSubBlock sb(m_block.data() + i, subblockSize); if (!sb.isNull() && strncmp(sb.id(), id, 3) == 0) { return sb; } i += subblockSize + 8; } return {}; } int Vendor0080BLBlock::subblockOffset(const Uic9183Block& block) { const auto certCount = *(block.data() + 14) - '0'; const auto certSize = block.version() == 2 ? 46 : 26; return 15 + certSize * certCount + 2; } Uic9183Block Uic9183ParserPrivate::findBlock(const char name[6]) const { // 6x header name // 2x block version // 4x block size as string, including the header for (int i = 0; i < m_payload.size() - 12;) { const int blockSize = m_payload.mid(i + 8, 4).toInt(); if (blockSize + i > m_payload.size()) { qCWarning(Log) << "UIC 918-3 block size exceeds payload size."; return {}; } if (strncmp(name, m_payload.data() + i, 6) == 0) { return {m_payload.data() + i, blockSize}; } i += blockSize; } return {}; } Rct2TicketField::Rct2TicketField(const char *data, int size) : m_data(data) , m_size(size) { if (size <= 13) { // too small qCWarning(Log) << "Found too small RCT2 field:" << size; m_data = nullptr; return; } // invalid format if (!std::all_of(data, data + 8, isdigit) || !std::all_of(data + 9, data + 13, isdigit)) { qCWarning(Log) << "Found RCT2 field with invalid format"; m_data = nullptr; return; } // size is too large if (this->size() > m_size) { qCWarning(Log) << "Found RCT2 field with invalid size" << this->size() << m_size; m_data = nullptr; return; } } bool Rct2TicketField::isNull() const { return !m_data || m_size <= 13; } int Rct2TicketField::size() const { return asciiToInt(m_data + 9, 4) + 13; } int Rct2TicketField::row() const { return asciiToInt(m_data, 2); } int Rct2TicketField::column() const { return asciiToInt(m_data + 2, 2); } int Rct2TicketField::height() const { return asciiToInt(m_data + 4, 2); } int Rct2TicketField::width() const { return asciiToInt(m_data + 6, 2); } QString Rct2TicketField::text() const { return QString::fromUtf8(m_data + 13, asciiToInt(m_data + 9, 4)); } Rct2TicketField Rct2TicketField::next() const { const auto thisSize = size(); const auto remaining = m_size - size(); if (remaining < 0) { return {}; } // search for the next field // in theory this should always trigger at i == 0, unless // the size field is wrong, which happens unfortunately for (int i = 0; i < remaining - 13; ++i) { Rct2TicketField f(m_data + thisSize + i, remaining - i); if (!f.isNull()) { return f; } } return {}; } Rct2TicketField Rct2TicketPrivate::firstField() const { if (block.size() > 20) { return Rct2TicketField(block.data() + 20, block.size() - 20); } return {}; } QString Rct2TicketPrivate::fieldText(int row, int column, int width, int height) const { QString s; for (auto f = firstField(); !f.isNull(); f = f.next()) { if (f.row() + f.height() - 1 < row || f.row() > row + height - 1) { continue; } if (f.column() + f.width() - 1 < column || f.column() > column + width - 1) { continue; } //qDebug() << "Field:" << f.height() << f.column() << f.height() << f.width() << f.size() << f.text(); // split field into lines // TODO this needs to follow the RCT2 word-wrapping algorithm? const auto content = f.text(); const auto lines = content.splitRef(QLatin1Char('\n')); // cut out the right part of the line for (int i = 0; i < lines.size(); ++i) { if (f.row() + i < row) { continue; } if (f.row() + i > row + height - 1) { break; } // TODO also truncate by w const auto offset = column - f.column(); if (offset >= 0) { s += lines.at(i).mid(offset).left(width); } else { s += lines.at(i); // TODO left padding by offset, truncate by width + offset } } } //qDebug() << "Result:" << row << column << width << height << s; return s; } QDate Rct2TicketPrivate::firstDayOfValidity() const { const auto f = fieldText(3, 1, 48); const auto it = std::find_if(f.begin(), f.end(), [](QChar c) { return c.isDigit(); }); if (it == f.end()) { return {}; } const auto dtStr = f.midRef(std::distance(f.begin(), it)); auto dt = QDate::fromString(dtStr.left(10).toString(), QStringLiteral("dd.MM.yyyy")); if (dt.isValid()) { return dt; } dt = QDate::fromString(dtStr.left(8).toString(), QStringLiteral("dd.MM.yy")); if (dt.isValid()) { if (dt.year() < 2000) { dt.setDate(dt.year() + 100, dt.month(), dt.day()); } return dt; } dt = QDate::fromString(dtStr.left(4).toString(), QStringLiteral("yyyy")); return dt; } QDateTime Rct2TicketPrivate::parseTime(const QString &dateStr, const QString &timeStr) const { const auto 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")); } const auto validDt = firstDayOfValidity(); const auto year = validDt.isValid() ? validDt.year() : contextDt.date().year(); return QDateTime({year, d.month(), d.day()}, t); } // 6x "U_TLAY" // 2x version (always "01") // 4x record length, numbers as ASCII text // 4x ticket layout type ("RCT2") // 4x field count // Nx fields (see Rct2TicketField) Rct2Ticket::Rct2Ticket() : d(new Rct2TicketPrivate) { } Rct2Ticket::Rct2Ticket(Uic9183Block block) : d(new Rct2TicketPrivate) { d->block = block; qDebug() << QByteArray(block.data(), block.size()); std::vector out; for (auto f = d->firstField(); !f.isNull(); f = f.next()) { qDebug() << "Field:" << f.row() << f.column() << f.width() << f.height() << f.text() << f.size(); out.resize(std::max(f.row() + 1, out.size())); out[f.row()].resize(std::max(out[f.row()].size(), f.column() + f.width() + 1), QLatin1Char(' ')); out[f.row()].replace(f.column(), f.width(), f.text()); } for (const auto &line : out) { qDebug() << line; } } Rct2Ticket::Rct2Ticket(const Rct2Ticket&) = default; Rct2Ticket::~Rct2Ticket() = default; Rct2Ticket& Rct2Ticket::operator=(const Rct2Ticket&) = default; bool Rct2Ticket::isValid() const { return !d->block.isNull() && d->block.size() > 34 && std::strncmp(d->block.data() + 6, "01", 2) == 0 && std::strncmp(d->block.data() + 12, "RCT2", 4) == 0; } void Rct2Ticket::setContextDate(const QDateTime &contextDt) { d->contextDt = contextDt; } QDate Rct2Ticket::firstDayOfValidity() const { return d->firstDayOfValidity(); } Rct2Ticket::Type Rct2Ticket::type() const { // ### this field can theoretically be translated if (d->fieldText(0, 18, 51).trimmed().compare(QLatin1String("RESERVATION"), Qt::CaseInsensitive) == 0) { return Reservation; } return Unknown; } QDateTime Rct2Ticket::outboundDepartureTime() const { return d->parseTime(d->fieldText(6, 1, 5), d->fieldText(6, 7, 5)); } QDateTime Rct2Ticket::outboundArrivalTime() const { return d->parseTime(d->fieldText(6, 52, 5), d->fieldText(6, 58, 5)); } QString Rct2Ticket::outboundDepartureStation() const { return d->fieldText(6, 13, 17).trimmed(); } QString Rct2Ticket::outboundArrivalStation() const { return d->fieldText(6, 34, 17).trimmed(); } QString Rct2Ticket::outboundClass() const { return d->fieldText(6, 66, 5).trimmed(); } QString Rct2Ticket::trainNumber() const { if (type() == Reservation) { return d->fieldText(8, 7, 5).trimmed(); } return {}; } QString Rct2Ticket::coachNumber() const { if (type() == Reservation) { return d->fieldText(8, 26, 3).trimmed(); } return {}; } QString Rct2Ticket::seatNumber() const { if (type() == Reservation) { return d->fieldText(8, 48, 23).trimmed(); } return {}; } Uic9183Parser::Uic9183Parser() : d(new Uic9183ParserPrivate) { } Uic9183Parser::Uic9183Parser(const Uic9183Parser&) = default; Uic9183Parser::~Uic9183Parser() = default; Uic9183Parser& Uic9183Parser::operator=(const Uic9183Parser&) = default; void Uic9183Parser::setContextDate(const QDateTime &contextDt) { d->m_contextDt = contextDt; } void Uic9183Parser::parse(const QByteArray &data) { d->m_payload.clear(); // header and signature block (64 byte total) if (!Uic9183Parser::maybeUic9183(data)) { qCWarning(Log) << "UIC 918-3 ticket too short or has wrong header/version."; return; } // 3x header // 2x version // 4x UIC code of the signing carrier // 5x signature key id // 50x ASN.1 signature // zlib compressed payload if (data.size() < 64 + 8) { qCWarning(Log) << "UIC 918-3 payload too short."; return; } // 4x compressed payload size as string // 2x zlib header 0x789C if (data[68] != 0x78 || ((uchar)data[69] != 0x9C && (uchar)data[69] != 0xDA)) { qCWarning(Log) << "UIC 918-3 payload has wrong zlib header."; return; } // nx zlib payload d->m_payload.resize(4096); z_stream stream; stream.zalloc = nullptr; stream.zfree = nullptr; stream.opaque = nullptr; stream.avail_in = data.size() - 68; stream.next_in = reinterpret_cast(const_cast(data.data() + 68)); stream.avail_out = d->m_payload.size(); stream.next_out = reinterpret_cast(d->m_payload.data()); inflateInit(&stream); const auto res = inflate(&stream, Z_NO_FLUSH); switch (res) { case Z_OK: case Z_STREAM_END: break; // all good default: qCWarning(Log) << "UIC 918.3 payload zlib decompression failed" << stream.msg; return; } inflateEnd(&stream); d->m_payload.truncate(d->m_payload.size() - stream.avail_out); //qCDebug(Log) << res << d->m_payload << stream.avail_out; } bool Uic9183Parser::isValid() const { return !d->m_payload.isEmpty(); } // U_HEAD (version 1, size 53) // 4x issuing carrier id // 6x PNR // 20x unique ticket key // 12x issuing date/time as ddMMyyyyHHMM, as UTC // 1x flags // 2x ticket language // 2x secondary ticket language QString Uic9183Parser::pnr() const { const auto b = d->findBlock("U_HEAD"); if (b.isNull() || b.version() != 1 || b.size() != 53) { return {}; } return QString::fromUtf8(b.data() + 16, 6); } QString Uic9183Parser::carrierId() const { const auto b = d->findBlock("U_HEAD"); if (b.isNull() || b.version() != 1 || b.size() != 53) { return {}; } return QString::fromUtf8(b.data() + 12, 4); } Person Uic9183Parser::person() const { // Deutsche Bahn vendor block const auto b = Vendor0080BLBlock(d->findBlock("0080BL")); if (b.isValid()) { // S028 contains family and given name separated by a '#', UTF-8 encoded auto sblock = b.findSubBlock("028"); if (!sblock.isNull()) { const auto endIt = sblock.data() + sblock.size(); auto it = std::find(sblock.data(), endIt, '#'); if (it != endIt) { Person p; p.setGivenName(QString::fromUtf8(sblock.data(), std::distance(sblock.data(), it))); ++it; p.setFamilyName(QString::fromUtf8(it, std::distance(it, endIt))); return p; } } // S023 contains the full name, UTF-8 encoded sblock = b.findSubBlock("023"); if (!sblock.isNull()) { Person p; p.setName(sblock.toString()); return p; } } // RCT2 tickets const auto rct2 = rct2Ticket(); if (rct2.isValid()) { const auto name = rct2.d->fieldText(0, 52, 19); if (!name.isEmpty()) { Person p; p.setName(name); return p; } } return {}; } QString Uic9183Parser::outboundDepartureStationId() const { const auto b = Vendor0080BLBlock(d->findBlock("0080BL")); if (b.isValid()) { // S035 contains the IBNR, possible with leading '80' country code and leading 0 stripped const auto sblock = b.findSubBlock("035"); if (!sblock.isNull() && sblock.size() <= 7) { QString ibnr = QStringLiteral("ibnr:8000000"); const auto s = sblock.toString(); return ibnr.replace(ibnr.size() - s.size(), s.size(), s); } } return {}; } QString Uic9183Parser::outboundArrivalStationId() const { const auto b = Vendor0080BLBlock(d->findBlock("0080BL")); if (b.isValid()) { // S036 contains the IBNR, possible with leading '80' country code and leading 0 stripped const auto sblock = b.findSubBlock("036"); if (!sblock.isNull() && sblock.size() <= 7) { QString ibnr = QStringLiteral("ibnr:8000000"); const auto s = sblock.toString(); return ibnr.replace(ibnr.size() - s.size(), s.size(), s); } } return {}; } Rct2Ticket Uic9183Parser::rct2Ticket() const { Rct2Ticket rct2(d->findBlock("U_TLAY")); rct2.setContextDate(d->m_contextDt); return rct2; } QVariant Uic9183Parser::rct2TicketVariant() const { const auto rct2 = rct2Ticket(); if (rct2.isValid()) { return QVariant::fromValue(rct2); } return {}; } bool Uic9183Parser::maybeUic9183(const QByteArray& data) { if (data.size() < 64) { return false; } if (!data.startsWith("#UT") && !data.startsWith("OTI")) { return false; } if (data.at(3) != '0' || data.at(4) != '1') { return false; } return true; } #include "moc_uic9183parser.cpp" diff --git a/src/uic9183parser.h b/src/uic9183parser.h index 5f91ce3..f3e877e 100644 --- a/src/uic9183parser.h +++ b/src/uic9183parser.h @@ -1,167 +1,167 @@ /* 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 . + along with this program. If not, see . */ #ifndef KITINERARY_UIC9183PARSER_H #define KITINERARY_UIC9183PARSER_H #include "kitinerary_export.h" #include #include #include class QDateTime; namespace KItinerary { class Rct2TicketPrivate; class Uic9183Block; class Uic9183Parser; /** RCT2 ticket layout payload of an UIC 918.3 ticket token. */ class KITINERARY_EXPORT Rct2Ticket { Q_GADGET Q_PROPERTY(QDate firstDayOfValidity READ firstDayOfValidity) Q_PROPERTY(Type type READ type) Q_PROPERTY(QDateTime outboundDepartureTime READ outboundDepartureTime) Q_PROPERTY(QDateTime outboundArrivalTime READ outboundArrivalTime) Q_PROPERTY(QString outboundDepartureStation READ outboundDepartureStation) Q_PROPERTY(QString outboundArrivalStation READ outboundArrivalStation) Q_PROPERTY(QString outboundClass READ outboundClass) Q_PROPERTY(QString trainNumber READ trainNumber) Q_PROPERTY(QString coachNumber READ coachNumber) Q_PROPERTY(QString seatNumber READ seatNumber) public: Rct2Ticket(); Rct2Ticket(const Rct2Ticket&); ~Rct2Ticket(); Rct2Ticket& operator=(const Rct2Ticket&); /** Returns whether this is a valid RCT2 ticket layout block. */ bool isValid() const; /** Date/time this ticket was first encounted, to recover possibly missing year numbers. */ void setContextDate(const QDateTime &contextDt); /** First day the ticket is valid. */ QDate firstDayOfValidity() const; /** Type of RCT2 ticket. * @see ERA TAP TSI Annex B.6. */ enum Type { Reservation, ///< an reservation-only ticket (RES) Unknown ///< ticket type could not be detected, or ticket type not supported yet }; Q_ENUM(Type); /** Returns the ticket type. */ Type type() const; /** Departure time of the outbound segment. */ QDateTime outboundDepartureTime() const; /** Arrival time of the outbound segment. */ QDateTime outboundArrivalTime() const; /** Departure station of the outbound segment. */ QString outboundDepartureStation() const; /** Arrival station of the outbound segement. */ QString outboundArrivalStation() const; /** Class of the outbound segment. */ QString outboundClass() const; /** Train number (for reservation tickets). */ QString trainNumber() const; /** Coach number (for reservation tickets). */ QString coachNumber() const; /** Seat number (for reservation tickets). */ QString seatNumber() const; private: friend class Uic9183Parser; Rct2Ticket(Uic9183Block block); QExplicitlySharedDataPointer d; }; class Uic9183ParserPrivate; /** Parser for UIC 918.3 and 918.3* train tickets. * * @see http://www.era.europa.eu/Document-Register/Documents/ERA_Technical_Document_TAP_B_7_v1.2.pdf * for information about the general UIC 918-3 structure * @see http://www.era.europa.eu/Document-Register/Documents/ERA_Technical_Document_TAP_B_6_v1_2.pdf * for information about the U_TLAY block * @see https://www.bahn.de/p/view/angebot/regio/barcode.shtml * for information about the 0080VU vendor block */ class KITINERARY_EXPORT Uic9183Parser { Q_GADGET Q_PROPERTY(QString pnr READ pnr) Q_PROPERTY(QString carrierId READ carrierId) Q_PROPERTY(KItinerary::Person person READ person) Q_PROPERTY(QString outboundDepartureStationId READ outboundDepartureStationId) Q_PROPERTY(QString outboundArrivalStationId READ outboundArrivalStationId) /** RCT2 ticket layout block, if present, @c null otherwise. */ Q_PROPERTY(QVariant rct2Ticket READ rct2TicketVariant) public: Uic9183Parser(); Uic9183Parser(const Uic9183Parser&); ~Uic9183Parser(); Uic9183Parser& operator=(const Uic9183Parser&); /** Date/time this ticket was first encountered. * This is used to recover a missing year in the ticket data. */ void setContextDate(const QDateTime &contextDt); void parse(const QByteArray &data); bool isValid() const; /** The booking reference. */ QString pnr() const; /** The UIC carrier code. */ QString carrierId() const; /** The person this ticket is issued to. */ Person person() const; /** Station identifier for the departure station of the outbound trip. */ QString outboundDepartureStationId() const; /** Station identifier for the arrival station of the outbound trip. */ QString outboundArrivalStationId() const; /** RCT2 ticket layout, if present. */ Rct2Ticket rct2Ticket() const; /** Quickly checks if @p might be UIC 918.3 content. * This priorizes speed over correctness and is used in barcode content auto-detection. */ static bool maybeUic9183(const QByteArray &data); private: QVariant rct2TicketVariant() const; QExplicitlySharedDataPointer d; }; } Q_DECLARE_METATYPE(KItinerary::Rct2Ticket) Q_DECLARE_METATYPE(KItinerary::Uic9183Parser) #endif // KITINERARY_UIC9183PARSER_H