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.
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.
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?
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.
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.
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.
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.
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)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With