YAMAGUCHI::weblog

海水パンツとゴーグルで、巨万の富を築きました。カリブの怪物、フリーアルバイター瞳です。

lxmlを使ってXMLの要素をソートして返す

はじめに

こんにちは、NEETです。最近はいろいろなデータ表現方法が出てきてるわけですが、やはりXMLっていうのは便利でしてできればデータをそのまま弄りたいってこともあるわけです。で、Webサービスとかから引っ張ってきたXMLをid順とかに要素を並べ替えたものにしたい時にlxml使うと割と簡単に出来ました。

サンプルコード

こんな感じです。何したかを簡単に書いとくと

  1. XML中のソートしたい対象のタグをXPathで指定してリストを引っこ抜いてくる
  2. 各要素中のソート基準となる要素に関する比較関数を書く
  3. リストのソート関数を使って並び替える
  4. 大元のElement Treeから並び替えた順に最後尾に差し替えていく

あとはコード読んでください。

from lxml import etree

xml = """
<statuses>
  <status>
    <id>5</id>
    <text>spam</text>
  </status>
  <status>
    <id>1</id>
    <text>egg</text>
  </status>
  <status>
    <id>100</id>
    <text>ham</text>
  </status>
  <status>
    <id>2</id>
    <text>bacon</text>
  </status>
</statuses>
"""

def sort_by_id(xml):
    def asc_cmp(x, y):
        """
        上のスキーマ決め打ちでstatusを昇順にするための比較関数。
        xpathは必ずリストで返してくるので1個しかないと分かっていたら0番目の要素を指定する。
        """
        return int(x.xpath('./id/text()')[0]) - int(y.xpath('./id/text()')[0])

    try:
        tree = etree.fromstring(xml)
        statuses = tree.xpath('//statuses')
        st_list = statuses[0].xpath('./status')
        st_list.sort(asc_cmp)

        for st in st_list:
            # ここでremove, appendする対象はtreeでもstatusesでもstatuses[0]でも結果は一緒なのに注意
            tree.remove(st)
            tree.append(st)

        return etree.tostring(tree, pretty_print=True) # pretty_printにしても崩れる

    except Exception, e:
        """
        適当に処理して
        """
        raise e


if __name__ == '__main__':
    print xml
    print '--------------------'
    print sort_by_id(xml)

statuses[0]の中身だけゴボッと入れ替えられればforとか書かなくていいし、出力したときに綺麗になるしなんだけど、もっといいやり方ないかな。

実行結果

$ python sortxml.py 

<statuses>
  <status>
    <id>5</id>
    <text>spam</text>
  </status>
  <status>
    <id>1</id>
    <text>egg</text>
  </status>
  <status>
    <id>100</id>
    <text>ham</text>
  </status>
  <status>
    <id>2</id>
    <text>bacon</text>
  </status>
</statuses>

--------------------
<statuses>
  <status>
    <id>1</id>
    <text>egg</text>
  </status>
  <status>
    <id>2</id>
    <text>bacon</text>
  </status>
<status>
    <id>5</id>
    <text>spam</text>
  </status>
  <status>
    <id>100</id>
    <text>ham</text>
  </status>
  </statuses>