RubyでXMLファイルをHashとしてパースする方法の比較

XMLファイルを使う必要にかられて、ちょっと調べてみました。

実際のプログラムを作る際には、XPashやCSSでの検索に対応したGem[^1]のオブジェクトを使った方がいいとは思いますが、それでも軽く中身を確認するためにはHashで取り込めると便利なので。

具体的には、ActiveSupportを使った方法とXmlSimpleというgemを使った方法が見つかったので、それらの比較になります。

# インストール方法

```bash:ActiveSupport
$ gem install activesupport
```

```bash:XmlSimple
$ gem install xml-simple
```

gemなので、特筆することはなく。

# 使い方

```rb:ActiveSupport
require 'active_support'
require 'active_support/core_ext'

hash = Hash.from_xml(open('path/to/file.xml'))
```

```rb:XmlSimple
require 'xmlsimple'

hash = XmlSimple.xml_in(open("path/to/file.xml"))
```

使い方もほぼ同じですね。
なお、今回の例ではそれぞれの引数に`open`メソッドで開いたFileオブジェクトを渡していますが、XMLのデータ自体を文字列で渡すことも出来るようです。

# 出力結果
以下のXMLデータを読み込ませた結果で比較します。

```xml:テストデータ
<package>
  <track is_inst="true">
    <track_no>1</track_no>
    <title>タイトル1</title>
  </track>
  <track>
    <track_no>2</track_no>
    <title code="12345">タイトル2</title>
    <single />
  </track>
</package>
```

```rb:ActiveSupport
{
    "package" => {
        "track" => [
            [0] {
                 "is_inst" => "true",
                "track_no" => "1",
                   "title" => "タイトル1"
            },
            [1] {
                "track_no" => "2",
                   "title" => "タイトル2",
                  "single" => nil
            }
        ]
    }
}
```

```rb:XmlSimple
{
    "track" => [
        [0] {
             "is_inst" => "true",
            "track_no" => [
                [0] "1"
            ],
               "title" => [
                [0] "タイトル1"
            ]
        },
        [1] {
            "track_no" => [
                [0] "2"
            ],
               "title" => [
                [0] {
                       "code" => "12345",
                    "content" => "タイトル2"
                }
            ],
              "single" => [
                [0] {}
            ]
        }
    ]
}
```

ActiveSupportは、`code`というattributeが欠損してますね(`is_inst`は残っていますが、その差の違いは不明です・・・)。なので、attributeが重要なシーンでは使えなさそうです。
逆に、XmlSimpleはすべてのデータが反映されていますが、attributeはString型、elementはArray型で格納されるようです。確かにわかりやすいですが、elementしか使わない場合などに一気にeach句で回したりするのには不便ですね・・・
**結論:** 適材適所。

# 処理時間

最後に処理時間を計測しました。

- 実行コマンド
    - [使い方](#使い方)の部分のコマンドをそのまま。
- 対象データ
    - 少し大きめの某XMLファイル(5.2MB/144747行)
- 計測方法
    - 計測マシンは、自分のMacbookPro(Core i7 3GHz/16GB)
    - rubyのバージョン`2.2.0p0`
    - ruby標準の[benchmark](http://docs.ruby-lang.org/ja/2.2.0/library/benchmark.html)ライブラリを使いました。

```dummy:ActiveSupport
      user     system      total        real
  9.870000   0.170000  10.040000 ( 10.047934)
```

```dummy:XmlSimple
      user     system      total        real
 10.600000   0.180000  10.780000 ( 10.855242)
```

**結論:** 性能は考慮材料にならなさそう。

# 注意点
どこに書いたらいいのかわからなかったので、ハマった点を最後に書いておきます。

rubyのopenメソッドでは文字コードを指定してファイルを開く事が可能ですが、今回の両gemはそれよりもXMLデータ内のXML宣言(`<?xml version="1.0" encoding="Shift_Jis"?>`みたいなの)の方が優先されるようです。
そのため、ShiftJISのデータをパースしようとすると、文字化けが起きるばかりではなく、いわゆる[ダメ文字](https://ja.wikipedia.org/wiki/Shift_JIS#2.E3.83.90.E3.82.A4.E3.83.88.E7.9B.AE.E3.81.8C5C.E7.AD.89.E3.81.AB.E3.81.AA.E3.82.8A.E3.81.86.E3.82.8B.E3.81.93.E3.81.A8.E3.81.AB.E3.82.88.E3.82.8B.E5.95.8F.E9.A1.8C)のせいで失敗する事さえあります。

これを回避するためには、XMLデータ内のXML宣言を消した上で処理するしかなさそうです。やっぱり、この方法で本番利用するのは現実味がないです。

[^1]: [Nokogiri](http://www.nokogiri.org/)が有名ですね。詳しくは[このページ](http://qiita.com/gravitonMain/items/720f441713b1378fe55c)がよくまとまっていて参考になります。