1
1
using System ;
2
2
using System . Collections . Generic ;
3
- using System . Diagnostics ;
4
3
using System . Linq ;
5
4
using AngleSharp . Dom ;
6
5
7
6
namespace Egil . AngleSharp . Diffing
8
7
{
8
+
9
9
public enum CompareResult
10
10
{
11
11
Same ,
@@ -16,7 +16,8 @@ public interface IHtmlCompareStrategy
16
16
{
17
17
bool NodeFilter ( INode node ) ;
18
18
bool AttributeFilter ( IAttr attribute , IElement owningElement ) ;
19
- CompareResult Compare ( in Comparison comparison ) ;
19
+ CompareResult CompareNode ( in Comparison comparison ) ;
20
+ CompareResult CompareAttribute ( string attributeName , in Comparison comparisonContext ) ;
20
21
}
21
22
22
23
public class HtmlDifferenceEngine
@@ -28,48 +29,58 @@ public HtmlDifferenceEngine(IHtmlCompareStrategy strategy)
28
29
_strategy = strategy ;
29
30
}
30
31
31
- public IReadOnlyCollection < Diff > Compare ( INodeList controlNodes , INodeList testNodes ) => DoCompare ( controlNodes , testNodes ) ;
32
+ public IReadOnlyList < Diff > Compare ( INodeList controlNodes , INodeList testNodes ) => DoCompare ( controlNodes , testNodes ) ;
32
33
33
- private IReadOnlyCollection < Diff > DoCompare ( INodeList controlNodes , INodeList testNodes )
34
+ private IReadOnlyList < Diff > DoCompare ( INodeList rootControlNodes , INodeList rootTestNodes )
34
35
{
35
- if ( controlNodes is null ) throw new ArgumentNullException ( nameof ( controlNodes ) ) ;
36
- if ( testNodes is null ) throw new ArgumentNullException ( nameof ( testNodes ) ) ;
36
+ if ( rootControlNodes is null ) throw new ArgumentNullException ( nameof ( rootControlNodes ) ) ;
37
+ if ( rootTestNodes is null ) throw new ArgumentNullException ( nameof ( rootTestNodes ) ) ;
37
38
38
39
var result = new List < Diff > ( ) ;
39
40
40
- var comparisons = GetComparisons ( controlNodes , testNodes ) ;
41
+ var compareQueue = new Queue < ( INodeList ControlNodes , INodeList TestNodes ) > ( ) ;
42
+ compareQueue . Enqueue ( ( rootControlNodes , rootTestNodes ) ) ;
41
43
42
- foreach ( var comparison in comparisons )
44
+ while ( compareQueue . Count > 0 )
43
45
{
44
- result . AddRange ( GetDifferences ( in comparison ) ) ;
45
- //// Compare child nodes
46
- //if (comparison.Control.HasChildNodes)
47
- // foreach (var diff in Compare(comparison.Control.ChildNodes, comparison.Test.ChildNodes))
48
- // yield return diff;
49
- }
46
+ var ( controlNodes , testNodes ) = compareQueue . Dequeue ( ) ;
47
+ var comparisons = GetComparisons ( controlNodes , testNodes ) ;
50
48
51
- var matchedTestNodes = comparisons . Where ( x => x . Test . HasValue ) . Select ( x => x . Test . Value ) ;
52
- var unmatchedTestNodes = testNodes . Select ( ( n , i ) => new ComparisonSource ( n , i ) ) . Except ( matchedTestNodes ) ;
49
+ foreach ( var comparison in comparisons )
50
+ {
51
+ result . AddRange ( GetDifferences ( in comparison ) ) ;
52
+ }
53
53
54
- // detect unmatched test nodes
55
- foreach ( var node in unmatchedTestNodes )
56
- {
57
- result . Add ( node . Node . NodeType switch
54
+ var matchedTestNodes = comparisons . Where ( x => x . Test . HasValue ) . Select ( x => x . Test . Value ) ;
55
+ var unmatchedTestNodes = testNodes . Select ( ( n , i ) => new ComparisonSource ( n , i ) ) . Except ( matchedTestNodes ) ;
56
+
57
+ // detect unmatched test nodes
58
+ foreach ( var node in unmatchedTestNodes )
58
59
{
59
- NodeType . Comment => new Diff ( DiffType . UnexpectedComment , test : node ) ,
60
- NodeType . Element => new Diff ( DiffType . UnexpectedElement , test : node ) ,
61
- NodeType . Text => new Diff ( DiffType . UnexpectedTextNode , test : node ) ,
62
- _ => throw new InvalidOperationException ( $ "Unexpected nodetype, { node . Node . NodeType } , in test nodes list.")
63
- } ) ;
60
+ result . Add ( node . Node . NodeType switch
61
+ {
62
+ NodeType . Comment => new Diff ( DiffType . UnexpectedComment , test : node ) ,
63
+ NodeType . Element => new Diff ( DiffType . UnexpectedElement , test : node ) ,
64
+ NodeType . Text => new Diff ( DiffType . UnexpectedTextNode , test : node ) ,
65
+ _ => throw new InvalidOperationException ( $ "Unexpected nodetype, { node . Node . NodeType } , in test nodes list.")
66
+ } ) ;
67
+ }
68
+
69
+ foreach ( var c in comparisons )
70
+ {
71
+ if ( c . Status == MatchStatus . TestNodeNotFound ) continue ;
72
+ if ( c . Control . Node . HasChildNodes || ( c . Test ? . Node . HasChildNodes ?? false ) )
73
+ compareQueue . Enqueue ( ( c . Control . Node . ChildNodes , c . Test ? . Node . ChildNodes ?? EmptyNodeList . Instance ) ) ;
74
+ }
64
75
}
65
76
66
77
return result ;
67
78
}
68
79
69
- private ICollection < Comparison > GetComparisons ( INodeList controlNodes , INodeList testNodes )
80
+ private List < Comparison > GetComparisons ( INodeList controlNodes , INodeList testNodes )
70
81
{
71
82
var evenTreeBranch = controlNodes . Length == testNodes . Length ;
72
- var result = new Comparison [ controlNodes . Length ] ;
83
+ var result = new List < Comparison > ( controlNodes . Length ) ;
73
84
var lastFoundIndex = - 1 ;
74
85
75
86
for ( int index = 0 ; index < controlNodes . Length ; index ++ )
@@ -83,13 +94,13 @@ private ICollection<Comparison> GetComparisons(INodeList controlNodes, INodeList
83
94
? EqualTreeSizeNodeMatcher ( in controlSource )
84
95
: ForwardSearchingNodeMatcher ( in controlSource ) ;
85
96
86
- result [ index ] = new Comparison ( controlSource , testSource ) ;
97
+ result . Add ( new Comparison ( controlSource , testSource ) ) ;
87
98
}
88
99
89
100
return result ;
90
101
91
102
//bool ShouldSkipNode(INode node) => !_strategy.NodeFilter(node);
92
-
103
+
93
104
ComparisonSource ? EqualTreeSizeNodeMatcher ( in ComparisonSource comparisonSource )
94
105
{
95
106
// Consider skipping strategy effect
@@ -135,99 +146,39 @@ private ICollection<Diff> GetDifferences(in Comparison comparison) // in
135
146
_ => throw new InvalidOperationException ( $ "Unexpected nodetype, { comparison . Control . Node . NodeType } , in test nodes list.")
136
147
} ) ;
137
148
}
138
- else if ( comparison . Status == MatchStatus . TestNodeFound && _strategy . Compare ( in comparison ) == CompareResult . Different )
149
+ else
139
150
{
140
- result . Add ( comparison . Control . Node . NodeType switch
151
+ if ( _strategy . CompareNode ( in comparison ) == CompareResult . Different )
141
152
{
142
- NodeType . Comment => new Diff ( DiffType . DifferentComment , comparison . Control , comparison . Test ) ,
143
- NodeType . Element => new Diff ( DiffType . DifferentElementTagName , comparison . Control , comparison . Test ) ,
144
- NodeType . Text => new Diff ( DiffType . DifferentTextNode , comparison . Control , comparison . Test ) ,
145
- _ => throw new InvalidOperationException ( $ "Unexpected nodetype, { comparison . Control . Node . NodeType } , in test nodes list.")
146
- } ) ;
147
- }
148
-
149
- return result ;
150
- }
151
- }
152
-
153
- public enum DiffType
154
- {
155
- DifferentComment ,
156
- DifferentElementTagName ,
157
- DifferentTextNode ,
158
- MissingComment ,
159
- MissingElement ,
160
- MissingTextNode ,
161
- UnexpectedComment ,
162
- UnexpectedElement ,
163
- UnexpectedTextNode
164
- }
165
-
166
- [ DebuggerDisplay ( "Diff={Type} Control={Control?.Node.NodeName}[{Control?.Index}] Test={Test?.Node.NodeName}[{Test?.Index}]" ) ]
167
- public readonly struct Diff : IEquatable < Diff >
168
- {
169
- public DiffType Type { get ; }
170
-
171
- public ComparisonSource ? Control { get ; }
172
-
173
- public ComparisonSource ? Test { get ; }
174
-
175
- public Diff ( DiffType type , in ComparisonSource ? control = null , in ComparisonSource ? test = null )
176
- {
177
- Type = type ;
178
- Control = control ;
179
- Test = test ;
180
- }
181
-
182
- #region Equals and Hashcode
183
- public bool Equals ( Diff other ) => Type == other . Type ;
184
- public override int GetHashCode ( ) => ( Type ) . GetHashCode ( ) ;
185
- public override bool Equals ( object obj ) => obj is Diff other && Equals ( other ) ;
186
- public static bool operator == ( Diff left , Diff right ) => left . Equals ( right ) ;
187
- public static bool operator != ( Diff left , Diff right ) => ! ( left == right ) ;
188
- #endregion
189
- }
190
-
191
- public readonly struct Comparison : IEquatable < Comparison >
192
- {
193
- public ComparisonSource Control { get ; }
194
-
195
- public ComparisonSource ? Test { get ; }
196
-
197
- public MatchStatus Status => Test is null ? MatchStatus . TestNodeNotFound : MatchStatus . TestNodeFound ;
198
-
199
- public Comparison ( in ComparisonSource control , in ComparisonSource ? test )
200
- {
201
- Control = control ;
202
- Test = test ;
203
- }
153
+ result . Add ( comparison . Control . Node . NodeType switch
154
+ {
155
+ NodeType . Comment => new Diff ( DiffType . DifferentComment , comparison . Control , comparison . Test ) ,
156
+ NodeType . Element => new Diff ( DiffType . DifferentElementTagName , comparison . Control , comparison . Test ) ,
157
+ NodeType . Text => new Diff ( DiffType . DifferentTextNode , comparison . Control , comparison . Test ) ,
158
+ _ => throw new InvalidOperationException ( $ "Unexpected nodetype, { comparison . Control . Node . NodeType } , in test nodes list.")
159
+ } ) ;
160
+ }
204
161
205
- #region Equals and HashCode
206
- public bool Equals ( Comparison other ) => Control == other . Control && Test == other . Test ;
207
- public override bool Equals ( object obj ) => obj is Comparison other && Equals ( other ) ;
208
- public override int GetHashCode ( ) => ( Control , Test ) . GetHashCode ( ) ;
209
- public static bool operator == ( Comparison left , Comparison right ) => left . Equals ( right ) ;
210
- public static bool operator != ( Comparison left , Comparison right ) => ! ( left == right ) ;
211
- #endregion
212
- }
162
+ var controlElm = comparison . Control . Node as IElement ;
163
+ var testElm = comparison . Test ? . Node as IElement ;
213
164
214
- public readonly struct ComparisonSource : IEquatable < ComparisonSource >
215
- {
216
- public INode Node { get ; }
217
- public int Index { get ; }
165
+ if ( controlElm is { } && testElm is { } )
166
+ {
167
+ foreach ( var controlAttr in controlElm . Attributes )
168
+ {
169
+ if ( testElm . HasAttribute ( controlAttr . Name ) )
170
+ {
171
+ var attrCompareRes = _strategy . CompareAttribute ( controlAttr . Name , in comparison ) ;
172
+ if ( attrCompareRes == CompareResult . Different )
173
+ {
174
+ result . Add ( new Diff ( DiffType . DifferentAttribute , comparison . Control , comparison . Test ) ) ;
175
+ }
176
+ }
177
+ }
178
+ }
179
+ }
218
180
219
- public ComparisonSource ( INode node , int index )
220
- {
221
- Node = node ;
222
- Index = index ;
181
+ return result ;
223
182
}
224
-
225
- #region Equals and HashCode
226
- public bool Equals ( ComparisonSource other ) => Node == other . Node && Index == other . Index ;
227
- public override int GetHashCode ( ) => ( Node , Index ) . GetHashCode ( ) ;
228
- public override bool Equals ( object obj ) => obj is ComparisonSource other && Equals ( other ) ;
229
- public static bool operator == ( ComparisonSource left , ComparisonSource right ) => left . Equals ( right ) ;
230
- public static bool operator != ( ComparisonSource left , ComparisonSource right ) => ! ( left == right ) ;
231
- #endregion
232
183
}
233
184
}
0 commit comments