【问题标题】:Removing XML subelement tags with Python using elementTree and .remove()使用 Python 使用 elementTree 和 .remove() 删除 XML 子元素标签
【发布时间】:2016-09-17 02:01:57
【问题描述】:

我需要帮助使用 Python 和 elementTree 库调整我的 XML 文件。

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

您能否更正我对术语的使用和理解?我不只是想让代码工作,而是要知道我对它的工作原理的理解是正确的。

问题本身:

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

使用 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)

我已经阅读了this helpful resource,但已经到了我被卡住的地步。

第二个问题:这个上下文中节点和元素的关系是什么?

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

【问题讨论】:

  • 感谢您的链接,@Robᵩ! :) 我只是在研究这个,因为术语差异几乎相反。在 Stackoverflow 上看到另一个链接,我发现 this post 引用的帖子将其称为:“与水果和苹果之间的相同。每个 XmlElement 都是 XmlNode,但不是每个 XmlNode 都是 XmlElement。XmlElement 只是一种 XmlNode。其他是 XmlAttribute、XmlText 等"所以基本上,XML 中的元素总是节点的子集?
  • 那篇文章解释了“节点”在 DOM 中的含义。您没有使用 DOM,因此它的解释不适用。据我所知,“节点”在 XML 和 xml.etree.ElementTree API 中都没有技术意义。 ElementTree API 文档确实使用了这个词,但仅在图论意义上:树由通过父子关系连接的节点层次结构组成。在 ElementTree API 中,树代表 XML 文档的结构,每个节点代表一个 XML 元素。
  • 另外,由于您使用的是 Python 2.7,您可能会发现官方 2.7 ElementTree 文档比您链接到的非官方 3.4 版本更有用。官方 2.7:docs.python.org/2/library/xml.etree.elementtree.html 官方 3.5:docs.python.org/3.5/library/xml.etree.elementtree.html
  • 啊,好吧!在图论设置中以这种方式使用它更有意义。为了安全起见,我肯定会在未来术语重叠的地方小心行事。感谢您的解释和链接!

标签: python xml elementtree xml.etree


【解决方案1】:

从树中删除一个元素,无论它在树中的位置如何,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 使用“节点”或多或少与“元素”互换。更具体地说,它似乎使用“节点”一词来指代“元素”类型的 Python 对象或此类对象所指的 XML 元素。

【讨论】:

  • 嗨@Robᵩ首先,感谢您抽出时间这么快回复这种深度和解释。非常感谢,并且绝对可以帮助我学习,而不仅仅是修补问题并继续前进。只是为了确保我正确理解您在 root.findall('.//weight/..') 中使用 XPATH: 为父级显示的方法:* 此代码行采用 './/weight/..'理解为取当前位置,选择父元素,找到并选择'weight',然后是weight的节点。
  • (续) 选择了父元素后,由于 Python 本身并不知道这一点,因此我们必须先为“权重”选择实际元素,然后才能将其删除。我绝对可以看到使用 lxml 库会比这种方式简单得多。对于像 Python 这样的程序来说,这似乎更直观,如果我想要真正的速度,我会使用 C 甚至 Fortran。
  • 另外,再次感谢您在此上下文中解释有关“节点”和“元素”的更多信息!这是你自学时很难获得的信息。
  • lxml 绝对是一个很好的解决方案,我不知道它的存在,感谢您让我发现它
【解决方案2】:

您的问题是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 文档都具有逻辑结构和物理结构。 在物理上,文档由称为实体的单元组成。一个 实体可能会引用其他实体以使其包含在 文档。文档从“根”或文档实体开始。逻辑上, 文档由声明、元素、cmets、 字符引用和处理指令,所有这些都是 通过显式标记在文档中指示。逻辑和物理 结构必须正确嵌套,如 4.3.2 格式良好中所述 已解析的实体。

【讨论】:

  • 嗨@siwica,也感谢您的回答!了解“node.remove()”一次只能使用一个深度级别肯定会有所帮助。这样一来,它就很像推土机,而不是带有数据的挖掘机。您的方法也是最容易快速实施的方法,以使代码在旅途中工作和可读。 :) 但是,我真的很高兴看到你和 Rob 的方法,这样我就可以将它们存储起来以备将来使用。
  • 这是对 XML 结构术语的有用见解。与我最初接触大多数语言的感觉相比,这似乎很直观,但我相信我会在这个周末抽出一些时间阅读“4.3.2 格式良好的解析实体”:)
【解决方案3】:

如果你知道你只有一个权重标签的实例,你可以避免循环的痛苦,只需找到父子元素,然后删除子元素,例如:

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)

【讨论】:

    【解决方案4】:

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

    您会在下面注意到,没有使用一个循环。在 XSLT 脚本中,身份转换按原样复制整个文档,匹配到 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"))
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-04-12
      • 2018-08-27
      • 2011-10-14
      • 2017-09-18
      • 2013-02-17
      • 1970-01-01
      • 1970-01-01
      • 2021-09-01
      相关资源
      最近更新 更多