AngleSharp by Florian Rappl

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

 CssParser

public class CssParser : IParser
The CSS parser. See http://dev.w3.org/csswg/css-syntax/#parsing for more details.
using AngleSharp.DOM; using AngleSharp.DOM.Collections; using AngleSharp.DOM.Css; using System; using System.Collections.Generic; using System.IO; using System.Text; using System.Threading.Tasks; namespace AngleSharp.Css { public class CssParser : IParser { private bool started; private bool quirksFlag; private CssTokenizer tokenizer; private CSSStyleSheet sheet; private TaskCompletionSource<bool> tcs; private StringBuilder buffer; private Stack<CSSRule> open; private bool ignore; public bool IsAsync => tcs != null; public CSSStyleSheet Result { get { Parse(); return sheet; } } public bool IsQuirksMode { get { return quirksFlag; } set { quirksFlag = value; } } internal CSSRule CurrentRule { get { if (open.Count <= 0) return null; return open.Peek(); } } public event EventHandler<ParseErrorEventArgs> ErrorOccurred; public CssParser(string source) : this(new CSSStyleSheet(), new SourceManager(source)) { } public CssParser(Stream stream) : this(new CSSStyleSheet(), new SourceManager(stream)) { } public CssParser(CSSStyleSheet stylesheet, string source) : this(stylesheet, new SourceManager(source)) { } public CssParser(CSSStyleSheet stylesheet, Stream stream) : this(stylesheet, new SourceManager(stream)) { } internal CssParser(CSSStyleSheet stylesheet, SourceManager source) { ignore = true; buffer = new StringBuilder(); tokenizer = new CssTokenizer(source); CssTokenizer cssTokenizer = tokenizer; EventHandler<ParseErrorEventArgs> value = delegate(object s, ParseErrorEventArgs ev) { if (this.ErrorOccurred != null) this.ErrorOccurred(this, ev); }; cssTokenizer.ErrorOccurred += value; started = false; sheet = stylesheet; open = new Stack<CSSRule>(); } public Task ParseAsync() { if (!started) { started = true; tcs = new TaskCompletionSource<bool>(); return tcs.Task; } if (tcs == null) { TaskCompletionSource<bool> taskCompletionSource = new TaskCompletionSource<bool>(); taskCompletionSource.SetResult(true); return taskCompletionSource.Task; } return tcs.Task; } public void Parse() { if (!started) { started = true; AppendRules(tokenizer.Iterator, sheet.CssRules.List); } } private void AppendRules(IEnumerator<CssToken> source, List<CSSRule> rules) { while (source.MoveNext()) { switch (source.Current.Type) { case CssTokenType.AtKeyword: rules.Add(CreateAtRule(source)); break; default: rules.Add(CreateStyleRule(source)); break; case CssTokenType.Cdo: case CssTokenType.Cdc: case CssTokenType.Whitespace: break; } } } private void AppendDeclarations(IEnumerator<CssToken> source, List<CSSProperty> declarations) { while (source.MoveNext()) { switch (source.Current.Type) { case CssTokenType.Ident: { IEnumerable<CssToken> enumerable = LimitToSemicolon(source); IEnumerator<CssToken> enumerator = enumerable.GetEnumerator(); enumerator.MoveNext(); CSSProperty cSSProperty = CreateDeclaration(enumerator); if (cSSProperty != null) declarations.Add(cSSProperty); break; } default: RaiseErrorOccurred(ErrorCode.InvalidCharacter); SkipToNextSemicolon(source); break; case CssTokenType.Semicolon: case CssTokenType.Whitespace: break; } } } private void AppendMediaList(IEnumerator<CssToken> source, MediaList media, CssTokenType endToken = CssTokenType.Semicolon) { do { if (source.Current.Type != CssTokenType.Whitespace) { if (source.Current.Type == endToken) break; while (source.Current.Type != CssTokenType.Comma && source.Current.Type != endToken) { if (source.Current.Type == CssTokenType.Whitespace) buffer.Append(' '); else buffer.Append(source.Current.ToValue()); if (!source.MoveNext()) break; } media.AppendMedium(buffer.ToString()); buffer.Clear(); if (source.Current.Type == endToken) break; } } while (source.MoveNext()); } private List<CSSValueList> CreateMultipleValues(IEnumerator<CssToken> source) { List<CSSValueList> list = new List<CSSValueList>(); do { CSSValueList cSSValueList = CreateValueList(source); if (cSSValueList.Length > 0) list.Add(cSSValueList); } while (source.Current != null && source.Current.Type == CssTokenType.Comma); return list; } private CSSValueList CreateValueList(IEnumerator<CssToken> source) { List<CSSValue> list = new List<CSSValue>(); while (SkipToNextNonWhitespace(source)) { if (source.Current.Type == CssTokenType.Semicolon) break; if (source.Current.Type == CssTokenType.Comma) break; CSSValue cSSValue = CreateValue(source); if (cSSValue == null) { SkipToNextSemicolon(source); break; } list.Add(cSSValue); } return new CSSValueList(list); } private CSSValue CreateValue(IEnumerator<CssToken> source) { CSSValue result = null; HtmlColor htmlColor; switch (source.Current.Type) { case CssTokenType.String: result = new CSSPrimitiveValue(UnitType.String, ((CssStringToken)source.Current).Data); break; case CssTokenType.Url: result = new CSSPrimitiveValue(UnitType.Uri, ((CssStringToken)source.Current).Data); break; case CssTokenType.Ident: result = new CSSPrimitiveValue(UnitType.Ident, ((CssKeywordToken)source.Current).Data); break; case CssTokenType.Percentage: result = new CSSPrimitiveValue(UnitType.Percentage, ((CssUnitToken)source.Current).Data); break; case CssTokenType.Dimension: result = new CSSPrimitiveValue(((CssUnitToken)source.Current).Unit, ((CssUnitToken)source.Current).Data); break; case CssTokenType.Number: result = new CSSPrimitiveValue(UnitType.Number, ((CssNumberToken)source.Current).Data); break; case CssTokenType.Hash: if (HtmlColor.TryFromHex(((CssKeywordToken)source.Current).Data, out htmlColor)) result = new CSSPrimitiveValue(htmlColor); break; case CssTokenType.Delim: if (((CssDelimToken)source.Current).Data == '#') { string text = string.Empty; while (source.MoveNext()) { bool flag = false; switch (source.Current.Type) { case CssTokenType.Ident: case CssTokenType.Number: case CssTokenType.Dimension: { string text2 = source.Current.ToValue(); if (text.Length + text2.Length <= 6) text += text2; else flag = true; break; } default: flag = true; break; } if (flag || text.Length == 6) break; } if (HtmlColor.TryFromHex(text, out htmlColor)) result = new CSSPrimitiveValue(htmlColor); } break; case CssTokenType.Function: result = CreateFunction(source); break; } return result; } private CSSFunction CreateFunction(IEnumerator<CssToken> source) { string data = ((CssKeywordToken)source.Current).Data; CSSValueList arguments = new CSSValueList(); while (source.MoveNext() && source.Current.Type != CssTokenType.RoundBracketClose) { } return CSSFunction.Create(data, arguments); } private CSSStyleRule CreateStyleRule(IEnumerator<CssToken> source) { CSSStyleRule cSSStyleRule = new CSSStyleRule(); CssSelectorConstructor cssSelectorConstructor = new CssSelectorConstructor(); cssSelectorConstructor.IgnoreErrors = ignore; cSSStyleRule.ParentStyleSheet = sheet; cSSStyleRule.ParentRule = CurrentRule; open.Push(cSSStyleRule); do { if (source.Current.Type == CssTokenType.CurlyBracketOpen) { if (SkipToNextNonWhitespace(source)) { IEnumerable<CssToken> enumerable = LimitToCurrentBlock(source); AppendDeclarations(enumerable.GetEnumerator(), cSSStyleRule.Style.List); } break; } cssSelectorConstructor.PickSelector(source); } while (source.MoveNext()); cSSStyleRule.Selector = cssSelectorConstructor.Result; open.Pop(); return cSSStyleRule; } private CSSRule CreateAtRule(IEnumerator<CssToken> source) { string data = ((CssKeywordToken)source.Current).Data; SkipToNextNonWhitespace(source); switch (data) { case "media": return CreateMediaRule(source); case "page": return CreatePageRule(source); case "import": return CreateImportRule(source); case "font-face": return CreateFontFaceRule(source); case "charset": return CreateCharsetRule(source); case "namespace": return CreateNamespaceRule(source); case "supports": return CreateSupportsRule(source); case "keyframes": return CreateKeyframesRule(source); default: return CreateUnknownRule(data, source); } } private CSSProperty CreateDeclaration(IEnumerator<CssToken> source) { string data = ((CssKeywordToken)source.Current).Data; CSSProperty cSSProperty = null; CSSValue value = CSSValue.Inherit; bool flag = SkipToNextNonWhitespace(source) && source.Current.Type == CssTokenType.Colon; if (flag) value = CreateValueList(source); switch (data) { default: cSSProperty = new CSSProperty(data); cSSProperty.Value = value; if (flag && source.Current.Type == CssTokenType.Delim && ((CssDelimToken)source.Current).Data == '!' && SkipToNextNonWhitespace(source)) cSSProperty.Important = (source.Current.Type == CssTokenType.Ident && ((CssKeywordToken)source.Current).Data.Equals("important", StringComparison.OrdinalIgnoreCase)); SkipBehindNextSemicolon(source); return cSSProperty; } } private CSSUnknownRule CreateUnknownRule(string name, IEnumerator<CssToken> source) { CSSUnknownRule cSSUnknownRule = new CSSUnknownRule(); int num = 0; cSSUnknownRule.ParentStyleSheet = sheet; cSSUnknownRule.ParentRule = CurrentRule; open.Push(cSSUnknownRule); buffer.Append(name).Append(" "); do { if (source.Current.Type == CssTokenType.Semicolon && num == 0) { source.MoveNext(); break; } buffer.Append(source.Current.ToString()); if (source.Current.Type == CssTokenType.CurlyBracketOpen) num++; else if (source.Current.Type == CssTokenType.CurlyBracketClose && --num == 0) { break; } } while (source.MoveNext()); cSSUnknownRule.SetText(buffer.ToString()); buffer.Clear(); open.Pop(); return cSSUnknownRule; } private CSSKeyframesRule CreateKeyframesRule(IEnumerator<CssToken> source) { CSSKeyframesRule cSSKeyframesRule = new CSSKeyframesRule(); cSSKeyframesRule.ParentStyleSheet = sheet; cSSKeyframesRule.ParentRule = CurrentRule; open.Push(cSSKeyframesRule); if (source.Current.Type == CssTokenType.Ident) { cSSKeyframesRule.Name = ((CssKeywordToken)source.Current).Data; SkipToNextNonWhitespace(source); if (source.Current.Type == CssTokenType.CurlyBracketOpen) { SkipToNextNonWhitespace(source); IEnumerator<CssToken> enumerator = LimitToCurrentBlock(source).GetEnumerator(); while (SkipToNextNonWhitespace(enumerator)) { cSSKeyframesRule.CssRules.List.Add(CreateKeyframeRule(enumerator)); } } } open.Pop(); return cSSKeyframesRule; } private CSSKeyframeRule CreateKeyframeRule(IEnumerator<CssToken> source) { CSSKeyframeRule cSSKeyframeRule = new CSSKeyframeRule(); cSSKeyframeRule.ParentStyleSheet = sheet; cSSKeyframeRule.ParentRule = CurrentRule; open.Push(cSSKeyframeRule); do { if (source.Current.Type == CssTokenType.CurlyBracketOpen) { if (SkipToNextNonWhitespace(source)) { IEnumerable<CssToken> enumerable = LimitToCurrentBlock(source); AppendDeclarations(enumerable.GetEnumerator(), cSSKeyframeRule.Style.List); } break; } buffer.Append(source.Current.ToString()); } while (source.MoveNext()); cSSKeyframeRule.KeyText = buffer.ToString(); buffer.Clear(); open.Pop(); return cSSKeyframeRule; } private CSSSupportsRule CreateSupportsRule(IEnumerator<CssToken> source) { CSSSupportsRule cSSSupportsRule = new CSSSupportsRule(); cSSSupportsRule.ParentStyleSheet = sheet; cSSSupportsRule.ParentRule = CurrentRule; open.Push(cSSSupportsRule); do { if (source.Current.Type == CssTokenType.CurlyBracketOpen) { if (SkipToNextNonWhitespace(source)) { IEnumerable<CssToken> enumerable = LimitToCurrentBlock(source); AppendRules(enumerable.GetEnumerator(), cSSSupportsRule.CssRules.List); } break; } buffer.Append(source.Current.ToString()); } while (source.MoveNext()); cSSSupportsRule.ConditionText = buffer.ToString(); buffer.Clear(); open.Pop(); return cSSSupportsRule; } private CSSNamespaceRule CreateNamespaceRule(IEnumerator<CssToken> source) { CSSNamespaceRule cSSNamespaceRule = new CSSNamespaceRule(); cSSNamespaceRule.ParentStyleSheet = sheet; if (source.Current.Type == CssTokenType.Ident) { cSSNamespaceRule.Prefix = source.Current.ToValue(); SkipToNextNonWhitespace(source); if (source.Current.Type == CssTokenType.String) cSSNamespaceRule.NamespaceURI = source.Current.ToValue(); } SkipToNextSemicolon(source); return cSSNamespaceRule; } private CSSCharsetRule CreateCharsetRule(IEnumerator<CssToken> source) { CSSCharsetRule cSSCharsetRule = new CSSCharsetRule(); cSSCharsetRule.ParentStyleSheet = sheet; if (source.Current.Type == CssTokenType.String) cSSCharsetRule.Encoding = ((CssStringToken)source.Current).Data; SkipToNextSemicolon(source); return cSSCharsetRule; } private CSSFontFaceRule CreateFontFaceRule(IEnumerator<CssToken> source) { CSSFontFaceRule cSSFontFaceRule = new CSSFontFaceRule(); cSSFontFaceRule.ParentStyleSheet = sheet; cSSFontFaceRule.ParentRule = CurrentRule; open.Push(cSSFontFaceRule); if (source.Current.Type == CssTokenType.CurlyBracketOpen && SkipToNextNonWhitespace(source)) { IEnumerable<CssToken> enumerable = LimitToCurrentBlock(source); AppendDeclarations(enumerable.GetEnumerator(), cSSFontFaceRule.CssRules.List); } open.Pop(); return cSSFontFaceRule; } private CSSImportRule CreateImportRule(IEnumerator<CssToken> source) { CSSImportRule cSSImportRule = new CSSImportRule(); cSSImportRule.ParentStyleSheet = sheet; cSSImportRule.ParentRule = CurrentRule; open.Push(cSSImportRule); switch (source.Current.Type) { case CssTokenType.Semicolon: source.MoveNext(); break; case CssTokenType.String: case CssTokenType.Url: cSSImportRule.Href = ((CssStringToken)source.Current).Data; AppendMediaList(source, cSSImportRule.Media, CssTokenType.Semicolon); break; default: SkipToNextSemicolon(source); break; } open.Pop(); return cSSImportRule; } private CSSPageRule CreatePageRule(IEnumerator<CssToken> source) { CSSPageRule cSSPageRule = new CSSPageRule(); cSSPageRule.ParentStyleSheet = sheet; cSSPageRule.ParentRule = CurrentRule; open.Push(cSSPageRule); CssSelectorConstructor cssSelectorConstructor = new CssSelectorConstructor(); cssSelectorConstructor.IgnoreErrors = ignore; do { if (source.Current.Type == CssTokenType.CurlyBracketOpen && SkipToNextNonWhitespace(source)) { IEnumerable<CssToken> enumerable = LimitToCurrentBlock(source); AppendDeclarations(enumerable.GetEnumerator(), cSSPageRule.Style.List); break; } cssSelectorConstructor.PickSelector(source); } while (source.MoveNext()); cSSPageRule.Selector = cssSelectorConstructor.Result; open.Pop(); return cSSPageRule; } private CSSMediaRule CreateMediaRule(IEnumerator<CssToken> source) { CSSMediaRule cSSMediaRule = new CSSMediaRule(); cSSMediaRule.ParentStyleSheet = sheet; cSSMediaRule.ParentRule = CurrentRule; open.Push(cSSMediaRule); AppendMediaList(source, cSSMediaRule.Media, CssTokenType.CurlyBracketOpen); if (source.Current.Type == CssTokenType.CurlyBracketOpen && SkipToNextNonWhitespace(source)) { IEnumerable<CssToken> enumerable = LimitToCurrentBlock(source); AppendRules(enumerable.GetEnumerator(), cSSMediaRule.CssRules.List); } open.Pop(); return cSSMediaRule; } private static bool SkipToNextNonWhitespace(IEnumerator<CssToken> source) { while (source.MoveNext()) { if (source.Current.Type != CssTokenType.Whitespace) return true; } return false; } private static bool SkipToNextSemicolon(IEnumerator<CssToken> source) { do { if (source.Current.Type == CssTokenType.Semicolon) return true; } while (source.MoveNext()); return false; } private static bool SkipBehindNextSemicolon(IEnumerator<CssToken> source) { do { if (source.Current.Type == CssTokenType.Semicolon) { source.MoveNext(); return true; } } while (source.MoveNext()); return false; } private static IEnumerable<CssToken> LimitToSemicolon(IEnumerator<CssToken> source) { while (source.Current.Type != CssTokenType.Semicolon) { yield return source.Current; if (!source.MoveNext()) break; } } private static IEnumerable<CssToken> LimitToCurrentBlock(IEnumerator<CssToken> source) { int open = 1; do { if (source.Current.Type == CssTokenType.CurlyBracketOpen) open++; else if (source.Current.Type == CssTokenType.CurlyBracketClose) { int num; open = (num = open - 1); if (num == 0) break; } yield return source.Current; } while (source.MoveNext()); } public static Selector ParseSelector(string selector, bool quirksMode = false) { CssParser cssParser = new CssParser(selector); cssParser.IsQuirksMode = quirksMode; IEnumerator<CssToken> iterator = cssParser.tokenizer.Iterator; CssSelectorConstructor cssSelectorConstructor = new CssSelectorConstructor(); while (iterator.MoveNext()) { cssSelectorConstructor.PickSelector(iterator); } return cssSelectorConstructor.Result; } public static CSSStyleSheet ParseStyleSheet(string stylesheet, bool quirksMode = false) { CssParser cssParser = new CssParser(stylesheet); cssParser.IsQuirksMode = quirksMode; return cssParser.Result; } public static CSSRule ParseRule(string rule, bool quirksMode = false) { CssParser cssParser = new CssParser(rule); cssParser.ignore = false; cssParser.IsQuirksMode = quirksMode; IEnumerator<CssToken> iterator = cssParser.tokenizer.Iterator; if (SkipToNextNonWhitespace(iterator)) { if (iterator.Current.Type == CssTokenType.Cdo || iterator.Current.Type == CssTokenType.Cdc) throw new DOMException(ErrorCode.SyntaxError); if (iterator.Current.Type != CssTokenType.AtKeyword) return cssParser.CreateStyleRule(iterator); return cssParser.CreateAtRule(iterator); } return new CSSUnknownRule(); } public static CSSStyleDeclaration ParseDeclarations(string declarations, bool quirksMode = false) { CssParser cssParser = new CssParser(declarations); cssParser.IsQuirksMode = quirksMode; cssParser.ignore = false; IEnumerator<CssToken> iterator = cssParser.tokenizer.Iterator; CSSStyleDeclaration cSSStyleDeclaration = new CSSStyleDeclaration(); cssParser.AppendDeclarations(iterator, cSSStyleDeclaration.List); return cSSStyleDeclaration; } public static CSSValue ParseValue(string source, bool quirksMode = false) { CssParser cssParser = new CssParser(source); cssParser.IsQuirksMode = quirksMode; cssParser.ignore = false; IEnumerator<CssToken> iterator = cssParser.tokenizer.Iterator; SkipToNextNonWhitespace(iterator); return cssParser.CreateValue(iterator); } internal static CSSValueList ParseValueList(string source, bool quirksMode = false) { CssParser cssParser = new CssParser(source); cssParser.IsQuirksMode = quirksMode; cssParser.ignore = false; IEnumerator<CssToken> iterator = cssParser.tokenizer.Iterator; return cssParser.CreateValueList(iterator); } internal static List<CSSValueList> ParseMultipleValues(string source, bool quirksMode = false) { CssParser cssParser = new CssParser(source); cssParser.IsQuirksMode = quirksMode; cssParser.ignore = false; IEnumerator<CssToken> iterator = cssParser.tokenizer.Iterator; return cssParser.CreateMultipleValues(iterator); } internal static CSSKeyframeRule ParseKeyframeRule(string rule, bool quirksMode = false) { CssParser cssParser = new CssParser(rule); cssParser.IsQuirksMode = quirksMode; cssParser.ignore = false; IEnumerator<CssToken> iterator = cssParser.tokenizer.Iterator; if (SkipToNextNonWhitespace(iterator)) { if (iterator.Current.Type == CssTokenType.Cdo || iterator.Current.Type == CssTokenType.Cdc) throw new DOMException(ErrorCode.SyntaxError); return cssParser.CreateKeyframeRule(iterator); } return null; } private void RaiseErrorOccurred(ErrorCode code) { if (this.ErrorOccurred != null) { ParseErrorEventArgs parseErrorEventArgs = new ParseErrorEventArgs((int)code, Errors.GetError(code)); parseErrorEventArgs.Line = tokenizer.Stream.Line; parseErrorEventArgs.Column = tokenizer.Stream.Column; this.ErrorOccurred(this, parseErrorEventArgs); } } } }