Skip to content

Commit 3ece65b

Browse files
authored
Merge pull request #145 from gdeswardt/feature/specific-selector
Implement specific selectors
2 parents a25e42e + 1aeea3c commit 3ece65b

29 files changed

Lines changed: 439 additions & 349 deletions

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,3 +240,7 @@ ModelManifest.xml
240240

241241
# FAKE - F# Make
242242
.fake/
243+
244+
# JetBrains Rider
245+
.idea/
246+
*.sln.iml
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
using System.Threading.Tasks;
4+
using Xunit;
5+
6+
namespace ExCSS.Tests;
7+
8+
public class AttrSelectorTests
9+
{
10+
[Fact]
11+
public async Task FindAllAttrMatchSelectorsThatMatchAttributeName()
12+
{
13+
// Arrange
14+
var sheet = await GetAttributeStylesheetAsync("");
15+
16+
// Act
17+
var list = GetAttributeStyleRules<AttrMatchSelector>(sheet);
18+
19+
// Assert
20+
Assert.Equal(2, list.Count());
21+
}
22+
23+
[Fact]
24+
public async Task FindAllAttrInListSelectorsThatMatchAttributeName()
25+
{
26+
// Arrange
27+
var sheet = await GetAttributeStylesheetAsync("~");
28+
29+
// Act
30+
var list = GetAttributeStyleRules<AttrListSelector>(sheet);
31+
32+
// Assert
33+
Assert.Equal(2, list.Count());
34+
}
35+
36+
[Fact]
37+
public async Task FindAllAttrHyphenSelectorsThatMatchAttributeName()
38+
{
39+
// Arrange
40+
var sheet = await GetAttributeStylesheetAsync("|");
41+
42+
// Act
43+
var list = GetAttributeStyleRules<AttrHyphenSelector>(sheet);
44+
45+
// Assert
46+
Assert.Equal(2, list.Count());
47+
}
48+
49+
[Fact]
50+
public async Task FindAllAttrBeginsSelectorsThatMatchAttributeName()
51+
{
52+
// Arrange
53+
var sheet = await GetAttributeStylesheetAsync("^");
54+
55+
// Act
56+
var list = GetAttributeStyleRules<AttrBeginsSelector>(sheet);
57+
58+
// Assert
59+
Assert.Equal(2, list.Count());
60+
}
61+
62+
[Fact]
63+
public async Task FindAllAttrEndsSelectorsThatMatchAttributeName()
64+
{
65+
// Arrange
66+
var sheet = await GetAttributeStylesheetAsync("$");
67+
68+
// Act
69+
var list = GetAttributeStyleRules<AttrEndsSelector>(sheet);
70+
71+
// Assert
72+
Assert.Equal(2, list.Count());
73+
}
74+
75+
[Fact]
76+
public async Task FindAllAttrContainsSelectorsThatMatchAttributeName()
77+
{
78+
// Arrange
79+
var sheet = await GetAttributeStylesheetAsync("*");
80+
81+
// Act
82+
var list = GetAttributeStyleRules<AttrContainsSelector>(sheet);
83+
84+
// Assert
85+
Assert.Equal(2, list.Count());
86+
}
87+
88+
[Fact]
89+
public async Task FindAllAttrNotMatchSelectorsThatMatchAttributeName()
90+
{
91+
// Arrange
92+
var sheet = await GetAttributeStylesheetAsync("!");
93+
94+
// Act
95+
var list = GetAttributeStyleRules<AttrNotMatchSelector>(sheet);
96+
97+
// Assert
98+
Assert.Equal(2, list.Count());
99+
}
100+
101+
private async Task<Stylesheet> GetAttributeStylesheetAsync(string combinator)
102+
{
103+
var css = @"[type" + combinator + "='button'] { background-color: #101010 } .sample-class[type"
104+
+ combinator + "='input'] { background-color: #121212 }";
105+
106+
return await new StylesheetParser().ParseAsync(css);
107+
}
108+
109+
private IEnumerable<IStyleRule> GetAttributeStyleRules<T>(Stylesheet sheet) where T : IAttrSelector
110+
{
111+
return sheet.StyleRules
112+
.Where(x =>
113+
(x.Selector is CompoundSelector selector &&
114+
selector.Any(y => y is T { Attribute: "type" }))
115+
|| x.Selector is T { Attribute: "type" }
116+
);
117+
}
118+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using System.Linq;
2+
using System.Threading.Tasks;
3+
using Xunit;
4+
5+
namespace ExCSS.Tests;
6+
7+
public class ClassSelectorTests
8+
{
9+
[Fact]
10+
public async Task FindAllClassSelectorsThatMatchClassName()
11+
{
12+
// Arrange
13+
var css =
14+
@".sample-class { background-color: #101010 } .sample-class[type='input'] { background-color: #121212 }";
15+
var sheet = await new StylesheetParser().ParseAsync(css);
16+
17+
// Act
18+
var list = sheet.StyleRules
19+
.Where(x =>
20+
(x.Selector is CompoundSelector selector &&
21+
selector.Any(y => y is ClassSelector { Class: "sample-class" }))
22+
|| x.Selector is ClassSelector { Class: "sample-class" }
23+
);
24+
25+
// Assert
26+
Assert.Equal(2, list.Count());
27+
}
28+
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ public async Task FindAllStyleRulesForAnElement()
1616
// Act
1717
var list = sheet.StyleRules
1818
.Where(r =>
19-
r.Selector is SimpleSelector { Text: "input" }
20-
|| (r.Selector is CompoundSelector selector && selector.First() is SimpleSelector { Text: "input" })
19+
r.Selector is TypeSelector { Text: "input" }
20+
|| (r.Selector is CompoundSelector selector && selector.First() is TypeSelector { Text: "input" })
2121
);
2222

2323
// Assert

src/ExCSS/Factories/AttributeSelectorFactory.cs

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,17 @@ namespace ExCSS
55
{
66
public sealed class AttributeSelectorFactory
77
{
8-
public delegate ISelector Creator(string name, string value, string prefix);
8+
private static readonly Lazy<AttributeSelectorFactory> Lazy = new(() => new AttributeSelectorFactory());
99

10-
private static readonly Lazy<AttributeSelectorFactory> Lazy =
11-
new(() => new AttributeSelectorFactory());
12-
13-
private readonly Dictionary<string, Creator> _creators = new()
10+
private readonly Dictionary<string, Type> _types = new()
1411
{
15-
{Combinators.Exactly, SimpleSelector.AttrMatch},
16-
{Combinators.InList, SimpleSelector.AttrList},
17-
{Combinators.InToken, SimpleSelector.AttrHyphen},
18-
{Combinators.Begins, SimpleSelector.AttrBegins},
19-
{Combinators.Ends, SimpleSelector.AttrEnds},
20-
{Combinators.InText, SimpleSelector.AttrContains},
21-
{Combinators.Unlike, SimpleSelector.AttrNotMatch}
12+
{ Combinators.Exactly, typeof(AttrMatchSelector) },
13+
{ Combinators.InList, typeof(AttrListSelector) },
14+
{ Combinators.InToken, typeof(AttrHyphenSelector) },
15+
{ Combinators.Begins, typeof(AttrBeginsSelector) },
16+
{ Combinators.Ends, typeof(AttrEndsSelector) },
17+
{ Combinators.InText, typeof(AttrContainsSelector) },
18+
{ Combinators.Unlike, typeof(AttrNotMatchSelector) },
2219
};
2320

2421
private AttributeSelectorFactory()
@@ -27,16 +24,29 @@ private AttributeSelectorFactory()
2724

2825
internal static AttributeSelectorFactory Instance => Lazy.Value;
2926

30-
public ISelector Create(string combinator, string name, string value, string prefix)
27+
public IAttrSelector Create(string combinator, string match, string value, string prefix)
28+
{
29+
var name = match;
30+
31+
if (!string.IsNullOrEmpty(prefix))
32+
{
33+
name = FormFront(prefix, match);
34+
_ = FormMatch(prefix, match);
35+
}
36+
37+
return _types.TryGetValue(combinator, out var type)
38+
? (IAttrSelector)Activator.CreateInstance(type, name, value)
39+
: new AttrAvailableSelector(name, value);
40+
}
41+
42+
private string FormFront(string prefix, string match)
3143
{
32-
return _creators.TryGetValue(combinator, out var creator)
33-
? creator.Invoke(name, value, prefix)
34-
: CreateDefault(name, value);
44+
return string.Concat(prefix, Combinators.Pipe, match);
3545
}
3646

37-
private ISelector CreateDefault(string name, string value)
47+
private string FormMatch(string prefix, string match)
3848
{
39-
return SimpleSelector.AttrAvailable(name, value);
49+
return prefix.Is(Keywords.Asterisk) ? match : string.Concat(prefix, PseudoClassNames.Separator, match);
4050
}
4151
}
4252
}

src/ExCSS/Factories/PseudoClassSelectorFactory.cs

Lines changed: 3 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Linq;
34

45
namespace ExCSS
56
{
@@ -24,145 +25,44 @@ public sealed class PseudoClassSelectorFactory
2425
#region Selectors
2526

2627
private static readonly Dictionary<string, ISelector> Selectors =
27-
new(StringComparer.OrdinalIgnoreCase)
28-
{
28+
new HashSet<string>(StringComparer.OrdinalIgnoreCase)
2929
{
3030
PseudoClassNames.Root,
31-
SimpleSelector.PseudoClass(PseudoClassNames.Root)
32-
},
33-
{
3431
PseudoClassNames.Scope,
35-
SimpleSelector.PseudoClass(PseudoClassNames.Scope)
36-
},
37-
{
3832
PseudoClassNames.OnlyType,
39-
SimpleSelector.PseudoClass(PseudoClassNames.OnlyType)
40-
},
41-
{
4233
PseudoClassNames.FirstOfType,
43-
SimpleSelector.PseudoClass(PseudoClassNames.FirstOfType)
44-
},
45-
{
4634
PseudoClassNames.LastOfType,
47-
SimpleSelector.PseudoClass(PseudoClassNames.LastOfType)
48-
},
49-
{
5035
PseudoClassNames.OnlyChild,
51-
SimpleSelector.PseudoClass(PseudoClassNames.OnlyChild)
52-
},
53-
{
5436
PseudoClassNames.FirstChild,
55-
SimpleSelector.PseudoClass(PseudoClassNames.FirstChild)
56-
},
57-
{
5837
PseudoClassNames.LastChild,
59-
SimpleSelector.PseudoClass(PseudoClassNames.LastChild)
60-
},
61-
{
6238
PseudoClassNames.Empty,
63-
SimpleSelector.PseudoClass(PseudoClassNames.Empty)
64-
},
65-
{
6639
PseudoClassNames.AnyLink,
67-
SimpleSelector.PseudoClass(PseudoClassNames.AnyLink)
68-
},
69-
{
7040
PseudoClassNames.Link,
71-
SimpleSelector.PseudoClass(PseudoClassNames.Link)
72-
},
73-
{
7441
PseudoClassNames.Visited,
75-
SimpleSelector.PseudoClass(PseudoClassNames.Visited)
76-
},
77-
{
7842
PseudoClassNames.Active,
79-
SimpleSelector.PseudoClass(PseudoClassNames.Active)
80-
},
81-
{
8243
PseudoClassNames.Hover,
83-
SimpleSelector.PseudoClass(PseudoClassNames.Hover)
84-
},
85-
{
8644
PseudoClassNames.Focus,
87-
SimpleSelector.PseudoClass(PseudoClassNames.Focus)
88-
},
89-
{
9045
PseudoClassNames.FocusVisible,
91-
SimpleSelector.PseudoClass(PseudoClassNames.FocusVisible)
92-
},
93-
{
9446
PseudoClassNames.FocusWithin,
95-
SimpleSelector.PseudoClass(PseudoClassNames.FocusWithin)
96-
},
97-
{
9847
PseudoClassNames.Target,
99-
SimpleSelector.PseudoClass(PseudoClassNames.Target)
100-
},
101-
{
10248
PseudoClassNames.Enabled,
103-
SimpleSelector.PseudoClass(PseudoClassNames.Enabled)
104-
},
105-
{
10649
PseudoClassNames.Disabled,
107-
SimpleSelector.PseudoClass(PseudoClassNames.Disabled)
108-
},
109-
{
11050
PseudoClassNames.Default,
111-
SimpleSelector.PseudoClass(PseudoClassNames.Default)
112-
},
113-
{
11451
PseudoClassNames.Checked,
115-
SimpleSelector.PseudoClass(PseudoClassNames.Checked)
116-
},
117-
{
11852
PseudoClassNames.Indeterminate,
119-
SimpleSelector.PseudoClass(PseudoClassNames.Indeterminate)
120-
},
121-
{
12253
PseudoClassNames.PlaceholderShown,
123-
SimpleSelector.PseudoClass(PseudoClassNames.PlaceholderShown)
124-
},
125-
{
12654
PseudoClassNames.Unchecked,
127-
SimpleSelector.PseudoClass(PseudoClassNames.Unchecked)
128-
},
129-
{
13055
PseudoClassNames.Valid,
131-
SimpleSelector.PseudoClass(PseudoClassNames.Valid)
132-
},
133-
{
13456
PseudoClassNames.Invalid,
135-
SimpleSelector.PseudoClass(PseudoClassNames.Invalid)
136-
},
137-
{
13857
PseudoClassNames.Required,
139-
SimpleSelector.PseudoClass(PseudoClassNames.Required)
140-
},
141-
{
14258
PseudoClassNames.ReadOnly,
143-
SimpleSelector.PseudoClass(PseudoClassNames.ReadOnly)
144-
},
145-
{
14659
PseudoClassNames.ReadWrite,
147-
SimpleSelector.PseudoClass(PseudoClassNames.ReadWrite)
148-
},
149-
{
15060
PseudoClassNames.InRange,
151-
SimpleSelector.PseudoClass(PseudoClassNames.InRange)
152-
},
153-
{
15461
PseudoClassNames.OutOfRange,
155-
SimpleSelector.PseudoClass(PseudoClassNames.OutOfRange)
156-
},
157-
{
15862
PseudoClassNames.Optional,
159-
SimpleSelector.PseudoClass(PseudoClassNames.Optional)
160-
},
161-
{
16263
PseudoClassNames.Shadow,
163-
SimpleSelector.PseudoClass(PseudoClassNames.Shadow)
16464
}
165-
};
65+
.ToDictionary(x => x, PseudoClassSelector.Create);
16666

16767
#endregion
16868

0 commit comments

Comments
 (0)