require "rexml/functions"

module REXML
	class XPath
		def XPath::first( element, xpath=nil )
			each( element, xpath ) { |child| 
				return child 
			}
			return nil
		end

		def XPath::each( element, path=nil, &block )
			if path.nil?
				element.each do |child| 
					yield child if child.kind_of? Element 
				end
				return
			end
			path =~ /(\.+|\/+|.*)/
			match = $1
			rest = $'
			case match
			when "."
				match [element], rest, &block
			when ".."
				match [element.parent], rest, &block
			when "..."
				ancestors = []
				until element.parent.nil?
					ancestors << element.parent
				end
				match ancestors, rest, &block
			when "/"
				if path == "/"
					yield element.root
				else
					match element.root.dup_children, rest, &block
				end
			when "//"
				match [element.root], path, &block
			else
				if path =~ /([\w-]+)::/
					match [element], path, &block
				else
					match element.dup_children, path, &block
				end
			end
		end

		def XPath::match( elements, path, &block)
			if path.nil?
				elements.each{ |child| yield child if child.kind_of? Element }
				return
			end
			path =~ /(\.+|\/+|\*|\w+::|[\w-]+\(|[\w-]+|\[)(.*)/
			name,rest = $1,$2
			child = nil		# speed
			case name
			when /::/
				axe elements, name, rest, &block
			when /\(/	#/
				yield function elements, path
			when "["
				predicate elements, "[#{rest}", &block
			when "*"
				elements.each do |child|
					match([child], rest, &block) if child.kind_of? Element
				end
			when "/"
				elements.each do |child|
					match(child.dup_children,rest,&block) if child.kind_of? Element
				end
			when "//"
				elements.each do |child|
					match([child], rest, &block) if child.kind_of? Element
				end
				elements.each do |child|
					match(child.dup_children,path,&block) if child.kind_of? Element
				end
			when "."
				match elements, rest, &block
			when ".."
				elements.each do |child|
					match([child.parent], rest, &block) if child.kind_of? Element
				end
			when "..."
				element.each do |child|
					ancestors = []
					until element.parent.nil?
						ancestors << element.parent
						element = element.parent
					end
					match ancestors, rest, &block
				end
			else
				elements.delete_if do |child|
					unless child.kind_of? Element
						true
					else
						name.nil? ? false : !child.has_name?(name)
					end
				end
				match elements, rest, &block
			end
		end

		def XPath::predicate( children, predicates, &block )
			predicates = predicates.scan(/\[(.*?)\]/)
			rest = $'
			# predeclare these for speed
			predicate = child = nil
			predicates.each do |predicate|
				predicate = predicate[0]	# scan puts everything into subarrays
				if predicate[0] == ?@
					predicate =~ /@(\w+)\s*=\s*["'](.*?)['"]/
					key,value = $1, $2
					children.delete_if do |child|
						not (child.kind_of? Element and
						child.attributes[key] == value )
					end
				elsif predicate =~ /^\d+$/
					index = predicate.to_i
					count = 0
					children = [ children.find do |child|
						child.kind_of? Element and
						(count += 1) == index
					end ]
				elsif predicate =~ /([\w-]+\(.*?\))=(\d+|['"].*?['"])/
					func,test = $1, $2
					if test[0] == ?' or test[0] == ?"	#'
						test = test[1..-2]
					else
						test = test.to_i
					end
					res = function children, func
					res.each_index do |child|	# child already declared (speed)
						yield children[child] if res[child] == test
					end
				elsif predicate =~ /[[\w-]+\(.*?\)/
				  res = function children, predicate
				  predicate children, "[#{res}]", &block
				else
					predicate =~ /\s*(\w+)\s*=\s*["'](.*?)['"]/
					key,value = $1, $2
					
					children.delete_if do |child|
						not (child.kind_of? Element and child.text == value)
					end
				end
			end
			match children, rest, &block
		end

		# Filters an array against an axe and a path
		def XPath::axe( elements, axe, path, &block )
			axe = axe[0..-3]
			element = nil	# predeclare for speed
			case axe
			when "child"
				match elements, "/#{path}", &block
			when "attribute"		# Actually returns an attribute, which means
				path =~ /.*/		# we can be sure that the path is in a given
				name = $&			# format.  Also, we're returning a String
				elements.each do |element|
					yield element.attributes[name] if child.kind_of? Element and
						!element.attributes[name].nil?
				end
			when /^descendant/
				match(elements, path, &block) if axe == "descendant-or-self"
				elements.each do |element|
					match element.dup_children, "//#{path}", &block
				end
			when /^ancestor/
				match(elements, path, &block) if axe == "ancestor-or-self"
				ancestors = []
				elements.each do |element|
					until element.parent.nil?
						break if ancestors.include? element.parent
						ancestors << element.parent
						element = element.parent
					end
				end
				match ancestors, path, &block
			when "self"
				match elements, path, &block
			when /-sibling::$/
				elements.each do |element|
					match([element.next_sibling], path, &block)
				end if axe == "following-sibling"
				elements.each do |element|
					match([element.previous_sibling],path,&block)
				end if axe == "preceeding-sibling"
			end
		end

		def XPath::function( elements, path )
			path =~ /([\w-]+)\((.*)\)/
			function = $1.tr "-", "_"
			arguments = $2
			rest = $'
			arguments = arguments.scan( /[^,]+/ )
			arguments.unshift elements
			return Functions.send( function, *arguments )
		end

		# A private helper method
		def XPath::literalize name
			literal = !(name =~ /^'.*'$/).nil?
			name = name[1..-2] if literal
			[literal, name]
		end
	end
end
