| File: System\Xml\Xsl\Runtime\WhitespaceRuleLookup.cs | Web Access |
| Project: src\runtime\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; } } } }