1 //------------------------------------------------------------------------------ 2 // <copyright file="XmlElementList.cs" company="Microsoft"> 3 // Copyright (c) Microsoft Corporation. All rights reserved. 4 // </copyright> 5 // <owner current="true" primary="true">Microsoft</owner> 6 //------------------------------------------------------------------------------ 7 8 namespace System.Xml { 9 using System; 10 using System.Collections; 11 using System.Diagnostics; 12 13 internal class XmlElementList: XmlNodeList { 14 string asterisk; 15 int changeCount; //recording the total number that the dom tree has been changed ( insertion and deletetion ) 16 //the member vars below are saved for further reconstruction 17 string name; //only one of 2 string groups will be initialized depends on which constructor is called. 18 string localName; 19 string namespaceURI; 20 XmlNode rootNode; 21 // the memeber vars belwo serves the optimization of accessing of the elements in the list 22 int curInd; // -1 means the starting point for a new search round 23 XmlNode curElem; // if sets to rootNode, means the starting point for a new search round 24 bool empty; // whether the list is empty 25 bool atomized; //whether the localname and namespaceuri are aomized 26 int matchCount; // cached list count. -1 means it needs reconstruction 27 28 WeakReference listener; // XmlElementListListener 29 XmlElementList( XmlNode parent)30 private XmlElementList( XmlNode parent) { 31 Debug.Assert ( parent != null ); 32 Debug.Assert( parent.NodeType == XmlNodeType.Element || parent.NodeType == XmlNodeType.Document ); 33 this.rootNode = parent; 34 Debug.Assert( parent.Document != null ); 35 this.curInd = -1; 36 this.curElem = rootNode; 37 this.changeCount = 0; 38 this.empty = false; 39 this.atomized = true; 40 this.matchCount = -1; 41 // This can be a regular reference, but it would cause some kind of loop inside the GC 42 this.listener = new WeakReference(new XmlElementListListener(parent.Document, this)); 43 } 44 ~XmlElementList()45 ~XmlElementList() { 46 Dispose(false); 47 } 48 ConcurrencyCheck(XmlNodeChangedEventArgs args)49 internal void ConcurrencyCheck(XmlNodeChangedEventArgs args){ 50 if( atomized == false ) { 51 XmlNameTable nameTable = this.rootNode.Document.NameTable; 52 this.localName = nameTable.Add( this.localName ); 53 this.namespaceURI = nameTable.Add( this.namespaceURI ); 54 this.atomized = true; 55 } 56 if ( IsMatch( args.Node ) ) { 57 this.changeCount++ ; 58 this.curInd = -1; 59 this.curElem = rootNode; 60 if( args.Action == XmlNodeChangedAction.Insert ) 61 this.empty = false; 62 } 63 this.matchCount = -1; 64 } 65 XmlElementList( XmlNode parent, string name )66 internal XmlElementList( XmlNode parent, string name ): this( parent ) { 67 Debug.Assert( parent.Document != null ); 68 XmlNameTable nt = parent.Document.NameTable; 69 Debug.Assert( nt != null ); 70 asterisk = nt.Add("*"); 71 this.name = nt.Add( name ); 72 this.localName = null; 73 this.namespaceURI = null; 74 } 75 XmlElementList( XmlNode parent, string localName, string namespaceURI )76 internal XmlElementList( XmlNode parent, string localName, string namespaceURI ): this( parent ) { 77 Debug.Assert( parent.Document != null ); 78 XmlNameTable nt = parent.Document.NameTable; 79 Debug.Assert( nt != null ); 80 asterisk = nt.Add("*"); 81 this.localName = nt.Get( localName ); 82 this.namespaceURI = nt.Get( namespaceURI ); 83 if( (this.localName == null) || (this.namespaceURI== null) ) { 84 this.empty = true; 85 this.atomized = false; 86 this.localName = localName; 87 this.namespaceURI = namespaceURI; 88 } 89 this.name = null; 90 } 91 92 internal int ChangeCount { 93 get { return changeCount; } 94 } 95 96 // return the next element node that is in PreOrder NextElemInPreOrder( XmlNode curNode )97 private XmlNode NextElemInPreOrder( XmlNode curNode ) { 98 Debug.Assert( curNode != null ); 99 //For preorder walking, first try its child 100 XmlNode retNode = curNode.FirstChild; 101 if ( retNode == null ) { 102 //if no child, the next node forward will the be the NextSibling of the first ancestor which has NextSibling 103 //so, first while-loop find out such an ancestor (until no more ancestor or the ancestor is the rootNode 104 retNode = curNode; 105 while ( retNode != null 106 && retNode != rootNode 107 && retNode.NextSibling == null ) { 108 retNode = retNode.ParentNode; 109 } 110 //then if such ancestor exists, set the retNode to its NextSibling 111 if ( retNode != null && retNode != rootNode ) 112 retNode = retNode.NextSibling; 113 } 114 if ( retNode == this.rootNode ) 115 //if reach the rootNode, consider having walked through the whole tree and no more element after the curNode 116 retNode = null; 117 return retNode; 118 } 119 120 // return the previous element node that is in PreOrder PrevElemInPreOrder( XmlNode curNode )121 private XmlNode PrevElemInPreOrder( XmlNode curNode ) { 122 Debug.Assert( curNode != null ); 123 //For preorder walking, the previous node will be the right-most node in the tree of PreviousSibling of the curNode 124 XmlNode retNode = curNode.PreviousSibling; 125 // so if the PreviousSibling is not null, going through the tree down to find the right-most node 126 while ( retNode != null ) { 127 if ( retNode.LastChild == null ) 128 break; 129 retNode = retNode.LastChild; 130 } 131 // if no PreviousSibling, the previous node will be the curNode's parentNode 132 if ( retNode == null ) 133 retNode = curNode.ParentNode; 134 // if the final retNode is rootNode, consider having walked through the tree and no more previous node 135 if ( retNode == this.rootNode ) 136 retNode = null; 137 return retNode; 138 } 139 140 // if the current node a matching element node IsMatch( XmlNode curNode )141 private bool IsMatch ( XmlNode curNode ) { 142 if (curNode.NodeType == XmlNodeType.Element) { 143 if ( this.name != null ) { 144 if ( Ref.Equal(this.name, asterisk) || Ref.Equal(curNode.Name, this.name) ) 145 return true; 146 } 147 else { 148 if ( 149 (Ref.Equal(this.localName, asterisk) || Ref.Equal(curNode.LocalName, this.localName) ) && 150 (Ref.Equal(this.namespaceURI, asterisk) || curNode.NamespaceURI == this.namespaceURI ) 151 ) { 152 return true; 153 } 154 } 155 } 156 return false; 157 } 158 GetMatchingNode( XmlNode n, bool bNext )159 private XmlNode GetMatchingNode( XmlNode n, bool bNext ) { 160 Debug.Assert( n!= null ); 161 XmlNode node = n; 162 do { 163 if ( bNext ) 164 node = NextElemInPreOrder( node ); 165 else 166 node = PrevElemInPreOrder( node ); 167 } while ( node != null && !IsMatch( node ) ); 168 return node; 169 } 170 GetNthMatchingNode( XmlNode n, bool bNext, int nCount )171 private XmlNode GetNthMatchingNode( XmlNode n, bool bNext, int nCount ) { 172 Debug.Assert( n!= null ); 173 XmlNode node = n; 174 for ( int ind = 0 ; ind < nCount; ind++ ) { 175 node = GetMatchingNode( node, bNext ); 176 if ( node == null ) 177 return null; 178 } 179 return node; 180 } 181 182 //the function is for the enumerator to find out the next available matching element node GetNextNode( XmlNode n )183 public XmlNode GetNextNode( XmlNode n ) { 184 if( this.empty == true ) 185 return null; 186 XmlNode node = ( n == null ) ? rootNode : n; 187 return GetMatchingNode( node, true ); 188 } 189 Item(int index)190 public override XmlNode Item(int index) { 191 if ( rootNode == null || index < 0 ) 192 return null; 193 194 if( this.empty == true ) 195 return null; 196 if ( curInd == index ) 197 return curElem; 198 int nDiff = index - curInd; 199 bool bForward = ( nDiff > 0 ); 200 if ( nDiff < 0 ) 201 nDiff = -nDiff; 202 XmlNode node; 203 if ( ( node = GetNthMatchingNode( curElem, bForward, nDiff ) ) != null ) { 204 curInd = index; 205 curElem = node; 206 return curElem; 207 } 208 return null; 209 } 210 211 public override int Count { 212 get { 213 if( this.empty == true ) 214 return 0; 215 if (this.matchCount < 0) { 216 int currMatchCount = 0; 217 int currChangeCount = this.changeCount; 218 XmlNode node = rootNode; 219 while ((node = GetMatchingNode(node, true)) != null) { 220 currMatchCount++; 221 } 222 if (currChangeCount != this.changeCount) { 223 return currMatchCount; 224 } 225 this.matchCount = currMatchCount; 226 } 227 return this.matchCount; 228 } 229 } 230 GetEnumerator()231 public override IEnumerator GetEnumerator() { 232 if( this.empty == true ) 233 return new XmlEmptyElementListEnumerator(this);; 234 return new XmlElementListEnumerator(this); 235 } 236 PrivateDisposeNodeList()237 protected override void PrivateDisposeNodeList() { 238 GC.SuppressFinalize(this); 239 Dispose(true); 240 } 241 Dispose(bool disposing)242 protected virtual void Dispose(bool disposing) { 243 if (this.listener != null) { 244 XmlElementListListener listener = (XmlElementListListener)this.listener.Target; 245 if (listener != null) { 246 listener.Unregister(); 247 } 248 this.listener = null; 249 } 250 } 251 } 252 253 internal class XmlElementListEnumerator : IEnumerator { 254 XmlElementList list; 255 XmlNode curElem; 256 int changeCount; //save the total number that the dom tree has been changed ( insertion and deletetion ) when this enumerator is created 257 XmlElementListEnumerator( XmlElementList list )258 public XmlElementListEnumerator( XmlElementList list ) { 259 this.list = list; 260 this.curElem = null; 261 this.changeCount = list.ChangeCount; 262 } 263 MoveNext()264 public bool MoveNext() { 265 if ( list.ChangeCount != this.changeCount ) { 266 //the number mismatch, there is new change(s) happened since last MoveNext() is called. 267 throw new InvalidOperationException( Res.GetString(Res.Xdom_Enum_ElementList) ); 268 } 269 else { 270 curElem = list.GetNextNode( curElem ); 271 } 272 return curElem != null; 273 } 274 Reset()275 public void Reset() { 276 curElem = null; 277 //reset the number of changes to be synced with current dom tree as well 278 this.changeCount = list.ChangeCount; 279 } 280 281 public object Current { 282 get { return curElem; } 283 } 284 } 285 286 internal class XmlEmptyElementListEnumerator : IEnumerator { XmlEmptyElementListEnumerator( XmlElementList list )287 public XmlEmptyElementListEnumerator( XmlElementList list ) { 288 } 289 MoveNext()290 public bool MoveNext() { 291 return false; 292 } 293 Reset()294 public void Reset() { 295 } 296 297 public object Current { 298 get { return null; } 299 } 300 } 301 302 internal class XmlElementListListener { 303 WeakReference elemList; 304 XmlDocument doc; 305 XmlNodeChangedEventHandler nodeChangeHandler = null; 306 XmlElementListListener(XmlDocument doc, XmlElementList elemList)307 internal XmlElementListListener(XmlDocument doc, XmlElementList elemList) { 308 this.doc = doc; 309 this.elemList = new WeakReference(elemList); 310 this.nodeChangeHandler = new XmlNodeChangedEventHandler( this.OnListChanged ); 311 doc.NodeInserted += this.nodeChangeHandler; 312 doc.NodeRemoved += this.nodeChangeHandler; 313 } 314 OnListChanged( object sender, XmlNodeChangedEventArgs args )315 private void OnListChanged( object sender, XmlNodeChangedEventArgs args ) { 316 lock (this) { 317 if (this.elemList != null) { 318 XmlElementList el = (XmlElementList)this.elemList.Target; 319 if (null != el) { 320 el.ConcurrencyCheck(args); 321 } else { 322 this.doc.NodeInserted -= this.nodeChangeHandler; 323 this.doc.NodeRemoved -= this.nodeChangeHandler; 324 this.elemList = null; 325 } 326 } 327 } 328 } 329 330 // This method is called from the finalizer of XmlElementList Unregister()331 internal void Unregister() { 332 lock (this) { 333 if (elemList != null) { 334 this.doc.NodeInserted -= this.nodeChangeHandler; 335 this.doc.NodeRemoved -= this.nodeChangeHandler; 336 this.elemList = null; 337 } 338 } 339 } 340 } 341 } 342