# Copyright (C) 2003-2005 Kouichirou Eto, All rights reserved.

$LOAD_PATH << "../../lib" unless $LOAD_PATH.include?("../../lib")

class Array
  def find_nth_element(num=1, *names)
    i = 1
    traverse_element(*names) {|e|
      return e if i == num
      i += 1
    }
    nil
  end

  def get_tag(tag)
    tag = tag.to_s
    tag, num = split_name_num(tag)
    find_nth_element(num, tag.intern)
  end

  def split_name_num(name)
    name = name.sub(%r|(^//)|, "")
    if /^(\w+)\[(\d+)\]$/ =~ name
      return [$1, $2.to_i]
    end
    [name, 1]
  end

  def traverse_element(*tags, &b)
    if tags.length == 0 || (self[0].is_a?(Symbol) && tags.include?(self[0]))
      yield(self)
    end

    self.each {|s|
      next unless s.is_a?(Array)
      s.traverse_element(*tags, &b)
    }
  end

  def each_has_attr(attr, value)
    traverse_element {|e|
      if e.get_attr(attr) == value
	yield(e)
      end
    }
  end

  def get_has_attr(attr, value)
    each_has_attr(attr, value) {|l|
      return l
    }
    nil
  end

  def get_by_class(klass)
    get_has_attr(:class, klass)
  end

  def parse_a_path(path)
    if /\A(.*)\[(.+)\]\z/ =~ path
      tag = $1
      attr_selector = $2
      attr, value = parse_attr_selector(attr_selector)
    end
    return get_tag(path)
    
  end

  def parse_attr_selector(attr_selector)
    unless attr_selector.include?("=")
      return nil
    end

    left, right = attr_selector.split("=", 2)

    if /\A@([a-z:]+)\z/ =~ left
      attr_name = $1.intern
    else
      raise "unknown attribute selector "+left
    end

    right = $1 if /\A'(.+)'\z/ =~ right
    right = $1 if /\A"(.+)"\z/ =~ right

    if /\A(.*)\z/ =~ right
      value = $1
    else
      raise "unknown attribute value "+right
    end

    return [attr_name, value]
  end

  def get_a_path(path)
    if /\A(.*)\[(.+)\]\z/ =~ path
      tag = $1
      select = $2
      attr, value = parse_attr_selector(select)
      if attr
	return get_has_attr(attr, value)
      end
    end
    return get_tag(path)
  end

  def nuget_a_path(path)
    if /\A(.*)\[(.+)\]\z/ =~ path
      tag = $1
      select = $2
      if /\A@([a-z]+)='([a-z]+)'\z/ =~ select
	attr = $1
	value = $2
	return get_has_attr(attr.intern, value)
      end
    end
    return get_tag(path)
  end

  def get_path(path)
    xml = self
    path.split("/").each {|pa|
      next unless 0 < pa.length
      return nil if xml.nil?
      xml = xml.get_a_path(pa)
    }
    xml
  end

  def get_attr(k=nil)
    attr = {}
    i = 1
    while self[i].is_a?(Hash)
      attr.update(self[i])
      i += 1
    end
    return attr if k.nil?
    return attr[k]
  end

  def attributes_str
    attr = get_attr
    hash_to_str(attr)
  end

  def hash_to_str(src)
    h = {}
    src.each {|k, v|
      next if v.nil?
      h[k.to_s] = v.to_s
    }
    h
  end
  private :hash_to_str

  def text
    i = 0
    while true
      x = self[i]
      if x.is_a?(Symbol) || x.is_a?(Hash)
	i += 1
	next
      else
	break
      end
    end

    (i...self.length).map {|i|
      x = self[i]
      if x.is_a?(String)
	x
      elsif x.is_a?(Array)
	if x[0] != :"!--"
	  x.text
	end
      else
	nil
      end
    }.join
  end
  alias extract_text text

  def inside
    self.reject {|x| x.is_a?(Symbol) || x.is_a?(Hash) }
  end
end

if $0 == __FILE__
  require "test/unit"
  $debug = true
end

if $debug
  class TC_Wabisabi_get < Test::Unit::TestCase
    def test_all
      # test_get_tag

      # test_split_name_num
      e = [:e]
      assert_equal(["sidebar", 1], e.split_name_num("sidebar"))
      assert_equal(["sidebar", 1], e.split_name_num("sidebar[1]"))
      assert_equal(["sidebar", 2], e.split_name_num("sidebar[2]"))

      # test_get_tag
      h = [:a, [:b, [:c]]]
      assert_equal([:c], h.get_tag("c"))
      assert_equal([:b, [:c]], h.get_tag("b"))

      # test_get_tag2
      h = [:a, [:b, "tb"], [:b, "tb2"], [:c]]
      assert_equal([:b, "tb"], h.get_tag("b"))
      assert_equal(nil, h.get_tag("nosuchtag"))
      assert_equal([:b, "tb"], h.get_tag("b[1]"))
      assert_equal([:b, "tb2"], h.get_tag("b[2]"))
      assert_equal([:b, "tb"], h.get_tag("//b"))

      # test_get_path
      h = [:html, [:div, {:class=>"main"},
	  [:div, {:class=>"day"}, [:h2, "bh2"]],
	  [:div, {:class=>"sidebar"}, [:h2, "sh2", [:b, "a"], "sh2a"]]]]
      assert_equal([:div, {:class=>"sidebar"}, [:h2, "sh2", [:b, "a"], "sh2a"]],
		   h.get_has_attr(:class, "sidebar"))
      assert_equal([:div, {:class=>"sidebar"}, [:h2, "sh2", [:b, "a"], "sh2a"]],
		   h.get_a_path("div[@class='sidebar']"))
      assert_equal([:b, "a"],
		   h.get_a_path("div[@class='sidebar']").get_a_path("b"))
      assert_equal([:div, {:class=>"sidebar"}, [:h2, "sh2", [:b, "a"], "sh2a"]],
		   h.get_path("//div[@class='sidebar']"))
      assert_equal([:b, "a"], h.get_path("//div[@class='sidebar']/b"))

      h = [:a, {:href=>"t1", :alt=>"t2"}, "t3"]
      e = h.get_tag("a")
      assert_equal([:a, {:href=>"t1", :alt=>"t2"}, "t3"], e)
      assert_equal({"href"=>"t1", "alt"=>"t2"}, e.attributes_str)

      # test_parse_attr_selector
#      assert_raise(RuntimeError){ h.parse_attr_selector("a") }
      assert_raise(RuntimeError){ h.parse_attr_selector("a=b") }
      assert_equal([:a, "b"], h.parse_attr_selector("@a=b"))
      assert_equal([:a, "b c"], h.parse_attr_selector("@a='b c'"))

      # test_get_path_with_space
      h = [:html, [:div, {:class=>"main"},
	  [:div, {:class=>"day edit"}, [:h2, "bh2"]],
	  [:div, {:class=>"sidebar"}, [:h2, "sh2", [:b, "a"], "sh2a"]]]]
      assert_equal([:div, {:class=>"sidebar"}, [:h2, "sh2", [:b, "a"], "sh2a"]],
		   h.get_path("//div[@class='sidebar']"))
      assert_equal([:div, {:class=>"day edit"}, [:h2, "bh2"]],
		   h.get_path("//div[@class='day edit']"))

      # test_get_has_attr
      h = [:html,
	[:div, {:class=>"main"},
	  [:div, {:id=>"menu"},
	    [:h2, "menuh2"]]]]
      assert_equal([:div, {:id=>"menu"}, [:h2, "menuh2"]],
		   h.get_has_attr(:id, "menu"))
      assert_equal([:div, {:id=>"menu"}, [:h2, "menuh2"]],
		   h.get_path("//div[@id='menu']"))

      # test_text
      h = [:a, "t"]
      assert_equal("t", h.text)
      h = [:a, ["t"]]
      assert_equal("t", h.text)

      # test_inside
      h = [:a, {:href=>"t.html"}, "t", [:b, "b"]]
      assert_equal(["t", [:b, "b"]], h.inside)
    end
  end
end
