ネット上のチュートリアルを調べてもなんとも上手くいかなかったので備忘として投稿します。
【2019/11/22追記】
お恥ずかしい限りなのですが、本件は私の勘違いが原因だったようで最初に提示していた問題はそもそも起こらないものでした。。。
自戒の意味も合わせて「.//」周りの挙動は多少参考にもなると思われるため、記事は残します。
事の起こり
python3でXML(文字コードはEUC-JP)を扱う機会があり、その対応をしていたところ
<root><tag>
<hoge>
<hogehoge>aaa</hogehoge>
</hoge>
<fuga>bbb</fuga>
<fugo>ccc</fugo>
・
(中略)
・
</tag>
<tag2>
・
(中略)
・
</tag2>
</root>
要素へのアクセス方法としては下記が挙げられますが
import xml.etree.ElementTree as Et
def test():
# ElementTree.parse()はunicodeにしか対応していないそうなので
# 一旦ファイルをopenしてreadから文字列を取得している
with open(r'HogePath/FugaPath/test.xml', 'r', encoding='euc_jp') as f:
root: Et.Element = Et.fromstring(f.read())
# hogehogeのテキスト「aaa」を取得する場合
print(root[0][0][0].text)
if __name__=='__main__':
test()
XMLが複雑になってくるとXPathで取りたくなってきたので、ネットの情報を参考に
print(root.findall('./hoge/hogehoge')[0].text)
と書いてみたものの上手く動かず「IndexError: list index out of range」が出ました。
そもそも下記の結果自体が空になっている…つまりは正しく取れていない模様。←ここが間違い!!
print(root.findall('./hoge/hogehoge'))
例ではあまりにもシンプルで分かりやすい状況ですが、このとき私は「tag」を見落としていたんです。。。
下記はこの見落としをした状態で半ば無理矢理に解決した方法になります。
(誤った)解決(自体は一応できる)方法
XPathのルート周りの指定方法に問題があったようで
print(root.findall('.//hoge/hogehoge')[0].text)
のように頭の部分を「.//」とすれば解決しました。←無理矢理に解決したような状態です
URL等と同じだと覚えればいいのかな…こうなる原理までは辿っていないのでどなたかご教示頂けますと嬉しいです。
【2019/11/22更新】
コメントにて@LOZTPX様にご教示頂いた内容によると
//は起点となるノードの子孫すべての集合を表し、ノードパスを省略するものです。
なるほど! だから上記の見落としていた状態でも「tag」を意識しないうちに拾えていたんですね…これは勉強になりました。
反省
扱うXMLファイルの中身はちゃんと見よう()