From 2a2f5da39f9e243eda66e84be9c9e16538886f49 Mon Sep 17 00:00:00 2001 From: Florian Rappl Date: Thu, 16 Feb 2023 09:02:12 +0100 Subject: [PATCH 01/53] Empty string behaves like null --- src/AngleSharp.Css/Dom/Internal/MediaList.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/AngleSharp.Css/Dom/Internal/MediaList.cs b/src/AngleSharp.Css/Dom/Internal/MediaList.cs index 215b4716..d7b4c942 100644 --- a/src/AngleSharp.Css/Dom/Internal/MediaList.cs +++ b/src/AngleSharp.Css/Dom/Internal/MediaList.cs @@ -56,7 +56,8 @@ public String MediaText public void SetMediaText(String value, Boolean throwOnError) { _media.Clear(); - var media = MediaParser.Parse(value ?? CssKeywords.All, ValidatorFactory); + var v = String.IsNullOrEmpty(value) ? CssKeywords.All : value; + var media = MediaParser.Parse(v, ValidatorFactory); if (media != null) { From 8ecae0825171767cb486d240ec650b6d5aba2134 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jochen=20K=C3=BChner?= Date: Thu, 20 Apr 2023 06:50:44 +0200 Subject: [PATCH 02/53] Update ColorParser.cs fix #134 --- src/AngleSharp.Css/Parser/Micro/ColorParser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AngleSharp.Css/Parser/Micro/ColorParser.cs b/src/AngleSharp.Css/Parser/Micro/ColorParser.cs index bffb1b37..8a679951 100644 --- a/src/AngleSharp.Css/Parser/Micro/ColorParser.cs +++ b/src/AngleSharp.Css/Parser/Micro/ColorParser.cs @@ -214,7 +214,7 @@ static class ColorParser { if (unit.Dimension == "%") { - return (Byte)(255f / 100f * value); + return (Byte)((255f * value) / 100f); } else if (unit.Dimension == String.Empty) { From 6ab71660fa3645d7f97281e6104537e6fae1e652 Mon Sep 17 00:00:00 2001 From: Florian Rappl Date: Tue, 6 Jun 2023 13:24:11 +0200 Subject: [PATCH 03/53] WIP --- .../Rules/CssMediaList.cs | 52 ++++++++++++++++++- src/AngleSharp.Css.nuspec | 2 +- src/AngleSharp.Css/AngleSharp.Css.csproj | 2 +- src/AngleSharp.Css/Dom/Internal/MediaList.cs | 19 +++---- .../Dom/Internal/PseudoElement.cs | 2 + .../Parser/Micro/MediaParser.cs | 9 +--- .../Parser/Micro/MediumParser.cs | 26 +++++++--- .../AngleSharp.Performance.Css.csproj | 2 +- 8 files changed, 84 insertions(+), 30 deletions(-) diff --git a/src/AngleSharp.Css.Tests/Rules/CssMediaList.cs b/src/AngleSharp.Css.Tests/Rules/CssMediaList.cs index b8cd8cf4..9bf6611d 100644 --- a/src/AngleSharp.Css.Tests/Rules/CssMediaList.cs +++ b/src/AngleSharp.Css.Tests/Rules/CssMediaList.cs @@ -1,4 +1,4 @@ -namespace AngleSharp.Css.Tests.Rules +namespace AngleSharp.Css.Tests.Rules { using AngleSharp.Css.Dom; using NUnit.Framework; @@ -187,7 +187,7 @@ public void NoMediaQueryGivenSkip() Assert.AreEqual(1, sheet.Rules.Length); Assert.AreEqual(CssRuleType.Media, sheet.Rules[0].Type); var media = sheet.Rules[0] as ICssMediaRule; - Assert.AreEqual("not all", media.ConditionText); + Assert.AreEqual("", media.ConditionText); Assert.AreEqual(1, media.Rules.Length); } @@ -393,5 +393,53 @@ public void CssMediaListApiWithAppendDeleteAndTextShouldWork() Assert.AreEqual(media[2], list[1]); Assert.AreEqual(String.Concat(media[0], ", ", media[2]), list.MediaText); } + + [Test] + public void ReplacesInvalidPartsCommaWithNotAll() + { + var source = @"@media (example, all,), speech { + h1 { color: green } +}"; + var sheet = ParseStyleSheet(source); + Assert.AreEqual(1, sheet.Rules.Length); + Assert.IsInstanceOf(sheet.Rules[0]); + var media = (CssMediaRule)sheet.Rules[0]; + Assert.AreEqual("not all, speech", media.Media.MediaText); + var list = media.Media; + Assert.AreEqual(2, list.Length); + Assert.AreEqual(1, media.Rules.Length); + } + + [Test] + public void ReplacesInvalidPartsAmpersandWithNotAll() + { + var source = @"@media test&, speech { + h1 { color: green } +}"; + var sheet = ParseStyleSheet(source); + Assert.AreEqual(1, sheet.Rules.Length); + Assert.IsInstanceOf(sheet.Rules[0]); + var media = (CssMediaRule)sheet.Rules[0]; + Assert.AreEqual("not all, speech", media.Media.MediaText); + var list = media.Media; + Assert.AreEqual(2, list.Length); + Assert.AreEqual(1, media.Rules.Length); + } + + [Test] + public void ReplacesUnclosedParansWithNotAll() + { + var source = @"@media (example, speech { + h1 { color: green } +}"; + var sheet = ParseStyleSheet(source); + Assert.AreEqual(1, sheet.Rules.Length); + Assert.IsInstanceOf(sheet.Rules[0]); + var media = (CssMediaRule)sheet.Rules[0]; + Assert.AreEqual("not all", media.Media.MediaText); + var list = media.Media; + Assert.AreEqual(1, list.Length); + Assert.AreEqual(1, media.Rules.Length); + } } } diff --git a/src/AngleSharp.Css.nuspec b/src/AngleSharp.Css.nuspec index ae1d28b0..55da3364 100644 --- a/src/AngleSharp.Css.nuspec +++ b/src/AngleSharp.Css.nuspec @@ -15,7 +15,7 @@ Copyright 2016-2023, AngleSharp html html5 css css3 dom styling library anglesharp angle - + diff --git a/src/AngleSharp.Css/AngleSharp.Css.csproj b/src/AngleSharp.Css/AngleSharp.Css.csproj index 5422d00f..048ac0d0 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/MediaList.cs b/src/AngleSharp.Css/Dom/Internal/MediaList.cs index d7b4c942..8856ff45 100644 --- a/src/AngleSharp.Css/Dom/Internal/MediaList.cs +++ b/src/AngleSharp.Css/Dom/Internal/MediaList.cs @@ -6,6 +6,8 @@ namespace AngleSharp.Css.Dom using System.Collections; using System.Collections.Generic; using System.IO; + using System.Linq; + using System.Text; /// /// Represents a list of media elements. @@ -17,6 +19,8 @@ sealed class MediaList : IMediaList private readonly IBrowsingContext _context; private readonly List _media; + private static readonly CssMedium replacementMedium = new CssMedium(CssKeywords.All, inverse: true, exclusive: false); + #endregion #region ctor @@ -56,22 +60,15 @@ public String MediaText public void SetMediaText(String value, Boolean throwOnError) { _media.Clear(); - var v = String.IsNullOrEmpty(value) ? CssKeywords.All : value; - var media = MediaParser.Parse(v, ValidatorFactory); + var v = String.IsNullOrEmpty(value) ? String.Empty : value; + var media = MediaParser.Parse(v, ValidatorFactory) ?? Enumerable.Repeat(null, 1); - if (media != null) - { - _media.AddRange(media); - } - else if (throwOnError) + if (throwOnError && media.Contains(null)) { throw new DomException(DomError.Syntax); } - if (_media.Count == 0) - { - _media.Add(new CssMedium(CssKeywords.All, inverse: true, exclusive: false)); - } + _media.AddRange(media.Select(m => m ?? replacementMedium)); } public void Add(String newMedium) diff --git a/src/AngleSharp.Css/Dom/Internal/PseudoElement.cs b/src/AngleSharp.Css/Dom/Internal/PseudoElement.cs index ac376292..00dcd530 100644 --- a/src/AngleSharp.Css/Dom/Internal/PseudoElement.cs +++ b/src/AngleSharp.Css/Dom/Internal/PseudoElement.cs @@ -53,6 +53,8 @@ public String Slot public ITokenList ClassList => _host.ClassList; + public String GivenNamespaceUri => _host.GivenNamespaceUri; + public String ClassName { get => _host.ClassName; diff --git a/src/AngleSharp.Css/Parser/Micro/MediaParser.cs b/src/AngleSharp.Css/Parser/Micro/MediaParser.cs index d549bf53..83c6f08d 100644 --- a/src/AngleSharp.Css/Parser/Micro/MediaParser.cs +++ b/src/AngleSharp.Css/Parser/Micro/MediaParser.cs @@ -37,14 +37,7 @@ public static IEnumerable ParseMedia(this StringSource source, IFeatu source.SkipCurrentAndSpaces(); } - var medium = source.ParseMedium(factory); - - if (medium == null) - { - return null; - } - - media.Add(medium); + media.Add(source.ParseMedium(factory)); current = source.SkipSpacesAndComments(); } diff --git a/src/AngleSharp.Css/Parser/Micro/MediumParser.cs b/src/AngleSharp.Css/Parser/Micro/MediumParser.cs index b1222c37..2909e60f 100644 --- a/src/AngleSharp.Css/Parser/Micro/MediumParser.cs +++ b/src/AngleSharp.Css/Parser/Micro/MediumParser.cs @@ -23,7 +23,7 @@ internal static CssMedium Parse(String str, IFeatureValidatorFactory factory) public static CssMedium ParseMedium(this StringSource source, IFeatureValidatorFactory factory) { source.SkipSpacesAndComments(); - var ident = source.ParseIdent(); + var ident = source.ParseMediumIdent(); var inverse = false; var exclusive = false; var type = String.Empty; @@ -34,13 +34,13 @@ public static CssMedium ParseMedium(this StringSource source, IFeatureValidatorF { inverse = true; source.SkipSpacesAndComments(); - ident = source.ParseIdent(); + ident = source.ParseMediumIdent(); } else if (ident.Isi(CssKeywords.Only)) { exclusive = true; source.SkipSpacesAndComments(); - ident = source.ParseIdent(); + ident = source.ParseMediumIdent(); } } @@ -49,7 +49,7 @@ public static CssMedium ParseMedium(this StringSource source, IFeatureValidatorF type = ident; source.SkipSpacesAndComments(); var position = source.Index; - ident = source.ParseIdent(); + ident = source.ParseMediumIdent(); if (ident == null || !ident.Isi(CssKeywords.And)) { @@ -81,7 +81,7 @@ public static CssMedium ParseMedium(this StringSource source, IFeatureValidatorF features.Add(feature); source.SkipCurrentAndSpaces(); var position = source.Index; - ident = source.ParseIdent(); + ident = source.ParseMediumIdent(); if (ident == null || !ident.Isi(CssKeywords.And)) { @@ -96,9 +96,23 @@ public static CssMedium ParseMedium(this StringSource source, IFeatureValidatorF return new CssMedium(type, inverse, exclusive, features); } + private static String ParseMediumIdent(this StringSource source) + { + var ident = source.ParseIdent(); + var current = source.Current; + + while (current == '&' || current == '#' || current == ';' || current == '-') + { + ident = null; + current = source.Next(); + } + + return ident; + } + private static IMediaFeature ParseFeature(StringSource source) { - var name = source.ParseIdent(); + var name = source.ParseMediumIdent(); var value = default(String); if (name != null) diff --git a/src/AngleSharp.Performance.Css/AngleSharp.Performance.Css.csproj b/src/AngleSharp.Performance.Css/AngleSharp.Performance.Css.csproj index 5a192d2e..d85242ba 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 From c8321b81d45a62bea6735c59d0e8bb7f155b791b Mon Sep 17 00:00:00 2001 From: Florian Rappl Date: Sat, 10 Jun 2023 17:52:35 +0200 Subject: [PATCH 04/53] Working on new media list parser --- .../AngleSharp.Css.Tests.csproj | 2 +- src/AngleSharp.Css.Tests/Styling/CssCases.cs | 56 ++-- src/AngleSharp.Css/AngleSharp.Css.csproj | 2 +- src/AngleSharp.Css/Dom/Internal/CssMedium.cs | 27 +- .../Dom/Internal/MediaFeature.cs | 12 +- src/AngleSharp.Css/Dom/Internal/MediaList.cs | 1 - .../Parser/Micro/MediumParser.cs | 239 ++++++++++++++++++ .../AngleSharp.Performance.Css.csproj | 2 +- 8 files changed, 297 insertions(+), 44 deletions(-) diff --git a/src/AngleSharp.Css.Tests/AngleSharp.Css.Tests.csproj b/src/AngleSharp.Css.Tests/AngleSharp.Css.Tests.csproj index 8bf7df15..b61b451a 100644 --- a/src/AngleSharp.Css.Tests/AngleSharp.Css.Tests.csproj +++ b/src/AngleSharp.Css.Tests/AngleSharp.Css.Tests.csproj @@ -16,7 +16,7 @@ - + diff --git a/src/AngleSharp.Css.Tests/Styling/CssCases.cs b/src/AngleSharp.Css.Tests/Styling/CssCases.cs index cf46959b..2e7d8bb5 100644 --- a/src/AngleSharp.Css.Tests/Styling/CssCases.cs +++ b/src/AngleSharp.Css.Tests/Styling/CssCases.cs @@ -319,9 +319,9 @@ public void StyleSheetEscapes() /*#\{\}{background:lime;}*/"); Assert.AreEqual(42, sheet.Rules.Length); - Assert.AreEqual(@".:`(", ((ICssStyleRule)sheet.Rules[0]).SelectorText); - Assert.AreEqual(@".1a2b3c", ((ICssStyleRule)sheet.Rules[1]).SelectorText); - Assert.AreEqual(@"##fake-id", ((ICssStyleRule)sheet.Rules[2]).SelectorText); + Assert.AreEqual(@".\:\`\(", ((ICssStyleRule)sheet.Rules[0]).SelectorText); + Assert.AreEqual(@".\31 a2b3c", ((ICssStyleRule)sheet.Rules[1]).SelectorText); + Assert.AreEqual(@"#\#fake-id", ((ICssStyleRule)sheet.Rules[2]).SelectorText); Assert.AreEqual(@"#---", ((ICssStyleRule)sheet.Rules[3]).SelectorText); Assert.AreEqual(@"#-a-b-c-", ((ICssStyleRule)sheet.Rules[4]).SelectorText); Assert.AreEqual(@"#©", ((ICssStyleRule)sheet.Rules[5]).SelectorText); @@ -346,57 +346,57 @@ public void StyleSheetEscapes() Assert.AreEqual(@"rgba(0, 255, 0, 1)", ((ICssStyleRule)sheet.Rules[14]).Style["background"]); Assert.AreEqual(@"#𝄞♪♩♫♬", ((ICssStyleRule)sheet.Rules[15]).SelectorText); Assert.AreEqual(@"rgba(0, 255, 0, 1)", ((ICssStyleRule)sheet.Rules[15]).Style["background"]); - Assert.AreEqual(@"#?", ((ICssStyleRule)sheet.Rules[16]).SelectorText); + Assert.AreEqual(@"#\?", ((ICssStyleRule)sheet.Rules[16]).SelectorText); Assert.AreEqual(@"rgba(0, 255, 0, 1)", ((ICssStyleRule)sheet.Rules[16]).Style["background"]); - Assert.AreEqual(@"#@", ((ICssStyleRule)sheet.Rules[17]).SelectorText); + Assert.AreEqual(@"#\@", ((ICssStyleRule)sheet.Rules[17]).SelectorText); Assert.AreEqual(@"rgba(0, 255, 0, 1)", ((ICssStyleRule)sheet.Rules[17]).Style["background"]); - Assert.AreEqual(@"#.", ((ICssStyleRule)sheet.Rules[18]).SelectorText); + Assert.AreEqual(@"#\.", ((ICssStyleRule)sheet.Rules[18]).SelectorText); Assert.AreEqual(@"rgba(0, 255, 0, 1)", ((ICssStyleRule)sheet.Rules[18]).Style["background"]); - Assert.AreEqual(@"#:)", ((ICssStyleRule)sheet.Rules[19]).SelectorText); + Assert.AreEqual(@"#\:\)", ((ICssStyleRule)sheet.Rules[19]).SelectorText); Assert.AreEqual(@"rgba(0, 255, 0, 1)", ((ICssStyleRule)sheet.Rules[19]).Style["background"]); - Assert.AreEqual(@"#:`(", ((ICssStyleRule)sheet.Rules[20]).SelectorText); + Assert.AreEqual(@"#\:\`\(", ((ICssStyleRule)sheet.Rules[20]).SelectorText); Assert.AreEqual(@"rgba(0, 255, 0, 1)", ((ICssStyleRule)sheet.Rules[20]).Style["background"]); - Assert.AreEqual(@"#123", ((ICssStyleRule)sheet.Rules[21]).SelectorText); + Assert.AreEqual(@"#\31 23", ((ICssStyleRule)sheet.Rules[21]).SelectorText); Assert.AreEqual(@"rgba(0, 255, 0, 1)", ((ICssStyleRule)sheet.Rules[21]).Style["background"]); - Assert.AreEqual(@"#1a2b3c", ((ICssStyleRule)sheet.Rules[22]).SelectorText); + Assert.AreEqual(@"#\31 a2b3c", ((ICssStyleRule)sheet.Rules[22]).SelectorText); Assert.AreEqual(@"rgba(0, 255, 0, 1)", ((ICssStyleRule)sheet.Rules[22]).Style["background"]); - Assert.AreEqual(@"#

", ((ICssStyleRule)sheet.Rules[23]).SelectorText); + Assert.AreEqual(@"#\", ((ICssStyleRule)sheet.Rules[23]).SelectorText); Assert.AreEqual(@"rgba(0, 255, 0, 1)", ((ICssStyleRule)sheet.Rules[23]).Style["background"]); - Assert.AreEqual(@"#<><<<>><>", ((ICssStyleRule)sheet.Rules[24]).SelectorText); + Assert.AreEqual(@"#\<\>\<\<\<\>\>\<\>", ((ICssStyleRule)sheet.Rules[24]).SelectorText); Assert.AreEqual(@"rgba(0, 255, 0, 1)", ((ICssStyleRule)sheet.Rules[24]).Style["background"]); - Assert.AreEqual(@"#++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.------.--------.>+.>.", ((ICssStyleRule)sheet.Rules[25]).SelectorText); + Assert.AreEqual("#\\+\\+\\+\\+\\+\\+\\+\\+\\+\\+\\[\\>\\+\\+\\+\\+\\+\\+\\+\\>\\+\\+\\+\\+\\+\\+\\+\\+\\+\\+\\>\\+\\+\\+\\>\\+\\<\\<\\<\\<-\\]\\>\\+\\+\\.\\>\\+\\.\\+\\+\\+\\+\\+\\+\\+\\.\\.\\+\\+\\+\\.\\>\\+\\+\\.\\<\\<\\+\\+\\+\\+\\+\\+\\+\\+\\+\\+\\+\\+\\+\\+\\+\\.\\>\\.\\+\\+\\+\\.------\\.--------\\.\\>\\+\\.\\>\\.", ((ICssStyleRule)sheet.Rules[25]).SelectorText); Assert.AreEqual(@"rgba(0, 255, 0, 1)", ((ICssStyleRule)sheet.Rules[25]).Style["background"]); - Assert.AreEqual(@"##", ((ICssStyleRule)sheet.Rules[26]).SelectorText); + Assert.AreEqual(@"#\#", ((ICssStyleRule)sheet.Rules[26]).SelectorText); Assert.AreEqual(@"rgba(0, 255, 0, 1)", ((ICssStyleRule)sheet.Rules[26]).Style["background"]); - Assert.AreEqual(@"###", ((ICssStyleRule)sheet.Rules[27]).SelectorText); + Assert.AreEqual(@"#\#\#", ((ICssStyleRule)sheet.Rules[27]).SelectorText); Assert.AreEqual(@"rgba(0, 255, 0, 1)", ((ICssStyleRule)sheet.Rules[27]).Style["background"]); - Assert.AreEqual(@"##.#.#", ((ICssStyleRule)sheet.Rules[28]).SelectorText); + Assert.AreEqual(@"#\#\.\#\.\#", ((ICssStyleRule)sheet.Rules[28]).SelectorText); Assert.AreEqual(@"rgba(0, 255, 0, 1)", ((ICssStyleRule)sheet.Rules[28]).Style["background"]); Assert.AreEqual(@"#_", ((ICssStyleRule)sheet.Rules[29]).SelectorText); Assert.AreEqual(@"rgba(0, 255, 0, 1)", ((ICssStyleRule)sheet.Rules[29]).Style["background"]); - Assert.AreEqual(@"#.fake-class", ((ICssStyleRule)sheet.Rules[30]).SelectorText); + Assert.AreEqual(@"#\.fake-class", ((ICssStyleRule)sheet.Rules[30]).SelectorText); Assert.AreEqual(@"rgba(0, 255, 0, 1)", ((ICssStyleRule)sheet.Rules[30]).Style["background"]); - Assert.AreEqual(@"#foo.bar", ((ICssStyleRule)sheet.Rules[31]).SelectorText); + Assert.AreEqual(@"#foo\.bar", ((ICssStyleRule)sheet.Rules[31]).SelectorText); Assert.AreEqual(@"rgba(0, 255, 0, 1)", ((ICssStyleRule)sheet.Rules[31]).Style["background"]); - Assert.AreEqual(@"#:hover", ((ICssStyleRule)sheet.Rules[32]).SelectorText); + Assert.AreEqual(@"#\:hover", ((ICssStyleRule)sheet.Rules[32]).SelectorText); Assert.AreEqual(@"rgba(0, 255, 0, 1)", ((ICssStyleRule)sheet.Rules[32]).Style["background"]); - Assert.AreEqual(@"#:hover:focus:active", ((ICssStyleRule)sheet.Rules[33]).SelectorText); + Assert.AreEqual(@"#\:hover\:focus\:active", ((ICssStyleRule)sheet.Rules[33]).SelectorText); Assert.AreEqual(@"rgba(0, 255, 0, 1)", ((ICssStyleRule)sheet.Rules[33]).Style["background"]); - Assert.AreEqual(@"#[attr=value]", ((ICssStyleRule)sheet.Rules[34]).SelectorText); + Assert.AreEqual(@"#\[attr\=value\]", ((ICssStyleRule)sheet.Rules[34]).SelectorText); Assert.AreEqual(@"rgba(0, 255, 0, 1)", ((ICssStyleRule)sheet.Rules[34]).Style["background"]); - Assert.AreEqual(@"#f/o/o", ((ICssStyleRule)sheet.Rules[35]).SelectorText); + Assert.AreEqual(@"#f\/o\/o", ((ICssStyleRule)sheet.Rules[35]).SelectorText); Assert.AreEqual(@"rgba(0, 255, 0, 1)", ((ICssStyleRule)sheet.Rules[35]).Style["background"]); - Assert.AreEqual(@"#f\o\o", ((ICssStyleRule)sheet.Rules[36]).SelectorText); + Assert.AreEqual(@"#f\\o\\o", ((ICssStyleRule)sheet.Rules[36]).SelectorText); Assert.AreEqual(@"rgba(0, 255, 0, 1)", ((ICssStyleRule)sheet.Rules[36]).Style["background"]); - Assert.AreEqual(@"#f*o*o", ((ICssStyleRule)sheet.Rules[37]).SelectorText); + Assert.AreEqual(@"#f\*o\*o", ((ICssStyleRule)sheet.Rules[37]).SelectorText); Assert.AreEqual(@"rgba(0, 255, 0, 1)", ((ICssStyleRule)sheet.Rules[37]).Style["background"]); - Assert.AreEqual(@"#f!o!o", ((ICssStyleRule)sheet.Rules[38]).SelectorText); + Assert.AreEqual(@"#f\!o\!o", ((ICssStyleRule)sheet.Rules[38]).SelectorText); Assert.AreEqual(@"rgba(0, 255, 0, 1)", ((ICssStyleRule)sheet.Rules[38]).Style["background"]); - Assert.AreEqual(@"#f'o'o", ((ICssStyleRule)sheet.Rules[39]).SelectorText); + Assert.AreEqual(@"#f\'o\'o", ((ICssStyleRule)sheet.Rules[39]).SelectorText); Assert.AreEqual(@"rgba(0, 255, 0, 1)", ((ICssStyleRule)sheet.Rules[39]).Style["background"]); - Assert.AreEqual(@"#f~o~o", ((ICssStyleRule)sheet.Rules[40]).SelectorText); + Assert.AreEqual(@"#f\~o\~o", ((ICssStyleRule)sheet.Rules[40]).SelectorText); Assert.AreEqual(@"rgba(0, 255, 0, 1)", ((ICssStyleRule)sheet.Rules[40]).Style["background"]); - Assert.AreEqual(@"#f+o+o", ((ICssStyleRule)sheet.Rules[41]).SelectorText); + Assert.AreEqual(@"#f\+o\+o", ((ICssStyleRule)sheet.Rules[41]).SelectorText); Assert.AreEqual(@"rgba(0, 255, 0, 1)", ((ICssStyleRule)sheet.Rules[41]).Style["background"]); } diff --git a/src/AngleSharp.Css/AngleSharp.Css.csproj b/src/AngleSharp.Css/AngleSharp.Css.csproj index 048ac0d0..7170241b 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/CssMedium.cs b/src/AngleSharp.Css/Dom/Internal/CssMedium.cs index 8e50b2d7..67c744d6 100644 --- a/src/AngleSharp.Css/Dom/Internal/CssMedium.cs +++ b/src/AngleSharp.Css/Dom/Internal/CssMedium.cs @@ -18,6 +18,7 @@ public sealed class CssMedium : ICssMedium private readonly String _type; private readonly Boolean _exclusive; private readonly Boolean _inverse; + private readonly String _connector; #endregion @@ -30,7 +31,7 @@ public sealed class CssMedium : ICssMedium /// Specifies if it should be inverted. /// Specifies if the rule is exclusive. public CssMedium(String type, Boolean inverse, Boolean exclusive) - : this(type, inverse, exclusive, Enumerable.Empty()) + : this(type, inverse, exclusive, Enumerable.Empty(), "and") { } @@ -41,12 +42,14 @@ public CssMedium(String type, Boolean inverse, Boolean exclusive) /// Specifies if it should be inverted. /// Specifies if the rule is exclusive. /// The features of the medium. - public CssMedium(String type, Boolean inverse, Boolean exclusive, IEnumerable features) + /// The connector of the features ("and" or "or"). + public CssMedium(String type, Boolean inverse, Boolean exclusive, IEnumerable features, String connector) { _features = new List(features); _type = type; _inverse = inverse; _exclusive = exclusive; + _connector = connector; } #endregion @@ -63,6 +66,11 @@ public CssMedium(String type, Boolean inverse, Boolean exclusive, IEnumerable public String Type => _type; + ///

+ /// Gets the connector of the contained features. + /// + public String Connector => _connector; + /// /// Gets if the medium is exclusive to other media. /// @@ -76,7 +84,7 @@ public CssMedium(String type, Boolean inverse, Boolean exclusive, IEnumerable /// Gets the constraints - i.e., the stringified features. ///
- public String Constraints => String.Join(" and ", Features.Select(m => m.ToCss())); + public String Constraints => String.Join($" {_connector} ", Features.Select(m => m.ToCss())); #endregion @@ -87,10 +95,11 @@ public override Boolean Equals(Object obj) { var other = obj as CssMedium; - if (other != null && - other.IsExclusive == IsExclusive && - other.IsInverse == IsInverse && - other.Type.Is(Type) && + if (other != null && + other.IsExclusive == IsExclusive && + other.IsInverse == IsInverse && + other.Type.Is(Type) && + other.Connector.Is(Connector) && other.Features.Count() == Features.Count()) { foreach (var feature in other.Features) @@ -143,7 +152,9 @@ public void ToCss(TextWriter writer, IStyleFormatter formatter) for (var i = offset; i < _features.Count; i++) { - writer.Write(" and "); + writer.Write(' '); + writer.Write(_connector); + writer.Write(' '); _features[i].ToCss(writer, formatter); } } diff --git a/src/AngleSharp.Css/Dom/Internal/MediaFeature.cs b/src/AngleSharp.Css/Dom/Internal/MediaFeature.cs index ed97d792..b2b29d7b 100644 --- a/src/AngleSharp.Css/Dom/Internal/MediaFeature.cs +++ b/src/AngleSharp.Css/Dom/Internal/MediaFeature.cs @@ -14,13 +14,17 @@ sealed class MediaFeature : IMediaFeature private readonly Boolean _min; private readonly Boolean _max; private readonly String _name; - private readonly String _value; + private readonly ICssValue _value; #endregion #region ctor - internal MediaFeature(String name, String value) + internal MediaFeature(String name) + : this(name, null) + {} + + internal MediaFeature(String name, ICssValue value) { _name = name; _value = value; @@ -38,7 +42,7 @@ internal MediaFeature(String name, String value) public Boolean IsMaximum => _max; - public String Value => _value; + public String Value => _value?.CssText ?? String.Empty; public Boolean HasValue => _value != null; @@ -54,7 +58,7 @@ public void ToCss(TextWriter writer, IStyleFormatter formatter) if (_value != null) { writer.Write(": "); - writer.Write(_value); + writer.Write(Value); } writer.Write(Symbols.RoundBracketClose); diff --git a/src/AngleSharp.Css/Dom/Internal/MediaList.cs b/src/AngleSharp.Css/Dom/Internal/MediaList.cs index 8856ff45..42188e78 100644 --- a/src/AngleSharp.Css/Dom/Internal/MediaList.cs +++ b/src/AngleSharp.Css/Dom/Internal/MediaList.cs @@ -7,7 +7,6 @@ namespace AngleSharp.Css.Dom using System.Collections.Generic; using System.IO; using System.Linq; - using System.Text; /// /// Represents a list of media elements. diff --git a/src/AngleSharp.Css/Parser/Micro/MediumParser.cs b/src/AngleSharp.Css/Parser/Micro/MediumParser.cs index 2909e60f..d60603a6 100644 --- a/src/AngleSharp.Css/Parser/Micro/MediumParser.cs +++ b/src/AngleSharp.Css/Parser/Micro/MediumParser.cs @@ -4,6 +4,7 @@ namespace AngleSharp.Css.Parser using AngleSharp.Text; using System; using System.Collections.Generic; + using System.Linq; /// /// Represents extensions to for medium (media type) values. @@ -96,6 +97,244 @@ public static CssMedium ParseMedium(this StringSource source, IFeatureValidatorF return new CssMedium(type, inverse, exclusive, features); } + /// + /// Parses a medium value. + /// + public static CssMedium ParseMediumNew(this StringSource source, IFeatureValidatorFactory factory) + { + // = | + source.SkipSpacesAndComments(); + return source.ParseMediaCondition() ?? source.ParseMediaType(); + } + + private static CssMedium ParseMediaCondition(this StringSource source) + { + // = | [ * | * ] + + var medium = source.ParseMediaNot(); + + if (medium is null) + { + medium = source.ParseMediaInParens(); + + if (medium != null) + { + var other = source.ParseMediaAnd(); + //TODO + } + } + + return medium; + } + + private static CssMedium ParseMediaConditionWithoutOr(this StringSource source) + { + // = | * + + var medium = source.ParseMediaNot(); + + if (medium is null) + { + medium = source.ParseMediaInParens(); + + if (medium is not null) + { + do + { + source.SkipSpacesAndComments(); + var other = source.ParseMediaAnd(); + + if (other is null) + { + break; + } + + medium = new CssMedium(medium.Type, medium.IsInverse, medium.IsExclusive, medium.Features.Concat(other.Features)); + } + while (!source.IsDone); + } + } + + return medium; + } + + private static CssMedium ParseMediaNot(this StringSource source) + { + // = not + + if (source.IsIdentifier("not")) + { + var pos = source.Index; + source.ParseIdent(); + source.SkipCurrentAndSpaces(); + var medium = source.ParseMediaInParens(); + + if (medium is not null) + { + source.SkipSpacesAndComments(); + return new CssMedium(medium.Type, !medium.IsInverse, medium.IsExclusive, medium.Features); + } + + source.BackTo(pos); + } + + return null; + } + + private static CssMedium ParseMediaInParens(this StringSource source) + { + // = ( ) | | + + if (source.Current == '(') + { + var pos = source.Index; + source.SkipCurrentAndSpaces(); + var medium = source.ParseMediaCondition(); + + if (medium is not null && source.Current == ')') + { + source.SkipCurrentAndSpaces(); + return medium; + } + + source.BackTo(pos); + } + + return source.ParseMediaFeature() ?? source.ParseGeneralEnclosed(); + } + + private static CssMedium ParseMediaAnd(this StringSource source) + { + // = and + + if (source.IsIdentifier("and")) + { + var pos = source.Index; + source.ParseIdent(); + source.SkipCurrentAndSpaces(); + var medium = source.ParseMediaInParens(); + + if (medium is not null) + { + source.SkipSpacesAndComments(); + return new CssMedium(medium.Type, medium.IsInverse, medium.IsExclusive, medium.Features, "and"); + } + + source.BackTo(pos); + } + + return null; + } + + private static CssMedium ParseMediaOr(this StringSource source) + { + // = or + + if (source.IsIdentifier("or")) + { + var pos = source.Index; + source.ParseIdent(); + source.SkipCurrentAndSpaces(); + var medium = source.ParseMediaInParens(); + + if (medium is not null) + { + source.SkipSpacesAndComments(); + return new CssMedium(medium.Type, medium.IsInverse, medium.IsExclusive, medium.Features, "or"); + } + + source.BackTo(pos); + } + + return null; + } + + private static CssMedium ParseMediaType(this StringSource source) + { + // = [ not | only ]? [ and ]? + //TODO + return null; + } + + private static CssMedium ParseMediaFeature(this StringSource source) + { + // = ( [ | | ] ) + + if (source.Current == '(') + { + var feature = source.ParseMediaFeaturePlain() ?? source.ParseMediaFeatureBoolean() ?? source.ParseMediaFeatureRange(); + + if (feature is not null && source.Current == ')') + { + source.SkipCurrentAndSpaces(); + return new CssMedium("", false, false, Enumerable.Repeat(feature, 1), "and"); + } + } + + return null; + } + + private static CssMedium ParseGeneralEnclosed(this StringSource source) + { + // = [ ) ] | ( ) + //TODO + return null; + } + + private static MediaFeature ParseMediaFeaturePlain(this StringSource source) + { + // = : + var pos = source.Index; + var ident = source.ParseIdent(); + + if (ident is not null && source.Current == ':') + { + var value = source.ParseMediaFeatureValue(); + + if (value is not null) + { + source.SkipSpacesAndComments(); + return new MediaFeature(ident, value); + } + + source.BackTo(pos); + } + + return null; + } + + private static MediaFeature ParseMediaFeatureBoolean(this StringSource source) + { + // = + var ident = source.ParseIdent(); + + if (ident is not null) + { + source.SkipSpacesAndComments(); + return new MediaFeature(ident); + } + + return null; + } + + private static MediaFeature ParseMediaFeatureRange(this StringSource source) + { + // = '<' '='? | '>' '='? | '=' + // | '<' '='? | '>' '='? | '=' + // | '<' '='? '<' '='? + // | '>' '='? '>' '='? + //TODO + return null; + } + + private static ICssValue ParseMediaFeatureValue(this StringSource source) + { + // = | | | + //TODO + var ident = source.ParseNumber() ?? source.ParseIdent() ?? source.ParseUnit() ?? source.ParseRatio(); + return null; + } + private static String ParseMediumIdent(this StringSource source) { var ident = source.ParseIdent(); diff --git a/src/AngleSharp.Performance.Css/AngleSharp.Performance.Css.csproj b/src/AngleSharp.Performance.Css/AngleSharp.Performance.Css.csproj index d85242ba..dd1ffa2f 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 From 3b89d01339efd4ce8ca8eac47f82e65184da75b5 Mon Sep 17 00:00:00 2001 From: Florian Rappl Date: Wed, 14 Jun 2023 13:45:05 +0200 Subject: [PATCH 05/53] Fixed serialization due to broken longhands #129 --- CHANGELOG.md | 7 +++++++ .../Library/StringRepresentation.cs | 12 ++++++++++++ .../Dom/Internal/CssStyleDeclaration.cs | 16 +++++++++++++--- 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ff21124..458708fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# 1.0.0 + +Released on tbd. + +- Updated to use AngleSharp 1.0 +- Fixed issue when updating shorthands with invalid values (#129) + # 0.17.0 Released on Sunday, January 15 2023. diff --git a/src/AngleSharp.Css.Tests/Library/StringRepresentation.cs b/src/AngleSharp.Css.Tests/Library/StringRepresentation.cs index 8aae59f3..c5087b4e 100644 --- a/src/AngleSharp.Css.Tests/Library/StringRepresentation.cs +++ b/src/AngleSharp.Css.Tests/Library/StringRepresentation.cs @@ -105,5 +105,17 @@ public void EscapePropertyNames_UnknownDeclaration_Issue120() Assert.AreEqual(css, generatedCss); } + + [Test] + public void BorderWithEmptyPx_Issue129() + { + var html = "
"; + var dom = html.ToHtmlDocument(Configuration.Default.WithCss()); + var div = dom.Body?.FirstElementChild; + var style = div.GetStyle(); + var css = style.ToCss(); + + Assert.AreEqual("border-width: 1px", css); + } } } diff --git a/src/AngleSharp.Css/Dom/Internal/CssStyleDeclaration.cs b/src/AngleSharp.Css/Dom/Internal/CssStyleDeclaration.cs index 05c51c0b..01cb51c9 100644 --- a/src/AngleSharp.Css/Dom/Internal/CssStyleDeclaration.cs +++ b/src/AngleSharp.Css/Dom/Internal/CssStyleDeclaration.cs @@ -134,7 +134,7 @@ private ICssProperty TryCreateShorthand(String shorthandName, IEnumerable m.Name == name).FirstOrDefault(); - if (property != null) + if (property?.Value is not null) { usedProperties.Add(name); count = count + 1; @@ -318,8 +318,18 @@ internal void UpdateDeclarations(IEnumerable decls) => private ICssProperty GetPropertyShorthand(String name) => TryCreateShorthand(name, Enumerable.Empty(), new List(), true); - private ICssProperty CreateProperty(String propertyName) => - GetProperty(propertyName) ?? _context.CreateProperty(propertyName); + private ICssProperty CreateProperty(String propertyName) + { + var newProperty = _context.CreateProperty(propertyName); + var existing = GetProperty(propertyName); + + if (existing is not null) + { + newProperty.RawValue = existing.RawValue; + } + + return newProperty; + } private void SetProperty(ICssProperty property) { From b9aef4c45724e9d9a57fce9a36a758741f043e38 Mon Sep 17 00:00:00 2001 From: Florian Rappl Date: Wed, 14 Jun 2023 14:41:00 +0200 Subject: [PATCH 06/53] Fixed appending eof character #123 --- CHANGELOG.md | 1 + .../Library/StringRepresentation.cs | 34 +++++++++++++++++++ src/AngleSharp.Css/Parser/CssTokenizer.cs | 5 +++ 3 files changed, 40 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 458708fc..7e7a8a31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ Released on tbd. - Updated to use AngleSharp 1.0 - Fixed issue when updating shorthands with invalid values (#129) +- Fixed issue with appended EOF character in `CssText` (#123) # 0.17.0 diff --git a/src/AngleSharp.Css.Tests/Library/StringRepresentation.cs b/src/AngleSharp.Css.Tests/Library/StringRepresentation.cs index c5087b4e..0e156855 100644 --- a/src/AngleSharp.Css.Tests/Library/StringRepresentation.cs +++ b/src/AngleSharp.Css.Tests/Library/StringRepresentation.cs @@ -106,6 +106,40 @@ public void EscapePropertyNames_UnknownDeclaration_Issue120() Assert.AreEqual(css, generatedCss); } + [Test] + public void CssTextShouldNotAddReplacementCharacter_Issue123() + { + var html = @"Ipsum"; + var dom = html.ToHtmlDocument(Configuration.Default.WithCss(new CssParserOptions + { + IsIncludingUnknownDeclarations = true, + IsIncludingUnknownRules = true, + IsToleratingInvalidSelectors = true, + })); + var div = dom.Body?.FirstElementChild; + var style = div.GetStyle(); + var css = style.ToCss(); + + Assert.AreEqual("background-image: var(--urlSpellingErrorV2,url(\"https://www.example.com/))", css); + } + + [Test] + public void CssTextShouldNotTrailingSemicolonCharacter_Issue123() + { + var html = @"Ipsum"; + var dom = html.ToHtmlDocument(Configuration.Default.WithCss(new CssParserOptions + { + IsIncludingUnknownDeclarations = true, + IsIncludingUnknownRules = true, + IsToleratingInvalidSelectors = true, + })); + var div = dom.Body?.FirstElementChild; + var style = div.GetStyle(); + var css = style.ToCss(); + + Assert.AreEqual("color: rgba(255, 0, 0, 1)", css); + } + [Test] public void BorderWithEmptyPx_Issue129() { diff --git a/src/AngleSharp.Css/Parser/CssTokenizer.cs b/src/AngleSharp.Css/Parser/CssTokenizer.cs index c41d099a..504f8d31 100644 --- a/src/AngleSharp.Css/Parser/CssTokenizer.cs +++ b/src/AngleSharp.Css/Parser/CssTokenizer.cs @@ -70,6 +70,11 @@ public String ContentFrom(Int32 position) { var token = Data(current); + if (Current is Symbols.EndOfFile) + { + Back(); + } + if (token.Type == CssTokenType.Whitespace) { spaced++; From 6905fa5ea6d159449ecdf284d658d41d9606e07a Mon Sep 17 00:00:00 2001 From: Florian Rappl Date: Wed, 14 Jun 2023 14:57:59 +0200 Subject: [PATCH 07/53] Fixed missing semicolon separator #135 --- CHANGELOG.md | 1 + src/AngleSharp.Css.Tests/Rules/CssPageRule.cs | 32 +++++++++++++++++++ .../Dom/Internal/Rules/CssPageRule.cs | 2 +- 3 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 src/AngleSharp.Css.Tests/Rules/CssPageRule.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e7a8a31..83998ab2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ Released on tbd. - Updated to use AngleSharp 1.0 - Fixed issue when updating shorthands with invalid values (#129) - Fixed issue with appended EOF character in `CssText` (#123) +- Fixed missing semicolon in `@page` rule (#135) # 0.17.0 diff --git a/src/AngleSharp.Css.Tests/Rules/CssPageRule.cs b/src/AngleSharp.Css.Tests/Rules/CssPageRule.cs new file mode 100644 index 00000000..29ef1f69 --- /dev/null +++ b/src/AngleSharp.Css.Tests/Rules/CssPageRule.cs @@ -0,0 +1,32 @@ +namespace AngleSharp.Css.Tests.Rules +{ + using AngleSharp.Css.Dom; + using NUnit.Framework; + using System.Linq; + + [TestFixture] + public class CssPageRuleTests + { + [Test] + public void SemiColonsShouldNotMissInPageRule_Issue135() + { + var html = ""; + var document = html.ToHtmlDocument(Configuration.Default.WithCss()); + var styleSheet = document.StyleSheets.OfType().First(); + var css = styleSheet.ToCss(); + + Assert.AreEqual("@page { margin-top: 25mm; margin-right: 25mm }", css); + } + + [Test] + public void PageRuleShouldRecombineShorthandDeclaration_Issue135() + { + var html = ""; + var document = html.ToHtmlDocument(Configuration.Default.WithCss()); + var styleSheet = document.StyleSheets.OfType().First(); + var css = styleSheet.ToCss(); + + Assert.AreEqual("@page { margin: 25mm }", css); + } + } +} diff --git a/src/AngleSharp.Css/Dom/Internal/Rules/CssPageRule.cs b/src/AngleSharp.Css/Dom/Internal/Rules/CssPageRule.cs index c422d079..9a116c6f 100644 --- a/src/AngleSharp.Css/Dom/Internal/Rules/CssPageRule.cs +++ b/src/AngleSharp.Css/Dom/Internal/Rules/CssPageRule.cs @@ -60,7 +60,7 @@ protected override void ReplaceWith(ICssRule rule) public override void ToCss(TextWriter writer, IStyleFormatter formatter) { - var rules = formatter.BlockRules(_style); + var rules = _style.ToCssBlock(formatter); writer.Write(formatter.Rule(RuleNames.Page, SelectorText, rules)); } From 1f3d71895cd8e0cde6e7efd67a4dcb0c0a38ef45 Mon Sep 17 00:00:00 2001 From: Florian Rappl Date: Wed, 14 Jun 2023 15:06:13 +0200 Subject: [PATCH 08/53] Fixed keyframe stop truncation #128 --- CHANGELOG.md | 1 + src/AngleSharp.Css.Tests/Rules/CssKeyframeRule.cs | 12 +++++++++++- src/AngleSharp.Css/Dom/Internal/KeyframeSelector.cs | 2 +- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 83998ab2..ddaa5995 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ Released on tbd. - Fixed issue when updating shorthands with invalid values (#129) - Fixed issue with appended EOF character in `CssText` (#123) - Fixed missing semicolon in `@page` rule (#135) +- Fixed integer serialization of keyframe stops (#128) # 0.17.0 diff --git a/src/AngleSharp.Css.Tests/Rules/CssKeyframeRule.cs b/src/AngleSharp.Css.Tests/Rules/CssKeyframeRule.cs index d2b63469..efcc9a55 100644 --- a/src/AngleSharp.Css.Tests/Rules/CssKeyframeRule.cs +++ b/src/AngleSharp.Css.Tests/Rules/CssKeyframeRule.cs @@ -1,4 +1,4 @@ -namespace AngleSharp.Css.Tests.Rules +namespace AngleSharp.Css.Tests.Rules { using NUnit.Framework; using System.Linq; @@ -74,5 +74,15 @@ public void KeyframeRuleWith0AndNoDeclarations() Assert.AreEqual(1, rule.Key.Stops.Count()); Assert.AreEqual(0, rule.Style.Length); } + + [Test] + public void KeyframeRuleWithPercentage_Issue128() + { + var rule = ParseKeyframeRule(@" 0.52%, 50.0%,92.82% { }"); + Assert.IsNotNull(rule); + Assert.AreEqual("0.52%, 50%, 92.82%", rule.KeyText); + Assert.AreEqual(3, rule.Key.Stops.Count()); + Assert.AreEqual(0, rule.Style.Length); + } } } diff --git a/src/AngleSharp.Css/Dom/Internal/KeyframeSelector.cs b/src/AngleSharp.Css/Dom/Internal/KeyframeSelector.cs index 14ed2e7f..d2792cec 100644 --- a/src/AngleSharp.Css/Dom/Internal/KeyframeSelector.cs +++ b/src/AngleSharp.Css/Dom/Internal/KeyframeSelector.cs @@ -56,7 +56,7 @@ public void ToCss(TextWriter writer, IStyleFormatter formatter) private static void Write(TextWriter writer, Double value) { - var pc = Math.Truncate(100.0 * value); + var pc = 100.0 * value; var str = pc.ToString(CultureInfo.InvariantCulture); writer.Write(str); writer.Write(Symbols.Percent); From 12f61a15d1a6b0ddba3e7319f6e6b3f6fd0f9aed Mon Sep 17 00:00:00 2001 From: Florian Rappl Date: Wed, 14 Jun 2023 20:54:10 +0200 Subject: [PATCH 09/53] Fixed grid and grid-gap ordering #137 --- CHANGELOG.md | 1 + .../Declarations/CssGridProperty.cs | 16 ++++++ .../Declarations/GapDeclaration.cs | 4 +- .../Declarations/GridDeclaration.cs | 51 ++++++++++--------- .../Declarations/GridGapDeclaration.cs | 4 +- .../GridTemplateAreasDeclaration.cs | 1 + .../GridTemplateColumnsDeclaration.cs | 1 + .../Dom/Internal/CssStyleDeclaration.cs | 12 ++--- 8 files changed, 55 insertions(+), 35 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ddaa5995..76beec50 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ Released on tbd. - Fixed issue with appended EOF character in `CssText` (#123) - Fixed missing semicolon in `@page` rule (#135) - Fixed integer serialization of keyframe stops (#128) +- Fixed ordering of rows and columns in `grid` and `grid-gap` (#137) # 0.17.0 diff --git a/src/AngleSharp.Css.Tests/Declarations/CssGridProperty.cs b/src/AngleSharp.Css.Tests/Declarations/CssGridProperty.cs index 7d366d01..8e956b6b 100644 --- a/src/AngleSharp.Css.Tests/Declarations/CssGridProperty.cs +++ b/src/AngleSharp.Css.Tests/Declarations/CssGridProperty.cs @@ -909,5 +909,21 @@ public void CssGridTemplateLonghands_Issue68() var style = ParseDeclarations(snippet); Assert.AreEqual("grid-template: none", style.CssText); } + + [Test] + public void CssGridPreservesParts_Issue137() + { + var snippet = "grid: 10px / 80px"; + var style = ParseDeclarations(snippet); + Assert.AreEqual("grid: 10px / 80px", style.CssText); + } + + [Test] + public void CssGridGapPreservesParts_Issue137() + { + var snippet = "grid-gap: 10px 80px"; + var style = ParseDeclarations(snippet); + Assert.AreEqual("grid-gap: 10px 80px", style.CssText); + } } } diff --git a/src/AngleSharp.Css/Declarations/GapDeclaration.cs b/src/AngleSharp.Css/Declarations/GapDeclaration.cs index 1512f71c..3fcd63b3 100644 --- a/src/AngleSharp.Css/Declarations/GapDeclaration.cs +++ b/src/AngleSharp.Css/Declarations/GapDeclaration.cs @@ -30,8 +30,8 @@ sealed class GapAggregagtor : IValueAggregator, IValueConverter public ICssValue Merge(ICssValue[] values) { - var col = values[0]; - var row = values[1]; + var row = values[0]; + var col = values[1]; if (row != null || col != null) { diff --git a/src/AngleSharp.Css/Declarations/GridDeclaration.cs b/src/AngleSharp.Css/Declarations/GridDeclaration.cs index d5053f4f..42bfba6e 100644 --- a/src/AngleSharp.Css/Declarations/GridDeclaration.cs +++ b/src/AngleSharp.Css/Declarations/GridDeclaration.cs @@ -6,6 +6,7 @@ namespace AngleSharp.Css.Declarations using AngleSharp.Text; using System; using System.Collections.Generic; + using System.Linq; static class GridDeclaration { @@ -37,11 +38,11 @@ public ICssValue Convert(StringSource source) { var template = source.ParseGridTemplate(); - if (template == null) + if (template is null) { var rows = source.ParseTrackList() ?? source.ParseAutoTrackList(); - if (rows != null) + if (rows is not null) { if (source.SkipSpacesAndComments() == Symbols.Solidus) { @@ -153,30 +154,30 @@ public ICssValue[] Split(ICssValue value) gt.TemplateRows, gt.TemplateColumns, gt.TemplateAreas, - null, - null, - null, - null, - null, - null, - null, + null, //new Identifier(CssKeywords.Auto), + null, //new Identifier(CssKeywords.Auto), + null, //new Identifier(CssKeywords.Row), + null, //Length.Zero, + null, //Length.Zero, + null, //new Identifier(CssKeywords.Normal), + null, //new Identifier(CssKeywords.Normal), }; } else if (value is CssGridValue grid) { - var dense = grid.Rows != null ? CssKeywords.Row : CssKeywords.Column; + var dense = grid.Rows is not null ? CssKeywords.Row : CssKeywords.Column; return new[] { grid.Rows, grid.Columns, - null, - grid.Columns != null ? new CssTupleValue(grid.Sizes) : null, - grid.Rows != null ? new CssTupleValue(grid.Sizes) : null, - grid.IsDense ? new Identifier(dense) as ICssValue : null, - null, - null, - null, - null, + null, //new Identifier(CssKeywords.None), + grid.Columns is not null ? new CssTupleValue(grid.Sizes) : null, //new Identifier(CssKeywords.Auto), + grid.Rows is not null ? new CssTupleValue(grid.Sizes) : null, //new Identifier(CssKeywords.Auto), + grid.IsDense ? new Identifier(dense) : null, + null, //Length.Zero, + null, //Length.Zero, + null, //new Identifier(CssKeywords.Normal), + null, //new Identifier(CssKeywords.Normal), }; } else if (value is Identifier) @@ -186,13 +187,13 @@ public ICssValue[] Split(ICssValue value) value, value, value, - null, - null, - null, - null, - null, - null, - null, + null, //new Identifier(CssKeywords.Auto), + null, //new Identifier(CssKeywords.Auto), + null, //new Identifier(CssKeywords.Row), + null, //Length.Zero, + null, //Length.Zero, + null, //new Identifier(CssKeywords.Normal), + null, //new Identifier(CssKeywords.Normal), }; } diff --git a/src/AngleSharp.Css/Declarations/GridGapDeclaration.cs b/src/AngleSharp.Css/Declarations/GridGapDeclaration.cs index fc0a9b41..f7178963 100644 --- a/src/AngleSharp.Css/Declarations/GridGapDeclaration.cs +++ b/src/AngleSharp.Css/Declarations/GridGapDeclaration.cs @@ -30,8 +30,8 @@ sealed class GridGapAggregagtor : IValueAggregator, IValueConverter public ICssValue Merge(ICssValue[] values) { - var col = values[0]; - var row = values[1]; + var row = values[0]; + var col = values[1]; if (row != null || col != null) { diff --git a/src/AngleSharp.Css/Declarations/GridTemplateAreasDeclaration.cs b/src/AngleSharp.Css/Declarations/GridTemplateAreasDeclaration.cs index 151372a1..9772062f 100644 --- a/src/AngleSharp.Css/Declarations/GridTemplateAreasDeclaration.cs +++ b/src/AngleSharp.Css/Declarations/GridTemplateAreasDeclaration.cs @@ -11,6 +11,7 @@ static class GridTemplateAreasDeclaration public static readonly String[] Shorthands = new[] { + PropertyNames.Grid, PropertyNames.GridTemplate, }; diff --git a/src/AngleSharp.Css/Declarations/GridTemplateColumnsDeclaration.cs b/src/AngleSharp.Css/Declarations/GridTemplateColumnsDeclaration.cs index 85ac0130..3f49b08e 100644 --- a/src/AngleSharp.Css/Declarations/GridTemplateColumnsDeclaration.cs +++ b/src/AngleSharp.Css/Declarations/GridTemplateColumnsDeclaration.cs @@ -10,6 +10,7 @@ static class GridTemplateColumnsDeclaration public static readonly String[] Shorthands = new[] { + PropertyNames.Grid, PropertyNames.GridTemplate, }; diff --git a/src/AngleSharp.Css/Dom/Internal/CssStyleDeclaration.cs b/src/AngleSharp.Css/Dom/Internal/CssStyleDeclaration.cs index 01cb51c9..776795d5 100644 --- a/src/AngleSharp.Css/Dom/Internal/CssStyleDeclaration.cs +++ b/src/AngleSharp.Css/Dom/Internal/CssStyleDeclaration.cs @@ -175,7 +175,7 @@ public String ToCssBlock(IStyleFormatter formatter) var usedProperties = new List(); var shorthand = TryCreateShorthand(shorthandName, serialized, usedProperties, false); - if (shorthand != null) + if (shorthand is not null) { list.Add(shorthand); @@ -278,17 +278,17 @@ public void SetProperty(String propertyName, String propertyValue, String priori if (!String.IsNullOrEmpty(propertyValue)) { - if (priority == null || priority.Isi(CssKeywords.Important)) + if (priority is null || priority.Isi(CssKeywords.Important)) { var property = CreateProperty(propertyName); - if (property != null) + if (property is not null) { property.Value = propertyValue; - if (property.RawValue != null) + if (property.RawValue is not null) { - property.IsImportant = priority != null; + property.IsImportant = priority is not null; SetProperty(property); RaiseChanged(); } @@ -421,7 +421,7 @@ private void SetShorthand(ICssProperty shorthand) { var properties = _context.CreateLonghands(shorthand); - if (properties != null) + if (properties is not null) { foreach (var property in properties) { From ad7aff45fe055202bb595cfdd7888028cf6d75f2 Mon Sep 17 00:00:00 2001 From: Florian Rappl Date: Thu, 15 Jun 2023 00:01:09 +0200 Subject: [PATCH 10/53] Improved CSS minification of grid #89 --- CHANGELOG.md | 1 + .../Declarations/CssGridProperty.cs | 2 +- .../Styling/BasicStyling.cs | 8 ++++++++ .../Declarations/GridAreaDeclaration.cs | 18 ++++++++++++++---- 4 files changed, 24 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 76beec50..d33cad7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ Released on tbd. - Fixed missing semicolon in `@page` rule (#135) - Fixed integer serialization of keyframe stops (#128) - Fixed ordering of rows and columns in `grid` and `grid-gap` (#137) +- Added further compactification of CSS tuples (#89, #93) # 0.17.0 diff --git a/src/AngleSharp.Css.Tests/Declarations/CssGridProperty.cs b/src/AngleSharp.Css.Tests/Declarations/CssGridProperty.cs index 8e956b6b..e75811f9 100644 --- a/src/AngleSharp.Css.Tests/Declarations/CssGridProperty.cs +++ b/src/AngleSharp.Css.Tests/Declarations/CssGridProperty.cs @@ -534,7 +534,7 @@ public void CssGridAreaTextValueLegal1() var css = ParseStyleSheet(source); var text = css.Rules[0].CssText; - var expected = "#nav-header { grid-area: aaa / aaa / aaa / aaa }"; + var expected = "#nav-header { grid-area: aaa }"; Assert.AreEqual(expected, text); } diff --git a/src/AngleSharp.Css.Tests/Styling/BasicStyling.cs b/src/AngleSharp.Css.Tests/Styling/BasicStyling.cs index 9af111fd..1133852f 100644 --- a/src/AngleSharp.Css.Tests/Styling/BasicStyling.cs +++ b/src/AngleSharp.Css.Tests/Styling/BasicStyling.cs @@ -210,5 +210,13 @@ public void MinifyWithMultipleDeclarations() var result = sheet.ToCss(new MinifyStyleFormatter()); Assert.AreEqual("h1{top:0;left:2px;border:none}h2{border:1px solid rgba(255, 0, 0, 1)}", result); } + + [Test] + public void MinifyMinimizesProperties_Issue89() + { + var sheet = ParseStyleSheet("a { grid-area: aa / aa / aa / aa }"); + var result = sheet.ToCss(new MinifyStyleFormatter()); + Assert.AreEqual("a{grid-area:aa}", result); + } } } diff --git a/src/AngleSharp.Css/Declarations/GridAreaDeclaration.cs b/src/AngleSharp.Css/Declarations/GridAreaDeclaration.cs index 59b875b7..10a43977 100644 --- a/src/AngleSharp.Css/Declarations/GridAreaDeclaration.cs +++ b/src/AngleSharp.Css/Declarations/GridAreaDeclaration.cs @@ -37,7 +37,16 @@ sealed class GridAreaAggregator : IValueAggregator, IValueConverter public ICssValue Convert(StringSource source) => converter.Convert(source); - public ICssValue Merge(ICssValue[] values) => new CssTupleValue(values, seperator); + public ICssValue Merge(ICssValue[] values) + { + // Make single value if all resolve to the same text + if (values.Length == 4 && values[0]?.CssText == values[1]?.CssText && values[0]?.CssText == values[2]?.CssText && values[0]?.CssText == values[3]?.CssText) + { + return values[0]; + } + + return new CssTupleValue(values, seperator); + } public ICssValue[] Split(ICssValue value) { @@ -75,12 +84,13 @@ private static ICssValue GetItem(CssTupleValue tuple, Int32 index) private static ICssValue GetItemSimple(CssTupleValue tuple, Int32 index) { var val = UnitParser.ParseUnit(new StringSource(tuple.Items[0].CssText)); + if (index <= 2) { if (tuple.Items.Length <= index) { - if (!int.TryParse(tuple.Items[0].CssText, out int _)) + if (!Int32.TryParse(tuple.Items[0].CssText, out var _)) { return tuple.Items[0]; } @@ -90,12 +100,12 @@ private static ICssValue GetItemSimple(CssTupleValue tuple, Int32 index) { if (tuple.Items.Length > 1) { - if (!int.TryParse(tuple.Items[1].CssText, out int _)) + if (!Int32.TryParse(tuple.Items[1].CssText, out var _)) { return tuple.Items[1]; } } - else if (!int.TryParse(tuple.Items[0].CssText, out int _)) + else if (!Int32.TryParse(tuple.Items[0].CssText, out var _)) { return tuple.Items[0]; } From 78740fe1e08494980b63b0e395dfabd211811881 Mon Sep 17 00:00:00 2001 From: Florian Rappl Date: Thu, 15 Jun 2023 00:39:58 +0200 Subject: [PATCH 11/53] Added more tests for background-size --- .../Declarations/CssBackgroundProperty.cs | 24 +++++++++++++++++++ .../Parser/Micro/PointParser.cs | 4 ++-- .../Composites/CssBackgroundSizeValue.cs | 21 ++++++++++++---- 3 files changed, 42 insertions(+), 7 deletions(-) diff --git a/src/AngleSharp.Css.Tests/Declarations/CssBackgroundProperty.cs b/src/AngleSharp.Css.Tests/Declarations/CssBackgroundProperty.cs index bb5c2d02..03d9da7c 100644 --- a/src/AngleSharp.Css.Tests/Declarations/CssBackgroundProperty.cs +++ b/src/AngleSharp.Css.Tests/Declarations/CssBackgroundProperty.cs @@ -6,6 +6,30 @@ namespace AngleSharp.Css.Tests.Declarations [TestFixture] public class CssBackgroundPropertyTests { + [Test] + public void CssBackgroundSizeCoverTest() + { + var snippet = "background-size : cover"; + var property = ParseDeclaration(snippet); + Assert.AreEqual("background-size", property.Name); + Assert.IsFalse(property.IsImportant); + Assert.IsFalse(property.IsInherited); + Assert.IsTrue(property.HasValue); + Assert.AreEqual("cover", property.Value); + } + + [Test] + public void CssBackgroundSizeContainTest() + { + var snippet = "background-size : contain"; + var property = ParseDeclaration(snippet); + Assert.AreEqual("background-size", property.Name); + Assert.IsFalse(property.IsImportant); + Assert.IsFalse(property.IsInherited); + Assert.IsTrue(property.HasValue); + Assert.AreEqual("contain", property.Value); + } + [Test] public void CssBackgroundAttachmentScrollLegal() { diff --git a/src/AngleSharp.Css/Parser/Micro/PointParser.cs b/src/AngleSharp.Css/Parser/Micro/PointParser.cs index 66fbcf7f..a8c506bd 100644 --- a/src/AngleSharp.Css/Parser/Micro/PointParser.cs +++ b/src/AngleSharp.Css/Parser/Micro/PointParser.cs @@ -167,7 +167,7 @@ public static CssBackgroundSizeValue ParseSize(this StringSource source) { var w = source.ParseDistanceOrCalc(); - if (w == null && !source.IsIdentifier(CssKeywords.Auto)) + if (w is null && !source.IsIdentifier(CssKeywords.Auto)) { return null; } @@ -175,7 +175,7 @@ public static CssBackgroundSizeValue ParseSize(this StringSource source) source.SkipSpacesAndComments(); var h = source.ParseDistanceOrCalc(); - if (h == null) + if (h is null) { source.IsIdentifier(CssKeywords.Auto); } diff --git a/src/AngleSharp.Css/Values/Composites/CssBackgroundSizeValue.cs b/src/AngleSharp.Css/Values/Composites/CssBackgroundSizeValue.cs index 5ad1aee2..f139cfa2 100644 --- a/src/AngleSharp.Css/Values/Composites/CssBackgroundSizeValue.cs +++ b/src/AngleSharp.Css/Values/Composites/CssBackgroundSizeValue.cs @@ -30,8 +30,8 @@ public sealed class CssBackgroundSizeValue : IEquatable, private CssBackgroundSizeValue(ValueMode mode) { - _width = Length.Zero; - _height = Length.Zero; + _width = Length.Auto; + _height = Length.Auto; _mode = mode; } @@ -94,18 +94,29 @@ public String CssText ///
/// The other background size. /// True if both are equivalent, otherwise false. - public Boolean Equals(CssBackgroundSizeValue other) => - _mode == other._mode && _height == other._height && _width == other._width; + public Boolean Equals(CssBackgroundSizeValue other) => _mode == other._mode && _height == other._height && _width == other._width; #endregion #region Value Mode + /// + /// Represents the value modes for the vertical and horizontal axis. + /// private enum ValueMode { + /// + /// The size is explicitly specified. + /// Explicit, + /// + /// The size should cover the axis. + /// Cover, - Contain + /// + /// The size should contain the axis. + /// + Contain, } #endregion From 6d52e794096b4f2ad6a166d620bbd1e4aaf7cc6a Mon Sep 17 00:00:00 2001 From: Florian Rappl Date: Thu, 15 Jun 2023 18:18:24 +0200 Subject: [PATCH 12/53] Updated media parsing #133 --- CHANGELOG.md | 1 + .../CssConstructionFunctions.cs | 4 +- .../Rules/CssMediaList.cs | 20 +- .../Dom/Internal/MediaFeature.cs | 32 +- .../Extensions/CssValueExtensions.cs | 4 + .../AspectRatioFeatureValidator.cs | 2 +- .../DeviceAspectRatioFeatureValidator.cs | 2 +- src/AngleSharp.Css/Parser/Micro/CalcParser.cs | 7 +- .../Parser/Micro/IdentParser.cs | 15 + .../Parser/Micro/MediaParser.cs | 17 +- .../Parser/Micro/MediumParser.cs | 313 ++++++++++-------- .../Parser/Micro/NumberParser.cs | 5 +- src/AngleSharp.Css/ValueConverters.cs | 2 +- .../Values/Primitives/CounterValue.cs | 2 +- .../Values/Primitives/Fraction.cs | 3 +- .../Values/Primitives/Identifier.cs | 2 +- src/AngleSharp.Css/Values/Primitives/Label.cs | 2 +- src/AngleSharp.Css/Values/Primitives/Quote.cs | 2 +- src/AngleSharp.Css/Values/Primitives/Ratio.cs | 101 ++++++ 19 files changed, 369 insertions(+), 167 deletions(-) create mode 100644 src/AngleSharp.Css/Values/Primitives/Ratio.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index d33cad7b..45115f23 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ Released on tbd. - Updated to use AngleSharp 1.0 +- Updated media parsing to media L4 spec (#133) - Fixed issue when updating shorthands with invalid values (#129) - Fixed issue with appended EOF character in `CssText` (#123) - Fixed missing semicolon in `@page` rule (#135) diff --git a/src/AngleSharp.Css.Tests/CssConstructionFunctions.cs b/src/AngleSharp.Css.Tests/CssConstructionFunctions.cs index e62c5ee5..dd915242 100644 --- a/src/AngleSharp.Css.Tests/CssConstructionFunctions.cs +++ b/src/AngleSharp.Css.Tests/CssConstructionFunctions.cs @@ -2,6 +2,7 @@ namespace AngleSharp.Css.Tests { using AngleSharp.Css.Dom; using AngleSharp.Css.Parser; + using AngleSharp.Css.Values; using AngleSharp.Html.Dom; using AngleSharp.Html.Parser; using System; @@ -70,7 +71,6 @@ internal static CssProperty ParseDeclaration(String source, CssParserOptions opt internal static CssStyleDeclaration ParseDeclarations(String declarations) { var context = BrowsingContext.New(Configuration.Default.WithCss()); - var parser = context.GetService(); var style = new CssStyleDeclaration(context); style.Update(declarations); return style; @@ -93,7 +93,7 @@ internal static CssImportRule ParseImportRule(String source) internal static Predicate CreateValidator(String name, String value) { var validator = CreateMediaFeatureValidator(name); - var feature = new MediaFeature(name, value); + var feature = new MediaFeature(name, new CssAnyValue(value)); return device => validator.Validate(feature, device); } diff --git a/src/AngleSharp.Css.Tests/Rules/CssMediaList.cs b/src/AngleSharp.Css.Tests/Rules/CssMediaList.cs index 9bf6611d..b6acf22f 100644 --- a/src/AngleSharp.Css.Tests/Rules/CssMediaList.cs +++ b/src/AngleSharp.Css.Tests/Rules/CssMediaList.cs @@ -132,7 +132,7 @@ public void FeatureMinWidthMediaList() } [Test] - public void OnlyFeatureWidthMediaList() + public void OnlyFeatureWidthMediaListInvalid() { var source = @"@media only (width: 640px) { h1 { color: green } @@ -141,7 +141,23 @@ public void OnlyFeatureWidthMediaList() Assert.AreEqual(1, sheet.Rules.Length); Assert.IsInstanceOf(sheet.Rules[0]); var media = (CssMediaRule)sheet.Rules[0]; - Assert.AreEqual("only (width: 640px)", media.Media.MediaText); + Assert.AreEqual("not all", media.Media.MediaText); + var list = media.Media; + Assert.AreEqual(1, list.Length); + Assert.AreEqual(1, media.Rules.Length); + } + + [Test] + public void OnlyFeatureWidthScreenAndMediaList() + { + var source = @"@media only screen and (width: 640px) { + h1 { color: green } +}"; + var sheet = ParseStyleSheet(source); + Assert.AreEqual(1, sheet.Rules.Length); + Assert.IsInstanceOf(sheet.Rules[0]); + var media = (CssMediaRule)sheet.Rules[0]; + Assert.AreEqual("only screen and (width: 640px)", media.Media.MediaText); var list = media.Media; Assert.AreEqual(1, list.Length); Assert.AreEqual(1, media.Rules.Length); diff --git a/src/AngleSharp.Css/Dom/Internal/MediaFeature.cs b/src/AngleSharp.Css/Dom/Internal/MediaFeature.cs index b2b29d7b..11c1dd02 100644 --- a/src/AngleSharp.Css/Dom/Internal/MediaFeature.cs +++ b/src/AngleSharp.Css/Dom/Internal/MediaFeature.cs @@ -1,5 +1,6 @@ -namespace AngleSharp.Css.Dom +namespace AngleSharp.Css.Dom { + using AngleSharp.Css.Values; using AngleSharp.Text; using System; using System.IO; @@ -13,8 +14,9 @@ sealed class MediaFeature : IMediaFeature private readonly Boolean _min; private readonly Boolean _max; - private readonly String _name; + private readonly ICssValue _name; private readonly ICssValue _value; + private readonly String _op; #endregion @@ -26,17 +28,28 @@ internal MediaFeature(String name) internal MediaFeature(String name, ICssValue value) { - _name = name; + _name = new CssAnyValue(name); _value = value; _min = name.StartsWith("min-"); _max = name.StartsWith("max-"); } + internal MediaFeature(String name, ICssValue value, String op) + : this(new CssAnyValue(name), value, op) + {} + + internal MediaFeature(ICssValue name, ICssValue value, String op) + { + _name = name; + _value = value; + _op = op; + } + #endregion #region Properties - public String Name => _name; + public String Name => _name?.CssText ?? String.Empty; public Boolean IsMinimum => _min; @@ -53,9 +66,16 @@ internal MediaFeature(String name, ICssValue value) public void ToCss(TextWriter writer, IStyleFormatter formatter) { writer.Write(Symbols.RoundBracketOpen); - writer.Write(_name); + writer.Write(Name); - if (_value != null) + if (_op is not null) + { + writer.Write(" "); + writer.Write(_op); + writer.Write(" "); + writer.Write(Value); + } + else if (_value is not null) { writer.Write(": "); writer.Write(Value); diff --git a/src/AngleSharp.Css/Extensions/CssValueExtensions.cs b/src/AngleSharp.Css/Extensions/CssValueExtensions.cs index ae021904..ddda4fa0 100644 --- a/src/AngleSharp.Css/Extensions/CssValueExtensions.cs +++ b/src/AngleSharp.Css/Extensions/CssValueExtensions.cs @@ -24,6 +24,10 @@ public static Double AsDouble(this ICssValue value) { return fr.Value; } + else if (value is Ratio ratio) + { + return ratio.Value; + } else if (value is ICssMultipleValue multiple && multiple.Count == 1) { return multiple[0].AsDouble(); diff --git a/src/AngleSharp.Css/FeatureValidators/AspectRatioFeatureValidator.cs b/src/AngleSharp.Css/FeatureValidators/AspectRatioFeatureValidator.cs index 1611c816..9cd3596e 100644 --- a/src/AngleSharp.Css/FeatureValidators/AspectRatioFeatureValidator.cs +++ b/src/AngleSharp.Css/FeatureValidators/AspectRatioFeatureValidator.cs @@ -11,7 +11,7 @@ public Boolean Validate(IMediaFeature feature, IRenderDevice renderDevice) { var ratio = RatioConverter.Convert(feature.Value); - if (ratio != null) + if (ratio is not null) { var desired = ratio.AsDouble(); var available = renderDevice.ViewPortWidth / (Double)renderDevice.ViewPortHeight; diff --git a/src/AngleSharp.Css/FeatureValidators/DeviceAspectRatioFeatureValidator.cs b/src/AngleSharp.Css/FeatureValidators/DeviceAspectRatioFeatureValidator.cs index 9aefe13d..235f0bf9 100644 --- a/src/AngleSharp.Css/FeatureValidators/DeviceAspectRatioFeatureValidator.cs +++ b/src/AngleSharp.Css/FeatureValidators/DeviceAspectRatioFeatureValidator.cs @@ -11,7 +11,7 @@ public Boolean Validate(IMediaFeature feature, IRenderDevice renderDevice) { var ratio = RatioConverter.Convert(feature.Value); - if (ratio != null) + if (ratio is not null) { var desired = ratio.AsDouble(); var available = renderDevice.DeviceWidth / (Double)renderDevice.DeviceHeight; diff --git a/src/AngleSharp.Css/Parser/Micro/CalcParser.cs b/src/AngleSharp.Css/Parser/Micro/CalcParser.cs index 05b9085f..9ee6a278 100644 --- a/src/AngleSharp.Css/Parser/Micro/CalcParser.cs +++ b/src/AngleSharp.Css/Parser/Micro/CalcParser.cs @@ -141,8 +141,11 @@ private static ICssValue ParseBracketExpression(this StringSource source) return source.ParseAtomicExpression(); } - - private static ICssValue ParseAtomicExpression(this StringSource source) + + /// + /// Parses a unit value into a ICssValue. + /// + public static ICssValue ParseAtomicExpression(this StringSource source) { var unit = source.ParseUnit(); diff --git a/src/AngleSharp.Css/Parser/Micro/IdentParser.cs b/src/AngleSharp.Css/Parser/Micro/IdentParser.cs index 8899ce71..0074d358 100644 --- a/src/AngleSharp.Css/Parser/Micro/IdentParser.cs +++ b/src/AngleSharp.Css/Parser/Micro/IdentParser.cs @@ -48,6 +48,21 @@ public static String ParseIdent(this StringSource source) return Start(source, current, buffer); } + /// + /// Parses a CSS identifier value. + /// + public static ICssValue ParseIdentAsValue(this StringSource source) + { + var value = source.ParseIdent(); + + if (value is not null) + { + return new Identifier(value); + } + + return null; + } + /// /// Parses a CSS constant value from a given dictionary. /// diff --git a/src/AngleSharp.Css/Parser/Micro/MediaParser.cs b/src/AngleSharp.Css/Parser/Micro/MediaParser.cs index 83c6f08d..2a216d26 100644 --- a/src/AngleSharp.Css/Parser/Micro/MediaParser.cs +++ b/src/AngleSharp.Css/Parser/Micro/MediaParser.cs @@ -4,6 +4,8 @@ namespace AngleSharp.Css.Parser using AngleSharp.Text; using System; using System.Collections.Generic; + using System.Diagnostics.Tracing; + using System.Linq; /// /// Represents extensions to for media values. @@ -31,7 +33,20 @@ public static IEnumerable ParseMedia(this StringSource source, IFeatu { if (current != Symbols.Comma) { - return null; + media[media.Count - 1] = null; + + while (!source.IsDone && current != Symbols.Comma) + { + if (current == Symbols.RoundBracketOpen) + { + source.Next(); + source.TakeUntilClosed(); + } + + current = source.Next(); + } + + continue; } source.SkipCurrentAndSpaces(); diff --git a/src/AngleSharp.Css/Parser/Micro/MediumParser.cs b/src/AngleSharp.Css/Parser/Micro/MediumParser.cs index d60603a6..d5de04cd 100644 --- a/src/AngleSharp.Css/Parser/Micro/MediumParser.cs +++ b/src/AngleSharp.Css/Parser/Micro/MediumParser.cs @@ -1,6 +1,7 @@ namespace AngleSharp.Css.Parser { using AngleSharp.Css.Dom; + using AngleSharp.Css.Values; using AngleSharp.Text; using System; using System.Collections.Generic; @@ -23,92 +24,27 @@ internal static CssMedium Parse(String str, IFeatureValidatorFactory factory) /// public static CssMedium ParseMedium(this StringSource source, IFeatureValidatorFactory factory) { - source.SkipSpacesAndComments(); - var ident = source.ParseMediumIdent(); - var inverse = false; - var exclusive = false; - var type = String.Empty; - - if (ident != null) - { - if (ident.Isi(CssKeywords.Not)) - { - inverse = true; - source.SkipSpacesAndComments(); - ident = source.ParseMediumIdent(); - } - else if (ident.Isi(CssKeywords.Only)) - { - exclusive = true; - source.SkipSpacesAndComments(); - ident = source.ParseMediumIdent(); - } - } - - if (ident != null) - { - type = ident; - source.SkipSpacesAndComments(); - var position = source.Index; - ident = source.ParseMediumIdent(); - - if (ident == null || !ident.Isi(CssKeywords.And)) - { - source.BackTo(position); - return new CssMedium(type, inverse, exclusive); - } - - source.SkipSpacesAndComments(); - } + // Syntax: + // = | - var features = new List(); + source.SkipSpacesAndComments(); + var medium = source.ParseMediaCondition() ?? source.ParseMediaType(); - do + if (medium is not null) { - var start = source.Current; - source.SkipCurrentAndSpaces(); - var feature = ParseFeature(source); - var end = source.Current; - - if (feature == null || - start != Symbols.RoundBracketOpen || - end != Symbols.RoundBracketClose) - { - return null; - } - - var validator = factory?.Create(feature.Name); - feature.AssociateValidator(validator); - features.Add(feature); - source.SkipCurrentAndSpaces(); - var position = source.Index; - ident = source.ParseMediumIdent(); - - if (ident == null || !ident.Isi(CssKeywords.And)) + foreach (var feature in medium.Features) { - source.BackTo(position); - break; + var validator = factory?.Create(feature.Name); + feature.AssociateValidator(validator); } - - source.SkipSpacesAndComments(); } - while (!source.IsDone); - - return new CssMedium(type, inverse, exclusive, features); - } - /// - /// Parses a medium value. - /// - public static CssMedium ParseMediumNew(this StringSource source, IFeatureValidatorFactory factory) - { - // = | - source.SkipSpacesAndComments(); - return source.ParseMediaCondition() ?? source.ParseMediaType(); + return medium; } private static CssMedium ParseMediaCondition(this StringSource source) { + // Syntax: // = | [ * | * ] var medium = source.ParseMediaNot(); @@ -117,10 +53,15 @@ private static CssMedium ParseMediaCondition(this StringSource source) { medium = source.ParseMediaInParens(); - if (medium != null) + if (medium is not null) { - var other = source.ParseMediaAnd(); - //TODO + source.SkipSpacesAndComments(); + var other = source.ParseMediaConnectorMultiple(CssKeywords.And) ?? source.ParseMediaConnectorMultiple(CssKeywords.Or); + + if (other is not null) + { + medium = new CssMedium(medium.Type, medium.IsInverse, medium.IsExclusive, medium.Features.Concat(other.Features), other.Connector); + } } } @@ -129,6 +70,7 @@ private static CssMedium ParseMediaCondition(this StringSource source) private static CssMedium ParseMediaConditionWithoutOr(this StringSource source) { + // Syntax: // = | * var medium = source.ParseMediaNot(); @@ -142,14 +84,14 @@ private static CssMedium ParseMediaConditionWithoutOr(this StringSource source) do { source.SkipSpacesAndComments(); - var other = source.ParseMediaAnd(); + var other = source.ParseMediaConnector(CssKeywords.And); if (other is null) { break; } - medium = new CssMedium(medium.Type, medium.IsInverse, medium.IsExclusive, medium.Features.Concat(other.Features)); + medium = new CssMedium(medium.Type, medium.IsInverse, medium.IsExclusive, medium.Features.Concat(other.Features), medium.Connector); } while (!source.IsDone); } @@ -160,38 +102,39 @@ private static CssMedium ParseMediaConditionWithoutOr(this StringSource source) private static CssMedium ParseMediaNot(this StringSource source) { + // Syntax: // = not - if (source.IsIdentifier("not")) + var pos = source.Index; + + if (source.IsIdentifier(CssKeywords.Not)) { - var pos = source.Index; - source.ParseIdent(); - source.SkipCurrentAndSpaces(); + source.SkipSpacesAndComments(); var medium = source.ParseMediaInParens(); if (medium is not null) { source.SkipSpacesAndComments(); - return new CssMedium(medium.Type, !medium.IsInverse, medium.IsExclusive, medium.Features); + return new CssMedium(medium.Type, !medium.IsInverse, medium.IsExclusive, medium.Features, medium.Connector); } - - source.BackTo(pos); } + source.BackTo(pos); return null; } private static CssMedium ParseMediaInParens(this StringSource source) { + // Syntax: // = ( ) | | - if (source.Current == '(') + if (source.Current == Symbols.RoundBracketOpen) { var pos = source.Index; source.SkipCurrentAndSpaces(); var medium = source.ParseMediaCondition(); - if (medium is not null && source.Current == ')') + if (medium is not null && source.Current == Symbols.RoundBracketClose) { source.SkipCurrentAndSpaces(); return medium; @@ -203,72 +146,107 @@ private static CssMedium ParseMediaInParens(this StringSource source) return source.ParseMediaFeature() ?? source.ParseGeneralEnclosed(); } - private static CssMedium ParseMediaAnd(this StringSource source) + private static CssMedium ParseMediaConnectorMultiple(this StringSource source, String connector) { - // = and + // Syntax: + // [ * | * ] - if (source.IsIdentifier("and")) + var part = source.ParseMediaConnector(connector); + + if (part is not null) { - var pos = source.Index; - source.ParseIdent(); - source.SkipCurrentAndSpaces(); - var medium = source.ParseMediaInParens(); + var features = Enumerable.Empty(); - if (medium is not null) + while (part is not null) { source.SkipSpacesAndComments(); - return new CssMedium(medium.Type, medium.IsInverse, medium.IsExclusive, medium.Features, "and"); + features = features.Concat(part.Features); + part = source.ParseMediaConnector(connector); } - source.BackTo(pos); + return new CssMedium(String.Empty, false, false, features, connector); } return null; } - private static CssMedium ParseMediaOr(this StringSource source) + private static CssMedium ParseMediaConnector(this StringSource source, String connector) { + // Syntax: + // = and + // or // = or - if (source.IsIdentifier("or")) + var pos = source.Index; + + if (source.IsIdentifier(connector)) { - var pos = source.Index; - source.ParseIdent(); - source.SkipCurrentAndSpaces(); + source.SkipSpacesAndComments(); var medium = source.ParseMediaInParens(); if (medium is not null) { source.SkipSpacesAndComments(); - return new CssMedium(medium.Type, medium.IsInverse, medium.IsExclusive, medium.Features, "or"); + return new CssMedium(medium.Type, medium.IsInverse, medium.IsExclusive, medium.Features, connector); } - - source.BackTo(pos); } + source.BackTo(pos); return null; } private static CssMedium ParseMediaType(this StringSource source) { + // Syntax: // = [ not | only ]? [ and ]? - //TODO + + var pos = source.Index; + var inverse = source.IsIdentifier(CssKeywords.Not); + source.SkipSpacesAndComments(); + var only = !inverse && source.IsIdentifier(CssKeywords.Only); + source.SkipSpacesAndComments(); + var type = source.ParseIdent(); + source.SkipSpacesAndComments(); + + if (type is not null) + { + if (!source.IsIdentifier(CssKeywords.And)) + { + return new CssMedium(type, inverse, only); + } + + source.SkipSpacesAndComments(); + var features = source.ParseMediaConditionWithoutOr(); + + if (features is not null) + { + return new CssMedium(type, inverse, only, features.Features, features.Connector); + } + + } + + source.BackTo(pos); return null; } private static CssMedium ParseMediaFeature(this StringSource source) { + // Syntax: // = ( [ | | ] ) - if (source.Current == '(') + if (source.Current == Symbols.RoundBracketOpen) { + var pos = source.Index; + source.SkipCurrentAndSpaces(); var feature = source.ParseMediaFeaturePlain() ?? source.ParseMediaFeatureBoolean() ?? source.ParseMediaFeatureRange(); - if (feature is not null && source.Current == ')') + if (feature is not null && source.Current == Symbols.RoundBracketClose) { source.SkipCurrentAndSpaces(); - return new CssMedium("", false, false, Enumerable.Repeat(feature, 1), "and"); + return new CssMedium(String.Empty, false, false, Enumerable.Repeat(feature, 1), CssKeywords.And); } + + source.BackTo(pos); } return null; @@ -276,6 +254,7 @@ private static CssMedium ParseMediaFeature(this StringSource source) private static CssMedium ParseGeneralEnclosed(this StringSource source) { + // Syntax: // = [ ) ] | ( ) //TODO return null; @@ -283,12 +262,16 @@ private static CssMedium ParseGeneralEnclosed(this StringSource source) private static MediaFeature ParseMediaFeaturePlain(this StringSource source) { - // = : + // Syntax: + // mf-plain> = : + var pos = source.Index; var ident = source.ParseIdent(); + source.SkipSpacesAndComments(); - if (ident is not null && source.Current == ':') + if (ident is not null && source.Current == Symbols.Colon) { + source.SkipCurrentAndSpaces(); var value = source.ParseMediaFeatureValue(); if (value is not null) @@ -296,16 +279,16 @@ private static MediaFeature ParseMediaFeaturePlain(this StringSource source) source.SkipSpacesAndComments(); return new MediaFeature(ident, value); } - - source.BackTo(pos); } + source.BackTo(pos); return null; } private static MediaFeature ParseMediaFeatureBoolean(this StringSource source) { // = + var ident = source.ParseIdent(); if (ident is not null) @@ -319,51 +302,95 @@ private static MediaFeature ParseMediaFeatureBoolean(this StringSource source) private static MediaFeature ParseMediaFeatureRange(this StringSource source) { - // = '<' '='? | '>' '='? | '=' + // = + // '<' '='? | '>' '='? | '=' // | '<' '='? | '>' '='? | '=' // | '<' '='? '<' '='? // | '>' '='? '>' '='? - //TODO + + var pos = source.Index; + var name = source.ParseIdentAsValue() ?? source.ParseMediaFeatureValue(); + + if (name is not null) + { + source.SkipSpacesAndComments(); + var op = ""; + + if (source.Current == Symbols.LessThan || source.Current == Symbols.GreaterThan) + { + var c = source.Current; + + if (source.Next() == Symbols.Equality) + { + op = $"{c}="; + source.SkipCurrentAndSpaces(); + } + else + { + op = $"{c}"; + source.SkipSpacesAndComments(); + } + } + else if (source.Current == Symbols.Equality) + { + op = "="; + source.SkipCurrentAndSpaces(); + } + else + { + source.BackTo(pos); + return null; + } + + if (name is Identifier) + { + var value = source.ParseMediaFeatureValue(); + + if (value is not null) + { + return new MediaFeature(name, value, op); + } + } + else + { + var value = source.ParseIdentAsValue(); + + if (value is not null) + { + return new MediaFeature(name, value, op); + } + } + + source.BackTo(pos); + } + return null; } private static ICssValue ParseMediaFeatureValue(this StringSource source) { + // Syntax: // = | | | - //TODO - var ident = source.ParseNumber() ?? source.ParseIdent() ?? source.ParseUnit() ?? source.ParseRatio(); - return null; - } - private static String ParseMediumIdent(this StringSource source) - { var ident = source.ParseIdent(); - var current = source.Current; - - while (current == '&' || current == '#' || current == ';' || current == '-') + + if (ident is not null) { - ident = null; - current = source.Next(); + return new Identifier(ident); } - return ident; - } - - private static IMediaFeature ParseFeature(StringSource source) - { - var name = source.ParseMediumIdent(); - var value = default(String); + var ratio = source.ParseRatio(); - if (name != null) + if (ratio.HasValue) { + return ratio.Value; + } - if (source.Current == Symbols.Colon) - { - source.SkipCurrentAndSpaces(); - value = source.TakeUntilClosed(); - } - - return new MediaFeature(name, value); + var unit = source.ParseAtomicExpression(); + + if (unit is not null) + { + return unit; } return null; diff --git a/src/AngleSharp.Css/Parser/Micro/NumberParser.cs b/src/AngleSharp.Css/Parser/Micro/NumberParser.cs index bdb94895..202d8cc2 100644 --- a/src/AngleSharp.Css/Parser/Micro/NumberParser.cs +++ b/src/AngleSharp.Css/Parser/Micro/NumberParser.cs @@ -1,5 +1,6 @@ namespace AngleSharp.Css.Parser { + using AngleSharp.Css.Values; using AngleSharp.Text; using System; using System.Globalization; @@ -30,7 +31,7 @@ public static class NumberParser /// /// Parses the ratio (double) value. /// - public static Double? ParseRatio(this StringSource source) + public static Ratio? ParseRatio(this StringSource source) { var pos = source.Index; var top = source.ParseNumber(); @@ -39,7 +40,7 @@ public static class NumberParser if (top.HasValue && bottom.HasValue && c == Symbols.Solidus) { - return top.Value / bottom.Value; + return new Ratio(top.Value, bottom.Value); } source.BackTo(pos); diff --git a/src/AngleSharp.Css/ValueConverters.cs b/src/AngleSharp.Css/ValueConverters.cs index 10dd13bf..054e81ae 100644 --- a/src/AngleSharp.Css/ValueConverters.cs +++ b/src/AngleSharp.Css/ValueConverters.cs @@ -742,7 +742,7 @@ static class ValueConverters /// Represents a ratio object. /// https://developer.mozilla.org/en-US/docs/Web/CSS/ratio ///
- public static readonly IValueConverter RatioConverter = new StructValueConverter(FromNumber(NumberParser.ParseRatio)); + public static readonly IValueConverter RatioConverter = new StructValueConverter(NumberParser.ParseRatio); /// /// Represents multiple shadow objects. diff --git a/src/AngleSharp.Css/Values/Primitives/CounterValue.cs b/src/AngleSharp.Css/Values/Primitives/CounterValue.cs index cd71ba54..7dd93338 100644 --- a/src/AngleSharp.Css/Values/Primitives/CounterValue.cs +++ b/src/AngleSharp.Css/Values/Primitives/CounterValue.cs @@ -6,7 +6,7 @@ namespace AngleSharp.Css.Values /// /// Sets a CSS counter. /// - struct CounterValue : ICssPrimitiveValue, IEquatable + public struct CounterValue : ICssPrimitiveValue, IEquatable { #region Fields diff --git a/src/AngleSharp.Css/Values/Primitives/Fraction.cs b/src/AngleSharp.Css/Values/Primitives/Fraction.cs index ed8606e3..456154eb 100644 --- a/src/AngleSharp.Css/Values/Primitives/Fraction.cs +++ b/src/AngleSharp.Css/Values/Primitives/Fraction.cs @@ -1,12 +1,11 @@ namespace AngleSharp.Css.Values { using System; - using System.Globalization; /// /// Represents a fractional value. /// - struct Fraction : IEquatable, IComparable, ICssPrimitiveValue + public struct Fraction : IEquatable, IComparable, ICssPrimitiveValue { #region Fields diff --git a/src/AngleSharp.Css/Values/Primitives/Identifier.cs b/src/AngleSharp.Css/Values/Primitives/Identifier.cs index 0f6b2d92..871e34fb 100644 --- a/src/AngleSharp.Css/Values/Primitives/Identifier.cs +++ b/src/AngleSharp.Css/Values/Primitives/Identifier.cs @@ -6,7 +6,7 @@ namespace AngleSharp.Css.Values /// /// Represents a CSS identifier value. /// - struct Identifier : ICssPrimitiveValue, IEquatable + public struct Identifier : ICssPrimitiveValue, IEquatable { #region Fields diff --git a/src/AngleSharp.Css/Values/Primitives/Label.cs b/src/AngleSharp.Css/Values/Primitives/Label.cs index d1b14de2..5359bc8a 100644 --- a/src/AngleSharp.Css/Values/Primitives/Label.cs +++ b/src/AngleSharp.Css/Values/Primitives/Label.cs @@ -6,7 +6,7 @@ namespace AngleSharp.Css.Values /// /// Represents a CSS label ("string") value. /// - struct Label : ICssPrimitiveValue, IEquatable public static readonly String Hwba = "hwba"; + /// + /// The LAB (Lightness, A-axis, B-axis) function. + /// + public static readonly String Lab = "lab"; + + /// + /// The LCH (Lightness, Chroma, Hue) function. + /// + public static readonly String Lch = "lch"; + + /// + /// The Oklab (Lightness, A-axis, B-axis) function. + /// + public static readonly String Oklab = "oklab"; + + /// + /// The Oklch (Lightness, Chroma, Hue) function. + /// + public static readonly String Oklch = "oklch"; + /// /// The content function. /// diff --git a/src/AngleSharp.Css/Parser/Micro/ColorParser.cs b/src/AngleSharp.Css/Parser/Micro/ColorParser.cs index a67f22d5..59234a0c 100644 --- a/src/AngleSharp.Css/Parser/Micro/ColorParser.cs +++ b/src/AngleSharp.Css/Parser/Micro/ColorParser.cs @@ -21,6 +21,10 @@ static class ColorParser { FunctionNames.Gray, ParseGray }, { FunctionNames.Hwb, ParseHwba }, { FunctionNames.Hwba, ParseHwba }, + { FunctionNames.Lab, ParseLab }, + { FunctionNames.Lch, ParseLch }, + { FunctionNames.Oklab, ParseOklab}, + { FunctionNames.Oklch, ParseOklch }, }; /// @@ -221,6 +225,35 @@ static class ColorParser return null; } + private static Color? ParseLab(StringSource source) + { + // lab(50% 40 59.5) + // lab(50 % 40 59.5 / 0.5) + return null; + } + + private static Color? ParseLch(StringSource source) + { + // lch(52.2% 72.2 50) + // lch(52.2 % 72.2 50 / 0.5) + return null; + } + + + private static Color? ParseOklab(StringSource source) + { + // oklab(59% 0.1 0.1) + // oklab(59 % 0.1 0.1 / 0.5) + return null; + } + + private static Color? ParseOklch(StringSource source) + { + // oklch(60% 0.15 50) + // oklch(60 % 0.15 50 / 0.5) + return null; + } + private static Color? ParseHwba(StringSource source) { var h = ParseAngle(source); From 649beb7b4baa283a367966e5ac03258da5c43a85 Mon Sep 17 00:00:00 2001 From: Florian Rappl Date: Mon, 19 Jun 2023 00:39:12 +0200 Subject: [PATCH 20/53] Added implementation of new color functions #131 --- .../Functions/CssColorFunction.cs | 132 ++++++++++++++ .../Parser/Micro/ColorParser.cs | 164 ++++++++++++++++-- src/AngleSharp.Css/Values/Primitives/Color.cs | 115 ++++++++++++ 3 files changed, 392 insertions(+), 19 deletions(-) diff --git a/src/AngleSharp.Css.Tests/Functions/CssColorFunction.cs b/src/AngleSharp.Css.Tests/Functions/CssColorFunction.cs index dd2a932d..cf36e11b 100644 --- a/src/AngleSharp.Css.Tests/Functions/CssColorFunction.cs +++ b/src/AngleSharp.Css.Tests/Functions/CssColorFunction.cs @@ -52,6 +52,138 @@ public void ParseRgbWithSpacesAndNoneL4_Issue131() var color = s.GetColor(); Assert.AreEqual("rgba(255, 0, 128, 0.35)", color); } + + [Test] + public void ParseOklabToRgb_First() + { + var html = @"

Text

"; + var dom = ParseDocument(html); + var p = dom.QuerySelector("p"); + var s = p.GetStyle(); + var color = s.GetColor(); + Assert.AreEqual("rgba(125, 35, 40, 1)", color); + } + + [Test] + public void ParseOklabToRgb_Second() + { + var html = @"

Text

"; + var dom = ParseDocument(html); + var p = dom.QuerySelector("p"); + var s = p.GetStyle(); + var color = s.GetColor(); + Assert.AreEqual("rgba(198, 93, 7, 1)", color); + } + + [Test] + public void ParseOklabToRgb_Alpha() + { + var html = @"

Text

"; + var dom = ParseDocument(html); + var p = dom.QuerySelector("p"); + var s = p.GetStyle(); + var color = s.GetColor(); + Assert.AreEqual("rgba(198, 93, 7, 0.5)", color); + } + + [Test] + public void ParseOklchToRgb_First() + { + var html = @"

Text

"; + var dom = ParseDocument(html); + var p = dom.QuerySelector("p"); + var s = p.GetStyle(); + var color = s.GetColor(); + Assert.AreEqual("rgba(125, 35, 40, 1)", color); + } + + [Test] + public void ParseOklchToRgb_Second() + { + var html = @"

Text

"; + var dom = ParseDocument(html); + var p = dom.QuerySelector("p"); + var s = p.GetStyle(); + var color = s.GetColor(); + Assert.AreEqual("rgba(198, 93, 7, 1)", color); + } + + [Test] + public void ParseOklchToRgb_Alpha() + { + var html = @"

Text

"; + var dom = ParseDocument(html); + var p = dom.QuerySelector("p"); + var s = p.GetStyle(); + var color = s.GetColor(); + Assert.AreEqual("rgba(198, 93, 7, 0.5)", color); + } + + [Test] + public void ParseLabToRgb_First() + { + var html = @"

Text

"; + var dom = ParseDocument(html); + var p = dom.QuerySelector("p"); + var s = p.GetStyle(); + var color = s.GetColor(); + Assert.AreEqual("rgba(125, 35, 40, 1)", color); + } + + [Test] + public void ParseLabToRgb_Second() + { + var html = @"

Text

"; + var dom = ParseDocument(html); + var p = dom.QuerySelector("p"); + var s = p.GetStyle(); + var color = s.GetColor(); + Assert.AreEqual("rgba(198, 93, 6, 1)", color); + } + + [Test] + public void ParseLabToRgb_Alpha() + { + var html = @"

Text

"; + var dom = ParseDocument(html); + var p = dom.QuerySelector("p"); + var s = p.GetStyle(); + var color = s.GetColor(); + Assert.AreEqual("rgba(198, 93, 6, 0.5)", color); + } + + [Test] + public void ParseLchToRgb_First() + { + var html = @"

Text

"; + var dom = ParseDocument(html); + var p = dom.QuerySelector("p"); + var s = p.GetStyle(); + var color = s.GetColor(); + Assert.AreEqual("rgba(125, 35, 40, 1)", color); + } + + [Test] + public void ParseLchToRgb_Second() + { + var html = @"

Text

"; + var dom = ParseDocument(html); + var p = dom.QuerySelector("p"); + var s = p.GetStyle(); + var color = s.GetColor(); + Assert.AreEqual("rgba(198, 93, 6, 1)", color); + } + + [Test] + public void ParseLchToRgb_Alpha() + { + var html = @"

Text

"; + var dom = ParseDocument(html); + var p = dom.QuerySelector("p"); + var s = p.GetStyle(); + var color = s.GetColor(); + Assert.AreEqual("rgba(198, 93, 6, 0.5)", color); + } } } diff --git a/src/AngleSharp.Css/Parser/Micro/ColorParser.cs b/src/AngleSharp.Css/Parser/Micro/ColorParser.cs index 59234a0c..b7aaffe5 100644 --- a/src/AngleSharp.Css/Parser/Micro/ColorParser.cs +++ b/src/AngleSharp.Css/Parser/Micro/ColorParser.cs @@ -1,7 +1,6 @@ namespace AngleSharp.Css.Parser { using AngleSharp.Css.Values; - using AngleSharp.Io; using AngleSharp.Text; using System; using System.Collections.Generic; @@ -227,30 +226,130 @@ static class ColorParser private static Color? ParseLab(StringSource source) { - // lab(50% 40 59.5) - // lab(50 % 40 59.5 / 0.5) + var l = ParseLabComponent(source); + source.SkipSpacesAndComments(); + var a = ParseLabComponent(source); + source.SkipSpacesAndComments(); + var b = ParseLabComponent(source); + source.SkipSpacesAndComments(); + var c = source.Current; + var alpha = new Nullable(1.0); + + if (l != null && a != null && b != null) + { + source.SkipCurrentAndSpaces(); + + if (c == Symbols.Solidus) + { + alpha = ParseAlpha(source); + source.SkipSpacesAndComments(); + c = source.Current; + source.SkipCurrentAndSpaces(); + } + + if (c == Symbols.RoundBracketClose) + { + return Color.FromLab(l.Value, a.Value, b.Value, alpha.Value); + } + } + return null; } private static Color? ParseLch(StringSource source) { - // lch(52.2% 72.2 50) - // lch(52.2 % 72.2 50 / 0.5) + var l = ParseLabComponent(source); + source.SkipSpacesAndComments(); + var c = ParseLabComponent(source); + source.SkipSpacesAndComments(); + var h = ParseAngle(source); + source.SkipSpacesAndComments(); + var chr = source.Current; + var a = new Nullable(1.0); + + if (l != null && c != null && h != null) + { + source.SkipCurrentAndSpaces(); + + if (chr == Symbols.Solidus) + { + a = ParseAlpha(source); + source.SkipSpacesAndComments(); + chr = source.Current; + source.SkipCurrentAndSpaces(); + } + + if (chr == Symbols.RoundBracketClose) + { + return Color.FromLch(l.Value, c.Value, h.Value, a.Value); + } + } + return null; } private static Color? ParseOklab(StringSource source) { - // oklab(59% 0.1 0.1) - // oklab(59 % 0.1 0.1 / 0.5) + var l = ParseLabComponent(source); + source.SkipSpacesAndComments(); + var a = ParseLabComponent(source); + source.SkipSpacesAndComments(); + var b = ParseLabComponent(source); + source.SkipSpacesAndComments(); + var c = source.Current; + var alpha = new Nullable(1.0); + + if (l != null && a != null && b != null) + { + source.SkipCurrentAndSpaces(); + + if (c == Symbols.Solidus) + { + alpha = ParseAlpha(source); + source.SkipSpacesAndComments(); + c = source.Current; + source.SkipCurrentAndSpaces(); + } + + if (c == Symbols.RoundBracketClose) + { + return Color.FromOklab(l.Value, a.Value, b.Value, alpha.Value); + } + } + return null; } private static Color? ParseOklch(StringSource source) { - // oklch(60% 0.15 50) - // oklch(60 % 0.15 50 / 0.5) + var l = ParseLabComponent(source); + source.SkipSpacesAndComments(); + var c = ParseLabComponent(source); + source.SkipSpacesAndComments(); + var h = ParseAngle(source); + source.SkipSpacesAndComments(); + var chr = source.Current; + var a = new Nullable(1.0); + + if (l != null && c != null && h != null) + { + source.SkipCurrentAndSpaces(); + + if (chr == Symbols.Solidus) + { + a = ParseAlpha(source); + source.SkipSpacesAndComments(); + chr = source.Current; + source.SkipCurrentAndSpaces(); + } + + if (chr == Symbols.RoundBracketClose) + { + return Color.FromOklch(l.Value, c.Value, h.Value, a.Value); + } + } + return null; } @@ -285,6 +384,31 @@ static class ColorParser return null; } + private static Double? ParseLabComponent(StringSource source) + { + var pos = source.Index; + var unit = source.ParseUnit(); + + if (unit == null) + { + source.BackTo(pos); + + if (source.IsIdentifier(CssKeywords.None)) + { + return 0; + } + + return null; + } + + if ((unit.Dimension == String.Empty || unit.Dimension == "%") && Double.TryParse(unit.Value, NumberStyles.Float, CultureInfo.InvariantCulture, out var value)) + { + return value; + } + + return null; + } + private static Byte? ParseRgbOrNoneComponent(StringSource source) { var pos = source.Index; @@ -309,17 +433,19 @@ static class ColorParser { var unit = source.ParseUnit(); - if (unit != null && - Int32.TryParse(unit.Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var value)) + if (unit == null) { - if (unit.Dimension == "%") - { - return (Byte)Math.Round((255.0 * value) / 100.0); - } - else if (unit.Dimension == String.Empty) - { - return (Byte)Math.Max(Math.Min(value, 255f), 0f); - } + return null; + } + + if (unit.Dimension == String.Empty && Int32.TryParse(unit.Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var num)) + { + return (Byte)Math.Max(Math.Min(num, 255f), 0f); + } + + if (unit.Dimension == "%" && Double.TryParse(unit.Value, NumberStyles.Float, CultureInfo.InvariantCulture, out var val)) + { + return (Byte)Math.Round((255.0 * val) / 100.0); } return null; diff --git a/src/AngleSharp.Css/Values/Primitives/Color.cs b/src/AngleSharp.Css/Values/Primitives/Color.cs index 7ad00192..180c044e 100644 --- a/src/AngleSharp.Css/Values/Primitives/Color.cs +++ b/src/AngleSharp.Css/Values/Primitives/Color.cs @@ -174,6 +174,95 @@ public static Color FromGray(Double value, Double alpha = 1.0) => /// The CSS color value. public static Color FromRgb(Byte r, Byte g, Byte b) => new Color(r, g, b); + /// + /// Returns the color that represents the given Lab values. + /// + /// The value for perceptual lightness [0,100]. + /// The value for the first axis [-100,100]. + /// The value for the second axis [-100,100]. + /// The alpha value [0,1]. + /// The CSS color value. + public static Color? FromLab(Double l, Double a, Double b, Double alpha) + { + var fy = (l + 16.0) / 116.0; + var fx = a / 500.0 + fy; + var fz = fy - b / 200.0; + var y = Grow(fy); + var x = Grow(fx) * (0.3457 / 0.3585); + var z = Grow(fz) * ((1.0 - 0.3457 - 0.3585) / 0.3585); + return FromXyz50(x, y, z, alpha); + } + + private static Color? FromXyz50(Double x, Double y, Double z, Double alpha) + { + var r = x * 3.1341359569958707 - y * 1.6173863321612538 - z * 0.4906619460083532; + var g = x * -0.978795502912089 + y * 1.916254567259524 + z * 0.03344273116131949; + var b = x * 0.07195537988411677 - y * 0.2289768264158322 + z * 1.405386058324125; + return FromLrgb(r, g, b, alpha); + } + + private static Color? FromLrgb(Double red, Double green, Double blue, Double alpha) + { + var r = Normalize(Project(red)); + var g = Normalize(Project(green)); + var b = Normalize(Project(blue)); + var a = Normalize(alpha); + return new Color(r, g, b, a); + } + + /// + /// Returns the color that represents the given Lch values. + /// + /// The value for perceptual lightness [0,100]. + /// The value for the axis [0,150]. + /// The value for the angle to the axis [0,1]. + /// The alpha value [0,1]. + /// The CSS color value. + public static Color? FromLch(Double l, Double c, Double h, Double alpha) + { + var angle = h * 2.0 * Math.PI; + var a = c != 0 ? c * Math.Cos(angle) : 0.0; + var b = c != 0 ? c * Math.Sin(angle) : 0.0; + return FromLab(l, a, b, alpha); + } + + /// + /// Returns the color that represents the given Oklab values. + /// + /// The value for perceptual lightness [0,100]. + /// The value for the first axis [-0.4,0.4]. + /// The value for the second axis [-0.4,0.4]. + /// The alpha value [0,1]. + /// The CSS color value. + public static Color? FromOklab(Double l, Double a, Double b, Double alpha) + { + // normalize to 0..1 + l *= 0.01; + var L = Math.Pow(l * 0.99999999845051981432 + 0.39633779217376785678 * a + 0.21580375806075880339 * b, 3); + var M = Math.Pow(l * 1.0000000088817607767 - 0.1055613423236563494 * a - 0.063854174771705903402 * b, 3); + var S = Math.Pow(l * 1.0000000546724109177 - 0.089484182094965759684 * a - 1.2914855378640917399 * b, 3); + var red = +4.076741661347994 * L - 3.307711590408193 * M + 0.230969928729428 * S; + var green = -1.2684380040921763 * L + 2.6097574006633715 * M - 0.3413193963102197 * S; + var blue = -0.004196086541837188 * L - 0.7034186144594493 * M + 1.7076147009309444 * S; + return FromLrgb(red, green, blue, alpha); + } + + /// + /// Returns the color that represents the given Oklch values. + /// + /// The value for perceptual lightness [0,100]. + /// The value for the axis [0,0.4]. + /// The value for the angle to the axis [0,1]. + /// The alpha value [0,1]. + /// The CSS color value. + public static Color? FromOklch(Double l, Double c, Double h, Double alpha) + { + var angle = h * 2.0 * Math.PI; + var a = c != 0 ? c * Math.Cos(angle) : 0.0; + var b = c != 0 ? c * Math.Sin(angle) : 0.0; + return FromOklab(l, a, b, alpha); + } + /// /// Returns the color from the given hex string. /// @@ -537,6 +626,32 @@ public static Color Mix(Double alpha, Color above, Color below) #region Helpers + private static Double Project(Double c) + { + var abs = Math.Abs(c); + + if (abs > 0.0031308) + { + var sgn = Math.Sign(c); + + if (sgn == 0) + { + sgn = 1; + } + + return sgn * (1.055 * Math.Pow(abs, 1.0 / 2.4) - 0.055); + } + + return c * 12.92; + } + + private static Double Grow(Double v) + { + var k = Math.Pow(29.0, 3.0) / Math.Pow(3.0, 3.0); + var e = Math.Pow(6.0, 3.0) / Math.Pow(29.0, 3.0); + return (Math.Pow(v, 3.0) > e ? Math.Pow(v, 3.0) : (116.0 * v - 16.0) / k); + } + private static Byte Normalize(Double value) => (Byte)Math.Max(Math.Min(Math.Truncate(256.0 * value), 255.0), 0.0); From 240cee3b9647308c73497f5c97a5e13f6e64959e Mon Sep 17 00:00:00 2001 From: Florian Rappl Date: Fri, 15 Dec 2023 21:37:05 +0100 Subject: [PATCH 21/53] Update Directory.Build.props --- src/Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 3a5ecd70..4d1df3cd 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -2,6 +2,6 @@ Extends the CSSOM from the core AngleSharp library. AngleSharp.Css - 0.17.0 + 1.0.0 From 7a96a5587ff8335047156c3419ae5f2526ca79fd Mon Sep 17 00:00:00 2001 From: Florian Rappl Date: Tue, 16 Jan 2024 14:18:08 +0100 Subject: [PATCH 22/53] Added support for recent list-type --- CHANGELOG.md | 1 + LICENSE | 2 +- README.md | 2 +- .../Declarations/CssListProperty.cs | 98 +++++++- src/AngleSharp.Css.nuspec | 2 +- src/AngleSharp.Css/Constants/CssKeywords.cs | 220 ++++++++++++++++++ src/AngleSharp.Css/Constants/FunctionNames.cs | 5 + src/AngleSharp.Css/Constants/Map.cs | 208 ++++++++++------- .../Declarations/ContentDeclaration.cs | 2 +- .../Dom/ElementCssInlineStyleExtensions.cs | 2 +- src/AngleSharp.Css/Dom/Internal/MediaList.cs | 2 +- .../Dom/Internal/Rules/CssFontFaceRule.cs | 2 +- .../Dom/Internal/Rules/CssViewportRule.cs | 2 +- src/AngleSharp.Css/Dom/ListStyle.cs | 160 ++++++++++++- src/AngleSharp.Css/Dom/SymbolsType.cs | 29 +++ .../Extensions/MediaListExtensions.cs | 2 +- .../Factories/DefaultDeclarationFactory.cs | 2 +- .../DefaultDocumentFunctionFactory.cs | 2 +- .../DefaultFeatureValidatorFactory.cs | 2 +- .../Factories/DefaultPseudoElementFactory.cs | 2 +- src/AngleSharp.Css/Factories/Factory.cs | 10 +- .../Parser/Micro/ColorParser.cs | 2 +- .../Parser/Micro/CssUriParser.cs | 25 +- .../Parser/Micro/GradientParser.cs | 2 +- .../Parser/Micro/IdentParser.cs | 2 +- .../Parser/Micro/StringParser.cs | 11 +- .../Parser/Micro/SymbolsParser.cs | 63 +++++ .../Parser/Micro/TimingFunctionParser.cs | 2 +- .../Parser/Micro/TransformParser.cs | 2 +- src/AngleSharp.Css/ValueConverters.cs | 19 +- .../Composites/CssBackgroundSizeValue.cs | 4 +- src/AngleSharp.Css/Values/CssColors.cs | 2 +- .../Values/Functions/CssCubicBezierValue.cs | 10 +- .../Values/Functions/CssScaleValue.cs | 2 +- .../Values/Functions/CssSymbolsValue.cs | 71 ++++++ src/AngleSharp.Css/Values/Primitives/Angle.cs | 10 +- src/AngleSharp.Css/Values/Primitives/Color.cs | 28 +-- .../Values/Primitives/Length.cs | 18 +- src/AngleSharp.Css/Values/Primitives/Point.cs | 18 +- src/AngleSharp.Css/Values/Primitives/Time.cs | 2 +- .../Values/Raws/CssInheritValue.cs | 2 +- src/AngleSharp.Css/Values/TransformMatrix.cs | 4 +- 42 files changed, 875 insertions(+), 181 deletions(-) create mode 100644 src/AngleSharp.Css/Dom/SymbolsType.cs create mode 100644 src/AngleSharp.Css/Parser/Micro/SymbolsParser.cs create mode 100644 src/AngleSharp.Css/Values/Functions/CssSymbolsValue.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index d2e0402a..94dc2d2e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ Released on tbd. - Added more CSSOM possibilities and helpers (#6) - Added parts of recent color spec update such as `rgb` with spaces (#131) - Added now Color L4 parsing with `hsl`, `hwb`, `lab`, `lch`, `oklab`, and `oklch` +- Added support for recent CSS `list-type` values (#152) # 0.17.0 diff --git a/LICENSE b/LICENSE index 470f190b..10b5b1c2 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2013 - 2023 AngleSharp +Copyright (c) 2013 - 2024 AngleSharp Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 5eee1792..d0fcc9ed 100644 --- a/README.md +++ b/README.md @@ -99,7 +99,7 @@ This project is supported by the [.NET Foundation](https://dotnetfoundation.org) The MIT License (MIT) -Copyright (c) 2016 - 2022 AngleSharp +Copyright (c) 2016 - 2024 AngleSharp Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: diff --git a/src/AngleSharp.Css.Tests/Declarations/CssListProperty.cs b/src/AngleSharp.Css.Tests/Declarations/CssListProperty.cs index 1fca39c3..e23f95ac 100644 --- a/src/AngleSharp.Css.Tests/Declarations/CssListProperty.cs +++ b/src/AngleSharp.Css.Tests/Declarations/CssListProperty.cs @@ -1,4 +1,4 @@ -namespace AngleSharp.Css.Tests.Declarations +namespace AngleSharp.Css.Tests.Declarations { using NUnit.Framework; using static CssConstructionFunctions; @@ -125,14 +125,15 @@ public void CssListStyleTypeDecimalLeadingZeroLegal() } [Test] - public void CssListStyleTypeNumberIllegal() + public void CssListStyleTypeSomeValueLegal() { var snippet = "list-style-type: number "; var property = ParseDeclaration(snippet); Assert.AreEqual("list-style-type", property.Name); Assert.IsFalse(property.IsImportant); - Assert.IsTrue(property.IsInherited); - Assert.IsFalse(property.HasValue); + Assert.IsFalse(property.IsInherited); + Assert.IsTrue(property.HasValue); + Assert.AreEqual("number", property.Value); } [Test] @@ -277,5 +278,94 @@ public void CssCounterIncrementLegal() Assert.IsTrue(property.HasValue); Assert.AreEqual("chapter 1 section 2 page 1", property.Value); } + + [Test] + public void CssListStyleStringValue_Issue152() + { + var snippet = "list-style-type: \"-\""; + var property = ParseDeclaration(snippet); + Assert.AreEqual("list-style-type", property.Name); + Assert.IsTrue(property.HasValue); + Assert.AreEqual("\"-\"", property.Value); + } + + [Test] + public void CssListStyleKannada_Issue152() + { + var snippet = "list-style-type: kannada"; + var property = ParseDeclaration(snippet); + Assert.AreEqual("list-style-type", property.Name); + Assert.IsTrue(property.HasValue); + Assert.AreEqual("kannada", property.Value); + } + + [Test] + public void CssListStyleTradChineseInformal_Issue152() + { + var snippet = "list-style-type: trad-chinese-informal;"; + var property = ParseDeclaration(snippet); + Assert.AreEqual("list-style-type", property.Name); + Assert.IsTrue(property.HasValue); + Assert.AreEqual("trad-chinese-informal", property.Value); + } + + [Test] + public void CssListStyleGeorgian_Issue152() + { + var snippet = "list-style-type: georgian"; + var property = ParseDeclaration(snippet); + Assert.AreEqual("list-style-type", property.Name); + Assert.IsTrue(property.HasValue); + Assert.AreEqual("georgian", property.Value); + } + + [Test] + public void CssListStyleDecimal_Issue152() + { + var snippet = "list-style-type: decimal"; + var property = ParseDeclaration(snippet); + Assert.AreEqual("list-style-type", property.Name); + Assert.IsTrue(property.HasValue); + Assert.AreEqual("decimal", property.Value); + } + + [Test] + public void CssListStyleSquare_Issue152() + { + var snippet = "list-style-type: square"; + var property = ParseDeclaration(snippet); + Assert.AreEqual("list-style-type", property.Name); + Assert.IsTrue(property.HasValue); + Assert.AreEqual("square", property.Value); + } + + [Test] + public void CssListStyleCircle_Issue152() + { + var snippet = "list-style-type: circle"; + var property = ParseDeclaration(snippet); + Assert.AreEqual("list-style-type", property.Name); + Assert.IsTrue(property.HasValue); + Assert.AreEqual("circle", property.Value); + } + + [Test] + public void CssListStyleSymbolsFunction_Issue152() + { + var snippet = "list-style-type: symbols(cyclic \"*\" \"†\" \"‡\")"; + var property = ParseDeclaration(snippet); + Assert.AreEqual("list-style-type", property.Name); + Assert.IsTrue(property.HasValue); + Assert.AreEqual("symbols(cyclic \"*\" \"†\" \"‡\")", property.Value); + } + + [Test] + public void CssListStyleSymbolsFailingForWrongType_Issue152() + { + var snippet = "list-style-type: symbols(foo \"*\" \"†\" \"‡\")"; + var property = ParseDeclaration(snippet); + Assert.AreEqual("list-style-type", property.Name); + Assert.IsFalse(property.HasValue); + } } } diff --git a/src/AngleSharp.Css.nuspec b/src/AngleSharp.Css.nuspec index 55da3364..dbab86aa 100644 --- a/src/AngleSharp.Css.nuspec +++ b/src/AngleSharp.Css.nuspec @@ -12,7 +12,7 @@ false Extends the CSSOM from the core AngleSharp library. https://github.com/AngleSharp/AngleSharp.Css/blob/main/CHANGELOG.md - Copyright 2016-2023, AngleSharp + Copyright 2016-2024, AngleSharp html html5 css css3 dom styling library anglesharp angle diff --git a/src/AngleSharp.Css/Constants/CssKeywords.cs b/src/AngleSharp.Css/Constants/CssKeywords.cs index b2087620..4439cb45 100644 --- a/src/AngleSharp.Css/Constants/CssKeywords.cs +++ b/src/AngleSharp.Css/Constants/CssKeywords.cs @@ -27,6 +27,26 @@ public static class CssKeywords ///
public static readonly String Clip = "clip"; + /// + /// The cyclic keyword. + /// + public static readonly String Cyclic = "cyclic"; + + /// + /// The numeric keyword. + /// + public static readonly String Numeric = "numeric"; + + /// + /// The alphabetic keyword. + /// + public static readonly String Alphabetic = "alphabetic"; + + /// + /// The symbolic keyword. + /// + public static readonly String Symbolic = "symbolic"; + /// /// The legacy keyword. /// @@ -37,6 +57,11 @@ public static class CssKeywords /// public static readonly String Normal = "normal"; + /// + /// The arabic-indic keyword. + /// + public static readonly String ArabicIndic = "arabic-indic"; + /// /// The pre keyword. /// @@ -97,6 +122,16 @@ public static class CssKeywords /// public static readonly String InterCharacter = "inter-character"; + /// + /// The katakana keyword. + /// + public static readonly String Katakana = "katakana"; + + /// + /// The katakana-iroha keyword. + /// + public static readonly String KatakanaIroha = "katakana-iroha"; + /// /// The kashida keyword. /// @@ -1127,16 +1162,101 @@ public static class CssKeywords /// public static readonly String UpperLatin = "upper-latin"; + /// + /// The malayalam keyword. + /// + public static readonly String Malayalam = "malayalam"; + + /// + /// The myanmar keyword. + /// + public static readonly String Myanmar = "myanmar"; + + /// + /// The mongolian keyword. + /// + public static readonly String Mongolian = "mongolian"; + + /// + /// The oriya keyword. + /// + public static readonly String Oriya = "oriya"; + + /// + /// The persian keyword. + /// + public static readonly String Persian = "persian"; + + /// + /// The tamil keyword. + /// + public static readonly String Tamil = "tamil"; + + /// + /// The thai keyword. + /// + public static readonly String Thai = "thai"; + + /// + /// The telugu keyword. + /// + public static readonly String Telugu = "telugu"; + + /// + /// The lao keyword. + /// + public static readonly String Lao = "lao"; + + /// + /// The tibetan keyword. + /// + public static readonly String Tibetan = "tibetan"; + + /// + /// The trad-chinese-formal keyword. + /// + public static readonly String TradChineseFormal = "trad-chinese-formal"; + + /// + /// The trad-chinese-informal keyword. + /// + public static readonly String TradChineseInformal = "trad-chinese-informal"; + /// /// The armenian keyword. /// public static readonly String Armenian = "armenian"; + /// + /// The lower-armenian keyword. + /// + public static readonly String LowerArmenian = "lower-armenian"; + + /// + /// The upper-armenian keyword. + /// + public static readonly String UpperArmenian = "upper-armenian"; + /// /// The georgian keyword. /// public static readonly String Georgian = "georgian"; + /// + /// The kannada keyword. + /// + public static readonly String Kannada = "kannada"; + + /// + /// The disclosure-open keyword. + /// + public static readonly String DisclosureOpen = "disclosure-open"; + + /// + /// The disclosure-closed keyword. + /// + public static readonly String DisclosureClosed = "disclosure-closed"; + /// /// The lower-alpha keyword. /// @@ -1317,6 +1437,106 @@ public static class CssKeywords /// public static readonly String Fill = "fill"; + /// + /// The cjk-decimal keyword. + /// + public static readonly String CjkDecimal = "cjk-decimal"; + + /// + /// The cjk-earthly-branch keyword. + /// + public static readonly String CjkEarthlyBranch = "cjk-earthly-branch"; + + /// + /// The cjk-heavenly-stem keyword. + /// + public static readonly String CjkHeavenlyStem = "cjk-heavenly-stem"; + + /// + /// The cjk-ideographic keyword. + /// + public static readonly String CjkIdeographic = "cjk-ideographic"; + + /// + /// The bengali keyword. + /// + public static readonly String Bengali = "bengali"; + + /// + /// The cambodian keyword. + /// + public static readonly String Cambodian = "cambodian"; + + /// + /// The devanagari keyword. + /// + public static readonly String Devanagari = "devanagari"; + + /// + /// The ethiopic-numeric keyword. + /// + public static readonly String EthiopicNumeric = "ethiopic-numeric"; + + /// + /// The gurmukhi keyword. + /// + public static readonly String Gurmukhi = "gurmukhi"; + + /// + /// The gujarati keyword. + /// + public static readonly String Gujarati = "gujarati"; + + /// + /// The hebrew keyword. + /// + public static readonly String Hebrew = "hebrew"; + + /// + /// The hiragana keyword. + /// + public static readonly String Hiragana = "hiragana"; + + /// + /// The hiragana-iroha keyword. + /// + public static readonly String HiraganaIroha = "hiragana-iroha"; + + /// + /// The japanese-formal keyword. + /// + public static readonly String JapaneseFormal = "japanese-formal"; + + /// + /// The japanese-informal keyword. + /// + public static readonly String JapaneseInformal = "japanese-informal"; + + /// + /// The simp-chinese-informal keyword. + /// + public static readonly String SimpChineseInformal = "simp-chinese-informal"; + + /// + /// The simp-chinese-formal keyword. + /// + public static readonly String SimpChineseFormal = "simp-chinese-formal"; + + /// + /// The korean-hangul-formal keyword. + /// + public static readonly String KoreanHangulFormal = "korean-hangul-formal"; + + /// + /// The korean-hanja-formal keyword. + /// + public static readonly String KoreanHanjaFormal = "korean-hanja-formal"; + + /// + /// The korean-hanja-informal keyword. + /// + public static readonly String KoreanHanjaInformal = "korean-hanja-informal"; + /// /// The screen keyword. /// diff --git a/src/AngleSharp.Css/Constants/FunctionNames.cs b/src/AngleSharp.Css/Constants/FunctionNames.cs index d9c4c872..207fd2df 100644 --- a/src/AngleSharp.Css/Constants/FunctionNames.cs +++ b/src/AngleSharp.Css/Constants/FunctionNames.cs @@ -57,6 +57,11 @@ public static class FunctionNames /// public static readonly String Attr = "attr"; + /// + /// The symbols function. + /// + public static readonly String Symbols = "symbols"; + /// /// The fit-content function. /// diff --git a/src/AngleSharp.Css/Constants/Map.cs b/src/AngleSharp.Css/Constants/Map.cs index f5f6d81c..0c72c40d 100644 --- a/src/AngleSharp.Css/Constants/Map.cs +++ b/src/AngleSharp.Css/Constants/Map.cs @@ -14,7 +14,7 @@ static class Map /// /// Contains the string-Whitespace mapping. /// - public static readonly Dictionary Whitespaces = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary Whitespaces = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.Normal, Whitespace.Normal }, { CssKeywords.Pre, Whitespace.Pre }, @@ -26,7 +26,7 @@ static class Map /// /// Contains the string-Angle mapping for linear-gradients.s /// - public static readonly Dictionary GradientAngles = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary GradientAngles = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.Left, new Angle(270.0, Angle.Unit.Deg) }, { CssKeywords.Top, new Angle(0.0, Angle.Unit.Deg) }, @@ -41,7 +41,7 @@ static class Map /// /// Contains the string-TextTransform mapping. /// - public static readonly Dictionary TextTransforms = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary TextTransforms = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.None, TextTransform.None }, { CssKeywords.Capitalize, TextTransform.Capitalize }, @@ -53,7 +53,7 @@ static class Map /// /// Contains the string-TextAlignLast mapping. /// - public static readonly Dictionary TextAlignLasts = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary TextAlignLasts = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.Auto, TextAlignLast.Auto }, { CssKeywords.Start, TextAlignLast.Start }, @@ -67,7 +67,7 @@ static class Map /// /// Contains the string-TextAnchor mapping. /// - public static readonly Dictionary TextAnchors = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary TextAnchors = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.Start, TextAnchor.Start }, { CssKeywords.Middle, TextAnchor.Middle }, @@ -77,7 +77,7 @@ static class Map /// /// Contains the string-TextJustify mapping. /// - public static readonly Dictionary TextJustifies = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary TextJustifies = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.Auto, TextJustify.Auto }, { CssKeywords.Distribute, TextJustify.Distribute }, @@ -93,7 +93,7 @@ static class Map /// /// Contains the string-HorizontalAlignment mapping. /// - public static readonly Dictionary HorizontalAlignments = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary HorizontalAlignments = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.Left, HorizontalAlignment.Left }, { CssKeywords.Right, HorizontalAlignment.Right }, @@ -104,7 +104,7 @@ static class Map /// /// Contains the string-VerticalAlignment mapping. /// - public static readonly Dictionary VerticalAlignments = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary VerticalAlignments = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.Baseline, VerticalAlignment.Baseline }, { CssKeywords.Sub, VerticalAlignment.Sub }, @@ -119,7 +119,7 @@ static class Map /// /// Contains the string-LineStyle mapping. /// - public static readonly Dictionary LineStyles = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary LineStyles = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.None, LineStyle.None }, { CssKeywords.Solid, LineStyle.Solid }, @@ -136,7 +136,7 @@ static class Map /// /// Contains the string-BoxModel mapping. /// - public static readonly Dictionary BoxModels = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary BoxModels = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.BorderBox, BoxModel.BorderBox }, { CssKeywords.PaddingBox, BoxModel.PaddingBox }, @@ -146,7 +146,7 @@ static class Map /// /// Contains the string-TimingFunction mapping. /// - public static readonly Dictionary TimingFunctions = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary TimingFunctions = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.Ease, new CssCubicBezierValue(0.25, 0.1, 0.25, 1.0) }, { CssKeywords.EaseIn, new CssCubicBezierValue(0.42, 0.0, 1.0, 1.0) }, @@ -160,7 +160,7 @@ static class Map /// /// Contains the string-AnimationFillStyle mapping. /// - public static readonly Dictionary AnimationFillStyles = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary AnimationFillStyles = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.None, AnimationFillStyle.None }, { CssKeywords.Forwards, AnimationFillStyle.Forwards }, @@ -171,7 +171,7 @@ static class Map /// /// Contains the string-AnimationDirection mapping. /// - public static readonly Dictionary AnimationDirections = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary AnimationDirections = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.Normal, AnimationDirection.Normal }, { CssKeywords.Reverse, AnimationDirection.Reverse }, @@ -182,7 +182,7 @@ static class Map /// /// Contains the string-Visibility mapping. /// - public static readonly Dictionary Visibilities = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary Visibilities = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.Visible, Visibility.Visible }, { CssKeywords.Hidden, Visibility.Hidden }, @@ -192,7 +192,7 @@ static class Map /// /// Contains the string-PlayState mapping. /// - public static readonly Dictionary PlayStates = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary PlayStates = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.Running, PlayState.Running }, { CssKeywords.Paused, PlayState.Paused }, @@ -201,7 +201,7 @@ static class Map /// /// Contains the string-FontVariant mapping. /// - public static readonly Dictionary FontVariants = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary FontVariants = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.Normal, FontVariant.Normal }, { CssKeywords.SmallCaps, FontVariant.SmallCaps }, @@ -210,38 +210,90 @@ static class Map /// /// Contains the string-DirectionMode mapping. /// - public static readonly Dictionary DirectionModes = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary DirectionModes = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.Ltr, DirectionMode.Ltr }, { CssKeywords.Rtl, DirectionMode.Rtl }, }; + /// + /// Contains the string-SymbolsType mapping. + /// + public static readonly Dictionary SymbolsTypes = new(StringComparer.OrdinalIgnoreCase) + { + { CssKeywords.Cyclic, SymbolsType.Cyclic }, + { CssKeywords.Numeric, SymbolsType.Numeric }, + { CssKeywords.Alphabetic, SymbolsType.Alphabetic }, + { CssKeywords.Symbolic, SymbolsType.Symbolic }, + { CssKeywords.Fixed, SymbolsType.Fixed }, + }; + /// /// Contains the string-ListStyle mapping. /// - public static readonly Dictionary ListStyles = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary ListStyles = new(StringComparer.OrdinalIgnoreCase) { + { CssKeywords.None, ListStyle.None }, { CssKeywords.Disc, ListStyle.Disc }, { CssKeywords.Circle, ListStyle.Circle }, { CssKeywords.Square, ListStyle.Square }, { CssKeywords.Decimal, ListStyle.Decimal }, + { CssKeywords.CjkDecimal, ListStyle.CjkDecimal }, { CssKeywords.DecimalLeadingZero, ListStyle.DecimalLeadingZero }, { CssKeywords.LowerRoman, ListStyle.LowerRoman }, { CssKeywords.UpperRoman, ListStyle.UpperRoman }, { CssKeywords.LowerGreek, ListStyle.LowerGreek }, { CssKeywords.LowerLatin, ListStyle.LowerLatin }, + { CssKeywords.LowerAlpha, ListStyle.LowerLatin }, { CssKeywords.UpperLatin, ListStyle.UpperLatin }, + { CssKeywords.UpperAlpha, ListStyle.UpperLatin }, + { CssKeywords.ArabicIndic, ListStyle.ArabicIndic }, { CssKeywords.Armenian, ListStyle.Armenian }, + { CssKeywords.Bengali, ListStyle.Bengali }, + { CssKeywords.Cambodian, ListStyle.Cambodian }, + { CssKeywords.CjkEarthlyBranch, ListStyle.CjkEarthlyBranch }, + { CssKeywords.CjkHeavenlyStem, ListStyle.CjkHeavenlyStem }, + { CssKeywords.CjkIdeographic, ListStyle.TradChineseInformal }, + { CssKeywords.Devanagari, ListStyle.Devanagari }, + { CssKeywords.EthiopicNumeric, ListStyle.EthiopicNumeric }, { CssKeywords.Georgian, ListStyle.Georgian }, - { CssKeywords.LowerAlpha, ListStyle.LowerLatin }, - { CssKeywords.UpperAlpha, ListStyle.UpperLatin }, - { CssKeywords.None, ListStyle.None }, + { CssKeywords.Gurmukhi, ListStyle.Gurmukhi }, + { CssKeywords.Hebrew, ListStyle.Hebrew }, + { CssKeywords.Gujarati, ListStyle.Gujarati }, + { CssKeywords.Hiragana, ListStyle.Hiragana }, + { CssKeywords.HiraganaIroha, ListStyle.IrohaHiragana }, + { CssKeywords.JapaneseFormal, ListStyle.JapaneseFormal }, + { CssKeywords.JapaneseInformal, ListStyle.JapaneseInformal }, + { CssKeywords.Kannada, ListStyle.Kannada }, + { CssKeywords.KatakanaIroha, ListStyle.KatakanaIroha }, + { CssKeywords.Katakana, ListStyle.Katakana }, + { CssKeywords.KoreanHangulFormal, ListStyle.KoreanHangulFormal }, + { CssKeywords.KoreanHanjaFormal, ListStyle.KoreanHanjaFormal }, + { CssKeywords.KoreanHanjaInformal, ListStyle.KoreanHanjaInformal }, + { CssKeywords.Lao, ListStyle.Lao }, + { CssKeywords.UpperArmenian, ListStyle.UpperArmenian }, + { CssKeywords.LowerArmenian, ListStyle.LowerArmenian }, + { CssKeywords.Malayalam, ListStyle.Malayalam }, + { CssKeywords.Mongolian, ListStyle.Mongolian }, + { CssKeywords.Myanmar, ListStyle.Myanmar }, + { CssKeywords.Oriya, ListStyle.Oriya }, + { CssKeywords.Persian, ListStyle.Persian }, + { CssKeywords.SimpChineseFormal, ListStyle.SimpChineseFormal }, + { CssKeywords.SimpChineseInformal, ListStyle.SimpChineseInformal }, + { CssKeywords.Tamil, ListStyle.Tamil }, + { CssKeywords.Telugu, ListStyle.Telugu }, + { CssKeywords.Thai, ListStyle.Thai }, + { CssKeywords.Tibetan, ListStyle.Tibetan }, + { CssKeywords.TradChineseFormal, ListStyle.TradChineseFormal }, + { CssKeywords.TradChineseInformal, ListStyle.TradChineseInformal }, + { CssKeywords.DisclosureClosed, ListStyle.DisclosureClosed }, + { CssKeywords.DisclosureOpen, ListStyle.DisclosureOpen }, }; /// /// Contains the string-ListPosition mapping. /// - public static readonly Dictionary ListPositions = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary ListPositions = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.Inside, ListPosition.Inside }, { CssKeywords.Outside, ListPosition.Outside }, @@ -250,7 +302,7 @@ static class Map /// /// Contains the string-length mapping. /// - public static readonly Dictionary FontSizes = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary FontSizes = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.XxSmall, new Length(0.6, Length.Unit.Em) }, { CssKeywords.XSmall, new Length(0.75, Length.Unit.Em) }, @@ -267,7 +319,7 @@ static class Map /// /// Contains the string-TextDecorationStyle mapping. /// - public static readonly Dictionary TextDecorationStyles = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary TextDecorationStyles = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.Solid, TextDecorationStyle.Solid }, { CssKeywords.Double, TextDecorationStyle.Double }, @@ -279,7 +331,7 @@ static class Map /// /// Contains the string-TextDecorationLine mapping. /// - public static readonly Dictionary TextDecorationLines = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary TextDecorationLines = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.Underline, TextDecorationLine.Underline }, { CssKeywords.Overline, TextDecorationLine.Overline }, @@ -290,7 +342,7 @@ static class Map /// /// Contains the string-BorderRepeat mapping. /// - public static readonly Dictionary BorderRepeats = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary BorderRepeats = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.Stretch, BorderRepeat.Stretch }, { CssKeywords.Repeat, BorderRepeat.Repeat }, @@ -300,7 +352,7 @@ static class Map /// /// Contains the string-border width mapping. /// - public static readonly Dictionary BorderWidths = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary BorderWidths = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.Thin, Length.Thin }, { CssKeywords.Medium, Length.Medium }, @@ -310,7 +362,7 @@ static class Map /// /// Contains the string-font family mapping. /// - public static readonly Dictionary FontFamilies = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary FontFamilies = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.Serif, "Times New Roman" }, { CssKeywords.SansSerif, "Arial" }, @@ -322,7 +374,7 @@ static class Map /// /// Contains the string-BackgroundAttachment mapping. /// - public static readonly Dictionary BackgroundAttachments = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary BackgroundAttachments = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.Fixed, BackgroundAttachment.Fixed }, { CssKeywords.Local, BackgroundAttachment.Local }, @@ -332,7 +384,7 @@ static class Map /// /// Contains the string-FontStyle mapping. /// - public static readonly Dictionary FontStyles = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary FontStyles = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.Normal, FontStyle.Normal }, { CssKeywords.Italic, FontStyle.Italic }, @@ -342,7 +394,7 @@ static class Map /// /// Contains the string-FontStretch mapping. /// - public static readonly Dictionary FontStretches = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary FontStretches = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.Normal, FontStretch.Normal }, { CssKeywords.UltraCondensed, FontStretch.UltraCondensed }, @@ -358,7 +410,7 @@ static class Map /// /// Contains the string-BreakMode (general) mapping. /// - public static readonly Dictionary BreakModes = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary BreakModes = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.Auto, BreakMode.Auto }, { CssKeywords.Always, BreakMode.Always }, @@ -374,7 +426,7 @@ static class Map /// /// Contains the string-BreakMode (page) mapping. /// - public static readonly Dictionary PageBreakModes = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary PageBreakModes = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.Auto, BreakMode.Auto }, { CssKeywords.Always, BreakMode.Always }, @@ -386,7 +438,7 @@ static class Map /// /// Contains the string-BreakMode (inside) mapping. /// - public static readonly Dictionary BreakInsideModes = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary BreakInsideModes = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.Auto, BreakMode.Auto }, { CssKeywords.Avoid, BreakMode.Avoid }, @@ -398,7 +450,7 @@ static class Map /// /// Contains the string-BreakMode (page/inside) mapping. /// - public static readonly Dictionary PageBreakInsideModes = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary PageBreakInsideModes = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.Auto, BreakMode.Auto }, { CssKeywords.Avoid, BreakMode.Avoid }, @@ -407,7 +459,7 @@ static class Map /// /// Contains the string-horizontal modes mapping. /// - public static readonly Dictionary HorizontalModes = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary HorizontalModes = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.Left, 0.0 }, { CssKeywords.Center, 0.5 }, @@ -417,7 +469,7 @@ static class Map /// /// Contains the string-vertical modes mapping. /// - public static readonly Dictionary VerticalModes = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary VerticalModes = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.Top, 0.0 }, { CssKeywords.Center, 0.5 }, @@ -427,7 +479,7 @@ static class Map /// /// Contains the string-UnicodeMode mapping. /// - public static readonly Dictionary UnicodeModes = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary UnicodeModes = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.Normal, UnicodeMode.Normal }, { CssKeywords.Embed, UnicodeMode.Embed }, @@ -440,7 +492,7 @@ static class Map /// /// Contains the string-whitespace mapping. /// - public static readonly Dictionary SystemCursors = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary SystemCursors = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.Auto, SystemCursor.Auto }, { CssKeywords.Default, SystemCursor.Default }, @@ -483,7 +535,7 @@ static class Map /// /// Contains the string-PositionMode mapping. /// - public static readonly Dictionary PositionModes = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary PositionModes = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.Static, PositionMode.Static }, { CssKeywords.Relative, PositionMode.Relative }, @@ -495,7 +547,7 @@ static class Map /// /// Contains the string-OverflowMode mapping. /// - public static readonly Dictionary OverflowModes = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary OverflowModes = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.Visible, OverflowMode.Visible }, { CssKeywords.Hidden, OverflowMode.Hidden }, @@ -506,7 +558,7 @@ static class Map /// /// Contains the extended string-OverflowMode mapping. /// - public static readonly Dictionary OverflowExtendedModes = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary OverflowExtendedModes = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.Visible, OverflowMode.Visible }, { CssKeywords.Hidden, OverflowMode.Hidden }, @@ -518,7 +570,7 @@ static class Map /// /// Contains the string-Floating mapping. /// - public static readonly Dictionary Floatings = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary Floatings = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.None, Floating.None }, { CssKeywords.Left, Floating.Left }, @@ -529,7 +581,7 @@ static class Map /// /// Contains the string-DisplayMode mapping. /// - public static readonly Dictionary DisplayModes = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary DisplayModes = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.None, DisplayMode.None }, { CssKeywords.Inline, DisplayMode.Inline }, @@ -555,7 +607,7 @@ static class Map /// /// Contains the string-ClearMode mapping. /// - public static readonly Dictionary ClearModes = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary ClearModes = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.None, ClearMode.None }, { CssKeywords.Left, ClearMode.Left }, @@ -566,7 +618,7 @@ static class Map /// /// Contains the string-BackgroundRepeat mapping. /// - public static readonly Dictionary BackgroundRepeats = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary BackgroundRepeats = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.NoRepeat, BackgroundRepeat.NoRepeat }, { CssKeywords.Repeat, BackgroundRepeat.Repeat }, @@ -577,7 +629,7 @@ static class Map /// /// Contains the string-BlendMode mapping. /// - public static readonly Dictionary BlendModes = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary BlendModes = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.Color, BlendMode.Color }, { CssKeywords.ColorBurn, BlendMode.ColorBurn }, @@ -600,7 +652,7 @@ static class Map /// /// Contains the string-UpdateFrequency mapping. /// - public static readonly Dictionary UpdateFrequencies = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary UpdateFrequencies = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.None, UpdateFrequency.None }, { CssKeywords.Slow, UpdateFrequency.Slow }, @@ -610,7 +662,7 @@ static class Map /// /// Contains the string-ScriptingState mapping. /// - public static readonly Dictionary ScriptingStates = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary ScriptingStates = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.None, ScriptingState.None }, { CssKeywords.InitialOnly, ScriptingState.InitialOnly }, @@ -620,7 +672,7 @@ static class Map /// /// Contains the string-PointerAccuracy mapping. /// - public static readonly Dictionary PointerAccuracies = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary PointerAccuracies = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.None, PointerAccuracy.None }, { CssKeywords.Coarse, PointerAccuracy.Coarse }, @@ -630,7 +682,7 @@ static class Map /// /// Contains the string-HoverAbility mapping. /// - public static readonly Dictionary HoverAbilities = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary HoverAbilities = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.None, HoverAbility.None }, { CssKeywords.OnDemand, HoverAbility.OnDemand }, @@ -640,7 +692,7 @@ static class Map /// /// Contains the string-SizeMode mapping. /// - public static readonly Dictionary RadialGradientSizeModes = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary RadialGradientSizeModes = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.ClosestSide, CssRadialGradientValue.SizeMode.ClosestSide }, { CssKeywords.FarthestSide, CssRadialGradientValue.SizeMode.FarthestSide }, @@ -651,7 +703,7 @@ static class Map /// /// Contains the string-ObjectFitting mapping. /// - public static readonly Dictionary ObjectFittings = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary ObjectFittings = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.None, ObjectFitting.None }, { CssKeywords.Cover, ObjectFitting.Cover }, @@ -663,7 +715,7 @@ static class Map /// /// Contains the string-FontWeight mapping. /// - public static readonly Dictionary FontWeights = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary FontWeights = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.Normal, FontWeight.Normal }, { CssKeywords.Bold, FontWeight.Bold }, @@ -674,7 +726,7 @@ static class Map /// /// Contains the string-SystemFont mapping. /// - public static readonly Dictionary SystemFonts = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary SystemFonts = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.Caption, SystemFont.Caption }, { CssKeywords.Icon, SystemFont.Icon }, @@ -687,7 +739,7 @@ static class Map /// /// Contains the string-StrokeLinecap mapping. /// - public static readonly Dictionary StrokeLinecaps = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary StrokeLinecaps = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.Butt, StrokeLinecap.Butt }, { CssKeywords.Round, StrokeLinecap.Round }, @@ -697,7 +749,7 @@ static class Map /// /// Contains the string-StrokeLinejoin mapping. /// - public static readonly Dictionary StrokeLinejoins = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary StrokeLinejoins = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.Miter, StrokeLinejoin.Miter }, { CssKeywords.Round, StrokeLinejoin.Round }, @@ -707,7 +759,7 @@ static class Map /// /// Contains the string-WordBreak mapping. /// - public static readonly Dictionary WordBreaks = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary WordBreaks = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.Normal, WordBreak.Normal }, { CssKeywords.BreakAll, WordBreak.BreakAll }, @@ -717,7 +769,7 @@ static class Map /// /// Contains the string-WordBreak mapping. /// - public static readonly Dictionary OverflowWraps = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary OverflowWraps = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.Normal, OverflowWrap.Normal }, { CssKeywords.BreakWord, OverflowWrap.BreakWord }, @@ -726,7 +778,7 @@ static class Map /// /// Contains the string-ResizeMode mapping. /// - public static readonly Dictionary ResizeModes = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary ResizeModes = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.None, ResizeMode.None }, { CssKeywords.Both, ResizeMode.Both }, @@ -739,7 +791,7 @@ static class Map /// /// Contains the string-RubyAlignment mapping. /// - public static readonly Dictionary RubyAlignments = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary RubyAlignments = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.Center, RubyAlignment.Center }, { CssKeywords.Start, RubyAlignment.Start }, @@ -750,7 +802,7 @@ static class Map /// /// Contains the string-RubyPosition mapping. /// - public static readonly Dictionary RubyPositions = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary RubyPositions = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.InterCharacter, RubyPosition.InterCharacter }, { CssKeywords.Over, RubyPosition.Over }, @@ -760,7 +812,7 @@ static class Map /// /// Contains the string-RubyOverhangMode mapping. /// - public static readonly Dictionary RubyOverhangModes = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary RubyOverhangModes = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.Auto, RubyOverhangMode.Auto }, { CssKeywords.End, RubyOverhangMode.End }, @@ -771,7 +823,7 @@ static class Map /// /// Contains the string-PointerEvent mapping. /// - public static readonly Dictionary PointerEvents = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary PointerEvents = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.None, PointerEvent.None }, { CssKeywords.Auto, PointerEvent.Auto }, @@ -788,7 +840,7 @@ static class Map /// /// Contains the string-FlexDirection mapping. /// - public static readonly Dictionary FlexDirections = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary FlexDirections = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.Column, FlexDirection.Column }, { CssKeywords.ColumnReverse, FlexDirection.ColumnReverse }, @@ -799,7 +851,7 @@ static class Map /// /// Contains the string-FlexWrapMode mapping. /// - public static readonly Dictionary FlexWrapModes = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary FlexWrapModes = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.Nowrap, FlexWrapMode.NoWrap }, { CssKeywords.Wrap, FlexWrapMode.Wrap }, @@ -809,7 +861,7 @@ static class Map /// /// Contains the string-FlexContentMode mapping. /// - public static readonly Dictionary JustifyContentModes = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary JustifyContentModes = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.FlexStart, FlexContentMode.Start }, { CssKeywords.FlexEnd, FlexContentMode.End }, @@ -821,7 +873,7 @@ static class Map /// /// Contains the string-FlexContentMode mapping. /// - public static readonly Dictionary AlignContentModes = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary AlignContentModes = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.FlexStart, FlexContentMode.Start }, { CssKeywords.FlexEnd, FlexContentMode.End }, @@ -834,7 +886,7 @@ static class Map /// /// Contains the string-FlexContentMode mapping. /// - public static readonly Dictionary AlignItemsModes = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary AlignItemsModes = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.FlexStart, FlexContentMode.Start }, { CssKeywords.FlexEnd, FlexContentMode.End }, @@ -846,7 +898,7 @@ static class Map /// /// Contains the string-FlexContentMode mapping. /// - public static readonly Dictionary AlignSelfModes = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary AlignSelfModes = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.Auto, FlexContentMode.Auto }, { CssKeywords.FlexStart, FlexContentMode.Start }, @@ -859,7 +911,7 @@ static class Map /// /// Contains the string-BookmarkState mapping. /// - public static readonly Dictionary BookmarkStates = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary BookmarkStates = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.Open, BookmarkState.Open }, { CssKeywords.Closed, BookmarkState.Closed }, @@ -868,7 +920,7 @@ static class Map /// /// Contains the string-FootnotePolicy mapping. /// - public static readonly Dictionary FootnotePolicies = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary FootnotePolicies = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.Auto, FootnotePolicy.Auto }, { CssKeywords.Block, FootnotePolicy.Block }, @@ -878,7 +930,7 @@ static class Map /// /// Contains the string-FootnoteDisplay mapping. /// - public static readonly Dictionary FootnoteDisplays = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary FootnoteDisplays = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.Block, FootnoteDisplay.Block }, { CssKeywords.Compact, FootnoteDisplay.Compact }, @@ -888,7 +940,7 @@ static class Map /// /// Contains the string-length mapping. /// - public static readonly Dictionary Sizings = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary Sizings = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.FitContent, Sizing.FitContent }, { CssKeywords.MinContent, Sizing.MinContent}, @@ -898,7 +950,7 @@ static class Map /// /// Contains the string-ContentVisibility mapping. /// - public static readonly Dictionary ContentVisibilities = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary ContentVisibilities = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.Visible, Visibility.Visible }, { CssKeywords.Hidden, Visibility.Hidden }, @@ -908,7 +960,7 @@ static class Map /// /// Contains the scroll-snap-axis mapping. /// - public static readonly Dictionary ScrollSnapAxises = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary ScrollSnapAxises = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.X, ScrollSnapAxis.X }, { CssKeywords.Y, ScrollSnapAxis.Y }, @@ -920,7 +972,7 @@ static class Map /// /// Contains the scroll-snap-strictness mapping. /// - public static readonly Dictionary ScrollSnapStrictnesses = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary ScrollSnapStrictnesses = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.Proximity, ScrollSnapStrictness.Proximity}, { CssKeywords.Mandatory, ScrollSnapStrictness.Mandatory }, @@ -929,7 +981,7 @@ static class Map /// /// Contains the scroll-snap-align mapping. /// - public static readonly Dictionary ScrollSnapAlignments = new Dictionary(StringComparer.OrdinalIgnoreCase) + public static readonly Dictionary ScrollSnapAlignments = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.None, ScrollSnapAlign.None }, { CssKeywords.Start, ScrollSnapAlign.Start}, diff --git a/src/AngleSharp.Css/Declarations/ContentDeclaration.cs b/src/AngleSharp.Css/Declarations/ContentDeclaration.cs index 89255772..dc3dc8f7 100644 --- a/src/AngleSharp.Css/Declarations/ContentDeclaration.cs +++ b/src/AngleSharp.Css/Declarations/ContentDeclaration.cs @@ -21,7 +21,7 @@ static class ContentDeclaration sealed class ContentValueConverter : IValueConverter { - private static readonly Dictionary ContentModes = new Dictionary(StringComparer.OrdinalIgnoreCase) + private static readonly Dictionary ContentModes = new(StringComparer.OrdinalIgnoreCase) { { CssKeywords.OpenQuote, new OpenQuoteContentMode() }, { CssKeywords.NoOpenQuote, new NoOpenQuoteContentMode() }, diff --git a/src/AngleSharp.Css/Dom/ElementCssInlineStyleExtensions.cs b/src/AngleSharp.Css/Dom/ElementCssInlineStyleExtensions.cs index a6cab36f..80da8dae 100644 --- a/src/AngleSharp.Css/Dom/ElementCssInlineStyleExtensions.cs +++ b/src/AngleSharp.Css/Dom/ElementCssInlineStyleExtensions.cs @@ -14,7 +14,7 @@ namespace AngleSharp.Css.Dom [DomExposed("SVGElement")] public static class ElementCssInlineStyleExtensions { - private static readonly ConditionalWeakTable _styles = new ConditionalWeakTable(); + private static readonly ConditionalWeakTable _styles = new(); /// /// Gets the style declaration of an element. diff --git a/src/AngleSharp.Css/Dom/Internal/MediaList.cs b/src/AngleSharp.Css/Dom/Internal/MediaList.cs index 42188e78..c34bfe3e 100644 --- a/src/AngleSharp.Css/Dom/Internal/MediaList.cs +++ b/src/AngleSharp.Css/Dom/Internal/MediaList.cs @@ -18,7 +18,7 @@ sealed class MediaList : IMediaList private readonly IBrowsingContext _context; private readonly List _media; - private static readonly CssMedium replacementMedium = new CssMedium(CssKeywords.All, inverse: true, exclusive: false); + private static readonly CssMedium replacementMedium = new(CssKeywords.All, inverse: true, exclusive: false); #endregion diff --git a/src/AngleSharp.Css/Dom/Internal/Rules/CssFontFaceRule.cs b/src/AngleSharp.Css/Dom/Internal/Rules/CssFontFaceRule.cs index a463aa78..809644ff 100644 --- a/src/AngleSharp.Css/Dom/Internal/Rules/CssFontFaceRule.cs +++ b/src/AngleSharp.Css/Dom/Internal/Rules/CssFontFaceRule.cs @@ -13,7 +13,7 @@ sealed class CssFontFaceRule : CssDeclarationRule, ICssFontFaceRule { #region Fields - private static readonly HashSet ContainedProperties = new HashSet(StringComparer.OrdinalIgnoreCase) + private static readonly HashSet ContainedProperties = new(StringComparer.OrdinalIgnoreCase) { PropertyNames.FontFamily, PropertyNames.Src, diff --git a/src/AngleSharp.Css/Dom/Internal/Rules/CssViewportRule.cs b/src/AngleSharp.Css/Dom/Internal/Rules/CssViewportRule.cs index 7718d748..e7d272ae 100644 --- a/src/AngleSharp.Css/Dom/Internal/Rules/CssViewportRule.cs +++ b/src/AngleSharp.Css/Dom/Internal/Rules/CssViewportRule.cs @@ -9,7 +9,7 @@ namespace AngleSharp.Css.Dom /// sealed class CssViewportRule : CssDeclarationRule { - private static readonly HashSet ContainedProperties = new HashSet(StringComparer.OrdinalIgnoreCase) + private static readonly HashSet ContainedProperties = new(StringComparer.OrdinalIgnoreCase) { PropertyNames.MinWidth, PropertyNames.MaxWidth, diff --git a/src/AngleSharp.Css/Dom/ListStyle.cs b/src/AngleSharp.Css/Dom/ListStyle.cs index fcd81273..66bccf17 100644 --- a/src/AngleSharp.Css/Dom/ListStyle.cs +++ b/src/AngleSharp.Css/Dom/ListStyle.cs @@ -1,4 +1,4 @@ -namespace AngleSharp.Css.Dom +namespace AngleSharp.Css.Dom { /// /// An enumeration over possible list styles. @@ -56,6 +56,162 @@ public enum ListStyle : byte /// /// Traditional Georgian numbering, E.g.an, ban, gan, ... he, tan, in… /// - Georgian + Georgian, + /// + /// Kannada numbering. + /// + Kannada, + /// + /// Traditional Chinese formal numbering. + /// + TradChineseFormal, + /// + /// Traditional Chinese informal numbering. + /// + TradChineseInformal, + /// + /// Han decimal numbers. + /// + CjkDecimal, + /// + /// Arabic-Indic numbers. + /// + ArabicIndic, + /// + /// Bengali numbering. + /// + Bengali, + /// + /// Cambodian/Khmer numbering. + /// + Cambodian, + /// + /// Han "Earthly Branch" ordinals. + /// + CjkEarthlyBranch, + /// + /// Han "Heavenly Stem" ordinals. + /// + CjkHeavenlyStem, + /// + /// Devanagari numbering. + /// + Devanagari, + /// + /// Ethiopic numbering. + /// + EthiopicNumeric, + /// + /// Gurmukhi numbering. + /// + Gurmukhi, + /// + /// Traditional Hebrew numbering. + /// + Hebrew, + /// + /// Gujarati numbering. + /// + Gujarati, + /// + /// Dictionary-order hiragana lettering. + /// + Hiragana, + /// + /// Iroha-order hiragana lettering. + /// + IrohaHiragana, + /// + /// Japanese informal numbering. + /// + JapaneseInformal, + /// + /// Japanese formal numbering to be used in legal or financial documents. The kanjis are designed so that they can't be modified to look like another correct one. + /// + JapaneseFormal, + /// + /// Iroha-order katakana lettering. + /// + KatakanaIroha, + /// + /// Dictionary-order katakana lettering. + /// + Katakana, + /// + /// Korean hangul numbering. + /// + KoreanHangulFormal, + /// + /// Formal Korean Han numbering. + /// + KoreanHanjaFormal, + /// + /// Korean hanja numbering. + /// + KoreanHanjaInformal, + /// + /// Laotian numbering. + /// + Lao, + /// + /// Uppercase Armenian numbering. + /// + UpperArmenian, + /// + /// Lowercase Armenian numbering. + /// + LowerArmenian, + /// + /// Malayalam numbering. + /// + Malayalam, + /// + /// Mongolian numbering. + /// + Mongolian, + /// + /// Myanmar (Burmese) numbering. + /// + Myanmar, + /// + /// Oriya numbering. + /// + Oriya, + /// + /// Persian numbering. + /// + Persian, + /// + /// Simplified Chinese formal numbering. + /// + SimpChineseFormal, + /// + /// Simplified Chinese informal numbering. + /// + SimpChineseInformal, + /// + /// Tamil numbering. + /// + Tamil, + /// + /// Telugu numbering. + /// + Telugu, + /// + /// Thai numbering. + /// + Thai, + /// + /// Tibetan numbering. + /// + Tibetan, + /// + /// Symbol indicating that a disclosure widget, like <details> is closed. + /// + DisclosureClosed, + /// + /// Symbol indicating that a disclosure widget such as <details> is opened. + /// + DisclosureOpen, } } diff --git a/src/AngleSharp.Css/Dom/SymbolsType.cs b/src/AngleSharp.Css/Dom/SymbolsType.cs new file mode 100644 index 00000000..7be06392 --- /dev/null +++ b/src/AngleSharp.Css/Dom/SymbolsType.cs @@ -0,0 +1,29 @@ +namespace AngleSharp.Css.Dom +{ + /// + /// An enumeration with all possible symbol types. + /// + public enum SymbolsType : byte + { + /// + /// The system cycles through the given values in the order of their definition, and returns to the start when it reaches the end. + /// + Cyclic, + /// + /// The system interprets the given values as the successive units of a place-value numbering system. + /// + Numeric, + /// + /// The system interprets the given values as the digits of an alphabetic numbering system, like a place-value numbering system but without 0. + /// + Alphabetic, + /// + /// The system cycles through the values, printing them an additional time at each cycle (one time for the first cycle, two times for the second, etc.). + /// + Symbolic, + /// + /// The system cycles through the given values once, then falls back to Arabic numerals. + /// + Fixed, + } +} diff --git a/src/AngleSharp.Css/Extensions/MediaListExtensions.cs b/src/AngleSharp.Css/Extensions/MediaListExtensions.cs index 8ed669c3..036a3d8b 100644 --- a/src/AngleSharp.Css/Extensions/MediaListExtensions.cs +++ b/src/AngleSharp.Css/Extensions/MediaListExtensions.cs @@ -8,7 +8,7 @@ namespace AngleSharp.Css.Dom static class MediaListExtensions { private readonly static ConditionalWeakTable AssociatedValidators = - new ConditionalWeakTable(); + new(); private readonly static String[] KnownTypes = { diff --git a/src/AngleSharp.Css/Factories/DefaultDeclarationFactory.cs b/src/AngleSharp.Css/Factories/DefaultDeclarationFactory.cs index 2cdcb0f7..72d4ae0a 100644 --- a/src/AngleSharp.Css/Factories/DefaultDeclarationFactory.cs +++ b/src/AngleSharp.Css/Factories/DefaultDeclarationFactory.cs @@ -9,7 +9,7 @@ namespace AngleSharp.Css /// public class DefaultDeclarationFactory : IDeclarationFactory { - private readonly Dictionary _declarations = new Dictionary(StringComparer.OrdinalIgnoreCase) + private readonly Dictionary _declarations = new(StringComparer.OrdinalIgnoreCase) { { BookmarkLabelDeclaration.Name, new DeclarationInfo( diff --git a/src/AngleSharp.Css/Factories/DefaultDocumentFunctionFactory.cs b/src/AngleSharp.Css/Factories/DefaultDocumentFunctionFactory.cs index 63ce44e7..c4816c3a 100644 --- a/src/AngleSharp.Css/Factories/DefaultDocumentFunctionFactory.cs +++ b/src/AngleSharp.Css/Factories/DefaultDocumentFunctionFactory.cs @@ -17,7 +17,7 @@ public class DefaultDocumentFunctionFactory : IDocumentFunctionFactory /// The created document function for the given url. public delegate IDocumentFunction Creator(String url); - private readonly Dictionary _creators = new Dictionary(StringComparer.OrdinalIgnoreCase) + private readonly Dictionary _creators = new(StringComparer.OrdinalIgnoreCase) { { FunctionNames.Url, str => new UrlFunction(str) }, { FunctionNames.Domain, str => new DomainFunction(str) }, diff --git a/src/AngleSharp.Css/Factories/DefaultFeatureValidatorFactory.cs b/src/AngleSharp.Css/Factories/DefaultFeatureValidatorFactory.cs index 187abdea..c3e2795a 100644 --- a/src/AngleSharp.Css/Factories/DefaultFeatureValidatorFactory.cs +++ b/src/AngleSharp.Css/Factories/DefaultFeatureValidatorFactory.cs @@ -15,7 +15,7 @@ public class DefaultFeatureValidatorFactory : IFeatureValidatorFactory /// The created media feature validator. public delegate IFeatureValidator Creator(); - private readonly Dictionary _creators = new Dictionary(StringComparer.OrdinalIgnoreCase) + private readonly Dictionary _creators = new(StringComparer.OrdinalIgnoreCase) { { FeatureNames.MinWidth, () => new WidthFeatureValidator() }, { FeatureNames.MaxWidth, () => new WidthFeatureValidator() }, diff --git a/src/AngleSharp.Css/Factories/DefaultPseudoElementFactory.cs b/src/AngleSharp.Css/Factories/DefaultPseudoElementFactory.cs index 0b7cb5b2..abd4cf8d 100644 --- a/src/AngleSharp.Css/Factories/DefaultPseudoElementFactory.cs +++ b/src/AngleSharp.Css/Factories/DefaultPseudoElementFactory.cs @@ -17,7 +17,7 @@ public class DefaultPseudoElementFactory : IPseudoElementFactory /// The new pseudo element. public delegate IPseudoElement Creator(IElement host); - private readonly Dictionary _creators = new Dictionary(StringComparer.OrdinalIgnoreCase) + private readonly Dictionary _creators = new(StringComparer.OrdinalIgnoreCase) { { PseudoElementNames.Before, element => new PseudoElement(element, PseudoElementNames.Before) }, { PseudoElementNames.After, element => new PseudoElement(element, PseudoElementNames.After) }, diff --git a/src/AngleSharp.Css/Factories/Factory.cs b/src/AngleSharp.Css/Factories/Factory.cs index 5ba847f8..8f32d626 100644 --- a/src/AngleSharp.Css/Factories/Factory.cs +++ b/src/AngleSharp.Css/Factories/Factory.cs @@ -2,14 +2,14 @@ namespace AngleSharp.Css { static class Factory { - public static DefaultFeatureValidatorFactory FeatureValidator = new DefaultFeatureValidatorFactory(); + public static DefaultFeatureValidatorFactory FeatureValidator = new(); - public static DefaultPseudoElementFactory PseudoElement = new DefaultPseudoElementFactory(); + public static DefaultPseudoElementFactory PseudoElement = new(); - public static DefaultDocumentFunctionFactory DocumentFunction = new DefaultDocumentFunctionFactory(); + public static DefaultDocumentFunctionFactory DocumentFunction = new(); - public static DefaultDeclarationFactory Declaration = new DefaultDeclarationFactory(); + public static DefaultDeclarationFactory Declaration = new(); - public static StyleAttributeObserver Observer = new StyleAttributeObserver(); + public static StyleAttributeObserver Observer = new(); } } diff --git a/src/AngleSharp.Css/Parser/Micro/ColorParser.cs b/src/AngleSharp.Css/Parser/Micro/ColorParser.cs index b7aaffe5..1249a68e 100644 --- a/src/AngleSharp.Css/Parser/Micro/ColorParser.cs +++ b/src/AngleSharp.Css/Parser/Micro/ColorParser.cs @@ -11,7 +11,7 @@ namespace AngleSharp.Css.Parser /// static class ColorParser { - private static readonly Dictionary> ColorFunctions = new Dictionary>(StringComparer.OrdinalIgnoreCase) + private static readonly Dictionary> ColorFunctions = new(StringComparer.OrdinalIgnoreCase) { { FunctionNames.Rgb, ParseRgba }, { FunctionNames.Rgba, ParseRgba }, diff --git a/src/AngleSharp.Css/Parser/Micro/CssUriParser.cs b/src/AngleSharp.Css/Parser/Micro/CssUriParser.cs index 3c0c3802..14a2b5e4 100644 --- a/src/AngleSharp.Css/Parser/Micro/CssUriParser.cs +++ b/src/AngleSharp.Css/Parser/Micro/CssUriParser.cs @@ -19,23 +19,14 @@ public static CssUrlValue ParseUri(this StringSource source) { var current = source.SkipSpacesAndComments(); - switch (current) - { - case Symbols.DoubleQuote: - return DoubleQuoted(source); - - case Symbols.SingleQuote: - return SingleQuoted(source); - - case Symbols.RoundBracketClose: - return new CssUrlValue(String.Empty); - - case Symbols.EndOfFile: - return new CssUrlValue(String.Empty); - - default: - return Unquoted(source); - } + return current switch + { + Symbols.DoubleQuote => DoubleQuoted(source), + Symbols.SingleQuote => SingleQuoted(source), + Symbols.RoundBracketClose => new CssUrlValue(String.Empty), + Symbols.EndOfFile => new CssUrlValue(String.Empty), + _ => Unquoted(source), + }; } return null; diff --git a/src/AngleSharp.Css/Parser/Micro/GradientParser.cs b/src/AngleSharp.Css/Parser/Micro/GradientParser.cs index d14ec8b1..6745b220 100644 --- a/src/AngleSharp.Css/Parser/Micro/GradientParser.cs +++ b/src/AngleSharp.Css/Parser/Micro/GradientParser.cs @@ -11,7 +11,7 @@ namespace AngleSharp.Css.Parser /// public static class GradientParser { - private static readonly Dictionary> GradientFunctions = new Dictionary>(StringComparer.OrdinalIgnoreCase) + private static readonly Dictionary> GradientFunctions = new(StringComparer.OrdinalIgnoreCase) { { FunctionNames.LinearGradient, ParseLinearGradient }, { FunctionNames.RepeatingLinearGradient, ParseRepeatingLinearGradient }, diff --git a/src/AngleSharp.Css/Parser/Micro/IdentParser.cs b/src/AngleSharp.Css/Parser/Micro/IdentParser.cs index 0074d358..3131f74e 100644 --- a/src/AngleSharp.Css/Parser/Micro/IdentParser.cs +++ b/src/AngleSharp.Css/Parser/Micro/IdentParser.cs @@ -306,7 +306,7 @@ private static String Rest(StringSource source, Char current, StringBuilder buff } } - private static readonly HashSet Animatables = new HashSet(StringComparer.OrdinalIgnoreCase) + private static readonly HashSet Animatables = new(StringComparer.OrdinalIgnoreCase) { "backdrop-filter", "background", diff --git a/src/AngleSharp.Css/Parser/Micro/StringParser.cs b/src/AngleSharp.Css/Parser/Micro/StringParser.cs index 9015f7f4..358be517 100644 --- a/src/AngleSharp.Css/Parser/Micro/StringParser.cs +++ b/src/AngleSharp.Css/Parser/Micro/StringParser.cs @@ -16,13 +16,12 @@ public static String ParseString(this StringSource source) { var current = source.Current; - switch (current) + return current switch { - case Symbols.DoubleQuote: return DoubleQuoted(source); - case Symbols.SingleQuote: return SingleQuoted(source); - } - - return null; + Symbols.DoubleQuote => DoubleQuoted(source), + Symbols.SingleQuote => SingleQuoted(source), + _ => null, + }; } private static String DoubleQuoted(StringSource source) diff --git a/src/AngleSharp.Css/Parser/Micro/SymbolsParser.cs b/src/AngleSharp.Css/Parser/Micro/SymbolsParser.cs new file mode 100644 index 00000000..7b81abf0 --- /dev/null +++ b/src/AngleSharp.Css/Parser/Micro/SymbolsParser.cs @@ -0,0 +1,63 @@ +namespace AngleSharp.Css.Parser +{ + using AngleSharp.Css.Values; + using AngleSharp.Text; + using System; + using System.Collections.Generic; + + /// + /// Represents extensions to for symbols() values. + /// + public static class SymbolsParser + { + /// + /// Parse a CSS symbols() value. + /// + public static CssSymbolsValue ParseSymbols(this StringSource source) + { + var start = source.Index; + + if (source.IsFunction(FunctionNames.Symbols)) + { + source.SkipSpacesAndComments(); + var pos = source.Index; + var type = ValueConverters.SymbolsTypeConverter.Convert(source); + var entries = new List(); + + if (type is null) + { + source.BackTo(pos); + } + else + { + source.SkipSpacesAndComments(); + } + + do + { + var entry = StringParser.ParseString(source); + + if (entry is null) + { + break; + } + + entries.Add(entry); + + var c = source.SkipSpacesAndComments(); + + if (c == Symbols.RoundBracketClose) + { + source.Next(); + return new CssSymbolsValue(type, entries); + } + } + while (true); + + source.BackTo(start); + } + + return null; + } + } +} diff --git a/src/AngleSharp.Css/Parser/Micro/TimingFunctionParser.cs b/src/AngleSharp.Css/Parser/Micro/TimingFunctionParser.cs index 013997bb..821a93db 100644 --- a/src/AngleSharp.Css/Parser/Micro/TimingFunctionParser.cs +++ b/src/AngleSharp.Css/Parser/Micro/TimingFunctionParser.cs @@ -10,7 +10,7 @@ namespace AngleSharp.Css.Parser /// public static class TimingFunctionParser { - private static readonly Dictionary> TimingFunctions = new Dictionary>(StringComparer.OrdinalIgnoreCase) + private static readonly Dictionary> TimingFunctions = new(StringComparer.OrdinalIgnoreCase) { { FunctionNames.Steps, ParseSteps }, { FunctionNames.CubicBezier, ParseCubicBezier }, diff --git a/src/AngleSharp.Css/Parser/Micro/TransformParser.cs b/src/AngleSharp.Css/Parser/Micro/TransformParser.cs index ae3cbb5e..b98a42df 100644 --- a/src/AngleSharp.Css/Parser/Micro/TransformParser.cs +++ b/src/AngleSharp.Css/Parser/Micro/TransformParser.cs @@ -11,7 +11,7 @@ namespace AngleSharp.Css.Parser /// public static class TransformParser { - private static readonly Dictionary> TransformFunctions = new Dictionary>(StringComparer.OrdinalIgnoreCase) + private static readonly Dictionary> TransformFunctions = new(StringComparer.OrdinalIgnoreCase) { { FunctionNames.Skew, ParseSkew2d }, { FunctionNames.SkewX, ParseSkewX }, diff --git a/src/AngleSharp.Css/ValueConverters.cs b/src/AngleSharp.Css/ValueConverters.cs index 054e81ae..f2c906d0 100644 --- a/src/AngleSharp.Css/ValueConverters.cs +++ b/src/AngleSharp.Css/ValueConverters.cs @@ -125,6 +125,12 @@ static class ValueConverters /// public static readonly IValueConverter IdentifierConverter = new IdentifierValueConverter(IdentParser.ParseNormalizedIdent); + /// + /// Represents an identifier object. + /// https://developer.mozilla.org/en-US/docs/Web/CSS/custom-ident + /// + public static readonly IValueConverter CustomIdentConverter = new IdentifierValueConverter(IdentParser.ParseCustomIdent); + /// /// Represents an identifier object that matches the production rules of a single transition property. /// http://dev.w3.org/csswg/css-transitions/#single-transition-property @@ -227,6 +233,12 @@ static class ValueConverters /// public static readonly IValueConverter PointYConverter = FromParser(PointParser.ParsePointY); + /// + /// Represents an symbols object. + /// https://developer.mozilla.org/en-US/docs/Web/CSS/symbols + /// + public static readonly IValueConverter SymbolsConverter = FromParser(SymbolsParser.ParseSymbols); + #endregion #region Functions @@ -315,7 +327,12 @@ static class ValueConverters /// /// Represents a converter for the ListStyle enumeration. /// - public static readonly IValueConverter ListStyleConverter = Map.ListStyles.ToConverter(); + public static readonly IValueConverter ListStyleConverter = Or(Map.ListStyles.ToConverter(), StringConverter, SymbolsConverter, CustomIdentConverter); + + /// + /// Represents a converter for the SymbolsType enumeration. + /// + public static readonly IValueConverter SymbolsTypeConverter = Map.SymbolsTypes.ToConverter(); /// /// Represents a converter for the BreakMode enumeration. diff --git a/src/AngleSharp.Css/Values/Composites/CssBackgroundSizeValue.cs b/src/AngleSharp.Css/Values/Composites/CssBackgroundSizeValue.cs index f139cfa2..69aea546 100644 --- a/src/AngleSharp.Css/Values/Composites/CssBackgroundSizeValue.cs +++ b/src/AngleSharp.Css/Values/Composites/CssBackgroundSizeValue.cs @@ -17,12 +17,12 @@ public sealed class CssBackgroundSizeValue : IEquatable, /// /// Used to declare cover background size. /// - public static readonly CssBackgroundSizeValue Cover = new CssBackgroundSizeValue(ValueMode.Cover); + public static readonly CssBackgroundSizeValue Cover = new(ValueMode.Cover); /// /// Used to declare contain background size. /// - public static readonly CssBackgroundSizeValue Contain = new CssBackgroundSizeValue(ValueMode.Contain); + public static readonly CssBackgroundSizeValue Contain = new(ValueMode.Contain); #endregion diff --git a/src/AngleSharp.Css/Values/CssColors.cs b/src/AngleSharp.Css/Values/CssColors.cs index 4d048bab..47795be6 100644 --- a/src/AngleSharp.Css/Values/CssColors.cs +++ b/src/AngleSharp.Css/Values/CssColors.cs @@ -12,7 +12,7 @@ static class CssColors { #region Fields - private static readonly Dictionary TheColors = new Dictionary(StringComparer.OrdinalIgnoreCase) + private static readonly Dictionary TheColors = new(StringComparer.OrdinalIgnoreCase) { // Extended color keywords { "aliceblue", new Color(240, 248, 255) }, diff --git a/src/AngleSharp.Css/Values/Functions/CssCubicBezierValue.cs b/src/AngleSharp.Css/Values/Functions/CssCubicBezierValue.cs index d957a03a..6c6b6f72 100644 --- a/src/AngleSharp.Css/Values/Functions/CssCubicBezierValue.cs +++ b/src/AngleSharp.Css/Values/Functions/CssCubicBezierValue.cs @@ -21,27 +21,27 @@ sealed class CssCubicBezierValue : ICssTimingFunctionValue, IEquatable /// The pre-configured ease function. /// - public static readonly CssCubicBezierValue Ease = new CssCubicBezierValue(0.25, 0.1, 0.25, 1.0); + public static readonly CssCubicBezierValue Ease = new(0.25, 0.1, 0.25, 1.0); /// /// The pre-configured ease-in function. /// - public static readonly CssCubicBezierValue EaseIn = new CssCubicBezierValue(0.42, 0.0, 1.0, 1.0); + public static readonly CssCubicBezierValue EaseIn = new(0.42, 0.0, 1.0, 1.0); /// /// The pre-configured ease-out function. /// - public static readonly CssCubicBezierValue EaseOut = new CssCubicBezierValue(0.0, 0.0, 0.58, 1.0); + public static readonly CssCubicBezierValue EaseOut = new(0.0, 0.0, 0.58, 1.0); /// /// The pre-configured ease-in-out function. /// - public static readonly CssCubicBezierValue EaseInOut = new CssCubicBezierValue(0.42, 0.0, 0.58, 1.0); + public static readonly CssCubicBezierValue EaseInOut = new(0.42, 0.0, 0.58, 1.0); /// /// The pre-configured linear function. /// - public static readonly CssCubicBezierValue Linear = new CssCubicBezierValue(0.0, 0.0, 1.0, 1.0); + public static readonly CssCubicBezierValue Linear = new(0.0, 0.0, 1.0, 1.0); #endregion diff --git a/src/AngleSharp.Css/Values/Functions/CssScaleValue.cs b/src/AngleSharp.Css/Values/Functions/CssScaleValue.cs index 6ce3addd..ee681442 100644 --- a/src/AngleSharp.Css/Values/Functions/CssScaleValue.cs +++ b/src/AngleSharp.Css/Values/Functions/CssScaleValue.cs @@ -137,7 +137,7 @@ public String CssText /// /// The transformation matrix representation. public TransformMatrix ComputeMatrix(IRenderDimensions renderDimensions) => - new TransformMatrix(_sx, 0f, 0f, 0f, _sy, 0f, 0f, 0f, _sz, 0f, 0f, 0f, 0f, 0f, 0f); + new(_sx, 0f, 0f, 0f, _sy, 0f, 0f, 0f, _sz, 0f, 0f, 0f, 0f, 0f, 0f); #endregion } diff --git a/src/AngleSharp.Css/Values/Functions/CssSymbolsValue.cs b/src/AngleSharp.Css/Values/Functions/CssSymbolsValue.cs new file mode 100644 index 00000000..6ea40501 --- /dev/null +++ b/src/AngleSharp.Css/Values/Functions/CssSymbolsValue.cs @@ -0,0 +1,71 @@ +namespace AngleSharp.Css.Values +{ + using AngleSharp.Css.Dom; + using AngleSharp.Text; + using System; + using System.Collections.Generic; + using System.Data; + using System.Linq; + + /// + /// Represents a CSS symbols function call. + /// + public sealed class CssSymbolsValue : ICssFunctionValue + { + #region Fields + + private readonly ICssValue _type; + private readonly List _entries; + + #endregion + + #region ctor + + /// + /// Creates a new sybols function call. + /// + /// The used type. + /// The contained entries. + public CssSymbolsValue(ICssValue type, List entries) + { + _type = type; + _entries = entries; + } + + #endregion + + #region Properties + + /// + /// Gets the used type, if any. + /// + public ICssValue Type => _type; + + /// + /// Gets the name of the function. + /// + public String Name => FunctionNames.Symbols; + + /// + /// Gets the arguments. + /// + public ICssValue[] Arguments => _entries.Select(e => new Label(e) as ICssValue).ToArray(); + + /// + /// Gets the CSS text representation. + /// + public String CssText =>Name.CssFunction(GetArgs()); + + #endregion + + #region Helpers + + private String GetArgs() + { + var arg = String.Join(" ", _entries.Select(m => m.CssString())); + return _type is not null ? $"{_type.CssText} {arg}" : arg; + } + + #endregion + } +} diff --git a/src/AngleSharp.Css/Values/Primitives/Angle.cs b/src/AngleSharp.Css/Values/Primitives/Angle.cs index 246701de..e2904896 100644 --- a/src/AngleSharp.Css/Values/Primitives/Angle.cs +++ b/src/AngleSharp.Css/Values/Primitives/Angle.cs @@ -13,27 +13,27 @@ public struct Angle : IEquatable, IComparable, ICssPrimitiveValue /// /// The zero angle. /// - public static readonly Angle Zero = new Angle(0.0, Angle.Unit.Rad); + public static readonly Angle Zero = new(0.0, Angle.Unit.Rad); /// /// The 45° angle. /// - public static readonly Angle HalfQuarter = new Angle(45.0, Angle.Unit.Deg); + public static readonly Angle HalfQuarter = new(45.0, Angle.Unit.Deg); /// /// The 90° angle. /// - public static readonly Angle Quarter = new Angle(90.0, Angle.Unit.Deg); + public static readonly Angle Quarter = new(90.0, Angle.Unit.Deg); /// /// The 135° angle. /// - public static readonly Angle TripleHalfQuarter = new Angle(135.0, Angle.Unit.Deg); + public static readonly Angle TripleHalfQuarter = new(135.0, Angle.Unit.Deg); /// /// The 180° angle. /// - public static readonly Angle Half = new Angle(180.0, Angle.Unit.Deg); + public static readonly Angle Half = new(180.0, Angle.Unit.Deg); #endregion diff --git a/src/AngleSharp.Css/Values/Primitives/Color.cs b/src/AngleSharp.Css/Values/Primitives/Color.cs index 180c044e..0462526f 100644 --- a/src/AngleSharp.Css/Values/Primitives/Color.cs +++ b/src/AngleSharp.Css/Values/Primitives/Color.cs @@ -17,52 +17,52 @@ public struct Color : IEquatable, IComparable, ICssPrimitiveValue /// /// The color #000000. /// - public static readonly Color Black = new Color(0, 0, 0); + public static readonly Color Black = new(0, 0, 0); /// /// The color #FFFFFF. /// - public static readonly Color White = new Color(255, 255, 255); + public static readonly Color White = new(255, 255, 255); /// /// The color #FF0000. /// - public static readonly Color Red = new Color(255, 0, 0); + public static readonly Color Red = new(255, 0, 0); /// /// The color #FF00FF. /// - public static readonly Color Magenta = new Color(255, 0, 255); + public static readonly Color Magenta = new(255, 0, 255); /// /// The color #008000. /// - public static readonly Color Green = new Color(0, 128, 0); + public static readonly Color Green = new(0, 128, 0); /// /// The color #00FF00. /// - public static readonly Color PureGreen = new Color(0, 255, 0); + public static readonly Color PureGreen = new(0, 255, 0); /// /// The color #0000FF. /// - public static readonly Color Blue = new Color(0, 0, 255); + public static readonly Color Blue = new(0, 0, 255); /// /// The color #00000000. /// - public static readonly Color Transparent = new Color(0, 0, 0, 0); + public static readonly Color Transparent = new(0, 0, 0, 0); /// /// The color #00010203. /// - public static readonly Color CurrentColor = new Color(0, 1, 2, 3); + public static readonly Color CurrentColor = new(0, 1, 2, 3); /// /// The color #30201000. /// - public static readonly Color InvertedColor = new Color(48, 32, 16, 0); + public static readonly Color InvertedColor = new(48, 32, 16, 0); #endregion @@ -127,7 +127,7 @@ public Color(Byte r, Byte g, Byte b, Byte a) /// The value for alpha [0,1]. /// The CSS color value. public static Color FromRgba(Byte r, Byte g, Byte b, Double a) => - new Color(r, g, b, Normalize(a)); + new(r, g, b, Normalize(a)); /// /// Returns the color from the given primitives. @@ -138,7 +138,7 @@ public static Color FromRgba(Byte r, Byte g, Byte b, Double a) => /// The value for alpha [0,1]. /// The CSS color value. public static Color FromRgba(Double r, Double g, Double b, Double a) => - new Color(Normalize(r), Normalize(g), Normalize(b), Normalize(a)); + new(Normalize(r), Normalize(g), Normalize(b), Normalize(a)); /// /// Returns the gray color from the given value. @@ -147,7 +147,7 @@ public static Color FromRgba(Double r, Double g, Double b, Double a) => /// The value for alpha [0,1]. /// The CSS color value. public static Color FromGray(Byte number, Double alpha = 1.0) => - new Color(number, number, number, Normalize(alpha)); + new(number, number, number, Normalize(alpha)); /// /// Returns the gray color from the given value. @@ -172,7 +172,7 @@ public static Color FromGray(Double value, Double alpha = 1.0) => /// The value for green [0,255]. /// The value for blue [0,255]. /// The CSS color value. - public static Color FromRgb(Byte r, Byte g, Byte b) => new Color(r, g, b); + public static Color FromRgb(Byte r, Byte g, Byte b) => new(r, g, b); /// /// Returns the color that represents the given Lab values. diff --git a/src/AngleSharp.Css/Values/Primitives/Length.cs b/src/AngleSharp.Css/Values/Primitives/Length.cs index f60304f0..0512ddb4 100644 --- a/src/AngleSharp.Css/Values/Primitives/Length.cs +++ b/src/AngleSharp.Css/Values/Primitives/Length.cs @@ -12,47 +12,47 @@ public struct Length : IEquatable, IComparable, ICssPrimitiveVal /// /// Gets a zero pixel length value. /// - public static readonly Length Zero = new Length(0.0, Unit.Px); + public static readonly Length Zero = new(0.0, Unit.Px); /// /// Gets the half relative length, i.e. 50%. /// - public static readonly Length Half = new Length(50.0, Unit.Percent); + public static readonly Length Half = new(50.0, Unit.Percent); /// /// Gets the full relative length, i.e. 100%. /// - public static readonly Length Full = new Length(100.0, Unit.Percent); + public static readonly Length Full = new(100.0, Unit.Percent); /// /// Gets a thin length value. /// - public static readonly Length Thin = new Length(1.0, Unit.Px); + public static readonly Length Thin = new(1.0, Unit.Px); /// /// Gets a medium length value. /// - public static readonly Length Medium = new Length(3.0, Unit.Px); + public static readonly Length Medium = new(3.0, Unit.Px); /// /// Gets a thick length value. /// - public static readonly Length Thick = new Length(5.0, Unit.Px); + public static readonly Length Thick = new(5.0, Unit.Px); /// /// Gets the auto value. /// - public static readonly Length Auto = new Length(Double.NaN, Unit.Vmax); + public static readonly Length Auto = new(Double.NaN, Unit.Vmax); /// /// Gets the content value. /// - public static readonly Length Content = new Length(Double.NaN, Unit.Percent); + public static readonly Length Content = new(Double.NaN, Unit.Percent); /// /// Gets the normal value. /// - public static readonly Length Normal = new Length(Double.NaN, Unit.Em); + public static readonly Length Normal = new(Double.NaN, Unit.Em); #endregion diff --git a/src/AngleSharp.Css/Values/Primitives/Point.cs b/src/AngleSharp.Css/Values/Primitives/Point.cs index a496212d..70c04011 100644 --- a/src/AngleSharp.Css/Values/Primitives/Point.cs +++ b/src/AngleSharp.Css/Values/Primitives/Point.cs @@ -13,47 +13,47 @@ public struct Point : IEquatable, ICssPrimitiveValue /// /// Gets the (50%, 50%) point. /// - public static readonly Point Center = new Point(Length.Half, Length.Half); + public static readonly Point Center = new(Length.Half, Length.Half); /// /// Gets the (0, 0) point. /// - public static readonly Point LeftTop = new Point(Length.Zero, Length.Zero); + public static readonly Point LeftTop = new(Length.Zero, Length.Zero); /// /// Gets the (100%, 0) point. /// - public static readonly Point RightTop = new Point(Length.Full, Length.Zero); + public static readonly Point RightTop = new(Length.Full, Length.Zero); /// /// Gets the (100%, 100%) point. /// - public static readonly Point RightBottom = new Point(Length.Full, Length.Full); + public static readonly Point RightBottom = new(Length.Full, Length.Full); /// /// Gets the (0, 100%) point. /// - public static readonly Point LeftBottom = new Point(Length.Zero, Length.Full); + public static readonly Point LeftBottom = new(Length.Zero, Length.Full); /// /// Gets the (0, 50%) point. /// - public static readonly Point Left = new Point(Length.Zero, Length.Half); + public static readonly Point Left = new(Length.Zero, Length.Half); /// /// Gets the (100%, 50%) point. /// - public static readonly Point Right = new Point(Length.Full, Length.Half); + public static readonly Point Right = new(Length.Full, Length.Half); /// /// Gets the (50%, 100%) point. /// - public static readonly Point Bottom = new Point(Length.Half, Length.Full); + public static readonly Point Bottom = new(Length.Half, Length.Full); /// /// Gets the (50%, 0) point. /// - public static readonly Point Top = new Point(Length.Half, Length.Zero); + public static readonly Point Top = new(Length.Half, Length.Zero); #endregion diff --git a/src/AngleSharp.Css/Values/Primitives/Time.cs b/src/AngleSharp.Css/Values/Primitives/Time.cs index 873f1ee1..c1024901 100644 --- a/src/AngleSharp.Css/Values/Primitives/Time.cs +++ b/src/AngleSharp.Css/Values/Primitives/Time.cs @@ -12,7 +12,7 @@ public struct Time : IEquatable public static readonly String Separate = "separate"; + /// + /// The match-parent keyword. + /// + public static readonly String MatchParent = "match-parent"; + /// /// The start keyword. /// diff --git a/src/AngleSharp.Css/Constants/Map.cs b/src/AngleSharp.Css/Constants/Map.cs index 0c72c40d..0d04e4e5 100644 --- a/src/AngleSharp.Css/Constants/Map.cs +++ b/src/AngleSharp.Css/Constants/Map.cs @@ -93,12 +93,16 @@ static class Map /// /// Contains the string-HorizontalAlignment mapping. /// - public static readonly Dictionary HorizontalAlignments = new(StringComparer.OrdinalIgnoreCase) - { - { CssKeywords.Left, HorizontalAlignment.Left }, - { CssKeywords.Right, HorizontalAlignment.Right }, - { CssKeywords.Center, HorizontalAlignment.Center }, - { CssKeywords.Justify, HorizontalAlignment.Justify }, + public static readonly Dictionary HorizontalAlignments = new(StringComparer.OrdinalIgnoreCase) + { + { CssKeywords.Left, TextAlign.Left }, + { CssKeywords.Right, TextAlign.Right }, + { CssKeywords.Center, TextAlign.Center }, + { CssKeywords.Justify, TextAlign.Justify }, + { CssKeywords.Start, TextAlign.Start }, + { CssKeywords.End, TextAlign.End }, + { CssKeywords.JustifyAll, TextAlign.JustifyAll }, + { CssKeywords.MatchParent, TextAlign.MatchParent }, }; /// diff --git a/src/AngleSharp.Css/Dom/TextAlign.cs b/src/AngleSharp.Css/Dom/TextAlign.cs new file mode 100644 index 00000000..e2bb8d2c --- /dev/null +++ b/src/AngleSharp.Css/Dom/TextAlign.cs @@ -0,0 +1,44 @@ +namespace AngleSharp.Css.Dom +{ + /// + /// An enumeration with all possible text alignments. + /// + public enum TextAlign: byte + { + /// + /// The inline contents are aligned to the left edge of the line box. + /// This is the default value for table data. + /// + Left, + /// + /// The inline contents are centered within the line box. This is + /// the default value for table headers. + /// + Center, + /// + /// The inline contents are aligned to the right edge of the line box. + /// + Right, + /// + /// The text is justified. Text should line up their left and right + /// edges to the left and right content edges of the paragraph. + /// + Justify, + /// + /// The same as left if direction is left-to-right and right if direction is right-to-left. + /// + Start, + /// + /// The same as right if direction is left-to-right and left if direction is right-to-left. + /// + End, + /// + /// Same as justify, but also forces the last line to be justified. + /// + JustifyAll, + /// + /// Similar to inherit, but the values start and end are calculated according to the parent's direction and are replaced by the appropriate left or right value. + /// + MatchParent, + } +} \ No newline at end of file From 6ab4a6304102db9d6a11b4d41027b077b01ec3bb Mon Sep 17 00:00:00 2001 From: Florian Rappl Date: Tue, 16 Jan 2024 16:24:01 +0100 Subject: [PATCH 24/53] Improved rule prios --- CHANGELOG.md | 3 +- .../Extensions/AnalysisWindow.cs | 13 ++- src/AngleSharp.Css/Dom/ICssStyleRule.cs | 8 +- .../Dom/Internal/Rules/CssStyleRule.cs | 90 +++++++++++++++++-- .../Extensions/StyleCollectionExtensions.cs | 22 ++++- src/AngleSharp.Css/Parser/CssBuilder.cs | 2 +- src/AngleSharp.Css/Parser/CssParser.cs | 1 - 7 files changed, 127 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4254262d..becdcff9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ Released on tbd. -- Updated to use AngleSharp 1.0 +- Updated to use AngleSharp 1.0 (#150) - Updated media parsing to media L4 spec (#133) - Fixed issue when updating shorthands with invalid values (#129) - Fixed issue with appended EOF character in `CssText` (#123) @@ -11,6 +11,7 @@ Released on tbd. - Fixed ordering of rows and columns in `grid` and `grid-gap` (#137) - Fixed inclusion of CSS from stylesheets (#116, #140) - Fixed style empty if `text-align` is `start` (#151) +- Fixed computation of priority in CSS rules using multi selector - Added further compactification of CSS tuples (#89, #93) - Added support for 8-digit hex color codes (#132) - Added more CSSOM possibilities and helpers (#6) diff --git a/src/AngleSharp.Css.Tests/Extensions/AnalysisWindow.cs b/src/AngleSharp.Css.Tests/Extensions/AnalysisWindow.cs index d842410e..0cd3d727 100644 --- a/src/AngleSharp.Css.Tests/Extensions/AnalysisWindow.cs +++ b/src/AngleSharp.Css.Tests/Extensions/AnalysisWindow.cs @@ -2,7 +2,9 @@ namespace AngleSharp.Css.Tests.Extensions { using AngleSharp.Css.Dom; using AngleSharp.Dom; + using AngleSharp.Html.Dom; using NUnit.Framework; + using System.Linq; using System.Text; using System.Threading.Tasks; using static CssConstructionFunctions; @@ -284,10 +286,19 @@ public async Task NullSelectorStillWorks_Issue52() { var sheet = ParseStyleSheet("a {}"); var document = await sheet.Context.OpenAsync(res => res.Content("")); - sheet.Add(new CssStyleRule(sheet)); var sc = new StyleCollection(new[] { sheet }, new DefaultRenderDevice()); var decl = sc.ComputeCascadedStyle(document.Body); Assert.IsNotNull(decl); } + + [Test] + public async Task PriorityInMultiSelectorIsEvaluatedPerMatch() + { + var sheet = ParseStyleSheet(@"#target {color: blue} h3, #nottarget { color: purple; } "); + var document = await sheet.Context.OpenAsync(res => res.Content(@"

Test

")); + var sc = new StyleCollection(new[] { sheet }, new DefaultRenderDevice()); + var style = sc.ComputeCascadedStyle(document.QuerySelector("h3")); + Assert.AreEqual("rgba(0, 0, 255, 1)", style.GetColor()); + } } } diff --git a/src/AngleSharp.Css/Dom/ICssStyleRule.cs b/src/AngleSharp.Css/Dom/ICssStyleRule.cs index 4747ad24..adb740d8 100644 --- a/src/AngleSharp.Css/Dom/ICssStyleRule.cs +++ b/src/AngleSharp.Css/Dom/ICssStyleRule.cs @@ -1,6 +1,7 @@ -namespace AngleSharp.Css.Dom +namespace AngleSharp.Css.Dom { using AngleSharp.Attributes; + using AngleSharp.Dom; using System; /// @@ -26,5 +27,10 @@ public interface ICssStyleRule : ICssRule /// Gets the selector for matching elements. /// ISelector Selector { get; } + + /// + /// Gets the selector for matching elements. + /// + Boolean TryMatch(IElement element, IElement? scope, out Priority specificity); } } diff --git a/src/AngleSharp.Css/Dom/Internal/Rules/CssStyleRule.cs b/src/AngleSharp.Css/Dom/Internal/Rules/CssStyleRule.cs index af5bf90d..da198e7c 100644 --- a/src/AngleSharp.Css/Dom/Internal/Rules/CssStyleRule.cs +++ b/src/AngleSharp.Css/Dom/Internal/Rules/CssStyleRule.cs @@ -3,19 +3,22 @@ namespace AngleSharp.Css.Dom using AngleSharp.Css; using AngleSharp.Dom; using System; + using System.Collections.Generic; using System.Diagnostics; using System.IO; + using System.Linq; /// /// Represents a CSS style rule. /// [DebuggerDisplay(null, Name = "CssStyleRule ({SelectorText})")] - sealed class CssStyleRule : CssRule, ICssStyleRule + sealed class CssStyleRule : CssRule, ICssStyleRule, ISelectorVisitor { #region Fields private readonly CssStyleDeclaration _style; private ISelector _selector; + private IEnumerable _selectorList; #endregion @@ -25,18 +28,23 @@ internal CssStyleRule(ICssStyleSheet owner) : base(owner, CssRuleType.Style) { _style = new CssStyleDeclaration(this); + _selectorList = null; } #endregion #region Properties - public ISelector Selector => _selector; + public ISelector Selector + { + get => _selector; + private set => ChangeSelector(value); + } public String SelectorText { get => _selector?.Text; - set => _selector = ParseSelector(value); + set => ChangeSelector(ParseSelector(value)); } ICssStyleDeclaration ICssStyleRule.Style => _style; @@ -55,10 +63,34 @@ internal void SetInvalidSelector(String selectorText) protected override void ReplaceWith(ICssRule rule) { var newRule = (ICssStyleRule)rule; - _selector = newRule.Selector; + ChangeSelector(newRule.Selector); _style.SetDeclarations(newRule.Style); } + public Boolean TryMatch(IElement element, IElement? scope, out Priority specificity) + { + if (_selectorList is not null) + { + foreach (var selector in _selectorList.OrderByDescending(m => m.Specificity)) + { + if (selector.Match(element, scope)) + { + specificity = selector.Specificity; + return true; + } + } + } + + if (_selector is not null && _selector.Match(element, scope)) + { + specificity = _selector.Specificity; + return true; + } + + specificity = default; + return false; + } + public override void ToCss(TextWriter writer, IStyleFormatter formatter) { var block = _style.ToCssBlock(formatter); @@ -69,7 +101,55 @@ public override void ToCss(TextWriter writer, IStyleFormatter formatter) #region Selector - class InvalidSelector : ISelector + private void ChangeSelector(ISelector value) + { + _selectorList = null; + _selector = value; + value?.Accept(this); + } + + void ISelectorVisitor.Attribute(string name, string op, string value) + { + } + + void ISelectorVisitor.Type(string name) + { + } + + void ISelectorVisitor.Id(string value) + { + } + + void ISelectorVisitor.Child(string name, int step, int offset, ISelector selector) + { + } + + void ISelectorVisitor.Class(string name) + { + } + + void ISelectorVisitor.PseudoClass(string name) + { + } + + void ISelectorVisitor.PseudoElement(string name) + { + } + + void ISelectorVisitor.List(IEnumerable selectors) + { + _selectorList = selectors; + } + + void ISelectorVisitor.Combinator(IEnumerable selectors, IEnumerable symbols) + { + } + + void ISelectorVisitor.Many(IEnumerable selectors) + { + } + + sealed class InvalidSelector : ISelector { private readonly String _text; diff --git a/src/AngleSharp.Css/Extensions/StyleCollectionExtensions.cs b/src/AngleSharp.Css/Extensions/StyleCollectionExtensions.cs index d637e6f4..75808f97 100644 --- a/src/AngleSharp.Css/Extensions/StyleCollectionExtensions.cs +++ b/src/AngleSharp.Css/Extensions/StyleCollectionExtensions.cs @@ -109,8 +109,26 @@ public static ICssStyleDeclaration ComputeCascadedStyle(this IEnumerable SortBySpecificity(this IEnumerable rules, IElement element) => - rules.Where(m => m.Selector?.Match(element) ?? false).OrderBy(m => m.Selector.Specificity); + private static IEnumerable SortBySpecificity(this IEnumerable rules, IElement element) + { + Tuple MapPriority(ICssStyleRule rule) + { + if (rule.TryMatch(element, null, out var specificity)) + { + return Tuple.Create(rule, specificity); + } + + return null; + } + + return rules.Select(MapPriority).Where(IsNotNull).OrderBy(GetPriority).Select(GetRule); + } + + private static Boolean IsNotNull(Tuple item) => item is not null; + + private static Priority GetPriority(Tuple item) => item.Item2; + + private static ICssStyleRule GetRule(Tuple item) => item.Item1; #endregion } diff --git a/src/AngleSharp.Css/Parser/CssBuilder.cs b/src/AngleSharp.Css/Parser/CssBuilder.cs index a287e6c7..daffe65d 100644 --- a/src/AngleSharp.Css/Parser/CssBuilder.cs +++ b/src/AngleSharp.Css/Parser/CssBuilder.cs @@ -491,7 +491,7 @@ public TextPosition CreateRules(CssStyleSheet sheet) token = NextToken(); CollectTrivia(ref token); - if (rule != null) + if (rule is not null) { sheet.Add(rule); } diff --git a/src/AngleSharp.Css/Parser/CssParser.cs b/src/AngleSharp.Css/Parser/CssParser.cs index afa35d2c..3a4dd1f6 100644 --- a/src/AngleSharp.Css/Parser/CssParser.cs +++ b/src/AngleSharp.Css/Parser/CssParser.cs @@ -203,7 +203,6 @@ internal ICssStyleSheet ParseStylesheet(TextSource source) { var sheet = new CssStyleSheet(_context, source); var tokenizer = CreateTokenizer(source); - var start = tokenizer.GetCurrentPosition(); var builder = new CssBuilder(_options, tokenizer, _context); InvokeEventListener(new CssParseEvent(sheet, completed: false)); builder.CreateRules(sheet); From 0c3aa785c5a67cb47d5e3046026ce8bdb4de8aa6 Mon Sep 17 00:00:00 2001 From: Florian Rappl Date: Tue, 16 Jan 2024 18:02:05 +0100 Subject: [PATCH 25/53] Fix for GetInnerText #155 --- CHANGELOG.md | 1 + CONTRIBUTORS.md | 1 + .../Extensions/InnerText.cs | 3 + .../Extensions/ElementExtensions.cs | 105 ++++-------------- 4 files changed, 26 insertions(+), 84 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index becdcff9..545e2378 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ Released on tbd. - Fixed inclusion of CSS from stylesheets (#116, #140) - Fixed style empty if `text-align` is `start` (#151) - Fixed computation of priority in CSS rules using multi selector +- Fixed `GetInnerText` multi-line / text node behavior (#155) @Seyden - Added further compactification of CSS tuples (#89, #93) - Added support for 8-digit hex color codes (#132) - Added more CSSOM possibilities and helpers (#6) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 25959ec7..fb4a0608 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -16,6 +16,7 @@ AngleSharp.Css contains code written by (in order of first pull request / commit * [Bastian Buchholz](https://github.com/campersau) * [Fraaankes](https://github.com/Fraaankes) * [Eric Mutta](https://github.com/ericmutta) +* [Seyden](https://github.com/Seyden) Without these awesome people AngleSharp.Css could not exist. Thanks to everyone for your contributions! :beers: diff --git a/src/AngleSharp.Css.Tests/Extensions/InnerText.cs b/src/AngleSharp.Css.Tests/Extensions/InnerText.cs index ab55c6c9..f12fb385 100644 --- a/src/AngleSharp.Css.Tests/Extensions/InnerText.cs +++ b/src/AngleSharp.Css.Tests/Extensions/InnerText.cs @@ -37,6 +37,9 @@ public void SetInnerText(String fixture, String expectedInnerText, String expect // paragraph [TestCase("

test

", "test")] [TestCase("

test1

test2

", "test1\n\ntest2")] + [TestCase("

test1

\n

test2

", "test1\n\ntest2")] + [TestCase("

test1

\n \n

test2

", "test1\n\ntest2")] + [TestCase("

test1

a\n \n b

test2

", "test1\n\na b\n\ntest2")] // block-level [TestCase("
test1
test2
test3
", "test1\ntest2\ntest3")] [TestCase(@"test1test2test3", "test1\ntest2\ntest3")] diff --git a/src/AngleSharp.Css/Extensions/ElementExtensions.cs b/src/AngleSharp.Css/Extensions/ElementExtensions.cs index 7e687f21..cc257851 100644 --- a/src/AngleSharp.Css/Extensions/ElementExtensions.cs +++ b/src/AngleSharp.Css/Extensions/ElementExtensions.cs @@ -163,7 +163,7 @@ private static void ItcInCssBox(ICssStyleDeclaration elementStyle, ICssStyleDecl { var elementHidden = new Nullable(); - if (elementStyle != null) + if (elementStyle is not null) { if (!String.IsNullOrEmpty(elementStyle.GetDisplay())) { @@ -196,13 +196,13 @@ private static void ItcInCssBox(ICssStyleDeclaration elementStyle, ICssStyleDecl var lastLine = node.NextSibling is null || String.IsNullOrEmpty(node.NextSibling.TextContent) || node.NextSibling is IHtmlBreakRowElement; - ProcessText(textElement.Data, sb, parentStyle, lastLine); + ProcessText(textElement.Data, sb, parentStyle, lastLine, requiredLineBreakCounts); } else if (node is IHtmlBreakRowElement) { sb.Append(Symbols.LineFeed); } - else if (elementStyle != null && ((node is IHtmlTableCellElement && String.IsNullOrEmpty(elementStyle.GetDisplay())) || elementStyle.GetDisplay() == CssKeywords.TableCell)) + else if (elementStyle is not null && ((node is IHtmlTableCellElement && String.IsNullOrEmpty(elementStyle.GetDisplay())) || elementStyle.GetDisplay() == CssKeywords.TableCell)) { if (node.NextSibling is IElement nextSibling) { @@ -214,7 +214,7 @@ private static void ItcInCssBox(ICssStyleDeclaration elementStyle, ICssStyleDecl } } } - else if (elementStyle != null && ((node is IHtmlTableRowElement && String.IsNullOrEmpty(elementStyle.GetDisplay())) || elementStyle.GetDisplay() == CssKeywords.TableRow)) + else if (elementStyle is not null && ((node is IHtmlTableRowElement && String.IsNullOrEmpty(elementStyle.GetDisplay())) || elementStyle.GetDisplay() == CssKeywords.TableRow)) { if (node.NextSibling is IElement nextSibling) { @@ -243,7 +243,7 @@ private static void ItcInCssBox(ICssStyleDeclaration elementStyle, ICssStyleDecl } } - if (elementStyle != null) + if (elementStyle is not null) { if (IsBlockLevelDisplay(elementStyle.GetDisplay())) { @@ -297,105 +297,42 @@ public static IEnumerable SetStyle(this IEnumerable false, + _ => true, + }; } private static Boolean IsBlockLevelDisplay(String display) { // https://www.w3.org/TR/css-display-3/#display-value-summary // https://hg.mozilla.org/mozilla-central/file/0acceb224b7d/servo/components/layout/query.rs#l1016 - switch (display) + return display switch { - case "block": - case "flow-root": - case "flex": - case "grid": - case "table": - case "table-caption": - return true; - default: - return false; - } + "block" or "flow-root" or "flex" or "grid" or "table" or "table-caption" => true, + _ => false, + }; } private static Boolean IsBlockLevel(INode node) { // https://developer.mozilla.org/en-US/docs/Web/HTML/Block-level_elements - switch (node.NodeName) + return node.NodeName switch { - case "ADDRESS": - case "ARTICLE": - case "ASIDE": - case "BLOCKQUOTE": - case "CANVAS": - case "DD": - case "DIV": - case "DL": - case "DT": - case "FIELDSET": - case "FIGCAPTION": - case "FIGURE": - case "FOOTER": - case "FORM": - case "H1": - case "H2": - case "H3": - case "H4": - case "H5": - case "H6": - case "HEADER": - case "GROUP": - case "HR": - case "LI": - case "MAIN": - case "NAV": - case "NOSCRIPT": - case "OL": - case "OPTION": - case "OUTPUT": - case "P": - case "PRE": - case "SECTION": - case "TABLE": - case "TFOOT": - case "UL": - case "VIDEO": - return true; - default: - return false; - } + "ADDRESS" or "ARTICLE" or "ASIDE" or "BLOCKQUOTE" or "CANVAS" or "DD" or "DIV" or "DL" or "DT" or "FIELDSET" or "FIGCAPTION" or "FIGURE" or "FOOTER" or "FORM" or "H1" or "H2" or "H3" or "H4" or "H5" or "H6" or "HEADER" or "GROUP" or "HR" or "LI" or "MAIN" or "NAV" or "NOSCRIPT" or "OL" or "OPTION" or "OUTPUT" or "P" or "PRE" or "SECTION" or "TABLE" or "TFOOT" or "UL" or "VIDEO" => true, + _ => false, + }; } - private static void ProcessText(String text, StringBuilder sb, ICssStyleDeclaration style, Boolean lastLine) + private static Boolean IsWhiteSpace(Char c) => Char.IsWhiteSpace(c) && c != Symbols.NoBreakSpace; + + private static void ProcessText(String text, StringBuilder sb, ICssStyleDeclaration style, Boolean lastLine, Dictionary requiredLineBreakCounts) { var startIndex = sb.Length; var whiteSpace = style?.GetWhiteSpace(); var textTransform = style?.GetTextTransform(); - var isWhiteSpace = startIndex > 0 ? Char.IsWhiteSpace(sb[startIndex - 1]) && sb[startIndex - 1] != Symbols.NoBreakSpace : true; + var isWhiteSpace = startIndex <= 0 || IsWhiteSpace(sb[startIndex - 1]) || (requiredLineBreakCounts.ContainsKey(startIndex) && IsWhiteSpace(text[0])); for (var i = 0; i < text.Length; i++) { From c39af8a8f125622cd77e702b027404356409e2ad Mon Sep 17 00:00:00 2001 From: Florian Rappl Date: Wed, 17 Jan 2024 01:35:27 +0100 Subject: [PATCH 26/53] Working on CSS nesting --- CHANGELOG.md | 1 + .../Extensions/AnalysisWindow.cs | 2 - .../Extensions/Nesting.cs | 33 ++++++++++++ src/AngleSharp.Css/Dom/ICssStyleRule.cs | 7 +++ .../Dom/Internal/Rules/CssStyleRule.cs | 16 ++++++ .../Extensions/StyleCollectionExtensions.cs | 21 +++++--- src/AngleSharp.Css/Parser/CssBuilder.cs | 13 +++++ .../Parser/CssTokenExtensions.cs | 54 ++++++++++++++++++- 8 files changed, 137 insertions(+), 10 deletions(-) create mode 100644 src/AngleSharp.Css.Tests/Extensions/Nesting.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 545e2378..057fd583 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ Released on tbd. - Fixed computation of priority in CSS rules using multi selector - Fixed `GetInnerText` multi-line / text node behavior (#155) @Seyden - Added further compactification of CSS tuples (#89, #93) +- Added support for CSS nesting in style rules (#148) - Added support for 8-digit hex color codes (#132) - Added more CSSOM possibilities and helpers (#6) - Added parts of recent color spec update such as `rgb` with spaces (#131) diff --git a/src/AngleSharp.Css.Tests/Extensions/AnalysisWindow.cs b/src/AngleSharp.Css.Tests/Extensions/AnalysisWindow.cs index 0cd3d727..cbf5b955 100644 --- a/src/AngleSharp.Css.Tests/Extensions/AnalysisWindow.cs +++ b/src/AngleSharp.Css.Tests/Extensions/AnalysisWindow.cs @@ -2,9 +2,7 @@ namespace AngleSharp.Css.Tests.Extensions { using AngleSharp.Css.Dom; using AngleSharp.Dom; - using AngleSharp.Html.Dom; using NUnit.Framework; - using System.Linq; using System.Text; using System.Threading.Tasks; using static CssConstructionFunctions; diff --git a/src/AngleSharp.Css.Tests/Extensions/Nesting.cs b/src/AngleSharp.Css.Tests/Extensions/Nesting.cs new file mode 100644 index 00000000..d270236c --- /dev/null +++ b/src/AngleSharp.Css.Tests/Extensions/Nesting.cs @@ -0,0 +1,33 @@ +namespace AngleSharp.Css.Tests.Extensions +{ + using AngleSharp.Css.Dom; + using AngleSharp.Dom; + using NUnit.Framework; + using static CssConstructionFunctions; + + [TestFixture] + public class NestingTests + { + [Test] + public void SimpleSelectorNesting() + { + var source = @"
Larger and green"; + var document = ParseDocument(source); + var window = document.DefaultView; + Assert.IsNotNull(document); + + var element = document.QuerySelector(".bar"); + Assert.IsNotNull(element); + + var style = window.GetComputedStyle(element); + Assert.IsNotNull(style); + + Assert.AreEqual("1.4rem", style.GetFontSize()); + } + } +} diff --git a/src/AngleSharp.Css/Dom/ICssStyleRule.cs b/src/AngleSharp.Css/Dom/ICssStyleRule.cs index adb740d8..ed3efe65 100644 --- a/src/AngleSharp.Css/Dom/ICssStyleRule.cs +++ b/src/AngleSharp.Css/Dom/ICssStyleRule.cs @@ -32,5 +32,12 @@ public interface ICssStyleRule : ICssRule /// Gets the selector for matching elements. ///
Boolean TryMatch(IElement element, IElement? scope, out Priority specificity); + + /// + /// Gets a CSSRuleList of the CSS rules in the style sheet. + /// + [DomName("cssRules")] + [DomName("rules")] + ICssRuleList Rules { get; } } } diff --git a/src/AngleSharp.Css/Dom/Internal/Rules/CssStyleRule.cs b/src/AngleSharp.Css/Dom/Internal/Rules/CssStyleRule.cs index da198e7c..60901066 100644 --- a/src/AngleSharp.Css/Dom/Internal/Rules/CssStyleRule.cs +++ b/src/AngleSharp.Css/Dom/Internal/Rules/CssStyleRule.cs @@ -17,6 +17,7 @@ sealed class CssStyleRule : CssRule, ICssStyleRule, ISelectorVisitor #region Fields private readonly CssStyleDeclaration _style; + private readonly CssRuleList _rules; private ISelector _selector; private IEnumerable _selectorList; @@ -28,6 +29,7 @@ internal CssStyleRule(ICssStyleSheet owner) : base(owner, CssRuleType.Style) { _style = new CssStyleDeclaration(this); + _rules = new CssRuleList(); _selectorList = null; } @@ -51,10 +53,24 @@ public String SelectorText public CssStyleDeclaration Style => _style; + public ICssRuleList Rules => _rules; + #endregion #region Methods + public void Add(ICssRule rule) + { + _rules.Add(rule); + rule.SetParent(this); + } + + public void Remove(ICssRule rule) + { + _rules.Remove(rule); + rule.SetParent(null); + } + internal void SetInvalidSelector(String selectorText) { _selector = new InvalidSelector(selectorText); diff --git a/src/AngleSharp.Css/Extensions/StyleCollectionExtensions.cs b/src/AngleSharp.Css/Extensions/StyleCollectionExtensions.cs index 75808f97..4b050635 100644 --- a/src/AngleSharp.Css/Extensions/StyleCollectionExtensions.cs +++ b/src/AngleSharp.Css/Extensions/StyleCollectionExtensions.cs @@ -111,21 +111,28 @@ public static ICssStyleDeclaration ComputeCascadedStyle(this IEnumerable SortBySpecificity(this IEnumerable rules, IElement element) { - Tuple MapPriority(ICssStyleRule rule) + IEnumerable> MapPriority(ICssStyleRule rule) { if (rule.TryMatch(element, null, out var specificity)) { - return Tuple.Create(rule, specificity); + yield return Tuple.Create(rule, specificity); + + foreach (var subRule in rule.Rules) + { + if (subRule is ICssStyleRule style) + { + foreach (var item in MapPriority(style)) + { + yield return item; + } + } + } } - - return null; } - return rules.Select(MapPriority).Where(IsNotNull).OrderBy(GetPriority).Select(GetRule); + return rules.SelectMany(MapPriority).OrderBy(GetPriority).Select(GetRule); } - private static Boolean IsNotNull(Tuple item) => item is not null; - private static Priority GetPriority(Tuple item) => item.Item2; private static ICssStyleRule GetRule(Tuple item) => item.Item1; diff --git a/src/AngleSharp.Css/Parser/CssBuilder.cs b/src/AngleSharp.Css/Parser/CssBuilder.cs index daffe65d..57e1ea96 100644 --- a/src/AngleSharp.Css/Parser/CssBuilder.cs +++ b/src/AngleSharp.Css/Parser/CssBuilder.cs @@ -524,6 +524,19 @@ public void CreateDeclarationWith(ICssProperties properties, ref CssToken token) CollectTrivia(ref token); var start = token.Position; + if (token.IsPotentiallyNested() && properties is ICssStyleDeclaration decl && decl.Parent is CssStyleRule style) + { + var rule = new CssStyleRule(style.Owner); + var result = CreateStyle(rule, token); + + if (result is not null) + { + style.Add(result); + token = NextToken(); + return; + } + } + if (token.IsNot(CssTokenType.EndOfFile, CssTokenType.CurlyBracketClose, CssTokenType.Colon) && token.IsNot(CssTokenType.Semicolon, CssTokenType.CurlyBracketOpen)) { diff --git a/src/AngleSharp.Css/Parser/CssTokenExtensions.cs b/src/AngleSharp.Css/Parser/CssTokenExtensions.cs index cae2dc71..716f4e71 100644 --- a/src/AngleSharp.Css/Parser/CssTokenExtensions.cs +++ b/src/AngleSharp.Css/Parser/CssTokenExtensions.cs @@ -1,6 +1,7 @@ -namespace AngleSharp.Css.Parser +namespace AngleSharp.Css.Parser { using AngleSharp.Css.Parser.Tokens; + using AngleSharp.Text; using System; /// @@ -8,6 +9,24 @@ /// static class CssTokenExtensions { + public static Boolean IsPotentiallyNested(this CssToken token) + { + if (token.Is(CssTokenType.Hash, CssTokenType.Colon, CssTokenType.SquareBracketOpen)) + { + return true; + } + else if (token.Type == CssTokenType.Delim && token.Data.Length == 1) + { + return token.Data[0] switch + { + Symbols.Asterisk or Symbols.Plus or Symbols.Dollar or Symbols.Dot or Symbols.Tilde or Symbols.GreaterThan or Symbols.Ampersand => true, + _ => false, + }; + } + + return false; + } + /// /// Checks if the provided token is either of the first or the second /// type of token. @@ -22,6 +41,39 @@ public static Boolean Is(this CssToken token, CssTokenType a, CssTokenType b) return type == a || type == b; } + /// + /// Checks if the provided token is one of the list of tokens. + /// + /// The token to examine. + /// The 1st type to match. + /// The 2nd type to match. + /// The 3rd type to match. + /// Result of the examination. + public static Boolean Is(this CssToken token, CssTokenType a, CssTokenType b, CssTokenType c) + { + var type = token.Type; + return type == a || type == b || type == c; + } + + /// + /// Checks if the provided token is one of the list of tokens. + /// + /// The token to examine. + /// The 1st type to match. + /// The 2nd type to match. + /// The 3rd type to match. + /// The 4th type to match. + /// The 5th type to match. + /// The 6th type to match. + /// The 7th type to match. + /// The 8th type to match. + /// Result of the examination. + public static Boolean Is(this CssToken token, CssTokenType a, CssTokenType b, CssTokenType c, CssTokenType d, CssTokenType e, CssTokenType f, CssTokenType g, CssTokenType h) + { + var type = token.Type; + return type == a || type == b || type == c || type == d || type == e || type == f || type == g || type == h; + } + /// /// Checks if the provided token is neither of the first nor the second /// type of token. From bf4db74be02554ae8f05098bb5184fc410e4e27b Mon Sep 17 00:00:00 2001 From: Florian Rappl Date: Wed, 17 Jan 2024 09:35:15 +0100 Subject: [PATCH 27/53] Allow disabling of nesting --- src/AngleSharp.Css.Tests/Styling/CssCases.cs | 1 + .../Extensions/StyleCollectionExtensions.cs | 12 ++++++------ src/AngleSharp.Css/Parser/CssBuilder.cs | 2 +- src/AngleSharp.Css/Parser/CssParserOptions.cs | 13 +++++++++++++ 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/AngleSharp.Css.Tests/Styling/CssCases.cs b/src/AngleSharp.Css.Tests/Styling/CssCases.cs index 2e7d8bb5..fb1e6593 100644 --- a/src/AngleSharp.Css.Tests/Styling/CssCases.cs +++ b/src/AngleSharp.Css.Tests/Styling/CssCases.cs @@ -15,6 +15,7 @@ private static ICssStyleSheet ParseSheet(String text) { IsIncludingUnknownDeclarations = true, IsIncludingUnknownRules = true, + IsExcludingNesting = true, IsToleratingInvalidSelectors = false, }); } diff --git a/src/AngleSharp.Css/Extensions/StyleCollectionExtensions.cs b/src/AngleSharp.Css/Extensions/StyleCollectionExtensions.cs index 4b050635..bfbfcef6 100644 --- a/src/AngleSharp.Css/Extensions/StyleCollectionExtensions.cs +++ b/src/AngleSharp.Css/Extensions/StyleCollectionExtensions.cs @@ -116,15 +116,15 @@ IEnumerable> MapPriority(ICssStyleRule rule) if (rule.TryMatch(element, null, out var specificity)) { yield return Tuple.Create(rule, specificity); + } - foreach (var subRule in rule.Rules) + foreach (var subRule in rule.Rules) + { + if (subRule is ICssStyleRule style) { - if (subRule is ICssStyleRule style) + foreach (var item in MapPriority(style)) { - foreach (var item in MapPriority(style)) - { - yield return item; - } + yield return item; } } } diff --git a/src/AngleSharp.Css/Parser/CssBuilder.cs b/src/AngleSharp.Css/Parser/CssBuilder.cs index 57e1ea96..78a342e4 100644 --- a/src/AngleSharp.Css/Parser/CssBuilder.cs +++ b/src/AngleSharp.Css/Parser/CssBuilder.cs @@ -524,7 +524,7 @@ public void CreateDeclarationWith(ICssProperties properties, ref CssToken token) CollectTrivia(ref token); var start = token.Position; - if (token.IsPotentiallyNested() && properties is ICssStyleDeclaration decl && decl.Parent is CssStyleRule style) + if (!_options.IsExcludingNesting && token.IsPotentiallyNested() && properties is ICssStyleDeclaration decl && decl.Parent is CssStyleRule style) { var rule = new CssStyleRule(style.Owner); var result = CreateStyle(rule, token); diff --git a/src/AngleSharp.Css/Parser/CssParserOptions.cs b/src/AngleSharp.Css/Parser/CssParserOptions.cs index e14811af..a3432e85 100644 --- a/src/AngleSharp.Css/Parser/CssParserOptions.cs +++ b/src/AngleSharp.Css/Parser/CssParserOptions.cs @@ -36,5 +36,18 @@ public Boolean IsToleratingInvalidSelectors get; set; } + + /// + /// Gets or sets if nesting of style rules should be + /// disabled; if disabled the interpretation is closer to + /// older browser also accepting declarations such as + /// "*x-overflow", which would otherwise be an invalid + /// nesting rule. + /// + public Boolean IsExcludingNesting + { + get; + set; + } } } \ No newline at end of file From 06b15e350a0a6b4b9469c7b81648b7ba0da7664b Mon Sep 17 00:00:00 2001 From: Florian Rappl Date: Wed, 17 Jan 2024 14:30:49 +0100 Subject: [PATCH 28/53] 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 From e97935a6761c89cdc9fba43d45530d3188ede1e7 Mon Sep 17 00:00:00 2001 From: Florian Rappl Date: Wed, 17 Jan 2024 15:32:29 +0100 Subject: [PATCH 29/53] Added margin and padding decls --- CHANGELOG.md | 2 + src/AngleSharp.Css/Constants/InitialValues.cs | 8 ++ src/AngleSharp.Css/Constants/PropertyNames.cs | 68 ++++++++++- .../Converters/FlowRelativeValueConverter.cs | 44 +++++++ .../Declarations/MarginBlockDeclaration.cs | 56 +++++++++ .../Declarations/MarginBlockEndDeclaration.cs | 22 ++++ .../MarginBlockStartDeclaration.cs | 22 ++++ .../Declarations/MarginInlineDeclaration.cs | 56 +++++++++ .../MarginInlineEndDeclaration.cs | 22 ++++ .../MarginInlineStartDeclaration.cs | 22 ++++ .../Declarations/PaddingBlockDeclaration.cs | 56 +++++++++ .../PaddingBlockEndDeclaration.cs | 22 ++++ .../PaddingBlockStartDeclaration.cs | 22 ++++ .../Declarations/PaddingInlineDeclaration.cs | 56 +++++++++ .../PaddingInlineEndDeclaration.cs | 22 ++++ .../PaddingInlineStartDeclaration.cs | 22 ++++ .../Extensions/ValueConverterExtensions.cs | 3 + .../Factories/DefaultDeclarationFactory.cs | 96 +++++++++++++++ .../Values/Multiples/CssFlowRelativeValue.cs | 114 ++++++++++++++++++ 19 files changed, 731 insertions(+), 4 deletions(-) create mode 100644 src/AngleSharp.Css/Converters/FlowRelativeValueConverter.cs create mode 100644 src/AngleSharp.Css/Declarations/MarginBlockDeclaration.cs create mode 100644 src/AngleSharp.Css/Declarations/MarginBlockEndDeclaration.cs create mode 100644 src/AngleSharp.Css/Declarations/MarginBlockStartDeclaration.cs create mode 100644 src/AngleSharp.Css/Declarations/MarginInlineDeclaration.cs create mode 100644 src/AngleSharp.Css/Declarations/MarginInlineEndDeclaration.cs create mode 100644 src/AngleSharp.Css/Declarations/MarginInlineStartDeclaration.cs create mode 100644 src/AngleSharp.Css/Declarations/PaddingBlockDeclaration.cs create mode 100644 src/AngleSharp.Css/Declarations/PaddingBlockEndDeclaration.cs create mode 100644 src/AngleSharp.Css/Declarations/PaddingBlockStartDeclaration.cs create mode 100644 src/AngleSharp.Css/Declarations/PaddingInlineDeclaration.cs create mode 100644 src/AngleSharp.Css/Declarations/PaddingInlineEndDeclaration.cs create mode 100644 src/AngleSharp.Css/Declarations/PaddingInlineStartDeclaration.cs create mode 100644 src/AngleSharp.Css/Values/Multiples/CssFlowRelativeValue.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 057fd583..efebf3e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,8 @@ Released on tbd. - Added further compactification of CSS tuples (#89, #93) - Added support for CSS nesting in style rules (#148) - 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 - Added more CSSOM possibilities and helpers (#6) - Added parts of recent color spec update such as `rgb` with spaces (#131) - Added now Color L4 parsing with `hsl`, `hwb`, `lab`, `lch`, `oklab`, and `oklch` diff --git a/src/AngleSharp.Css/Constants/InitialValues.cs b/src/AngleSharp.Css/Constants/InitialValues.cs index 6fa6a640..21c9a7c5 100644 --- a/src/AngleSharp.Css/Constants/InitialValues.cs +++ b/src/AngleSharp.Css/Constants/InitialValues.cs @@ -95,10 +95,18 @@ static class InitialValues public static readonly ICssValue MinWidthDecl = new Constant(CssKeywords.Auto, Length.Auto); public static readonly ICssValue MaxHeightDecl = new Constant(CssKeywords.None, null); public static readonly ICssValue MaxWidthDecl = new Constant(CssKeywords.None, null); + public static readonly ICssValue MarginBlockEndDecl = Length.Zero; + public static readonly ICssValue MarginBlockStartDecl = Length.Zero; + public static readonly ICssValue MarginInlineEndDecl = Length.Zero; + public static readonly ICssValue MarginInlineStartDecl = Length.Zero; public static readonly ICssValue MarginLeftDecl = Length.Zero; public static readonly ICssValue MarginBottomDecl = Length.Zero; public static readonly ICssValue MarginRightDecl = Length.Zero; public static readonly ICssValue MarginTopDecl = Length.Zero; + public static readonly ICssValue PaddingBlockEndDecl = Length.Zero; + public static readonly ICssValue PaddingBlockStartDecl = Length.Zero; + public static readonly ICssValue PaddingInlineEndDecl = Length.Zero; + public static readonly ICssValue PaddingInlineStartDecl = Length.Zero; public static readonly ICssValue PaddingLeftDecl = Length.Zero; public static readonly ICssValue PaddingBottomDecl = Length.Zero; public static readonly ICssValue PaddingRightDecl = Length.Zero; diff --git a/src/AngleSharp.Css/Constants/PropertyNames.cs b/src/AngleSharp.Css/Constants/PropertyNames.cs index e08efff8..e5642e3d 100644 --- a/src/AngleSharp.Css/Constants/PropertyNames.cs +++ b/src/AngleSharp.Css/Constants/PropertyNames.cs @@ -802,6 +802,36 @@ public static class PropertyNames /// public static readonly String ListStyle = "list-style"; + /// + /// The margin-block declaration. + /// + public static readonly String MarginBlock = "margin-block"; + + /// + /// The margin-block-end declaration. + /// + public static readonly String MarginBlockEnd = "margin-block-end"; + + /// + /// The margin-block-start declaration. + /// + public static readonly String MarginBlockStart = "margin-block-start"; + + /// + /// The margin-inline declaration. + /// + public static readonly String MarginInline = "margin-inline"; + + /// + /// The margin-inline-end declaration. + /// + public static readonly String MarginInlineEnd = "margin-inline-end"; + + /// + /// The margin-inline-start declaration. + /// + public static readonly String MarginInlineStart = "margin-inline-start"; + /// /// The margin-right declaration. /// @@ -927,10 +957,40 @@ public static class PropertyNames /// public static readonly String OverflowWrap = "overflow-wrap"; - /// - /// The padding-top declaration. - /// - public static readonly String PaddingTop = "padding-top"; + /// + /// The padding-block declaration. + /// + public static readonly String PaddingBlock = "padding-block"; + + /// + /// The padding-block-end declaration. + /// + public static readonly String PaddingBlockEnd = "padding-block-end"; + + /// + /// The padding-block-start declaration. + /// + public static readonly String PaddingBlockStart = "padding-block-start"; + + /// + /// The padding-inline declaration. + /// + public static readonly String PaddingInline = "padding-inline"; + + /// + /// The padding-inline-end declaration. + /// + public static readonly String PaddingInlineEnd = "padding-inline-end"; + + /// + /// The padding-inline-start declaration. + /// + public static readonly String PaddingInlineStart = "padding-inline-start"; + + /// + /// The padding-top declaration. + /// + public static readonly String PaddingTop = "padding-top"; /// /// The padding-right declaration. diff --git a/src/AngleSharp.Css/Converters/FlowRelativeValueConverter.cs b/src/AngleSharp.Css/Converters/FlowRelativeValueConverter.cs new file mode 100644 index 00000000..5994deeb --- /dev/null +++ b/src/AngleSharp.Css/Converters/FlowRelativeValueConverter.cs @@ -0,0 +1,44 @@ +namespace AngleSharp.Css.Converters +{ + using AngleSharp.Css.Dom; + using AngleSharp.Css.Parser; + using AngleSharp.Css.Values; + using AngleSharp.Text; + using System; + + sealed class FlowRelativeValueConverter : IValueConverter + { + private readonly IValueConverter _converter; + + public FlowRelativeValueConverter(IValueConverter converter) + { + _converter = converter; + } + + public ICssValue Convert(StringSource source) + { + var options = new ICssValue[2]; + var length = 0; + + for (var i = 0; i < options.Length; i++) + { + options[i] = _converter.Convert(source); + source.SkipSpacesAndComments(); + + if (options[length] != null) + { + length++; + } + } + + if (length > 0) + { + var values = new ICssValue[length]; + Array.Copy(options, values, length); + return new CssFlowRelativeValue(values); + } + + return null; + } + } +} diff --git a/src/AngleSharp.Css/Declarations/MarginBlockDeclaration.cs b/src/AngleSharp.Css/Declarations/MarginBlockDeclaration.cs new file mode 100644 index 00000000..91dfc287 --- /dev/null +++ b/src/AngleSharp.Css/Declarations/MarginBlockDeclaration.cs @@ -0,0 +1,56 @@ +namespace AngleSharp.Css.Declarations +{ + using AngleSharp.Css.Converters; + using AngleSharp.Css.Dom; + using AngleSharp.Css.Values; + using AngleSharp.Text; + using System; + using static ValueConverters; + + static class MarginBlockDeclaration + { + public static String Name = PropertyNames.MarginBlock; + + public static IValueConverter Converter = new MarginBlockAggregator(); + + public static ICssValue InitialValue = null; + + public static PropertyFlags Flags = PropertyFlags.Shorthand; + + public static String[] Longhands = new[] + { + PropertyNames.MarginBlockStart, + PropertyNames.MarginBlockEnd, + }; + + sealed class MarginBlockAggregator : IValueAggregator, IValueConverter + { + private static readonly IValueConverter converter = Or(AutoLengthOrPercentConverter, AssignInitial(Length.Zero)).FlowRelative(); + + public ICssValue Convert(StringSource source) => converter.Convert(source); + + public ICssValue Merge(ICssValue[] values) + { + var start = values[0]; + var end = values[1]; + + if (start != null && end != null) + { + return new CssFlowRelativeValue(new[] { start, end }); + } + + return null; + } + + public ICssValue[] Split(ICssValue value) + { + if (value is CssFlowRelativeValue flowRelative) + { + return new[] { flowRelative.Start, flowRelative.End }; + } + + return null; + } + } + } +} diff --git a/src/AngleSharp.Css/Declarations/MarginBlockEndDeclaration.cs b/src/AngleSharp.Css/Declarations/MarginBlockEndDeclaration.cs new file mode 100644 index 00000000..fe51f054 --- /dev/null +++ b/src/AngleSharp.Css/Declarations/MarginBlockEndDeclaration.cs @@ -0,0 +1,22 @@ +namespace AngleSharp.Css.Declarations +{ + using System; + using Dom; + using static ValueConverters; + + static class MarginBlockEndDeclaration + { + public static String Name = PropertyNames.MarginBlockEnd; + + public static String[] Shorthands = new[] + { + PropertyNames.MarginBlock, + }; + + public static IValueConverter Converter = AutoLengthOrPercentConverter; + + public static ICssValue InitialValue = InitialValues.MarginBlockEndDecl; + + public static PropertyFlags Flags = PropertyFlags.Unitless | PropertyFlags.Animatable; + } +} diff --git a/src/AngleSharp.Css/Declarations/MarginBlockStartDeclaration.cs b/src/AngleSharp.Css/Declarations/MarginBlockStartDeclaration.cs new file mode 100644 index 00000000..968a30bd --- /dev/null +++ b/src/AngleSharp.Css/Declarations/MarginBlockStartDeclaration.cs @@ -0,0 +1,22 @@ +namespace AngleSharp.Css.Declarations +{ + using System; + using Dom; + using static ValueConverters; + + static class MarginBlockStartDeclaration + { + public static String Name = PropertyNames.MarginBlockStart; + + public static String[] Shorthands = new[] + { + PropertyNames.MarginBlock, + }; + + public static IValueConverter Converter = AutoLengthOrPercentConverter; + + public static ICssValue InitialValue = InitialValues.MarginBlockStartDecl; + + public static PropertyFlags Flags = PropertyFlags.Unitless | PropertyFlags.Animatable; + } +} \ No newline at end of file diff --git a/src/AngleSharp.Css/Declarations/MarginInlineDeclaration.cs b/src/AngleSharp.Css/Declarations/MarginInlineDeclaration.cs new file mode 100644 index 00000000..9e18f10e --- /dev/null +++ b/src/AngleSharp.Css/Declarations/MarginInlineDeclaration.cs @@ -0,0 +1,56 @@ +namespace AngleSharp.Css.Declarations +{ + using AngleSharp.Css.Converters; + using AngleSharp.Css.Dom; + using AngleSharp.Css.Values; + using AngleSharp.Text; + using System; + using static ValueConverters; + + static class MarginInlineDeclaration + { + public static String Name = PropertyNames.MarginInline; + + public static IValueConverter Converter = new MarginInlineAggregator(); + + public static ICssValue InitialValue = null; + + public static PropertyFlags Flags = PropertyFlags.Shorthand; + + public static String[] Longhands = new[] + { + PropertyNames.MarginInlineStart, + PropertyNames.MarginInlineEnd, + }; + + sealed class MarginInlineAggregator : IValueAggregator, IValueConverter + { + private static readonly IValueConverter converter = Or(AutoLengthOrPercentConverter, AssignInitial(Length.Zero)).FlowRelative(); + + public ICssValue Convert(StringSource source) => converter.Convert(source); + + public ICssValue Merge(ICssValue[] values) + { + var start = values[0]; + var end = values[1]; + + if (start != null && end != null) + { + return new CssFlowRelativeValue(new[] { start, end }); + } + + return null; + } + + public ICssValue[] Split(ICssValue value) + { + if (value is CssFlowRelativeValue flowRelative) + { + return new[] { flowRelative.Start, flowRelative.End }; + } + + return null; + } + } + } +} diff --git a/src/AngleSharp.Css/Declarations/MarginInlineEndDeclaration.cs b/src/AngleSharp.Css/Declarations/MarginInlineEndDeclaration.cs new file mode 100644 index 00000000..978b08c2 --- /dev/null +++ b/src/AngleSharp.Css/Declarations/MarginInlineEndDeclaration.cs @@ -0,0 +1,22 @@ +namespace AngleSharp.Css.Declarations +{ + using Dom; + using System; + using static ValueConverters; + + static class MarginInlineEndDeclaration + { + public static String Name = PropertyNames.MarginInlineEnd; + + public static String[] Shorthands = new[] + { + PropertyNames.MarginInline, + }; + + public static IValueConverter Converter = AutoLengthOrPercentConverter; + + public static ICssValue InitialValue = InitialValues.MarginInlineEndDecl; + + public static PropertyFlags Flags = PropertyFlags.Unitless | PropertyFlags.Animatable; + } +} diff --git a/src/AngleSharp.Css/Declarations/MarginInlineStartDeclaration.cs b/src/AngleSharp.Css/Declarations/MarginInlineStartDeclaration.cs new file mode 100644 index 00000000..5216e7a4 --- /dev/null +++ b/src/AngleSharp.Css/Declarations/MarginInlineStartDeclaration.cs @@ -0,0 +1,22 @@ +namespace AngleSharp.Css.Declarations +{ + using System; + using Dom; + using static ValueConverters; + + static class MarginInlineStartDeclaration + { + public static String Name = PropertyNames.MarginInlineStart; + + public static String[] Shorthands = new[] + { + PropertyNames.MarginInline, + }; + + public static IValueConverter Converter = AutoLengthOrPercentConverter; + + public static ICssValue InitialValue = InitialValues.MarginInlineStartDecl; + + public static PropertyFlags Flags = PropertyFlags.Unitless | PropertyFlags.Animatable; + } +} \ No newline at end of file diff --git a/src/AngleSharp.Css/Declarations/PaddingBlockDeclaration.cs b/src/AngleSharp.Css/Declarations/PaddingBlockDeclaration.cs new file mode 100644 index 00000000..f24d51cb --- /dev/null +++ b/src/AngleSharp.Css/Declarations/PaddingBlockDeclaration.cs @@ -0,0 +1,56 @@ +namespace AngleSharp.Css.Declarations +{ + using AngleSharp.Css.Converters; + using AngleSharp.Css.Dom; + using AngleSharp.Css.Values; + using AngleSharp.Text; + using System; + using static ValueConverters; + + static class PaddingBlockDeclaration + { + public static String Name = PropertyNames.PaddingBlock; + + public static IValueConverter Converter = new PaddingBlockAggregator(); + + public static ICssValue InitialValue = null; + + public static PropertyFlags Flags = PropertyFlags.Shorthand; + + public static String[] Longhands = new[] + { + PropertyNames.PaddingBlockStart, + PropertyNames.PaddingBlockEnd, + }; + + sealed class PaddingBlockAggregator : IValueAggregator, IValueConverter + { + private static readonly IValueConverter converter = Or(AutoLengthOrPercentConverter, AssignInitial(Length.Zero)).FlowRelative(); + + public ICssValue Convert(StringSource source) => converter.Convert(source); + + public ICssValue Merge(ICssValue[] values) + { + var start = values[0]; + var end = values[1]; + + if (start != null && end != null) + { + return new CssFlowRelativeValue(new[] { start, end }); + } + + return null; + } + + public ICssValue[] Split(ICssValue value) + { + if (value is CssFlowRelativeValue flowRelative) + { + return new[] { flowRelative.Start, flowRelative.End }; + } + + return null; + } + } + } +} diff --git a/src/AngleSharp.Css/Declarations/PaddingBlockEndDeclaration.cs b/src/AngleSharp.Css/Declarations/PaddingBlockEndDeclaration.cs new file mode 100644 index 00000000..5c631b13 --- /dev/null +++ b/src/AngleSharp.Css/Declarations/PaddingBlockEndDeclaration.cs @@ -0,0 +1,22 @@ +namespace AngleSharp.Css.Declarations +{ + using System; + using Dom; + using static ValueConverters; + + static class PaddingBlockEndDeclaration + { + public static String Name = PropertyNames.PaddingBlockEnd; + + public static String[] Shorthands = new[] + { + PropertyNames.PaddingBlock, + }; + + public static IValueConverter Converter = AutoLengthOrPercentConverter; + + public static ICssValue InitialValue = InitialValues.PaddingBlockEndDecl; + + public static PropertyFlags Flags = PropertyFlags.Unitless | PropertyFlags.Animatable; + } +} diff --git a/src/AngleSharp.Css/Declarations/PaddingBlockStartDeclaration.cs b/src/AngleSharp.Css/Declarations/PaddingBlockStartDeclaration.cs new file mode 100644 index 00000000..492d5e5b --- /dev/null +++ b/src/AngleSharp.Css/Declarations/PaddingBlockStartDeclaration.cs @@ -0,0 +1,22 @@ +namespace AngleSharp.Css.Declarations +{ + using System; + using Dom; + using static ValueConverters; + + static class PaddingBlockStartDeclaration + { + public static String Name = PropertyNames.PaddingBlockStart; + + public static String[] Shorthands = new[] + { + PropertyNames.PaddingBlock, + }; + + public static IValueConverter Converter = AutoLengthOrPercentConverter; + + public static ICssValue InitialValue = InitialValues.PaddingBlockStartDecl; + + public static PropertyFlags Flags = PropertyFlags.Unitless | PropertyFlags.Animatable; + } +} \ No newline at end of file diff --git a/src/AngleSharp.Css/Declarations/PaddingInlineDeclaration.cs b/src/AngleSharp.Css/Declarations/PaddingInlineDeclaration.cs new file mode 100644 index 00000000..c003209b --- /dev/null +++ b/src/AngleSharp.Css/Declarations/PaddingInlineDeclaration.cs @@ -0,0 +1,56 @@ +namespace AngleSharp.Css.Declarations +{ + using AngleSharp.Css.Converters; + using AngleSharp.Css.Dom; + using AngleSharp.Css.Values; + using AngleSharp.Text; + using System; + using static ValueConverters; + + static class PaddingInlineDeclaration + { + public static String Name = PropertyNames.PaddingInline; + + public static IValueConverter Converter = new PaddingInlineAggregator(); + + public static ICssValue InitialValue = null; + + public static PropertyFlags Flags = PropertyFlags.Shorthand; + + public static String[] Longhands = new[] + { + PropertyNames.PaddingInlineStart, + PropertyNames.PaddingInlineEnd, + }; + + sealed class PaddingInlineAggregator : IValueAggregator, IValueConverter + { + private static readonly IValueConverter converter = Or(AutoLengthOrPercentConverter, AssignInitial(Length.Zero)).FlowRelative(); + + public ICssValue Convert(StringSource source) => converter.Convert(source); + + public ICssValue Merge(ICssValue[] values) + { + var start = values[0]; + var end = values[1]; + + if (start != null && end != null) + { + return new CssFlowRelativeValue(new[] { start, end }); + } + + return null; + } + + public ICssValue[] Split(ICssValue value) + { + if (value is CssFlowRelativeValue flowRelative) + { + return new[] { flowRelative.Start, flowRelative.End }; + } + + return null; + } + } + } +} \ No newline at end of file diff --git a/src/AngleSharp.Css/Declarations/PaddingInlineEndDeclaration.cs b/src/AngleSharp.Css/Declarations/PaddingInlineEndDeclaration.cs new file mode 100644 index 00000000..ebf6002d --- /dev/null +++ b/src/AngleSharp.Css/Declarations/PaddingInlineEndDeclaration.cs @@ -0,0 +1,22 @@ +namespace AngleSharp.Css.Declarations +{ + using System; + using Dom; + using static ValueConverters; + + static class PaddingInlineEndDeclaration + { + public static String Name = PropertyNames.PaddingInlineEnd; + + public static String[] Shorthands = new[] + { + PropertyNames.PaddingInline, + }; + + public static IValueConverter Converter = AutoLengthOrPercentConverter; + + public static ICssValue InitialValue = InitialValues.PaddingInlineEndDecl; + + public static PropertyFlags Flags = PropertyFlags.Unitless | PropertyFlags.Animatable; + } +} diff --git a/src/AngleSharp.Css/Declarations/PaddingInlineStartDeclaration.cs b/src/AngleSharp.Css/Declarations/PaddingInlineStartDeclaration.cs new file mode 100644 index 00000000..43eb90bd --- /dev/null +++ b/src/AngleSharp.Css/Declarations/PaddingInlineStartDeclaration.cs @@ -0,0 +1,22 @@ +namespace AngleSharp.Css.Declarations +{ + using System; + using Dom; + using static ValueConverters; + + static class PaddingInlineStartDeclaration + { + public static String Name = PropertyNames.PaddingInlineStart; + + public static String[] Shorthands = new[] + { + PropertyNames.PaddingInline, + }; + + public static IValueConverter Converter = AutoLengthOrPercentConverter; + + public static ICssValue InitialValue = InitialValues.PaddingInlineStartDecl; + + public static PropertyFlags Flags = PropertyFlags.Unitless | PropertyFlags.Animatable; + } +} \ No newline at end of file diff --git a/src/AngleSharp.Css/Extensions/ValueConverterExtensions.cs b/src/AngleSharp.Css/Extensions/ValueConverterExtensions.cs index 17b59636..92f8f5ab 100644 --- a/src/AngleSharp.Css/Extensions/ValueConverterExtensions.cs +++ b/src/AngleSharp.Css/Extensions/ValueConverterExtensions.cs @@ -37,6 +37,9 @@ public static IValueConverter FromList(this IValueConverter converter) => public static IValueConverter ToConverter(this Dictionary values) => new DictionaryValueConverter(values); + public static IValueConverter FlowRelative(this IValueConverter converter) => + new FlowRelativeValueConverter(converter); + public static IValueConverter Periodic(this IValueConverter converter) => new PeriodicValueConverter(converter); diff --git a/src/AngleSharp.Css/Factories/DefaultDeclarationFactory.cs b/src/AngleSharp.Css/Factories/DefaultDeclarationFactory.cs index 72d4ae0a..80cbe8a1 100644 --- a/src/AngleSharp.Css/Factories/DefaultDeclarationFactory.cs +++ b/src/AngleSharp.Css/Factories/DefaultDeclarationFactory.cs @@ -770,6 +770,38 @@ public class DefaultDeclarationFactory : IDeclarationFactory flags: ColumnRuleColorDeclaration.Flags, shorthands: ColumnRuleColorDeclaration.Shorthands) }, + { + PaddingBlockEndDeclaration.Name, new DeclarationInfo( + name: PaddingBlockEndDeclaration.Name, + converter: PaddingBlockEndDeclaration.Converter, + initialValue: PaddingBlockEndDeclaration.InitialValue, + flags: PaddingBlockEndDeclaration.Flags, + shorthands: PaddingBlockEndDeclaration.Shorthands) + }, + { + PaddingBlockStartDeclaration.Name, new DeclarationInfo( + name: PaddingBlockStartDeclaration.Name, + converter: PaddingBlockStartDeclaration.Converter, + initialValue: PaddingBlockStartDeclaration.InitialValue, + flags: PaddingBlockStartDeclaration.Flags, + shorthands: PaddingBlockStartDeclaration.Shorthands) + }, + { + PaddingInlineEndDeclaration.Name, new DeclarationInfo( + name: PaddingInlineEndDeclaration.Name, + converter: PaddingInlineEndDeclaration.Converter, + initialValue: PaddingInlineEndDeclaration.InitialValue, + flags: PaddingInlineEndDeclaration.Flags, + shorthands: PaddingInlineEndDeclaration.Shorthands) + }, + { + PaddingInlineStartDeclaration.Name, new DeclarationInfo( + name: PaddingInlineStartDeclaration.Name, + converter: PaddingInlineStartDeclaration.Converter, + initialValue: PaddingInlineStartDeclaration.InitialValue, + flags: PaddingInlineStartDeclaration.Flags, + shorthands: PaddingInlineStartDeclaration.Shorthands) + }, { PaddingTopDeclaration.Name, new DeclarationInfo( name: PaddingTopDeclaration.Name, @@ -801,6 +833,38 @@ public class DefaultDeclarationFactory : IDeclarationFactory initialValue: PaddingBottomDeclaration.InitialValue, flags: PaddingBottomDeclaration.Flags, shorthands: PaddingBottomDeclaration.Shorthands) + }, + { + MarginBlockEndDeclaration.Name, new DeclarationInfo( + name: MarginBlockEndDeclaration.Name, + converter: MarginBlockEndDeclaration.Converter, + initialValue: MarginBlockEndDeclaration.InitialValue, + flags: MarginBlockEndDeclaration.Flags, + shorthands: MarginBlockEndDeclaration.Shorthands) + }, + { + MarginBlockStartDeclaration.Name, new DeclarationInfo( + name: MarginBlockStartDeclaration.Name, + converter: MarginBlockStartDeclaration.Converter, + initialValue: MarginBlockStartDeclaration.InitialValue, + flags: MarginBlockStartDeclaration.Flags, + shorthands: MarginBlockStartDeclaration.Shorthands) + }, + { + MarginInlineEndDeclaration.Name, new DeclarationInfo( + name: MarginInlineEndDeclaration.Name, + converter: MarginInlineEndDeclaration.Converter, + initialValue: MarginInlineEndDeclaration.InitialValue, + flags: MarginInlineEndDeclaration.Flags, + shorthands: MarginInlineEndDeclaration.Shorthands) + }, + { + MarginInlineStartDeclaration.Name, new DeclarationInfo( + name: MarginInlineStartDeclaration.Name, + converter: MarginInlineStartDeclaration.Converter, + initialValue: MarginInlineStartDeclaration.InitialValue, + flags: MarginInlineStartDeclaration.Flags, + shorthands: MarginInlineStartDeclaration.Shorthands) }, { MarginTopDeclaration.Name, new DeclarationInfo( @@ -1324,6 +1388,22 @@ public class DefaultDeclarationFactory : IDeclarationFactory flags: PaddingDeclaration.Flags, longhands: PaddingDeclaration.Longhands) }, + { + PaddingBlockDeclaration.Name, new DeclarationInfo( + name: PaddingBlockDeclaration.Name, + converter: PaddingBlockDeclaration.Converter, + initialValue: PaddingBlockDeclaration.InitialValue, + flags: PaddingBlockDeclaration.Flags, + longhands: PaddingBlockDeclaration.Longhands) + }, + { + PaddingInlineDeclaration.Name, new DeclarationInfo( + name: PaddingInlineDeclaration.Name, + converter: PaddingInlineDeclaration.Converter, + initialValue: PaddingInlineDeclaration.InitialValue, + flags: PaddingInlineDeclaration.Flags, + longhands: PaddingInlineDeclaration.Longhands) + }, { MarginDeclaration.Name, new DeclarationInfo( name: MarginDeclaration.Name, @@ -1332,6 +1412,22 @@ public class DefaultDeclarationFactory : IDeclarationFactory flags: MarginDeclaration.Flags, longhands: MarginDeclaration.Longhands) }, + { + MarginBlockDeclaration.Name, new DeclarationInfo( + name: MarginBlockDeclaration.Name, + converter: MarginBlockDeclaration.Converter, + initialValue: MarginBlockDeclaration.InitialValue, + flags: MarginBlockDeclaration.Flags, + longhands: MarginBlockDeclaration.Longhands) + }, + { + MarginInlineDeclaration.Name, new DeclarationInfo( + name: MarginInlineDeclaration.Name, + converter: MarginInlineDeclaration.Converter, + initialValue: MarginInlineDeclaration.InitialValue, + flags: MarginInlineDeclaration.Flags, + longhands: MarginInlineDeclaration.Longhands) + }, { BorderRadiusDeclaration.Name, new DeclarationInfo( name: BorderRadiusDeclaration.Name, diff --git a/src/AngleSharp.Css/Values/Multiples/CssFlowRelativeValue.cs b/src/AngleSharp.Css/Values/Multiples/CssFlowRelativeValue.cs new file mode 100644 index 00000000..448d577d --- /dev/null +++ b/src/AngleSharp.Css/Values/Multiples/CssFlowRelativeValue.cs @@ -0,0 +1,114 @@ +namespace AngleSharp.Css.Values +{ + using AngleSharp.Css.Dom; + using AngleSharp.Text; + using System; + using System.Collections; + using System.Collections.Generic; + + /// + /// Represents a flow relative CSS value. + /// + public class CssFlowRelativeValue : ICssMultipleValue + where T : ICssValue + { + #region Fields + + private readonly T[] _values; + + #endregion + + #region ctor + + /// + /// Creates a new CSS flow relative value container. + /// + /// The items to contain. + public CssFlowRelativeValue(T[] values = null) + { + _values = values ?? Array.Empty(); + } + + #endregion + + #region Properties + + /// + public ICssValue this[Int32 index] + { + get + { + switch (index) + { + case 0: + return Start; + case 1: + return End; + default: + throw new ArgumentOutOfRangeException(nameof(index)); + } + } + } + + /// + public String CssText + { + get + { + var l = _values.Length; + var parts = new String[l]; + + for (var i = 0; i < l; i++) + { + parts[i] = _values[i].CssText; + } + + if (l == 2 && parts[1].Is(parts[0])) + { + l = 1; + parts = new[] { parts[0] }; + } + + return String.Join(" ", parts); + } + } + + /// + public T Start => _values.Length > 0 ? _values[0] : default; + + /// + public T End => _values.Length > 1 ? _values[1] : Start; + + /// + public Int32 Count => 2; + + #endregion + + #region Methods + + IEnumerator IEnumerable.GetEnumerator() + { + yield return Start; + yield return End; + } + + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)this).GetEnumerator(); + + #endregion + } + + /// + /// Represents a flow relative CSS value. + /// + sealed class CssFlowRelativeValue : CssFlowRelativeValue + { + #region ctor + + public CssFlowRelativeValue(ICssValue[] values = null) + : base(values) + { + } + + #endregion + } +} From a5bcabf90c647efaa2e945297e1cf38cee541989 Mon Sep 17 00:00:00 2001 From: Florian Rappl Date: Wed, 17 Jan 2024 15:55:38 +0100 Subject: [PATCH 30/53] Improved render tree --- .../Extensions/Elements.cs | 1 + .../Dom/Internal/CssProperty.cs | 2 + .../RenderTree/ElementRenderNode.cs | 23 ++- .../RenderTree/RenderTreeBuilder.cs | 168 ++++++++++++++++-- .../RenderTree/TextRenderNode.cs | 11 +- .../Values/Primitives/Length.cs | 8 + 6 files changed, 184 insertions(+), 29 deletions(-) diff --git a/src/AngleSharp.Css.Tests/Extensions/Elements.cs b/src/AngleSharp.Css.Tests/Extensions/Elements.cs index 0134dc61..c5dff0e6 100644 --- a/src/AngleSharp.Css.Tests/Extensions/Elements.cs +++ b/src/AngleSharp.Css.Tests/Extensions/Elements.cs @@ -39,6 +39,7 @@ public async Task DownloadResources() }; var config = Configuration.Default .WithDefaultLoader(loaderOptions) + .WithRenderDevice() .WithCss(); var document = "
    ".ToHtmlDocument(config); var tree = document.DefaultView.Render(); diff --git a/src/AngleSharp.Css/Dom/Internal/CssProperty.cs b/src/AngleSharp.Css/Dom/Internal/CssProperty.cs index f4fa91e1..8af6a154 100644 --- a/src/AngleSharp.Css/Dom/Internal/CssProperty.cs +++ b/src/AngleSharp.Css/Dom/Internal/CssProperty.cs @@ -52,6 +52,8 @@ public String Value public Boolean HasValue => _value != null; + public PropertyFlags Flags => _flags; + public Boolean IsInherited => (((_flags & PropertyFlags.Inherited) == PropertyFlags.Inherited) && IsInitial) || (HasValue && _value.CssText.Is(CssKeywords.Inherit)); public Boolean CanBeInherited => (_flags & PropertyFlags.Inherited) == PropertyFlags.Inherited; diff --git a/src/AngleSharp.Css/RenderTree/ElementRenderNode.cs b/src/AngleSharp.Css/RenderTree/ElementRenderNode.cs index ad6363f3..6e42cf19 100644 --- a/src/AngleSharp.Css/RenderTree/ElementRenderNode.cs +++ b/src/AngleSharp.Css/RenderTree/ElementRenderNode.cs @@ -3,20 +3,27 @@ namespace AngleSharp.Css.RenderTree using AngleSharp.Css.Dom; using AngleSharp.Dom; using System.Collections.Generic; - using System.Linq; - class ElementRenderNode : IRenderNode + sealed class ElementRenderNode : IRenderNode { - public INode Ref { get; set; } + public ElementRenderNode(IElement reference, IEnumerable children, ICssStyleDeclaration specifiedStyle, ICssStyleDeclaration computedStyle) + { + Ref = reference; + Children = children; + SpecifiedStyle = specifiedStyle; + ComputedStyle = computedStyle; + } - public IEnumerable Children { get; set; } = Enumerable.Empty(); + public IElement Ref { get; } - public ICssStyleDeclaration SpecifiedStyle { get; set; } + INode IRenderNode.Ref => Ref; - public ICssStyleDeclaration ComputedStyle { get; set; } + public IEnumerable Children { get; } - public RenderValues UsedValue { get; set; } + public IRenderNode? Parent { get; set; } - public RenderValues ActualValue { get; set; } + public ICssStyleDeclaration SpecifiedStyle { get; } + + public ICssStyleDeclaration ComputedStyle { get; } } } diff --git a/src/AngleSharp.Css/RenderTree/RenderTreeBuilder.cs b/src/AngleSharp.Css/RenderTree/RenderTreeBuilder.cs index fc4e3f64..34eb5466 100644 --- a/src/AngleSharp.Css/RenderTree/RenderTreeBuilder.cs +++ b/src/AngleSharp.Css/RenderTree/RenderTreeBuilder.cs @@ -1,12 +1,15 @@ namespace AngleSharp.Css.RenderTree { using AngleSharp.Css.Dom; + using AngleSharp.Css.Values; using AngleSharp.Dom; + using System; using System.Collections.Generic; using System.Linq; - class RenderTreeBuilder + sealed class RenderTreeBuilder { + private readonly IBrowsingContext _context; private readonly IWindow _window; private readonly IEnumerable _defaultSheets; private readonly IRenderDevice _device; @@ -15,8 +18,9 @@ public RenderTreeBuilder(IWindow window, IRenderDevice device = null) { var ctx = window.Document.Context; var defaultStyleSheetProvider = ctx.GetServices(); - _device = device ?? ctx.GetService(); - _defaultSheets = defaultStyleSheetProvider.Select(m => m.Default).Where(m => m != null); + _context = ctx; + _device = device ?? ctx.GetService() ?? throw new ArgumentNullException(nameof(device)); + _defaultSheets = defaultStyleSheetProvider.Select(m => m.Default).Where(m => m is not null); _window = window; } @@ -24,40 +28,166 @@ public IRenderNode RenderDocument() { var document = _window.Document; var currentSheets = document.GetStyleSheets().OfType(); - var stylesheets = _defaultSheets.Concat(currentSheets); + var stylesheets = _defaultSheets.Concat(currentSheets).ToList(); var collection = new StyleCollection(stylesheets, _device); - return RenderElement(document.DocumentElement, collection); + var rootStyle = collection.ComputeCascadedStyle(document.DocumentElement); + var rootFontSize = ((Length?)rootStyle.GetProperty(PropertyNames.FontSize)?.RawValue)?.Value ?? 16; + return RenderElement(rootFontSize, document.DocumentElement, collection); } - private ElementRenderNode RenderElement(IElement reference, StyleCollection collection, ICssStyleDeclaration parent = null) + private ElementRenderNode RenderElement(double rootFontSize, IElement reference, StyleCollection collection, ICssStyleDeclaration? parent = null) { - var style = collection.ComputeCascadedStyle(reference, parent); + var style = collection.ComputeCascadedStyle(reference); + var computedStyle = Compute(rootFontSize, style, parent); + if (parent != null) + { + computedStyle.UpdateDeclarations(parent); + } var children = new List(); foreach (var child in reference.ChildNodes) { if (child is IText text) { - children.Add(RenderText(text, collection)); + children.Add(RenderText(text)); } - else if (child is IElement element) + else if (child is IElement element) { - children.Add(RenderElement(element, collection, style)); + children.Add(RenderElement(rootFontSize, element, collection, computedStyle)); } } - return new ElementRenderNode + // compute unitless line-height after rendering children + if (computedStyle.GetProperty(PropertyNames.LineHeight).RawValue is Length { Type: Length.Unit.None } unitlessLineHeight) { - Ref = reference, - SpecifiedStyle = style, - ComputedStyle = style.Compute(_device), - Children = children, - }; + var fontSize = computedStyle.GetProperty(PropertyNames.FontSize).RawValue is Length { Type: Length.Unit.Px } fontSizeLength ? fontSizeLength.Value : rootFontSize; + var pixelValue = unitlessLineHeight.Value * fontSize; + var computedLineHeight = new Length(pixelValue, Length.Unit.Px); + + // create a new property because SetProperty would change the parent value + var lineHeightProperty = _context.CreateProperty(PropertyNames.LineHeight); + lineHeightProperty.RawValue = computedLineHeight; + computedStyle.SetDeclarations(new[] { lineHeightProperty }); + } + + var node = new ElementRenderNode(reference, children, style, computedStyle); + + foreach (var child in children) + { + if (child is ElementRenderNode elementChild) + { + elementChild.Parent = node; + } + else if (child is TextRenderNode textChild) + { + textChild.Parent = node; + } + else + { + throw new InvalidOperationException(); + } + } + + return node; } - private IRenderNode RenderText(IText text, StyleCollection collection) => new TextRenderNode + private IRenderNode RenderText(IText text) => new TextRenderNode(text); + + private CssStyleDeclaration Compute(Double rootFontSize, ICssStyleDeclaration style, ICssStyleDeclaration? parentStyle) { - Ref = text, - }; + var computedStyle = new CssStyleDeclaration(_context); + var parentFontSize = ((Length?)parentStyle?.GetProperty(PropertyNames.FontSize)?.RawValue)?.ToPixel(_device) ?? rootFontSize; + var fontSize = parentFontSize; + // compute font-size first because other properties may depend on it + if (style.GetProperty(PropertyNames.FontSize) is { RawValue: not null } fontSizeProperty) + { + fontSize = GetFontSizeInPixels(fontSizeProperty.RawValue); + } + var declarations = style.OfType().Select(property => + { + var name = property.Name; + var value = property.RawValue; + if (name == PropertyNames.FontSize) + { + // font-size was already computed + value = new Length(fontSize, Length.Unit.Px); + } + else if (value is Length { IsAbsolute: true, Type: not Length.Unit.Px } absoluteLength) + { + value = new Length(absoluteLength.ToPixel(_device), Length.Unit.Px); + } + else if (value is Length { Type: Length.Unit.Percent } percentLength) + { + if (name == PropertyNames.VerticalAlign || name == PropertyNames.LineHeight) + { + var pixelValue = percentLength.Value / 100 * fontSize; + value = new Length(pixelValue, Length.Unit.Px); + } + else + { + // TODO: compute for other properties that should be absolute + } + } + else if (value is Length { IsRelative: true, Type: not Length.Unit.None } relativeLength) + { + var pixelValue = relativeLength.Type switch + { + Length.Unit.Em => relativeLength.Value * fontSize, + Length.Unit.Rem => relativeLength.Value * rootFontSize, + _ => relativeLength.ToPixel(_device), + }; + value = new Length(pixelValue, Length.Unit.Px); + } + + return new CssProperty(name, property.Converter, property.Flags, value, property.IsImportant); + }); + + computedStyle.SetDeclarations(declarations); + + return computedStyle; + + Double GetFontSizeInPixels(ICssValue value) => value switch + { + Constant constLength when constLength.CssText == CssKeywords.XxSmall => 9D / 16 * rootFontSize, + Constant constLength when constLength.CssText == CssKeywords.XSmall => 10D / 16 * rootFontSize, + Constant constLength when constLength.CssText == CssKeywords.Small => 13D / 16 * rootFontSize, + Constant constLength when constLength.CssText == CssKeywords.Medium => 16D / 16 * rootFontSize, + Constant constLength when constLength.CssText == CssKeywords.Large => 18D / 16 * rootFontSize, + Constant constLength when constLength.CssText == CssKeywords.XLarge => 24D / 16 * rootFontSize, + Constant constLength when constLength.CssText == CssKeywords.XxLarge => 32D / 16 * rootFontSize, + Constant constLength when constLength.CssText == CssKeywords.XxxLarge => 48D / 16 * rootFontSize, + Constant constLength when constLength.CssText == CssKeywords.Smaller => ComputeRelativeFontSize(constLength), + Constant constLength when constLength.CssText == CssKeywords.Larger => ComputeRelativeFontSize(constLength), + Length { Type: Length.Unit.Px } length => length.Value, + Length { IsAbsolute: true } length => length.ToPixel(_device), + Length { Type: Length.Unit.Vh or Length.Unit.Vw or Length.Unit.Vmax or Length.Unit.Vmin } length => length.ToPixel(_device), + Length { IsRelative: true } length => ComputeRelativeFontSize(length), + ICssSpecialValue specialValue when specialValue.CssText == CssKeywords.Inherit || specialValue.CssText == CssKeywords.Unset => parentFontSize, + ICssSpecialValue specialValue when specialValue.CssText == CssKeywords.Initial => rootFontSize, + _ => throw new InvalidOperationException("Font size must be a length"), + }; + + Double ComputeRelativeFontSize(ICssValue value) + { + var ancestorValue = parentStyle?.GetProperty(PropertyNames.FontSize)?.RawValue; + var ancestorPixels = ancestorValue switch + { + Length { IsAbsolute: true } ancestorLength => ancestorLength.ToPixel(_device), + null => rootFontSize, + _ => throw new InvalidOperationException(), + }; + + // set a minimum size of 9px for relative sizes + return Math.Max(9, value switch + { + Constant constLength when constLength.CssText == CssKeywords.Smaller => ancestorPixels / 1.2, + Constant constLength when constLength.CssText == CssKeywords.Larger => ancestorPixels * 1.2, + Length { Type: Length.Unit.Rem } length => length.Value * rootFontSize, + Length { Type: Length.Unit.Em } length => length.Value * ancestorPixels, + Length { Type: Length.Unit.Percent } length => length.Value / 100 * ancestorPixels, + _ => throw new InvalidOperationException(), + }); + } + } } } diff --git a/src/AngleSharp.Css/RenderTree/TextRenderNode.cs b/src/AngleSharp.Css/RenderTree/TextRenderNode.cs index 9dc7f36d..9bfa8568 100644 --- a/src/AngleSharp.Css/RenderTree/TextRenderNode.cs +++ b/src/AngleSharp.Css/RenderTree/TextRenderNode.cs @@ -4,10 +4,17 @@ namespace AngleSharp.Css.RenderTree using System.Collections.Generic; using System.Linq; - class TextRenderNode : IRenderNode + sealed class TextRenderNode : IRenderNode { - public INode Ref { get; set; } + public TextRenderNode(INode reference) + { + Ref = reference; + } + + public INode Ref { get; } public IEnumerable Children => Enumerable.Empty(); + + public IRenderNode? Parent { get; set; } } } diff --git a/src/AngleSharp.Css/Values/Primitives/Length.cs b/src/AngleSharp.Css/Values/Primitives/Length.cs index 0512ddb4..a5f3be67 100644 --- a/src/AngleSharp.Css/Values/Primitives/Length.cs +++ b/src/AngleSharp.Css/Values/Primitives/Length.cs @@ -267,6 +267,14 @@ public static Unit GetUnit(String s) } } + /// + /// Converts the length to a number of pixels, if possible. If the + /// current unit is relative, then an exception will be thrown. + /// + /// the render device used to calculate relative units, can be null if units are absolute. + /// The number of pixels represented by the current length. + public Double ToPixel(IRenderDimensions renderDimensions) => ToPixel(renderDimensions, RenderMode.Horizontal); + /// /// Converts the length to a number of pixels, if possible. If the /// current unit is relative, then an exception will be thrown. From baca67ae9fa2cd37bb41440f6a71186210fc91dd Mon Sep 17 00:00:00 2001 From: Florian Rappl Date: Wed, 17 Jan 2024 17:52:30 +0100 Subject: [PATCH 31/53] Improved computation #136 --- CHANGELOG.md | 1 + .../Extensions/AnalysisWindow.cs | 10 ++++++++++ src/AngleSharp.Css/Dom/ICssProperty.cs | 7 +++++++ src/AngleSharp.Css/Dom/Internal/CssProperty.cs | 17 +++++++++++++++++ .../Dom/Internal/CssStyleDeclaration.cs | 15 +++++++++++++++ .../Extensions/CssOmExtensions.cs | 15 ++++++--------- .../Extensions/StyleCollectionExtensions.cs | 16 ++++++++-------- src/AngleSharp.Css/Values/Primitives/Angle.cs | 2 +- .../Values/Primitives/Constant.cs | 2 +- .../Values/Primitives/CounterDefinition.cs | 2 +- .../Values/Primitives/CounterValue.cs | 2 +- .../Values/Primitives/Fraction.cs | 2 +- .../Values/Primitives/Frequency.cs | 2 +- .../Values/Primitives/Identifier.cs | 2 +- src/AngleSharp.Css/Values/Primitives/Label.cs | 2 +- src/AngleSharp.Css/Values/Primitives/Length.cs | 2 +- .../Values/Primitives/LineNames.cs | 2 +- src/AngleSharp.Css/Values/Primitives/Point.cs | 2 +- src/AngleSharp.Css/Values/Primitives/Quote.cs | 2 +- src/AngleSharp.Css/Values/Primitives/Ratio.cs | 2 +- .../Values/Primitives/Resolution.cs | 2 +- src/AngleSharp.Css/Values/Primitives/Time.cs | 2 +- .../Values/Raws/CssInitialValue.cs | 2 +- src/AngleSharp.Css/Values/Raws/CssUnsetValue.cs | 2 +- 24 files changed, 81 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index efebf3e1..474a6935 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ Released on tbd. - Fixed style empty if `text-align` is `start` (#151) - Fixed computation of priority in CSS rules using multi selector - Fixed `GetInnerText` multi-line / text node behavior (#155) @Seyden +- 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 support for 8-digit hex color codes (#132) diff --git a/src/AngleSharp.Css.Tests/Extensions/AnalysisWindow.cs b/src/AngleSharp.Css.Tests/Extensions/AnalysisWindow.cs index cbf5b955..dd1269da 100644 --- a/src/AngleSharp.Css.Tests/Extensions/AnalysisWindow.cs +++ b/src/AngleSharp.Css.Tests/Extensions/AnalysisWindow.cs @@ -298,5 +298,15 @@ public async Task PriorityInMultiSelectorIsEvaluatedPerMatch() var style = sc.ComputeCascadedStyle(document.QuerySelector("h3")); Assert.AreEqual("rgba(0, 0, 255, 1)", style.GetColor()); } + + [Test] + public async Task ComputesAbsoluteValuesFromRelative_Issue136() + { + var sheet = ParseStyleSheet(@"p { font-size: 1.5em }"); + var document = await sheet.Context.OpenAsync(res => res.Content(@"

    This is only a test.

    ")); + var sc = new StyleCollection(new[] { sheet }, new DefaultRenderDevice()); + var style = sc.ComputeDeclarations(document.QuerySelector("span")); + Assert.AreEqual("24px", style.GetFontSize()); + } } } diff --git a/src/AngleSharp.Css/Dom/ICssProperty.cs b/src/AngleSharp.Css/Dom/ICssProperty.cs index 0a91b87f..bb1f7eaf 100644 --- a/src/AngleSharp.Css/Dom/ICssProperty.cs +++ b/src/AngleSharp.Css/Dom/ICssProperty.cs @@ -57,5 +57,12 @@ public interface ICssProperty : IStyleFormattable /// Gets if the property is a shorthand. ///
    Boolean IsShorthand { get; } + + /// + /// Creates a computed version of the property. + /// + /// The device to compute for. + /// The computed version of the property if uncomputed, otherwise the same. + ICssProperty Compute(IRenderDevice device); } } diff --git a/src/AngleSharp.Css/Dom/Internal/CssProperty.cs b/src/AngleSharp.Css/Dom/Internal/CssProperty.cs index 8af6a154..0b2d5a76 100644 --- a/src/AngleSharp.Css/Dom/Internal/CssProperty.cs +++ b/src/AngleSharp.Css/Dom/Internal/CssProperty.cs @@ -2,6 +2,7 @@ namespace AngleSharp.Css.Dom { using AngleSharp.Css; using AngleSharp.Css.Converters; + using AngleSharp.Css.Values; using AngleSharp.Text; using System; using System.IO; @@ -86,6 +87,22 @@ public Boolean IsImportant #endregion + #region Methods + + public ICssProperty Compute(IRenderDevice device) + { + if (_value is Length length && length.Type != Length.Unit.Px) + { + var px = length.ToPixel(device); + var value = new Length(px, Length.Unit.Px); + return new CssProperty(_name, _converter, _flags, value, _important); + } + + return this; + } + + #endregion + #region String Representation public void ToCss(TextWriter writer, IStyleFormatter formatter) => writer.Write(formatter.Declaration(CssUtilities.Escape(Name), Value, IsImportant)); diff --git a/src/AngleSharp.Css/Dom/Internal/CssStyleDeclaration.cs b/src/AngleSharp.Css/Dom/Internal/CssStyleDeclaration.cs index 776795d5..a1bfbe30 100644 --- a/src/AngleSharp.Css/Dom/Internal/CssStyleDeclaration.cs +++ b/src/AngleSharp.Css/Dom/Internal/CssStyleDeclaration.cs @@ -32,6 +32,11 @@ sealed class CssStyleDeclaration : ICssStyleDeclaration #region ctor + public CssStyleDeclaration() + : this(default(IBrowsingContext)) + { + } + public CssStyleDeclaration(IBrowsingContext context) { _declarations = new List(); @@ -301,6 +306,16 @@ public void SetProperty(String propertyName, String propertyValue, String priori } } + public void AddProperty(ICssProperty declaration) + { + _declarations.Add(declaration); + } + + public void RemoveProperty(ICssProperty declaration) + { + _declarations.Remove(declaration); + } + #endregion #region Internal Methods diff --git a/src/AngleSharp.Css/Extensions/CssOmExtensions.cs b/src/AngleSharp.Css/Extensions/CssOmExtensions.cs index 8ad0d928..71dc44b1 100644 --- a/src/AngleSharp.Css/Extensions/CssOmExtensions.cs +++ b/src/AngleSharp.Css/Extensions/CssOmExtensions.cs @@ -1,7 +1,6 @@ namespace AngleSharp.Css.Dom { using AngleSharp.Css.Parser; - using AngleSharp.Css.Values; using AngleSharp.Text; using System; using System.Linq; @@ -74,16 +73,14 @@ public static ICssValue GetValueOf(this ICssStyleRule rule, String propertyName) /// A new style declaration with the existing or computed values. public static ICssStyleDeclaration Compute(this ICssStyleDeclaration style, IRenderDevice device) { - //var prop = style.GetProperty("font-size"); + var computedStyle = new CssStyleDeclaration(); - //if (prop is not null && prop.RawValue is Length length) - //{ - // var px = length.ToPixel(device, RenderMode.Horizontal); - // var prio = prop.IsImportant ? CssKeywords.Important : null; - // style.SetProperty(prop.Name, $"{px}px", prio); - //} + foreach (var property in style) + { + computedStyle.AddProperty(property.Compute(device)); + } - return style; + return computedStyle; } } } diff --git a/src/AngleSharp.Css/Extensions/StyleCollectionExtensions.cs b/src/AngleSharp.Css/Extensions/StyleCollectionExtensions.cs index bfbfcef6..26453031 100644 --- a/src/AngleSharp.Css/Extensions/StyleCollectionExtensions.cs +++ b/src/AngleSharp.Css/Extensions/StyleCollectionExtensions.cs @@ -36,14 +36,14 @@ public static IStyleCollection GetStyleCollection(this IWindow window) /// Computes the declarations for the given element in the context of /// the specified styling rules. ///
    - /// The styles to use. + /// The styles to use. /// The element that is questioned. /// The optional pseudo selector to use. /// The style declaration containing all the declarations. - public static ICssStyleDeclaration ComputeDeclarations(this IEnumerable rules, IElement element, String pseudoSelector = null) + public static ICssStyleDeclaration ComputeDeclarations(this IStyleCollection styles, IElement element, String pseudoSelector = null) { var ctx = element.Owner?.Context; - var device = ctx?.GetService(); + var device = styles.Device; var computedStyle = new CssStyleDeclaration(ctx); var nodes = element.GetAncestors().OfType(); @@ -57,11 +57,11 @@ public static ICssStyleDeclaration ComputeDeclarations(this IEnumerable - /// The style rules to apply. + /// The style rules to apply. /// The element to compute the cascade for. /// The potential parent for the cascade. /// Returns the cascaded read-only style declaration. - public static ICssStyleDeclaration ComputeCascadedStyle(this IEnumerable styleCollection, IElement element, ICssStyleDeclaration parent = null) + public static ICssStyleDeclaration ComputeCascadedStyle(this IStyleCollection styles, IElement element, ICssStyleDeclaration parent = null) { var computedStyle = new CssStyleDeclaration(element.Owner?.Context); - var rules = styleCollection.SortBySpecificity(element); + var rules = styles.SortBySpecificity(element); foreach (var rule in rules) { diff --git a/src/AngleSharp.Css/Values/Primitives/Angle.cs b/src/AngleSharp.Css/Values/Primitives/Angle.cs index e2904896..9f13ad9d 100644 --- a/src/AngleSharp.Css/Values/Primitives/Angle.cs +++ b/src/AngleSharp.Css/Values/Primitives/Angle.cs @@ -6,7 +6,7 @@ namespace AngleSharp.Css.Values /// Represents an angle object. /// https://developer.mozilla.org/en-US/docs/Web/CSS/angle /// - public struct Angle : IEquatable, IComparable, ICssPrimitiveValue + public readonly struct Angle : IEquatable, IComparable, ICssPrimitiveValue { #region Basic angles diff --git a/src/AngleSharp.Css/Values/Primitives/Constant.cs b/src/AngleSharp.Css/Values/Primitives/Constant.cs index 9f58e7ad..6f324820 100644 --- a/src/AngleSharp.Css/Values/Primitives/Constant.cs +++ b/src/AngleSharp.Css/Values/Primitives/Constant.cs @@ -5,7 +5,7 @@ namespace AngleSharp.Css.Values /// /// Represents a selected CSS enum value. /// - public struct Constant : ICssPrimitiveValue, IEquatable> + public readonly struct Constant : ICssPrimitiveValue, IEquatable> { #region Fields diff --git a/src/AngleSharp.Css/Values/Primitives/CounterDefinition.cs b/src/AngleSharp.Css/Values/Primitives/CounterDefinition.cs index 2be72db7..46bfe052 100644 --- a/src/AngleSharp.Css/Values/Primitives/CounterDefinition.cs +++ b/src/AngleSharp.Css/Values/Primitives/CounterDefinition.cs @@ -6,7 +6,7 @@ namespace AngleSharp.Css.Values /// /// Represents a CSS counter. /// - public struct CounterDefinition : ICssPrimitiveValue, IEquatable + public readonly struct CounterDefinition : ICssPrimitiveValue, IEquatable { #region Fields diff --git a/src/AngleSharp.Css/Values/Primitives/CounterValue.cs b/src/AngleSharp.Css/Values/Primitives/CounterValue.cs index 7dd93338..afecc30f 100644 --- a/src/AngleSharp.Css/Values/Primitives/CounterValue.cs +++ b/src/AngleSharp.Css/Values/Primitives/CounterValue.cs @@ -6,7 +6,7 @@ namespace AngleSharp.Css.Values /// /// Sets a CSS counter. /// - public struct CounterValue : ICssPrimitiveValue, IEquatable + public readonly struct CounterValue : ICssPrimitiveValue, IEquatable { #region Fields diff --git a/src/AngleSharp.Css/Values/Primitives/Fraction.cs b/src/AngleSharp.Css/Values/Primitives/Fraction.cs index 456154eb..8f5c5f84 100644 --- a/src/AngleSharp.Css/Values/Primitives/Fraction.cs +++ b/src/AngleSharp.Css/Values/Primitives/Fraction.cs @@ -5,7 +5,7 @@ namespace AngleSharp.Css.Values /// /// Represents a fractional value. /// - public struct Fraction : IEquatable, IComparable, ICssPrimitiveValue + public readonly struct Fraction : IEquatable, IComparable, ICssPrimitiveValue { #region Fields diff --git a/src/AngleSharp.Css/Values/Primitives/Frequency.cs b/src/AngleSharp.Css/Values/Primitives/Frequency.cs index f75b2067..ee2dea58 100644 --- a/src/AngleSharp.Css/Values/Primitives/Frequency.cs +++ b/src/AngleSharp.Css/Values/Primitives/Frequency.cs @@ -5,7 +5,7 @@ namespace AngleSharp.Css.Values /// /// Represents a time value. /// - public struct Frequency : IEquatable, IComparable, ICssPrimitiveValue + public readonly struct Frequency : IEquatable, IComparable, ICssPrimitiveValue { #region Fields diff --git a/src/AngleSharp.Css/Values/Primitives/Identifier.cs b/src/AngleSharp.Css/Values/Primitives/Identifier.cs index 871e34fb..b7d31d0a 100644 --- a/src/AngleSharp.Css/Values/Primitives/Identifier.cs +++ b/src/AngleSharp.Css/Values/Primitives/Identifier.cs @@ -6,7 +6,7 @@ namespace AngleSharp.Css.Values /// /// Represents a CSS identifier value. /// - public struct Identifier : ICssPrimitiveValue, IEquatable + public readonly struct Identifier : ICssPrimitiveValue, IEquatable { #region Fields diff --git a/src/AngleSharp.Css/Values/Primitives/Label.cs b/src/AngleSharp.Css/Values/Primitives/Label.cs index 5359bc8a..01a5c6cd 100644 --- a/src/AngleSharp.Css/Values/Primitives/Label.cs +++ b/src/AngleSharp.Css/Values/Primitives/Label.cs @@ -6,7 +6,7 @@ namespace AngleSharp.Css.Values /// /// Represents a CSS label ("string") value. /// - public struct Label : ICssPrimitiveValue, IEquatable