File indexing completed on 2024-04-28 04:50:48

0001 /*
0002  * iso-codes.cpp
0003  *
0004  * Copyright (C) 2017 Mauro Carvalho Chehab <mchehab+samsung@kernel.org>
0005  * Copyright (C) 2017 Pino Toscano <pino@kde.org>
0006  *
0007  * This program is free software; you can redistribute it and/or modify
0008  * it under the terms of the GNU General Public License as published by
0009  * the Free Software Foundation; either version 2 of the License, or
0010  * (at your option) any later version.
0011  *
0012  * This program is distributed in the hope that it will be useful,
0013  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0014  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0015  * GNU General Public License for more details.
0016  */
0017 
0018 #include "log.h"
0019 
0020 #include <KLocalizedString>
0021 #include <QFile>
0022 #include <QLocale>
0023 #include <QRegularExpression>
0024 #include <QStandardPaths>
0025 #include <QXmlStreamReader>
0026 
0027 #include "iso-codes.h"
0028 
0029 namespace IsoCodes
0030 {
0031     static void load(QHash<QString, QString> *code_3letters,
0032              QHash<QString, QString> *code_2letters,
0033              QString file,
0034              QString main_key,
0035              QString entry_key,
0036              QString code_3_key,
0037              QString code_2_key,
0038              QString name_key)
0039     {
0040         if (!code_3letters->isEmpty())
0041             return;
0042 
0043         const QString fileName = QStandardPaths::locate(QStandardPaths::GenericDataLocation, file);
0044         if (fileName.isEmpty()) {
0045             qCInfo(logConfig,
0046                    "Could not locate %s (is iso-codes installed?)",
0047                    qPrintable(file));
0048             return;
0049         }
0050 
0051         QFile f(fileName);
0052         if (!f.open(QIODevice::ReadOnly)) {
0053             qCWarning(logConfig,
0054                   "Could not open %s (%s)",
0055                   qPrintable(fileName),
0056                   qPrintable(f.errorString()));
0057             return;
0058         }
0059 
0060         QXmlStreamReader r(&f);
0061         bool inDoc = false;
0062         while (!r.atEnd()) {
0063             const QXmlStreamReader::TokenType t = r.readNext();
0064             QStringRef name;
0065             switch (t) {
0066             case QXmlStreamReader::StartElement:
0067                 name = r.name();
0068                 if (inDoc && name == entry_key) {
0069                     const QXmlStreamAttributes attrs = r.attributes();
0070                     const QString code3 = attrs.value(code_3_key).toString().toUpper();
0071                     const QString lang = attrs.value(name_key).toString();
0072                     code_3letters->insert(code3, lang);
0073                     if (code_2letters) {
0074                         const QString code2 = attrs.value(code_2_key).toString().toUpper();
0075                         if (!code2.isEmpty())
0076                             code_2letters->insert(code2, code3);
0077                     }
0078                 } else if (name == main_key) {
0079                     inDoc = true;
0080                 }
0081                 break;
0082             case QXmlStreamReader::EndElement:
0083                 name = r.name();
0084                 if (inDoc && name == main_key) {
0085                     inDoc = false;
0086                 }
0087                 break;
0088             case QXmlStreamReader::NoToken:
0089             case QXmlStreamReader::Invalid:
0090             case QXmlStreamReader::StartDocument:
0091             case QXmlStreamReader::EndDocument:
0092             case QXmlStreamReader::Characters:
0093             case QXmlStreamReader::Comment:
0094             case QXmlStreamReader::DTD:
0095             case QXmlStreamReader::EntityReference:
0096             case QXmlStreamReader::ProcessingInstruction:
0097                 break;
0098             }
0099         }
0100         if (code_3letters->isEmpty())
0101             qCWarning(logConfig,
0102                   "Error parsing %s: no entries found.",
0103                   qPrintable(fileName));
0104     }
0105 
0106     /*
0107      * ISO 639-2 language codes
0108      * Loaded and translated at runtime from iso-codes.
0109      */
0110     static QHash<QString, QString> iso639_2_codes;
0111 
0112     /*
0113      * ISO 639-1 to ISO 639-2 language code conversion
0114      * Loaded and translated at runtime from iso-codes.
0115      */
0116     static QHash<QString, QString> iso639_1_codes;
0117 
0118     bool getLanguage(const QString &iso_code, QString *language)
0119     {
0120         static bool first = true;
0121 
0122         QString code = iso_code.toUpper();
0123 
0124         if (code == "QAA") {
0125             *language = i18n("Original Language");
0126             return true;
0127         }
0128 
0129         if (first) {
0130             load(&iso639_2_codes,
0131                  &iso639_1_codes,
0132                  QString("xml/iso-codes/iso_639-2.xml"),
0133                  QLatin1String("iso_639_entries"),
0134                  QLatin1String("iso_639_entry"),
0135                  QLatin1String("iso_639_2B_code"),
0136                  QLatin1String("iso_639_1_code"),
0137                  QLatin1String("name"));
0138             first = false;
0139         }
0140 
0141         QHash<QString, QString>::ConstIterator it = iso639_2_codes.constFind(code);
0142         if (it == iso639_2_codes.constEnd()) {
0143             /*
0144              * The ETSI EN 300 468 Annex F spec defines the
0145              * original audio soundtrack code to be "QAA". Yet,
0146              * TV bundle providers could use something else.
0147              *
0148              * At least here, my provider actually uses "ORG"
0149              * instead. Currently, this is an unused ISO 639
0150              * code, so we can safely accept it as well,
0151              * at least as a fallback code while ISO doesn't
0152              * define it.
0153              */
0154             if (code == "ORG") {
0155                 *language = i18n("Original Language");
0156                 return true;
0157             }
0158             return false;
0159         }
0160 
0161         if (language) {
0162             *language = i18nd("iso_639-2", it.value().toUtf8().constData());
0163         }
0164         return true;
0165     }
0166 
0167     const QString code2Convert(const QString &code2)
0168     {
0169         static bool first = true;
0170 
0171         QString code = code2.toUpper();
0172 
0173         /* Ignore any embedded Country data */
0174         code.remove(QRegularExpression("_.*"));
0175 
0176         if (first) {
0177             load(&iso639_2_codes,
0178                  &iso639_1_codes,
0179                  QString("xml/iso-codes/iso_639-2.xml"),
0180                  QLatin1String("iso_639_entries"),
0181                  QLatin1String("iso_639_entry"),
0182                  QLatin1String("iso_639_2B_code"),
0183                  QLatin1String("iso_639_1_code"),
0184                  QLatin1String("name"));
0185             first = false;
0186         }
0187 
0188         QHash<QString, QString>::ConstIterator it = iso639_1_codes.constFind(code);
0189         if (it == iso639_1_codes.constEnd()) {
0190             return "QAA";
0191         }
0192         return it.value().toUtf8().constData();
0193     }
0194 
0195     /*
0196     * ISO 3166-1 Alpha 3 Country codes
0197     * Loaded and translated at runtime from iso-codes.
0198     */
0199     static QHash<QString, QString> iso3166_1_codes, iso3166_2_codes;
0200 
0201     bool getCountry(const QString &_code, QString *country)
0202     {
0203         static bool first = true;
0204         QString code;
0205 
0206         if (first) {
0207             load(&iso3166_1_codes,
0208                  &iso3166_2_codes,
0209                  QString("xml/iso-codes/iso_3166-1.xml"),
0210                  QLatin1String("iso_3166_entries"),
0211                  QLatin1String("iso_3166_entry"),
0212                  QLatin1String("alpha_3_code"),
0213                  QString("alpha_2_code"),
0214                  QLatin1String("name"));
0215             first = false;
0216         }
0217 
0218         if (_code.size() == 2) {
0219             QHash<QString, QString>::ConstIterator it = iso3166_1_codes.constFind(code);
0220             if (it == iso3166_2_codes.constEnd())
0221                 return false;
0222             code = it.value();
0223         } else {
0224             code = _code;
0225         }
0226 
0227         QHash<QString, QString>::ConstIterator it = iso3166_1_codes.constFind(code);
0228         if (it == iso3166_1_codes.constEnd()) {
0229             return false;
0230         }
0231 
0232         if (country) {
0233             *country = i18nd("iso_3166-1", it.value().toUtf8().constData());
0234         }
0235         return true;
0236     }
0237 }