#!/usr/bin/ruby -I.

require "rexml/document"
require "rexml/streamlistener"
parsers = {}
begin
	require 'rexml/parsers/pullparser'
	parsers['pull'] = REXML::Parsers::PullParser
rescue LoadError
end
begin
	require 'rexml/parsers/sax2parser'
	parsers['sax2'] = REXML::Parsers::SAX2Parser
rescue LoadError
	require 'rexml/sax2parser'
	parsers['sax2'] = REXML::SAX2Parser
end
begin
	require 'rexml/parsers/lightparser'
	parsers['light'] = REXML::Parsers::PullParser
rescue LoadError
end
require "cps"
require 'date'

###########################################################################
# Check the system to determine which software is installed               #
###########################################################################
puts "Checking system..."
begin
	print "Have nqxml? " ; $stdout.flush
	require "nqxml/treeparser"
	require "nqxml/writer"
	require 'nqxml/streamingparser'
	require 'nqxml/info'
	have_nqxml = true
rescue Exception
	have_nqxml = false
end
puts have_nqxml
begin
	print "Have java? " ; $stdout.flush
	`java -version 2>&1`
	raise "no java" unless $?==0
	have_java = true
rescue Exception
	have_java = false
end
puts have_java
have_exml = have_java
if have_java
	begin
		print "Have EXML and flatbench test? " ; $stdout.flush
		`java -mx128m -cp EXML.jar:. flatbench verify`
		raise "no flatbench or EXML.jar" unless $? == 0
		have_exml = true
	rescue Exception
		have_exml = false
	end
  puts have_exml
end
begin
	print "Have XMLParser? " ; $stdout.flush
	require "xmlparser"
	require "xmltree"
	require "xmltreebuilder"
	have_xmlparser = true
rescue Exception
	have_xmlparser = false
end
puts have_xmlparser

###########################################################################
# Define some helper methods                                              #
###########################################################################
def ms a, b
	mod = "%0.2f"%(b/a)
	"#{b} (#{mod})"
end

BESTCOLOR = '#99FF99'
WORSTCOLOR = '#FFDD99'
NACOLOR = "#FFAAAA"
def best( table )
	best = []
	worst = []
	for row in REXML::XPath.match( table, 'TR' )
		columns = REXML::XPath.match( row, 'TD' )
		columns.each_index do |ind|
			elem = columns[ind]
			if elem.text =~ /^\d/
				cur = elem.text.split[0].to_f
				best[ ind ] = [ elem, cur ] if best[ind].nil? or cur > best[ind][1]
				worst[ ind ] = [ elem, cur ] if !worst[ind].nil? and cur < best[ind][1]
			else
				elem.attributes['BGCOLOR'] = NACOLOR
			end
		end
	end

	best.each { |pair| pair[0].attributes['BGCOLOR'] = BESTCOLOR if pair}
	worst.each { |pair| pair[0].attributes['BGCOLOR'] = WORSTCOLOR if pair}
end

DOCUMENTATION = "../docs/documentation.xml"
TUTORIAL = "../docs/tutorial.xml"

###########################################################################
# Set up the HTML output document                                         #
###########################################################################
PARSING = "Parsing small document"
ADDING = "Adding new elemnt"
DOCUMENT = "Document creation"
WRITING = "Writing tree"
DETECT = "By-hand search"
XPATH = "XPath search"
BIG = "Parse large document"
STREAM = "Stream parsing"
PULL = "Pull parsing"
SAX2 = "SAX2 parsing"
LIGHT = 'Lightweight parsing'

table = nil
new_document = true

b = CPS.new 5
if ( File.exists? 'index.html' )
	puts "Appending to existing index"
	htmlout = REXML::Document.new( File.new( 'index.html' ) )
	table = REXML::XPath.first( htmlout, '/HTML/BODY/TABLE[2]' )
	new_document = false
else
	puts "Creating new index"
	htmlout = REXML::Document.new
	htmlout << REXML::Element.new("HTML")
	body = htmlout.root.add_element "BODY"
	body.add_element("H1").text = "REXML Comparisons"

	body.add_element("H2").text = "Timing Comparison"
	body.add_element("P").text = <<-EOF
	This page contains information comparing REXML to other XML parsing
	technologies.  Included are timing comparisons.  Each benchmark was
	run for 5 seconds, which probably isn't enough to flatten out startup
	bumps and so on, but EXML in particular barfed at a 10 second benchmark
	when it ran out of memory.
	EOF
	body.add_element("P").text = "The numbers under the column headers are the versions of the library being used.  The version under the EXML column is the version of Java that the test was run under; I'm not aware of a way to get the EXML library version.  The version of Ruby is #{`ruby -v`}."
	body.add_element("P").text = "The test for hand-parsing and XPath are looking for the same elements in a large document (tutorial.xml).  Therefore, if the XPath value is half of the hand-parsing value, then on average, XPath is half as fast (but much easier!) as hand parsing."
	body.add_element("P").text = "These values are in calls-per-second; higher is better. The numbers in parenthesis are multiples of the REXML score; more than one is faster than REXML, less than one is slower.   N/A means 'Not Applicable', meaning the parser doesn't support that function.  The scores are color coded; Java scores are not included in this, since it wouldn't be meaningful.  In these tests, Java is (nearly) always faster than Ruby.  The color coding is as follows:"
	colors = body.add_element "TABLE", {"BORDER"=>"0"}
	colors = colors.add_element "TR"
	colors.add_element("TD", {"BGCOLOR"=>WORSTCOLOR}).text = "slowest"
	colors.add_element("TD", {"BGCOLOR"=>BESTCOLOR}).text = "fastest"
	colors.add_element("TD", {"BGCOLOR"=>NACOLOR}).text = "not available"

	# A row is:
	# library | parsing | new | tree | writing | detecting | xpath | big
	# | stream | pull | sax2 | light
	table = body.add_element "TABLE", {"BORDER"=>"1", "WIDTH"=>"100%"}
	table_header = table.add_element "TR"
	for name in [ 'Library', PARSING, ADDING, DOCUMENT, WRITING,
		DETECT, XPATH, BIG, STREAM, PULL, SAX2, LIGHT ]
		table_header.add_element("TH", {"HALIGN"=>"CENTER"}).add_text name
	end
end



begin
	######################################################################
	# Rexml benchmarks                                                   #
	######################################################################
	puts "GENERATING REXML"

	row = table.add_element( "TR" )
	row.add_element( "TD" ).add_text( "REXML #{REXML::VERSION} #{Date.today.to_s}" )

	# PARSING
	source = File.new "project.xml"
	parsing_rex =  b.time(PARSING){
		REXML::Document.new source
		source.rewind
	}
	source.close
	row.add_element("TD").text = parsing_rex.to_s
	source = nil
	GC.start


	# ADDING
	document = REXML::Document.new
	element = document.add_element("x")
	el_new_rex=b.time(ADDING){element.add( REXML::Element.new("x") )}
	row.add_element("TD").text = el_new_rex.to_s
	document = element = nil
	GC.start

	
	# DOCUMENT
	tag2 = tag3 = nil
	tree_rex = b.time(DOCUMENT) {
		document = REXML::Document.new()
		document.add(REXML::Element.new("tag1"))
		document.root.attributes["blah"] = "four"

		tag2 = REXML::Element.new( "tag2" )
		tag2.attributes["some"] = "value"
		document.root.add(tag2)

		tag3 = REXML::Element.new( "tag3")
		tag2.add(tag3)
	}
	row.add_element("TD").text = tree_rex.to_s
	tag2 = tag3 = nil
	GC.start


	# WRITING
	source = File.new TUTORIAL
	document = REXML::Document.new(source)
	source.close
	string = nil
	writing_rex = b.time(WRITING) {
		string = ""
		document.write string, -1
	}
	row.add_element("TD").text = writing_rex.to_s
	source = string = nil
	GC.start


	# DETECT
	node = nil
	detecting_rex = b.time(DETECT) {
		element = document.root.detect{ |node|
			node.kind_of?(REXML::Element) and node.name == "overview"
		}
		element = element.detect{ |node|
			node.kind_of?(REXML::Element) and node.name == "general"
		}
		element = element.detect{ |node|
			node.kind_of?(REXML::Element) and
			node.name == "subsection" and
			node.attributes['title'] == 'Iterating'
		}
	}
	row.add_element("TD").text = detecting_rex.to_s
	node = element = nil
	GC.start


	# XPATH
	xpath_rex = b.time(XPATH) {
		document.root.elements["overview/general/subsection[@title='Iterating']"]
	}
	row.add_element("TD").text = xpath_rex.to_s
	GC.start


	# BIG
	source = File.new DOCUMENTATION
	parsebig_rex = b.time(BIG) {
		REXML::Document.new(source)
		source.rewind
	}
	source.close
	row.add_element("TD").text = parsebig_rex.to_s
	document = source = element = nil
	GC.start 


	# STREAM
	class Listener
		include REXML::StreamListener
	end
	source_file = File.new(DOCUMENTATION)
	listener = Listener.new
	source = nil
	stream_rex = b.time(STREAM) {
		source = REXML::SourceFactory.create_from source_file
		REXML::Document.parse_stream source, listener
		source_file.rewind
	}
	row.add_element("TD").text = stream_rex.to_s
	source_file.rewind
	GC.start


	# PULL
	if parsers['pull']
		pull_rex = b.time(PULL) do
			begin
				source = REXML::IOSource.new( source_file )
				parser = REXML::Parsers::PullParser.new( source )
				while parser.has_next?
					parser.pull
				end
			rescue Exception
				puts $!
			end
			source_file.rewind
		end
		row.add_element("TD").text = pull_rex.to_s
		source_file.rewind
		GC.start
	else
		row.add_element("TD").text = ""
	end


	# SAX2
	sax2_rex = b.time(SAX2) do
		source = REXML::IOSource.new( source_file )
		sp = parsers['sax2'].new( source )
		sp.parse
		source_file.rewind
	end
	row.add_element("TD").text = sax2_rex.to_s
	source_file.rewind
	GC.start

	
	if parsers['light']
		# Lightweight parsing
		parser = REXML::Parsers::LightParser.new( source_file );
		lw_rex = b.time( LIGHT ) do
			parser.parse
			parser.rewind
		end
		row.add_element('TD').text = lw_rex.to_s

		source_file.close
		source_file = nil
		GC.start
	else
		row.add_element('TD').text = ""
	end


	if new_document
		######################################################################
		# NQXML                                                              #
		######################################################################
		if have_nqxml
			puts "\nGENERATING NQXML"

			row = table.add_element( 'TR' )
			row.add_element( 'TD' ).add_text( "NQXML #{NQXML::Version} #{Date.today.to_s}" )

			# PARSING
			source = File.new "project.xml"
			val = b.time(PARSING){
				NQXML::TreeParser.new(source).document
				source.rewind
			}
			source.close
			row.add_element("TD").text = ms(parsing_rex, val)
			source = nil
			GC.start


			# ADDING
			document = NQXML::Document.new
			element = document.setRoot(NQXML::Tag.new("x", {}))
			val=b.time(ADDING){element.addChild(NQXML::Tag.new("x", {}))}
			row.add_element("TD").text = ms(el_new_rex, val)
			document = element = nil
			GC.start

			
			# DOCUMENT
			tag2 = tag3 = nil
			val = b.time(DOCUMENT) {
				document = NQXML::Document.new()
				document.setRoot(NQXML::Tag.new("tag1", {"blah"=>"four"}))

				tag2 = NQXML::Tag.new( "tag2", {"some"=>"value"})
				node = document.rootNode.addChild( tag2 )

				tag3 = NQXML::Tag.new( "tag3", {})
				node.addChild( tag3 )
			}
			row.add_element("TD").text = ms(tree_rex, val)
			document = tag2 = tag3 = nil
			GC.start


			# WRITING
			source = File.new TUTORIAL
			document = NQXML::TreeParser.new(source).document
			source.close
			writer = string = nil
			val = b.time(WRITING) {
				string = ""
				writer = NQXML::Writer.new string
				writer.writeDocument( document )
			}
			row.add_element("TD").text = ms(writing_rex, val)
			source = writer = string = nil
			GC.start


			# DETECT
			node = entity = nil
			val = b.time(DETECT) {
				element = document.rootNode.children.detect{ |node|
					entity = node.entity
					entity.kind_of?(NQXML::Tag) and entity.name == "overview"
				}
				element = element.children.detect{ |node|
					entity = node.entity
					entity.kind_of?(NQXML::Tag) and entity.name == "general"
				}
				element = element.children.detect{ |node|
					entity = node.entity
					entity.kind_of?(NQXML::Tag) and
					entity.name == "subsection" and
					entity.attrs['title'] == 'Iterating'
				}
			}
			row.add_element("TD").text = ms(detecting_rex, val)
			element = document = node = entity = nil
			GC.start


			# XPATH
			na = row.add_element("TD").text = "N/A"
			na.attributes["BGCOLOR"] = NACOLOR


			# BIG
			source = File.new DOCUMENTATION 
			val = b.time(BIG) {
				NQXML::TreeParser.new(source).document
				source.rewind
			}
			source.close
			row.add_element("TD").text = ms(parsebig_rex, val)
			source = nil
			GC.start


			# STREAM
			source_file = File.new(DOCUMENTATION)
			listener = Listener.new
			parser = entity = nil
			val = b.time(STREAM) {
				parser = NQXML::StreamingParser.new(source_file)
				parser.each { | entity | }
				source_file.rewind
			}
			source_file.close
			source_file = nil
			row.add_element("TD").text = ms(stream_rex,val)
			parser = entity = nil


			# PULL
			na = row.add_element("TD").text = "N/A"
			na.attributes["BGCOLOR"] = NACOLOR


			# SAX2
			na = row.add_element("TD").text = "N/A"
			na.attributes["BGCOLOR"] = NACOLOR
			GC.start
		end # have_nqxml



		if have_xmlparser
			puts "\nGENERATING XMLParser"
			# TAKAHASHI Masayoshi <maki@inac.co.jp>
			
			row = table.new_element('TR')
			row.add_element('TD').text = "XMLParser"

			# PARSING
			class SampleBuilder < XML::SimpleTreeBuilder; end
			source = File.new "project.xml"
			val = b.time(PARSING){
				SampleBuilder.new().parse(source)
				source.rewind
			}
			source.close
			row.add_element("TD").text = ms(parsing_rex, val)
			source = nil
			GC.start


			# ADDING
			document = XML::DOM::Document.new
			element = XML::DOM::Element.new("x", [])
			document.appendChild( element )
			val=b.time(ADDING){
				element.appendChild(XML::DOM::Element.new("x", []))
			}
			row.add_element("TD").text = ms(el_new_rex, val)
			document = element = nil
			GC.start


			# DOCUMENT
			tag2 = tag3 = node = nil
			val = b.time(DOCUMENT) {
				document = XML::DOM::Document.new()
				document.appendChild(XML::DOM::Element.new("tag1",
								 [Attr.new("blah",
										"four") ]))
				tag2 = XML::DOM::Element.new("tag2", [Attr.new("some",
										"value") ])
				node = document.appendChild( tag2 )
				tag3 = XML::DOM::Element.new("tag3", [])
				node = document.appendChild( tag3 )
			}
			row.add_element("TD").text = ms(tree_rex, val)
			document = tag2 = tag3 = nil
			GC.start

			# WRITING
			na = row.add_element("TD").text = "N/A"
			na.attributes["BGCOLOR"] = NACOLOR


			# DETECT
			entity = document = nil
			File.open(TUTORIAL) do |source|
				document = SampleBuilder.new().parse(source)
			end
			val = b.time(DETECT) {
				element = document.getElementsByTagName("overview")[0]
				nodeList = element.getElementsByTagName("general")[0]
				nodeList = element.getElementsByTagName("subsiction")
				nodeList.each{ |node|
					if node.getAttribute('title') == 'Iterating'
						element = node
						break
					end
				}
			}
			row.add_element("TD").text = ms(detecting_rex, val)
			element = document = node = entity = nil
			GC.start


			# XPATH
			na = row.add_element("TD").text = "N/A"
			na.attributes["BGCOLOR"] = NACOLOR


			# BIG
			source = File.new DOCUMENTATION 
			val = b.time(BIG) {
				SampleBuilder.new().parse(source)
				source.rewind
			}
			source.close
			row.add_element("TD").text = ms(parsebig_rex, val)
			source = nil
			GC.start


			# STREAM
			source_file = File.new(DOCUMENTATION)
			parser = entity = nil
			val = b.time(STREAM) {
				parser = XMLParser.new.parse(source_file)
				source_file.rewind
			}
			source_file.close
			source_file = nil
			row.add_element("TD").text = ms(stream_rex,val)
			parser = entity = nil


			# XPATH
			na = row.add_element("TD").text = "N/A"
			na.attributes["BGCOLOR"] = NACOLOR


			# SAX2
			na = row.add_element("TD").text = "N/A"
			na.attributes["BGCOLOR"] = NACOLOR
			GC.start
		end

		if have_exml
			tags = [parsing, el_new, tree, writing, detecting, xpath, parsebig, stream, pull]
			rextags = [parsing_rex, el_new_rex, tree_rex, writing_rex, detecting_rex, xpath_rex, parsebig_rex, stream_rex, pull_rex]
			ind = 0
			block = proc do |line|
				puts line
				lines = line.scan(/\((.*?)\)/m)
				if lines[0].nil?
					na = row.add_element("TD").text = "N/A"
					na.attributes["BGCOLOR"] = NACOLOR
				else
					val = lines[0][0].chop.chop.to_f
					row.add_element("TD").text = ms(rextags[ind], val)
				end
				ind += 1
			end

			# Electric NOJIT
			puts "\nGENERATING Electric, no JIT"
			row = table.add_element('TR')
			row.add_element('TD').text = 'Java (no JIT)'
			process = IO.popen("java -mx64m -Djava.compiler=nojit -cp .:EXML.jar flatbench") 
			ind = 0
			process.each( &block )

			# Electric JIT
			puts "\nGENERATING Electric, JIT"
			row = table.add_element('TR')
			row.add_element('TD').text = 'Java (JIT)'
			process = IO.popen("java -mx64m -cp .:EXML.jar flatbench") 
			ind = 0
			process.each( &block )
		end
	end

rescue 
	puts "Error:"
	puts $!
	puts "Writing index anyway."
end

best(table)

body.add_element "P" if new_document

fout = File.new("index.html", "w+")
htmlout.write fout
fout.close
