MythTV  master
xmltvparser.cpp
Go to the documentation of this file.
1 #include "xmltvparser.h"
2 
3 // Qt headers
4 #include <QFile>
5 #include <QStringList>
6 #include <QDateTime>
7 #include <QDomDocument>
8 #include <QUrl>
9 
10 // C++ headers
11 #include <iostream>
12 #include <cstdlib>
13 
14 // libmyth headers
15 #include "exitcodes.h"
16 #include "mythcorecontext.h"
17 #include "mythdate.h"
18 
19 // libmythtv headers
20 #include "programinfo.h"
21 #include "programdata.h"
22 #include "dvbdescriptors.h"
23 #include "channelinfo.h"
24 
25 // libmythmetadata headers
26 #include "metadatadownload.h"
27 
28 // filldata headers
29 #include "channeldata.h"
30 #include "fillutil.h"
31 
33 {
34  m_currentYear = MythDate::current().date().toString("yyyy").toUInt();
35 }
36 
37 static uint ELFHash(const QByteArray &ba)
38 {
39  const auto *k = (const uchar *)ba.data();
40  uint h = 0;
41 
42  if (k)
43  {
44  while (*k)
45  {
46  uint g = 0;
47  h = (h << 4) + *k++;
48  if ((g = (h & 0xf0000000)) != 0)
49  h ^= g >> 24;
50  h &= ~g;
51  }
52  }
53 
54  return h;
55 }
56 
57 static void fromXMLTVDate(QString &timestr, QDateTime &dt)
58 {
59  // The XMLTV spec requires dates to either be in UTC/GMT or to specify a
60  // valid timezone. We are sticking to the spec and require all grabbers
61  // to comply.
62 
63  if (timestr.isEmpty())
64  {
65  LOG(VB_XMLTV, LOG_ERR, "Found empty Date/Time in XMLTV data, ignoring");
66  return;
67  }
68 
69  QStringList split = timestr.split(" ", QString::SkipEmptyParts);
70  QString ts = split[0];
71  QDate tmpDate;
72  QTime tmpTime;
73  QString tzoffset;
74 
75  // Process the TZ offset (if any)
76  if (split.size() > 1)
77  {
78  tzoffset = split[1];
79  // These shouldn't be required and they aren't ISO 8601 but the
80  // xmltv spec mentions these and just these so handle them just in
81  // case
82  if (tzoffset == "GMT" || tzoffset == "UTC")
83  tzoffset = "+0000";
84  else if (tzoffset == "BST")
85  tzoffset = "+0100";
86  }
87  else
88  {
89  // We will accept a datetime with a trailing Z as being explicit
90  if (ts.endsWith('Z'))
91  {
92  tzoffset = "+0000";
93  ts.truncate(ts.length()-1);
94  }
95  else
96  {
97  tzoffset = "+0000";
98  static bool s_warnedOnceOnImplicitUtc = false;
99  if (!s_warnedOnceOnImplicitUtc)
100  {
101  LOG(VB_XMLTV, LOG_WARNING, "No explicit time zone found, "
102  "guessing implicit UTC! Please consider enhancing "
103  "the guide source to provide explicit UTC or local "
104  "time instead.");
105  s_warnedOnceOnImplicitUtc = true;
106  }
107  }
108  }
109 
110  // Process the date part
111  QString tsDate = ts.left(8);
112  if (tsDate.length() == 8)
113  tmpDate = QDate::fromString(tsDate, "yyyyMMdd");
114  else if (tsDate.length() == 6)
115  tmpDate = QDate::fromString(tsDate, "yyyyMM");
116  else if (tsDate.length() == 4)
117  tmpDate = QDate::fromString(tsDate, "yyyy");
118  if (!tmpDate.isValid())
119  {
120  LOG(VB_XMLTV, LOG_ERR,
121  QString("Invalid datetime (date) in XMLTV data, ignoring: %1")
122  .arg(timestr));
123  return;
124  }
125 
126  // Process the time part (if any)
127  if (ts.length() > 8)
128  {
129  QString tsTime = ts.mid(8);
130  if (tsTime.length() == 6)
131  {
132  if (tsTime == "235960")
133  tsTime = "235959";
134  tmpTime = QTime::fromString(tsTime, "HHmmss");
135  }
136  else if (tsTime.length() == 4)
137  tmpTime = QTime::fromString(tsTime, "HHmm");
138  else if (tsTime.length() == 2)
139  tmpTime = QTime::fromString(tsTime, "HH");
140  if (!tmpTime.isValid())
141  {
142  // Time part exists, but is (somehow) invalid
143  LOG(VB_XMLTV, LOG_ERR,
144  QString("Invalid datetime (time) in XMLTV data, ignoring: %1")
145  .arg(timestr));
146  return;
147  }
148  }
149 
150  QDateTime tmpDT = QDateTime(tmpDate, tmpTime, Qt::UTC);
151  if (!tmpDT.isValid())
152  {
153  LOG(VB_XMLTV, LOG_ERR,
154  QString("Invalid datetime (combination of date/time) "
155  "in XMLTV data, ignoring: %1").arg(timestr));
156  return;
157  }
158 
159  // While this seems like a hack, it's better than what was done before
160  QString isoDateString = tmpDT.toString(Qt::ISODate);
161  if (isoDateString.endsWith('Z')) // Should always be Z, but ...
162  isoDateString.truncate(isoDateString.length()-1);
163  isoDateString += tzoffset;
164  dt = QDateTime::fromString(isoDateString, Qt::ISODate).toUTC();
165 
166  if (!dt.isValid())
167  {
168  LOG(VB_XMLTV, LOG_ERR,
169  QString("Invalid datetime (zone offset) in XMLTV data, "
170  "ignoring: %1").arg(timestr));
171  return;
172  }
173 
175 }
176 
177 static int readNextWithErrorCheck(QXmlStreamReader &xml)
178 {
179  xml.readNext();
180  if (xml.hasError())
181  {
182  LOG(VB_GENERAL, LOG_ERR, QString("Malformed XML file at line %1, %2").arg(xml.lineNumber()).arg(xml.errorString()));
183  return false;
184  }
185  return true;
186 }
187 
189  const QString& filename, ChannelInfoList *chanlist,
190  QMap<QString, QList<ProgInfo> > *proglist)
191 {
194  QFile f;
195  if (!dash_open(f, filename, QIODevice::ReadOnly))
196  {
197  LOG(VB_GENERAL, LOG_ERR,
198  QString("Error unable to open '%1' for reading.") .arg(filename));
199  return false;
200  }
201 
202  QXmlStreamReader xml(&f);
203  QUrl baseUrl;
204  QUrl sourceUrl;
205  QString aggregatedTitle;
206  QString aggregatedDesc;
207  bool haveReadTV = false;
208  QString last_channel = ""; //xmltvId of the last program element we read
209  QDateTime last_starttime; //starttime of the last program element we read
210  while (!xml.atEnd() && !xml.hasError() && (! (xml.isEndElement() && xml.name() == "tv")))
211  {
212  if (xml.readNextStartElement())
213  {
214  if (xml.name() == "tv")
215  {
216  sourceUrl = QUrl(xml.attributes().value("source-info-url").toString());
217  baseUrl = QUrl(xml.attributes().value("source-data-url").toString());
218  haveReadTV = true;
219  }
220  if (xml.name() == "channel")
221  {
222  if (!haveReadTV)
223  {
224  LOG(VB_GENERAL, LOG_ERR, QString("Malformed XML file, no <tv> element found, at line %1, %2").arg(xml.lineNumber()).arg(xml.errorString()));
225  return false;
226  }
227 
228  //get id attribute
229  QString xmltvid;
230  xmltvid = xml.attributes().value( "id").toString();
231  auto *chaninfo = new ChannelInfo;
232  chaninfo->m_xmltvId = xmltvid;
233  chaninfo->m_tvFormat = "Default";
234 
235  //readNextStartElement says it reads for the next start element WITHIN the current element; but it doesnt; so we use readNext()
236  do
237  {
238  if (!readNextWithErrorCheck(xml))
239  return false;
240  if (xml.name() == "icon")
241  {
242  if (chaninfo->m_icon.isEmpty())
243  {
244  QString path = xml.attributes().value("src").toString();
245  if (!path.isEmpty() && !path.contains("://"))
246  {
247  QString base = baseUrl.toString(QUrl::StripTrailingSlash);
248  chaninfo->m_icon = base +
249  ((path.startsWith("/")) ? path : QString("/") + path);
250  }
251  else if (!path.isEmpty())
252  {
253  QUrl url(path);
254  if (url.isValid())
255  chaninfo->m_icon = url.toString();
256  }
257  }
258  }
259  else if (xml.name() == "display-name")
260  {
261  //now get text
262  QString text;
263  text = xml.readElementText(QXmlStreamReader::SkipChildElements);
264  if (!text.isEmpty())
265  {
266  if (chaninfo->m_name.isEmpty())
267  {
268  chaninfo->m_name = text;
269  }
270  else if (chaninfo->m_callSign.isEmpty())
271  {
272  chaninfo->m_callSign = text;
273  }
274  else if (chaninfo->m_chanNum.isEmpty())
275  {
276  chaninfo->m_chanNum = text;
277  }
278  }
279  }
280  }
281  while (! (xml.isEndElement() && xml.name() == "channel"));
282  chaninfo->m_freqId = chaninfo->m_chanNum;
283  //TODO optimize this, no use to do al this parsing if xmltvid is empty; but make sure you will read until the next channel!!
284  if (!chaninfo->m_xmltvId.isEmpty())
285  chanlist->push_back(*chaninfo);
286  delete chaninfo;
287  }//channel
288  else if (xml.name() == "programme")
289  {
290  if (!haveReadTV)
291  {
292  LOG(VB_GENERAL, LOG_ERR, QString("Malformed XML file, no <tv> element found, at line %1, %2").arg(xml.lineNumber()).arg(xml.errorString()));
293  return false;
294  }
295 
296  QString programid, season, episode, totalepisodes;
297  auto *pginfo = new ProgInfo();
298 
299  QString text = xml.attributes().value("start").toString();
300  fromXMLTVDate(text, pginfo->m_starttime);
301  pginfo->m_startts = text;
302 
303  text = xml.attributes().value("stop").toString();
304  //not a mandatory attribute according to XMLTV DTD https://github.com/XMLTV/xmltv/blob/master/xmltv.dtd
305  fromXMLTVDate(text, pginfo->m_endtime);
306  pginfo->m_endts = text;
307 
308  text = xml.attributes().value("channel").toString();
309  QStringList split = text.split(" ");
310  pginfo->m_channel = split[0];
311 
312  text = xml.attributes().value("clumpidx").toString();
313  if (!text.isEmpty())
314  {
315  split = text.split('/');
316  pginfo->m_clumpidx = split[0];
317  pginfo->m_clumpmax = split[1];
318  }
319 
320  do
321  {
322  if (!readNextWithErrorCheck(xml))
323  return false;
324  if (xml.name() == "title")
325  {
326  QString text2=xml.readElementText(QXmlStreamReader::SkipChildElements);
327  if (xml.attributes().value("lang").toString() == "ja_JP")
328  {
329  pginfo->m_title = text2;
330  }
331  else if (xml.attributes().value("lang").toString() == "ja_JP@kana")
332  {
333  pginfo->m_title_pronounce = text2;
334  }
335  else if (pginfo->m_title.isEmpty())
336  {
337  pginfo->m_title = text2;
338  }
339  }
340  else if (xml.name() == "sub-title" && pginfo->m_subtitle.isEmpty())
341  {
342  pginfo->m_subtitle = xml.readElementText(QXmlStreamReader::SkipChildElements);
343  }
344  else if (xml.name() == "subtitles")
345  {
346  if (xml.attributes().value("type").toString() == "teletext")
347  pginfo->m_subtitleType |= SUB_NORMAL;
348  else if (xml.attributes().value("type").toString() == "onscreen")
349  pginfo->m_subtitleType |= SUB_ONSCREEN;
350  else if (xml.attributes().value("type").toString() == "deaf-signed")
351  pginfo->m_subtitleType |= SUB_SIGNED;
352  }
353  else if (xml.name() == "desc" && pginfo->m_description.isEmpty())
354  {
355  pginfo->m_description = xml.readElementText(QXmlStreamReader::SkipChildElements);
356  }
357  else if (xml.name() == "category")
358  {
359  const QString cat = xml.readElementText(QXmlStreamReader::SkipChildElements);
360 
362  {
363  pginfo->m_categoryType = string_to_myth_category_type(cat);
364  }
365  else if (pginfo->m_category.isEmpty())
366  {
367  pginfo->m_category = cat;
368  }
369  if ((cat.compare(QObject::tr("movie"),Qt::CaseInsensitive) == 0) || (cat.compare(QObject::tr("film"),Qt::CaseInsensitive) == 0))
370  {
371  // Hack for tv_grab_uk_rt
372  pginfo->m_categoryType = ProgramInfo::kCategoryMovie;
373  }
374  pginfo->m_genres.append(cat);
375  }
376  else if (xml.name() == "date" && (pginfo->m_airdate == 0U))
377  {
378  // Movie production year
379  QString date = xml.readElementText(QXmlStreamReader::SkipChildElements);
380  pginfo->m_airdate = date.left(4).toUInt();
381  }
382  else if (xml.name() == "star-rating")
383  {
384  QString stars;
385  float rating = 0.0;
386 
387  // Use the first rating to appear in the xml, this should be
388  // the most important one.
389  //
390  // Averaging is not a good idea here, any subsequent ratings
391  // are likely to represent that days recommended programmes
392  // which on a bad night could given to an average programme.
393  // In the case of uk_rt it's not unknown for a recommendation
394  // to be given to programmes which are 'so bad, you have to
395  // watch!'
396  //
397  // XMLTV uses zero based ratings and signals no rating by absence.
398  // A rating from 1 to 5 is encoded as 0/4 to 4/4.
399  // MythTV uses zero to signal no rating!
400  // The same rating is encoded as 0.2 to 1.0 with steps of 0.2, it
401  // is not encoded as 0.0 to 1.0 with steps of 0.25 because
402  // 0 signals no rating!
403  // See http://xmltv.cvs.sourceforge.net/viewvc/xmltv/xmltv/xmltv.dtd?revision=1.47&view=markup#l539
404  stars = "0"; //no rating
405  do
406  {
407  if (!readNextWithErrorCheck(xml))
408  return false;
409  if (xml.isStartElement())
410  {
411  if (xml.name() == "value")
412  {
413  stars=xml.readElementText(QXmlStreamReader::SkipChildElements);
414  }
415  }
416  }
417  while (! (xml.isEndElement() && xml.name() == "star-rating"));
418  if (pginfo->m_stars == 0.0F)
419  {
420  float num = stars.section('/', 0, 0).toFloat() + 1;
421  float den = stars.section('/', 1, 1).toFloat() + 1;
422  if (0.0F < den)
423  rating = num/den;
424  }
425  pginfo->m_stars = rating;
426  }
427  else if (xml.name() == "rating")
428  {
429  // again, the structure of ratings seems poorly represented
430  // in the XML. no idea what we'd do with multiple values.
431  QString rat;
432  QString rating_system = xml.attributes().value("system").toString();
433  if (rating_system == NULL)
434  rating_system = "";
435 
436  do
437  {
438  if (!readNextWithErrorCheck(xml))
439  return false;
440  if (xml.isStartElement())
441  {
442  if (xml.name() == "value")
443  {
444  rat=xml.readElementText(QXmlStreamReader::SkipChildElements);
445  }
446  }
447  }
448  while (! (xml.isEndElement() && xml.name() == "rating"));
449 
450  if (!rat.isEmpty())
451  {
453  rating.m_system = rating_system;
454  rating.m_rating = rat;
455  pginfo->m_ratings.append(rating);
456  }
457  }
458  else if (xml.name() == "previously-shown")
459  {
460  pginfo->m_previouslyshown = true;
461  QString prevdate = xml.attributes().value( "start").toString();
462  if (!prevdate.isEmpty())
463  {
464  QDateTime date;
465  fromXMLTVDate(prevdate, date);
466  pginfo->m_originalairdate = date.date();
467  }
468  }
469  else if (xml.name() == "credits")
470  {
471  do
472  {
473  if (!readNextWithErrorCheck(xml))
474  return false;
475  if (xml.isStartElement())
476  {
477  QString tagname=xml.name().toString();
478  QString text2=xml.readElementText(QXmlStreamReader::SkipChildElements);
479  pginfo->AddPerson(tagname, text2);
480  }
481  }
482  while (! (xml.isEndElement() && xml.name() == "credits"));
483  }
484  else if (xml.name() == "audio")
485  {
486  do
487  {
488  if (!readNextWithErrorCheck(xml))
489  return false;
490  if (xml.isStartElement())
491  {
492  if (xml.name() == "stereo")
493  {
494  QString text2=xml.readElementText(QXmlStreamReader::SkipChildElements);
495  if (text2 == "mono")
496  {
497  pginfo->m_audioProps |= AUD_MONO;
498  }
499  else if (text2 == "stereo")
500  {
501  pginfo->m_audioProps |= AUD_STEREO;
502  }
503  else if (text2 == "dolby" || text2 == "dolby digital")
504  {
505  pginfo->m_audioProps |= AUD_DOLBY;
506  }
507  else if (text2 == "surround")
508  {
509  pginfo->m_audioProps |= AUD_SURROUND;
510  }
511  }
512  }
513  }
514  while (! (xml.isEndElement() && xml.name() == "audio"));
515  }
516  else if (xml.name() == "video")
517  {
518  do
519  {
520  if (!readNextWithErrorCheck(xml))
521  return false;
522  if (xml.isStartElement())
523  {
524  if (xml.name() == "quality")
525  {
526  if (xml.readElementText(QXmlStreamReader::SkipChildElements) == "HDTV")
527  pginfo->m_videoProps |= VID_HDTV;
528  }
529  else if (xml.name() == "aspect")
530  {
531  if (xml.readElementText(QXmlStreamReader::SkipChildElements) == "16:9")
532  pginfo->m_videoProps |= VID_WIDESCREEN;
533  }
534  }
535  }
536  while (! (xml.isEndElement() && xml.name() == "video"));
537  }
538  else if (xml.name() == "episode-num")
539  {
540  QString system = xml.attributes().value( "system").toString();
541  if (system == "dd_progid")
542  {
543  QString episodenum(xml.readElementText(QXmlStreamReader::SkipChildElements));
544  // if this field includes a dot, strip it out
545  int idx = episodenum.indexOf('.');
546  if (idx != -1)
547  episodenum.remove(idx, 1);
548  programid = episodenum;
549  // Only EPisodes and SHows are part of a series for SD
550  if (programid.startsWith(QString("EP")) ||
551  programid.startsWith(QString("SH")))
552  pginfo->m_seriesId = QString("EP") + programid.mid(2,8);
553  }
554  else if (system == "xmltv_ns")
555  {
556  QString episodenum(xml.readElementText(QXmlStreamReader::SkipChildElements));
557  episode = episodenum.section('.',1,1);
558  totalepisodes = episode.section('/',1,1).trimmed();
559  episode = episode.section('/',0,0).trimmed();
560  season = episodenum.section('.',0,0).trimmed();
561  season = season.section('/',0,0).trimmed();
562  QString part(episodenum.section('.',2,2));
563  QString partnumber(part.section('/',0,0).trimmed());
564  QString parttotal(part.section('/',1,1).trimmed());
565  pginfo->m_categoryType = ProgramInfo::kCategorySeries;
566  if (!season.isEmpty())
567  {
568  int tmp = season.toUInt() + 1;
569  pginfo->m_season = tmp;
570  season = QString::number(tmp);
571  pginfo->m_syndicatedepisodenumber = 'S' + season;
572  }
573  if (!episode.isEmpty())
574  {
575  int tmp = episode.toUInt() + 1;
576  pginfo->m_episode = tmp;
577  episode = QString::number(tmp);
578  pginfo->m_syndicatedepisodenumber.append('E' + episode);
579  }
580  if (!totalepisodes.isEmpty())
581  {
582  pginfo->m_totalepisodes = totalepisodes.toUInt();
583  }
584  uint partno = 0;
585  if (!partnumber.isEmpty())
586  {
587  bool ok = false;
588  partno = partnumber.toUInt(&ok) + 1;
589  partno = (ok) ? partno : 0;
590  }
591  if (!parttotal.isEmpty() && partno > 0)
592  {
593  bool ok = false;
594  uint partto = parttotal.toUInt(&ok);
595  if (ok && partnumber <= parttotal)
596  {
597  pginfo->m_parttotal = partto;
598  pginfo->m_partnumber = partno;
599  }
600  }
601  }
602  else if (system == "onscreen")
603  {
604  pginfo->m_categoryType = ProgramInfo::kCategorySeries;
605  if (pginfo->m_subtitle.isEmpty())
606  {
607  pginfo->m_subtitle = xml.readElementText(QXmlStreamReader::SkipChildElements);
608  }
609  }
610  else if ((system == "themoviedb.org") && (m_movieGrabberPath.endsWith(QString("/tmdb3.py"))))
611  {
612  // text is movie/<inetref>
613  QString inetrefRaw(xml.readElementText(QXmlStreamReader::SkipChildElements));
614  if (inetrefRaw.startsWith(QString("movie/")))
615  {
616  QString inetref(QString ("tmdb3.py_") + inetrefRaw.section('/',1,1).trimmed());
617  pginfo->m_inetref = inetref;
618  }
619  }
620  else if ((system == "thetvdb.com") && (m_tvGrabberPath.endsWith(QString("/ttvdb.py"))))
621  {
622  // text is series/<inetref>
623  QString inetrefRaw(xml.readElementText(QXmlStreamReader::SkipChildElements));
624  if (inetrefRaw.startsWith(QString("series/")))
625  {
626  QString inetref(QString ("ttvdb.py_") + inetrefRaw.section('/',1,1).trimmed());
627  pginfo->m_inetref = inetref;
628  // ProgInfo does not have a collectionref, so we don't set any
629  }
630  }
631  }//episode-num
632  }
633  while (! (xml.isEndElement() && xml.name() == "programme"));
634 
635  if (pginfo->m_category.isEmpty() && pginfo->m_categoryType != ProgramInfo::kCategoryNone)
636  pginfo->m_category = myth_category_type_to_string(pginfo->m_categoryType);
637 
638  if (!pginfo->m_airdate && ProgramInfo::kCategorySeries != pginfo->m_categoryType)
639  pginfo->m_airdate = m_currentYear;
640 
641  if (programid.isEmpty())
642  {
643  //Let's build ourself a programid
644  if (ProgramInfo::kCategoryMovie == pginfo->m_categoryType)
645  programid = "MV";
646  else if (ProgramInfo::kCategorySeries == pginfo->m_categoryType)
647  programid = "EP";
648  else if (ProgramInfo::kCategorySports == pginfo->m_categoryType)
649  programid = "SP";
650  else
651  programid = "SH";
652 
653  QString seriesid = QString::number(ELFHash(pginfo->m_title.toUtf8()));
654  pginfo->m_seriesId = seriesid;
655  programid.append(seriesid);
656 
657  if (!episode.isEmpty() && !season.isEmpty())
658  {
659  /* Append unpadded episode and season number to the seriesid (to
660  maintain consistency with historical encoding), but limit the
661  season number representation to a single base-36 character to
662  ensure unique programid generation. */
663  int season_int = season.toInt();
664  if (season_int > 35)
665  {
666  // Cannot represent season as a single base-36 character, so
667  // remove the programid and fall back to normal dup matching.
668  if (ProgramInfo::kCategoryMovie != pginfo->m_categoryType)
669  programid.clear();
670  }
671  else
672  {
673  programid.append(episode);
674  programid.append(QString::number(season_int, 36));
675  if (pginfo->m_partnumber && pginfo->m_parttotal)
676  {
677  programid += QString::number(pginfo->m_partnumber);
678  programid += QString::number(pginfo->m_parttotal);
679  }
680  }
681  }
682  else
683  {
684  /* No ep/season info? Well then remove the programid and rely on
685  normal dupchecking methods instead. */
686  if (ProgramInfo::kCategoryMovie != pginfo->m_categoryType)
687  programid.clear();
688  }
689  }
690  pginfo->m_programId = programid;
691  if (!(pginfo->m_starttime.isValid()))
692  {
693  LOG(VB_GENERAL, LOG_WARNING, QString("Invalid programme (%1), " "invalid start time, " "skipping").arg(pginfo->m_title));
694  }
695  else if (pginfo->m_channel.isEmpty())
696  {
697  LOG(VB_GENERAL, LOG_WARNING, QString("Invalid programme (%1), " "missing channel, " "skipping").arg(pginfo->m_title));
698  }
699  else if (pginfo->m_startts == pginfo->m_endts)
700  {
701  LOG(VB_GENERAL, LOG_WARNING, QString("Invalid programme (%1), " "identical start and end " "times, skipping").arg(pginfo->m_title));
702  }
703  else
704  {
705  // so we have a (relatively) clean program element now, which is good enough to process or to store
706  if (pginfo->m_channel != last_channel) {
707  //we have a channel change here
708  last_channel = pginfo->m_channel;
709  last_starttime = QDateTime(QDate(1970, 1, 1), QTime(0, 0, 0)); //initialize it to a time far, far away ...
710  }
711  else {
712  //we are still on the same channel
713  if (pginfo->m_starttime >= last_starttime) {
714  last_starttime = pginfo->m_starttime;
715  }
716  else {
717  LOG(VB_GENERAL, LOG_ERR, QString("Malformed XML file, program out of order at line %1, %2").arg(xml.lineNumber()).arg(xml.errorString()));
718  return false;
719  }
720  }
721 
722  if (pginfo->m_clumpidx.isEmpty())
723  (*proglist)[pginfo->m_channel].push_back(*pginfo);
724  else
725  {
726  /* append all titles/descriptions from one clump */
727  if (pginfo->m_clumpidx.toInt() == 0)
728  {
729  aggregatedTitle.clear();
730  aggregatedDesc.clear();
731  }
732  if (!pginfo->m_title.isEmpty())
733  {
734  if (!aggregatedTitle.isEmpty())
735  aggregatedTitle.append(" | ");
736  aggregatedTitle.append(pginfo->m_title);
737  }
738  if (!pginfo->m_description.isEmpty())
739  {
740  if (!aggregatedDesc.isEmpty())
741  aggregatedDesc.append(" | ");
742  aggregatedDesc.append(pginfo->m_description);
743  }
744  if (pginfo->m_clumpidx.toInt() == pginfo->m_clumpmax.toInt() - 1)
745  {
746  pginfo->m_title = aggregatedTitle;
747  pginfo->m_description = aggregatedDesc;
748  (*proglist)[pginfo->m_channel].push_back(*pginfo);
749  }
750  }
751  }
752  delete pginfo;
753  }//if programme
754  }//if readNextStartElement
755  }//while loop
756  if (! (xml.isEndElement() && xml.name() == "tv"))
757  {
758  LOG(VB_GENERAL, LOG_ERR, QString("Malformed XML file, missing </tv> element, at line %1, %2").arg(xml.lineNumber()).arg(xml.errorString()));
759  return false;
760  }
761  //TODO add code for adding data on the run
762  f.close();
763 
764  return true;
765 }
bool dash_open(QFile &file, const QString &filename, int m, FILE *handle)
Definition: fillutil.cpp:11
unsigned int m_currentYear
Definition: xmltvparser.h:24
#define NULL
Definition: H264Parser.h:62
QDateTime fromString(const QString &dtstr)
Converts kFilename && kISODate formats to QDateTime.
Definition: mythdate.cpp:30
static guint32 * tmp
Definition: goom_core.c:35
ProgramInfo::CategoryType string_to_myth_category_type(const QString &category_type)
def rating(profile, smoonURL, gate)
Definition: scan.py:39
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
Definition: mythdate.cpp:10
QString m_tvGrabberPath
Definition: xmltvparser.h:26
unsigned int uint
Definition: compat.h:140
QString toString(const QDateTime &raw_dt, uint format)
Returns formatted string representing the time.
Definition: mythdate.cpp:101
static QString GetTelevisionGrabber()
static uint ELFHash(const QByteArray &ba)
Definition: xmltvparser.cpp:37
QString myth_category_type_to_string(ProgramInfo::CategoryType category_type)
QString m_movieGrabberPath
Definition: xmltvparser.h:25
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: mythlogging.h:41
static void fromXMLTVDate(QString &timestr, QDateTime &dt)
Definition: xmltvparser.cpp:57
Default UTC.
Definition: mythdate.h:14
QString m_xmltvId
Definition: channelinfo.h:99
Default UTC, "yyyyMMddhhmmss".
Definition: mythdate.h:15
bool parseFile(const QString &filename, ChannelInfoList *chanlist, QMap< QString, QList< ProgInfo > > *proglist)
static QString GetMovieGrabber()
static int readNextWithErrorCheck(QXmlStreamReader &xml)
vector< ChannelInfo > ChannelInfoList
Definition: channelinfo.h:133