diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7dd9e083..b4f81021 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,9 @@
+# 1.0.0
+
+Released on Sunday, July 26 2026.
+
+- First stable version
+
# 0.18.0
Released on Saturday, July 25 2026.
diff --git a/docs/README.md b/docs/README.md
index d179b648..5b458978 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -17,8 +17,8 @@ We have more detailed information regarding the following subjects:
- [API Documentation](tutorials/01-API.md)
- [Examples](tutorials/02-Examples.md)
-- [FAQ](tutorials/03-Questions.md)
-- [Render Tree Examples](tutorials/04-Render-Tree.md)
+- [Render Tree Examples](tutorials/03-Render-Tree.md)
+- [FAQ](tutorials/04-Questions.md)
## Recommended Reading Order
@@ -29,9 +29,9 @@ If you are new to AngleSharp.Css, this order gives a practical ramp-up:
3. [API Documentation](tutorials/01-API.md) for deeper understanding of the CSSOM model.
4. [CSSOM](general/03-CSSOM.md) and [Core Interfaces](general/04-Core-Interfaces.md) once you need a deeper model understanding.
5. [Supported At-Rules](general/07-Supported-At-Rules.md) and [Supported Declarations](general/08-Supported-Declarations.md) when checking implementation coverage and constraints.
-6. [Value Model](general/02-Values.md) and [Render Tree Examples](tutorials/04-Render-Tree.md) for style computation workflows.
+6. [Value Model](general/02-Values.md) and [Render Tree Examples](tutorials/03-Render-Tree.md) for style computation workflows.
7. [Extensibility](general/05-Extensibility.md) and [Provided Services](general/06-Provided-Services.md) for custom integrations.
-8. [FAQ](tutorials/03-Questions.md) for common pitfalls and output customization.
+8. [FAQ](tutorials/04-Questions.md) for common pitfalls and output customization.
## What You Can Do With AngleSharp.Css
diff --git a/docs/general/07-Supported-At-Rules.md b/docs/general/07-Supported-At-Rules.md
index 49248c2d..2157d3a3 100644
--- a/docs/general/07-Supported-At-Rules.md
+++ b/docs/general/07-Supported-At-Rules.md
@@ -1,3 +1,7 @@
+---
+title: "Supported Rules"
+section: "AngleSharp.Css"
+---
# Supported At-Rules
This page documents the at-rules recognized by the AngleSharp.Css parser (`CssBuilder.AtRuleMap`) and the corresponding CSSOM rule implementations.
diff --git a/docs/general/08-Supported-Declarations.md b/docs/general/08-Supported-Declarations.md
index 69f65260..59e14c02 100644
--- a/docs/general/08-Supported-Declarations.md
+++ b/docs/general/08-Supported-Declarations.md
@@ -1,3 +1,7 @@
+---
+title: "Supported Declarations"
+section: "AngleSharp.Css"
+---
# Supported Declarations
This page documents declaration/property support in AngleSharp.Css based on the registration in `DefaultDeclarationFactory` and declaration classes in `src/AngleSharp.Css/Declarations`.
diff --git a/docs/tutorials/02-Examples.md b/docs/tutorials/02-Examples.md
index 6c1f6905..5edffa79 100644
--- a/docs/tutorials/02-Examples.md
+++ b/docs/tutorials/02-Examples.md
@@ -200,5 +200,5 @@ This preserves comments, but not their exact original positions in every case. D
## 11. Where To Go Next
- Read [API Documentation](01-API.md) for deeper CSSOM details.
-- Read [Render Tree Examples](04-Render-Tree.md) for style-aware tree traversal and resource download workflows.
-- Read [FAQ](03-Questions.md) for serializer behavior and common edge cases.
+- Read [Render Tree Examples](03-Render-Tree.md) for style-aware tree traversal and resource download workflows.
+- Read [FAQ](04-Questions.md) for serializer behavior and common edge cases.
diff --git a/docs/tutorials/04-Render-Tree.md b/docs/tutorials/03-Render-Tree.md
similarity index 100%
rename from docs/tutorials/04-Render-Tree.md
rename to docs/tutorials/03-Render-Tree.md
diff --git a/docs/tutorials/03-Questions.md b/docs/tutorials/04-Questions.md
similarity index 100%
rename from docs/tutorials/03-Questions.md
rename to docs/tutorials/04-Questions.md
diff --git a/src/AngleSharp.Css.Docs/package.json b/src/AngleSharp.Css.Docs/package.json
index 27de660b..2ba7b5bf 100644
--- a/src/AngleSharp.Css.Docs/package.json
+++ b/src/AngleSharp.Css.Docs/package.json
@@ -1,6 +1,6 @@
{
"name": "@anglesharp/css",
- "version": "0.18.0",
+ "version": "1.0.0",
"preview": true,
"description": "The doclet for the AngleSharp.Css documentation.",
"keywords": [
diff --git a/src/AngleSharp.Css/Parser/CssBuilder.cs b/src/AngleSharp.Css/Parser/CssBuilder.cs
index 050ebff2..344369cd 100644
--- a/src/AngleSharp.Css/Parser/CssBuilder.cs
+++ b/src/AngleSharp.Css/Parser/CssBuilder.cs
@@ -887,34 +887,13 @@ private void CollectTrivia(ICssStyleSheet owner, ref CssToken token)
{
if (token.Type == CssTokenType.Comment)
{
- var comment = NormalizeCommentData(token.Data);
- _commentRuleTarget?.Add(new CssCommentRule(owner, comment));
+ _commentRuleTarget?.Add(new CssCommentRule(owner, token.Data));
}
token = _tokenizer.Get();
}
}
- private static String NormalizeCommentData(String comment)
- {
- if (!String.IsNullOrEmpty(comment) && comment.StartsWith("/*", StringComparison.Ordinal) && comment.EndsWith("*/", StringComparison.Ordinal))
- {
- return comment.Substring(2, comment.Length - 4);
- }
-
- if (!String.IsNullOrEmpty(comment) && comment.StartsWith("/*", StringComparison.Ordinal))
- {
- return comment.Substring(2);
- }
-
- if (!String.IsNullOrEmpty(comment) && comment.EndsWith("*/", StringComparison.Ordinal))
- {
- return comment.Substring(0, comment.Length - 2);
- }
-
- return comment;
- }
-
private void SkipDeclarations(CssToken token)
{
RaiseErrorOccurred(CssParseError.InvalidToken, token.Position);
@@ -932,9 +911,25 @@ private String CreateValue(ref CssToken token, out Boolean important)
{
var keyword = CssKeywords.BangImportant;
var value = _tokenizer.ContentFrom(token.Position.Position);
- important = value.EndsWith(keyword, StringComparison.OrdinalIgnoreCase);
+ var keywordStart = value.Length - keyword.Length;
+ important = keywordStart >= 0 &&
+ value[keywordStart] == Symbols.ExclamationMark &&
+ String.Compare(value, keywordStart, keyword, 0, keyword.Length, StringComparison.OrdinalIgnoreCase) == 0;
token = NextToken();
- return important ? value.Substring(0, value.Length - keyword.Length).Trim() : value;
+
+ if (!important)
+ {
+ return value;
+ }
+
+ var end = keywordStart;
+
+ while (end > 0 && value[end - 1].IsSpaceCharacter())
+ {
+ end--;
+ }
+
+ return value.Substring(0, end);
}
private String GetArgument(ref CssToken token)
diff --git a/src/AngleSharp.Css/Parser/CssTokenExtensions.cs b/src/AngleSharp.Css/Parser/CssTokenExtensions.cs
index 716f4e71..cfae152d 100644
--- a/src/AngleSharp.Css/Parser/CssTokenExtensions.cs
+++ b/src/AngleSharp.Css/Parser/CssTokenExtensions.cs
@@ -9,7 +9,7 @@ namespace AngleSharp.Css.Parser
///
static class CssTokenExtensions
{
- public static Boolean IsPotentiallyNested(this CssToken token)
+ public static Boolean IsPotentiallyNested(this in CssToken token)
{
if (token.Is(CssTokenType.Hash, CssTokenType.Colon, CssTokenType.SquareBracketOpen))
{
@@ -35,7 +35,7 @@ public static Boolean IsPotentiallyNested(this CssToken token)
/// The first type to match.
/// The alternative match for the token.
/// Result of the examination.
- public static Boolean Is(this CssToken token, CssTokenType a, CssTokenType b)
+ public static Boolean Is(this in CssToken token, CssTokenType a, CssTokenType b)
{
var type = token.Type;
return type == a || type == b;
@@ -49,7 +49,7 @@ public static Boolean Is(this CssToken token, CssTokenType a, CssTokenType b)
/// 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)
+ public static Boolean Is(this in CssToken token, CssTokenType a, CssTokenType b, CssTokenType c)
{
var type = token.Type;
return type == a || type == b || type == c;
@@ -68,7 +68,7 @@ public static Boolean Is(this CssToken token, CssTokenType a, CssTokenType b, Cs
/// 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)
+ public static Boolean Is(this in 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;
@@ -82,7 +82,7 @@ public static Boolean Is(this CssToken token, CssTokenType a, CssTokenType b, Cs
/// The first type to unmatch.
/// The alternative unmatch for the token.
/// Result of the examination.
- public static Boolean IsNot(this CssToken token, CssTokenType a, CssTokenType b)
+ public static Boolean IsNot(this in CssToken token, CssTokenType a, CssTokenType b)
{
var type = token.Type;
return type != a && type != b;
@@ -97,7 +97,7 @@ public static Boolean IsNot(this CssToken token, CssTokenType a, CssTokenType b)
/// The alternative unmatch for the token.
/// The final unmatch for the token.
/// Result of the examination.
- public static Boolean IsNot(this CssToken token, CssTokenType a, CssTokenType b, CssTokenType c)
+ public static Boolean IsNot(this in CssToken token, CssTokenType a, CssTokenType b, CssTokenType c)
{
var type = token.Type;
return type != a && type != b && type != c;
diff --git a/src/AngleSharp.Css/Parser/CssTokenizer.cs b/src/AngleSharp.Css/Parser/CssTokenizer.cs
index 649e0721..47cd672c 100644
--- a/src/AngleSharp.Css/Parser/CssTokenizer.cs
+++ b/src/AngleSharp.Css/Parser/CssTokenizer.cs
@@ -52,7 +52,6 @@ public CssTokenizer(TextSource source)
public CssToken Get()
{
var current = GetNext();
- _position = GetCurrentPosition();
return Data(current);
}
@@ -70,27 +69,37 @@ public String ContentFrom(Int32 position)
while (current != Symbols.EndOfFile)
{
- if (current is Symbols.DoubleQuote or Symbols.SingleQuote && previous != Symbols.ReverseSolidus)
+ if (current == Symbols.Semicolon || current == Symbols.CurlyBracketOpen || current == Symbols.CurlyBracketClose)
+ {
+ break;
+ }
+
+ if ((current == Symbols.DoubleQuote || current == Symbols.SingleQuote) && previous != Symbols.ReverseSolidus)
{
trailingWhitespace = 0;
var quote = current;
sb.Append(current);
+ previous = current;
current = GetNext();
while (current != Symbols.EndOfFile && current != quote)
{
+ sb.Append(current);
+
if (current == Symbols.ReverseSolidus)
{
- sb.Append(current);
+ previous = current;
current = GetNext();
if (current == Symbols.EndOfFile)
{
break;
}
+
+ sb.Append(current);
}
- sb.Append(current);
+ previous = current;
current = GetNext();
}
@@ -100,27 +109,23 @@ public String ContentFrom(Int32 position)
previous = current;
current = GetNext();
}
+
+ continue;
}
- else if (current is Symbols.Semicolon or Symbols.CurlyBracketOpen or Symbols.CurlyBracketClose)
+
+ sb.Append(current);
+
+ if (current == Symbols.Space || current == Symbols.Tab || current == Symbols.LineFeed || current == Symbols.CarriageReturn || current == Symbols.FormFeed)
{
- break;
+ trailingWhitespace++;
}
else
{
- sb.Append(current);
-
- if (current.IsSpaceCharacter())
- {
- trailingWhitespace++;
- }
- else
- {
- trailingWhitespace = 0;
- }
-
- previous = current;
- current = GetNext();
+ trailingWhitespace = 0;
}
+
+ previous = current;
+ current = GetNext();
}
if (trailingWhitespace > 0)
@@ -603,24 +608,23 @@ private CssToken HashRest()
private CssToken Comment()
{
var current = GetNext();
- StringBuffer.Append(Symbols.Solidus).Append(Symbols.Asterisk);
while (current != Symbols.EndOfFile)
{
- StringBuffer.Append(current);
-
if (current == Symbols.Asterisk)
{
current = GetNext();
- StringBuffer.Append(current);
if (current == Symbols.Solidus)
{
return NewComment(FlushBuffer());
}
+
+ StringBuffer.Append(Symbols.Asterisk).Append(current);
}
else
{
+ StringBuffer.Append(current);
current = GetNext();
}
}
diff --git a/src/AngleSharp.Css/Parser/Micro/IdentParser.cs b/src/AngleSharp.Css/Parser/Micro/IdentParser.cs
index 49799a07..53f458de 100644
--- a/src/AngleSharp.Css/Parser/Micro/IdentParser.cs
+++ b/src/AngleSharp.Css/Parser/Micro/IdentParser.cs
@@ -102,13 +102,25 @@ public static ICssValue ParseConstant(this StringSource source, IDictionary
public static Boolean IsFunction(this StringSource source, String name)
{
+ var pos = source.Index;
var rest = source.Content.Length - source.Index;
if (rest >= name.Length + 2)
{
+ var content = source.Content;
+ var hasEscape = content.IndexOf(Symbols.ReverseSolidus, pos, name.Length) >= 0;
+
+ if (!hasEscape &&
+ content[pos + name.Length] == Symbols.RoundBracketOpen &&
+ String.Compare(content, pos, name, 0, name.Length, StringComparison.OrdinalIgnoreCase) == 0)
+ {
+ source.NextTo(pos + name.Length + 1);
+ source.SkipSpacesAndComments();
+ return true;
+ }
+
var length = 0;
var current = source.Current;
- var pos = source.Index;
while (length < name.Length)
{
@@ -122,7 +134,7 @@ public static Boolean IsFunction(this StringSource source, String name)
current = next[0];
}
- if (Char.ToLowerInvariant(current) != Char.ToLowerInvariant(name[length]))
+ if (!EqualsIgnoreCase(current, name[length]))
break;
length++;
@@ -245,18 +257,38 @@ public static String ParseLiteral(this StringSource source)
public static String ParseAnimatableIdent(this StringSource source)
{
var pos = source.Index;
- var test = source.ParseNormalizedIdent();
+ var test = source.ParseIdent();
//TODO Replace Animatables with call to DeclarationFactory - get from flags
if (test != null && (test.Isi(CssKeywords.All) || Animatables.Contains(test)))
{
- return test;
+ return test.ToLowerFast();
}
source.BackTo(pos);
return null;
}
+ private static Boolean EqualsIgnoreCase(Char a, Char b)
+ {
+ if (a == b)
+ {
+ return true;
+ }
+
+ if (a >= 'A' && a <= 'Z')
+ {
+ a = (Char)(a + 32);
+ }
+
+ if (b >= 'A' && b <= 'Z')
+ {
+ b = (Char)(b + 32);
+ }
+
+ return a == b;
+ }
+
private static String Start(StringSource source, Char current, StringBuilder buffer)
{
if (current == Symbols.Minus)
diff --git a/src/AngleSharp.Css/Parser/Micro/NumberParser.cs b/src/AngleSharp.Css/Parser/Micro/NumberParser.cs
index b3ceafa4..ceed84d1 100644
--- a/src/AngleSharp.Css/Parser/Micro/NumberParser.cs
+++ b/src/AngleSharp.Css/Parser/Micro/NumberParser.cs
@@ -171,7 +171,17 @@ public static class NumberParser
}
var negative = value.StartsWith("-");
- var allNumbers = (negative ? value.Substring(1) : value).All(m => m.IsDigit());
+ var allNumbers = true;
+ var start = negative ? 1 : 0;
+
+ for (var i = start; i < value.Length; i++)
+ {
+ if (!value[i].IsDigit())
+ {
+ allNumbers = false;
+ break;
+ }
+ }
if (allNumbers)
{
diff --git a/src/AngleSharp.Css/Parser/StringSourceExtensions.cs b/src/AngleSharp.Css/Parser/StringSourceExtensions.cs
index 48ff2ff6..b4463176 100644
--- a/src/AngleSharp.Css/Parser/StringSourceExtensions.cs
+++ b/src/AngleSharp.Css/Parser/StringSourceExtensions.cs
@@ -77,9 +77,9 @@ public static String TakeUntilClosed(this StringSource source)
}
var end = source.Index;
- source.BackTo(lastNonWhitespace);
+ source.Back(source.Index - lastNonWhitespace);
var content = source.Substring(start);
- source.NextTo(end);
+ source.Next(end - source.Index);
return content;
}
@@ -127,16 +127,7 @@ public static Char SkipCurrentAndSpaces(this StringSource source)
///
public static Char BackTo(this StringSource source, Int32 index)
{
- var diff = source.Index - index;
- var current = Symbols.Null;
-
- while (diff > 0)
- {
- current = source.Back();
- diff--;
- }
-
- return current;
+ return source.Back(source.Index - index);
}
///
@@ -144,16 +135,7 @@ public static Char BackTo(this StringSource source, Int32 index)
///
public static Char NextTo(this StringSource source, Int32 index)
{
- var diff = index - source.Index;
- var current = Symbols.Null;
-
- while (diff > 0)
- {
- current = source.Next();
- diff--;
- }
-
- return current;
+ return source.Next(index - source.Index);
}
}
}
diff --git a/src/AngleSharp.Css/Parser/Tokens/CssCommentToken.cs b/src/AngleSharp.Css/Parser/Tokens/CssCommentToken.cs
deleted file mode 100644
index d21ffd66..00000000
--- a/src/AngleSharp.Css/Parser/Tokens/CssCommentToken.cs
+++ /dev/null
@@ -1,32 +0,0 @@
-namespace AngleSharp.Css.Parser.Tokens
-{
- using System;
-
- ///
- /// Represents a CSS comment token.
- ///
- sealed class CssCommentToken : CssToken
- {
- #region Fields
-
- private readonly Boolean _bad;
-
- #endregion
-
- #region ctor
-
- public CssCommentToken(String data, Boolean bad)
- : base(CssTokenType.Comment, data)
- {
- _bad = bad;
- }
-
- #endregion
-
- #region Properties
-
- public Boolean IsBad => _bad;
-
- #endregion
- }
-}
diff --git a/src/AngleSharp.Css/Parser/Tokens/CssStringToken.cs b/src/AngleSharp.Css/Parser/Tokens/CssStringToken.cs
deleted file mode 100644
index 348c90f6..00000000
--- a/src/AngleSharp.Css/Parser/Tokens/CssStringToken.cs
+++ /dev/null
@@ -1,32 +0,0 @@
-namespace AngleSharp.Css.Parser.Tokens
-{
- using System;
-
- ///
- /// Represents a CSS string token.
- ///
- sealed class CssStringToken : CssToken
- {
- #region Fields
-
- private readonly Boolean _bad;
-
- #endregion
-
- #region ctor
-
- public CssStringToken(String data, Boolean bad = false)
- : base(CssTokenType.String, data)
- {
- _bad = bad;
- }
-
- #endregion
-
- #region Properties
-
- public Boolean IsBad => _bad;
-
- #endregion
- }
-}
diff --git a/src/AngleSharp.Css/Parser/Tokens/CssToken.cs b/src/AngleSharp.Css/Parser/Tokens/CssToken.cs
index 7de428fa..893fe3ae 100644
--- a/src/AngleSharp.Css/Parser/Tokens/CssToken.cs
+++ b/src/AngleSharp.Css/Parser/Tokens/CssToken.cs
@@ -6,12 +6,13 @@ namespace AngleSharp.Css.Parser.Tokens
///
/// The base class token for the CSS parser.
///
- class CssToken
+ struct CssToken
{
#region Fields
private readonly CssTokenType _type;
private readonly String _data;
+ private TextPosition _position;
#endregion
@@ -33,8 +34,8 @@ public CssToken(CssTokenType type, String data)
public TextPosition Position
{
- get;
- set;
+ get => _position;
+ set => _position = value;
}
#endregion
diff --git a/src/AngleSharp.Css/Parser/Tokens/CssUrlToken.cs b/src/AngleSharp.Css/Parser/Tokens/CssUrlToken.cs
deleted file mode 100644
index bd1bfa09..00000000
--- a/src/AngleSharp.Css/Parser/Tokens/CssUrlToken.cs
+++ /dev/null
@@ -1,32 +0,0 @@
-namespace AngleSharp.Css.Parser.Tokens
-{
- using System;
-
- ///
- /// Represents a CSS URL token.
- ///
- sealed class CssUrlToken : CssToken
- {
- #region Fields
-
- private readonly Boolean _bad;
-
- #endregion
-
- #region ctor
-
- public CssUrlToken(String data, Boolean bad = false)
- : base(CssTokenType.Url, data)
- {
- _bad = bad;
- }
-
- #endregion
-
- #region Properties
-
- public Boolean IsBad => _bad;
-
- #endregion
- }
-}
diff --git a/src/Directory.Build.props b/src/Directory.Build.props
index 2443fb9b..929f1545 100644
--- a/src/Directory.Build.props
+++ b/src/Directory.Build.props
@@ -2,7 +2,7 @@
Extends the CSSOM from the core AngleSharp library.
AngleSharp.Css
- 0.18.0
+ 1.0.0
enable
latest
true