lxmlのxpathで正規表現を使う方法,あるいはrrdtool-dumpのバージョン差異

PythonXMLパーサモジュールであるlxmlxpathにおいて,正規表現を使って検索をする方法のメモ.

いま,下のようなXMLがあるとする.

<rrd>
  <rra>
    <cf>AVERAGE</cf>
    <pdp_per_row>1</pdp_per_row>
  </rra>
  <rra>
    <cf> AVERAGE </cf>
    <pdp_per_row>5</pdp_per_row>
  </rra>
  <rra>
    <cf>MAX</cf>
    <pdp_per_row>1</pdp_per_row>
  </rra>
  <rra>
    <cf> MAX </cf>
    <pdp_per_row>5</pdp_per_row>
  </rra>
</rrd>

このとき,cfの中身が"AVERAGE"あるいは" AVERAGE "(前後にスペースあり)であるツリーのpdp_per_rowの値を取得したい場合,正規表現で検索すると簡単に扱うことができる.

pdp_per_row_values = list(
    map(
        lambda e: int(e.text),
        tree.xpath(
            './rra/cf[re:match(text(), "[ ]*AVERAGE[ ]*")]/parent::node()/pdp_per_row',
            namespaces={"re": "http://exslt.org/regular-expressions"})))
                                            
print(pdp_per_row_values) # [1, 5]

何故こういうことをやりたかったかというと,rrdtool dumpコマンドでRRDファイルをXMLでダンプしたときに出力されるXMLrrdtoolのバージョンによって異なるから.

rrdtool 1.3.8 ("AVERAGE"の前後にスペースあり):

$ rrdtool dump traffic.rrd | xpath '//rra/cf'
Found 5 nodes:
-- NODE --
<cf> AVERAGE </cf>-- NODE --
<cf> AVERAGE </cf>-- NODE --
<cf> AVERAGE </cf>-- NODE --
<cf> AVERAGE </cf>-- NODE --
<cf> AVERAGE </cf>

rrdtool 1.4.8 ("AVERAGE"の前後にスペースなし):

$ rrdtool dump traffic.rrd | xpath '//rra/cf'
Found 5 nodes:
-- NODE --
<cf>AVERAGE</cf>-- NODE --
<cf>AVERAGE</cf>-- NODE --
<cf>AVERAGE</cf>-- NODE --
<cf>AVERAGE</cf>-- NODE --
<cf>AVERAGE</cf>

rrdtool 1.4.8が入っている手元の環境からrrdtool 1.3.8が入っている別の環境にスクリプトを持っていったところ動かなくなったので,この罠に気付いた*1

参考

*1:どのバージョンから挙動が変わったのかまでは調べていない