Skip to content

Commit 57519a0

Browse files
committed
ADD: diff:ignoreChildren comparer
1 parent 9bcc66c commit 57519a0

File tree

7 files changed

+156
-8
lines changed

7 files changed

+156
-8
lines changed

src/AngleSharp.Diffing.Tests/Core/HtmlDifferenceEngineTest.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,23 @@ public void Test2()
357357
results.ShouldBeEmpty();
358358
}
359359

360+
[Fact(DisplayName = "When comparer returns SkipChildren from an element comparison, the attributes are compared but child nodes not compared")]
361+
public void Test3()
362+
{
363+
var sut = CreateHtmlDiffer(
364+
nodeMatcher: OneToOneNodeListMatcher,
365+
nodeFilter: NoneNodeFilter,
366+
nodeComparer: c => c.Control.Node.NodeName == "P" ? CompareResult.Same | CompareResult.SkipChildren : throw new Exception("NODE COMPARER SHOULD NOT BE CALLED ON CHILD NODES"),
367+
attrMatcher: AttributeNameMatcher,
368+
attrFilter: NoneAttrFilter,
369+
attrComparer: SameResultAttrComparer
370+
);
371+
372+
var results = sut.Compare(ToNodeList(@"<p id=""bar""><em>foo</em></p>"), ToNodeList(@"<p id=""bar""><span>baz</span></p>"));
373+
374+
results.ShouldBeEmpty();
375+
}
376+
360377
#region NodeFilters
361378
private static FilterDecision NoneNodeFilter(ComparisonSource source) => FilterDecision.Keep;
362379
private static FilterDecision RemoveCommentNodeFilter(ComparisonSource source) => source.Node.NodeType == NodeType.Comment ? FilterDecision.Exclude : FilterDecision.Keep;
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
using System.Linq;
2+
3+
using AngleSharp.Diffing.Core;
4+
5+
using Shouldly;
6+
7+
using Xunit;
8+
9+
namespace AngleSharp.Diffing.Strategies.ElementStrategies
10+
{
11+
public class IgnoreChildrenElementComparerTest : DiffingTestBase
12+
{
13+
public IgnoreChildrenElementComparerTest(DiffingTestFixture fixture) : base(fixture)
14+
{
15+
}
16+
17+
[Theory(DisplayName = "When a control element does not contain the 'diff:ignoreChildren' attribute or it is 'diff:ignoreChildren=false', the current decision is returned")]
18+
[InlineData(@"<p></p>")]
19+
[InlineData(@"<p diff:ignoreChildren=""false""></p>")]
20+
[InlineData(@"<p diff:ignoreChildren=""FALSE""></p>")]
21+
[InlineData(@"<p diff:ignoreChildren=""faLsE""></p>")]
22+
public void Test001(string controlHtml)
23+
{
24+
var comparison = ToComparison(controlHtml, "<p><em></em></p>");
25+
26+
IgnoreChildrenElementComparer.Compare(comparison, CompareResult.Different).ShouldBe(CompareResult.Different);
27+
IgnoreChildrenElementComparer.Compare(comparison, CompareResult.Same).ShouldBe(CompareResult.Same);
28+
IgnoreChildrenElementComparer.Compare(comparison, CompareResult.Skip).ShouldBe(CompareResult.Skip);
29+
}
30+
31+
[Theory(DisplayName = "When a control element has 'diff:ignoreChildren' attribute, SameAndBreak is returned")]
32+
[InlineData(@"<p diff:ignoreChildren></p>")]
33+
[InlineData(@"<p diff:ignoreChildren=""true""></p>")]
34+
[InlineData(@"<p diff:ignoreChildren=""TRUE""></p>")]
35+
[InlineData(@"<p diff:ignoreChildren=""TrUe""></p>")]
36+
public void Test002(string controlHtml)
37+
{
38+
var comparison = ToComparison(controlHtml, "<p><em></em></p>");
39+
40+
IgnoreChildrenElementComparer.Compare(comparison, CompareResult.Same).ShouldBe(CompareResult.Same | CompareResult.SkipChildren);
41+
IgnoreChildrenElementComparer.Compare(comparison, CompareResult.Different).ShouldBe(CompareResult.Different | CompareResult.SkipChildren);
42+
}
43+
44+
[Fact(DisplayName = "When a control element has 'diff:ignoreChildren', calling Build() with DefaultOptions() returns expected diffs")]
45+
public void Test003()
46+
{
47+
var control = @"<p attr=""foo"" missing diff:ignoreChildren>hello <em>world</em></p>";
48+
var test = @"<p attr=""bar"" unexpected>world says <strong>hello</strong></p>";
49+
50+
var diffs = DiffBuilder
51+
.Compare(control)
52+
.WithTest(test)
53+
.Build()
54+
.ToList();
55+
56+
diffs.Count.ShouldBe(3);
57+
diffs.SingleOrDefault(x => x is AttrDiff).ShouldNotBeNull();
58+
diffs.SingleOrDefault(x => x is MissingAttrDiff).ShouldNotBeNull();
59+
diffs.SingleOrDefault(x => x is UnexpectedAttrDiff).ShouldNotBeNull();
60+
}
61+
62+
[Theory(DisplayName = "When a control element has 'diff:ignoreChildren', calling Build() with DefaultOptions() returns empty diffs")]
63+
[InlineData(@"<p attr=""foo"" diff:ignoreChildren>hello <em>world</em></p>",
64+
@"<p attr=""foo"">world says <strong>hello</strong></p>")]
65+
[InlineData(@"<p attr=""foo"" expected diff:ignoreChildren>hello <em>world</em></p>",
66+
@"<p attr=""foo"" expected>world says <strong>hello</strong></p>")]
67+
public void Test004(string control, string test)
68+
{
69+
var diffs = DiffBuilder
70+
.Compare(control)
71+
.WithTest(test)
72+
.Build()
73+
.ToList();
74+
75+
diffs.ShouldBeEmpty();
76+
}
77+
}
78+
}

src/AngleSharp.Diffing/Core/CompareResult.cs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,29 @@
1-
namespace AngleSharp.Diffing.Core
1+
using System;
2+
3+
namespace AngleSharp.Diffing.Core
24
{
35
/// <summary>
46
/// Represents a result of a comparison.
57
/// </summary>
8+
[Flags]
69
public enum CompareResult
710
{
811
/// <summary>
912
/// Use when the two compared nodes or attributes are the same.
1013
/// </summary>
11-
Same,
14+
Same = 1,
1215
/// <summary>
1316
/// Use when the two compared nodes or attributes are the different.
1417
/// </summary>
15-
Different,
18+
Different = 2,
1619
/// <summary>
1720
/// Use when the comparison should be skipped and any child-nodes or attributes skipped as well.
1821
/// </summary>
19-
Skip
22+
Skip = 4,
23+
/// <summary>
24+
/// Use when the comparison should skip any child-nodes.
25+
/// </summary>
26+
SkipChildren = 8,
2027
}
2128

2229
/// <summary>

src/AngleSharp.Diffing/Core/HtmlDifferenceEngine.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ private IEnumerable<IDiff> CompareNode(in Comparison comparison)
9292
}
9393

9494
var compareRes = _diffingStrategy.Compare(comparison);
95-
if (compareRes == CompareResult.Different)
95+
if (compareRes.HasFlag(CompareResult.Different))
9696
{
9797
IDiff diff = new NodeDiff(comparison);
9898
return new[] { diff };
@@ -106,15 +106,16 @@ private IEnumerable<IDiff> CompareElement(in Comparison comparison)
106106
var result = new List<IDiff>();
107107

108108
var compareRes = _diffingStrategy.Compare(comparison);
109-
if (compareRes == CompareResult.Different)
109+
if (compareRes.HasFlag(CompareResult.Different))
110110
{
111111
result.Add(new NodeDiff(comparison));
112112
}
113113

114114
if (compareRes != CompareResult.Skip)
115115
{
116116
result.AddRange(CompareElementAttributes(comparison));
117-
result.AddRange(CompareChildNodes(comparison));
117+
if (!compareRes.HasFlag(CompareResult.SkipChildren))
118+
result.AddRange(CompareChildNodes(comparison));
118119
}
119120

120121
return result;

src/AngleSharp.Diffing/Strategies/DiffingStrategyPipelineBuilderExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,13 @@ public static IDiffingStrategyCollection AddDefaultOptions(this IDiffingStrategy
1919
.AddAttributeNameMatcher()
2020
.AddElementComparer()
2121
.AddIgnoreElementSupport()
22+
.AddIgnoreChildrenElementSupport()
2223
.AddStyleSheetComparer()
2324
.AddTextComparer(WhitespaceOption.Normalize, ignoreCase: false)
2425
.AddAttributeComparer()
2526
.AddClassAttributeComparer()
2627
.AddBooleanAttributeComparer(BooleanAttributeComparision.Strict)
2728
.AddStyleAttributeComparer();
28-
;
2929
}
3030
}
3131
}

src/AngleSharp.Diffing/Strategies/ElementStrategies/DiffingStrategyPipelineBuilderExtensions.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,16 @@ public static IDiffingStrategyCollection AddIgnoreElementSupport(this IDiffingSt
3333
builder.AddComparer(IgnoreElementComparer.Compare, StrategyType.Specialized);
3434
return builder;
3535
}
36+
37+
/// <summary>
38+
/// Enables the ignore children element `diff:ignorechildren` attribute during diffing.
39+
/// </summary>
40+
/// <param name="builder"></param>
41+
/// <returns></returns>
42+
public static IDiffingStrategyCollection AddIgnoreChildrenElementSupport(this IDiffingStrategyCollection builder)
43+
{
44+
builder.AddComparer(IgnoreChildrenElementComparer.Compare, StrategyType.Specialized);
45+
return builder;
46+
}
3647
}
3748
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
using AngleSharp.Diffing.Core;
2+
using AngleSharp.Diffing.Extensions;
3+
using AngleSharp.Dom;
4+
5+
namespace AngleSharp.Diffing.Strategies.ElementStrategies
6+
{
7+
/// <summary>
8+
/// Represents the ignore children element comparer.
9+
/// </summary>
10+
public static class IgnoreChildrenElementComparer
11+
{
12+
private const string DIFF_IGNORE_CHILDREN_ATTRIBUTE = "diff:ignorechildren";
13+
14+
/// <summary>
15+
/// The ignore children element comparer.
16+
/// </summary>
17+
public static CompareResult Compare(in Comparison comparison, CompareResult currentDecision)
18+
{
19+
if (currentDecision == CompareResult.Skip)
20+
return currentDecision;
21+
22+
return ControlHasTruthyIgnoreAttribute(comparison)
23+
? currentDecision | CompareResult.SkipChildren
24+
: currentDecision;
25+
}
26+
27+
private static bool ControlHasTruthyIgnoreAttribute(in Comparison comparison)
28+
{
29+
return comparison.Control.Node is IElement element &&
30+
element.TryGetAttrValue(DIFF_IGNORE_CHILDREN_ATTRIBUTE, out bool shouldIgnore) &&
31+
shouldIgnore;
32+
}
33+
}
34+
}

0 commit comments

Comments
 (0)