lxml で XML で子要素のテキストをフラットにする。
下のような XML があったとする。
<document> out outer1 <outer> in outer1 <inner> in inner </inner> in outer2 </outer> out outer2 </document>
ここで、 document 以下のテキストを一気に書き換えたい。
期待するのは以下の様な動作。(上記 XML を sample.xml とする)
>>> import lxml.html >>> xml = open('sample.xml', 'rb').read() >>> root = lxml.html.fromstring(xml) >>> root.text_content() '\r\n out outer1 in outer1 in inner in outer2 out outer2\r\n' >>> root.text = "new text" >>> root.text_content() 'new text' #こうならない
実際はこうなる
>>> root.text_content() 'new text in outer1 in inner in outer2 out outer2\r\n' >>> root.text 'new text'
Element#text は子要素までのテキストを取ってくる。 text_content() 全部を書き換えたい時はどうすればいいのか?
ということで、タグを外す drop_tag() を使うことにした。
要素のタグを外す Element#drop_tag()
要素のタグだけを外し、テキストはそのまま。
これを子要素に適用していく。
>>> root[0] <Element outer at 0x1e65f60> # 子要素は outer >>> root[0].drop_tag() >>> root.text_content() 'new text in outer1 in inner in outer2 out outer2\r\n' #テキスト全体は変化なし >>> root.text 'new text in outer1 ' # outer が持っていたテキストを保持 >>> root[0] <Element inner at 0x1e65f60> # 子要素が inner になっている
子要素が無くなるまで drop_tag() を実行すれば、全テキストを親要素が保持することになる。
>>> root[0].drop_tag() >>> root[0] Traceback (most recent call last): File "<stdin>", line 1, in <module> File "lxml.etree.pyx", line 1069, in lxml.etree._Element.__getitem__ (src/lxml /lxml.etree.c:37264) IndexError: list index out of range # 子要素がなくなった >>> root.text 'new text in outer1 in inner in outer2 out outer2\r\n' >>> root.text = "new text 2" >>> root.text 'new text 2' >>> root.text_content() 'new text 2' # テキスト全体が置換されている
子要素のリストは list() 関数を HTML Element に使うと得られる。
>>> list(root) [<Element outer at 0x1f68b10>] >>> for e in list(root): ... print e.text ... in outer1 >>> root
ここだと直接の子要素が1つ(outer)しかないので分かりづらいかも
HTML Element を引数に取り、子要素のタグをすべて外す関数 flatten_text()
例の root みたいな Element を渡したら、子要素をテキストだけ残してフラットにしてくれる。テキストごと消したければ、drop_tag() の代わりに Element#drop_tree() を使えばいい。
""" flatten children of HTML Element element: HTML Element, not list """ def flatten_text(element): while list(element): children = list(element) for child in children: child.drop_tag()
実行例:
>>> import lxml.html >>> root = lxml.html.fromstring(open('sample.xml', 'rb').read()) >>> root.text '\r\n out outer1 ' >>> root.text_content() '\r\n out outer1 in outer1 in inner in outer2 out outer2\r\n' >>> flatten_text(root) >>> root.text '\r\n out outer1 in outer1 in inner in outer2 out outer2\r\n' #テキストが取り出されている >>> root.text_content() '\r\n out outer1 in outer1 in inner in outer2 out outer2\r\n' >>> root.text = 'new text' >>> root.text 'new text' >>> root.text_content() 'new text' # すべてのテキストを置き換えることができた