File indexing completed on 2024-05-12 04:42:37

0001 /*
0002     SPDX-FileCopyrightText: 2023 Jonah Brüchert <jbb@kaidan.im>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include <QFile>
0008 #include <QJsonDocument>
0009 #include <QJsonArray>
0010 
0011 #include <unordered_map>
0012 
0013 #include "localbackendutils.h"
0014 #include "journeyrequest.h"
0015 
0016 
0017 using namespace KPublicTransport::LocalBackendUtils;
0018 
0019 class Transliteration {
0020 public:
0021     /// simplify less common characters into simpler multi-character equivalents
0022     QString latinize(const QString &string) const {
0023         static std::unordered_map<QChar, QStringView> table {
0024             {u'đ', u"dj"}, {u'ć', u"c"}
0025         };
0026 
0027         QString out;
0028         out.reserve(string.size());
0029 
0030         for (auto c : string) {
0031             if (table.contains(c)) {
0032                 out.append(table.at(c));
0033             } else {
0034                 out.push_back(c);
0035             }
0036         }
0037 
0038         return out;
0039     }
0040 
0041     /// Converts strings from the cyrillic alphabet to their latin equivalents
0042     QString transliterate(const QString &cyrillic) const {
0043         // Avoid construction of transliteration table and manual string copy if nothing requires transliteration
0044         if (std::none_of(cyrillic.begin(), cyrillic.end(), [](QChar c) {
0045                 return c.script() == QChar::Script_Cyrillic;
0046             })) {
0047             // No copy, because implicitly shared
0048             return cyrillic;
0049         }
0050 
0051         // Generated from <table> element on https://de.wikipedia.org/wiki/ISO_9
0052         // using src/lib/networks/stations/generate_transliteration_table.py
0053         static std::unordered_map<QChar, QStringView> iso9Table = {
0054             {u'А', u"A"}, {u'а', u"a"}, {u'Ӑ', u"Ă"}, {u'ӑ', u"ă"}, {u'Ӓ', u"Ä"},
0055             {u'ӓ', u"ä"}, {u'Ә', u"A"}, {u'ә', u"a"}, {u'Б', u"B"}, {u'б', u"b"},
0056             {u'В', u"V"}, {u'в', u"v"}, {u'Г', u"G"}, {u'г', u"g"}, {u'Ґ', u"G"},
0057             {u'ґ', u"g"}, {u'Ҕ', u"Ğ"}, {u'ҕ', u"ğ"}, {u'Ғ', u"Ġ"}, {u'ғ', u"ġ"},
0058             {u'Д', u"D"}, {u'д', u"d"}, {u'Ђ', u"Đ"}, {u'ђ', u"đ"}, {u'Ѓ', u"Ǵ"},
0059             {u'ѓ', u"ǵ"}, {u'Е', u"E"}, {u'е', u"e"}, {u'Ё', u"Ë"}, {u'ё', u"ë"},
0060             {u'Ӗ', u"Ĕ"}, {u'ӗ', u"ĕ"}, {u'Є', u"Ê"}, {u'є', u"ê"}, {u'Ҽ', u"C"},
0061             {u'ҽ', u"c"}, {u'Ҿ', u"Ç"}, {u'ҿ', u"ç"}, {u'Ж', u"Ž"}, {u'ж', u"ž"},
0062             {u'Ӂ', u"Z"}, {u'ӂ', u"z"}, {u'Ӝ', u"Z"}, {u'ӝ', u"z"}, {u'Җ', u"Ž"},
0063             {u'җ', u"ž"}, {u'З', u"Z"}, {u'з', u"z"}, {u'Ӟ', u"Z"}, {u'ӟ', u"z"},
0064             {u'Ѕ', u"Ẑ"}, {u'ѕ', u"ẑ"}, {u'Ӡ', u"Ź"}, {u'ӡ', u"ź"}, {u'И', u"I"},
0065             {u'и', u"i"}, {u'Ӥ', u"Î"}, {u'ӥ', u"î"}, {u'І', u"Ì"}, {u'і', u"ì"},
0066             {u'Ї', u"Ï"}, {u'ї', u"ï"}, {u'Й', u"J"}, {u'й', u"j"}, {u'Ј', u"J"},
0067             {u'ј', u"ǰ"}, {u'К', u"K"}, {u'к', u"k"}, {u'Қ', u"Ķ"}, {u'қ', u"ķ"},
0068             {u'Ҟ', u"K"}, {u'ҟ', u"k"}, {u'Л', u"L"}, {u'л', u"l"}, {u'Љ', u"L"},
0069             {u'љ', u"l̂"}, {u'М', u"M"}, {u'м', u"m"}, {u'Н', u"N"}, {u'н', u"n"},
0070             {u'Њ', u"N"}, {u'њ', u"n"}, {u'Ҥ', u"Ṅ"}, {u'ҥ', u"ṅ"}, {u'Ң', u"Ṇ"},
0071             {u'ң', u"ṇ"}, {u'О', u"O"}, {u'о', u"o"}, {u'Ӧ', u"Ö"}, {u'ӧ', u"ö"},
0072             {u'Ө', u"Ô"}, {u'ө', u"ô"}, {u'П', u"P"}, {u'п', u"p"}, {u'Ҧ', u"Ṕ"},
0073             {u'ҧ', u"ṕ"}, {u'Р', u"R"}, {u'р', u"r"}, {u'С', u"S"}, {u'с', u"s"},
0074             {u'Ҫ', u"Ç"}, {u'ҫ', u"ç"}, {u'Т', u"T"}, {u'т', u"t"}, {u'Ҭ', u"Ţ"},
0075             {u'ҭ', u"ţ"}, {u'Ћ', u"Ć"}, {u'ћ', u"ć"}, {u'Ќ', u"Ḱ"}, {u'ќ', u"ḱ"},
0076             {u'У', u"U"}, {u'у', u"u"}, {u'У', u"Ú"}, {u'у', u"ú"}, {u'Ў', u"Ŭ"},
0077             {u'ў', u"ŭ"}, {u'Ӱ', u"Ü"}, {u'ӱ', u"ü"}, {u'Ӳ', u"Ű"}, {u'ӳ', u"ű"},
0078             {u'Ү', u"Ù"}, {u'ү', u"ù"}, {u'Ф', u"F"}, {u'ф', u"f"}, {u'Х', u"H"},
0079             {u'х', u"h"}, {u'Ҳ', u"Ḩ"}, {u'ҳ', u"ḩ"}, {u'Һ', u"Ḥ"}, {u'һ', u"ḥ"},
0080             {u'Ц', u"C"}, {u'ц', u"c"}, {u'Ҵ', u"C"}, {u'ҵ', u"c"}, {u'Ч', u"Č"},
0081             {u'ч', u"č"}, {u'Ӵ', u"C"}, {u'ӵ', u"c"}, {u'Ҷ', u"Ç"}, {u'ҷ', u"ç"},
0082             {u'Џ', u"D"}, {u'џ', u"d"}, {u'Ш', u"Š"}, {u'ш', u"š"}, {u'Щ', u"Ŝ"},
0083             {u'щ', u"ŝ"}, {u'Ы', u"Y"}, {u'ы', u"y"}, {u'Ӹ', u"Ÿ"}, {u'ӹ', u"ÿ"},
0084             {u'Э', u"È"}, {u'э', u"è"}, {u'Ю', u"Û"}, {u'ю', u"û"}, {u'Я', u"Â"},
0085             {u'я', u"â"}, {u'Ѣ', u"Ě"}, {u'ѣ', u"ě"}, {u'Ѫ', u"Ǎ"}, {u'ѫ', u"ǎ"},
0086             {u'Ѳ', u"F"}, {u'ѳ', u"f"}, {u'Ѵ', u"Ỳ"}, {u'ѵ', u"ỳ"}, {u'Ҩ', u"Ò"},
0087             {u'ҩ', u"ò"}, {u'Ӏ', u"‡"}, {u'’', u"‵"},
0088         };
0089 
0090 
0091         QString out;
0092         out.reserve(cyrillic.size());
0093 
0094         for (auto c : cyrillic) {
0095             if (iso9Table.contains(c)) {
0096                 out.append(iso9Table.at(c));
0097             } else {
0098                 out.push_back(c);
0099             }
0100         }
0101 
0102         return out;
0103     };
0104 };
0105 
0106 
0107 QString KPublicTransport::LocalBackendUtils::makeSearchableName(const QString &name)
0108 {
0109     Transliteration t;
0110     return t.latinize(t.transliterate(name))
0111         // Remove parts in parantheses, the DB API likes to add the country in parantheses
0112         .replace(QRegularExpression(QStringLiteral(R"(\([^)]*\))")), QString())
0113         // Split accents into their own charcters
0114         .normalized(QString::NormalizationForm_D)
0115         // filter those characters out
0116         .replace(QRegularExpression(QStringLiteral("[^a-zA-Z0-9\\s]")), QString())
0117         // Remove spaces
0118         .replace(QStringLiteral(" "), QString())
0119         .toLower()
0120         .trimmed();
0121 }
0122 
0123 bool KPublicTransport::LocalBackendUtils::isInSelectedTimeframe(const QDateTime &departure, const QDateTime &arrival, const JourneyRequest &req)
0124 {
0125     // Filter for requested arrival / departure time frame
0126     if (req.dateTimeMode() == JourneyRequest::Departure) {
0127         if (departure < req.dateTime()) {
0128             return false;
0129         }
0130     } else {
0131         if (arrival > req.dateTime()) {
0132             return false;
0133         }
0134     }
0135 
0136     return true;
0137 }