Ruby で書かれた簡単な XML パーサです。
以下の特徴があります。
以下の機能は実装されていません。
また,整形式制約のチェックは完全ではありません。
現在の最新版は <URL:http://www.yoshidam.net/Ruby_ja.html#ymXML> です。
インストーラはありません。 ymxml.rb を適当にインストールしてください。
ymXML は Ruby 1.8,および 1.6 に対応していますが, Ruby 1.8.1 以降での使用をお勧めします。
日本語 EUC,Shift_JIS の XML ファイルを扱うには uconv ライブラリもインストールされている必要があります。
ymXML ライブラリを使うときは以下の require 文が必要です。
require 'ymxml'
YmXML::Parser.new でインスタンスを作成し, parse メソッドでパースを開始します。 parse の引数として YmXML::InputStream オブジェクトを渡します。 ファイル名を渡すときは YmXML::InputStream.openFile を, URI を渡すときは YmXML::InputStream.openURI を使います。 また,UTF-8 の文字列ならば parse に直接渡すこともできます。
parser = YMXML::Parser.new
stream = YmXML::InputStream.openFile('test.xml')
parser.parse(stream)
上記の例では text.xml をパースします。 イベントハンドラがないため,構文チェックのみを行い, エラーを見付けると YmXML::ParseError 例外で報告します。
ymXML は XMLParser と同様,パース結果をイベントとして報告します。 イベントモデルはほぼ XMLParser (Expat) と同じです。 XML ファイルの文字符号化にかかわらず,パース結果は常に UTF-8 に変換されます。
アプリケーションがパース結果を得る方法として, XMLParser と同様,イベントハンドラを定義する方法とイテレータとして使う方法があります。
イベントハンドラを使う場合,基本的に YmXML::Parser クラスを継承し, イベントハンドラメソッドを定義して使ってください。 YmXML::Parser クラスのインスタンスに特異メソッドを定義しても構いません。 イベントハンドラ名,引数の詳細についてはリファレンスを参照してください。
class MyParser < YmXML::Parser
def startElement(name, attrs)
puts "startElement: #{name} #{attrs.inspect}"
end
def endElement(name)
puts "endElement: #{name}"
end
def character(data)
puts "character: #{data.inspect}"
end
end
parser = MyParser.new
parser.parse(YmXML::InputStream.openURI('http://www.yoshidam.net/index.xml'))
イベントハンドラを使う方法は XMLParser との互換のために存在します。 YmXML::Parser クラスと派生クラスの間でインスタンス変数の衝突などの副作用の起きる可能性があるため, イベントハンドラの使用はあまりお勧めではありません。
イテレータとして使う方法は, より Ruby らしい(と思われる)やりかたです。 ブロックを使って繰り返し構文と同様に記述することができます。
YmXML::Parser#parse メソッドにブロックを渡すことでイテレータモードで動作するようになります。 イテレータモードではたとえイベントハンドラを定義していても無視されます。 イテレータはイベントタイプ,(主に)名称, (主に)データ,パーサオブジェクトという 4 つの変数をブロックに渡します。 イベントタイプはイベント名のシンボルです。 名称,データの意味はイベントによって異なります。 パーサオブジェクトはイベントを発生させたオブジェクトです。 通常はレシーバそのものですが,実体パース中は実体用のパーサオブジェクトを返します。
parser = YmXML::Parser.new
stream = YMXML::InputStream.openURI('http://www.yoshidam.net/index.xml')
parser.parse(stream) do |etype, name, data, currentParser|
case etype
when :START_ELEM
puts "startElement: #{name} #{data.inspect}"
when :END_ELEM
puts "endElement: #{name}"
when :CDATA
puts "character: #{data.inspect}"
end
end
イテレータモードの応用としてリスナオブジェクトを渡す方法があります。 リスナオブジェクトに対してイベントハンドラを定義します。 リスナオブジェクトは YmXML::Listener モジュールをインクルードし, listen メソッドの返値に & をつけてイテレータに渡してください。 イベントハンドラ内でパーサオブジェクトを得たい場合は YmXML::Listener#currentParser メソッドを使うことができます。
class MyListener
include YmXML::Listener
def startElement(name, attrs)
puts "startElement: #{name} #{attrs.inspect}"
end
def endElement(name)
puts "endElement: #{name}"
end
def character(data)
puts "character: #{data.inspect}"
end
end
parser = YmXML::Parser.new
listener = MyListener.new
stream = YMXML::InputStream.openURI('http://www.yoshidam.net/index.xml')
parser.parse(stream, &listener.listen)
入力文字符号化は UTF-8, UTF-16 などに対応しています。 uconv ライブラリがインストールされていると EUC-JP, Shift_JIS, ISO-2022-JP も使うことができます。
YmXML::InputStream を拡張することでその他の文字符号化に対応することもできます。 YmXML::InputStream の拡張には YmXML::InputStream.new にブロックを与える方法と YmXML::InputStream#unknownEncoding をオーバーライドする方法があります。
YmXML::InputStream.new にブロックを与える場合,以下のようになります。 encoding は常に小文字になります。 変換できない場合は例外を発生してください。
stream = YmXML::InputStream.openURI('...') do |encoding, content|
case encoding
when "euc-kr"
Iconv.iconv("UTF-8", "EUC-KR", content)[0]
else
raise YmXML::EncodingError.new("unknown encoding: #{encoding}")
end
end
YmXML::InputStream#unknownEncoding のオーバーライドは以下のようになります。 encoding は常に小文字になります。 変換できない場合は super を呼び出してください。
class MyInputStream < YmXML::InputStream
require 'iconv'
def unknownEncoding(encoding, content)
case encoding
when "euc-kr"
return Iconv.iconv("UTF-8", "EUC-KR", content)[0]
end
super
end
end
EBCDIC などの ASCII 互換でない文字符号化の場合, YmXML::InputStream を拡張し, さらに YmXML::InputStream.openURI 等で encoding 引数を指定してください。
ymXML は名前空間をサポートします。 YmXML::Parser.new の第二引数 nssep に nil 以外の文字列を渡すと, パーサが名前空間対応モードで動作するようになります。
名前空間対応モードでは START_NAMESPACE_DECL, END_NAMESPACE_DECL イベントが発生します。 また,要素名,属性名が名前空間 URI, ローカル名, 名前空間プレフィックスを nssep で連結した名前になります。 これは XMLParser で setReturnNSTriplet(true) の場合と同じ動作です。
xml = "
<test xmlns:ns1='http://www.yoshidam.net/ns/ns1'>
<ns1:aa/>
</text>"
YmXML::Parser.new(nil, '|').parse(xml) do |etype, name, data, currentParser|
case etype
when :START_ELEM
p [etype, name]
when :START_NAMESPACE_DECL,:END_NAMESPACE_DECL
p [etype, name, data]
end
end
上記コードの実行結果は以下のようになります。
[:START_NAMESPACE_DECL, "ns1", "http://www.yoshidam.net/ns/ns1"] [:START_NAMESPACE_DECL, nil, nil] [:START_ELEM, "|test|"] [:START_ELEM, "http://www.yoshidam.net/ns/ns1|aa|ns1"] [:END_NAMESPACE_DECL, nil, nil] [:END_NAMESPACE_DECL, "ns1", nil]
name.split(nssep,3) 等で分割することもできます。 URI と プレフィックスは空文字列になることがありますが, これは START_NAMESPACE_DECL のパラメータでの nil を意味していることに注意してください。
ymXML は内部 DTD サブセットで宣言された外部解析対象実体のパースが可能です。
EXTERNAL_ENTITY_REF イベント内で YmXML::Parser#createChildParser メソッドで実体パーサを生成するか, YmXML::Parser#parseExternalEntity メソッドでパースしてください。 イテレータモードでは YmXML::Parser#parseExternalEntity メソッドしか使えません。
EXTERNAL_ENTITY_REF イベントで通知される base は YmXML::Parser#setBase で設定された URI ベースです。 外部解析対象実体をパースする場合は YmXML::Parser#setBase で正しい URI ベースを設定してください。 YmXML::InputStream#getURIBase メソッドを使って URI ベースを得ることもできます。
class MyParser < YmXML::Parser
def startElement(name, attrs)
puts "startElement: #{name} #{attrs.inspect}"
end
def endElement(name)
puts "endElement: #{name}"
end
def character(data)
puts "character: #{data.inspect}"
end
def externalEntityRef(context, base, systemId, publicId)
stream = YmXML::InputStream.openURI(systemId, base)
## createChildParser を使う場合
# cp = createChildParser(context)
# cp.setBase(stream.getURIBase)
# cp.parse(stream)
# cp.done
## parseExternalEntity を使う場合
parseExternalEntity(context, stream)
end
end
parser = MyParser.new
stream = YmXML::InputStream.openFile(ARGV[0])
parser.setBase(stream.getURIBase)
parser.parse(stream)
パースエラー(XML 仕様における致命的なエラー)が発見すると,例外を発生します。 文字符号化の変換に失敗した場合,YmXML::EncodingError, それ以外のエラーは YmXML::ParseError です。 また,YmXML::Parser#stop メソッドを呼び出した場合 YmXML::ParseStopped 例外が発生します。
YmXML::Parser#getContentURI でパース中の文書ファイル名(あるいは実体のファイル名)と およそのエラー位置を得ることができます。
begin
p = YmXML::Parser.new
p.parse(stream)
rescue YmXML::Error
uri, l = p.getContentURI
$stderr.puts "#{uri}(#{l}): #{$!}"
$stderr.puts $!.backtrace.join("\n\tfrom ")
end
XML 1.1 に一部対応しています。
文書,実体の XML 宣言,テキスト宣言の version が "1.1" の時に XML 1.1 モードになります。 XML 宣言,テキスト宣言がない場合は XML 1.0 モードになります。 これは XML 1.1 の仕様上,正しい動作とは言えないことに注意してください。
Unicode の正規化チェックは行いません。
注意: XML 1.0 と XML 1.1 には互換性がありません。 特別な理由がない限り XML 1.1 は使うべきではありません。
XML パーサクラスです。
XMLParser のクラスメソッドのうち, expatVersion, getFeatureList, およびインスタンスメソッドのうち defaultCurrent, column, byteIndex, byteCount, setParamEntityParsing, getInputContext, getIdAttribute, reset, useForeignDTD は対応するメソッドが存在しません。
YmXML::Parser.new(encoding = nil,
nssep = nil)YmXML::Parser#parse(stream, &block)| etype | name | data |
|---|---|---|
| CDATA | nil | text |
| XML_DECL | nil | [version, encoding, standalone] |
| PI | name | data |
| COMMENT | nil | data |
| START_ELEM | name | attrs |
| END_ELEM | name | nil |
| START_NAMESPACE_DECL | prefix | uri |
| END_NAMESPACE_DECL | prefix | nil |
| ELEMENT_DECL | name | model |
| ATTLIST_DECL | elname | [attname, att_type, dflt, isrequired] |
| NOTATION_DECL | notationName | [base, systemId, publicId] |
| ENTITY_DECL | entityName | [isparameter_entity, value, base, systenId, publicId, notationName] |
| EXTERNAL_ENTITY_REF | context | [base, systemId, publicId] |
| SKIPPED_ENTITY | name | isParameterEntity |
| START_CDATA | nil | nil |
| END_CDATA | nil | nil |
| START_DOCUMENT | nil | nil |
| END_DOCUMENT | nil | nil |
YmXML::Parser#stop()YmXML::Parser#done()YmXML::Parser#createChildParser(context, encoding = nil, nssep = nil)YmXML::Parser#parseExternalEntity(context, content)YmXML::Parser#setBase(base)YmXML::Parser#getBase()YmXML::Parser#getContentURI()YmXML::Parser#line()YmXML::Parser#getSpecifiedAttributes()YmXML::Parser#setReturnNSTriplet(flag)XMLParser のイベントのうち,UNPARSED_ENTITY_DECL,START_DOCTYPE_DECL, END_DOCTYPE_DECL,DEFAULT は存在しません。
YmXML::Parser#character(text)YmXML::Parser#xmlDecl(version, encoding, standalone)YmXML::Parser#processingInstruction(name, data)YmXML::Parser#comment(data)YmXML::Parser#startElement(name, attrs)YmXML::Parser#endElement(name)YmXML::Parser#startNamespaceDecl(prefix, uri)YmXML::Parser#endNamespaceDecl(prefix)YmXML::Parser#elementDecl(name, model)YmXML::Parser#attlistDecl(elname, attname, att_type, dflt, isrequired)
<?xml version="1.0" standalone="no?>
<!DOCTYPE test [
<!ATTLIST test attr1 CDATA "ATTR1"
attr2 CDATA "ATTR2">
<!ATTLIST test attr1 CDATA "ATTR1a"
attr3 CDATA "ATTR3">
%PE;
<!ATTLIST test attr4 CDATA "ATTR4"
attr5 CDATA "ATTR5">
]>
| elname | attrname | dflt |
|---|---|---|
| test | attr1 | ATTR1 |
| test | attr2 | ATTR2 |
| test | attr3 | ATTR3 |
YmXML::Parser#notationDecl(notationName, base, systemId, publicId)YmXML::Parser#entityDecl(entityName, isparameter_entity, value,
base, systenId, publicId, notationName)YmXML::Parser#externalEntityRef(context, base, systemId, publicId)YmXML::Parser#skippedEntity(name, isParameterEntity)YmXML::Parser#startCdata()YmXML::Parser#endCdata()YmXML::Parser#startDocument()YmXML::Parser#endDocument()イテレータモード用のリスナオブジェクトを作るためのモジュールです。 YmXML::Parser のイベントハンドラと同じメソッドが使えます。
YmXML::Listener#listen()class MyListener include YmXML::Listener end listner = MyListener.new YMXML::Parser.new.parse(file, &listener.listen)
YmXML::Listener#currentParser()入力 XML ファイルを管理するためのクラスです。
YmXML::InputStream.new(stream, encoding = nil, &block)YmXML::InputStream.openFile(file, encoding = nil, &block)YmXML::InputStream.openURI(uri, base = nil, encoding = nil, &block)YmXML::InputStream.setURIResolver(cmd)
YmXML::InputStream.setURIResolver(proc {|uri| open(uri) })
YmXML::InputStream.setURIResolver("wget -O - -o /dev/null")
YmXML::InputStream#setURI(uri)YmXML::InputStream#getURI()YmXML::InputStream#getURIBase()YmXML::InputStream#unknownEncoding(encoding, content)以下の点で XML 1.0 仕様の non-validating プロセッサに適合しません。
ruby-1.6 では正規表現の互換性の問題で,文字クラスチェックが正常に動作しません。 ruby-1.8 以降での使用をお勧めします。
本ライブラリの著作権は吉田正人が保持します。
本ライブラリは,Ruby ライセンスにしたがって利用することができます。
Nov 24, 2005 version 0.5.1 パラメタ実体対応 Apr 1, 2004 version 0.4.7 ユーザーガイド公開 Feb 5, 2004 version 0.4.0 XML 1.1 対応など Mar 25, 2003 version 0.3.0 namespace 対応,DTD 対応など Mar 14, 2003 version 0.2.1 開発再開 Jan 7, 1999 version 0.2 Ruby で書き直し Apr 10, 1998 version 0.1 Perl 版