AngleSharp by AngleSharp

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

 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 AngleSharp.Extensions; 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; private readonly Stack<CssNode> _nodes; public CssNode Container { get { if (_nodes.Count <= 0) return null; return _nodes.Peek(); } } public CssBuilder(CssTokenizer tokenizer, CssParser parser) { _tokenizer = tokenizer; _parser = parser; _nodes = new Stack<CssNode>(); if (parser.Options.IsStoringTrivia) _nodes.Push(new CssNode()); } public CssRule CreateAtRule(CssToken token) { if (token.Data.Is(RuleNames.Media)) return CreateMedia(token); if (token.Data.Is(RuleNames.FontFace)) return CreateFontFace(token); if (token.Data.Is(RuleNames.Keyframes)) return CreateKeyframes(token); if (token.Data.Is(RuleNames.Import)) return CreateImport(token); if (token.Data.Is(RuleNames.Charset)) return CreateCharset(token); if (token.Data.Is(RuleNames.Namespace)) return CreateNamespace(token); if (token.Data.Is(RuleNames.Page)) return CreatePage(token); if (token.Data.Is(RuleNames.Supports)) return CreateSupports(token); if (token.Data.Is(RuleNames.ViewPort)) return CreateViewport(token); if (token.Data.Is(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: RaiseErrorOccurred(CssParseError.InvalidBlockStart, token); SkipRule(token); return null; case CssTokenType.String: case CssTokenType.Url: case CssTokenType.RoundBracketClose: case CssTokenType.CurlyBracketClose: case CssTokenType.SquareBracketClose: RaiseErrorOccurred(CssParseError.InvalidToken, token); SkipRule(token); return null; default: return CreateStyle(token); } } public CssRule CreateCharset(CssToken current) { CssCharsetRule cssCharsetRule = new CssCharsetRule(_parser); CssToken token = NextToken(); CollectTrivia(ref token); if (token.Type == CssTokenType.String) cssCharsetRule.CharacterSet = token.Data; JumpToEnd(token); return cssCharsetRule; } public CssRule CreateDocument(CssToken current) { CssDocumentRule cssDocumentRule = new CssDocumentRule(_parser); CssToken token = NextToken(); CollectTrivia(ref token); FillFunctions(cssDocumentRule.Conditions, ref token); CollectTrivia(ref token); if (token.Type != CssTokenType.CurlyBracketOpen) return SkipDeclarations(token); FillRules(cssDocumentRule); return cssDocumentRule; } public CssRule CreateViewport(CssToken current) { CssViewportRule cssViewportRule = new CssViewportRule(_parser); CssToken token = NextToken(); CollectTrivia(ref token); if (token.Type != CssTokenType.CurlyBracketOpen) return SkipDeclarations(token); FillDeclarations(cssViewportRule, Factory.Properties.CreateViewport); return cssViewportRule; } public CssRule CreateFontFace(CssToken current) { CssFontFaceRule cssFontFaceRule = new CssFontFaceRule(_parser); CssToken token = NextToken(); CollectTrivia(ref token); if (token.Type != CssTokenType.CurlyBracketOpen) return SkipDeclarations(token); FillDeclarations(cssFontFaceRule, Factory.Properties.CreateFont); return cssFontFaceRule; } public CssRule CreateImport(CssToken current) { CssImportRule cssImportRule = new CssImportRule(_parser); CssToken token = NextToken(); CollectTrivia(ref token); if (token.Is(CssTokenType.String, CssTokenType.Url)) { cssImportRule.Href = token.Data; token = NextToken(); CollectTrivia(ref token); FillMediaList(cssImportRule.Media, CssTokenType.Semicolon, ref token); } CollectTrivia(ref token); JumpToEnd(token); return cssImportRule; } public CssRule CreateKeyframes(CssToken current) { CssKeyframesRule cssKeyframesRule = new CssKeyframesRule(_parser); CssToken token = NextToken(); CollectTrivia(ref token); cssKeyframesRule.Name = GetRuleName(ref token); CollectTrivia(ref token); if (token.Type != CssTokenType.CurlyBracketOpen) return SkipDeclarations(token); FillKeyframeRules(cssKeyframesRule); return cssKeyframesRule; } public CssRule CreateMedia(CssToken current) { CssMediaRule cssMediaRule = new CssMediaRule(_parser); CssToken token = NextToken(); CollectTrivia(ref token); FillMediaList(cssMediaRule.Media, CssTokenType.CurlyBracketOpen, ref token); CollectTrivia(ref token); if (token.Type != CssTokenType.CurlyBracketOpen) { while (token.Type != CssTokenType.Eof) { if (token.Type == CssTokenType.Semicolon) return null; if (token.Type == CssTokenType.CurlyBracketOpen) break; token = NextToken(); } } FillRules(cssMediaRule); return cssMediaRule; } public CssRule CreateNamespace(CssToken current) { CssNamespaceRule cssNamespaceRule = new CssNamespaceRule(_parser); CssToken token = NextToken(); CollectTrivia(ref token); cssNamespaceRule.Prefix = GetRuleName(ref token); CollectTrivia(ref token); if (token.Type == CssTokenType.Url) cssNamespaceRule.NamespaceUri = token.Data; JumpToEnd(token); return cssNamespaceRule; } public CssRule CreatePage(CssToken current) { CssPageRule cssPageRule = new CssPageRule(_parser); CssToken token = NextToken(); CollectTrivia(ref token); cssPageRule.Selector = CreateSelector(ref token); CollectTrivia(ref token); if (token.Type != CssTokenType.CurlyBracketOpen) return SkipDeclarations(token); FillDeclarations(cssPageRule.Style); return cssPageRule; } public CssRule CreateSupports(CssToken current) { CssSupportsRule cssSupportsRule = new CssSupportsRule(_parser); CssToken token = NextToken(); CollectTrivia(ref token); cssSupportsRule.Condition = AggregateCondition(ref token); CollectTrivia(ref token); if (token.Type != CssTokenType.CurlyBracketOpen) return SkipDeclarations(token); FillRules(cssSupportsRule); return cssSupportsRule; } public CssRule CreateStyle(CssToken current) { CssStyleRule cssStyleRule = new CssStyleRule(_parser); CollectTrivia(ref current); cssStyleRule.Selector = CreateSelector(ref current); FillDeclarations(cssStyleRule.Style); if (cssStyleRule.Selector == null) return null; return cssStyleRule; } public CssKeyframeRule CreateKeyframeRule(CssToken current) { CssKeyframeRule cssKeyframeRule = new CssKeyframeRule(_parser); CollectTrivia(ref current); cssKeyframeRule.Key = CreateKeyframeSelector(ref current); CollectTrivia(ref current); FillDeclarations(cssKeyframeRule.Style); return cssKeyframeRule; } public CssRule CreateUnknown(CssToken current) { CssUnknownRule cssUnknownRule = null; if (_parser.Options.IsIncludingUnknownRules) { CssToken cssToken = NextToken(); cssUnknownRule = new CssUnknownRule(current.Data, _parser); while (cssToken.IsNot(CssTokenType.CurlyBracketOpen, CssTokenType.Semicolon, CssTokenType.Eof)) { cssUnknownRule.Prelude.Add(cssToken); cssToken = NextToken(); } if (cssToken.Type != CssTokenType.Eof) { cssUnknownRule.Content.Add(cssToken); if (cssToken.Type == CssTokenType.CurlyBracketOpen) { int num = 1; do { cssToken = NextToken(); cssUnknownRule.Content.Add(cssToken); switch (cssToken.Type) { case CssTokenType.CurlyBracketOpen: num++; break; case CssTokenType.CurlyBracketClose: num--; break; case CssTokenType.Eof: num = 0; break; } } while (num != 0); } } } else { RaiseErrorOccurred(CssParseError.UnknownAtRule, current); SkipRule(current); } return cssUnknownRule; } 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>(); CollectTrivia(ref token); while (token.Type != CssTokenType.Eof) { CreateNewNode(); CssMedium cssMedium = CreateMedium(ref token); if (cssMedium == null || token.IsNot(CssTokenType.Comma, CssTokenType.Eof)) throw new DomException(DomError.Syntax); token = NextToken(); CollectTrivia(ref token); list.Add(CloseNode(cssMedium)); } return list; } public void CreateRules(CssStyleSheet sheet) { CssToken token = NextToken(); CollectTrivia(ref token); while (token.Type != CssTokenType.Eof) { CreateNewNode(); CssRule entity = CreateRule(token); token = NextToken(); CollectTrivia(ref token); sheet.AddRule(CloseNode(entity)); } } public CssCondition CreateCondition(ref CssToken token) { CollectTrivia(ref token); return AggregateCondition(ref token); } public KeyframeSelector CreateKeyframeSelector(ref CssToken token) { List<Percent> list = new List<Percent>(); bool flag = true; CssToken token2 = token; CreateNewNode(); CollectTrivia(ref token); while (token.Type != CssTokenType.Eof) { if (list.Count > 0) { if (token.Type == CssTokenType.CurlyBracketOpen) break; if (token.Type != CssTokenType.Comma) flag = false; else token = NextToken(); CollectTrivia(ref token); } if (token.Type == CssTokenType.Percentage) list.Add(new Percent(((CssUnitToken)token).Value)); else if (token.Type == CssTokenType.Ident && token.Data.Is(Keywords.From)) { list.Add(Percent.Zero); } else if (token.Type == CssTokenType.Ident && token.Data.Is(Keywords.To)) { list.Add(Percent.Hundred); } else { flag = false; } token = NextToken(); CollectTrivia(ref token); } if (!flag) RaiseErrorOccurred(CssParseError.InvalidSelector, token2); return CloseNode(new KeyframeSelector(list)); } public List<CssDocumentFunction> CreateFunctions(ref CssToken token) { List<CssDocumentFunction> list = new List<CssDocumentFunction>(); CollectTrivia(ref token); FillFunctions(list, ref token); return list; } public void FillDeclarations(CssStyleDeclaration style) { CssToken token = NextToken(); CollectTrivia(ref token); while (token.IsNot(CssTokenType.Eof, CssTokenType.CurlyBracketClose)) { CssProperty cssProperty = CreateDeclarationWith(Factory.Properties.Create, ref token); if (cssProperty != null && cssProperty.HasValue) style.SetProperty(cssProperty); CollectTrivia(ref token); } } public CssProperty CreateDeclarationWith(Func<string, CssProperty> createProperty, ref CssToken token) { CssProperty cssProperty = null; CreateNewNode(); StringBuilder stringBuilder = Pool.NewStringBuilder(); while (token.Type != CssTokenType.Eof && token.Type != CssTokenType.Colon && token.Type != CssTokenType.Whitespace && token.Type != CssTokenType.Comment && token.Type != CssTokenType.CurlyBracketOpen && token.Type != CssTokenType.Semicolon) { stringBuilder.Append(token.ToValue()); token = NextToken(); } string text = stringBuilder.ToPool(); if (text.Length > 0) { cssProperty = ((_parser.Options.IsIncludingUnknownDeclarations || _parser.Options.IsToleratingInvalidValues) ? new CssUnknownProperty(text) : createProperty(text)); if (cssProperty == null) RaiseErrorOccurred(CssParseError.UnknownDeclarationName, token); CollectTrivia(ref token); if (token.Type == CssTokenType.Colon) { 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; } CollectTrivia(ref token); } else RaiseErrorOccurred(CssParseError.ColonMissing, token); JumpToDeclEnd(ref token); } else if (token.Type != CssTokenType.Eof) { RaiseErrorOccurred(CssParseError.IdentExpected, token); JumpToDeclEnd(ref token); } if (token.Type == CssTokenType.Semicolon) token = NextToken(); return CloseNode(cssProperty); } public CssProperty CreateDeclaration(ref CssToken token) { CollectTrivia(ref token); return CreateDeclarationWith(Factory.Properties.Create, ref token); } public CssMedium CreateMedium(ref CssToken token) { CssMedium cssMedium = new CssMedium(); CollectTrivia(ref token); if (token.Type == CssTokenType.Ident) { string data = token.Data; if (data.Isi(Keywords.Not)) { cssMedium.IsInverse = true; token = NextToken(); CollectTrivia(ref token); } else if (data.Isi(Keywords.Only)) { cssMedium.IsExclusive = true; token = NextToken(); CollectTrivia(ref token); } } if (token.Type == CssTokenType.Ident) { cssMedium.Type = token.Data; token = NextToken(); CollectTrivia(ref token); if (token.Type != CssTokenType.Ident || !token.Data.Isi(Keywords.And)) return cssMedium; token = NextToken(); CollectTrivia(ref token); } do { if (token.Type != CssTokenType.RoundBracketOpen) return null; token = NextToken(); CollectTrivia(ref token); CreateNewNode(); MediaFeature mediaFeature = CloseNode(CreateFeature(ref token)); if (mediaFeature != null) cssMedium.AddConstraint(mediaFeature); if (token.Type != CssTokenType.RoundBracketClose) return null; token = NextToken(); CollectTrivia(ref token); if (mediaFeature == null) return null; if (token.Type != CssTokenType.Ident || !token.Data.Isi(Keywords.And)) break; token = NextToken(); CollectTrivia(ref token); } while (token.Type != CssTokenType.Eof); return cssMedium; } private void SkipRule(CssToken current) { int num = 0; while (current.Type != CssTokenType.Eof) { if (current.Type == CssTokenType.CurlyBracketOpen) num++; else if (current.Type == CssTokenType.CurlyBracketClose) { num--; } if (num <= 0 && current.Is(CssTokenType.Semicolon, CssTokenType.CurlyBracketClose)) break; current = NextToken(); } } private void JumpToEnd(CssToken current) { while (current.IsNot(CssTokenType.Eof, CssTokenType.Semicolon)) { current = NextToken(); } } private void JumpToArgEnd(ref CssToken current) { int num = 0; while (current.Type != CssTokenType.Eof) { if (current.Type == CssTokenType.RoundBracketOpen) num++; else { if (num <= 0 && current.Type == CssTokenType.RoundBracketClose) break; if (current.Type == CssTokenType.RoundBracketClose) num--; } current = NextToken(); } } private void JumpToDeclEnd(ref CssToken current) { int num = 0; while (current.Type != CssTokenType.Eof) { if (current.Type == CssTokenType.CurlyBracketOpen) num++; else { if (num <= 0 && current.Is(CssTokenType.CurlyBracketClose, CssTokenType.Semicolon)) break; if (current.Type == CssTokenType.CurlyBracketClose) num--; } current = NextToken(); } } private CssToken NextToken() { CssToken cssToken = _tokenizer.Get(); if (_nodes.Count > 0) _nodes.Peek().Tokens.Add(cssToken); return cssToken; } private CssNode CreateNewNode() { CssNode cssNode = null; if (_parser.Options.IsStoringTrivia) { List<CssToken> tokens = _nodes.Peek().Tokens; cssNode = new CssNode(); if (tokens.Count > 0) { cssNode.Tokens.Add(tokens[tokens.Count - 1]); tokens.RemoveAt(tokens.Count - 1); } _nodes.Peek().Children.Add(cssNode); _nodes.Push(cssNode); } return cssNode; } private T CloseNode<T>(T entity) where T : IStyleFormattable { if (_nodes.Count > 0) { CssNode cssNode = _nodes.Pop(); List<CssToken> tokens = cssNode.Tokens; cssNode.Entity = (IStyleFormattable)(object)entity; if (tokens.Count > 0) { _nodes.Peek().Tokens.Add(tokens[tokens.Count - 1]); tokens.RemoveAt(tokens.Count - 1); } } return entity; } private void CollectTrivia(ref CssToken token) { if (_nodes.Count > 0) StoreTrivia(ref token); else RemoveTrivia(ref token); } private void StoreTrivia(ref CssToken token) { List<CssToken> tokens = _nodes.Peek().Tokens; while (token.Type == CssTokenType.Whitespace || token.Type == CssTokenType.Comment) { token = _tokenizer.Get(); tokens.Add(token); } } private void RemoveTrivia(ref CssToken token) { while (token.Type == CssTokenType.Whitespace || token.Type == CssTokenType.Comment) { token = _tokenizer.Get(); } } private CssRule SkipDeclarations(CssToken token) { RaiseErrorOccurred(CssParseError.InvalidToken, token); SkipRule(token); return null; } private void RaiseErrorOccurred(CssParseError code, CssToken token) { _tokenizer.RaiseErrorOccurred(code, token.Position); } private CssCondition AggregateCondition(ref CssToken token) { CssCondition cssCondition = ExtractCondition(ref token); if (cssCondition != null) { CollectTrivia(ref token); string data = token.Data; Func<IEnumerable<CssCondition>, CssCondition> creator = data.GetCreator(); if (creator != null) { token = NextToken(); CollectTrivia(ref token); CreateNewNode(); List<CssCondition> arg = MultipleConditions(cssCondition, data, ref token); cssCondition = CloseNode(creator(arg)); } } return cssCondition; } private CssCondition ExtractCondition(ref CssToken token) { CssCondition cssCondition = null; CreateNewNode(); if (token.Type == CssTokenType.RoundBracketOpen) { token = NextToken(); CollectTrivia(ref token); cssCondition = AggregateCondition(ref token); if (cssCondition != null) cssCondition = new GroupCondition(cssCondition); else if (token.Type == CssTokenType.Ident) { cssCondition = DeclarationCondition(ref token); } if (token.Type == CssTokenType.RoundBracketClose) { token = NextToken(); CollectTrivia(ref token); } } else if (token.Data.Isi(Keywords.Not)) { token = NextToken(); CollectTrivia(ref token); cssCondition = ExtractCondition(ref token); if (cssCondition != null) cssCondition = new NotCondition(cssCondition); } return CloseNode(cssCondition); } private CssCondition DeclarationCondition(ref CssToken token) { CssProperty cssProperty = Factory.Properties.Create(token.Data) ?? new CssUnknownProperty(token.Data); DeclarationCondition entity = null; CreateNewNode(); token = NextToken(); CollectTrivia(ref token); if (token.Type == CssTokenType.Colon) { bool important = false; CssValue cssValue = CreateValue(CssTokenType.RoundBracketClose, ref token, out important); cssProperty.IsImportant = important; if (cssValue != null) entity = new DeclarationCondition(cssProperty, cssValue); } return CloseNode(entity); } private List<CssCondition> MultipleConditions(CssCondition condition, string connector, ref CssToken token) { List<CssCondition> list = new List<CssCondition>(); CollectTrivia(ref token); list.Add(condition); while (token.Type != CssTokenType.Eof) { condition = ExtractCondition(ref token); if (condition == null) break; list.Add(condition); if (!token.Data.Isi(connector)) break; token = NextToken(); CollectTrivia(ref token); } return list; } private void FillFunctions(List<CssDocumentFunction> functions, ref CssToken token) { do { CreateNewNode(); CssDocumentFunction cssDocumentFunction = token.ToDocumentFunction(); if (cssDocumentFunction == null) break; token = NextToken(); CollectTrivia(ref token); functions.Add(CloseNode(cssDocumentFunction)); if (token.Type != CssTokenType.Comma) break; token = NextToken(); CollectTrivia(ref token); } while (token.Type == CssTokenType.Eof); } private void FillKeyframeRules(CssKeyframesRule parentRule) { CssToken token = NextToken(); CollectTrivia(ref token); while (token.IsNot(CssTokenType.Eof, CssTokenType.CurlyBracketClose)) { CreateNewNode(); CssKeyframeRule entity = CreateKeyframeRule(token); token = NextToken(); CollectTrivia(ref token); parentRule.AddRule(CloseNode(entity)); } } private void FillDeclarations(CssDeclarationRule rule, Func<string, CssProperty> createProperty) { CssToken token = NextToken(); CollectTrivia(ref token); while (token.IsNot(CssTokenType.Eof, CssTokenType.CurlyBracketClose)) { CssProperty cssProperty = CreateDeclarationWith(createProperty, ref token); if (cssProperty != null && cssProperty.HasValue) rule.SetProperty(cssProperty); CollectTrivia(ref token); } } private void FillRules(CssGroupingRule group) { CssToken token = NextToken(); CollectTrivia(ref token); while (token.IsNot(CssTokenType.Eof, CssTokenType.CurlyBracketClose)) { CreateNewNode(); CssRule entity = CreateRule(token); token = NextToken(); CollectTrivia(ref token); group.AddRule(CloseNode(entity)); } } private void FillMediaList(MediaList list, CssTokenType end, ref CssToken token) { if (token.Type != end) { while (token.Type != CssTokenType.Eof) { CreateNewNode(); CssMedium cssMedium = CloseNode(CreateMedium(ref token)); if (cssMedium != null) list.Add(cssMedium); if (token.Type != CssTokenType.Comma) break; token = NextToken(); CollectTrivia(ref token); } if (token.Type != end || list.Length <= 0) { list.Clear(); list.Add(new CssMedium { IsInverse = true, Type = Keywords.All }); } } } private ISelector CreateSelector(ref CssToken token) { CssSelectorConstructor cssSelectorConstructor = Pool.NewSelectorConstructor(); CssToken token2 = token; CreateNewNode(); while (token.IsNot(CssTokenType.Eof, CssTokenType.CurlyBracketOpen, CssTokenType.CurlyBracketClose)) { cssSelectorConstructor.Apply(token); token = NextToken(); } ISelector entity = cssSelectorConstructor.ToPool(); if (!cssSelectorConstructor.IsValid && !_parser.Options.IsToleratingInvalidValues) { RaiseErrorOccurred(CssParseError.InvalidSelector, token2); entity = null; } return CloseNode(entity); } private CssValue CreateValue(CssTokenType closing, ref CssToken token, out bool important) { CssValueBuilder cssValueBuilder = Pool.NewValueBuilder(); _tokenizer.IsInValue = true; token = NextToken(); CssToken token2 = token; CreateNewNode(); while (token.Type != CssTokenType.Eof && !token.Is(CssTokenType.Semicolon, closing)) { cssValueBuilder.Apply(token); token = NextToken(); } important = cssValueBuilder.IsImportant; _tokenizer.IsInValue = false; CssValue entity = cssValueBuilder.ToPool(); if (!cssValueBuilder.IsValid && !_parser.Options.IsToleratingInvalidValues) { RaiseErrorOccurred(CssParseError.InvalidValue, token2); entity = null; } return CloseNode(entity); } private string GetRuleName(ref CssToken token) { string result = string.Empty; if (token.Type == CssTokenType.Ident) { result = token.Data; token = NextToken(); } return result; } private MediaFeature CreateFeature(ref CssToken token) { if (token.Type == CssTokenType.Ident) { CssValue value = CssValue.Empty; MediaFeature mediaFeature = _parser.Options.IsToleratingInvalidConstraints ? new UnknownMediaFeature(token.Data) : Factory.MediaFeatures.Create(token.Data); token = NextToken(); if (token.Type == CssTokenType.Colon) { CssValueBuilder cssValueBuilder = Pool.NewValueBuilder(); token = NextToken(); while ((token.Type != CssTokenType.RoundBracketClose || !cssValueBuilder.IsReady) && token.Type != CssTokenType.Eof) { cssValueBuilder.Apply(token); token = NextToken(); } value = cssValueBuilder.ToPool(); } else if (token.Type == CssTokenType.Eof) { return null; } if (mediaFeature != null && mediaFeature.TrySetValue(value)) return mediaFeature; } else JumpToArgEnd(ref token); return null; } } }