AtomParser.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ 4.0 / 4.0 / DEVDIV_TFS / Dev10 / Releases / RTMRel / ndp / fx / src / DataWeb / Client / System / Data / Services / Client / AtomParser.cs / 1305376 / AtomParser.cs

                            //---------------------------------------------------------------------- 
// 
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// 
//  
// Parses an XML stream into structured AtomEntry or related instances.
//  
//--------------------------------------------------------------------- 

namespace System.Data.Services.Client 
{
    #region Namespaces.

    using System; 
    using System.Collections;
    using System.Collections.Generic; 
    using System.Diagnostics; 
    using System.Linq;
    using System.Reflection; 
    using System.Xml;
    using System.Xml.Linq;
    using System.Text;
 
    #endregion Namespaces.
 
    /// Parser for DataService payloads (mostly ATOM). 
    /// 
    /// There are four types of documents parsed: 
    /// 1. A single non-entity value.
    /// 2. A list of non-entity values.
    /// 3. An entity.
    /// 4. A feed. 
    ///
    /// In case of (1), the parser will go through these states: 
    ///  None -> Custom -> Finished 
    ///
    /// In case of (2), the parser will go through these states: 
    ///  None -> Custom -> Custom -> Finished
    ///
    /// In case of (3), the parser will go through these states:
    ///  None -> Entry -> Finished 
    ///
    /// In case of (4), the parser will go through these states: 
    ///  None -> (FeedData | Entry) -> (FeedData | Entry) -> Finished 
    ///
    /// Note that the parser will always stop on 'top-level' packets; all recursive data 
    /// structures are handled internally.
    /// 
    [DebuggerDisplay("AtomParser {kind} {reader}")]
    internal class AtomParser 
    {
        #region Private fields. 
 
        /// Callback invoked each time an ATOM entry is found.
        ///  
        /// This callback takes the current XmlReader and returns a
        /// subtree XmlReader and an object that is assigned to the
        /// entry's Tag property.
        ///  
        private readonly Func> entryCallback;
 
        /// Stack of available XmlReaders. 
        private readonly Stack readers;
 
        /// Scheme used to find type information on ATOM category elements.
        private readonly string typeScheme;

        /// ATOM entry being parsed. 
        private AtomEntry entry;
 
        /// ATOM feed being parsed. 
        private AtomFeed feed;
 
        /// Current data kind (nothing, entry, feed, custom-top-level-thingy, etc).
        private AtomDataKind kind;

        /// Current . 
        private XmlReader reader;
 
        /// The data namespace 
        private string currentDataNamespace;
 
        #endregion Private fields.

        #region Constructors.
 
        /// Initializes a new  instance.
        ///  to parse content from. 
        ///  
        /// Callback invoked each time an ATOM entry is found; see the comments
        /// on the entryCallback field. 
        /// 
        /// 
        /// Scheme used to find type information on ATOM category elements.
        ///  
        /// The xml document's DataWeb Namespace
        internal AtomParser(XmlReader reader, Func> entryCallback, string typeScheme, string currentDataNamespace) 
        { 
            Debug.Assert(reader != null, "reader != null");
            Debug.Assert(typeScheme != null, "typeScheme != null"); 
            Debug.Assert(entryCallback != null, "entryCallback != null");
            Debug.Assert(!String.IsNullOrEmpty(currentDataNamespace), "currentDataNamespace is empty or null");

            this.reader = reader; 
            this.readers = new Stack();
            this.entryCallback = entryCallback; 
            this.typeScheme = typeScheme; 
            this.currentDataNamespace = currentDataNamespace;
 
            Debug.Assert(this.kind == AtomDataKind.None, "this.kind == AtomDataKind.None -- otherwise not initialized correctly");
        }

        #endregion Constructors. 

        #region Internal properties. 
 
        /// Entry being materialized; possibly null.
        internal AtomEntry CurrentEntry 
        {
            get
            {
                return this.entry; 
            }
        } 
 
        /// Feed being materialized; possibly null.
        internal AtomFeed CurrentFeed 
        {
            get
            {
                return this.feed; 
            }
        } 
 
        /// Kind of ATOM data available on the parser.
        internal AtomDataKind DataKind 
        {
            get
            {
                return this.kind; 
            }
        } 
 
        /// 
        /// Returns true if the current element is in the data web namespace 
        /// 
        internal bool IsDataWebElement
        {
            get { return this.reader.NamespaceURI == this.currentDataNamespace; } 
        }
 
        #endregion Internal properties. 

        #region Internal methods. 

        /// 
        /// Creates an  instance for ATOM entries.
        ///  
        /// Reader being used.
        ///  
        /// A pair of an XmlReader instance and an object to be assigned 
        /// to the Tag on the entry (available for materialization callbacks
        /// later in the pipeline). 
        /// 
        /// 
        /// A no-op implementation would do this instead:
        /// 
        /// return new KeyValuePair<XmlReader, object>(reader.ReadSubtree(), null);
        ///  
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "not required")] 
        internal static KeyValuePair XElementBuilderCallback(XmlReader reader)
        { 
            Debug.Assert(reader != null, "reader != null");
            Debug.Assert(reader is Xml.XmlWrappingReader, "reader must be a instance of XmlWrappingReader");

            string readerBaseUri = reader.BaseURI; 
            XElement element = XElement.Load(reader.ReadSubtree(), LoadOptions.None);
            return new KeyValuePair(Xml.XmlWrappingReader.CreateReader(readerBaseUri, element.CreateReader()), element); 
        } 

        #endregion Internal methods. 

        #region Internal methods.

        /// Consumes the next chunk of content from the underlying XML reader. 
        /// 
        /// true if another piece of content is available, identified by DataKind. 
        /// false if there is no more content. 
        /// 
        internal bool Read() 
        {
            // When an external caller 'insists', we'll come all the way down (which is the 'most local'
            // scope at which this is known), and unwind as a no-op.
            if (this.DataKind == AtomDataKind.Finished) 
            {
                return false; 
            } 

            while (this.reader.Read()) 
            {
                if (ShouldIgnoreNode(this.reader))
                {
                    continue; 
                }
 
                Debug.Assert( 
                    this.reader.NodeType == XmlNodeType.Element || this.reader.NodeType == XmlNodeType.EndElement,
                    "this.reader.NodeType == XmlNodeType.Element || this.reader.NodeType == XmlNodeType.EndElement -- otherwise we should have ignored or thrown"); 

                AtomDataKind readerData = ParseStateForReader(this.reader);

                if (this.reader.NodeType == XmlNodeType.EndElement) 
                {
                    // The only case in which we expect to see an end-element at the top level 
                    // is for a feed. Custom elements and entries should be consumed by 
                    // their own parsing methods. However we are tolerant of additional EndElements,
                    // which at this point mean we have nothing else to consume. 
                    break;
                }

                switch (readerData) 
                {
                    case AtomDataKind.Custom: 
                        if (this.DataKind == AtomDataKind.None) 
                        {
                            this.kind = AtomDataKind.Custom; 
                            return true;
                        }
                        else
                        { 
                            MaterializeAtom.SkipToEnd(this.reader);
                            continue; 
                        } 

                    case AtomDataKind.Entry: 
                        this.kind = AtomDataKind.Entry;
                        this.ParseCurrentEntry(out this.entry);
                        return true;
 
                    case AtomDataKind.Feed:
                        if (this.DataKind == AtomDataKind.None) 
                        { 
                            this.feed = new AtomFeed();
                            this.kind = AtomDataKind.Feed; 
                            return true;
                        }

                        throw new InvalidOperationException(Strings.AtomParser_FeedUnexpected); 

                    case AtomDataKind.FeedCount: 
                        this.ParseCurrentFeedCount(); 
                        break;
 
                    case AtomDataKind.PagingLinks:
                        if (this.feed == null)
                        {
                            // paging link outside of feed? 
                            throw new InvalidOperationException(Strings.AtomParser_PagingLinkOutsideOfFeed);
                        } 
 
                        this.kind = AtomDataKind.PagingLinks;
                        this.ParseCurrentFeedPagingLinks(); 
                        return true;

                    default:
                        Debug.Assert(false, "Atom Parser is in a wrong state...Did you add a new AtomDataKind?"); 
                        break;
                } 
            } 

            this.kind = AtomDataKind.Finished; 
            this.entry = null;
            return false;
        }
 
        /// Reads the current property value from the reader.
        /// A structured property instance. 
        ///  
        /// This method should only be called for top-level complex properties.
        /// 
        /// For top-level primitive values, 
        /// should be used to preserve V1 behavior in which mixed-content
        /// XML elements are allowed.
        ///  
        internal AtomContentProperty ReadCurrentPropertyValue()
        { 
            Debug.Assert( 
                this.kind == AtomDataKind.Custom,
                "this.kind == AtomDataKind.Custom -- otherwise caller shouldn't invoke ReadCurrentPropertyValue"); 
            return this.ReadPropertyValue();
        }

        /// Reads the current property value from the reader as a string. 
        /// A structured property instance.
        ///  
        /// This method should only be called for top-level primitive types. 
        ///
        /// For top-level complex type values,  
        /// should be used to capture all information.
        /// 
        internal string ReadCustomElementString()
        { 
            Debug.Assert(
                this.kind == AtomDataKind.Custom, 
                "this.kind == AtomDataKind.Custom -- otherwise caller shouldn't invoke ReadCustomElementString"); 
            return MaterializeAtom.ReadElementString(this.reader, true);
        } 

        /// Replaces the current reader with the specified reader.
        /// New reader to use.
        ///  
        /// This is a very odd method, here only to allow inline count to
        /// read everything ahead and "reopen" where we are. No checks are 
        /// done as to whether we are in a safe place to do so. 
        /// 
        internal void ReplaceReader(XmlReader newReader) 
        {
            Debug.Assert(newReader != null, "newReader != null");
            this.reader = newReader;
        } 

        #endregion Internal methods. 
 
        #region Private methods.
 
        /// 
        /// Determines what the parse state should be for the specified
        /// .
        ///  
        /// Reader to check.
        /// The data kind derived from the current element. 
        ///  
        /// Note that no previous state is considered, so state transitions
        /// aren't handled by the method - instead, certain known elements 
        /// are mapped to parser states.
        /// 
        private static AtomDataKind ParseStateForReader(XmlReader reader)
        { 
            Debug.Assert(reader != null, "reader != null");
            Debug.Assert( 
                reader.NodeType == XmlNodeType.Element || reader.NodeType == XmlNodeType.EndElement, 
                "reader.NodeType == XmlNodeType.Element || EndElement -- otherwise can't determine");
 
            AtomDataKind result = AtomDataKind.Custom;
            string elementName = reader.LocalName;
            string namespaceURI = reader.NamespaceURI;
            if (Util.AreSame(XmlConstants.AtomNamespace, namespaceURI)) 
            {
                if (Util.AreSame(XmlConstants.AtomEntryElementName, elementName)) 
                { 
                    result = AtomDataKind.Entry;
                } 
                else if (Util.AreSame(XmlConstants.AtomFeedElementName, elementName))
                {
                    result = AtomDataKind.Feed;
                } 
                else if (Util.AreSame(XmlConstants.AtomLinkElementName, elementName) &&
                    Util.AreSame(XmlConstants.AtomLinkNextAttributeString, reader.GetAttribute(XmlConstants.AtomLinkRelationAttributeName))) 
                { 
                    result = AtomDataKind.PagingLinks;
                } 
            }
            else if (Util.AreSame(XmlConstants.DataWebMetadataNamespace, namespaceURI))
            {
                if (Util.AreSame(XmlConstants.RowCountElement, elementName)) 
                {
                    result = AtomDataKind.FeedCount; 
                } 
            }
 
            return result;
        }

        ///  
        /// Reads from the specified  and moves to the
        /// child element which should match the specified name. 
        ///  
        /// Reader to consume.
        /// Expected local name of child element. 
        /// Expected namespace of child element.
        /// 
        /// true if the  is left position on a child
        /// with the given name; false otherwise. 
        /// 
        private static bool ReadChildElement(XmlReader reader, string localName, string namespaceUri) 
        { 
            Debug.Assert(localName != null, "localName != null");
            Debug.Assert(namespaceUri != null, "namespaceUri != null"); 
            Debug.Assert(!reader.IsEmptyElement, "!reader.IsEmptyElement");
            Debug.Assert(reader.NodeType != XmlNodeType.EndElement, "reader.NodeType != XmlNodeType.EndElement");

            return reader.Read() && reader.IsStartElement(localName, namespaceUri); 
        }
 
        ///  
        /// Skips all content on the specified  until
        /// the specified  is reached; such that 
        /// a call to .Read() will move to the sibling of the element at
        /// .
        /// 
        /// Reader to advance. 
        /// Desired depth.
        ///  
        /// The reader may already be on an element at ; 
        /// if it's an empty element (<foo />) then the reader isn't
        /// moved. 
        /// 
        private static void SkipToEndAtDepth(XmlReader reader, int depth)
        {
            Debug.Assert(reader != null, "reader != null"); 
            Debug.Assert(reader.Depth >= depth, "reader.Depth >= depth");
 
            while (!(reader.Depth == depth && 
                     (reader.NodeType == XmlNodeType.EndElement ||
                      (reader.NodeType == XmlNodeType.Element && reader.IsEmptyElement)))) 
            {
                reader.Read();
            }
        } 

        ///  
        /// Reads the text inside the element on the . 
        /// 
        /// Reader to get text from. 
        /// The text inside the specified .
        /// 
        /// This method was designed to be compatible with the results
        /// of evaluating the text of an XElement. 
        ///
        /// In short, this means that nulls are never returned, and 
        /// that all non-text nodes are ignored (but elements are 
        /// recursed into).
        ///  
        private static string ReadElementStringForText(XmlReader reader)
        {
            Debug.Assert(reader != null, "reader != null");
            if (reader.IsEmptyElement) 
            {
                return String.Empty; 
            } 

            StringBuilder result = new StringBuilder(); 
            int depth = reader.Depth;
            while (reader.Read())
            {
                if (reader.Depth == depth) 
                {
                    Debug.Assert( 
                        reader.NodeType == XmlNodeType.EndElement, 
                        "reader.NodeType == XmlNodeType.EndElement -- otherwise XmlReader is acting odd");
                    break; 
                }

                if (reader.NodeType == XmlNodeType.SignificantWhitespace ||
                    reader.NodeType == XmlNodeType.Text) 
                {
                    result.Append(reader.Value); 
                } 
            }
 
            return result.ToString();
        }

        ///  
        /// Checks whether the current node on the specified 
        /// should be ignored. 
        ///  
        /// Reader to check.
        /// true if the node should be ignored; false if it should be processed. 
        /// 
        /// This method will throw an exception on unexpected content (CDATA, entity references,
        /// text); therefore it should not be used if mixed content is allowed.
        ///  
        private static bool ShouldIgnoreNode(XmlReader reader)
        { 
            Debug.Assert(reader != null, "reader != null"); 

            switch (reader.NodeType) 
            {
                case XmlNodeType.CDATA:
                case XmlNodeType.EntityReference:
                case XmlNodeType.EndEntity: 
                    Error.ThrowInternalError(InternalError.UnexpectedXmlNodeTypeWhenReading);
                    break; 
                case XmlNodeType.Text: 
                case XmlNodeType.SignificantWhitespace:
                    // throw Error.InvalidOperation(Strings.Deserialize_MixedContent(currentType.ElementTypeName)); 
                    Error.ThrowInternalError(InternalError.UnexpectedXmlNodeTypeWhenReading);
                    break;
                case XmlNodeType.Element:
                case XmlNodeType.EndElement: 
                    return false;
                default: 
                    break; 
            }
 
            return true;
        }

        ///  
        /// Checks if the given content type string matches with 'application/xml' or
        /// 'application/atom+xml' case insensitively. 
        ///  
        /// Input content type.
        /// true if match found, false otherwise. 
        private static bool IsAllowedContentType(string contentType)
        {
            return (String.Equals(XmlConstants.MimeApplicationXml, contentType, StringComparison.OrdinalIgnoreCase) ||
                    String.Equals(XmlConstants.MimeApplicationAtom, contentType, StringComparison.OrdinalIgnoreCase)); 
        }
 
        ///  
        /// Checks if the given link type matches 'application/atom+xml;type=feed' or
        /// 'application/atom+xml;type=entry' case insensitively. 
        /// 
        /// Input link type.
        /// Output parameter indicating whether we are reading a feed or an entry inline.
        /// true if match found, false otherwise. 
        private static bool IsAllowedLinkType(string linkType, out bool isFeed)
        { 
            isFeed = String.Equals(XmlConstants.LinkMimeTypeFeed, linkType, StringComparison.OrdinalIgnoreCase); 
            return isFeed ? true : String.Equals(XmlConstants.LinkMimeTypeEntry, linkType, StringComparison.OrdinalIgnoreCase);
        } 

        /// 
        /// Parses the content on the reader into the specified .
        ///  
        /// Target to read values into.
        private void ParseCurrentContent(AtomEntry targetEntry) 
        { 
            Debug.Assert(targetEntry != null, "targetEntry != null");
            Debug.Assert(this.reader.NodeType == XmlNodeType.Element, "this.reader.NodeType == XmlNodeType.Element"); 

            string propertyValue = this.reader.GetAttributeEx(XmlConstants.AtomContentSrcAttributeName, XmlConstants.AtomNamespace);
            if (propertyValue != null)
            { 
                // This is a media link entry
                // Note that in this case we don't actually use this URL (or the link/edit-media URL) 
                // for editing. We rely on the Astoria URL convention (/propname/$value or just /$value) 
                if (!this.reader.IsEmptyElement)
                { 
                    throw Error.InvalidOperation(Strings.Deserialize_ExpectedEmptyMediaLinkEntryContent);
                }

                targetEntry.MediaLinkEntry = true; 
                targetEntry.MediaContentUri = new Uri(propertyValue, UriKind.RelativeOrAbsolute);
            } 
            else 
            {
                // This is a regular (non-media link) entry 
                if (targetEntry.MediaLinkEntry.HasValue && targetEntry.MediaLinkEntry.Value)
                {
                    // This means we saw a  element but now we have a Content element
                    // that's not just a media link entry pointer (src) 
                    throw Error.InvalidOperation(Strings.Deserialize_ContentPlusPropertiesNotAllowed);
                } 
 
                targetEntry.MediaLinkEntry = false;
 
                propertyValue = this.reader.GetAttributeEx(XmlConstants.AtomTypeAttributeName, XmlConstants.AtomNamespace);
                if (AtomParser.IsAllowedContentType(propertyValue))
                {
                    if (this.reader.IsEmptyElement) 
                    {
                        return; 
                    } 

                    if (ReadChildElement(this.reader, XmlConstants.AtomPropertiesElementName, XmlConstants.DataWebMetadataNamespace)) 
                    {
                        this.ReadCurrentProperties(targetEntry.DataValues);
                    }
                    else if (this.reader.NodeType != XmlNodeType.EndElement) 
                    {
                        throw Error.InvalidOperation(Strings.Deserialize_NotApplicationXml); 
                    } 
                }
            } 
        }

        /// Parses a link for the specified .
        /// Entry to update with link information. 
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "not required to dispose of the nested XmlReader")]
        private void ParseCurrentLink(AtomEntry targetEntry) 
        { 
            Debug.Assert(targetEntry != null, "targetEntry != null");
            Debug.Assert( 
                this.reader.NodeType == XmlNodeType.Element,
                "this.reader.NodeType == XmlNodeType.Element -- otherwise we shouldn't try to parse a link");
            Debug.Assert(
                this.reader.LocalName == "link", 
                "this.reader.LocalName == 'link' -- otherwise we shouldn't try to parse a link");
 
            string relation = this.reader.GetAttribute(XmlConstants.AtomLinkRelationAttributeName); 
            if (relation == null)
            { 
                return;
            }

            if (relation == XmlConstants.AtomEditRelationAttributeValue && targetEntry.EditLink == null) 
            {
                // Only process the first link that has @rel='edit'. 
                string href = this.reader.GetAttribute(XmlConstants.AtomHRefAttributeName); 
                if (String.IsNullOrEmpty(href))
                { 
                    throw Error.InvalidOperation(Strings.Context_MissingEditLinkInResponseBody);
                }

                targetEntry.EditLink = this.ConvertHRefAttributeValueIntoURI(href); 
            }
            else if (relation == XmlConstants.AtomSelfRelationAttributeValue && targetEntry.QueryLink == null) 
            { 
                // Only process the first link that has @rel='self'.
                string href = this.reader.GetAttribute(XmlConstants.AtomHRefAttributeName); 
                if (String.IsNullOrEmpty(href))
                {
                    throw Error.InvalidOperation(Strings.Context_MissingSelfLinkInResponseBody);
                } 

                targetEntry.QueryLink = this.ConvertHRefAttributeValueIntoURI(href); 
            } 
            else if (relation == XmlConstants.AtomEditMediaRelationAttributeValue && targetEntry.MediaEditUri == null)
            { 
                string href = this.reader.GetAttribute(XmlConstants.AtomHRefAttributeName);
                if (String.IsNullOrEmpty(href))
                {
                    throw Error.InvalidOperation(Strings.Context_MissingEditMediaLinkInResponseBody); 
                }
 
                targetEntry.MediaEditUri = this.ConvertHRefAttributeValueIntoURI(href); 
                targetEntry.StreamETagText = this.reader.GetAttribute(XmlConstants.AtomETagAttributeName, XmlConstants.DataWebMetadataNamespace);
            } 

            if (!this.reader.IsEmptyElement)
            {
                string propertyName = UriUtil.GetNameFromAtomLinkRelationAttribute(relation); 
                if (propertyName == null)
                { 
                    return; 
                }
 
                string propertyValueText = this.reader.GetAttribute(XmlConstants.AtomTypeAttributeName);
                bool isFeed;

                if (!IsAllowedLinkType(propertyValueText, out isFeed)) 
                {
                    return; 
                } 

                if (!ReadChildElement(this.reader, XmlConstants.AtomInlineElementName, XmlConstants.DataWebMetadataNamespace)) 
                {
                    return;
                }
 
                bool emptyInlineCollection = this.reader.IsEmptyElement;
                object propertyValue = null; 
 
                if (!emptyInlineCollection)
                { 
                    AtomFeed nestedFeed = null;
                    AtomEntry nestedEntry = null;
                    List feedEntries = null;
 
                    Debug.Assert(this.reader is Xml.XmlWrappingReader, "reader must be a instance of XmlWrappingReader");
                    string readerBaseUri = this.reader.BaseURI; 
                    XmlReader nestedReader = Xml.XmlWrappingReader.CreateReader(readerBaseUri, this.reader.ReadSubtree()); 
                    nestedReader.Read();
                    Debug.Assert(nestedReader.LocalName == "inline", "nestedReader.LocalName == 'inline'"); 

                    AtomParser nested = new AtomParser(nestedReader, this.entryCallback, this.typeScheme, this.currentDataNamespace);
                    while (nested.Read())
                    { 
                        switch (nested.DataKind)
                        { 
                            case AtomDataKind.Feed: 
                                feedEntries = new List();
                                nestedFeed = nested.CurrentFeed; 
                                propertyValue = nestedFeed;
                                break;
                            case AtomDataKind.Entry:
                                nestedEntry = nested.CurrentEntry; 
                                if (feedEntries != null)
                                { 
                                    feedEntries.Add(nestedEntry); 
                                }
                                else 
                                {
                                    propertyValue = nestedEntry;
                                }
 
                                break;
                            case AtomDataKind.PagingLinks: 
                                // Here the inner feed parser found a paging link, and stored it on nestedFeed.NextPageLink 
                                // we are going to add it into a link table and associate
                                // with the collection at AtomMaterializer::Materialize() 
                                // Do nothing for now.
                                break;
                            default:
                                throw new InvalidOperationException(Strings.AtomParser_UnexpectedContentUnderExpandedLink); 
                        }
                    } 
 
                    if (nestedFeed != null)
                    { 
                        Debug.Assert(
                            nestedFeed.Entries == null,
                            "nestedFeed.Entries == null -- otherwise someone initialized this for us");
                        nestedFeed.Entries = feedEntries; 
                    }
                } 
 
                AtomContentProperty property = new AtomContentProperty();
                property.Name = propertyName; 

                if (emptyInlineCollection || propertyValue == null)
                {
                    property.IsNull = true; 
                    if (isFeed)
                    { 
                        property.Feed = new AtomFeed(); 
                        property.Feed.Entries = Enumerable.Empty();
                    } 
                    else
                    {
                        property.Entry = new AtomEntry();
                        property.Entry.IsNull = true; 
                    }
                } 
                else 
                {
                    property.Feed = propertyValue as AtomFeed; 
                    property.Entry = propertyValue as AtomEntry;
                }

                targetEntry.DataValues.Add(property); 
            }
        } 
 
        /// 
        /// Reads a property value and adds it as a text or a sub-property of 
        /// the specified .
        /// 
        /// Property to read content into.
        private void ReadPropertyValueIntoResult(AtomContentProperty property) 
        {
            Debug.Assert(this.reader != null, "reader != null"); 
            Debug.Assert(property != null, "property != null"); 

            switch (this.reader.NodeType) 
            {
                case XmlNodeType.CDATA:
                case XmlNodeType.SignificantWhitespace:
                case XmlNodeType.Text: 
                    if (!String.IsNullOrEmpty(property.Text))
                    { 
                        throw Error.InvalidOperation(Strings.Deserialize_MixedTextWithComment); 
                    }
 
                    property.Text = this.reader.Value;
                    break;

                case XmlNodeType.Comment: 
                case XmlNodeType.Whitespace:
                case XmlNodeType.ProcessingInstruction: 
                case XmlNodeType.EndElement: 
                    // Do nothing.
                    // ProcessingInstruction, Whitespace would have thrown before 
                    break;

                case XmlNodeType.Element:
                    // We found an element while reading a property value. This should be 
                    // a complex type.
                    if (!String.IsNullOrEmpty(property.Text)) 
                    { 
                        throw Error.InvalidOperation(Strings.Deserialize_ExpectingSimpleValue);
                    } 

                    property.EnsureProperties();
                    AtomContentProperty prop = this.ReadPropertyValue();
 
                    if (prop != null)
                    { 
                        property.Properties.Add(prop); 
                    }
 
                    break;

                default:
                    throw Error.InvalidOperation(Strings.Deserialize_ExpectingSimpleValue); 
            }
        } 
 
        /// This method will read a string or a complex type.
        /// The property value read. 
        /// Always checks for null attribute.
        private AtomContentProperty ReadPropertyValue()
        {
            Debug.Assert(this.reader != null, "reader != null"); 
            Debug.Assert(
                this.reader.NodeType == XmlNodeType.Element, 
                "reader.NodeType == XmlNodeType.Element -- otherwise caller is confused as to where the reader is"); 

            if (!this.IsDataWebElement) 
            {
                // we expect ... only
                SkipToEndAtDepth(this.reader, this.reader.Depth);
                return null; 
            }
 
            AtomContentProperty result = new AtomContentProperty(); 
            result.Name = this.reader.LocalName;
            result.TypeName = this.reader.GetAttributeEx(XmlConstants.AtomTypeAttributeName, XmlConstants.DataWebMetadataNamespace); 
            result.IsNull = Util.DoesNullAttributeSayTrue(this.reader);
            result.Text = result.IsNull ? null : String.Empty;

            if (!this.reader.IsEmptyElement) 
            {
                int depth = this.reader.Depth; 
                while (this.reader.Read()) 
                {
                    this.ReadPropertyValueIntoResult(result); 
                    if (this.reader.Depth == depth)
                    {
                        break;
                    } 
                }
            } 
 
            return result;
        } 

        /// 
        /// Reads properties from the current reader into the
        /// specified  collection. 
        /// 
        /// Values to read into. 
        private void ReadCurrentProperties(List values) 
        {
            Debug.Assert(values != null, "values != null"); 
            Debug.Assert(this.reader.NodeType == XmlNodeType.Element, "this.reader.NodeType == XmlNodeType.Element");

            while (this.reader.Read())
            { 
                if (ShouldIgnoreNode(this.reader))
                { 
                    continue; 
                }
 
                if (this.reader.NodeType == XmlNodeType.EndElement)
                {
                    return;
                } 

                if (this.reader.NodeType == XmlNodeType.Element) 
                { 
                    AtomContentProperty prop = this.ReadPropertyValue();
 
                    if (prop != null)
                    {
                        values.Add(prop);
                    } 
                }
            } 
        } 

        ///  
        /// Parses the current reader into a new 
        /// instance.
        /// 
        ///  
        /// After invocation, the target entry that was created as a result
        /// of parsing the current reader. 
        ///  
        private void ParseCurrentEntry(out AtomEntry targetEntry)
        { 
            Debug.Assert(this.reader.NodeType == XmlNodeType.Element, "this.reader.NodeType == XmlNodeType.Element");

            // Push reader.
            var callbackResult = this.entryCallback(this.reader); 
            Debug.Assert(callbackResult.Key != null, "callbackResult.Key != null");
            this.readers.Push(this.reader); 
            this.reader = callbackResult.Key; 

            this.reader.Read(); 
            Debug.Assert(this.reader.LocalName == "entry", "this.reader.LocalName == 'entry' - otherwise we're not reading the subtree");

            bool hasContent = false;
            targetEntry = new AtomEntry(); 
            targetEntry.DataValues = new List();
            targetEntry.Tag = callbackResult.Value; 
            targetEntry.ETagText = this.reader.GetAttribute(XmlConstants.AtomETagAttributeName, XmlConstants.DataWebMetadataNamespace); 

            while (this.reader.Read()) 
            {
                if (ShouldIgnoreNode(this.reader))
                {
                    continue; 
                }
 
                if (this.reader.NodeType == XmlNodeType.Element) 
                {
                    int depth = this.reader.Depth; 
                    string elementName = this.reader.LocalName;
                    string namespaceURI = this.reader.NamespaceURI;
                    if (namespaceURI == XmlConstants.AtomNamespace)
                    { 
                        if (elementName == XmlConstants.AtomCategoryElementName && targetEntry.TypeName == null)
                        { 
                            string text = this.reader.GetAttributeEx(XmlConstants.AtomCategorySchemeAttributeName, XmlConstants.AtomNamespace); 
                            if (text == this.typeScheme)
                            { 
                                targetEntry.TypeName = this.reader.GetAttributeEx(XmlConstants.AtomCategoryTermAttributeName, XmlConstants.AtomNamespace);
                            }
                        }
                        else if (elementName == XmlConstants.AtomContentElementName) 
                        {
                            hasContent = true; 
                            this.ParseCurrentContent(targetEntry); 
                        }
                        else if (elementName == XmlConstants.AtomIdElementName && targetEntry.Identity == null) 
                        {
                            // The .Identity == null check ensures that only the first id element is processed.
                            string idText = ReadElementStringForText(this.reader);
                            idText = Util.ReferenceIdentity(idText); 

                            // here we could just assign idText to Identity 
                            // however we used to check for AbsoluteUri, thus we need to 
                            // convert string to Uri and check for absoluteness
                            Uri idUri = Util.CreateUri(idText, UriKind.RelativeOrAbsolute); 
                            if (!idUri.IsAbsoluteUri)
                            {
                                throw Error.InvalidOperation(Strings.Context_TrackingExpectsAbsoluteUri);
                            } 

                            targetEntry.Identity = idText; 
                        } 
                        else if (elementName == XmlConstants.AtomLinkElementName)
                        { 
                            this.ParseCurrentLink(targetEntry);
                        }
                    }
                    else if (namespaceURI == XmlConstants.DataWebMetadataNamespace) 
                    {
                        if (elementName == XmlConstants.AtomPropertiesElementName) 
                        { 
                            if (targetEntry.MediaLinkEntry.HasValue && !targetEntry.MediaLinkEntry.Value)
                            { 
                                // This means we saw a non-empty  element but now we have a Properties element
                                // that also carries properties
                                throw Error.InvalidOperation(Strings.Deserialize_ContentPlusPropertiesNotAllowed);
                            } 

                            targetEntry.MediaLinkEntry = true; 
 
                            if (!this.reader.IsEmptyElement)
                            { 
                                this.ReadCurrentProperties(targetEntry.DataValues);
                            }
                        }
                    } 

                    SkipToEndAtDepth(this.reader, depth); 
                } 
            }
 
            if (targetEntry.Identity == null)
            {
                throw Error.InvalidOperation(Strings.Deserialize_MissingIdElement);
            } 

            if (!hasContent) 
            { 
                // Content is expected for the GetResponse operation
                throw Error.BatchStreamContentExpected(BatchStreamState.GetResponse); 
            }

            this.reader = this.readers.Pop();
        } 

        /// Parses the value for the current feed count. 
        /// This method will update the value on the current feed. 
        private void ParseCurrentFeedCount()
        { 
            if (this.feed == null)
            {
                throw new InvalidOperationException(Strings.AtomParser_FeedCountNotUnderFeed);
            } 

            if (this.feed.Count.HasValue) 
            { 
                throw new InvalidOperationException(Strings.AtomParser_ManyFeedCounts);
            } 

            long countValue;
            if (!long.TryParse(MaterializeAtom.ReadElementString(this.reader, true), System.Globalization.NumberStyles.Integer, System.Globalization.CultureInfo.InvariantCulture, out countValue))
            { 
                throw new FormatException(Strings.MaterializeFromAtom_CountFormatError);
            } 
 
            if (countValue < 0)
            { 
                throw new FormatException(Strings.MaterializeFromAtom_CountFormatError);
            }

            this.feed.Count = countValue; 
        }
 
        ///  
        /// Parsing paging links
        ///  
        private void ParseCurrentFeedPagingLinks()
        {
            // feed should never be null here since there is an outer check
            // just need to assert 
            Debug.Assert(this.feed != null, "Trying to parser paging links but feed is null.");
 
            if (this.feed.NextLink != null) 
            {
                // we have set next link before, this is a duplicate 
                // atom spec does not allow duplicated next links
                throw new InvalidOperationException(Strings.AtomMaterializer_DuplicatedNextLink);
            }
 
            string nextLink = this.reader.GetAttribute(XmlConstants.AtomHRefAttributeName);
 
            if (nextLink == null) 
            {
                throw new InvalidOperationException(Strings.AtomMaterializer_LinksMissingHref); 
            }
            else
            {
                this.feed.NextLink = this.ConvertHRefAttributeValueIntoURI(nextLink); 
            }
        } 
 
        /// 
        /// creates a new uri instance which takes into account the base uri of the reader. 
        /// 
        /// href attribute value.
        /// a new instance of uri as refered by the 
        private Uri ConvertHRefAttributeValueIntoURI(string href) 
        {
            Uri uri = Util.CreateUri(href, UriKind.RelativeOrAbsolute); 
            if (!uri.IsAbsoluteUri && !String.IsNullOrEmpty(this.reader.BaseURI)) 
            {
                Uri baseUri = Util.CreateUri(this.reader.BaseURI, UriKind.RelativeOrAbsolute); 

                // The reason why we can't use Util.CreateUri function here, is that the util method
                // checks for trailing slashes in the baseuri and starting forward slashes in the request uri
                // and does some tricks which is not consistent with the uri class behaviour. Hence using the 
                // uri class directly here.
                uri = new Uri(baseUri, uri); 
            } 

            return uri; 
        }

        #endregion Private methods.
    } 
}

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
//---------------------------------------------------------------------- 
// 
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// 
//  
// Parses an XML stream into structured AtomEntry or related instances.
//  
//--------------------------------------------------------------------- 

namespace System.Data.Services.Client 
{
    #region Namespaces.

    using System; 
    using System.Collections;
    using System.Collections.Generic; 
    using System.Diagnostics; 
    using System.Linq;
    using System.Reflection; 
    using System.Xml;
    using System.Xml.Linq;
    using System.Text;
 
    #endregion Namespaces.
 
    /// Parser for DataService payloads (mostly ATOM). 
    /// 
    /// There are four types of documents parsed: 
    /// 1. A single non-entity value.
    /// 2. A list of non-entity values.
    /// 3. An entity.
    /// 4. A feed. 
    ///
    /// In case of (1), the parser will go through these states: 
    ///  None -> Custom -> Finished 
    ///
    /// In case of (2), the parser will go through these states: 
    ///  None -> Custom -> Custom -> Finished
    ///
    /// In case of (3), the parser will go through these states:
    ///  None -> Entry -> Finished 
    ///
    /// In case of (4), the parser will go through these states: 
    ///  None -> (FeedData | Entry) -> (FeedData | Entry) -> Finished 
    ///
    /// Note that the parser will always stop on 'top-level' packets; all recursive data 
    /// structures are handled internally.
    /// 
    [DebuggerDisplay("AtomParser {kind} {reader}")]
    internal class AtomParser 
    {
        #region Private fields. 
 
        /// Callback invoked each time an ATOM entry is found.
        ///  
        /// This callback takes the current XmlReader and returns a
        /// subtree XmlReader and an object that is assigned to the
        /// entry's Tag property.
        ///  
        private readonly Func> entryCallback;
 
        /// Stack of available XmlReaders. 
        private readonly Stack readers;
 
        /// Scheme used to find type information on ATOM category elements.
        private readonly string typeScheme;

        /// ATOM entry being parsed. 
        private AtomEntry entry;
 
        /// ATOM feed being parsed. 
        private AtomFeed feed;
 
        /// Current data kind (nothing, entry, feed, custom-top-level-thingy, etc).
        private AtomDataKind kind;

        /// Current . 
        private XmlReader reader;
 
        /// The data namespace 
        private string currentDataNamespace;
 
        #endregion Private fields.

        #region Constructors.
 
        /// Initializes a new  instance.
        ///  to parse content from. 
        ///  
        /// Callback invoked each time an ATOM entry is found; see the comments
        /// on the entryCallback field. 
        /// 
        /// 
        /// Scheme used to find type information on ATOM category elements.
        ///  
        /// The xml document's DataWeb Namespace
        internal AtomParser(XmlReader reader, Func> entryCallback, string typeScheme, string currentDataNamespace) 
        { 
            Debug.Assert(reader != null, "reader != null");
            Debug.Assert(typeScheme != null, "typeScheme != null"); 
            Debug.Assert(entryCallback != null, "entryCallback != null");
            Debug.Assert(!String.IsNullOrEmpty(currentDataNamespace), "currentDataNamespace is empty or null");

            this.reader = reader; 
            this.readers = new Stack();
            this.entryCallback = entryCallback; 
            this.typeScheme = typeScheme; 
            this.currentDataNamespace = currentDataNamespace;
 
            Debug.Assert(this.kind == AtomDataKind.None, "this.kind == AtomDataKind.None -- otherwise not initialized correctly");
        }

        #endregion Constructors. 

        #region Internal properties. 
 
        /// Entry being materialized; possibly null.
        internal AtomEntry CurrentEntry 
        {
            get
            {
                return this.entry; 
            }
        } 
 
        /// Feed being materialized; possibly null.
        internal AtomFeed CurrentFeed 
        {
            get
            {
                return this.feed; 
            }
        } 
 
        /// Kind of ATOM data available on the parser.
        internal AtomDataKind DataKind 
        {
            get
            {
                return this.kind; 
            }
        } 
 
        /// 
        /// Returns true if the current element is in the data web namespace 
        /// 
        internal bool IsDataWebElement
        {
            get { return this.reader.NamespaceURI == this.currentDataNamespace; } 
        }
 
        #endregion Internal properties. 

        #region Internal methods. 

        /// 
        /// Creates an  instance for ATOM entries.
        ///  
        /// Reader being used.
        ///  
        /// A pair of an XmlReader instance and an object to be assigned 
        /// to the Tag on the entry (available for materialization callbacks
        /// later in the pipeline). 
        /// 
        /// 
        /// A no-op implementation would do this instead:
        /// 
        /// return new KeyValuePair<XmlReader, object>(reader.ReadSubtree(), null);
        ///  
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "not required")] 
        internal static KeyValuePair XElementBuilderCallback(XmlReader reader)
        { 
            Debug.Assert(reader != null, "reader != null");
            Debug.Assert(reader is Xml.XmlWrappingReader, "reader must be a instance of XmlWrappingReader");

            string readerBaseUri = reader.BaseURI; 
            XElement element = XElement.Load(reader.ReadSubtree(), LoadOptions.None);
            return new KeyValuePair(Xml.XmlWrappingReader.CreateReader(readerBaseUri, element.CreateReader()), element); 
        } 

        #endregion Internal methods. 

        #region Internal methods.

        /// Consumes the next chunk of content from the underlying XML reader. 
        /// 
        /// true if another piece of content is available, identified by DataKind. 
        /// false if there is no more content. 
        /// 
        internal bool Read() 
        {
            // When an external caller 'insists', we'll come all the way down (which is the 'most local'
            // scope at which this is known), and unwind as a no-op.
            if (this.DataKind == AtomDataKind.Finished) 
            {
                return false; 
            } 

            while (this.reader.Read()) 
            {
                if (ShouldIgnoreNode(this.reader))
                {
                    continue; 
                }
 
                Debug.Assert( 
                    this.reader.NodeType == XmlNodeType.Element || this.reader.NodeType == XmlNodeType.EndElement,
                    "this.reader.NodeType == XmlNodeType.Element || this.reader.NodeType == XmlNodeType.EndElement -- otherwise we should have ignored or thrown"); 

                AtomDataKind readerData = ParseStateForReader(this.reader);

                if (this.reader.NodeType == XmlNodeType.EndElement) 
                {
                    // The only case in which we expect to see an end-element at the top level 
                    // is for a feed. Custom elements and entries should be consumed by 
                    // their own parsing methods. However we are tolerant of additional EndElements,
                    // which at this point mean we have nothing else to consume. 
                    break;
                }

                switch (readerData) 
                {
                    case AtomDataKind.Custom: 
                        if (this.DataKind == AtomDataKind.None) 
                        {
                            this.kind = AtomDataKind.Custom; 
                            return true;
                        }
                        else
                        { 
                            MaterializeAtom.SkipToEnd(this.reader);
                            continue; 
                        } 

                    case AtomDataKind.Entry: 
                        this.kind = AtomDataKind.Entry;
                        this.ParseCurrentEntry(out this.entry);
                        return true;
 
                    case AtomDataKind.Feed:
                        if (this.DataKind == AtomDataKind.None) 
                        { 
                            this.feed = new AtomFeed();
                            this.kind = AtomDataKind.Feed; 
                            return true;
                        }

                        throw new InvalidOperationException(Strings.AtomParser_FeedUnexpected); 

                    case AtomDataKind.FeedCount: 
                        this.ParseCurrentFeedCount(); 
                        break;
 
                    case AtomDataKind.PagingLinks:
                        if (this.feed == null)
                        {
                            // paging link outside of feed? 
                            throw new InvalidOperationException(Strings.AtomParser_PagingLinkOutsideOfFeed);
                        } 
 
                        this.kind = AtomDataKind.PagingLinks;
                        this.ParseCurrentFeedPagingLinks(); 
                        return true;

                    default:
                        Debug.Assert(false, "Atom Parser is in a wrong state...Did you add a new AtomDataKind?"); 
                        break;
                } 
            } 

            this.kind = AtomDataKind.Finished; 
            this.entry = null;
            return false;
        }
 
        /// Reads the current property value from the reader.
        /// A structured property instance. 
        ///  
        /// This method should only be called for top-level complex properties.
        /// 
        /// For top-level primitive values, 
        /// should be used to preserve V1 behavior in which mixed-content
        /// XML elements are allowed.
        ///  
        internal AtomContentProperty ReadCurrentPropertyValue()
        { 
            Debug.Assert( 
                this.kind == AtomDataKind.Custom,
                "this.kind == AtomDataKind.Custom -- otherwise caller shouldn't invoke ReadCurrentPropertyValue"); 
            return this.ReadPropertyValue();
        }

        /// Reads the current property value from the reader as a string. 
        /// A structured property instance.
        ///  
        /// This method should only be called for top-level primitive types. 
        ///
        /// For top-level complex type values,  
        /// should be used to capture all information.
        /// 
        internal string ReadCustomElementString()
        { 
            Debug.Assert(
                this.kind == AtomDataKind.Custom, 
                "this.kind == AtomDataKind.Custom -- otherwise caller shouldn't invoke ReadCustomElementString"); 
            return MaterializeAtom.ReadElementString(this.reader, true);
        } 

        /// Replaces the current reader with the specified reader.
        /// New reader to use.
        ///  
        /// This is a very odd method, here only to allow inline count to
        /// read everything ahead and "reopen" where we are. No checks are 
        /// done as to whether we are in a safe place to do so. 
        /// 
        internal void ReplaceReader(XmlReader newReader) 
        {
            Debug.Assert(newReader != null, "newReader != null");
            this.reader = newReader;
        } 

        #endregion Internal methods. 
 
        #region Private methods.
 
        /// 
        /// Determines what the parse state should be for the specified
        /// .
        ///  
        /// Reader to check.
        /// The data kind derived from the current element. 
        ///  
        /// Note that no previous state is considered, so state transitions
        /// aren't handled by the method - instead, certain known elements 
        /// are mapped to parser states.
        /// 
        private static AtomDataKind ParseStateForReader(XmlReader reader)
        { 
            Debug.Assert(reader != null, "reader != null");
            Debug.Assert( 
                reader.NodeType == XmlNodeType.Element || reader.NodeType == XmlNodeType.EndElement, 
                "reader.NodeType == XmlNodeType.Element || EndElement -- otherwise can't determine");
 
            AtomDataKind result = AtomDataKind.Custom;
            string elementName = reader.LocalName;
            string namespaceURI = reader.NamespaceURI;
            if (Util.AreSame(XmlConstants.AtomNamespace, namespaceURI)) 
            {
                if (Util.AreSame(XmlConstants.AtomEntryElementName, elementName)) 
                { 
                    result = AtomDataKind.Entry;
                } 
                else if (Util.AreSame(XmlConstants.AtomFeedElementName, elementName))
                {
                    result = AtomDataKind.Feed;
                } 
                else if (Util.AreSame(XmlConstants.AtomLinkElementName, elementName) &&
                    Util.AreSame(XmlConstants.AtomLinkNextAttributeString, reader.GetAttribute(XmlConstants.AtomLinkRelationAttributeName))) 
                { 
                    result = AtomDataKind.PagingLinks;
                } 
            }
            else if (Util.AreSame(XmlConstants.DataWebMetadataNamespace, namespaceURI))
            {
                if (Util.AreSame(XmlConstants.RowCountElement, elementName)) 
                {
                    result = AtomDataKind.FeedCount; 
                } 
            }
 
            return result;
        }

        ///  
        /// Reads from the specified  and moves to the
        /// child element which should match the specified name. 
        ///  
        /// Reader to consume.
        /// Expected local name of child element. 
        /// Expected namespace of child element.
        /// 
        /// true if the  is left position on a child
        /// with the given name; false otherwise. 
        /// 
        private static bool ReadChildElement(XmlReader reader, string localName, string namespaceUri) 
        { 
            Debug.Assert(localName != null, "localName != null");
            Debug.Assert(namespaceUri != null, "namespaceUri != null"); 
            Debug.Assert(!reader.IsEmptyElement, "!reader.IsEmptyElement");
            Debug.Assert(reader.NodeType != XmlNodeType.EndElement, "reader.NodeType != XmlNodeType.EndElement");

            return reader.Read() && reader.IsStartElement(localName, namespaceUri); 
        }
 
        ///  
        /// Skips all content on the specified  until
        /// the specified  is reached; such that 
        /// a call to .Read() will move to the sibling of the element at
        /// .
        /// 
        /// Reader to advance. 
        /// Desired depth.
        ///  
        /// The reader may already be on an element at ; 
        /// if it's an empty element (<foo />) then the reader isn't
        /// moved. 
        /// 
        private static void SkipToEndAtDepth(XmlReader reader, int depth)
        {
            Debug.Assert(reader != null, "reader != null"); 
            Debug.Assert(reader.Depth >= depth, "reader.Depth >= depth");
 
            while (!(reader.Depth == depth && 
                     (reader.NodeType == XmlNodeType.EndElement ||
                      (reader.NodeType == XmlNodeType.Element && reader.IsEmptyElement)))) 
            {
                reader.Read();
            }
        } 

        ///  
        /// Reads the text inside the element on the . 
        /// 
        /// Reader to get text from. 
        /// The text inside the specified .
        /// 
        /// This method was designed to be compatible with the results
        /// of evaluating the text of an XElement. 
        ///
        /// In short, this means that nulls are never returned, and 
        /// that all non-text nodes are ignored (but elements are 
        /// recursed into).
        ///  
        private static string ReadElementStringForText(XmlReader reader)
        {
            Debug.Assert(reader != null, "reader != null");
            if (reader.IsEmptyElement) 
            {
                return String.Empty; 
            } 

            StringBuilder result = new StringBuilder(); 
            int depth = reader.Depth;
            while (reader.Read())
            {
                if (reader.Depth == depth) 
                {
                    Debug.Assert( 
                        reader.NodeType == XmlNodeType.EndElement, 
                        "reader.NodeType == XmlNodeType.EndElement -- otherwise XmlReader is acting odd");
                    break; 
                }

                if (reader.NodeType == XmlNodeType.SignificantWhitespace ||
                    reader.NodeType == XmlNodeType.Text) 
                {
                    result.Append(reader.Value); 
                } 
            }
 
            return result.ToString();
        }

        ///  
        /// Checks whether the current node on the specified 
        /// should be ignored. 
        ///  
        /// Reader to check.
        /// true if the node should be ignored; false if it should be processed. 
        /// 
        /// This method will throw an exception on unexpected content (CDATA, entity references,
        /// text); therefore it should not be used if mixed content is allowed.
        ///  
        private static bool ShouldIgnoreNode(XmlReader reader)
        { 
            Debug.Assert(reader != null, "reader != null"); 

            switch (reader.NodeType) 
            {
                case XmlNodeType.CDATA:
                case XmlNodeType.EntityReference:
                case XmlNodeType.EndEntity: 
                    Error.ThrowInternalError(InternalError.UnexpectedXmlNodeTypeWhenReading);
                    break; 
                case XmlNodeType.Text: 
                case XmlNodeType.SignificantWhitespace:
                    // throw Error.InvalidOperation(Strings.Deserialize_MixedContent(currentType.ElementTypeName)); 
                    Error.ThrowInternalError(InternalError.UnexpectedXmlNodeTypeWhenReading);
                    break;
                case XmlNodeType.Element:
                case XmlNodeType.EndElement: 
                    return false;
                default: 
                    break; 
            }
 
            return true;
        }

        ///  
        /// Checks if the given content type string matches with 'application/xml' or
        /// 'application/atom+xml' case insensitively. 
        ///  
        /// Input content type.
        /// true if match found, false otherwise. 
        private static bool IsAllowedContentType(string contentType)
        {
            return (String.Equals(XmlConstants.MimeApplicationXml, contentType, StringComparison.OrdinalIgnoreCase) ||
                    String.Equals(XmlConstants.MimeApplicationAtom, contentType, StringComparison.OrdinalIgnoreCase)); 
        }
 
        ///  
        /// Checks if the given link type matches 'application/atom+xml;type=feed' or
        /// 'application/atom+xml;type=entry' case insensitively. 
        /// 
        /// Input link type.
        /// Output parameter indicating whether we are reading a feed or an entry inline.
        /// true if match found, false otherwise. 
        private static bool IsAllowedLinkType(string linkType, out bool isFeed)
        { 
            isFeed = String.Equals(XmlConstants.LinkMimeTypeFeed, linkType, StringComparison.OrdinalIgnoreCase); 
            return isFeed ? true : String.Equals(XmlConstants.LinkMimeTypeEntry, linkType, StringComparison.OrdinalIgnoreCase);
        } 

        /// 
        /// Parses the content on the reader into the specified .
        ///  
        /// Target to read values into.
        private void ParseCurrentContent(AtomEntry targetEntry) 
        { 
            Debug.Assert(targetEntry != null, "targetEntry != null");
            Debug.Assert(this.reader.NodeType == XmlNodeType.Element, "this.reader.NodeType == XmlNodeType.Element"); 

            string propertyValue = this.reader.GetAttributeEx(XmlConstants.AtomContentSrcAttributeName, XmlConstants.AtomNamespace);
            if (propertyValue != null)
            { 
                // This is a media link entry
                // Note that in this case we don't actually use this URL (or the link/edit-media URL) 
                // for editing. We rely on the Astoria URL convention (/propname/$value or just /$value) 
                if (!this.reader.IsEmptyElement)
                { 
                    throw Error.InvalidOperation(Strings.Deserialize_ExpectedEmptyMediaLinkEntryContent);
                }

                targetEntry.MediaLinkEntry = true; 
                targetEntry.MediaContentUri = new Uri(propertyValue, UriKind.RelativeOrAbsolute);
            } 
            else 
            {
                // This is a regular (non-media link) entry 
                if (targetEntry.MediaLinkEntry.HasValue && targetEntry.MediaLinkEntry.Value)
                {
                    // This means we saw a  element but now we have a Content element
                    // that's not just a media link entry pointer (src) 
                    throw Error.InvalidOperation(Strings.Deserialize_ContentPlusPropertiesNotAllowed);
                } 
 
                targetEntry.MediaLinkEntry = false;
 
                propertyValue = this.reader.GetAttributeEx(XmlConstants.AtomTypeAttributeName, XmlConstants.AtomNamespace);
                if (AtomParser.IsAllowedContentType(propertyValue))
                {
                    if (this.reader.IsEmptyElement) 
                    {
                        return; 
                    } 

                    if (ReadChildElement(this.reader, XmlConstants.AtomPropertiesElementName, XmlConstants.DataWebMetadataNamespace)) 
                    {
                        this.ReadCurrentProperties(targetEntry.DataValues);
                    }
                    else if (this.reader.NodeType != XmlNodeType.EndElement) 
                    {
                        throw Error.InvalidOperation(Strings.Deserialize_NotApplicationXml); 
                    } 
                }
            } 
        }

        /// Parses a link for the specified .
        /// Entry to update with link information. 
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "not required to dispose of the nested XmlReader")]
        private void ParseCurrentLink(AtomEntry targetEntry) 
        { 
            Debug.Assert(targetEntry != null, "targetEntry != null");
            Debug.Assert( 
                this.reader.NodeType == XmlNodeType.Element,
                "this.reader.NodeType == XmlNodeType.Element -- otherwise we shouldn't try to parse a link");
            Debug.Assert(
                this.reader.LocalName == "link", 
                "this.reader.LocalName == 'link' -- otherwise we shouldn't try to parse a link");
 
            string relation = this.reader.GetAttribute(XmlConstants.AtomLinkRelationAttributeName); 
            if (relation == null)
            { 
                return;
            }

            if (relation == XmlConstants.AtomEditRelationAttributeValue && targetEntry.EditLink == null) 
            {
                // Only process the first link that has @rel='edit'. 
                string href = this.reader.GetAttribute(XmlConstants.AtomHRefAttributeName); 
                if (String.IsNullOrEmpty(href))
                { 
                    throw Error.InvalidOperation(Strings.Context_MissingEditLinkInResponseBody);
                }

                targetEntry.EditLink = this.ConvertHRefAttributeValueIntoURI(href); 
            }
            else if (relation == XmlConstants.AtomSelfRelationAttributeValue && targetEntry.QueryLink == null) 
            { 
                // Only process the first link that has @rel='self'.
                string href = this.reader.GetAttribute(XmlConstants.AtomHRefAttributeName); 
                if (String.IsNullOrEmpty(href))
                {
                    throw Error.InvalidOperation(Strings.Context_MissingSelfLinkInResponseBody);
                } 

                targetEntry.QueryLink = this.ConvertHRefAttributeValueIntoURI(href); 
            } 
            else if (relation == XmlConstants.AtomEditMediaRelationAttributeValue && targetEntry.MediaEditUri == null)
            { 
                string href = this.reader.GetAttribute(XmlConstants.AtomHRefAttributeName);
                if (String.IsNullOrEmpty(href))
                {
                    throw Error.InvalidOperation(Strings.Context_MissingEditMediaLinkInResponseBody); 
                }
 
                targetEntry.MediaEditUri = this.ConvertHRefAttributeValueIntoURI(href); 
                targetEntry.StreamETagText = this.reader.GetAttribute(XmlConstants.AtomETagAttributeName, XmlConstants.DataWebMetadataNamespace);
            } 

            if (!this.reader.IsEmptyElement)
            {
                string propertyName = UriUtil.GetNameFromAtomLinkRelationAttribute(relation); 
                if (propertyName == null)
                { 
                    return; 
                }
 
                string propertyValueText = this.reader.GetAttribute(XmlConstants.AtomTypeAttributeName);
                bool isFeed;

                if (!IsAllowedLinkType(propertyValueText, out isFeed)) 
                {
                    return; 
                } 

                if (!ReadChildElement(this.reader, XmlConstants.AtomInlineElementName, XmlConstants.DataWebMetadataNamespace)) 
                {
                    return;
                }
 
                bool emptyInlineCollection = this.reader.IsEmptyElement;
                object propertyValue = null; 
 
                if (!emptyInlineCollection)
                { 
                    AtomFeed nestedFeed = null;
                    AtomEntry nestedEntry = null;
                    List feedEntries = null;
 
                    Debug.Assert(this.reader is Xml.XmlWrappingReader, "reader must be a instance of XmlWrappingReader");
                    string readerBaseUri = this.reader.BaseURI; 
                    XmlReader nestedReader = Xml.XmlWrappingReader.CreateReader(readerBaseUri, this.reader.ReadSubtree()); 
                    nestedReader.Read();
                    Debug.Assert(nestedReader.LocalName == "inline", "nestedReader.LocalName == 'inline'"); 

                    AtomParser nested = new AtomParser(nestedReader, this.entryCallback, this.typeScheme, this.currentDataNamespace);
                    while (nested.Read())
                    { 
                        switch (nested.DataKind)
                        { 
                            case AtomDataKind.Feed: 
                                feedEntries = new List();
                                nestedFeed = nested.CurrentFeed; 
                                propertyValue = nestedFeed;
                                break;
                            case AtomDataKind.Entry:
                                nestedEntry = nested.CurrentEntry; 
                                if (feedEntries != null)
                                { 
                                    feedEntries.Add(nestedEntry); 
                                }
                                else 
                                {
                                    propertyValue = nestedEntry;
                                }
 
                                break;
                            case AtomDataKind.PagingLinks: 
                                // Here the inner feed parser found a paging link, and stored it on nestedFeed.NextPageLink 
                                // we are going to add it into a link table and associate
                                // with the collection at AtomMaterializer::Materialize() 
                                // Do nothing for now.
                                break;
                            default:
                                throw new InvalidOperationException(Strings.AtomParser_UnexpectedContentUnderExpandedLink); 
                        }
                    } 
 
                    if (nestedFeed != null)
                    { 
                        Debug.Assert(
                            nestedFeed.Entries == null,
                            "nestedFeed.Entries == null -- otherwise someone initialized this for us");
                        nestedFeed.Entries = feedEntries; 
                    }
                } 
 
                AtomContentProperty property = new AtomContentProperty();
                property.Name = propertyName; 

                if (emptyInlineCollection || propertyValue == null)
                {
                    property.IsNull = true; 
                    if (isFeed)
                    { 
                        property.Feed = new AtomFeed(); 
                        property.Feed.Entries = Enumerable.Empty();
                    } 
                    else
                    {
                        property.Entry = new AtomEntry();
                        property.Entry.IsNull = true; 
                    }
                } 
                else 
                {
                    property.Feed = propertyValue as AtomFeed; 
                    property.Entry = propertyValue as AtomEntry;
                }

                targetEntry.DataValues.Add(property); 
            }
        } 
 
        /// 
        /// Reads a property value and adds it as a text or a sub-property of 
        /// the specified .
        /// 
        /// Property to read content into.
        private void ReadPropertyValueIntoResult(AtomContentProperty property) 
        {
            Debug.Assert(this.reader != null, "reader != null"); 
            Debug.Assert(property != null, "property != null"); 

            switch (this.reader.NodeType) 
            {
                case XmlNodeType.CDATA:
                case XmlNodeType.SignificantWhitespace:
                case XmlNodeType.Text: 
                    if (!String.IsNullOrEmpty(property.Text))
                    { 
                        throw Error.InvalidOperation(Strings.Deserialize_MixedTextWithComment); 
                    }
 
                    property.Text = this.reader.Value;
                    break;

                case XmlNodeType.Comment: 
                case XmlNodeType.Whitespace:
                case XmlNodeType.ProcessingInstruction: 
                case XmlNodeType.EndElement: 
                    // Do nothing.
                    // ProcessingInstruction, Whitespace would have thrown before 
                    break;

                case XmlNodeType.Element:
                    // We found an element while reading a property value. This should be 
                    // a complex type.
                    if (!String.IsNullOrEmpty(property.Text)) 
                    { 
                        throw Error.InvalidOperation(Strings.Deserialize_ExpectingSimpleValue);
                    } 

                    property.EnsureProperties();
                    AtomContentProperty prop = this.ReadPropertyValue();
 
                    if (prop != null)
                    { 
                        property.Properties.Add(prop); 
                    }
 
                    break;

                default:
                    throw Error.InvalidOperation(Strings.Deserialize_ExpectingSimpleValue); 
            }
        } 
 
        /// This method will read a string or a complex type.
        /// The property value read. 
        /// Always checks for null attribute.
        private AtomContentProperty ReadPropertyValue()
        {
            Debug.Assert(this.reader != null, "reader != null"); 
            Debug.Assert(
                this.reader.NodeType == XmlNodeType.Element, 
                "reader.NodeType == XmlNodeType.Element -- otherwise caller is confused as to where the reader is"); 

            if (!this.IsDataWebElement) 
            {
                // we expect ... only
                SkipToEndAtDepth(this.reader, this.reader.Depth);
                return null; 
            }
 
            AtomContentProperty result = new AtomContentProperty(); 
            result.Name = this.reader.LocalName;
            result.TypeName = this.reader.GetAttributeEx(XmlConstants.AtomTypeAttributeName, XmlConstants.DataWebMetadataNamespace); 
            result.IsNull = Util.DoesNullAttributeSayTrue(this.reader);
            result.Text = result.IsNull ? null : String.Empty;

            if (!this.reader.IsEmptyElement) 
            {
                int depth = this.reader.Depth; 
                while (this.reader.Read()) 
                {
                    this.ReadPropertyValueIntoResult(result); 
                    if (this.reader.Depth == depth)
                    {
                        break;
                    } 
                }
            } 
 
            return result;
        } 

        /// 
        /// Reads properties from the current reader into the
        /// specified  collection. 
        /// 
        /// Values to read into. 
        private void ReadCurrentProperties(List values) 
        {
            Debug.Assert(values != null, "values != null"); 
            Debug.Assert(this.reader.NodeType == XmlNodeType.Element, "this.reader.NodeType == XmlNodeType.Element");

            while (this.reader.Read())
            { 
                if (ShouldIgnoreNode(this.reader))
                { 
                    continue; 
                }
 
                if (this.reader.NodeType == XmlNodeType.EndElement)
                {
                    return;
                } 

                if (this.reader.NodeType == XmlNodeType.Element) 
                { 
                    AtomContentProperty prop = this.ReadPropertyValue();
 
                    if (prop != null)
                    {
                        values.Add(prop);
                    } 
                }
            } 
        } 

        ///  
        /// Parses the current reader into a new 
        /// instance.
        /// 
        ///  
        /// After invocation, the target entry that was created as a result
        /// of parsing the current reader. 
        ///  
        private void ParseCurrentEntry(out AtomEntry targetEntry)
        { 
            Debug.Assert(this.reader.NodeType == XmlNodeType.Element, "this.reader.NodeType == XmlNodeType.Element");

            // Push reader.
            var callbackResult = this.entryCallback(this.reader); 
            Debug.Assert(callbackResult.Key != null, "callbackResult.Key != null");
            this.readers.Push(this.reader); 
            this.reader = callbackResult.Key; 

            this.reader.Read(); 
            Debug.Assert(this.reader.LocalName == "entry", "this.reader.LocalName == 'entry' - otherwise we're not reading the subtree");

            bool hasContent = false;
            targetEntry = new AtomEntry(); 
            targetEntry.DataValues = new List();
            targetEntry.Tag = callbackResult.Value; 
            targetEntry.ETagText = this.reader.GetAttribute(XmlConstants.AtomETagAttributeName, XmlConstants.DataWebMetadataNamespace); 

            while (this.reader.Read()) 
            {
                if (ShouldIgnoreNode(this.reader))
                {
                    continue; 
                }
 
                if (this.reader.NodeType == XmlNodeType.Element) 
                {
                    int depth = this.reader.Depth; 
                    string elementName = this.reader.LocalName;
                    string namespaceURI = this.reader.NamespaceURI;
                    if (namespaceURI == XmlConstants.AtomNamespace)
                    { 
                        if (elementName == XmlConstants.AtomCategoryElementName && targetEntry.TypeName == null)
                        { 
                            string text = this.reader.GetAttributeEx(XmlConstants.AtomCategorySchemeAttributeName, XmlConstants.AtomNamespace); 
                            if (text == this.typeScheme)
                            { 
                                targetEntry.TypeName = this.reader.GetAttributeEx(XmlConstants.AtomCategoryTermAttributeName, XmlConstants.AtomNamespace);
                            }
                        }
                        else if (elementName == XmlConstants.AtomContentElementName) 
                        {
                            hasContent = true; 
                            this.ParseCurrentContent(targetEntry); 
                        }
                        else if (elementName == XmlConstants.AtomIdElementName && targetEntry.Identity == null) 
                        {
                            // The .Identity == null check ensures that only the first id element is processed.
                            string idText = ReadElementStringForText(this.reader);
                            idText = Util.ReferenceIdentity(idText); 

                            // here we could just assign idText to Identity 
                            // however we used to check for AbsoluteUri, thus we need to 
                            // convert string to Uri and check for absoluteness
                            Uri idUri = Util.CreateUri(idText, UriKind.RelativeOrAbsolute); 
                            if (!idUri.IsAbsoluteUri)
                            {
                                throw Error.InvalidOperation(Strings.Context_TrackingExpectsAbsoluteUri);
                            } 

                            targetEntry.Identity = idText; 
                        } 
                        else if (elementName == XmlConstants.AtomLinkElementName)
                        { 
                            this.ParseCurrentLink(targetEntry);
                        }
                    }
                    else if (namespaceURI == XmlConstants.DataWebMetadataNamespace) 
                    {
                        if (elementName == XmlConstants.AtomPropertiesElementName) 
                        { 
                            if (targetEntry.MediaLinkEntry.HasValue && !targetEntry.MediaLinkEntry.Value)
                            { 
                                // This means we saw a non-empty  element but now we have a Properties element
                                // that also carries properties
                                throw Error.InvalidOperation(Strings.Deserialize_ContentPlusPropertiesNotAllowed);
                            } 

                            targetEntry.MediaLinkEntry = true; 
 
                            if (!this.reader.IsEmptyElement)
                            { 
                                this.ReadCurrentProperties(targetEntry.DataValues);
                            }
                        }
                    } 

                    SkipToEndAtDepth(this.reader, depth); 
                } 
            }
 
            if (targetEntry.Identity == null)
            {
                throw Error.InvalidOperation(Strings.Deserialize_MissingIdElement);
            } 

            if (!hasContent) 
            { 
                // Content is expected for the GetResponse operation
                throw Error.BatchStreamContentExpected(BatchStreamState.GetResponse); 
            }

            this.reader = this.readers.Pop();
        } 

        /// Parses the value for the current feed count. 
        /// This method will update the value on the current feed. 
        private void ParseCurrentFeedCount()
        { 
            if (this.feed == null)
            {
                throw new InvalidOperationException(Strings.AtomParser_FeedCountNotUnderFeed);
            } 

            if (this.feed.Count.HasValue) 
            { 
                throw new InvalidOperationException(Strings.AtomParser_ManyFeedCounts);
            } 

            long countValue;
            if (!long.TryParse(MaterializeAtom.ReadElementString(this.reader, true), System.Globalization.NumberStyles.Integer, System.Globalization.CultureInfo.InvariantCulture, out countValue))
            { 
                throw new FormatException(Strings.MaterializeFromAtom_CountFormatError);
            } 
 
            if (countValue < 0)
            { 
                throw new FormatException(Strings.MaterializeFromAtom_CountFormatError);
            }

            this.feed.Count = countValue; 
        }
 
        ///  
        /// Parsing paging links
        ///  
        private void ParseCurrentFeedPagingLinks()
        {
            // feed should never be null here since there is an outer check
            // just need to assert 
            Debug.Assert(this.feed != null, "Trying to parser paging links but feed is null.");
 
            if (this.feed.NextLink != null) 
            {
                // we have set next link before, this is a duplicate 
                // atom spec does not allow duplicated next links
                throw new InvalidOperationException(Strings.AtomMaterializer_DuplicatedNextLink);
            }
 
            string nextLink = this.reader.GetAttribute(XmlConstants.AtomHRefAttributeName);
 
            if (nextLink == null) 
            {
                throw new InvalidOperationException(Strings.AtomMaterializer_LinksMissingHref); 
            }
            else
            {
                this.feed.NextLink = this.ConvertHRefAttributeValueIntoURI(nextLink); 
            }
        } 
 
        /// 
        /// creates a new uri instance which takes into account the base uri of the reader. 
        /// 
        /// href attribute value.
        /// a new instance of uri as refered by the 
        private Uri ConvertHRefAttributeValueIntoURI(string href) 
        {
            Uri uri = Util.CreateUri(href, UriKind.RelativeOrAbsolute); 
            if (!uri.IsAbsoluteUri && !String.IsNullOrEmpty(this.reader.BaseURI)) 
            {
                Uri baseUri = Util.CreateUri(this.reader.BaseURI, UriKind.RelativeOrAbsolute); 

                // The reason why we can't use Util.CreateUri function here, is that the util method
                // checks for trailing slashes in the baseuri and starting forward slashes in the request uri
                // and does some tricks which is not consistent with the uri class behaviour. Hence using the 
                // uri class directly here.
                uri = new Uri(baseUri, uri); 
            } 

            return uri; 
        }

        #endregion Private methods.
    } 
}

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
                        

Link Menu

Network programming in C#, Network Programming in VB.NET, Network Programming in .NET
This book is available now!
Buy at Amazon US or
Buy at Amazon UK