File: System\Data\Services\Client\Xml\XmlWrappingReader.cs
Project: ndp\fx\src\DataWeb\Client\System.Data.Services.Client.csproj (System.Data.Services.Client)
//---------------------------------------------------------------------
// <copyright file="XmlWrappingReader.cs" company="Microsoft">
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
// <summary>
//      Provides a class used to wrap XmlReader instances.
// </summary>
//
// @owner  Microsoft
//---------------------------------------------------------------------
 
namespace System.Data.Services.Client.Xml
{
    #region Namespaces.
 
    using System.Xml;
    using System.Xml.Schema;
    using System.Collections.Generic;
    using System.Diagnostics;
 
    #endregion Namespaces.
 
    /// <summary>Use this class to wrap an existing <see cref="XmlReader"/>.</summary>
    internal class XmlWrappingReader : XmlReader, IXmlLineInfo
    {
        #region Private fields.
 
        /// <summary>Wrapper reader.</summary>
        private XmlReader reader;
 
        /// <summary>Line information of the wrapper reader (possibly null).</summary>
        private IXmlLineInfo readerAsIXmlLineInfo;
 
        /// <summary>stack to keep track of the base uri.</summary>
        private Stack<XmlBaseState> xmlBaseStack;
 
        /// <summary>while creating a nested reader, we need to use the base uri from the previous reader. This field is used for that purpose.</summary>
        private string previousReaderBaseUri;
 
        #endregion Private fields.
 
        /// <summary>Initializes a new <see cref="XmlWrappingReader"/> instance.</summary>
        /// <param name="baseReader">Reader to wrap.</param>
        internal XmlWrappingReader(XmlReader baseReader)
        {
            this.Reader = baseReader;
        }
 
        #region Properties.
 
        /// <summary>Gets the number of attributes on the current node.</summary>
        public override int AttributeCount
        {
            get
            {
                return this.reader.AttributeCount;
            }
        }
 
        /// <summary>Gets the base URI of the current node.</summary>
        public override string BaseURI
        {
            get
            {
                if (this.xmlBaseStack != null && this.xmlBaseStack.Count > 0)
                {
                    return this.xmlBaseStack.Peek().BaseUri.AbsoluteUri;
                }
                else if (!String.IsNullOrEmpty(this.previousReaderBaseUri))
                {
                    return this.previousReaderBaseUri;
                }
 
                return this.reader.BaseURI;
            }
        }
 
        /// <summary>Gets a value indicating whether this reader can parse and resolve entities.</summary>
        public override bool CanResolveEntity
        {
            get
            {
                return this.reader.CanResolveEntity;
            }
        }
 
        /// <summary>Gets the depth of the current node in the XML document.</summary>
        public override int Depth
        {
            get
            {
                return this.reader.Depth;
            }
        }
 
        // NOTE: there is no support for wrapping the DtdSchemaInfo property.
 
        /// <summary>Gets a value indicating whether the reader is positioned at the end of the stream.</summary>
        public override bool EOF
        {
            get
            {
                return this.reader.EOF;
            }
        }
 
        /// <summary>Gets a value indicating whether the current node has any attributes.</summary>
        public override bool HasAttributes
        {
            get
            {
                return this.reader.HasAttributes;
            }
        }
 
        /// <summary>Gets a value indicating whether the current node can have a <see cref="Value"/>.</summary>
        public override bool HasValue
        {
            get
            {
                return this.reader.HasValue;
            }
        }
 
        /// <summary>
        /// Gets a value indicating whether the current node is an attribute that was generated from the default value 
        /// defined in the DTD or schema.
        /// </summary>
        public override bool IsDefault
        {
            get
            {
                return this.reader.IsDefault;
            }
        }
 
        /// <summary>Gets a value indicating whether the current node is an empty element.</summary>
        public override bool IsEmptyElement
        {
            get
            {
                return this.reader.IsEmptyElement;
            }
        }
 
        /// <summary>Gets the current line number.</summary>
        public virtual int LineNumber
        {
            get
            {
                if (this.readerAsIXmlLineInfo != null)
                {
                    return this.readerAsIXmlLineInfo.LineNumber;
                }
 
                return 0;
            }
        }
 
        /// <summary>Gets the current line position.</summary>
        public virtual int LinePosition
        {
            get
            {
                if (this.readerAsIXmlLineInfo != null)
                {
                    return this.readerAsIXmlLineInfo.LinePosition;
                }
 
                return 0;
            }
        }
 
        /// <summary>Gets the local name of the current node.</summary>
        public override string LocalName
        {
            get
            {
                return this.reader.LocalName;
            }
        }
 
        /// <summary>Gets the qualified name of the current node.</summary>
        public override string Name
        {
            get
            {
                return this.reader.Name;
            }
        }
 
        /// <summary>Gets the namespace URI (as defined in the W3C Namespace specification) of the node on which the reader is positioned.</summary>
        public override string NamespaceURI
        {
            get
            {
                return this.reader.NamespaceURI;
            }
        }
 
        /// <summary>Gets the XmlNameTable associated with this implementation.</summary>
        public override XmlNameTable NameTable
        {
            get
            {
                return this.reader.NameTable;
            }
        }
 
        /// <summary>Gets the type of the current node.</summary>
        public override XmlNodeType NodeType
        {
            get
            {
                return this.reader.NodeType;
            }
        }
 
        /// <summary>Gets the namespace prefix associated with the current node.</summary>
        public override string Prefix
        {
            get
            {
                return this.reader.Prefix;
            }
        }
 
#if !ASTORIA_LIGHT
 
        /// <summary>Gets the quotation mark character used to enclose the value of an attribute node.</summary>
        public override char QuoteChar
        {
            get
            {
                return this.reader.QuoteChar;
            }
        }
 
#endif
 
        /// <summary>Gets the state of the reader.</summary>
        public override ReadState ReadState
        {
            get
            {
                return this.reader.ReadState;
            }
        }
 
#if !ASTORIA_LIGHT
 
        /// <summary>Gets the schema information that has been assigned to the current node as a result of schema validation.</summary>
        public override IXmlSchemaInfo SchemaInfo
        {
            get
            {
                return this.reader.SchemaInfo;
            }
        }
#endif
 
        /// <summary>Gets the XmlReaderSettings object used to create this XmlReader instance.</summary>
        public override XmlReaderSettings Settings
        {
            get
            {
                return this.reader.Settings;
            }
        }
 
        /// <summary>Gets the text value of the current node.</summary>
        public override string Value
        {
            get
            {
                return this.reader.Value;
            }
        }
 
        /// <summary>Gets the Common Language Runtime (CLR) type for the current node.</summary>
        public override Type ValueType
        {
            get
            {
                return this.reader.ValueType;
            }
        }
 
        /// <summary>Gets the current xml:lang scope.</summary>
        public override string XmlLang
        {
            get
            {
                return this.reader.XmlLang;
            }
        }
 
        /// <summary>Gets the current xml:space scope.</summary>
        public override XmlSpace XmlSpace
        {
            get
            {
                return this.reader.XmlSpace;
            }
        }
 
        /// <summary>Wrapped reader.</summary>
        protected XmlReader Reader
        {
            get
            {
                return this.reader;
            }
 
            set
            {
                this.reader = value;
                this.readerAsIXmlLineInfo = value as IXmlLineInfo;
            }
        }
 
        #endregion Properties.
 
        #region Methods.
 
        /// <summary>Changes the ReadState to Closed.</summary>
        public override void Close()
        {
            this.reader.Close();
        }
 
        /// <summary>Gets the value of the attribute with the specified index.</summary>
        /// <param name="i">The index of the attribute. The index is zero-based. (The first attribute has index 0.)</param>
        /// <returns>The value of the specified attribute. This method does not move the reader.</returns>
        public override string GetAttribute(int i)
        {
            return this.reader.GetAttribute(i);
        }
 
        /// <summary>Gets the value of the attribute with the specified name.</summary>
        /// <param name="name">The qualified name of the attribute.</param>
        /// <returns>
        /// The value of the specified attribute. If the attribute is not found or the value is String.Empty, 
        /// a null reference is returned.
        /// </returns>
        public override string GetAttribute(string name)
        {
            return this.reader.GetAttribute(name);
        }
 
        /// <summary>Gets the value of the attribute with the specified index.</summary>
        /// <param name="name">The local name of the attribute.</param>
        /// <param name="namespaceURI">The namespace URI of the attribute.</param>
        /// <returns>
        /// The value of the specified attribute. If the attribute is not found or the value is String.Empty, 
        /// a null reference is returned.
        /// </returns>
        public override string GetAttribute(string name, string namespaceURI)
        {
            return this.reader.GetAttribute(name, namespaceURI);
        }
 
        /// <summary>Gets a value indicating whether the class can return line information.</summary>
        /// <returns>true if LineNumber and LinePosition can be provided; otherwise, false.</returns>
        public virtual bool HasLineInfo()
        {
            return ((this.readerAsIXmlLineInfo != null) && this.readerAsIXmlLineInfo.HasLineInfo());
        }
 
        /// <summary>Resolves a namespace prefix in the current element's scope.</summary>
        /// <param name="prefix">
        /// The prefix whose namespace URI you want to resolve. To match the default namespace, pass an empty string.
        /// </param>
        /// <returns>The namespace URI to which the prefix maps or a null reference.</returns>
        public override string LookupNamespace(string prefix)
        {
            return this.reader.LookupNamespace(prefix);
        }
 
        /// <summary>Moves to the attribute with the specified index.</summary>
        /// <param name="i">The index of the attribute.</param>
        public override void MoveToAttribute(int i)
        {
            this.reader.MoveToAttribute(i);
        }
 
        /// <summary>Moves to the attribute with the specified name.</summary>
        /// <param name="name">The qualified name of the attribute.</param>
        /// <returns>
        /// true if the attribute is found; otherwise, false. If false, the reader's position does not change.
        /// </returns>
        public override bool MoveToAttribute(string name)
        {
            return this.reader.MoveToAttribute(name);
        }
 
        /// <summary>Moves to the attribute with the specified name.</summary>
        /// <param name="name">The local name of the attribute.</param>
        /// <param name="ns">The namespace URI of the attribute.</param>
        /// <returns>
        /// true if the attribute is found; otherwise, false. If false, the reader's position does not change.
        /// </returns>
        public override bool MoveToAttribute(string name, string ns)
        {
            return this.reader.MoveToAttribute(name, ns);
        }
 
        /// <summary>Moves to the element that contains the current attribute node.</summary>
        /// <returns>
        /// true if the reader is positioned on an attribute (the reader moves to the element that owns the attribute); 
        /// false if the reader is not positioned on an attribute (the position of the reader does not change).
        /// </returns>
        public override bool MoveToElement()
        {
            return this.reader.MoveToElement();
        }
 
        /// <summary>Moves to the first attribute.</summary>
        /// <returns>
        /// true if an attribute exists (the reader moves to the first attribute); otherwise, false (the position of 
        /// the reader does not change).
        /// </returns>
        public override bool MoveToFirstAttribute()
        {
            return this.reader.MoveToFirstAttribute();
        }
 
        /// <summary>Moves to the next attribute.</summary>
        /// <returns>true if there is a next attribute; false if there are no more attributes.</returns>
        public override bool MoveToNextAttribute()
        {
            return this.reader.MoveToNextAttribute();
        }
 
        /// <summary>Reads the next node from the stream.</summary>
        /// <returns>true if the next node was read successfully; false if there are no more nodes to read.</returns>
        public override bool Read()
        {
            if (this.reader.NodeType == XmlNodeType.EndElement)
            {
                this.PopXmlBase();
            }
            else
            {
                // If the xmlreader is located at the attributes, IsEmptyElement will always return false.
                // Hence we need to call MoveToElement first, before checking for IsEmptyElement
                this.reader.MoveToElement();
                if (this.reader.IsEmptyElement)
                {
                    this.PopXmlBase();
                }
            }
 
            bool result = this.reader.Read();
            if (result) 
            {
                if (this.reader.NodeType == XmlNodeType.Element &&
                    this.reader.HasAttributes) 
                {
                    string baseAttribute = this.reader.GetAttribute(XmlConstants.XmlBaseAttributeNameWithPrefix);
                    if (String.IsNullOrEmpty(baseAttribute))
                    {
                        // If there is no xml base attribute specified, don't do anything
                        return result;
                    }
 
                    Uri newBaseUri = null;
                    newBaseUri = Util.CreateUri(baseAttribute, UriKind.RelativeOrAbsolute);
 
                    // Initialize the stack first time you see the xml base uri
                    if (this.xmlBaseStack == null)
                    {
                        this.xmlBaseStack = new Stack<XmlBaseState>();
                    }
 
                    if (this.xmlBaseStack.Count > 0)
                    {
                        // If there is a xml:base attribute already specified, then the new xml base
                        // value must be relative to the previous xml base.
                        // For more information, look into section 3 of the following RFC
                        // http://www.w3.org/TR/2001/REC-xmlbase-20010627/
                        newBaseUri = Util.CreateUri(this.xmlBaseStack.Peek().BaseUri, newBaseUri);
                    }
 
                    // Push current state and allocate new one
                    this.xmlBaseStack.Push(new XmlBaseState(newBaseUri, this.reader.Depth));
                }
            }
 
            return result;
        }
 
        /// <summary>Parses the attribute value into one or more Text, EntityReference, or EndEntity nodes.</summary>
        /// <returns>true if there are nodes to return.</returns>
        public override bool ReadAttributeValue()
        {
            return this.reader.ReadAttributeValue();
        }
 
        /// <summary>Resolves the entity reference for EntityReference nodes.</summary>
        public override void ResolveEntity()
        {
            this.reader.ResolveEntity();
        }
 
        /// <summary>Skips the children of the current node.</summary>
        public override void Skip()
        {
            this.reader.Skip();
        }
 
        /// <summary>
        /// Creates a new instance of XmlWrappingReader instance which wraps the <paramref name="newReader"/>
        /// </summary>
        /// <param name="currentBaseUri">current base uri.</param>
        /// <param name="newReader">xml reader which needs to be wrapped.</param>
        /// <returns>a new instance of XmlWrappingReader.</returns>
        internal static XmlWrappingReader CreateReader(string currentBaseUri, XmlReader newReader)
        {
            Debug.Assert(!(newReader is XmlWrappingReader), "The new reader must not be a xmlWrappingReader");
            XmlWrappingReader reader = new XmlWrappingReader(newReader);
            reader.previousReaderBaseUri = currentBaseUri;
            return reader;
        }
 
        /// <summary>
        /// Releases the unmanaged resources used by the XmlReader and optionally releases the managed resources.
        /// </summary>
        /// <param name="disposing">
        /// true to release both managed and unmanaged resources; false to release only unmanaged resources.
        /// </param>
        protected override void Dispose(bool disposing)
        {
            if (this.reader != null)
            {
                ((IDisposable)this.reader).Dispose();
            }
 
            // XmlReader.Dispose(bool) does nothing, but for completeness w/ FxCop
            base.Dispose(disposing);
        }
 
        /// <summary>
        /// Pops the xml base from the top of the stack, if required.
        /// </summary>
        private void PopXmlBase()
        {
            if (this.xmlBaseStack != null && this.xmlBaseStack.Count > 0 && this.reader.Depth == this.xmlBaseStack.Peek().Depth)
            {
                this.xmlBaseStack.Pop();
            }
        }
 
        #endregion Methods.
 
        #region Private Class
 
        /// <summary>
        /// Private class to maintain the state information for keeping track of base uri's.
        /// </summary>
        private class XmlBaseState
        {
            /// <summary>
            /// Creates a new instance of the XmlBaseState class.
            /// </summary>
            /// <param name="baseUri">base uri for the given element.</param>
            /// <param name="depth">depth of the element.</param>
            internal XmlBaseState(Uri baseUri, int depth)
            {
                this.BaseUri = baseUri;
                this.Depth = depth;
            }
 
            /// <summary>base uri as specified in the xmml:base attribute of the given element.</summary>
            public Uri BaseUri
            {
                get;
                private set;
            }
 
            /// <summary>depth of the element.</summary>
            public int Depth
            {
                get;
                private set;
            }
        }
 
        #endregion // Private Class
    }
}