Parsing XML
XML Tree Structure and Elements
XML is a hierarchical data format with inherited structure, best represented as a tree. The xml.etree.ElementTree module provides two key classes: ElementTree represents an entire XML document as a tree, while Element represents individual nodes within that tree. File-level read/write operations typically occur at the ElementTree level, whereas interactions with individual XML elements and their children happen at the Element level.
The following XML document serves as our example data through out this section:
You can load this data by reading from a file:
The element tag, attribute names, and attribute values can be either
bytes or strings.
*tag* is the element name. *attrib* is an optional dictionary containing
element attributes. *extra* are additional element attributes given as
keyword arguments.
Example form:
<tag attrib>text<child/>...</tag>tail
"""
# The element's name
tag = None
# Dictionary of the element's attributes
attrib = None
# Text before first subelement. Either a string or None
text = None
# Text after this element's end tag, before next sibling's start tag
tail = None
def __init__(self, tag, attrib={}, **extra):
if not isinstance(attrib, dict):
raise TypeError("attrib must be dict, not %s" % (
attrib.__class__.__name__,))
attrib = attrib.copy()
attrib.update(extra)
self.tag = tag
self.attrib = attrib
self._children = []
def makeelement(self, tag, attrib):
"""Create a new element with the same type."""
return self.__class__(tag, attrib)
def copy(self):
"""Return a shallow copy of current element."""
elem = self.makeelement(self.tag, self.attrib)
elem.text = self.text
elem.tail = self.tail
elem[:] = self
return elem
def __len__(self):
return len(self._children)
def __getitem__(self, index):
return self._children[index]
def __setitem__(self, index, element):
self._children[index] = element
def __delitem__(self, index):
del self._children[index]
def append(self, subelement):
"""Add *subelement* to the end of this element."""
self._assert_is_element(subelement)
self._children.append(subelement)
def extend(self, elements):
"""Append multiple subelements from a sequence."""
for element in elements:
self._assert_is_element(element)
self._children.extend(elements)
def insert(self, index, subelement):
"""Insert *subelement* at position *index*."""
self._assert_is_element(subelement)
self._children.insert(index, subelement)
def remove(self, subelement):
"""Remove matching subelement by identity."""
self._children.remove(subelement)
def find(self, path, namespaces=None):
"""Find first matching element by tag name or path."""
return ElementPath.find(self, path, namespaces)
def findtext(self, path, default=None, namespaces=None):
"""Find text for first matching element."""
return ElementPath.findtext(self, path, default, namespaces)
def findall(self, path, namespaces=None):
"""Find all matching subelements by tag name or path."""
return ElementPath.findall(self, path, namespaces)
def iterfind(self, path, namespaces=None):
"""Find all matching subelements and return an iterator."""
return ElementPath.iterfind(self, path, namespaces)
def clear(self):
"""Reset element - removes all subelements and attributes."""
self.attrib.clear()
self._children = []
self.text = self.tail = None
def get(self, key, default=None):
"""Get element attribute value."""
return self.attrib.get(key, default)
def set(self, key, value):
"""Set element attribute."""
self.attrib[key] = value
def keys(self):
"""Get list of attribute names."""
return self.attrib.keys()
def items(self):
"""Get element attributes as (name, value) tuples."""
return self.attrib.items()
def iter(self, tag=None):
"""Create tree iterator yielding all matching elements."""
if tag == "*":
tag = None
if tag is None or self.tag == tag:
yield self
for e in self._children:
yield from e.iter(tag)
def itertext(self):
"""Create text iterator yielding all inner text."""
tag = self.tag
if not isinstance(tag, str) and tag is not None:
return
if self.text:
yield self.text
for e in self:
yield from e.itertext()
if e.tail:
yield e.tail
</div>Since every node has access to these methods, and the parsing process provides us with the root node, we can leverage these methods to manipulate XML files.
**a. Iterating Through All XML Content**
<div>```
from xml.etree import ElementTree as ET
# Parse XML file directly
tree = ET.parse("country_data.xml")
# Get the root node
root = tree.getroot()
# Root tag
print(root.tag)
# Iterate through second level
for child in root:
print(child.tag, child.attrib)
# Iterate through third level
for sub in child:
print(sub.tag, sub.text)
Parse XML file
tree = ET.parse("country_data.xml")
Get the root node
root = tree.getroot()
Root tag
print(root.tag)
Find all 'year' nodes
for node in root.iter('year'): print(node.tag, node.text)
</div>**c. Modifying Node Content**
Modifications occur in memory and don't affect the original file. To persist changes, you must write the modified content back to a file.
Method 1: Parse string, modify, save
<div>```
from xml.etree import ElementTree as ET
# Parse from string
str_xml = open('country_data.xml', 'r').read()
root = ET.XML(str_xml)
# Operations
print(root.tag)
# Loop through all year nodes
for node in root.iter('year'):
new_year = int(node.text) + 1
node.text = str(new_year)
# Set attributes
node.set('name', 'alex')
node.set('age', '18')
# Delete attribute
del node.attrib['name']
# Save file
tree = ET.ElementTree(root)
tree.write("modified.xml", encoding='utf-8')
Parse file directly
tree = ET.parse("country_data.xml") root = tree.getroot()
Operations
print(root.tag)
Loop through all year nodes
for node in root.iter('year'): new_year = int(node.text) + 1 node.text = str(new_year)
# Set attributes
node.set('name', 'alex')
node.set('age', '18')
# Delete attribute
del node.attrib['name']
Save file
tree.write("modified.xml", encoding='utf-8')
</div>**d. Deleting Nodes**
Method 1: Parse string, delete, save
<div>```
from xml.etree import ElementTree as ET
# Parse from string
str_xml = open('country_data.xml', 'r').read()
root = ET.XML(str_xml)
# Operations
print(root.tag)
# Iterate through all country nodes
for country in root.findall('country'):
rank = int(country.find('rank').text)
if rank > 50:
root.remove(country)
# Save file
tree = ET.ElementTree(root)
tree.write("filtered.xml", encoding='utf-8')
Parse file
tree = ET.parse("country_data.xml") root = tree.getroot()
Operations
print(root.tag)
Iterate through all country nodes
for country in root.findall('country'): rank = int(country.find('rank').text)
if rank > 50:
root.remove(country)
Save file
tree.write("filtered.xml", encoding='utf-8')
</div>#### Creating XML Documents
Method 1: Using Element constrcutor
<div>```
from xml.etree import ElementTree as ET
# Create root node
root = ET.Element("family")
# Create first child
son1 = ET.Element('son', {'name': 'child1'})
# Create second child
son2 = ET.Element('son', {"name": "child2"})
# Create grandchildren for first child
grandson1 = ET.Element('grandson', {'name': 'child11'})
grandson2 = ET.Element('grandson', {'name': 'child12'})
son1.append(grandson1)
son1.append(grandson2)
# Add children to root
root.append(son1)
root.append(son2)
tree = ET.ElementTree(root)
tree.write('output.xml', encoding='utf-8', short_empty_elements=False)
Create root node
root = ET.Element("family")
Create children using makeelement
son1 = root.makeelement('son', {'name': 'child1'}) son2 = root.makeelement('son', {"name": 'child2'})
Create grandchildren
grandson1 = son1.makeelement('grandson', {'name': 'child11'}) grandson2 = son1.makeelement('grandson', {'name': 'child12'})
son1.append(grandson1) son1.append(grandson2)
Add children to root
root.append(son1) root.append(son2)
tree = ET.ElementTree(root) tree.write('output.xml', encoding='utf-8', short_empty_elements=False)
</div>Method 3: Using SubElement
<div>```
from xml.etree import ElementTree as ET
# Create root node
root = ET.Element("family")
# Create children using SubElement
son1 = ET.SubElement(root, "son", attrib={'name': 'child1'})
son2 = ET.SubElement(root, "son", attrib={"name": "child2"})
# Create grandchild for first child
grandson1 = ET.SubElement(son1, "age", attrib={'name': 'child11'})
grandson1.text = 'value'
tree = ET.ElementTree(root)
tree.write("test.xml", encoding="utf-8", xml_declaration=True, short_empty_elements=False)
def prettify(elem): """Convert element to string with indentation.""" rough_string = ET.tostring(elem, 'utf-8') reparsed = minidom.parseString(rough_string) return reparsed.toprettyxml(indent="\t")
Create root node
root = ET.Element("family")
Create children
son1 = root.makeelement('son', {'name': 'child1'}) son2 = root.makeelement('son', {"name": 'child2'})
Create grandchildren
grandson1 = son1.makeelement('grandson', {'name': 'child11'}) grandson2 = son1.makeelement('grandson', {'name': 'child12'})
son1.append(grandson1) son1.append(grandson2)
Add children to root
root.append(son1) root.append(son2)
Write with formatting
raw_str = prettify(root) f = open("formatted.xml", 'w', encoding='utf-8') f.write(raw_str) f.close()
</div>