XML Parsing
ElementTree
Python Programming
XML Namespace
Data Processing

Parsing XML with namespace in Python via 'ElementTree'

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

XML namespaces prevent element name collisions when combining documents from different sources. Python's ElementTree requires you to include the full namespace URI in every tag lookup, which makes code verbose. The practical approach is to define a namespace dictionary and pass it to find() and findall(). Without this, your XPath queries silently return no results because ElementTree treats unqualified names and namespace-qualified names as different tags.

The Problem

xml
1<!-- data.xml -->
2<root xmlns:ns="http://example.com/schema">
3    <ns:item id="1">
4        <ns:name>Widget</ns:name>
5        <ns:price>9.99</ns:price>
6    </ns:item>
7</root>
python
1import xml.etree.ElementTree as ET
2
3tree = ET.parse('data.xml')
4root = tree.getroot()
5
6# This finds NOTHING — namespace is missing
7items = root.findall('item')
8print(items)  # []
9
10# This also finds nothing
11names = root.findall('ns:item/ns:name')
12print(names)  # []

ElementTree does not understand the ns: prefix from the XML file. You must use the full namespace URI.

Method 1: Full Namespace URI (Verbose)

python
1import xml.etree.ElementTree as ET
2
3tree = ET.parse('data.xml')
4root = tree.getroot()
5
6# Use the full URI in curly braces
7items = root.findall('{http://example.com/schema}item')
8for item in items:
9    name = item.find('{http://example.com/schema}name').text
10    price = item.find('{http://example.com/schema}price').text
11    print(f"{name}: ${price}")
12# Widget: $9.99

This works but is hard to read with long URIs.

python
1import xml.etree.ElementTree as ET
2
3tree = ET.parse('data.xml')
4root = tree.getroot()
5
6# Define namespace mapping
7ns = {'ns': 'http://example.com/schema'}
8
9# Use the prefix from your dict, not from the XML file
10items = root.findall('ns:item', ns)
11for item in items:
12    name = item.find('ns:name', ns).text
13    price = item.find('ns:price', ns).text
14    print(f"{name}: ${price}")

The prefix in your dictionary (ns) does not need to match the prefix in the XML file. It is just a local alias for the URI.

Method 3: Default Namespace

Many XML documents use a default namespace (no prefix):

xml
1<!-- data.xml -->
2<root xmlns="http://example.com/schema">
3    <item id="1">
4        <name>Widget</name>
5        <price>9.99</price>
6    </item>
7</root>
python
1import xml.etree.ElementTree as ET
2
3tree = ET.parse('data.xml')
4root = tree.getroot()
5
6# Still need the namespace — even though XML has no prefix
7ns = {'d': 'http://example.com/schema'}
8
9items = root.findall('d:item', ns)
10for item in items:
11    name = item.find('d:name', ns).text
12    print(name)  # Widget

You must assign a prefix (d) in your dictionary for the default namespace.

Extracting Namespaces Automatically

python
1import xml.etree.ElementTree as ET
2
3# Parse and extract namespace from root element
4tree = ET.parse('data.xml')
5root = tree.getroot()
6
7# Root tag includes the namespace: '{http://example.com/schema}root'
8print(root.tag)
9
10# Extract namespace from tag
11namespace = root.tag.split('}')[0] + '}' if '{' in root.tag else ''
12print(namespace)  # {http://example.com/schema}
13
14# Use it dynamically
15items = root.findall(f'{namespace}item')

Using iterparse to Collect All Namespaces

python
1import xml.etree.ElementTree as ET
2
3namespaces = {}
4for event, elem in ET.iterparse('data.xml', events=['start-ns']):
5    prefix, uri = elem
6    namespaces[prefix or 'default'] = uri
7
8print(namespaces)
9# {'ns': 'http://example.com/schema'} or {'default': 'http://example.com/schema'}

Multiple Namespaces

xml
1<root xmlns:a="http://example.com/alpha"
2      xmlns:b="http://example.com/beta">
3    <a:item>
4        <a:name>Widget</a:name>
5        <b:details>
6            <b:weight>1.5</b:weight>
7        </b:details>
8    </a:item>
9</root>
python
1ns = {
2    'a': 'http://example.com/alpha',
3    'b': 'http://example.com/beta'
4}
5
6items = root.findall('a:item', ns)
7for item in items:
8    name = item.find('a:name', ns).text
9    weight = item.find('b:details/b:weight', ns).text
10    print(f"{name}: {weight}kg")

Using lxml (More Powerful Alternative)

lxml has better namespace support and full XPath:

python
1from lxml import etree
2
3tree = etree.parse('data.xml')
4root = tree.getroot()
5
6# lxml automatically handles namespace prefixes from the document
7ns = root.nsmap
8# {'ns': 'http://example.com/schema'} or {None: 'http://example.com/schema'}
9
10# Full XPath support
11items = root.xpath('//ns:item', namespaces={'ns': 'http://example.com/schema'})
12for item in items:
13    name = item.xpath('ns:name/text()', namespaces={'ns': 'http://example.com/schema'})[0]
14    print(name)

Real-World Example: Parsing an Atom Feed

python
1import xml.etree.ElementTree as ET
2import urllib.request
3
4url = 'https://example.com/feed.atom'
5response = urllib.request.urlopen(url)
6tree = ET.parse(response)
7root = tree.getroot()
8
9ns = {'atom': 'http://www.w3.org/2005/Atom'}
10
11for entry in root.findall('atom:entry', ns):
12    title = entry.find('atom:title', ns).text
13    link = entry.find('atom:link', ns).get('href')
14    published = entry.find('atom:published', ns).text
15    print(f"{title}{link} ({published})")

Common Pitfalls

  • Omitting the namespace in find()/findall(): Without the namespace, findall('item') returns an empty list even though the XML clearly has <item> elements. Elements in a namespace and elements without one are different tags.
  • Using the XML prefix instead of your dict prefix: findall('ns:item', ns) only works if your namespace dict has the key 'ns'. The prefix in the dict is what matters, not the prefix in the XML file.
  • Default namespace confusion: <root xmlns="..."> applies the namespace to all child elements without a prefix. You still need to specify the namespace in every find() call.
  • None from find() when element is missing: find() returns None if the element is not found (often due to missing namespace). Accessing .text on None raises AttributeError. Always check: elem = node.find('ns:name', ns); text = elem.text if elem is not None else ''.
  • ET.register_namespace() is for writing, not reading: ET.register_namespace('ns', 'http://...') controls how namespaces appear when serializing XML. It does not affect parsing or find()/findall().

Summary

  • Pass a namespace dict to find() and findall(): root.findall('ns:item', {'ns': 'http://...'})
  • The dict prefix is your local alias — it does not need to match the XML file's prefix
  • Default namespaces (xmlns="...") still require explicit namespace handling
  • Use iterparse with 'start-ns' events to extract namespaces automatically
  • Use lxml instead of ElementTree for full XPath and better namespace handling
  • Always check for None returns from find() before accessing .text

Course illustration
Course illustration

All Rights Reserved.