Range
A DOM range to gather DOM tree information.
using AngleSharp.Extensions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace AngleSharp.Dom.Collections
{
internal sealed class Range : IRange
{
private struct Boundary : IEquatable<Boundary>
{
public INode Node;
public int Offset;
public INode ChildAtOffset {
get {
if (Node.ChildNodes.Length <= Offset)
return null;
return Node.ChildNodes[Offset];
}
}
public static bool operator >(Boundary a, Boundary b)
{
return false;
}
public static bool operator <(Boundary a, Boundary b)
{
return false;
}
public bool Equals(Boundary other)
{
if (Node == other.Node)
return Offset == other.Offset;
return false;
}
public RangePosition CompareTo(Boundary other)
{
if (this < other)
return RangePosition.Before;
if (this > other)
return RangePosition.After;
return RangePosition.Equal;
}
}
private Boundary _start;
private Boundary _end;
public INode Root => _start.Node.GetRoot();
public IEnumerable<INode> Nodes => CommonAncestor.GetElements<INode>(true, Intersects);
public INode Head => _start.Node;
public int Start => _start.Offset;
public INode Tail => _end.Node;
public int End => _end.Offset;
public bool IsCollapsed => _start.Node == _end.Node;
public INode CommonAncestor {
get {
INode node = Head;
while (node != null && !Tail.Contains(node)) {
node = node.Parent;
}
return node;
}
}
public Range(IDocument document)
{
Boundary boundary = new Boundary {
Offset = 0,
Node = document
};
_start = boundary;
boundary = new Boundary {
Offset = 0,
Node = document
};
_end = boundary;
}
private Range(Boundary start, Boundary end)
{
_start = start;
_end = end;
}
public void StartWith(INode refNode, int offset)
{
if (refNode == null)
throw new ArgumentNullException("refNode");
if (refNode.NodeType == NodeType.DocumentType)
throw new DomException(DomError.InvalidNodeType);
if (offset > refNode.ChildNodes.Length)
throw new DomException(DomError.IndexSizeError);
Boundary boundary = default(Boundary);
boundary.Node = refNode;
boundary.Offset = offset;
Boundary boundary2 = boundary;
if (boundary2 > _end || Root != refNode.GetRoot())
_start = boundary2;
}
public void EndWith(INode refNode, int offset)
{
if (refNode == null)
throw new ArgumentNullException("refNode");
if (refNode.NodeType == NodeType.DocumentType)
throw new DomException(DomError.InvalidNodeType);
if (offset > refNode.ChildNodes.Length)
throw new DomException(DomError.IndexSizeError);
Boundary boundary = default(Boundary);
boundary.Node = refNode;
boundary.Offset = offset;
Boundary boundary2 = boundary;
if (boundary2 < _start || Root != refNode.GetRoot())
_end = boundary2;
}
public void StartBefore(INode refNode)
{
if (refNode == null)
throw new ArgumentNullException("refNode");
INode parent = refNode.Parent;
if (parent == null)
throw new DomException(DomError.InvalidNodeType);
_start = new Boundary {
Node = parent,
Offset = parent.ChildNodes.Index(refNode)
};
}
public void EndBefore(INode refNode)
{
if (refNode == null)
throw new ArgumentNullException("refNode");
INode parent = refNode.Parent;
if (parent == null)
throw new DomException(DomError.InvalidNodeType);
_end = new Boundary {
Node = parent,
Offset = parent.ChildNodes.Index(refNode)
};
}
public void StartAfter(INode refNode)
{
if (refNode == null)
throw new ArgumentNullException("refNode");
INode parent = refNode.Parent;
if (parent == null)
throw new DomException(DomError.InvalidNodeType);
_start = new Boundary {
Node = parent,
Offset = parent.ChildNodes.Index(refNode) + 1
};
}
public void EndAfter(INode refNode)
{
if (refNode == null)
throw new ArgumentNullException("refNode");
INode parent = refNode.Parent;
if (parent == null)
throw new DomException(DomError.InvalidNodeType);
_end = new Boundary {
Node = parent,
Offset = parent.ChildNodes.Index(refNode) + 1
};
}
public void Collapse(bool toStart)
{
if (toStart)
_end = _start;
else
_start = _end;
}
public void Select(INode refNode)
{
if (refNode == null)
throw new ArgumentNullException("refNode");
INode parent = refNode.Parent;
if (parent == null)
throw new DomException(DomError.InvalidNodeType);
int num = parent.ChildNodes.Index(refNode);
Boundary boundary = _start = new Boundary {
Node = parent,
Offset = num
};
boundary = (_end = new Boundary {
Node = parent,
Offset = num + 1
});
}
public void SelectContent(INode refNode)
{
if (refNode == null)
throw new ArgumentNullException("refNode");
if (refNode.NodeType == NodeType.DocumentType)
throw new DomException(DomError.InvalidNodeType);
int length = refNode.ChildNodes.Length;
Boundary boundary = _start = new Boundary {
Node = refNode,
Offset = 0
};
boundary = (_end = new Boundary {
Node = refNode,
Offset = length
});
}
public void ClearContent()
{
if (!_start.Equals(_end)) {
Boundary boundary = default(Boundary);
Boundary start = _start;
Boundary end = _end;
if (end.Node == start.Node && start.Node is ICharacterData) {
int offset = start.Offset;
ICharacterData obj = (ICharacterData)start.Node;
int count = end.Offset - start.Offset;
obj.Replace(offset, count, string.Empty);
} else {
INode[] array = (from m in Nodes
where !Intersects(m.Parent)
select m).ToArray();
if (!start.Node.IsInclusiveAncestorOf(end.Node)) {
INode node = start.Node;
while (node.Parent != null && node.Parent.IsInclusiveAncestorOf(end.Node)) {
node = node.Parent;
}
Boundary boundary2 = default(Boundary);
boundary2.Node = node.Parent;
boundary2.Offset = node.Parent.ChildNodes.Index(node) + 1;
boundary = boundary2;
} else
boundary = start;
if (start.Node is ICharacterData) {
int offset2 = start.Offset;
ICharacterData obj2 = (ICharacterData)start.Node;
int count2 = end.Offset - start.Offset;
obj2.Replace(offset2, count2, string.Empty);
}
INode[] array2 = array;
foreach (INode node2 in array2) {
node2.Parent.RemoveChild(node2);
}
if (end.Node is ICharacterData) {
int offset3 = 0;
ICharacterData obj3 = (ICharacterData)end.Node;
int offset4 = end.Offset;
obj3.Replace(offset3, offset4, string.Empty);
}
_start = boundary;
_end = boundary;
}
}
}
public IDocumentFragment ExtractContent()
{
IDocumentFragment documentFragment = _start.Node.Owner.CreateDocumentFragment();
if (_start.Equals(_end))
return documentFragment;
Boundary boundary = _start;
Boundary start = _start;
Boundary end = _end;
if (start.Node == end.Node && _start.Node is ICharacterData) {
ICharacterData characterData = (ICharacterData)start.Node;
int offset = start.Offset;
int count = end.Offset - start.Offset;
ICharacterData characterData2 = (ICharacterData)characterData.Clone(true);
characterData2.Data = characterData.Substring(offset, count);
documentFragment.AppendChild(characterData2);
characterData.Replace(offset, count, string.Empty);
return documentFragment;
}
INode node = start.Node;
while (!node.IsInclusiveAncestorOf(end.Node)) {
node = node.Parent;
}
INode node2 = (!start.Node.IsInclusiveAncestorOf(end.Node)) ? node.GetElements<INode>(true, IsPartiallyContained).FirstOrDefault() : null;
INode node3 = (!end.Node.IsInclusiveAncestorOf(start.Node)) ? node.GetElements<INode>(true, IsPartiallyContained).LastOrDefault() : null;
List<INode> list = node.GetElements<INode>(true, Intersects).ToList();
if (list.OfType<IDocumentType>().Any())
throw new DomException(DomError.HierarchyRequest);
Boundary boundary2;
if (!start.Node.IsInclusiveAncestorOf(end.Node)) {
INode node4 = start.Node;
while (node4.Parent != null && !node4.IsInclusiveAncestorOf(end.Node)) {
node4 = node4.Parent;
}
boundary2 = default(Boundary);
boundary2.Node = node4;
boundary2.Offset = node4.Parent.ChildNodes.Index(node4) + 1;
boundary = boundary2;
}
if (node2 is ICharacterData) {
ICharacterData characterData3 = (ICharacterData)start.Node;
int offset2 = start.Offset;
int count2 = characterData3.Length - start.Offset;
ICharacterData characterData4 = (ICharacterData)characterData3.Clone(true);
characterData4.Data = characterData3.Substring(offset2, count2);
documentFragment.AppendChild(characterData4);
characterData3.Replace(offset2, count2, string.Empty);
} else if (node2 != null) {
INode child = node2.Clone(true);
documentFragment.AppendChild(child);
Boundary start2 = start;
boundary2 = new Boundary {
Node = node2,
Offset = node2.ChildNodes.Length
};
IDocumentFragment child2 = new Range(start2, boundary2).ExtractContent();
documentFragment.AppendChild(child2);
}
foreach (INode item in list) {
documentFragment.AppendChild(item);
}
if (node3 is ICharacterData) {
ICharacterData characterData5 = (ICharacterData)end.Node;
ICharacterData characterData6 = (ICharacterData)characterData5.Clone(true);
characterData6.Data = characterData5.Substring(0, end.Offset);
documentFragment.AppendChild(characterData6);
characterData5.Replace(0, end.Offset, string.Empty);
} else if (node3 != null) {
INode child3 = node3.Clone(true);
documentFragment.AppendChild(child3);
boundary2 = default(Boundary);
boundary2.Node = node3;
boundary2.Offset = 0;
IDocumentFragment child4 = new Range(boundary2, end).ExtractContent();
documentFragment.AppendChild(child4);
}
_start = boundary;
_end = boundary;
return documentFragment;
}
public IDocumentFragment CopyContent()
{
IDocumentFragment documentFragment = _start.Node.Owner.CreateDocumentFragment();
if (_start.Equals(_end))
return documentFragment;
Boundary start = _start;
Boundary end = _end;
if (start.Node == end.Node && _start.Node is ICharacterData) {
ICharacterData characterData = (ICharacterData)start.Node;
int offset = start.Offset;
int count = end.Offset - start.Offset;
ICharacterData characterData2 = (ICharacterData)characterData.Clone(true);
characterData2.Data = characterData.Substring(offset, count);
documentFragment.AppendChild(characterData2);
return documentFragment;
}
INode node = start.Node;
while (!node.IsInclusiveAncestorOf(end.Node)) {
node = node.Parent;
}
INode node2 = (!start.Node.IsInclusiveAncestorOf(end.Node)) ? node.GetElements<INode>(true, IsPartiallyContained).FirstOrDefault() : null;
INode node3 = (!end.Node.IsInclusiveAncestorOf(start.Node)) ? node.GetElements<INode>(true, IsPartiallyContained).LastOrDefault() : null;
List<INode> list = node.GetElements<INode>(true, Intersects).ToList();
if (list.OfType<IDocumentType>().Any())
throw new DomException(DomError.HierarchyRequest);
Boundary boundary;
if (node2 is ICharacterData) {
ICharacterData characterData3 = (ICharacterData)start.Node;
int offset2 = start.Offset;
int count2 = characterData3.Length - start.Offset;
ICharacterData characterData4 = (ICharacterData)characterData3.Clone(true);
characterData4.Data = characterData3.Substring(offset2, count2);
documentFragment.AppendChild(characterData4);
} else if (node2 != null) {
INode child = node2.Clone(true);
documentFragment.AppendChild(child);
Boundary start2 = start;
boundary = new Boundary {
Node = node2,
Offset = node2.ChildNodes.Length
};
IDocumentFragment child2 = new Range(start2, boundary).CopyContent();
documentFragment.AppendChild(child2);
}
foreach (INode item in list) {
documentFragment.AppendChild(item.Clone(true));
}
if (node3 is ICharacterData) {
ICharacterData characterData5 = (ICharacterData)end.Node;
ICharacterData characterData6 = (ICharacterData)characterData5.Clone(true);
characterData6.Data = characterData5.Substring(0, end.Offset);
documentFragment.AppendChild(characterData6);
} else if (node3 != null) {
INode child3 = node3.Clone(true);
documentFragment.AppendChild(child3);
boundary = default(Boundary);
boundary.Node = node3;
boundary.Offset = 0;
IDocumentFragment child4 = new Range(boundary, end).CopyContent();
documentFragment.AppendChild(child4);
}
return documentFragment;
}
public void Insert(INode node)
{
if (node == null)
throw new ArgumentNullException("node");
INode node2 = _start.Node;
NodeType nodeType = node2.NodeType;
bool flag = nodeType == NodeType.Text;
if (nodeType == NodeType.ProcessingInstruction || nodeType == NodeType.Comment || (flag && node2.Parent == null))
throw new DomException(DomError.HierarchyRequest);
INode node3 = flag ? node2 : _start.ChildAtOffset;
INode node4 = (node3 == null) ? node2 : node3.Parent;
node4.EnsurePreInsertionValidity(node, node3);
if (flag) {
node3 = ((IText)node2).Split(_start.Offset);
node4 = node3.Parent;
}
if (node == node3)
node3 = node3.NextSibling;
node.Parent?.RemoveChild(node);
int num = (node3 == null) ? node4.ChildNodes.Length : node4.ChildNodes.Index(node3);
num += ((node.NodeType != NodeType.DocumentFragment) ? 1 : node.ChildNodes.Length);
node4.PreInsert(node, node3);
if (_start.Equals(_end))
_end = new Boundary {
Node = node4,
Offset = num
};
}
public void Surround(INode newParent)
{
if (newParent == null)
throw new ArgumentNullException("newParent");
if (Nodes.Any(delegate(INode m) {
if (m.NodeType != NodeType.Text)
return IsPartiallyContained(m);
return false;
}))
throw new DomException(DomError.InvalidState);
NodeType nodeType = newParent.NodeType;
if (nodeType == NodeType.Document || nodeType == NodeType.DocumentType || nodeType == NodeType.DocumentFragment)
throw new DomException(DomError.InvalidNodeType);
IDocumentFragment node = ExtractContent();
while (newParent.HasChildNodes) {
newParent.RemoveChild(newParent.FirstChild);
}
Insert(newParent);
newParent.PreInsert(node, null);
Select(newParent);
}
public IRange Clone()
{
return new Range(_start, _end);
}
public void Detach()
{
}
public bool Contains(INode node, int offset)
{
if (node == null)
throw new ArgumentNullException("node");
if (node.GetRoot() == Root) {
if (node.NodeType == NodeType.DocumentType)
throw new DomException(DomError.InvalidNodeType);
if (offset > node.ChildNodes.Length)
throw new DomException(DomError.IndexSizeError);
if (!IsStartAfter(node, offset))
return !IsEndBefore(node, offset);
return false;
}
return false;
}
public RangePosition CompareBoundaryTo(RangeType how, IRange sourceRange)
{
if (sourceRange == null)
throw new ArgumentNullException("sourceRange");
if (Root != sourceRange.Head.GetRoot())
throw new DomException(DomError.WrongDocument);
Boundary boundary = default(Boundary);
Boundary boundary2 = default(Boundary);
Boundary boundary3;
switch (how) {
case RangeType.StartToStart:
boundary = _start;
boundary3 = default(Boundary);
boundary3.Node = sourceRange.Head;
boundary3.Offset = sourceRange.Start;
boundary2 = boundary3;
break;
case RangeType.StartToEnd:
boundary = _end;
boundary3 = default(Boundary);
boundary3.Node = sourceRange.Head;
boundary3.Offset = sourceRange.Start;
boundary2 = boundary3;
break;
case RangeType.EndToEnd:
boundary = _start;
boundary3 = default(Boundary);
boundary3.Node = sourceRange.Tail;
boundary3.Offset = sourceRange.End;
boundary2 = boundary3;
break;
case RangeType.EndToStart:
boundary = _end;
boundary3 = default(Boundary);
boundary3.Node = sourceRange.Tail;
boundary3.Offset = sourceRange.End;
boundary2 = boundary3;
break;
default:
throw new DomException(DomError.NotSupported);
}
return boundary.CompareTo(boundary2);
}
public RangePosition CompareTo(INode node, int offset)
{
if (node == null)
throw new ArgumentNullException("node");
if (Root != _start.Node.GetRoot())
throw new DomException(DomError.WrongDocument);
if (node.NodeType == NodeType.DocumentType)
throw new DomException(DomError.InvalidNodeType);
if (offset > node.ChildNodes.Length)
throw new DomException(DomError.IndexSizeError);
if (IsStartAfter(node, offset))
return RangePosition.Before;
if (IsEndBefore(node, offset))
return RangePosition.After;
return RangePosition.Equal;
}
public bool Intersects(INode node)
{
if (node == null)
throw new ArgumentNullException("node");
if (Root == node.GetRoot()) {
INode parent = node.Parent;
if (parent != null) {
int num = parent.ChildNodes.Index(node);
if (IsEndAfter(parent, num))
return IsStartBefore(parent, num + 1);
return false;
}
return true;
}
return false;
}
public override string ToString()
{
StringBuilder stringBuilder = Pool.NewStringBuilder();
int start = Start;
int end = End;
IText text = Head as IText;
IText text2 = Tail as IText;
if (text != null && Head == Tail)
return text.Substring(start, end - start);
if (text != null)
stringBuilder.Append(text.Substring(start, text.Length - start));
foreach (IText item in CommonAncestor.Descendents<IText>()) {
if (IsStartBefore(item, 0) && IsEndAfter(item, item.Length))
stringBuilder.Append(item.Text);
}
if (text2 != null)
stringBuilder.Append(text2.Substring(0, end));
return stringBuilder.ToPool();
}
private bool IsStartBefore(INode node, int offset)
{
return _start < new Boundary {
Node = node,
Offset = offset
};
}
private bool IsStartAfter(INode node, int offset)
{
return _start > new Boundary {
Node = node,
Offset = offset
};
}
private bool IsEndBefore(INode node, int offset)
{
return _end < new Boundary {
Node = node,
Offset = offset
};
}
private bool IsEndAfter(INode node, int offset)
{
return _end > new Boundary {
Node = node,
Offset = offset
};
}
private bool IsPartiallyContained(INode node)
{
bool flag = node.IsInclusiveAncestorOf(_start.Node);
bool flag2 = node.IsInclusiveAncestorOf(_end.Node);
if (!flag || flag2)
return !flag & flag2;
return true;
}
}
}