File: System\Data\Services\Client\XmlUtil.cs
Project: ndp\fx\src\DataWeb\Client\System.Data.Services.Client.csproj (System.Data.Services.Client)
//---------------------------------------------------------------------
// <copyright file="XmlUtil.cs" company="Microsoft">
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
// <summary>
// static xml utility functions
// </summary>
//---------------------------------------------------------------------
 
#if ASTORIA_CLIENT
namespace System.Data.Services.Client
#else
namespace System.Data.Services
#endif
{
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Globalization;
    using System.IO;
    using System.Text;
    using System.Xml;
 
    /// <summary>
    /// static uri utility functions
    /// </summary>
    internal static partial class UriUtil
    {
#if !ASTORIA_CLIENT
        /// <summary>Creates a URI suitable for host-agnostic comparison purposes.</summary>
        /// <param name="uri">URI to compare.</param>
        /// <returns>URI suitable for comparison.</returns>
        private static Uri CreateBaseComparableUri(Uri uri)
        {
            Debug.Assert(uri != null, "uri != null");
 
            uri = new Uri(CommonUtil.UriToString(uri).ToUpper(CultureInfo.InvariantCulture), UriKind.RelativeOrAbsolute);
 
            UriBuilder builder = new UriBuilder(uri);
            builder.Host = "h";
            builder.Port = 80;
            builder.Scheme = "http";
            return builder.Uri;
        }
#endif
 
        /// <summary>Parse the atom link relation attribute value</summary>
        /// <param name="value">atom link relation attribute value</param>
        /// <returns>the assocation name or null</returns>
        internal static string GetNameFromAtomLinkRelationAttribute(string value)
        {
            string name = null;
            if (!String.IsNullOrEmpty(value))
            {
                Uri uri = null;
                try
                {
                    uri = new Uri(value, UriKind.RelativeOrAbsolute);
                }
                catch (UriFormatException)
                {   // ignore the exception, obviously we don't have our expected relation value
                }
 
                if ((null != uri) && uri.IsAbsoluteUri)
                {
                    string unescaped = uri.GetComponents(UriComponents.AbsoluteUri, UriFormat.SafeUnescaped);
                    if (unescaped.StartsWith(XmlConstants.DataWebRelatedNamespace, StringComparison.Ordinal))
                    {
                        name = unescaped.Substring(XmlConstants.DataWebRelatedNamespace.Length);
                    }
                }
            }
 
            return name;
        }
#if !ASTORIA_CLIENT
        /// <summary>is the serviceRoot the base of the request uri</summary>
        /// <param name="baseUriWithSlash">baseUriWithSlash</param>
        /// <param name="requestUri">requestUri</param>
        /// <returns>true if the serviceRoot is the base of the request uri</returns>
        internal static bool IsBaseOf(Uri baseUriWithSlash, Uri requestUri)
        {
            return baseUriWithSlash.IsBaseOf(requestUri);
        }
        /// <summary>
        /// Determines whether the <paramref name="current"/> Uri instance is a 
        /// base of the specified Uri instance. 
        /// </summary>
        /// <param name="current">Candidate base URI.</param>
        /// <param name="uri">The specified Uri instance to test.</param>
        /// <returns>true if the current Uri instance is a base of uri; otherwise, false.</returns>
        internal static bool UriInvariantInsensitiveIsBaseOf(Uri current, Uri uri)
        {
            Debug.Assert(current != null, "current != null");
            Debug.Assert(uri != null, "uri != null");
 
            Uri upperCurrent = CreateBaseComparableUri(current);
            Uri upperUri = CreateBaseComparableUri(uri);
 
            return UriUtil.IsBaseOf(upperCurrent, upperUri);
        }
#endif
    }
 
    /// <summary>
    /// static xml utility function
    /// </summary>
    internal static partial class XmlUtil
    {
        /// <summary>
        /// An initial nametable so that string comparisons during
        /// deserialization become reference comparisions
        /// </summary>
        /// <returns>nametable with element names used in application/atom+xml payload</returns>
        private static NameTable CreateAtomNameTable()
        {
            NameTable table = new NameTable();
            table.Add(XmlConstants.AtomNamespace);
            table.Add(XmlConstants.DataWebNamespace);
            table.Add(XmlConstants.DataWebMetadataNamespace);
 
            table.Add(XmlConstants.AtomContentElementName);
#if ASTORIA_CLIENT
            table.Add(XmlConstants.AtomContentSrcAttributeName);
#endif
            table.Add(XmlConstants.AtomEntryElementName);
            table.Add(XmlConstants.AtomETagAttributeName);
            table.Add(XmlConstants.AtomFeedElementName);
#if ASTORIA_CLIENT
            table.Add(XmlConstants.AtomIdElementName);
#endif
            table.Add(XmlConstants.AtomInlineElementName);
#if ASTORIA_CLIENT
            table.Add(XmlConstants.AtomLinkElementName);
            table.Add(XmlConstants.AtomLinkRelationAttributeName);
#endif
            table.Add(XmlConstants.AtomNullAttributeName);
            table.Add(XmlConstants.AtomPropertiesElementName);
            table.Add(XmlConstants.AtomTitleElementName);
            table.Add(XmlConstants.AtomTypeAttributeName);
 
            table.Add(XmlConstants.XmlErrorCodeElementName);
            table.Add(XmlConstants.XmlErrorElementName);
            table.Add(XmlConstants.XmlErrorInnerElementName);
            table.Add(XmlConstants.XmlErrorMessageElementName);
            table.Add(XmlConstants.XmlErrorTypeElementName);
            return table;
        }
 
        /// <summary>
        /// Creates a new XmlReader instance using the specified stream reader
        /// </summary>
        /// <param name="stream">The stream reader from which you want to read</param>
        /// <param name="encoding">The encoding of the stream</param>
        /// <returns>XmlReader with the appropriate xml settings</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Caller is responsible for disposing the XmlReader")]
        internal static XmlReader CreateXmlReader(Stream stream, Encoding encoding)
        {
            Debug.Assert(null != stream, "null stream");
 
            XmlReaderSettings settings = new XmlReaderSettings();
            settings.CheckCharacters = false;
#if ASTORIA_CLIENT
            // we should close the underlying stream only for the client.
            // In server, we should never close the underlying stream - neither after reading nor after writing,
            // since the underlying host owns the stream.
            settings.CloseInput = true;
#endif
            settings.IgnoreWhitespace = true;
            settings.NameTable = XmlUtil.CreateAtomNameTable();
 
            if (null == encoding)
            {   // auto-detect the encoding
                return XmlReader.Create(stream, settings);
            }
 
            return XmlReader.Create(new StreamReader(stream, encoding), settings);
        }
 
        /// <summary>
        /// Creates a new XmlWriterSettings instance using the encoding.
        /// </summary>
        /// <param name="encoding"> Encoding that you want to specify in the reader settings as well as the processing instruction </param>
        internal static XmlWriterSettings CreateXmlWriterSettings(Encoding encoding)
        {
            Debug.Assert(null != encoding, "null != encoding");
 
            // No need to close the underlying stream here for client,
            // since it always MemoryStream for writing i.e. it caches the response before processing.
            XmlWriterSettings settings = new XmlWriterSettings();
            settings.CheckCharacters = false;
            settings.ConformanceLevel = ConformanceLevel.Fragment;
            settings.Encoding = encoding;
            settings.Indent = true;
            settings.NewLineHandling = NewLineHandling.Entitize;
 
            Debug.Assert(!settings.CloseOutput, "!settings.CloseOutput -- otherwise default changed?");
 
            return settings;
        }
 
        /// <summary>
        /// Creates a new XmlWriter instance using the specified stream and writers the processing instruction
        /// with the given encoding value
        /// </summary>
        /// <param name="stream"> The stream to which you want to write</param>
        /// <param name="encoding"> Encoding that you want to specify in the reader settings as well as the processing instruction </param>
        /// <returns>XmlWriter with the appropriate xml settings and processing instruction</returns>
        internal static XmlWriter CreateXmlWriterAndWriteProcessingInstruction(Stream stream, Encoding encoding)
        {
            Debug.Assert(null != stream, "null != stream");
            Debug.Assert(null != encoding, "null != encoding");
 
            XmlWriterSettings settings = CreateXmlWriterSettings(encoding);
            XmlWriter writer = XmlWriter.Create(stream, settings);
            writer.WriteProcessingInstruction("xml", "version=\"1.0\" encoding=\"" + encoding.WebName + "\" standalone=\"yes\"");
            return writer;
        }
 
#if ASTORIA_CLIENT
        /// <summary>
        /// get attribute value from specified namespace or empty namespace
        /// </summary>
        /// <param name="reader">reader</param>
        /// <param name="attributeName">attributeName</param>
        /// <param name="namespaceUri">namespaceUri</param>
        /// <returns>attribute value</returns>
        internal static string GetAttributeEx(this XmlReader reader, string attributeName, string namespaceUri)
        {
            return reader.GetAttribute(attributeName, namespaceUri) ?? reader.GetAttribute(attributeName);
        }
 
        /// <summary>Recursively removes duplicate namespace attributes from the specified <paramref name="element"/>.</summary>
        /// <param name="element">Element to remove duplicate attributes from.</param>
        internal static void RemoveDuplicateNamespaceAttributes(System.Xml.Linq.XElement element)
        {
            Debug.Assert(element != null, "element != null");
 
            // Nesting XElements through our client wrappers may produce duplicate namespaces.
            // Might need an XML fix, but won't happen for 3.5 for back compat.
            HashSet<string> names = new HashSet<string>(EqualityComparer<string>.Default);
            foreach (System.Xml.Linq.XElement e in element.DescendantsAndSelf())
            {
                bool attributesFound = false;
                foreach (var attribute in e.Attributes())
                {
                    if (!attributesFound)
                    {
                        attributesFound = true;
                        names.Clear();
                    }
 
                    if (attribute.IsNamespaceDeclaration)
                    {
                        string localName = attribute.Name.LocalName;
                        bool alreadyPresent = names.Add(localName) == false;
                        if (alreadyPresent)
                        {
                            attribute.Remove();
                        }
                    }
                }
            }
        }
#endif
    }
}