Iterating Through Multiple XML Nodes
findall() is a path-following function, not a global search function. It requires you to specify the exact hierarchy to your target element.
The Empty-List Puzzle
A findall() call can return an empty list even when the requested elements clearly exist in the XML document. The usual reason is not that the elements are missing. It is that the path given to findall() does not match the document's hierarchy. The key idea is simple: findall() follows a path; it does not search globally for every element with a matching name.
What do you think happens?
An XML root named stuff contains users, and users contains two user elements. Which path should findall() use to locate both user elements?
Reveal answer
Answer: users/user
The path begins with the root's child, users, and then descends to user. The root element, stuff, is excluded from the path.
Following the XML Hierarchy
Imagine the XML document as a tree. The root element is at the top. Its children are beneath it, and deeper elements are beneath their own parents. When findall() receives a path, it starts from the element on which the method is called and follows each path segment in order. For a root element named stuff containing users and then user, the path users/user means: first move from stuff to users, then move from users to user. Every parent level between the starting element and the target must appear in the path.
The root element is the starting point, not a path segment. Include all parent-level elements below the root, followed by the target element. For this structure, use users/user rather than stuff/users/user or user.
Constructing a Nested Path
A nested findall() path lists element names in hierarchy order and separates them with a slash. The first name identifies a child of the element on which findall() is called. Each following name identifies the next level down. Therefore, users/user describes two levels below stuff: users first, then user.
Locating Nested User Elements
A document has the hierarchy stuff, then users, then multiple user elements. Determine the path that locates the user elements.
Start at the method's element: The method is called on stuff, so stuff is the starting element and is not included in the path.
Add the intermediate parent: The user elements are inside users, so users must be the first path segment.
Add the target: The target elements are named user, so user follows users after the slash.
The complete path is users/user. It matches the document hierarchy and finds the two user elements.
Tracing Success and Failure
Consider the same XML tree: stuff contains users, and users contains two user elements. Calling findall() with users/user succeeds because the path matches both levels below the root. Calling it with user fails because user is not a direct child of stuff. In that second case, findall() is not looking one level deeper automatically; it is checking for user elements at the direct-child level requested by the path.
| Path | What it asks for | Result |
|---|---|---|
| users/user | Find users, then find user elements inside them | Finds 2 elements |
| user | Find user elements directly inside stuff | Finds 0 elements |
The two paths begin from the same root element but describe different hierarchy levels.
A successful result depends on structural agreement, not just on the target name. Both paths mention user, but only users/user describes where user actually appears.
Diagnosing an Empty Result
Treating findall() as a global search
findall() follows the supplied hierarchy instead of searching the entire XML tree for every matching name.
Fix:
Write the complete path below the root: users/user.Omitting an intermediate parent
The requested user element is not a direct child of the starting element.
Fix:
Include every ancestor between the starting element and the target, except the root element.Including the root in the path
The method already starts at stuff, so the path begins with its child level.
Fix:
Leave out stuff and use users/user.Changing the capitalization of an element name
XML element names are case-sensitive, so User and user are different names.
Fix:
Match the capitalization used in the XML exactly.
- Check the XML hierarchy from the starting element to the target.
- List every parent-level element below the root.
- Place the names in order and separate adjacent levels with a slash.
- Confirm that the root element itself is excluded.
- Check the spelling and capitalization of every element name.
Using the Returned Nodes
Once the complete path matches the XML hierarchy, findall() can locate multiple elements at the final path level. In the source example, users/user locates two user elements. Those returned elements are the nodes that can then be processed one by one through iteration. The important separation is this: the path determines which nodes are selected, and iteration moves through the nodes that were successfully selected.
A root element named catalog contains section, and section contains multiple item elements. What path should be supplied to findall() when it is called on catalog? Explain why the root name is or is not included.
Hints
- Start with the child level immediately below the root.
- Include section before item.
- Do not include catalog because it is the element on which findall() starts.
Before writing a path, trace the nesting visually from the starting element to the target. Then translate each level below the root into one path segment. This makes the path reflect the XML structure instead of relying on the assumption that findall() searches globally.
Path Rules to Remember
- findall() follows a specified XML path; it does not perform a global search.
- Include every parent-level element between the root and the target.
- Exclude the root element because findall() starts from the element on which it is called.
- Use slash-separated element names in hierarchy order, such as users/user.
- If the result is an empty list, check the hierarchy, omitted parents, root inclusion, and capitalization first.
Key Takeaways
- findall() is a path-following function rather than a global element-name search.
- A nested target requires every parent-level element in the path except the root.
- For stuff containing users containing user elements, users/user succeeds while user returns an empty list.
- Element names must match the XML exactly, including capitalization.
- When findall() returns no results, compare the requested path with the actual XML hierarchy.