require "runit/testcase"
require "runit/cui/testrunner"

require "rexml/document"

class XPathTester < RUNIT::TestCase
	include REXML
	SOURCE = <<-EOF
		<a id='1'>
			<b id='2' x='y'>
				<c id='3'/>
				<c id='4'/>
			</b>
			<d id='5'>
				<c id='6' x='y'/>
				<c id='7'/>
				<c id='8'/>
				<q id='19'/>
			</d>
			<e id='9'>
				<f id='10' a='b'/>
				<f id='11' a='c'/>
				<f id='12' a='d'>
					<g id='13'/>
				</f>
				<f id='14' a='d'/>
			</e>
			<m id='15'>
				<n id='16'>
					<o id='17'>
						<p id='18'/>
					</o>
				</n>
			</m>
		</a>
		EOF
	JENI_TENNISON = <<-EOJT
	<a>
		<b>
			<c>
				<d>
					<e id='x'>
						<f/>
					</e>
				</d>
			</c>
			<c>
				<d>
					<e id='y'/>
				</d>
			</c>
		</b>
		<b>
			<c>
				<d>
					<e id='z'/>
				</d>
			</c>
		</b>
	</a>
	EOJT

	def setup
	  @@doc = Document.new(SOURCE) unless defined? @@doc
		@@jeni = Document.new( JENI_TENNISON ) unless defined? @@jeni
	end

	def each_test( element, xpath, num_children )
		count = 0
		XPath::each( element, xpath ) { |child|
			count += 1
			yield child if block_given?
		}
		assert_equal num_children, count
	end

	def test_descendant
		p = XPath.first( @@doc, "//p" )
		assert_equal "p", p.name
		each_test( @@doc, "//c", 5 ) { |child| assert_equal "c", child.name }
		each_test( @@doc.root, "b//c", 2) { |child|
			assert_equal "c", child.name
		}
	end

	def test_root
		source = "<a><b/></a>"
		doc = Document.new( source )
		assert_equal "a", doc.root.name
		assert_equal "a", XPath::first( doc, "/" ).elements[1].name
	end

	def test_abbreviated_simple_child
		assert_equal "a", XPath::first(@@doc, "a").name
	end

	def test_child
		c = XPath::first( @@doc, "a/b/c" )
		assert_equal "c", c.name
		assert_equal "3", XPath::first(@@doc, "a/b/c").attributes["id"]
	end

	def test_root_child
		assert_equal "a", XPath::first(@@doc, "/a").name
		c = XPath::first( @@doc, "a/b/c" )
		assert_equal "a", XPath::first(c, "/a").name
	end

	def test_root_children
		c = XPath::first( @@doc, "a/b/c" )
		assert_equal "2", XPath::first(c, "/a/b").attributes["id"]
	end

	def test_abbreviated_step
		c = XPath::first( @@doc, "a/b/c" )
		assert_equal "a", XPath::first(@@doc.root, ".").name
		assert_equal "b", XPath::first(c, "..").name
		assert_equal "a", XPath::first(@@doc, "a/b/..").name

		doc = REXML::Document.new File.new("benchmarks/project.xml")
		each_test(doc.root, "./Description",1 ) { |child|
			assert_equal "Description",child.name
		}
	end

	# Things that aren't tested elsewhere
	def test_predicates
		#each_test( @@doc, nil, 1 )
		#each_test( @@doc.root, nil, 4 )
		assert_equal "12", XPath::first(@@doc, "a/e/f[3]").attributes["id"]
		assert_equal "13", XPath::first(@@doc, "a/e/f[3]/g").attributes["id"]
		assert_equal "14", XPath::first(@@doc, "a/e/f[@a='d'][2]").attributes["id"]
		assert_equal "14", XPath::first(@@doc, "a/e/f[@a='d'][@id='14']").attributes["id"]
		assert_equal "a", XPath::first( @@doc, "*[name()='a' and @id='1']" ).name
		each_test( @@doc, "//*[name()='f' and @a='d']", 2) { |i|
			assert_equal "f", i.name
		}
		each_test( @@doc, "//*[name()='m' or @a='d']", 3) { |i|
			assert ["m","f"].include?(i.name)
		}

		assert_equal "b", XPath::first( @@doc, "//b[@x]" ).name
	end

	def test_node_type
		doc = Document.new "<a><?foo bar?><!--comment-->text</a>"
		res = XPath::first(doc.root, "text()")
		assert_equal "text", res.to_s

		res = XPath::first(doc, "*")
		assert_equal "a", res.name

		assert_equal Instruction, XPath::first(doc.root, "processing-instruction()").type
		assert_equal Comment, XPath::first(doc.root, "comment()").type
	end

	def test_functions
		# trivial text() test
		# confuse-a-function
		source = "<a>more <b id='1'/><b id='2'>dumb</b><b id='3'/><c/> text</a>"
		doc = Document.new source
		res = ""
		XPath::each(doc.root, "text()") {|val| res << val.to_s}
		assert_equal "more  text", res

		res = XPath::first(doc.root, "b[last()]")
		assert_equal '3', res.attributes['id']
		res = XPath::first(doc.root, "b[position()=2]")
		assert_equal '2', res.attributes['id']
		res = XPath::first(doc.root, "*[name()='c']")
		assert_equal "c", res.name
	end

	def no_test_ancestor
		doc = REXML::Document.new(File.new("test/testsrc.xml"))
		doc.elements.each("//item") { |el| print el.name
			if el.attributes['x']
				puts " -- "+el.attributes['x']
			else
				puts
			end
		}
		doc.elements.each("//item/ancestor::") { |el| print el.name
			if el.attributes['x']
				puts " -- "+el.attributes['x']
			else
				puts
			end
		}
	end

	# Here are some XPath tests that were originally submitted by ...
	# The code has changed some, but the logic and the source documents are the
	# same.
	# This method reads a document from a file, and then a series of xpaths, 
	# also from a file.  It then checks each xpath against the source file.
#=begin
	def test_more
    xmlsource   = "test/testsrc.xml"
    xpathtests  = "test/xp.tst"

		doc = REXML::Document.new(File.new(xmlsource))
		#results = ""
		results = REXML::Document.new
		results.add_element "test-results"
		for line in File.new(xpathtests)
			line.strip!
			begin
				rt = doc.root
				#puts "#"*80
				print "\nDoing #{line} " ; $stdout.flush
				doc.elements.each(line) do |el| 
					print "." ; $stdout.flush
					results.root << el.clone
					#results << el.to_s
				end
				#ObjectSpace.garbage_collect
				GC::start
			rescue Exception => z
				#puts "\n'#{line}' failed"
				assert_fail "Error on line #{line}:\n#{z.message}\n"+z.backtrace[0,10].join("\n")
				#results.root.add_element( "error", {"path"=>line}).text = z.message+"\n"+z.backtrace[0,10].join("\n")
				#results << "<error path='"+line+"'>"+z.message+"</error>"
			end
		end
		#results.elements.each("//error") { |el|
		#	puts "\nERROR:\n\tPath: #{el.attributes['path']}\n\tMessage: #{el.text}"
		#}
		#assert_nil results.elements["//error"].name if results.elements["//error"]
	end
#=end

	def test_axe_descendant
		assert_equal "f", XPath::first( @@doc, "descendant::f").name
	end

	def test_axe_parent
		q = XPath.first( @@doc, "a/d/c/parent::*/q" )
		assert_equal 19, q.attributes["id"].to_i
	end

	def test_abbreviated_attribute
		assert_equal 'a', XPath::first( @@doc, "a[@id='1']" ).name
		c = XPath::first( @@doc, "a/b/c[@id='4']" )
		assert_equal 'c', c.name
		assert_equal '4', c.attributes['id']

		assert_equal "11", XPath::first( @@doc, "descendant::f[@a='c']").attributes['id']
		
		assert_equal "11", XPath::first(@@doc, "a/e/f[@a='c']").attributes["id"]
		assert_equal "11", XPath::first(@@doc, "a/e/*[@a='c']").attributes["id"]
	end

	def test_axe_self
		c = XPath::first( @@doc, "a/b/c" )
		assert_not_nil c
		assert_equal "c", c.name
		assert_equal "c", XPath::first( c, "self::node()" ).name
	end

	def test_axe_ancestor
		doc = REXML::Document.new "
		<a>
			<b id='1'>
				<c>
					<b id='2'>
						<d/>
					</b>
				</c>
			</b>
		</a>"

		d = XPath.first( doc, "//d" )
		assert_equal "d", d.name
		b = each_test( d, "ancestor::b", 2 ) { |el|
			assert((1..2) === el.attributes['id'].to_i, 
				"Expected #{el.attributes['id']} to be either 1 or 2"
			) 
		}
	end

	def test_axe_child
		m = XPath.first( @@doc, "a/child::m" )
		assert_equal 15, m.attributes['id'].to_i
	end

	def test_axe_attribute
		a = XPath.first( @@doc, "a/attribute::id" )
		assert_equal "1", a
		a = XPath.first( @@doc, "a/e/f[@id='14']/attribute::a" )
		assert_equal "d", a
	end

	def test_axe_sibling
		doc = Document.new "<a><b><c/></b><e><f id='10'/><f id='11'/><f id='12'/></e></a>"
		first_f = XPath.first( doc, "a/e/f" )
		assert_not_nil first_f
		assert_equal '10', first_f.attributes['id']
		next_f = XPath.first( doc, "a/e/f/following-sibling::node()" )
		assert_equal '11', next_f.attributes['id']

		b = XPath.first( doc, "a/e/preceding-sibling::node()" )
		assert_equal 'b', b.name
	end

	def test_lang
		doc = Document.new(File.new("test/lang.xml"))

		each_test( doc, "//language/*", 4 ) { |element|
			#puts "#{element.name}: #{element.text}"
		}
	end

	def test_namespaces_1
		source = <<-EOF
			<foo xmlns:ts="this" xmlns:tt="that">
				<ts:bar>this bar</ts:bar>
				<tt:bar>that bar</tt:bar>
			</foo>
		EOF
		doc = Document.new source
		result = XPath.each( doc, "//bar" ) {
			fail "'bar' should match nothing in this case"
		}

		namespace = {"t","this"}
		results = XPath.first( doc, "//t:bar", namespace )
		assert_equal "this bar", results.text
	end

	def test_namespaces_2
		source = <<-EOF
			<foo xmlns:ts="this" xmlns:tt="that">
				<ts:bar>this bar</ts:bar>
				<tt:bar>that bar</tt:bar>
			</foo>
		EOF
		doc = Document.new source
		res = XPath::first(doc, "//*[name()='bar']")
		assert_equal 'this', res.namespace
		res = XPath::first(doc.root, "*[namespace_uri()='that']")
		assert_equal 'that bar', res.text
	end

	def test_complex
		next_f = XPath.first( @@doc, "a/e/f[@id='11']/following-sibling::*" )
		assert_equal 12, next_f.attributes['id'].to_i
		prev_f = XPath.first( @@doc, "a/e/f[@id='11']/preceding-sibling::*" )
		assert_equal 10, prev_f.attributes['id'].to_i
		each_test( @@doc, "descendant-or-self::*[@x='y']", 2 )
	end

	def test_grouping
		t = XPath.first( @@doc, "a/d/*[name()='d' and (name()='f' or name()='q')]" )
		assert_nil t
		t = XPath.first( @@doc, "a/d/*[(name()='d' and name()='f') or name()='q']" )
		assert_equal 'q', t.name
	end

	def test_preceding
		d = Document.new "<a><b id='0'/><b/><b><c id='1'/><c id='2'/></b><b id='1'/></a>"
		start = XPath.first( d, "/a/b[@id='1']" )
		assert_equal 'b', start.name
		c = XPath.first( start, "preceding::c" )
		assert_equal '2', c.attributes['id']
	end

	def test_following
		d = Document.new "<a><b id='0'/><b/><b><c id='1'/><c id='2'/></b><b id='1'/></a>"
		start = XPath.first( d, "/a/b[@id='0']" )
		assert_equal 'b', start.name
		c = XPath.first( start, "following::c" )
		assert_equal '1', c.attributes['id']
	end

	# The following three paths were provided by 
	# Jeni Tennison <jeni@jenitennison.com>
	# a consultant who is also an XSL and XPath expert 
	#def test_child_cubed
	#	els = @@jeni.elements.to_a("*****")
	#	assert_equal 3, els.size
	#end

	#def test_div_2
	#	results = doc.elements.to_a("/ div 2")
	#end

	#def test_nested_predicates
	#	puts @@jeni.root.elements[1].elements[1].name
	#	results = @@jeni.root.elements[1].elements[1].elements.to_a("../following-sibling::*[*[name() = name(current())]]")
	#	puts results
	#end
end
