Welcome to mirror list, hosted at ThFree Co, Russian Federation.

XmlElementList.cs « Dom « Xml « System « System.Xml « referencesource « class « mcs - github.com/mono/mono.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: d76e66c2a1cf1f51d45a864cd198b20c30a384cb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
//------------------------------------------------------------------------------
// <copyright file="XmlElementList.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
// <owner current="true" primary="true">[....]</owner>
//------------------------------------------------------------------------------

namespace System.Xml {
    using System;
    using System.Collections;
    using System.Diagnostics;

    internal class XmlElementList: XmlNodeList {
        string      asterisk;
        int         changeCount; //recording the total number that the dom tree has been changed ( insertion and deletetion )
        //the member vars below are saved for further reconstruction        
        string      name;         //only one of 2 string groups will be initialized depends on which constructor is called.
        string      localName; 
        string      namespaceURI;
        XmlNode     rootNode;
        // the memeber vars belwo serves the optimization of accessing of the elements in the list
        int         curInd;       // -1 means the starting point for a new search round
        XmlNode     curElem;      // if sets to rootNode, means the starting point for a new search round
        bool        empty;        // whether the list is empty
        bool        atomized;     //whether the localname and namespaceuri are aomized
        int         matchCount;   // cached list count. -1 means it needs reconstruction

        WeakReference listener;   // XmlElementListListener

        private XmlElementList( XmlNode parent) {
            Debug.Assert ( parent != null );
            Debug.Assert( parent.NodeType == XmlNodeType.Element || parent.NodeType == XmlNodeType.Document );
            this.rootNode = parent;
            Debug.Assert( parent.Document != null );
            this.curInd = -1;
            this.curElem = rootNode;
            this.changeCount = 0;
            this.empty = false;
            this.atomized = true; 
            this.matchCount = -1;
            // This can be a regular reference, but it would cause some kind of loop inside the GC
            this.listener = new WeakReference(new XmlElementListListener(parent.Document, this));
        }

        ~XmlElementList() {
            Dispose(false);
        }

        internal void ConcurrencyCheck(XmlNodeChangedEventArgs args){
            if( atomized == false ) {
                XmlNameTable nameTable = this.rootNode.Document.NameTable;
                this.localName = nameTable.Add( this.localName );
                this.namespaceURI = nameTable.Add( this.namespaceURI );
                this.atomized = true;
            }                
            if ( IsMatch( args.Node ) ) {
                this.changeCount++ ;
                this.curInd = -1;
                this.curElem = rootNode;
                if( args.Action == XmlNodeChangedAction.Insert )
                    this.empty = false;
            }
            this.matchCount = -1;
        }

        internal XmlElementList( XmlNode parent, string name ): this( parent ) {
            Debug.Assert( parent.Document != null );            
            XmlNameTable nt = parent.Document.NameTable;
            Debug.Assert( nt != null );
            asterisk = nt.Add("*");
            this.name = nt.Add( name );
            this.localName = null;
            this.namespaceURI = null;
        }

        internal XmlElementList( XmlNode parent, string localName, string namespaceURI ): this( parent ) {
            Debug.Assert( parent.Document != null );
            XmlNameTable nt = parent.Document.NameTable;
            Debug.Assert( nt != null );
            asterisk = nt.Add("*");
            this.localName = nt.Get( localName );
            this.namespaceURI = nt.Get( namespaceURI );
            if( (this.localName == null) || (this.namespaceURI== null) ) {
                this.empty = true;
                this.atomized = false;
                this.localName = localName;
                this.namespaceURI = namespaceURI;
            }   
                this.name = null;
        }
        
        internal int ChangeCount {
            get { return changeCount; }
        }

        // return the next element node that is in PreOrder
        private XmlNode NextElemInPreOrder( XmlNode curNode ) {
            Debug.Assert( curNode != null );
            //For preorder walking, first try its child
            XmlNode retNode = curNode.FirstChild;
            if ( retNode == null ) {
                //if no child, the next node forward will the be the NextSibling of the first ancestor which has NextSibling
                //so, first while-loop find out such an ancestor (until no more ancestor or the ancestor is the rootNode
                retNode = curNode;
                while ( retNode != null 
                        && retNode != rootNode 
                        && retNode.NextSibling == null ) {
                    retNode = retNode.ParentNode;
                }
                //then if such ancestor exists, set the retNode to its NextSibling
                if ( retNode != null && retNode != rootNode )
                    retNode = retNode.NextSibling;
            }
            if ( retNode == this.rootNode ) 
                //if reach the rootNode, consider having walked through the whole tree and no more element after the curNode
                retNode = null;
            return retNode;
        }

        // return the previous element node that is in PreOrder
        private XmlNode PrevElemInPreOrder( XmlNode curNode ) {
            Debug.Assert( curNode != null );
            //For preorder walking, the previous node will be the right-most node in the tree of PreviousSibling of the curNode
            XmlNode retNode = curNode.PreviousSibling;
            // so if the PreviousSibling is not null, going through the tree down to find the right-most node
            while ( retNode != null ) {
                if ( retNode.LastChild == null )
                    break;
                retNode = retNode.LastChild;
            }
            // if no PreviousSibling, the previous node will be the curNode's parentNode
            if ( retNode == null )
                retNode = curNode.ParentNode;
            // if the final retNode is rootNode, consider having walked through the tree and no more previous node
            if ( retNode == this.rootNode )
                retNode = null;
            return retNode;
        }

        // if the current node a matching element node
        private bool IsMatch ( XmlNode curNode ) {
            if (curNode.NodeType == XmlNodeType.Element) {
                if ( this.name != null ) {
                    if ( Ref.Equal(this.name, asterisk) || Ref.Equal(curNode.Name, this.name) )
                        return true;
                } 
                else {
                    if (
                        (Ref.Equal(this.localName, asterisk) || Ref.Equal(curNode.LocalName, this.localName) ) && 
                        (Ref.Equal(this.namespaceURI, asterisk) || curNode.NamespaceURI == this.namespaceURI )
                    ) {
                        return true;
                    }
                }
            }     
            return false;
        }

        private XmlNode GetMatchingNode( XmlNode n, bool bNext ) {
            Debug.Assert( n!= null );
            XmlNode node = n;            
            do {
                if ( bNext )
                    node = NextElemInPreOrder( node );
                else
                    node = PrevElemInPreOrder( node );
            } while ( node != null && !IsMatch( node ) );
            return node;
        }

        private XmlNode GetNthMatchingNode( XmlNode n, bool bNext, int nCount ) {
            Debug.Assert( n!= null );
            XmlNode node = n;
            for ( int ind = 0 ; ind < nCount; ind++ ) {
                node = GetMatchingNode( node, bNext );
                if ( node == null )
                    return null;
            } 
            return node;
        }

        //the function is for the enumerator to find out the next available matching element node
        public XmlNode GetNextNode( XmlNode n ) {
            if( this.empty == true )
                return null;
            XmlNode node = ( n == null ) ? rootNode : n;
            return GetMatchingNode( node, true );
        }

        public override XmlNode Item(int index) {
            if ( rootNode == null || index < 0 ) 
                return null;

            if( this.empty == true )
                return null;
            if ( curInd == index )
                return curElem;
            int nDiff = index - curInd;
            bool bForward = ( nDiff > 0 );
            if ( nDiff < 0 )
                nDiff = -nDiff;
            XmlNode node;
            if ( ( node = GetNthMatchingNode( curElem, bForward, nDiff ) ) != null ) {
                curInd = index;
                curElem = node;
                return curElem;
            } 
            return null;
        }

        public override int Count { 
            get { 
                if( this.empty == true )
                    return 0;
                if (this.matchCount < 0) {
                    int currMatchCount = 0;
                    int currChangeCount = this.changeCount;
                    XmlNode node = rootNode;
                    while ((node = GetMatchingNode(node, true)) != null) {
                        currMatchCount++;
                    }
                    if (currChangeCount != this.changeCount) {
                        return currMatchCount;
                    }
                    this.matchCount = currMatchCount;
                }
                return this.matchCount;
            }
        }
    
        public override IEnumerator GetEnumerator() {
            if( this.empty == true )
                return new XmlEmptyElementListEnumerator(this);;
            return new XmlElementListEnumerator(this);
        }

        protected override void PrivateDisposeNodeList() {
            GC.SuppressFinalize(this);
            Dispose(true);
        }

        protected virtual void Dispose(bool disposing) {
            if (this.listener != null) {
                XmlElementListListener listener = (XmlElementListListener)this.listener.Target;
                if (listener != null) {
                    listener.Unregister();
                }
                this.listener = null;
            }
        }
    }

     internal class XmlElementListEnumerator : IEnumerator {
        XmlElementList  list;
        XmlNode         curElem;
        int             changeCount; //save the total number that the dom tree has been changed ( insertion and deletetion ) when this enumerator is created

        public XmlElementListEnumerator( XmlElementList list ) {
            this.list = list;
            this.curElem = null;
            this.changeCount = list.ChangeCount;
        }

        public bool MoveNext() { 
            if ( list.ChangeCount != this.changeCount ) {
                //the number mismatch, there is new change(s) happened since last MoveNext() is called.
                throw new InvalidOperationException( Res.GetString(Res.Xdom_Enum_ElementList) );
            } 
            else  {
                curElem = list.GetNextNode( curElem );                
            }
            return curElem != null;
        }

        public void Reset() {
            curElem = null;
            //reset the number of changes to be synced with current dom tree as well
            this.changeCount = list.ChangeCount;
        }

        public object Current {
            get { return curElem; }
        }
    }
    
    internal class XmlEmptyElementListEnumerator : IEnumerator {        
        public XmlEmptyElementListEnumerator( XmlElementList list ) {
        }

        public bool MoveNext() { 
            return false;
        }

        public void Reset() {
        }
        
        public object Current {
            get { return null; }
        }
    }

    internal class XmlElementListListener {
        WeakReference elemList;
        XmlDocument doc;
        XmlNodeChangedEventHandler nodeChangeHandler = null;

        internal XmlElementListListener(XmlDocument doc, XmlElementList elemList) {
            this.doc = doc;
            this.elemList = new WeakReference(elemList);
            this.nodeChangeHandler = new XmlNodeChangedEventHandler( this.OnListChanged );
            doc.NodeInserted += this.nodeChangeHandler;
            doc.NodeRemoved += this.nodeChangeHandler;
        }

        private void OnListChanged( object sender, XmlNodeChangedEventArgs args ) {
            lock (this) {
                if (this.elemList != null) {
                    XmlElementList el = (XmlElementList)this.elemList.Target;
                    if (null != el) {
                        el.ConcurrencyCheck(args);
                    } else {
                        this.doc.NodeInserted -= this.nodeChangeHandler;
                        this.doc.NodeRemoved -= this.nodeChangeHandler;
                        this.elemList = null;
                    }
                }
            }
        }

        // This method is called from the finalizer of XmlElementList
        internal void Unregister() {
            lock (this) {
                if (elemList != null) {
                    this.doc.NodeInserted -= this.nodeChangeHandler;
                    this.doc.NodeRemoved -= this.nodeChangeHandler;
                    this.elemList = null;
                }
            }
        }
    }
}