| File: System\Xml\Dom\XmlElementList.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.Collections; using System.Diagnostics; namespace System.Xml { internal sealed class XmlElementList : XmlNodeList { private readonly string _asterisk = null!; private int _changeCount; //recording the total number that the dom tree has been changed ( insertion and deletion ) //the member vars below are saved for further reconstruction private readonly string? _name; //only one of 2 string groups will be initialized depends on which constructor is called. private string? _localName; private string? _namespaceURI; private readonly XmlNode _rootNode; // the member vars below serves the optimization of accessing of the elements in the list private int _curInd; // -1 means the starting point for a new search round private XmlNode _curElem; // if sets to rootNode, means the starting point for a new search round private bool _empty; // whether the list is empty private bool _atomized; //whether the localname and namespaceuri are atomized private int _matchCount; // cached list count. -1 means it needs reconstruction private WeakReference<XmlElementListListener>? _listener; private XmlElementList(XmlNode parent) { Debug.Assert(parent != null); Debug.Assert(parent.NodeType == XmlNodeType.Element || parent.NodeType == XmlNodeType.Document); _rootNode = parent; Debug.Assert(parent.Document != null); _curInd = -1; _curElem = _rootNode; _changeCount = 0; _empty = false; _atomized = true; _matchCount = -1; // This can be a regular reference, but it would cause some kind of loop inside the GC _listener = new WeakReference<XmlElementListListener>(new XmlElementListListener(parent.Document, this)); } ~XmlElementList() { Dispose(); } internal void ConcurrencyCheck(XmlNodeChangedEventArgs args) { if (!_atomized) { XmlNameTable nameTable = _rootNode.Document.NameTable; _localName = nameTable.Add(_localName!); _namespaceURI = nameTable.Add(_namespaceURI!); _atomized = true; } if (IsMatch(args.Node!)) { _changeCount++; _curInd = -1; _curElem = _rootNode; if (args.Action == XmlNodeChangedAction.Insert) _empty = false; } _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("*"); _name = nt.Add(name); _localName = null; _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("*"); _localName = nt.Get(localName); _namespaceURI = nt.Get(namespaceURI); if ((_localName == null) || (_namespaceURI == null)) { _empty = true; _atomized = false; _localName = localName; _namespaceURI = namespaceURI; } _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 == _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 retNode ??= curNode.ParentNode; // if the final retNode is rootNode, consider having walked through the tree and no more previous node if (retNode == _rootNode) retNode = null; return retNode; } // if the current node a matching element node private bool IsMatch(XmlNode curNode) { if (curNode.NodeType == XmlNodeType.Element) { if (_name != null) { if (Ref.Equal(_name, _asterisk) || Ref.Equal(curNode.Name, _name)) return true; } else { if ( (Ref.Equal(_localName, _asterisk) || Ref.Equal(curNode.LocalName, _localName)) && (Ref.Equal(_namespaceURI, _asterisk) || curNode.NamespaceURI == _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 (_empty) return null; XmlNode node = n ?? _rootNode; return GetMatchingNode(node, true); } public override XmlNode? Item(int index) { if (_rootNode == null || index < 0) return null; if (_empty) 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 (_empty) return 0; if (_matchCount < 0) { int currMatchCount = 0; int currChangeCount = _changeCount; XmlNode? node = _rootNode; while ((node = GetMatchingNode(node, true)) != null) { currMatchCount++; } if (currChangeCount != _changeCount) { return currMatchCount; } _matchCount = currMatchCount; } return _matchCount; } } public override IEnumerator GetEnumerator() { if (_empty) return new XmlEmptyElementListEnumerator(); return new XmlElementListEnumerator(this); } protected override void PrivateDisposeNodeList() { GC.SuppressFinalize(this); Dispose(); } private void Dispose() { if (_listener != null) { if (_listener.TryGetTarget(out XmlElementListListener? listener)) { listener.Unregister(); } _listener = null; } } } internal sealed class XmlElementListEnumerator : IEnumerator { private readonly XmlElementList _list; private XmlNode? _curElem; private int _changeCount; //save the total number that the dom tree has been changed ( insertion and deletion ) when this enumerator is created public XmlElementListEnumerator(XmlElementList list) { _list = list; _curElem = null; _changeCount = list.ChangeCount; } public bool MoveNext() { if (_list.ChangeCount != _changeCount) { //the number mismatch, there is new change(s) happened since last MoveNext() is called. throw new InvalidOperationException(SR.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 _changeCount = _list.ChangeCount; } public object? Current { get { return _curElem; } } } internal sealed class XmlEmptyElementListEnumerator : IEnumerator { public XmlEmptyElementListEnumerator() { } public bool MoveNext() { return false; } public void Reset() { } public object? Current { get { return null; } } } internal sealed class XmlElementListListener { private WeakReference<XmlElementList>? _elemList; private readonly XmlDocument _doc; private readonly XmlNodeChangedEventHandler _nodeChangeHandler; internal XmlElementListListener(XmlDocument doc, XmlElementList elemList) { _doc = doc; _elemList = new WeakReference<XmlElementList>(elemList); _nodeChangeHandler = new XmlNodeChangedEventHandler(this.OnListChanged); doc.NodeInserted += _nodeChangeHandler; doc.NodeRemoved += _nodeChangeHandler; } private void OnListChanged(object sender, XmlNodeChangedEventArgs args) { lock (this) { if (_elemList != null) { if (_elemList.TryGetTarget(out XmlElementList? el)) { el.ConcurrencyCheck(args); } else { _doc.NodeInserted -= _nodeChangeHandler; _doc.NodeRemoved -= _nodeChangeHandler; _elemList = null; } } } } // This method is called from the finalizer of XmlElementList internal void Unregister() { lock (this) { if (_elemList != null) { _doc.NodeInserted -= _nodeChangeHandler; _doc.NodeRemoved -= _nodeChangeHandler; _elemList = null; } } } } }