mirror of
				https://github.com/ruby/ruby.git
				synced 2022-11-09 12:17:21 -05:00 
			
		
		
		
	 d430692b95
			
		
	
	
		d430692b95
		
	
	
	
	
		
			
			escape '[' to avoid warning. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@4746 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
		
			
				
	
	
		
			266 lines
		
	
	
	
		
			7.7 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
			
		
		
	
	
			266 lines
		
	
	
	
		
			7.7 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
| require 'rexml/functions'
 | |
| require 'rexml/xmltokens'
 | |
| 
 | |
| module REXML
 | |
| 	class QuickPath
 | |
| 		include Functions
 | |
| 		include XMLTokens
 | |
| 
 | |
| 		EMPTY_HASH = {}
 | |
| 
 | |
| 		def QuickPath::first element, path, namespaces=EMPTY_HASH
 | |
| 			match(element, path, namespaces)[0]
 | |
| 		end
 | |
| 
 | |
| 		def QuickPath::each element, path, namespaces=EMPTY_HASH, &block
 | |
| 			path = "*" unless path
 | |
| 			match(element, path, namespaces).each( &block )
 | |
| 		end
 | |
| 
 | |
| 		def QuickPath::match element, path, namespaces=EMPTY_HASH
 | |
| 			raise "nil is not a valid xpath" unless path
 | |
| 			results = nil
 | |
| 			Functions::namespace_context = namespaces
 | |
| 			case path
 | |
| 			when /^\/([^\/]|$)/u
 | |
| 				# match on root
 | |
| 				path = path[1..-1]
 | |
| 				return [element.root.parent] if path == ''
 | |
| 				results = filter([element.root], path)
 | |
| 			when /^[-\w]*::/u
 | |
| 				results = filter([element], path)
 | |
| 			when /^\*/u
 | |
| 				results = filter(element.to_a, path)
 | |
| 			when /^[\[!\w:]/u
 | |
| 				# match on child
 | |
| 				matches = []
 | |
| 				children = element.to_a
 | |
| 				results = filter(children, path)
 | |
| 			else
 | |
| 				results = filter([element], path)
 | |
| 			end
 | |
| 			return results
 | |
| 		end
 | |
| 
 | |
| 		# Given an array of nodes it filters the array based on the path. The
 | |
| 		# result is that when this method returns, the array will contain elements
 | |
| 		# which match the path
 | |
| 		def QuickPath::filter elements, path
 | |
| 			return elements if path.nil? or path == '' or elements.size == 0
 | |
| 			case path
 | |
| 			when /^\/\//u											# Descendant
 | |
| 				return axe( elements, "descendant-or-self", $' )
 | |
| 			when /^\/?\b(\w[-\w]*)\b::/u							# Axe
 | |
| 				axe_name = $1
 | |
| 				rest = $'
 | |
| 				return axe( elements, $1, $' )
 | |
| 			when /^\/(?=\b([:!\w][-\.\w]*:)?[-!\*\.\w]*\b([^:(]|$)|\*)/u	# Child
 | |
| 				rest = $'
 | |
| 				results = []
 | |
| 				elements.each do |element|
 | |
| 					results |= filter( element.to_a, rest )
 | |
| 				end
 | |
| 				return results
 | |
| 			when /^\/?(\w[-\w]*)\(/u							# / Function
 | |
| 				return function( elements, $1, $' )
 | |
| 			when Namespace::NAMESPLIT		# Element name
 | |
| 				name = $2
 | |
| 				ns = $1
 | |
| 				rest = $'
 | |
| 				elements.delete_if do |element|
 | |
| 					!(element.kind_of? Element and 
 | |
| 						(element.expanded_name == name or
 | |
| 						 (element.name == name and
 | |
| 						  element.namespace == Functions.namespace_context[ns])))
 | |
| 				end
 | |
| 				return filter( elements, rest )
 | |
| 			when /^\/\[/u
 | |
| 				matches = []
 | |
| 				elements.each do |element|
 | |
| 					matches |= predicate( element.to_a, path[1..-1] ) if element.kind_of? Element
 | |
| 				end
 | |
| 				return matches
 | |
| 			when /^\[/u												# Predicate
 | |
| 				return predicate( elements, path )
 | |
| 			when /^\/?\.\.\./u										# Ancestor
 | |
| 				return axe( elements, "ancestor", $' )
 | |
| 			when /^\/?\.\./u											# Parent
 | |
| 				return filter( elements.collect{|e|e.parent}, $' )
 | |
| 			when /^\/?\./u												# Self
 | |
| 				return filter( elements, $' )
 | |
| 			when /^\*/u													# Any
 | |
| 				results = []
 | |
| 				elements.each do |element|
 | |
| 					results |= filter( [element], $' ) if element.kind_of? Element
 | |
| 					#if element.kind_of? Element
 | |
| 					#	children = element.to_a
 | |
| 					#	children.delete_if { |child| !child.kind_of?(Element) }
 | |
| 					#	results |= filter( children, $' )
 | |
| 					#end
 | |
| 				end
 | |
| 				return results
 | |
| 			end
 | |
| 			return []
 | |
| 		end
 | |
| 
 | |
| 		def QuickPath::axe( elements, axe_name, rest )
 | |
| 			matches = []
 | |
| 			matches = filter( elements.dup, rest ) if axe_name =~ /-or-self$/u
 | |
| 			case axe_name
 | |
| 			when /^descendant/u
 | |
| 				elements.each do |element|
 | |
| 					matches |= filter( element.to_a, "descendant-or-self::#{rest}" ) if element.kind_of? Element
 | |
| 				end
 | |
| 			when /^ancestor/u
 | |
| 				elements.each do |element|
 | |
| 					while element.parent
 | |
| 						matches << element.parent
 | |
| 						element = element.parent
 | |
| 					end
 | |
| 				end
 | |
| 				matches = filter( matches, rest )
 | |
| 			when "self"
 | |
| 				matches = filter( elements, rest )
 | |
| 			when "child"
 | |
| 				elements.each do |element|
 | |
| 					matches |= filter( element.to_a, rest ) if element.kind_of? Element
 | |
| 				end
 | |
| 			when "attribute"
 | |
| 				elements.each do |element|
 | |
| 					matches << element.attributes[ rest ] if element.kind_of? Element
 | |
| 				end
 | |
| 			when "parent"
 | |
| 				matches = filter(elements.collect{|element| element.parent}.uniq, rest)
 | |
| 			when "following-sibling"
 | |
| 				matches = filter(elements.collect{|element| element.next_sibling}.uniq,
 | |
| 					rest)
 | |
| 			when "previous-sibling"
 | |
| 				matches = filter(elements.collect{|element| 
 | |
| 					element.previous_sibling}.uniq, rest )
 | |
| 			end
 | |
| 			return matches.uniq
 | |
| 		end
 | |
| 
 | |
| 		# A predicate filters a node-set with respect to an axis to produce a
 | |
| 		# new node-set. For each node in the node-set to be filtered, the 
 | |
| 		# PredicateExpr is evaluated with that node as the context node, with 
 | |
| 		# the number of nodes in the node-set as the context size, and with the 
 | |
| 		# proximity position of the node in the node-set with respect to the
 | |
| 		# axis as the context position; if PredicateExpr evaluates to true for
 | |
| 		# that node, the node is included in the new node-set; otherwise, it is
 | |
| 		# not included.
 | |
| 		#
 | |
| 		# A PredicateExpr is evaluated by evaluating the Expr and converting
 | |
| 		# the result to a boolean. If the result is a number, the result will
 | |
| 		# be converted to true if the number is equal to the context position
 | |
| 		# and will be converted to false otherwise; if the result is not a
 | |
| 		# number, then the result will be converted as if by a call to the
 | |
| 		# boolean function. Thus a location path para[3] is equivalent to
 | |
| 		# para[position()=3].
 | |
| 		def QuickPath::predicate( elements, path ) 
 | |
| 			ind = 1
 | |
| 			bcount = 1
 | |
| 			while bcount > 0
 | |
| 				bcount += 1 if path[ind] == ?[
 | |
| 				bcount -= 1 if path[ind] == ?]
 | |
| 				ind += 1
 | |
| 			end
 | |
| 			ind -= 1
 | |
| 			predicate = path[1..ind-1]
 | |
| 			rest = path[ind+1..-1]
 | |
| 
 | |
| 			# have to change 'a [=<>] b [=<>] c' into 'a [=<>] b and b [=<>] c'
 | |
| 			predicate.gsub!( /([^\s(and)(or)<>=]+)\s*([<>=])\s*([^\s(and)(or)<>=]+)\s*([<>=])\s*([^\s(and)(or)<>=]+)/u ) { 
 | |
| 				"#$1 #$2 #$3 and #$3 #$4 #$5"
 | |
| 			}
 | |
| 			# Let's do some Ruby trickery to avoid some work:
 | |
| 			predicate.gsub!( /&/u, "&&" )
 | |
| 			predicate.gsub!( /=/u, "==" )
 | |
| 			predicate.gsub!( /@(\w[-\w.]*)/u ) {
 | |
| 				"attribute(\"#$1\")" 
 | |
| 			}
 | |
| 			predicate.gsub!( /\bmod\b/u, "%" )
 | |
| 			predicate.gsub!( /\b(\w[-\w.]*\()/u ) {
 | |
| 				fname = $1
 | |
| 				fname.gsub( /-/u, "_" )
 | |
| 			}
 | |
| 			
 | |
| 			Functions.pair = [ 0, elements.size ]
 | |
| 			results = []
 | |
| 			elements.each do |element|
 | |
| 				Functions.pair[0] += 1
 | |
| 				Functions.node = element
 | |
| 				res = eval( predicate )
 | |
| 				case res
 | |
| 				when true
 | |
| 					results << element
 | |
| 				when Fixnum
 | |
| 					results << element if Functions.pair[0] == res
 | |
| 				when String
 | |
| 					results << element
 | |
| 				end
 | |
| 			end
 | |
| 			return filter( results, rest )
 | |
| 		end
 | |
| 
 | |
| 		def QuickPath::attribute( name )
 | |
| 			return Functions.node.attributes[name] if Functions.node.kind_of? Element
 | |
| 		end
 | |
| 
 | |
| 		def QuickPath::name()
 | |
| 			return Functions.node.name if Functions.node.kind_of? Element
 | |
| 		end
 | |
| 
 | |
| 		def QuickPath::method_missing( id, *args )
 | |
| 			begin
 | |
| 				Functions.send( id.id2name, *args )
 | |
| 			rescue Exception
 | |
| 				raise "METHOD: #{id.id2name}(#{args.join ', '})\n#{$!.message}"
 | |
| 			end
 | |
| 		end
 | |
| 
 | |
| 		def QuickPath::function( elements, fname, rest )
 | |
| 			args = parse_args( elements, rest )
 | |
| 			Functions.pair = [0, elements.size]
 | |
| 			results = []
 | |
| 			elements.each do |element|
 | |
| 				Functions.pair[0] += 1
 | |
| 				Functions.node = element
 | |
| 				res = Functions.send( fname, *args )
 | |
| 				case res
 | |
| 				when true
 | |
| 					results << element
 | |
| 				when Fixnum
 | |
| 					results << element if Functions.pair[0] == res
 | |
| 				end
 | |
| 			end
 | |
| 			return results
 | |
| 		end
 | |
| 
 | |
| 		def QuickPath::parse_args( element, string )
 | |
| 			# /.*?(?:\)|,)/
 | |
| 			arguments = []
 | |
| 			buffer = ""
 | |
| 			while string and string != ""
 | |
| 				c = string[0]
 | |
| 				string.sub!(/^./u, "")
 | |
| 				case c
 | |
| 				when ?,
 | |
| 					# if depth = 1, then we start a new argument
 | |
| 					arguments << evaluate( buffer )
 | |
| 					#arguments << evaluate( string[0..count] )
 | |
| 				when ?(
 | |
| 					# start a new method call
 | |
| 					function( element, buffer, string )
 | |
| 					buffer = ""
 | |
| 				when ?)
 | |
| 					# close the method call and return arguments
 | |
| 					return arguments
 | |
| 				else
 | |
| 					buffer << c
 | |
| 				end
 | |
| 			end
 | |
| 			""
 | |
| 		end
 | |
| 	end
 | |
| end
 |