Skip to content

Commit 15156f3

Browse files
committed
Node selectors done
1 parent d6c569a commit 15156f3

36 files changed

+492
-162
lines changed

Directory.Build.props

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22
<PropertyGroup>
33
<LangVersion>8.0</LangVersion>
44
<Nullable>enable</Nullable>
5+
<WarningsAsErrors>CS8600;CS8602;CS8603;CS8625</WarningsAsErrors>
56
</PropertyGroup>
67
<PropertyGroup>
78
<AngleSharpVersion>0.13.0</AngleSharpVersion>
8-
<FxCopAnalyzersVersion>2.9.4</FxCopAnalyzersVersion>
9+
<FxCopAnalyzersVersion>2.9.6</FxCopAnalyzersVersion>
910
</PropertyGroup>
1011
</Project>

docs/Strategies.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ var diffs = DiffBuilder
4646
.Build();
4747
```
4848

49+
Read more about each of the strategies below, including some that are not part of the default set.
50+
4951
## Filter strategies
5052
These are the built-in filter strategies.
5153

@@ -99,7 +101,7 @@ var diffs = DiffBuilder
99101
```
100102

101103
#### Forward-searching node matcher
102-
The forward-searching node-matcher strategy will only match control nodes with test nodes, if their `NodeName` match. It does this by taking one control node at the time, and searching from the previously matched test node until it finds a match. If it does not, it reports the control node as *missing*. After, any unmatched test nodes is reported as *unexpected*.
104+
The forward-searching node-matcher strategy will only match control nodes with test nodes, if their `NodeName` match. It does this by taking one control node at the time, and searching after the previously matched test node until it finds a match. If it does not, continues with the next control node, and the unmatched control node is marked as *missing*. After, any unmatched test nodes is marked as *unexpected*.
103105

104106
The follow JavaScript-ish-code illustrates how the algorithm works:
105107

@@ -117,11 +119,11 @@ forwardSearchingMatcher(controlNodes, testNodes) {
117119
lastMatchedTestNode = index
118120
index = testNodes.length
119121
}
122+
index++
120123
}
121124
}
122125
return matches
123126
}
124-
125127
```
126128

127129
To choose this matcher, use the `WithSearchingNodeMatcher()` method on the `DiffBuilder` class, e.g.:

src/Core/AttributeComparison.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using AngleSharp.Dom;
22
using System;
3-
using System.Diagnostics.CodeAnalysis;
43

54
namespace Egil.AngleSharp.Diffing.Core
65
{

src/Core/Comparison.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
using System;
22
using AngleSharp.Dom;
33
using System.Diagnostics.CodeAnalysis;
4+
using System.Diagnostics;
45

56
namespace Egil.AngleSharp.Diffing.Core
67
{
78
/// <summary>
89
/// Represent a comparison between two nodes.
910
/// </summary>
1011
/// <typeparam name="TNode"></typeparam>
12+
[DebuggerDisplay("Control: {Control.Path} | Test: {Test.Path}")]
1113
public readonly struct Comparison : IEquatable<Comparison>
1214
{
1315
public ComparisonSource Control { get; }
@@ -23,7 +25,7 @@ public Comparison(in ComparisonSource control, in ComparisonSource test)
2325
public bool AreNodeTypesEqual() => Control.Node.NodeType == Test.Node.NodeType && Control.Node.NodeName == Test.Node.NodeName;
2426

2527
public bool TryGetNodesAsType<TNode>([NotNullWhen(true)]out TNode? controlNode, [NotNullWhen(true)]out TNode? testNode) where TNode : class, INode
26-
{
28+
{
2729
if (Control.Node is TNode ctrl && Test.Node is TNode test)
2830
{
2931
controlNode = ctrl;

src/Core/ComparisonSource.cs

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22
using System.Diagnostics;
33
using System.Diagnostics.CodeAnalysis;
44
using AngleSharp.Dom;
5+
using Egil.AngleSharp.Diffing.Extensions;
56

67
namespace Egil.AngleSharp.Diffing.Core
78
{
9+
[SuppressMessage("Globalization", "CA1308:Normalize strings to uppercase", Justification = "Path should be in lower case")]
810
[DebuggerDisplay("{Index} : {Path}")]
911
public readonly struct ComparisonSource : IEquatable<ComparisonSource>, IComparisonSource
1012
{
@@ -18,7 +20,15 @@ namespace Egil.AngleSharp.Diffing.Core
1820

1921
public ComparisonSourceType SourceType { get; }
2022

21-
[SuppressMessage("Globalization", "CA1308:Normalize strings to uppercase", Justification = "Path should be in lower case")]
23+
public ComparisonSource(INode node, ComparisonSourceType sourceType)
24+
{
25+
Node = node;
26+
Index = GetNodeIndex(node);
27+
Path = CalculateNodePath(node, Index);
28+
SourceType = sourceType;
29+
_hashCode = (Node, Index, Path, SourceType).GetHashCode();
30+
}
31+
2232
public ComparisonSource(INode node, int index, string path, ComparisonSourceType sourceType)
2333
{
2434
if (node is null) throw new ArgumentNullException(nameof(node));
@@ -33,6 +43,30 @@ public ComparisonSource(INode node, int index, string path, ComparisonSourceType
3343
_hashCode = (Node, Index, Path, SourceType).GetHashCode();
3444
}
3545

46+
private static int GetNodeIndex(INode node)
47+
{
48+
if (node.TryGetNodeIndex(out var index))
49+
{
50+
return index;
51+
}
52+
else
53+
{
54+
throw new ArgumentException("When the node does not have a parent its index cannot be calculated.", nameof(node));
55+
}
56+
}
57+
58+
private static string CalculateNodePath(INode node, int index)
59+
{
60+
var path = $"{node.NodeName.ToLowerInvariant()}({index})";
61+
var parent = node.Parent;
62+
while (parent is { } && parent.TryGetNodeIndex(out var parentIndex))
63+
{
64+
path = $"{parent.NodeName.ToLowerInvariant()}({parentIndex}) > {path}";
65+
parent = parent.Parent;
66+
}
67+
return path;
68+
}
69+
3670
#region Equals and HashCode
3771
public bool Equals(ComparisonSource other) => Node.Equals(other.Node) && Index == other.Index && Path.Equals(other.Path, StringComparison.Ordinal) && SourceType == other.SourceType;
3872
public override int GetHashCode() => _hashCode;

src/Core/SourceCollection.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,9 @@ public SourceCollection(ComparisonSourceType sourceType, IEnumerable<ComparisonS
3333
EnsureSourcesAreInCorrectOrder();
3434
}
3535

36-
public IEnumerable<ComparisonSource> GetUnmatched()
36+
public IEnumerable<ComparisonSource> GetUnmatched(int startIndex = 0)
3737
{
38-
for (int i = 0; i < _sources.Length; i++)
38+
for (int i = startIndex; i < _sources.Length; i++)
3939
{
4040
if (_status[_sources[i].Index] == SOURCE_UNMATCHED)
4141
{

src/DiffingStrategyPipeline.cs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,4 @@
1-
using System;
2-
using System.Collections.Generic;
3-
using System.Linq;
4-
using System.Text;
5-
using System.Threading.Tasks;
1+
using System.Collections.Generic;
62
using Egil.AngleSharp.Diffing.Core;
73

84
namespace Egil.AngleSharp.Diffing

src/Egil.AngleSharp.Diffing.csproj

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22

33
<PropertyGroup>
44
<TargetFramework>netstandard2.0</TargetFramework>
5-
<Nullable>enable</Nullable>
6-
<WarningsAsErrors>CS8600;CS8602;CS8603;CS8625</WarningsAsErrors>
75
</PropertyGroup>
86

97
<ItemGroup>
@@ -14,7 +12,7 @@
1412

1513
<ItemGroup>
1614
<PackageReference Include="AngleSharp" Version="$(AngleSharpVersion)" />
17-
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.5">
15+
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="$(FxCopAnalyzersVersion)">
1816
<PrivateAssets>all</PrivateAssets>
1917
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
2018
</PackageReference>

src/Extensions/ElementExtensions.cs

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,13 @@ public static bool TryGetAttrValue(this IElement element, string attributeName,
1313
static bool ParseBoolAttribute(string boolValue) => string.IsNullOrWhiteSpace(boolValue) || bool.Parse(boolValue);
1414
}
1515

16+
public static bool TryGetAttrValue(this IElement element, string attributeName, [NotNullWhen(true)]out string result)
17+
{
18+
return TryGetAttrValue(element, attributeName, GetStringAttrValue, out result);
19+
20+
static string GetStringAttrValue(string value) => value;
21+
}
22+
1623
public static bool TryGetAttrValue<T>(this IElement element, string attributeName, out T result) where T : System.Enum
1724
{
1825
return TryGetAttrValue(element, attributeName, ParseEnum, out result);
@@ -32,9 +39,9 @@ public static bool TryGetAttrValue<T>(this IElement element, string attributeNam
3239
}
3340
else
3441
{
35-
#pragma warning disable CS8653 // A default expression introduces a null value for a type parameter.
42+
#pragma warning disable CS8653 // A default expression introduces a null value for a type parameter.
3643
result = default;
37-
#pragma warning restore CS8653 // A default expression introduces a null value for a type parameter.
44+
#pragma warning restore CS8653 // A default expression introduces a null value for a type parameter.
3845
return false;
3946
}
4047
}
@@ -63,5 +70,25 @@ public static T GetInlineOptionOrDefault<T>(this IElement startElement, string o
6370

6471
return defaultValue;
6572
}
73+
74+
public static bool TryGetNodeIndex(this INode node, [NotNullWhen(true)]out int index)
75+
{
76+
index = -1;
77+
78+
if(node.ParentElement is null) return false;
79+
80+
var parentElement = node.ParentElement;
81+
82+
for (int i = 0; i < parentElement.ChildNodes.Length; i++)
83+
{
84+
if (parentElement.ChildNodes[i].Equals(node))
85+
{
86+
index = i;
87+
return true;
88+
}
89+
}
90+
91+
return false;
92+
}
6693
}
6794
}

src/Extensions/EnumExtensions.cs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,4 @@
11
using System;
2-
using System.Collections.Generic;
3-
using System.Linq;
4-
using System.Text;
5-
using System.Threading.Tasks;
62

73
namespace Egil.AngleSharp.Diffing.Extensions
84
{

0 commit comments

Comments
 (0)