=begin Start of Document
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title>htmlsplit.rb</title>
<link href="rubydoc.css" rel="stylesheet">
</head>
<body>
| <a href="./">提る</a> |
<h1>HTML Split Library</h1>
<p> HTMLを粕み今きする。 粕み哈んだ矢今はタグと矢机误の芹误になる。 to_sメソッドでHTMLに提すことが叫丸る。</p>
<h2>クラス办枉</h2>
<table bgcolor="#FFFFFF" border="1">
  <tr><td><a href="#HTMLSplit">HTMLSplit</a><td>HTMLをタグと矢机デ〖タに尸充する。 
<tr>
	<td><a href="#CharacterData">CharacterData</a>
	<td>矢机デ〖タ
  <tr><td><a href="#EmptyElementTag">EmptyElementTag</a>
	<td>鄂妥燎のタグ
  <tr>
	<td><a href="#StartTag">StartTag</a>
	<td>倡幌タグ
  <tr>
	<td><a href="#EndTag">EndTag</a>
	<td>姜位タグ
  <tr>
	<td><a href="#Comment">Comment</a>
	<td>コメント
  <tr>
	<td><a href="#Declaration">Declaration</a>
	<td>离咐(DOCTYPE)
  <tr>
	<td><a href="#SSI">SSI</a>
	<td>SSIⅷ
  <tr>
	<td><a href="#ERuby">ERuby</a>
	<td>eRuby/ASP/JSPスクリプトⅷ
  <tr>
	<td><a href="#PHP">PHP</a>
	<td>PHPスクリプトⅷ
  </table>
ⅷタグの掳拉猛などに虽め哈まれたスクリプトは千急できません。
<h2>蝗い数 </h2>
<h3>粕み哈み</h3>
<pre class="Exception"><samp>#!/usr/bin/ruby
require "htmlsplit"

obj = HTMlSplit.new(ARGF.read)</samp></pre>
<h3>叫蜗</h3>
<pre>
obj.document.each {|e|
    print e.to_s
}
</pre>
<h3>掳拉の肋年</h3>
<pre>
img = Tag('img/')
img['src']='xxx.png'  #&lt;img src="xxx.png"&gt;

o = Tag('option')
o['selected']=true    #&lt;option selected&gt;
</pre>
=end

require "cgi"
require "kconv"

=begin EmptyElementTag
 
<h2><a name="EmptyElementTag">EmptyElementTag</a></h2>
鄂妥燎のタグ 
<h3>クラスメソッド</h3>
<dl compact>
  <dt>new(<var class="String">name</var>[,<var class="Hash">attr</var>])
  <dd>糠しいオブジェクトを栏喇する。 <var class="String">name</var>はタグの叹涟 <var class="Hash">attr</var>はタグの掳拉nilまたはHash
</dl>
<h3>メソッド</h3>
<dl compact>
  <dt class="String">name
  <dd>タグ叹を手す。
  <dt class="Hash">attr 
  <dd>掳拉を手す。
  <dt class="String">to_s 
  <dd>HTMLを手す。
  <dt class="String">self[<var class="String">key</var>]
  <dd>keyに簇息づけられた掳拉猛を手します。
      澈碰するキ〖が判峡されていない箕には·nilを手します。
  <dt class="String">self[<var class="String">key</var>]= <var class="String">value</var>
  <dd><var class="String">key</var>に滦して<var class="String">value</var>を簇息づけます。
      <var class="String">value</var>がnilの箕·<var class="String">key</var>に滦する簇息を艰り近きます。
</dl>
=end
class EmptyElementTag
	def initialize(name,attr=nil)
		@name = name.downcase
		@attr = attr
	end
	attr_accessor :name
	attr_accessor :attr
	def to_s
		if @attr
			"<"+@name+@attr.keys.sort.collect{|n|
				v = @attr[n]
				if v==true
					' ' + n
				else
					' ' + n + '="' + CGI::escapeHTML(v) + '"'
				end
			}.to_s+">"
		else
			"<#{@name}>"
		end
	end
	def [](key)
		attr and attr[key]
	end
	def []=(key,value)
		if attr
			attr[key]=value
		else
			attr = value and {key=>value}
		end
	end
end
=begin StartTag
<h2>StartTag</h2>
倡幌タグ
<h3>クラスメソッド</h3>
<dl compact>
  <dt>new(<var class="String">name</var>[,<var class="Hash">attr</var>])
  <dd>糠しいオブジェクトを栏喇する。 <var class="String">name</var>はタグの叹涟 <var class="Hash">attr</var>はタグの掳拉nilまたはHash
</dl>
<h3>メソッド</h3>
<dl compact>
  <dt class="String">name
  <dd>タグ叹を手す。
  <dt class="Hash">attr 
  <dd>掳拉を手す。
  <dt class="String">to_s 
  <dd>HTMLを手す。
  <dt class="String">self[<var class="String">key</var>]
  <dd>keyに簇息づけられた掳拉猛を手します。
      澈碰するキ〖が判峡されていない箕には·nilを手します。
  <dt class="String">self[<var class="String">key</var>]= <var class="String">value</var>
  <dd><var class="String">key</var>に滦して<var class="String">value</var>を簇息づけます。
      <var class="String">value</var>がnilの箕·<var class="String">key</var>に滦する簇息を艰り近きます。
</dl>
=end
class StartTag
	attr_accessor :name
	attr_accessor :attr
	def initialize(name,attr=nil)
		@name = name.downcase
		@attr = attr
	end
	def to_s
		if @attr
			"<"+@name+@attr.keys.sort.collect{|n|
				v = @attr[n]
				if v==true
					' ' + n
				else
					' ' + n + '="' + CGI::escapeHTML(v) + '"'
				end
			}.to_s+">"
		else
			"<#{@name}>"
		end
	end
	def [](key)
		attr and attr[key]
	end
	def []=(key,value)
		if attr
			attr[key]=value
		else
			attr = value and {key=>value}
		end
	end
end
=begin EndTag
<h2>EndTag</h2>
姜位タグ
<h3>クラスメソッド</h3>
<dl compact>
  <dt>new(<var class="String">name</var>)
  <dd>糠しいオブジェクトを栏喇する。 <var class="String">name</var>はタグの叹涟
</dl>
<h3>メソッド</h3>
<dl compact>
  <dt class="String">name 
  <dd>タグ叹を手す。
  <dt class="String">to_s 
  <dd>HTMLを手す。
</dl>
=end
class EndTag
	def initialize(name)
		@name = name.downcase
	end
	attr_accessor :name
	def to_s
		"</#{@name}>"
	end
end
=begin CharacterData
<h2>CharacterData</h2>
矢机デ〖タ
<h3>クラスメソッド</h3>
<dl compact>
  <dt>new(<var class="String">text</var>)
  <dd>糠しいオブジェクトを栏喇する。 <var class="String">text</var>はテキスト
</dl>
<h3>メソッド</h3>
<dl compact>
  <dt class="String">text 
  <dd>テキストを手す。
  <dt class="String">to_s 
  <dd>HTMLを手す。
</dl>
=end
class CharacterData
	def initialize(text)
		@text = text
	end
	attr_accessor :text
	def to_s
		@text
	end
end
=begin Declaraion
<h2>Declaraion</h2>
SGML离咐
<h3>クラスメソッド</h3>
<dl compact>
  <dt>new(<var class="String">text</var>)
  <dd>糠しいオブジェクトを栏喇する。 <var class="String">text</var>はテキスト
</dl>
<h3>メソッド</h3>
<dl compact>
  <dt class="String">text 
  <dd>テキストを手す。
  <dt class="String">to_s 
  <dd>HTMLを手す。
</dl>
=end
class Declaration
	def initialize(text)
		@text = text
	end
	attr_accessor :text
	def to_s
		"<!#{@text}>"
	end
end
=begin Comment
<h2>Comment</h2>
コメント
<h3>クラスメソッド</h3>
<dl compact>
  <dt>new(<var class="String">text</var>)
  <dd>糠しいオブジェクトを栏喇する。 <var class="String">text</var>はテキスト
</dl>
<h3>メソッド</h3>
<dl compact>
  <dt class="String">text 
  <dd>テキストを手す。
  <dt class="String">to_s 
  <dd>HTMLを手す。
</dl>
=end
class Comment
	def initialize(text)
		@text = text
	end
	attr_accessor :text
	def to_s
		"<!--#{@text}-->"
	end
end
=begin SSI
<h2>SSI</h2>
SSI
<h3>クラスメソッド</h3>
<dl compact>
  <dt>new(<var class="String">text</var>)
  <dd>糠しいオブジェクトを栏喇する。 <var class="String">text</var>はテキスト
</dl>
<h3>メソッド</h3>
<dl compact>
  <dt class="String">text 
  <dd>テキストを手す。
  <dt class="String">to_s 
  <dd>HTMLを手す。
</dl>
=end
class SSI
	def initialize(text)
		@text = text
	end
	attr_accessor :text
	def to_s
		"<!--#{@text}-->"
	end
end
=begin ERuby
<h2>ERuby</h2>
eRuby/ASP/JSPスクリプト
<h3>クラスメソッド</h3>
<dl compact>
  <dt>new(<var class="String">text</var>)
  <dd>糠しいオブジェクトを栏喇する。 <var class="String">text</var>はテキスト
</dl>
<h3>メソッド</h3>
<dl compact>
  <dt class="String">text 
  <dd>テキストを手す。
  <dt class="String">to_s 
  <dd>HTMLを手す。
</dl>
=end
class ERuby
	def initialize(text)
		@text = text
	end
	attr_accessor :text
	def to_s
		"<%#{@text}%>"
	end
end
=begin PHP
<h2>PHP</h2>
PHPスクリプト
<h3>クラスメソッド</h3>
<dl compact>
  <dt>new(<var class="String">text</var>)
  <dd>糠しいオブジェクトを栏喇する。 <var class="String">text</var>はテキスト
</dl>
<h3>メソッド</h3>
<dl compact>
  <dt class="String">text 
  <dd>テキストを手す。
  <dt class="String">to_s 
  <dd>HTMLを手す。
</dl>
=end
class PHP
	attr_accessor :text
	def initialize(text)
		@text = text
	end
	def to_s
		"<?#{@text}?>"
	end
end
=begin HTMLSplit
 
<h2><a name="HTMLSplit">HTMLSplit</a></h2>
HTML粕み今き 
<h3>クラスメソッド</h3>
<dl compact>
  <dt>new(<var class="String">html</var>)
  <dd>糠しいオブジェクトを栏喇する。 <var class="String">html</var>はHTML矢今
</dl>
<h3>メソッド</h3>
<dl compact>
  <dt class="Array">document 
  <dd>ドキュメントの芹误を手す。
  <dt class="String">to_s 
  <dd>HTMLを手す。
  <dt class="Iterator">each {|<var>obj</var>,<var class="Array">tag</var>| ...}
  <dd>ドキュメントの称オブジェクト(<var>obj</var>)に滦してブロックを删擦します。
      <var class="Array">tag</var>は倡幌タグのリスト∈ [ StartTag , <var class="Integer">インデクス</var>] ∷
  <dt class="Integer">index(<var class="Class">class</var>, <var class="Integer">start</var>, <var class="Integer">end</var>, <var>value</var>, <var class="Integer">count</var>) {|obj| ...}
  <dd><var class="Integer">start</var>から<var class="Integer">end</var>までの妥燎で<var class="Class">class</var>と霹しい<var class="Integer">count</var>戎誊の妥燎の疤弥を手します。
      霹しい妥燎がひとつもなかった箕にはnilを手します。<br>
      <var>value</var>にnil笆嘲の猛を回年した箕には妥燎が<var>value</var>と霹しいかチェックを乖います。<var class="Class">class</var>がEmptyElementTag,StartTag,EndTagの箕はタグ叹、それ笆嘲はテキストによって孺秤します。<br>
      ブロックを回年して钙び叫された箕にはブロックで妥燎が霹しいか删擦する。
  <dt class="Integer">end_index(<var class="Integer">start</var>)
  <dd><var class="Integer">start</var>に滦炳するEndTagのインデクスを手します。
      滦炳する妥燎がなかった箕にはnilを手します。<br>
</dl>
=end
class HTMLSplit
	EMPTY = %w(area base basefont bgsound br col frame hr img input isindex 
	           keygen link meta nextid param spacer wbr)
	def initialize(html)
		@document = []	#パ〖スしたHTMLのリスト
		name = ''
		text = ''
		attr = {}
		attrname = ''
		state = :TEXT
		#
		html.each_byte {|c|
			char = c.chr
			case state
			when :TEXT
				if c==60
					if text.length>0
						@document << CharacterData.new(text)
					end
					name = ''
					attr={}
					state = :TAGNAME
				else
					text << char
				end
			when :TAGNAME
				case char
				when '>'
					name.downcase!
					if EMPTY.include?(name)
						@document << EmptyElementTag.new(name,nil)
					else
						if name[0,1]=='/'
							@document << EndTag.new(name[1..-1])
						else
							@document << StartTag.new(name,nil)
						end
					end
					text = ''
					state = :TEXT
				when '!'
					text = ''
					state = :DECLARE
				when '%'
					text = ''
					state = :ERUBY
				when '?'
					text = ''
					state = :PHP
				when /\s/
					text=''
					state = :SPACE
				else
					name << char
				end
			when :SPACE	#掳拉粗の鄂球
				case char
				when '>'
					name.downcase!
					if EMPTY.include?(name)
						@document << EmptyElementTag.new(name,attr)
					else
						if name[0,1]=='/'
							@document << EndTag.new(name[1..-1])
						else
							@document << StartTag.new(name,attr)
						end
					end
					text = ''
					state = :TEXT
				when /\s/
				else
					attrname=char
					state = :ATTRNAME
				end
			when :ATTRNAME	#掳拉叹
				case char
				when /\s/
					state = :BEFOREEQUAL
				when '='
					state = :AFTEREQUAL
				when '>'
					attr[attrname.downcase]=true
					name.downcase!
					if EMPTY.include?(name)
						@document << EmptyElementTag.new(name,attr)
					else
						if name[0,1]=='/'
							@document << EndTag.new(name[1..-1])
						else
							@document << StartTag.new(name,attr)
						end
					end
					text = ''
					state = :TEXT
				else
					attrname << char
				end
			when :BEFOREEQUAL	#=
				case char
				when '='
					state = :AFTEREQUAL
				when '>'
					attr[attrname.downcase]=true
					name.downcase!
					if EMPTY.include?(name)
						@document << EmptyElementTag.new(name,attr)
					else
						if name[0,1]=='/'
							@document << EndTag.new(name[1..-1])
						else
							@document << StartTag.new(name,attr)
						end
					end
					text = ''
					state = :TEXT
				when /\s/
				else
					attr[attrname.downcase]=true
					attrname = char
					state = :ATTRNAME
				end
			when :AFTEREQUAL	#=
				case char
				when "'"
					text=''
					state = :SQVALUE
				when '"'
					text=''
					state = :DQVALUE
				when '>'
					attr[attrname.downcase]=true
					name.downcase!
					if EMPTY.include?(name)
						@document << EmptyElementTag.new(name,attr)
					else
						if name[0,1]=='/'
							@document << EndTag.new(name[1..-1])
						else
							@document << StartTag.new(name,attr)
						end
					end
					text = ''
					state = :TEXT
				when /\s/
				else
					text=char
					state = :VALUE
				end
			when :VALUE		#猛
				case char
				when /\s/
					attr[attrname.downcase]=CGI::unescapeHTML(text)
					state = :SPACE
				when '>'
					attr[attrname.downcase]=CGI::unescapeHTML(text)
					name.downcase!
					if EMPTY.include?(name)
						@document << EmptyElementTag.new(name,attr)
					else
						if name[0,1]=='/'
							@document << EndTag.new(name[1..-1])
						else
							@document << StartTag.new(name,attr)
						end
					end
					text = ''
					state = :TEXT
				else
					text << char
				end
			when :SQVALUE	#'猛'
				if c==39
					attr[attrname.downcase]=CGI::unescapeHTML(text)
					state = :SPACE
				else
					text << char
				end
			when :DQVALUE	#"猛"
				if c==34
					attr[attrname.downcase]=CGI::unescapeHTML(text)
					state = :SPACE
				else
					text << char
				end
			when :COMMENT
				case char
				when '>'
					if text[-2,2]=='--'	#コメント姜位	
						text = text[0..-3]
						if text=~/^#[a-z]+/	#SSI
							@document << SSI.new(text)
						else
							@document << Comment.new(text)
						end
						text = ''
						state = :TEXT
					else
						text << char
					end
				else
					text << char
				end
			when :ERUBY
				case char
				when '>'
					if text[-1,1]=='%'	#eRuby姜位	
						text = text[0..-2]
						@document << ERuby.new(text)
						text = ''
						state = :TEXT
					else
						text << char
					end
				else
					text << char
				end
			when :PHP
				case char
				when '>'
					if text[-1,1]=='?'	#eRuby姜位	
						text = text[0..-2]
						@document << PHP.new(text)
						text = ''
						state = :TEXT
					else
						text << char
					end
				else
					text << char
				end
			when :DECLARE
				case char
				when '>'
					@document << Declaration.new(text)
					text = ''
					state = :TEXT
				else
					text << char
					if text=='--'
						text = ''
						state = :COMMENT
					end
				end
			end
		}
		#EOFの借妄
		case state
		when :TEXT
			@document << CharacterData.new(text) if text.length>0
		when :TAGNAME
			@document << CharacterData.new('<'+text)
		when :SPACE	#掳拉粗の鄂球
			name.downcase!
			if EMPTY.include?(name)
				@document << EmptyElementTag.new(name,attr)
			else
				if name[0,1]=='/'
					@document << EndTag.new(name[1..-1])
				else
					@document << StartTag.new(name,attr)
				end
			end
		when :ATTRNAME	#掳拉叹
			attr[attrname.downcase]=true
			name.downcase!
			if EMPTY.include?(name)
				@document << EmptyElementTag.new(name,attr)
			else
				if name[0,1]=='/'
					@document << EndTag.new(name[1..-1])
				else
					@document << StartTag.new(name,attr)
				end
			end
		when :BEFOREEQUAL	#=
			attr[attrname.downcase]=true
			name.downcase!
			if EMPTY.include?(name)
				@document << EmptyElementTag.new(name,attr)
			else
				if name[0,1]=='/'
					@document << EndTag.new(name[1..-1])
				else
					@document << StartTag.new(name,attr)
				end
			end
		when :AFTEREQUAL	#=
			attr[attrname.downcase]=true
			name.downcase!
			if EMPTY.include?(name)
				@document << EmptyElementTag.new(name,attr)
			else
				if name[0,1]=='/'
					@document << EndTag.new(name[1..-1])
				else
					@document << StartTag.new(name,attr)
				end
			end
		when :VALUE		#猛
			attr[attrname.downcase]=CGI::unescapeHTML(text)
			name.downcase!
			if EMPTY.include?(name)
				@document << EmptyElementTag.new(name,attr)
			else
				if name[0,1]=='/'
					@document << EndTag.new(name[1..-1])
				else
					@document << StartTag.new(name,attr)
				end
			end
		when :SQVALUE	#'猛'
			attr[attrname.downcase]=CGI::unescapeHTML(text)
		when :DQVALUE	#"猛"
			attr[attrname.downcase]=CGI::unescapeHTML(text)
		when :COMMENT
			if text=~/^#[a-zA-Z]+/	#SSI
				@document << SSI.new(text)
			else
				@document << Comment.new(text)
			end
		when :ERUBY
			@document << ERuby.new(text)
		when :PHP
			@document << PHP.new(text)
		when :DECLARE
			@document << Declaration.new(text)
		end
	end
	#
	attr_accessor :document
	#
	def to_s
		s = ''
		@document.each {|e|
			s<<(e.to_s)
		}
		s
	end
	#
	def each
		tag = []
		i = 0
		@document.each {|e|
			case e
			when StartTag
				tag.push [e,i]
			when EndTag
				idx = nil
				(tag.size-1).downto(0) {|j|
					if tag[j][0].name==e.name
						idx = j
						break
					end
				}
				#
				if idx
					if idx==0
						tag = []
					else
						tag = tag[0..idx-1]
					end
				end
			else
			end
			yield e,tag
			i += 1
		}
	end
	#
	def index(_class,_start=0,_end=-1,value=nil,count=1)
		idx=_start
		found=false
		@document[_start.._end].each {|obj|
			if obj.type==_class
				if value
					case obj
					when StartTag,EmptyElementTag,EndTag
						if value===obj.name
							if (not iterator?) or yield(obj)
								if (count-=1)<=0
									found = true
									break
								end
							end
						end
					else
						if value===obj.text
							if (not iterator?) or yield(obj)
								if (count-=1)<=0
									found = true
									break
								end
							end
						end
					end
				else
					if (not iterator?) or yield(obj)
						if (count-=1)<=0
							found = true
							break
						end
					end
				end
			end
			idx+=1
		}
		if found
			idx
		else
			nil
		end
	end
	#
	def end_index(start_index)
		tag = []
		end_index = nil
		(start_index...@document.size).each {|idx|
			e= @document[idx]
			case e
			when StartTag
				tag.push [e,idx]
			when EndTag
				i = nil
				(tag.size-1).downto(0) {|j|
					if tag[j][0].name==e.name
						i = j
						break
					end
				}
				#
				if i
					if i==0
						tag = []
					else
						tag = tag[0..i-1]
					end
				end
				if tag.size==0
					end_index = idx
					break
				end
			else
			end
		}
		end_index
	end
end
=begin End of Document
</body>
</html>
=end