| File: System\Xml\Xsl\Runtime\XmlAttributeCache.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.Diagnostics; using System.Xml; using System.Xml.Schema; using System.Xml.XPath; namespace System.Xml.Xsl.Runtime { /// <summary> /// This writer supports only writer methods which write attributes. Attributes are stored in a /// data structure until StartElementContent() is called, at which time the attributes are flushed /// to the wrapped writer. In the case of duplicate attributes, the last attribute's value is used. /// </summary> internal sealed class XmlAttributeCache : XmlRawWriter, IRemovableWriter { private XmlRawWriter? _wrapped; private OnRemoveWriter? _onRemove; // Event handler that is called when cached attributes are flushed to wrapped writer private AttrNameVal[]? _arrAttrs; // List of cached attribute names and value parts private int _numEntries; // Number of attributes in the cache private int _idxLastName; // The entry containing the name of the last attribute to be cached private int _hashCodeUnion; // Set of hash bits that can quickly guarantee a name is not a duplicate /// <summary> /// Initialize the cache. Use this method instead of a constructor in order to reuse the cache. /// </summary> public void Init(XmlRawWriter wrapped) { SetWrappedWriter(wrapped); // Clear attribute list _numEntries = 0; _idxLastName = 0; _hashCodeUnion = 0; } /// <summary> /// Return the number of cached attributes. /// </summary> public int Count { get { return _numEntries; } } //----------------------------------------------- // IRemovableWriter interface //----------------------------------------------- /// <summary> /// This writer will raise this event once cached attributes have been flushed in order to signal that the cache /// no longer needs to be part of the pipeline. /// </summary> public OnRemoveWriter? OnRemoveWriterEvent { get { return _onRemove; } set { _onRemove = value; } } /// <summary> /// The wrapped writer will callback on this method if it wishes to remove itself from the pipeline. /// </summary> private void SetWrappedWriter(XmlRawWriter? writer) { // If new writer might remove itself from pipeline, have it callback on this method when its ready to go IRemovableWriter? removable = writer as IRemovableWriter; removable?.OnRemoveWriterEvent = SetWrappedWriter; _wrapped = writer; } //----------------------------------------------- // XmlWriter interface //----------------------------------------------- /// <summary> /// Add an attribute to the cache. If an attribute if the same name already exists, replace it. /// </summary> public override void WriteStartAttribute(string? prefix, string localName, string? ns) { int hashCode; int idx = 0; Debug.Assert(!string.IsNullOrEmpty(localName) && prefix != null && ns != null); // Compute hashcode based on first letter of the localName hashCode = (1 << ((int)localName[0] & 31)); // If the hashcode is not in the union, then name will not be found by a scan if ((_hashCodeUnion & hashCode) != 0) { // The name may or may not be present, so scan for it Debug.Assert(_numEntries != 0); do { if (_arrAttrs![idx].IsDuplicate(localName, ns, hashCode)) break; // Next attribute name idx = _arrAttrs[idx].NextNameIndex; } while (idx != 0); } else { // Insert hashcode into union _hashCodeUnion |= hashCode; } // Insert new attribute; link attribute names together in a list EnsureAttributeCache(); if (_numEntries != 0) _arrAttrs![_idxLastName].NextNameIndex = _numEntries; _idxLastName = _numEntries++; _arrAttrs![_idxLastName].Init(prefix, localName, ns, hashCode); } /// <summary> /// No-op. /// </summary> public override void WriteEndAttribute() { } /// <summary> /// Pass through namespaces to underlying writer. If any attributes have been cached, flush them. /// </summary> internal override void WriteNamespaceDeclaration(string prefix, string ns) { FlushAttributes(); _wrapped!.WriteNamespaceDeclaration(prefix, ns); } /// <summary> /// Add a block of text to the cache. This text block makes up some or all of the untyped string /// value of the current attribute. /// </summary> public override void WriteString(string? text) { Debug.Assert(text != null); Debug.Assert(_arrAttrs != null && _numEntries != 0); EnsureAttributeCache(); _arrAttrs[_numEntries++].Init(text); } /// <summary> /// All other WriteValue methods are implemented by XmlWriter to delegate to WriteValue(object), so /// only this method needs to be implemented. /// </summary> public override void WriteValue(object value) { Debug.Assert(value is XmlAtomicValue, "value should always be an XmlAtomicValue, as XmlAttributeCache is only used by XmlQueryOutput"); Debug.Assert(_arrAttrs != null && _numEntries != 0); EnsureAttributeCache(); _arrAttrs[_numEntries++].Init((XmlAtomicValue)value); } /// <summary> /// Send cached, non-overridden attributes to the specified writer. Calling this method has /// the side effect of clearing the attribute cache. /// </summary> internal override void StartElementContent() { FlushAttributes(); // Call StartElementContent on wrapped writer _wrapped!.StartElementContent(); } public override void WriteStartElement(string? prefix, string localName, string? ns) { Debug.Fail("Should never be called on XmlAttributeCache."); } internal override void WriteEndElement(string? prefix, string localName, string? ns) { Debug.Fail("Should never be called on XmlAttributeCache."); } public override void WriteComment(string? text) { Debug.Fail("Should never be called on XmlAttributeCache."); } public override void WriteProcessingInstruction(string name, string? text) { Debug.Fail("Should never be called on XmlAttributeCache."); } public override void WriteEntityRef(string name) { Debug.Fail("Should never be called on XmlAttributeCache."); } public override void WriteValue(string? value) { Debug.Fail("Should never be called on XmlAttributeCache."); } /// <summary> /// Forward call to wrapped writer. /// </summary> public override void Close() { _wrapped!.Close(); } /// <summary> /// Forward call to wrapped writer. /// </summary> public override void Flush() { _wrapped!.Flush(); } //----------------------------------------------- // Helper methods //----------------------------------------------- private void FlushAttributes() { int idx = 0, idxNext; string? localName; while (idx != _numEntries) { // Get index of next attribute's name (0 if this is the last attribute) idxNext = _arrAttrs![idx].NextNameIndex; if (idxNext == 0) idxNext = _numEntries; // If localName is null, then this is a duplicate attribute that has been marked as "deleted" localName = _arrAttrs[idx].LocalName; if (localName != null) { string prefix = _arrAttrs[idx].Prefix; string ns = _arrAttrs[idx].Namespace; _wrapped!.WriteStartAttribute(prefix, localName, ns); // Output all of this attribute's text or typed values while (++idx != idxNext) { string? text = _arrAttrs[idx].Text; if (text != null) _wrapped.WriteString(text); else _wrapped.WriteValue(_arrAttrs[idx].Value!); } _wrapped.WriteEndAttribute(); } else { // Skip over duplicate attributes idx = idxNext; } } // Notify event listener that attributes have been flushed _onRemove?.Invoke(_wrapped!); } private struct AttrNameVal { private string? _localName; private string _prefix; private string _namespaceName; private string? _text; private XmlAtomicValue? _value; private int _hashCode; private int _nextNameIndex; public string? LocalName { get { return _localName; } } public string Prefix { get { return _prefix; } } public string Namespace { get { return _namespaceName; } } public string? Text { get { return _text; } } public XmlAtomicValue? Value { get { return _value; } } public int NextNameIndex { get { return _nextNameIndex; } set { _nextNameIndex = value; } } /// <summary> /// Cache an attribute's name and type. /// </summary> public void Init(string prefix, string localName, string ns, int hashCode) { _localName = localName; _prefix = prefix; _namespaceName = ns; _hashCode = hashCode; _nextNameIndex = 0; } /// <summary> /// Cache all or part of the attribute's string value. /// </summary> public void Init(string text) { _text = text; _value = null; } /// <summary> /// Cache all or part of the attribute's typed value. /// </summary> public void Init(XmlAtomicValue value) { _text = null; _value = value; } /// <summary> /// Returns true if this attribute has the specified name (and thus is a duplicate). /// </summary> public bool IsDuplicate(string localName, string ns, int hashCode) { // If attribute is not marked as deleted if (_localName != null) { // And if hash codes match, if (_hashCode == hashCode) { // And if local names match, if (_localName.Equals(localName)) { // And if namespaces match, if (_namespaceName.Equals(ns)) { // Then found duplicate attribute, so mark the attribute as deleted _localName = null; return true; } } } } return false; } } #if DEBUG private const int DefaultCacheSize = 2; #else private const int DefaultCacheSize = 32; #endif /// <summary> /// Ensure that attribute array has been created and is large enough for at least one /// additional entry. /// </summary> private void EnsureAttributeCache() { if (_arrAttrs == null) { // Create caching array _arrAttrs = new AttrNameVal[DefaultCacheSize]; } else if (_numEntries >= _arrAttrs.Length) { // Resize caching array Debug.Assert(_numEntries == _arrAttrs.Length); AttrNameVal[] arrNew = new AttrNameVal[_numEntries * 2]; Array.Copy(_arrAttrs, arrNew, _numEntries); _arrAttrs = arrNew; } } } }