File: Compilation\WCFModel\SchemaMerger.cs
Project: ndp\fx\src\xsp\system\Extensions\System.Web.Extensions.csproj (System.Web.Extensions)
#region Copyright (c) Microsoft Corporation
/// <copyright company='Microsoft Corporation'>
///    Copyright (c) Microsoft Corporation. All Rights Reserved.
///    Information Contained Herein is Proprietary and Confidential.
/// </copyright>
#endregion
 
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Xml;
using System.Xml.Schema;
 
#if WEB_EXTENSIONS_CODE
using System.Web.Resources;
#else
using Microsoft.VSDesigner.WCF.Resources;
#endif
 
#if WEB_EXTENSIONS_CODE
namespace System.Web.Compilation.WCFModel
#else
namespace Microsoft.VSDesigner.WCFModel
#endif
{
    /// <summary>
    /// a utility class to merge schema files, and remove duplicated part
    /// </summary>
    internal class SchemaMerger
    {
        // xml serializable attributes
        private static Type[] xmlSerializationAttributes = new Type[] {
                            typeof(System.Xml.Serialization.XmlElementAttribute),
                            typeof(System.Xml.Serialization.XmlAttributeAttribute),
                            typeof(System.Xml.Serialization.XmlAnyAttributeAttribute),
                            typeof(System.Xml.Serialization.XmlAnyElementAttribute),
                            typeof(System.Xml.Serialization.XmlTextAttribute),
        };
 
        // elements in the schema (we don't process annotation node)
        private static SchemaTopLevelItemType[] schemaTopLevelItemTypes = new SchemaTopLevelItemType[] {
            new SchemaTopLevelItemType(typeof(XmlSchemaType), "type"),
            new SchemaTopLevelItemType(typeof(XmlSchemaElement), "element"),
            new SchemaTopLevelItemType(typeof(XmlSchemaAttribute), "attribute"),
            new SchemaTopLevelItemType(typeof(XmlSchemaGroup), "group"),
            new SchemaTopLevelItemType(typeof(XmlSchemaAttributeGroup), "attributeGroup"),
        };
 
        // when properties defined in those types are different, we only report warnings, but not error messages
        private static Type[] ignorablePropertyTypes = new Type[] {
            typeof(XmlAttribute[]),
            typeof(XmlElement[]),
            typeof(XmlNode[]),
            typeof(XmlSchemaAnnotation),
        };
 
        private readonly static XmlAttribute[] emptyXmlAttributeCollection = new XmlAttribute[0];
        private readonly static object[] emptyCollection = new object[0];
 
        /// <summary>
        /// Merge and remove duplicated part from the schema list
        /// </summary>
        /// <param name="schemaList">schemas with names</param>
        /// <param name="importErrors">error messages</param>
        /// <param name="duplicatedSchemas">error messages</param>
        /// <remarks></remarks>
        internal static void MergeSchemas(IEnumerable<XmlSchema> schemaList, IList<ProxyGenerationError> importErrors, out IEnumerable<XmlSchema> duplicatedSchemas)
        {
            if (schemaList == null)
            {
                throw new ArgumentNullException("schemaList");
            }
            if (importErrors == null)
            {
                throw new ArgumentNullException("importErrors");
            }
 
            List<XmlSchema> duplicatedSchemaList = new List<XmlSchema>();
            duplicatedSchemas = duplicatedSchemaList;
 
            // types, elements, groups have their own name space
            Dictionary<XmlQualifiedName, XmlSchemaObject>[] knownItemTables = new Dictionary<XmlQualifiedName, XmlSchemaObject>[schemaTopLevelItemTypes.Length];
            for (int i = 0; i < schemaTopLevelItemTypes.Length; i++)
            {
                knownItemTables[i] = new Dictionary<XmlQualifiedName, XmlSchemaObject>();
            }
 
            foreach (XmlSchema schema in schemaList)
            {
 
                bool hasNewDefinedItems = false;
                List<XmlSchemaObject> duplicatedItems = new List<XmlSchemaObject>();
 
                for (int i = 0; i < schemaTopLevelItemTypes.Length; i++)
                {
                    Dictionary<XmlQualifiedName, XmlSchemaObject> knownItemTable = knownItemTables[i];
                    int knownItemCount = knownItemTable.Count;
                    FindDuplicatedItems(schema, schemaTopLevelItemTypes[i].ItemType, schemaTopLevelItemTypes[i].Name, knownItemTable, duplicatedItems, importErrors);
                    if (knownItemTable.Count > knownItemCount)
                    {
                        hasNewDefinedItems = true;
                    }
                }
 
                if (duplicatedItems.Count > 0)
                {
                    if (!hasNewDefinedItems)
                    {
                        // remove the whole schema...
                        duplicatedSchemaList.Add(schema);
                    }
                    else
                    {
                        // remove duplicated items only
                        foreach (XmlSchemaObject item in duplicatedItems)
                        {
                            schema.Items.Remove(item);
                        }
                    }
                }
            }
        }
 
        /// <summary>
        /// Find duplicated items in a schema
        /// </summary>
        /// <remarks></remarks>
        private static void FindDuplicatedItems(
                    XmlSchema schema,
                    Type itemType,
                    string itemTypeName,
                    Dictionary<XmlQualifiedName, XmlSchemaObject> knownItemTable,
                    List<XmlSchemaObject> duplicatedItems,
                    IList<ProxyGenerationError> importErrors)
        {
 
            string targetNamespace = schema.TargetNamespace;
            if (String.IsNullOrEmpty(targetNamespace))
            {
                targetNamespace = String.Empty;
            }
 
            foreach (XmlSchemaObject item in schema.Items)
            {
                if (itemType.IsInstanceOfType(item))
                {
 
                    XmlQualifiedName combinedName = new XmlQualifiedName(GetSchemaItemName(item), targetNamespace);
 
                    XmlSchemaObject originalItem = null;
                    if (knownItemTable.TryGetValue(combinedName, out originalItem))
                    {
                        string differentLocation;
 
                        if (!AreSchemaObjectsEquivalent(originalItem, item, out differentLocation))
                        {
                            differentLocation = CombinePath(".", differentLocation);
                            importErrors.Add(
                                    new ProxyGenerationError(
                                        ProxyGenerationError.GeneratorState.MergeMetadata,
                                        String.Empty,
                                        new InvalidOperationException(
                                            String.Format(CultureInfo.CurrentCulture, WCFModelStrings.ReferenceGroup_DuplicatedSchemaItems, itemTypeName, combinedName.ToString(), schema.SourceUri, originalItem.SourceUri, differentLocation)
                                        )
                                    )
                            );
                        }
                        else if (!String.IsNullOrEmpty(differentLocation))
                        {
                            // warning: ignorable difference found
                            differentLocation = CombinePath(".", differentLocation);
                            importErrors.Add(
                                    new ProxyGenerationError(
                                        ProxyGenerationError.GeneratorState.MergeMetadata,
                                        String.Empty,
                                        new InvalidOperationException(
                                            String.Format(CultureInfo.CurrentCulture, WCFModelStrings.ReferenceGroup_DuplicatedSchemaItemsIgnored, itemTypeName, combinedName.ToString(), schema.SourceUri, originalItem.SourceUri, differentLocation)
                                        ),
                                        true        // isWarning = true
                                    )
                            );
                        }
                        duplicatedItems.Add(item);
                    }
                    else
                    {
                        item.SourceUri = schema.SourceUri;
                        knownItemTable.Add(combinedName, item);
                    }
                }
            }
        }
 
        /// <summary>
        /// Compare two schema objects
        /// </summary>
        /// <return></return>
        /// <remarks>
        /// For all those functions, we follow the same pattern:
        ///  return false: find not-ignorable difference between them.  differentLocation will contain the path
        ///  return true, with empty differentLocation -- no difference found
        ///  return true, with non-empty differentLocation -- ignorable difference found under that location
        /// </remarks>
        private static bool AreSchemaObjectsEquivalent(XmlSchemaObject originalItem, XmlSchemaObject item, out string differentLocation)
        {
            differentLocation = String.Empty;
 
            Type itemType = originalItem.GetType();
            if (itemType != item.GetType())
            {
                return false;
            }
 
            string ignorableDifferenceLocation = String.Empty;
 
            PropertyInfo[] properties = itemType.GetProperties();
            foreach (PropertyInfo property in properties)
            {
                if (IsPersistedProperty(property))
                {
 
                    bool ignorableProperty = ShouldIgnoreSchemaProperty(property);
 
                    object originalValue = property.GetValue(originalItem, new object[] { });
                    object newValue = property.GetValue(item, new object[] { });
 
                    if (!CompareSchemaPropertyValues(property, originalValue, newValue, out differentLocation) && !ignorableProperty)
                    {
                        return false;
                    }
                    if (String.IsNullOrEmpty(ignorableDifferenceLocation))
                    {
                        ignorableDifferenceLocation = differentLocation;
                    }
                }
            }
 
            differentLocation = ignorableDifferenceLocation;
            return true;
        }
 
        /// <summary>
        /// Compare two property of a schema object
        /// </summary>
        /// <return>true: if the value are same</return>
        /// <remarks></remarks>
        private static bool CompareSchemaPropertyValues(PropertyInfo propertyInfo, object originalValue, object newValue, out string differentLocation)
        {
            differentLocation = String.Empty;
 
            if (originalValue == null && newValue == null)
            {
                return true;
            }
 
            // we create empty collection so a meaningful differentLocation could be generated
            if (typeof(XmlAttribute[]) == propertyInfo.PropertyType)
            {
                if (originalValue == null)
                {
                    originalValue = emptyXmlAttributeCollection;
                }
                if (newValue == null)
                {
                    newValue = emptyXmlAttributeCollection;
                }
 
                XmlAttribute differentAttribute1, differentAttribute2;
                if (!CompareXmlAttributeCollections((XmlAttribute[])originalValue, (XmlAttribute[])newValue, out differentAttribute1, out differentAttribute2))
                {
                    differentLocation = GetSchemaPropertyNameInXml(propertyInfo, differentAttribute1, differentAttribute2);
                    return false;
                }
                return true;
            }
 
            if (typeof(System.Collections.ICollection).IsAssignableFrom(propertyInfo.PropertyType))
            {
                if (originalValue == null)
                {
                    originalValue = emptyCollection;
                }
                if (newValue == null)
                {
                    newValue = emptyCollection;
                }
 
                object differentItem1, differentItem2;
                if (!CompareSchemaCollections((System.Collections.ICollection)originalValue, (System.Collections.ICollection)newValue, out differentItem1, out differentItem2, out differentLocation))
                {
                    differentLocation = CombinePath(GetSchemaPropertyNameInXml(propertyInfo, differentItem1, differentItem2), differentLocation);
                    return false;
                }
                else
                {
                    if (!String.IsNullOrEmpty(differentLocation))
                    {
                        // ignorable difference...
                        differentLocation = CombinePath(GetSchemaPropertyNameInXml(propertyInfo, differentItem1, differentItem2), differentLocation);
                    }
                    return true;
                }
            }
 
            if (originalValue == null || newValue == null)
            {
                differentLocation = CombinePath(GetSchemaPropertyNameInXml(propertyInfo, originalValue, newValue), differentLocation);
                return false;
            }
 
            if (originalValue.GetType() != newValue.GetType())
            {
                differentLocation = CombinePath(GetSchemaPropertyNameInXml(propertyInfo, originalValue, newValue), differentLocation);
                return false;
            }
 
            if (!CompareSchemaValues(originalValue, newValue, out differentLocation))
            {
                differentLocation = CombinePath(GetSchemaPropertyNameInXml(propertyInfo, originalValue, newValue), differentLocation);
                return false;
            }
            else
            {
                // ignorable difference...
                if (!String.IsNullOrEmpty(differentLocation))
                {
                    differentLocation = CombinePath(GetSchemaPropertyNameInXml(propertyInfo, originalValue, newValue), differentLocation);
                }
                return true;
            }
        }
 
        /// <summary>
        /// Compare two schema values
        /// </summary>
        /// <return></return>
        /// <remarks></remarks>
        private static bool CompareSchemaValues(object originalValue, object newValue, out string differentLocation)
        {
            differentLocation = String.Empty;
 
            if (originalValue == null || newValue == null)
            {
                return (originalValue == null && newValue == null);
            }
 
            if (originalValue.GetType() != newValue.GetType())
            {
                return false;
            }
 
            if (originalValue is XmlSchemaObject)
            {
                return AreSchemaObjectsEquivalent((XmlSchemaObject)originalValue, (XmlSchemaObject)newValue, out differentLocation);
            }
 
            if (originalValue is XmlAttribute)
            {
                return CompareXmlAttributes((XmlAttribute)originalValue, (XmlAttribute)newValue);
            }
 
            if (originalValue is XmlElement)
            {
                return CompareXmlElements((XmlElement)originalValue, (XmlElement)newValue, out differentLocation);
            }
 
            if (originalValue is XmlText)
            {
                return CompareXmlTexts((XmlText)originalValue, (XmlText)newValue);
            }
 
            return originalValue.Equals(newValue);
        }
 
        /// <summary>
        /// Compare two collections of items
        /// </summary>
        /// <return></return>
        /// <remarks></remarks>
        private static bool CompareSchemaCollections(System.Collections.IEnumerable originalCollection, System.Collections.IEnumerable newCollection,
                out object differentItem1, out object differentItem2, out string differentLocation)
        {
 
            differentLocation = String.Empty;
 
            System.Collections.IEnumerator list1 = originalCollection.GetEnumerator();
            System.Collections.IEnumerator list2 = newCollection.GetEnumerator();
 
            string ignorableDifferenceLocation = String.Empty;
            object ignorableDifferenceItem1 = null;
            object ignorableDifferenceItem2 = null;
 
            do
            {
                differentItem1 = list1.MoveNext() ? list1.Current : null;
                differentItem2 = list2.MoveNext() ? list2.Current : null;
 
                if (!CompareSchemaValues(differentItem1, differentItem2, out differentLocation))
                {
                    return false;
                }
 
                if (String.IsNullOrEmpty(ignorableDifferenceLocation))
                {
                    ignorableDifferenceItem1 = differentItem1;
                    ignorableDifferenceItem2 = differentItem2;
                    ignorableDifferenceLocation = differentLocation;
                }
            }
            while (differentItem1 != null && differentItem2 != null);
 
            Debug.Assert(differentItem1 == null && differentItem2 == null);
 
            differentLocation = ignorableDifferenceLocation;
            differentItem1 = ignorableDifferenceItem1;
            differentItem2 = ignorableDifferenceItem2;
 
            return true;
        }
 
        /// <summary>
        /// Compare two attributes
        /// </summary>
        /// <return></return>
        /// <remarks></remarks>
        private static bool CompareXmlAttributes(XmlAttribute attribute1, XmlAttribute attribute2)
        {
            return String.Equals(attribute1.LocalName, attribute2.LocalName, StringComparison.Ordinal) &&
                    String.Equals(attribute1.NamespaceURI, attribute2.NamespaceURI, StringComparison.Ordinal) &&
                    String.Equals(attribute1.Value, attribute2.Value, StringComparison.Ordinal);
        }
 
        /// <summary>
        /// Compare two attribute collections
        /// </summary>
        /// <return></return>
        /// <remarks></remarks>
        private static bool CompareXmlAttributeCollections(System.Collections.ICollection attributeCollection1, System.Collections.ICollection attributeCollection2, out XmlAttribute differentAttribute1, out XmlAttribute differentAttribute2)
        {
            differentAttribute1 = null;
            differentAttribute2 = null;
 
            XmlAttribute[] attributeArray1 = GetSortedAttributeArray(attributeCollection1);
            XmlAttribute[] attributeArray2 = GetSortedAttributeArray(attributeCollection2);
 
            object differentItem1, differentItem2;
            string differentLocation;
            if (!CompareSchemaCollections(attributeArray1, attributeArray2, out differentItem1, out differentItem2, out differentLocation))
            {
                differentAttribute1 = (XmlAttribute)differentItem1;
                differentAttribute2 = (XmlAttribute)differentItem2;
                return false;
            }
            return true;
        }
 
        /// <summary>
        /// sort XmlAttribute array, so we can compare two collections without being affected by the order
        /// </summary>
        /// <return></return>
        /// <remarks></remarks>
        private static XmlAttribute[] GetSortedAttributeArray(System.Collections.ICollection attributeCollection)
        {
            XmlAttribute[] attributeArray = new XmlAttribute[attributeCollection.Count];
            int index = 0;
            foreach (XmlAttribute attribute in attributeCollection)
            {
                attributeArray[index++] = attribute;
            }
 
            Array.Sort(attributeArray, new AttributeComparer());
            return attributeArray;
        }
 
        /// <summary>
        /// Compare two elements
        /// </summary>
        /// <return></return>
        /// <remarks></remarks>
        private static bool CompareXmlElements(XmlElement element1, XmlElement element2, out string differentLocation)
        {
            differentLocation = String.Empty;
 
            if (!String.Equals(element1.LocalName, element2.LocalName, StringComparison.Ordinal) ||
                    !String.Equals(element1.NamespaceURI, element2.NamespaceURI, StringComparison.Ordinal))
            {
                return false;
            }
 
            XmlAttribute differentAttribute1, differentAttribute2;
            if (!CompareXmlAttributeCollections(element1.Attributes, element2.Attributes, out differentAttribute1, out differentAttribute2))
            {
                string attributeName1 = differentAttribute1 != null ? "@" + differentAttribute1.LocalName : String.Empty;
                string attributeName2 = differentAttribute2 != null ? "@" + differentAttribute2.LocalName : String.Empty;
                differentLocation = CombineTwoNames(attributeName1, attributeName2);
                return false;
            }
 
            object differentChild1, differentChild2;
            if (!CompareSchemaCollections(element1.ChildNodes, element2.ChildNodes, out differentChild1, out differentChild2, out differentLocation))
            {
                string child1Name = differentChild1 != null ? ((XmlNode)differentChild1).LocalName : String.Empty;
                string child2Name = differentChild2 != null ? ((XmlNode)differentChild2).LocalName : String.Empty;
                differentLocation = CombinePath(CombineTwoNames(child1Name, child2Name), differentLocation);
                return false;
            }
            return true;
        }
 
        /// <summary>
        /// Compare two text nodes
        /// </summary>
        /// <return></return>
        /// <remarks></remarks>
        private static bool CompareXmlTexts(XmlText text1, XmlText text2)
        {
            return String.Equals(text1.Value, text2.Value, StringComparison.Ordinal);
        }
 
        /// <summary>
        /// Combine two path (similar to xpath) in error messages
        /// </summary>
        /// <return></return>
        /// <remarks></remarks>
        private static string CombinePath(string path1, string path2)
        {
            if (String.IsNullOrEmpty(path1))
            {
                return path2;
            }
            else if (String.IsNullOrEmpty(path2))
            {
                return path1;
            }
 
            return path1 + "/" + path2;
        }
 
        /// <summary>
        /// Get Name of a top level schema item
        /// </summary>
        /// <param name="item"></param>
        /// <return></return>
        /// <remarks></remarks>
        private static string GetSchemaItemName(XmlSchemaObject item)
        {
            if (item == null)
            {
                throw new ArgumentNullException("item");
            }
 
            Type itemType = item.GetType();
            PropertyInfo nameProperty = itemType.GetProperty("Name");
            if (nameProperty != null)
            {
                object nameValue = nameProperty.GetValue(item, new object[] { });
                if (nameValue is string)
                {
                    return (string)nameValue;
                }
                return String.Empty;
            }
 
            return String.Empty;
        }
 
        /// <summary>
        /// Generate end-user unstandable property name -- we will use name in the schema file, but not name in object model
        /// </summary>
        /// <return></return>
        /// <remarks></remarks>
        private static string GetSchemaPropertyNameInXml(PropertyInfo property, object value1, object value2)
        {
            object[] propertyAttributes = property.GetCustomAttributes(true);
            string name = String.Empty;
 
            if (propertyAttributes != null)
            {
                string name1 = GetSchemaPropertyNameInXmlHelper(propertyAttributes, value1);
                string name2 = GetSchemaPropertyNameInXmlHelper(propertyAttributes, value2);
 
                name = CombineTwoNames(name1, name2);
            }
 
            if (String.IsNullOrEmpty(name))
            {
                Debug.Fail("Why we didn't get a property name with normal routine?");
                name = property.Name;
            }
 
            return name;
        }
 
        /// <summary>
        /// Combine names of two properties in error messages
        /// </summary>
        /// <remarks></remarks>
        private static string CombineTwoNames(string name1, string name2)
        {
            string name = String.Empty;
            if (name1.Length > 0)
            {
                if (name2.Length > 0)
                {
                    if (String.Equals(name1, name2, StringComparison.Ordinal))
                    {
                        name = name1;
                    }
                    else
                    {
                        name = name1 + "|" + name2;
                    }
                }
                else
                {
                    name = name1;
                }
            }
            else if (name2.Length > 0)
            {
                name = name2;
            }
            return name;
        }
 
        /// <summary>
        /// a helper function to generate names
        /// </summary>
        /// <remarks></remarks>
        private static string GetSchemaPropertyNameInXmlHelper(object[] propertyAttributes, object value)
        {
            if (value != null)
            {
                foreach (object attribute in propertyAttributes)
                {
                    if (attribute is System.Xml.Serialization.XmlAttributeAttribute)
                    {
                        return "@" + ((System.Xml.Serialization.XmlAttributeAttribute)attribute).AttributeName;
                    }
                    if (attribute is System.Xml.Serialization.XmlElementAttribute)
                    {
                        System.Xml.Serialization.XmlElementAttribute elementAttribute = (System.Xml.Serialization.XmlElementAttribute)attribute;
                        Type elementType = elementAttribute.Type;
                        if (elementType == null || elementType.IsInstanceOfType(value))
                        {
                            if (value is XmlSchemaObject)
                            {
                                string itemName = GetSchemaItemName((XmlSchemaObject)value);
                                if (itemName.Length > 0)
                                {
                                    return String.Format(System.Globalization.CultureInfo.InvariantCulture, "{0}[@name='{1}']", elementAttribute.ElementName, itemName);
                                }
                            }
                            return elementAttribute.ElementName;
                        }
                    }
                    if (attribute is System.Xml.Serialization.XmlAnyAttributeAttribute)
                    {
                        if (value is XmlAttribute)
                        {
                            return "@" + ((XmlAttribute)value).LocalName;
                        }
                    }
                    if (attribute is System.Xml.Serialization.XmlAnyElementAttribute)
                    {
                        if (value is XmlElement)
                        {
                            return ((XmlElement)value).LocalName;
                        }
                    }
                    if (attribute is System.Xml.Serialization.XmlTextAttribute)
                    {
                        if (value is XmlText)
                        {
                            return ((XmlText)value).Name;
                        }
                    }
                }
            }
 
            return String.Empty;
        }
 
        /// <summary>
        /// Check whether a property is persisted with XmlSerialization
        /// </summary>
        /// <param name="property"></param>
        /// <return></return>
        /// <remarks></remarks>
        private static bool IsPersistedProperty(PropertyInfo property)
        {
            object[] propertyAttributes = property.GetCustomAttributes(true);
            if (propertyAttributes != null)
            {
                foreach (object attribute in propertyAttributes)
                {
                    foreach (Type serializationAttibuteType in xmlSerializationAttributes)
                    {
                        if (serializationAttibuteType.IsInstanceOfType(attribute))
                        {
                            return true;
                        }
                    }
                }
            }
            return false;
        }
 
        /// <summary>
        /// check whether we should report warning but not error messages, when the property is different
        /// </summary>
        /// <remarks></remarks>
        private static bool ShouldIgnoreSchemaProperty(PropertyInfo property)
        {
            Type propertyType = property.PropertyType;
            foreach (Type ignoreableType in ignorablePropertyTypes)
            {
                if (propertyType == ignoreableType || propertyType.IsSubclassOf(ignoreableType))
                {
                    return true;
                }
            }
 
            // special case constraints...
            if (String.Equals(property.Name, "Constraints", StringComparison.Ordinal))
            {
                return true;
            }
            return false;
        }
 
        /// <summary>
        /// a helper structure to hold top level items we want to scan
        /// </summary>
        /// <remarks></remarks>
        private struct SchemaTopLevelItemType
        {
            public Type ItemType;
            public string Name;
 
            public SchemaTopLevelItemType(Type itemType, string name)
            {
                this.ItemType = itemType;
                this.Name = name;
            }
        };
 
        /// <summary>
        /// Helper class to compare two XmlAttributes
        /// </summary>
        /// <remarks></remarks>
        private class AttributeComparer : System.Collections.Generic.IComparer<XmlAttribute>
        {
 
            public int Compare(System.Xml.XmlAttribute x, System.Xml.XmlAttribute y)
            {
                int namespaceResult = String.Compare(x.NamespaceURI, y.NamespaceURI, StringComparison.Ordinal);
                if (namespaceResult != 0)
                {
                    return namespaceResult;
                }
 
                return String.Compare(x.Name, y.Name, StringComparison.Ordinal);
            }
        }
    }
}