- XPath =
{
:descendant => "//*",
:child => "/*",
:adjacent => "/following-sibling::*[1]",
:laterSibling => '/following-sibling::*',
:tagName => lambda { |m|
return '' if m[1] == '*'
"[name()='" + m[1] + "']"
},
:className => "[contains(concat(' ', @class, ' '), ' \#{1} ')]",
:id => "[@id='\#{1}']",
:attrPresence => lambda { |m|
m[1].downcase!
"[@\#{1}]".interpolate(m)
},
:attr => lambda { |m|
m[1].downcase!
m[3] = m[5] || m[6]
Selector::XPath[:operators][m[2]].interpolate(m)
},
:pseudo => lambda { |m|
h = Selector::XPath[:pseudos][m[1]]
return '' if h.nil?
return h.call(m) if h.kind_of? Proc
Selector::XPath[:pseudos][m[1]].interpolate(m)
},
:operators => {
'=' => "[@\#{1}='\#{3}']",
'!=' => "[@\#{1}!='\#{3}']",
'^=' => "[starts-with(@\#{1}, '\#{3}')]",
'$=' => "[substring(@\#{1}, (string-length(@\#{1}) - string-length('\#{3}') + 1))='\#{3}']",
'*=' => "[contains(@\#{1}, '\#{3}')]",
'~=' => "[contains(concat(' ', @\#{1}, ' '), ' \#{3} ')]",
'|=' => "[contains(concat('-', @\#{1}, '-'), '-\#{3}-')]"
},
:pseudos => {
'first-child' => '[not(preceding-sibling::*)]',
'last-child' => '[not(following-sibling::*)]',
'only-child' => '[not(preceding-sibling::* or following-sibling::*)]',
'empty' => "[count(*) = 0 and (count(text()) = 0)]",
'checked' => "[@checked]",
'disabled' => "[(@disabled) and (@type!='hidden')]",
'enabled' => "[not(@disabled) and (@type!='hidden')]",
'not' => lambda { |m|
e = m[6]; le = nil; exclusion = []
while (e != '') and (le != e) and (e =~ /\S/)
le = e.dup
Selector::Patterns.each do |pattern|
n = Selector::XPath[pattern[:name]]
if m = e.match(pattern[:re])
v = n.kind_of?(Proc) ? n.call(m) : n.interpolate(m)
exclusion << ('(' + v[1, v.length - 2] + ')')
e.gsub! m[0], ''
break
end
end
end
"[not(" + exclusion.join(" and ") + ")]"
},
'nth-child' => lambda { |m|
Selector::XPath[:pseudos]['nth'].call("(count(./preceding-sibling::*) + 1) ", m)
},
'nth-last-child' => lambda { |m|
Selector::XPath[:pseudos]['nth'].call("(count(./following-sibling::*) + 1) ", m)
},
'nth-of-type' => lambda { |m|
Selector::XPath[:pseudos]['nth'].call("position() ", m)
},
'nth-last-of-type' => lambda { |m|
Selector::XPath[:pseudos]['nth'].call("(last() + 1 - position()) ", m)
},
'first-of-type' => lambda { |m|
m[6] = "1"; Selector::XPath[:pseudos]['nth-of-type'].call(m)
},
'last-of-type' => lambda { |m|
m[6] = "1"; Selector::XPath[:pseudos]['nth-last-of-type'].call(m)
},
'only-of-type' => lambda { |m|
p = Selector::XPath[:pseudos]
p['first-of-type'].call(m) + p['last-of-type'].call(m)
},
'nth' => lambda { |fragment, m|
mm = nil; formula = m[6]; predicate = nil
formula = '2n+0' if formula == 'even'
formula = '2n+1' if formula == 'odd'
return "[#{fragment}= #{mm[1]}]" if mm = formula.match(/^(\d+)$/) if mm = formula.match(/^(-?\d*)?n(([+-])(\d+))?/).to_a mm[1] = -1 if mm[1] == '-'
mm[1] = nil if mm[0] == 'n' a = mm[1] ? mm[1].to_i : 1
b = mm[2] ? mm[2].to_i : 0
"[((#{fragment} - #{b}) mod #{a} = 0) and ((#{fragment} - #{b}) div #{a} >= 0)]"
end
}
}
}
- Patterns =
[
{ :name => :laterSibling, :re => %r{^\s*~\s*} },
{ :name => :child, :re => %r{^\s*>\s*} },
{ :name => :adjacent, :re => %r{^\s*\+\s*} },
{ :name => :descendant, :re => %r{^\s} },
{ :name => :tagName, :re => %r{^\s*(\*|[\w\-]+)(\b|$)?} },
{ :name => :id, :re => %r{^#([\w\-\*]+)(\b|$)} },
{ :name => :className, :re => %r{^\.([\w\-\*]+)(\b|$)} },
{ :name => :pseudo, :re => %r{^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))} },
{ :name => :attrPresence, :re => %r{^\[((?:[\w-]+:)?[\w-]+)\]} },
{ :name => :attr, :re => %r{\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]} }
]
- Assertions =
{
"tagName" => lambda { |element, matches|
matches[1].upcase == element.name.upcase
},
"className" => lambda { |element, matches|
element.has_class_name? matches[1]
},
"id" => lambda { |element, matches|
element.read_attribute("id") == matches[1]
},
"attrPresence" => lambda { |element, matches|
element.has_attribute? matches[1]
},
"attr" => lambda { |element, matches|
value = element.read_attribute matches[1]
value and Selector::Operators[matches[2]].call(value, matches[5] || matches[6])
}
}
- Operators =
{
'=' => lambda { |nv, v| nv == v },
'!=' => lambda { |nv, v| nv != v },
'^=' => lambda { |nv, v| nv == v or (nv and nv.start_with? v) },
'$=' => lambda { |nv, v| nv == v or (nv and nv.end_with? v) },
'*=' => lambda { |nv, v| nv == v or (nv and nv.include? v) },
'~=' => lambda { |nv, v| " #{nv} ".include? " #{v} " },
'|=' => lambda { |nv, v| "-#{(nv || '').upcase}-".include? "-#{(v || '').upcase}-" }
}
- @@cache =
{}