using System; using System.Collections.Generic; using System.IO; using System.Text; using System.Threading.Tasks; namespace ExCSS { public sealed class CssParser { CssSelectorConstructor selector; Stack<FunctionBuffer> function; Boolean skipExceptions; Lexer tokenizer; Boolean fraction; CSSProperty property; CSSValue value; List<CSSValue> mvalues; CSSValueList cvalues; Boolean started; Boolean quirks; CSSStyleSheet sheet; Stack<CSSRule> open; StringBuilder buffer; CssState state; Object sync; Task task; #region ctor /// <summary> /// Creates a new CSS parser instance parser with the specified stylesheet /// based on the given source manager. /// </summary> /// <param name="stylesheet">The stylesheet to be constructed.</param> /// <param name="source">The source to use.</param> internal CssParser(StylesheetReader reader) { selector = Pool.NewSelectorConstructor(); sync = new Object(); skipExceptions = true; tokenizer = new Lexer(reader); started = false; function = new Stack<FunctionBuffer>(); sheet = stylesheet; open = new Stack<CSSRule>(); SwitchTo(CssState.Data); } #endregion #region Properties /// <summary> /// Gets if the parser has been started asynchronously. /// </summary> public Boolean IsAsync { get { return task != null; } } /// <summary> /// Gets or sets if the quirks-mode is activated. /// </summary> public Boolean IsQuirksMode { get { return quirks; } set { quirks = value; } } /// <summary> /// Gets the current rule if any. /// </summary> internal CSSRule CurrentRule { get { return open.Count > 0 ? open.Peek() : null; } } #endregion #region Methods /// <summary> /// Parses the given source asynchronously and creates the stylesheet. /// </summary> /// <returns>The task which could be awaited or continued differently.</returns> public Task ParseAsync() { lock (sync) { if (!started) { started = true; task = Task.Run(() => Kernel()); } else if (task == null) throw new InvalidOperationException("The parser has already run synchronously."); return task; } } /// <summary> /// Parses the given source code. /// </summary> public void Parse() { var run = false; lock (sync) { if (!started) { started = true; run = true; } } if (run) { Kernel(); } } #endregion #region States /// <summary> /// The general state. /// </summary> /// <param name="token">The current token.</param> /// <returns>The status.</returns> Boolean Data(CssToken token) { if (token.Type == CssTokenType.AtKeyword) { switch (((CssKeywordToken)token).Data) { case RuleNames.MEDIA: { AddRule(new CSSMediaRule()); SwitchTo(CssState.InMediaList); break; } case RuleNames.PAGE: { AddRule(new CSSPageRule()); SwitchTo(CssState.InSelector); break; } case RuleNames.IMPORT: { AddRule(new CSSImportRule()); SwitchTo(CssState.BeforeImport); break; } case RuleNames.FONT_FACE: { AddRule(new CSSFontFaceRule()); SwitchTo(CssState.InDeclaration); break; } case RuleNames.CHARSET: { AddRule(new CSSCharsetRule()); SwitchTo(CssState.BeforeCharset); break; } case RuleNames.NAMESPACE: { AddRule(new CSSNamespaceRule()); SwitchTo(CssState.BeforeNamespacePrefix); break; } case RuleNames.SUPPORTS: { buffer = Pool.NewStringBuilder(); AddRule(new CSSSupportsRule()); SwitchTo(CssState.InCondition); break; } case RuleNames.KEYFRAMES: { AddRule(new CSSKeyframesRule()); SwitchTo(CssState.BeforeKeyframesName); break; } case RuleNames.DOCUMENT: { AddRule(new CSSDocumentRule()); SwitchTo(CssState.BeforeDocumentFunction); break; } default: { buffer = Pool.NewStringBuilder(); AddRule(new CSSUnknownRule()); SwitchTo(CssState.InUnknown); InUnknown(token); break; } } return true; } else if (token.Type == CssTokenType.CurlyBracketClose) { return CloseRule(); } else { AddRule(new CSSStyleRule()); SwitchTo(CssState.InSelector); InSelector(token); return true; } } /// <summary> /// State that is called once in the head of an unknown @ rule. /// </summary> /// <param name="token">The current token.</param> /// <returns>The status.</returns> Boolean InUnknown(CssToken token) { switch (token.Type) { case CssTokenType.Semicolon: CurrentRuleAs<CSSUnknownRule>().SetInstruction(buffer.ToPool()); SwitchTo(CssState.Data); return CloseRule(); case CssTokenType.CurlyBracketOpen: CurrentRuleAs<CSSUnknownRule>().SetCondition(buffer.ToPool()); SwitchTo(CssState.Data); break; default: buffer.Append(token.ToValue()); break; } return true; } /// <summary> /// State that is called once we are in a CSS selector. /// </summary> /// <param name="token">The current token.</param> /// <returns>The status.</returns> Boolean InSelector(CssToken token) { if (token.Type == CssTokenType.CurlyBracketOpen) { var rule = CurrentRule as ICssSelector; if (rule != null) rule.Selector = selector.Result; SwitchTo(CurrentRule is CSSStyleRule ? CssState.InDeclaration : CssState.Data); } else if (token.Type == CssTokenType.CurlyBracketClose) return false; else selector.Apply(token); return true; } /// <summary> /// Called before the property name has been detected. /// </summary> /// <param name="token">The current token.</param> /// <returns>The status.</returns> Boolean InDeclaration(CssToken token) { if (token.Type == CssTokenType.CurlyBracketClose) { CloseProperty(); SwitchTo(CurrentRule is CSSKeyframeRule ? CssState.KeyframesData : CssState.Data); return CloseRule(); } else if (token.Type == CssTokenType.Ident) { AddDeclaration(CSSProperty.Create(((CssKeywordToken)token).Data)); SwitchTo(CssState.AfterProperty); return true; } return false; } /// <summary> /// After instruction rules a semicolon is required. /// </summary> /// <param name="token">The current token.</param> /// <returns>The status.</returns> Boolean AfterInstruction(CssToken token) { if (token.Type == CssTokenType.Semicolon) { SwitchTo(CssState.Data); return CloseRule(); } return false; } /// <summary> /// In the condition text of a supports rule. /// </summary> /// <param name="token">The current token.</param> /// <returns>The status.</returns> Boolean InCondition(CssToken token) { switch (token.Type) { case CssTokenType.CurlyBracketOpen: CurrentRuleAs<CSSSupportsRule>().ConditionText = buffer.ToPool(); SwitchTo(CssState.Data); break; default: buffer.Append(token.ToValue()); break; } return true; } /// <summary> /// Called before a prefix has been found for the namespace rule. /// </summary> /// <param name="token">The current token.</param> /// <returns>The status.</returns> Boolean BeforePrefix(CssToken token) { if (token.Type == CssTokenType.Ident) { CurrentRuleAs<CSSNamespaceRule>().Prefix = ((CssKeywordToken)token).Data; SwitchTo(CssState.AfterNamespacePrefix); return true; } SwitchTo(CssState.AfterInstruction); return AfterInstruction(token); } /// <summary> /// Called before a namespace has been found for the namespace rule. /// </summary> /// <param name="token">The current token.</param> /// <returns>The status.</returns> Boolean BeforeNamespace(CssToken token) { SwitchTo(CssState.AfterInstruction); if (token.Type == CssTokenType.String) { CurrentRuleAs<CSSNamespaceRule>().NamespaceURI = ((CssStringToken)token).Data; return true; } return AfterInstruction(token); } /// <summary> /// Before a charset string has been found. /// </summary> /// <param name="token">The current token.</param> /// <returns>The status.</returns> Boolean BeforeCharset(CssToken token) { SwitchTo(CssState.AfterInstruction); if (token.Type == CssTokenType.String) { CurrentRuleAs<CSSCharsetRule>().Encoding = ((CssStringToken)token).Data; return true; } return AfterInstruction(token); } /// <summary> /// Before an URL has been found for the import rule. /// </summary> /// <param name="token">The current token.</param> /// <returns>The status.</returns> Boolean BeforeImport(CssToken token) { if (token.Type == CssTokenType.String || token.Type == CssTokenType.Url) { CurrentRuleAs<CSSImportRule>().Href = ((CssStringToken)token).Data; SwitchTo(CssState.InMediaList); return true; } SwitchTo(CssState.AfterInstruction); return false; } /// <summary> /// Called before the property separating colon has been seen. /// </summary> /// <param name="token">The current token.</param> /// <returns>The status.</returns> Boolean AfterProperty(CssToken token) { if (token.Type == CssTokenType.Colon) { fraction = false; SwitchTo(CssState.BeforeValue); return true; } else if (token.Type == CssTokenType.Semicolon || token.Type == CssTokenType.CurlyBracketClose) AfterValue(token); return false; } /// <summary> /// Called before any token in the value regime had been seen. /// </summary> /// <param name="token">The current token.</param> /// <returns>The status.</returns> Boolean BeforeValue(CssToken token) { if (token.Type == CssTokenType.Semicolon) SwitchTo(CssState.InDeclaration); else if (token.Type == CssTokenType.CurlyBracketClose) InDeclaration(token); else { SwitchTo(CssState.InSingleValue); return InSingleValue(token); } return false; } /// <summary> /// Called when a value has to be computed. /// </summary> /// <param name="token">The current token.</param> /// <returns>The status.</returns> Boolean InSingleValue(CssToken token) { switch (token.Type) { case CssTokenType.Dimension: // e.g. "3px" return AddValue(new CSSPrimitiveValue(((CssUnitToken)token).Unit, ((CssUnitToken)token).Data)); case CssTokenType.Hash:// e.g. "#ABCDEF" return InSingleValueHexColor(((CssKeywordToken)token).Data); case CssTokenType.Delim:// e.g. "#" return InSingleValueDelim((CssDelimToken)token); case CssTokenType.Ident: // e.g. "auto" return InSingleValueIdent((CssKeywordToken)token); case CssTokenType.String:// e.g. "'i am a string'" return AddValue(new CSSPrimitiveValue(CssUnit.String, ((CssStringToken)token).Data)); case CssTokenType.Url:// e.g. "url('this is a valid URL')" return AddValue(new CSSPrimitiveValue(CssUnit.Uri, ((CssStringToken)token).Data)); case CssTokenType.Percentage: // e.g. "5%" return AddValue(new CSSPrimitiveValue(CssUnit.Percentage, ((CssUnitToken)token).Data)); case CssTokenType.Number: // e.g. "173" return AddValue(new CSSPrimitiveValue(CssUnit.Number, ((CssNumberToken)token).Data)); case CssTokenType.Whitespace: // e.g. " " SwitchTo(CssState.InValueList); return true; case CssTokenType.Function: //e.g. rgba(...) function.Push(new FunctionBuffer(((CssKeywordToken)token).Data)); SwitchTo(CssState.InFunction); return true; case CssTokenType.Comma: // e.g. "," SwitchTo(CssState.InValuePool); return true; case CssTokenType.Semicolon: // e.g. ";" case CssTokenType.CurlyBracketClose: // e.g. "}" return AfterValue(token); default: return false; } } /// <summary> /// Gathers a value inside a function. /// </summary> /// <param name="token">The current token.</param> /// <returns>The status.</returns> Boolean InValueFunction(CssToken token) { switch (token.Type) { case CssTokenType.RoundBracketClose: SwitchTo(CssState.InSingleValue); return AddValue(function.Pop().Done()); case CssTokenType.Comma: function.Peek().Include(); return true; default: return InSingleValue(token); } } /// <summary> /// Called when a new value is seen from the zero-POV (whitespace seen previously). /// </summary> /// <param name="token">The current token.</param> /// <returns>The status.</returns> Boolean InValueList(CssToken token) { if (token.Type == CssTokenType.Semicolon || token.Type == CssTokenType.CurlyBracketClose) AfterValue(token); else if (token.Type == CssTokenType.Comma) SwitchTo(CssState.InValuePool); else { //TDO SwitchTo(CssState.InSingleValue); return InSingleValue(token); } return true; } /// <summary> /// Called when a new value is seen from the zero-POV (comma seen previously). /// </summary> /// <param name="token">The current token.</param> /// <returns>The status.</returns> Boolean InValuePool(CssToken token) { if (token.Type == CssTokenType.Semicolon || token.Type == CssTokenType.CurlyBracketClose) AfterValue(token); else { //TODO SwitchTo(CssState.InSingleValue); return InSingleValue(token); } return false; } /// <summary> /// Called if a # sign has been found. /// </summary> /// <param name="token">The current token.</param> /// <returns>The status.</returns> Boolean InHexValue(CssToken token) { switch (token.Type) { case CssTokenType.Number: case CssTokenType.Dimension: case CssTokenType.Ident: var rest = token.ToValue(); if (buffer.Length + rest.Length <= 6) { buffer.Append(rest); return true; } break; } var s = buffer.ToPool(); InSingleValueHexColor(buffer.ToString()); SwitchTo(CssState.InSingleValue); return InSingleValue(token); } /// <summary> /// Called after the value is known to be over. /// </summary> /// <param name="token">The current token.</param> /// <returns>The status.</returns> Boolean AfterValue(CssToken token) { if (token.Type == CssTokenType.Semicolon) { CloseProperty(); SwitchTo(CssState.InDeclaration); return true; } else if (token.Type == CssTokenType.CurlyBracketClose) return InDeclaration(token); return false; } /// <summary> /// Called once an important instruction is expected. /// </summary> /// <param name="token">The current token.</param> /// <returns>The status.</returns> Boolean ValueImportant(CssToken token) { if (token.Type == CssTokenType.Ident && ((CssKeywordToken)token).Data == "important") { SwitchTo(CssState.AfterValue); property.Important = true; return true; } return AfterValue(token); } /// <summary> /// Before the name of an @keyframes rule has been detected. /// </summary> /// <param name="token">The current token.</param> /// <returns>The status.</returns> Boolean BeforeKeyframesName(CssToken token) { SwitchTo(CssState.BeforeKeyframesData); if (token.Type == CssTokenType.Ident) { CurrentRuleAs<CSSKeyframesRule>().Name = ((CssKeywordToken)token).Data; return true; } else if (token.Type == CssTokenType.CurlyBracketOpen) { SwitchTo(CssState.KeyframesData); } return false; } /// <summary> /// Before the curly bracket of an @keyframes rule has been seen. /// </summary> /// <param name="token">The current token.</param> /// <returns>The status.</returns> Boolean BeforeKeyframesData(CssToken token) { if (token.Type == CssTokenType.CurlyBracketOpen) { SwitchTo(CssState.BeforeKeyframesData); return true; } return false; } /// <summary> /// Called in the @keyframes rule. /// </summary> /// <param name="token">The current token.</param> /// <returns>The status.</returns> Boolean KeyframesData(CssToken token) { if (token.Type == CssTokenType.CurlyBracketClose) { SwitchTo(CssState.Data); return CloseRule(); } else { buffer = Pool.NewStringBuilder(); return InKeyframeText(token); } } /// <summary> /// Called in the text for a frame in the @keyframes rule. /// </summary> /// <param name="token">The current token.</param> /// <returns>The status.</returns> Boolean InKeyframeText(CssToken token) { if (token.Type == CssTokenType.CurlyBracketOpen) { var frame = new CSSKeyframeRule(); frame.KeyText = buffer.ToPool(); AddRule(frame); SwitchTo(CssState.InDeclaration); return true; } else if (token.Type == CssTokenType.CurlyBracketClose) { buffer.ToPool(); KeyframesData(token); return false; } buffer.Append(token.ToValue()); return true; } /// <summary> /// Called before a document function has been found. /// </summary> /// <param name="token">The current token.</param> /// <returns>The status.</returns> Boolean BeforeDocumentFunction(CssToken token) { switch (token.Type) { case CssTokenType.Url: CurrentRuleAs<CSSDocumentRule>().Conditions.Add(Tuple.Create(CSSDocumentRule.DocumentFunction.Url, ((CssStringToken)token).Data)); break; case CssTokenType.UrlPrefix: CurrentRuleAs<CSSDocumentRule>().Conditions.Add(Tuple.Create(CSSDocumentRule.DocumentFunction.UrlPrefix, ((CssStringToken)token).Data)); break; case CssTokenType.Domain: CurrentRuleAs<CSSDocumentRule>().Conditions.Add(Tuple.Create(CSSDocumentRule.DocumentFunction.Domain, ((CssStringToken)token).Data)); break; case CssTokenType.Function: if (String.Compare(((CssKeywordToken)token).Data, "regexp", StringComparison.OrdinalIgnoreCase) == 0) { SwitchTo(CssState.InDocumentFunction); return true; } SwitchTo(CssState.AfterDocumentFunction); return false; default: SwitchTo(CssState.Data); return false; } SwitchTo(CssState.BetweenDocumentFunctions); return true; } /// <summary> /// Called before the argument of a document function has been found. /// </summary> /// <param name="token">The current token.</param> /// <returns>The status.</returns> Boolean InDocumentFunction(CssToken token) { SwitchTo(CssState.AfterDocumentFunction); if (token.Type == CssTokenType.String) { CurrentRuleAs<CSSDocumentRule>().Conditions.Add(Tuple.Create(CSSDocumentRule.DocumentFunction.RegExp, ((CssStringToken)token).Data)); return true; } return false; } /// <summary> /// Called after the arguments of a document function has been found. /// </summary> /// <param name="token">The current token.</param> /// <returns>The status.</returns> Boolean AfterDocumentFunction(CssToken token) { SwitchTo(CssState.BetweenDocumentFunctions); return token.Type == CssTokenType.RoundBracketClose; } /// <summary> /// Called after a function has been completed. /// </summary> /// <param name="token">The current token.</param> /// <returns>The status.</returns> Boolean BetweenDocumentFunctions(CssToken token) { if (token.Type == CssTokenType.Comma) { SwitchTo(CssState.BeforeDocumentFunction); return true; } else if (token.Type == CssTokenType.CurlyBracketOpen) { SwitchTo(CssState.Data); return true; } SwitchTo(CssState.Data); return false; } /// <summary> /// Before any medium has been found for the @media or @import rule. /// </summary> /// <param name="token">The current token.</param> /// <returns>The status.</returns> Boolean InMediaList(CssToken token) { if (token.Type == CssTokenType.Semicolon) { CloseRule(); SwitchTo(CssState.Data); return true; } buffer = Pool.NewStringBuilder(); SwitchTo(CssState.InMediaValue); return InMediaValue(token); } /// <summary> /// Scans the current medium for the @media or @import rule. /// </summary> /// <param name="token">The current token.</param> /// <returns>The status.</returns> Boolean InMediaValue(CssToken token) { switch (token.Type) { case CssTokenType.CurlyBracketOpen: case CssTokenType.Semicolon: { var container = CurrentRule as ICssMedia; var s = buffer.ToPool(); if (container != null) container.Media.AppendMedium(s); if (CurrentRule is CSSImportRule) return AfterInstruction(token); SwitchTo(CssState.Data); return token.Type == CssTokenType.CurlyBracketClose; } case CssTokenType.Comma: { var container = CurrentRule as ICssMedia; if (container != null) container.Media.AppendMedium(buffer.ToString()); buffer.Clear(); return true; } case CssTokenType.Whitespace: { buffer.Append(' '); return true; } default: { buffer.Append(token.ToValue()); return true; } } } #endregion #region Substates /// <summary> /// Called in a value - a delimiter has been found. /// </summary> /// <param name="token">The current delim token.</param> /// <returns>The status.</returns> Boolean InSingleValueDelim(CssDelimToken token) { switch (token.Data) { case Specification.EM: SwitchTo(CssState.ValueImportant); return true; case Specification.NUM: buffer = Pool.NewStringBuilder(); SwitchTo(CssState.InHexValue); return true; case Specification.SOLIDUS: fraction = true; return true; default: return false; } } /// <summary> /// Called in a value - an identifier has been found. /// </summary> /// <param name="token">The current keyword token.</param> /// <returns>The status.</returns> Boolean InSingleValueIdent(CssKeywordToken token) { if (token.Data == "inherit") { property.Value = CSSValue.Inherit; SwitchTo(CssState.AfterValue); return true; } return AddValue(new CSSPrimitiveValue(CssUnit.Ident, token.Data)); } /// <summary> /// Called in a value - a hash (probably hex) value has been found. /// </summary> /// <param name="token">The value of the token.</param> /// <returns>The status.</returns> Boolean InSingleValueHexColor(String color) { CSSColor value; if (CSSColor.TryFromHex(color, out value)) return AddValue(new CSSPrimitiveValue(value)); return false; } #endregion #region Rule management /// <summary> /// Adds the new value to the current value (or replaces it). /// </summary> /// <param name="value">The value to add.</param> /// <returns>The status.</returns> Boolean AddValue(CSSValue value) { if (fraction) { if (this.value != null) { value = new CSSPrimitiveValue(CssUnit.Unknown, this.value.ToCss() + "/" + value.ToCss()); this.value = null; } fraction = false; } if (function.Count > 0) function.Peek().Arguments.Add(value); else if (this.value == null) this.value = value; else return false; return true; } /// <summary> /// Closes a property. /// </summary> void CloseProperty() { if (property != null) property.Value = value; value = null; property = null; } /// <summary> /// Closes the current rule (if any). /// </summary> /// <returns>The status.</returns> Boolean CloseRule() { if (open.Count > 0) { open.Pop(); return true; } return false; } /// <summary> /// Adds a new rule. /// </summary> /// <param name="rule">The new rule.</param> void AddRule(CSSRule rule) { rule.ParentStyleSheet = sheet; if (open.Count > 0) { var container = open.Peek() as ICssRules; if (container != null) { container.CssRules.List.Add(rule); rule.ParentRule = open.Peek(); } } else sheet.CssRules.List.Add(rule); open.Push(rule); } /// <summary> /// Adds a declaration. /// </summary> /// <param name="property">The new property.</param> void AddDeclaration(CSSProperty property) { this.property = property; var rule = CurrentRule as IStyleDeclaration; if (rule != null) rule.Style.List.Add(property); } #endregion #region Helpers /// <summary> /// Gets the current rule casted to the given type. /// </summary> T CurrentRuleAs<T>() where T : CSSRule { if (open.Count > 0) return open.Peek() as T; return default(T); } /// <summary> /// Switches the current state to the given one. /// </summary> /// <param name="newState">The state to switch to.</param> void SwitchTo(CssState newState) { switch (newState) { case CssState.InSelector: tokenizer.IgnoreComments = true; tokenizer.IgnoreWhitespace = false; selector.Reset(); selector.IgnoreErrors = skipExceptions; break; case CssState.InHexValue: case CssState.InUnknown: case CssState.InCondition: case CssState.InSingleValue: case CssState.InMediaValue: tokenizer.IgnoreComments = true; tokenizer.IgnoreWhitespace = false; break; default: tokenizer.IgnoreComments = true; tokenizer.IgnoreWhitespace = true; break; } state = newState; } /// <summary> /// The kernel that is pulling the tokens into the parser. /// </summary> void Kernel() { var tokens = tokenizer.Tokens; foreach (var token in tokens) { if (General(token) == false) RaiseErrorOccurred(ErrorCode.InputUnexpected); } if (property != null) General(CssSpecialCharacter.Semicolon); selector.ToPool(); } /// <summary> /// Examines the token by using the current state. /// </summary> /// <param name="token">The current token.</param> /// <returns>The status.</returns> Boolean General(CssToken token) { switch (state) { case CssState.Data: return Data(token); case CssState.InSelector: return InSelector(token); case CssState.InDeclaration: return InDeclaration(token); case CssState.AfterProperty: return AfterProperty(token); case CssState.BeforeValue: return BeforeValue(token); case CssState.InValuePool: return InValuePool(token); case CssState.InValueList: return InValueList(token); case CssState.InSingleValue: return InSingleValue(token); case CssState.ValueImportant: return ValueImportant(token); case CssState.AfterValue: return AfterValue(token); case CssState.InMediaList: return InMediaList(token); case CssState.InMediaValue: return InMediaValue(token); case CssState.BeforeImport: return BeforeImport(token); case CssState.AfterInstruction: return AfterInstruction(token); case CssState.BeforeCharset: return BeforeCharset(token); case CssState.BeforeNamespacePrefix: return BeforePrefix(token); case CssState.AfterNamespacePrefix: return BeforeNamespace(token); case CssState.InCondition: return InCondition(token); case CssState.InUnknown: return InUnknown(token); case CssState.InKeyframeText: return InKeyframeText(token); case CssState.BeforeDocumentFunction: return BeforeDocumentFunction(token); case CssState.InDocumentFunction: return InDocumentFunction(token); case CssState.AfterDocumentFunction: return AfterDocumentFunction(token); case CssState.BetweenDocumentFunctions: return BetweenDocumentFunctions(token); case CssState.BeforeKeyframesName: return BeforeKeyframesName(token); case CssState.BeforeKeyframesData: return BeforeKeyframesData(token); case CssState.KeyframesData: return KeyframesData(token); case CssState.InHexValue: return InHexValue(token); case CssState.InFunction: return InValueFunction(token); default: return false; } } #endregion #region Public static methods /// <summary> /// Takes a string and transforms it into a selector object. /// </summary> /// <param name="selector">The string to parse.</param> /// <returns>The Selector object.</returns> public static Selector ParseSelector(String selector) { var tokenizer = new CssTokenizer(new SourceManager(selector)); var tokens = tokenizer.Tokens; var selctor = Pool.NewSelectorConstructor(); foreach (var token in tokens) selctor.Apply(token); var result = selctor.Result; selctor.ToPool(); return result; } /// <summary> /// Takes a string and transforms it into a CSS stylesheet. /// </summary> /// <param name="stylesheet">The string to parse.</param> /// <param name="quirksMode">Optional: The status of the quirks mode flag (usually not set).</param> /// <returns>The CSSStyleSheet object.</returns> public static CSSStyleSheet ParseStyleSheet(String stylesheet, Boolean quirksMode = false) { var parser = new CssParser(stylesheet); parser.IsQuirksMode = quirksMode; return parser.Result; } /// <summary> /// Takes a string and transforms it into a CSS rule. /// </summary> /// <param name="rule">The string to parse.</param> /// <param name="quirksMode">Optional: The status of the quirks mode flag (usually not set).</param> /// <returns>The CSSRule object.</returns> public static CSSRule ParseRule(String rule, Boolean quirksMode = false) { var parser = new CssParser(rule); parser.skipExceptions = false; parser.IsQuirksMode = quirksMode; parser.Parse(); if (parser.sheet.CssRules.Length > 0) return parser.sheet.CssRules[0]; return null; } /// <summary> /// Takes a string and transforms it into CSS declarations. /// </summary> /// <param name="declarations">The string to parse.</param> /// <param name="quirksMode">Optional: The status of the quirks mode flag (usually not set).</param> /// <returns>The CSSStyleDeclaration object.</returns> public static CSSStyleDeclaration ParseDeclarations(String declarations, Boolean quirksMode = false) { var decl = new CSSStyleDeclaration(); AppendDeclarations(decl, declarations, quirksMode); return decl; } /// <summary> /// Takes a string and transforms it into a CSS declaration (CSS property). /// </summary> /// <param name="declarations">The string to parse.</param> /// <param name="quirksMode">Optional: The status of the quirks mode flag (usually not set).</param> /// <returns>The CSSProperty object.</returns> public static CSSProperty ParseDeclaration(String declarations, Boolean quirksMode = false) { var parser = new CssParser(declarations); parser.state = CssState.InDeclaration; parser.IsQuirksMode = quirksMode; parser.skipExceptions = false; parser.Parse(); return parser.property; } /// <summary> /// Takes a string and transforms it into a CSS value. /// </summary> /// <param name="source">The string to parse.</param> /// <param name="quirksMode">Optional: The status of the quirks mode flag (usually not set).</param> /// <returns>The CSSValue object.</returns> public static CSSValue ParseValue(String source, Boolean quirksMode = false) { var parser = new CssParser(source); var property = new CSSProperty(String.Empty); parser.property = property; parser.IsQuirksMode = quirksMode; parser.skipExceptions = false; parser.state = CssState.BeforeValue; parser.Parse(); return property.Value; } #endregion #region Internal static methods /// <summary> /// Takes a string and transforms it into a list of CSS values. /// </summary> /// <param name="source">The string to parse.</param> /// <param name="quirksMode">Optional: The status of the quirks mode flag (usually not set).</param> /// <returns>The CSSValueList object.</returns> internal static CSSValueList ParseValueList(String source, Boolean quirksMode = false) { var parser = new CssParser(source); var list = new CSSValueList(); var property = new CSSProperty(String.Empty); property.Value = list; parser.property = property; parser.IsQuirksMode = quirksMode; parser.skipExceptions = false; parser.state = CssState.InValueList; parser.Parse(); return list; } /// <summary> /// Takes a comma separated string and transforms it into a list of CSS values. /// </summary> /// <param name="source">The string to parse.</param> /// <param name="quirksMode">Optional: The status of the quirks mode flag (usually not set).</param> /// <returns>The CSSValueList object.</returns> internal static CSSValuePool ParseMultipleValues(String source, Boolean quirksMode = false) { var parser = new CssParser(source); var pool = new CSSValuePool(); var property = new CSSProperty(String.Empty); property.Value = pool; parser.property = property; parser.IsQuirksMode = quirksMode; parser.skipExceptions = false; parser.state = CssState.InValuePool; parser.Parse(); return pool; } /// <summary> /// Takes a string and transforms it into a CSS keyframe rule. /// </summary> /// <param name="rule">The string to parse.</param> /// <param name="quirksMode">Optional: The status of the quirks mode flag (usually not set).</param> /// <returns>The CSSKeyframeRule object.</returns> internal static CSSKeyframeRule ParseKeyframeRule(String rule, Boolean quirksMode = false) { var parser = new CssParser(rule); var keyframe = new CSSKeyframeRule(); parser.AddRule(keyframe); parser.IsQuirksMode = quirksMode; parser.skipExceptions = false; parser.state = CssState.InKeyframeText; parser.Parse(); return keyframe; } /// <summary> /// Takes a string and appends all rules to the given list of properties. /// </summary> /// <param name="list">The list of css properties to append to.</param> /// <param name="declarations">The string to parse.</param> /// <param name="quirksMode">Optional: The status of the quirks mode flag (usually not set).</param> internal static void AppendDeclarations(CSSStyleDeclaration list, String declarations, Boolean quirksMode = false) { var parser = new CssParser(declarations); parser.IsQuirksMode = quirksMode; parser.skipExceptions = false; if (list.ParentRule != null) parser.AddRule(list.ParentRule); else parser.AddRule(new CSSStyleRule(list)); parser.state = CssState.InDeclaration; parser.Parse(); } #endregion #region State Enumeration /// <summary> /// The enumeration with possible state values. /// </summary> enum CssState { Data, InSelector, InDeclaration, AfterProperty, BeforeValue, InValuePool, InValueList, InSingleValue, InMediaList, InMediaValue, BeforeImport, BeforeCharset, BeforeNamespacePrefix, AfterNamespacePrefix, AfterInstruction, InCondition, BeforeKeyframesName, BeforeKeyframesData, KeyframesData, InKeyframeText, BeforeDocumentFunction, InDocumentFunction, AfterDocumentFunction, BetweenDocumentFunctions, InUnknown, ValueImportant, AfterValue, InHexValue, InFunction } /// <summary> /// A buffer for functions. /// </summary> class FunctionBuffer { #region Members String name; List<CSSValue> arguments; CSSValue value; #endregion #region ctor internal FunctionBuffer(String name) { this.arguments = new List<CSSValue>(); this.name = name; } #endregion #region Properties public List<CSSValue> Arguments { get { return arguments; } } public CSSValue Value { get { return value; } set { this.value = value; } } #endregion #region Methods public void Include() { if (value != null) arguments.Add(value); value = null; } public CSSValue Done() { Include(); return CSSFunction.Create(name, arguments); } #endregion } #endregion } }