使用Python的xml.etree查找元素的起始和结束字符偏移量
我有一些XML数据,看起来像这样:
<xml>
The captial of <place pid="1">South Africa</place> is <place>Pretoria</place>.
</xml>
我想提取以下内容:
- 当前在etree中提供的XML元素。
- 文档中开始和结束标签之间的完整纯文本。
- 每个开始元素在纯文本中的位置,以字符偏移量表示。
现在最重要的是(3);etree可以很好地提供(1)。
我找不到直接实现(3)的方法,但我希望通过遍历文档树中的元素,返回很多小字符串,然后重新组合,从而提供(2)和(3)。不过,获取根节点的.text属性只返回根节点和第一个元素之间的文本,比如“首都是”。
使用SAX来实现(1)可能需要重新实现很多已经写过的东西,比如在minidom和etree中。对于这个代码要放入的包,使用lxml不是一个选项。有人能帮忙吗?
5 个回答
(3) 可以通过 XMLParser.CurrentByteIndex 来实现,方法如下:
import xml.etree.ElementTree as ET
class MyTreeBuilder(ET.TreeBuilder):
def start(self, tag, attrs):
print(parser.parser.CurrentByteIndex)
ET.TreeBuilder.start(self, tag, attrs)
builder = MyTreeBuilder()
parser = ET.XMLParser(target=builder)
builder.parser = parser
tree = ET.parse('test.xml', parser=parser)
另外,可以参考 这个回答,它提供了一个SAX的替代方案。不过要注意,字节索引和字符索引是不一样的,在Python中可能没有简单的方法将字节索引转换为字符索引。(也可以查看 这里。)
如果想要获取字符的偏移量而不是字节的偏移量,有一个(虽然不太优雅的)解决办法,就是将字节重新编码为字符。假设实际的编码是utf8:
import xml.etree.ElementTree as ET
class MyTreeBuilder(ET.TreeBuilder):
def start(self, tag, attrs):
print(parser.parser.CurrentByteIndex)
ET.TreeBuilder.start(self, tag, attrs)
builder = MyTreeBuilder()
parser = ET.XMLParser(target=builder)
builder.parser = parser
with open('test.xml', 'rb') as f:
parser.feed(f.read().decode('latin1').encode('utf8'))
你需要关注一下 .tail
属性和 .text
属性:.text
是在开始标签后面直接的文本,而 .tail
是在结束标签后面直接的文本。这会帮你得到你所说的“很多小字符串”。
小提示:你可以使用 etree.iterwalk(elem)
(这个和 etree.iterparse()
做的事情一样,不过是针对已经存在的树)来遍历开始标签和结束标签。想法是这样的:
for event, elem in etree.iterwalk(xml_elem, events=('start', 'end')):
if event == 'start':
# it's a start tag
print 'starting element', elem.tag
print elem.text
elif event == 'end':
# it's an end tag
print 'ending element', elem.tag
if elem is not xml_elem:
# dont' want the text trailing xml_elem
print elem.tail
我想你可以自己完成剩下的部分吧?
注意:.text
和 .tail
可能会是 None
,所以如果你想把它们连接起来,你需要注意这一点(比如可以用 (elem.text or '')
)。
如果你对 sax 有了解(或者有现成的 sax 代码可以用),lxml 允许你 从一个元素或树中生成 sax 事件:
lxml.sax.saxify(elem, handler)
在提取元素中的所有文本时,还有一些其他的东西可以关注:.itertext()
方法,xpath 表达式 .//text()
(lxml 让你可以从 xpath 表达式中返回“智能字符串”:它们可以让你检查这些字符串属于哪个元素等等)。
iterparse()
函数可以在 xml.etree
中找到:
import xml.etree.cElementTree as etree
for event, elem in etree.iterparse(file, events=('start', 'end')):
if event == 'start':
print(elem.tag) # use only tag name and attributes here
elif event == 'end':
# elem children elements, elem.text, elem.tail are available
if elem.text is not None and elem.tail is not None:
print(repr(elem.tail))
另一个选择是重写 start()
、data()
和 end()
方法,这些方法属于 etree.TreeBuilder()
:
from xml.etree.ElementTree import XMLParser, TreeBuilder
class MyTreeBuilder(TreeBuilder):
def start(self, tag, attrs):
print("<%s>" % tag)
return TreeBuilder.start(self, tag, attrs)
def data(self, data):
print(repr(data))
TreeBuilder.data(self, data)
def end(self, tag):
return TreeBuilder.end(self, tag)
text = """<xml>
The captial of <place pid="1">South Africa</place> is <place>Pretoria</place>.
</xml>"""
# ElementTree.fromstring()
parser = XMLParser(target=MyTreeBuilder())
parser.feed(text)
root = parser.close() # return an ordinary Element
输出
<xml>
'\nThe captial of '
<place>
'South Africa'
' is '
<place>
'Pretoria'
'.\n'