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标签之间的内容,包括任何标签:Some text with <extradata>data</extradata> in it. It spans <sometag>multiple lines: <tag>one</tag>, <tag>two</tag> or more</sometag>.

现在我使用正则表达式,但它有点乱,我不喜欢这种方法。我倾向于基于 XML 解析器的解决方案。我查看了minidom, etreelxmlBeautifulSoup找不到这种情况的解决方案(整个内容,包括内部标签)。

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()andtostring()函数:

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()接收 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']

得到第二个结果字符串:

>>> 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 中完全有效的字符,即使在属性内部也是如此。

无论如何,我必须承认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 的解决方案,但是我发现当使用他的第二个选项(转换子节点,而不是树的根)时,它不处理实体。

他上面的代码(修改为添加实体):

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 回答