File: XmlSiteMapProvider.cs
Project: ndp\fx\src\xsp\system\Web\System.Web.csproj (System.Web)
//------------------------------------------------------------------------------
// <copyright file="XmlSiteMapProvider.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
 
/*
 * XmlSiteMapProvider class definition
 *
 * Copyright (c) 2002 Microsoft Corporation
 */
 
namespace System.Web {
 
    using System;
    using System.Collections;
    using System.Collections.Specialized;
    using System.ComponentModel;
    using System.ComponentModel.Design;
    using System.Configuration;
    using System.Configuration.Provider;
    using System.Diagnostics.CodeAnalysis;
    using System.Globalization;
    using System.IO;
    using System.Resources;
    using System.Security;
    using System.Security.Permissions;
    using System.Web.Compilation;
    using System.Web.Configuration;
    using System.Web.Hosting;
    using System.Web.UI;
    using System.Web.Util;
    using System.Xml;
 
    // XmlMapProvider that generates sitemap tree from xml files
 
    public class XmlSiteMapProvider : StaticSiteMapProvider, IDisposable {
 
        private string _filename;
        private VirtualPath _virtualPath;
        private VirtualPath _normalizedVirtualPath;
        private SiteMapNode _siteMapNode;
        private XmlDocument _document;
        private bool _initialized;
        private FileChangeEventHandler _handler;
        private StringCollection _parentSiteMapFileCollection;
 
        private const string _providerAttribute = "provider";
        private const string _siteMapFileAttribute = "siteMapFile";
        private const string _siteMapNodeName = "siteMapNode";
        private const string _xmlSiteMapFileExtension = ".sitemap";
        private const string _resourcePrefix = "$resources:";
        private const int _resourcePrefixLength = 10;
        private const char _resourceKeySeparator = ',';
        private static readonly char[] _seperators = new char[] { ';', ',' };
 
        private ArrayList _childProviderList;
 
        // table containing mappings from child providers to their root nodes.
        private Hashtable _childProviderTable;
 
 
        public XmlSiteMapProvider() {
        }
 
        private ArrayList ChildProviderList {
            get {
                ArrayList returnList = _childProviderList;
                if (returnList == null) {
                    lock (_lock) {
                        if (_childProviderList == null) {
                            returnList = ArrayList.ReadOnly(new ArrayList(ChildProviderTable.Keys));
                            _childProviderList = returnList;
                        }
                        else {
                            returnList = _childProviderList;
                        }
                    }
                }
 
                return returnList;
            }
        }
 
        private Hashtable ChildProviderTable {
            get {
                if (_childProviderTable == null) {
                    lock (_lock) {
                        if (_childProviderTable == null) {
                            _childProviderTable = new Hashtable();
                        }
                    }
                }
 
                return _childProviderTable;
            }
        }
 
 
        public override SiteMapNode RootNode {
            get {
                BuildSiteMap();
                SiteMapNode node = ReturnNodeIfAccessible(_siteMapNode);
                return ApplyModifierIfExists(node);
            }
        }
 
        public override SiteMapNode CurrentNode {
            get {
                return ApplyModifierIfExists(base.CurrentNode);
            }
        }
 
        public override SiteMapNode GetParentNode(SiteMapNode node) {
            SiteMapNode parentNode = base.GetParentNode(node);
            return ApplyModifierIfExists(parentNode);
        }
 
        public override SiteMapNodeCollection GetChildNodes(SiteMapNode node) {
            SiteMapNodeCollection subNodes = base.GetChildNodes(node);
            HttpContext context = HttpContext.Current;
 
            // Do nothing if the modifier doesn't apply
            if (context == null || !context.Response.UsePathModifier || subNodes.Count == 0) {
                return subNodes;
            }
 
            // Apply the modifier to the children nodes
            SiteMapNodeCollection resultNodes = new SiteMapNodeCollection(subNodes.Count);
        
            foreach (SiteMapNode n in subNodes) {
                resultNodes.Add(ApplyModifierIfExists(n));
            }
 
            return resultNodes;
        }
 
        protected internal override void AddNode(SiteMapNode node, SiteMapNode parentNode) {
            if (node == null) {
                throw new ArgumentNullException("node");
            }
 
            if (parentNode == null) {
                throw new ArgumentNullException("parentNode");
            }
 
            SiteMapProvider ownerProvider = node.Provider;
            SiteMapProvider parentOwnerProvider = parentNode.Provider;
 
            if (ownerProvider != this) {
                throw new ArgumentException(SR.GetString(SR.XmlSiteMapProvider_cannot_add_node, node.ToString()), "node");
            }
 
            if (parentOwnerProvider != this) {
                throw new ArgumentException(SR.GetString(SR.XmlSiteMapProvider_cannot_add_node, parentNode.ToString()), "parentNode");
            }
 
            lock (_lock) {
                // First remove it from its current location.
                RemoveNode(node);
                AddNodeInternal(node, parentNode, null);
            }
        }
 
        private void AddNodeInternal(SiteMapNode node, SiteMapNode parentNode, XmlNode xmlNode) {
            lock (_lock) {
                String url = node.Url;
                String key = node.Key;
 
                bool isValidUrl = false;
 
                // Only add the node to the url table if it's a static node.
                if (!String.IsNullOrEmpty(url)) {
                    if (UrlTable[url] != null) {
                        if (xmlNode != null) {
                            throw new ConfigurationErrorsException(
                                SR.GetString(SR.XmlSiteMapProvider_Multiple_Nodes_With_Identical_Url, url),
                                xmlNode);
                        }
                        else {
                            throw new InvalidOperationException(SR.GetString(
                                SR.XmlSiteMapProvider_Multiple_Nodes_With_Identical_Url, url));
                        }
                    }
 
                    isValidUrl = true;
                }
 
                if (KeyTable.Contains(key)) {
                    if (xmlNode != null) {
                        throw new ConfigurationErrorsException(
                            SR.GetString(SR.XmlSiteMapProvider_Multiple_Nodes_With_Identical_Key, key),
                            xmlNode);
                    }
                    else {
                        throw new InvalidOperationException(
                           SR.GetString(SR.XmlSiteMapProvider_Multiple_Nodes_With_Identical_Key, key));
                    }
                }
 
                if (isValidUrl) {
                    UrlTable[url] = node;
                }
 
                KeyTable[key] = node;
 
                // Add the new node into parentNode collection
                if (parentNode != null) {
                    ParentNodeTable[node] = parentNode;
 
                    if (ChildNodeCollectionTable[parentNode] == null) {
                        ChildNodeCollectionTable[parentNode] = new SiteMapNodeCollection();
                    }
 
                    ((SiteMapNodeCollection)ChildNodeCollectionTable[parentNode]).Add(node);
                }
            }
        }
 
        protected virtual void AddProvider(string providerName, SiteMapNode parentNode) {
            if (parentNode == null) {
                throw new ArgumentNullException("parentNode");
            }
 
            if (parentNode.Provider != this) {
                throw new ArgumentException(SR.GetString(SR.XmlSiteMapProvider_cannot_add_node, parentNode.ToString()), "parentNode");
            }
 
            SiteMapNode node = GetNodeFromProvider(providerName);
            AddNodeInternal(node, parentNode, null);
        }
 
 
        [SuppressMessage("Microsoft.Security", "MSEC1205:DoNotAllowDtdOnXmlTextReader", Justification = "Legacy code that trusts our developer-controlled input.")]
        [SuppressMessage("Microsoft.Security.Xml", "CA3054:DoNotAllowDtdOnXmlTextReader", Justification = "Legacy code that trusts our developer-controlled input.")]
        public override SiteMapNode BuildSiteMap() {
 
            SiteMapNode tempNode = _siteMapNode;
 
            // If siteMap is already constructed, simply returns it.
            // Child providers will only be updated when the parent providers need to access them.
            if (tempNode != null) {
                return tempNode;
            }
 
            XmlDocument document = GetConfigDocument();
 
            lock (_lock) {
                if (_siteMapNode != null) {
                    return _siteMapNode;
                }
 
                Clear();
 
                // Need to check if the sitemap file exists before opening it.
                CheckSiteMapFileExists();
 
                try {
                    using (Stream stream = _normalizedVirtualPath.OpenFile()) {
                        XmlReader reader = new XmlTextReader(stream);
                        document.Load(reader);
                    }
                }
                catch (XmlException e) {
                    string sourceFile = _virtualPath.VirtualPathString;
                    string physicalDir = _normalizedVirtualPath.MapPathInternal();
                    if (physicalDir != null && HttpRuntime.HasPathDiscoveryPermission(physicalDir)) {
                        sourceFile = physicalDir;
                    }
 
                    throw new ConfigurationErrorsException(
                                            SR.GetString(SR.XmlSiteMapProvider_Error_loading_Config_file, _virtualPath, e.Message),
                                            e, sourceFile, e.LineNumber);
                }
                catch (Exception e) {
                    throw new ConfigurationErrorsException(
                        SR.GetString(SR.XmlSiteMapProvider_Error_loading_Config_file, _virtualPath, e.Message), e);
                }
 
                XmlNode node = null;
                foreach (XmlNode siteMapMode in document.ChildNodes) {
                    if (String.Equals(siteMapMode.Name, "siteMap", StringComparison.Ordinal)) {
                        node = siteMapMode;
                        break;
                    }
                }
 
                if (node == null)
                    throw new ConfigurationErrorsException(
                        SR.GetString(SR.XmlSiteMapProvider_Top_Element_Must_Be_SiteMap),
                        document);
 
                bool enableLocalization = false;
                HandlerBase.GetAndRemoveBooleanAttribute(node, "enableLocalization", ref enableLocalization);
                EnableLocalization = enableLocalization;
 
                XmlNode topElement = null;
                foreach (XmlNode subNode in node.ChildNodes) {
                    if (subNode.NodeType == XmlNodeType.Element) {
                        if (!_siteMapNodeName.Equals(subNode.Name)) {
                            throw new ConfigurationErrorsException(
                                SR.GetString(SR.XmlSiteMapProvider_Only_SiteMapNode_Allowed),
                                subNode);
                        }
 
                        if (topElement != null) {
                            throw new ConfigurationErrorsException(
                                SR.GetString(SR.XmlSiteMapProvider_Only_One_SiteMapNode_Required_At_Top),
                                subNode);
                        }
 
                        topElement = subNode;
                    }
                }
 
                if (topElement == null) {
                    throw new ConfigurationErrorsException(
                         SR.GetString(SR.XmlSiteMapProvider_Only_One_SiteMapNode_Required_At_Top),
                         node);
                }
 
                Queue queue = new Queue(50);
 
                // The parentnode of the top node does not exist,
                // simply add a null to satisfy the ConvertFromXmlNode condition.
                queue.Enqueue(null);
                queue.Enqueue(topElement);
                _siteMapNode = ConvertFromXmlNode(queue);
 
                return _siteMapNode;
            }
        }
 
        private void CheckSiteMapFileExists() {
            if (!System.Web.UI.Util.VirtualFileExistsWithAssert(_normalizedVirtualPath)) {
                throw new InvalidOperationException(
                    SR.GetString(SR.XmlSiteMapProvider_FileName_does_not_exist, _virtualPath));
            }
        }
 
 
        protected override void Clear() {
            lock (_lock) {
                ChildProviderTable.Clear();
                _siteMapNode = null;
                _childProviderList = null;
 
                base.Clear();
            }
        }
 
        // helper method to convert an XmlNode to a SiteMapNode
        private SiteMapNode ConvertFromXmlNode(Queue queue) {
 
            SiteMapNode rootNode = null;
            while (true) {
                if (queue.Count == 0) {
                    return rootNode;
                }
 
                SiteMapNode parentNode = (SiteMapNode)queue.Dequeue();
                XmlNode xmlNode = (XmlNode)queue.Dequeue();
 
                SiteMapNode node = null;
 
                if (!_siteMapNodeName.Equals(xmlNode.Name)) {
                    throw new ConfigurationErrorsException(
                        SR.GetString(SR.XmlSiteMapProvider_Only_SiteMapNode_Allowed),
                        xmlNode);
                }
 
                string providerName = null;
                HandlerBase.GetAndRemoveNonEmptyStringAttribute(xmlNode, _providerAttribute, ref providerName);
 
                // If the siteMapNode references another provider
                if (providerName != null) {
                    node = GetNodeFromProvider(providerName);
 
                    // No other attributes or child nodes are allowed on a provider node.
                    HandlerBase.CheckForUnrecognizedAttributes(xmlNode);
                    HandlerBase.CheckForNonCommentChildNodes(xmlNode);
                }
                else {
                    string siteMapFile = null;
                    HandlerBase.GetAndRemoveNonEmptyStringAttribute(xmlNode, _siteMapFileAttribute, ref siteMapFile);
 
                    if (siteMapFile != null) {
                        node = GetNodeFromSiteMapFile(xmlNode, VirtualPath.Create(siteMapFile));
                    }
                    else {
                        node = GetNodeFromXmlNode(xmlNode, queue);
                    }
                }
 
                AddNodeInternal(node, parentNode, xmlNode);
 
                if (rootNode == null) {
                    rootNode = node;
                }
            }
        }
 
        protected virtual void Dispose(bool disposing) {
            if (_handler != null) {
                Debug.Assert(_filename != null);
                HttpRuntime.FileChangesMonitor.StopMonitoringFile(_filename, _handler);
            }
        }
 
        public void Dispose() {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
 
        private void EnsureChildSiteMapProviderUpToDate(SiteMapProvider childProvider) {
            SiteMapNode oldNode = (SiteMapNode)ChildProviderTable[childProvider];
 
            SiteMapNode newNode = childProvider.GetRootNodeCore();
            if (newNode == null) {
                throw new ProviderException(SR.GetString(SR.XmlSiteMapProvider_invalid_sitemapnode_returned, childProvider.Name));
            }
 
            // child providers have been updated.
            if (!oldNode.Equals(newNode)) {
 
                // If the child provider table has been updated, simply return null.
                // This will happen when the current provider's sitemap file is changed or Clear() is called;
                if (oldNode == null) {
                    return;
                }
 
                lock (_lock) {
                    oldNode = (SiteMapNode)ChildProviderTable[childProvider];
                    // If the child provider table has been updated, simply return null. See above.
                    if (oldNode == null) {
                        return;
                    }
 
                    newNode = childProvider.GetRootNodeCore();
                    if (newNode == null) {
                        throw new ProviderException(SR.GetString(SR.XmlSiteMapProvider_invalid_sitemapnode_returned, childProvider.Name));
                    }
 
                    if (!oldNode.Equals(newNode)) {
 
                        // If the current provider does not contain any nodes but one child provider
                        // ie. _siteMapNode == oldNode
                        // the oldNode needs to be removed from Url table and the new node will be added.
                        if (_siteMapNode.Equals(oldNode)) {
                            UrlTable.Remove(oldNode.Url);
                            KeyTable.Remove(oldNode.Key);
 
                            UrlTable.Add(newNode.Url, newNode);
                            KeyTable.Add(newNode.Key, newNode);
 
                            _siteMapNode = newNode;
                        }
 
                        // First find the parent node
                        SiteMapNode parent = (SiteMapNode)ParentNodeTable[oldNode];
 
                        // parent is null when the provider does not contain any static nodes, ie.
                        // it only contains definition to include one child provider.
                        if (parent != null) {
                            // Update the child nodes table
                            SiteMapNodeCollection list = (SiteMapNodeCollection)ChildNodeCollectionTable[parent];
 
                            // Add the newNode to where the oldNode is within parent node's collection.
                            int index = list.IndexOf(oldNode);
                            if (index != -1) {
                                list.Remove(oldNode);
                                list.Insert(index, newNode);
                            }
                            else {
                                list.Add(newNode);
                            }
 
                            // Update the parent table
                            ParentNodeTable[newNode] = parent;
                            ParentNodeTable.Remove(oldNode);
 
                            // Update the Url table
                            UrlTable.Remove(oldNode.Url);
                            KeyTable.Remove(oldNode.Key);
 
                            UrlTable.Add(newNode.Url, newNode);
                            KeyTable.Add(newNode.Key, newNode);
                        }
                        else {
                            // Notify the parent provider to update its child provider collection.
                            XmlSiteMapProvider provider = ParentProvider as XmlSiteMapProvider;
                            if (provider != null) {
                                provider.EnsureChildSiteMapProviderUpToDate(this);
                            }
                        }
 
                        // Update provider nodes;
                        ChildProviderTable[childProvider] = newNode;
                        _childProviderList = null;
                    }
                }
            }
        }
 
        // Returns sitemap node; Search recursively in child providers if not found.
 
        public override SiteMapNode FindSiteMapNode(string rawUrl) {
            SiteMapNode node = base.FindSiteMapNode(rawUrl);
 
            if (node == null) {
                foreach(SiteMapProvider provider in ChildProviderList) {
                    // First make sure the child provider is up-to-date.
                    EnsureChildSiteMapProviderUpToDate(provider);
 
                    node = provider.FindSiteMapNode(rawUrl);
                    if (node != null) {
                        return node;
                    }
                }
            }
 
            return node;
        }
 
        // Returns sitemap node; Search recursively in child providers if not found.
        public override SiteMapNode FindSiteMapNodeFromKey(string key) {
            SiteMapNode node = base.FindSiteMapNodeFromKey(key);
 
            if (node == null) {
                foreach (SiteMapProvider provider in ChildProviderList) {
                    // First make sure the child provider is up-to-date.
                    EnsureChildSiteMapProviderUpToDate(provider);
 
                    node = provider.FindSiteMapNodeFromKey(key);
                    if (node != null) {
                        return node;
                    }
                }
            }
 
            return node;
        }
 
        private XmlDocument GetConfigDocument() {
            if (_document != null)
                return _document;
 
            if (!_initialized) {
                throw new InvalidOperationException(
                    SR.GetString(SR.XmlSiteMapProvider_Not_Initialized));
            }
 
            // Do the error checking here
            if (_virtualPath == null) {
                throw new ArgumentException(
                    SR.GetString(SR.XmlSiteMapProvider_missing_siteMapFile, _siteMapFileAttribute));
            }
 
            if (!_virtualPath.Extension.Equals(_xmlSiteMapFileExtension, StringComparison.OrdinalIgnoreCase)) {
                throw new InvalidOperationException(
                    SR.GetString(SR.XmlSiteMapProvider_Invalid_Extension, _virtualPath));
            }
 
            _normalizedVirtualPath = _virtualPath.CombineWithAppRoot();
            _normalizedVirtualPath.FailIfNotWithinAppRoot();
 
            // Make sure the file exists
            CheckSiteMapFileExists();
 
            _parentSiteMapFileCollection = new StringCollection();
            XmlSiteMapProvider xmlParentProvider = ParentProvider as XmlSiteMapProvider;
            if (xmlParentProvider != null && xmlParentProvider._parentSiteMapFileCollection != null) {
                if (xmlParentProvider._parentSiteMapFileCollection.Contains(_normalizedVirtualPath.VirtualPathString)) {
                    throw new InvalidOperationException(
                        SR.GetString(SR.XmlSiteMapProvider_FileName_already_in_use, _virtualPath));
                }
 
                // Copy the sitemapfiles in used from parent provider to current provider.
                foreach (string filename in xmlParentProvider._parentSiteMapFileCollection) {
                    _parentSiteMapFileCollection.Add(filename);
                }
            }
 
            // Add current sitemap file to the collection
            _parentSiteMapFileCollection.Add(_normalizedVirtualPath.VirtualPathString);
 
            _filename = HostingEnvironment.MapPathInternal(_normalizedVirtualPath);
 
            if (!String.IsNullOrEmpty(_filename)) {
                _handler = new FileChangeEventHandler(this.OnConfigFileChange);
                HttpRuntime.FileChangesMonitor.StartMonitoringFile(_filename, _handler);
                ResourceKey = (new FileInfo(_filename)).Name;
            }
 
            _document = new ConfigXmlDocument();
 
            return _document;
        }
 
        private SiteMapNode GetNodeFromProvider(string providerName) {
            SiteMapProvider provider = GetProviderFromName(providerName);
            SiteMapNode node = null;
 
            // Check infinite recursive sitemap files
            if (provider is XmlSiteMapProvider) {
                XmlSiteMapProvider xmlProvider = (XmlSiteMapProvider)provider;
 
                StringCollection parentSiteMapFileCollection = new StringCollection();
                if (_parentSiteMapFileCollection != null) {
                    foreach (string filename in _parentSiteMapFileCollection) {
                        parentSiteMapFileCollection.Add(filename);
                    }
                }
 
                // Make sure the provider is initialized before adding to the collection.
                xmlProvider.BuildSiteMap();
 
                parentSiteMapFileCollection.Add(_normalizedVirtualPath.VirtualPathString);
                if (parentSiteMapFileCollection.Contains(VirtualPath.GetVirtualPathString(xmlProvider._normalizedVirtualPath))) {
                    throw new InvalidOperationException(SR.GetString(SR.XmlSiteMapProvider_FileName_already_in_use, xmlProvider._virtualPath));
                }
 
                xmlProvider._parentSiteMapFileCollection = parentSiteMapFileCollection;
            }
 
            node = provider.GetRootNodeCore();
            if (node == null) {
                throw new InvalidOperationException(
                    SR.GetString(SR.XmlSiteMapProvider_invalid_GetRootNodeCore, ((ProviderBase)provider).Name));
            }
 
            ChildProviderTable.Add(provider, node);
            _childProviderList = null;
 
            provider.ParentProvider = this;
 
            return node;
        }
 
        private SiteMapNode GetNodeFromSiteMapFile(XmlNode xmlNode, VirtualPath siteMapFile) {
 
            SiteMapNode node = null;
 
            // For external sitemap files, its secuity setting is inherited from parent provider
            bool secuityTrimmingEnabled = SecurityTrimmingEnabled;
            HandlerBase.GetAndRemoveBooleanAttribute(xmlNode, _securityTrimmingEnabledAttrName, ref secuityTrimmingEnabled);
 
            // No other attributes or non-comment nodes are allowed on a siteMapFile node
            HandlerBase.CheckForUnrecognizedAttributes(xmlNode);
            HandlerBase.CheckForNonCommentChildNodes(xmlNode);
 
            XmlSiteMapProvider childProvider = new XmlSiteMapProvider();
 
            // siteMapFile was relative to the sitemap file where this xmlnode is defined, make it an application path.
            siteMapFile = _normalizedVirtualPath.Parent.Combine(siteMapFile);
 
            childProvider.ParentProvider = this;
            childProvider.Initialize(siteMapFile, secuityTrimmingEnabled);
            childProvider.BuildSiteMap();
 
            node = childProvider._siteMapNode;
 
            ChildProviderTable.Add(childProvider, node);
            _childProviderList = null;
 
            return node;
        }
 
        private void HandleResourceAttribute(XmlNode xmlNode, ref NameValueCollection collection, 
            string attrName, ref string text, bool allowImplicitResource) {
            if (String.IsNullOrEmpty(text)) {
                return;
            }
 
            string resourceKey = null;
            string temp = text.TrimStart(new char[] { ' ' });
 
            if (temp != null && temp.Length > _resourcePrefixLength) {
                if (temp.ToLower(CultureInfo.InvariantCulture).StartsWith(_resourcePrefix, StringComparison.Ordinal)) {
                    if (!allowImplicitResource) {
                        throw new ConfigurationErrorsException(
                            SR.GetString(SR.XmlSiteMapProvider_multiple_resource_definition, attrName), xmlNode);
                    }
 
                    resourceKey = temp.Substring(_resourcePrefixLength + 1);
 
                    if (resourceKey.Length == 0) {
                        throw new ConfigurationErrorsException(
                            SR.GetString(SR.XmlSiteMapProvider_resourceKey_cannot_be_empty), xmlNode);
                    }
 
                    // Retrieve className from attribute
                    string className = null;
                    string key = null;
                    int index = resourceKey.IndexOf(_resourceKeySeparator);
                    if (index == -1) {
                        throw new ConfigurationErrorsException(
                            SR.GetString(SR.XmlSiteMapProvider_invalid_resource_key, resourceKey), xmlNode);
                    }
 
                    className = resourceKey.Substring(0, index);
                    key = resourceKey.Substring(index + 1);
 
                    // Retrieve resource key and default value from attribute
                    int defaultIndex = key.IndexOf(_resourceKeySeparator);
                    if (defaultIndex != -1) {
                        text = key.Substring(defaultIndex + 1);
                        key = key.Substring(0, defaultIndex);
                    }
                    else {
                        text = null;
                    }
 
                    if (collection == null) {
                        collection = new NameValueCollection();
                    }
 
                    collection.Add(attrName, className.Trim());
                    collection.Add(attrName, key.Trim());
                }
            }
        }
 
        private SiteMapNode GetNodeFromXmlNode(XmlNode xmlNode, Queue queue) {
            SiteMapNode node = null;
            // static nodes
            string title = null, url = null, description = null, roles = null, resourceKey = null;
 
            // Url attribute is NOT required for a xml node.
            HandlerBase.GetAndRemoveStringAttribute(xmlNode, "url", ref url);
            HandlerBase.GetAndRemoveStringAttribute(xmlNode, "title", ref title);
            HandlerBase.GetAndRemoveStringAttribute(xmlNode, "description", ref description);
            HandlerBase.GetAndRemoveStringAttribute(xmlNode, "roles", ref roles);
            HandlerBase.GetAndRemoveStringAttribute(xmlNode, "resourceKey", ref resourceKey);
 
            // Do not add the resourceKey if the resource is not valid.
            if (!String.IsNullOrEmpty(resourceKey) && 
                !ValidateResource(ResourceKey, resourceKey + ".title")) {
                resourceKey = null;
            }
 
            HandlerBase.CheckForbiddenAttribute(xmlNode, _securityTrimmingEnabledAttrName);
 
            NameValueCollection resourceKeyCollection = null;
            bool allowImplicitResourceAttribute = String.IsNullOrEmpty(resourceKey);
            HandleResourceAttribute(xmlNode, ref resourceKeyCollection, 
                "title", ref title, allowImplicitResourceAttribute);
            HandleResourceAttribute(xmlNode, ref resourceKeyCollection, 
                "description", ref description, allowImplicitResourceAttribute);
 
            ArrayList roleList = new ArrayList();
            if (roles != null) {
                int foundIndex = roles.IndexOf('?');
                if (foundIndex != -1) {
                    throw new ConfigurationErrorsException(
                        SR.GetString(SR.Auth_rule_names_cant_contain_char,
                        roles[foundIndex].ToString(CultureInfo.InvariantCulture)), xmlNode);
                }
 
                foreach (string role in roles.Split(_seperators)) {
                    string trimmedRole = role.Trim();
                    if (trimmedRole.Length > 0) {
                        roleList.Add(trimmedRole);
                    }
                }
            }
            roleList = ArrayList.ReadOnly(roleList);
 
            String key = null;
 
            // Make urls absolute.
            if (!String.IsNullOrEmpty(url)) {
                // URL needs to be trimmed. VSWhidbey 411041
                url = url.Trim();
 
                if (!UrlPath.IsAbsolutePhysicalPath(url)) {
                    if (UrlPath.IsRelativeUrl(url)) {
                        url = UrlPath.Combine(HttpRuntime.AppDomainAppVirtualPathString, url);
                    }
                }
 
                // VSWhidbey 418056, Reject any suspicious or mal-formed Urls.
                string decodedUrl = HttpUtility.UrlDecode(url);
                if (!String.Equals(url, decodedUrl, StringComparison.Ordinal)) {
                    throw new ConfigurationErrorsException(
                        SR.GetString(SR.Property_Had_Malformed_Url, "url", url), xmlNode);
                }
 
                key = url.ToLowerInvariant();
            }
            else {
                key = Guid.NewGuid().ToString();
            }
 
            // attribute collection does not contain pre-defined properties like title, url, etc.
            ReadOnlyNameValueCollection attributeCollection = new ReadOnlyNameValueCollection();
            attributeCollection.SetReadOnly(false);
            foreach (XmlAttribute attribute in xmlNode.Attributes) {
                string value = attribute.Value;
                HandleResourceAttribute(xmlNode, ref resourceKeyCollection, attribute.Name, ref value, allowImplicitResourceAttribute);
                attributeCollection[attribute.Name] = value;
            }
            attributeCollection.SetReadOnly(true);
 
            node = new SiteMapNode(this, key, url, title, description, roleList, attributeCollection, resourceKeyCollection, resourceKey);
            node.ReadOnly = true;
 
            foreach (XmlNode subNode in xmlNode.ChildNodes) {
                if (subNode.NodeType != XmlNodeType.Element)
                    continue;
 
                queue.Enqueue(node);
                queue.Enqueue(subNode);
            }
 
            return node;
        }
 
        private SiteMapProvider GetProviderFromName(string providerName) {
            Debug.Assert(providerName != null);
 
            SiteMapProvider provider = SiteMap.Providers[providerName];
            if (provider == null) {
                throw new ProviderException(SR.GetString(SR.Provider_Not_Found, providerName));
            }
 
            return provider;
        }
 
        protected internal override SiteMapNode GetRootNodeCore() {
            BuildSiteMap();
            return _siteMapNode;
        }
 
 
        public override void Initialize(string name, NameValueCollection attributes) {
            if (_initialized) {
                throw new InvalidOperationException(
                    SR.GetString(SR.XmlSiteMapProvider_Cannot_Be_Inited_Twice));
            }
 
            if (attributes != null) {
                if (string.IsNullOrEmpty(attributes["description"])) {
                    attributes.Remove("description");
                    attributes.Add("description", SR.GetString(SR.XmlSiteMapProvider_Description));
                }
 
                string siteMapFile = null;
                ProviderUtil.GetAndRemoveStringAttribute(attributes, _siteMapFileAttribute, name, ref siteMapFile);
                _virtualPath = VirtualPath.CreateAllowNull(siteMapFile);
            }
 
            base.Initialize(name, attributes);
 
            if (attributes != null) {
                ProviderUtil.CheckUnrecognizedAttributes(attributes, name);
            }
 
            _initialized = true;
        }
 
        private void Initialize(VirtualPath virtualPath, bool secuityTrimmingEnabled) {
            NameValueCollection coll = new NameValueCollection();
            coll.Add(_siteMapFileAttribute, virtualPath.VirtualPathString);
            coll.Add(_securityTrimmingEnabledAttrName, System.Web.UI.Util.GetStringFromBool(secuityTrimmingEnabled));
 
            // Use the siteMapFile virtual path as the provider name
            Initialize(virtualPath.VirtualPathString, coll);
        }
 
        private void OnConfigFileChange(Object sender, FileChangeEvent e) {
            // Notifiy the parent for the change.
            XmlSiteMapProvider parentProvider = ParentProvider as XmlSiteMapProvider;
            if (parentProvider != null) {
                parentProvider.OnConfigFileChange(sender, e);
            }
 
            Clear();
        }
 
        protected internal override void RemoveNode(SiteMapNode node) {
            if (node == null) {
                throw new ArgumentNullException("node");
            }
 
            SiteMapProvider ownerProvider = node.Provider;
 
            if (ownerProvider != this) {
 
                // Only nodes defined in this provider tree can be removed.
                SiteMapProvider parentProvider = ownerProvider.ParentProvider;
                while (parentProvider != this) {
                    if (parentProvider == null) {
                        // Cannot remove nodes defined in other providers
                        throw new InvalidOperationException(
                            SR.GetString(SR.XmlSiteMapProvider_cannot_remove_node, node.ToString(), 
                            this.Name, ownerProvider.Name));
                    }
 
                    parentProvider = parentProvider.ParentProvider;
                }
            }
 
            if (node.Equals(ownerProvider.GetRootNodeCore())) {
                throw new InvalidOperationException(SR.GetString(SR.SiteMapProvider_cannot_remove_root_node));
            }
 
            if (ownerProvider != this) {
                // Remove node from the owner provider.
                ownerProvider.RemoveNode(node);
            }
 
            base.RemoveNode(node);
        }
 
        protected virtual void RemoveProvider(string providerName) {
            if (providerName == null) {
                throw new ArgumentNullException("providerName");
            }
 
            lock (_lock) {
                SiteMapProvider provider = GetProviderFromName(providerName);
                SiteMapNode rootNode = (SiteMapNode)ChildProviderTable[provider];
 
                if (rootNode == null) {
                    throw new InvalidOperationException(SR.GetString(SR.XmlSiteMapProvider_cannot_find_provider, provider.Name, this.Name));
                }
 
                provider.ParentProvider = null;
                ChildProviderTable.Remove(provider);
                _childProviderList = null;
 
                base.RemoveNode(rootNode);
            }
        }
 
        // VSWhidbey: 493981 Helper method to check if the valid resource type exists. 
        // Note that this only returns false if the classKey cannot be found, regardless of resourceKey.
        private bool ValidateResource(string classKey, string resourceKey) {
            try {
                HttpContext.GetGlobalResourceObject(classKey, resourceKey);
            }
            catch (MissingManifestResourceException) {
                return false;
            }
 
            return true;
        }
 
        // Dev10# 923217 - SiteMapProvider URL Table Invalid Using Cookieless
        // Don't keep the modifier inside the links table. Apply the modifier as approriate on demand
        private static SiteMapNode ApplyModifierIfExists(SiteMapNode node) {
            HttpContext context = HttpContext.Current;
 
            // Do nothing if the modifier doesn't apply
            if (node == null || context == null || !context.Response.UsePathModifier) {
                return node;
            }
 
            // Set Url with the modifier applied
            SiteMapNode resultNode = node.Clone();
            resultNode.Url = context.Response.ApplyAppPathModifier(node.Url);
 
            return resultNode;
        }
 
        private class ReadOnlyNameValueCollection : NameValueCollection {
 
            public ReadOnlyNameValueCollection() {
                IsReadOnly = true;
            }
 
            internal void SetReadOnly(bool isReadonly) {
                IsReadOnly = isReadonly;
            }
        }
    }
}