# $Id: qxml.rb,v 1.26 2004/04/13 01:48:11 eto Exp $
# Copyright (C) 2003-2004 Kouichirou Eto, All rights reserved.
# This is free software with ABSOLUTELY NO WARRANTY.
# You can redistribute it and/or modify it under the terms of the GNU GPL2.

require "iconv"
require "rexml/document"
require "rexml/quickpath"

module REXML
  module Encoding
    @@__REXML_encoding_methods =<<-EOL
    def decode( str )
      return Iconv::iconv("utf-8", "shift-jis", str)[0]
    end

    def encode( content )
      return Iconv.iconv_to_utf8(@encoding, content)
    end
    EOL
  end

  class QDocument < Document
    def initialize(str)
      arg = {:respect_whitespace => %w(pre textarea)}
      super(str, arg)
    end
  end

  class Document
    def add_html_doctype
      self << REXML::DocType.new('html', 'PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"')
    end

    def add_xhtml_doctype
      self << REXML::XMLDecl.new(1.0, 'Shift_JIS')
      self << REXML::DocType.new('html', 'PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"')
      self.root.add_attribute('xmlns', "http://www.w3.org/1999/xhtml")
    end

    def to_xml(indent=-1, transitive=true, ie_hack=true)
      str = ""
      write(str, indent, transitive, ie_hack)
      str
    end
  end

  class Attr < Hash
    def initialize(h)
      self.update(h)
    end
  end

  class Text
    def delete_spaces
      if @normalized
	@normalized = @normalized.sub(/^\s+/, '').sub(/\s+$/, '')
      else
	@string = @string.sub(/^\s+/, '').sub(/\s+$/, '')
      end
    end

    alias to_xhtml to_s
  end

  class << Text
    if !defined? org_normalize
      alias org_normalize normalize
      def normalize(input, doctype=nil, entity_filter=nil)
	org_normalize(input, doctype, entity_filter).gsub(/&apos;/u, "'")
      end
    end
  end

  class Element
    def tag(*tags, &block)
      tags.each {|ta|
	QuickPath.each(self, "//#{ta}"){|e|
	  block.call(e) if block
	}
      }
    end

    def delete_empty_tag(*ar)
      tag(*ar){|e|
	e.parent.delete_element(e) if e.size == 0
      }
    end

    def delete_id
      self.traverse {|e|
	eid = e.attributes['id']
	next if eid.nil?
	e.delete_attribute('id')
      }
    end

    def rawtext=(text)
      text = Text.new(text, true, nil, true, nil, /nosuchpattern/ )
      self.text = text
    end

    def delete_tag(tag)
      QuickPath.each(self, "//#{tag}"){|e|
	e.parent.delete_element(e)
      }
    end

    def traverse(&block)
      self.elements.each {|e|
	block.call(e) if block
	e.traverse(&block) if 0 < e.size
      }
    end

    def apply(data)
      keys = data.keys
      self.traverse {|e|
	eid = e.attributes['id']
	next if eid.nil?
	v = data[eid.intern]
	next if v.nil?

	if v.is_a? Attr
	  v.each {|ak, av|
	    e.add_attribute(ak.to_s, av.to_s)
	  }
	elsif v.is_a? String
	  e.text = v
	elsif v.is_a?(REXML::Document)
	  apply_add_ele(e, v.root)
	elsif v.is_a?(REXML::Element)
	  apply_add_ele(e, v)
	else
	  e.text = v
	end
      }
    end

    def apply_add_ele(e, v) # add v to e
      if v.name == 'body' || v.name == 'head'
	v.elements.each {|ee|
	  e << ee
	}
      else
	e << v
      end
    end
    private :apply_add_ele

    def to_s(indent=-1, transitive=false, ie_hack=false)
      str = ""
      write(str, indent, transitive, ie_hack)
      str
    end

    def to_xhtml(indent=-1)
      to_s(indent, true, true)
    end

    def method_missing(symbol, *args, &block)
      sym = symbol.to_s.gsub(/_/, '-')
      add_attribute(sym, args.to_s)
      if block
	y = block.call(self)
	ya = y
	ya = [y] if ! y.kind_of?(Array)
	ya.each {|y|
	  y = Text.new(y.to_s) if ! y.kind_of?(Element)
	  self << y
	}
      end
      self
    end

    def attr(*args)
      if ! args.empty?
	args.first.each {|key, value|
	  value && self.attributes[key.to_s] = value.to_s
	}
      end
    end

    def title(*args, &block)
      method_missing(:title, *args, &block)
    end

    def p(*args, &block)
      method_missing(:p, *args, &block)
    end
  end
end

class Qxml
  def method_missing(symbol, *args, &block)
    Qxml.make(symbol, *args, &block)
  end

  def make(symbol, *args, &block)
    Qxml.make(symbol, *args, &block)
  end

  def self.make(symbol, *args, &block)
    sym = symbol.to_s.gsub(/_/, '-')
    ele = REXML::Element.new(sym)
    if block
      y = block.call(ele)
      ya = y
      ya = [y] if ! y.kind_of?(Array)
      ya.each {|y|
	y = REXML::Text.new(y.to_s) if ! y.kind_of?(REXML::Element)
	ele << y
      }
    end
    if ! args.empty?
      args.first.each {|key, value|
	value && ele.attributes[key.to_s] = value.to_s
      }
    end
    ele
  end
end

class Qxhtml < Qxml
  def a(arg=[], &block)
    return Qxml.make(:a, {:href=>arg}, &block) if arg.kind_of?(String)
    return Qxml.make(:a, arg, &block)
  end

  def img(src="", alt="")
    Qxml.make(:img, {:src=>src, :alt=>alt})
  end

  def text(name="", value=nil, size=nil, maxsize=nil)
    Qxml.make(:input, {:name=>name, :value=>value, :size=>size, :maxsize=>maxsize})
  end

  def password(name="", value=nil, size=nil)
    Qxml.make(:input, {:type=>'password', :name=>name, :value=>value, :size=>size})
  end

  def hidden(name="", value=nil)
    Qxml.make(:input, {:type=>'hidden', :name=>name, :value=>value})
  end

  def select(name="", *args)
    s = Qxml.make(:select, {:name=>name})
    args.each {|arg|
      s << Qxml.make(:option, {:name=>arg}){arg}
    }
    s
  end

  def textarea(name="", cols=nil, rows=nil, &block)
    Qxml.make(:textarea, {:name=>name, :cols=>cols, :rows=>rows}){
      block.call
    }
  end

  def submit(value=nil, name=nil)
    Qxml.make(:input, {:type=>'submit', :name=>name, :value=>value})
  end

  def contenttype(content="")
    Qxml.make(:meta, {"http-equiv"=>"Content-Type", :content=>content})
  end

  def stylesheet(url="", media=nil)
    Qxml.make(:link, {:rel=>"stylesheet", :type=>"text/css", :href=>url, :media=>media})
  end

  def refresh(sec=0, url="")
    Qxml.make(:meta, {"http-equiv"=>"Refresh", :content=>"#{sec}; url=#{url}"})
  end
end
