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 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); }