Skip to content

Commit 8295892

Browse files
committed
Implemented CSS variable resolution #62
1 parent 1c9efd4 commit 8295892

File tree

9 files changed

+195
-20
lines changed

9 files changed

+195
-20
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ Released on tbd.
1616
- Fixed computation of relative (`em`) values to absolute (`px`) for `Length` (#136)
1717
- Added further compactification of CSS tuples (#89, #93)
1818
- Added support for CSS nesting in style rules (#148)
19+
- Added resolution of CSS variable names (#62)
1920
- Added support for 8-digit hex color codes (#132)
2021
- Added support for `margin-block` and `margin-inline` declarations
2122
- Added support for `padding-block` and `padding-inline` declarations

src/AngleSharp.Css.Tests/Declarations/CssVariables.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ public void LegitVariableReferenceWithFallback()
5959
Assert.IsNotNull(variable);
6060
Assert.AreEqual(1, variable.References.Length);
6161
Assert.AreEqual("--my-bar", variable.References[0].VariableName);
62-
Assert.AreEqual("24px", variable.References[0].DefaultValue);
62+
Assert.AreEqual("24px", variable.References[0].DefaultValue.CssText);
6363
}
6464

6565
[Test]
@@ -72,7 +72,7 @@ public void LegitVariableReferenceWithFallbackContainingComma()
7272
Assert.IsNotNull(variable);
7373
Assert.AreEqual(1, variable.References.Length);
7474
Assert.AreEqual("--color", variable.References[0].VariableName);
75-
Assert.AreEqual("red, blue", variable.References[0].DefaultValue);
75+
Assert.AreEqual("red, blue", variable.References[0].DefaultValue.CssText);
7676
}
7777

7878
[Test]
@@ -113,7 +113,7 @@ public void LegitMultipleVariableReferenceInBorderShorthand()
113113
Assert.AreEqual("--width", variable.References[0].VariableName);
114114
Assert.IsNull(variable.References[0].DefaultValue);
115115
Assert.AreEqual("--color", variable.References[1].VariableName);
116-
Assert.AreEqual("black", variable.References[1].DefaultValue);
116+
Assert.AreEqual("black", variable.References[1].DefaultValue.CssText);
117117
}
118118
}
119119
}

src/AngleSharp.Css.Tests/Extensions/AnalysisWindow.cs

+88
Original file line numberDiff line numberDiff line change
@@ -308,5 +308,93 @@ public async Task ComputesAbsoluteValuesFromRelative_Issue136()
308308
var style = sc.ComputeDeclarations(document.QuerySelector("span"));
309309
Assert.AreEqual("24px", style.GetFontSize());
310310
}
311+
312+
[Test]
313+
public async Task ResolvesCssVariables_Issue62()
314+
{
315+
var sheet = ParseStyleSheet(@"
316+
:root {
317+
--color: #FFFFFF;
318+
}
319+
320+
p {
321+
color: var(--color);
322+
}");
323+
var document = await sheet.Context.OpenAsync(res => res.Content(@"<p>This is a test</p>"));
324+
var sc = new StyleCollection(new[] { sheet }, new DefaultRenderDevice());
325+
var style = sc.ComputeDeclarations(document.QuerySelector("p"));
326+
Assert.AreEqual("rgba(255, 255, 255, 1)", style.GetColor());
327+
}
328+
329+
[Test]
330+
public async Task ResolvesCssVariablesWithUnusedFallback_Issue62()
331+
{
332+
var sheet = ParseStyleSheet(@"
333+
:root {
334+
--color: #FFFFFF;
335+
}
336+
337+
p {
338+
color: var(--color, green);
339+
}");
340+
var document = await sheet.Context.OpenAsync(res => res.Content(@"<p>This is a test</p>"));
341+
var sc = new StyleCollection(new[] { sheet }, new DefaultRenderDevice());
342+
var style = sc.ComputeDeclarations(document.QuerySelector("p"));
343+
Assert.AreEqual("rgba(255, 255, 255, 1)", style.GetColor());
344+
}
345+
346+
[Test]
347+
public async Task ResolvesCssVariablesWithUsedFallback_Issue62()
348+
{
349+
var sheet = ParseStyleSheet(@"
350+
:root {}
351+
352+
p {
353+
color: var(--color, green);
354+
}");
355+
var document = await sheet.Context.OpenAsync(res => res.Content(@"<p>This is a test</p>"));
356+
var sc = new StyleCollection(new[] { sheet }, new DefaultRenderDevice());
357+
var style = sc.ComputeDeclarations(document.QuerySelector("p"));
358+
Assert.AreEqual("rgba(0, 128, 0, 1)", style.GetColor());
359+
}
360+
361+
[Test]
362+
public async Task ResolvesCssVariablesWithUsedFallbackVarReference_Issue62()
363+
{
364+
var sheet = ParseStyleSheet(@"
365+
:root {
366+
--defaultColor: green;
367+
}
368+
369+
p {
370+
color: var(--color, var(--defaultColor));
371+
}");
372+
var document = await sheet.Context.OpenAsync(res => res.Content(@"<p>This is a test</p>"));
373+
var sc = new StyleCollection(new[] { sheet }, new DefaultRenderDevice());
374+
var style = sc.ComputeDeclarations(document.QuerySelector("p"));
375+
Assert.AreEqual("rgba(0, 128, 0, 1)", style.GetColor());
376+
}
377+
378+
[Test]
379+
public async Task ResolvesCssVariablesWithCascade_Issue62()
380+
{
381+
var sheet = ParseStyleSheet(@"
382+
:root {
383+
--color: blue;
384+
--defaultColor: red;
385+
}
386+
387+
body {
388+
--color: green;
389+
}
390+
391+
p {
392+
color: var(--color, var(--defaultColor));
393+
}");
394+
var document = await sheet.Context.OpenAsync(res => res.Content(@"<p>This is a test</p>"));
395+
var sc = new StyleCollection(new[] { sheet }, new DefaultRenderDevice());
396+
var style = sc.ComputeDeclarations(document.QuerySelector("p"));
397+
Assert.AreEqual("rgba(0, 128, 0, 1)", style.GetColor());
398+
}
311399
}
312400
}

src/AngleSharp.Css/Dom/Internal/CssProperty.cs

+27-2
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ internal class CssProperty : ICssProperty
2828

2929
internal CssProperty(String name, IValueConverter converter, PropertyFlags flags = PropertyFlags.None, ICssValue value = null, Boolean important = false)
3030
{
31-
_name = name.ToLowerInvariant();
31+
_name = name.StartsWith("--") ? name : name.ToLowerInvariant();
3232
_converter = converter;
3333
_flags = flags;
3434
_value = value;
@@ -91,7 +91,8 @@ public Boolean IsImportant
9191

9292
public ICssProperty Compute(ICssComputeContext context)
9393
{
94-
var computedValue = _value?.Compute(context);
94+
var propertyContext = new PropertyComputeContext(context, _converter);
95+
var computedValue = _value?.Compute(propertyContext);
9596

9697
if (computedValue != _value)
9798
{
@@ -103,6 +104,30 @@ public ICssProperty Compute(ICssComputeContext context)
103104

104105
#endregion
105106

107+
#region Compute Context
108+
109+
sealed class PropertyComputeContext : ICssComputeContext
110+
{
111+
private readonly ICssComputeContext _parent;
112+
private readonly IValueConverter _converter;
113+
114+
public PropertyComputeContext(ICssComputeContext parent, IValueConverter converter)
115+
{
116+
_parent = parent;
117+
_converter = converter;
118+
}
119+
120+
public IRenderDevice Device => _parent.Device;
121+
122+
public IBrowsingContext Context => _parent.Context;
123+
124+
public IValueConverter Converter => _converter;
125+
126+
public ICssValue Resolve(String name) =>_parent.Resolve(name);
127+
}
128+
129+
#endregion
130+
106131
#region String Representation
107132

108133
public void ToCss(TextWriter writer, IStyleFormatter formatter) => writer.Write(formatter.Declaration(CssUtilities.Escape(Name), Value, IsImportant));

src/AngleSharp.Css/Extensions/StyleCollectionExtensions.cs

+17-2
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ public static IStyleCollection GetStyleCollection(this IWindow window)
4444
public static ICssStyleDeclaration ComputeDeclarations(this IStyleCollection styles, IElement element, String pseudoSelector = null)
4545
{
4646
var ctx = element.Owner?.Context;
47-
var context = new CssComputeContext(styles.Device, ctx);
4847
var computedStyle = new CssStyleDeclaration(ctx);
48+
var context = new CssComputeContext(styles.Device, ctx, computedStyle);
4949
var nodes = element.GetAncestors().OfType<IElement>();
5050

5151
if (!String.IsNullOrEmpty(pseudoSelector))
@@ -142,16 +142,31 @@ sealed class CssComputeContext : ICssComputeContext
142142
{
143143
private readonly IRenderDevice _device;
144144
private readonly IBrowsingContext _context;
145+
private readonly ICssProperties _properties;
145146

146-
public CssComputeContext(IRenderDevice device, IBrowsingContext context)
147+
public CssComputeContext(IRenderDevice device, IBrowsingContext context, ICssProperties properties)
147148
{
148149
_device = device ?? new DefaultRenderDevice();
149150
_context = context;
151+
_properties = properties;
150152
}
151153

152154
public IRenderDevice Device => _device;
153155

154156
public IBrowsingContext Context => _context;
157+
158+
public IValueConverter Converter => null;
159+
160+
public ICssValue Resolve(String name)
161+
{
162+
if (name.StartsWith("--"))
163+
{
164+
var property = _properties.FirstOrDefault(m => m.Name.Equals(name, StringComparison.Ordinal));
165+
return property?.RawValue;
166+
}
167+
168+
return null;
169+
}
155170
}
156171

157172
#endregion

src/AngleSharp.Css/Parser/Micro/FunctionParser.cs

+20-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
namespace AngleSharp.Css.Parser
22
{
3+
using AngleSharp.Css.Dom;
34
using AngleSharp.Css.Values;
45
using AngleSharp.Text;
56
using System;
@@ -93,22 +94,38 @@ public static CssVarValue ParseVar(this StringSource source)
9394
var name = source.ParseCustomIdent();
9495
var f = source.SkipGetSkip();
9596

96-
if (name != null)
97+
if (name is not null)
9798
{
9899
switch (f)
99100
{
100101
case Symbols.RoundBracketClose:
101102
return new CssVarValue(name);
102103
case Symbols.Comma:
103-
var defaultValue = source.TakeUntilClosed();
104-
source.SkipCurrentAndSpaces();
104+
source.SkipSpacesAndComments();
105+
var defaultValue = ParseVarFallback(source);
105106
return new CssVarValue(name, defaultValue);
106107
}
107108
}
108109

109110
return null;
110111
}
111112

113+
/// <summary>
114+
/// Parses a CSS var (variable) fallback value.
115+
/// </summary>
116+
public static ICssValue ParseVarFallback(this StringSource source)
117+
{
118+
if (!source.IsFunction(FunctionNames.Var))
119+
{
120+
var content = source.TakeUntilClosed();
121+
source.SkipCurrentAndSpaces();
122+
return new CssAnyValue(content);
123+
}
124+
125+
return source.ParseVar();
126+
127+
}
128+
112129
/// <summary>
113130
/// Parses a CSS content value.
114131
/// </summary>

src/AngleSharp.Css/Values/Functions/CssVarValue.cs

+14-8
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public sealed class CssVarValue : ICssFunctionValue
1313
#region Fields
1414

1515
private readonly String _variableName;
16-
private readonly String _defaultValue;
16+
private readonly ICssValue _defaultValue;
1717

1818
#endregion
1919

@@ -24,7 +24,7 @@ public sealed class CssVarValue : ICssFunctionValue
2424
/// </summary>
2525
/// <param name="variableName">The name of the custom property.</param>
2626
/// <param name="defaultValue">The fallback value, if any.</param>
27-
public CssVarValue(String variableName, String defaultValue = null)
27+
public CssVarValue(String variableName, ICssValue defaultValue = null)
2828
{
2929
_variableName = variableName;
3030
_defaultValue = defaultValue;
@@ -53,7 +53,7 @@ public ICssValue[] Arguments
5353

5454
if (_defaultValue != null)
5555
{
56-
list.Add(new CssAnyValue(_defaultValue));
56+
list.Add(_defaultValue);
5757
}
5858

5959
return list.ToArray();
@@ -68,7 +68,7 @@ public ICssValue[] Arguments
6868
/// <summary>
6969
/// Gets the defined fallback value, if any.
7070
/// </summary>
71-
public String DefaultValue => _defaultValue;
71+
public ICssValue DefaultValue => _defaultValue;
7272

7373
/// <summary>
7474
/// Gets the CSS text representation.
@@ -83,9 +83,9 @@ public String CssText
8383
_variableName,
8484
};
8585

86-
if (!String.IsNullOrEmpty(_defaultValue))
86+
if (_defaultValue is not null)
8787
{
88-
args.Add(_defaultValue);
88+
args.Add(_defaultValue.CssText);
8989
}
9090

9191
return fn.CssFunction(String.Join(", ", args));
@@ -104,8 +104,14 @@ public String CssText
104104
/// <returns>The resolved value or null.</returns>
105105
public ICssValue Compute(ICssComputeContext context)
106106
{
107-
//return _expression.Compute(context);
108-
return null;
107+
var value = context.Resolve(_variableName)?.Compute(context);
108+
109+
if (value is not null)
110+
{
111+
return value;
112+
}
113+
114+
return _defaultValue?.Compute(context);
109115
}
110116

111117
#endregion

src/AngleSharp.Css/Values/ICssComputeContext.cs

+15
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
namespace AngleSharp.Css.Values
22
{
3+
using AngleSharp.Css.Dom;
4+
using System;
5+
36
/// <summary>
47
/// Defines the context for computing styles.
58
/// </summary>
@@ -14,5 +17,17 @@ public interface ICssComputeContext
1417
/// Gets the associated browsing context.
1518
/// </summary>
1619
IBrowsingContext Context { get; }
20+
21+
/// <summary>
22+
/// Gets the currently associated value converter.
23+
/// </summary>
24+
IValueConverter Converter { get; }
25+
26+
/// <summary>
27+
/// Resolves a CSS variable by its name.
28+
/// </summary>
29+
/// <param name="name">The name of the variable.</param>
30+
/// <returns>The value of the variable or null if no such variable exists.</returns>
31+
ICssValue Resolve(String name);
1732
}
1833
}

src/AngleSharp.Css/Values/Raws/CssAnyValue.cs

+10-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
namespace AngleSharp.Css.Values
22
{
3+
using AngleSharp.Css.Converters;
34
using AngleSharp.Css.Dom;
45
using System;
56

@@ -45,8 +46,15 @@ public CssAnyValue(String text)
4546

4647
ICssValue ICssValue.Compute(ICssComputeContext context)
4748
{
48-
//TODO INVALID
49-
return this;
49+
var converter = context.Converter;
50+
51+
if (converter is not null && converter is not AnyValueConverter)
52+
{
53+
var value = converter.Convert(_text);
54+
return value?.Compute(context);
55+
}
56+
57+
return null;
5058
}
5159

5260
#endregion

0 commit comments

Comments
 (0)