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 function; Boolean skipExceptions; Lexer tokenizer; Boolean fraction; CSSProperty property; CSSValue value; List mvalues; CSSValueList cvalues; Boolean started; Boolean quirks; CSSStyleSheet sheet; Stack open; StringBuilder buffer; CssState state; Object sync; Task task; #region ctor /// /// Creates a new CSS parser instance parser with the specified stylesheet /// based on the given source manager. /// /// The stylesheet to be constructed. /// The source to use. internal CssParser(StylesheetReader reader) { selector = Pool.NewSelectorConstructor(); sync = new Object(); skipExceptions = true; tokenizer = new Lexer(reader); started = false; function = new Stack(); sheet = stylesheet; open = new Stack(); SwitchTo(CssState.Data); } #endregion #region Properties /// /// Gets if the parser has been started asynchronously. /// public Boolean IsAsync { get { return task != null; } } /// /// Gets or sets if the quirks-mode is activated. /// public Boolean IsQuirksMode { get { return quirks; } set { quirks = value; } } /// /// Gets the current rule if any. /// internal CSSRule CurrentRule { get { return open.Count > 0 ? open.Peek() : null; } } #endregion #region Methods /// /// Parses the given source asynchronously and creates the stylesheet. /// /// The task which could be awaited or continued differently. 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; } } /// /// Parses the given source code. /// public void Parse() { var run = false; lock (sync) { if (!started) { started = true; run = true; } } if (run) { Kernel(); } } #endregion #region States /// /// The general state. /// /// The current token. /// The status. 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; } } /// /// State that is called once in the head of an unknown @ rule. /// /// The current token. /// The status. Boolean InUnknown(CssToken token) { switch (token.Type) { case CssTokenType.Semicolon: CurrentRuleAs().SetInstruction(buffer.ToPool()); SwitchTo(CssState.Data); return CloseRule(); case CssTokenType.CurlyBracketOpen: CurrentRuleAs().SetCondition(buffer.ToPool()); SwitchTo(CssState.Data); break; default: buffer.Append(token.ToValue()); break; } return true; } /// /// State that is called once we are in a CSS selector. /// /// The current token. /// The status. 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; } /// /// Called before the property name has been detected. /// /// The current token. /// The status. 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; } /// /// After instruction rules a semicolon is required. /// /// The current token. /// The status. Boolean AfterInstruction(CssToken token) { if (token.Type == CssTokenType.Semicolon) { SwitchTo(CssState.Data); return CloseRule(); } return false; } /// /// In the condition text of a supports rule. /// /// The current token. /// The status. Boolean InCondition(CssToken token) { switch (token.Type) { case CssTokenType.CurlyBracketOpen: CurrentRuleAs().ConditionText = buffer.ToPool(); SwitchTo(CssState.Data); break; default: buffer.Append(token.ToValue()); break; } return true; } /// /// Called before a prefix has been found for the namespace rule. /// /// The current token. /// The status. Boolean BeforePrefix(CssToken token) { if (token.Type == CssTokenType.Ident) { CurrentRuleAs().Prefix = ((CssKeywordToken)token).Data; SwitchTo(CssState.AfterNamespacePrefix); return true; } SwitchTo(CssState.AfterInstruction); return AfterInstruction(token); } /// /// Called before a namespace has been found for the namespace rule. /// /// The current token. /// The status. Boolean BeforeNamespace(CssToken token) { SwitchTo(CssState.AfterInstruction); if (token.Type == CssTokenType.String) { CurrentRuleAs().NamespaceURI = ((CssStringToken)token).Data; return true; } return AfterInstruction(token); } /// /// Before a charset string has been found. /// /// The current token. /// The status. Boolean BeforeCharset(CssToken token) { SwitchTo(CssState.AfterInstruction); if (token.Type == CssTokenType.String) { CurrentRuleAs().Encoding = ((CssStringToken)token).Data; return true; } return AfterInstruction(token); } /// /// Before an URL has been found for the import rule. /// /// The current token. /// The status. Boolean BeforeImport(CssToken token) { if (token.Type == CssTokenType.String || token.Type == CssTokenType.Url) { CurrentRuleAs().Href = ((CssStringToken)token).Data; SwitchTo(CssState.InMediaList); return true; } SwitchTo(CssState.AfterInstruction); return false; } /// /// Called before the property separating colon has been seen. /// /// The current token. /// The status. 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; } /// /// Called before any token in the value regime had been seen. /// /// The current token. /// The status. 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; } /// /// Called when a value has to be computed. /// /// The current token. /// The status. 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; } } /// /// Gathers a value inside a function. /// /// The current token. /// The status. 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); } } /// /// Called when a new value is seen from the zero-POV (whitespace seen previously). /// /// The current token. /// The status. 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; } /// /// Called when a new value is seen from the zero-POV (comma seen previously). /// /// The current token. /// The status. 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; } /// /// Called if a # sign has been found. /// /// The current token. /// The status. 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); } /// /// Called after the value is known to be over. /// /// The current token. /// The status. 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; } /// /// Called once an important instruction is expected. /// /// The current token. /// The status. Boolean ValueImportant(CssToken token) { if (token.Type == CssTokenType.Ident && ((CssKeywordToken)token).Data == "important") { SwitchTo(CssState.AfterValue); property.Important = true; return true; } return AfterValue(token); } /// /// Before the name of an @keyframes rule has been detected. /// /// The current token. /// The status. Boolean BeforeKeyframesName(CssToken token) { SwitchTo(CssState.BeforeKeyframesData); if (token.Type == CssTokenType.Ident) { CurrentRuleAs().Name = ((CssKeywordToken)token).Data; return true; } else if (token.Type == CssTokenType.CurlyBracketOpen) { SwitchTo(CssState.KeyframesData); } return false; } /// /// Before the curly bracket of an @keyframes rule has been seen. /// /// The current token. /// The status. Boolean BeforeKeyframesData(CssToken token) { if (token.Type == CssTokenType.CurlyBracketOpen) { SwitchTo(CssState.BeforeKeyframesData); return true; } return false; } /// /// Called in the @keyframes rule. /// /// The current token. /// The status. Boolean KeyframesData(CssToken token) { if (token.Type == CssTokenType.CurlyBracketClose) { SwitchTo(CssState.Data); return CloseRule(); } else { buffer = Pool.NewStringBuilder(); return InKeyframeText(token); } } /// /// Called in the text for a frame in the @keyframes rule. /// /// The current token. /// The status. 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; } /// /// Called before a document function has been found. /// /// The current token. /// The status. Boolean BeforeDocumentFunction(CssToken token) { switch (token.Type) { case CssTokenType.Url: CurrentRuleAs().Conditions.Add(Tuple.Create(CSSDocumentRule.DocumentFunction.Url, ((CssStringToken)token).Data)); break; case CssTokenType.UrlPrefix: CurrentRuleAs().Conditions.Add(Tuple.Create(CSSDocumentRule.DocumentFunction.UrlPrefix, ((CssStringToken)token).Data)); break; case CssTokenType.Domain: CurrentRuleAs().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; } /// /// Called before the argument of a document function has been found. /// /// The current token. /// The status. Boolean InDocumentFunction(CssToken token) { SwitchTo(CssState.AfterDocumentFunction); if (token.Type == CssTokenType.String) { CurrentRuleAs().Conditions.Add(Tuple.Create(CSSDocumentRule.DocumentFunction.RegExp, ((CssStringToken)token).Data)); return true; } return false; } /// /// Called after the arguments of a document function has been found. /// /// The current token. /// The status. Boolean AfterDocumentFunction(CssToken token) { SwitchTo(CssState.BetweenDocumentFunctions); return token.Type == CssTokenType.RoundBracketClose; } /// /// Called after a function has been completed. /// /// The current token. /// The status. 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; } /// /// Before any medium has been found for the @media or @import rule. /// /// The current token. /// The status. Boolean InMediaList(CssToken token) { if (token.Type == CssTokenType.Semicolon) { CloseRule(); SwitchTo(CssState.Data); return true; } buffer = Pool.NewStringBuilder(); SwitchTo(CssState.InMediaValue); return InMediaValue(token); } /// /// Scans the current medium for the @media or @import rule. /// /// The current token. /// The status. 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 /// /// Called in a value - a delimiter has been found. /// /// The current delim token. /// The status. 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; } } /// /// Called in a value - an identifier has been found. /// /// The current keyword token. /// The status. 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)); } /// /// Called in a value - a hash (probably hex) value has been found. /// /// The value of the token. /// The status. Boolean InSingleValueHexColor(String color) { CSSColor value; if (CSSColor.TryFromHex(color, out value)) return AddValue(new CSSPrimitiveValue(value)); return false; } #endregion #region Rule management /// /// Adds the new value to the current value (or replaces it). /// /// The value to add. /// The status. 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; } /// /// Closes a property. /// void CloseProperty() { if (property != null) property.Value = value; value = null; property = null; } /// /// Closes the current rule (if any). /// /// The status. Boolean CloseRule() { if (open.Count > 0) { open.Pop(); return true; } return false; } /// /// Adds a new rule. /// /// The new rule. 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); } /// /// Adds a declaration. /// /// The new property. void AddDeclaration(CSSProperty property) { this.property = property; var rule = CurrentRule as IStyleDeclaration; if (rule != null) rule.Style.List.Add(property); } #endregion #region Helpers /// /// Gets the current rule casted to the given type. /// T CurrentRuleAs() where T : CSSRule { if (open.Count > 0) return open.Peek() as T; return default(T); } /// /// Switches the current state to the given one. /// /// The state to switch to. 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; } /// /// The kernel that is pulling the tokens into the parser. /// 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(); } /// /// Examines the token by using the current state. /// /// The current token. /// The status. 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 /// /// Takes a string and transforms it into a selector object. /// /// The string to parse. /// The Selector object. 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; } /// /// Takes a string and transforms it into a CSS stylesheet. /// /// The string to parse. /// Optional: The status of the quirks mode flag (usually not set). /// The CSSStyleSheet object. public static CSSStyleSheet ParseStyleSheet(String stylesheet, Boolean quirksMode = false) { var parser = new CssParser(stylesheet); parser.IsQuirksMode = quirksMode; return parser.Result; } /// /// Takes a string and transforms it into a CSS rule. /// /// The string to parse. /// Optional: The status of the quirks mode flag (usually not set). /// The CSSRule object. 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; } /// /// Takes a string and transforms it into CSS declarations. /// /// The string to parse. /// Optional: The status of the quirks mode flag (usually not set). /// The CSSStyleDeclaration object. public static CSSStyleDeclaration ParseDeclarations(String declarations, Boolean quirksMode = false) { var decl = new CSSStyleDeclaration(); AppendDeclarations(decl, declarations, quirksMode); return decl; } /// /// Takes a string and transforms it into a CSS declaration (CSS property). /// /// The string to parse. /// Optional: The status of the quirks mode flag (usually not set). /// The CSSProperty object. 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; } /// /// Takes a string and transforms it into a CSS value. /// /// The string to parse. /// Optional: The status of the quirks mode flag (usually not set). /// The CSSValue object. 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 /// /// Takes a string and transforms it into a list of CSS values. /// /// The string to parse. /// Optional: The status of the quirks mode flag (usually not set). /// The CSSValueList object. 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; } /// /// Takes a comma separated string and transforms it into a list of CSS values. /// /// The string to parse. /// Optional: The status of the quirks mode flag (usually not set). /// The CSSValueList object. 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; } /// /// Takes a string and transforms it into a CSS keyframe rule. /// /// The string to parse. /// Optional: The status of the quirks mode flag (usually not set). /// The CSSKeyframeRule object. 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; } /// /// Takes a string and appends all rules to the given list of properties. /// /// The list of css properties to append to. /// The string to parse. /// Optional: The status of the quirks mode flag (usually not set). 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 /// /// The enumeration with possible state values. /// 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 } /// /// A buffer for functions. /// class FunctionBuffer { #region Members String name; List arguments; CSSValue value; #endregion #region ctor internal FunctionBuffer(String name) { this.arguments = new List(); this.name = name; } #endregion #region Properties public List 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 } }