# -*- ruby -*-
#
# This is not W3C XPath!
#

require 'xtemplate'

module XTemplate
  module XPath
    include Util

    PathSeparator = '/'
    PathUnion   = '|'
    PathIfNot   = '?'
    AnyNode     = '*'
    AnyNode0    = ''
    AnyNode2    = '**'
    TextNode    = nil
    RootNode    = "\000"
    CurrentNode = "."
    ParentNode  = ".."

    class Action
      include XPath
      include Util

      def _alt_exist?(name)
        @alts ||= {}
        @alts.key?(name)
      end

      def _alt(name)
        @alts ||= {}
        @alts[name] = true
      end

      def _unalt(name)
        @alts.delete(name)
      end

      def alt(val, *args)
        args.each{|arg|
          if( _alt_exist?(arg) )
            val = nil
          else
            _alt(arg)
          end
        }
        val
      end

      def unalt(val, *args)
        args.each{|arg|
          _unalt(arg)
        }
        val
      end

      def attr(val, *args)
	case val
	when Hash
	  newval = val.dup
	  args.each{|a|
	    a.strip!
	    if( v = newval[a] )
	      newval.delete(a)
	      newval['@'+a] = v
	    end
	  }
	  newval
	when Array
	  val.collect{|v| attr(v, *args)}
	else
	  nil
	end
      end

      def unattr(val, *args)
	case val
	when Hash
	  newval = val.dup
	  args.each{|a|
	    a.strip!
	    k = '@'+a
	    if( v = newval[k] )
	      newval.delete(k)
	      newval[a] = v
	    end
	  }
	  newval
	when Array
	  val.collect{|v| unattr(v, *args)}
	else
	  nil
	end
      end
      
      def node(val)
	case val
	when Hash
	  val.reject{|k,v| (k[0] == ?@ || k == TextNode)}
	when Array
	  val.collect{|v| node(v)}
	else
	  nil
	end
      end
      
      def text(val)
	case val
	when Hash
	  val[TextNode]
	when Array
	  val.collect{|v| text(v)}
	else
	  val
	end
      end
      
      def copy(val, x, y)
	case val
	when Hash
	  newval = val.dup
	  if( v = newval[x] )
	    newval[y] = v
	  end
	  newval
	when Array
	  val.collect{|v| copy(v, x, y)}
	else
	  nil
	end
      end

      def rename(val, x, y)
	case val
	when Hash
	  newval = val.dup
	  if( v = newval[x] )
	    newval[y] = v
	    newval.delete(x)
	  end
	  newval
	when Array
	  val.collect{|v| rename(v, x, y)}
	else
	  nil
	end
      end
      
      def tag(val, tag)
	case val
	when Hash
	  newval = val.dup
	  if( v = newval[TextNode] )
	    newval[tag] = v
	    newval.delete(TextNode)
	  end
	  newval
	when Array
	  val.collect{|v| tag(v, tag)}
	else
	  {tag => val}
	end
      end
      
      def untag(val, tag)
	case val
	when Hash
	  newval = val.dup
	  if( v = newval[tag] )
	    newval[TextNode] = v
	    newval.delete(tag)
	  end
	  newval
	when Array
	  val.collect{|v| untag(v,tag)}
	else
	  nil
	end
      end
      
      def sort(val, path=nil)
	if( val.is_a?(Array) )
	  if( path )
	    newval = val.sort{|x,y|
	      path.split("/").each{|key|
		key.strip!
		if( x.is_a?(Hash) && y.is_a?(Hash) )
		  x,y = x[key], y[key]
		end
	      }
	      if( x.is_a?(Comparable) && y.is_a?(Comparable) )
		x <=> y
	      else
		0
	      end
	    }
	  else
	    newval = val.sort{|x,y|
	      if( x && y )
		x <=> y
	      else
		0
	      end
	    }
	  end
	else
	  val
	end
      end
      
      def int(val)
	if( val[TextNode] )
	  newval = val.dup
	  newval[TextNode] = newval[TextNode].to_i
	else
	  newval = val.to_i
	end
	newval
      end
      
      def float(val)
	if( val[TextNode] )
	  newval = val.dup
	  newval[TextNode] = newval[TextNode].to_i
	else
	  newval = val.to_f
	end
	newval
      end
      
      
      def string(val)
	if( val[TextNode] )
	  newval = val.dup
	  newval[TextNode] = newval[TextNode].to_i
	else
	  newval = val.to_s
	end
	newval
      end

      def array(val)
        if( val.is_a?(Enumerable) )
          newval = val.to_a
        else
          newval = val
        end
        newval
      end
      
      def strip(val)
	case val
	when Hash
	  if( val[TextNode] )
	    newval = val.dup()
	    newval[TextNode] = newval[TextNode].strip()
	  else
	    newval = val
	  end
	when Array
	  newval = val.reject{|v|
	    case v
	    when Hash, Array
	      false
	    else
	      v.to_s() =~ /\A\s*\z/
	    end
	  }
	else
	  newval = val.to_s().strip()
	  if( newval.size == 0 )
	    newval = nil
	  end
	end
	newval
      end
      
      def size(val)
	case val
	when Array
	  val.size
	when nil
	  0
	else
	  1
	end
      end
      
      def index(val, tag, ini=0)
	if( ini )
	  if( ini =~ /^([1-9]\d*)$/ )
	    ini = $1.to_i
	  end
	end
	case val
	when Hash
	  newval = val.dup
	  newval[tag] = ini
	when Array
	  newval = []
	  idx = ini
	  val.each{|v|
	    case v
	    when Hash
	      v = v.dup
	      v[tag] = idx
	      newval.push(v)
	    else
	      v = {TextNode => v, tag => idx}
	      newval.push(v)
	    end
	    idx = idx.succ
	  }
	else
	  newval = {tag => ini, TextNode => val}
	end
	newval
      end
      
      def flatten(val)
	warn("The action 'flatten' and 'hash' will be obsoleted.")
	case val
	when Array
	  newval = {}
	  text   = ""
	  val.each{|elem|
	    case elem
	    when Hash
	      elem.each{|k,v|
		newval[k] = v
	      }
	    else
	      text.concat(elem.to_s)
	    end
	  }
	  if( text.size > 0 )
	    newval[TextNode] = text
	  end
	else
	  newval = val
	end
	newval
      end
      alias hash flatten
      
      def dump(val, name=nil)
        if( name )
          parent = XNode.new(name)
        else
          parent = XNode.new()
        end
        value_to_xml(val, parent)
        parent.to_s
      end

      alias __sanitize__ sanitize
      def sanitize(val)
        s = String.new(val.to_s)
        __sanitize__(s)
      end

      alias __unsanitize__ unsanitize
      def unsanitize(val)
        s = SanitizedString[val.to_s]
        SanitizedString[__unsanitize__(s)]
      end
      
      def reverse(val)
	if( val.is_a?(Array) )
	  val.reverse
	else
	  nil
	end
      end
      
      def delete(val, *args)
	case val
	when Hash
	  newval = val.dup
	  args.each{|s|
	    s.strip!
	    if( newval[s] )
	      newval.delete(s)
	    end
	  }
	  newval
	when Array
	  val.collect{|v| delete(v, *args)}
	else
	  nil
	end
      end
      
      def push(val, name)
	stack = ((Thread.current[:xtemplate] ||= {})[name.intern] ||= [])
	stack.push(val)
	case val
	when Hash
	  {}
	when Array
	  []
	else
	  nil
	end
      end
      
      def pop(val, name)
	stack = ((Thread.current[:xtemplate] ||= [])[name.intern] ||= [])
	v = stack.pop
	[val, v].flatten
      end
      
      def pop_all(val, name)
	stack = ((Thread.current[:xtemplate] ||= [])[name.intern] ||= [])
	newval = [val, stack.dup].flatten
	stack.clear
	newval
      end
      
      def clear_stack(val, name)
	stack = ((Thread.current[:xtemplate] ||= [])[name.intern] ||= [])
	stack.clear
	val
      end

      def clear(val)
	[]
      end
      
      def import(val, *args)
	case args[0]
	when /^xml:\/\/(.+)/
	  fn = $1
	  doc = File.open(fn){|f|f.read}
	  parser = XMLParser.new()
	  node = parser.parse(doc)
	  node.prepare()
	  XMLDocument.new(node).to_hash
	when /^data:\/\/(.+)/
	  fn = $1
	  eval(File.open(fn){|f|f.read})
	when /^var:(.+)/
	  eval($1)
	when /^yaml:\/\/(.+)/
	  require 'xtemplate/yaml'
	  fn = $1
	  File.open(fn){|f|YAMLDocument.new(f)}
	when /^soap:\/\/(.+)/
	  require 'soap/driver'
	  args.shift
	  endpoint  = $1
	  namespace = args.shift
	  if( namespace =~ /^\s*$/ )
	    namespace = nil
	  end
	  obj = SOAP::Driver.new(nil, nil, namespace, "http://#{endpoint}")
	  m = args.shift
	  argv = args.collect{|s| eval_expr(s,val,nil) }
	  i = -1
	  obj.addMethod(m, *(argv.collect{ i+=1; "arg" + i.to_s }))
	  obj.call(m, *argv)
	when /^xmlrpc:\/\/(.+)/
	  require 'xmlrpc/client'
	  m = args[1]
	  argv = args[2..-1].collect{|s| eval_expr(s,val,nil) }
	  client = XMLRPC::Client.new2("http://#{$1}")
	  client.call(m, *argv)
	when /^dbi:(.+)/
	  require 'dbi'
	  h = DBI.connect("dbi:#{$1}")
	  query = args[1..-1].collect{|s|s.gsub(/\#{(.+)}/){eval_expr($1,val,nil).to_s }}.join(',')
	  newval = []
	  h.execute(query){|sth|
	    while( r = sth.fetch_hash )
	      newval.push(r)
	    end
	  }
	  newval
	else
	  eval(args[0])
	end
      end

      def time(val, fmt, tag=nil)
	if( tag )
	  case val
	  when Array
	    val.collect{|v| time(v, fmt, tag)}
	  when Hash
	    str = Time.now.strftime(fmt)
	    newval = val.dup
	    tag ||= TextNode
	    newval[tag] = str
	    newval
	  else
	    if( val )
	      str = Time.now.strftime(fmt)
	      newval = {
		tag => str,
		TextNode => val,
	      }
	      newval
	    else
	      nil
	    end
	  end
	else
	  Time.now.strftime(fmt)
	end
      end
      alias date time

      def p(val, out=nil)
	case out
	when "stdout"
	  f = $stdout
	when "stderr"
	  f = $stderr
	else
	  f = $stdout
	end
	f.print(value_inspect(val),"\n")
	val
      end
    end  # end of Action

    def value_inspect(val)
      case val
      when Hash
	val = val.dup
	val.delete(ParentNode)
	"{" + val.collect{|k,v| "#{k.inspect}=>#{value_inspect(v)}" }.join(", ") + "}"
      when Array
	"#{val.class}[" + val.collect{|v| value_inspect(v)}.join(", ") + "]"
      when SanitizedString
	val.to_s.inspect
      else
	val.inspect
      end
    end

    def value_depth(val)
      case val
      when Hash
	max = 0
	val.each{|key,val|
	  if( key == ParentNode )
	    next
	  end
	  if( (x = value_depth(val)) > max )
	    max = x
	  end
	}
	max + 1
      when Array
	max = 0
	val.each{|val|
	  if( (x = value_depth(val)) > max )
	    max = x
	  end
	}
	max
      else
	0
      end
    end

    def value_p(val)
      puts(value_inspect(val))
    end

    def value_to_xml(val, parent)
      case val
      when Hash
	val.each{|k,v|
	  case k
	  when ParentNode
	    # do nothing
	  when TextNode
	    parent.add_child(v)
	  when /^@(.+)/
	    parent.add_attr($1)
	    parent.add_attrval(v)
	  else
	    node = XNode.new(k)
	    value_to_xml(v,node)
	    parent.add_child(node)
	  end
	}
      when Array
	val = val.collect{|v|
	  case v
	  when Hash, Array
	    v
	  else
	    {TextNode => v}
	  end
	}
	val.each{|v|
	  value_to_xml(v,parent)
	}
      when nil
	nil
      else
	parent.add_child(val)
      end
    end

    # also implemented in xt.c
    def path_split(path)
      i = 0
      l = 0
      s = 0
      ids = []
      path.each_byte{|c|
	case c
	when ?{, ?[
	  l += 1
	when ?}, ?]
	  l -= 1
	when ?/
	  if( l == 0 )
	    ids.push(path[s..i].chop)
	    s = i + 1
	  end
	end
	i += 1
      }
      ids.push(path[s..i])
      if( path[0] == ?/ )
	ids[0] = RootNode
      end
      ids
    end

    # also implemented in xt.c
    def args_split(args)
      args = unsanitize(args)
      i = 0
      l = false
      s = 0
      escape = false
      inref  = false
      ids = []
      args.each_byte{|c|
	case c
	when ?', ?"
	  if( escape )
	    escape = false
	  else
	    if( l )
	      l = false
	    else
	      l = true
	    end
	  end
	when ?\\
	  escape = true
	when ?,
	  if( !l )
	    ids.push(args[s..i].chop)
	    s = i + 1
	  end
	end
	i += 1
      }
      ids.push(args[s..i])
      ids.collect{|s| s.strip.gsub(/(\A['"])|(["']\z)/,'') }.reject{|s| s.empty? }
    end

    # also implemented in xt.c
    def cond_split(path)
      i = 0
      l = 0
      s = 0
      xs = []
      path.each_byte{|c|
	case c
	when ?{, ?[
	  if( l == 0 )
	    case i
	    when 0
	      xs.push("")
	    when s
	      # do nothing
	    else
	      xs.push(path[s..(i-1)])
	    end
	    s = i
	  end
	  l += 1
	when ?}, ?]
	  l -= 1
	  if( l == 0 )
	    xs.push(path[s..i])
	    s = i + 1
	  end
	end
	i += 1
      }
      unless( s == i )
	xs.push(path[s..i])
      end
      xs
    end

    def xpath(path, data, plugin=nil)
      plugin ||= Action.new
      x = value_by_path2(path, data, nil, data, plugin)
      if( x.nil? )
	nil
      else
	case x
	when Array
	  x
	else
	  [x]
	end
      end
    end

    # Returns an empty array instead of 'nil' to eliminate a tag.
    def value_by_path2(path, data, pdata, rdata, plugin)
      if( path.is_a?(String) )
	if( path =~ /([\?\|])/ )
	  case $1[0]
	  when ?|
	    paths = path.split(PathUnion)
	    return paths.collect{|path|
	      path.strip!
	      value_by_path2(path, data, pdata, rdata, plugin)
	    }.flatten
	  when ??
	    paths = path.split(PathIfNot)
	    for path in paths
	      path.strip!
	      vals = value_by_path2(path, data, pdata, rdata, plugin)
	      if( vals.nil? || vals.empty? )
		next
	      end
	      break
	    end
	    return vals
	  end
	end
	ids = path_split(path)
      else
	ids = path.dup
      end

      x = value_by_path(ids, data, pdata, rdata, plugin)

      if( x )
	case x
	when Array
	  x.reject!{|e|e.nil?}
	  if( x.size == 1 )
	    x[0]
	  else
	    x
	  end
	else
	  x
	end
      else
	[]
      end
    end

    def normalize(val)
      case val
      when Hash
	if( val[TextNode] && val.size == 1 )
	  val = val[TextNode]
	end
      when XArray
	val = val.reject{|v| v.is_a?(String) && (v =~ /\A\s*\z/) }
	if( val.size > 1 )
	  catch(:break){
	    h = {}
	    node_p = false
	    val.each{|v|
	      case v
	      when Hash
		v.each{|k,x|
		  if( h[k] )
		    throw(:break)
		  else
		    h[k] = x
		    unless( k && (k[0] == ?@) )
		      node_p = true
		    end
		  end
		}
	      else
		if( h[TextNode] )
		  throw(:break)
		else
		  h[TextNode] = v
		end
	      end
	    }
	    if( h[TextNode] )
	      if( h.size == 1 )
		h = h[TextNode]
	      else
		if( node_p )
		  throw(:break)
		end
	      end
	    end
	    val.clear
	    val.push(h)
	  }
	end
      when Array
        val.reject!{|x|x.nil?}
      end
      val
    end

    VALUE_BY_PATH1 = %q`
    # (1)path (2)current data (3)parent data (4)root data (5)plugin object
    def value_by_path(ids, data, pdata, rdata, plugin)
      data[ParentNode] ||= pdata
      hd,*ids = ids
      case hd
      when nil
	data
      when RootNode
	value_by_path(ids, rdata, nil, rdata, plugin)
      when CurrentNode
	value_by_path(ids, data, pdata, rdata, plugin)
      when ParentNode
	value_by_path(ids, data[ParentNode], nil, rdata, plugin)
      when AnyNode0
	[ value_by_path(ids, data, pdata, rdata, plugin),
	  value_by_path([AnyNode2]+ids, data, pdata, rdata, plugin)
	].flatten
      when /^(@\*|\*\*|\*)([\[\{].+[\]\}])?$/
	if( $1 == '@*' )
	  opt = $2
	  if( opt )
	    val = data.keys.select{|x| x[0] == ?@}.collect{|key|
	      key = key + opt
	      value_by_path([key]+ids, data, pdata, rdata, plugin)
	    }.flatten
	  else
	    val = data.keys.select{|x| x[0] == ?@}.collect{|key|
	      value_by_path([key]+ids, data, pdata, rdata, plugin)
	    }.flatten
	  end
	elsif( $1 == '*' )
	  opt = $2
	  if( opt )
	    val = data.keys.select{|x| !(x == ParentNode || x == TextNode) }.collect{|key|
	      key = key + opt
	      value_by_path([key]+ids, data, pdata, rdata, plugin)
	    }.flatten
	  else
	    val = data.keys.reject{|x| (x == ParentNode || x == TextNode) }.collect{|key|
	      value_by_path([key]+ids, data, pdata, rdata, plugin)
	    }.flatten
	  end
	else # case '**'
	  opt = $2
	  ss = []
	  vals = []
	  d = value_depth(data)
	  # {'1' => {'2' => {'3' => '...'}}} d = 3
	  # { *  => { *  => {'3' => '...'}}}
	  if( opt )
	    for i in 0..(d - 2)
	      ss.push(AnyNode + opt)
	      v = value_by_path(ss+ids, data, pdata, rdata, plugin)
	      ss.pop
	      ss.push(AnyNode)
	      vals.push(v)
	    end
	  else
	    for i in 0..(d - 2)
	      ss.push(AnyNode)
	      v = value_by_path(ss+ids, data, pdata, rdata, plugin)
	      vals.push(v)
	    end
	  end
	  val = vals.flatten
	end
	val
      else
    ` # don't eliminate this quote.

    VALUE_BY_PATH2_1 = %q`
	case hd[-1]
	when ?]
	  hd,*opts = cond_split(hd)
	when ?}
	  hd,*opts = cond_split(hd)
	else
	  opts = []
	end

	if( hd.size > 0 )
          hd,recv,*rargs = hd.split(".")
	  val = data[hd]
          if( recv )
            if( recv =~ /(.+)\((.*)\)/ )
              recv  = $1
              rargs = $2.split(',').collect{|c| eval_expr(c, data, plugin) }
            else
              rargs = []
            end
            val = val.__send__(recv,*rargs)
          end
	else
	  val = data
	end

        if( (val = normalize(val)).nil? )
          return nil
        end
	for opt in opts
	  case opt
	  when /^\{(.+)\}/
	    val = eval_action($1.strip, val, plugin)
	  when /^\[(\d+)\.\.\.(\d+)\]/
	    range = ($1.to_i ... $2.to_i)
	    val = val[range]
	  when /^\[(\d+)\.\.(\d+)\]/
	    range = ($1.to_i .. $2.to_i)
	    val = val[range]
	  when /^\[(\d+),(\d+)\]/
	    pos = $1.to_i
	    n   = $2.to_i
	    val = val[pos,n]
	  when /^\[(\d+)\]/
	    pos = $1.to_i
	    val = val[pos]
	  when /^\[(.+)\]/
	    cond = $1
	    case val
	    when Array
	      val = val.select{|n|
		eval_condition(cond, n, plugin)
	      }
	    else
	      unless( eval_condition(cond,val,plugin) )
		val = nil
	      end
	    end
	  else
	    raise(RuntimeError, "unknown path components: #{opt}")
	  end
          if( (val = normalize(val)).nil? )
            return nil
          end
	end # end of 'for'
    ` # don't eliminate this quote.

    VALUE_BY_PATH2_2 = %q`
	if( hd.size > 0 )
          hd,recv,*rargs = hd.split(".")
	  val = data[hd]
          if( recv )
            if( recv =~ /(.+)\((.*)\)/ )
              recv  = $1
              rargs = $2.split(',').collect{|c| eval_expr(c, data, plugin) }
            else
              rargs = []
            end
            val = val.__send__(recv,*rargs)
          end
	else
	  val = data
	end

	val = normalize(val)
    ` # don't eliminate this quote.

    VALUE_BY_PATH3 = %q`
	case val
	when Hash
	  value_by_path(ids, val, data, rdata, plugin)
	when Array
	  val.collect{|x|
	    case x
	    when Hash
	      value_by_path(ids, x, data, rdata, plugin)
	    else
	      if( ids.empty? )
		x
	      else
		nil
	      end
	    end
	  }.flatten
	when nil
	  nil
	else
	  if( ids.empty? )  # 'val' should be a text node, if 'ids' is empty.
	    val
	  else
	    nil
	  end
	end
      end # end of 'case hd'
    end # end of value_by_path()
    ` # don't eliminate this quote.

    def use_default_xpath()
      XPath.module_eval(VALUE_BY_PATH1 + VALUE_BY_PATH2_1 + VALUE_BY_PATH3)
    end

    def use_simple_xpath()
      XPath.module_eval(VALUE_BY_PATH1 + VALUE_BY_PATH2_2 + VALUE_BY_PATH3)
    end

    module_function :use_default_xpath, :use_simple_xpath
    use_default_xpath()

    def eval_expr(expr, val, plugin)
      case expr
      when "text()"
	if( val.is_a?(Hash) && val[TextNode] && val.size == 1 )
	  val[TextNode]
	else
	  val
	end
      when "size()"
	case val
	when Array
	  val.size
	when nil
	  0
	else
	  1
	end
      when /int\((.+)\)/
	eval_expr($1,val,plugin).to_i
      when /float\((.+)\)/
	eval_expr($1,val,plugin).to_f
      when /^(-?\d+)$/
	$1.to_i
      when /^(-?\d+)\.(\d+)$/
	$1.to_f
      when /^('|"|&quot;|&apos;)(.+)('|"|&quot;|&apos;)$/
	str = $2
	str.gsub(/\\./){|m| $1}
      when /^%q\((.+)\)$/
	str = $1
	str.gsub(/\\./){|m| $1}
      when /^%r\((.+)\)$/, %r{/(.+)/}
	str = $1
	str.gsub!(/\\./){|m| $1}
	Regexp.new(str)
      when 'nil'
        nil
      else
	path_split(expr).each{|path|
	  case val
	  when Hash
	    val = val[path]
	    if( val.is_a?(Hash) && val[TextNode] && val.size == 1 )
	      val = val[TextNode]
	    end
	  when Array
	    val = val.collect{|v|
	      eval_expr(path, v, plugin)
	    }.flatten.reject{|v| v.nil?}
	    if( val.size == 0 )
	      val = nil
	    end
	  else
	    val = nil
	    break
	  end
	}
	val
      end
    end

    def eval_condition(expr, val, plugin)
      if( expr =~ /\s+or\s+/ )
	expr.split(/\s+or\s+/).any?{|x| eval_condition(x.strip,val,plugin) }
      elsif( expr =~ /\s+and\s+/ )
	expr.split(/\s+and\s+/).all?{|x| eval_condition(x.strip,val,plugin) }
      elsif( expr =~ /^not\s+(.+)$/ )
	! eval_condition($1.strip,val,plugin)
      else
	case expr
	when /^([^!=<>~\s\(\)]+(\([^!=<>~\s\(\)]*\))?)\s*(=|!=|<|>|<=|>=|=~|!~|&lt;=?|&gt;=?)\s*([^!=<>~\s\(\)]+(\([^!=<>~\s\(\)]*\))?)$/
	  lhs = eval_expr($1.strip,val,plugin)
	  op  = $3
	  rhs = eval_expr($4.strip,val,plugin)
	  unless( lhs.nil? || rhs.nil? )
	    case op
	    when '='
	      (lhs == rhs)
	    when '&lt;', '<'
	      (lhs < rhs)
	    when '&lt;=', '<='
	      (lhs <= rhs)
	    when '&gt;', '>'
	      (lhs > rhs)
	    when '&gt;=', '>='
	      (lhs >= rhs)
	    when '=~'
	      (lhs =~ rhs)
	    when '!~'
	      (lhs !~ rhs)
	    else
	      raise(NotImplementedError, "'#{op}'")
	    end
	  else
	    false
	  end
	when /^([^=<>~]+)$/
	  eval_expr($1.strip,val,plugin)
	else
	  nil
	end
      end # end of 'else'
    end

    def eval_action(act, val, plugin)
      newval = nil
      act.strip!
      if( act.include?(";") )
	newval = val
	act.split(";").each{|a|
	  newval = eval_action(a.strip, newval, plugin)
	}
	return newval
      end
      if( act =~ /^([^\(\)]+)\(([^\(\)]*)\)$/ )
	func = $1.strip
	args = args_split($2)
	newval = plugin.__send__(func,val,*args)
      else
	case val
	when Array
	  newval = val.collect{|x| {act => x} }
	else
	  newval = {act => val}
	end
      end
      newval
    end # end of eval_action()
  end # end of XPath
end
