1 //------------------------------------------------------------------------------
2 // <copyright file="XmlElementList.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 // <owner current="true" primary="true">Microsoft</owner>
6 //------------------------------------------------------------------------------
10 using System.Collections;
11 using System.Diagnostics;
13 internal class XmlElementList: XmlNodeList {
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.
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
28 WeakReference listener; // XmlElementListListener
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 );
36 this.curElem = rootNode;
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));
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 );
56 if ( IsMatch( args.Node ) ) {
59 this.curElem = rootNode;
60 if( args.Action == XmlNodeChangedAction.Insert )
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;
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) ) {
85 this.atomized = false;
86 this.localName = localName;
87 this.namespaceURI = namespaceURI;
92 internal int ChangeCount {
93 get { return changeCount; }
96 // return the next element node that is in PreOrder
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
105 while ( retNode != null
106 && retNode != rootNode
107 && retNode.NextSibling == null ) {
108 retNode = retNode.ParentNode;
110 //then if such ancestor exists, set the retNode to its NextSibling
111 if ( retNode != null && retNode != rootNode )
112 retNode = retNode.NextSibling;
114 if ( retNode == this.rootNode )
115 //if reach the rootNode, consider having walked through the whole tree and no more element after the curNode
120 // return the previous element node that is in PreOrder
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 )
129 retNode = retNode.LastChild;
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 )
140 // if the current node a matching element node
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) )
149 (Ref.Equal(this.localName, asterisk) || Ref.Equal(curNode.LocalName, this.localName) ) &&
150 (Ref.Equal(this.namespaceURI, asterisk) || curNode.NamespaceURI == this.namespaceURI )
159 private XmlNode GetMatchingNode( XmlNode n, bool bNext ) {
160 Debug.Assert( n!= null );
164 node = NextElemInPreOrder( node );
166 node = PrevElemInPreOrder( node );
167 } while ( node != null && !IsMatch( node ) );
171 private XmlNode GetNthMatchingNode( XmlNode n, bool bNext, int nCount ) {
172 Debug.Assert( n!= null );
174 for ( int ind = 0 ; ind < nCount; ind++ ) {
175 node = GetMatchingNode( node, bNext );
182 //the function is for the enumerator to find out the next available matching element node
183 public XmlNode GetNextNode( XmlNode n ) {
184 if( this.empty == true )
186 XmlNode node = ( n == null ) ? rootNode : n;
187 return GetMatchingNode( node, true );
190 public override XmlNode Item(int index) {
191 if ( rootNode == null || index < 0 )
194 if( this.empty == true )
196 if ( curInd == index )
198 int nDiff = index - curInd;
199 bool bForward = ( nDiff > 0 );
203 if ( ( node = GetNthMatchingNode( curElem, bForward, nDiff ) ) != null ) {
211 public override int Count {
213 if( this.empty == true )
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) {
222 if (currChangeCount != this.changeCount) {
223 return currMatchCount;
225 this.matchCount = currMatchCount;
227 return this.matchCount;
231 public override IEnumerator GetEnumerator() {
232 if( this.empty == true )
233 return new XmlEmptyElementListEnumerator(this);;
234 return new XmlElementListEnumerator(this);
237 protected override void PrivateDisposeNodeList() {
238 GC.SuppressFinalize(this);
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();
248 this.listener = null;
253 internal class XmlElementListEnumerator : IEnumerator {
256 int changeCount; //save the total number that the dom tree has been changed ( insertion and deletetion ) when this enumerator is created
258 public XmlElementListEnumerator( XmlElementList list ) {
261 this.changeCount = list.ChangeCount;
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) );
270 curElem = list.GetNextNode( curElem );
272 return curElem != null;
275 public void Reset() {
277 //reset the number of changes to be synced with current dom tree as well
278 this.changeCount = list.ChangeCount;
281 public object Current {
282 get { return curElem; }
286 internal class XmlEmptyElementListEnumerator : IEnumerator {
287 public XmlEmptyElementListEnumerator( XmlElementList list ) {
290 public bool MoveNext() {
294 public void Reset() {
297 public object Current {
302 internal class XmlElementListListener {
303 WeakReference elemList;
305 XmlNodeChangedEventHandler nodeChangeHandler = null;
307 internal XmlElementListListener(XmlDocument doc, XmlElementList elemList) {
309 this.elemList = new WeakReference(elemList);
310 this.nodeChangeHandler = new XmlNodeChangedEventHandler( this.OnListChanged );
311 doc.NodeInserted += this.nodeChangeHandler;
312 doc.NodeRemoved += this.nodeChangeHandler;
315 private void OnListChanged( object sender, XmlNodeChangedEventArgs args ) {
317 if (this.elemList != null) {
318 XmlElementList el = (XmlElementList)this.elemList.Target;
320 el.ConcurrencyCheck(args);
322 this.doc.NodeInserted -= this.nodeChangeHandler;
323 this.doc.NodeRemoved -= this.nodeChangeHandler;
324 this.elemList = null;
330 // This method is called from the finalizer of XmlElementList
331 internal void Unregister() {
333 if (elemList != null) {
334 this.doc.NodeInserted -= this.nodeChangeHandler;
335 this.doc.NodeRemoved -= this.nodeChangeHandler;
336 this.elemList = null;