"Fossies" - the Fresh Open Source Software Archive

Member "kaffeine-2.0.18/src/dvb/xmltv.cpp" (14 May 2019, 16703 Bytes) of package /linux/misc/kaffeine-2.0.18.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "xmltv.cpp" see the Fossies "Dox" file reference documentation.

    1 /*
    2  * xmltv.cpp
    3  *
    4  * Copyright (C) 2019 Mauro Carvalho Chehab <mchehab+samsung@kernel.org>
    5  *
    6  * Matches the xmltv dtd found on version 0.6.1
    7  *
    8  * This program is free software; you can redistribute it and/or modify
    9  * it under the terms of the GNU General Public License as published by
   10  * the Free Software Foundation; either version 2 of the License, or
   11  * (at your option) any later version.
   12  *
   13  * This program is distributed in the hope that it will be useful,
   14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
   15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   16  * GNU General Public License for more details.
   17  */
   18 
   19 #include "log.h"
   20 
   21 #include <KLocalizedString>
   22 #include <QFile>
   23 #include <QLocale>
   24 #include <QRegularExpression>
   25 #include <QStandardPaths>
   26 #include <QXmlStreamReader>
   27 
   28 #include "dvbchannel.h"
   29 #include "dvbepg.h"
   30 #include "dvbmanager.h"
   31 #include "iso-codes.h"
   32 #include "xmltv.h"
   33 
   34 #include <QEventLoop>
   35 
   36 XmlTv::XmlTv(DvbManager *manager_) : manager(manager_), r(NULL)
   37 {
   38     channelModel = manager->getChannelModel();
   39     epgModel = manager->getEpgModel();
   40 
   41     connect(&watcher, &QFileSystemWatcher::fileChanged,
   42         this, &XmlTv::load);
   43 };
   44 
   45 void XmlTv::addFile(QString file)
   46 {
   47     if (file == "")
   48         return;
   49 
   50     load(file);
   51 };
   52 
   53 void XmlTv::clear()
   54 {
   55     if (watcher.files().empty())
   56         return;
   57     channelMap.clear();
   58     watcher.removePaths(watcher.files());
   59 };
   60 
   61 
   62 // This function is very close to the one at dvbepg.cpp
   63 DvbEpgLangEntry *XmlTv::getLangEntry(DvbEpgEntry &epgEntry,
   64                      QString &code,
   65                      bool add_code = true)
   66 {
   67     DvbEpgLangEntry *langEntry;
   68 
   69     if (!epgEntry.langEntry.contains(code)) {
   70         if (!add_code)
   71             return NULL;
   72 
   73         DvbEpgLangEntry e;
   74         epgEntry.langEntry.insert(code, e);
   75         if (!manager->languageCodes.contains(code)) {
   76             manager->languageCodes[code] = true;
   77             emit epgModel->languageAdded(code);
   78         }
   79     }
   80     langEntry = &epgEntry.langEntry[code];
   81 
   82     return langEntry;
   83 }
   84 
   85 bool XmlTv::parseChannel(void)
   86 {
   87     const QString emptyString("");
   88     QStringRef empty(&emptyString);
   89 
   90     const QXmlStreamAttributes attrs = r->attributes();
   91     QStringRef channelName = attrs.value("id");
   92     QList<QString>list;
   93 
   94     QString current = r->name().toString();
   95     while (!r->atEnd()) {
   96         const QXmlStreamReader::TokenType t = r->readNext();
   97 
   98         if (t == QXmlStreamReader::EndElement) {
   99             if (r->name() == current)
  100                 break;
  101         }
  102 
  103         if (t != QXmlStreamReader::StartElement)
  104             continue;
  105 
  106         QStringRef name = r->name();
  107         if (name == "display-name") {
  108             QString display = r->readElementText();
  109             list.append(display);
  110         } else if (name != "icon" && name != "url") {
  111             static QString lastNotFound("");
  112             if (name.toString() != lastNotFound) {
  113                 qCWarning(logDvb,
  114                     "Ignoring unknown channel tag '%s'",
  115                     qPrintable(name.toString()));
  116                 lastNotFound = name.toString();
  117             }
  118         }
  119     }
  120 
  121     channelMap.insert(channelName.toString(), list);
  122     return true;
  123 }
  124 
  125 void XmlTv::parseKeyValues(QHash<QString, QString> &keyValues)
  126 {
  127     QXmlStreamAttributes attrs;
  128     QHash<QString, QString>::ConstIterator it;
  129 
  130     QString current = r->name().toString();
  131     while (!r->atEnd()) {
  132         const QXmlStreamReader::TokenType t = r->readNext();
  133 
  134         if (t == QXmlStreamReader::EndElement) {
  135             if (r->name() == current)
  136                 return;
  137         }
  138 
  139         if (t != QXmlStreamReader::StartElement)
  140             continue;
  141 
  142         attrs = r->attributes();
  143         QString key = r->name().toString();
  144         QString value;
  145 
  146         it = keyValues.constFind(key);
  147         if (it != keyValues.constEnd())
  148             value = *it + ", ";
  149 
  150         value += r->readElementText();
  151 
  152         keyValues.insert(key, value);
  153     }
  154 }
  155 
  156 void XmlTv::ignoreTag(void)
  157 {
  158     QXmlStreamAttributes attrs;
  159 
  160     QString current = r->name().toString();
  161     while (!r->atEnd()) {
  162         const QXmlStreamReader::TokenType t = r->readNext();
  163         if (t == QXmlStreamReader::EndElement) {
  164             if (r->name() == current)
  165                 return;
  166         }
  167     }
  168 }
  169 
  170 QString XmlTv::getValue(QHash<QString, QString> &keyValues, QString key)
  171 {
  172     QHash<QString, QString>::ConstIterator it;
  173 
  174     it = keyValues.constFind(key);
  175     if (it == keyValues.constEnd())
  176         return QString("");
  177 
  178     return *it;
  179 }
  180 
  181 QString XmlTv::parseCredits(void)
  182 {
  183     QHash<QString, QString>::ConstIterator it;
  184     QHash<QString, QString> keyValues;
  185     QXmlStreamAttributes attrs;
  186     QString name, values;
  187 
  188     // Store everything into a hash
  189     QString current = r->name().toString();
  190     while (!r->atEnd()) {
  191         const QXmlStreamReader::TokenType t = r->readNext();
  192 
  193         if (t == QXmlStreamReader::EndElement) {
  194             if (r->name() == current)
  195                 break;
  196         }
  197 
  198         if (t != QXmlStreamReader::StartElement)
  199             continue;
  200 
  201         attrs = r->attributes();
  202         QString key = r->name().toString();
  203         QString value;
  204 
  205         it = keyValues.constFind(key);
  206         if (it != keyValues.constEnd())
  207             value = *it + ", ";
  208 
  209         value += r->readElementText();
  210 
  211         if (key == "actor") {
  212             value += i18n(" as ") + attrs.value("role").toString();
  213         }
  214 
  215         keyValues.insert(key, value);
  216     }
  217 
  218     // Parse the hash values
  219     foreach(const QString &key, keyValues.keys()) {
  220         // Be explicit here, in order to allow translations
  221         if (key == "director")
  222             name = i18n("Director(s)");
  223         else if (key == "actor")
  224             name = i18n("Actor(s)");
  225         else if (key == "writer")
  226             name = i18n("Writer(s)");
  227         else if (key == "adapter")
  228             name = i18n("Adapter(s)");
  229         else if (key == "producer")
  230             name = i18n("Producer(s)");
  231         else if (key == "composer")
  232             name = i18n("Composer(s)");
  233         else if (key == "editor")
  234             name = i18n("Editor(s)");
  235         else if (key == "presenter")
  236             name = i18n("Presenter(s)");
  237         else if (key == "commentator")
  238             name = i18n("Commentator(s)");
  239         else if (key == "guest")
  240             name = i18n("Guest(s)");
  241         else
  242             name = key + "(s)";
  243 
  244         values += name + ": " + keyValues.value(key) + "\n";
  245     }
  246 
  247     return values;
  248 }
  249 
  250 bool XmlTv::parseProgram(void)
  251 {
  252     const QString emptyString("");
  253     QStringRef empty(&emptyString);
  254 
  255     QXmlStreamAttributes attrs = r->attributes();
  256     QStringRef channelName = attrs.value("channel");
  257     QHash<QString, QList<QString>>::ConstIterator it;
  258 
  259     it = channelMap.constFind(channelName.toString());
  260     if (it == channelMap.constEnd()) {
  261         qCWarning(logDvb,
  262               "Error parsing program: channel %s not found",
  263               qPrintable(channelName.toString()));
  264         return false;
  265     }
  266 
  267     QList<QString>list = it.value();
  268     QList<QString>::iterator name;
  269     bool has_channel = false;
  270 
  271     for (name = list.begin(); name != list.end(); name++) {
  272         if (channelModel->hasChannelByName(*name)) {
  273             has_channel = true;
  274             break;
  275         }
  276     }
  277 
  278     if (!has_channel) {
  279 #if 0 // This can be too noisy to keep enabled
  280         static QString lastNotFound("");
  281         if (channelName.toString() != lastNotFound) {
  282             qCWarning(logDvb,
  283                 "Error: channel %s not found at transponders",
  284                 qPrintable(channelName.toString()));
  285             lastNotFound = channelName.toString();
  286         }
  287 #endif
  288         ignoreTag();
  289 
  290         return true; // Not a parsing error
  291     }
  292 
  293     DvbSharedChannel channel = channelModel->findChannelByName(*name);
  294     DvbEpgEntry epgEntry;
  295     DvbEpgLangEntry *langEntry;
  296     QString start = attrs.value("start").toString();
  297     QString stop = attrs.value("stop").toString();
  298 
  299     /* Place "-", ":" and spaces to date formats for Qt::ISODate parser */
  300     start.replace(QRegularExpression("^(\\d...)(\\d)"), "\\1-\\2");
  301     start.replace(QRegularExpression("^(\\d...-\\d.)(\\d)"), "\\1-\\2");
  302     start.replace(QRegularExpression("^(\\d...-\\d.-\\d.)(\\d)"), "\\1 \\2");
  303     start.replace(QRegularExpression("^(\\d...-\\d.-\\d. \\d.)(\\d)"), "\\1:\\2");
  304     start.replace(QRegularExpression("^(\\d...-\\d.-\\d. \\d.:\\d.)(\\d)"), "\\1:\\2");
  305 
  306     stop.replace(QRegularExpression("^(\\d...)(\\d)"), "\\1-\\2");
  307     stop.replace(QRegularExpression("^(\\d...-\\d.)(\\d)"), "\\1-\\2");
  308     stop.replace(QRegularExpression("^(\\d...-\\d.-\\d.)(\\d)"), "\\1 \\2");
  309     stop.replace(QRegularExpression("^(\\d...-\\d.-\\d. \\d.)(\\d)"), "\\1:\\2");
  310     stop.replace(QRegularExpression("^(\\d...-\\d.-\\d. \\d.:\\d.)(\\d)"), "\\1:\\2");
  311 
  312     /* Convert formats to QDateTime */
  313     epgEntry.begin = QDateTime::fromString(start, Qt::ISODate);
  314     QDateTime end = QDateTime::fromString(stop, Qt::ISODate);
  315     epgEntry.duration = QTime(0, 0, 0).addSecs(epgEntry.begin.secsTo(end));
  316 
  317     epgEntry.begin.setTimeSpec(Qt::UTC);
  318     epgEntry.channel = channel;
  319 
  320     QString starRating, credits, date, language, origLanguage, country;
  321     QString episode;
  322     QHash<QString, QString>category, keyword;
  323 
  324     QString current = r->name().toString();
  325     while (!r->atEnd()) {
  326         const QXmlStreamReader::TokenType t = r->readNext();
  327 
  328         if (t == QXmlStreamReader::EndElement) {
  329             if (r->name() == current)
  330                 break;
  331         }
  332 
  333         if (t != QXmlStreamReader::StartElement)
  334             continue;
  335 
  336         QString lang;
  337         QStringRef element = r->name();
  338         if (element == "title") {
  339             attrs = r->attributes();
  340             lang = IsoCodes::code2Convert(attrs.value("lang").toString());
  341             langEntry = getLangEntry(epgEntry, lang);
  342             if (langEntry->title != "")
  343                 langEntry->title += " ";
  344             langEntry->title += r->readElementText();
  345         } else if (element == "sub-title") {
  346             attrs = r->attributes();
  347             lang = IsoCodes::code2Convert(attrs.value("lang").toString());
  348             langEntry = getLangEntry(epgEntry, lang);
  349             if (langEntry->subheading != "")
  350                 langEntry->subheading += " ";
  351             langEntry->subheading += r->readElementText();
  352         } else if (element == "desc") {
  353             attrs = r->attributes();
  354             lang = IsoCodes::code2Convert(attrs.value("lang").toString());
  355             langEntry = getLangEntry(epgEntry, lang);
  356             if (langEntry->details != "")
  357                 langEntry->details += " ";
  358             langEntry->details += r->readElementText();
  359         } else if (element == "rating") {
  360             QHash<QString, QString> keyValues;
  361 
  362             attrs = r->attributes();
  363             QString system = attrs.value("system").toString();
  364 
  365             parseKeyValues(keyValues);
  366             QString value = getValue(keyValues, "value");
  367 
  368             if (value == "")
  369                 continue;
  370 
  371             if (epgEntry.parental != "")
  372                 epgEntry.parental += ", ";
  373 
  374             if (system != "")
  375                 epgEntry.parental += system + " ";
  376 
  377             epgEntry.parental += i18n("rating: ") + value;
  378         } else if (element == "star-rating") {
  379             QHash<QString, QString> keyValues;
  380 
  381             attrs = r->attributes();
  382             QString system = attrs.value("system").toString();
  383 
  384             parseKeyValues(keyValues);
  385             QString value = getValue(keyValues, "value");
  386 
  387             if (value == "")
  388                 continue;
  389 
  390             if (system != "")
  391                 starRating += system + " ";
  392 
  393             starRating += value;
  394         } else if (element == "category") {
  395             attrs = r->attributes();
  396             lang = IsoCodes::code2Convert(attrs.value("lang").toString());
  397 
  398             QString cat = getValue(category, lang);
  399             if (cat != "")
  400                 cat += ", ";
  401             cat += r->readElementText();
  402             category[lang] = cat;
  403         } else if (element == "keyword") {
  404             attrs = r->attributes();
  405             lang = IsoCodes::code2Convert(attrs.value("lang").toString());
  406 
  407             QString kw = getValue(keyword, lang);
  408             if (kw != "")
  409                 kw += ", ";
  410             kw += r->readElementText();
  411             keyword[lang] = kw;
  412         } else if (element == "credits") {
  413             credits = parseCredits();
  414         } else if ((element == "date")) {
  415             QString rawdate = r->readElementText();
  416             date = rawdate.mid(0, 4);
  417             QString month = rawdate.mid(4, 2);
  418             QString day = rawdate.mid(6, 2);
  419             if (month != "")
  420                 date += "-" + month;
  421             if (day != "") {
  422                 date += "-" + day;
  423                 QDate d = QDate::fromString(date, Qt::ISODate);
  424                 date = d.toString(Qt::DefaultLocaleShortDate);
  425             }
  426         } else if (element == "language") {
  427             language = r->readElementText();
  428             if (language.size() == 2)
  429                 IsoCodes::getLanguage(IsoCodes::code2Convert(language), &language);
  430         } else if (element == "orig-language") {
  431             origLanguage = r->readElementText();
  432             if (origLanguage.size() == 2)
  433                 IsoCodes::getLanguage(IsoCodes::code2Convert(origLanguage), &origLanguage);
  434         } else if (element == "country") {
  435             country = r->readElementText();
  436             if (origLanguage.size() == 2)
  437                 IsoCodes::getCountry(IsoCodes::code2Convert(country), &country);
  438         } else if (element == "episode-num") {
  439             attrs = r->attributes();
  440             QString system = attrs.value("system").toString();
  441 
  442             if (system != "xmltv_ns")
  443                 continue;
  444 
  445             episode = r->readElementText();
  446             episode.remove(" ");
  447             episode.replace(QRegularExpression("/.*"), "");
  448             QStringList list = episode.split(".");
  449             if (!list.size())
  450                 continue;
  451             episode = i18n("Season ") + QString::number(list[0].toInt() + 1);
  452             if (list.size() < 2)
  453                 continue;
  454             episode += i18n(" Episode ") + QString::number(list[1].toInt() + 1);
  455         } else if ((element == "aspect") ||
  456                (element == "audio") ||
  457                (element == "icon") ||
  458                (element == "length") ||
  459                (element == "last-chance") ||
  460                (element == "new") ||
  461                (element == "premiere") ||
  462                (element == "previously-shown") ||
  463                (element == "quality") ||
  464                (element == "review") ||
  465                (element == "stereo") ||
  466                (element == "subtitles") ||
  467                (element == "url") ||
  468                (element == "video")) {
  469             ignoreTag();
  470         } else {
  471             static QString lastNotFound("");
  472             if (element.toString() != lastNotFound) {
  473                 qCWarning(logDvb,
  474                     "Ignoring unknown programme tag '%s'",
  475                     qPrintable(element.toString()));
  476                 lastNotFound = element.toString();
  477             }
  478         }
  479     }
  480 
  481     /* Those extra fields are not language-specific data */
  482     if (starRating != "") {
  483         if (epgEntry.content != "")
  484             epgEntry.content += "\n";
  485         epgEntry.content += i18n("Star rating: ") + starRating;
  486     }
  487 
  488     if (date != "") {
  489         if (epgEntry.content != "")
  490             epgEntry.content += "\n";
  491         epgEntry.content += i18n("Date: ") + date;
  492     }
  493 
  494     foreach(const QString &key, category.keys()) {
  495         QString value = i18n("Category: ") + category[key];
  496         QString lang = key;
  497         if (key != "QAA")
  498             langEntry = getLangEntry(epgEntry, lang, false);
  499         if (!langEntry) {
  500             if (epgEntry.content != "")
  501                 epgEntry.content += "\n";
  502             epgEntry.content += value;
  503         } else {
  504             langEntry->details.replace(QRegularExpression("\\n*$"), "<p/>");
  505             langEntry->details += value;
  506         }
  507     }
  508     foreach(const QString &key, keyword.keys()) {
  509         QString value = i18n("Keyword: ") + keyword[key];
  510         QString lang = key;
  511         if (key != "QAA")
  512             langEntry = getLangEntry(epgEntry, lang, false);
  513         if (!langEntry) {
  514             if (epgEntry.content != "")
  515                 epgEntry.content += "\n";
  516             epgEntry.content += value;
  517         } else {
  518             langEntry->details.replace(QRegularExpression("\\n*$"), "<p/>");
  519             langEntry->details += value;
  520         }
  521     }
  522 
  523     if (episode != "") {
  524         if (epgEntry.content != "")
  525             epgEntry.content += "\n";
  526         epgEntry.content += i18n("language: ") + episode;
  527     }
  528 
  529     if (language != "") {
  530         if (epgEntry.content != "")
  531             epgEntry.content += "\n";
  532         epgEntry.content += i18n("language: ") + language;
  533     }
  534 
  535     if (origLanguage != "") {
  536         if (epgEntry.content != "")
  537             epgEntry.content += "\n";
  538         epgEntry.content += i18n("Original language: ") + origLanguage;
  539     }
  540 
  541     if (country != "") {
  542         if (epgEntry.content != "")
  543             epgEntry.content += "\n";
  544         epgEntry.content += i18n("Country: ") + country;
  545     }
  546 
  547     if (credits != "") {
  548         if (epgEntry.content != "")
  549             epgEntry.content += "\n";
  550         epgEntry.content += credits;
  551     }
  552 
  553     epgEntry.content.replace(QRegularExpression("\\n+$"), "");
  554     epgEntry.content.replace(QRegularExpression("\\n"), "<p/>");
  555 
  556     epgModel->addEntry(epgEntry);
  557 
  558     /*
  559      * It is not uncommon to have the same xmltv channel
  560      * associated with multiple DVB channels. It happens,
  561      * for example, when there is a SD, HD, 4K video
  562      * streams associated with the same programs.
  563      * So, add entries also for the other channels.
  564      */
  565     for (; name != list.end(); name++) {
  566         if (channelModel->hasChannelByName(*name)) {
  567             channel = channelModel->findChannelByName(*name);
  568             epgEntry.channel = channel;
  569             epgModel->addEntry(epgEntry);
  570         }
  571     }
  572     return true;
  573 }
  574 
  575 bool XmlTv::load(QString file)
  576 {
  577     bool parseError = false;
  578 
  579     watcher.removePath(file);
  580     if (file.isEmpty()) {
  581         qCInfo(logDvb, "File to load not specified");
  582         return false;
  583     }
  584 
  585     QFile f(file);
  586     if (!f.open(QIODevice::ReadOnly)) {
  587         qCWarning(logDvb,
  588                 "Error opening %s: %s. Will stop monitoring it",
  589                 qPrintable(file),
  590                 qPrintable(f.errorString()));
  591         return false;
  592     }
  593 
  594     qCInfo(logDvb, "Reading XMLTV file from %s", qPrintable(file));
  595 
  596     r = new QXmlStreamReader(&f);
  597     while (!r->atEnd()) {
  598         if (r->readNext() != QXmlStreamReader::StartElement)
  599             continue;
  600 
  601         QStringRef name = r->name();
  602 
  603         if (name == "channel") {
  604             if (!parseChannel())
  605                 parseError = true;
  606         } else if (name == "programme") {
  607             if (!parseProgram())
  608                 parseError = true;
  609         } else if (name != "tv") {
  610             static QString lastNotFound("");
  611             if (name.toString() != lastNotFound) {
  612                 qCWarning(logDvb,
  613                     "Ignoring unknown main tag '%s'",
  614                     qPrintable(r->qualifiedName().toString()));
  615                 lastNotFound = name.toString();
  616             }
  617         }
  618     }
  619 
  620     if (r->error()) {
  621         qCWarning(logDvb, "XMLTV: error: %s",
  622               qPrintable(r->errorString()));
  623     }
  624 
  625     if (parseError) {
  626         qCWarning(logDvb, "XMLTV: parsing error");
  627     }
  628 
  629     f.close();
  630     watcher.addPath(file);
  631     return parseError;
  632 }