# -*- ruby -*-

require 'xtemplate'
require 'xtemplate/xpath'

if( !(defined?(REXML) || defined?(XMLScan)) )
  begin
    require 'xmlscan/parser'
  rescue LoadError
    begin
      require 'rexml/document'
    rescue LoadError
      raise(LoadError, "XTemplate can't find both of xmlscan and REXML.")
    end
  end
end

module XTemplate
  module XNodeManager
    attr_reader :root

    NEWLINE  = $/

    def init_root()
      @root  = XNode.new(nil)
    end

    def push_tag(tag)
      node = XNode.new(SanitizedString.new(tag))
      @root.add_child(node)
      @root = node
    end

    def push_attr(attr)
      @root.add_attr(attr)
    end

    def push_attrval(val)
      @root.add_attrval(SanitizedString[val])
    end

    def push_content(*str)
      @root.add_child(SanitizedString[*str])
    end

    def pop_node
      @root = @root.parent
    end

    def push_optname(str)
      @root.add_optname(str)
    end

    def push_optval(val)
      @root.add_optval(SanitizedString[val])
    end

    def push_pi(target, pi)
      case target.downcase
      when "xtemplate"
	push_tag(nil)
	@root.pi = pi
	pop_node()
      else
	if( pi && pi.strip != "" )
	  push_content("<?#{target} #{pi}?>")
	else
	  push_content("<?#{target}?>")
	end
      end
    end

    def to_s
      @root.to_s
    end
  end

  if( defined?(XMLScan) )

  class XMLVisitor
    include Util
    include XNodeManager

    def initialize
      init_root()
      #@orig_kcode = $KCODE
    end

    def set_parser(x)
      @parser = x
    end

    def parse_error(msg)
      raise(RuntimeError, "%s:%d:%s" % [@parser.path, @parser.lineno, msg])
    end

    def wellformed_error(msg)
      raise(RuntimeError, "%s:%d:%s" % [@parser.path, @parser.lineno, msg])
    end

    def valid_error(msg)
      raise(RuntimeError, "%s:%d:%s" % [@parser.path, @parser.lineno, msg])
    end

    def warning(msg)
    end

    def on_xmldecl
      push_content("<?xml")
    end

    def on_xmldecl_version(str)
      push_content(" version=\"#{str}\"")
    end

    def on_xmldecl_encoding(str)
      #$KCODE = str
      push_content(" encoding=\"#{str}\"")
    end

    def on_xmldecl_standalone(str)
      push_content(" standalone=\"#{str}\"")
    end

    def on_xmldecl_other(name,value)
      push_content(" #{name}=\"#{value}\"")
    end

    def on_xmldecl_end
      push_content("?>", NEWLINE)
    end

    def on_doctype(root,pubid,sysid)
      if( pubid )
	if( sysid )
	  push_content("<!DOCTYPE #{root} PUBLIC \"#{pubid}\" \"#{sysid}\">")
	else
	  push_content("<!DOCTYPE #{root} PUBLIC \"#{pubid}\">")
	end
      else
	if( sysid )
	  push_content("<!DOCTYPE #{root} SYSTEM \"#{sysid}\">")
	else
	  push_content("<!DOCTYPE #{root}>")
	end
      end
      push_content(NEWLINE)
    end

    def on_prolog_space(str)
      push_content(str)
    end

    def on_comment(str)
      push_content("<!--#{str}-->")
    end

    def on_pi(target, pi)
      push_pi(target,pi)
    end

    def on_chardata(str)
      push_content(str)
    end

    def on_cdata(str)
      push_content("<![CDATA[#{str}]]>")
    end

    def on_entityref(ref)
      push_content("&#{ref};")
    end

    def on_charref(code)
      push_content("&\##{code};")
    end

    def on_charref_hex(code)
      push_content("&\#x#{'%x' % code};")
    end

    def on_start_document
    end

    def on_end_document
      #@root.add_child(NEWLINE)
      #$KCODE = @orig_kcode
    end

    def on_attribute(name)
      push_attr(name)
    end

    def on_attr_value(str)
      push_attrval(str)
    end

    def on_attr_entityref(ref)
      push_attrval("&#{ref};")
    end

    def on_attr_charref(code)
      push_attrval("&\##{code};")
    end

    def on_attr_charref_hex(code)
      push_attrval("&\#x#{'%x' % code};")
    end

    def on_attribute_end(name)
    end

    def on_stag(name)
      push_tag(name)
    end

    def on_stag_end_empty(name)
      on_stag_end(name)
      on_etag(name)
    end

    def on_stag_end(name)
    end

    def on_etag(name)
      pop_node()
    end
  end

  class XMLScanParser < XMLScan::XMLParser
    private
    def on_entityref(ref)
      @visitor.on_entityref ref
    end

    def on_attr_entityref(ref)
      @visitor.on_attr_entityref ref
    end
  end

  class XMLParser
    def initialize()
      @visitor = XMLVisitor.new
      @parser = XMLScanParser.new(@visitor)
      @visitor.set_parser(@parser)
    end

    def parse(doc)
      @parser.parse(doc)
      @visitor.root
    end
  end

  elsif( defined?(REXML) )

  class XMLListener
    include XNodeManager

    def initialize
      init_root()
    end

    def tag_start(name, attrs)
      push_tag(name)
      attrs.each{|attr,val|
	push_attr(attr)
	push_attrval(val)
      }
    end

    def tag_end(name)
      pop_node()
    end

    def text(str)
      push_content(REXML::Text::normalize(str))
    end

    def instruction(target, pi)
      push_pi(target, pi)
    end

    def comment(str)
      push_content("<!--#{str}-->")
    end

    def doctype(root, pub_sys, long_name, uri)
      if( root && pub_sys && uri )
	push_content("<!DOCTYPE #{root} #{pub_sys} \"#{long_name}\" \"#{uri}\">")
      elsif( root && pub_sys && !uri )
	push_content("<!DOCTYPE #{root} #{pub_sys} \"#{long_name}\">")
      elsif( root && !pubid && !uri )
	push_content("<!DOCTYPE #{root}>")
      end
      push_content(NEWLINE)
    end

    def attlistdecl(content)
      push_content("<!ATTLIST #{content}>")
    end

    def elementdecl(content)
      push_content("<!ELEMENT #{content}>")
    end

    def entitydecl(contents)
      push_content("<!ENTITY #{contents.join(' ')}>")
    end

    def notationdecl(content)
      push_content("<!NOTATION #{content}>")
    end

    def entity(s)
      push_content("%#{s};")
    end

    def cdata(s)
      push_content("<![CDATA[#{s}]]>")
    end

    def xmldecl(version, encoding, standalone)
      content = []
      if( version )
	content.push("version=\"#{version}\"")
      end
      if( encoding )
	content.push("encoding=\"#{encoding}\"")
      end
      if( standalone )
	content.push("standalone=\"#{standalone}\"")
      end
      push_content("<?xml #{content.join(' ')}?>", NEWLINE)
    end
  end

  class XMLParser
    def initialize
      @listener = XMLListener.new
    end

    def parse(io)
      REXML::Document.parse_stream(io, @listener)
      @listener.root
    end
  end

  end # REXML, XMLScan

  class XMLDocument
    include XPath

    def initialize(text)
      @hash = nil
      case text
      when XNode
	@node = text
      when Hash,Array
	@node = XNode.new()
	value_to_xml(text, @node)
      else
	@node = parse(text)
	@node.prepare(nil, {:@type => true, :include => true, :template => true})
      end
    end

    def parse(text)
      XMLParser.new.parse(text)
    end

    def strip!(recursive=true)
      @node.strip!(recursive)
    end

    def to_hash()
      @node.to_hash()
    end

    def to_s()
      @node.to_s()
    end

    def [](path)
      root = to_hash()
      xpath(path,root)
    end
  end
end
