風柳メモ

ソフトウェア・プログラミング関連の覚書が中心

lxmlでテキストノードを除去する方法を知りたい

困っていること

_ElementStringResult や lxml.etree._ElementUnicodeResult といったobjectをドキュメントツリーから除去する方法がわからないので、ご存じの方、教えてください。
lxmlは2.3.3、2.3.5、3.0、3.1beta1といったもので試したが、結果は同様。

! /usr/bin/env python
# -*- coding: utf-8 -*-

import lxml.html

TEST_HTML=u"""
<html>
  <head><title>TEST</title></head>
  <body>
    <div id="text">
      書き始め。
      <p>
        本文はここ。<br />
        ここまでだけ残して、後は消したいの。
      </p>
      <hr />
      <!-- cutting line  -->
      <p>ここから先は要らない</p>
      dust
      <b>ゴミ</b>
      廃棄物<br />
      unnecessary text
    </div>
  </body>
</html>
"""

#{ // def remove_elm_list()
def remove_elm_list(elm_list):
  for (ci, elm) in enumerate(elm_list):
    print u'No.%2d' % (ci+1)
    print elm
    try:
      elm.drop_tree()
    except Exception, s:
      print u'[Error-1] %s' % (s)
      pelm = elm.getparent()
      try:
        pelm.remove(elm)
      except Exception, s:
        print u'[Error-2] %s' % (s)

    print u'='*50+u'\n'

#} // end of remove_elm_list()


if __name__ == '__main__':
  doc = lxml.html.fromstring(TEST_HTML)
  junk_elm_list = doc.xpath(u'//div[@id="text"]/hr/following-sibling::node()')
  remove_elm_list(junk_elm_list)

  print u'■結果'
  print lxml.html.tostring(doc, method='html', encoding=unicode)

# ■ end of file
実行結果
No. 1
<!-- cutting line  -->
==================================================

No. 2
<Element p at 0xb78201ac>
==================================================

No. 3

      dust

[Error-1] '_ElementStringResult' object has no attribute 'drop_tree'
[Error-2] Argument 'element' has incorrect type (expected lxml.etree._Element, got _ElementStringResult)
==================================================

No. 4
<Element b at 0x8565d4c>
==================================================

No. 5

      廃棄物
[Error-1] 'lxml.etree._ElementUnicodeResult' object has no attribute 'drop_tree'
[Error-2] Argument 'element' has incorrect type (expected lxml.etree._Element, got lxml.etree._ElementUnicodeResult)
==================================================

No. 6
<Element br at 0x8565d7c>
==================================================

No. 7

      unnecessary text

[Error-1] '_ElementStringResult' object has no attribute 'drop_tree'
[Error-2] Argument 'element' has incorrect type (expected lxml.etree._Element, got _ElementStringResult)
==================================================

■結果
<html><head><title>TEST</title></head><body>
    <div id="text">
      書き始め。
      <p>
        本文はここ。<br>
        ここまでだけ残して、後は消したいの。
      </p>
      <hr>
      dust

      廃棄物
      unnecessary text
    </div>
  </body></html>


【2013/01/03追記】無理やりやってみる…

#! /usr/bin/env python
# -*- coding: utf-8 -*-

import lxml.html
from lxml.html import Element

TEST_HTML=u"""
<html>
  <head><title>TEST</title></head>
  <body>
    <div id="text">
      書き始め。
      <p>
        本文はここ。<br />
        ここまでだけ残して、後は消したいの。
      </p>
      <hr />
      <!-- cutting line  -->
      <p>ここから先は要らない</p>
      dust
      <b>ゴミ</b>
      廃棄物<br />
      unnecessary text
    </div>
  </body>
</html>
"""
TAG_TEXTNODE = '__TextNode__'

#{ // def remove_elm_list()
def remove_elm_list(elm_list):
  for (ci, elm) in enumerate(elm_list):
    print u'No.%2d' % (ci+1)
    print elm
    try:
      elm.drop_tree()
    except Exception, s:
      print u'[Error-1] %s' % (s)

    print u'='*50+u'\n'

#} // end of remove_elm_list()


#{ // def add_textnode_marks()
def add_textnode_marks(top_elm):
  for elm in top_elm.xpath(u'.//*'):
    etext = elm.text
    if etext:
      t_elm = Element(TAG_TEXTNODE)
      t_elm.text = etext
      elm.text = None
      elm.insert(0, t_elm)
    ttext = elm.tail
    if ttext:
      t_elm = Element(TAG_TEXTNODE)
      t_elm.text = ttext
      elm.tail = None
      elm.addnext(t_elm)

#} // end of def add_textnode_marks()


#{ // def remove_textnode_marks()
def remove_textnode_marks(top_elm):
  for elm in top_elm.xpath('.//%s' % (TAG_TEXTNODE)):
    elm.drop_tag()
#} // end of def remove_textnode_marks()


if __name__ == '__main__':
  doc = lxml.html.fromstring(TEST_HTML)
  add_textnode_marks(doc)
  junk_elm_list = doc.xpath(u'//div[@id="text"]/hr/following-sibling::node()')
  remove_elm_list(junk_elm_list)
  remove_textnode_marks(doc)

  print u'■結果'
  print lxml.html.tostring(doc, method='html', encoding=unicode)

# ■ end of file
実行結果
No. 1
<!-- cutting line  -->
==================================================

No. 2
<Element p at 0xb7821f8c>
==================================================

No. 3
<Element __TextNode__ at 0xb74ceecc>
==================================================

No. 4
<Element b at 0xb74cee9c>
==================================================

No. 5
<Element __TextNode__ at 0xb74ced4c>
==================================================

No. 6
<Element br at 0xb74ced7c>
==================================================

No. 7
<Element __TextNode__ at 0xb74cedac>
==================================================

■結果
<html><head><title>TEST</title></head><body>
    <div id="text">
      書き始め。
      <p>
        本文はここ。<br>
        ここまでだけ残して、後は消したいの。
      </p>
      <hr></div>
  </body></html>

テキスト部分を<__TextNode__>要素に変換、処理してのち元のテキストに戻している……効率悪いし、使いどころが限られるなぁ。
ポイントないので期待薄だけれど、一応人力検索でも質問してみた。