=begin Start of Document htmlsplit.rb | 戻る |

HTML Split Library

HTMLを読み書きする。 読み込んだ文書はタグと文字列の配列になる。 to_sメソッドでHTMLに戻すことが出来る。

クラス一覧

HTMLSplitHTMLをタグと文字データに分割する。
CharacterData 文字データ
EmptyElementTag 空要素のタグ
StartTag 開始タグ
EndTag 終了タグ
Comment コメント
Declaration 宣言(DOCTYPE)
SSI SSI※
ERuby eRuby/ASP/JSPスクリプト※
PHP PHPスクリプト※
※タグの属性値などに埋め込まれたスクリプトは認識できません。

使い方

読み込み

#!/usr/bin/ruby
require "htmlsplit"

obj = HTMlSplit.new(ARGF.read)

出力

obj.document.each {|e|
    print e.to_s
}

属性の設定

img = Tag('img/')
img['src']='xxx.png'  #<img src="xxx.png">

o = Tag('option')
o['selected']=true    #<option selected>
=end require "cgi" require "kconv" =begin EmptyElementTag

EmptyElementTag

空要素のタグ

クラスメソッド

new(name[,attr])
新しいオブジェクトを生成する。 nameはタグの名前 attrはタグの属性nilまたはHash

メソッド

name
タグ名を返す。
attr
属性を返す。
to_s
HTMLを返す。
self[key]
keyに関連づけられた属性値を返します。 該当するキーが登録されていない時には,nilを返します。
self[key]= value
keyに対してvalueを関連づけます。 valueがnilの時,keyに対する関連を取り除きます。
=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

StartTag

開始タグ

クラスメソッド

new(name[,attr])
新しいオブジェクトを生成する。 nameはタグの名前 attrはタグの属性nilまたはHash

メソッド

name
タグ名を返す。
attr
属性を返す。
to_s
HTMLを返す。
self[key]
keyに関連づけられた属性値を返します。 該当するキーが登録されていない時には,nilを返します。
self[key]= value
keyに対してvalueを関連づけます。 valueがnilの時,keyに対する関連を取り除きます。
=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

EndTag

終了タグ

クラスメソッド

new(name)
新しいオブジェクトを生成する。 nameはタグの名前

メソッド

name
タグ名を返す。
to_s
HTMLを返す。
=end class EndTag def initialize(name) @name = name.downcase end attr_accessor :name def to_s "" end end =begin CharacterData

CharacterData

文字データ

クラスメソッド

new(text)
新しいオブジェクトを生成する。 textはテキスト

メソッド

text
テキストを返す。
to_s
HTMLを返す。
=end class CharacterData def initialize(text) @text = text end attr_accessor :text def to_s @text end end =begin Declaraion

Declaraion

SGML宣言

クラスメソッド

new(text)
新しいオブジェクトを生成する。 textはテキスト

メソッド

text
テキストを返す。
to_s
HTMLを返す。
=end class Declaration def initialize(text) @text = text end attr_accessor :text def to_s "" end end =begin Comment

Comment

コメント

クラスメソッド

new(text)
新しいオブジェクトを生成する。 textはテキスト

メソッド

text
テキストを返す。
to_s
HTMLを返す。
=end class Comment def initialize(text) @text = text end attr_accessor :text def to_s "" end end =begin SSI

SSI

SSI

クラスメソッド

new(text)
新しいオブジェクトを生成する。 textはテキスト

メソッド

text
テキストを返す。
to_s
HTMLを返す。
=end class SSI def initialize(text) @text = text end attr_accessor :text def to_s "" end end =begin ERuby

ERuby

eRuby/ASP/JSPスクリプト

クラスメソッド

new(text)
新しいオブジェクトを生成する。 textはテキスト

メソッド

text
テキストを返す。
to_s
HTMLを返す。
=end class ERuby def initialize(text) @text = text end attr_accessor :text def to_s "<%#{@text}%>" end end =begin PHP

PHP

PHPスクリプト

クラスメソッド

new(text)
新しいオブジェクトを生成する。 textはテキスト

メソッド

text
テキストを返す。
to_s
HTMLを返す。
=end class PHP attr_accessor :text def initialize(text) @text = text end def to_s "" end end =begin HTMLSplit

HTMLSplit

HTML読み書き

クラスメソッド

new(html)
新しいオブジェクトを生成する。 htmlはHTML文書

メソッド

document
ドキュメントの配列を返す。
to_s
HTMLを返す。
each {|obj,tag| ...}
ドキュメントの各オブジェクト(obj)に対してブロックを評価します。 tagは開始タグのリスト( [ StartTag , インデクス] )
index(class, start, end, value, count) {|obj| ...}
startからendまでの要素でclassと等しいcount番目の要素の位置を返します。 等しい要素がひとつもなかった時にはnilを返します。
valueにnil以外の値を指定した時には要素がvalueと等しいかチェックを行います。classがEmptyElementTag,StartTag,EndTagの時はタグ名、それ以外はテキストによって比較します。
ブロックを指定して呼び出された時にはブロックで要素が等しいか評価する。
end_index(start)
startに対応するEndTagのインデクスを返します。 対応する要素がなかった時にはnilを返します。
=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 =end