XPath クエリの結果として返るはずのリーフノードの名前を「XPathから」知る必要に迫られ…。
理由は聞かないでくれ。あまりに馬鹿げてるので説明する気にもなれない。
というわけで、XPath のパーサ的なもの、が必要になり。
ちょっと探したらすぐに見つかった。なぜかドキュメントされていないのだが、標準添付ライブラリの xml.etreeの中に、こっそり「ElementPath」というモジュールがいて、これがちょっとした XPath 処理を担っている。(モジュール名は、Python 3.x でも ElementPath のまま。)
そもそも Python 標準添付には「XMLのフルスペック」を扱えるだけのものはないので、同じく XPath にもそれは言えて、当然 ElementPath も「完全」では全くなくて。ただ、使ってみたら案外良い:
1 # -*- coding: utf-8 -*-
2 from xml.etree.ElementPath import xpath_tokenizer
3
4 def xpath_simplify_tokenize(xpath, nsmaps={}):
5 depth = 0
6 for tok in xpath_tokenizer(xpath, nsmaps):
7 if tok[0] == '[':
8 depth += 1
9 elif tok[0] == ']':
10 depth -= 1
11 elif depth == 0:
12 yield tok
13
14 print(list(
15 xpath_simplify_tokenize(
16 '/books/author[last-name [position()=1]= "Bob"]')))
17
18 # ancestor::は無論「namespace」ではないが、xpath_tokenizer は「馬鹿」なので。
19 # そもそも xpath フルスペックを扱おうとするものではないので…。
20 # 戻りには {ancestor}: のように、python 文字列の format が扱える形("{a}".format(a=1)という
21 # 具合)で返るので、nsmaps の工夫で多分なんとかなる。
22 print(list(
23 xpath_simplify_tokenize(
24 'ancestor::author[last-name [position()=1]= "Bob"]',
25 {'ancestor': 'ancestor'})))
パーザではなくトークナイザなんだけれど、逆にトークナイザであることが功を奏しているというか。
何をしたいかと言えば、XPath の述語部分(predicate)を取り除きたい。クエリ結果の末端ノードを知りたいだけの目的に、述語だけがとてつもなく邪魔なのだわ。
実行結果はこんなね:
1 me@host: ~$ python aaa.py
2 [('/', ''), ('', 'books'), ('/', ''), ('', 'author')]
3 [('', '{ancestor}:author')]
4 me@host: ~$
当たり前ですが、例えば「/*」なんて XPath はどうしようもないよ。これはクエリ叩いてみねばわからんとです。などなど、根本的には制約だらけ、ですよ。だからさ、XPath から結果のノードを知るなんぞ、発想が間違ってるのです。