/// <summary>
        /// Reads the atom:category element in the entry content.
        /// </summary>
        /// <param name="entryState">The reader entry state for the entry being read.</param>
        /// <remarks>
        /// Pre-Condition:  XmlNodeType.Element (atom:category) - the atom:category element to read.
        /// Post-Condition: Any                                 - the node after the atom:category which was read.
        /// </remarks>
        internal void ReadAtomCategoryElementInEntryContent(IODataAtomReaderEntryState entryState)
        {
            Debug.Assert(entryState != null, "entryState != null");
            this.AssertXmlCondition(XmlNodeType.Element);
            Debug.Assert(
                this.XmlReader.NamespaceURI == AtomConstants.AtomNamespace && this.XmlReader.LocalName == AtomConstants.AtomCategoryElementName,
                "Only atom:category element can be read by this method.");

            // Read the attributes and create the category metadata regardless if we will need it or not.
            // We can do this since there's no validation done on any of the values and thus this operation will never fail.
            // If we then decide we don't need it, we can safely throw it away.
            if (this.ReadAtomMetadata)
            {
                AtomCategoryMetadata categoryMetadata = this.ReadAtomCategoryElement();

                // No point in trying to figure out if we will need the category for EPM or not here.
                // Our EPM syndication reader must handle unneeded categories anyway (if ATOM metadata reading is on)
                // So instead of burning the cycles to compute if we need it, just store it anyway.
                entryState.AtomEntryMetadata.AddCategory(categoryMetadata);
            }
            else
            {
                // Skip the element in any case (we only ever consume attributes on it anyway).
                this.XmlReader.Skip();
            }
        }
        /// <summary>
        /// Reads an "atom:category" element and returns the data as an <seealso cref="AtomCategoryMetadata"/> object.
        /// </summary>
        /// <returns>An <seealso cref="AtomCategoryMetadata"/> object with its properties filled in according to what was found in the XML.</returns>
        /// <remarks>
        /// Pre-Condition:  XmlNodeType.Element - The start of the atom:category element.
        /// Post-Condition: Any                 - The next node after the atom:category element.
        /// </remarks>
        private AtomCategoryMetadata ReadCategoryElementInCollection()
        {
            this.AssertXmlCondition(XmlNodeType.Element);
            Debug.Assert(this.XmlReader.LocalName == AtomConstants.AtomCategoryElementName, "Expected element named 'category'.");
            Debug.Assert(this.XmlReader.NamespaceURI == AtomConstants.AtomNamespace, "Element 'category' should be in the atom namespace.");

            AtomCategoryMetadata categoryMetadata = new AtomCategoryMetadata();

            while (this.XmlReader.MoveToNextAttribute())
            {
                string attributeValue = this.XmlReader.Value;

                if (this.XmlReader.NamespaceEquals(this.EmptyNamespace))
                {
                    if (this.XmlReader.LocalNameEquals(this.AtomCategoryTermAttributeName))
                    {
                        categoryMetadata.Term = attributeValue;
                    }
                    else if (this.XmlReader.LocalNameEquals(this.AtomCategorySchemeAttributeName))
                    {
                        categoryMetadata.Scheme = attributeValue;
                    }
                    else if (this.XmlReader.LocalNameEquals(this.AtomCategoryLabelAttributeName))
                    {
                        categoryMetadata.Label = attributeValue;
                    }
                }
            }

            return(categoryMetadata);
        }
 /// <summary>
 /// Writes the type name category element for the entry.
 /// </summary>
 /// <param name="typeName">The type name to write.</param>
 /// <param name="entryMetadata">The entry metadata if available.</param>
 internal void WriteEntryTypeName(string typeName, AtomEntryMetadata entryMetadata)
 {
     if (typeName != null)
     {
         AtomCategoryMetadata mergedCategoryMetadata = ODataAtomWriterMetadataUtils.MergeCategoryMetadata(
             entryMetadata == null ? null : entryMetadata.CategoryWithTypeName,
             typeName,
             AtomConstants.ODataSchemeNamespace);
         this.atomEntryMetadataSerializer.WriteCategory(mergedCategoryMetadata);
     }
 }
        /// <summary>
        /// Reads the atom:category element.
        /// </summary>
        /// <returns>The ATOM category metadata read.</returns>
        /// <remarks>
        /// Pre-Condition:  XmlNodeType.Element (atom:category) - the atom:category element to read.
        /// Post-Condition: Any                                 - the node after the atom:category which was read.
        /// </remarks>
        internal AtomCategoryMetadata ReadAtomCategoryElement()
        {
            this.AssertXmlCondition(XmlNodeType.Element);
            Debug.Assert(
                this.XmlReader.NamespaceURI == AtomConstants.AtomNamespace && this.XmlReader.LocalName == AtomConstants.AtomCategoryElementName,
                "Only atom:category element can be read by this method.");

            AtomCategoryMetadata categoryMetadata = new AtomCategoryMetadata();

            // Read the attributes
            while (this.XmlReader.MoveToNextAttribute())
            {
                // Astoria client recognizes attributes in ATOM namespace as well when looking
                // for category/@scheme and category/@term attributes.
                // If multiple 'scheme' or 'term' attributes are present the one with non-empty namespace wins.
                if (this.XmlReader.NamespaceEquals(this.EmptyNamespace))
                {
                    switch (this.XmlReader.LocalName)
                    {
                    case AtomConstants.AtomCategorySchemeAttributeName:
                        categoryMetadata.Scheme = categoryMetadata.Scheme ?? this.XmlReader.Value;
                        break;

                    case AtomConstants.AtomCategoryTermAttributeName:
                        categoryMetadata.Term = categoryMetadata.Term ?? this.XmlReader.Value;
                        break;

                    case AtomConstants.AtomCategoryLabelAttributeName:
                        categoryMetadata.Label = this.XmlReader.Value;
                        break;

                    default:
                        // Ignore all other attributes.
                        break;
                    }
                }
            }

            // Skip the element in any case (we only ever consume attributes on it anyway).
            this.XmlReader.Skip();

            return(categoryMetadata);
        }
Exemple #5
0
        /// <summary>
        /// Creates a new <see cref="AtomCategoryMetadata"/> instance by merging the given
        /// <paramref name="categoryMetadata"/> (if any) with the specified <paramref name="term"/> and <paramref name="scheme"/>.
        /// </summary>
        /// <param name="categoryMetadata">The metadata to merge with the <paramref name="term"/> and <paramref name="scheme"/>.</param>
        /// <param name="term">The term to use in the merged metadata.</param>
        /// <param name="scheme">The scheme to use in the merged metadata.</param>
        /// <returns>A new <see cref="AtomCategoryMetadata"/> instance created by merging all the arguments.</returns>
        /// <remarks>
        /// If the <paramref name="categoryMetadata"/> already holds values for <paramref name="term"/> or <paramref name="scheme"/>
        /// this method validates that they are the same as the ones specified in the method arguments.
        /// </remarks>
        internal static AtomCategoryMetadata MergeCategoryMetadata(AtomCategoryMetadata categoryMetadata, string term, string scheme)
        {
            Debug.Assert(term != null, "term != null");
            Debug.Assert(scheme != null, "scheme != null");

            AtomCategoryMetadata mergedCategoryMetadata = new AtomCategoryMetadata(categoryMetadata);

            // Set the term.
            string metadataTerm = mergedCategoryMetadata.Term;

            if (metadataTerm != null)
            {
                // Validate that terms are the same.
                if (string.CompareOrdinal(term, metadataTerm) != 0)
                {
                    throw new ODataException(Strings.ODataAtomWriterMetadataUtils_CategoryTermsMustMatch(term, metadataTerm));
                }
            }
            else
            {
                mergedCategoryMetadata.Term = term;
            }

            // Set the scheme.
            string metadataScheme = mergedCategoryMetadata.Scheme;

            if (metadataScheme != null)
            {
                // Validate that schemes are the same.
                if (string.CompareOrdinal(scheme, metadataScheme) != 0)
                {
                    throw new ODataException(Strings.ODataAtomWriterMetadataUtils_CategorySchemesMustMatch(scheme, metadataScheme));
                }
            }
            else
            {
                mergedCategoryMetadata.Scheme = scheme;
            }

            return(mergedCategoryMetadata);
        }
Exemple #6
0
        /// <summary>
        /// Reads an atom:category element in a feed.
        /// </summary>
        /// <param name="atomFeedMetadata">The feed metadata to augment.</param>
        /// <remarks>
        /// Pre-Condition:  XmlNodeType.Element (atom:category) - the atom:category element to read.
        /// Post-Condition: Any                                 - the node after the atom:category which was read.
        /// </remarks>
        private void ReadCategoryElement(AtomFeedMetadata atomFeedMetadata)
        {
            this.AssertXmlCondition(XmlNodeType.Element);
            Debug.Assert(
                this.XmlReader.NamespaceURI == AtomConstants.AtomNamespace && this.XmlReader.LocalName == AtomConstants.AtomCategoryElementName,
                "Only atom:category element can be read by this method.");

            AtomCategoryMetadata categoryMetadata = new AtomCategoryMetadata();

            // Read the attributes
            while (this.XmlReader.MoveToNextAttribute())
            {
                if (this.XmlReader.NamespaceEquals(this.EmptyNamespace))
                {
                    switch (this.XmlReader.LocalName)
                    {
                    case AtomConstants.AtomCategorySchemeAttributeName:
                        categoryMetadata.Scheme = this.XmlReader.Value;
                        break;

                    case AtomConstants.AtomCategoryTermAttributeName:
                        categoryMetadata.Term = this.XmlReader.Value;
                        break;

                    case AtomConstants.AtomCategoryLabelAttributeName:
                        categoryMetadata.Label = this.XmlReader.Value;
                        break;

                    default:
                        // Ignore all other attributes.
                        break;
                    }
                }
            }

            atomFeedMetadata.AddCategory(categoryMetadata);

            // Skip the rest of the category element.
            this.XmlReader.Skip();
        }
Exemple #7
0
        /// <summary>
        /// Reads the atom:category element.
        /// </summary>
        /// <returns>The ATOM category metadata read.</returns>
        /// <remarks>
        /// Pre-Condition:  XmlNodeType.Element (atom:category) - the atom:category element to read.
        /// Post-Condition: Any                                 - the node after the atom:category which was read.
        /// </remarks>
        internal AtomCategoryMetadata ReadAtomCategoryElement()
        {
            this.AssertXmlCondition(XmlNodeType.Element);
            Debug.Assert(
                this.XmlReader.NamespaceURI == AtomConstants.AtomNamespace && this.XmlReader.LocalName == AtomConstants.AtomCategoryElementName,
                "Only atom:category element can be read by this method.");

            AtomCategoryMetadata categoryMetadata = new AtomCategoryMetadata();

            // Read the attributes
            while (this.XmlReader.MoveToNextAttribute())
            {
                if (this.XmlReader.NamespaceEquals(this.EmptyNamespace))
                {
                    switch (this.XmlReader.LocalName)
                    {
                    case AtomConstants.AtomCategorySchemeAttributeName:
                        categoryMetadata.Scheme = categoryMetadata.Scheme ?? this.XmlReader.Value;
                        break;

                    case AtomConstants.AtomCategoryTermAttributeName:
                        categoryMetadata.Term = categoryMetadata.Term ?? this.XmlReader.Value;
                        break;

                    case AtomConstants.AtomCategoryLabelAttributeName:
                        categoryMetadata.Label = this.XmlReader.Value;
                        break;

                    default:
                        // Ignore all other attributes.
                        break;
                    }
                }
            }

            // Skip the element in any case (we only ever consume attributes on it anyway).
            this.XmlReader.Skip();

            return(categoryMetadata);
        }
        private static AtomCategoryMetadata CreateCategoryMetadata(IEnumerable<XmlTreeAnnotation> children)
        {
            AtomCategoryMetadata metadata = null;

            foreach (XmlTreeAnnotation epmTree in children.Where(child => child.IsAttribute))
            {
                if (string.IsNullOrEmpty(epmTree.NamespaceName))
                {
                    if (metadata == null)
                    {
                        metadata = new AtomCategoryMetadata();
                    }

                    string localName = epmTree.LocalName;
                    if (localName == TestAtomConstants.AtomCategoryTermAttributeName)
                    {
                        metadata.Term = epmTree.PropertyValue;
                    }
                    else if (localName == TestAtomConstants.AtomCategorySchemeAttributeName)
                    {
                        metadata.Scheme = string.IsNullOrEmpty(epmTree.PropertyValue) ? null : epmTree.PropertyValue;
                    }
                    else if (localName == TestAtomConstants.AtomCategoryLabelAttributeName)
                    {
                        metadata.Label = epmTree.PropertyValue;
                    }
                    else
                    {
                        throw new NotSupportedException("Unsupported metadata '" + localName + "' found for category.");
                    }
                }
            }

            return metadata;
        }
        /// <summary>
        /// Creates a new <see cref="AtomCategoryMetadata"/> instance by merging the given
        /// <paramref name="categoryMetadata"/> (if any) with the specified <paramref name="term"/> and <paramref name="scheme"/>.
        /// </summary>
        /// <param name="categoryMetadata">The metadata to merge with the <paramref name="term"/> and <paramref name="scheme"/>.</param>
        /// <param name="term">The term to use in the merged metadata.</param>
        /// <param name="scheme">The scheme to use in the merged metadata.</param>
        /// <returns>A new <see cref="AtomCategoryMetadata"/> instance created by merging all the arguments.</returns>
        /// <remarks>
        /// If the <paramref name="categoryMetadata"/> already holds values for <paramref name="term"/> or <paramref name="scheme"/>
        /// this method validates that they are the same as the ones specified in the method arguments.
        /// </remarks>
        internal static AtomCategoryMetadata MergeCategoryMetadata(AtomCategoryMetadata categoryMetadata, string term, string scheme)
        {
            Debug.Assert(term != null, "term != null");
            Debug.Assert(scheme != null, "scheme != null");

            AtomCategoryMetadata mergedCategoryMetadata = new AtomCategoryMetadata(categoryMetadata);

            // Set the term.
            string metadataTerm = mergedCategoryMetadata.Term;
            if (metadataTerm != null)
            {
                // Validate that terms are the same.
                if (string.CompareOrdinal(term, metadataTerm) != 0)
                {
                    throw new ODataException(Strings.ODataAtomWriterMetadataUtils_CategoryTermsMustMatch(term, metadataTerm));
                }
            }
            else
            {
                mergedCategoryMetadata.Term = term;
            }

            // Set the scheme.
            string metadataScheme = mergedCategoryMetadata.Scheme;
            if (metadataScheme != null)
            {
                // Validate that schemes are the same.
                if (string.CompareOrdinal(scheme, metadataScheme) != 0)
                {
                    throw new ODataException(Strings.ODataAtomWriterMetadataUtils_CategorySchemesMustMatch(scheme, metadataScheme));
                }
            }
            else
            {
                mergedCategoryMetadata.Scheme = scheme;
            }

            return mergedCategoryMetadata;
        }
        /// <summary>
        /// Reads an "atom:category" element and returns the data as an <seealso cref="AtomCategoryMetadata"/> object.
        /// </summary>
        /// <returns>An <seealso cref="AtomCategoryMetadata"/> object with its properties filled in according to what was found in the XML.</returns>
        /// <remarks>
        /// Pre-Condition:  XmlNodeType.Element - The start of the atom:category element.
        /// Post-Condition: Any                 - The next node after the atom:category element.
        /// </remarks>
        private AtomCategoryMetadata ReadCategoryElementInCollection()
        {
            this.AssertXmlCondition(XmlNodeType.Element);
            Debug.Assert(this.XmlReader.LocalName == AtomConstants.AtomCategoryElementName, "Expected element named 'category'.");
            Debug.Assert(this.XmlReader.NamespaceURI == AtomConstants.AtomNamespace, "Element 'category' should be in the atom namespace.");

            AtomCategoryMetadata categoryMetadata = new AtomCategoryMetadata();

            while (this.XmlReader.MoveToNextAttribute())
            {
                string attributeValue = this.XmlReader.Value;

                if (this.XmlReader.NamespaceEquals(this.EmptyNamespace))
                {
                    if (this.XmlReader.LocalNameEquals(this.AtomCategoryTermAttributeName))
                    {
                        categoryMetadata.Term = attributeValue;
                    }
                    else if (this.XmlReader.LocalNameEquals(this.AtomCategorySchemeAttributeName))
                    {
                        categoryMetadata.Scheme = attributeValue;
                    }
                    else if (this.XmlReader.LocalNameEquals(this.AtomCategoryLabelAttributeName))
                    {
                        categoryMetadata.Label = attributeValue;
                    }
                }
            }

            return categoryMetadata;
        }
        /// <summary>
        /// Reads an atom:category element in a feed.
        /// </summary>
        /// <param name="atomFeedMetadata">The feed metadata to augment.</param>
        /// <remarks>
        /// Pre-Condition:  XmlNodeType.Element (atom:category) - the atom:category element to read.
        /// Post-Condition: Any                                 - the node after the atom:category which was read.
        /// </remarks>
        private void ReadCategoryElement(AtomFeedMetadata atomFeedMetadata)
        {
            this.AssertXmlCondition(XmlNodeType.Element);
            Debug.Assert(
                this.XmlReader.NamespaceURI == AtomConstants.AtomNamespace && this.XmlReader.LocalName == AtomConstants.AtomCategoryElementName,
                "Only atom:category element can be read by this method.");

            AtomCategoryMetadata categoryMetadata = new AtomCategoryMetadata();

            // Read the attributes
            while (this.XmlReader.MoveToNextAttribute())
            {
                if (this.XmlReader.NamespaceEquals(this.EmptyNamespace))
                {
                    switch (this.XmlReader.LocalName)
                    {
                        case AtomConstants.AtomCategorySchemeAttributeName:
                            categoryMetadata.Scheme = this.XmlReader.Value;
                            break;
                        case AtomConstants.AtomCategoryTermAttributeName:
                            categoryMetadata.Term = this.XmlReader.Value;
                            break;
                        case AtomConstants.AtomCategoryLabelAttributeName:
                            categoryMetadata.Label = this.XmlReader.Value;
                            break;
                        default:
                            // Ignore all other attributes.
                            break;
                    }
                }
            }

            atomFeedMetadata.AddCategory(categoryMetadata);

            // Skip the rest of the category element.
            this.XmlReader.Skip();
        }
        /// <summary>
        /// Writes the 'atom:category' element given category metadata.
        /// </summary>
        /// <param name="category">The category information to write.</param>
        internal void WriteCategory(AtomCategoryMetadata category)
        {
            Debug.Assert(category != null, "Category must not be null.");

            this.WriteCategory(AtomConstants.AtomNamespacePrefix, category.Term, category.Scheme, category.Label);
        }
        public void EntryMetadataWriterTest()
        {
            const string testPublished = "2010-10-20T20:10:00Z";
            AtomTextConstruct testRights = new AtomTextConstruct { Text = "Copyright Data Fx team." };
            AtomTextConstruct testSummary = new AtomTextConstruct { Text = "Test summary." };
            AtomTextConstruct testTitle = new AtomTextConstruct { Text = "Test title" };
            const string testUpdated = "2010-11-01T00:04:00Z";
            const string testIcon = "http://odata.org/icon";
            const string testSourceId = "http://odata.org/id/random";
            const string testLogo = "http://odata.org/logo";
            AtomTextConstruct testSubtitle = new AtomTextConstruct { Text = "Test subtitle." };

            const string testAuthorName = "Test Author 1";
            const string testAuthorEmail = "*****@*****.**";
            const string testAuthorUri = "http://odata.org/authors/1";

            var testAuthors = new AtomPersonMetadata[]
                {
                    new AtomPersonMetadata()
                    {
                        Email = testAuthorEmail,
                        Name = testAuthorName,
                        Uri = new Uri(testAuthorUri)
                    }
                };

            var testAuthors2 = new AtomPersonMetadata[0];

            const string testCategoryTerm = "Test category 1 term";
            const string testCategoryLabel = "Test category 1 label";
            const string testCategoryScheme = "http://odata.org/categories/1";

            var testCategories = new AtomCategoryMetadata[]
                {
                    new AtomCategoryMetadata()
                    {
                        Term = testCategoryTerm,
                        Label = testCategoryLabel,
                        Scheme = testCategoryScheme
                    }
                };

            const string testContributorName = "Test Contributor 1";
            const string testContributorEmail = "*****@*****.**";
            const string testContributorUri = "http://odata.org/contributors/1";

            var testContributors = new AtomPersonMetadata[]
                {
                    new AtomPersonMetadata()
                    {
                        Email = testContributorEmail,
                        Name = testContributorName,
                        Uri = new Uri(testContributorUri)
                    }
                };

            const string testGeneratorName = "Test generator";
            const string testGeneratorUri = "http://odata.org/generator";
            const string testGeneratorVersion = "3.0";

            var testGenerator = new AtomGeneratorMetadata()
                {
                    Name = testGeneratorName,
                    Uri = new Uri(testGeneratorUri),
                    Version = testGeneratorVersion
                };

            const string testLinkRelation = "http://odata.org/links/1";
            const string testLinkTitle = "Test link 1";
            const string testLinkHref = "http://odata.org/links/1";
            const string testLinkHrefLang = "de-AT";
            int? testLinkLength = 999;
            const string testLinkMediaType = "image/png";

            var testLinks = new AtomLinkMetadata[] 
                {
                    new AtomLinkMetadata()
                    {
                        Relation = testLinkRelation,
                        Title = testLinkTitle,
                        Href = new Uri(testLinkHref),
                        HrefLang = testLinkHrefLang,
                        Length = testLinkLength,
                        MediaType = testLinkMediaType
                    }
                };

            AtomFeedMetadata testSource = new AtomFeedMetadata()
            {
                Authors = testAuthors,
                Categories = testCategories,
                Contributors = testContributors,
                Generator = testGenerator,
                Icon = new Uri(testIcon),
                SourceId = new Uri(testSourceId),
                Links = testLinks,
                Logo = new Uri(testLogo),
                Rights = testRights,
                Subtitle = testSubtitle,
                Title = testTitle,
                Updated = DateTimeOffset.Parse(testUpdated)
            };

            Func<string, Func<XElement, XElement>> fragmentExtractor = (localName) => (e) => e.Element(TestAtomConstants.AtomXNamespace + localName);

            // TODO, ckerer: specify an Id via metadata if the entry does not specify one; we first have to decide what rules
            //               we want to apply to merging of metadata and ODataLib OM data.
            var testCases = new[] {
                new { // specify an author via metadata
                    CustomizeMetadata = new Action<AtomEntryMetadata>(metadata => metadata.Authors = testAuthors),
                    Xml = string.Join(
                        "$(NL)",
                        @"<author xmlns=""" + TestAtomConstants.AtomNamespace + @""">",
                        @"  <name>" + testAuthorName + @"</name>",
                        @"  <uri>" + testAuthorUri + @"</uri>",
                        @"  <email>" + testAuthorEmail + @"</email>",
                        @"</author>"),
                    Extractor = fragmentExtractor(TestAtomConstants.AtomAuthorElementName)
                },
                new { // specify an empty author array via metadata
                    CustomizeMetadata = new Action<AtomEntryMetadata>(metadata => metadata.Authors = testAuthors2),
                    Xml = string.Join(
                        "$(NL)",
                        @"<author xmlns=""" + TestAtomConstants.AtomNamespace + @""">",
                        @"  <name />",
                        @"</author>"),
                    Extractor = fragmentExtractor(TestAtomConstants.AtomAuthorElementName)
                },
                new { // specify no authors via metadata
                    CustomizeMetadata = new Action<AtomEntryMetadata>(metadata => metadata.Authors = null),
                    Xml = string.Join(
                        "$(NL)",
                        @"<author xmlns=""" + TestAtomConstants.AtomNamespace + @""">",
                        @"$(Indent)<name />",
                        @"</author>"),
                    Extractor = fragmentExtractor(TestAtomConstants.AtomAuthorElementName)
                },
                new { // specify a category via metadata
                    CustomizeMetadata = new Action<AtomEntryMetadata>(metadata => metadata.Categories = testCategories),
                    Xml = @"<category term=""" + testCategoryTerm + @""" scheme=""" + testCategoryScheme + @""" label=""" + testCategoryLabel + @""" xmlns=""" + TestAtomConstants.AtomNamespace + @""" />",
                    Extractor = fragmentExtractor(TestAtomConstants.AtomCategoryElementName)
                },
                new { // specify a contributor via metadata
                    CustomizeMetadata = new Action<AtomEntryMetadata>(metadata => metadata.Contributors = testContributors),
                    Xml = string.Join(
                        "$(NL)",
                        @"<contributor xmlns=""" + TestAtomConstants.AtomNamespace + @""">",
                        @"  <name>" + testContributorName + @"</name>",
                        @"  <uri>" + testContributorUri + @"</uri>",
                        @"  <email>" + testContributorEmail + @"</email>",
                        @"</contributor>"),
                    Extractor = fragmentExtractor(TestAtomConstants.AtomContributorElementName)
                },
                new { // specify a link via metadata
                    CustomizeMetadata = new Action<AtomEntryMetadata>(metadata => metadata.Links = testLinks),
                    Xml = @"<link rel=""" + testLinkRelation + @""" type = """ + testLinkMediaType + @""" title=""" + testLinkTitle + @""" href=""" + testLinkHref + @""" hreflang=""" + testLinkHrefLang + @""" length=""" + testLinkLength + @"""  xmlns=""" + TestAtomConstants.AtomNamespace + @"""/>",
                    Extractor = new Func<XElement, XElement>(
                            (e) => e.Elements(TestAtomConstants.AtomXNamespace + TestAtomConstants.AtomLinkElementName)
                                    .Where(l => l.Attribute(TestAtomConstants.AtomLinkRelationAttributeName).Value != TestAtomConstants.AtomSelfRelationAttributeValue)
                                    .Single())
                },
                new { // specify a published date via metadata
                    CustomizeMetadata = new Action<AtomEntryMetadata>(metadata => metadata.Published = DateTimeOffset.Parse(testPublished)),
                    Xml = @"<published xmlns=""" + TestAtomConstants.AtomNamespace + @""">" + testPublished + @"</published>",
                    Extractor = fragmentExtractor(TestAtomConstants.AtomPublishedElementName)
                },
                new { // specify rights via metadata
                    CustomizeMetadata = new Action<AtomEntryMetadata>(metadata => metadata.Rights = testRights),
                    Xml = @"<rights type=""text"" xmlns=""" + TestAtomConstants.AtomNamespace + @""">" + testRights.Text + @"</rights>",
                    Extractor = fragmentExtractor(TestAtomConstants.AtomRightsElementName)
                },
                new { // specify a source via metadata
                    CustomizeMetadata = new Action<AtomEntryMetadata>(metadata => metadata.Source = testSource),
                    Xml = string.Join(
                        "$(NL)",
                        @"<source xmlns=""" + TestAtomConstants.AtomNamespace + @""">",
                        @"  <id>" + testSourceId + "</id>",
                        @"  <title type=""text"">" + testTitle.Text + @"</title>",
                        @"  <subtitle type=""text"">" + testSubtitle.Text + @"</subtitle>",
                        @"  <updated>" + testUpdated + @"</updated>",
                        @"  <link rel=""" + testLinkRelation + @""" type = """ + testLinkMediaType + @""" title=""" + testLinkTitle + @""" href=""" + testLinkHref + @""" hreflang=""" + testLinkHrefLang + @""" length=""" + testLinkLength + @"""/>",
                        @"  <category term=""" + testCategoryTerm + @""" scheme=""" + testCategoryScheme + @""" label=""" + testCategoryLabel + @""" />",
                        @"  <logo>" + testLogo + @"</logo>",
                        @"  <rights type=""text"">" + testRights.Text + @"</rights>",
                        @"  <contributor>",
                        @"    <name>" + testContributorName + @"</name>",
                        @"    <uri>" + testContributorUri + @"</uri>",
                        @"    <email>" + testContributorEmail + @"</email>",
                        @"  </contributor>",
                        @"  <generator uri=""" + testGeneratorUri + @""" version=""" +testGeneratorVersion + @""">" + testGeneratorName + @"</generator>",
                        @"  <icon>" + testIcon + @"</icon>",
                        @"  <author>",
                        @"    <name>" + testAuthorName + @"</name>",
                        @"    <uri>" + testAuthorUri + @"</uri>",
                        @"    <email>" + testAuthorEmail + @"</email>",
                        @"  </author>",
                        @"</source>"),
                    Extractor = fragmentExtractor(TestAtomConstants.AtomSourceElementName)
                },
                new { // specify default feed metadata as source
                    CustomizeMetadata = new Action<AtomEntryMetadata>(metadata => metadata.Source = new AtomFeedMetadata()),
                    Xml = string.Join(
                        "$(NL)",
                        @"<source xmlns=""" + TestAtomConstants.AtomNamespace + @""">",
                        @"  <id />",
                        @"  <title />",
                        @"  <updated />",
                        @"</source>"),
                    Extractor = new Func<XElement, XElement>(result => {
                        var source = fragmentExtractor(TestAtomConstants.AtomSourceElementName)(result);
                        // Remove the value of updates as it can't be reliably predicted
                        source.Element(TestAtomConstants.AtomXNamespace + TestAtomConstants.AtomUpdatedElementName).Nodes().Remove();
                        return source;
                    })
                },
                new { // specify a summary via metadata
                    CustomizeMetadata = new Action<AtomEntryMetadata>(metadata => metadata.Summary = testSummary),
                    Xml = @"<summary type=""text"" xmlns=""" + TestAtomConstants.AtomNamespace + @""">" + testSummary.Text + @"</summary>",
                    Extractor = fragmentExtractor(TestAtomConstants.AtomSummaryElementName)
                },
                new { // specify a title via metadata
                    CustomizeMetadata = new Action<AtomEntryMetadata>(metadata => metadata.Title = testTitle),
                    Xml = @"<title type=""text"" xmlns=""" + TestAtomConstants.AtomNamespace + @""">" + testTitle.Text + @"</title>",
                    Extractor = fragmentExtractor(TestAtomConstants.AtomTitleElementName)
                },
                new { // specify an updated date via metadata
                    CustomizeMetadata = new Action<AtomEntryMetadata>(metadata => metadata.Updated = DateTimeOffset.Parse(testUpdated)),
                    Xml = @"<updated xmlns=""" + TestAtomConstants.AtomNamespace + @""">" + testUpdated + @"</updated>",
                    Extractor = fragmentExtractor(TestAtomConstants.AtomUpdatedElementName)
                },
            };

            // Convert test cases to test descriptions
            IEnumerable<Func<ODataEntry>> entryCreators = new Func<ODataEntry>[]
                {
                    () => ObjectModelUtils.CreateDefaultEntry(),
                    () => ObjectModelUtils.CreateDefaultEntryWithAtomMetadata(),
                };
            var testDescriptors = testCases.SelectMany(testCase =>
                entryCreators.Select(entryCreator =>
                {
                    ODataEntry entry = entryCreator();
                    AtomEntryMetadata metadata = entry.Atom();
                    this.Assert.IsNotNull(metadata, "Expected default entry metadata on a default entry.");
                    testCase.CustomizeMetadata(metadata);
                    return new PayloadWriterTestDescriptor<ODataItem>(this.Settings, entry, testConfiguration =>
                        new AtomWriterTestExpectedResults(this.Settings.ExpectedResultSettings) { Xml = testCase.Xml, FragmentExtractor = testCase.Extractor });
                }));

            this.CombinatorialEngineProvider.RunCombinations(
                testDescriptors.PayloadCases(WriterPayloads.EntryPayloads),
                this.WriterTestConfigurationProvider.AtomFormatConfigurations,
                (testDescriptor, testConfiguration) =>
                {
                    testConfiguration = testConfiguration.Clone();
                    testConfiguration.MessageWriterSettings.SetServiceDocumentUri(ServiceDocumentUri);

                    TestWriterUtils.WriteAndVerifyODataPayload(testDescriptor, testConfiguration, this.Assert, this.Logger);
                });
        }
 /// <summary>
 /// Visits an ATOM category metadata.
 /// </summary>
 /// <param name="atomCategoryMetadata">The category metadata to visit.</param>
 protected virtual void VisitAtomCategoryMetadata(AtomCategoryMetadata atomCategoryMetadata)
 {
 }
        /// <summary>
        /// Adds a new category to feed metadata.
        /// </summary>
        /// <param name="feedMetadata">The feed metadata to add the category to.</param>
        /// <param name="categoryMetadata">The category metadata to add.</param>
        internal static void AddCategory(this AtomFeedMetadata feedMetadata, AtomCategoryMetadata categoryMetadata)
        {
            Debug.Assert(feedMetadata != null, "feedMetadata != null");
            Debug.Assert(categoryMetadata != null, "categoryMetadata != null");

            feedMetadata.Categories = feedMetadata.Categories.ConcatToReadOnlyEnumerable("Categories", categoryMetadata);
        }
        /// <summary>
        /// Adds a new category to entry metadata.
        /// </summary>
        /// <param name="entryMetadata">The entry metadata to add the category to.</param>
        /// <param name="categoryMetadata">The category metadata to add.</param>
        internal static void AddCategory(this AtomEntryMetadata entryMetadata, AtomCategoryMetadata categoryMetadata)
        {
            Debug.Assert(entryMetadata != null, "entryMetadata != null");
            Debug.Assert(categoryMetadata != null, "categoryMetadata != null");

            entryMetadata.Categories = entryMetadata.Categories.ConcatToReadOnlyEnumerable("Categories", categoryMetadata);
        }
        /// <summary>
        /// Reads the atom:category element.
        /// </summary>
        /// <returns>The ATOM category metadata read.</returns>
        /// <remarks>
        /// Pre-Condition:  XmlNodeType.Element (atom:category) - the atom:category element to read.
        /// Post-Condition: Any                                 - the node after the atom:category which was read.
        /// </remarks>
        internal AtomCategoryMetadata ReadAtomCategoryElement()
        {
            this.AssertXmlCondition(XmlNodeType.Element);
            Debug.Assert(
                this.XmlReader.NamespaceURI == AtomConstants.AtomNamespace && this.XmlReader.LocalName == AtomConstants.AtomCategoryElementName,
                "Only atom:category element can be read by this method.");

            AtomCategoryMetadata categoryMetadata = new AtomCategoryMetadata();

            // Read the attributes
            while (this.XmlReader.MoveToNextAttribute())
            {
                // Astoria client recognizes attributes in ATOM namespace as well when looking 
                // for category/@scheme and category/@term attributes.
                // If multiple 'scheme' or 'term' attributes are present the one with non-empty namespace wins.
                if (this.XmlReader.NamespaceEquals(this.EmptyNamespace))
                {
                    switch (this.XmlReader.LocalName)
                    {
                        case AtomConstants.AtomCategorySchemeAttributeName:
                            categoryMetadata.Scheme = categoryMetadata.Scheme ?? this.XmlReader.Value;
                            break;
                        case AtomConstants.AtomCategoryTermAttributeName:
                            categoryMetadata.Term = categoryMetadata.Term ?? this.XmlReader.Value;
                            break;
                        case AtomConstants.AtomCategoryLabelAttributeName:
                            categoryMetadata.Label = this.XmlReader.Value;
                            break;
                        default:
                            // Ignore all other attributes.
                            break;
                    }
                }
            }

            // Skip the element in any case (we only ever consume attributes on it anyway).
            this.XmlReader.Skip();

            return categoryMetadata;
        }
        /// <summary>
        /// Writes the 'atom:category' element given category metadata.
        /// </summary>
        /// <param name="category">The category information to write.</param>
        internal void WriteCategory(AtomCategoryMetadata category)
        {
            Debug.Assert(category != null, "Category must not be null.");

            this.WriteCategory(AtomConstants.AtomNamespacePrefix, category.Term, category.Scheme, category.Label);
        }