require 'stringio'

if RUBY_PLATFORM == 'java' then
    $Anguilla_method = 'java-sax'
elsif RUBY_ENGINE == 'ironruby' then
    $Anguilla_method = 'net-pull'
else
    begin
        require 'nokogiri'
        $Anguilla_method = 'nokogiri'
    rescue LoadError
        require 'rexml/document'
        $Anguilla_method = 'REXML'
    end
end
puts "Anguilla: use #{$Anguilla_method}"

require "rexml/streamlistener"  # All delegating callbacks will also use this as a basis

##
# This module is a meta xml parser:
# since REXML is sometimes slow, it enables to choose automatically between
# - Nokogiri, if installed
# - REXML instead
# In all cases callback is a REXML callback, for other libraries an adapter is provided
module Anguilla

    case $Anguilla_method
       when 'REXML'
             def self.parse(source,callback)
                 if source.is_a? IO then
                    REXML::Document.parse_stream(source, callback) 
                 elsif source.is_a? String then
                    if source =~ /^.+\.\w+$/ then # file name
                        callback.file = source if callback.respond_to?('file=')
                        File.open(source) { |io| REXML::Document.parse_stream(io, callback) }
                    elsif source =~ /<\w/ then # XML string
                        REXML::Document.parse_stream(StringIO.new(source), callback)
                    else
                        raise 'Cannot parse this string'
                    end
                 end
             end
             
       when 'nokogiri'
            class NokogiriCallbacksDelegate < Nokogiri::XML::SAX::Document
                def initialize(delegated) @delegated = delegated end
                def to_hash(attrs)
                    res = Hash.new
                    attrs.each { |row| res[row['localname']] = row['value'] }
                    return res
                end                    
                def start_element(name, attrs = []) @delegated.tag_start(name,to_hash(attrs)) end
                def start_element_namespace(name, attrs = [], prefix = nil, uri = nil, ns = []) @delegated.tag_start(name,to_hash(attrs)) end
                def end_element_namespace(name, prefix = nil, uri = nil) @delegated.tag_end(name) end
                def characters(text)  @delegated.text(text) end
             end
             
             def self.parse(source,callback)
                 parser = Nokogiri::XML::SAX::Parser.new(NokogiriCallbacksDelegate.new(callback))
                 if source.is_a? IO then
                    parser.parse(source)
                 elsif source.is_a? String then
                    if source =~ /^.+\.\w+$/ then # file name
                        callback.file = source if callback.respond_to?('file=')
                        File.open(source) { |io| parser.parse(io) }
                    elsif source =~ /<\w/ then # XML string
                        parser.parse(StringIO.new(source))
                    else
                        raise 'Cannot parse this string'
                    end
                 end
             end
           
       when 'java-sax'
           $ParserFactory = javax.xml.parsers.SAXParserFactory::newInstance()
           $ParserFactory.namespaceAware = true
           $ParserFactory.validating = false
                      
            class JavaSaxToRuby < org.xml.sax.helpers.DefaultHandler
                def initialize(delegated) super(); @delegated = delegated end
                
                def characters(chars,start,length) @delegated.text(java.lang.String.new(chars,start,length)) end
                def startElement(uri,localName,qName,attrs) @delegated.tag_start(firstName(qName,localName),to_hash(attrs)) end
                def endElement(uri,localName,qName) @delegated.tag_end(firstName(qName,localName)) end
                def resolveEntity(publicId,systemId) org.xml.sax.InputSource.new(java.io.StringReader.new('')) end
                    
                def firstName(name1,name2) 
                    return name1 unless name1 == nil or name1.length() == 0
                    return name2
                end
                def to_hash(attrs)
                    res = {}
                    (0 .. (attrs.getLength() - 1)).each do |index| 
                        res[firstName(attrs.getQName(index), attrs.getLocalName(index))] = attrs.getValue(index)
                    end
                    return res
                end
             end
             
             def self.parse(source,callback)
                 parser = $ParserFactory.newSAXParser()
                 if source.is_a? IO then
                     require 'rexml/document'
                    REXML::Document.parse_stream(source, callback) # cannot convert from Ruby IO to java Inputstream
                 elsif source.is_a? String then
                    if source =~ /^.+\.\w+$/ then # file name
                        callback.file = source if callback.respond_to?('file=')
                        parser.parse(java.io.File.new(source), JavaSaxToRuby.new(callback))
                    elsif source =~ /<\w/ then # XML string
                        parser.parse(org.xml.sax.InputSource.new(java.io.StringReader.new(source)), JavaSaxToRuby.new(callback))
                    else
                        raise 'Cannot parse this string'
                    end
                 end
             end  
             
       when 'net-pull'
           require 'System.Xml'
           
           def self.parse_reader(reader,callback)
               reader.MoveToContent
               while reader.Read 
                   case reader.NodeType
                      when System::Xml::XmlNodeType.Element then 
                          attrs = {}
                          (0 .. reader.AttributeCount - 1).each do |attIdx|
                              reader.MoveToAttribute attIdx 
                              attrs[reader.Name] = reader.Value
                          end
                          reader.MoveToElement
                          callback.tag_start(reader.Name, attrs)
                      when System::Xml::XmlNodeType.EndElement then 
                          callback.tag_end(reader.Name) 
                      when System::Xml::XmlNodeType.Text then 
                          callback.text(reader.Value)                         
                   end
               end
           end
           
             def self.parse(source,callback)
                 if source.is_a? IO then
                     require 'rexml/document'
                    REXML::Document.parse_stream(source, callback) # cannot convert from Ruby IO to java Inputstream
                 elsif source.is_a? String then
                    if source =~ /^.+\.\w+$/ then # file name
                        callback.file = source if callback.respond_to?('file=')
                        reader = System::Xml::XmlReader::Create(source)
                        parse_reader(reader,callback)
                        reader.Dispose
                    elsif source =~ /<\w/ then # XML string
                        require 'System.IO'
                        reader = System::Xml::XmlReader::Create(System.IO.StringReader.new(source))
                        parse_reader(reader,callback)
                        reader.Dispose
                    else
                        raise 'Cannot parse this string'
                    end
                 end
             end  
        end # Case
    
end
