Skip to content

Commit 1aeea3c

Browse files
author
Gideon de Swardt
committed
Implement specific selectors
Replace the simple selector with implementation for more specific selectors. This allows the document object model to be queried via LINQ to retrieve specific queries based on selectors.
1 parent d577b2e commit 1aeea3c

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)