単純なXMLをhashrefに変換する。

急にPerlの仕事が来たので。

  • 属性は使用しない
  • 同一階層で要素名が重複しない
  • 要素の中身は要素のリストかテキストデータ

というとても単純なXML(というよりSML?)を返す会員情報取得APIがあったのでhashrefで受け取れるようにしたよ。

XML::Parser::Liteを使う練習。

パーサの準備

use strict;
use warnings;

use XML::Parser::Lite;

sub makeHandlers {
    my $node;
    return {
        Init  => sub {
          $node = {};  
        },
        Start => sub {
            my $elementName = $_[1];
            my $parent = $node;
            $node = {
                conclude => sub {
                    my $node = shift;
                    $parent->{elements}->{$elementName} =
                        defined $node->{elements} ? $node->{elements} : $node->{text};
                    return $parent;
                },
                text => ""
            };
        },
        Char  => sub {
            $node->{text} .= $_[1];
        },
        End   => sub {
            $node = $node->{conclude}($node);
        },
        Final => sub {
            return $node->{elements};
        }
    };
}

my $parser = new XML::Parser::Lite;
$parser->setHandlers(%{makeHandlers()});

使用例

こんな感じのXMLがあって

<?xml version="1.0" encoding="UTF-8"?>
<response>
  <siteId>1</siteId>
  <sitename>my site</sitename>
  <user1>
    <userId>1</userId>
    <username>terazzo</username>
    <status>001</status>
  </user1>
  <user2>
    <userId>2</userId>
    <username>petazzo</username>
    <status>000</status>
  </user2>
</response>

というような文字列データが変数$xmlに入っていたとして、

my $results = $parser->parse($xml);

printf "site name: '%s'\n", $results->{response}->{sitename};

while (my ($key, $user) = each(%{$results->{response}})) {
    next unless $key =~ /^user/;
    printf "\tuser: '%s'(id=%d)\n", $user->{username}, $user->{userId};
}

出力結果

site name: 'my site'
	user: 'terazzo'(id=1)
	user: 'petazzo'(id=2)

感想

Visitorパターンで不変オブジェクトベースで実装するにはどうしたらいいか考えてるんだけど難しそうだなあ。