From 06b15e350a0a6b4b9469c7b81648b7ba0da7664b Mon Sep 17 00:00:00 2001 From: Florian Rappl Date: Wed, 17 Jan 2024 14:30:49 +0100 Subject: [PATCH] Updated nested implementation #148 --- .../Extensions/Nesting.cs | 77 +++++++++++++++++-- src/AngleSharp.Css/AngleSharp.Css.csproj | 2 +- .../Dom/Internal/ReferencedNestedSelector.cs | 29 +++++++ .../Dom/Internal/Rules/CssStyleRule.cs | 38 ++++++++- src/AngleSharp.Css/Parser/CssBuilder.cs | 13 ++++ .../AngleSharp.Performance.Css.csproj | 2 +- 6 files changed, 150 insertions(+), 11 deletions(-) create mode 100644 src/AngleSharp.Css/Dom/Internal/ReferencedNestedSelector.cs diff --git a/src/AngleSharp.Css.Tests/Extensions/Nesting.cs b/src/AngleSharp.Css.Tests/Extensions/Nesting.cs index d270236c..79955d60 100644 --- a/src/AngleSharp.Css.Tests/Extensions/Nesting.cs +++ b/src/AngleSharp.Css.Tests/Extensions/Nesting.cs @@ -9,25 +9,92 @@ namespace AngleSharp.Css.Tests.Extensions public class NestingTests { [Test] - public void SimpleSelectorNesting() + public void SimpleSelectorNestingImplicit() { var source = @"
Larger and green"; var document = ParseDocument(source); var window = document.DefaultView; - Assert.IsNotNull(document); + var element = document.QuerySelector(".bar"); + var style = window.GetComputedStyle(element); + Assert.AreEqual("1.4rem", style.GetFontSize()); + } + + [Test] + public void SimpleSelectorNestingExplicit() + { + var source = @"
Larger and green"; + var document = ParseDocument(source); + var window = document.DefaultView; var element = document.QuerySelector(".bar"); - Assert.IsNotNull(element); + var style = window.GetComputedStyle(element); + + Assert.AreEqual("1.4rem", style.GetFontSize()); + } + + [Test] + public void SimpleSelectorNestingOverwritten() + { + var source = @"
Larger and green"; + var document = ParseDocument(source); + var window = document.DefaultView; + var element = document.QuerySelector(".bar"); var style = window.GetComputedStyle(element); - Assert.IsNotNull(style); Assert.AreEqual("1.4rem", style.GetFontSize()); } + + [Test] + public void CombinedSelectorNesting() + { + var source = @"
green"; + var document = ParseDocument(source); + var window = document.DefaultView; + var element = document.QuerySelector(".bar"); + var style = window.GetComputedStyle(element); + + Assert.AreEqual("rgba(0, 128, 0, 1)", style.GetColor()); + } + + [Test] + public void ReversedSelectorNesting() + { + var source = @"
  • green"; + var document = ParseDocument(source); + var window = document.DefaultView; + var element = document.QuerySelector("li"); + var style = window.GetComputedStyle(element); + + Assert.AreEqual("rgba(0, 128, 0, 1)", style.GetColor()); + } } } diff --git a/src/AngleSharp.Css/AngleSharp.Css.csproj b/src/AngleSharp.Css/AngleSharp.Css.csproj index 7170241b..a821523d 100644 --- a/src/AngleSharp.Css/AngleSharp.Css.csproj +++ b/src/AngleSharp.Css/AngleSharp.Css.csproj @@ -21,7 +21,7 @@ - + diff --git a/src/AngleSharp.Css/Dom/Internal/ReferencedNestedSelector.cs b/src/AngleSharp.Css/Dom/Internal/ReferencedNestedSelector.cs new file mode 100644 index 00000000..771cf08c --- /dev/null +++ b/src/AngleSharp.Css/Dom/Internal/ReferencedNestedSelector.cs @@ -0,0 +1,29 @@ +namespace AngleSharp.Css.Dom +{ + using AngleSharp.Dom; + using System; + + internal class ReferencedNestedSelector : ISelector + { + private readonly ISelector _referenced; + + public ReferencedNestedSelector(ISelector referenced) + { + _referenced = referenced; + } + + public String Text => "&"; + + public Priority Specificity => _referenced.Specificity; + + public void Accept(ISelectorVisitor visitor) + { + // Right now we have nothing here. + } + + public Boolean Match(IElement element, IElement scope) + { + return _referenced.Match(element, scope); + } + } +} diff --git a/src/AngleSharp.Css/Dom/Internal/Rules/CssStyleRule.cs b/src/AngleSharp.Css/Dom/Internal/Rules/CssStyleRule.cs index 60901066..b9b9a135 100644 --- a/src/AngleSharp.Css/Dom/Internal/Rules/CssStyleRule.cs +++ b/src/AngleSharp.Css/Dom/Internal/Rules/CssStyleRule.cs @@ -20,6 +20,7 @@ sealed class CssStyleRule : CssRule, ICssStyleRule, ISelectorVisitor private readonly CssRuleList _rules; private ISelector _selector; private IEnumerable _selectorList; + private Boolean _nested; #endregion @@ -37,6 +38,12 @@ internal CssStyleRule(ICssStyleSheet owner) #region Properties + public Boolean IsNested + { + get => _nested; + set => _nested = value; + } + public ISelector Selector { get => _selector; @@ -85,13 +92,37 @@ protected override void ReplaceWith(ICssRule rule) public Boolean TryMatch(IElement element, IElement? scope, out Priority specificity) { + specificity = Priority.Zero; + scope ??= element?.Owner!.DocumentElement; + + if (!_nested && Parent is CssStyleRule parent) + { + var pe = element; + + do + { + if (pe == scope) + { + return false; + } + + pe = pe.ParentElement; + + if (pe is null) + { + return false; + } + } + while (!parent.TryMatch(pe, scope, out specificity)); + } + if (_selectorList is not null) { foreach (var selector in _selectorList.OrderByDescending(m => m.Specificity)) { if (selector.Match(element, scope)) { - specificity = selector.Specificity; + specificity += selector.Specificity; return true; } } @@ -99,11 +130,10 @@ public Boolean TryMatch(IElement element, IElement? scope, out Priority specific if (_selector is not null && _selector.Match(element, scope)) { - specificity = _selector.Specificity; + specificity += _selector.Specificity; return true; } - specificity = default; return false; } @@ -117,7 +147,7 @@ public override void ToCss(TextWriter writer, IStyleFormatter formatter) #region Selector - private void ChangeSelector(ISelector value) + internal void ChangeSelector(ISelector value) { _selectorList = null; _selector = value; diff --git a/src/AngleSharp.Css/Parser/CssBuilder.cs b/src/AngleSharp.Css/Parser/CssBuilder.cs index 78a342e4..66ccf126 100644 --- a/src/AngleSharp.Css/Parser/CssBuilder.cs +++ b/src/AngleSharp.Css/Parser/CssBuilder.cs @@ -526,8 +526,21 @@ public void CreateDeclarationWith(ICssProperties properties, ref CssToken token) if (!_options.IsExcludingNesting && token.IsPotentiallyNested() && properties is ICssStyleDeclaration decl && decl.Parent is CssStyleRule style) { + var factory = _context.GetService(); var rule = new CssStyleRule(style.Owner); + var previous = factory.Unregister("&"); + factory.Register("&", (_, _, _, _) => + { + rule.IsNested = true; + return new ReferencedNestedSelector(style.Selector); + }); var result = CreateStyle(rule, token); + factory.Unregister("&"); + + if (previous is not null) + { + factory.Register("&", previous); + } if (result is not null) { diff --git a/src/AngleSharp.Performance.Css/AngleSharp.Performance.Css.csproj b/src/AngleSharp.Performance.Css/AngleSharp.Performance.Css.csproj index dd1ffa2f..6b9a1aba 100644 --- a/src/AngleSharp.Performance.Css/AngleSharp.Performance.Css.csproj +++ b/src/AngleSharp.Performance.Css/AngleSharp.Performance.Css.csproj @@ -21,7 +21,7 @@ - + \ No newline at end of file