Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Removing XML subelement tags with Python using elementTree and .remove()

I need help adjusting my XML file with Python and the elementTree library.

For some background, I am not a student and work in industry. I hope to save myself a great deal of manual effort by making these changes automated and typically I would have just done this in a language such as C++ that I am more familiar with. However, there is a push to use Python in my group so I am using this as both a functional and learning exercise.

Could you please correct my use of terms and understanding? I do not simply just want the code to work, but to know that my understanding of how it works is correct.

The Problem itself:

Goal: remove the sub-element "weight" from the XML file.

Using the xml code (let's just say it is called "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>

Thus far, I have the following code:

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)

I have read this helpful resource, but have reached a point where I am stuck.

Second question: What is the relation of nodes and elements in this context?

I come from a finite element background, where nodes are understood as part of an element, defining portions / corner boundaries of what creates an element. However, am I wrong in thinking the terminology is used differently here so that nodes are not a subset of elements? Are the two terms still related in a similar way?

like image 489
jbioni Avatar asked May 20 '16 00:05

jbioni


People also ask

What is the role of parse() function in ElementTree?

Parsing XML fromstring() parses XML from a string directly into an Element , which is the root element of the parsed tree. Other parsing functions may create an ElementTree . Check the documentation to be sure. Not all elements of the XML input will end up as elements of the parsed tree.

What is ElementTree in Python?

ElementTree is an important Python library that allows you to parse and navigate an XML document. Using ElementTree breaks down the XML document in a tree structure that is easy to work with.


3 Answers

Removing an element from a tree, regardless of its location in the tree, is needlessly complicated by the ElementTree API. Specifically, no element knows its own parent, so we have to discover that relationship "by hand."

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)

If you can switch to lxml, the loop is slightly less cumbersome:

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

As to your second question, the ElementTree documentation uses "node" more-or-less interchangably with "element." More specifically, it appears to use the word "node" to refer either to a Python object of type "Element" or the XML element to which such an object refers.

like image 191
Robᵩ Avatar answered Nov 01 '22 18:11

Robᵩ


Your problem is that node.remove() only removes direct subelements of node. In the XML-file you posted the weight element is no direct subelement of XML_level_1 but a direct subelement of XML_level_2. Also the way ElementTree is implemented it seems there is no link from a child to its parent.

You could change your code as follows:

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)

Explanation: root.iter() iterates over the entire tree in depth first order and list(elem) lists all children of a particular element. You then filter out the elements with name (tag) weight and thus have references to both parent and child and thus can now remove an element.

The Library seems to make no particular distinction between node and element although you would only find the term element in an XML context.

Each XML document has both a logical and a physical structure. Physically, the document is composed of units called entities. An entity may refer to other entities to cause their inclusion in the document. A document begins in a "root" or document entity. Logically, the document is composed of declarations, elements, comments, character references, and processing instructions, all of which are indicated in the document by explicit markup. The logical and physical structures must nest properly, as described in 4.3.2 Well-Formed Parsed Entities.

like image 32
Simon Fromme Avatar answered Nov 01 '22 19:11

Simon Fromme


If you know that you only have one instance of the weight tag, you can avoid the pain of looping and just find the parent and child elements, then remove the child, eg:

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)

like image 20
Iain Hunter Avatar answered Nov 01 '22 19:11

Iain Hunter