我尝试在一个开放的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
,etree
,lxml
和BeautifulSoup
,但找不到针对此案例的解决方案(整个内容,包括内部代码)。
答案 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
一个Element
,with_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 & 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 & 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 & 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
绝对是去这里的方式!
编辑://对不起我以为你只想要孩子之间的文字,我的不好