File: System\Xml\XmlNamespacemanager.cs
Web Access
Project: src\src\libraries\System.Private.Xml\src\System.Private.Xml.csproj (System.Private.Xml)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
 
namespace System.Xml
{
    public class XmlNamespaceManager : IXmlNamespaceResolver, IEnumerable
    {
        private struct NamespaceDeclaration
        {
            public string prefix;
            public string? uri;
            public int scopeId;
            public int previousNsIndex;
 
            public void Set(string prefix, string uri, int scopeId, int previousNsIndex)
            {
                this.prefix = prefix;
                this.uri = uri;
                this.scopeId = scopeId;
                this.previousNsIndex = previousNsIndex;
            }
        }
 
        // array with namespace declarations
        private NamespaceDeclaration[]? _nsdecls;
 
        // index of last declaration
        private int _lastDecl;
 
        // name table
        private readonly XmlNameTable? _nameTable;
 
        // ID (depth) of the current scope
        private int _scopeId;
 
        // hash table for faster lookup when there is lots of namespaces
        private Dictionary<string, int>? _hashTable;
        private bool _useHashtable;
 
        // atomized prefixes for "xml" and "xmlns"
        private readonly string? _xml;
        private readonly string? _xmlNs;
 
        // Constants
        private const int MinDeclsCountForHashtable = 16;
 
        internal XmlNamespaceManager()
        {
        }
 
        public XmlNamespaceManager(XmlNameTable nameTable)
        {
            _nameTable = nameTable;
            _xml = nameTable.Add("xml");
            _xmlNs = nameTable.Add("xmlns");
 
            _nsdecls = new NamespaceDeclaration[8];
            string emptyStr = nameTable.Add(string.Empty);
            _nsdecls[0].Set(emptyStr, emptyStr, -1, -1);
            _nsdecls[1].Set(_xmlNs, nameTable.Add(XmlReservedNs.NsXmlNs), -1, -1);
            _nsdecls[2].Set(_xml, nameTable.Add(XmlReservedNs.NsXml), 0, -1);
            _lastDecl = 2;
            _scopeId = 1;
        }
 
        public virtual XmlNameTable? NameTable
        {
            get
            {
                return _nameTable;
            }
        }
 
        public virtual string DefaultNamespace
        {
            get
            {
                string? defaultNs = LookupNamespace(string.Empty);
                return defaultNs ?? string.Empty;
            }
        }
 
        public virtual void PushScope()
        {
            _scopeId++;
        }
 
        public virtual bool PopScope()
        {
            int decl = _lastDecl;
            if (_scopeId == 1)
            {
                return false;
            }
 
            Debug.Assert(_nsdecls != null);
 
            while (_nsdecls[decl].scopeId == _scopeId)
            {
                if (_useHashtable)
                {
                    Debug.Assert(_hashTable != null);
                    _hashTable[_nsdecls[decl].prefix] = _nsdecls[decl].previousNsIndex;
                }
 
                decl--;
                Debug.Assert(decl >= 2);
            }
 
            _lastDecl = decl;
            _scopeId--;
            return true;
        }
 
        public virtual void AddNamespace(string prefix, [StringSyntax(StringSyntaxAttribute.Uri)] string uri)
        {
            ArgumentNullException.ThrowIfNull(prefix);
            ArgumentNullException.ThrowIfNull(uri);
 
            Debug.Assert(_nameTable != null);
            Debug.Assert(_nsdecls != null);
            prefix = _nameTable.Add(prefix);
            uri = _nameTable.Add(uri);
 
            if ((Ref.Equal(_xml, prefix) && !uri.Equals(XmlReservedNs.NsXml)))
            {
                throw new ArgumentException(SR.Xml_XmlPrefix);
            }
            if (Ref.Equal(_xmlNs, prefix))
            {
                throw new ArgumentException(SR.Xml_XmlnsPrefix);
            }
 
            int declIndex = LookupNamespaceDecl(prefix);
            int previousDeclIndex = -1;
            if (declIndex != -1)
            {
                if (_nsdecls[declIndex].scopeId == _scopeId)
                {
                    // redefine if in the same scope
                    _nsdecls[declIndex].uri = uri;
                    return;
                }
                else
                {
                    // otherwise link
                    previousDeclIndex = declIndex;
                }
            }
 
            // set new namespace declaration
            if (_lastDecl == _nsdecls.Length - 1)
            {
                NamespaceDeclaration[] newNsdecls = new NamespaceDeclaration[_nsdecls.Length * 2];
                Array.Copy(_nsdecls, newNsdecls, _nsdecls.Length);
                _nsdecls = newNsdecls;
            }
 
            _nsdecls[++_lastDecl].Set(prefix, uri, _scopeId, previousDeclIndex);
 
            // add to hashTable
            if (_useHashtable)
            {
                Debug.Assert(_hashTable != null);
                _hashTable[prefix] = _lastDecl;
            }
            // or create a new hashTable if the threshold has been reached
            else if (_lastDecl >= MinDeclsCountForHashtable)
            {
                // add all to hash table
                Debug.Assert(_hashTable == null);
                _hashTable = new Dictionary<string, int>(_lastDecl);
                for (int i = 0; i <= _lastDecl; i++)
                {
                    _hashTable[_nsdecls[i].prefix] = i;
                }
 
                _useHashtable = true;
            }
        }
 
        public virtual void RemoveNamespace(string prefix, [StringSyntax(StringSyntaxAttribute.Uri)] string uri)
        {
            ArgumentNullException.ThrowIfNull(prefix);
            ArgumentNullException.ThrowIfNull(uri);
 
            Debug.Assert(_nsdecls != null);
 
            int declIndex = LookupNamespaceDecl(prefix);
            while (declIndex != -1)
            {
                if (string.Equals(_nsdecls[declIndex].uri, uri) && _nsdecls[declIndex].scopeId == _scopeId)
                {
                    _nsdecls[declIndex].uri = null;
                }
                declIndex = _nsdecls[declIndex].previousNsIndex;
            }
        }
 
        public virtual IEnumerator GetEnumerator()
        {
            Debug.Assert(_nsdecls != null);
 
            Dictionary<string, string> prefixes = new Dictionary<string, string>(_lastDecl + 1);
            for (int thisDecl = 0; thisDecl <= _lastDecl; thisDecl++)
            {
                if (_nsdecls[thisDecl].uri != null)
                {
                    prefixes[_nsdecls[thisDecl].prefix] = _nsdecls[thisDecl].prefix;
                }
            }
            return prefixes.Keys.GetEnumerator();
        }
 
        // This pragma disables a warning that the return type is not CLS-compliant, but generics are part of CLS in Whidbey.
#pragma warning disable 3002
        public virtual IDictionary<string, string> GetNamespacesInScope(XmlNamespaceScope scope)
#pragma warning restore 3002
        {
            Debug.Assert(_nsdecls != null);
 
            int i = 0;
            switch (scope)
            {
                case XmlNamespaceScope.All:
                    i = 2;
                    break;
                case XmlNamespaceScope.ExcludeXml:
                    i = 3;
                    break;
                case XmlNamespaceScope.Local:
                    i = _lastDecl;
                    while (_nsdecls[i].scopeId == _scopeId)
                    {
                        i--;
                        Debug.Assert(i >= 2);
                    }
                    i++;
                    break;
            }
 
            Dictionary<string, string> dict = new Dictionary<string, string>(_lastDecl - i + 1);
            for (; i <= _lastDecl; i++)
            {
                string? prefix = _nsdecls[i].prefix;
                string? uri = _nsdecls[i].uri;
                Debug.Assert(prefix != null);
 
                if (uri != null)
                {
                    if (uri.Length > 0 || prefix.Length > 0 || scope == XmlNamespaceScope.Local)
                    {
                        dict[prefix] = uri;
                    }
                    else
                    {
                        // default namespace redeclared to "" -> remove from list for all scopes other than local
                        dict.Remove(prefix);
                    }
                }
            }
 
            return dict;
        }
 
        public virtual string? LookupNamespace(string prefix)
        {
            Debug.Assert(_nsdecls != null);
            int declIndex = LookupNamespaceDecl(prefix);
            return (declIndex == -1) ? null : _nsdecls[declIndex].uri;
        }
 
        private int LookupNamespaceDecl(string prefix)
        {
            Debug.Assert(_nsdecls != null);
 
            if (_useHashtable)
            {
                Debug.Assert(_hashTable != null);
                int declIndex;
                if (_hashTable.TryGetValue(prefix, out declIndex))
                {
                    while (declIndex != -1 && _nsdecls[declIndex].uri == null)
                    {
                        declIndex = _nsdecls[declIndex].previousNsIndex;
                    }
                    return declIndex;
                }
                return -1;
            }
            else
            {
                // First assume that prefix is atomized
                for (int thisDecl = _lastDecl; thisDecl >= 0; thisDecl--)
                {
                    if ((object)_nsdecls[thisDecl].prefix == (object)prefix && _nsdecls[thisDecl].uri != null)
                    {
                        return thisDecl;
                    }
                }
 
                // Non-atomized lookup
                for (int thisDecl = _lastDecl; thisDecl >= 0; thisDecl--)
                {
                    if (string.Equals(_nsdecls[thisDecl].prefix, prefix) && _nsdecls[thisDecl].uri != null)
                    {
                        return thisDecl;
                    }
                }
            }
            return -1;
        }
 
        public virtual string? LookupPrefix([StringSyntax(StringSyntaxAttribute.Uri)] string uri)
        {
            Debug.Assert(_nsdecls != null);
 
            // Don't assume that prefix is atomized
            for (int thisDecl = _lastDecl; thisDecl >= 0; thisDecl--)
            {
                if (string.Equals(_nsdecls[thisDecl].uri, uri))
                {
                    string prefix = _nsdecls[thisDecl].prefix;
                    if (string.Equals(LookupNamespace(prefix), uri))
                    {
                        return prefix;
                    }
                }
            }
            return null;
        }
 
        public virtual bool HasNamespace(string prefix)
        {
            Debug.Assert(_nsdecls != null);
 
            // Don't assume that prefix is atomized
            for (int thisDecl = _lastDecl; _nsdecls[thisDecl].scopeId == _scopeId; thisDecl--)
            {
                if (string.Equals(_nsdecls[thisDecl].prefix, prefix) && _nsdecls[thisDecl].uri != null)
                {
                    if (prefix.Length > 0 || _nsdecls[thisDecl].uri!.Length > 0)
                    {
                        return true;
                    }
                    return false;
                }
            }
            return false;
        }
 
        internal bool GetNamespaceDeclaration(int idx, [NotNullWhen(true)] out string? prefix, out string? uri)
        {
            idx = _lastDecl - idx;
            if (idx < 0)
            {
                prefix = uri = null;
                return false;
            }
 
            Debug.Assert(_nsdecls != null);
            prefix = _nsdecls[idx].prefix;
            uri = _nsdecls[idx].uri;
 
            return true;
        }
    } //XmlNamespaceManager
}