如何在Python中获取两个xml标记之间的全部内容?

时间:2012-06-20 15:01:37

标签: python xml xml-parsing lxml

我尝试在一个开放的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解析器的解决方案。我查看了minidometreelxmlBeautifulSoup,但找不到针对此案例的解决方案(整个内容,包括内部代码)。

5 个答案:

答案 0 :(得分: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子类(<class 'lxml.etree._ElementStringResult'>),则直接将它们呈现为unicode,如果它是,则调用etree.tostring一个Elementwith_tail=False避免重复尾部。

如果它们存在,您可能需要处理其他节点类型。

答案 1 :(得分: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.'

答案 2 :(得分:1)

使用parse()tostring()函数lxml *相当容易:

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中完全有效的char,甚至在属性内部。

无论如何,我必须承认MattH solution是真正的一般解决方案。

*实际上这个解决方案也适用于ElementTree,如果您不想依赖于lxml,那就太棒了。唯一的区别是你将无法使用XPath。

答案 3 :(得分: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.

裸露/未转义'&amp;'字符而不是适当的实体('&amp; amp;')。

我的解决方案是在节点级别(而不是所有子节点)上调用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。

答案 4 :(得分:-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绝对是去这里的方式!

编辑://对不起我以为你只想要孩子之间的文字,我的不好