Skip to content

Commit 07cbdff

Browse files
committed
Fix various bugs on Range based on DOM Standard
DOM Standard: https://dom.spec.whatwg.org/#interface-range Also added unit tests
1 parent 649cb57 commit 07cbdff

File tree

3 files changed

+153
-38
lines changed

3 files changed

+153
-38
lines changed

src/AngleSharp.Core.Tests/Library/Range.cs

Lines changed: 133 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
namespace AngleSharp.Core.Tests.Library
22
{
33
using NUnit.Framework;
4-
using static System.Net.Mime.MediaTypeNames;
54

65
[TestFixture]
76
public class RangeTests
@@ -45,7 +44,7 @@ public void CanSelectSomeRange_Issue1119()
4544
var text2 = common.LastChild;
4645
var range = document.CreateRange();
4746
range.StartBefore(text1);
48-
range.EndAfter(text2);
47+
range.EndBefore(text2);
4948

5049
Assert.AreEqual(0, range.Start);
5150
Assert.AreEqual(text1.Parent, range.Head);
@@ -56,21 +55,143 @@ public void CanSelectSomeRange_Issue1119()
5655
}
5756

5857
[Test]
59-
public void CanSelectRangeSomeRange_Issue1147()
58+
public void CanSelectSomeRange_Issue1147()
6059
{
6160
var document = "<body></body>".ToHtmlDocument();
6261
var text1 = document.Body.AppendChild(document.CreateTextNode("Text1"));
6362
var text2 = document.Body.AppendChild(document.CreateTextNode("TextLonger2"));
63+
var range1 = document.CreateRange();
64+
var range2 = document.CreateRange();
65+
range1.StartWith(text1, "Text".Length);
66+
range1.EndWith(text2, "TextLonger".Length);
67+
range2.StartWith(text1, "Text".Length);
68+
range2.EndWith(text2, "TextLonger".Length);
69+
range2.StartWith(text2, "TextLonger2".Length);
70+
71+
Assert.AreEqual("Text".Length, range1.Start);
72+
Assert.AreEqual(text1, range1.Head);
73+
Assert.AreEqual("TextLonger".Length, range1.End);
74+
Assert.AreEqual(text2, range1.Tail);
75+
Assert.AreEqual(document.Body, range1.CommonAncestor);
76+
Assert.IsFalse(range1.IsCollapsed);
77+
Assert.AreEqual("TextLonger2".Length, range2.Start);
78+
Assert.AreEqual("TextLonger2".Length, range2.End);
79+
Assert.IsTrue(range2.IsCollapsed);
80+
}
81+
82+
[Test]
83+
public void CheckCommonAncestor()
84+
{
85+
var document = "<body></body>".ToHtmlDocument();
86+
var p1 = document.Body.AppendChild(document.CreateElement("p"));
87+
var p2 = document.Body.AppendChild(document.CreateElement("p"));
88+
var p11 = p1.AppendChild(document.CreateElement("p"));
89+
var p12 = p1.AppendChild(document.CreateElement("p"));
90+
var p21 = p2.AppendChild(document.CreateElement("p"));
91+
92+
var range1 = document.CreateRange();
93+
var range2 = document.CreateRange();
94+
95+
range1.StartAfter(p11);
96+
range1.EndBefore(p12);
97+
range2.StartAfter(p11);
98+
range2.EndBefore(p21);
99+
100+
Assert.AreEqual(p1, range1.CommonAncestor);
101+
Assert.AreEqual(document.Body, range2.CommonAncestor);
102+
}
103+
104+
[Test]
105+
public void CanIntersects()
106+
{
107+
var document = "<body></body>".ToHtmlDocument();
108+
var p1 = document.Body.AppendChild(document.CreateElement("p"));
109+
var p2 = document.Body.AppendChild(document.CreateElement("p"));
110+
var p3 = document.Body.AppendChild(document.CreateElement("p"));
111+
112+
var range1 = document.CreateRange();
113+
var range2 = document.CreateRange();
114+
115+
range1.StartAfter(p1);
116+
range1.EndBefore(p3);
117+
range2.StartWith(p1, 0);
118+
range2.EndWith(p3, 0);
119+
120+
Assert.IsFalse(range1.Intersects(p1));
121+
Assert.IsTrue(range1.Intersects(p2));
122+
Assert.IsFalse(range1.Intersects(p3));
123+
Assert.IsTrue(range1.Intersects(document.Body));
124+
125+
Assert.IsTrue(range2.Intersects(p1));
126+
Assert.IsTrue(range2.Intersects(p2));
127+
Assert.IsTrue(range2.Intersects(p3));
128+
Assert.IsTrue(range2.Intersects(document.Body));
129+
}
130+
131+
[Test]
132+
public void CanContains()
133+
{
134+
var document = "<body></body>".ToHtmlDocument();
135+
var p1 = document.Body.AppendChild(document.CreateElement("p"));
136+
var p2 = document.Body.AppendChild(document.CreateElement("p"));
137+
var p3 = document.Body.AppendChild(document.CreateElement("p"));
138+
139+
var range1 = document.CreateRange();
140+
var range2 = document.CreateRange();
141+
var range3 = document.CreateRange();
142+
143+
range1.StartAfter(p1);
144+
range1.EndBefore(p3);
145+
range2.StartWith(p1, 0);
146+
range2.EndWith(p3, 0);
147+
range3.Select(document.Body);
148+
149+
Assert.IsFalse(range1.Contains(p1, 0));
150+
Assert.IsTrue(range1.Contains(p2, 0));
151+
Assert.IsFalse(range1.Contains(p3, 0));
152+
Assert.IsFalse(range1.Contains(document.Body, 0));
153+
154+
Assert.IsFalse(range2.Contains(p1, 0));
155+
Assert.IsTrue(range2.Contains(p2, 0));
156+
Assert.IsFalse(range2.Contains(p3, 0));
157+
Assert.IsFalse(range2.Contains(document.Body, 0));
158+
159+
Assert.IsTrue(range3.Contains(p1, 0));
160+
Assert.IsTrue(range3.Contains(p2, 0));
161+
Assert.IsTrue(range3.Contains(p3, 0));
162+
Assert.IsTrue(range3.Contains(document.Body, 0));
163+
}
164+
165+
[Test]
166+
public void CanClearContent()
167+
{
168+
var document = @"
169+
<html>
170+
<head></head>
171+
<body>
172+
<p> No deletion before start <span id=""start""></span> This should be cleared </p>
173+
<p> <span id=""toDelete""></span>This should be deleted too <span id=""end""></span> This is not to be deleted</p>
174+
<p> This should not be deleted either</p>
175+
</body>
176+
</html>".ToHtmlDocument();
177+
var start = document.QuerySelector("#start");
178+
var end = document.QuerySelector("#end");
179+
var toDelete = document.QuerySelector("#toDelete");
180+
64181
var range = document.CreateRange();
65-
range.StartWith(text1, "Text".Length);
66-
range.EndWith(text2, "TextLonger".Length);
67-
68-
Assert.AreEqual("Text".Length, range.Start);
69-
Assert.AreEqual(text1, range.Head);
70-
Assert.AreEqual("TextLonger".Length, range.End);
71-
Assert.AreEqual(text2, range.Tail);
72-
Assert.AreEqual(document.Body, range.CommonAncestor);
73-
Assert.IsFalse(range.IsCollapsed);
182+
183+
range.StartWith(start, 0);
184+
range.EndWith(end, 0);
185+
186+
range.ClearContent();
187+
188+
var htmlRaw = document.DocumentElement.OuterHtml;
189+
Assert.IsTrue(htmlRaw.Contains("No deletion before start"));
190+
Assert.IsTrue(htmlRaw.Contains("This is not to be deleted"));
191+
Assert.IsTrue(htmlRaw.Contains("This should not be deleted either"));
192+
Assert.IsFalse(htmlRaw.Contains("This should be cleared"));
193+
Assert.IsFalse(htmlRaw.Contains("This should be deleted too"));
194+
Assert.IsFalse(document.Contains(toDelete));
74195
}
75196
}
76197
}

src/AngleSharp/Dom/IRange.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
namespace AngleSharp.Dom
1+
namespace AngleSharp.Dom
22
{
33
using AngleSharp.Attributes;
44
using System;
@@ -206,7 +206,7 @@ public interface IRange
206206
RangePosition CompareTo(INode node, Int32 offset);
207207

208208
/// <summary>
209-
/// Checks if the given node is contained in this range.
209+
/// Checks if the given node is intersected by this range.
210210
/// </summary>
211211
/// <param name="node">The node to check for.</param>
212212
/// <returns>

src/AngleSharp/Dom/Internal/Range.cs

Lines changed: 18 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ public INode CommonAncestor
5656
{
5757
var container = Head;
5858

59-
while (container != null && !container.Contains(Tail))
59+
while (container != null && !container.IsInclusiveAncestorOf(Tail))
6060
{
6161
container = container.Parent;
6262
}
@@ -95,15 +95,11 @@ public void StartWith(INode refNode, Int32 offset)
9595

9696
var bp = new Boundary(refNode, offset);
9797

98-
if (!_end.IsExplicit)
98+
if (!_end.IsExplicit || Root != refNode.GetRoot() || bp > _end)
9999
{
100-
_start = bp;
101100
_end = bp;
102101
}
103-
else if (bp <= _end && Root == refNode.GetRoot())
104-
{
105-
_start = bp;
106-
}
102+
_start = bp;
107103
}
108104

109105
public void EndWith(INode refNode, Int32 offset)
@@ -132,15 +128,11 @@ public void EndWith(INode refNode, Int32 offset)
132128

133129
var bp = new Boundary(refNode, offset);
134130

135-
if (!_start.IsExplicit)
131+
if (!_start.IsExplicit || Root != refNode.GetRoot() || bp < _start)
136132
{
137133
_start = bp;
138-
_end = bp;
139-
}
140-
else if (bp >= _end && Root == refNode.GetRoot())
141-
{
142-
_end = bp;
143134
}
135+
_end = bp;
144136
}
145137

146138
public void StartBefore(INode refNode)
@@ -201,7 +193,7 @@ public void StartAfter(INode refNode)
201193
throw new DomException(DomError.InvalidNodeType);
202194
}
203195

204-
_start = new Boundary(parent, parent.ChildNodes.Index(refNode));
196+
_start = new Boundary(parent, parent.ChildNodes.Index(refNode) + 1);
205197

206198
if (!_end.IsExplicit)
207199
{
@@ -223,7 +215,7 @@ public void EndAfter(INode refNode)
223215
throw new DomException(DomError.InvalidNodeType);
224216
}
225217

226-
_end = new Boundary(parent, parent.ChildNodes.Index(refNode));
218+
_end = new Boundary(parent, parent.ChildNodes.Index(refNode) + 1);
227219

228220
if (!_start.IsExplicit)
229221
{
@@ -296,18 +288,18 @@ public void ClearContent()
296288
}
297289
else
298290
{
299-
var nodesToRemove = Nodes.Where(m => !Intersects(m.Parent!)).ToArray();
291+
var nodesToRemove = CommonAncestor.GetNodes<INode>(predicate: (node)=>Contains(node,0)).Where(m => !Contains(m.Parent!, 0)).ToArray();
300292

301293
if (!originalStart.Node.IsInclusiveAncestorOf(originalEnd.Node))
302294
{
303295
var referenceNode = originalStart.Node;
304296

305-
while (referenceNode.Parent != null && referenceNode.Parent.IsInclusiveAncestorOf(originalEnd.Node))
297+
while (referenceNode.Parent != null && !referenceNode.Parent.IsInclusiveAncestorOf(originalEnd.Node))
306298
{
307299
referenceNode = referenceNode.Parent;
308300
}
309301

310-
newBoundary = new Boundary(referenceNode.Parent!, referenceNode.Parent!.ChildNodes.Index(referenceNode) + 1);
302+
newBoundary = new Boundary(referenceNode.Parent!, referenceNode.Index() + 1);
311303
}
312304
else
313305
{
@@ -318,7 +310,7 @@ public void ClearContent()
318310
{
319311
var strt = originalStart.Offset;
320312
var text = (ICharacterData)originalStart.Node;
321-
var span = originalEnd.Offset - originalStart.Offset;
313+
var span = text.Data.Length - originalStart.Offset;
322314
text.Replace(strt, span, String.Empty);
323315
}
324316

@@ -626,8 +618,8 @@ public Boolean Contains(INode node, Int32 offset)
626618
{
627619
throw new DomException(DomError.IndexSizeError);
628620
}
629-
630-
return !IsStartAfter(node, offset) && !IsEndBefore(node, offset);
621+
int length = node is IDocumentType || node is IAttr ? 0 : (node is ICharacterData charData ? charData.Data.Length : node.ChildNodes.Length);
622+
return new Boundary(node, offset) > _start && new Boundary(node, length) < _end;
631623
}
632624

633625
return false;
@@ -725,7 +717,7 @@ public Boolean Intersects(INode node)
725717
if (parent != null)
726718
{
727719
var offset = parent.ChildNodes.Index(node);
728-
return IsEndAfter(parent, offset) && IsStartBefore(parent, offset + 1);
720+
return new Boundary(parent, offset) < _end && new Boundary(parent, offset + 1) > _start;
729721
}
730722

731723
return true;
@@ -896,7 +888,8 @@ public RangePosition CompareTo(Boundary other)
896888
else if (Offset == other.Offset)
897889
{
898890
return RangePosition.Equal;
899-
} else if (Offset > other.Offset)
891+
}
892+
else if (Offset > other.Offset)
900893
{
901894
return RangePosition.After;
902895
}
@@ -907,7 +900,8 @@ public RangePosition CompareTo(Boundary other)
907900
if (position == RangePosition.Before)
908901
{
909902
return RangePosition.After;
910-
} else if (position== RangePosition.After)
903+
}
904+
else if (position == RangePosition.After)
911905
{
912906
return RangePosition.Before;
913907
}

0 commit comments

Comments
 (0)