Skip to content

Commit

Permalink
Implemented CSS variable resolution #62
Browse files Browse the repository at this point in the history
  • Loading branch information
FlorianRappl committed Jan 18, 2024
1 parent 1c9efd4 commit 8295892
Show file tree
Hide file tree
Showing 9 changed files with 195 additions and 20 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Released on tbd.
- Fixed computation of relative (`em`) values to absolute (`px`) for `Length` (#136)
- Added further compactification of CSS tuples (#89, #93)
- Added support for CSS nesting in style rules (#148)
- Added resolution of CSS variable names (#62)
- Added support for 8-digit hex color codes (#132)
- Added support for `margin-block` and `margin-inline` declarations
- Added support for `padding-block` and `padding-inline` declarations
Expand Down
6 changes: 3 additions & 3 deletions src/AngleSharp.Css.Tests/Declarations/CssVariables.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public void LegitVariableReferenceWithFallback()
Assert.IsNotNull(variable);
Assert.AreEqual(1, variable.References.Length);
Assert.AreEqual("--my-bar", variable.References[0].VariableName);
Assert.AreEqual("24px", variable.References[0].DefaultValue);
Assert.AreEqual("24px", variable.References[0].DefaultValue.CssText);
}

[Test]
Expand All @@ -72,7 +72,7 @@ public void LegitVariableReferenceWithFallbackContainingComma()
Assert.IsNotNull(variable);
Assert.AreEqual(1, variable.References.Length);
Assert.AreEqual("--color", variable.References[0].VariableName);
Assert.AreEqual("red, blue", variable.References[0].DefaultValue);
Assert.AreEqual("red, blue", variable.References[0].DefaultValue.CssText);
}

[Test]
Expand Down Expand Up @@ -113,7 +113,7 @@ public void LegitMultipleVariableReferenceInBorderShorthand()
Assert.AreEqual("--width", variable.References[0].VariableName);
Assert.IsNull(variable.References[0].DefaultValue);
Assert.AreEqual("--color", variable.References[1].VariableName);
Assert.AreEqual("black", variable.References[1].DefaultValue);
Assert.AreEqual("black", variable.References[1].DefaultValue.CssText);
}
}
}
88 changes: 88 additions & 0 deletions src/AngleSharp.Css.Tests/Extensions/AnalysisWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -308,5 +308,93 @@ public async Task ComputesAbsoluteValuesFromRelative_Issue136()
var style = sc.ComputeDeclarations(document.QuerySelector("span"));
Assert.AreEqual("24px", style.GetFontSize());
}

[Test]
public async Task ResolvesCssVariables_Issue62()
{
var sheet = ParseStyleSheet(@"
:root {
--color: #FFFFFF;
}
p {
color: var(--color);
}");
var document = await sheet.Context.OpenAsync(res => res.Content(@"<p>This is a test</p>"));
var sc = new StyleCollection(new[] { sheet }, new DefaultRenderDevice());
var style = sc.ComputeDeclarations(document.QuerySelector("p"));
Assert.AreEqual("rgba(255, 255, 255, 1)", style.GetColor());
}

[Test]
public async Task ResolvesCssVariablesWithUnusedFallback_Issue62()
{
var sheet = ParseStyleSheet(@"
:root {
--color: #FFFFFF;
}
p {
color: var(--color, green);
}");
var document = await sheet.Context.OpenAsync(res => res.Content(@"<p>This is a test</p>"));
var sc = new StyleCollection(new[] { sheet }, new DefaultRenderDevice());
var style = sc.ComputeDeclarations(document.QuerySelector("p"));
Assert.AreEqual("rgba(255, 255, 255, 1)", style.GetColor());
}

[Test]
public async Task ResolvesCssVariablesWithUsedFallback_Issue62()
{
var sheet = ParseStyleSheet(@"
:root {}
p {
color: var(--color, green);
}");
var document = await sheet.Context.OpenAsync(res => res.Content(@"<p>This is a test</p>"));
var sc = new StyleCollection(new[] { sheet }, new DefaultRenderDevice());
var style = sc.ComputeDeclarations(document.QuerySelector("p"));
Assert.AreEqual("rgba(0, 128, 0, 1)", style.GetColor());
}

[Test]
public async Task ResolvesCssVariablesWithUsedFallbackVarReference_Issue62()
{
var sheet = ParseStyleSheet(@"
:root {
--defaultColor: green;
}
p {
color: var(--color, var(--defaultColor));
}");
var document = await sheet.Context.OpenAsync(res => res.Content(@"<p>This is a test</p>"));
var sc = new StyleCollection(new[] { sheet }, new DefaultRenderDevice());
var style = sc.ComputeDeclarations(document.QuerySelector("p"));
Assert.AreEqual("rgba(0, 128, 0, 1)", style.GetColor());
}

[Test]
public async Task ResolvesCssVariablesWithCascade_Issue62()
{
var sheet = ParseStyleSheet(@"
:root {
--color: blue;
--defaultColor: red;
}
body {
--color: green;
}
p {
color: var(--color, var(--defaultColor));
}");
var document = await sheet.Context.OpenAsync(res => res.Content(@"<p>This is a test</p>"));
var sc = new StyleCollection(new[] { sheet }, new DefaultRenderDevice());
var style = sc.ComputeDeclarations(document.QuerySelector("p"));
Assert.AreEqual("rgba(0, 128, 0, 1)", style.GetColor());
}
}
}
29 changes: 27 additions & 2 deletions src/AngleSharp.Css/Dom/Internal/CssProperty.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ internal class CssProperty : ICssProperty

internal CssProperty(String name, IValueConverter converter, PropertyFlags flags = PropertyFlags.None, ICssValue value = null, Boolean important = false)
{
_name = name.ToLowerInvariant();
_name = name.StartsWith("--") ? name : name.ToLowerInvariant();
_converter = converter;
_flags = flags;
_value = value;
Expand Down Expand Up @@ -91,7 +91,8 @@ public Boolean IsImportant

public ICssProperty Compute(ICssComputeContext context)
{
var computedValue = _value?.Compute(context);
var propertyContext = new PropertyComputeContext(context, _converter);
var computedValue = _value?.Compute(propertyContext);

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

#endregion

#region Compute Context

sealed class PropertyComputeContext : ICssComputeContext
{
private readonly ICssComputeContext _parent;
private readonly IValueConverter _converter;

public PropertyComputeContext(ICssComputeContext parent, IValueConverter converter)
{
_parent = parent;
_converter = converter;
}

public IRenderDevice Device => _parent.Device;

public IBrowsingContext Context => _parent.Context;

public IValueConverter Converter => _converter;

public ICssValue Resolve(String name) =>_parent.Resolve(name);
}

#endregion

#region String Representation

public void ToCss(TextWriter writer, IStyleFormatter formatter) => writer.Write(formatter.Declaration(CssUtilities.Escape(Name), Value, IsImportant));
Expand Down
19 changes: 17 additions & 2 deletions src/AngleSharp.Css/Extensions/StyleCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ public static IStyleCollection GetStyleCollection(this IWindow window)
public static ICssStyleDeclaration ComputeDeclarations(this IStyleCollection styles, IElement element, String pseudoSelector = null)
{
var ctx = element.Owner?.Context;
var context = new CssComputeContext(styles.Device, ctx);
var computedStyle = new CssStyleDeclaration(ctx);
var context = new CssComputeContext(styles.Device, ctx, computedStyle);
var nodes = element.GetAncestors().OfType<IElement>();

if (!String.IsNullOrEmpty(pseudoSelector))
Expand Down Expand Up @@ -142,16 +142,31 @@ sealed class CssComputeContext : ICssComputeContext
{
private readonly IRenderDevice _device;
private readonly IBrowsingContext _context;
private readonly ICssProperties _properties;

public CssComputeContext(IRenderDevice device, IBrowsingContext context)
public CssComputeContext(IRenderDevice device, IBrowsingContext context, ICssProperties properties)
{
_device = device ?? new DefaultRenderDevice();
_context = context;
_properties = properties;
}

public IRenderDevice Device => _device;

public IBrowsingContext Context => _context;

public IValueConverter Converter => null;

public ICssValue Resolve(String name)
{
if (name.StartsWith("--"))
{
var property = _properties.FirstOrDefault(m => m.Name.Equals(name, StringComparison.Ordinal));
return property?.RawValue;
}

return null;
}
}

#endregion
Expand Down
23 changes: 20 additions & 3 deletions src/AngleSharp.Css/Parser/Micro/FunctionParser.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
namespace AngleSharp.Css.Parser
{
using AngleSharp.Css.Dom;
using AngleSharp.Css.Values;
using AngleSharp.Text;
using System;
Expand Down Expand Up @@ -93,22 +94,38 @@ public static CssVarValue ParseVar(this StringSource source)
var name = source.ParseCustomIdent();
var f = source.SkipGetSkip();

if (name != null)
if (name is not null)
{
switch (f)
{
case Symbols.RoundBracketClose:
return new CssVarValue(name);
case Symbols.Comma:
var defaultValue = source.TakeUntilClosed();
source.SkipCurrentAndSpaces();
source.SkipSpacesAndComments();
var defaultValue = ParseVarFallback(source);
return new CssVarValue(name, defaultValue);
}
}

return null;
}

/// <summary>
/// Parses a CSS var (variable) fallback value.
/// </summary>
public static ICssValue ParseVarFallback(this StringSource source)
{
if (!source.IsFunction(FunctionNames.Var))
{
var content = source.TakeUntilClosed();
source.SkipCurrentAndSpaces();
return new CssAnyValue(content);
}

return source.ParseVar();

}

/// <summary>
/// Parses a CSS content value.
/// </summary>
Expand Down
22 changes: 14 additions & 8 deletions src/AngleSharp.Css/Values/Functions/CssVarValue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public sealed class CssVarValue : ICssFunctionValue
#region Fields

private readonly String _variableName;
private readonly String _defaultValue;
private readonly ICssValue _defaultValue;

#endregion

Expand All @@ -24,7 +24,7 @@ public sealed class CssVarValue : ICssFunctionValue
/// </summary>
/// <param name="variableName">The name of the custom property.</param>
/// <param name="defaultValue">The fallback value, if any.</param>
public CssVarValue(String variableName, String defaultValue = null)
public CssVarValue(String variableName, ICssValue defaultValue = null)
{
_variableName = variableName;
_defaultValue = defaultValue;
Expand Down Expand Up @@ -53,7 +53,7 @@ public ICssValue[] Arguments

if (_defaultValue != null)
{
list.Add(new CssAnyValue(_defaultValue));
list.Add(_defaultValue);
}

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

/// <summary>
/// Gets the CSS text representation.
Expand All @@ -83,9 +83,9 @@ public String CssText
_variableName,
};

if (!String.IsNullOrEmpty(_defaultValue))
if (_defaultValue is not null)
{
args.Add(_defaultValue);
args.Add(_defaultValue.CssText);
}

return fn.CssFunction(String.Join(", ", args));
Expand All @@ -104,8 +104,14 @@ public String CssText
/// <returns>The resolved value or null.</returns>
public ICssValue Compute(ICssComputeContext context)
{
//return _expression.Compute(context);
return null;
var value = context.Resolve(_variableName)?.Compute(context);

if (value is not null)
{
return value;
}

return _defaultValue?.Compute(context);
}

#endregion
Expand Down
15 changes: 15 additions & 0 deletions src/AngleSharp.Css/Values/ICssComputeContext.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
namespace AngleSharp.Css.Values
{
using AngleSharp.Css.Dom;
using System;

/// <summary>
/// Defines the context for computing styles.
/// </summary>
Expand All @@ -14,5 +17,17 @@ public interface ICssComputeContext
/// Gets the associated browsing context.
/// </summary>
IBrowsingContext Context { get; }

/// <summary>
/// Gets the currently associated value converter.
/// </summary>
IValueConverter Converter { get; }

/// <summary>
/// Resolves a CSS variable by its name.
/// </summary>
/// <param name="name">The name of the variable.</param>
/// <returns>The value of the variable or null if no such variable exists.</returns>
ICssValue Resolve(String name);
}
}
12 changes: 10 additions & 2 deletions src/AngleSharp.Css/Values/Raws/CssAnyValue.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
namespace AngleSharp.Css.Values
{
using AngleSharp.Css.Converters;
using AngleSharp.Css.Dom;
using System;

Expand Down Expand Up @@ -45,8 +46,15 @@ public CssAnyValue(String text)

ICssValue ICssValue.Compute(ICssComputeContext context)
{
//TODO INVALID
return this;
var converter = context.Converter;

if (converter is not null && converter is not AnyValueConverter)
{
var value = converter.Convert(_text);
return value?.Compute(context);
}

return null;
}

#endregion
Expand Down

0 comments on commit 8295892

Please sign in to comment.