# frozen_string_literal: truemoduleNokogirimoduleCSSclassXPathVisitor# :nodoc:defvisit_functionnodemsg=:"visit_function_#{node.value.first.gsub(/[(]/,'')}"returnself.send(msg,node)ifself.respond_to?(msg)casenode.value.firstwhen/^text\(/'child::text()'when/^self\(/"self::#{node.value[1]}"when/^eq\(/"position()=#{node.value[1]}"when/^(nth|nth-of-type)\(/ifnode.value[1].is_a?(Nokogiri::CSS::Node)andnode.value[1].type==:NTHnth(node.value[1])else"position()=#{node.value[1]}"endwhen/^nth-child\(/ifnode.value[1].is_a?(Nokogiri::CSS::Node)andnode.value[1].type==:NTHnth(node.value[1],:child=>true)else"count(preceding-sibling::*)=#{node.value[1].to_i-1}"endwhen/^nth-last-of-type\(/ifnode.value[1].is_a?(Nokogiri::CSS::Node)andnode.value[1].type==:NTHnth(node.value[1],:last=>true)elseindex=node.value[1].to_i-1index==0?"position()=last()":"position()=last()-#{index}"endwhen/^nth-last-child\(/ifnode.value[1].is_a?(Nokogiri::CSS::Node)andnode.value[1].type==:NTHnth(node.value[1],:last=>true,:child=>true)else"count(following-sibling::*)=#{node.value[1].to_i-1}"endwhen/^(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(',')})"endenddefvisit_notnodechild=node.value.firstif:ELEMENT_NAME==child.type"not(self::#{child.accept(self)})"else"not(#{child.accept(self)})"endenddefvisit_idnodenode.value.first=~/^#(.*)$/"@id='#{$1}'"enddefvisit_attribute_conditionnodeattribute=if(node.value.first.type==:FUNCTION)or(node.value.first.value.first=~/::/)''else'@'endattribute+=node.value.first.accept(self)# non-standard. attributes starting with '@'attribute.gsub!(/^@@/,'@')returnattributeunlessnode.value.length==3value=node.value.lastvalue="'#{value}'"ifvalue!~/^['"]/# quoted values - see test_attribute_value_with_quotes in test/css/test_parser.rbif(value[0]==value[-1])&&%q{"'}.include?(value[0])str_value=value[1..-2]ifstr_value.include?(value[0])value='concat("'+str_value.split('"',-1).join(%q{",'"',"})+'","")'endendcasenode.value[1]when:equalattribute+"="+"#{value}"when:not_equalattribute+"!="+"#{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:includesvalue=value[1..-2]# strip quotescss_class(attribute,value)when:suffix_match"substring(#{attribute},string-length(#{attribute})-string-length(#{value})+1,string-length(#{value}))=#{value}"elseattribute+" #{node.value[1]} "+"#{value}"endenddefvisit_pseudo_classnodeifnode.value.first.is_a?(Nokogiri::CSS::Node)andnode.value.first.type==:FUNCTIONnode.value.first.accept(self)elsemsg=:"visit_pseudo_class_#{node.value.first.gsub(/[(]/,'')}"returnself.send(msg,node)ifself.respond_to?(msg)casenode.value.firstwhen"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::*)"elsenode.value.first+"(.)"endendenddefvisit_class_conditionnodecss_class("@class",node.value.first)enddefvisit_combinatornodeifis_of_type_pseudo_class?(node.value.last)"#{node.value.first.accept(self)ifnode.value.first}][#{node.value.last.accept(self)}"else"#{node.value.first.accept(self)ifnode.value.first} and #{node.value.last.accept(self)}"endend{'direct_adjacent_selector'=>"/following-sibling::*[1]/self::",'following_selector'=>"/following-sibling::",'descendant_selector'=>'//','child_selector'=>'/',}.eachdo|k,v|class_eval%{
def visit_#{k} node
"\#{node.value.first.accept(self) if node.value.first}#{v}\#{node.value.last.accept(self)}"
end
}enddefvisit_conditional_selectornodenode.value.first.accept(self)+'['+node.value.last.accept(self)+']'enddefvisit_element_namenodenode.value.firstenddefacceptnodenode.accept(self)endprivatedefnthnode,options={}raiseArgumentError,"expected an+b node to contain 4 tokens, but is #{node.value.inspect}"unlessnode.value.size==4a,b=read_a_and_positive_bnode.valueposition=ifoptions[:child]options[:last]?"(count(following-sibling::*)+1)":"(count(preceding-sibling::*)+1)"elseoptions[:last]?"(last()-position()+1)":"position()"endifb.zero?"(#{position} mod #{a})=0"elsecompare=a<0?"<=":">="ifa.abs==1"#{position}#{compare}#{b}"else"(#{position}#{compare}#{b}) and (((#{position}-#{b}) mod #{a.abs})=0)"endendenddefread_a_and_positive_bvaluesop=values[2]ifop=="+"a=values[0].to_ib=values[3].to_ielsifop=="-"a=values[0].to_ib=a-(values[3].to_i%a)elseraiseArgumentError,"expected an+b node to have either + or - as the operator, but is #{op.inspect}"end[a,b]enddefis_of_type_pseudo_class?nodeifnode.type==:PSEUDO_CLASSifnode.value[0].is_a?(Nokogiri::CSS::Node)andnode.value[0].type==:FUNCTIONnode.value[0].value[0]elsenode.value[0]end=~/(nth|first|last|only)-of-type(\()?/endend# use only ordinary xpath functionsdefcss_class_standard(hay,needle)"contains(concat(' ',normalize-space(#{hay}),' '),' #{needle} ')"end# use the builtin implementationdefcss_class_builtin(hay,needle)"nokogiri-builtin:css-class(#{hay},'#{needle}')"endalias_method:css_class,:css_class_standardendclassXPathVisitorAlwaysUseBuiltins<XPathVisitor# :nodoc:privatealias_method:css_class,:css_class_builtinendclassXPathVisitorOptimallyUseBuiltins<XPathVisitor# :nodoc:privateifNokogiri.uses_libxml?alias_method:css_class,:css_class_builtinelsealias_method:css_class,:css_class_standardendendendend