Skip to content

Commit 44dc153

Browse files
committed
Small refactoring
1 parent 220d330 commit 44dc153

File tree

5 files changed

+64
-18
lines changed

5 files changed

+64
-18
lines changed

README.md

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -58,14 +58,17 @@ var diffs = DiffBuilder
5858

5959
_**NOTE**: Currently, the ignore comment strategy does NOT remove comments from CSS or JavaScript embedded in `<style>` or `<script>` tags._
6060

61-
### Whitespace handling
61+
### Text (text nodes) strategies
62+
The built-in text strategies offer a bunch of ways to control how text (text nodes) is handled during the diffing process.
63+
64+
#### Whitespace handling
6265
Whitespace can be a source of false-positives when comparing two HTML fragments. Thus, the whitespace handling strategy offer different ways to deal with it during a comparison.
6366

64-
- `Preserve`: Does not change or filter out any whitespace in control and test HTML. Default, same as not specifying any options.
67+
- `Preserve` (default): Does not change or filter out any whitespace in text nodes the control and test HTML.
6568
- `RemoveWhitespaceNodes`: Using this option filters out all text nodes that only consist of whitespace characters.
66-
- `Normalize`: Using this option will _trim_ all text nodes and replace two or more whitespace characters with a single space character.
69+
- `Normalize`: Using this option will _trim_ all text nodes and replace two or more whitespace characters with a single space character. This option implicitly includes the `RemoveWhitespaceNodes` option.
6770

68-
These options can be set either _globally_ for the entire comparison, or on a _specific subtrees in the comparison_.
71+
These options can be set either _globally_ for the entire comparison, or inline on a _specific subtrees in the comparison_.
6972

7073
To set a global default, call the method `Whitespace(WhitespaceOption)` on a `DiffBuilder` instance, e.g.:
7174

@@ -77,15 +80,15 @@ var diffs = DiffBuilder
7780
.Build();
7881
```
7982

80-
To configure/override whitespace rules on a specific subtree in the comparison, use the `diff:whitespace="WhitespaceOption"` on a control node, and it and all nodes below it will use that whitespace option, unless it is overridden on a child node. In the example below, all whitespace inside the `<h1>` element is preserved:
83+
To configure/override whitespace rules on a specific subtree in the comparison, use the `diff:whitespace="WhitespaceOption"` inline on a control element, and it and all text nodes below it will use that whitespace option, unless it is overridden on a child element. In the example below, all whitespace inside the `<h1>` element is preserved:
8184

8285
```html
8386
<header>
84-
<h1 diff:whitespace="Preserve">Hello <em> woooorld</em></h1>
87+
<h1 diff:whitespace="preserve">Hello <em> woooorld</em></h1>
8588
</header>
8689
```
8790

88-
**Special case for `<pre>`-tags:** The content of `<pre />` tags will always be treated as the `Preserve` option, even if whitespace strategy is globally set to `RemoveWhitespaceNodes` or `Normalize`. To override this, add a local `diff:whitespace" attribute to the tag, e.g.:
91+
**Special case for `<pre>` elements:** The content of `<pre>` elements will always be treated as the `Preserve` option, even if whitespace option is globally set to `RemoveWhitespaceNodes` or `Normalize`. To override this, add a inline `diff:whitespace` attribute to the `<pre>`-tag, e.g.:
8992

9093
```html
9194
<pre diff:whitespace="RemoveWhitespaceNodes">...</pre>
@@ -95,6 +98,38 @@ To configure/override whitespace rules on a specific subtree in the comparison,
9598

9699
**Special case for `<script>`-tags:** It is on the issues list to deal with whitespace properly inside `<script>`-tags.
97100

101+
#### Perform case-_insensitve_ text comparison
102+
To compare the text in two text nodes to eachother using a case-insensitive comparison, call the `IgnoreCase()` method on a `DiffBuilder` instance, e.g.:
103+
104+
```csharp
105+
var diffs = DiffBuilder
106+
.Compare(controlHtml)
107+
.WithTest(testHtml)
108+
.IgnoreCase()
109+
.Build();
110+
```
111+
112+
To configure/override ignore case rules on a specific subtree in the comparison, use the `diff:ignoreCase="true|false"` inline on a control element, and it and all text nodes below it will use that ignore case setting, unless it is overridden on a child element. In the example below, ignore case is set active for all text inside the `<h1>` element:
113+
114+
```html
115+
<header>
116+
<h1 diff:ignoreCase="true">Hello <em> woooorld</em></h1>
117+
</header>
118+
```
119+
120+
Note, as with all HTML5 boolean attributes, the `="true"` or `="false"` parts are optional.
121+
122+
#### Use regular expression when comparing text
123+
By using the inline attribute `diff:regex` on the element containing the text node being compared, the comparer will consider the control text to be a regular expression, and will use that to test whether the test text node is as expected. This can be combined with the inline `diff:ignoreCase` attribute, to make the regular expression case-insensitive. E.g.:
124+
125+
```html
126+
<header>
127+
<h1 diff:regex diff:ignoreCase>Hello World \d{4}</h1>
128+
</header>
129+
```
130+
131+
The above control text would use case-insensitive regular expression to match against a test text string (e.g. "HELLO WORLD 2020").
132+
98133
### Ignore attribute
99134
If the `diff:ignore="true"` attribute is used on a control element (`="true"` implicit/optional), all their attributes and child nodes are skipped/ignored during comparison, including those of the test element, the control element is matched with.
100135

src/Extensions/ElementExtensions.cs

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,30 +7,36 @@ namespace Egil.AngleSharp.Diffing.Extensions
77
public static class ElementExtensions
88
{
99
public static bool TryGetAttrValue(this IElement element, string attributeName, out bool result)
10-
=> TryGetAttrValue(element, attributeName, x => string.IsNullOrWhiteSpace(x) || bool.Parse(x), out result);
10+
{
11+
return TryGetAttrValue(element, attributeName, ParseBoolAttribute, out result);
12+
13+
static bool ParseBoolAttribute(string boolValue) => string.IsNullOrWhiteSpace(boolValue) || bool.Parse(boolValue);
14+
}
1115

1216
public static bool TryGetAttrValue<T>(this IElement element, string attributeName, out T result) where T : System.Enum
1317
{
1418
return TryGetAttrValue(element, attributeName, ParseEnum, out result);
1519

16-
static T ParseEnum(string enumValue)
17-
{
18-
return (T)Enum.Parse(typeof(T), enumValue, true);
19-
}
20+
static T ParseEnum(string enumValue) => (T)Enum.Parse(typeof(T), enumValue, true);
2021
}
2122

2223
public static bool TryGetAttrValue<T>(this IElement element, string attributeName, Func<string, T> resultFunc, [NotNullWhen(true)] out T result)
2324
{
2425
if (element is null) throw new ArgumentNullException(nameof(element));
2526
if (resultFunc is null) throw new ArgumentNullException(nameof(resultFunc));
2627

27-
result = default;
2828
if (element.Attributes[attributeName] is IAttr optAttr)
2929
{
3030
result = resultFunc(optAttr.Value);
3131
return true;
3232
}
33-
return false;
33+
else
34+
{
35+
#pragma warning disable CS8653 // A default expression introduces a null value for a type parameter.
36+
result = default;
37+
#pragma warning restore CS8653 // A default expression introduces a null value for a type parameter.
38+
return false;
39+
}
3440
}
3541

3642
public static TEnum GetInlineOptionOrDefault<TEnum>(this IElement startElement, string optionName, TEnum defaultValue)
@@ -41,6 +47,9 @@ public static bool GetInlineOptionOrDefault(this IElement startElement, string o
4147

4248
public static T GetInlineOptionOrDefault<T>(this IElement startElement, string optionName, Func<string, T> resultFunc, T defaultValue)
4349
{
50+
if (startElement is null) throw new ArgumentNullException(nameof(startElement));
51+
if (resultFunc is null) throw new ArgumentNullException(nameof(resultFunc));
52+
4453
var element = startElement;
4554

4655
while (element is { })

src/Strategies/TextNodeStrategies/TextNodeFilter.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,10 @@ public TextNodeFilter(WhitespaceOption option)
2424
public FilterDecision Filter(in ComparisonSource source, FilterDecision currentDecision)
2525
{
2626
if (currentDecision.IsExclude()) return currentDecision;
27-
return source.Node is IText textNode ? Filter(source, textNode) : currentDecision;
27+
return source.Node is IText textNode ? Filter(textNode) : currentDecision;
2828
}
2929

30-
private FilterDecision Filter(in ComparisonSource source, IText textNode)
30+
private FilterDecision Filter(IText textNode)
3131
{
3232
var option = GetWhitespaceOption(textNode);
3333
return option != WhitespaceOption.Preserve && string.IsNullOrWhiteSpace(textNode.Data)

tests/DiffingStrategyPipelineTest.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ public class DiffingStrategyPipelineTest : DiffingTestBase
1515
private FilterDecision NegateDecision(FilterDecision decision) => decision switch
1616
{
1717
FilterDecision.Keep => FilterDecision.Exclude,
18-
FilterDecision.Exclude => FilterDecision.Keep
18+
FilterDecision.Exclude => FilterDecision.Keep,
19+
_ => throw new InvalidOperationException()
1920
};
2021

2122
[Fact(DisplayName = "Wen zero filter strategies have been added, true is returned")]
@@ -116,5 +117,6 @@ public void Test8(CompareResult first, CompareResult final)
116117
sut.Compare(new AttributeComparison()).ShouldBe(final);
117118
}
118119

120+
// if compare returns andStop, dont call any more comparers?
119121
}
120122
}

tests/Strategies/TextNodeStrategies/TextNodeFilterTest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ public void Test5()
6060
}
6161

6262
[Fact(DisplayName = "If parent node is <pre> element with a diff:whitespace, the option is take from the attribute")]
63-
public void Test5_1()
63+
public void Test51()
6464
{
6565
var sut = new TextNodeFilter(WhitespaceOption.Normalize);
6666
var pre = ToComparisonSource("<pre diff:whitespace=\"RemoveWhitespaceNodes\"> \n\t </pre>");

0 commit comments

Comments
 (0)