The Labs \ Source Viewer \ SSCLI \ System.Xml.Xsl.Runtime \ XmlAttributeCache

  1. //------------------------------------------------------------------------------
  2. // <copyright file="XmlAttributeCache.cs" company="Microsoft">
  3. //
  4. // Copyright (c) 2006 Microsoft Corporation. All rights reserved.
  5. //
  6. // The use and distribution terms for this software are contained in the file
  7. // named license.txt, which can be found in the root of this distribution.
  8. // By using this software in any fashion, you are agreeing to be bound by the
  9. // terms of this license.
  10. //
  11. // You must not remove this notice, or any other, from this software.
  12. //
  13. // </copyright>
  14. //------------------------------------------------------------------------------
  15. namespace System.Xml.Xsl.Runtime
  16. {
  17.     using System;
  18.     using System.Diagnostics;
  19.     using System.Xml;
  20.     using System.Xml.XPath;
  21.     using System.Xml.Schema;
  22.    
  23.    
  24.     /// <summary>
  25.     /// This writer supports only writer methods which write attributes. Attributes are stored in a
  26.     /// data structure until StartElementContent() is called, at which time the attributes are flushed
  27.     /// to the wrapped writer. In the case of duplicate attributes, the last attribute's value is used.
  28.     /// </summary>
  29.     internal sealed class XmlAttributeCache : XmlRawWriter, IRemovableWriter
  30.     {
  31.         private XmlRawWriter wrapped;
  32.         private OnRemoveWriter onRemove;
  33.         // Event handler that is called when cached attributes are flushed to wrapped writer
  34.         private AttrNameVal[] arrAttrs;
  35.         // List of cached attribute names and value parts
  36.         private int numEntries;
  37.         // Number of attributes in the cache
  38.         private int idxLastName;
  39.         // The entry containing the name of the last attribute to be cached
  40.         private int hashCodeUnion;
  41.         // Set of hash bits that can quickly guarantee a name is not a duplicate
  42.         /// <summary>
  43.         /// Initialize the cache. Use this method instead of a constructor in order to reuse the cache.
  44.         /// </summary>
  45.         public void Init(XmlRawWriter wrapped)
  46.         {
  47.             SetWrappedWriter(wrapped);
  48.            
  49.             // Clear attribute list
  50.             this.numEntries = 0;
  51.             this.idxLastName = 0;
  52.             this.hashCodeUnion = 0;
  53.         }
  54.        
  55.         /// <summary>
  56.         /// Return the number of cached attributes.
  57.         /// </summary>
  58.         public int Count {
  59.             get { return this.numEntries; }
  60.         }
  61.        
  62.        
  63.         //-----------------------------------------------
  64.         // IRemovableWriter interface
  65.         //-----------------------------------------------
  66.        
  67.         /// <summary>
  68.         /// This writer will raise this event once cached attributes have been flushed in order to signal that the cache
  69.         /// no longer needs to be part of the pipeline.
  70.         /// </summary>
  71.         public OnRemoveWriter OnRemoveWriterEvent {
  72.             get { return this.onRemove; }
  73.             set { this.onRemove = value; }
  74.         }
  75.        
  76.         /// <summary>
  77.         /// The wrapped writer will callback on this method if it wishes to remove itself from the pipeline.
  78.         /// </summary>
  79.         private void SetWrappedWriter(XmlRawWriter writer)
  80.         {
  81.             // If new writer might remove itself from pipeline, have it callback on this method when its ready to go
  82.             IRemovableWriter removable = writer as IRemovableWriter;
  83.             if (removable != null)
  84.                 removable.OnRemoveWriterEvent = SetWrappedWriter;
  85.            
  86.             this.wrapped = writer;
  87.         }
  88.        
  89.        
  90.         //-----------------------------------------------
  91.         // XmlWriter interface
  92.         //-----------------------------------------------
  93.        
  94.         /// <summary>
  95.         /// Add an attribute to the cache. If an attribute if the same name already exists, replace it.
  96.         /// </summary>
  97.         public override void WriteStartAttribute(string prefix, string localName, string ns)
  98.         {
  99.             int hashCode;
  100.             int idx = 0;
  101.             Debug.Assert(localName != null && localName.Length != 0 && prefix != null && ns != null);
  102.            
  103.             // Compute hashcode based on first letter of the localName
  104.             hashCode = (1 << ((int)localName[0] & 31));
  105.            
  106.             // If the hashcode is not in the union, then name will not be found by a scan
  107.             if ((this.hashCodeUnion & hashCode) != 0) {
  108.                 // The name may or may not be present, so scan for it
  109.                 Debug.Assert(this.numEntries != 0);
  110.                
  111.                 do {
  112.                     if (this.arrAttrs[idx].IsDuplicate(localName, ns, hashCode))
  113.                         break;
  114.                    
  115.                     // Next attribute name
  116.                     idx = this.arrAttrs[idx].NextNameIndex;
  117.                 }
  118.                 while (idx != 0);
  119.             }
  120.             else {
  121.                 // Insert hashcode into union
  122.                 this.hashCodeUnion |= hashCode;
  123.             }
  124.            
  125.             // Insert new attribute; link attribute names together in a list
  126.             EnsureAttributeCache();
  127.             if (this.numEntries != 0)
  128.                 this.arrAttrs[this.idxLastName].NextNameIndex = this.numEntries;
  129.             this.idxLastName = this.numEntries++;
  130.             this.arrAttrs[this.idxLastName].Init(prefix, localName, ns, hashCode);
  131.         }
  132.        
  133.         /// <summary>
  134.         /// No-op.
  135.         /// </summary>
  136.         public override void WriteEndAttribute()
  137.         {
  138.         }
  139.        
  140.         /// <summary>
  141.         /// Pass through namespaces to underlying writer. If any attributes have been cached, flush them.
  142.         /// </summary>
  143.         internal override void WriteNamespaceDeclaration(string prefix, string ns)
  144.         {
  145.             FlushAttributes();
  146.             this.wrapped.WriteNamespaceDeclaration(prefix, ns);
  147.         }
  148.        
  149.         /// <summary>
  150.         /// Add a block of text to the cache. This text block makes up some or all of the untyped string
  151.         /// value of the current attribute.
  152.         /// </summary>
  153.         public override void WriteString(string text)
  154.         {
  155.             Debug.Assert(text != null);
  156.             Debug.Assert(this.arrAttrs != null && this.numEntries != 0);
  157.             EnsureAttributeCache();
  158.             this.arrAttrs[this.numEntries++].Init(text);
  159.         }
  160.        
  161.         /// <summary>
  162.         /// All other WriteValue methods are implemented by XmlWriter to delegate to WriteValue(object) or WriteValue(string), so
  163.         /// only these two methods need to be implemented.
  164.         /// </summary>
  165.         public override void WriteValue(object value)
  166.         {
  167.             Debug.Assert(value is XmlAtomicValue, "value should always be an XmlAtomicValue, as XmlAttributeCache is only used by XmlQueryOutput");
  168.             Debug.Assert(this.arrAttrs != null && this.numEntries != 0);
  169.             EnsureAttributeCache();
  170.             this.arrAttrs[this.numEntries++].Init((XmlAtomicValue)value);
  171.         }
  172.        
  173.         public override void WriteValue(string value)
  174.         {
  175.             WriteValue(value);
  176.         }
  177.        
  178.         /// <summary>
  179.         /// Send cached, non-overriden attributes to the specified writer. Calling this method has
  180.         /// the side effect of clearing the attribute cache.
  181.         /// </summary>
  182.         internal override void StartElementContent()
  183.         {
  184.             FlushAttributes();
  185.            
  186.             // Call StartElementContent on wrapped writer
  187.             this.wrapped.StartElementContent();
  188.         }
  189.        
  190.         public override void WriteStartElement(string prefix, string localName, string ns)
  191.         {
  192.             Debug.Assert(false, "Should never be called on XmlAttributeCache.");
  193.         }
  194.         internal override void WriteEndElement(string prefix, string localName, string ns)
  195.         {
  196.             Debug.Assert(false, "Should never be called on XmlAttributeCache.");
  197.         }
  198.         public override void WriteComment(string text)
  199.         {
  200.             Debug.Assert(false, "Should never be called on XmlAttributeCache.");
  201.         }
  202.         public override void WriteProcessingInstruction(string name, string text)
  203.         {
  204.             Debug.Assert(false, "Should never be called on XmlAttributeCache.");
  205.         }
  206.         public override void WriteEntityRef(string name)
  207.         {
  208.             Debug.Assert(false, "Should never be called on XmlAttributeCache.");
  209.         }
  210.        
  211.         /// <summary>
  212.         /// Forward call to wrapped writer.
  213.         /// </summary>
  214.         public override void Close()
  215.         {
  216.             this.wrapped.Close();
  217.         }
  218.        
  219.         /// <summary>
  220.         /// Forward call to wrapped writer.
  221.         /// </summary>
  222.         public override void Flush()
  223.         {
  224.             this.wrapped.Flush();
  225.         }
  226.        
  227.        
  228.         //-----------------------------------------------
  229.         // Helper methods
  230.         //-----------------------------------------------
  231.        
  232.         private void FlushAttributes()
  233.         {
  234.             int idx = 0;
  235.             int idxNext;
  236.             string localName;
  237.            
  238.             while (idx != this.numEntries) {
  239.                 // Get index of next attribute's name (0 if this is the last attribute)
  240.                 idxNext = this.arrAttrs[idx].NextNameIndex;
  241.                 if (idxNext == 0)
  242.                     idxNext = this.numEntries;
  243.                
  244.                 // If localName is null, then this is a duplicate attribute that has been marked as "deleted"
  245.                 localName = this.arrAttrs[idx].LocalName;
  246.                 if (localName != null) {
  247.                     string prefix = this.arrAttrs[idx].Prefix;
  248.                     string ns = this.arrAttrs[idx].Namespace;
  249.                    
  250.                     this.wrapped.WriteStartAttribute(prefix, localName, ns);
  251.                    
  252.                     // Output all of this attribute's text or typed values
  253.                     while (++idx != idxNext) {
  254.                         string text = this.arrAttrs[idx].Text;
  255.                        
  256.                         if (text != null)
  257.                             this.wrapped.WriteString(text);
  258.                         else
  259.                             this.wrapped.WriteValue(this.arrAttrs[idx].Value);
  260.                     }
  261.                    
  262.                     this.wrapped.WriteEndAttribute();
  263.                 }
  264.                 else {
  265.                     // Skip over duplicate attributes
  266.                     idx = idxNext;
  267.                 }
  268.             }
  269.            
  270.             // Notify event listener that attributes have been flushed
  271.             if (this.onRemove != null)
  272.                 this.onRemove(this.wrapped);
  273.         }
  274.        
  275.         private struct AttrNameVal
  276.         {
  277.             private string localName;
  278.             private string prefix;
  279.             private string namespaceName;
  280.             private string text;
  281.             private XmlAtomicValue value;
  282.             private int hashCode;
  283.             private int nextNameIndex;
  284.            
  285.             public string LocalName {
  286.                 get { return this.localName; }
  287.             }
  288.             public string Prefix {
  289.                 get { return this.prefix; }
  290.             }
  291.             public string Namespace {
  292.                 get { return this.namespaceName; }
  293.             }
  294.             public string Text {
  295.                 get { return this.text; }
  296.             }
  297.             public XmlAtomicValue Value {
  298.                 get { return this.value; }
  299.             }
  300.             public int NextNameIndex {
  301.                 get { return this.nextNameIndex; }
  302.                 set { this.nextNameIndex = value; }
  303.             }
  304.            
  305.             /// <summary>
  306.             /// Cache an attribute's name and type.
  307.             /// </summary>
  308.             public void Init(string prefix, string localName, string ns, int hashCode)
  309.             {
  310.                 this.localName = localName;
  311.                 this.prefix = prefix;
  312.                 this.namespaceName = ns;
  313.                 this.hashCode = hashCode;
  314.                 this.nextNameIndex = 0;
  315.             }
  316.            
  317.             /// <summary>
  318.             /// Cache all or part of the attribute's string value.
  319.             /// </summary>
  320.             public void Init(string text)
  321.             {
  322.                 this.text = text;
  323.                 this.value = null;
  324.             }
  325.            
  326.             /// <summary>
  327.             /// Cache all or part of the attribute's typed value.
  328.             /// </summary>
  329.             public void Init(XmlAtomicValue value)
  330.             {
  331.                 this.text = null;
  332.                 this.value = value;
  333.             }
  334.            
  335.             /// <summary>
  336.             /// Returns true if this attribute has the specified name (and thus is a duplicate).
  337.             /// </summary>
  338.             public bool IsDuplicate(string localName, string ns, int hashCode)
  339.             {
  340.                 // If attribute is not marked as deleted
  341.                 if (this.localName != null) {
  342.                     // And if hash codes match,
  343.                     if (this.hashCode == hashCode) {
  344.                         // And if local names match,
  345.                         if (this.localName.Equals(localName)) {
  346.                             // And if namespaces match,
  347.                             if (this.namespaceName.Equals(ns)) {
  348.                                 // Then found duplicate attribute, so mark the attribute as deleted
  349.                                 this.localName = null;
  350.                                 return true;
  351.                             }
  352.                         }
  353.                     }
  354.                 }
  355.                 return false;
  356.             }
  357.         }
  358.        
  359.         #if DEBUG
  360.         private const int DefaultCacheSize = 2;
  361.         #else
  362.         private const int DefaultCacheSize = 32;
  363.         #endif
  364.        
  365.         /// <summary>
  366.         /// Ensure that attribute array has been created and is large enough for at least one
  367.         /// additional entry.
  368.         /// </summary>
  369.         private void EnsureAttributeCache()
  370.         {
  371.             if (this.arrAttrs == null) {
  372.                 // Create caching array
  373.                 this.arrAttrs = new AttrNameVal[DefaultCacheSize];
  374.             }
  375.             else if (this.numEntries >= this.arrAttrs.Length) {
  376.                 // Resize caching array
  377.                 Debug.Assert(this.numEntries == this.arrAttrs.Length);
  378.                 AttrNameVal[] arrNew = new AttrNameVal[this.numEntries * 2];
  379.                 Array.Copy(this.arrAttrs, arrNew, this.numEntries);
  380.                 this.arrAttrs = arrNew;
  381.             }
  382.         }
  383.     }
  384. }

Developer Fusion