Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
# 0.17.0

Released on Wednesday, September 8, 2021.

- Added the ability to ignore an elements children or its attributes. By [@grishat](https://github.com/grishat).

# 0.16.0

Released on Wednesday, June 24, 2021.
Expand Down
38 changes: 38 additions & 0 deletions src/AngleSharp.Diffing.Tests/Core/HtmlDifferenceEngineTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,44 @@ public void Test2()
results.ShouldBeEmpty();
}

[Theory(DisplayName = "When comparer returns SkipChildren flag from an element comparison, child nodes are not compared")]
[InlineData(CompareResult.Same | CompareResult.SkipChildren)]
[InlineData(CompareResult.Skip | CompareResult.SkipChildren)]
public void Test3(CompareResult compareResult)
{
var sut = CreateHtmlDiffer(
nodeMatcher: OneToOneNodeListMatcher,
nodeFilter: NoneNodeFilter,
nodeComparer: c => c.Control.Node.NodeName == "P" ? compareResult : throw new Exception("NODE COMPARER SHOULD NOT BE CALLED ON CHILD NODES"),
attrMatcher: AttributeNameMatcher,
attrFilter: NoneAttrFilter,
attrComparer: SameResultAttrComparer
);

var results = sut.Compare(ToNodeList(@"<p><em>foo</em></p>"), ToNodeList(@"<p><span>baz</span></p>"));

results.ShouldBeEmpty();
}

[Theory(DisplayName = "When comparer returns SkipAttributes flag from an element comparison, attributes are not compared")]
[InlineData(CompareResult.Same | CompareResult.SkipAttributes)]
[InlineData(CompareResult.Skip | CompareResult.SkipAttributes)]
public void Test4(CompareResult compareResult)
{
var sut = CreateHtmlDiffer(
nodeMatcher: OneToOneNodeListMatcher,
nodeFilter: NoneNodeFilter,
nodeComparer: c => compareResult,
attrMatcher: AttributeNameMatcher,
attrFilter: NoneAttrFilter,
attrComparer: SameResultAttrComparer
);

var results = sut.Compare(ToNodeList(@"<p id=""foo""></p>"), ToNodeList(@"<p id=""bar"" unexpected></p>"));

results.ShouldBeEmpty();
}

#region NodeFilters
private static FilterDecision NoneNodeFilter(ComparisonSource source) => FilterDecision.Keep;
private static FilterDecision RemoveCommentNodeFilter(ComparisonSource source) => source.Node.NodeType == NodeType.Comment ? FilterDecision.Exclude : FilterDecision.Keep;
Expand Down
36 changes: 36 additions & 0 deletions src/AngleSharp.Diffing.Tests/DiffBuilderTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,5 +89,41 @@ public void Test004()
nodeComparerCalled.ShouldBeTrue();
attrComparerCalled.ShouldBeTrue();
}

[Theory(DisplayName = "When a control element has 'diff:ignoreChildren', calling Build() with DefaultOptions() returns empty diffs")]
[InlineData(@"<p diff:ignoreChildren>hello <em>world</em></p>",
@"<p>world says <strong>hello</strong></p>")]
[InlineData(@"<p diff:ignoreChildren>hello</p>",
@"<p>world says <strong>hello</strong></p>")]
[InlineData(@"<p diff:ignoreChildren>hello <em>world</em></p>",
@"<p>world says</p>")]
public void Test005(string control, string test)
{
var diffs = DiffBuilder
.Compare(control)
.WithTest(test)
.Build()
.ToList();

diffs.ShouldBeEmpty();
}

[Theory(DisplayName = "When a control element has 'diff:ignoreAttributes', calling Build() with DefaultOptions() returns empty diffs")]
[InlineData(@"<p id=""foo"" diff:ignoreAttributes></p>",
@"<p id=""bar""></p>")]
[InlineData(@"<p diff:ignoreAttributes></p>",
@"<p unexpected></p>")]
[InlineData(@"<p id=""foo"" diff:ignoreAttributes></p>",
@"<p></p>")]
public void Test006(string control, string test)
{
var diffs = DiffBuilder
.Compare(control)
.WithTest(test)
.Build()
.ToList();

diffs.ShouldBeEmpty();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using System.Linq;

using AngleSharp.Diffing.Core;

using Shouldly;

using Xunit;

namespace AngleSharp.Diffing.Strategies.ElementStrategies
{
public class IgnoreAttributesElementComparerTest : DiffingTestBase
{
public IgnoreAttributesElementComparerTest(DiffingTestFixture fixture) : base(fixture)
{
}

[Theory(DisplayName = "When a control element does not contain the 'diff:ignoreAttributes' attribute or it is 'diff:ignoreAttributes=false', the current decision is returned")]
[InlineData(@"<p></p>")]
[InlineData(@"<p diff:ignoreAttributes=""false""></p>")]
[InlineData(@"<p diff:ignoreAttributes=""FALSE""></p>")]
[InlineData(@"<p diff:ignoreAttributes=""faLsE""></p>")]
public void Test001(string controlHtml)
{
var comparison = ToComparison(controlHtml, "<p></p>");

IgnoreAttributesElementComparer.Compare(comparison, CompareResult.Different).ShouldBe(CompareResult.Different);
IgnoreAttributesElementComparer.Compare(comparison, CompareResult.Same).ShouldBe(CompareResult.Same);
IgnoreAttributesElementComparer.Compare(comparison, CompareResult.Skip).ShouldBe(CompareResult.Skip);
}

[Theory(DisplayName = "When a control element has 'diff:ignoreAttributes' attribute, CompareResult.SkipAttributes flag is returned")]
[InlineData(@"<p diff:ignoreAttributes></p>")]
[InlineData(@"<p diff:ignoreAttributes=""true""></p>")]
[InlineData(@"<p diff:ignoreAttributes=""TRUE""></p>")]
[InlineData(@"<p diff:ignoreAttributes=""TrUe""></p>")]
public void Test002(string controlHtml)
{
var comparison = ToComparison(controlHtml, "<p></p>");

IgnoreAttributesElementComparer.Compare(comparison, CompareResult.Same).ShouldBe(CompareResult.Same | CompareResult.SkipAttributes);
IgnoreAttributesElementComparer.Compare(comparison, CompareResult.Different).ShouldBe(CompareResult.Different | CompareResult.SkipAttributes);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using System.Linq;

using AngleSharp.Diffing.Core;

using Shouldly;

using Xunit;

namespace AngleSharp.Diffing.Strategies.ElementStrategies
{
public class IgnoreChildrenElementComparerTest : DiffingTestBase
{
public IgnoreChildrenElementComparerTest(DiffingTestFixture fixture) : base(fixture)
{
}

[Theory(DisplayName = "When a control element does not contain the 'diff:ignoreChildren' attribute or it is 'diff:ignoreChildren=false', the current decision is returned")]
[InlineData(@"<p></p>")]
[InlineData(@"<p diff:ignoreChildren=""false""></p>")]
[InlineData(@"<p diff:ignoreChildren=""FALSE""></p>")]
[InlineData(@"<p diff:ignoreChildren=""faLsE""></p>")]
public void Test001(string controlHtml)
{
var comparison = ToComparison(controlHtml, "<p></p>");

IgnoreChildrenElementComparer.Compare(comparison, CompareResult.Different).ShouldBe(CompareResult.Different);
IgnoreChildrenElementComparer.Compare(comparison, CompareResult.Same).ShouldBe(CompareResult.Same);
IgnoreChildrenElementComparer.Compare(comparison, CompareResult.Skip).ShouldBe(CompareResult.Skip);
}

[Theory(DisplayName = "When a control element has 'diff:ignoreChildren' attribute, CompareResult.SkipChildren flag is returned")]
[InlineData(@"<p diff:ignoreChildren></p>")]
[InlineData(@"<p diff:ignoreChildren=""true""></p>")]
[InlineData(@"<p diff:ignoreChildren=""TRUE""></p>")]
[InlineData(@"<p diff:ignoreChildren=""TrUe""></p>")]
public void Test002(string controlHtml)
{
var comparison = ToComparison(controlHtml, "<p></p>");

IgnoreChildrenElementComparer.Compare(comparison, CompareResult.Same).ShouldBe(CompareResult.Same | CompareResult.SkipChildren);
IgnoreChildrenElementComparer.Compare(comparison, CompareResult.Different).ShouldBe(CompareResult.Different | CompareResult.SkipChildren);
}
}
}
19 changes: 15 additions & 4 deletions src/AngleSharp.Diffing/Core/CompareResult.cs
Original file line number Diff line number Diff line change
@@ -1,22 +1,33 @@
namespace AngleSharp.Diffing.Core
using System;

namespace AngleSharp.Diffing.Core
{
/// <summary>
/// Represents a result of a comparison.
/// </summary>
[Flags]
public enum CompareResult
{
/// <summary>
/// Use when the two compared nodes or attributes are the same.
/// </summary>
Same,
Same = 1,
/// <summary>
/// Use when the two compared nodes or attributes are the different.
/// </summary>
Different,
Different = 2,
/// <summary>
/// Use when the comparison should be skipped and any child-nodes or attributes skipped as well.
/// </summary>
Skip
Skip = 4,
/// <summary>
/// Use when the comparison should skip any child-nodes.
/// </summary>
SkipChildren = 8,
/// <summary>
/// Use when the comparison should skip any attributes.
/// </summary>
SkipAttributes = 16,
}

/// <summary>
Expand Down
12 changes: 7 additions & 5 deletions src/AngleSharp.Diffing/Core/HtmlDifferenceEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ private IEnumerable<IDiff> CompareNode(in Comparison comparison)
}

var compareRes = _diffingStrategy.Compare(comparison);
if (compareRes == CompareResult.Different)
if (compareRes.HasFlag(CompareResult.Different))
{
IDiff diff = new NodeDiff(comparison);
return new[] { diff };
Expand All @@ -106,15 +106,17 @@ private IEnumerable<IDiff> CompareElement(in Comparison comparison)
var result = new List<IDiff>();

var compareRes = _diffingStrategy.Compare(comparison);
if (compareRes == CompareResult.Different)
if (compareRes.HasFlag(CompareResult.Different))
{
result.Add(new NodeDiff(comparison));
}

if (compareRes != CompareResult.Skip)
if (!compareRes.HasFlag(CompareResult.Skip))
{
result.AddRange(CompareElementAttributes(comparison));
result.AddRange(CompareChildNodes(comparison));
if (!compareRes.HasFlag(CompareResult.SkipAttributes))
result.AddRange(CompareElementAttributes(comparison));
if (!compareRes.HasFlag(CompareResult.SkipChildren))
result.AddRange(CompareChildNodes(comparison));
}

return result;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@ public static IDiffingStrategyCollection AddDefaultOptions(this IDiffingStrategy
.AddAttributeComparer()
.AddClassAttributeComparer()
.AddBooleanAttributeComparer(BooleanAttributeComparision.Strict)
.AddStyleAttributeComparer();
;
.AddStyleAttributeComparer()
.AddIgnoreChildrenElementSupport()
.AddIgnoreAttributesElementSupport()
;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,27 @@ public static IDiffingStrategyCollection AddIgnoreElementSupport(this IDiffingSt
builder.AddComparer(IgnoreElementComparer.Compare, StrategyType.Specialized);
return builder;
}

/// <summary>
/// Enables the ignore children element `diff:ignoreChildren` attribute during diffing.
/// </summary>
/// <param name="builder"></param>
/// <returns></returns>
public static IDiffingStrategyCollection AddIgnoreChildrenElementSupport(this IDiffingStrategyCollection builder)
{
builder.AddComparer(IgnoreChildrenElementComparer.Compare, StrategyType.Specialized);
return builder;
}

/// <summary>
/// Enables the ignore attributes element `diff:ignoreAttributes` attribute during diffing.
/// </summary>
/// <param name="builder"></param>
/// <returns></returns>
public static IDiffingStrategyCollection AddIgnoreAttributesElementSupport(this IDiffingStrategyCollection builder)
{
builder.AddComparer(IgnoreAttributesElementComparer.Compare, StrategyType.Specialized);
return builder;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using AngleSharp.Diffing.Core;
using AngleSharp.Diffing.Extensions;
using AngleSharp.Dom;

namespace AngleSharp.Diffing.Strategies.ElementStrategies
{
/// <summary>
/// Represents the ignore attributes element comparer.
/// </summary>
public static class IgnoreAttributesElementComparer
{
private const string DIFF_IGNORE_ATTRIBUTES_ATTRIBUTE = "diff:ignoreattributes";

/// <summary>
/// The ignore attributes element comparer.
/// </summary>
public static CompareResult Compare(in Comparison comparison, CompareResult currentDecision)
{
if (currentDecision == CompareResult.Skip)
return currentDecision;

return ControlHasTruthyIgnoreAttributesAttribute(comparison)
? currentDecision | CompareResult.SkipAttributes
: currentDecision;
}

private static bool ControlHasTruthyIgnoreAttributesAttribute(in Comparison comparison)
{
return comparison.Control.Node is IElement element &&
element.TryGetAttrValue(DIFF_IGNORE_ATTRIBUTES_ATTRIBUTE, out bool shouldIgnore) &&
shouldIgnore;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using AngleSharp.Diffing.Core;
using AngleSharp.Diffing.Extensions;
using AngleSharp.Dom;

namespace AngleSharp.Diffing.Strategies.ElementStrategies
{
/// <summary>
/// Represents the ignore children element comparer.
/// </summary>
public static class IgnoreChildrenElementComparer
{
private const string DIFF_IGNORE_CHILDREN_ATTRIBUTE = "diff:ignorechildren";

/// <summary>
/// The ignore children element comparer.
/// </summary>
public static CompareResult Compare(in Comparison comparison, CompareResult currentDecision)
{
if (currentDecision == CompareResult.Skip)
return currentDecision;

return ControlHasTruthyIgnoreChildrenAttribute(comparison)
? currentDecision | CompareResult.SkipChildren
: currentDecision;
}

private static bool ControlHasTruthyIgnoreChildrenAttribute(in Comparison comparison)
{
return comparison.Control.Node is IElement element &&
element.TryGetAttrValue(DIFF_IGNORE_CHILDREN_ATTRIBUTE, out bool shouldIgnore) &&
shouldIgnore;
}
}
}