Understanding XML Structure and Hierarchy
The findall method retrieves a list of XML nodes using a path that must include all parent-level elements (except the root) separated by forward slashes.
Try it: XML Element Trees
How ET.fromstring() turns XML text into a tree of Elements (tag, attributes, .text), and how find(), findall() and iter() walk that tree along a path such as users/user.
How it works
- The parser reads start tags, text and end tags left to right, keeping a stack of open elements; each start tag becomes an Element that is a child of the element on top of the stack.
- Text before an element's first child becomes its .text (whitespace included); an end tag must match the open element or parsing stops with ParseError.
- findall(path) starts at the element it is called on (the root is not part of the path) and follows each path segment down one level of children.
- find(path) returns the first match or None; iter(tag) visits every element in document order, including the root.
- Reading .text, .tag or .get('attr') on each result gives the extracted values; reading an attribute from None raises AttributeError.
Default run (29 steps): ET.fromstring() receives 183 characters. The parser reads tags left to right and keeps a stack of open elements. … findall() returned a list of 2 elements; reading e.get('x') from each gives ['2', '7'].
Simplified: A small, hand-written XML parser for a subset: elements, attributes, text, self-closing tags, comments, CDATA, processing instructions and the five predefined entities plus numeric character references. DOCTYPE/DTDs and namespaces are not supported. Error messages follow Python's expat parser for these cases (column numbers can differ for non-ASCII documents). Paths support tags, *, ., //, [@attr], [@attr='v'], [tag] and [n].
Loading the simulation…
A Path That Finds Nothing
A findall statement can fail quietly. If user elements are nested inside a users element, findall('user') does not search everywhere for user elements. It looks for user elements directly under the current node. Because that location does not exist in this structure, the result is an empty list, and a loop over that list produces no output.
What do you think happens?
Suppose the current root contains users, and users contains user elements. What does findall('user') return?
Reveal answer
The direct user children of the current node
In this structure, user is not a direct child of the current node. The result is therefore an empty list, and a for loop over it never executes.
Following Parent Elements
findall retrieves a list of XML nodes by following a path. Each parent-level element between the current node and the target must appear in the path, separated by forward slashes. The root is the starting point for the search, so its name is omitted from the path. For user elements nested inside users, the path is users/user. This means: begin at the current node, move into users, and collect its user children.
The result stored in lst is a list of matching user nodes. The list contains the user nodes in the order in which they appear in the XML document. The path selects the nodes; it does not yet extract the text inside their children or the attributes attached to them.
Reading Each Matched Node
Extracting Names, IDs, and Attributes
Process the user nodes returned by findall('users/user'). For each node, retrieve the name text, the id text, and the x attribute.
- 1Retrieve the list
findall('users/user')returns the matching user nodes as a list. - 2Visit one node
The for loop assigns one user node at a time to the variable item.
- 3Read child text
item.find('name').text retrieves the text inside the name child element.item.find('id').text does the same for id. - 4Read the attribute
item.get('x')retrieves the value of the x attribute attached directly to the current user node.
The loop processes each user once, extracting child-element text and the current node's attribute value.
lst = stuff.findall('users/user')
for item in lst:
name = item.find('name').text
user_id = item.find('id').text
attribute_x = item.get('x')- 1The first line retrieves the user nodes using the complete path from the current node.
- 2The for statement takes the nodes from lst one at a time.
- 3
item.find('name').text locates the name child and extracts its text content. - 4
item.find('id').text locates the id child and extracts its text content. - 5
item.get('x')retrieves the x attribute from the current user node.
Chuck 001 2
Brent 009 7The loop visits the first user node, extracting Chuck, 001, and 2. It then visits the second user node, extracting Brent, 009, and 7. After the second iteration, the list has no remaining nodes, so the loop ends.
Text and Attributes
A child element's text and a node's attribute are stored in different parts of the XML structure. Text such as Chuck appears inside a child element such as name, so it is accessed with find('name').text. An attribute such as x="2" appears on the opening tag of the user element, so it is accessed with get('x'). Use find for a child element and its text; use get for an attribute on the current node.
| XML location | Access pattern | Value in the source example |
|---|---|---|
| Child element content | item.find('name').text | Chuck |
| Child element content | item.find('id').text | 001 |
| Attribute on the current node | item.get('x') | 2 or 7 |
find followed by text reads child-element content; get reads an attribute on the current node.
Path Mistakes and Repairs
lst = stuff.findall('user')
for item in lst:
process(item)The path searches for direct user children and returns an empty list.
lst = stuff.findall('users/user')
for item in lst:
process(item)The path includes the users parent and returns the matching user nodes.
Common mistake Leaving out the parent element
What it looks likefindall('user')Why it failsThe search looks for user elements directly under the current node, but the user elements are children of users.
Do this insteadUse findall('users/user').Common mistake Assuming findall searches the entire tree from any shortened name
What it looks likefindall('user')Why it failsThe path describes the required hierarchy from the current node; it is not an unrestricted search.
Do this insteadWrite every parent-level element between the current node and the target, separated by forward slashes.
Common mistake Using get for child-element content
What it looks likeitem.get('name')Why it failsname is a child element in the source structure, not an attribute on the user node.
Do this insteadUse item.find('name').text.Common mistake Using find to read an attribute
What it looks likeitem.find('x')Why it failsx is an attribute attached to the user node, not a child element.
Do this insteadUse item.get('x').
Check Your Path
An XML tree has a users element containing user elements. Write the findall path that retrieves the user nodes, then state which method reads a child name element and which method reads the x attribute on the current user node.
Hints
- Include the parent element before the target element.
- Use find followed by text for the name child.
- Use get with the attribute name for x.
Practice Solution
Retrieve user nodes nested inside users, then access each user's name text and x attribute.
- 1Build the path
The parent users and target user are written as users/user.
- 2Iterate
A for loop processes each node in the list one at a time.
- 3Read the two locations
Use
item.find('name').text for the child text anditem.get('x')for the node attribute.
lst = stuff.findall('users/user'); then use a for loop with item.find('name').text and item.get('x') inside it.
The Complete Mental Model
- 1findall returns a list of nodes selected by a parent-to-child path.
- 2The path includes all parent-level elements between the current node and the target, but not the root itself.
- 3A for loop visits each matching node once and preserves the order in which the nodes appear.
- 4Use find followed by text for child-element content and get for attributes on the current node.
- 5An omitted parent can produce an empty list, causing the loop to run without output.
Key Takeaways
- Describe the XML hierarchy from the current node to the target when writing a findall path.
- Use users/user rather than user when user elements are nested inside users.
- Process the returned list with a for loop, one node at a time.
- Read child text with
find(...).text and attributes withget(...). - Treat an empty list and a loop with no output as possible signs of an incomplete path.