AngleSharp by Florian Rappl

<PackageReference Include="AngleSharp" Version="0.8.7.1" />

 CssBuilder

sealed class CssBuilder
See http://dev.w3.org/csswg/css-syntax/#parsing for details.
using AngleSharp.Css; using AngleSharp.Css.Conditions; using AngleSharp.Css.MediaFeatures; using AngleSharp.Css.Values; using AngleSharp.Dom; using AngleSharp.Dom.Collections; using AngleSharp.Dom.Css; using System; using System.Collections.Generic; using System.Diagnostics; using System.Text; namespace AngleSharp.Parser.Css { [DebuggerStepThrough] internal sealed class CssBuilder { private readonly CssTokenizer _tokenizer; private readonly CssParser _parser; public CssBuilder(CssTokenizer tokenizer, CssParser parser) { _tokenizer = tokenizer; _parser = parser; } public CssRule CreateAtRule(CssToken token) { if (token.Data == RuleNames.Charset) return CreateCharset(token); if (token.Data == RuleNames.Page) return CreatePage(token); if (token.Data == RuleNames.Import) return CreateImport(token); if (token.Data == RuleNames.FontFace) return CreateFontFace(token); if (token.Data == RuleNames.Media) return CreateMedia(token); if (token.Data == RuleNames.Namespace) return CreateNamespace(token); if (token.Data == RuleNames.Supports) return CreateSupports(token); if (token.Data == RuleNames.Keyframes) return CreateKeyframes(token); if (token.Data == RuleNames.Document) return CreateDocument(token); return CreateUnknown(token); } public CssRule CreateRule(CssToken token) { switch (token.Type) { case CssTokenType.AtKeyword: return CreateAtRule(token); case CssTokenType.CurlyBracketOpen: _tokenizer.RaiseErrorOccurred(CssParseError.InvalidBlockStart, token.Position); _tokenizer.SkipUnknownRule(); return null; case CssTokenType.String: case CssTokenType.Url: case CssTokenType.RoundBracketClose: case CssTokenType.CurlyBracketClose: case CssTokenType.SquareBracketClose: _tokenizer.RaiseErrorOccurred(CssParseError.InvalidToken, token.Position); _tokenizer.SkipUnknownRule(); return null; default: return CreateStyle(token); } } public CssRule CreateCharset(CssToken current) { CssToken cssToken = _tokenizer.Get(); CssCharsetRule cssCharsetRule = new CssCharsetRule(_parser); if (cssToken.Type == CssTokenType.String) cssCharsetRule.CharacterSet = cssToken.Data; _tokenizer.JumpToNextSemicolon(); return cssCharsetRule; } public CssRule CreateDocument(CssToken current) { CssToken token = _tokenizer.Get(); CssDocumentRule cssDocumentRule = new CssDocumentRule(_parser); List<IDocumentFunction> collection = CreateFunctions(ref token); cssDocumentRule.Conditions.AddRange(collection); if (token.Type != CssTokenType.CurlyBracketOpen) return SkipDeclarations(token); FillRules(cssDocumentRule); return cssDocumentRule; } public CssRule CreateFontFace(CssToken current) { CssToken cssToken = _tokenizer.Get(); CssFontFaceRule cssFontFaceRule = new CssFontFaceRule(_parser); if (cssToken.Type != CssTokenType.CurlyBracketOpen) return SkipDeclarations(cssToken); FillFontFaceDeclarations(cssFontFaceRule); return cssFontFaceRule; } public CssRule CreateImport(CssToken current) { CssToken token = _tokenizer.Get(); CssImportRule cssImportRule = new CssImportRule(_parser); if (token.Is(CssTokenType.String, CssTokenType.Url)) { cssImportRule.Href = token.Data; token = _tokenizer.Get(); if (token.Type != CssTokenType.Semicolon) FillMediaList(cssImportRule.Media, ref token); } _tokenizer.JumpToNextSemicolon(); return cssImportRule; } public CssRule CreateKeyframes(CssToken current) { CssToken token = _tokenizer.Get(); CssKeyframesRule cssKeyframesRule = new CssKeyframesRule(_parser); cssKeyframesRule.Name = GetRuleName(ref token); if (token.Type != CssTokenType.CurlyBracketOpen) return SkipDeclarations(token); FillKeyframeRules(cssKeyframesRule); return cssKeyframesRule; } public CssRule CreateMedia(CssToken current) { CssToken token = _tokenizer.Get(); CssMediaRule cssMediaRule = new CssMediaRule(_parser); FillMediaList(cssMediaRule.Media, ref token); if (token.Type != CssTokenType.CurlyBracketOpen) return SkipDeclarations(token); FillRules(cssMediaRule); return cssMediaRule; } public CssRule CreateNamespace(CssToken current) { CssToken token = _tokenizer.Get(); CssNamespaceRule cssNamespaceRule = new CssNamespaceRule(_parser); cssNamespaceRule.Prefix = GetRuleName(ref token); if (token.Type == CssTokenType.Url) cssNamespaceRule.NamespaceUri = token.Data; _tokenizer.JumpToNextSemicolon(); return cssNamespaceRule; } public CssRule CreatePage(CssToken current) { CssToken token = _tokenizer.Get(); CssPageRule cssPageRule = new CssPageRule(_parser); cssPageRule.Selector = CreateSelector(ref token); if (token.Type != CssTokenType.CurlyBracketOpen) return SkipDeclarations(token); FillDeclarations(cssPageRule.Style); return cssPageRule; } public CssRule CreateStyle(CssToken current) { CssStyleRule cssStyleRule = new CssStyleRule(_parser); cssStyleRule.Selector = CreateSelector(ref current); FillDeclarations(cssStyleRule.Style); if (cssStyleRule.Selector == null) return null; return cssStyleRule; } public CssRule CreateSupports(CssToken current) { CssToken token = _tokenizer.Get(); CssSupportsRule cssSupportsRule = new CssSupportsRule(_parser); cssSupportsRule.Condition = CreateCondition(ref token); if (token.Type != CssTokenType.CurlyBracketOpen) return SkipDeclarations(token); FillRules(cssSupportsRule); return cssSupportsRule; } public CssRule CreateUnknown(CssToken current) { if (_parser.Options.IsIncludingUnknownRules) { CssUnknownRule cssUnknownRule = new CssUnknownRule(current.Data, _parser); _tokenizer.State = CssParseMode.Text; cssUnknownRule.Prelude = _tokenizer.Get().Data; _tokenizer.State = CssParseMode.Selector; StringBuilder stringBuilder = Pool.NewStringBuilder(); CssToken cssToken = _tokenizer.Get(); stringBuilder.Append(cssToken.ToValue()); if (cssToken.Type == CssTokenType.CurlyBracketOpen) { int num = 1; do { cssToken = _tokenizer.Get(); stringBuilder.Append(cssToken.ToValue()); switch (cssToken.Type) { case CssTokenType.CurlyBracketOpen: num++; break; case CssTokenType.CurlyBracketClose: num--; break; case CssTokenType.Eof: num = 0; break; } } while (num != 0); } cssUnknownRule.Content = stringBuilder.ToPool(); _tokenizer.State = CssParseMode.Data; return cssUnknownRule; } RaiseErrorOccurred(CssParseError.UnknownAtRule, current); _tokenizer.SkipUnknownRule(); return null; } public CssValue CreateValue(ref CssToken token) { bool important = false; return CreateValue(CssTokenType.CurlyBracketClose, ref token, out important); } public List<CssMedium> CreateMedia(ref CssToken token) { List<CssMedium> list = new List<CssMedium>(); while (token.Type != CssTokenType.Eof) { CssMedium cssMedium = CreateMedium(ref token); if (cssMedium == null || token.IsNot(CssTokenType.Comma, CssTokenType.Eof)) throw new DomException(DomError.Syntax); list.Add(cssMedium); token = _tokenizer.Get(); } return list; } public ICondition CreateCondition(ref CssToken token) { ICondition condition = ExtractCondition(ref token); if (condition != null) { if (token.Data.Equals(Keywords.And, StringComparison.OrdinalIgnoreCase)) { token = _tokenizer.Get(); List<ICondition> conditions = MultipleConditions(condition, Keywords.And, ref token); return new AndCondition(conditions); } if (token.Data.Equals(Keywords.Or, StringComparison.OrdinalIgnoreCase)) { token = _tokenizer.Get(); List<ICondition> conditions2 = MultipleConditions(condition, Keywords.Or, ref token); return new OrCondition(conditions2); } } return condition; } public CssKeyframeRule CreateKeyframeRule(CssToken token) { CssKeyframeRule cssKeyframeRule = new CssKeyframeRule(_parser); cssKeyframeRule.Key = CreateKeyframeSelector(ref token); if (cssKeyframeRule.Key == null) { _tokenizer.JumpToEndOfDeclaration(); return null; } FillDeclarations(cssKeyframeRule.Style); return cssKeyframeRule; } public KeyframeSelector CreateKeyframeSelector(ref CssToken token) { List<Percent> list = new List<Percent>(); while (token.Type != CssTokenType.Eof) { if (list.Count > 0) { if (token.Type == CssTokenType.CurlyBracketOpen) break; if (token.Type != CssTokenType.Comma) return null; token = _tokenizer.Get(); } if (token.Type == CssTokenType.Percentage) list.Add(new Percent(((CssUnitToken)token).Value)); else if (token.Type == CssTokenType.Ident && token.Data.Equals(Keywords.From)) { list.Add(Percent.Zero); } else { if (token.Type != CssTokenType.Ident || !token.Data.Equals(Keywords.To)) return null; list.Add(Percent.Hundred); } token = _tokenizer.Get(); } return new KeyframeSelector(list); } public List<IDocumentFunction> CreateFunctions(ref CssToken token) { List<IDocumentFunction> list = new List<IDocumentFunction>(); do { IDocumentFunction documentFunction = token.ToDocumentFunction(); if (documentFunction == null) break; list.Add(documentFunction); token = _tokenizer.Get(); } while (token.Type == CssTokenType.Comma); return list; } public void FillDeclarations(CssStyleDeclaration style) { CssToken token = _tokenizer.Get(); while (token.IsNot(CssTokenType.Eof, CssTokenType.CurlyBracketClose)) { CssProperty cssProperty = CreateDeclaration(ref token); if (cssProperty != null && cssProperty.HasValue) style.SetProperty(cssProperty); } } public CssProperty CreateDeclarationWith(Func<string, CssProperty> createProperty, ref CssToken token) { CssProperty cssProperty = null; if (token.Type == CssTokenType.Ident) { string data = token.Data; token = _tokenizer.Get(); if (token.Type != CssTokenType.Colon) RaiseErrorOccurred(CssParseError.ColonMissing, token); else { cssProperty = ((_parser.Options.IsIncludingUnknownDeclarations || _parser.Options.IsToleratingInvalidValues) ? new CssUnknownProperty(data) : createProperty(data)); if (cssProperty == null) RaiseErrorOccurred(CssParseError.UnknownDeclarationName, token); bool important = false; CssValue cssValue = CreateValue(CssTokenType.CurlyBracketClose, ref token, out important); if (cssValue == null) RaiseErrorOccurred(CssParseError.ValueMissing, token); else if (cssProperty != null && cssProperty.TrySetValue(cssValue)) { cssProperty.IsImportant = important; } } _tokenizer.JumpToEndOfDeclaration(); token = _tokenizer.Get(); } else if (token.Type != CssTokenType.Eof) { RaiseErrorOccurred(CssParseError.IdentExpected, token); _tokenizer.JumpToEndOfDeclaration(); token = _tokenizer.Get(); } if (token.Type == CssTokenType.Semicolon) token = _tokenizer.Get(); return cssProperty; } public CssProperty CreateDeclaration(ref CssToken token) { return CreateDeclarationWith(Factory.Properties.Create, ref token); } public CssMedium CreateMedium(ref CssToken token) { CssMedium cssMedium = new CssMedium(); if (token.Type == CssTokenType.Ident) { string data = token.Data; if (data.Equals(Keywords.Not, StringComparison.OrdinalIgnoreCase)) { cssMedium.IsInverse = true; token = _tokenizer.Get(); } else if (data.Equals(Keywords.Only, StringComparison.OrdinalIgnoreCase)) { cssMedium.IsExclusive = true; token = _tokenizer.Get(); } } if (token.Type == CssTokenType.Ident) { cssMedium.Type = token.Data; token = _tokenizer.Get(); if (token.Type != CssTokenType.Ident || string.Compare(token.Data, Keywords.And, StringComparison.OrdinalIgnoreCase) != 0) return cssMedium; token = _tokenizer.Get(); } do { if (token.Type != CssTokenType.RoundBracketOpen) return null; token = _tokenizer.Get(); bool flag = TrySetConstraint(cssMedium, ref token); if (token.Type != CssTokenType.RoundBracketClose) return null; token = _tokenizer.Get(); if (!flag) return null; if (token.Type != CssTokenType.Ident || string.Compare(token.Data, Keywords.And, StringComparison.OrdinalIgnoreCase) != 0) break; token = _tokenizer.Get(); } while (token.Type != CssTokenType.Eof); return cssMedium; } private ICondition ExtractCondition(ref CssToken token) { ICondition condition = null; if (token.Type == CssTokenType.RoundBracketOpen) { token = _tokenizer.Get(); condition = CreateCondition(ref token); if (condition != null) condition = new GroupCondition(condition); else if (token.Type == CssTokenType.Ident) { condition = DeclarationCondition(ref token); } if (token.Type == CssTokenType.RoundBracketClose) token = _tokenizer.Get(); } else if (token.Data.Equals(Keywords.Not, StringComparison.OrdinalIgnoreCase)) { token = _tokenizer.Get(); condition = ExtractCondition(ref token); if (condition != null) condition = new NotCondition(condition); } return condition; } private ICondition DeclarationCondition(ref CssToken token) { string data = token.Data; CssProperty cssProperty = Factory.Properties.Create(data); if (cssProperty == null) cssProperty = new CssUnknownProperty(data); token = _tokenizer.Get(); if (token.Type == CssTokenType.Colon) { bool important = false; CssValue cssValue = CreateValue(CssTokenType.RoundBracketClose, ref token, out important); cssProperty.IsImportant = important; if (cssValue != null) return new DeclarationCondition(cssProperty, cssValue); } return null; } private List<ICondition> MultipleConditions(ICondition condition, string connector, ref CssToken token) { List<ICondition> list = new List<ICondition>(); list.Add(condition); while (token.Type != CssTokenType.Eof) { condition = ExtractCondition(ref token); if (condition == null) break; list.Add(condition); if (!token.Data.Equals(connector, StringComparison.OrdinalIgnoreCase)) break; token = _tokenizer.Get(); } return list; } private void FillKeyframeRules(CssKeyframesRule parentRule) { CssToken token = _tokenizer.Get(); while (token.IsNot(CssTokenType.Eof, CssTokenType.CurlyBracketClose)) { CssKeyframeRule cssKeyframeRule = CreateKeyframeRule(token); if (cssKeyframeRule != null) parentRule.Rules.Add(cssKeyframeRule, parentRule.Owner, parentRule); token = _tokenizer.Get(); } } private void FillFontFaceDeclarations(CssFontFaceRule rule) { CssToken token = _tokenizer.Get(); while (token.IsNot(CssTokenType.Eof, CssTokenType.CurlyBracketClose)) { CssProperty cssProperty = CreateDeclarationWith(CreateProperty, ref token); if (cssProperty != null && cssProperty.HasValue) rule.SetProperty(cssProperty); } } private CssProperty CreateProperty(string propertyName) { if (propertyName.Equals(PropertyNames.Src, StringComparison.OrdinalIgnoreCase)) return new CssSrcProperty(); if (propertyName.Equals(PropertyNames.UnicodeRange, StringComparison.OrdinalIgnoreCase)) return new CssUnicodeRangeProperty(); return Factory.Properties.Create(propertyName); } private CssRule SkipDeclarations(CssToken token) { RaiseErrorOccurred(CssParseError.InvalidToken, token); _tokenizer.SkipUnknownRule(); return null; } private void FillRules(CssGroupingRule group) { CssToken token = _tokenizer.Get(); while (token.IsNot(CssTokenType.Eof, CssTokenType.CurlyBracketClose)) { CssRule rule = CreateRule(token); group.AddRule(rule); token = _tokenizer.Get(); } } private ISelector CreateSelector(ref CssToken token) { CssSelectorConstructor cssSelectorConstructor = Pool.NewSelectorConstructor(); _tokenizer.State = CssParseMode.Selector; CssToken token2 = token; while (token.IsNot(CssTokenType.Eof, CssTokenType.CurlyBracketOpen, CssTokenType.CurlyBracketClose)) { cssSelectorConstructor.Apply(token); token = _tokenizer.Get(); } if (!cssSelectorConstructor.IsValid) RaiseErrorOccurred(CssParseError.InvalidSelector, token2); _tokenizer.State = CssParseMode.Data; return cssSelectorConstructor.ToPool(); } private CssValue CreateValue(CssTokenType closing, ref CssToken token, out bool important) { CssValueBuilder cssValueBuilder = Pool.NewValueBuilder(); _tokenizer.State = CssParseMode.Value; token = _tokenizer.Get(); while (token.Type != CssTokenType.Eof && !token.Is(CssTokenType.Semicolon, closing)) { cssValueBuilder.Apply(token); token = _tokenizer.Get(); } important = cssValueBuilder.IsImportant; _tokenizer.State = CssParseMode.Data; return cssValueBuilder.ToPool(); } private string GetRuleName(ref CssToken token) { string result = string.Empty; if (token.Type == CssTokenType.Ident) { result = token.Data; token = _tokenizer.Get(); } return result; } private void FillMediaList(MediaList list, ref CssToken token) { if (token.Type != CssTokenType.CurlyBracketOpen) { while (token.Type != CssTokenType.Eof) { CssMedium cssMedium = CreateMedium(ref token); if (cssMedium != null) list.Add(cssMedium); if (token.Type != CssTokenType.Comma) break; token = _tokenizer.Get(); } if (token.Type != CssTokenType.CurlyBracketOpen) { while (token.Type != CssTokenType.Eof && token.Type != CssTokenType.Semicolon) { token = _tokenizer.Get(); if (token.Type == CssTokenType.CurlyBracketOpen) break; } list.Clear(); } if (list.Length == 0) list.Add(new CssMedium { IsInverse = true, Type = Keywords.All }); } } private void RaiseErrorOccurred(CssParseError code, CssToken token) { _tokenizer.RaiseErrorOccurred(code, token.Position); } private bool TrySetConstraint(CssMedium medium, ref CssToken token) { if (token.Type != CssTokenType.Ident) { _tokenizer.JumpToClosedArguments(); token = _tokenizer.Get(); return false; } CssValueBuilder cssValueBuilder = Pool.NewValueBuilder(); string data = token.Data; token = _tokenizer.Get(); if (token.Type == CssTokenType.Colon) { _tokenizer.State = CssParseMode.Value; token = _tokenizer.Get(); while ((token.Type != CssTokenType.RoundBracketClose || !cssValueBuilder.IsReady) && token.Type != CssTokenType.Eof) { cssValueBuilder.Apply(token); token = _tokenizer.Get(); } _tokenizer.State = CssParseMode.Data; CssValue value = cssValueBuilder.ToPool(); MediaFeature mediaFeature = _parser.Options.IsToleratingInvalidConstraints ? new UnknownMediaFeature(data) : Factory.MediaFeatures.Create(data); if (mediaFeature == null || !mediaFeature.TrySetValue(value)) return false; medium.AddConstraint(mediaFeature); return true; } return token.Type != CssTokenType.Eof; } } }