/// <summary> /// Select from DOM using index. First non-class/tag/id selector will result in this being passed off to GetMatches /// </summary> /// <param name="document"></param> /// <returns></returns> public IEnumerable <IDomObject> Select(IDomDocument document, IEnumerable <IDomObject> context) { if (Selectors == null) { throw new ArgumentException("No selectors provided."); } if (Selectors.Count == 0) { yield break; } Document = document; IEnumerable <IDomObject> lastResult = null; HashSet <IDomObject> output = new HashSet <IDomObject>(); IEnumerable <IDomObject> selectionSource = context; // Disable the index if there is no context (e.g. disconnected elements) bool useIndex = context.IsNullOrEmpty() || !context.First().IsDisconnected; // Copy the list because it may change during the process ActiveSelectors = new List <Selector>(Selectors); for (activeSelectorId = 0; activeSelectorId < ActiveSelectors.Count; activeSelectorId++) { var selector = ActiveSelectors[activeSelectorId]; CombinatorType combinatorType = selector.CombinatorType; SelectorType selectorType = selector.SelectorType; TraversalType traversalType = selector.TraversalType; // Determine what kind of combining method we will use with previous selection results if (activeSelectorId != 0) { switch (combinatorType) { case CombinatorType.Cumulative: // do nothing break; case CombinatorType.Root: selectionSource = context; if (lastResult != null) { output.AddRange(lastResult); lastResult = null; } break; case CombinatorType.Chained: selectionSource = lastResult; lastResult = null; break; // default (chained): leave lastresult alone } } HashSet <IDomObject> tempResult = null; IEnumerable <IDomObject> interimResult = null; string key = ""; if (useIndex && !selector.NoIndex) { #if DEBUG_PATH if (type.HasFlag(SelectorType.Attribute)) { key = "!" + selector.AttributeName; type &= ~SelectorType.Attribute; if (selector.AttributeValue != null) { InsertAttributeValueSelector(selector); } } else if (type.HasFlag(SelectorType.Tag)) { key = "+" + selector.Tag; type &= ~SelectorType.Tag; } else if (type.HasFlag(SelectorType.ID)) { key = "#" + selector.ID; type &= ~SelectorType.ID; } else if (type.HasFlag(SelectorType.Class)) { key = "." + selector.Class; type &= ~SelectorType.Class; } #else if (selectorType.HasFlag(SelectorType.Attribute)) { key = "!" + (char)DomData.TokenID(selector.AttributeName); selectorType &= ~SelectorType.Attribute; if (selector.AttributeValue != null) { InsertAttributeValueSelector(selector); } } else if (selectorType.HasFlag(SelectorType.Tag)) { key = "+" + (char)DomData.TokenID(selector.Tag, true); selectorType &= ~SelectorType.Tag; } else if (selectorType.HasFlag(SelectorType.ID)) { key = "#" + (char)DomData.TokenID(selector.ID); selectorType &= ~SelectorType.ID; } else if (selectorType.HasFlag(SelectorType.Class)) { key = "." + (char)DomData.TokenID(selector.Class); selectorType &= ~SelectorType.Class; } #endif } // If part of the selector was indexed, key will not be empty. Return initial set from the // index. If any selectors remain after this they will be searched the hard way. if (key != String.Empty) { int depth = 0; bool descendants = true; switch (traversalType) { case TraversalType.Child: depth = selector.ChildDepth;; descendants = false; break; case TraversalType.Filter: depth = 0; descendants = false; break; case TraversalType.Descendent: depth = 1; descendants = true; break; } if (selectionSource == null) { interimResult = document.QueryIndex(key + DomData.indexSeparator, depth, descendants); } else { interimResult = new HashSet <IDomObject>(); foreach (IDomObject obj in selectionSource) { ((HashSet <IDomObject>)interimResult) .AddRange(document.QueryIndex(key + DomData.indexSeparator + obj.Path, depth, descendants)); } } } else if (selectorType.HasFlag(SelectorType.Elements)) { selectorType &= ~SelectorType.Elements; HashSet <IDomObject> source = new HashSet <IDomObject>(selectionSource); interimResult = new HashSet <IDomObject>(); foreach (IDomObject obj in selectionSource) { key = DomData.indexSeparator + obj.Path; HashSet <IDomObject> srcKeys = new HashSet <IDomObject>(document.QueryIndex(key)); foreach (IDomObject match in selector.SelectElements) { if (srcKeys.Contains(match)) { ((HashSet <IDomObject>)interimResult).Add(match); } } } } // TODO - GetMatch should work if passed with no selectors (returning nothing), now it returns everything // 12/10/11 - this todo is not verified, much has changed since it was written. TODO confirm this and // fix if needed. If having the conversation with self again, remove comments and forget it. This is // an example of why comments can do more harm than good. if ((selectorType & ~(SelectorType.SubSelectorNot | SelectorType.SubSelectorHas)) != 0) { IEnumerable <IDomObject> finalSelectWithin = interimResult ?? (combinatorType == CombinatorType.Chained ? lastResult : null) ?? selectionSource ?? document.ChildElements; // if there are no temporary results (b/c there was no indexed selector) then use the whole set interimResult = GetMatches(finalSelectWithin, selector); } // Deal with subselectors: has() and not() test for the presence of a selector within the children of // an element. This is essentially similar to the manual selection above. if (selectorType.HasFlag(SelectorType.SubSelectorHas) || selectorType.HasFlag(SelectorType.SubSelectorNot)) { bool isHasSelector = selectorType.HasFlag(SelectorType.SubSelectorHas); IEnumerable <IDomObject> subSelectWithin = interimResult ?? (combinatorType == CombinatorType.Chained ? lastResult : null) ?? selectionSource; // subselects are a filter. start a new interim result. HashSet <IDomObject> filteredResults = new HashSet <IDomObject>(); foreach (IDomObject obj in subSelectWithin) { bool match = true; foreach (var sub in selector.SubSelectors) { List <IDomObject> listOfOne = new List <IDomObject>(); listOfOne.Add(obj); bool has = !sub.Select(document, listOfOne).IsNullOrEmpty(); match &= isHasSelector == has; } if (match) { filteredResults.Add(obj); } } interimResult = filteredResults; } tempResult = new HashSet <IDomObject>(); if (lastResult != null) { tempResult.AddRange(lastResult); } if (interimResult != null) { tempResult.AddRange(interimResult); } lastResult = tempResult; } if (lastResult != null) { output.AddRange(lastResult); } if (output.IsNullOrEmpty()) { yield break; } else { // Selectors always return in DOM order. Selections may end up in a different order but // we always sort here. foreach (IDomObject item in output.OrderBy(item => item.Path, StringComparer.Ordinal)) { yield return(item); } } ActiveSelectors.Clear(); }
/// <summary> /// Returns a string representation of the parsed selector. This may not exactly match the input /// selector as it is regenerated. /// </summary> /// /// <returns> /// A CSS selector string. /// </returns> public override string ToString() { string output = ""; switch (TraversalType) { case TraversalType.Child: output += " > "; break; case TraversalType.Descendent: output += " "; break; case TraversalType.Adjacent: output += " + "; break; case TraversalType.Sibling: output += " ~ "; break; } if (SelectorType.HasFlag(SelectorType.Elements)) { output += "<ElementList[" + SelectElements.Count() + "]> "; } if (SelectorType.HasFlag(SelectorType.HTML)) { output += "<HTML[" + Html.Length + "]> "; } if (SelectorType.HasFlag(SelectorType.Tag)) { output += Tag; } if (SelectorType.HasFlag(SelectorType.ID)) { output += "#" + ID; } if (SelectorType.HasFlag(SelectorType.AttributeValue) //|| SelectorType.HasFlag(SelectorType.AttributeExists) ) { output += "[" + AttributeName; if (!String.IsNullOrEmpty(AttributeValue)) { output += "." + AttributeSelectorType.ToString() + ".'" + AttributeValue + "'"; } output += "]"; } if (SelectorType.HasFlag(SelectorType.Class)) { output += "." + Class; } if (SelectorType.HasFlag(SelectorType.All)) { output += "*"; } if (SelectorType.HasFlag(SelectorType.PseudoClass)) { output += ":" + PseudoSelector.Name; if (PseudoSelector.Arguments != null && PseudoSelector.Arguments.Length > 0) { output += "(" + String.Join(",", PseudoSelector.Arguments) + ")"; } } return(output); }
public override string ToString() { string output = ""; switch (TraversalType) { case TraversalType.All: output = ""; break; case TraversalType.Child: output += " > "; break; case TraversalType.Descendent: output += " "; break; } if (SelectorType.HasFlag(SelectorType.Elements)) { output += "<ElementList[" + SelectElements.Count() + "]> "; } if (SelectorType.HasFlag(SelectorType.HTML)) { output += "<HTML[" + Html.Length + "]> "; } if (SelectorType.HasFlag(SelectorType.Tag)) { output += Tag; } if (SelectorType.HasFlag(SelectorType.ID)) { output += "#" + ID; } if (SelectorType.HasFlag(SelectorType.Attribute)) { output += "[" + AttributeName; if (!String.IsNullOrEmpty(AttributeValue)) { output += "." + AttributeSelectorType.ToString() + ".'" + AttributeValue + "'"; } output += "]"; } if (SelectorType.HasFlag(SelectorType.Class)) { output += "." + Class; } if (SelectorType.HasFlag(SelectorType.All)) { output += "*"; } if (SelectorType.HasFlag(SelectorType.Position)) { output += ":" + PositionType.ToString(); if (IsFunction) { output += "(" + PositionIndex + ")"; } else if (SubSelectors.Count > 0) { output += SubSelectors.ToString(); } } if (SelectorType.HasFlag(SelectorType.Contains)) { output += ":contains(" + Criteria + ")"; } return(output); }