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

$LOAD_PATH << "../../lib" unless $LOAD_PATH.include?("../../lib")
require "qwik/util-escape"
begin
  require "ext/xmlformatter.so"
  $have_xmlformatter_so = true
rescue LoadError
  $have_xmlformatter_so = false
end

module Qwik
  class RB_XMLFormatter
    # copied from gonzui-0.1
    # To eliminate costs for object creation, the following
    # strings are defined as constants. It makes format_xml
    # 1.2 times faster.
    SP = " "
    QT = '"'
    EQ = "="
    LT = "<"
    GT = ">"
    LF = "\n"
    SL = "/"
    NL = "" # null
    DOCTYPE = "!DOCTYPE"

    def format(ar, indent=0, sindent=0)
      n = (0 <= indent) ? LF : NL
      sn = (0 <= sindent) ? LF : NL

      if !ar[0].is_a?(Symbol)
	out = ""
	ar.each {|x|
	  if x.is_a?(Array)
	    out << format(x, indent, sindent) # recursive
	  elsif x.is_a?(String)
	    out << x.escapeHTML
	  elsif x.is_a?(NilClass)
	    # do nothing
	  else
	    qp "what?", x
	    out << x.to_s.escapeHTML
	  end
	}
	return out
      end

      element = ar[0].to_s.escapeHTML

      if element == DOCTYPE # doctype
	return "<!DOCTYPE "+ar[1]+" "+ar[2]+
	  " \""+ar[3]+"\" \""+ar[4]+"\">"
      elsif element == "!--" # comment
	return "<!--"+ar[1]+"-->"
      end

      offset = 1
      attributes = ""
      while ar[offset].is_a?(Hash)
	attr = ar[offset]
	offset += 1
	attr.keys.sort{|a, b| a.to_s <=> b.to_s }.each {|k|
	  v = attr[k]
	  attributes << SP << k.to_s.escapeHTML << EQ << QT << v.to_s.escapeHTML << QT
	}
      end

      if ar[offset].nil? && ar.length == offset
	o = ""
	o << LT << element << attributes << n << SL << GT
	return o
      end

      out = ""
      out << LT << element << attributes << sn << GT
      (offset...ar.length).each {|i|
	x = ar[i]
	if x.is_a?(Array)
	  out << format(x, indent, sindent) # recursive
	elsif x.is_a?(String)
	  out << x.escapeHTML
	else
	  out << x.to_s.escapeHTML
	end
      }
      out << LT << SL << element << n << GT
      out
    end
  end
end

class Array
  def rb_format_xml(indent=0, sindent=0)
    formatter = Qwik::RB_XMLFormatter.new
    result = formatter.format(self, indent, sindent)
    result
  end

  def c_format_xml # no indent option
    formatter = Gonzui::XMLFormatter.new
    result = formatter.format(self)
    result
  end

  def format_xml
    return self.c_format_xml if $have_xmlformatter_so
    return self.rb_format_xml
  end
end

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

if $debug
  class TC_XMLFormatter < Test::Unit::TestCase
    def assert(e, w) # assert twice
      assert_equal(e, w.format_xml)
      assert_equal(e, w.rb_format_xml)
    end

    def test_rb_format_xml
      assert_equal("<a\n/>", [:a].rb_format_xml(0))
      assert_equal("<a></a\n>", [:a, ""].rb_format_xml(0, -1))
      assert_equal("<a\n></a\n>", [:a, ""].rb_format_xml)
      assert_equal("<a><b></b></a>", [:a, [:b, ""]].rb_format_xml(-1, -1))
      assert_equal("<a><b></b\n></a\n>", [:a, [:b, ""]].rb_format_xml(0, -1))
      assert_equal("<a\n><b\n></b\n></a\n>", [:a, [:b, ""]].rb_format_xml)
    end

    def test_all
      # test_basic
      assert("<a\n/>", [:a])
      assert("<a\n>t</a\n>", [:a, "t"])
      assert("<a\n><b\n></b\n></a\n>", [:a, [:b, ""]])
      assert("<a href=\"t\"\n>s</a\n>", [:a, {:href=>"t"}, "s"])

      # test_escape
      assert("<a\n>&lt;</a\n>", [:a, "<"])
      assert("<a href=\"&lt;\"\n/>", [:a, {:href=>"<"}])
      assert("<a &gt;=\"&lt;\"\n/>", [:a, {">"=>"<"}])
      assert("<a &lt;=\"&lt;\"\n/>", [:a, {"<"=>"<"}])
      assert("<&lt;\n/>", [:"<"])
      assert("<&lt; &amp;=\"&lt;\"\n/>", [:"<", {"&"=>"<"}])

      # test_doctype
      assert("<!DOCTYPE html PUBLIC \"-//W3C//DTD html 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">",
	     [:"!DOCTYPE", "html", "PUBLIC",
	       "-//W3C//DTD html 4.01 Transitional//EN",
	       "http://www.w3.org/TR/html4/loose.dtd"])

      # test_comment
      assert("<!--comment-->", [:"!--", "comment"])

      # test_text
      assert("", [])
      assert("", [""])
      assert("a", ["a"])
      assert("ab", ["a", "b"])
      assert("abc", ["a", ["b"], "c"])

      # test_null
      assert("<a\n></a\n>", [:a, ""])
      assert("<a\n></a\n>", [:a, nil])
      assert("<a\n></a\n>", [:a, []])
      assert("<a\n>nil</a\n>", [:a, :nil])
      assert("<p id=\"\"\n/>", [:p, {:id=>""}])
      assert("<p id=\"\"\n/>", [:p, {:id=>nil}])
      assert("<p id=\"\"\n/>", [:p, {:id=>[]}])
      assert("<p\n>b</p\n>", [:p, [["b"]]])
      assert("<p\n>bc</p\n>", [:p, ["b"], "c"])
      assert("<p id=\"\"\n></p\n>", [:p, {:id=>""}, ""])

      # test_non_destructive
      w = [:a]
      assert("<a\n/>", w)
      assert("<a\n/>", w)

      # test_bug
      assert("<a\n/><b\n/>", [[:a], [:b]])

      # test_format_xml # copied from gonzui-0.3
      html = [:html]
      head = [:head, [:title, "foo"]]
      body = [:body, [:h1, [:a, {:href => 'foo<&">.html'}, 'foo<&">']]]
      body.push([:p, "hello"])
      html.push(head)
      html.push(body)
      assert("<html\n><head\n><title\n>foo</title\n></head\n><body\n><h1\n><a href=\"foo&lt;&amp;&quot;&gt;.html\"\n>foo&lt;&amp;&quot;&gt;</a\n></h1\n><p\n>hello</p\n></body\n></html\n>", html)
    end
  end
end
