Skip to content

Commit

Permalink
Merge pull request #170 from matherm-aboehm/feature/non-standard-pseu…
Browse files Browse the repository at this point in the history
…do-element

Use option AllowInvalidSelectors to allow non-standard pseudo element selectors
  • Loading branch information
TylerBrinks authored Jan 21, 2024
2 parents daa0c7c + 3df8be9 commit 370dcae
Show file tree
Hide file tree
Showing 15 changed files with 218 additions and 57 deletions.
2 changes: 1 addition & 1 deletion .github/actions/dotnet/test/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ runs:
run: dotnet test --logger trx --results-directory ${{ inputs.results-directory }} --no-build --configuration ${{ inputs.dotnet-build-configuration }} --verbosity normal

- name: Parse the unit test files
uses: nasamin/trx-parser@v0.2.0
uses: nasamin/trx-parser@v0.5.0
with:
TRX_PATH: ${{ github.workspace }}/test-results
REPO_TOKEN: ${{ inputs.github-token }}
Expand Down
2 changes: 1 addition & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ The goal of ExCSS is to make it easy to read and parse stylesheets into a friend
Version 4 is a move forward in framework support. The new version targets the latest version of .NET; Core 3.1 and 4.8. The API surface is the same as version 3, but will target the future-facing, unified .NET version including the upcoming .NET 5. Version 3 was rewritten from the ground up; version 4 makes full use of those enhancements plus new additions under development! This is the most advanced ExCSS parser to date. The parser has been rebuild to have better white spaces support as well as the ability to handle unknown rule sets in the ever-changing web and CSS landscape.

# NuGet
[![NuGet Status](https://img.shields.io/nuget/v/excss.svg)](https://www.nuget.org/packages/excss/)
[![NuGet Status](https://img.shields.io/nuget/v/Anateus.ExCSS.svg)](https://www.nuget.org/packages/Anateus.ExCSS/)

# Lexing and Parsing - How it all Works
ExCSS uses a Lexer and a Parser based on a CSS3-specific grammar. The Lexer and Parser read CSS text and parse each
Expand Down
2 changes: 1 addition & 1 deletion src/ExCSS.Tests/Cases.cs
Original file line number Diff line number Diff line change
Expand Up @@ -891,7 +891,7 @@ public void StyleSheetPageAtRulesAndProperties()

var marginRule = pageRule.Children.Last() as MarginStyleRule;
Assert.Equal("@bottom-right", marginRule.SelectorText);
Assert.Equal(1, marginRule.Style.Children.Count());
Assert.Single(marginRule.Style.Children);
Assert.Equal("color: rgb(0, 0, 0)", (marginRule.Style.Children.First() as ColorProperty).CssText);
}

Expand Down
8 changes: 4 additions & 4 deletions src/ExCSS.Tests/DocumentFunction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public void CssDocumentRuleSingleUrlFunction()
var rule = ParseRule(snippet) as DocumentRule;
Assert.NotNull(rule);
Assert.Equal(RuleType.Document, rule.Type);
Assert.Equal(1, rule.Conditions.Count());
Assert.Single(rule.Conditions);
var condition = rule.Conditions.First();
Assert.Equal("url", condition.Name);
Assert.Equal("http://www.w3.org/", condition.Data);
Expand All @@ -28,7 +28,7 @@ public void CssDocumentRuleSingleUrlPrefixFunction()
var rule = ParseRule(snippet) as DocumentRule;
Assert.NotNull(rule);
Assert.Equal(RuleType.Document, rule.Type);
Assert.Equal(1, rule.Conditions.Count());
Assert.Single(rule.Conditions);
var condition = rule.Conditions.First();
Assert.Equal("url-prefix", condition.Name);
Assert.Equal("http://www.w3.org/Style/", condition.Data);
Expand All @@ -43,7 +43,7 @@ public void CssDocumentRuleSingleDomainFunction()
var rule = ParseRule(snippet) as DocumentRule;
Assert.NotNull(rule);
Assert.Equal(RuleType.Document, rule.Type);
Assert.Equal(1, rule.Conditions.Count());
Assert.Single(rule.Conditions);
var condition = rule.Conditions.First();
Assert.Equal("domain", condition.Name);
Assert.Equal("mozilla.org", condition.Data);
Expand All @@ -60,7 +60,7 @@ public void CssDocumentRuleSingleRegexpFunction()
var rule = ParseRule(snippet) as DocumentRule;
Assert.NotNull(rule);
Assert.Equal(RuleType.Document, rule.Type);
Assert.Equal(1, rule.Conditions.Count());
Assert.Single(rule.Conditions);
var condition = rule.Conditions.First();
Assert.Equal("regexp", condition.Name);
Assert.Equal("https:.*", condition.Data);
Expand Down
20 changes: 10 additions & 10 deletions src/ExCSS.Tests/Flexbox.cs
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ public static IEnumerable<object[]> FlexFlowTestDataValues
new object[] { "row nowrap" },
new object[] { "column wrap" },
new object[] { "column-reverse wrap-reverse" },
}.Union(GlobalKeywordTestValues.ToObjectArray());
}.Union(GlobalKeywordTestValues.ToObjectArray(), ObjectArrayComparer.Instance);
}
}

Expand Down Expand Up @@ -235,7 +235,7 @@ public static IEnumerable<object[]> FlexTestDataValues
new object[] { "1 30px" },
new object[] { "2 2" },
new object[] { "2 2 10%" },
}.Union(GlobalKeywordTestValues.ToObjectArray());
}.Union(GlobalKeywordTestValues.ToObjectArray(), ObjectArrayComparer.Instance);
}
}

Expand All @@ -260,7 +260,7 @@ public static IEnumerable<object[]> AlignContentTestDataValues
new object[] { Keywords.Stretch },
new object[] { $"{Keywords.Safe} {Keywords.Center}" },
new object[] { $"{Keywords.Unsafe} {Keywords.Center}" },
}.Union(GlobalKeywordTestValues.ToObjectArray());
}.Union(GlobalKeywordTestValues.ToObjectArray(), ObjectArrayComparer.Instance);
}
}

Expand All @@ -283,7 +283,7 @@ public static IEnumerable<object[]> AlignSelfTestDataValues
new object[] { $"{Keywords.Last} {Keywords.Baseline}" },
new object[] { $"{Keywords.Safe} {Keywords.Center}" },
new object[] { $"{Keywords.Unsafe} {Keywords.Center}" },
}.Union(GlobalKeywordTestValues.ToObjectArray());
}.Union(GlobalKeywordTestValues.ToObjectArray(), ObjectArrayComparer.Instance);
}
}

Expand All @@ -307,7 +307,7 @@ public static IEnumerable<object[]> JustifyContentTestDataValues
new object[] { Keywords.Stretch },
new object[] { $"{Keywords.Safe} {Keywords.Center}" },
new object[] { $"{Keywords.Unsafe} {Keywords.Center}" },
}.Union(GlobalKeywordTestValues.ToObjectArray());
}.Union(GlobalKeywordTestValues.ToObjectArray(), ObjectArrayComparer.Instance);
}
}

Expand All @@ -331,7 +331,7 @@ public static IEnumerable<object[]> AlignItemsTestDataValues
new object[] { $"{Keywords.Last} {Keywords.Baseline}" },
new object[] { $"{Keywords.Safe} {Keywords.Center}" },
new object[] { $"{Keywords.Unsafe} {Keywords.Center}" },
}.Union(GlobalKeywordTestValues.ToObjectArray());
}.Union(GlobalKeywordTestValues.ToObjectArray(), ObjectArrayComparer.Instance);
}
}

Expand All @@ -356,7 +356,7 @@ public static IEnumerable<object[]> AlignContentInvalidPrefixTestDataValues
new object[] { $"{Keywords.First} {Keywords.End}" },
new object[] { $"{Keywords.First} {Keywords.FlexStart}" },
new object[] { $"{Keywords.First} {Keywords.FlexEnd}" },

new object[] { $"{Keywords.Last} {Keywords.Start}" },
new object[] { $"{Keywords.Last} {Keywords.End}" },
new object[] { $"{Keywords.Last} {Keywords.FlexStart}" },
Expand Down Expand Up @@ -433,7 +433,7 @@ public static IEnumerable<object[]> FlexGrowShrinkTestDataValues
{
new object[] { "3" },
new object[] { "0.6" }
}.Union(GlobalKeywordTestValues.ToObjectArray());
}.Union(GlobalKeywordTestValues.ToObjectArray(), ObjectArrayComparer.Instance);
}
}

Expand All @@ -451,7 +451,7 @@ public static IEnumerable<object[]> FlexBasisTestDataValues
new object[] { Keywords.FitContent },
new object[] { Keywords.Content },
new object[] { Keywords.Auto }
}.Union(GlobalKeywordTestValues.ToObjectArray());
}.Union(GlobalKeywordTestValues.ToObjectArray(), ObjectArrayComparer.Instance);
}
}

Expand All @@ -463,7 +463,7 @@ public static IEnumerable<object[]> OrderTestDataValues
{
new object[] { "-1" },
new object[] { "1" },
}.Union(GlobalKeywordTestValues.ToObjectArray());
}.Union(GlobalKeywordTestValues.ToObjectArray(), ObjectArrayComparer.Instance);
}
}
}
Expand Down
14 changes: 7 additions & 7 deletions src/ExCSS.Tests/KeyframeRule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ public void KeyframeRuleWithFromAndMarginLeft()
}");
Assert.NotNull(rule);
Assert.Equal("0%", rule.KeyText);
Assert.Equal(1, rule.Key.Stops.Count());
Assert.Equal(1, rule.Style.Declarations.Count());
Assert.Single(rule.Key.Stops);
Assert.Single(rule.Style.Declarations);
Assert.Equal("margin-left", rule.Style.Declarations.First().Name);
}

Expand All @@ -27,7 +27,7 @@ public void KeyframeRuleWith50PercentAndMarginLeftOpacity()
}");
Assert.NotNull(rule);
Assert.Equal("50%", rule.KeyText);
Assert.Equal(1, rule.Key.Stops.Count());
Assert.Single(rule.Key.Stops);
Assert.Equal(2, rule.Style.Declarations.Count());
Assert.Equal("margin-left", rule.Style.Declarations.Skip(0).First().Name);
Assert.Equal("opacity", rule.Style.Declarations.Skip(1).First().Name);
Expand All @@ -41,8 +41,8 @@ public void KeyframeRuleWithToAndMarginLeft()
}");
Assert.NotNull(rule);
Assert.Equal("100%", rule.KeyText);
Assert.Equal(1, rule.Key.Stops.Count());
Assert.Equal(1, rule.Style.Declarations.Count());
Assert.Single(rule.Key.Stops);
Assert.Single(rule.Style.Declarations);
Assert.Equal("margin-left", rule.Style.Declarations.First().Name);
}

Expand All @@ -69,8 +69,8 @@ public void KeyframeRuleWith0AndNoDeclarations()
var rule = ParseKeyframeRule(@" 0% { }");
Assert.NotNull(rule);
Assert.Equal("0%", rule.KeyText);
Assert.Equal(1, rule.Key.Stops.Count());
Assert.Equal(0, rule.Style.Declarations.Count());
Assert.Single(rule.Key.Stops);
Assert.Empty(rule.Style.Declarations);
}
}
}
66 changes: 66 additions & 0 deletions src/ExCSS.Tests/ObjectArrayComparer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using System;
using System.Collections;
using System.Collections.Generic;

namespace ExCSS.Tests
{
class ObjectArrayComparer : IEqualityComparer, IEqualityComparer<object[]>
{
public static readonly ObjectArrayComparer Instance = new ObjectArrayComparer();
public bool Equals(object[] x, object[] y)
{
if (ReferenceEquals(x, y))
return true;

if (x == null || y == null)
return false;

if (x.Length != y.Length)
return false;

var comparer = EqualityComparer<object>.Default;
for (int i = 0; i < x.Length; i++)
{
if (!comparer.Equals(x[i], x[i])) return false;
}
return true;
}

public int GetHashCode(object[] obj)
{
if (obj == null || obj.Length == 0)
return 0;

int hash = obj[0]?.GetHashCode() ?? 0;
var comparer = EqualityComparer<object>.Default;
for (int i = 1; i < obj.Length; i++)
{
uint temp = (uint)(hash << 5) | ((uint)hash >> 27);
hash = ((int)temp + hash) ^ (obj[i]?.GetHashCode() ?? 0);
}
return hash;
}

bool IEqualityComparer.Equals(object x, object y)
{
if (x == y)
return true;
if (x == null || y == null)
return false;
if (x is object[] && y is object[])
return Equals((object[])x, (object[])y);

throw new ArgumentException("Type of argument is not compatible with this comparer.");
}

int IEqualityComparer.GetHashCode(object obj)
{
if (obj == null)
return 0;
if (obj is object[])
return GetHashCode((object[])obj);

throw new ArgumentException("Type of argument is not compatible with this comparer.");
}
}
}
2 changes: 1 addition & 1 deletion src/ExCSS.Tests/PropertyTests/GapProperty.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public static IEnumerable<object[]> GapTestValues
new object[] { "0.5cm 2mm" },
new object[] { "16% 100%" },
new object[] { "21px 82%" }
}.Union(LengthOrPercentOrGlobalTestValues.Union(GlobalKeywordTestValues.ToObjectArray()));
}.Union(LengthOrPercentOrGlobalTestValues.Union(GlobalKeywordTestValues.ToObjectArray(), ObjectArrayComparer.Instance), ObjectArrayComparer.Instance);
}
}

Expand Down
1 change: 1 addition & 0 deletions src/ExCSS.Tests/PropertyTests/TextProperty.cs
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,7 @@ public void WordBreakNoneIllegal()
Assert.IsType<WordBreakProperty>(property);
}

[Fact]
public void TextAlignLastAutoLegal()
{
var snippet = "text-align-last: auto";
Expand Down
54 changes: 52 additions & 2 deletions src/ExCSS.Tests/SelectorsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,60 @@ public async Task FindAllStyleRulesWithCompoundSelector()
Assert.Equal(7, list.Count());
}

private async Task<Stylesheet> ParseBootstrapAsync()
private static readonly string[] _standardPseudoElementNames = new[] {
PseudoElementNames.After,
PseudoElementNames.Before,
PseudoElementNames.Content,
PseudoElementNames.FirstLetter,
PseudoElementNames.FirstLine,
PseudoElementNames.Selection
};

public static string[] StandardPseudoElementNames
{
get
{
return _standardPseudoElementNames;
}
}

[Theory]
[InlineData(false, false, 277)]
[InlineData(false, true, 0)]
[InlineData(true, false, 277)]
[InlineData(true, true, 6)]
public async Task FindAllStandardPseudoElementSelectors(bool allowInvalidSelectors,
bool nonStandard,
int expectedCount)
{
// Arrange
var sheet = await ParseBootstrapAsync(allowInvalidSelectors);

// Act
var list = sheet.StyleRules
.Where(r => HasStandardPseudoElementSelector(r.Selector, negate: nonStandard));

// Assert
Assert.Equal(expectedCount, list.Count());
}

private static bool HasStandardPseudoElementSelector(ISelector selector, bool negate = false)
{
if (selector is PseudoElementSelector pes)
return negate ^ StandardPseudoElementNames.Contains(pes.Name);
else if (selector is CompoundSelector comp)
return HasStandardPseudoElementSelector(comp.Last(), negate);
else if (selector is ListSelector list)
return list.Any(s => HasStandardPseudoElementSelector(s, negate));
else if (selector is ComplexSelector complex)
return HasStandardPseudoElementSelector(complex.Last().Selector, negate);
return false;
}

private async Task<Stylesheet> ParseBootstrapAsync(bool tolerateInvalidSelectors = false)
{
await using var stream = GetStream("bootstrap.css");
var parser = new StylesheetParser();
var parser = new StylesheetParser(tolerateInvalidSelectors: tolerateInvalidSelectors);
return await parser.ParseAsync(stream);
}

Expand Down
Loading

0 comments on commit 370dcae

Please sign in to comment.