/// <summary> /// Adds all the type checkers to the given scanner. /// They basically automatically find CSS properties, units etc in assemblies. /// </summary> public static void AddToScanner(Modular.AssemblyScanner scanner) { // CSS functions: scanner.FindAllSubTypes(typeof(CssFunction), delegate(Type type){ // Add it: CssFunctions.Add(type); }); // CSS at rules: scanner.FindAllSubTypes(typeof(CssAtRule), delegate(Type type){ // Add it: CssAtRules.Add(type); }); // CSS units: scanner.FindAllSubTypes(typeof(CssUnit), delegate(Type type){ // Add it: CssUnits.Add(type); }); // CSS keywords: scanner.FindAllSubTypes(typeof(CssKeyword), delegate(Type type){ // Add it: CssKeywords.Add(type); }); // CSS properties (secondary pass; requires default values which can be any of the above): scanner.FindAllSubTypes(1, typeof(CssProperty), delegate(Type type){ // Add it: CssProperties.Add(type); }); }
/// <summary>Reads a single selector matcher. E.g. :hover or .test</summary> public static SelectorMatcher ReadSelectorMatcher(Css.Value value) { if (value == null) { return(null); } if (value.GetType() == typeof(SquareBracketUnit)) { // [attr]. Make the match object: return((value as SquareBracketUnit).GetMatch()); } // Selector fragment. string text = value[0].Text; RootMatcher result = null; if (text == "#") { // ID selector next. string id = value[1].Text; // Create ID root: result = new RootIDMatcher(); result.Text = id; return(result); } else if (text == ".") { // Class selector next. string className = value[1].Text; // Create class root: result = new RootClassMatcher(); result.Text = className; return(result); } else if (text == "*") { // Everything: return(new RootUniversalMatcher()); } else if (text == ":") { // Pseudo class or function next. May also be another colon. // after and before map to actual style properties. Css.Value current = value[1]; if (current != null && !current.IsFunction) { string keywordText = current.Text; // Second colon? if (keywordText == ":") { // Skip the double colon. current = value[2]; keywordText = current.Text; } // Keyword? current = CssKeywords.Get(keywordText.ToLower()); } if (current == null) { // Selector failure - return a blank one. return(null); } // Convert the value into a matcher: return(current.GetSelectorMatcher()); } // It's just a tag: string tag = text.ToLower(); // Create tag root: result = new RootTagMatcher(); result.Text = tag; return(result); }
private static Selector LoadSingleSelector(StyleSheet sheet, Css.Value value, List <Selector> all) { // Create the selector: Selector selector = new Selector(); selector.Value = value; if (sheet != null) { // Default NS: selector.Namespace = sheet.Namespace; } List <LocalMatcher> locals = null; List <RootMatcher> roots = new List <RootMatcher>(); RootMatcher currentRoot = null; // Current state: bool nextTarget = false; bool wasWhitespace = false; int max = value.Count; SelectorMatcher addMatcher = null; RootMatcher addRoot = null; // For each selector fragment.. for (int i = 0; i < max; i++) { Css.Value current = value[i]; if (current == null) { // Happens right at the end where the block was. continue; } if (current.GetType() == typeof(SquareBracketUnit)) { // [attr] SquareBracketUnit attrib = current as SquareBracketUnit; // Make the match object: AttributeMatch match = attrib.GetMatch(); addMatcher = match; } else if (current.Type == ValueType.Text) { // Selector fragment. string text = current.Text; if (text == " ") { wasWhitespace = true; continue; } if (text == "#") { // ID selector next. i++; string id = value[i].Text; // Create ID root: addRoot = new RootIDMatcher(); addRoot.Text = id; } else if (text == ".") { // Class selector next. i++; string className = value[i].Text; // Create class root: addRoot = new RootClassMatcher(); addRoot.Text = className; // Note that if you chain multiple class selectors (.a.b) // This is called twice and PreviousMatcher/NextMatcher remain null. // It then simply doesn't change element (which is what we want!). } else if (text == "*") { // Apply last locals: if (currentRoot != null && locals != null) { currentRoot.SetLocals(locals); locals.Clear(); } // Everything: currentRoot = CreateUniversal(roots); } else if (text == ":") { // Pseudo class or function next. May also be another colon. // after and before map to actual style properties. i++; current = value[i]; if (current != null && !current.IsFunction) { string keywordText = current.Text; // Second colon? if (keywordText == ":") { // Skip the double colon. i++; current = value[i]; keywordText = current.Text; } // Keyword? current = CssKeywords.Get(keywordText.ToLower()); } if (current == null) { // Selector failure - return a blank one. if (all != null) { all.Clear(); } return(null); } // Convert the value into a matcher: SelectorMatcher sm = current.GetSelectorMatcher(); if (sm == null) { // Selector failure - return a blank one. if (all != null) { all.Clear(); } return(null); } // Add it! addMatcher = sm; } else if (text == ">") { // Following "thing" being a direct child. addMatcher = new DirectParentMatch(); } else if (text == ">>") { // Following "thing" being a descendant (same as space). addMatcher = new ParentMatch(); } else if (text == "|") { // Namespace declaration was before. // Selector's namespace name is the latest root: string nsName = currentRoot.Text; // Pop the root: roots.RemoveAt(roots.Count - 1); if (roots.Count > 0) { currentRoot = roots[roots.Count - 1]; } else { currentRoot = null; } // Note: It's null for * which we can just ignore. if (nsName != null && sheet != null && sheet.Namespaces != null) { // Get the namespace: CssNamespace ns; sheet.Namespaces.TryGetValue(nsName.ToLower(), out ns); selector.Namespace = ns; } } else if (text == "!") { // CSS selectors level 4 selector target. nextTarget = true; continue; } else if (text == "+") { // Whenever this follows the following "thing" directly. addMatcher = new DirectPreviousSiblingMatch(); } else if (text == "~") { // Whenever this follows the following "thing". addMatcher = new PreviousSiblingMatch(); } else { // It's just a tag: string tag = text.ToLower(); // Create tag root: addRoot = new RootTagMatcher(); addRoot.Text = tag; } } if (addRoot != null) { if (currentRoot != null && locals != null) { currentRoot.SetLocals(locals); locals.Clear(); } if (currentRoot != null && wasWhitespace && currentRoot.NextMatcher == null) { // Space before it! // Following "thing" being a descendant. currentRoot.NextMatcher = new ParentMatch(); currentRoot.NextMatcher.Selector = selector; wasWhitespace = false; } // Add to root set: roots.Add(addRoot); currentRoot = addRoot; // Set selector: addRoot.Selector = selector; // Clear: addRoot = null; } if (addMatcher != null) { // Always clear whitespace: wasWhitespace = false; // Create implicit *: if (roots.Count == 0) { if (currentRoot != null && locals != null) { currentRoot.SetLocals(locals); locals.Clear(); } currentRoot = CreateUniversal(roots); } // Set selector: addMatcher.Selector = selector; if (addMatcher is StructureMatcher) { // Structural matcher: currentRoot.NextMatcher = addMatcher as StructureMatcher; } else if (addMatcher is PseudoSelectorMatch) { // Pseudo matcher: selector.PseudoElement = addMatcher as PseudoSelectorMatch; } else if (addMatcher is LocalMatcher) { if (locals == null) { locals = new List <LocalMatcher>(); } // Local matcher: locals.Add(addMatcher as LocalMatcher); } addMatcher = null; } if (nextTarget && currentRoot != null) { nextTarget = false; // Update target: currentRoot.IsTarget = true; selector.Target = currentRoot; } } // Apply last locals: if (currentRoot != null && locals != null) { currentRoot.SetLocals(locals); locals.Clear(); } // Always ensure at least 1: if (roots.Count == 0) { // *: CreateUniversal(roots); } // Update selector info now: selector.Roots = roots.ToArray(); selector.RootCount = roots.Count; selector.LastRoot = roots[roots.Count - 1]; selector.FirstRoot = roots[0]; // Set target if needed: if (selector.Target == null) { // Last root is the target: selector.Target = selector.LastRoot; selector.Target.IsTarget = true; } // Go through them backwards applying PreviousMatcher too: // (Not including 0). for (int i = roots.Count - 1; i > 0; i--) { // Hook up: roots[i].PreviousMatcher = roots[i - 1].NextMatcher; } if (sheet != null) { // Compute specifity now: selector.GetSpecifity(sheet.Priority); } // Add to all set, if there is one: if (all != null && all.Count == 0) { all.Add(selector); } return(selector); }
/// <summary>Reads a single value from the stream.</summary> public Css.Value ReadSingleValue() { Css.Value result = null; // Skip whitespaces. SkipJunk(); char current = Peek(); if (SelectorMode && current == '.') { Read(); // Class selector. return(new TextUnit(".")); } int charCode = (int)current; CssUnitHandlers set; if (CssUnits.AllStart.TryGetValue(current, out set)) { // Match on the pretext: result = set.Handle(this, false); if (result != null) { // Read the value: result = result.ReadStartValue(this); if (result == null) { return(null); } // Call ready: result.OnValueReady(this); // Ok! return(result); } } // Number: int numberStart = Position; while (charCode == (int)'-' || charCode == '+' || (charCode >= (int)'0' && charCode <= (int)'9') || charCode == (int)'.') { if (numberStart == Position) { // Special case if it's just +, . or - // Prefixed keywords fall through here too. if (charCode == (int)'.') { // Must be 0-9 next: int next = (int)Peek(1); if (next < (int)'0' || next > (int)'9') { // Non-numeric. break; } } else if (charCode == (int)'+' || charCode == (int)'-') { // Peek the next char. It must be either . or 0-9: int next = (int)Peek(1); if (next != (int)'.' && (next < (int)'0' || next > (int)'9')) { // Non-numeric. break; } } } // Read off: Read(); // Go to the next one: current = Peek(); // Get the code: charCode = (int)current; } if (numberStart != Position) { // Read a number of some kind. Now look for the units, if there are any. string num = Input.Substring(numberStart, Position - numberStart); if (CssUnits.AllEnd.TryGetValue(current, out set) && !SelectorMode) { // Units handler is likely! result = set.Handle(this, true); if (result != null) { // Return the result: result = result.Copy(); // Apply the number: float flt; float.TryParse(num, out flt); // Set: result.SetRawDecimal(flt); // Call ready: result.OnValueReady(this); return(result); } } else { // Apply the number: float nFlt; float.TryParse(num, out nFlt); // Create: result = new DecimalUnit(); // Set: result.SetRawDecimal(nFlt); // Call ready: result.OnValueReady(this); return(result); } } if (charCode == '\\') { string characters = ""; while (true) { // Special case here; we have one or more unicode escaped characters. Read(); current = Peek(); charCode = (int)current; int res = 0; for (int i = 0; i < 6; i++) { if (charCode >= (int)'0' && charCode <= (int)'9') { Read(); // Apply to charcode: res = (res << 4) | (charCode - (int)'0'); // Move on: current = Peek(); charCode = (int)current; } else if (charCode >= (int)'a' && charCode <= (int)'f') { Read(); // Apply to charcode: res = (res << 4) | (charCode + 10 - (int)'a'); // Move on: current = Peek(); charCode = (int)current; } else { // No longer valid unicode. break; } } characters += char.ConvertFromUtf32(res); if (charCode != '\\') { // Go again otherwise! break; } } return(new TextUnit(characters)); } // E.g. textual keyword or function comes down here. // If we spot a (, call ReadValue() to get the parameter set. int textStart = Position; while (charCode != 0 && (charCode > 128 || charCode == 45 || charCode == 40 || (95 <= charCode && charCode <= 122) || (65 <= charCode && charCode <= 90) || (48 <= charCode && charCode <= 57)) ) { if (current == '(') { // Got a function name (if there is one). string name = Input.Substring(textStart, Position - textStart); result = ReadFunction(name); return(result); } Read(); current = Peek(); charCode = (int)current; } if (textStart == Position && charCode != 0) { // The only thing we've read is a delimiter. if (current == '\r' || current == '\n' || current == ')' || current == ' ' || current == '}' || current == '{' || current == ';' || current == ',' ) { // Handled elsewhere. Ignore these. // They all terminate upper level value readers (or are junk!). } else { // Add the single character into the buffer. Read(); if (current == '$' || current == '*' || current == '^' || current == '~' || current == '|') { // Followed by equals? if (Peek() == '=') { // Yep - include that in this same unit. Read(); return(new TextUnit(current + "=")); } } else if (current == '>') { // Followed by another? if (Peek() == '>') { // Yep - include that in this same unit. Read(); return(new TextUnit(">>")); } } return(new TextUnit(current.ToString())); } } // Text or keyword. string text = Input.Substring(textStart, Position - textStart); // Must not match keywords/ colours when in selector mode // (because keywords are always lowercase and colours act like *): if (SelectorMode) { // Just text: return(new TextUnit(text)); } // Keyword tests. string keyword = text.ToLower(); // Colour? bool wasColour; UnityEngine.Color32 col = Css.ColourMap.GetColourByName(keyword, out wasColour); if (wasColour) { // It's a colour: ColourUnit cResult = new ColourUnit(col); // Call ready: cResult.OnValueReady(this); return(cResult); } // Global keyword? result = CssKeywords.Get(keyword); if (result != null) { // Keyword! Can't share the global instance because of specifity. return(result.Copy()); } // Just treat as some text otherwise: return(new TextUnit(text)); }