public static ISchemaCombinator?GetSchemaCombinator(this ISchema schema, CombinatorType combinatorType) { schema.AssertArgumentNotNull(nameof(schema)); string combinatorName = combinatorType.ToString(); return(schema.GetMetadata <ISchemaCombinator>(combinatorName)); }
private void CommitCurrentSelector(bool isLhsOfCombinator) { if (rhsSelector.Any()) { ISelector committingSelector = (rhsSelector.Count() == 1) ? rhsSelector.First() : new Selector(rhsSelector); switch (currentCombinator) { case CombinatorType.Descendant: ThrowIfLhsSelectorIsNull(); completedSelectors.Add(new DescendantSelector(lhsSelector, committingSelector)); lhsSelector = null; break; case CombinatorType.Child: ThrowIfLhsSelectorIsNull(); completedSelectors.Add(new ChildSelector(lhsSelector, committingSelector)); lhsSelector = null; break; case CombinatorType.AdjacentSibling: throw new NotImplementedException(); case CombinatorType.GeneralSibling: throw new NotImplementedException(); default: if (!isLhsOfCombinator) { // If this selector is not the left-hand side of a combinator, just add the selector directly to the list. completedSelectors.Add(committingSelector); } else { // Otherwise, set this selector as the left-hand side of the combinator. lhsSelector = committingSelector; } break; } } rhsSelector.Clear(); currentCombinator = CombinatorType.None; }
private static int GetPriority(CombinatorType type) { switch (type) { case CombinatorType.Group: return 0; case CombinatorType.Descendant: return 1; default: return 2; } }
/// <summary> /// Finishes any open selector and clears the current selector /// </summary> protected void FinishSelector() { if (Current.IsComplete) { var cur = Current.Clone(); Selectors.Add(cur); } Current.Clear(); NextTraversalType = TraversalType.Filter; NextCombinatorType = CombinatorType.Chained; }
/// <summary> /// Close the currently active selector. If it's partial (e.g. a descendant/child marker) then merge its into into the /// new selector created. /// </summary> /// <param name="selectorType"></param> /// <param name="combinatorType"></param> /// <param name="traversalType"></param> protected void StartNewSelector(SelectorType selectorType, CombinatorType combinatorType, TraversalType traversalType) { // if a selector was not finished, do not overwrite the existing combinator & traversal types, // as they could have been changed by a descendant or child selector. if (TryFinishSelector()) { Current.CombinatorType = combinatorType; Current.TraversalType = traversalType; } Current.SelectorType = selectorType; }
/// <summary> /// Insert a selector clause at the specified position. /// </summary> /// /// <exception cref="ArgumentException"> /// Thrown if the selector is not valid to insert at this position. /// </exception> /// /// <param name="index"> /// The position in the selector chain to insert this clause /// </param> /// <param name="clause"> /// The clause to insert /// </param> /// <param name="combinatorType"> /// (optional) type of the combinator. /// </param> public void Insert(int index, SelectorClause clause, CombinatorType combinatorType = CombinatorType.Chained) { if (combinatorType == CombinatorType.Root && Clauses.Count != 0) { throw new ArgumentException("Combinator type can only be root if there are no other selectors."); } if (Clauses.Count > 0 && index == 0) { Clauses[0].CombinatorType = combinatorType; clause.CombinatorType = CombinatorType.Root; clause.TraversalType = TraversalType.All; } Clauses.Insert(index, clause); }
/// <summary> /// Close the currently active selector. If it's partial (e.g. a descendant/child marker) then merge its into into the /// new selector created. /// </summary> /// <param name="selectorType"></param> /// <param name="combinatorType"></param> /// <param name="traversalType"></param> protected void StartNewSelector(SelectorType selectorType, CombinatorType combinatorType, TraversalType traversalType) { // if a selector was not finished, do not overwrite the existing combinator & traversal types, // as they could have been changed by a descendant or child selector. The exception is when // the new selector is an explicit "all" type; we always // a new selector will not have been started if there was an explicit "*" creating an all. However, if there's // anything other than a filter, we do actually want if (Current.IsComplete && Current.SelectorType != SelectorType.All || traversalType != TraversalType.Filter) { FinishSelector(); Current.CombinatorType = combinatorType; Current.TraversalType = traversalType; } Current.SelectorType = selectorType; }
protected void StartNewSelector(CombinatorType combinatorType, TraversalType traversalType) { StartNewSelector(0, combinatorType, traversalType); }
/// <summary> /// Parse the string, and return a sequence of Selector objects /// </summary> /// <param name="selector"></param> /// <returns></returns> public Selector Parse(string selector) { Selectors = new Selector(); string sel = (selector ?? String.Empty).Trim(); if (IsHtml(selector)) { Current.Html = sel; Current.SelectorType = SelectorType.HTML; Selectors.Add(Current); return Selectors; } scanner = Scanner.Create(sel); while (!scanner.Finished) { switch (scanner.Current) { case '*': StartNewSelector(SelectorType.All); scanner.Next(); break; case '<': // not selecting - creating html Current.Html = sel; scanner.End(); break; case ':': scanner.Next(); string key = scanner.Get(MatchFunctions.PseudoSelector).ToLower(); switch (key) { case "input": AddTagSelector("input"); AddTagSelector("textarea",true); AddTagSelector("select",true); AddTagSelector("button",true); break; case "text": StartNewSelector(SelectorType.AttributeValue | SelectorType.Tag); Current.Tag = "input"; Current.AttributeSelectorType = AttributeSelectorType.Equals; Current.AttributeName = "type"; Current.AttributeValue = "text"; StartNewSelector(SelectorType.AttributeValue | SelectorType.Tag, CombinatorType.Grouped, Current.TraversalType); Current.Tag = "input"; Current.AttributeSelectorType = AttributeSelectorType.NotExists; Current.AttributeName = "type"; Current.SelectorType |= SelectorType.Tag; Current.Tag = "input"; break; case "checkbox": case "radio": case "button": case "file": case "image": case "password": AddInputSelector(key,"input"); break; case "reset": case "submit": AddInputSelector(key); break; case "checked": case "selected": case "disabled": StartNewSelector(SelectorType.AttributeValue); Current.AttributeSelectorType = AttributeSelectorType.Exists; Current.AttributeName = key; break; case "enabled": StartNewSelector(SelectorType.AttributeValue); Current.AttributeSelectorType = AttributeSelectorType.NotExists; Current.AttributeName = "disabled"; break; case "first-letter": case "first-line": case "before": case "after": throw new NotImplementedException("The CSS pseudoelement selectors are not implemented in CsQuery."); case "target": case "link": case "hover": case "active": case "focus": case "visited": throw new NotImplementedException("Pseudoclasses that require a browser aren't implemented."); default: if (!AddPseudoSelector(key)) { throw new ArgumentException("Unknown pseudo-class :\"" + key + "\". If this is a valid CSS or jQuery selector, please let us know."); } break; } break; case '.': StartNewSelector(SelectorType.Class); scanner.Next(); Current.Class = scanner.Get(MatchFunctions.CssClassName); break; case '#': scanner.Next(); if (!scanner.Finished) { StartNewSelector(SelectorType.ID); Current.ID = scanner.Get(MatchFunctions.HtmlIDValue()); } break; case '[': StartNewSelector(SelectorType.AttributeValue); IStringScanner innerScanner = scanner.ExpectBoundedBy('[', true).ToNewScanner(); Current.AttributeName = innerScanner.Get(MatchFunctions.HTMLAttribute()); innerScanner.SkipWhitespace(); if (innerScanner.Finished) { Current.AttributeSelectorType = AttributeSelectorType.Exists; } else { string matchType = innerScanner.Get("=", "^=", "*=", "~=", "$=", "!=","|="); // CSS allows [attr=] as a synonym for [attr] if (innerScanner.Finished) { Current.AttributeSelectorType = AttributeSelectorType.Exists; } else { var rawValue = innerScanner.Expect(expectsOptionallyQuotedValue()).ToNewScanner(); Current.AttributeValue = rawValue.Finished ? "" : rawValue.Get(new EscapedString()); switch (matchType) { case "=": Current.SelectorType |= SelectorType.AttributeValue; Current.AttributeSelectorType = AttributeSelectorType.Equals; break; case "^=": Current.SelectorType |= SelectorType.AttributeValue; Current.AttributeSelectorType = AttributeSelectorType.StartsWith; // attributevalue starts with "" matches nothing if (Current.AttributeValue == "") { Current.AttributeValue = "" + (char)0; } break; case "*=": Current.SelectorType |= SelectorType.AttributeValue; Current.AttributeSelectorType = AttributeSelectorType.Contains; break; case "~=": Current.SelectorType |= SelectorType.AttributeValue; Current.AttributeSelectorType = AttributeSelectorType.ContainsWord; break; case "$=": Current.SelectorType |= SelectorType.AttributeValue; Current.AttributeSelectorType = AttributeSelectorType.EndsWith; break; case "!=": Current.AttributeSelectorType = AttributeSelectorType.NotEquals; // must matched manually - missing also validates as notEquals break; case "|=": Current.SelectorType |= SelectorType.AttributeValue; Current.AttributeSelectorType = AttributeSelectorType.StartsWithOrHyphen; break; default: throw new ArgumentException("Unknown attibute matching operator '" + matchType + "'"); } } } break; case ',': FinishSelector(); NextCombinatorType = CombinatorType.Root; NextTraversalType = TraversalType.All; scanner.NextNonWhitespace(); break; case '+': StartNewSelector(TraversalType.Adjacent); scanner.NextNonWhitespace(); break; case '~': StartNewSelector(TraversalType.Sibling); scanner.NextNonWhitespace(); break; case '>': StartNewSelector(TraversalType.Child); // This is a wierd thing because if you use the > selector against a set directly, the meaning is "filter" // whereas if it is used in a combination selector the meaning is "filter for 1st child" //Current.ChildDepth = (Current.CombinatorType == CombinatorType.Root ? 0 : 1); Current.ChildDepth = 1; scanner.NextNonWhitespace(); break; case ' ': // if a ">" or "," is later found, it will be overridden. scanner.NextNonWhitespace(); NextTraversalType = TraversalType.Descendent; break; default: string tag = ""; if (scanner.TryGet(MatchFunctions.HTMLTagSelectorName(), out tag)) { AddTagSelector(tag); } else { if (scanner.Index == 0) { Current.Html = sel; Current.SelectorType = SelectorType.HTML; scanner.End(); } else { throw new ArgumentException(scanner.LastError); } } break; } } // Close any open selectors FinishSelector(); if (Selectors.Count == 0) { var empty = new SelectorClause { SelectorType = SelectorType.None, TraversalType = TraversalType.Filter }; Selectors.Add(empty); } return Selectors; }
/// <summary> /// Finishes any open selector and clears the current selector /// </summary> protected void FinishSelector() { if (Current.IsComplete) { var cur = Current.Clone(); Selectors.Add(cur); } Current.Clear(); NextTraversalType = TraversalType.Filter; NextCombinatorType = CombinatorType.Chained; }
/// <summary> /// Close the currently active selector. If it's partial (e.g. a descendant/child marker) then merge its into into the /// new selector created. /// </summary> /// <param name="selectorType"></param> /// <param name="combinatorType"></param> /// <param name="traversalType"></param> protected void StartNewSelector(SelectorType selectorType, CombinatorType combinatorType, TraversalType traversalType) { // if a selector was not finished, do not overwrite the existing combinator & traversal types, // as they could have been changed by a descendant or child selector. The exception is when // the new selector is an explicit "all" type; we always // a new selector will not have been started if there was an explicit "*" creating an all. However, if there's // anything other than a filter, we do actually want if (Current.IsComplete && Current.SelectorType != SelectorType.All || traversalType != TraversalType.Filter) { FinishSelector(); Current.CombinatorType = combinatorType; Current.TraversalType = traversalType; } Current.SelectorType = selectorType; }
/// <summary> /// Start a new selector that does not yet have a type specified /// </summary> /// <param name="combinatorType"></param> /// <param name="traversalType"></param> protected void StartNewSelector(CombinatorType combinatorType, TraversalType traversalType) { StartNewSelector(0, combinatorType, traversalType); }
/// <summary> /// Initializes a new instance of the <see cref="SchemaCombinator"/> class. /// </summary> /// <param name="combinatorType">Combinator type.</param> /// <param name="schemas">Schemas to combine.</param> public SchemaCombinator(CombinatorType combinatorType, IReadOnlyCollection <ISchema> schemas) { CombinatorType = combinatorType; Schemas = schemas; }
/// <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> /// Parse the string, and return a sequence of Selector objects /// </summary> /// <param name="selector"></param> /// <returns></returns> public Selector Parse(string selector) { Selectors = new Selector(); string sel = (selector ?? String.Empty).Trim(); if (IsHtml(selector)) { Current.Html = sel; Current.SelectorType = SelectorType.HTML; Selectors.Add(Current); return(Selectors); } scanner = Scanner.Create(sel); while (!scanner.Finished) { switch (scanner.Current) { case '*': StartNewSelector(SelectorType.All); scanner.Next(); break; case '<': // not selecting - creating html Current.Html = sel; scanner.End(); break; case ':': scanner.Next(); string key = scanner.Get(MatchFunctions.PseudoSelector).ToLower(); switch (key) { case "input": AddTagSelector("input"); AddTagSelector("textarea", true); AddTagSelector("select", true); AddTagSelector("button", true); break; case "text": StartNewSelector(SelectorType.AttributeValue | SelectorType.Tag); Current.Tag = "input"; Current.AttributeSelectorType = AttributeSelectorType.Equals; Current.AttributeName = "type"; Current.AttributeValue = "text"; StartNewSelector(SelectorType.AttributeValue | SelectorType.Tag, CombinatorType.Grouped, Current.TraversalType); Current.Tag = "input"; Current.AttributeSelectorType = AttributeSelectorType.NotExists; Current.AttributeName = "type"; Current.SelectorType |= SelectorType.Tag; Current.Tag = "input"; break; case "checkbox": case "radio": case "button": case "file": case "image": case "password": AddInputSelector(key, "input"); break; case "reset": case "submit": AddInputSelector(key); break; case "checked": case "selected": case "disabled": StartNewSelector(SelectorType.AttributeValue); Current.AttributeSelectorType = AttributeSelectorType.Exists; Current.AttributeName = key; break; case "enabled": StartNewSelector(SelectorType.AttributeValue); Current.AttributeSelectorType = AttributeSelectorType.NotExists; Current.AttributeName = "disabled"; break; case "first-letter": case "first-line": case "before": case "after": throw new NotImplementedException("The CSS pseudoelement selectors are not implemented in CsQuery."); case "target": case "link": case "hover": case "active": case "focus": case "visited": throw new NotImplementedException("Pseudoclasses that require a browser aren't implemented."); default: if (!AddPseudoSelector(key)) { throw new ArgumentException("Unknown pseudo-class :\"" + key + "\". If this is a valid CSS or jQuery selector, please let us know."); } break; } break; case '.': StartNewSelector(SelectorType.Class); scanner.Next(); Current.Class = scanner.Get(MatchFunctions.CssClassName); break; case '#': scanner.Next(); if (!scanner.Finished) { StartNewSelector(SelectorType.ID); Current.ID = scanner.Get(MatchFunctions.HtmlIDValue()); } break; case '[': StartNewSelector(SelectorType.AttributeValue); IStringScanner innerScanner = scanner.ExpectBoundedBy('[', true).ToNewScanner(); Current.AttributeName = innerScanner.Get(MatchFunctions.HTMLAttribute()); innerScanner.SkipWhitespace(); if (innerScanner.Finished) { Current.AttributeSelectorType = AttributeSelectorType.Exists; } else { string matchType = innerScanner.Get("=", "^=", "*=", "~=", "$=", "!=", "|="); // CSS allows [attr=] as a synonym for [attr] if (innerScanner.Finished) { Current.AttributeSelectorType = AttributeSelectorType.Exists; } else { var rawValue = innerScanner.Expect(expectsOptionallyQuotedValue()).ToNewScanner(); Current.AttributeValue = rawValue.Finished ? "" : rawValue.Get(new EscapedString()); switch (matchType) { case "=": Current.SelectorType |= SelectorType.AttributeValue; Current.AttributeSelectorType = AttributeSelectorType.Equals; break; case "^=": Current.SelectorType |= SelectorType.AttributeValue; Current.AttributeSelectorType = AttributeSelectorType.StartsWith; // attributevalue starts with "" matches nothing if (Current.AttributeValue == "") { Current.AttributeValue = "" + (char)0; } break; case "*=": Current.SelectorType |= SelectorType.AttributeValue; Current.AttributeSelectorType = AttributeSelectorType.Contains; break; case "~=": Current.SelectorType |= SelectorType.AttributeValue; Current.AttributeSelectorType = AttributeSelectorType.ContainsWord; break; case "$=": Current.SelectorType |= SelectorType.AttributeValue; Current.AttributeSelectorType = AttributeSelectorType.EndsWith; break; case "!=": Current.AttributeSelectorType = AttributeSelectorType.NotEquals; // must matched manually - missing also validates as notEquals break; case "|=": Current.SelectorType |= SelectorType.AttributeValue; Current.AttributeSelectorType = AttributeSelectorType.StartsWithOrHyphen; break; default: throw new ArgumentException("Unknown attibute matching operator '" + matchType + "'"); } } } break; case ',': FinishSelector(); NextCombinatorType = CombinatorType.Root; NextTraversalType = TraversalType.All; scanner.NextNonWhitespace(); break; case '+': StartNewSelector(TraversalType.Adjacent); scanner.NextNonWhitespace(); break; case '~': StartNewSelector(TraversalType.Sibling); scanner.NextNonWhitespace(); break; case '>': StartNewSelector(TraversalType.Child); // This is a wierd thing because if you use the > selector against a set directly, the meaning is "filter" // whereas if it is used in a combination selector the meaning is "filter for 1st child" //Current.ChildDepth = (Current.CombinatorType == CombinatorType.Root ? 0 : 1); Current.ChildDepth = 1; scanner.NextNonWhitespace(); break; case ' ': // if a ">" or "," is later found, it will be overridden. scanner.NextNonWhitespace(); NextTraversalType = TraversalType.Descendent; break; default: string tag = ""; if (scanner.TryGet(MatchFunctions.HTMLTagSelectorName(), out tag)) { AddTagSelector(tag); } else { if (scanner.Index == 0) { Current.Html = sel; Current.SelectorType = SelectorType.HTML; scanner.End(); } else { throw new ArgumentException(scanner.LastError); } } break; } } // Close any open selectors FinishSelector(); if (Selectors.Count == 0) { var empty = new SelectorClause { SelectorType = SelectorType.None, TraversalType = TraversalType.Filter }; Selectors.Add(empty); } return(Selectors); }
private static SelectorExpression ProcessBinaryExpression(CombinatorType type, SelectorExpression left, TokensQueue tokens) { var tokenPriority = GetPriority(type); var other = ParseWithPriority(tokens, tokenPriority + 1); switch (type) { case CombinatorType.Combine: var combineCombinator = left as CombineCombinator; return combineCombinator != null ? combineCombinator.Add(other) : new CombineCombinator(left, other); case CombinatorType.Child: return new ChildCombinator(left, other); case CombinatorType.Sibling: return new SiblingCombinator(left, other); case CombinatorType.Descendant: var descendantCombinator = left as DescendantCombinator; return descendantCombinator != null ? descendantCombinator.Add(other) : new DescendantCombinator(left, other); case CombinatorType.Group: var groupCombinator = left as GroupCombinator; return groupCombinator != null ? groupCombinator.Add(other) : new GroupCombinator(left, other); default: throw new TokenException("unexpected operator", tokens.LastReadToken); } }
/// <summary> /// Close the currently active selector. If it's partial (e.g. a descendant/child marker) then merge its into into the /// new selector created. /// </summary> /// <param name="selectorType"></param> /// <param name="combinatorType"></param> /// <param name="traversalType"></param> protected void StartNewSelector(SelectorType selectorType, CombinatorType combinatorType, TraversalType traversalType) { // if a selector was not finished, do not overwrite the existing combinator & traversal types, // as they could have been changed by a descendant or child selector. if (TryFinishSelector()) { Current.CombinatorType = combinatorType; Current.TraversalType = traversalType; } Current.SelectorType = selectorType; }