/// <summary> /// Parse single argument passed to a pseudoselector /// </summary> /// /// <exception cref="ArgumentException"> /// Thrown when one or more arguments have unsupported or illegal values. /// </exception> /// <exception cref="NotImplementedException"> /// Thrown when the requested operation is unimplemented. /// </exception> /// /// <param name="value"> /// The arguments. /// </param> /// /// <returns> /// The parsed string /// </returns> protected string ParseSingleArg(string value) { IStringScanner scanner = Scanner.Create(value); var quoting = ParameterQuoted(0); switch (quoting) { case QuotingRule.OptionallyQuoted: scanner.Expect(MatchFunctions.OptionallyQuoted()); if (!scanner.Finished) { throw new ArgumentException(InvalidArgumentsError()); } return(scanner.Match); case QuotingRule.AlwaysQuoted: scanner.Expect(MatchFunctions.Quoted()); if (!scanner.Finished) { throw new ArgumentException(InvalidArgumentsError()); } return(scanner.Match); case QuotingRule.NeverQuoted: return(value); default: throw new NotImplementedException("Unimplemented quoting rule"); } }
/// <summary> /// Get the value of a style property for the first element in the set of matched elements, and /// converts to a numeric type T. Any numeric type strings are ignored when converting to numeric /// values. /// </summary> /// /// <typeparam name="T"> /// The type. This should probably be a numeric type, but the method will attempt to convert to /// any IConvertible type passed. /// </typeparam> /// <param name="style"> /// The name of the CSS style to retrieve. /// </param> /// /// <returns> /// A value of type T. /// </returns> /// /// <url> /// http://api.jquery.com/css/#css1 /// </url> public T Css <T>(String style) where T : IConvertible { IDomElement el = FirstElement(); if (el == null) { return(default(T)); } if (Objects.IsNumericType(typeof(T))) { IStringScanner scanner = Scanner.Create(el.Style[style] ?? ""); T num; if (scanner.TryGetNumber <T>(out num)) { return(num); } else { return(default(T)); } } else { return((T)Objects.ChangeType(el.Style[style] ?? "", typeof(T))); } }
/// <summary> /// Parse the arguments using the rules returned by the ParameterQuoted method. /// </summary> /// /// <param name="value"> /// The arguments /// </param> /// /// <returns> /// An array of strings /// </returns> protected string[] ParseArgs(string value) { List <string> parms = new List <string>(); int index = 0; IStringScanner scanner = Scanner.Create(value); while (!scanner.Finished) { var quoting = ParameterQuoted(index); switch (quoting) { case QuotingRule.OptionallyQuoted: scanner.Expect(MatchFunctions.OptionallyQuoted(",")); break; case QuotingRule.AlwaysQuoted: scanner.Expect(MatchFunctions.Quoted()); break; case QuotingRule.NeverQuoted: scanner.Seek(',', true); break; default: throw new NotImplementedException("Unimplemented quoting rule"); } parms.Add(scanner.Match); if (!scanner.Finished) { scanner.Next(); index++; } } return(parms.ToArray()); }
/// <summary> /// Returns the numeric value only of a style, ignoring units /// </summary> /// /// <param name="style"> /// The style. /// </param> /// /// <returns> /// A double, or null if the style did not exist or did not contain a numeric value. /// </returns> public double?NumberPart(string style) { string st = GetStyle(style); if (st == null) { return(null); } else { IStringScanner scanner = Scanner.Create(st); string numString; if (scanner.TryGet(MatchFunctions.Number(), out numString)) { double num; if (double.TryParse(numString, out num)) { return(num); } } return(null); } }
public IEnumerable <Selector> Parse(string selector) { Selectors = new List <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.NextChar) { case '*': Current.SelectorType = 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); switch (key) { case "checkbox": case "radio": case "button": case "file": case "text": case "password": StartNewSelector(SelectorType.Attribute); //Current.SelectorType |= SelectorType.Attribute; Current.AttributeSelectorType = AttributeSelectorType.Equals; Current.AttributeName = "type"; Current.AttributeValue = key; if (key == "button" && !Current.SelectorType.HasFlag(SelectorType.Tag)) { //StartNewSelector(CombinatorType.Cumulative); StartNewSelector(SelectorType.Tag, CombinatorType.Cumulative, Current.TraversalType); //Current.SelectorType = SelectorType.Tag; Current.Tag = "button"; } break; case "checked": case "selected": case "disabled": StartNewSelector(SelectorType.Attribute); Current.AttributeSelectorType = AttributeSelectorType.Exists; Current.AttributeName = key; break; case "enabled": StartNewSelector(SelectorType.Attribute); Current.AttributeSelectorType = AttributeSelectorType.NotExists; Current.AttributeName = "disabled"; break; case "contains": StartNewSelector(SelectorType.Contains); IStringScanner inner = scanner.ExpectBoundedBy('(', true).ToNewScanner(); Current.Criteria = inner.Get(MatchFunctions.OptionallyQuoted); break; case "eq": case "gt": case "lt": StartNewSelector(SelectorType.Position); switch (key) { case "eq": Current.PositionType = PositionType.IndexEquals; break; case "lt": Current.PositionType = PositionType.IndexLessThan; break; case "gt": Current.PositionType = PositionType.IndexGreaterThan; break; } scanner.ExpectChar('('); Current.PositionIndex = Convert.ToInt32(scanner.GetNumber()); scanner.ExpectChar(')'); break; case "even": StartNewSelector(SelectorType.Position); Current.PositionType = PositionType.Even; break; case "odd": StartNewSelector(SelectorType.Position); Current.PositionType = PositionType.Odd; break; case "first": StartNewSelector(SelectorType.Position); Current.PositionType = PositionType.First; break; case "last": StartNewSelector(SelectorType.Position); Current.PositionType = PositionType.Last; break; case "last-child": StartNewSelector(SelectorType.Position); Current.PositionType = PositionType.LastChild; break; case "first-child": StartNewSelector(SelectorType.Position); Current.PositionType = PositionType.FirstChild; break; case "nth-child": StartNewSelector(SelectorType.Position); Current.PositionType = PositionType.NthChild; Current.Criteria = scanner.GetBoundedBy('('); break; case "has": case "not": StartNewSelector(key == "has" ? SelectorType.SubSelectorHas : SelectorType.SubSelectorNot); Current.TraversalType = TraversalType.Descendent; string criteria = Current.Criteria = scanner.GetBoundedBy('(', true); SelectorChain subSelectors = new SelectorChain(criteria); Current.SubSelectors.Add(subSelectors); break; case "visible": StartNewSelector(SelectorType.Other); Current.OtherType = OtherType.Visible; break; default: throw new ArgumentOutOfRangeException("Unknown pseudoselector :\"" + key + "\""); } break; case '.': StartNewSelector(SelectorType.Class); scanner.Next(); Current.Class = scanner.Get(MatchFunctions.CssClass); break; case '#': scanner.Next(); if (!scanner.Finished) { StartNewSelector(SelectorType.ID); Current.ID = scanner.Get(MatchFunctions.HtmlIDValue); } break; case '[': StartNewSelector(SelectorType.Attribute); 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("=", "^=", "*=", "~=", "$=", "!="); Current.AttributeValue = innerScanner.Get(expectsOptionallyQuotedValue()); switch (matchType) { case "=": Current.AttributeSelectorType = AttributeSelectorType.Equals; break; case "^=": Current.AttributeSelectorType = AttributeSelectorType.StartsWith; break; case "*=": Current.AttributeSelectorType = AttributeSelectorType.Contains; break; case "~=": Current.AttributeSelectorType = AttributeSelectorType.ContainsWord; break; case "$=": Current.AttributeSelectorType = AttributeSelectorType.EndsWith; break; case "!=": Current.AttributeSelectorType = AttributeSelectorType.NotEquals; break; default: throw new ArgumentOutOfRangeException("Unknown attibute matching operator '" + matchType + "'"); } } break; case ',': FinishSelector(); scanner.NextNonWhitespace(); break; case '>': if (Current.IsComplete) { StartNewSelector(TraversalType.Child); } else { Current.TraversalType = 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); scanner.NextNonWhitespace(); break; case ' ': // if a ">" or "," is later found, it will be overridden. scanner.NextNonWhitespace(); StartNewSelector(TraversalType.Descendent); break; default: string tag = ""; if (scanner.TryGet(MatchFunctions.HTMLTagName, out tag)) { StartNewSelector(SelectorType.Tag); Current.Tag = tag; } else { if (scanner.Pos == 0) { Current.Html = sel; Current.SelectorType = SelectorType.HTML; scanner.End(); } else { throw new InvalidOperationException(scanner.LastError); } } break; } } // Close any open selectors FinishSelector(); return(Selectors); }
/// <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; }
public IOperand Parse <T>(string text) where T : IConvertible { IsTyped = typeof(T) != typeof(IConvertible); scanner = Scanner.Create(text); Clause = IsTyped ? new Sum <T>() : new Sum(); // it could have just one operand IOperand lastOperand = GetOperand <T>(); Clause.AddOperand(lastOperand); IOperation working = Clause; IOperand nextOperand = null; while (!ParseEnd) { IOperator op = GetOperation(); nextOperand = GetOperand <T>(); IOperation newOp; if (op.AssociationType == working.AssociationType) { // working can only be sum/product working.AddOperand(nextOperand, op.IsInverted); } else { switch (op.AssociationType) { case AssociationType.Addition: // always return to the root when adding if (!ReferenceEquals(working, Clause)) { working = Clause; } working.AddOperand(nextOperand, op.IsInverted); break; case AssociationType.Multiplicaton: //"steal" last operand from Clause, and change working to the new op newOp = op.GetFunction(); newOp.AddOperand(lastOperand); newOp.AddOperand(nextOperand, op.IsInverted); Clause.ReplaceLastOperand(newOp); working = newOp; break; case AssociationType.Power: // Similar to Multiplication, but does not change the active chain to the new operation. It can never be added to. newOp = op.GetFunction(); newOp.AddOperand(lastOperand); newOp.AddOperand(nextOperand, op.IsInverted); Clause.ReplaceLastOperand(newOp); break; case AssociationType.Function: // Similar to Multiplication, but does not change the active chain to the new operation. It can never be added to. newOp = op.GetFunction(); newOp.AddOperand(nextOperand, op.IsInverted); Clause.ReplaceLastOperand(newOp); break; default: throw new NotImplementedException("Unknown association type."); } } lastOperand = nextOperand; } Error = ""; return((IOperand)Clause); }
/// <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); }
public IEnumerable<Selector> Parse(string selector) { Selectors = new List<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.NextChar) { case '*': Current.SelectorType = 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); switch (key) { case "checkbox": case "radio": case "button": case "file": case "text": case "password": StartNewSelector(SelectorType.Attribute); //Current.SelectorType |= SelectorType.Attribute; Current.AttributeSelectorType = AttributeSelectorType.Equals; Current.AttributeName = "type"; Current.AttributeValue = key; if (key == "button" && !Current.SelectorType.HasFlag(SelectorType.Tag)) { //StartNewSelector(CombinatorType.Cumulative); StartNewSelector(SelectorType.Tag, CombinatorType.Cumulative, Current.TraversalType); //Current.SelectorType = SelectorType.Tag; Current.Tag = "button"; } break; case "checked": case "selected": case "disabled": StartNewSelector(SelectorType.Attribute); Current.AttributeSelectorType = AttributeSelectorType.Exists; Current.AttributeName = key; break; case "enabled": StartNewSelector(SelectorType.Attribute); Current.AttributeSelectorType = AttributeSelectorType.NotExists; Current.AttributeName = "disabled"; break; case "contains": StartNewSelector(SelectorType.Contains); IStringScanner inner = scanner.ExpectBoundedBy('(', true).ToNewScanner(); Current.Criteria = inner.Get(MatchFunctions.OptionallyQuoted); break; case "eq": case "gt": case "lt": StartNewSelector(SelectorType.Position); switch (key) { case "eq": Current.PositionType = PositionType.IndexEquals; break; case "lt": Current.PositionType = PositionType.IndexLessThan; break; case "gt": Current.PositionType = PositionType.IndexGreaterThan; break; } scanner.ExpectChar('('); Current.PositionIndex = Convert.ToInt32(scanner.GetNumber()); scanner.ExpectChar(')'); break; case "even": StartNewSelector(SelectorType.Position); Current.PositionType = PositionType.Even; break; case "odd": StartNewSelector(SelectorType.Position); Current.PositionType = PositionType.Odd; break; case "first": StartNewSelector(SelectorType.Position); Current.PositionType = PositionType.First; break; case "last": StartNewSelector(SelectorType.Position); Current.PositionType = PositionType.Last; break; case "last-child": StartNewSelector(SelectorType.Position); Current.PositionType = PositionType.LastChild; break; case "first-child": StartNewSelector(SelectorType.Position); Current.PositionType = PositionType.FirstChild; break; case "nth-child": StartNewSelector(SelectorType.Position); Current.PositionType = PositionType.NthChild; Current.Criteria = scanner.GetBoundedBy('('); break; case "has": case "not": StartNewSelector(key == "has" ? SelectorType.SubSelectorHas : SelectorType.SubSelectorNot); Current.TraversalType = TraversalType.Descendent; string criteria = Current.Criteria = scanner.GetBoundedBy('(', true); SelectorChain subSelectors = new SelectorChain(criteria); Current.SubSelectors.Add(subSelectors); break; case "visible": StartNewSelector(SelectorType.Other); Current.OtherType = OtherType.Visible; break; default: throw new ArgumentOutOfRangeException("Unknown pseudoselector :\"" + key + "\""); } break; case '.': StartNewSelector(SelectorType.Class); scanner.Next(); Current.Class = scanner.Get(MatchFunctions.CssClass); break; case '#': scanner.Next(); if (!scanner.Finished) { StartNewSelector(SelectorType.ID); Current.ID = scanner.Get(MatchFunctions.HtmlIDValue); } break; case '[': StartNewSelector(SelectorType.Attribute); 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("=", "^=", "*=", "~=", "$=", "!="); Current.AttributeValue = innerScanner.Get(expectsOptionallyQuotedValue()); switch (matchType) { case "=": Current.AttributeSelectorType = AttributeSelectorType.Equals; break; case "^=": Current.AttributeSelectorType = AttributeSelectorType.StartsWith; break; case "*=": Current.AttributeSelectorType = AttributeSelectorType.Contains; break; case "~=": Current.AttributeSelectorType = AttributeSelectorType.ContainsWord; break; case "$=": Current.AttributeSelectorType = AttributeSelectorType.EndsWith; break; case "!=": Current.AttributeSelectorType = AttributeSelectorType.NotEquals; break; default: throw new ArgumentOutOfRangeException("Unknown attibute matching operator '" + matchType + "'"); } } break; case ',': FinishSelector(); scanner.NextNonWhitespace(); break; case '>': if (Current.IsComplete) { StartNewSelector(TraversalType.Child); } else { Current.TraversalType = 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); scanner.NextNonWhitespace(); break; case ' ': // if a ">" or "," is later found, it will be overridden. scanner.NextNonWhitespace(); StartNewSelector(TraversalType.Descendent); break; default: string tag = ""; if (scanner.TryGet(MatchFunctions.HTMLTagName, out tag)) { StartNewSelector(SelectorType.Tag); Current.Tag = tag; } else { if (scanner.Pos == 0) { Current.Html = sel; Current.SelectorType = SelectorType.HTML; scanner.End(); } else { throw new InvalidOperationException(scanner.LastError); } } break; } } // Close any open selectors FinishSelector(); return Selectors; }