15

xml の開始タグと対応する終了タグの間のコンテンツ全体を取得しようとしています。

以下のような単純なケースでコンテンツを取得するのtitleは簡単ですが、混合コンテンツが使用されていて、内側のタグを保持したい場合、タグ間のコンテンツ全体を取得するにはどうすればよいですか?

<?xml version="1.0" encoding="UTF-8"?>
<review>
  <title>Some testing stuff</title>
  <text sometimes="attribute">Some text with <extradata>data</extradata> in it.
  It spans <sometag>multiple lines: <tag>one</tag>, <tag>two</tag> 
  or more</sometag>.</text>
</review>

私が欲しいのは、text任意のタグを含む、2 つのタグ間のコンテンツです。Some text with <extradata>data</extradata> in it. It spans <sometag>multiple lines: <tag>one</tag>, <tag>two</tag> or more</sometag>.

今のところ、正規表現を使用していますが、ちょっと面倒なので、このアプローチは好きではありません。私は XML パーサー ベースのソリューションに傾倒しています。minidometreelxmlを調べましたBeautifulSoupが、この場合の解決策が見つかりませんでした (内部タグを含むコンテンツ全体)。

4

5 に答える 5

7

私とあなたのサンプルでうまくいくものは次のとおりです。

from lxml import etree
doc = etree.XML(
"""<?xml version="1.0" encoding="UTF-8"?>
<review>
  <title>Some testing stuff</title>
  <text>Some text with <extradata>data</extradata> in it.</text>
</review>"""
)

def flatten(seq):
  r = []
  for item in seq:
    if isinstance(item,(str,unicode)):
      r.append(unicode(item))
    elif isinstance(item,(etree._Element,)):
      r.append(etree.tostring(item,with_tail=False))
  return u"".join(r)

print flatten(doc.xpath('/review/text/node()'))

収量:

Some text with <extradata>data</extradata> in it.

xpath は要素のすべての子ノードを選択し<text>、それらが文字列/Unicode サブクラス ( ) の場合は直接 Unicode にレンダリングするか、それがの場合はそれを<class 'lxml.etree._ElementStringResult'>呼び出して、末尾の重複を回避します。etree.tostringElementwith_tail=False

他のノード タイプが存在する場合は、それらを処理する必要がある場合があります。

于 2012-06-20T15:39:57.763 に答える
3

lxml * を使用すると、parse()およびtostring()関数を使用してかなり簡単に実行できます。

from  lxml.etree import parse, tostring

まず、ドキュメントを解析して要素を取得します (私は XPath を使用していますが、好きなものを使用できます)。

doc = parse('test.xml')
element = doc.xpath('//text')[0]

このtostring()関数は、要素のテキスト表現を返します。

>>> tostring(element)
'<text>Some <text>text</text> with <extradata>data</extradata> in it.</text>\n'

ただし、外部要素は必要ないため、簡単なstr.replace()呼び出しで削除できます。

>>> tostring(element).replace('<%s>'%element.tag, '', 1)
'Some <text>text</text> with <extradata>data</extradata> in it.</text>\n'

str.replace()3 番目のパラメーターとして 1 を受け取ったため、最初に出現した開始タグのみが削除されることに注意してください。終了タグでもできます。ここで、1 の代わりに -1 を渡して置き換えます。

>>> tostring(element).replace('</%s>'%element.tag, '', -1)
'<text>Some <text>text with <extradata>data</extradata> in it.\n'

もちろん、解決策はすべてを一度に行うことです。

>>> tostring(element).replace('<%s>'%element.tag, '', 1).replace('</%s>'%element.tag, '', -1)
'Some <text>text with <extradata>data</extradata> in it.\n'

編集: @Charles は良い点を指摘しました: タグは属性を持つことができるため、このコードは脆弱です。可能なまだ限定的な解決策は、文字列を最初に分割すること>です。

>>> tostring(element).split('>', 1)
['<text',
 'Some <text>text</text> with <extradata>data</extradata> in it.</text>\n']

2 番目の結果の文字列を取得します。

>>> tostring(element).split('>', 1)[1]
'Some <text>text</text> with <extradata>data</extradata> in it.</text>\n'

次にそれを分割します:

>>> tostring(element).split('>', 1)[1].rsplit('</', 1)
['Some <text>text</text> with <extradata>data</extradata> in it.', 'text>\n']

そして最後に最初の結果を取得します:

>>> tostring(element).split('>', 1)[1].rsplit('</', 1)[0]
'Some <text>text</text> with <extradata>data</extradata> in it.'

>とはいえ、属性内であっても XML では完全に有効な char であるため、このコードはまだ脆弱です。

いずれにせよ、MattH ソリューションが実際の一般的なソリューションであることを認めなければなりません。

* 実際、このソリューションはElementTreeでも機能します。これは、lxml に依存したくない場合に最適です。唯一の違いは、XPath を使用する方法がないことです。

于 2012-06-20T15:44:54.280 に答える
3
from lxml import etree
t = etree.XML(
"""<?xml version="1.0" encoding="UTF-8"?>
<review>
  <title>Some testing stuff</title>
  <text>Some text with <extradata>data</extradata> in it.</text>
</review>"""
)
(t.text + ''.join(map(etree.tostring, t))).strip()

ここでの秘訣は、t反復可能であり、反復するとすべての子ノードが生成されることです。etree はテキスト ノードを回避するため、最初の子タグの前のテキストをt.text.

In [50]: (t.text + ''.join(map(etree.tostring, t))).strip()
Out[50]: '<title>Some testing stuff</title>\n  <text>Some text with <extradata>data</extradata> in it.</text>'

または:

In [6]: e = t.xpath('//text')[0]

In [7]: (e.text + ''.join(map(etree.tostring, e))).strip()
Out[7]: 'Some text with <extradata>data</extradata> in it.'
于 2012-06-20T15:48:42.620 に答える
1

上記の@Marcinのソリューションが好きですが、彼の2番目のオプション(ツリーのルートではなくサブノードを変換する)を使用すると、エンティティが処理されないことがわかりました。

上記の彼のコード (エンティティを追加するように変更):

from lxml import etree
t = etree.XML("""<?xml version="1.0" encoding="UTF-8"?>
<review>
  <title>Some testing stuff</title>
    <text>this &amp; that.</text>
</review>""")
e = t.xpath('//text')[0]
print (e.text + ''.join(map(etree.tostring, e))).strip()

戻り値:

this & that.

適切なエンティティ ('&') の代わりに、そのままの/エスケープされていない '&' 文字を使用します。

私の解決策は、(すべての子ではなく) ノード レベルで etree.tostring を呼び出してから、正規表現を使用して開始タグと終了タグを削除することでした。

import re
from lxml import etree
t = etree.XML("""<?xml version="1.0" encoding="UTF-8"?>
<review>
  <title>Some testing stuff</title>
    <text>this &amp; that.</text>
</review>""")

e = t.xpath('//text')[0]
xml = etree.tostring(e)
inner = re.match('<[^>]*?>(.*)</[^>]*>\s*$', xml, flags=re.DOTALL).group(1)
print inner

生成:

this &amp; that.

re.DOTALL を使用して、これが改行を含む XML で確実に機能するようにしました。

于 2014-01-16T20:12:37.160 に答える
-2

解決策を見つけました。とても簡単です。

In [31]: t = x.find('text')

In [32]: t
Out[32]: <Element text at 0xa87ed74>

In [33]: list(t.itertext())
Out[33]: ['Some text with ', 'data', ' in it.']

In [34]: ''.join(_)
Out[34]: 'Some text with data in it.'

itertext間違いなくここに行く方法です!

編集://申し訳ありませんが、あなたは子供たちの間のテキストだけが欲しいと思いました、私の悪い

于 2012-06-20T15:21:02.083 に答える