使用elementTree和.remove()

时间:2016-05-20 00:01:41

标签: python xml elementtree xml.etree

您好Stackoverflow社区,

我很感激使用Python和elementTree库调整XML文件的一些指导。

在某些背景下,我不是学生并且在工业界工作。我希望通过自动化这些更改来节省大量的手动工作,通常我会用C ++这样的语言完成这项工作,我更熟悉。但是,在我的小组中使用Python是一种推动,因此我将其用作功能和学习练习。

因此,解决方案指导是有帮助的,但如有可能,请您更正我对术语和理解的使用吗?我不仅仅想让代码工作,而是要知道我对其工作方式的理解是正确的。

问题本身:

目标:从XML文件中删除子元素“weight”。

使用xml代码(我们只能说它叫做“example.xml”):

<XML_level_1 created="2014-08-19 16:55:02" userID="User@company">
<XML_level_2 manufacturer="company" number="store-25235">
  <padUnits value="mm" />
  <partDescription value="Part description explained here" />
  <weight value="5.2" />
</XML_level_2>
</XML_level_1>

到目前为止,我有以下代码:

from xml.etree import ElementTree

current_xml_tree = ElementTree.parse(filename_path) # Path to example.xml

current_xml_root = current_xml_tree.getroot()
current_xml_level_2_node = current_xml_root.findall('XML_level_2')

# Extract "weight" value for later use
for weight_value_elem in current_xml_root.iter('weight'):
    weight_value = weight_value_elem.get('value')

# Remove weight sub-element from XML
# -------------------------------------

# Get all nodes entitled 'weight' from element
weight_nodes = current_xml_root.findall('weight')
print weight_nodes     # result is an empty list

print weight_value_elem    # Location of element 'weight' is listed

for weight_node_loc in current_xml_tree.iter('weight'):
    print "for-loop check : loop has been entered"

    current_xml_tree.getroot().remove(weight_value_elem)
    print "for-loop has been processed"

print "Weight line removed from ", filename_path

# Write changes to XML File:
current_xml_tree.write(filename_path)

我看过很多页面,但是这一页:http://www.cmi.ac.in/~madhavan/courses/prog2-2015/docs/python-3.4.2-docs-html/library/xml.etree.elementtree.html似乎很有帮助,但已达到我被卡住的程度。提前谢谢大家!

问题B:此上下文中节点和元素的关系是什么?

我来自有限元素背景,其中节点被理解为元素的一部分,定义了创建元素的部分/角落边界。但是,我认为这里的术语使用方式不同,以至于节点不是元素的子集,我是错误的吗?这两个术语是否仍然以类似的方式相关?

4 个答案:

答案 0 :(得分:5)

从树中删除元素,无论它在树中的位置如何,都会因ElementTree API而不必要地复杂化。具体来说,没有元素知道它自己的父元素,所以我们必须“手动”发现这种关系。

from xml.etree import ElementTree
XML = '''
    <XML_level_1 created="2014-08-19 16:55:02" userID="User@company">
    <XML_level_2 manufacturer="company" number="store-25235">
      <padUnits value="mm" />
      <partDescription value="Part description explained here" />
      <weight value="5.2" />
    </XML_level_2>
    </XML_level_1>
'''

# parse the XML into a tree
root = ElementTree.XML(XML)

# Alternatively, parse the XML that lives in 'filename_path'
# tree = ElementTree.parse(filename_path)
# root = tree.getroot()

# Find the parent element of each "weight" element, using XPATH
for parent in root.findall('.//weight/..'):
    # Find each weight element
    for element in parent.findall('weight'):
        # Remove the weight element from its parent element
        parent.remove(element)

print ElementTree.tostring(root)

如果你可以切换到lxml,那么循环就不那么麻烦了:

for weight in tree.findall("//weight"):
  weight.getparent().remove(weight)

至于你的第二个问题,the ElementTree documentation或多或少地使用“节点”与“元素”互换。更具体地说,似乎使用“node”一词来指代“Element”类型的Python对象或这样一个对象所引用的XML元素。

答案 1 :(得分:1)

您的问题是node.remove()仅删除node的直接子元素。在您发布的XML文件中,weight元素不是XML_level_1的直接子元素,而是XML_level_2的直接子元素。此外,ElementTree的实现方式似乎没有从孩子到其父母的链接。

您可以按如下方式更改代码:

from xml.etree import ElementTree

xml_str = '''
    <XML_level_1 created="2014-08-19 16:55:02" userID="User@company">
        <XML_level_2 manufacturer="company" number="store-25235">
            <padUnits value="mm" />
            <partDescription value="Part description explained here" />
            <weight value="5.2" />
        </XML_level_2>
    </XML_level_1>
'''    

root = ElementTree.fromstring(xml_str)

for elem in root.iter():
    for child in list(elem):
        if child.tag == 'weight':
            elem.remove(child)

说明:root.iter()以深度优先顺序遍历整个树,list(elem)列出特定元素的所有子项。然后,您过滤掉名称(标记)weight的元素,从而引用父元素和子元素,因此现在可以删除元素。

图书馆似乎没有特别区分nodeelement,尽管您只能在XML context中找到element这个词。

  

每个XML文档都具有逻辑和物理结构。   在物理上,文档由称为实体的单元组成。一个   实体可以指其他实体将其纳入   文献。文档以“根”或文档实体开头。从逻辑上讲,   该文件由声明,元素,评论,   字符引用和处理指令,所有这些都是   通过显式标记在文档中指出。逻辑和物理   结构必须正确嵌套,如4.3.2 Well-Formed中所述   解析实体。

答案 2 :(得分:0)

要在不断增长的词汇表中再添加一个术语,请考虑XSLT,这是一种专用声明性语言,旨在转换XML文档以满足各种最终用途需求。事实上,XSLT是一个结构良好的XML文件,带有脚本指令!虽然Python的内置xml.etree没有XSLT处理器,但外部lxml(基于libxslt)模块维护着一个XSLT 1.0处理器。更重要的是,XSLT是可移植的,可以被其他语言(Java,PHP,Perl,VB,甚至C ++)甚至专用可执行文件(Saxon, Xalan)和命令行解释器(Bash,PowerShell)使用。

您将在下面注意到,不使用一个循环。在XSLT脚本中,Identity Transform按原样复制整个文档,空模板匹配到weight(无论它位于何处)将其删除。

import lxml.etree as ET

xml_str = '''
    <XML_level_1 created="2014-08-19 16:55:02" userID="User@company">
        <XML_level_2 manufacturer="company" number="store-25235">
            <padUnits value="mm" />
            <partDescription value="Part description explained here" />
            <weight value="5.2" />
        </XML_level_2>
    </XML_level_1>
'''
dom = ET.fromstring(xml_str)

xslt_str = '''
    <xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:output version="1.0" encoding="UTF-8" indent="yes" />
    <xsl:strip-space elements="*"/> 

      <!-- Identity Transform -->
      <xsl:template match="@*|node()">
        <xsl:copy>
          <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
      </xsl:template>    

      <!-- Empty Template -->
      <xsl:template match="weight"/>    
    </xsl:transform>
'''
xslt = ET.fromstring(xslt_str)

transform = ET.XSLT(xslt)                          # INITIALIZES TRANSFORMER
newdom = transform(dom)                            # RUNS TRANSFORMATION ON SOURCE XML
tree_out = ET.tostring(newdom, pretty_print=True)  # CONVERTS TREE OBJECT TO STRING
print(tree_out.decode("utf-8"))

答案 3 :(得分:0)

如果您知道您只有一个weight标签实例,则可以避免循环的麻烦,只需查找父元素和子元素,然后删除该子元素即可,例如:

xml_root = ElementTree.parse(filename_path).getroot() # Path to example.xml
parent_element = xml_root.find('./XML_level_2')
weight_element = xml_root.find('./XML_level_2/weight')
parent_element.remove(weight_element)