MythTV  master
rssparse.cpp
Go to the documentation of this file.
1 #include <QFile>
2 #include <QDataStream>
3 #include <QDomDocument>
4 #include <QDomImplementation>
5 #include <QHash>
6 #include <QLocale>
7 #include <QUrl>
8 #include <QFileInfo>
9 #include <QRegExp>
10 
11 #include "rssparse.h"
12 #include "mythcontext.h"
13 #include "mythdirs.h"
14 #include "mythdate.h"
15 #include "programinfo.h" // for format_season_and_episode()
16 #include "mythsorthelper.h"
17 
18 using namespace std;
19 
20 ResultItem::ResultItem(const QString& title, const QString& sortTitle,
21  const QString& subtitle, const QString& sortSubtitle,
22  const QString& desc, const QString& URL,
23  const QString& thumbnail, const QString& mediaURL,
24  const QString& author, const QDateTime& date,
25  const QString& time, const QString& rating,
26  const off_t& filesize, const QString& player,
27  const QStringList& playerargs, const QString& download,
28  const QStringList& downloadargs, const uint& width,
29  const uint& height, const QString& language,
30  const bool& downloadable, const QStringList& countries,
31  const uint& season, const uint& episode,
32  const bool& customhtml)
33 {
34  m_title = title;
35  m_sorttitle = sortTitle;
36  m_subtitle = subtitle;
37  m_sortsubtitle = sortSubtitle;
38  m_desc = desc;
39  m_url = URL;
40  m_thumbnail = thumbnail;
41  m_mediaURL = mediaURL;
42  m_author = author;
43  if (!date.isNull())
44  m_date = date;
45  else
46  m_date = QDateTime();
47  m_time = time;
48  m_rating = rating;
49  m_filesize = filesize;
50  m_player = player;
51  m_playerargs = playerargs;
52  m_download = download;
53  m_downloadargs = downloadargs;
54  m_width = width;
55  m_height = height;
56  m_language = language;
57  m_downloadable = downloadable;
58  m_countries = countries;
59  m_season = season;
60  m_episode = episode;
61  m_customhtml = customhtml;
62 
63  ensureSortFields();
64 }
65 
67 {
68  std::shared_ptr<MythSortHelper>sh = getMythSortHelper();
69 
70  if (m_sorttitle.isEmpty() and not m_title.isEmpty())
71  m_sorttitle = sh->doTitle(m_title);
72  if (m_sortsubtitle.isEmpty() and not m_subtitle.isEmpty())
73  m_sortsubtitle = sh->doTitle(m_subtitle);
74 }
75 
76 void ResultItem::toMap(InfoMap &metadataMap)
77 {
78  metadataMap["title"] = m_title;
79  metadataMap["sorttitle"] = m_sorttitle;
80  metadataMap["subtitle"] = m_subtitle;
81  metadataMap["sortsubtitle"] = m_sortsubtitle;
82  metadataMap["description"] = m_desc;
83  metadataMap["url"] = m_url;
84  metadataMap["thumbnail"] = m_thumbnail;
85  metadataMap["mediaurl"] = m_mediaURL;
86  metadataMap["author"] = m_author;
87 
88  if (m_date.isNull())
89  metadataMap["date"] = QString();
90  else
91  metadataMap["date"] = MythDate::toString(m_date, MythDate::kDateFull);
92 
93  if (m_time.toInt() == 0)
94  metadataMap["length"] = QString();
95  else
96  {
97  QTime time(0,0,0,0);
98  int secs = m_time.toInt();
99  QTime fin = time.addSecs(secs);
100  QString format;
101  if (secs >= 3600)
102  format = "H:mm:ss";
103  else if (secs >= 600)
104  format = "mm:ss";
105  else if (secs >= 60)
106  format = "m:ss";
107  else
108  format = ":ss";
109  metadataMap["length"] = fin.toString(format);
110  }
111 
112  if (m_rating == nullptr || m_rating.isNull())
113  metadataMap["rating"] = QString();
114  else
115  metadataMap["rating"] = m_rating;
116 
117  if (m_filesize == -1)
118  metadataMap["filesize"] = QString();
119  else if (m_filesize == 0 && !m_downloadable)
120  metadataMap["filesize"] = QObject::tr("Web Only");
121  else if (m_filesize == 0 && m_downloadable)
122  metadataMap["filesize"] = QObject::tr("Downloadable");
123  else
124  metadataMap["filesize"] = QString::number(m_filesize);
125 
126  QString tmpSize;
127  tmpSize.sprintf("%0.2f ", m_filesize / 1024.0 / 1024.0);
128  tmpSize += QObject::tr("MB", "Megabytes");
129  if (m_filesize == -1)
130  metadataMap["filesize_str"] = QString();
131  else if (m_filesize == 0 && !m_downloadable)
132  metadataMap["filesize_str"] = QObject::tr("Web Only");
133  else if (m_filesize == 0 && m_downloadable)
134  metadataMap["filesize_str"] = QObject::tr("Downloadable");
135  else
136  metadataMap["filesize"] = tmpSize;
137 
138  metadataMap["player"] = m_player;
139  metadataMap["playerargs"] = m_playerargs.join(", ");
140  metadataMap["downloader"] = m_download;
141  metadataMap["downloadargs"] = m_downloadargs.join(", ");
142  if (m_width == 0)
143  metadataMap["width"] = QString();
144  else
145  metadataMap["width"] = QString::number(m_width);
146  if (m_height == 0)
147  metadataMap["height"] = QString();
148  else
149  metadataMap["height"] = QString::number(m_height);
150  if (m_width == 0 || m_height == 0)
151  metadataMap["resolution"] = QString();
152  else
153  metadataMap["resolution"] = QString("%1x%2").arg(m_width).arg(m_height);
154  metadataMap["language"] = m_language;
155  metadataMap["countries"] = m_countries.join(", ");
156 
157 
158  if (m_season > 0 || m_episode > 0)
159  {
160  metadataMap["season"] = format_season_and_episode(m_season, 1);
161  metadataMap["episode"] = format_season_and_episode(m_episode, 1);
162  metadataMap["s##e##"] = metadataMap["s00e00"] = QString("s%1e%2")
163  .arg(format_season_and_episode(m_season, 2))
164  .arg(format_season_and_episode(m_episode, 2));
165  metadataMap["##x##"] = metadataMap["00x00"] = QString("%1x%2")
166  .arg(format_season_and_episode(m_season, 1))
167  .arg(format_season_and_episode(m_episode, 2));
168  }
169  else
170  {
171  metadataMap["season"] = QString();
172  metadataMap["episode"] = QString();
173  metadataMap["s##e##"] = metadataMap["s00e00"] = QString();
174  metadataMap["##x##"] = metadataMap["00x00"] = QString();
175  }
176 }
177 
178 namespace
179 {
180  QList<QDomNode> GetDirectChildrenNS(const QDomElement& elem,
181  const QString& ns, const QString& name)
182  {
183  QList<QDomNode> result;
184  QDomNodeList unf = elem.elementsByTagNameNS(ns, name);
185  for (int i = 0, size = unf.size(); i < size; ++i)
186  if (unf.at(i).parentNode() == elem)
187  result << unf.at(i);
188  return result;
189  }
190 }
191 
193 {
195  {
196  QString m_url;
197  QString m_rating;
198  QString m_ratingScheme;
199  QString m_title;
200  QString m_description;
201  QString m_keywords;
202  QString m_copyrightUrl;
204  int m_ratingAverage {0};
205  int m_ratingCount {0};
206  int m_ratingMin {0};
207  int m_ratingMax {0};
208  int m_views {0};
209  int m_favs {0};
210  QString m_tags;
211  QList<MRSSThumbnail> m_thumbnails;
212  QList<MRSSCredit> m_credits;
213  QList<MRSSComment> m_comments;
214  QList<MRSSPeerLink> m_peerLinks;
215  QList<MRSSScene> m_scenes;
216 
217  ArbitraryLocatedData() = default;
218 
222  ArbitraryLocatedData& operator+= (const ArbitraryLocatedData& child)
223  {
224  if (!child.m_url.isEmpty())
225  m_url = child.m_url;
226  if (!child.m_rating.isEmpty())
227  m_rating = child.m_rating;
228  if (!child.m_ratingScheme.isEmpty())
229  m_ratingScheme = child.m_ratingScheme;
230  if (!child.m_title.isEmpty())
231  m_title = child.m_title;
232  if (!child.m_description.isEmpty())
233  m_description = child.m_description;
234  if (!child.m_keywords.isEmpty())
235  m_keywords = child.m_keywords;
236  if (!child.m_copyrightUrl.isEmpty())
237  m_copyrightUrl = child.m_copyrightUrl;
238  if (!child.m_copyrightText.isEmpty())
239  m_copyrightText = child.m_copyrightText;
240  if (child.m_ratingAverage != 0)
241  m_ratingAverage = child.m_ratingAverage;
242  if (child.m_ratingCount != 0)
243  m_ratingCount = child.m_ratingCount;
244  if (child.m_ratingMin != 0)
245  m_ratingMin = child.m_ratingMin;
246  if (child.m_ratingMax != 0)
247  m_ratingMax = child.m_ratingMax;
248  if (child.m_views != 0)
249  m_views = child.m_views;
250  if (child.m_favs != 0)
251  m_favs = child.m_favs;
252  if (!child.m_tags.isEmpty())
253  m_tags = child.m_tags;
254 
255  m_thumbnails += child.m_thumbnails;
256  m_credits += child.m_credits;
257  m_comments += child.m_comments;
258  m_peerLinks += child.m_peerLinks;
259  m_scenes += child.m_scenes;
260  return *this;
261  }
262  };
263 
264 
265 public:
266  MRSSParser() = default;
267 
268  QList<MRSSEntry> operator() (const QDomElement& item)
269  {
270  QList<MRSSEntry> result;
271 
272  QDomNodeList groups = item.elementsByTagNameNS(Parse::kMediaRSS,
273  "group");
274 
275  for (int i = 0; i < groups.size(); ++i)
276  result += CollectChildren(groups.at(i).toElement());
277 
278  result += CollectChildren(item);
279 
280  return result;
281  }
282 
283 private:
284 
285  static QList<MRSSEntry> CollectChildren(const QDomElement& holder)
286  {
287  QList<MRSSEntry> result;
288  QDomNodeList entries = holder.elementsByTagNameNS(Parse::kMediaRSS,
289  "content");
290 
291  for (int i = 0; i < entries.size(); ++i)
292  {
293  MRSSEntry entry;
294 
295  QDomElement en = entries.at(i).toElement();
296  ArbitraryLocatedData d = GetArbitraryLocatedDataFor(en);
297 
298  if (en.hasAttribute("url"))
299  entry.URL = en.attribute("url");
300  else
301  entry.URL = d.m_url;
302 
303  entry.Size = en.attribute("fileSize").toInt();
304  entry.Type = en.attribute("type");
305  entry.Medium = en.attribute("medium");
306  entry.IsDefault = (en.attribute("isDefault") == "true");
307  entry.Expression = en.attribute("expression");
308  if (entry.Expression.isEmpty())
309  entry.Expression = "full";
310  entry.Bitrate = en.attribute("bitrate").toInt();
311  entry.Framerate = en.attribute("framerate").toDouble();
312  entry.SamplingRate = en.attribute("samplingrate").toDouble();
313  entry.Channels = en.attribute("channels").toInt();
314  if (!en.attribute("duration").isNull())
315  entry.Duration = en.attribute("duration").toInt();
316  else
317  entry.Duration = 0;
318  if (!en.attribute("width").isNull())
319  entry.Width = en.attribute("width").toInt();
320  else
321  entry.Width = 0;
322  if (!en.attribute("height").isNull())
323  entry.Height = en.attribute("height").toInt();
324  else
325  entry.Height = 0;
326  if (!en.attribute("lang").isNull())
327  entry.Lang = en.attribute("lang");
328  else
329  entry.Lang = QString();
330 
331  if (!en.attribute("rating").isNull())
332  entry.Rating = d.m_rating;
333  else
334  entry.Rating = QString();
335  entry.RatingScheme = d.m_ratingScheme;
336  entry.Title = d.m_title;
337  entry.Description = d.m_description;
338  entry.Keywords = d.m_keywords;
339  entry.CopyrightURL = d.m_copyrightUrl;
340  entry.CopyrightText = d.m_copyrightText;
341  if (d.m_ratingAverage != 0)
342  entry.RatingAverage = d.m_ratingAverage;
343  else
344  entry.RatingAverage = 0;
345  entry.RatingCount = d.m_ratingCount;
346  entry.RatingMin = d.m_ratingMin;
347  entry.RatingMax = d.m_ratingMax;
348  entry.Views = d.m_views;
349  entry.Favs = d.m_favs;
350  entry.Tags = d.m_tags;
351  entry.Thumbnails = d.m_thumbnails;
352  entry.Credits = d.m_credits;
353  entry.Comments = d.m_comments;
354  entry.PeerLinks = d.m_peerLinks;
355  entry.Scenes = d.m_scenes;
356 
357  result << entry;
358  }
359  return result;
360  }
361 
362  static ArbitraryLocatedData GetArbitraryLocatedDataFor(const QDomElement& holder)
363  {
364  ArbitraryLocatedData result;
365 
366  QList<QDomElement> parents;
367  QDomElement parent = holder;
368  while (!parent.isNull())
369  {
370  parents.prepend(parent);
371  parent = parent.parentNode().toElement();
372  }
373 
374  Q_FOREACH(QDomElement p, parents)
375  result += CollectArbitraryLocatedData(p);
376 
377  return result;
378  }
379 
380  static QString GetURL(const QDomElement& element)
381  {
382  QList<QDomNode> elems = GetDirectChildrenNS(element, Parse::kMediaRSS,
383  "player");
384  if (elems.empty())
385  return QString();
386 
387  return elems.at(0).toElement().attribute("url");
388  }
389 
390  static QString GetTitle(const QDomElement& element)
391  {
392  QList<QDomNode> elems = GetDirectChildrenNS(element, Parse::kMediaRSS,
393  "title");
394 
395  if (elems.empty())
396  return QString();
397 
398  QDomElement telem = elems.at(0).toElement();
399  return Parse::UnescapeHTML(telem.text());
400  }
401 
402  static QString GetDescription(const QDomElement& element)
403  {
404  QList<QDomNode> elems = GetDirectChildrenNS(element, Parse::kMediaRSS,
405  "description");
406 
407  if (elems.empty())
408  return QString();
409 
410  QDomElement telem = elems.at(0).toElement();
411  return Parse::UnescapeHTML(telem.text());
412  }
413 
414  static QString GetKeywords(const QDomElement& element)
415  {
416  QList<QDomNode> elems = GetDirectChildrenNS(element, Parse::kMediaRSS,
417  "keywords");
418 
419  if (elems.empty())
420  return QString();
421 
422  QDomElement telem = elems.at(0).toElement();
423  return telem.text();
424  }
425 
426  static int GetInt(const QDomElement& elem, const QString& attrname)
427  {
428  if (elem.hasAttribute(attrname))
429  {
430  bool ok = false;
431  int result = elem.attribute(attrname).toInt(&ok);
432  if (ok)
433  return result;
434  }
435  return int();
436  }
437 
438  static QList<MRSSThumbnail> GetThumbnails(const QDomElement& element)
439  {
440  QList<MRSSThumbnail> result;
441  QList<QDomNode> thumbs = GetDirectChildrenNS(element, Parse::kMediaRSS,
442  "thumbnail");
443  for (int i = 0; i < thumbs.size(); ++i)
444  {
445  QDomElement thumbNode = thumbs.at(i).toElement();
446  int widthOpt = GetInt(thumbNode, "width");
447  int width = widthOpt ? widthOpt : 0;
448  int heightOpt = GetInt(thumbNode, "height");
449  int height = heightOpt ? heightOpt : 0;
450  MRSSThumbnail thumb =
451  {
452  thumbNode.attribute("url"),
453  width,
454  height,
455  thumbNode.attribute("time")
456  };
457  result << thumb;
458  }
459  return result;
460  }
461 
462  static QList<MRSSCredit> GetCredits(const QDomElement& element)
463  {
464  QList<MRSSCredit> result;
465  QList<QDomNode> credits = GetDirectChildrenNS(element, Parse::kMediaRSS,
466  "credit");
467 
468  for (int i = 0; i < credits.size(); ++i)
469  {
470  QDomElement creditNode = credits.at(i).toElement();
471  if (!creditNode.hasAttribute("role"))
472  continue;
473  MRSSCredit credit =
474  {
475  creditNode.attribute("role"),
476  creditNode.text()
477  };
478  result << credit;
479  }
480  return result;
481  }
482 
483  static QList<MRSSComment> GetComments(const QDomElement& element)
484  {
485  QList<MRSSComment> result;
486  QList<QDomNode> commParents = GetDirectChildrenNS(element, Parse::kMediaRSS,
487  "comments");
488 
489  if (!commParents.empty())
490  {
491  QDomNodeList comments = commParents.at(0).toElement()
492  .elementsByTagNameNS(Parse::kMediaRSS,
493  "comment");
494  for (int i = 0; i < comments.size(); ++i)
495  {
496  MRSSComment comment =
497  {
498  QObject::tr("Comments"),
499  comments.at(i).toElement().text()
500  };
501  result << comment;
502  }
503  }
504 
505  QList<QDomNode> respParents = GetDirectChildrenNS(element, Parse::kMediaRSS,
506  "responses");
507 
508  if (!respParents.empty())
509  {
510  QDomNodeList responses = respParents.at(0).toElement()
511  .elementsByTagNameNS(Parse::kMediaRSS,
512  "response");
513  for (int i = 0; i < responses.size(); ++i)
514  {
515  MRSSComment comment =
516  {
517  QObject::tr("Responses"),
518  responses.at(i).toElement().text()
519  };
520  result << comment;
521  }
522  }
523 
524  QList<QDomNode> backParents = GetDirectChildrenNS(element, Parse::kMediaRSS,
525  "backLinks");
526 
527  if (!backParents.empty())
528  {
529  QDomNodeList backlinks = backParents.at(0).toElement()
530  .elementsByTagNameNS(Parse::kMediaRSS,
531  "backLink");
532  for (int i = 0; i < backlinks.size(); ++i)
533  {
534  MRSSComment comment =
535  {
536  QObject::tr("Backlinks"),
537  backlinks.at(i).toElement().text()
538  };
539  result << comment;
540  }
541  }
542  return result;
543  }
544 
545  static QList<MRSSPeerLink> GetPeerLinks(const QDomElement& element)
546  {
547  QList<MRSSPeerLink> result;
548  QList<QDomNode> links = GetDirectChildrenNS(element, Parse::kMediaRSS,
549  "peerLink");
550 
551  for (int i = 0; i < links.size(); ++i)
552  {
553  QDomElement linkNode = links.at(i).toElement();
554  MRSSPeerLink pl =
555  {
556  linkNode.attribute("type"),
557  linkNode.attribute("href")
558  };
559  result << pl;
560  }
561  return result;
562  }
563 
564  static QList<MRSSScene> GetScenes(const QDomElement& element)
565  {
566  QList<MRSSScene> result;
567  QList<QDomNode> scenesNode = GetDirectChildrenNS(element, Parse::kMediaRSS,
568  "scenes");
569 
570  if (!scenesNode.empty())
571  {
572  QDomNodeList scenesNodes = scenesNode.at(0).toElement()
573  .elementsByTagNameNS(Parse::kMediaRSS, "scene");
574 
575  for (int i = 0; i < scenesNodes.size(); ++i)
576  {
577  QDomElement sceneNode = scenesNodes.at(i).toElement();
578  MRSSScene scene =
579  {
580  sceneNode.firstChildElement("sceneTitle").text(),
581  sceneNode.firstChildElement("sceneDescription").text(),
582  sceneNode.firstChildElement("sceneStartTime").text(),
583  sceneNode.firstChildElement("sceneEndTime").text()
584  };
585  result << scene;
586  }
587  }
588  return result;
589  }
590 
591  static ArbitraryLocatedData CollectArbitraryLocatedData(const QDomElement& element)
592  {
593 
594  QString rating;
595  QString rscheme;
596  {
597  QList<QDomNode> elems = GetDirectChildrenNS(element, Parse::kMediaRSS,
598  "rating");
599 
600  if (!elems.empty())
601  {
602  QDomElement relem = elems.at(0).toElement();
603  rating = relem.text();
604  if (relem.hasAttribute("scheme"))
605  rscheme = relem.attribute("scheme");
606  else
607  rscheme = "urn:simple";
608  }
609  }
610 
611  QString curl;
612  QString ctext;
613  {
614  QList<QDomNode> elems = GetDirectChildrenNS(element, Parse::kMediaRSS,
615  "copyright");
616 
617  if (!elems.empty())
618  {
619  QDomElement celem = elems.at(0).toElement();
620  ctext = celem.text();
621  if (celem.hasAttribute("url"))
622  curl = celem.attribute("url");
623  }
624  }
625 
626  int raverage = 0;
627  int rcount = 0;
628  int rmin = 0;
629  int rmax = 0;
630  int views = 0;
631  int favs = 0;
632  QString tags;
633  {
634  QList<QDomNode> comms = GetDirectChildrenNS(element, Parse::kMediaRSS,
635  "community");
636  if (!comms.empty())
637  {
638  QDomElement comm = comms.at(0).toElement();
639  QDomNodeList stars = comm.elementsByTagNameNS(Parse::kMediaRSS,
640  "starRating");
641  if (stars.size())
642  {
643  QDomElement ratingDom = stars.at(0).toElement();
644  raverage = GetInt(ratingDom, "average");
645  rcount = GetInt(ratingDom, "count");
646  rmin = GetInt(ratingDom, "min");
647  rmax = GetInt(ratingDom, "max");
648  }
649 
650  QDomNodeList stats = comm.elementsByTagNameNS(Parse::kMediaRSS,
651  "statistics");
652  if (stats.size())
653  {
654  QDomElement stat = stats.at(0).toElement();
655  views = GetInt(stat, "views");
656  favs = GetInt(stat, "favorites");
657  }
658 
659  QDomNodeList tagsNode = comm.elementsByTagNameNS(Parse::kMediaRSS,
660  "tags");
661  if (tagsNode.size())
662  {
663  QDomElement tag = tagsNode.at(0).toElement();
664  tags = tag.text();
665  }
666  }
667  }
668 
669  ArbitraryLocatedData result;
670  result.m_url = GetURL(element);
671  result.m_rating = rating;
672  result.m_ratingScheme = rscheme;
673  result.m_title = GetTitle(element);
674  result.m_description = GetDescription(element);
675  result.m_keywords = GetKeywords(element);
676  result.m_copyrightUrl = curl;
677  result.m_copyrightText = ctext;
678  result.m_ratingAverage = raverage;
679  result.m_ratingCount = rcount;
680  result.m_ratingMin = rmin;
681  result.m_ratingMax = rmax;
682  result.m_views = views;
683  result.m_favs = favs;
684  result.m_tags = tags;
685  result.m_thumbnails = GetThumbnails(element);
686  result.m_credits = GetCredits(element);
687  result.m_comments = GetComments(element);
688  result.m_peerLinks = GetPeerLinks(element);
689  result.m_scenes = GetScenes(element);
690 
691  return result;
692  }
693 };
694 
695 
696 //========================================================================================
697 // Search Construction, Destruction
698 //========================================================================================
699 
700 const QString Parse::kDC = "http://purl.org/dc/elements/1.1/";
701 const QString Parse::kWFW = "http://wellformedweb.org/CommentAPI/";
702 const QString Parse::kAtom = "http://www.w3.org/2005/Atom";
703 const QString Parse::kRDF = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
704 const QString Parse::kSlash = "http://purl.org/rss/1.0/modules/slash/";
705 const QString Parse::kEnc = "http://purl.oclc.org/net/rss_2.0/enc#";
706 const QString Parse::kITunes = "http://www.itunes.com/dtds/podcast-1.0.dtd";
707 const QString Parse::kGeoRSSSimple = "http://www.georss.org/georss";
708 const QString Parse::kGeoRSSW3 = "http://www.w3.org/2003/01/geo/wgs84_pos#";
709 const QString Parse::kMediaRSS = "http://search.yahoo.com/mrss/";
710 const QString Parse::kMythRSS = "http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format";
711 
712 ResultItem::resultList Parse::parseRSS(const QDomDocument& domDoc)
713 {
715 
716  QString document = domDoc.toString();
717  LOG(VB_GENERAL, LOG_DEBUG, "Will Be Parsing: " + document);
718 
719  QDomElement root = domDoc.documentElement();
720  QDomElement channel = root.firstChildElement("channel");
721  while (!channel.isNull())
722  {
723  QDomElement item = channel.firstChildElement("item");
724  while (!item.isNull())
725  {
726  vList.append(ParseItem(item));
727  item = item.nextSiblingElement("item");
728  }
729  channel = channel.nextSiblingElement("channel");
730  }
731 
732  return vList;
733 }
734 
735 ResultItem* Parse::ParseItem(const QDomElement& item) const
736 {
737  QString title("");
738  QString subtitle("");
739  QString description("");
740  QString url("");
741  QString author("");
742  QString duration("");
743  QString rating("");
744  QString thumbnail("");
745  QString mediaURL("");
746  QString player("");
747  QString language("");
748  QString download("");
749  off_t filesize = 0;
750  uint width = 0;
751  uint height = 0;
752  uint season = 0;
753  uint episode = 0;
754  QDateTime date;
755  QStringList playerargs;
756  QStringList downloadargs;
757  QStringList countries;
758  bool downloadable = true;
759  bool customhtml = false;
760 
761  // Get the title of the article/video
762  title = item.firstChildElement("title").text();
763  title = UnescapeHTML(title);
764  if (title.isEmpty())
765  title = "";
766 
767  // Get the subtitle of this item.
768  QDomNodeList subt = item.elementsByTagNameNS(kMythRSS, "subtitle");
769  if (subt.size())
770  {
771  subtitle = subt.at(0).toElement().text();
772  }
773 
774  // Parse the description of the article/video
775  QDomElement descriptiontemp = item.firstChildElement("description");
776  if (!descriptiontemp.isNull())
777  description = descriptiontemp.text();
778  if (description.isEmpty())
779  {
780  QDomNodeList nodes = item.elementsByTagNameNS(kITunes, "summary");
781  if (nodes.size())
782  description = nodes.at(0).toElement().text();
783  }
784  // Unescape and remove HTML tags from the description.
785  if (description.isEmpty())
786  description = "";
787  else
788  description = UnescapeHTML(description);
789 
790  // Get the link (web playable)
791  url = item.firstChildElement("link").text();
792 
793  // Parse the item author
794  QDomElement authortemp = item.firstChildElement("author");
795  if (!authortemp.isNull())
796  author = authortemp.text();
797  if (author.isEmpty())
798  author = GetAuthor(item);
799 
800  // Turn the RFC-822 pubdate into a QDateTime
801  date = RFC822TimeToQDateTime(item.firstChildElement("pubDate").text());
802  if (!date.isValid() || date.isNull())
803  date = GetDCDateTime(item);
804  if (!date.isValid() || date.isNull())
805  date = MythDate::current();
806 
807  // Parse the insane iTunes duration (HH:MM:SS or H:MM:SS or MM:SS or M:SS or SS)
808  QDomNodeList dur = item.elementsByTagNameNS(kITunes, "duration");
809  if (dur.size())
810  {
811  QString itunestime = dur.at(0).toElement().text();
812  QString dateformat;
813 
814  if (itunestime.count() == 8)
815  dateformat = "hh:mm:ss";
816  else if (itunestime.count() == 7)
817  dateformat = "h:mm:ss";
818  else if (itunestime.count() == 5)
819  dateformat = "mm:ss";
820  else if (itunestime.count() == 4)
821  dateformat = "m:ss";
822  else if (itunestime.count() == 2)
823  dateformat = "ss";
824  else
825  duration = "0";
826 
827  if (!dateformat.isNull())
828  {
829  QTime itime = QTime::fromString(itunestime, dateformat);
830  if (itime.isValid())
831  {
832  int seconds = itime.second() + (itime.minute() * 60) + (itime.hour() * 3600);
833  duration = QString::number(seconds);
834  }
835  }
836  }
837 
838  // Get the rating
839  QDomElement ratingtemp = item.firstChildElement("rating");
840  if (!ratingtemp.isNull())
841  rating = ratingtemp.text();
842 
843  // Get the external player binary
844  QDomElement playertemp = item.firstChildElement("player");
845  if (!playertemp.isNull())
846  player = playertemp.text();
847 
848  // Get the arguments to pass to the external player
849  QDomElement playerargstemp = item.firstChildElement("playerargs");
850  if (!playerargstemp.isNull())
851  playerargs = playerargstemp.text().split(" ");
852 
853  // Get the external downloader binary/script
854  QDomElement downloadtemp = item.firstChildElement("download");
855  if (!downloadtemp.isNull())
856  download = downloadtemp.text();
857 
858  // Get the arguments to pass to the external downloader
859  QDomElement downloadargstemp = item.firstChildElement("downloadargs");
860  if (!downloadargstemp.isNull())
861  downloadargs = downloadargstemp.text().split(" ");
862 
863  // Get the countries in which this item is playable
864  QDomNodeList cties = item.elementsByTagNameNS(kMythRSS, "country");
865  if (cties.size())
866  {
867  int i = 0;
868  while (i < cties.size())
869  {
870  countries.append(cties.at(i).toElement().text());
871  i++;
872  }
873  }
874 
875  // Get the season number of this item.
876  QDomNodeList seas = item.elementsByTagNameNS(kMythRSS, "season");
877  if (seas.size())
878  {
879  season = seas.at(0).toElement().text().toUInt();
880  }
881 
882  // Get the Episode number of this item.
883  QDomNodeList ep = item.elementsByTagNameNS(kMythRSS, "episode");
884  if (ep.size())
885  {
886  episode = ep.at(0).toElement().text().toUInt();
887  }
888 
889  // Does this grabber return custom HTML?
890  QDomNodeList html = item.elementsByTagNameNS(kMythRSS, "customhtml");
891  if (html.size())
892  {
893  QString htmlstring = html.at(0).toElement().text();
894  if (htmlstring.toLower().contains("true") || htmlstring == "1" ||
895  htmlstring.toLower().contains("yes"))
896  customhtml = true;
897  }
898 
899  QList<MRSSEntry> enclosures = GetMediaRSS(item);
900 
901  if (!enclosures.empty())
902  {
903  MRSSEntry media = enclosures.takeAt(0);
904 
905  QList<MRSSThumbnail> thumbs = media.Thumbnails;
906  if (!thumbs.empty())
907  {
908  MRSSThumbnail thumb = thumbs.takeAt(0);
909  thumbnail = thumb.URL;
910  }
911 
912  mediaURL = media.URL;
913 
914  width = media.Width;
915  height = media.Height;
916  language = media.Lang;
917 
918  if (duration.isEmpty())
919  duration = QString::number(media.Duration);
920 
921  if (filesize == 0)
922  filesize = media.Size;
923 
924  if (rating.isEmpty())
925  rating = QString::number(media.RatingAverage);
926  }
927  if (mediaURL.isEmpty())
928  {
929  QList<Enclosure> stdEnc = GetEnclosures(item);
930 
931  if (!stdEnc.empty())
932  {
933  Enclosure e = stdEnc.takeAt(0);
934 
935  mediaURL = e.URL;
936 
937  if (filesize == 0)
938  filesize = e.Length;
939  }
940  }
941 
942  if (mediaURL.isNull() || mediaURL == url)
943  downloadable = false;
944 
945  std::shared_ptr<MythSortHelper>sh = getMythSortHelper();
946  return(new ResultItem(title, sh->doTitle(title),
947  subtitle, sh->doTitle(subtitle), description,
948  url, thumbnail, mediaURL, author, date, duration,
949  rating, filesize, player, playerargs,
950  download, downloadargs, width, height,
951  language, downloadable, countries, season,
952  episode, customhtml));
953 }
954 
955 QString Parse::GetLink(const QDomElement& parent)
956 {
957  QString result;
958  QDomElement link = parent.firstChildElement("link");
959  while(!link.isNull())
960  {
961  if (!link.hasAttribute("rel") || link.attribute("rel") == "alternate")
962  {
963  if (!link.hasAttribute("href"))
964  result = link.text();
965  else
966  result = link.attribute("href");
967  break;
968  }
969  link = link.nextSiblingElement("link");
970  }
971  return result;
972 }
973 
974 QString Parse::GetAuthor(const QDomElement& parent)
975 {
976  QString result("");
977  QDomNodeList nodes = parent.elementsByTagNameNS(kITunes,
978  "author");
979  if (nodes.size())
980  {
981  result = nodes.at(0).toElement().text();
982  return result;
983  }
984 
985  nodes = parent.elementsByTagNameNS(kDC,
986  "creator");
987  if (nodes.size())
988  {
989  result = nodes.at(0).toElement().text();
990  return result;
991  }
992 
993  return result;
994 }
995 
996 QString Parse::GetCommentsRSS(const QDomElement& parent)
997 {
998  QString result;
999  QDomNodeList nodes = parent.elementsByTagNameNS(kWFW,
1000  "commentRss");
1001  if (nodes.size())
1002  result = nodes.at(0).toElement().text();
1003  return result;
1004 }
1005 
1006 QString Parse::GetCommentsLink(const QDomElement& parent)
1007 {
1008  QString result;
1009  QDomNodeList nodes = parent.elementsByTagNameNS("", "comments");
1010  if (nodes.size())
1011  result = nodes.at(0).toElement().text();
1012  return result;
1013 }
1014 
1015 QDateTime Parse::GetDCDateTime(const QDomElement& parent)
1016 {
1017  QDomNodeList dates = parent.elementsByTagNameNS(kDC, "date");
1018  if (!dates.size())
1019  return QDateTime();
1020  return FromRFC3339(dates.at(0).toElement().text());
1021 }
1022 
1023 QDateTime Parse::RFC822TimeToQDateTime(const QString& t) const
1024 {
1025  if (t.size() < 20)
1026  return QDateTime();
1027 
1028  QString time = t.simplified();
1029  short int hoursShift = 0;
1030  short int minutesShift = 0;
1031 
1032  QStringList tmp = time.split(' ');
1033  if (tmp.isEmpty())
1034  return QDateTime();
1035  if (tmp. at(0).contains(QRegExp("\\D")))
1036  tmp.removeFirst();
1037  if (tmp.size() != 5)
1038  return QDateTime();
1039  QString tmpTimezone = tmp.takeAt(tmp.size() -1);
1040  if (tmpTimezone.size() == 5)
1041  {
1042  bool ok = false;
1043  int tz = tmpTimezone.toInt(&ok);
1044  if(ok)
1045  {
1046  hoursShift = tz / 100;
1047  minutesShift = tz % 100;
1048  }
1049  }
1050  else
1051  hoursShift = m_timezoneOffsets.value(tmpTimezone, 0);
1052 
1053  if (tmp.at(0).size() == 1)
1054  tmp[0].prepend("0");
1055  tmp [1].truncate(3);
1056 
1057  time = tmp.join(" ");
1058 
1059  QDateTime result;
1060  if (tmp.at(2).size() == 4)
1061  result = QLocale::c().toDateTime(time, "dd MMM yyyy hh:mm:ss");
1062  else
1063  result = QLocale::c().toDateTime(time, "dd MMM yy hh:mm:ss");
1064  if (result.isNull() || !result.isValid())
1065  return QDateTime();
1066  result = result.addSecs(hoursShift * 3600 * (-1) + minutesShift *60 * (-1));
1067  result.setTimeSpec(Qt::UTC);
1068  return result;
1069 }
1070 
1071 QDateTime Parse::FromRFC3339(const QString& t)
1072 {
1073  if (t.size() < 19)
1074  return QDateTime();
1075  QDateTime result = MythDate::fromString(t.left(19).toUpper());
1076  QRegExp fractionalSeconds("(\\.)(\\d+)");
1077  if (fractionalSeconds.indexIn(t) > -1)
1078  {
1079  bool ok = false;
1080  int fractional = fractionalSeconds.cap(2).toInt(&ok);
1081  if (ok)
1082  {
1083  if (fractional < 100)
1084  fractional *= 10;
1085  if (fractional <10)
1086  fractional *= 100;
1087  result = result.addMSecs(fractional);
1088  }
1089  }
1090  QRegExp timeZone("(\\+|\\-)(\\d\\d)(:)(\\d\\d)$");
1091  if (timeZone.indexIn(t) > -1)
1092  {
1093  short int multiplier = -1;
1094  if (timeZone.cap(1) == "-")
1095  multiplier = 1;
1096  int hoursShift = timeZone.cap(2).toInt();
1097  int minutesShift = timeZone.cap(4).toInt();
1098  result = result.addSecs(hoursShift * 3600 * multiplier + minutesShift * 60 * multiplier);
1099  }
1100  result.setTimeSpec(Qt::UTC);
1101  return result;
1102 }
1103 
1104 QList<Enclosure> Parse::GetEnclosures(const QDomElement& entry)
1105 {
1106  QList<Enclosure> result;
1107  QDomNodeList links = entry.elementsByTagName("enclosure");
1108  for (int i = 0; i < links.size(); ++i)
1109  {
1110  QDomElement link = links.at(i).toElement();
1111 
1112  Enclosure e =
1113  {
1114  link.attribute("url"),
1115  link.attribute("type"),
1116  link.attribute("length", "-1").toLongLong(),
1117  link.attribute("hreflang")
1118  };
1119 
1120  result << e;
1121  }
1122  return result;
1123 }
1124 
1125 QList<MRSSEntry> Parse::GetMediaRSS(const QDomElement& item)
1126 {
1127  return MRSSParser() (item);
1128 }
1129 
1130 QString Parse::UnescapeHTML(const QString& escaped)
1131 {
1132  QString result = escaped;
1133  result.replace("&amp;", "&");
1134  result.replace("&lt;", "<");
1135  result.replace("&gt;", ">");
1136  result.replace("&apos;", "\'");
1137  result.replace("&rsquo;", "\'");
1138  result.replace("&#x2019;", "\'");
1139  result.replace("&quot;", "\"");
1140  result.replace("&#8230;",QChar(8230));
1141  result.replace("&#233;",QChar(233));
1142  result.replace("&mdash;", QChar(8212));
1143  result.replace("&nbsp;", " ");
1144  result.replace("&#160;", QChar(160));
1145  result.replace("&#225;", QChar(225));
1146  result.replace("&#8216;", QChar(8216));
1147  result.replace("&#8217;", QChar(8217));
1148  result.replace("&#039;", "\'");
1149  result.replace("&ndash;", QChar(8211));
1150  result.replace("&auml;", QChar(0x00e4));
1151  result.replace("&ouml;", QChar(0x00f6));
1152  result.replace("&uuml;", QChar(0x00fc));
1153  result.replace("&Auml;", QChar(0x00c4));
1154  result.replace("&Ouml;", QChar(0x00d6));
1155  result.replace("&Uuml;", QChar(0x00dc));
1156  result.replace("&szlig;", QChar(0x00df));
1157  result.replace("&euro;", "€");
1158  result.replace("&#8230;", "...");
1159  result.replace("&#x00AE;", QChar(0x00ae));
1160  result.replace("&#x201C;", QChar(0x201c));
1161  result.replace("&#x201D;", QChar(0x201d));
1162  result.replace("<p>", "\n");
1163 
1164  QRegExp stripHTML(QLatin1String("<.*>"));
1165  stripHTML.setMinimal(true);
1166  result.remove(stripHTML);
1167 
1168  return result;
1169 }
double Framerate
Definition: rssparse.h:80
QList< MRSSScene > m_scenes
Definition: rssparse.cpp:215
QList< MRSSCredit > m_credits
Definition: rssparse.cpp:212
static QList< MRSSCredit > GetCredits(const QDomElement &element)
Definition: rssparse.cpp:462
QList< MRSSThumbnail > m_thumbnails
Definition: rssparse.cpp:211
static const QString kRDF
Definition: rssparse.h:221
QList< MRSSScene > Scenes
Definition: rssparse.h:106
QString Medium
Definition: rssparse.h:76
QString Description
Definition: rssparse.h:91
QHash< QString, QString > InfoMap
Definition: mythtypes.h:15
Describes an enclosure associated with an item.
Definition: rssparse.h:29
QList< MRSSComment > m_comments
Definition: rssparse.cpp:213
static QString GetAuthor(const QDomElement &)
Definition: rssparse.cpp:974
QString URL
Definition: rssparse.h:31
string dateformat
Definition: mythburn.py:141
QString Lang
Definition: rssparse.h:86
MPUBLIC QString format_season_and_episode(int seasEp, int digits)
static const QString kGeoRSSW3
Definition: rssparse.h:226
static const char URL[]
Definition: cddb.cpp:29
qint64 Size
Definition: rssparse.h:74
static ArbitraryLocatedData GetArbitraryLocatedDataFor(const QDomElement &holder)
Definition: rssparse.cpp:362
ResultItem * ParseItem(const QDomElement &item) const
Definition: rssparse.cpp:735
int RatingAverage
Definition: rssparse.h:95
int Views
Definition: rssparse.h:99
static const QString kAtom
Definition: rssparse.h:220
static guint32 * tmp
Definition: goom_core.c:35
int RatingMax
Definition: rssparse.h:98
QString URL
Definition: rssparse.h:73
#define off_t
static QDateTime GetDCDateTime(const QDomElement &)
Definition: rssparse.cpp:1015
QList< ResultItem * > resultList
Definition: rssparse.h:114
QString Tags
Definition: rssparse.h:101
int Favs
Definition: rssparse.h:100
QString CopyrightText
Definition: rssparse.h:94
static const QString kMythRSS
Definition: rssparse.h:228
static QString GetURL(const QDomElement &element)
Definition: rssparse.cpp:380
static QList< MRSSComment > GetComments(const QDomElement &element)
Definition: rssparse.cpp:483
qint64 Length
Definition: rssparse.h:33
static ArbitraryLocatedData CollectArbitraryLocatedData(const QDomElement &element)
Definition: rssparse.cpp:591
static QString GetLink(const QDomElement &)
Definition: rssparse.cpp:955
QList< MRSSThumbnail > Thumbnails
Definition: rssparse.h:102
static const QString kSlash
Definition: rssparse.h:222
QString CopyrightURL
Definition: rssparse.h:93
int Height
Definition: rssparse.h:85
QList< MRSSCredit > Credits
Definition: rssparse.h:103
def rating(profile, smoonURL, gate)
Definition: scan.py:39
int Channels
Definition: rssparse.h:82
static const QString kDC
Definition: rssparse.h:218
int RatingCount
Definition: rssparse.h:96
QDateTime RFC822TimeToQDateTime(const QString &) const
Definition: rssparse.cpp:1023
static QList< MRSSScene > GetScenes(const QDomElement &element)
Definition: rssparse.cpp:564
int Width
Definition: rssparse.h:84
static const uint16_t * d
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
Definition: mythdate.cpp:10
Default local time.
Definition: mythdate.h:16
static const QString kMediaRSS
Definition: rssparse.h:227
static const QString kEnc
Definition: rssparse.h:223
static QDateTime FromRFC3339(const QString &)
Definition: rssparse.cpp:1071
unsigned int uint
Definition: compat.h:140
void ensureSortFields(void)
Definition: rssparse.cpp:66
QString toString(const QDateTime &raw_dt, uint format)
Returns formatted string representing the time.
Definition: mythdate.cpp:101
static QList< MRSSThumbnail > GetThumbnails(const QDomElement &element)
Definition: rssparse.cpp:438
static int GetInt(MHParameter *parm, MHEngine *engine)
Definition: Programs.cpp:132
bool IsDefault
Definition: rssparse.h:77
static QString GetCommentsRSS(const QDomElement &)
Definition: rssparse.cpp:996
QString Title
Definition: rssparse.h:90
static QString UnescapeHTML(const QString &)
Definition: rssparse.cpp:1130
QString RatingScheme
Definition: rssparse.h:89
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: mythlogging.h:41
static const QString kITunes
Definition: rssparse.h:224
static const QString kWFW
Definition: rssparse.h:219
ResultItem::resultList parseRSS(const QDomDocument &domDoc)
Definition: rssparse.cpp:712
int Bitrate
Definition: rssparse.h:79
int Duration
Definition: rssparse.h:83
ResultItem()=default
QString Keywords
Definition: rssparse.h:92
static int GetInt(const QDomElement &elem, const QString &attrname)
Definition: rssparse.cpp:426
QString Rating
Definition: rssparse.h:88
QList< MRSSPeerLink > m_peerLinks
Definition: rssparse.cpp:214
static QString GetKeywords(const QDomElement &element)
Definition: rssparse.cpp:414
QList< MRSSPeerLink > PeerLinks
Definition: rssparse.h:105
static QList< MRSSPeerLink > GetPeerLinks(const QDomElement &element)
Definition: rssparse.cpp:545
double SamplingRate
Definition: rssparse.h:81
static QString GetDescription(const QDomElement &element)
Definition: rssparse.cpp:402
QString Type
Definition: rssparse.h:75
static const QString kGeoRSSSimple
Definition: rssparse.h:225
QList< MRSSComment > Comments
Definition: rssparse.h:104
static QList< MRSSEntry > GetMediaRSS(const QDomElement &)
Definition: rssparse.cpp:1125
static QString GetTitle(const QDomElement &element)
Definition: rssparse.cpp:390
static QString GetCommentsLink(const QDomElement &)
Definition: rssparse.cpp:1006
QDateTime fromString(const QString &dtstr)
Converts kFilename && kISODate formats to QDateTime.
Definition: mythdate.cpp:30
int RatingMin
Definition: rssparse.h:97
QDateTime RFC822TimeToQDateTime(const QString &t)
static QList< Enclosure > GetEnclosures(const QDomElement &entry)
Definition: rssparse.cpp:1104
QString URL
Definition: rssparse.h:39
static QList< MRSSEntry > CollectChildren(const QDomElement &holder)
Definition: rssparse.cpp:285
std::shared_ptr< MythSortHelper > getMythSortHelper(void)
Get a pointer to the MythSortHelper singleton.
QString Expression
Definition: rssparse.h:78
void toMap(InfoMap &metadataMap)
Definition: rssparse.cpp:76