File: //usr/local/rvm/gems/default/gems/nokogiri-1.12.5-x86_64-linux/lib/nokogiri/css/xpath_visitor.rb
# frozen_string_literal: true
module Nokogiri
module CSS
class XPathVisitor # :nodoc:
def visit_function node
msg = :"visit_function_#{node.value.first.gsub(/[(]/, '')}"
return self.send(msg, node) if self.respond_to?(msg)
case node.value.first
when /^text\(/
'child::text()'
when /^self\(/
"self::#{node.value[1]}"
when /^eq\(/
"position()=#{node.value[1]}"
when /^(nth|nth-of-type)\(/
if node.value[1].is_a?(Nokogiri::CSS::Node) and node.value[1].type == :NTH
nth(node.value[1])
else
"position()=#{node.value[1]}"
end
when /^nth-child\(/
if node.value[1].is_a?(Nokogiri::CSS::Node) and node.value[1].type == :NTH
nth(node.value[1], :child => true)
else
"count(preceding-sibling::*)=#{node.value[1].to_i-1}"
end
when /^nth-last-of-type\(/
if node.value[1].is_a?(Nokogiri::CSS::Node) and node.value[1].type == :NTH
nth(node.value[1], :last => true)
else
index = node.value[1].to_i - 1
index == 0 ? "position()=last()" : "position()=last()-#{index}"
end
when /^nth-last-child\(/
if node.value[1].is_a?(Nokogiri::CSS::Node) and node.value[1].type == :NTH
nth(node.value[1], :last => true, :child => true)
else
"count(following-sibling::*)=#{node.value[1].to_i-1}"
end
when /^(first|first-of-type)\(/
"position()=1"
when /^(last|last-of-type)\(/
"position()=last()"
when /^contains\(/
"contains(.,#{node.value[1]})"
when /^gt\(/
"position()>#{node.value[1]}"
when /^only-child\(/
"last()=1"
when /^comment\(/
"comment()"
when /^has\(/
is_direct = node.value[1].value[0].nil? # e.g. "has(> a)", "has(~ a)", "has(+ a)"
".#{"//" if !is_direct}#{node.value[1].accept(self)}"
else
# non-standard. this looks like a function call.
args = ['.'] + node.value[1..-1]
"#{node.value.first}#{args.join(',')})"
end
end
def visit_not node
child = node.value.first
if :ELEMENT_NAME == child.type
"not(self::#{child.accept(self)})"
else
"not(#{child.accept(self)})"
end
end
def visit_id node
node.value.first =~ /^#(.*)$/
"@id='#{$1}'"
end
def visit_attribute_condition node
attribute = if (node.value.first.type == :FUNCTION) or (node.value.first.value.first =~ /::/)
''
else
'@'
end
attribute += node.value.first.accept(self)
# non-standard. attributes starting with '@'
attribute.gsub!(/^@@/, '@')
return attribute unless node.value.length == 3
value = node.value.last
value = "'#{value}'" if value !~ /^['"]/
# quoted values - see test_attribute_value_with_quotes in test/css/test_parser.rb
if (value[0]==value[-1]) && %q{"'}.include?(value[0])
str_value = value[1..-2]
if str_value.include?(value[0])
value = 'concat("' + str_value.split('"', -1).join(%q{",'"',"}) + '","")'
end
end
case node.value[1]
when :equal
attribute + "=" + "#{value}"
when :not_equal
attribute + "!=" + "#{value}"
when :substring_match
"contains(#{attribute},#{value})"
when :prefix_match
"starts-with(#{attribute},#{value})"
when :dash_match
"#{attribute}=#{value} or starts-with(#{attribute},concat(#{value},'-'))"
when :includes
value = value[1..-2] # strip quotes
css_class(attribute, value)
when :suffix_match
"substring(#{attribute},string-length(#{attribute})-string-length(#{value})+1,string-length(#{value}))=#{value}"
else
attribute + " #{node.value[1]} " + "#{value}"
end
end
def visit_pseudo_class node
if node.value.first.is_a?(Nokogiri::CSS::Node) and node.value.first.type == :FUNCTION
node.value.first.accept(self)
else
msg = :"visit_pseudo_class_#{node.value.first.gsub(/[(]/, '')}"
return self.send(msg, node) if self.respond_to?(msg)
case node.value.first
when "first" then "position()=1"
when "first-child" then "count(preceding-sibling::*)=0"
when "last" then "position()=last()"
when "last-child" then "count(following-sibling::*)=0"
when "first-of-type" then "position()=1"
when "last-of-type" then "position()=last()"
when "only-child" then "count(preceding-sibling::*)=0 and count(following-sibling::*)=0"
when "only-of-type" then "last()=1"
when "empty" then "not(node())"
when "parent" then "node()"
when "root" then "not(parent::*)"
else
node.value.first + "(.)"
end
end
end
def visit_class_condition node
css_class("@class", node.value.first)
end
def visit_combinator node
if is_of_type_pseudo_class?(node.value.last)
"#{node.value.first.accept(self) if node.value.first}][#{node.value.last.accept(self)}"
else
"#{node.value.first.accept(self) if node.value.first} and #{node.value.last.accept(self)}"
end
end
{
'direct_adjacent_selector' => "/following-sibling::*[1]/self::",
'following_selector' => "/following-sibling::",
'descendant_selector' => '//',
'child_selector' => '/',
}.each do |k,v|
class_eval %{
def visit_#{k} node
"\#{node.value.first.accept(self) if node.value.first}#{v}\#{node.value.last.accept(self)}"
end
}
end
def visit_conditional_selector node
node.value.first.accept(self) + '[' +
node.value.last.accept(self) + ']'
end
def visit_element_name node
node.value.first
end
def accept node
node.accept(self)
end
private
def nth node, options={}
raise ArgumentError, "expected an+b node to contain 4 tokens, but is #{node.value.inspect}" unless node.value.size == 4
a, b = read_a_and_positive_b node.value
position = if options[:child]
options[:last] ? "(count(following-sibling::*)+1)" : "(count(preceding-sibling::*)+1)"
else
options[:last] ? "(last()-position()+1)" : "position()"
end
if b.zero?
"(#{position} mod #{a})=0"
else
compare = a < 0 ? "<=" : ">="
if a.abs == 1
"#{position}#{compare}#{b}"
else
"(#{position}#{compare}#{b}) and (((#{position}-#{b}) mod #{a.abs})=0)"
end
end
end
def read_a_and_positive_b values
op = values[2]
if op == "+"
a = values[0].to_i
b = values[3].to_i
elsif op == "-"
a = values[0].to_i
b = a - (values[3].to_i % a)
else
raise ArgumentError, "expected an+b node to have either + or - as the operator, but is #{op.inspect}"
end
[a, b]
end
def is_of_type_pseudo_class? node
if node.type==:PSEUDO_CLASS
if node.value[0].is_a?(Nokogiri::CSS::Node) and node.value[0].type == :FUNCTION
node.value[0].value[0]
else
node.value[0]
end =~ /(nth|first|last|only)-of-type(\()?/
end
end
# use only ordinary xpath functions
def css_class_standard(hay, needle)
"contains(concat(' ',normalize-space(#{hay}),' '),' #{needle} ')"
end
# use the builtin implementation
def css_class_builtin(hay, needle)
"nokogiri-builtin:css-class(#{hay},'#{needle}')"
end
alias_method :css_class, :css_class_standard
end
class XPathVisitorAlwaysUseBuiltins < XPathVisitor # :nodoc:
private
alias_method :css_class, :css_class_builtin
end
class XPathVisitorOptimallyUseBuiltins < XPathVisitor # :nodoc:
private
if Nokogiri.uses_libxml?
alias_method :css_class, :css_class_builtin
else
alias_method :css_class, :css_class_standard
end
end
end
end