// 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
}
}