/// <summary> /// Main iTunes parsing method. /// </summary> /// <param name="parent">Parent stack for current node.</param> /// <param name="reader">Current xml feed reader.</param> /// <param name="feed">Current feed result.</param> /// <param name="root">Flag indicating if parser is the default root parser.</param> /// <returns>Flag indicating if current node should be parsed or if next node should be retrieved.</returns> public override async Task <bool> Parse(Stack <NodeInformation> parent, XmlReader reader, Feed feed, bool root = true) { //Init bool result; //Verify Element Node if (result = reader.NodeType == XmlNodeType.Element && (!reader.IsEmptyElement || reader.HasAttributes)) { //Init ICommonITunes target; NodeInformation nodeInfo = reader.NodeInformation(); //Add iTunes to feed content type. ICommonFeed feedTarget = feed.CurrentItem ?? (ICommonFeed)feed; feedTarget.ContentType |= FeedContentType.ITunes; //Set common feed target if (feed.CurrentItem != null) { feed.CurrentItem.ITunes ??= new ITunesItem(); target = feed.CurrentItem.ITunes; } else { feed.ITunes ??= new ITunesFeed(); target = feed.ITunes; } //Identify node name switch (reader.LocalName) { #region Common case "author": //The group responsible for creating the show/episode. { target.Author = await reader.ReadStartElementAndContentAsStringAsync().ConfigureAwait(false); } break; case "block": //The podcast/episode show or hide status. (true/false/yes/no) { var content = await reader.ReadStartElementAndContentAsStringAsync().ConfigureAwait(false); if (bool.TryParse(content, out var blockFlag)) { target.Block = blockFlag; } else { target.Block = string.Equals(content, "yes"); } break; } case "explicit": //The podcast/episode parental advisory information. Explicit language or adult content. (true/false/yes/no) { var content = await reader.ReadStartElementAndContentAsStringAsync().ConfigureAwait(false); if (bool.TryParse(content, out var explicitFlag)) { target.Explicit = explicitFlag; } else { target.Explicit = string.Equals(content, "yes"); } break; } case "image": //The artwork for the show/episode. { var href = reader.GetAttribute("href"); if (href != null) { try { //Attempt to parse image URL target.Image = new FeedImage() { Url = new Uri(href) }; } catch (Exception ex) when(ex is ArgumentNullException || ex is UriFormatException) { //Unknown node format SetParseError(ParseErrorType.UnknownNodeFormat, nodeInfo, feed, href, ex.Message); } } else { //Missing href attribute SetParseError(ParseErrorType.MissingAttribute, nodeInfo, feed, null, "href"); } break; } case "keywords": //List of words or phrases used when searching. { var words = await reader.ReadStartElementAndContentAsStringAsync().ConfigureAwait(false); target.Keywords = words.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(word => word.Trim()).ToList(); break; } case "title": //The show/episode title specific for Apple Podcasts. { target.Title = await reader.ReadStartElementAndContentAsStringAsync().ConfigureAwait(false); break; } case "subtitle": //Used as the title of the podcast/episode. { target.Subtitle = await reader.ReadStartElementAndContentAsStringAsync().ConfigureAwait(false); break; } case "summary": //Description of the podcast/episode. { target.Summary = await reader.ReadStartElementAndContentAsStringAsync().ConfigureAwait(false); break; } #endregion Common #region Feed case "type": //The type of show. Episodic (default) / Serial. { if (feed.ITunes != null) { var type = await reader.ReadStartElementAndContentAsStringAsync().ConfigureAwait(false); switch (type) { case "episodic": feed.ITunes.Type = ITunesType.Episodic; break; case "serial": feed.ITunes.Type = ITunesType.Serial; break; default: SetParseError(ParseErrorType.UnknownNodeFormat, nodeInfo, feed, type); break; } } else { //Feed ITunes object missing throw new ArgumentNullException("Feed.ITunes"); } break; } case "category": //The show category information. { if (feed.ITunes != null) { //Get category text var category = reader.GetAttribute("text"); if (!string.IsNullOrWhiteSpace(category)) { //Decode and save category var subCategories = new List <string>(); feed.ITunes.Category ??= new Dictionary <string, List <string> >(); feed.ITunes.Category.Add(HttpUtility.HtmlDecode(category), subCategories); //Subcategories? if (!reader.IsEmptyElement) { var subTree = reader.ReadSubtree(); while (subTree.ReadToFollowing("category", reader.NamespaceURI)) { var subCategory = subTree.GetAttribute("text"); if (subCategory != null) { if (!category.Equals(subCategory)) { subCategories.Add(HttpUtility.HtmlDecode(subCategory)); } } else { //Missing text attribute SetParseError(ParseErrorType.MissingAttribute, nodeInfo, feed, null, "text"); } } } } else { //Missing text attribute SetParseError(ParseErrorType.MissingAttribute, nodeInfo, feed, null, "text"); } } else { //Feed ITunes object missing throw new ArgumentNullException("Feed.ITunes"); } break; } case "owner": //The podcast owner contact information. Name and Email address. { if (feed.ITunes != null) { //Get owner information var owner = new ITunesOwner(); var ownerElements = await reader.AllSubTreeElements().ConfigureAwait(false); foreach (var element in ownerElements) { switch (element.Key) { case "name": owner.Name = element.Value; break; case "email": { try { //Attempt to parse owner email owner.Email = element.Value.ToMailAddress(); } catch (Exception ex) when(ex is ArgumentException || ex is ArgumentNullException || ex is FormatException) { //Unknown node format SetParseError(ParseErrorType.UnknownNodeFormat, nodeInfo, feed, element.Value, $"Node: {element.Key}, {ex.Message}"); } break; } default: SetParseError(ParseErrorType.UnknownSubNode, nodeInfo, feed, element.Value, element.Key); break; } } feed.ITunes.Owner = owner; } else { //Feed ITunes object missing throw new ArgumentNullException("Feed.ITunes"); } break; } case "new-feed-url": //The new podcast RSS Feed URL. { if (feed.ITunes != null) { //Get docs var content = await reader.ReadStartElementAndContentAsStringAsync().ConfigureAwait(false); try { //Attempt to parse new feed URL feed.ITunes.NewFeedUrl = new Uri(content); } catch (Exception ex) when(ex is ArgumentNullException || ex is UriFormatException) { //Unknown node format SetParseError(ParseErrorType.UnknownNodeFormat, nodeInfo, feed, content, ex.Message); } } else { //Feed ITunes object missing throw new ArgumentNullException("Feed.ITunes"); } break; } #endregion Feed #region Item case "duration": //The duration of an episode. { if (feed.CurrentItem?.ITunes != null) { var content = await reader.ReadStartElementAndContentAsStringAsync().ConfigureAwait(false); if (double.TryParse(content, out var seconds)) { //If numeric, assume seconds feed.CurrentItem.ITunes.Duration = TimeSpan.FromSeconds(seconds); } else if (TimeSpan.TryParse(content, out var duration)) { //Duration in TimeSpan format feed.CurrentItem.ITunes.Duration = duration; } else { //Unknown node format SetParseError(ParseErrorType.UnknownNodeFormat, nodeInfo, feed, content); } } else { //Feed CurrentItem ITunes object missing throw new ArgumentNullException("Feed.CurrentItem.ITunes"); } break; } case "episodeType": { if (feed.CurrentItem?.ITunes != null) { var content = await reader.ReadStartElementAndContentAsStringAsync().ConfigureAwait(false); switch (content) { case "full": feed.CurrentItem.ITunes.EpisodeType = ITunesEpisodeType.Full; break; case "trailer": feed.CurrentItem.ITunes.EpisodeType = ITunesEpisodeType.Trailer; break; case "bonus": feed.CurrentItem.ITunes.EpisodeType = ITunesEpisodeType.Bonus; break; default: SetParseError(ParseErrorType.UnknownNodeFormat, nodeInfo, feed, content); break; } } else { //Feed CurrentItem ITunes object missing throw new ArgumentNullException("Feed.CurrentItem.ITunes"); } break; } case "episode": { if (feed.CurrentItem?.ITunes != null) { var content = await reader.ReadStartElementAndContentAsStringAsync().ConfigureAwait(false); if (int.TryParse(content, out var episode)) { feed.CurrentItem.ITunes.Episode = episode; } else { //Unknown node format SetParseError(ParseErrorType.UnknownNodeFormat, nodeInfo, feed, content); } } else { //Feed CurrentItem ITunes object missing throw new ArgumentNullException("Feed.CurrentItem.ITunes"); } break; } case "season": { if (feed.CurrentItem?.ITunes != null) { var content = await reader.ReadStartElementAndContentAsStringAsync().ConfigureAwait(false); if (int.TryParse(content, out var season)) { feed.CurrentItem.ITunes.Season = season; } else { //Unknown node format SetParseError(ParseErrorType.UnknownNodeFormat, nodeInfo, feed, content); } } else { //Feed CurrentItem ITunes object missing throw new ArgumentNullException("Feed.CurrentItem.ITunes"); } break; } #endregion Item default: //Unknown feed/item node, continue to next. { result = false; if (root) { SetParseError(ParseErrorType.UnknownNode, nodeInfo, feed); } break; } } } //Return result return(result); }
/// <summary> /// Main RSS 1.0 parsing method. /// </summary> /// <param name="parent">Parent stack for current node.</param> /// <param name="reader">Current xml feed reader.</param> /// <param name="feed">Current feed result.</param> /// <param name="root">Flag indicating if parser is the default root parser.</param> /// <returns>Flag indicating if current node should be parsed or if next node should be retrieved.</returns> /// <remarks> /// Because RSS 1.0 almost only overrides older RSS versions, if feed type is NOT RSS 1.0, parsing is handled by base classes. /// </remarks> public override async Task <bool> Parse(Stack <NodeInformation> parent, XmlReader reader, Feed feed, bool root = true) { //Init bool result; //Verify Element Node and feed type if (result = reader.NodeType == XmlNodeType.Element && (!reader.IsEmptyElement || reader.HasAttributes) && feed.Type == FeedType.RSS_1_0) { //Init var nodeInfo = reader.NodeInformation(); //Identify node name switch (reader.LocalName) { #region Required #region Feed case "channel": //Feed root node. (Override RSS 0.91) { //Attempt to get about attribute var about = reader.GetAttribute("rdf:about"); if (about != null) { try { //Attempt to parse about URL feed.About = new Uri(about); } catch (Exception ex) when(ex is ArgumentNullException || ex is UriFormatException) { //Unknown node format SetParseError(ParseErrorType.UnknownNodeFormat, nodeInfo, feed, about, $"Node: rdf:about, {ex.Message}"); } } else { //Missing about attribute SetParseError(ParseErrorType.MissingAttribute, nodeInfo, feed, null, "rdf:about"); } break; } #endregion Feed #region Item case "item": //Feed item start, add new feed item to feed. (Override RSS 0.91) { // Init var item = feed.CurrentItem ?? feed.AddItem(); //Attempt to get about attribute var about = reader.GetAttribute("rdf:about"); if (about != null) { try { //Attempt to parse about URL item.About = new Uri(about); } catch (Exception ex) when(ex is ArgumentNullException || ex is UriFormatException) { //Unknown node format SetParseError(ParseErrorType.UnknownNodeFormat, nodeInfo, feed, about, $"Node: rdf:about, {ex.Message}"); } } else { //Missing about attribute SetParseError(ParseErrorType.MissingAttribute, nodeInfo, feed, null, "rdf:about"); } break; } #endregion Item #endregion Required #region Optional #region Feed case "image": //Specifies a GIF, JPEG or PNG image that can be displayed with the feed. (Override RSS 0.91) { //Verify Correct Image Node. If empty, ignore. Otherwise parse. if (!reader.IsEmptyElement) { //Init var image = feed.Image ?? new FeedImage(); //Attempt to get about attribute var about = reader.GetAttribute("rdf:about"); if (about != null) { try { //Attempt to parse about URL image.About = new Uri(about); } catch (Exception ex) when(ex is ArgumentNullException || ex is UriFormatException) { //Unknown node format SetParseError(ParseErrorType.UnknownNodeFormat, nodeInfo, feed, about, $"Node: rdf:about, {ex.Message}"); } } else { //Missing about attribute SetParseError(ParseErrorType.MissingAttribute, nodeInfo, feed, null, "rdf:about"); } //Get image properties var imageElements = await reader.AllSubTreeElements().ConfigureAwait(false); foreach (var element in imageElements) { switch (element.Key) { case "title": image.Title = element.Value; break; case "description": image.Description = element.Value; break; case "url": { try { //Attempt to parse image URL image.Url = new Uri(element.Value); } catch (Exception ex) when(ex is ArgumentNullException || ex is UriFormatException) { //Unknown node format SetParseError(ParseErrorType.UnknownNodeFormat, nodeInfo, feed, element.Value, $"Node: {element.Key}, {ex.Message}"); } break; } case "link": { try { //Attempt to parse link URL image.Link = new Uri(element.Value); } catch (Exception ex) when(ex is ArgumentNullException || ex is UriFormatException) { //Unknown node format SetParseError(ParseErrorType.UnknownNodeFormat, nodeInfo, feed, element.Value, $"Node: {element.Key}, {ex.Message}"); } break; } case "width": { //Attempt to parse width if (int.TryParse(element.Value, out var width)) { image.Width = width; } else { //Unknown node format SetParseError(ParseErrorType.UnknownNodeFormat, nodeInfo, feed, element.Value, $"Node: {element.Key}"); } break; } case "height": { //Attempt to parse width if (int.TryParse(element.Value, out var height)) { image.Height = height; } else { //Unknown node format SetParseError(ParseErrorType.UnknownNodeFormat, nodeInfo, feed, element.Value, $"Node: {element.Key}"); } break; } default: { //Unknown node SetParseError(ParseErrorType.UnknownSubNode, nodeInfo, feed, element.Value, element.Key); break; } } } feed.Image = image; } break; } case "items": break; //An RDF Sequence is used to contain all the items to denote item order for rendering and reconstruction. (Ignore) case "textinput": //Specifies a text input box that can be displayed with the feed. (Override RSS 0.91) { //Verify Correct text input Node. If empty, ignore. Otherwise parse. if (!reader.IsEmptyElement) { //Init var textInput = feed.TextInput ?? new FeedTextInput(); //Attempt to get about attribute var about = reader.GetAttribute("rdf:about"); if (about != null) { try { //Attempt to parse about URL textInput.About = new Uri(about); } catch (Exception ex) when(ex is ArgumentNullException || ex is UriFormatException) { //Unknown node format SetParseError(ParseErrorType.UnknownNodeFormat, nodeInfo, feed, about, $"Node: rdf:about, {ex.Message}"); } } else { //Missing about attribute SetParseError(ParseErrorType.MissingAttribute, nodeInfo, feed, null, "rdf:about"); } //Get text input properties var textInputElements = await reader.AllSubTreeElements().ConfigureAwait(false); foreach (var element in textInputElements) { switch (element.Key) { case "description": textInput.Description = element.Value; break; case "link": { try { //Attempt to parse link URL textInput.Link = new Uri(element.Value); } catch (Exception ex) when(ex is ArgumentNullException || ex is UriFormatException) { //Unknown node format SetParseError(ParseErrorType.UnknownNodeFormat, nodeInfo, feed, element.Value, $"Node: {element.Key}, {ex.Message}"); } break; } case "name": textInput.Name = element.Value; break; case "title": textInput.Title = element.Value; break; default: SetParseError(ParseErrorType.UnknownSubNode, nodeInfo, feed, element.Value, element.Key); break; } } feed.TextInput = textInput; } break; } #endregion Feed #endregion Optional default: //Unknown feed/item node { //Try RSS 0.92 Parse result = await base.Parse(parent, reader, feed, false).ConfigureAwait(false); if (!result && root) { SetParseError(ParseErrorType.UnknownNode, nodeInfo, feed); } break; } } } else { //Try RSS 0.92 Parse result = await base.Parse(parent, reader, feed, false).ConfigureAwait(false); } //Return result return(result); }
/// <summary> /// Main RSS 0.91 parsing method. /// </summary> /// <param name="parent">Parent stack for current node.</param> /// <param name="reader">Current xml feed reader.</param> /// <param name="feed">Current feed result.</param> /// <param name="root">Flag indicating if parser is the default root parser.</param> /// <returns>Flag indicating if current node should be parsed or if next node should be retrieved.</returns> public override async Task <bool> Parse(Stack <NodeInformation> parent, XmlReader reader, Feed feed, bool root = true) { //Init bool result; //Verify Element Node if (result = reader.NodeType == XmlNodeType.Element && (!reader.IsEmptyElement || reader.HasAttributes)) { //Init var nodeInfo = reader.NodeInformation(); //Set common feed target ICommonFeed target = feed.CurrentItem ?? (ICommonFeed)feed; //Identify node name switch (reader.LocalName) { #region Required case "channel": break; //Feed root node. (Ignored) case "description": //Phrase or sentence describing the feed/item. { //Get and Set feed/item description var content = await reader.ReadStartElementAndContentAsStringAsync().ConfigureAwait(false); target.Description = new FeedText() { Text = content }; break; } case "language": //The language the feed is written in. (ISO 639) { //Get feed language var content = await reader.ReadStartElementAndContentAsStringAsync().ConfigureAwait(false); try { //Attempt to set feed Language feed.Language = CultureInfo.GetCultureInfo(content); } catch (Exception ex) when(ex is ArgumentException || ex is CultureNotFoundException) { //Unknown node format SetParseError(ParseErrorType.UnknownNodeFormat, nodeInfo, feed, content, ex.Message); } break; } case "link": //The URL to the HTML website corresponding to the feed/item. { //Get link var content = await reader.ReadStartElementAndContentAsStringAsync().ConfigureAwait(false); try { //Attempt to parse link URL target.Links = new List <FeedLink>() { new FeedLink() { Url = new Uri(content) } }; } catch (Exception ex) when(ex is ArgumentNullException || ex is UriFormatException) { //Unknown node format SetParseError(ParseErrorType.UnknownNodeFormat, nodeInfo, feed, content, ex.Message); } break; } case "title": //The name of the feed/item. { //Get and Set feed/item title var content = await reader.ReadStartElementAndContentAsStringAsync().ConfigureAwait(false); target.Title = new FeedText() { Text = content }; break; } #endregion Required #region Optional #region Common case "pubDate": //The publication date for the content in the feed/item. { //Get publication date var content = await reader.ReadStartElementAndContentAsStringAsync().ConfigureAwait(false); //Attemp to parser publication date if (DateTime.TryParse(content, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out var pubDate)) { target.PubDate = pubDate; } else { //Unknown node format SetParseError(ParseErrorType.UnknownNodeFormat, nodeInfo, feed, content); } break; } #endregion Common #region Feed case "copyright": //Copyright notice for content in the feed. { //Get and Set feed copyright var content = await reader.ReadStartElementAndContentAsStringAsync().ConfigureAwait(false); feed.Copyright = new FeedText() { Text = content }; break; } case "docs": //A URL that points to the documentation for the format used in the RSS file. { //Get docs var content = await reader.ReadStartElementAndContentAsStringAsync().ConfigureAwait(false); try { //Attempt to parse docs URL feed.Docs = new Uri(content); } catch (Exception ex) when(ex is ArgumentNullException || ex is UriFormatException) { //Unknown node format SetParseError(ParseErrorType.UnknownNodeFormat, nodeInfo, feed, content, ex.Message); } break; } case "image": //Specifies a GIF, JPEG or PNG image that can be displayed with the feed. { //Get image properties var image = feed.Image ?? new FeedImage(); var imageElements = await reader.AllSubTreeElements().ConfigureAwait(false); foreach (var element in imageElements) { switch (element.Key) { case "title": image.Title = element.Value; break; case "description": image.Description = element.Value; break; case "url": { try { //Attempt to parse image URL image.Url = new Uri(element.Value); } catch (Exception ex) when(ex is ArgumentNullException || ex is UriFormatException) { //Unknown node format SetParseError(ParseErrorType.UnknownNodeFormat, nodeInfo, feed, element.Value, $"Node: {element.Key}, {ex.Message}"); } break; } case "link": { try { //Attempt to parse link URL image.Link = new Uri(element.Value); } catch (Exception ex) when(ex is ArgumentNullException || ex is UriFormatException) { //Unknown node format SetParseError(ParseErrorType.UnknownNodeFormat, nodeInfo, feed, element.Value, $"Node: {element.Key}, {ex.Message}"); } break; } case "width": { //Attempt to parse width if (int.TryParse(element.Value, out var width)) { image.Width = width; } else { //Unknown node format SetParseError(ParseErrorType.UnknownNodeFormat, nodeInfo, feed, element.Value, $"Node: {element.Key}"); } break; } case "height": { //Attempt to parse height if (int.TryParse(element.Value, out var height)) { image.Height = height; } else { //Unknown node format SetParseError(ParseErrorType.UnknownNodeFormat, nodeInfo, feed, element.Value, $"Node: {element.Key}"); } break; } default: { //Unknown node SetParseError(ParseErrorType.UnknownSubNode, nodeInfo, feed, element.Value, element.Key); break; } } } feed.Image = image; break; } case "lastBuildDate": //The last time the content of the feed changed. { //Init var content = await reader.ReadStartElementAndContentAsStringAsync().ConfigureAwait(false); //Attempt to parse last build date if (DateTime.TryParse(content, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out var lastBuildDate)) { feed.LastBuildDate = lastBuildDate; } else { //Unknown node format SetParseError(ParseErrorType.UnknownNodeFormat, nodeInfo, feed, content); } break; } case "managingEditor": //Email address for person responsible for editorial content. { //Init var content = await reader.ReadStartElementAndContentAsStringAsync().ConfigureAwait(false); try { //Attempt to parse managing editor feed.ManagingEditor = content.ToMailAddress(); } catch (Exception ex) when(ex is ArgumentException || ex is ArgumentNullException || ex is FormatException) { //Unknown node format SetParseError(ParseErrorType.UnknownNodeFormat, nodeInfo, feed, content, ex.Message); } break; } case "rating": //Protocol for Web Description Resources (POWDER) { feed.Rating = await reader.ReadStartElementAndContentAsStringAsync().ConfigureAwait(false); break; } case "skipDays": //Identifies days of the week during which the feed is not updated. { //Get skip days var skipDays = FeedWeekDays.None; var skipDaysElements = await reader.AllSubTreeElements().ConfigureAwait(false); foreach (var element in skipDaysElements) { if (element.Key.Equals("day")) { switch (element.Value) { case "Monday": skipDays |= FeedWeekDays.Monday; break; case "Tuesday": skipDays |= FeedWeekDays.Tuesday; break; case "Wednesday": skipDays |= FeedWeekDays.Wednesday; break; case "Thursday": skipDays |= FeedWeekDays.Thursday; break; case "Friday": skipDays |= FeedWeekDays.Friday; break; case "Saturday": skipDays |= FeedWeekDays.Saturday; break; case "Sunday": skipDays |= FeedWeekDays.Sunday; break; default: SetParseError(ParseErrorType.UnknownNodeFormat, nodeInfo, feed, element.Value, $"Node: {element.Key}"); break; } } else { //Unknown node SetParseError(ParseErrorType.UnknownSubNode, nodeInfo, feed, element.Value, element.Key); } } feed.SkipDays = skipDays; break; } case "skipHours": //Identifies the hours of the day during which the feed is not updated. { //Get skip hours var skipHours = new List <int>(); var skipHoursElements = await reader.AllSubTreeElements().ConfigureAwait(false); foreach (var element in skipHoursElements) { if (element.Key.Equals("hour")) { if (int.TryParse(element.Value, out var hour)) { skipHours.Add(hour); } else { //Unknown node format SetParseError(ParseErrorType.UnknownNodeFormat, nodeInfo, feed, element.Value, $"Node: {element.Key}"); } } else { //Unknown node SetParseError(ParseErrorType.UnknownSubNode, nodeInfo, feed, element.Value, element.Key); } } feed.skipHours = skipHours; break; } case "textinput": //Specifies a text input box that can be displayed with the feed. { //Get text input properties var textInput = feed.TextInput ?? new FeedTextInput(); var textInputElements = await reader.AllSubTreeElements().ConfigureAwait(false); foreach (var element in textInputElements) { switch (element.Key) { case "description": textInput.Description = element.Value; break; case "link": { try { //Attempt to parse link URL textInput.Link = new Uri(element.Value); } catch (Exception ex) when(ex is ArgumentNullException || ex is UriFormatException) { //Unknown node format SetParseError(ParseErrorType.UnknownNodeFormat, nodeInfo, feed, element.Value, $"Node: {element.Key}, {ex.Message}"); } break; } case "name": textInput.Name = element.Value; break; case "title": textInput.Title = element.Value; break; default: SetParseError(ParseErrorType.UnknownSubNode, nodeInfo, feed, element.Value, element.Key); break; } } feed.TextInput = textInput; break; } case "webMaster": //Email address for person responsible for technical issues relating to the feed. { //Init var content = await reader.ReadStartElementAndContentAsStringAsync().ConfigureAwait(false); try { //Attempt to parse web master feed.WebMaster = content.ToMailAddress(); } catch (Exception ex) when(ex is ArgumentException || ex is ArgumentNullException || ex is FormatException) { //Unknown node format SetParseError(ParseErrorType.UnknownNodeFormat, nodeInfo, feed, content, ex.Message); } break; } case "item": //Feed item start, add new feed item to feed. { //Add new item if (feed.CurrentParseType == ParseType.Feed) { feed.AddItem(); } break; } #endregion Feed #endregion Optional default: //Unknown feed/item node, if main root parser, log unknown node and continue to next. { result = false; if (root) { SetParseError(ParseErrorType.UnknownNode, nodeInfo, feed); } break; } } } else if (result = reader.NodeType == XmlNodeType.EndElement) { switch (reader.LocalName) { case "item": //Feed item end, close current feed item. { feed.CloseItem(); break; } } } //Return result return(result); }
/// <summary> /// Main Atom parsing method. /// </summary> /// <param name="parent">Parent stack for current node.</param> /// <param name="reader">Current xml feed reader.</param> /// <param name="feed">Current feed result.</param> /// <param name="root">Flag indicating if parser is the default root parser.</param> /// <returns>Flag indicating if current node should be parsed or if next node should be retrieved.</returns> public override async Task <bool> Parse(Stack <NodeInformation> parent, XmlReader reader, Feed feed, bool root = true) { //Init bool result; //Verify Element Node if (result = reader.NodeType == XmlNodeType.Element && (!reader.IsEmptyElement || reader.HasAttributes)) { //Init ICommonAtom target = feed; ICommonAtomFeed? targetFeed = feed; ICommonAtomEntry?targetEntry = feed.CurrentItem; NodeInformation nodeInfo = reader.NodeInformation(); //Verify feed type if (feed.Type == FeedType.Atom) { //Set common target if (feed.CurrentItem != null) { target = feed.CurrentItem; } } else { //Add Atom to feed content type ICommonFeed feedTarget = feed.CurrentItem ?? (ICommonFeed)feed; feedTarget.ContentType |= FeedContentType.Atom; //Set common target if (feed.CurrentItem != null) { feed.CurrentItem.Atom ??= new AtomEntry(); target = feed.CurrentItem.Atom; } else { feed.Atom ??= new AtomFeed(); target = feed.Atom; } targetFeed = feed.Atom; targetEntry = feed.CurrentItem?.Atom; } //Identify node name switch (reader.LocalName) { #region Common case "author": //Names one author of the feed entry. { //Get author properties target.Author = new FeedPerson(); var authorElements = await reader.AllSubTreeElements().ConfigureAwait(false); foreach (var element in authorElements) { switch (element.Key) { case "email": { try { //Attempt to parse author email target.Author.Email = element.Value.ToMailAddress(); } catch (Exception ex) when(ex is ArgumentException || ex is ArgumentNullException || ex is FormatException) { //Unknown node format SetParseError(ParseErrorType.UnknownNodeFormat, nodeInfo, feed, element.Value, $"Node: {element.Key}, {ex.Message}"); } break; } case "name": target.Author.Name = element.Value; break; case "uri": { try { //Attempt to parse author URI target.Author.Uri = new Uri(element.Value); } catch (Exception ex) when(ex is ArgumentNullException || ex is UriFormatException) { //Unknown node format SetParseError(ParseErrorType.UnknownNodeFormat, nodeInfo, feed, element.Value, $"Node: {element.Key}, {ex.Message}"); } break; } default: { //Unknown node SetParseError(ParseErrorType.UnknownSubNode, nodeInfo, feed, element.Value, element.Key); break; } } } break; } case "category": //One or more categories that the feed/entry belongs to. { //Verify link node if (reader.HasAttributes && reader.GetAttribute("term") != null) { //Parse and add category to feed/entry catergories list var category = new FeedCategory() { Category = reader.GetAttribute("domain"), Label = reader.GetAttribute("label") }; //Attempt to get category scheme var scheme = reader.GetAttribute("scheme"); if (scheme != null) { try { //Attempt to parse category scheme URI category.Scheme = new Uri(scheme); } catch (Exception ex) when(ex is ArgumentNullException || ex is UriFormatException) { //Unknown node format SetParseError(ParseErrorType.UnknownNodeFormat, nodeInfo, feed, scheme, $"Node: scheme, {ex.Message}"); } } //Add category to categories list target.Categories ??= new List <FeedCategory>(); target.Categories.Add(category); } else { //Missing href attribute SetParseError(ParseErrorType.MissingAttribute, nodeInfo, feed, null, "term"); } break; } case "contributor": //Name of one or more contributors to the feed entry. { //Init var contributor = new FeedPerson(); //Get contributor properties var contributorElements = await reader.AllSubTreeElements().ConfigureAwait(false); foreach (var element in contributorElements) { switch (element.Key) { case "email": { try { //Attempt to parse contributor email contributor.Email = element.Value.ToMailAddress(); } catch (Exception ex) when(ex is ArgumentException || ex is ArgumentNullException || ex is FormatException) { //Unknown node format SetParseError(ParseErrorType.UnknownNodeFormat, nodeInfo, feed, element.Value, $"Node: {element.Key}, {ex.Message}"); } break; } case "name": contributor.Name = element.Value; break; case "uri": { try { //Attempt to parse author URI contributor.Uri = new Uri(element.Value); } catch (Exception ex) when(ex is ArgumentNullException || ex is UriFormatException) { //Unknown node format SetParseError(ParseErrorType.UnknownNodeFormat, nodeInfo, feed, element.Value, $"Node: {element.Key}, {ex.Message}"); } break; } default: { //Unknown node SetParseError(ParseErrorType.UnknownSubNode, nodeInfo, feed, element.Value, element.Key); break; } } } //Add contributor to contributors list target.Contributors ??= new List <FeedPerson>(); target.Contributors.Add(contributor); break; } case "entry": //Feed entry start, add new feed item to feed. { //Add new item if (feed.CurrentParseType == ParseType.Feed) { feed.AddItem(); } break; } case "id": //Identifies the feed/entry using a universally unique and permanent URI. { //Get id var content = await reader.ReadStartElementAndContentAsStringAsync().ConfigureAwait(false); try { //Attempt to parse id URI target.Id = new Uri(content); } catch (Exception ex) when(ex is ArgumentNullException || ex is UriFormatException) { //Unknown node format SetParseError(ParseErrorType.UnknownNodeFormat, nodeInfo, feed, content, ex.Message); } break; } case "link": //Link to the referenced resource (typically a Web page) { //Verify link node if (reader.HasAttributes && reader.GetAttribute("href") != null) { //Init var link = new FeedLink(); //Get link attributes while (reader.MoveToNextAttribute()) { //Attempt to parse attribute switch (reader.LocalName) { case "href": { try { //Attempt to parse link href link.Url = new Uri(reader.Value); } catch (Exception ex) when(ex is ArgumentNullException || ex is UriFormatException) { //Unknown node format SetParseError(ParseErrorType.UnknownNodeFormat, nodeInfo, feed, reader.Value, $"Node: {reader.LocalName}, {ex.Message}"); } break; } case "hreflang": { try { //Attempt to parse link hrefLang link.Language = CultureInfo.GetCultureInfo(reader.Value); } catch (Exception ex) when(ex is ArgumentException || ex is CultureNotFoundException) { //Unknown node format SetParseError(ParseErrorType.UnknownNodeFormat, nodeInfo, feed, reader.Value, $"Node: {reader.LocalName}, {ex.Message}"); } break; } case "length": { //Attempt to parse link length if (long.TryParse(reader.Value, out var length)) { link.Length = length; } else { //Unknown node format SetParseError(ParseErrorType.UnknownNodeFormat, nodeInfo, feed, reader.Value, $"Node: {reader.LocalName}"); } break; } case "rel": { //Attempt to parse link rel switch (reader.Value) { case "alternate": link.Type = FeedLinkType.Alternate; break; case "enclosure": link.Type = FeedLinkType.Enclosure; break; case "related": link.Type = FeedLinkType.Related; break; case "self": link.Type = FeedLinkType.Self; break; case "via": link.Type = FeedLinkType.Via; break; default: SetParseError(ParseErrorType.UnknownNodeFormat, nodeInfo, feed, reader.Value, $"Node: {reader.LocalName}"); break; } break; } case "title": link.Text = reader.Value; break; case "type": link.MediaType = reader.Value; break; default: { //Unknown node SetParseError(ParseErrorType.UnknownSubNode, nodeInfo, feed, reader.Value, reader.LocalName); break; } } } //Add link to links collection target.Links ??= new List <FeedLink>(); target.Links.Add(link); } else { //Missing href attribute SetParseError(ParseErrorType.MissingAttribute, nodeInfo, feed, null, "href"); } break; } case "rights": //Conveys information about rights, e.g. copyrights, held in and over the feed. { //Attemp to parse rights target.Rights = new FeedText() { Type = reader.GetAttribute("type") }; target.Rights.Text = await reader.ReadStartElementAndContentAsStringAsync(target.Rights.Type).ConfigureAwait(false); break; } case "title": //The name of the feed/entry. { //Attemp to parse title target.Title = new FeedText() { Type = reader.GetAttribute("type") }; target.Title.Text = await reader.ReadStartElementAndContentAsStringAsync(target.Title.Type).ConfigureAwait(false); break; } case "updated": //Indicates the last time the feed/entry was modified in a significant way. { //Init var content = await reader.ReadStartElementAndContentAsStringAsync().ConfigureAwait(false); //Attempt to parse updated date if (DateTime.TryParse(content, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out var updated)) { target.Updated = updated; } else { //Unknown node format SetParseError(ParseErrorType.UnknownNodeFormat, nodeInfo, feed, content); } break; } #endregion Common #region Feed case "icon": //Identifies a small image which provides iconic visual identification for the feed. { if (targetFeed != null) { //Get icon var content = await reader.ReadStartElementAndContentAsStringAsync().ConfigureAwait(false); try { //Attempt to parse icon URI targetFeed.Icon = new Uri(content); } catch (Exception ex) when(ex is ArgumentNullException || ex is UriFormatException) { //Unknown node format SetParseError(ParseErrorType.UnknownNodeFormat, nodeInfo, feed, content, ex.Message); } } else { //Feed Atom object missing throw new ArgumentNullException("Feed.Atom"); } break; } case "generator": //Indicating the program used to generate the feed. { if (targetFeed != null) { //Init targetFeed.Generator = new FeedGenerator(); //Attempt to parse optional attributes var uri = reader.GetAttribute("uri"); if (uri != null) { try { //Attempt to parse generator uri targetFeed.Generator.Uri = new Uri(uri); } catch (Exception ex) when(ex is ArgumentNullException || ex is UriFormatException) { //Unknown node format SetParseError(ParseErrorType.UnknownNodeFormat, nodeInfo, feed, uri, $"Node: uri, {ex.Message}"); } } targetFeed.Generator.Version = reader.GetAttribute("version"); //Attempt to parse feed generator targetFeed.Generator.Generator = await reader.ReadStartElementAndContentAsStringAsync().ConfigureAwait(false); } else { //Feed Atom object missing throw new ArgumentNullException("Feed.Atom"); } break; } case "logo": //Identifies a larger image which provides visual identification for the feed. { if (targetFeed != null) { //Init targetFeed.Logo = new FeedImage(); //Get logo var content = await reader.ReadStartElementAndContentAsStringAsync().ConfigureAwait(false); try { //Attempt to parse logo URI targetFeed.Logo.Url = new Uri(content); } catch (Exception ex) when(ex is ArgumentNullException || ex is UriFormatException) { //Unknown node format SetParseError(ParseErrorType.UnknownNodeFormat, nodeInfo, feed, content, ex.Message); } } else { //Feed Atom object missing throw new ArgumentNullException("Feed.Atom"); } break; } case "subtitle": //Contains a human-readable description or subtitle for the feed. { if (targetFeed != null) { //Attemp to parse subtitle targetFeed.Subtitle = new FeedText() { Type = reader.GetAttribute("type") }; targetFeed.Subtitle.Text = await reader.ReadStartElementAndContentAsStringAsync(targetFeed.Subtitle.Type).ConfigureAwait(false); } else { //Feed Atom object missing throw new ArgumentNullException("Feed.Atom"); } break; } #endregion Feed #region Entry case "content": //Contains or links to the complete content of the entry. { if (targetEntry != null) { //Attemp to parse content targetEntry.Content = new FeedContent() { Type = reader.GetAttribute("type") }; targetEntry.Content.Text = await reader.ReadStartElementAndContentAsStringAsync(targetEntry.Content.Type).ConfigureAwait(false); //Attempt to get content src var src = reader.GetAttribute("src"); if (src != null) { try { //Attempt to parse content src targetEntry.Content.Source = new Uri(src); } catch (Exception ex) when(ex is ArgumentNullException || ex is UriFormatException) { //Unknown node format SetParseError(ParseErrorType.UnknownNodeFormat, nodeInfo, feed, src, $"Node: src, {ex.Message}"); } } } else { if (feed.Type == FeedType.Atom) { //Feed item object missing throw new ArgumentNullException("Feed.CurrentItem"); } else { //Feed CurrentItem Atom object missing throw new ArgumentNullException("Feed.CurrentItem.Atom"); } } break; } case "published": { if (targetEntry != null) { //Get published var content = await reader.ReadStartElementAndContentAsStringAsync().ConfigureAwait(false); //Attemp to parser published if (DateTime.TryParse(content, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out var published)) { targetEntry.Published = published; } else { //Unknown node format SetParseError(ParseErrorType.UnknownNodeFormat, nodeInfo, feed, content); } } else { if (feed.Type == FeedType.Atom) { //Feed item object missing throw new ArgumentNullException("Feed.CurrentItem"); } else { //Feed CurrentItem Atom object missing throw new ArgumentNullException("Feed.CurrentItem.Atom"); } } break; } case "source": { if (targetEntry != null) { //Init targetEntry.Source = new FeedLink(); //Get source properties var sourceElements = await reader.AllSubTreeElements().ConfigureAwait(false); foreach (var element in sourceElements) { switch (element.Key) { case "id": { try { //Attempt to parse id URI targetEntry.Source.Url = new Uri(element.Value); } catch (Exception ex) when(ex is ArgumentNullException || ex is UriFormatException) { //Unknown node format SetParseError(ParseErrorType.UnknownNodeFormat, nodeInfo, feed, element.Value, $"Node: {element.Key}, {ex.Message}"); } break; } case "title": { //Set title text targetEntry.Source.Text = element.Value; break; } case "updated": { //Attempt to parse updated date if (DateTime.TryParse(element.Value, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out var updated)) { targetEntry.Source.Updated = updated; } else { //Unknown node format SetParseError(ParseErrorType.UnknownNodeFormat, nodeInfo, feed, element.Value, $"Node: {element.Key}"); } break; } default: { //Unknown node SetParseError(ParseErrorType.UnknownSubNode, nodeInfo, feed, element.Value, element.Key); break; } } } } else { if (feed.Type == FeedType.Atom) { //Feed item object missing throw new ArgumentNullException("Feed.CurrentItem"); } else { //Feed CurrentItem Atom object missing throw new ArgumentNullException("Feed.CurrentItem.Atom"); } } break; } case "summary": //Conveys a short summary, abstract, or excerpt of the entry. { if (targetEntry != null) { //Attemp to parse summary targetEntry.Summary = new FeedText() { Type = reader.GetAttribute("type") }; targetEntry.Summary.Text = await reader.ReadStartElementAndContentAsStringAsync(targetEntry.Summary.Type).ConfigureAwait(false); } else { if (feed.Type == FeedType.Atom) { //Feed item object missing throw new ArgumentNullException("Feed.CurrentItem"); } else { //Feed CurrentItem Atom object missing throw new ArgumentNullException("Feed.CurrentItem.Atom"); } } break; } #endregion Entry default: //Unknown feed/entry node, continue to next. { result = false; if (root) { SetParseError(ParseErrorType.UnknownNode, nodeInfo, feed); } break; } } } else if (result = reader.NodeType == XmlNodeType.EndElement) { switch (reader.LocalName) { case "entry": //Feed entry end, close current feed item. { feed.CloseItem(); break; } } } //Return result return(result); }