/// <summary>
        /// Main Content 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
                NodeInformation nodeInfo = reader.NodeInformation();

                //Add Content to feed content type.
                ICommonFeed feedTarget = feed.CurrentItem ?? (ICommonFeed)feed;
                feedTarget.ContentType |= FeedContentType.Content;

                //Set common feed target
                ICommonContent target = feed.CurrentItem ?? (ICommonContent)feed;

                //Identify node name
                switch (reader.LocalName)
                {
                    #region Common

                case "encoded":     //Contains the complete encoded content of the feed/item.
                {
                    //Attemp to get encoded content
                    target.Content = new FeedContent()
                    {
                        Type = "text/html"
                    };
                    target.Content.Text = await reader.ReadStartElementAndContentAsStringAsync(target.Content.Type).ConfigureAwait(false);

                    break;
                }

                    #endregion Common

                default:     //Unknown feed/item node, continue to next.
                {
                    result = false;
                    if (root)
                    {
                        SetParseError(ParseErrorType.UnknownNode, nodeInfo, feed);
                    }
                    break;
                }
                }
            }

            //Return result
            return(result);
        }
Example #2
0
        /// <summary>
        /// Main RSS 0.92 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 Optional

                    #region Common

                case "category":     //One or more categories that the feed/item belongs to.
                {
                    //Parse and add category to feed/item catergories list
                    target.Categories ??= new List <FeedCategory>();
                    var domain  = reader.GetAttribute("domain");
                    var content = await reader.ReadStartElementAndContentAsStringAsync().ConfigureAwait(false);

                    target.Categories.Add(new FeedCategory()
                        {
                            Domain = domain, Category = content
                        });
                    break;
                }

                    #endregion Common

                    #region Feed

                case "cloud":     //Allows processes to register with a cloud to be notified of updates to the feed, implementing a lightweight publish-subscribe protocol for RSS feeds.
                {
                    //Parse Cloud attributes
                    feed.Cloud = new FeedCloud()
                    {
                        Domain            = reader.GetAttribute("domain"),
                        Path              = reader.GetAttribute("path"),
                        Port              = reader.GetAttribute("port"),
                        Protocol          = reader.GetAttribute("protocol"),
                        RegisterProcedure = reader.GetAttribute("registerProcedure")
                    };
                    break;
                }

                    #endregion Feed

                    #region Item

                case "enclosure":     //Media object that is attached to the feed item.
                {
                    if (feed.CurrentItem != null)
                    {
                        //Attempt to parse enclosure
                        var content = new MediaContent()
                        {
                            Type = reader.GetAttribute("type")
                        };

                        //Attempt to parse length
                        if (long.TryParse(reader.GetAttribute("length"), out var length))
                        {
                            content.FileSize = length;
                        }

                        //Get enclosure url
                        var url = reader.GetAttribute("url");
                        try
                        {
                            //Attempt to parse enclosure URL
                            content.Url = new Uri(url);
                        }
                        catch (Exception ex) when(ex is ArgumentNullException || ex is UriFormatException)
                        {
                            //Unknown node format
                            SetParseError(ParseErrorType.UnknownNodeFormat, nodeInfo, feed, url, $"Node: url, {ex.Message}");
                        }

                        //Set item Enclosure
                        feed.CurrentItem.Enclosure = content;
                    }
                    else
                    {
                        //Feed item object missing
                        throw new ArgumentNullException("Feed.CurrentItem");
                    }
                    break;
                }

                case "source":     //The feed that the feed item came from.
                {
                    if (feed.CurrentItem != null)
                    {
                        //Init
                        var url     = reader.GetAttribute("url");
                        var content = await reader.ReadStartElementAndContentAsStringAsync().ConfigureAwait(false);

                        //Attempt to parse source
                        feed.CurrentItem.Source = new FeedLink()
                        {
                            Type = FeedLinkType.Via, Text = content
                        };

                        try
                        {
                            //Attempt to parse enclosure URL
                            feed.CurrentItem.Source.Url = new Uri(url);
                        }
                        catch (Exception ex) when(ex is ArgumentNullException || ex is UriFormatException)
                        {
                            //Unknown node format
                            SetParseError(ParseErrorType.UnknownNodeFormat, nodeInfo, feed, url, $"Node: url, {ex.Message}");
                        }
                    }
                    else
                    {
                        //Feed item object missing
                        throw new ArgumentNullException("Feed.CurrentItem");
                    }
                    break;
                }

                    #endregion Item

                    #endregion Optional

                default:     //Unknown feed/item node
                {
                    //Try RSS 0.91 Parse
                    result = await base.Parse(parent, reader, feed, false).ConfigureAwait(false);

                    if (!result && root)
                    {
                        SetParseError(ParseErrorType.UnknownNode, nodeInfo, feed);
                    }
                    break;
                }
                }
            }
            else
            {
                //Try RSS 0.91 Parse
                result = await base.Parse(parent, reader, feed, false).ConfigureAwait(false);
            }

            //Return result
            return(result);
        }
Example #3
0
        /// <summary>
        /// Main Rdf 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 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();
                var parentName = parent.Count > 0 ? parent.Peek().LocalName : string.Empty;

                //Add Rdf to feed content type.
                ICommonFeed feedTarget = feed.CurrentItem ?? (ICommonFeed)feed;
                feedTarget.ContentType |= FeedContentType.Rdf;

                //Identify node name
                switch (reader.LocalName)
                {
                case "Seq":
                {
                    switch (parentName)
                    {
                    case "items":             //An RDF Sequence is used to contain all the items to denote item order for rendering and reconstruction.
                    {
                        var itemsSequence = new List <Uri>();
                        if (!reader.IsEmptyElement)
                        {
                            var subTree = reader.ReadSubtree();
                            while (subTree.ReadToFollowing("li", reader.NamespaceURI))
                            {
                                var resource = subTree.GetAttribute("resource");
                                if (resource != null)
                                {
                                    try
                                    {
                                        //Attempt to parse resource URL
                                        itemsSequence.Add(new Uri(resource));
                                    }
                                    catch (Exception ex) when(ex is ArgumentNullException || ex is UriFormatException)
                                    {
                                        //Unknown node format
                                        SetParseError(ParseErrorType.UnknownNodeFormat, nodeInfo, feed, resource, ex.Message);
                                    }
                                }
                                else
                                {
                                    //Missing resource attribute
                                    SetParseError(ParseErrorType.MissingAttribute, nodeInfo, feed, null, "resource");
                                }
                            }
                        }
                        feed.itemsSequence = itemsSequence;
                        break;
                    }

                    default:
                        result = false;
                        break;
                    }
                    break;
                }

                default:     //Unknown feed/item node, continue to next.
                {
                    result = false;
                    if (root)
                    {
                        SetParseError(ParseErrorType.UnknownNode, nodeInfo, feed);
                    }
                    break;
                }
                }
            }

            //Return result
            return(Task.FromResult(result));
        }
Example #4
0
        /// <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 2.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>
        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
                NodeInformation nodeInfo = reader.NodeInformation();

                //Set common feed target
                ICommonFeed target = feed.CurrentItem ?? (ICommonFeed)feed;

                //Identify node name
                switch (reader.LocalName)
                {
                    #region Optional

                    #region Feed

                case "generator":     //A string indicating the program used to generate the feed.
                {
                    //Attempt to parse feed generator
                    feed.Generator ??= new FeedGenerator();
                    feed.Generator.Generator = await reader.ReadStartElementAndContentAsStringAsync().ConfigureAwait(false);

                    break;
                }

                case "ttl":     //Number of minutes that indicates how long a feed can be cached before refreshing from the source.
                {
                    //Attempt to parse feed time to live
                    var content = await reader.ReadStartElementAndContentAsStringAsync().ConfigureAwait(false);

                    if (double.TryParse(content, out var ttl))
                    {
                        feed.Ttl = TimeSpan.FromMinutes(ttl);
                    }
                    break;
                }

                    #endregion Feed

                    #region Item

                case "author":     //Email address of the author of the feed item.
                {
                    if (feed.CurrentItem != null)
                    {
                        //Init
                        var content = await reader.ReadStartElementAndContentAsStringAsync().ConfigureAwait(false);

                        try
                        {
                            //Attempt to parse feed item author
                            feed.CurrentItem.Author ??= new FeedPerson();
                            feed.CurrentItem.Author.Email = 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);
                        }
                    }
                    else
                    {
                        //Feed item object missing
                        throw new ArgumentNullException("Feed.CurrentItem");
                    }
                    break;
                }

                case "comments":     //URL of a page for comments relating to the feed item.
                {
                    if (feed.CurrentItem != null)
                    {
                        //Init
                        var content = await reader.ReadStartElementAndContentAsStringAsync().ConfigureAwait(false);

                        try
                        {
                            //Attempt to parse feed item comments URL
                            feed.CurrentItem.Comments = 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 item object missing
                        throw new ArgumentNullException("Feed.CurrentItem");
                    }
                    break;
                }

                case "guid":     //A string/link that uniquely identifies the feed item.
                {
                    if (feed.CurrentItem != null)
                    {
                        //Attempt to parse feed item guid
                        var isPermaLink = reader.GetAttribute("isPermaLink");
                        var content     = await reader.ReadStartElementAndContentAsStringAsync().ConfigureAwait(false);

                        feed.CurrentItem.Guid = new FeedGuid()
                        {
                            Guid = content
                        };
                        if (bool.TryParse(isPermaLink, out var isLink))
                        {
                            feed.CurrentItem.Guid.IsPermaLink = isLink;
                            if (isLink)
                            {
                                try
                                {
                                    //Attempt to parse feed item guid as URL
                                    feed.CurrentItem.Guid.Link = new Uri(content);
                                }
                                catch (Exception ex) when(ex is ArgumentNullException || ex is UriFormatException)
                                {
                                    //Unknown node format
                                    SetParseError(ParseErrorType.UnknownNodeFormat, nodeInfo, feed, content, $"Node: Link, {ex.Message}");
                                }
                            }
                        }
                    }
                    else
                    {
                        //Feed item object missing
                        throw new ArgumentNullException("Feed.CurrentItem");
                    }
                    break;
                }

                    #endregion Item

                    #endregion Optional

                default:     //Unknown feed/item node
                {
                    //Try RSS 1.0 Parse
                    result = await base.Parse(parent, reader, feed, false).ConfigureAwait(false);

                    if (!result && root)
                    {
                        SetParseError(ParseErrorType.UnknownNode, nodeInfo, feed);
                    }
                    break;
                }
                }
            }
            else
            {
                //Try RSS 1.0 Parse
                result = await base.Parse(parent, reader, feed, false).ConfigureAwait(false);
            }

            //Return result
            return(result);
        }
        /// <summary>
        /// Main Spotify 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();
                feed.Spotify ??= new SpotifyFeed();

                //Add Spotify to feed content type.
                ICommonFeed feedTarget = feed.CurrentItem ?? (ICommonFeed)feed;
                feedTarget.ContentType |= FeedContentType.Spotify;

                //Identify node name
                switch (reader.LocalName)
                {
                    #region Feed

                case "limit":     //Number of concurrent episodes (items) to display.
                {
                    if (feed.Spotify != null)
                    {
                        //Get limit count
                        var content = reader.GetAttribute("recentCount");
                        if (content != null)
                        {
                            if (int.TryParse(content, out var count))
                            {
                                feed.Spotify.Limit = count;
                            }
                            else
                            {
                                //Unknown node format
                                SetParseError(ParseErrorType.UnknownNodeFormat, nodeInfo, feed, content);
                            }
                        }
                        else
                        {
                            //Missing count attribute
                            SetParseError(ParseErrorType.MissingAttribute, nodeInfo, feed, null, "recentCount");
                        }
                    }
                    else
                    {
                        //Feed Spotify object missing
                        throw new ArgumentNullException("Feed.Spotify");
                    }
                }
                break;

                case "countryOfOrigin":     //Defines the intended market/territory ranked in order of priority where the podcast is relevant to the consumer. (List of ISO 3166 codes).
                {
                    if (feed.Spotify != null)
                    {
                        //Get Countries of origin
                        var content = await reader.ReadStartElementAndContentAsStringAsync().ConfigureAwait(false);

                        try
                        {
                            //Attempt to set country of origin list
                            feed.Spotify.countryOfOrigin = content.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries).Select(country => new RegionInfo(country)).ToList();
                        }
                        catch (Exception ex) when(ex is ArgumentNullException || ex is ArgumentException)
                        {
                            //Unknown node format
                            SetParseError(ParseErrorType.UnknownNodeFormat, nodeInfo, feed, content, ex.Message);
                        }
                    }
                    else
                    {
                        //Feed Spotify object missing
                        throw new ArgumentNullException("Feed.Spotify");
                    }
                    break;
                }

                    #endregion Feed

                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 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);
        }
        /// <summary>
        /// Main Geo RSS 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
                GeoInformation?target   = null;
                var            nodeInfo = reader.NodeInformation();

                //Add Geo RSS to feed content type.
                ICommonFeed feedTarget = feed.CurrentItem ?? (ICommonFeed)feed;
                feedTarget.ContentType |= FeedContentType.GeoRSS;

                //Media RSS Parent?
                var mediaParent = parent.FirstOrDefault(ancestor => FeedParser.ContentTypeNamespace[FeedContentType.MediaRSS].Contains(ancestor.Namespace));
                if (mediaParent != null)
                {
                    switch (mediaParent.LocalName)
                    {
                    case "location":
                    {
                        if (feed.CurrentItem != null)
                        {
                            target = feed.CurrentItem.Media?.CurrentInformation?.Locations?.LastOrDefault()?.GeoInformation ?? new GeoInformation();
                        }
                        else
                        {
                            target = feed.MediaInformation?.Locations?.LastOrDefault()?.GeoInformation ?? new GeoInformation();
                        }
                        break;
                    }
                    }
                }

                //Set feed/feed item target
                if (target == null)
                {
                    if (feed.CurrentItem != null)
                    {
                        //Get/Set feed item geographical information
                        target = feed.CurrentItem.GeoInformation ??= new GeoInformation();
                    }
                    else
                    {
                        //Get/Set feed geographical information
                        target = feed.GeoInformation ??= new GeoInformation();
                    }
                }

                //Identify node name
                switch (reader.LocalName)
                {
                case "box":     //A bounding box is a rectangular region, often used to define the extents of a map or a rough area of interest.
                case "lowerCorner":
                case "upperCorner":
                {
                    //Set target box coordinates
                    await SetCoordinates(GeoType.Box, target, reader, feed, nodeInfo).ConfigureAwait(false);

                    break;
                }

                case "elev":
                {
                    //Init
                    var content = await reader.ReadStartElementAndContentAsStringAsync().ConfigureAwait(false);

                    //Attempt to parse elevation.
                    if (int.TryParse(content, out var elevation))
                    {
                        target.Elevation = elevation;
                    }
                    else
                    {
                        //Unknown node format
                        SetParseError(ParseErrorType.UnknownNodeFormat, nodeInfo, feed, content);
                    }
                    break;
                }

                case "featurename":     //Feauture name of the geographical information.
                {
                    //Attempt to parse feature name.
                    target.FeatureName = await reader.ReadStartElementAndContentAsStringAsync().ConfigureAwait(false);

                    break;
                }

                case "featuretypetag":     //Feauture type of the geographical information.
                {
                    //Attempt to parse feature type.
                    target.FeatureType = await reader.ReadStartElementAndContentAsStringAsync().ConfigureAwait(false);

                    break;
                }

                case "floor":
                {
                    //Init
                    var content = await reader.ReadStartElementAndContentAsStringAsync().ConfigureAwait(false);

                    //Attempt to parse floor.
                    if (int.TryParse(content, out var floor))
                    {
                        target.Floor = floor;
                    }
                    else
                    {
                        //Unknown node format
                        SetParseError(ParseErrorType.UnknownNodeFormat, nodeInfo, feed, content);
                    }
                    break;
                }

                case "line":     //A line contains a space seperated list of latitude-longitude pairs, with each pair separated by whitespace.
                {
                    //Set target line coordinates
                    await SetCoordinates(GeoType.Line, target, reader, feed, nodeInfo).ConfigureAwait(false);

                    break;
                }

                case "point":     //A point contains a single latitude-longitude pair, separated by whitespace.
                case "pos":
                {
                    //Set target point coordinates
                    await SetCoordinates(GeoType.Point, target, reader, feed, nodeInfo).ConfigureAwait(false);

                    break;
                }

                case "posList":
                {
                    switch (parent.Peek().LocalName)
                    {
                    case "LinearRing":             //A polygon contains a space seperated list of latitude-longitude pairs, with each pair separated by whitespace.
                    {
                        //Set target polygon coordinates
                        await SetCoordinates(GeoType.Polygon, target, reader, feed, nodeInfo).ConfigureAwait(false);

                        break;
                    }

                    case "LineString":             //A line contains a space seperated list of latitude-longitude pairs, with each pair separated by whitespace.
                    {
                        //Set target line coordinates
                        await SetCoordinates(GeoType.Line, target, reader, feed, nodeInfo).ConfigureAwait(false);

                        break;
                    }
                    }
                    break;
                }

                case "polygon":     //A polygon contains a space seperated list of latitude-longitude pairs, with each pair separated by whitespace.
                {
                    //Set target polygon coordinates
                    await SetCoordinates(GeoType.Polygon, target, reader, feed, nodeInfo).ConfigureAwait(false);

                    break;
                }

                case "radius":
                {
                    //Init
                    var content = await reader.ReadStartElementAndContentAsStringAsync().ConfigureAwait(false);

                    //Attempt to parse radius.
                    if (int.TryParse(content, out var radius))
                    {
                        target.Radius = radius;
                    }
                    else
                    {
                        //Unknown node format
                        SetParseError(ParseErrorType.UnknownNodeFormat, nodeInfo, feed, content);
                    }
                    break;
                }

                case "relationshiptag":     //Relationship of the geographical information.
                {
                    //Attempt to parse relationship.
                    target.Relationship = await reader.ReadStartElementAndContentAsStringAsync().ConfigureAwait(false);

                    break;
                }

                case "Envelope":
                case "exterior":
                case "LinearRing":
                case "LineString":
                case "Point":
                case "where": break;

                default:     //Unknown feed/item node, continue to next.
                {
                    result = false;
                    if (root)
                    {
                        SetParseError(ParseErrorType.UnknownNode, nodeInfo, feed);
                    }
                    break;
                }
                }
            }

            //Return result
            return(result);
        }