|
@@ -1,1107 +0,0 @@
|
|
|
-using System;
|
|
|
-using System.Collections.Generic;
|
|
|
-using System.Globalization;
|
|
|
-using System.Linq;
|
|
|
-using System.Text.RegularExpressions;
|
|
|
-using System.Threading;
|
|
|
-using System.Xml;
|
|
|
-using Emby.XmlTv.Entities;
|
|
|
-
|
|
|
-namespace Emby.XmlTv.Classes
|
|
|
-{
|
|
|
- // Reads an XmlTv file
|
|
|
- public class XmlTvReader
|
|
|
- {
|
|
|
- private readonly string _fileName;
|
|
|
- private readonly string _language;
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Initializes a new instance of the <see cref="XmlTvReader" /> class.
|
|
|
- /// </summary>
|
|
|
- /// <param name="fileName">Name of the file.</param>
|
|
|
- /// <param name="language">The specific language to return.</param>
|
|
|
- public XmlTvReader(string fileName, string language = null)
|
|
|
- {
|
|
|
- _fileName = fileName;
|
|
|
-
|
|
|
- // Normalize null/string.empty
|
|
|
- if (string.IsNullOrWhiteSpace(language))
|
|
|
- {
|
|
|
- language = null;
|
|
|
- }
|
|
|
- _language = language;
|
|
|
- }
|
|
|
-
|
|
|
- private static XmlReader CreateXmlTextReader(string path)
|
|
|
- {
|
|
|
- var settings = new XmlReaderSettings();
|
|
|
-
|
|
|
- // https://msdn.microsoft.com/en-us/library/system.xml.xmlreadersettings.xmlresolver(v=vs.110).aspx
|
|
|
- // Looks like we don't need this anyway?
|
|
|
- // Starting with the .NET Framework 4.5.2, this setting has a default value of null.
|
|
|
- //settings.XmlResolver = null;
|
|
|
-
|
|
|
- settings.DtdProcessing = DtdProcessing.Ignore;
|
|
|
-
|
|
|
- settings.CheckCharacters = false;
|
|
|
- settings.IgnoreProcessingInstructions = true;
|
|
|
- settings.IgnoreComments = true;
|
|
|
- //settings.ValidationType = ValidationType.None;
|
|
|
-
|
|
|
- return XmlReader.Create(path, settings);
|
|
|
- }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Gets the list of channels present in the XML
|
|
|
- /// </summary>
|
|
|
- /// <returns></returns>
|
|
|
- public IEnumerable<XmlTvChannel> GetChannels()
|
|
|
- {
|
|
|
- var list = new List<XmlTvChannel>();
|
|
|
-
|
|
|
- using (var reader = CreateXmlTextReader(_fileName))
|
|
|
- {
|
|
|
- if (reader.ReadToDescendant("tv"))
|
|
|
- {
|
|
|
- if (reader.ReadToDescendant("channel"))
|
|
|
- {
|
|
|
- do
|
|
|
- {
|
|
|
- var channel = GetChannel(reader);
|
|
|
- if (channel != null)
|
|
|
- {
|
|
|
- list.Add(channel);
|
|
|
- }
|
|
|
- }
|
|
|
- while (reader.ReadToFollowing("channel"));
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- return list;
|
|
|
- }
|
|
|
-
|
|
|
- private XmlTvChannel GetChannel(XmlReader reader)
|
|
|
- {
|
|
|
- var id = reader.GetAttribute("id");
|
|
|
-
|
|
|
- if (string.IsNullOrEmpty(id))
|
|
|
- {
|
|
|
- // LogError("No id found for channel row");
|
|
|
- // Log.Error(" channel#{0} doesnt contain an id", iChannel);
|
|
|
- return null;
|
|
|
- }
|
|
|
-
|
|
|
- var result = new XmlTvChannel() { Id = id };
|
|
|
-
|
|
|
- using (var xmlChannel = reader.ReadSubtree())
|
|
|
- {
|
|
|
- xmlChannel.MoveToContent();
|
|
|
- xmlChannel.Read();
|
|
|
-
|
|
|
- // Read out the data for each node and process individually
|
|
|
- while (!xmlChannel.EOF && xmlChannel.ReadState == ReadState.Interactive)
|
|
|
- {
|
|
|
- if (xmlChannel.NodeType == XmlNodeType.Element)
|
|
|
- {
|
|
|
- switch (xmlChannel.Name)
|
|
|
- {
|
|
|
- case "display-name":
|
|
|
- ProcessNode(xmlChannel, s => result.DisplayName = s, _language, s => SetChannelNumber(result, s));
|
|
|
- break;
|
|
|
- case "url":
|
|
|
- result.Url = xmlChannel.ReadElementContentAsString();
|
|
|
- break;
|
|
|
- case "icon":
|
|
|
- result.Icon = ProcessIconNode(xmlChannel);
|
|
|
- xmlChannel.Skip();
|
|
|
- break;
|
|
|
- default:
|
|
|
- xmlChannel.Skip(); // unknown, skip entire node
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- xmlChannel.Read();
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if (string.IsNullOrEmpty(result.DisplayName))
|
|
|
- {
|
|
|
- // LogError("No display-name found for channel {0}", id);
|
|
|
- return null;
|
|
|
- }
|
|
|
-
|
|
|
- return result;
|
|
|
- }
|
|
|
-
|
|
|
- private void SetChannelNumber(XmlTvChannel channel, string value)
|
|
|
- {
|
|
|
- value = value.Replace("-", ".");
|
|
|
- if (double.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var number))
|
|
|
- {
|
|
|
- channel.Number = value;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Gets the programmes for a specified channel
|
|
|
- /// </summary>
|
|
|
- /// <param name="channelId">The channel id.</param>
|
|
|
- /// <param name="startDateUtc">The UTC start date.</param>
|
|
|
- /// <param name="endDateUtc">The UTC end date.</param>
|
|
|
- /// <param name="cancellationToken">A cancellation token.</param>
|
|
|
- /// <returns></returns>
|
|
|
- public IEnumerable<XmlTvProgram> GetProgrammes(
|
|
|
- string channelId,
|
|
|
- DateTimeOffset startDateUtc,
|
|
|
- DateTimeOffset endDateUtc,
|
|
|
- CancellationToken cancellationToken)
|
|
|
- {
|
|
|
- var list = new List<XmlTvProgram>();
|
|
|
-
|
|
|
- using (var reader = CreateXmlTextReader(_fileName))
|
|
|
- {
|
|
|
- if (reader.ReadToDescendant("tv"))
|
|
|
- {
|
|
|
- if (reader.ReadToDescendant("programme"))
|
|
|
- {
|
|
|
- do
|
|
|
- {
|
|
|
- if (cancellationToken.IsCancellationRequested)
|
|
|
- {
|
|
|
- continue; // Break out
|
|
|
- }
|
|
|
-
|
|
|
- var programme = GetProgramme(reader, channelId, startDateUtc, endDateUtc);
|
|
|
- if (programme != null)
|
|
|
- {
|
|
|
- list.Add(programme);
|
|
|
- }
|
|
|
- }
|
|
|
- while (reader.ReadToFollowing("programme"));
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- return list;
|
|
|
- }
|
|
|
-
|
|
|
- public XmlTvProgram GetProgramme(XmlReader reader, string channelId, DateTimeOffset startDateUtc, DateTimeOffset endDateUtc)
|
|
|
- {
|
|
|
- var result = new XmlTvProgram();
|
|
|
-
|
|
|
- PopulateHeader(reader, result);
|
|
|
-
|
|
|
- using (var xmlProg = reader.ReadSubtree())
|
|
|
- {
|
|
|
- // First up, validate that this is the correct channel, and programme is within the time we are expecting
|
|
|
- if (!string.Equals(result.ChannelId, channelId, StringComparison.OrdinalIgnoreCase))
|
|
|
- {
|
|
|
- return null;
|
|
|
- }
|
|
|
-
|
|
|
- if (result.EndDate < startDateUtc || result.StartDate >= endDateUtc)
|
|
|
- {
|
|
|
- return null;
|
|
|
- }
|
|
|
-
|
|
|
- xmlProg.MoveToContent();
|
|
|
- xmlProg.Read();
|
|
|
-
|
|
|
- // Loop through each element
|
|
|
- while (!xmlProg.EOF && xmlProg.ReadState == ReadState.Interactive)
|
|
|
- {
|
|
|
- if (xmlProg.NodeType == XmlNodeType.Element)
|
|
|
- {
|
|
|
- switch (xmlProg.Name)
|
|
|
- {
|
|
|
- case "title":
|
|
|
- ProcessTitleNode(xmlProg, result);
|
|
|
- break;
|
|
|
- case "category":
|
|
|
- ProcessCategory(xmlProg, result);
|
|
|
- break;
|
|
|
- case "country":
|
|
|
- ProcessCountry(xmlProg, result);
|
|
|
- break;
|
|
|
- case "desc":
|
|
|
- ProcessDescription(xmlProg, result);
|
|
|
- break;
|
|
|
- case "sub-title":
|
|
|
- ProcessSubTitle(xmlProg, result);
|
|
|
- break;
|
|
|
- case "new":
|
|
|
- ProcessNew(xmlProg, result);
|
|
|
- break;
|
|
|
- case "previously-shown":
|
|
|
- ProcessPreviouslyShown(xmlProg, result);
|
|
|
- break;
|
|
|
- case "quality":
|
|
|
- ProcessQuality(xmlProg, result);
|
|
|
- break;
|
|
|
- case "episode-num":
|
|
|
- ProcessEpisodeNum(xmlProg, result);
|
|
|
- break;
|
|
|
- case "date": // Copyright date
|
|
|
- ProcessCopyrightDate(xmlProg, result);
|
|
|
- break;
|
|
|
- case "star-rating": // Community Rating
|
|
|
- ProcessStarRating(xmlProg, result);
|
|
|
- break;
|
|
|
- case "rating": // Certification Rating
|
|
|
- ProcessRating(xmlProg, result);
|
|
|
- break;
|
|
|
- case "credits":
|
|
|
- if (xmlProg.IsEmptyElement)
|
|
|
- {
|
|
|
- xmlProg.Skip();
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- using (var subtree = xmlProg.ReadSubtree())
|
|
|
- {
|
|
|
- ProcessCredits(subtree, result);
|
|
|
- }
|
|
|
- }
|
|
|
- break;
|
|
|
- case "icon":
|
|
|
- result.Icon = ProcessIconNode(xmlProg);
|
|
|
- xmlProg.Skip();
|
|
|
- break;
|
|
|
- case "premiere":
|
|
|
- result.Premiere = new XmlTvPremiere();
|
|
|
- // This was causing data after the premiere node to not be read. Reactivate this and debug if the premiere details are ever needed.
|
|
|
- //ProcessPremiereNode(xmlProg, result);
|
|
|
- xmlProg.Skip();
|
|
|
- break;
|
|
|
- default:
|
|
|
- // unknown, skip entire node
|
|
|
- xmlProg.Skip();
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- xmlProg.Read();
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- return result;
|
|
|
- }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Gets the list of supported languages in the XML
|
|
|
- /// </summary>
|
|
|
- /// <returns></returns>
|
|
|
- public List<XmlTvLanguage> GetLanguages(CancellationToken cancellationToken)
|
|
|
- {
|
|
|
- var results = new Dictionary<string, int>();
|
|
|
-
|
|
|
- //Loop through and parse out all elements and then lang= attributes
|
|
|
- //logger.LogInformation("Loading file {0}", _fileName);
|
|
|
- using (var reader = CreateXmlTextReader(_fileName))
|
|
|
- {
|
|
|
- while (reader.Read())
|
|
|
- {
|
|
|
- if (cancellationToken.IsCancellationRequested)
|
|
|
- {
|
|
|
- continue; // Break out
|
|
|
- }
|
|
|
-
|
|
|
- if (reader.NodeType == XmlNodeType.Element)
|
|
|
- {
|
|
|
- var language = reader.GetAttribute("lang");
|
|
|
- if (!string.IsNullOrEmpty(language))
|
|
|
- {
|
|
|
- if (!results.ContainsKey(language))
|
|
|
- {
|
|
|
- results[language] = 0;
|
|
|
- }
|
|
|
- results[language]++;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- return
|
|
|
- results.Keys.Select(k => new XmlTvLanguage() { Name = k, Relevance = results[k] })
|
|
|
- .OrderByDescending(l => l.Relevance)
|
|
|
- .ToList();
|
|
|
- }
|
|
|
-
|
|
|
- private void ProcessCopyrightDate(XmlReader xmlProg, XmlTvProgram result)
|
|
|
- {
|
|
|
- var startValue = xmlProg.ReadElementContentAsString();
|
|
|
- if (string.IsNullOrEmpty(startValue))
|
|
|
- {
|
|
|
- // Log.Error(" programme#{0} doesnt contain a start date", iChannel);
|
|
|
- result.CopyrightDate = null;
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- var copyrightDate = ParseDate(startValue);
|
|
|
- if (copyrightDate != null)
|
|
|
- {
|
|
|
- result.CopyrightDate = copyrightDate;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- public void ProcessCredits(XmlReader creditsXml, XmlTvProgram result)
|
|
|
- {
|
|
|
- creditsXml.MoveToContent();
|
|
|
- creditsXml.Read();
|
|
|
-
|
|
|
- // Loop through each element
|
|
|
- while (!creditsXml.EOF && creditsXml.ReadState == ReadState.Interactive)
|
|
|
- {
|
|
|
- if (creditsXml.NodeType == XmlNodeType.Element)
|
|
|
- {
|
|
|
- XmlTvCredit credit = null;
|
|
|
- switch (creditsXml.Name)
|
|
|
- {
|
|
|
- case "director":
|
|
|
- credit = new XmlTvCredit() { Type = XmlTvCreditType.Director };
|
|
|
- break;
|
|
|
- case "actor":
|
|
|
- credit = new XmlTvCredit() { Type = XmlTvCreditType.Actor };
|
|
|
- break;
|
|
|
- case "writer":
|
|
|
- credit = new XmlTvCredit() { Type = XmlTvCreditType.Writer };
|
|
|
- break;
|
|
|
- case "adapter":
|
|
|
- credit = new XmlTvCredit() { Type = XmlTvCreditType.Adapter };
|
|
|
- break;
|
|
|
- case "producer":
|
|
|
- credit = new XmlTvCredit() { Type = XmlTvCreditType.Producer };
|
|
|
- break;
|
|
|
- case "composer":
|
|
|
- credit = new XmlTvCredit() { Type = XmlTvCreditType.Composer };
|
|
|
- break;
|
|
|
- case "editor":
|
|
|
- credit = new XmlTvCredit() { Type = XmlTvCreditType.Editor };
|
|
|
- break;
|
|
|
- case "presenter":
|
|
|
- credit = new XmlTvCredit() { Type = XmlTvCreditType.Presenter };
|
|
|
- break;
|
|
|
- case "commentator":
|
|
|
- credit = new XmlTvCredit() { Type = XmlTvCreditType.Commentator };
|
|
|
- break;
|
|
|
- case "guest":
|
|
|
- credit = new XmlTvCredit() { Type = XmlTvCreditType.Guest };
|
|
|
- break;
|
|
|
- }
|
|
|
-
|
|
|
- if (credit != null)
|
|
|
- {
|
|
|
- credit.Name = creditsXml.ReadElementContentAsString();
|
|
|
- result.Credits.Add(credit);
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- creditsXml.Skip();
|
|
|
- }
|
|
|
- }
|
|
|
- else
|
|
|
- creditsXml.Read();
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- public void ProcessStarRating(XmlReader reader, XmlTvProgram result)
|
|
|
- {
|
|
|
- /*
|
|
|
- <star-rating>
|
|
|
- <value>3/3</value>
|
|
|
- </star-rating>
|
|
|
- */
|
|
|
-
|
|
|
- reader.ReadToDescendant("value");
|
|
|
- if (reader.Name == "value")
|
|
|
- {
|
|
|
- var textValue = reader.ReadElementContentAsString();
|
|
|
- if (textValue.Contains("/"))
|
|
|
- {
|
|
|
- var components = textValue.Split('/');
|
|
|
- if (float.TryParse(components[0], out var value))
|
|
|
- {
|
|
|
- result.StarRating = value;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- reader.Skip();
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- public void ProcessRating(XmlReader reader, XmlTvProgram result)
|
|
|
- {
|
|
|
- /*
|
|
|
- <rating system="MPAA">
|
|
|
- <value>TV-G</value>
|
|
|
- </rating>
|
|
|
- */
|
|
|
-
|
|
|
- var system = reader.GetAttribute("system");
|
|
|
-
|
|
|
- reader.ReadToDescendant("value");
|
|
|
- if (reader.Name == "value")
|
|
|
- {
|
|
|
- result.Rating = new XmlTvRating()
|
|
|
- {
|
|
|
- System = system,
|
|
|
- Value = reader.ReadElementContentAsString()
|
|
|
- };
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- reader.Skip();
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- public void ProcessEpisodeNum(XmlReader reader, XmlTvProgram result)
|
|
|
- {
|
|
|
- /*
|
|
|
- <episode-num system="dd_progid">EP00003026.0666</episode-num>
|
|
|
- <episode-num system="onscreen">2706</episode-num>
|
|
|
- <episode-num system="xmltv_ns">.26/0.</episode-num>
|
|
|
- */
|
|
|
-
|
|
|
- var episodeSystem = reader.GetAttribute("system");
|
|
|
- switch (episodeSystem)
|
|
|
- {
|
|
|
- case "dd_progid":
|
|
|
- ParseEpisodeDataForProgramId(reader, result);
|
|
|
- break;
|
|
|
- case "icetv":
|
|
|
- result.ProviderIds["icetv"] = reader.ReadElementContentAsString();
|
|
|
- break;
|
|
|
- case "xmltv_ns":
|
|
|
- ParseEpisodeDataForXmlTvNs(reader, result);
|
|
|
- break;
|
|
|
- case "onscreen":
|
|
|
- ParseEpisodeDataForOnScreen(reader, result);
|
|
|
- break;
|
|
|
- case "thetvdb.com":
|
|
|
- ParseTvdbSystem(reader, result);
|
|
|
- break;
|
|
|
- case "imdb.com":
|
|
|
- ParseImdbSystem(reader, result);
|
|
|
- break;
|
|
|
- case "themoviedb.org":
|
|
|
- ParseMovieDbSystem(reader, result);
|
|
|
- break;
|
|
|
- case "SxxExx":
|
|
|
- ParseSxxExxSystem(reader, result);
|
|
|
- break;
|
|
|
- default: // Handles empty string and nulls
|
|
|
- reader.Skip();
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- public void ParseSxxExxSystem(XmlReader reader, XmlTvProgram result)
|
|
|
- {
|
|
|
- // <episode-num system="SxxExx">S012E32</episode-num>
|
|
|
-
|
|
|
- var value = reader.ReadElementContentAsString();
|
|
|
- var res = Regex.Match(value, "s([0-9]+)e([0-9]+)", RegexOptions.IgnoreCase);
|
|
|
-
|
|
|
- if (res.Success)
|
|
|
- {
|
|
|
- int parsedInt;
|
|
|
-
|
|
|
- if (int.TryParse(res.Groups[1].Value, out parsedInt))
|
|
|
- {
|
|
|
- result.Episode.Series = parsedInt;
|
|
|
- }
|
|
|
-
|
|
|
- if (int.TryParse(res.Groups[2].Value, out parsedInt))
|
|
|
- {
|
|
|
- result.Episode.Episode = parsedInt;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- public void ParseMovieDbSystem(XmlReader reader, XmlTvProgram result)
|
|
|
- {
|
|
|
- // <episode-num system="thetvdb.com">series/248841</episode-num>
|
|
|
- // <episode-num system="thetvdb.com">episode/4749206</episode-num>
|
|
|
-
|
|
|
- var value = reader.ReadElementContentAsString();
|
|
|
- var parts = value.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
|
|
|
-
|
|
|
- if (string.Equals(parts[0], "series", StringComparison.OrdinalIgnoreCase))
|
|
|
- {
|
|
|
- result.SeriesProviderIds["tmdb"] = parts[1];
|
|
|
- }
|
|
|
-
|
|
|
- else if (parts.Length == 1 || string.Equals(parts[0], "episode", StringComparison.OrdinalIgnoreCase))
|
|
|
- {
|
|
|
- result.ProviderIds["tmdb"] = parts.Last();
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- public void ParseImdbSystem(XmlReader reader, XmlTvProgram result)
|
|
|
- {
|
|
|
- // <episode-num system="imdb.com">series/tt1837576</episode-num>
|
|
|
- // <episode-num system="imdb.com">episode/tt3288596</episode-num>
|
|
|
-
|
|
|
- var value = reader.ReadElementContentAsString();
|
|
|
- if (string.IsNullOrWhiteSpace(value))
|
|
|
- {
|
|
|
- return;
|
|
|
- }
|
|
|
- var parts = value.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
|
|
|
-
|
|
|
- if (parts.Length != 2)
|
|
|
- {
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- if (string.Equals(parts[0], "series", StringComparison.OrdinalIgnoreCase))
|
|
|
- {
|
|
|
- result.SeriesProviderIds["imdb"] = parts[1];
|
|
|
- }
|
|
|
-
|
|
|
- else if (string.Equals(parts[0], "episode", StringComparison.OrdinalIgnoreCase))
|
|
|
- {
|
|
|
- result.ProviderIds["imdb"] = parts[1];
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- public void ParseTvdbSystem(XmlReader reader, XmlTvProgram result)
|
|
|
- {
|
|
|
- // <episode-num system="thetvdb.com">series/248841</episode-num>
|
|
|
- // <episode-num system="thetvdb.com">episode/4749206</episode-num>
|
|
|
-
|
|
|
- var value = reader.ReadElementContentAsString();
|
|
|
- var parts = value.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
|
|
|
-
|
|
|
- if (parts.Length != 2)
|
|
|
- {
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- if (string.Equals(parts[0], "series", StringComparison.OrdinalIgnoreCase))
|
|
|
- {
|
|
|
- result.SeriesProviderIds["tvdb"] = parts[1];
|
|
|
- }
|
|
|
-
|
|
|
- else if (string.Equals(parts[0], "episode", StringComparison.OrdinalIgnoreCase))
|
|
|
- {
|
|
|
- result.ProviderIds["tvdb"] = parts[1];
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- public void ParseEpisodeDataForOnScreen(XmlReader reader, XmlTvProgram result)
|
|
|
- {
|
|
|
- //// example: 'Episode #FFEE'
|
|
|
- //serEpNum = ConvertHTMLToAnsi(nodeEpisodeNum);
|
|
|
- //int num1 = serEpNum.IndexOf("#", 0);
|
|
|
- //if (num1 < 0) num1 = 0;
|
|
|
- //episodeNum = CorrectEpisodeNum(serEpNum.Substring(num1, serEpNum.Length - num1), 0);
|
|
|
-
|
|
|
- var value = reader.ReadElementContentAsString();
|
|
|
- // value = HttpUtility.HtmlDecode(value);
|
|
|
- value = value.Replace(" ", "");
|
|
|
-
|
|
|
- var hashIndex = value.IndexOf("#", StringComparison.Ordinal);
|
|
|
- if (hashIndex > -1)
|
|
|
- {
|
|
|
- // Take everything from the hash to the end.
|
|
|
- //TODO: This could be textual - how do we populate an Int32
|
|
|
- // result.EpisodeNumber
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- public void ParseEpisodeDataForProgramId(XmlReader reader, XmlTvProgram result)
|
|
|
- {
|
|
|
- var value = reader.ReadElementContentAsString();
|
|
|
- if (!string.IsNullOrWhiteSpace(value))
|
|
|
- {
|
|
|
- result.ProgramId = value;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- public void ParseEpisodeDataForXmlTvNs(XmlReader reader, XmlTvProgram result)
|
|
|
- {
|
|
|
- var value = reader.ReadElementContentAsString();
|
|
|
-
|
|
|
- value = value.Replace(" ", "");
|
|
|
-
|
|
|
- // Episode details
|
|
|
- var components = value.Split(new[] { "." }, StringSplitOptions.None);
|
|
|
-
|
|
|
- int parsedInt;
|
|
|
-
|
|
|
- if (!string.IsNullOrEmpty(components[0]))
|
|
|
- {
|
|
|
- // Handle either "5/12" or "5"
|
|
|
- var seriesComponents = components[0].Split(new[] { "/" }, StringSplitOptions.None);
|
|
|
-
|
|
|
- // handle the zero basing!
|
|
|
- if (int.TryParse(seriesComponents[0], out parsedInt))
|
|
|
- {
|
|
|
- result.Episode.Series = parsedInt + 1;
|
|
|
- if (seriesComponents.Length == 2)
|
|
|
- {
|
|
|
- if (int.TryParse(seriesComponents[1], out parsedInt))
|
|
|
- {
|
|
|
- result.Episode.SeriesCount = parsedInt;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if (components.Length >= 2)
|
|
|
- {
|
|
|
- if (!string.IsNullOrEmpty(components[1]))
|
|
|
- {
|
|
|
- // Handle either "5/12" or "5"
|
|
|
- var episodeComponents = components[1].Split(new[] { "/" }, StringSplitOptions.None);
|
|
|
-
|
|
|
- // handle the zero basing!
|
|
|
- if (int.TryParse(episodeComponents[0], out parsedInt))
|
|
|
- {
|
|
|
- result.Episode.Episode = parsedInt + 1;
|
|
|
- if (episodeComponents.Count() == 2)
|
|
|
- {
|
|
|
- if (int.TryParse(episodeComponents[1], out parsedInt))
|
|
|
- {
|
|
|
- result.Episode.EpisodeCount = parsedInt;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if (components.Length >= 3)
|
|
|
- {
|
|
|
- if (!string.IsNullOrEmpty(components[2]))
|
|
|
- {
|
|
|
- // Handle either "5/12" or "5"
|
|
|
- var partComponents = components[2].Split(new[] { "/" }, StringSplitOptions.None);
|
|
|
-
|
|
|
- // handle the zero basing!
|
|
|
- if (int.TryParse(partComponents[0], out parsedInt))
|
|
|
- {
|
|
|
- result.Episode.Part = parsedInt + 1;
|
|
|
- if (partComponents.Count() == 2)
|
|
|
- {
|
|
|
- if (int.TryParse(partComponents[1], out parsedInt))
|
|
|
- {
|
|
|
- result.Episode.PartCount = parsedInt;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- public void ProcessQuality(XmlReader reader, XmlTvProgram result)
|
|
|
- {
|
|
|
- result.Quality = reader.ReadElementContentAsString();
|
|
|
- }
|
|
|
-
|
|
|
- public void ProcessPreviouslyShown(XmlReader reader, XmlTvProgram result)
|
|
|
- {
|
|
|
- // <previously-shown start="20070708000000" />
|
|
|
- var value = reader.GetAttribute("start");
|
|
|
- if (!string.IsNullOrEmpty(value))
|
|
|
- {
|
|
|
- // TODO: this may not be correct = validate it
|
|
|
- result.PreviouslyShown = ParseDate(value);
|
|
|
- if (result.PreviouslyShown != result.StartDate)
|
|
|
- {
|
|
|
- result.IsPreviouslyShown = true;
|
|
|
- }
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- result.IsPreviouslyShown = true;
|
|
|
- }
|
|
|
-
|
|
|
- reader.Skip(); // Move on
|
|
|
- }
|
|
|
-
|
|
|
- public void ProcessNew(XmlReader reader, XmlTvProgram result)
|
|
|
- {
|
|
|
- result.IsNew = true;
|
|
|
- reader.Skip(); // Move on
|
|
|
- }
|
|
|
-
|
|
|
- public void ProcessCategory(XmlReader reader, XmlTvProgram result)
|
|
|
- {
|
|
|
- /*
|
|
|
- <category lang="en">News</category>
|
|
|
- */
|
|
|
-
|
|
|
- result.Categories = result.Categories ?? new List<string>();
|
|
|
- ProcessMultipleNodes(reader, s => result.Categories.Add(s), _language);
|
|
|
-
|
|
|
- //result.Categories.Add(reader.ReadElementContentAsString());
|
|
|
- }
|
|
|
- public void ProcessCountry(XmlReader reader, XmlTvProgram result)
|
|
|
- {
|
|
|
- /*
|
|
|
- <country>Canadá</country>
|
|
|
- <country>EE.UU</country>
|
|
|
- */
|
|
|
-
|
|
|
- result.Countries = result.Countries ?? new List<string>();
|
|
|
- ProcessNode(reader, s => result.Countries.Add(s), _language);
|
|
|
- }
|
|
|
-
|
|
|
- public void ProcessSubTitle(XmlReader reader, XmlTvProgram result)
|
|
|
- {
|
|
|
- /*
|
|
|
- <sub-title lang="en">Gino's Italian Escape - Islands in the Sun: Southern Sardinia Celebrate the Sea</sub-title>
|
|
|
- <sub-title lang="en">8782</sub-title>
|
|
|
- */
|
|
|
- ProcessNode(reader, s => result.Episode.Title = s, _language);
|
|
|
- }
|
|
|
-
|
|
|
- public void ProcessDescription(XmlReader reader, XmlTvProgram result)
|
|
|
- {
|
|
|
- ProcessNode(reader, s => result.Description = s, _language);
|
|
|
- }
|
|
|
-
|
|
|
- public void ProcessTitleNode(XmlReader reader, XmlTvProgram result)
|
|
|
- {
|
|
|
- // <title lang="en">Gino's Italian Escape</title>
|
|
|
- ProcessNode(reader, s => result.Title = s, _language);
|
|
|
- }
|
|
|
-
|
|
|
- public void ProcessPremiereNode(XmlReader reader, XmlTvProgram result)
|
|
|
- {
|
|
|
- // <title lang="en">Gino's Italian Escape</title>
|
|
|
- ProcessNode(reader,
|
|
|
- s =>
|
|
|
- {
|
|
|
- if (result.Premiere == null)
|
|
|
- {
|
|
|
- result.Premiere = new XmlTvPremiere() { Details = s };
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- result.Premiere.Details = s;
|
|
|
- }
|
|
|
- }, _language);
|
|
|
- }
|
|
|
-
|
|
|
- public XmlTvIcon ProcessIconNode(XmlReader reader)
|
|
|
- {
|
|
|
- var result = new XmlTvIcon();
|
|
|
- var isPopulated = false;
|
|
|
-
|
|
|
- var source = reader.GetAttribute("src");
|
|
|
- if (!string.IsNullOrEmpty(source))
|
|
|
- {
|
|
|
- result.Source = source;
|
|
|
- isPopulated = true;
|
|
|
- }
|
|
|
-
|
|
|
- var widthString = reader.GetAttribute("width");
|
|
|
- var width = 0;
|
|
|
- if (!string.IsNullOrEmpty(widthString) && int.TryParse(widthString, out width))
|
|
|
- {
|
|
|
- result.Width = width;
|
|
|
- isPopulated = true;
|
|
|
- }
|
|
|
-
|
|
|
- var heightString = reader.GetAttribute("height");
|
|
|
- var height = 0;
|
|
|
- if (!string.IsNullOrEmpty(heightString) && int.TryParse(heightString, out height))
|
|
|
- {
|
|
|
- result.Height = height;
|
|
|
- isPopulated = true;
|
|
|
- }
|
|
|
-
|
|
|
- return isPopulated ? result : null;
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- //public void ProcessNodeWithLanguage(XmlReader reader, Action<string> setter)
|
|
|
- //{
|
|
|
- // var currentElementName = reader.Name;
|
|
|
- // var result = string.Empty;
|
|
|
- // var resultCandidate = reader.ReadElementContentAsString();
|
|
|
- // var exactMatchFound = false;
|
|
|
-
|
|
|
- // while (reader.Name == currentElementName)
|
|
|
- // {
|
|
|
- // var language = reader.GetAttribute("lang");
|
|
|
- // resultCandidate = reader.ReadElementContentAsString();
|
|
|
-
|
|
|
- // if (language == _language && !exactMatchFound)
|
|
|
- // {
|
|
|
- // result = resultCandidate;
|
|
|
- // }
|
|
|
-
|
|
|
- // reader.Skip();
|
|
|
- // }
|
|
|
-
|
|
|
- // result = String.IsNullOrEmpty(result) ? resultCandidate : result;
|
|
|
- // setter(result);
|
|
|
- //}
|
|
|
-
|
|
|
- public void ProcessNode(XmlReader reader, Action<string> setter, string languageRequired = null, Action<string> allOccurrencesSetter = null)
|
|
|
- {
|
|
|
- /* <title lang="es">Homes Under the Hammer - Spanish</title>
|
|
|
- * <title lang="es">Homes Under the Hammer - Spanish 2</title>
|
|
|
- * <title lang="en">Homes Under the Hammer - English</title>
|
|
|
- * <title lang="en">Homes Under the Hammer - English 2</title>
|
|
|
- * <title lang="">Homes Under the Hammer - Empty Language</title>
|
|
|
- * <title lang="">Homes Under the Hammer - Empty Language 2</title>
|
|
|
- * <title>Homes Under the Hammer - No Language</title>
|
|
|
- * <title>Homes Under the Hammer - No Language 2</title>
|
|
|
- */
|
|
|
-
|
|
|
- /* Expected Behaviour:
|
|
|
- * - Language = Null Homes Under the Hammer - No Language
|
|
|
- * - Language = "" Homes Under the Hammer - No Language
|
|
|
- * - Language = es Homes Under the Hammer - Spanish
|
|
|
- * - Language = en Homes Under the Hammer - English
|
|
|
- */
|
|
|
-
|
|
|
- var results = new List<Tuple<string, string>>();
|
|
|
-
|
|
|
- // We will always use the first value - so that if there are no matches we can return something
|
|
|
- var currentElementName = reader.Name;
|
|
|
-
|
|
|
- var lang = reader.HasAttributes ? reader.GetAttribute("lang") : null;
|
|
|
- var currentValue = reader.ReadElementContentAsString();
|
|
|
- results.Add(new Tuple<string, string>(currentValue, lang));
|
|
|
-
|
|
|
- if (allOccurrencesSetter != null)
|
|
|
- {
|
|
|
- allOccurrencesSetter(currentValue);
|
|
|
- }
|
|
|
-
|
|
|
- while (!reader.EOF && reader.ReadState == ReadState.Interactive)
|
|
|
- {
|
|
|
- if (reader.NodeType == XmlNodeType.Element)
|
|
|
- {
|
|
|
- if (reader.Name == currentElementName)
|
|
|
- {
|
|
|
- lang = reader.HasAttributes ? reader.GetAttribute("lang") : null;
|
|
|
- currentValue = reader.ReadElementContentAsString();
|
|
|
-
|
|
|
- if (allOccurrencesSetter != null)
|
|
|
- {
|
|
|
- allOccurrencesSetter(currentValue);
|
|
|
- }
|
|
|
-
|
|
|
- results.Add(new Tuple<string, string>(currentValue, lang));
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- reader.Read();
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if (languageRequired != null)
|
|
|
- {
|
|
|
- foreach (var result in results)
|
|
|
- {
|
|
|
- if (string.Equals(languageRequired, result.Item2, StringComparison.OrdinalIgnoreCase))
|
|
|
- {
|
|
|
- setter(result.Item1);
|
|
|
- return;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- foreach (var result in results)
|
|
|
- {
|
|
|
- if (string.IsNullOrWhiteSpace(result.Item2))
|
|
|
- {
|
|
|
- setter(result.Item1);
|
|
|
- return;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- foreach (var result in results)
|
|
|
- {
|
|
|
- setter(result.Item1);
|
|
|
- return;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- public void ProcessMultipleNodes(XmlReader reader, Action<string> setter, string languageRequired = null)
|
|
|
- {
|
|
|
- /* <category lang="en">Property - English</category>
|
|
|
- * <category lang="en">Property - English 2</category>
|
|
|
- * <category lang="es">Property - Spanish</category>
|
|
|
- * <category lang="es">Property - Spanish 2</category>
|
|
|
- * <category lang="">Property - Empty Language</category>
|
|
|
- * <category lang="">Property - Empty Language 2</category>
|
|
|
- * <category>Property - No Language</category>
|
|
|
- * <category>Property - No Language 2</category>
|
|
|
- */
|
|
|
-
|
|
|
- /* Expected Behaviour:
|
|
|
- * - Language = Null Property - No Language / Property - No Language 2
|
|
|
- * - Language = "" Property - Empty Language / Property - Empty Language 2
|
|
|
- * - Language = es Property - Spanish / Property - Spanish 2
|
|
|
- * - Language = en Property - English / Property - English 2
|
|
|
- */
|
|
|
-
|
|
|
- var currentElementName = reader.Name;
|
|
|
- var values = new[] { new { Language = reader.GetAttribute("lang"), Value = reader.ReadElementContentAsString() } }.ToList();
|
|
|
-
|
|
|
- while (reader.Read())
|
|
|
- {
|
|
|
- if (reader.NodeType == XmlNodeType.Element && reader.Name != currentElementName)
|
|
|
- {
|
|
|
- break;
|
|
|
- }
|
|
|
-
|
|
|
- if (reader.NodeType == XmlNodeType.Element && reader.Name == currentElementName)
|
|
|
- {
|
|
|
- values.Add(new { Language = reader.GetAttribute("lang"), Value = reader.ReadElementContentAsString() });
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if (values.Count(v => v.Language == languageRequired) > 0)
|
|
|
- {
|
|
|
- values.RemoveAll(v => v.Language != languageRequired);
|
|
|
- }
|
|
|
-
|
|
|
- // ENumerate and return all the matches
|
|
|
- foreach (var result in values)
|
|
|
- {
|
|
|
- setter(result.Value);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- public void ProcessMultipleNodesWithLanguage(XmlReader reader, Action<string> setter)
|
|
|
- {
|
|
|
- var currentElementName = reader.Name;
|
|
|
- while (reader.Name == currentElementName)
|
|
|
- {
|
|
|
- var language = reader.GetAttribute("lang");
|
|
|
- if (string.IsNullOrEmpty(_language) || string.IsNullOrEmpty(language) || language == _language)
|
|
|
- {
|
|
|
- setter(reader.ReadElementContentAsString());
|
|
|
- }
|
|
|
- reader.Skip();
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private void PopulateHeader(XmlReader reader, XmlTvProgram result)
|
|
|
- {
|
|
|
- result.ChannelId = reader.GetAttribute("channel");
|
|
|
-
|
|
|
- var startValue = reader.GetAttribute("start");
|
|
|
- if (string.IsNullOrEmpty(startValue))
|
|
|
- {
|
|
|
- // Log.Error(" programme#{0} doesnt contain a start date", iChannel);
|
|
|
- result.StartDate = DateTimeOffset.MinValue;
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- result.StartDate = ParseDate(startValue).GetValueOrDefault();
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- var endValue = reader.GetAttribute("stop");
|
|
|
- if (string.IsNullOrEmpty(endValue))
|
|
|
- {
|
|
|
- // Log.Error(" programme#{0} doesnt contain an end date", iChannel);
|
|
|
- result.EndDate = DateTimeOffset.MinValue;
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- result.EndDate = ParseDate(endValue).GetValueOrDefault();
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- public const string _regDateWithOffset = @"^(?<dateDigits>[0-9]{4,14})(\s(?<dateOffset>[+-]*[0-9]{1,4}))?$";
|
|
|
-
|
|
|
- public DateTimeOffset? ParseDate(string dateValue)
|
|
|
- {
|
|
|
- /*
|
|
|
- All dates and times in this DTD follow the same format, loosely based
|
|
|
- on ISO 8601. They can be 'YYYYMMDDhhmmss' or some initial
|
|
|
- substring, for example if you only know the year and month you can
|
|
|
- have 'YYYYMM'. You can also append a timezone to the end; if no
|
|
|
- explicit timezone is given, UTC is assumed. Examples:
|
|
|
- '200007281733 BST', '200209', '19880523083000 +0300'. (BST == +0100.)
|
|
|
- */
|
|
|
-
|
|
|
- if (string.IsNullOrEmpty(dateValue))
|
|
|
- {
|
|
|
- return null;
|
|
|
- }
|
|
|
-
|
|
|
- var completeDate = "20000101000000";
|
|
|
- var dateComponent = string.Empty;
|
|
|
- var dateOffset = "+00:00";
|
|
|
- var match = Regex.Match(dateValue, _regDateWithOffset);
|
|
|
- if (match.Success)
|
|
|
- {
|
|
|
- dateComponent = match.Groups["dateDigits"].Value;
|
|
|
- if (!string.IsNullOrEmpty(match.Groups["dateOffset"].Value))
|
|
|
- {
|
|
|
- dateOffset = match.Groups["dateOffset"].Value; // Add in the colon to ease parsing later
|
|
|
- if (dateOffset.Length == 5)
|
|
|
- {
|
|
|
- dateOffset = dateOffset.Insert(3, ":"); // Add in the colon to ease parsing later
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- dateOffset = "+00:00";
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // Pad out the date component part to 14 characaters so 2016061509 becomes 20160615090000
|
|
|
- if (dateComponent.Length < 14)
|
|
|
- {
|
|
|
- dateComponent = dateComponent + completeDate.Substring(dateComponent.Length, completeDate.Length - dateComponent.Length);
|
|
|
- }
|
|
|
-
|
|
|
- var standardDate = string.Format("{0} {1}", dateComponent, dateOffset);
|
|
|
- if (DateTimeOffset.TryParseExact(standardDate, "yyyyMMddHHmmss zzz", CultureInfo.CurrentCulture, DateTimeStyles.None, out DateTimeOffset parsedDateTime))
|
|
|
- {
|
|
|
- return parsedDateTime.ToUniversalTime();
|
|
|
- }
|
|
|
-
|
|
|
- // Logger.LogWarning("Unable to parse the date {0} from standardised form {1}", dateValue, standardDate);
|
|
|
-
|
|
|
- return null;
|
|
|
- }
|
|
|
-
|
|
|
- public string StandardiseDate(string value)
|
|
|
- {
|
|
|
- var completeDate = "20000101000000";
|
|
|
- var dateComponent = string.Empty;
|
|
|
- var dateOffset = "+0000";
|
|
|
-
|
|
|
- var match = Regex.Match(value, _regDateWithOffset);
|
|
|
- if (match.Success)
|
|
|
- {
|
|
|
- dateComponent = match.Groups["dateDigits"].Value;
|
|
|
- dateOffset = match.Groups["dateOffset"].Value;
|
|
|
- }
|
|
|
-
|
|
|
- // Pad out the date component part to 14 characaters so 2016061509 becomes 20160615090000
|
|
|
- if (dateComponent.Length < 14)
|
|
|
- {
|
|
|
- dateComponent = dateComponent + completeDate.Substring(dateComponent.Length, completeDate.Length - dateComponent.Length);
|
|
|
- }
|
|
|
-
|
|
|
- return string.Format("{0} {1}", dateComponent, dateOffset);
|
|
|
- }
|
|
|
- }
|
|
|
-}
|