Perl モジュール XML::Simple の使い方

Perl のモジュール XML::Simple の使い方を解説します。
XML::Simple は名前の通りかなり簡単に使用できますが、オプションが複雑なのと、扱うXMLの個々のデータによってはエラーを吐くことなく実行が終了してしまうこともありますので、その際の留意点などをご紹介します。

なお、このページで紹介するのは、読み込み時( XMLin )の場合のオプションと使用方法のみです。
XMLout での書き出しが余り実用的でないという解説は、下記ページをご覧下さい。

Perl で日本語 XMLを扱う

<company></company> などのタグで囲まれた部分を 「エレメント」、<company id=”1234″>の「id=”1234″」の部分を「アトリビュート」と記載します。
「要素」「属性」はそれぞれ一般的な意味で使用しています。

ある程度 Perl の知識があり、XMLの概要も理解されている方を対象としています。

解析対象XML

このページでのサンプルに使用するXMLは、以下の通りです。

<?xml version="1.0" encoding="utf-8" ?>
<root>
<company id="1234">
<name>ウェブウェア・オルグ</name>
<address>東京都渋谷区円山町6-7 渋谷アムフラット1F</address>
<business>ウェブサイト構築</business>
<business>CGI作成</business>
<contact>
<tel avail="9-18">03-5428-8778</tel>
<mail avail="24h">info@web-ware.org</mail>
</contact>
</company>
</root>

上記XMLを XMLin で読み込み、読み込んだ構造体を Data::Dumper で解析したものを解説します。

オプション無しの場合

オプション無しで以下のようにXMLのパースを行うと、XML::Simple はアトリビュートもエレメントも一緒にフラットに、構造対にパックします。

#オプションなし
$xs = new XML::Simple();
$ref = $xs->XMLin($xml);
print Dumper($ref);

結果出力は以下の通りです。

$VAR1 = {
'company' => {
'contact' => {
'mail' => {
'content' => 'info@web-ware.org',
'avail' => '24h'
},
'tel' => {
'content' => '03-5428-8778',
'avail' => '9-18'
}
},
'address' => '東京都渋谷区円山町6-7 渋谷アムフラット1F',
'id' => '1234',
'business' => [
'ウェブサイト構築',
'CGI作成'
],
'name' => 'ウェブウェア・オルグ'
}
};

$ref->{company} の下に、アトリビュートである id もエレメントである name、address も同じ構成でパースされています。
これらの値は、$ref->{company}->{id} などでアクセスできます。

構造が違うのは、同じ名前の兄弟エレメントが2つある business と、入れ子になっている contact エレメント配下の要素です。

同じ名前のエレメントが2つ以上ある時は、それらは(リファレンスされた)配列に格納されます。
$ref->{company}->{business}->[0] が “ウェブサイト構築”
$ref->{company}->{business}->[1] が “CGI作成”
です。

contact の直下の mail エレメントと tel エレメントは、(リファレンスされた)ハッシュに格納されます。

mail エレメントの中身と tel エレメントの中身は、やはり(リファレンスされた)ハッシュに格納されますが、アトリビュート(avail)は (company@id が格納された場合と同じく、)アトリビュート名がハッシュのキーとなります。($ref->{company}->{contact}->{tel}->{avail} で参照できます。)

一つのエレメント配下にアトリビュートまたは他のエレメントとエレメント直下のテキストが両方ある場合、テキスト要素は、固定の文字列 「content」をキーとしてハッシュに格納されます。
つまり、company/contact/tel@avail アトリビュートが無ければ、$ref->{company}->{contact}->{tel} が “03-5428-8778″ となり、avail アトリビュートがある場合、$ref->{company}->{contact}->{tel}->{content} が “03-5428-8778″ となります。

forcearray の指定

上記例だと、絶対に要素構成が一定のXMLを読み込む場合は特に問題ありませんが、要素数が一定でないXMLを読み込む場合は不適です。

例えば、business エレメントが一つしかない company データを読み込む場合と2つ以上の business エレメントを持つ company データを読み込む場合で、business の値を格納する要素構成が変わってしまいます。

business エレメントが一つしかない場合、読み込まれた値を参照するには
$ref->{company}->{business} となりますが、
2つ以上 business エレメントがある場合、
$ref->{company}->{business}->[0]
$ref->{company}->{business}->[1] …
となり、処理が変わってしまいます。

このような場合は、forcearray オプションを指定し、必ずリファレンスされた配列に値を読み読み込むようにします。

サンプルコード1: forcearray => 1

#全てを配列に格納する
$xs = new XML::Simple(forcearray => 1);
$ref = $xs->XMLin($xml);
print Dumper($ref);

このように指定すると、出力の結果は以下の通りになります。

$VAR1 = {
'company' => [
{
'contact' => [
{
'mail' => [
{
'content' => 'info@web-ware.org',
'avail' => '24h'
}
],
'tel' => [
{
'content' => '03-5428-8778',
'avail' => '9-18'
}
]
}
],
'address' => [
'東京都渋谷区円山町6-7 渋谷アムフラット1F'
],
'id' => '1234',
'business' => [
'ウェブサイト構築',
'CGI作成'
],
'name' => [
'ウェブウェア・オルグ'
]
}
]
};

company 以下の全てのエレメントについて、同じ名前の兄弟エレメントがあろうと無かろうと、エレメント名をキーとするハッシュの値として配列の参照をとり、その配列の要素として、エレメント内のテキストまたは入れ子になったエレメントを表現するハッシュの参照が格納されています。
(アトリビュートの値は、オプション指定しない場合と同じく、直接ハッシュの値となります。)

オプション無しの場合には
$ref->{company}->{address}
とアクセスしていた値に、今度は以下のようにアクセスします。
$ref->{company}->[0]->{address}->[0]

サンプルコード2: forcearray => 配列の参照

上記「サンプルコード1」の方法だと、全てのエレメントが一旦配列に読み込まれてしまい、少々コードが面倒です。絶対一つしか出てこないエレメントは、そのままハッシュの値として読み込んだ方が簡単です。

このような場合、配列の構造にしたいエレメントを指定できます。
forcearray に、配列にしたいエレメント名の配列の参照を指定します。

#出現するエレメントが1つでも配列に格納するのは、address と business のみ
$xs = new XML::Simple(ForceArray => ['address', 'business']);
$ref = $xs->XMLin($xml);
print Dumper($ref);

この場合、出力は以下の通りです。

$VAR1 = {
'company' => {
'contact' => {
'mail' => {
'content' => 'info@web-ware.org',
'avail' => '24h'
},
'tel' => {
'content' => '03-5428-8778',
'avail' => '9-18'
}
},
'address' => [
'東京都渋谷区円山町6-7 渋谷アムフラット1F'
],
'id' => '1234',
'business' => [
'ウェブサイト構築',
'CGI作成'
],
'name' => 'ウェブウェア・オルグ'
}
};

forcearray に指定しなかった name エレメントは ハッシュの値としてテキストが格納されていますが、指定した address は、出現するエレメントが1つにも関わらず、配列の構造になっています。
(businessエレメントも、出現数が1つの場合でも配列に格納されます。)

今度は、$ref->{company}->{address}->[0] や $ref->{company}->{name} で値にアクセスできます。

keyattr オプションの使用方法

さて、ここで、解析対象のXMLに、もう一つ company 要素を加えて、2つの会社の情報が含まれたXMLを解析してみることにします。
以下の構成になります。

<root>
<company id="1234">
<name>ウェブウェア・オルグ</name>
<address>東京都渋谷区円山町6-7 渋谷アムフラット1F</address>
<business>ウェブサイト構築</business>
<business>CGI作成</business>
<contact>
<tel avail="9-18">03-5428-8778</tel>
<mail avail="24h">info@web-ware.org</mail>
</contact>
</company>
<company id="5678">
<name>有限会社すぐ使えるCGI</name>
<address>東京都渋谷区円山町6-7 渋谷アムフラット2F</address>
<business>CGI販売</business>
<contact>
<tel avail="9-18">03-5428-8778</tel>
<mail avail="24h">contact@sugutsukaeru.jp</mail>
</contact>
</company>
</root>

上記XMLを、最初の例と同じく、オプションなしでパースしてみます。

#オプションなし
$xs = new XML::Simple();
$ref = $xs->XMLin($xml);
print Dumper($ref);

結果出力は以下の通りです。

$VAR1 = {
'company' => {
'有限会社すぐ使えるCGI' => {
'contact' => {
'mail' => {
'content' => 'contact@sugutsukaeru.jp',
'avail' => '24h'
},
'tel' => {
'content' => '03-5428-8778',
'avail' => '9-18'
}
},
'address' => '東京都渋谷区円山町6-7 渋谷アムフラット2F',
'id' => '5678',
'business' => 'CGI販売'
},
'ウェブウェア・オルグ' => {
'contact' => {
'mail' => {
'content' => 'info@web-ware.org',
'avail' => '24h'
},
'tel' => {
'content' => '03-5428-8778',
'avail' => '9-18'
}
},
'address' => '東京都渋谷区円山町6-7 渋谷アムフラット1F',
'id' => '1234',
'business' => [
'ウェブサイト構築',
'CGI作成'
]
}
}
};

company エレメントが1個だった時とは、構造が違います。
会社名(company/name) をキーとするハッシュの値として、それぞれの company エレメント配下のノード構成を反映したハッシュの参照が格納されています。

これは、XML::Simple のデフォルトの動作として、同じ名前の兄弟エレメントがあった時、もしその配下に特定の名前のエレメントまたはアトリビュートがあれば、その要素の値をキーとするハッシュの値としてエレメントの構造体を格納する、という指定がされているためです。

その動作を変更するのが keyattr オプションです。
XML::Simple のデフォルトの動作は keyattr => ['name', 'key', 'id'] が指定されている場合と同じで、参照で指定された配列に指定のある順で配下のエレメント名またはアトリビュート名を探し、見つかった要素の値をキーとしてハッシュを構成します。

サンプルのXML構造では、company の配下に name というエレメントがあるので、この要素の値がハッシュのキーの値として採用されます。

このような解釈を止めたい場合は、keyattr => [] と空の配列を指定します。

また、キーとする要素を変えたい場合、例えば、id アトリビュートでハッシュを構成したい場合、keyattr => ['id'] を指定します。

なお、keyattrとして指定したエレメントまたはアトリビュートの値に重複する値が合った場合、読み込まれるハッシュの値は上書きされてしまいます。

※ forcearray => 1 の場合も、keyattr => [] の場合と同様、特定の要素の値をキーとしたハッシュへの変換がされなくなります。

空エレメントに関する注意点

解析するXMLを元のものからさらに単純なものに戻します。

<?xml version="1.0" encoding="utf-8" ?>
<root>
<company id="1234">
<name>ウェブウェア・オルグ</name>
<address>東京都渋谷区円山町6-7 渋谷アムフラット1F</address>
<business>ウェブサイト構築</business>
<business>CGI作成</business>
</company>
</root>

この場合、forcearray => 1 を指定して解析した結果は以下の通りになります。

$VAR1 = {
'company' => [
{
'address' => [
'東京都渋谷区円山町6-7 渋谷アムフラット1F'
],
'id' => '1234',
'business' => [
'ウェブサイト構築',
'CGI作成'
],
'name' => [
'ウェブウェア・オルグ'
]
}
]
};

では、次のXMLではどうなるでしょうか? address エレメントの値がありません。

<?xml version="1.0" encoding="utf-8" ?>
<root>
<company id="1234">
<name>ウェブウェア・オルグ</name>
<address></address>
<business>ウェブサイト構築</business>
<business>CGI作成</business>
</company>
</root>

出力結果は以下の通りです。

$VAR1 = {
'company' => [
{
'address' => [
{}
],
'id' => '1234',
'business' => [
'ウェブサイト構築',
'CGI作成'
],
'name' => [
'ウェブウェア・オルグ'
]
}
]
};

address の部分以外は同じですが、最初の例では ‘東京都渋谷区円山町6-7 渋谷アムフラット1F’ とスカラーが入っていた部分が、今度は {} と、空のハッシュの参照になっています。

このように、エレメントが空だと、XML::Simple の解析では空のハッシュを返します。

address エレメントに値があれば、その値には
$ref->{company}->[0]->{address}->[0] でアクセスできました。

ところが、address エレメントが空要素だった場合、
$ref->{company}->[0]->{address}->[0] (存在しない構成)を参照することは致命的エラーとなり、Perl はもの言わず落ちてしまいます。

これを回避するには、空である可能性のある値を参照する場合は、変数がリファレンスかスカラーかの判別をしてから処理を行います(下記参照)。

unless (ref $ref->{company}->[0]->{address}->[0]){ #リファレンスでない、つまりスカラー
print "address = $ref->{company}->[0]->{address}->[0]\n";
} else {
print "address = (no value)\n";
}

2006-06-26 空エレメントに関する注意点に関する補足

空エレメントの解析結果をコントロールするオプションがありました。
(フィードバックを頂きました。)

SuppressEmpty オプションを使用します。上記の forcearray => 1 に追加して使用すると下記の様になります。

SuppressEmpty => undef, forcearray => 1

#空エレメントはundef
$xs = new XML::Simple(SuppressEmpty => undef, forcearray => 1);
$ref = $xs->XMLin($xml);
print Dumper($ref);

「undef」を指定すると、値が無ければ undef になります。

$VAR1 = {
'company' => [
{
'address' => [
undef
],
'id' => '1234',
'business' => [
'ウェブサイト構築',
'CGI作成'
],
'name' => [
'ウェブウェア・オルグ'
]
}
]
};

SuppressEmpty => ”, forcearray => 1

#空エレメントは''(空文字列)
$xs = new XML::Simple(SuppressEmpty => '', forcearray => 1);
$ref = $xs->XMLin($xml);
print Dumper($ref);

「”」(シングルクオートで囲った空文字列)を指定すると、値が無ければ ” になります。

$VAR1 = {
'company' => [
{
'address' => [
''
],
'id' => '1234',
'business' => [
'ウェブサイト構築',
'CGI作成'
],
'name' => [
'ウェブウェア・オルグ'
]
}
]
};

SuppressEmpty => 1, forcearray => 1

1 を指定すると、ハッシュのエントリ($ref->{company}->[0]->{address} 自体)を作成しません。

おまけ

XMLの構造を設計する時、この例で参考にしたように

<company id="1234" />

と、情報を特定する属性をアトリビュートについつい指定してしまいがちですが、これは、やらない方が良いです。

ID番号などは、以下のように指定する方が良いです。

<company>
<id>1234</id>
</company>

アトリビュートを使用するのは、エレメントが囲っている値そのものを修飾する場合にとどめ、エレメントで済むことはエレメントで済ませた方が良いようです。

アトリビュートの指定が有効な例

<price currency="yen">5000</price>

理由は上手く説明できないのですが(^^;)、XPath などでデータを指定する時、アトリビュートによる指定はエレメントによる指定より複雑で、処理的にも expensive であり、必要なノードの取得ができないこともあります。