// Description: Html Agility Pack - HTML Parsers, selectors, traversors, manupulators. // Website & Documentation: http://html-agility-pack.net // Forum & Issues: https://github.com/zzzprojects/html-agility-pack // License: https://github.com/zzzprojects/html-agility-pack/blob/master/LICENSE // More projects: http://www.zzzprojects.com/ // Copyright © ZZZ Projects Inc. 2014 - 2017. All rights reserved. using System; using System.Collections; using System.Collections.Generic; namespace HtmlAgilityPack { /// /// Represents a combined list and collection of HTML nodes. /// public class HtmlNodeCollection : IList { #region Fields private readonly HtmlNode _parentnode; private readonly List _items = new List(); #endregion #region Constructors /// /// Initialize the HtmlNodeCollection with the base parent node /// /// The base node of the collection public HtmlNodeCollection(HtmlNode parentnode) { _parentnode = parentnode; // may be null } #endregion #region Properties /// Gets the parent node associated to the collection. internal HtmlNode ParentNode { get { return _parentnode; } } /// /// Gets a given node from the list. /// public int this[HtmlNode node] { get { int index = GetNodeIndex(node); if (index == -1) throw new ArgumentOutOfRangeException("node", "Node \"" + node.CloneNode(false).OuterHtml + "\" was not found in the collection"); return index; } } /// /// Get node with tag name /// /// /// public HtmlNode this[string nodeName] { get { for (int i = 0; i < _items.Count; i++) if (string.Equals(_items[i].Name, nodeName, StringComparison.OrdinalIgnoreCase)) return _items[i]; return null; } } #endregion #region IList Members /// /// Gets the number of elements actually contained in the list. /// public int Count { get { return _items.Count; } } /// /// Is collection read only /// public bool IsReadOnly { get { return false; } } /// /// Gets the node at the specified index. /// public HtmlNode this[int index] { get { return _items[index]; } set { _items[index] = value; } } /// /// Add node to the collection /// /// public void Add(HtmlNode node) { Add(node, true); } /// /// Add node to the collection /// /// /// public void Add(HtmlNode node, bool setParent) { _items.Add(node); if (setParent) { node.ParentNode = _parentnode; } } /// /// Clears out the collection of HtmlNodes. Removes each nodes reference to parentnode, nextnode and prevnode /// public void Clear() { foreach (HtmlNode node in _items) { node.ParentNode = null; node.NextSibling = null; node.PreviousSibling = null; } _items.Clear(); } /// /// Gets existence of node in collection /// /// /// public bool Contains(HtmlNode item) { return _items.Contains(item); } /// /// Copy collection to array /// /// /// public void CopyTo(HtmlNode[] array, int arrayIndex) { _items.CopyTo(array, arrayIndex); } /// /// Get Enumerator /// /// IEnumerator IEnumerable.GetEnumerator() { return _items.GetEnumerator(); } /// /// Get Explicit Enumerator /// /// IEnumerator IEnumerable.GetEnumerator() { return _items.GetEnumerator(); } /// /// Get index of node /// /// /// public int IndexOf(HtmlNode item) { return _items.IndexOf(item); } /// /// Insert node at index /// /// /// public void Insert(int index, HtmlNode node) { HtmlNode next = null; HtmlNode prev = null; if (index > 0) prev = _items[index - 1]; if (index < _items.Count) next = _items[index]; _items.Insert(index, node); if (prev != null) { if (node == prev) throw new InvalidProgramException("Unexpected error."); prev._nextnode = node; } if (next != null) next._prevnode = node; node._prevnode = prev; if (next == node) throw new InvalidProgramException("Unexpected error."); node._nextnode = next; node.SetParent(_parentnode); } /// /// Remove node /// /// /// public bool Remove(HtmlNode item) { int i = _items.IndexOf(item); RemoveAt(i); return true; } /// /// Remove at index /// /// public void RemoveAt(int index) { HtmlNode next = null; HtmlNode prev = null; HtmlNode oldnode = _items[index]; // KEEP a reference since it will be set to null var parentNode = _parentnode ?? oldnode._parentnode; if (index > 0) prev = _items[index - 1]; if (index < (_items.Count - 1)) next = _items[index + 1]; _items.RemoveAt(index); if (prev != null) { if (next == prev) throw new InvalidProgramException("Unexpected error."); prev._nextnode = next; } if (next != null) next._prevnode = prev; oldnode._prevnode = null; oldnode._nextnode = null; oldnode._parentnode = null; if (parentNode != null) { parentNode.SetChanged(); } } #endregion #region Public Methods /// /// Get first instance of node in supplied collection /// /// /// /// public static HtmlNode FindFirst(HtmlNodeCollection items, string name) { foreach (HtmlNode node in items) { if (node.Name.Equals(name, StringComparison.OrdinalIgnoreCase)) return node; if (!node.HasChildNodes) continue; HtmlNode returnNode = FindFirst(node.ChildNodes, name); if (returnNode != null) return returnNode; } return null; } /// /// Add node to the end of the collection /// /// public void Append(HtmlNode node) { HtmlNode last = null; if (_items.Count > 0) last = _items[_items.Count - 1]; _items.Add(node); node._prevnode = last; node._nextnode = null; node.SetParent(_parentnode); if (last == null) return; if (last == node) throw new InvalidProgramException("Unexpected error."); last._nextnode = node; } /// /// Get first instance of node with name /// /// /// public HtmlNode FindFirst(string name) { return FindFirst(this, name); } /// /// Get index of node /// /// /// public int GetNodeIndex(HtmlNode node) { // TODO: should we rewrite this? what would be the key of a node? for (int i = 0; i < _items.Count; i++) if (node == _items[i]) return i; return -1; } /// /// Add node to the beginning of the collection /// /// public void Prepend(HtmlNode node) { HtmlNode first = null; if (_items.Count > 0) first = _items[0]; _items.Insert(0, node); if (node == first) throw new InvalidProgramException("Unexpected error."); node._nextnode = first; node._prevnode = null; node.SetParent(_parentnode); if (first != null) first._prevnode = node; } /// /// Remove node at index /// /// /// public bool Remove(int index) { RemoveAt(index); return true; } /// /// Replace node at index /// /// /// public void Replace(int index, HtmlNode node) { HtmlNode next = null; HtmlNode prev = null; HtmlNode oldnode = _items[index]; if (index > 0) prev = _items[index - 1]; if (index < (_items.Count - 1)) next = _items[index + 1]; _items[index] = node; if (prev != null) { if (node == prev) throw new InvalidProgramException("Unexpected error."); prev._nextnode = node; } if (next != null) next._prevnode = node; node._prevnode = prev; if (next == node) throw new InvalidProgramException("Unexpected error."); node._nextnode = next; node.SetParent(_parentnode); oldnode._prevnode = null; oldnode._nextnode = null; oldnode._parentnode = null; } #endregion #region LINQ Methods /// /// Get all node descended from this collection /// /// public IEnumerable Descendants() { foreach (HtmlNode item in _items) foreach (HtmlNode n in item.Descendants()) yield return n; } /// /// Get all node descended from this collection with matching name /// /// public IEnumerable Descendants(string name) { foreach (HtmlNode item in _items) foreach (HtmlNode n in item.Descendants(name)) yield return n; } /// /// Gets all first generation elements in collection /// /// public IEnumerable Elements() { foreach (HtmlNode item in _items) foreach (HtmlNode n in item.ChildNodes) yield return n; } /// /// Gets all first generation elements matching name /// /// /// public IEnumerable Elements(string name) { foreach (HtmlNode item in _items) foreach (HtmlNode n in item.Elements(name)) yield return n; } /// /// All first generation nodes in collection /// /// public IEnumerable Nodes() { foreach (HtmlNode item in _items) foreach (HtmlNode n in item.ChildNodes) yield return n; } #endregion } }