File: System\Xml\Xsl\Runtime\WhitespaceRuleLookup.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;
using System.Xml;
using System.Xml.Xsl.Qil;
using MS.Internal.Xml;
 
namespace System.Xml.Xsl.Runtime
{
    /// <summary>
    /// This class keeps a list of whitespace rules in order to determine whether whitespace children of particular
    /// elements should be stripped.
    /// </summary>
    internal sealed class WhitespaceRuleLookup
    {
        private readonly Hashtable _qnames;
        private readonly ArrayList _wildcards;
        private readonly InternalWhitespaceRule? _ruleTemp;
        private XmlNameTable? _nameTable;
 
        public WhitespaceRuleLookup()
        {
            _qnames = new Hashtable();
            _wildcards = new ArrayList();
        }
 
        /// <summary>
        /// Create a new lookup internal class from the specified WhitespaceRules.
        /// </summary>
        public WhitespaceRuleLookup(IList<WhitespaceRule> rules) : this()
        {
            WhitespaceRule rule;
            InternalWhitespaceRule ruleInternal;
            Debug.Assert(rules != null);
 
            for (int i = rules.Count - 1; i >= 0; i--)
            {
                // Make a copy of each rule
                rule = rules[i];
                ruleInternal = new InternalWhitespaceRule(rule.LocalName, rule.NamespaceName, rule.PreserveSpace, -i);
 
                if (rule.LocalName == null || rule.NamespaceName == null)
                {
                    // Wildcard, so add to wildcards array
                    _wildcards.Add(ruleInternal);
                }
                else
                {
                    // Exact name, so add to hashtable
                    _qnames[ruleInternal] = ruleInternal;
                }
            }
 
            // Create a temporary (not thread-safe) InternalWhitespaceRule used for lookups
            _ruleTemp = new InternalWhitespaceRule();
        }
 
        /// <summary>
        /// Atomize all names contained within the whitespace rules with respect to "nameTable".
        /// </summary>
        public void Atomize(XmlNameTable nameTable)
        {
            // If names are already atomized with respect to "nameTable", no need to do it again
            if (nameTable != _nameTable)
            {
                _nameTable = nameTable;
 
                foreach (InternalWhitespaceRule rule in _qnames.Values)
                    rule.Atomize(nameTable);
 
                foreach (InternalWhitespaceRule rule in _wildcards)
                    rule.Atomize(nameTable);
            }
        }
 
        /// <summary>
        /// Return true if elements of the specified name should have whitespace children stripped.
        /// NOTE: This method is not thread-safe.  Different threads should create their own copy of the
        /// WhitespaceRuleLookup object.  This allows all names to be atomized according to a private NameTable.
        /// </summary>
        public bool ShouldStripSpace(string localName, string namespaceName)
        {
            InternalWhitespaceRule? qnameRule, wildcardRule;
            Debug.Assert(_nameTable != null && _ruleTemp != null);
            Debug.Assert(localName != null && (object?)_nameTable.Get(localName) == (object)localName);
            Debug.Assert(namespaceName != null && (object?)_nameTable.Get(namespaceName) == (object)namespaceName);
 
            _ruleTemp.Init(localName, namespaceName, false, 0);
 
            // Lookup name in qnames table
            // If found, the name will be stripped unless there is a preserve wildcard with higher priority
            qnameRule = _qnames[_ruleTemp] as InternalWhitespaceRule;
 
            for (int pos = _wildcards.Count; pos-- != 0;)
            {
                wildcardRule = _wildcards[pos] as InternalWhitespaceRule;
 
                if (qnameRule != null)
                {
                    // If qname priority is greater than any subsequent wildcard's priority, then we're done
                    if (qnameRule.Priority > wildcardRule!.Priority)
                        return !qnameRule.PreserveSpace;
 
                    // Don't bother to consider wildcards with the same PreserveSpace flag
                    if (qnameRule.PreserveSpace == wildcardRule.PreserveSpace)
                        continue;
                }
 
                if (wildcardRule!.LocalName == null || (object)wildcardRule.LocalName == (object)localName)
                {
                    if (wildcardRule.NamespaceName == null || (object)wildcardRule.NamespaceName == (object)namespaceName)
                    {
                        // Found wildcard match, so we're done (since wildcards are in priority order)
                        return !wildcardRule.PreserveSpace;
                    }
                }
            }
 
            return (qnameRule != null && !qnameRule.PreserveSpace);
        }
 
        private sealed class InternalWhitespaceRule : WhitespaceRule
        {
            private int _priority;       // Relative priority of this test
            private int _hashCode;       // Cached hashcode
 
            public InternalWhitespaceRule()
            {
            }
 
            public InternalWhitespaceRule(string? localName, string? namespaceName, bool preserveSpace, int priority)
            {
                Init(localName, namespaceName, preserveSpace, priority);
            }
 
            public void Init(string? localName, string? namespaceName, bool preserveSpace, int priority)
            {
                base.Init(localName, namespaceName, preserveSpace);
                _priority = priority;
 
                if (localName != null && namespaceName != null)
                {
                    _hashCode = localName.GetHashCode();
                }
            }
 
            public void Atomize(XmlNameTable nameTable)
            {
                if (LocalName != null)
                    LocalName = nameTable.Add(LocalName);
 
                if (NamespaceName != null)
                    NamespaceName = nameTable.Add(NamespaceName);
            }
 
            public int Priority
            {
                get { return _priority; }
            }
 
            public override int GetHashCode()
            {
                return _hashCode;
            }
 
            public override bool Equals([NotNullWhen(true)] object? obj)
            {
                Debug.Assert(obj is InternalWhitespaceRule);
                InternalWhitespaceRule? that = obj as InternalWhitespaceRule;
 
                Debug.Assert(LocalName != null && that!.LocalName != null);
                Debug.Assert(NamespaceName != null && that.NamespaceName != null);
 
                // string == operator compares object references first and if they are not the same compares contents
                // of the compared strings. As a result we do not have to cast strings to objects to force reference
                // comparison for atomized LocalNames and NamespaceNames.
                return LocalName == that.LocalName && NamespaceName == that.NamespaceName;
            }
        }
    }
}