/// <summary>True if the given root matchers are equal.</summary> public bool Equals(RootMatcher rm) { // Reference match: if (rm == this) { return(true); } // Type and text match: if (GetType() != rm.GetType() || Text != rm.Text) { return(false); } // Local matchers next. if (LocalMatchers != null) { if (rm.LocalMatchers == null || rm.LocalMatchers.Length != LocalMatchers.Length) { return(false); } // Compare each local matcher: for (int i = 0; i < LocalMatchers.Length; i++) { // Match? if (LocalMatchers[i].Equals(LocalMatchers[i])) { return(false); } } } else if (rm.LocalMatchers != null) { return(false); } return(true); }
/// <summary>True if this selector matches the structure of the DOM where the given CS is.</summary> public bool StructureMatch(ComputedStyle cs, CssEvent e) { // Get the node: Node node = cs.Element; if (node == null) { return(false); } // Apply target - this helps track which element we're actually testing: e.CurrentNode = node; // We always start from the tail and work backwards. // If we get a match, then the caller can do whatever it wants to the target. for (int i = RootCount - 1; i >= 0; i--) { // Get the matcher: RootMatcher rm = Roots[i]; // Try matching this root: if (!rm.TryMatch(e.CurrentNode)) { // Failed! If we had a matcher and it has Repeat set true, try again: if (rm.NextMatcher != null && rm.NextMatcher.Repeat) { // Move target: rm.NextMatcher.MoveUpwards(e); // Still got a node? if (e.CurrentNode == null) { return(false); } // Try matching again: i++; continue; } return(false); } // If we have a structure matcher, run it now. It'll move CurrentNode for us: if (rm.PreviousMatcher != null) { // Move target: rm.PreviousMatcher.MoveUpwards(e); // Still got a node? if (e.CurrentNode == null) { return(false); } } } // If we have a pseudo element, make sure parents haven't also matched this selector. if (PseudoElement != null) { // Have any parents matched this selector? Node parent = node.parentNode; while (parent != null) { if (parent["spark-virt"] != null) { // Already on a virtual element - quit there. return(false); } parent = parent.parentNode; } } // All clear! return(true); }
/// <summary>Applies a structurally matched selector to the DOM. /// Occurs shortly after StructureMatch.</summary> public MatchingSelector BakeToTarget(ComputedStyle cs, CssEvent e) { // Get the node: Node node = cs.Element; // First, generate our instance: MatchingSelector ms = new MatchingSelector(); // Update it: ms.Selector = this; ms.MatchedRoots = new MatchingRoot[RootCount]; // For each root, create a MatchingRoot object. // Apply target - this helps track which element we're actually testing: e.CurrentNode = node; e.SelectorTarget = null; // We always start from the tail and work backwards. // If we get a match, then the caller can do whatever it wants to the target. for (int i = RootCount - 1; i >= 0; i--) { // Get the matcher: RootMatcher rm = Roots[i]; // Try matching this root: if (!rm.TryMatch(e.CurrentNode)) { // Failed! If we had a matcher and it has Repeat set true, try again: if (rm.NextMatcher != null && rm.NextMatcher.Repeat) { // Move target: rm.NextMatcher.MoveUpwards(e); // Try matching again: i++; continue; } } else { // Match! e.CurrentNode is the node to add. // Create the instance: MatchingRoot matchedRoot = new MatchingRoot(); matchedRoot.Root = rm; matchedRoot.Selector = ms; matchedRoot.Node = e.CurrentNode; // Get renderable node: IRenderableNode renderable = (e.CurrentNode as IRenderableNode); // Add to selector: ms.MatchedRoots[i] = matchedRoot; // Add: ComputedStyle nodeCs = renderable.ComputedStyle; // Push the match now into the linked list: if (nodeCs.FirstMatch == null) { nodeCs.FirstMatch = matchedRoot; nodeCs.LastMatch = matchedRoot; } else { matchedRoot.PreviousInStyle = nodeCs.LastMatch; nodeCs.LastMatch.NextInStyle = matchedRoot; nodeCs.LastMatch = matchedRoot; } if (rm.IsTarget) { // Update the target now: e.SelectorTarget = renderable.RenderData; } } // If we have a structure matcher, run it now. It'll move CurrentNode for us: if (rm.PreviousMatcher != null) { // Move target: rm.PreviousMatcher.MoveUpwards(e); } } // Final pass - if we have a pseudo-element, apply it now: if (PseudoElement != null) { PseudoElement.Select(e); } // Apply target: ms.Target = e.SelectorTarget; // Finally, refresh all: ms.ResetActive(); return(ms); }
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); }