python lxml 使用 iterparse 编辑并输出 xml

4 投票
3 回答
3689 浏览
提问于 2025-04-17 22:54

我最近在玩lxml这个库,可能我理解得不太对或者漏掉了什么,但我就是搞不清楚怎么在抓到某个xpath后编辑文件,然后把它写回到xml里,同时一个一个元素地解析。

比如说,我们有这样一个xml作为例子:

<xml>
   <items>
      <pie>cherry</pie>
      <pie>apple</pie>
      <pie>chocolate</pie>
  </items>
</xml>

我想在解析的时候,当我找到"/xml/items/pie"这个xpath时,想在pie前面加一个元素,这样就变成这样:

<xml>
   <items>
      <item id="1"><pie>cherry</pie></item>
      <item id="2"><pie>apple</pie></item>
      <item id="3"><pie>chocolate</pie></item>
  </items>
</xml>

这个输出需要在我每次遇到标签并在特定的xpath处编辑xml时,一行一行地写入文件。我是可以通过硬编码某些部分来打印起始标签、文本、属性(如果有的话)和结束标签,但这样会显得很乱,如果能有办法避免那样就好了。

这是我猜的代码:

from lxml import etree

path=[]
count=0

context=etree.iterparse(file,events=('start','end'))
for event, element in context:
    if event=='start':
       path.append(element.tag)
       if /'+'/'.join(path)=='/xml/items/pie':
          itemnode=etree.Element('item',id=str(count))
          itemnode.text=""
          element.addprevious(itemnode)#Not the right way to do it of course
          #write/print out xml here.
    else:
        element.clear()
        path.pop()

补充:另外,我需要处理比较大的文件,所以我必须使用iterparse。

3 个回答

0

我建议你使用一个XSLT模板,因为这似乎更适合这个任务。一开始,XSLT可能有点难懂,直到你习惯它为止。如果你只是想从XML生成一些输出,那么XSLT是个很不错的工具。

1

有一种更简单的方法来进行你需要的修改:

  • 遍历 pie 元素
  • 创建一个 item 元素
  • 使用 replace() 方法把 pie 元素替换成 item

replace(self, old_element, new_element)

用第二个参数传入的元素替换掉一个子元素。


from lxml import etree
from lxml.etree import XMLParser, Element

data = """<xml>
   <items>
      <pie>cherry</pie>
      <pie>apple</pie>
      <pie>chocolate</pie>
  </items>
</xml>"""


tree = etree.fromstring(data, parser=XMLParser())
items = tree.find('.//items')
for index, pie in enumerate(items.xpath('.//pie'), start=1):
    item = Element('item', {'id': str(index)})
    items.replace(pie, item)
    item.append(pie)

print etree.tostring(tree, pretty_print=True)

输出:

<xml>
   <items>
      <item id="1"><pie>cherry</pie></item>
      <item id="2"><pie>apple</pie></item>
      <item id="3"><pie>chocolate</pie></item>
   </items>
</xml>
2

这里有一个使用 iterparse() 的解决方案。这个方法的思路是捕捉所有标签的“开始”事件,记住父标签(items),然后对于每一个 pie 标签,创建一个 item 标签,并把这个派放进去:

from StringIO import StringIO
from lxml import etree
from lxml.etree import Element

data = """<xml>
   <items>
      <pie>cherry</pie>
      <pie>apple</pie>
      <pie>chocolate</pie>
  </items>
</xml>"""

stream = StringIO(data)
context = etree.iterparse(stream, events=("start", ))

for action, elem in context:
    if elem.tag == 'items':
        items = elem
        index = 1
    elif elem.tag == 'pie':
        item = Element('item', {'id': str(index)})
        items.replace(elem, item)
        item.append(elem)
        index += 1

print etree.tostring(context.root)

输出结果是:

<xml>
   <items>
      <item id="1"><pie>cherry</pie></item>
      <item id="2"><pie>apple</pie></item>
      <item id="3"><pie>chocolate</pie></item>
   </items>
</xml>

撰写回答