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

如何在Python中的两个xml标记之间获取整个内容?,python,xml,xml-parsing,lxml,Python,Xml,Xml Parsing,Lxml,我尝试在开始的xml标记和结束的xml标记之间获取整个内容 在像下面的标题这样的直格中获取内容很容易,但是如果使用混合内容并且我想保留内部标记,如何在标记之间获取整个内容 <?xml version="1.0" encoding="UTF-8"?> <review> <title>Some testing stuff</title> <text sometimes="attribute">Some text with <e

我尝试在开始的xml标记和结束的xml标记之间获取整个内容

在像下面的
标题
这样的直格中获取内容很容易,但是如果使用混合内容并且我想保留内部标记,如何在标记之间获取整个内容

<?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
标记之间的内容,包括任何标记:
一些包含数据的文本。它跨越多行:一行、两行或多行。


现在我使用正则表达式,但它有点混乱,我不喜欢这种方法。我倾向于基于XML解析器的解决方案。我查看了
minidom
etree
lxml
BeautifulSoup
,但没有找到解决此问题的方法(整个内容,包括内部标记)。

刚刚找到了解决方案,非常简单:

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.'
[31]中的
:t=x.find('text'))
In[32]:t
出[32]:
在[33]中:列表(t.itertext())
Out[33]:[“某些文本中有”“数据”“,”。]
在[34]中:''.join(u)
Out[34]:“一些包含数据的文本。”
itertext
绝对是这条路


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

这里有一些对我和你的样本都有效的东西:

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()'))
从lxml导入etree
doc=etree.XML(
"""
一些测试材料
一些包含数据的文本。
"""
)
def展平(序号):
r=[]
对于序号中的项目:
如果isinstance(项,(str,unicode)):
r、 附加(unicode(项目))
elif isinstance(项目(etree.\U元素)如下所示:
r、 追加(etree.tostring(项,带\u tail=False))
返回u“”。加入(r)
打印展开(doc.xpath('/review/text/node()'))
收益率:

Some text with <extradata>data</extradata> in it.
一些包含数据的文本。
xpath选择
元素的所有子节点,如果它们是字符串/unicode子类(
),则直接将它们呈现为unicode;如果是
元素,则调用
etree.tostring
with_tail=False
可避免尾部重复


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

这对于*,使用
parse()
tostring()
函数非常容易:

from  lxml.etree import parse, tostring
首先解析文档并获取元素(我使用的是XPath,但您可以使用任何您想要的内容):

tostring()
函数返回元素的文本表示形式:

>>> tostring(element)
'<text>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).split('>', 1)[1]
'Some <text>text</text> with <extradata>data</extradata> in it.</text>\n'
>>>tostring(元素).split('>',1)[1]
'一些包含数据的文本。\n'
然后再拆分它:

>>> tostring(element).split('>', 1)[1].rsplit('</', 1)
['Some <text>text</text> with <extradata>data</extradata> in it.', 'text>\n']
>>tostring(元素).split('>',1)[1].rsplit('\n']
最后得到第一个结果:

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

[6]中的
:e=t.xpath('//text')[0]
在[7]中:(e.text+''.join(map(etree.tostring,e)).strip()
Out[7]:“一些包含数据的文本。”

我喜欢上面@Marcin的解决方案,但是我发现当使用他的第二个选项(转换子节点,而不是树的根)时,它不会处理实体

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

使用裸/未转义的“&”字符而不是正确的实体(“&;”)

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

我使用re.DOTALL来确保它适用于包含换行符的XML。

我想,我可以通过
x.find('text').get_text()获得相同的结果
。但是这种方法排除了内部标记,我需要它们。实际上,这并不能以任何方式解决OP问题。它需要维护内部标记。它确实维护内部标记,只是不超过一个级别,请参见我的编辑,
itertext
get的Everything迭代所有子级,而不仅仅是文本。文本替换是这里增加了很多脆弱性。如果您的输入文件上碰巧有属性?名称空间前缀?我觉得使用这种方法,我不会比纯正则表达式获得太多好处。因为开始标记至少有一个属性,它也会变得脆弱。
replace(“”%element.tag,,-1)
应该可以工作,但我不能使用
.replace('%element.tag',1)
因为有一个或多个属性,所以我必须再次使用regex(或者类似于
内容[content.index('>'):]
)等。这可以写得更简洁。使用这一行:
'.join(el if isinstance(el,str)els lxml.etree.tostring)(el,带_tail=False)用于doc.xpath('/review/text/node()')中的el
你可能只是不加区别地使用
tostring
。@Marcin:当我尝试时,
tostring
抱怨它无法序列化
\u ElementStringResult
OP想要获取特定元素的内容。你的解决方案在这种情况下不起作用,至少不能直接使用。我得到了一个带有
e的元素=t.xpath('//text')[0]
并尝试了它(
'.join(map(etree.tostring,e))
),但结果是
'data in it.
需要在更多的情况下进行测试,但您的最后一个示例对我来说效果很好(到目前为止)。当使用
find
而不是
xpath
时,它也适用于标准的
etree
>>> 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.'
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()
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.'
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.
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.