Skip to content

Commit 8c4949e

Browse files
committed
DiffPipelineStrategy
1 parent f856f81 commit 8c4949e

File tree

7 files changed

+205
-13
lines changed

7 files changed

+205
-13
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ var diffs = DiffBuilder
1515

1616
#### Built-in filters:
1717
- **`RemoveCommentNodeFilter`**: remove all comment nodes.
18-
- whitespace only text nodes
18+
- ** whitespace only text nodes
1919

2020
Matchers:
2121
- searching matcher, that will match nodes of the same type, and, optionally, element with the same element name.

src/Core/HtmlDifferenceEngine.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,12 @@ private IEnumerable<IDiff> CompareNodeLists(DiffContext context, SourceCollectio
5858

5959
private void ApplyNodeFilter(SourceCollection sources)
6060
{
61-
sources.Remove(_filterStrategy.NodeFilter);
61+
sources.Remove(_filterStrategy.Filter);
6262
}
6363

6464
private IEnumerable<Comparison> MatchNodes(DiffContext context, SourceCollection controls, SourceCollection tests)
6565
{
66-
foreach (var comparison in _matcherStrategy.MatchNodes(context, controls, tests))
66+
foreach (var comparison in _matcherStrategy.Match(context, controls, tests))
6767
{
6868
MarkSelectedSourcesAsMatched(comparison);
6969
yield return comparison;
@@ -146,12 +146,12 @@ private IEnumerable<IDiff> CompareElementAttributes(DiffContext context, in Comp
146146

147147
private void ApplyFilterAttributes(SourceMap controlAttrs)
148148
{
149-
controlAttrs.Remove(_filterStrategy.AttributeFilter);
149+
controlAttrs.Remove(_filterStrategy.Filter);
150150
}
151151

152152
private IEnumerable<AttributeComparison> MatchAttributes(DiffContext context, SourceMap controls, SourceMap tests)
153153
{
154-
foreach (var comparison in _matcherStrategy.MatchAttributes(context, controls, tests))
154+
foreach (var comparison in _matcherStrategy.Match(context, controls, tests))
155155
{
156156
MarkSelectedSourcesAsMatched(comparison);
157157
yield return comparison;

src/Core/IFilterStrategy.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,13 @@ public interface IFilterStrategy
1111
/// </summary>
1212
/// <param name="comparisonSource">A comparison source for the node</param>
1313
/// <returns>true if the node should be part of the comparison, false if the node should be filtered out.</returns>
14-
bool NodeFilter(in ComparisonSource comparisonSource);
14+
bool Filter(in ComparisonSource comparisonSource);
1515

1616
/// <summary>
1717
/// Decides whether an attribute should be part of the comparison.
1818
/// </summary>
1919
/// <param name="comparisonSource">A comparison source for the attribute</param>
2020
/// <returns>true if the attribute should be part of the comparison, false if the attribute should be filtered out.</returns>
21-
bool AttributeFilter(in AttributeComparisonSource attributeComparisonSource);
21+
bool Filter(in AttributeComparisonSource attributeComparisonSource);
2222
}
2323
}

src/Core/IMatcherStrategy.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@ public interface IMatcherStrategy
1414
/// depending on whether they are control or test nodes.
1515
/// </summary>
1616
/// <returns></returns>
17-
IEnumerable<Comparison> MatchNodes(DiffContext context, SourceCollection controlSources, SourceCollection testSources);
17+
IEnumerable<Comparison> Match(DiffContext context, SourceCollection controlSources, SourceCollection testSources);
1818

1919
/// <summary>
2020
/// Matches up the control attributes with test attributes in the two input lists. The matched control and test attributes will be compared to each other.
2121
/// Any unmatched control or test attributes will be reported as either <see cref="DiffResult.Missing"/> or <see cref="DiffResult.Unexpected"/>,
2222
/// depending on whether they are control or test attributes.
2323
/// </summary>
2424
/// <returns></returns>
25-
IEnumerable<AttributeComparison> MatchAttributes(DiffContext context, SourceMap controlAttrSources, SourceMap testAttrSources);
25+
IEnumerable<AttributeComparison> Match(DiffContext context, SourceMap controlAttrSources, SourceMap testAttrSources);
2626
}
2727
}

src/DiffingStrategyPipeline.cs

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
using Egil.AngleSharp.Diffing.Core;
7+
8+
namespace Egil.AngleSharp.Diffing
9+
{
10+
public delegate bool FilterStrategy<TSource>(in TSource source, bool currentDecision);
11+
public delegate IEnumerable<TComparison> MatchStrategy<in TSources, out TComparison>(DiffContext context, TSources controlSources, TSources testSources);
12+
public delegate CompareResult CompareStrategy<TComparison>(in TComparison comparison, CompareResult currentDecision);
13+
14+
public class DiffingStrategyPipeline : IFilterStrategy, IMatcherStrategy, ICompareStrategy
15+
{
16+
private readonly List<FilterStrategy<ComparisonSource>> _nodeFilters = new List<FilterStrategy<ComparisonSource>>();
17+
private readonly List<FilterStrategy<AttributeComparisonSource>> _attrsFilters = new List<FilterStrategy<AttributeComparisonSource>>();
18+
private readonly List<MatchStrategy<SourceCollection, Comparison>> _nodeMatchers = new List<MatchStrategy<SourceCollection, Comparison>>();
19+
private readonly List<MatchStrategy<SourceMap, AttributeComparison>> _attrsMatchers = new List<MatchStrategy<SourceMap, AttributeComparison>>();
20+
private readonly List<CompareStrategy<Comparison>> _nodeComparers = new List<CompareStrategy<Comparison>>();
21+
private readonly List<CompareStrategy<AttributeComparison>> _attrComparers = new List<CompareStrategy<AttributeComparison>>();
22+
23+
public bool Filter(in ComparisonSource comparisonSource) => Filter(comparisonSource, _nodeFilters);
24+
public bool Filter(in AttributeComparisonSource attributeComparisonSource) => Filter(attributeComparisonSource, _attrsFilters);
25+
26+
public IEnumerable<Comparison> Match(DiffContext context, SourceCollection controlSources, SourceCollection testSources)
27+
=> Match(context, controlSources, testSources, _nodeMatchers);
28+
29+
public IEnumerable<AttributeComparison> Match(DiffContext context, SourceMap controlAttrSources, SourceMap testAttrSources)
30+
=> Match(context, controlAttrSources, testAttrSources, _attrsMatchers);
31+
32+
public CompareResult Compare(in Comparison comparison)
33+
=> Compare(comparison, _nodeComparers, CompareResult.DifferentAndBreak);
34+
35+
public CompareResult Compare(in AttributeComparison comparison)
36+
=> Compare(comparison, _attrComparers, CompareResult.Different);
37+
38+
public void AddFilter(FilterStrategy<ComparisonSource> filterStrategy) => _nodeFilters.Add(filterStrategy);
39+
public void AddFilter(FilterStrategy<AttributeComparisonSource> filterStrategy) => _attrsFilters.Add(filterStrategy);
40+
41+
public void AddMatcher(MatchStrategy<SourceCollection, Comparison> matchStrategy) => _nodeMatchers.Add(matchStrategy);
42+
public void AddMatcher(MatchStrategy<SourceMap, AttributeComparison> matchStrategy) => _attrsMatchers.Add(matchStrategy);
43+
44+
public void AddComparer(CompareStrategy<Comparison> compareStrategy) => _nodeComparers.Add(compareStrategy);
45+
public void AddComparer(CompareStrategy<AttributeComparison> compareStrategy) => _attrComparers.Add(compareStrategy);
46+
47+
private bool Filter<T>(in T source, List<FilterStrategy<T>> filterStrategies)
48+
{
49+
var result = true;
50+
for (int i = 0; i < filterStrategies.Count; i++)
51+
{
52+
result = filterStrategies[i](source, result);
53+
}
54+
return result;
55+
}
56+
57+
private IEnumerable<TComparison> Match<TSources, TComparison>(DiffContext context, TSources controlSources, TSources testSources, List<MatchStrategy<TSources, TComparison>> matchStrategies)
58+
{
59+
foreach (var matcher in matchStrategies)
60+
{
61+
foreach (var comparison in matcher(context, controlSources, testSources))
62+
{
63+
yield return comparison;
64+
}
65+
}
66+
}
67+
68+
private CompareResult Compare<TComparison>(in TComparison comparison, List<CompareStrategy<TComparison>> compareStrategies, CompareResult initialResult)
69+
{
70+
var result = initialResult;
71+
foreach (var comparer in compareStrategies)
72+
{
73+
result = comparer(comparison, result);
74+
}
75+
return result;
76+
}
77+
}
78+
}

tests/Core/DiffingTestBase.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,12 +71,12 @@ public MockMatcherStrategy(
7171
_attrMatcher = attrMatcher;
7272
}
7373

74-
public IEnumerable<Comparison> MatchNodes(
74+
public IEnumerable<Comparison> Match(
7575
DiffContext context,
7676
SourceCollection controlNodes,
7777
SourceCollection testNodes) => _nodeMatcher!(context, controlNodes, testNodes);
7878

79-
public IEnumerable<AttributeComparison> MatchAttributes(
79+
public IEnumerable<AttributeComparison> Match(
8080
DiffContext context,
8181
SourceMap controlAttributes,
8282
SourceMap testAttributes) => _attrMatcher!(context, controlAttributes, testAttributes);
@@ -93,10 +93,10 @@ public MockFilterStrategy(Func<ComparisonSource, bool>? nodeFilter = null, Func<
9393
_attrFilter = attrFilter;
9494
}
9595

96-
public bool AttributeFilter(in AttributeComparisonSource attributeComparisonSource)
96+
public bool Filter(in AttributeComparisonSource attributeComparisonSource)
9797
=> _attrFilter!(attributeComparisonSource);
9898

99-
public bool NodeFilter(in ComparisonSource comparisonSource)
99+
public bool Filter(in ComparisonSource comparisonSource)
100100
=> _nodeFilter!(comparisonSource);
101101
}
102102

tests/DiffingStrategyPipelineTest.cs

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
using AngleSharp.Dom;
7+
using Egil.AngleSharp.Diffing.Core;
8+
using Shouldly;
9+
using Xunit;
10+
11+
namespace Egil.AngleSharp.Diffing
12+
{
13+
public class DiffingStrategyPipelineTest : DiffingTestBase
14+
{
15+
[Fact(DisplayName = "Wen zero filter strategies have been added, true is returned")]
16+
public void Test1()
17+
{
18+
var sut = new DiffingStrategyPipeline();
19+
20+
sut.Filter(new ComparisonSource()).ShouldBeTrue();
21+
sut.Filter(new AttributeComparisonSource()).ShouldBeTrue();
22+
}
23+
24+
[Theory(DisplayName = "When one or more filter strategy is added, the last decides the outcome")]
25+
[InlineData(true)]
26+
[InlineData(false)]
27+
public void Test5(bool expected)
28+
{
29+
var sut = new DiffingStrategyPipeline();
30+
31+
sut.AddFilter((in ComparisonSource s, bool currentDecision) => !expected);
32+
sut.AddFilter((in ComparisonSource s, bool currentDecision) => expected);
33+
sut.AddFilter((in AttributeComparisonSource s, bool currentDecision) => !expected);
34+
sut.AddFilter((in AttributeComparisonSource s, bool currentDecision) => expected);
35+
36+
sut.Filter(new ComparisonSource()).ShouldBe(expected);
37+
sut.Filter(new AttributeComparisonSource()).ShouldBe(expected);
38+
}
39+
40+
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
41+
#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type.
42+
[Fact(DisplayName = "When no matcher strategy have been added, none comparisons are returned")]
43+
public void Test2()
44+
{
45+
var sut = new DiffingStrategyPipeline();
46+
47+
sut.Match(null, null, (SourceCollection)null).ShouldBeEmpty();
48+
sut.Match(null, null, (SourceMap)null).ShouldBeEmpty();
49+
}
50+
#pragma warning restore CS8600 // Converting null literal or possible null value to non-nullable type.
51+
#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type.
52+
53+
[Fact(DisplayName = "Node Matchers are allowed to match in the order they are added in")]
54+
public void Test6()
55+
{
56+
var sources = ToComparisonSourceList("<p></p><span></span>").ToList();
57+
var context = new DiffContext((IElement)sources[0].Node, (IElement)sources[0].Node);
58+
var sourceColllection = new SourceCollection(ComparisonSourceType.Control, sources);
59+
var sut = new DiffingStrategyPipeline();
60+
sut.AddMatcher((ctx, s, t) => new[] { new Comparison(s[0], t[0]) });
61+
sut.AddMatcher((ctx, s, t) => new[] { new Comparison(s[1], t[1]) });
62+
63+
var result = sut.Match(context, sourceColllection, sourceColllection).ToList();
64+
65+
result[0].Control.ShouldBe(sources[0]);
66+
result[1].Control.ShouldBe(sources[1]);
67+
}
68+
69+
[Fact(DisplayName = "Attributes Matchers are allowed to match in the order they are added in")]
70+
public void Test7()
71+
{
72+
var source = ToComparisonSource(@"<p foo=""bar"" baz=""bum""></p>");
73+
var context = new DiffContext((IElement)source.Node, (IElement)source.Node);
74+
var sourceMap = new SourceMap(source);
75+
var sut = new DiffingStrategyPipeline();
76+
sut.AddMatcher((ctx, s, t) => new[] { new AttributeComparison(s["foo"], t["foo"]) });
77+
sut.AddMatcher((ctx, s, t) => new[] { new AttributeComparison(s["baz"], t["baz"]) });
78+
79+
var result = sut.Match(context, sourceMap, sourceMap).ToList();
80+
81+
result[0].Control.ShouldBe(sourceMap["foo"]);
82+
result[1].Control.ShouldBe(sourceMap["baz"]);
83+
}
84+
85+
[Fact(DisplayName = "When no compare strategy have been added, DifferentAndBreak is returned for node comparison and Different for attributes")]
86+
public void Test3()
87+
{
88+
var sut = new DiffingStrategyPipeline();
89+
90+
sut.Compare(new Comparison()).ShouldBe(CompareResult.DifferentAndBreak);
91+
sut.Compare(new AttributeComparison()).ShouldBe(CompareResult.Different);
92+
}
93+
94+
[Theory(DisplayName = "When multiple comparers are added, the last decides the outcome")]
95+
[InlineData(CompareResult.Different, CompareResult.Same)]
96+
[InlineData(CompareResult.Same, CompareResult.Different)]
97+
[InlineData(CompareResult.Same, CompareResult.SameAndBreak)]
98+
[InlineData(CompareResult.Different, CompareResult.DifferentAndBreak)]
99+
100+
public void Test8(CompareResult first, CompareResult final)
101+
{
102+
var sut = new DiffingStrategyPipeline();
103+
104+
sut.AddComparer((in Comparison c, CompareResult current) => first);
105+
sut.AddComparer((in Comparison c, CompareResult current) => final);
106+
sut.AddComparer((in AttributeComparison c, CompareResult current) => first);
107+
sut.AddComparer((in AttributeComparison c, CompareResult current) => final);
108+
109+
sut.Compare(new Comparison()).ShouldBe(final);
110+
sut.Compare(new AttributeComparison()).ShouldBe(final);
111+
}
112+
113+
}
114+
}

0 commit comments

Comments
 (0)