2012-06-20 62 views
12

我尝试获取开始xml标记和关闭对象之间的整个内容。下面如何在Python中的两个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,lxmlBeautifulSoup,但找不到这种情况下的解决方案(整个内容,包括内部标签)。

回答

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.' 
+0

OP想要获取特定元素的内容。在这种情况下,您的解决方案不起作用,至少不是直接。 II得到一个带有e = t.xpath('// text')[0]'的元素并试过('''.join(map(etree.tostring,e))'),但结果是'其中有数据。 – brandizzi

+0

@brandizzi好点。更新以反映这一点。 – Marcin

+0

需要测试一些更多的案例,但你的最后一个例子对我来说工作得很好(到目前为止)。当使用'find'而不是'xpath'时,它也可以与标准的''etree''一起使用。 – Brutus

-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是definitly去这里的路!

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

+1

我可以用'x.find('text')。get_text()''获得相同的结果。 **但是**这种方法不包括内部标签,我需要它们。 – Brutus

+1

这实际上并没有以任何方式解决OP问题。 *需要*维护内部标签。 – brandizzi

+0

它确实保持内部标签,只有不超过一个级别,请参阅我的编辑,'itertext'获取所有内容 – dav1d

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'>)或cal,则将它们直接呈现为unicode如果它是Elementwith_tail=False就可以避免重复尾部。

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

+0

+1使用'node()' – dusan

+1

这可以写得更紧凑。在这个单行内容中:'''.join(el,if isinstance(el,str)else lxml.etree.tostring(el,with_tail = False)for doc.xpath('/ review/text/node()' ))' –

+0

你可以不加区分地使用'tostring'。 – Marcin

1

那是相当容易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()收到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' 

然后rsplitting它:

>>> 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 solution是真正的通用解决方案。

*实际上,该解决方案也适用于ElementTree,如果您不想依赖lxml,这种方法非常好。唯一的区别是你将无法使用XPath。

+1

文字替换在这里增加了很多脆弱性。如果你的输入文件碰巧有它的属性?一个名称空间前缀? –

+0

我有这种感觉,我不会用这种方法获得很多纯正规表达。由于开始标签至少有一个属性,它也得到了片状。 – Brutus

+0

不需要文本修改。 – Marcin

1

我喜欢@以上马辛的解决方案,但是我发现,使用他的第二个选项时(将一个子节点,而不是树的根),它不处理实体。

他从上述(修改以添加的实体)代码:

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。

相关问题