Concepts / Nested Loops for Hierarchical XML Data

Nested Loops for Hierarchical XML Data

The central pattern is findall() followed by a for loop: findall() collects matching Element objects, and the loop processes each Element individually.

  • Programming
Interactive lab

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

  1. 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.
  2. 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.
  3. 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.
  4. find(path) returns the first match or None; iter(tag) visits every element in document order, including the root.
  5. 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].

Educational simulation

Loading the simulation…

From One Match to Many

When XML contains a collection of records, retrieving only one element is not enough. A users list can contain many user records, a product catalog can contain many items, and a weather feed can contain forecasts for multiple days. The useful pattern is to select all matching elements, receive them as a Python list, and process the list one element at a time.

Key point

The central pattern is findall() followed by a for loop: findall() collects matching Element objects, and the loop processes each Element individually.

users/userselected pathXML treeusers contains userelementsPython listmatching Element objectsuserfirst Elementuseranother Elementfindall() searchescollects matchescontainscontains
How does findall() move from a path in the XML tree to a Python list containing all matching Element objects?

Reading the XML Hierarchy

Before writing the loop, picture the hierarchy. In the running structure, user elements are nested inside a users container. Each user contains id and name child elements and also has an x attribute. The path users/user means: move through the users element, then select the user elements inside it. The selected user elements are the records that the loop will process.

userscontaineruserselected by users/useridtextnametextuserselected by users/userxattributecontainscontainscontainshas attributecontains
Which elements contain which child elements, and how does the selected path identify the nodes to process?
Definition

findall() returns a Python list of all Element objects matching a given path in the XML tree.

Tracing the Outer and Inner Steps

The outer step selects the collection of user elements. The for loop then advances through that collection one item at a time. During one iteration, the loop variable holds a complete Element object for one user. That object is not merely a text value: it represents the user subtree, so code inside the loop can call find() for a child element, get() for an attribute, or access .text for text content. After the work for one user finishes, the loop moves to the next user and repeats.

findall()list of usersfirst userloop variableid, name, xfind(), .text, get()next userloop variableid, name, xfind(), .text, get()first iterationinspectloop continuesinspect
How does control move from the collection of user elements to each child lookup during loop iterations?
Code walkthrough
matches = root.findall("users/user")

for user in matches:
    identifier = user.find("id").text
    name = user.find("name").text
    attribute_value = user.get("x")
    print(identifier, name, attribute_value)
  1. 1
    The path users/user follows the users container to its user children.
  2. 2
    The for loop runs once for each item in matches.
  3. 3
    Inside the loop, the current user Element is queried for id, name, and x.
  4. 4
    When the current iteration ends, the loop continues with the next matching Element.
  • findall() creates a list of matching user Element objects.
  • The loop variable user holds one Element object during each iteration.
  • find() locates a child element, and .text accesses that child's text content.
  • get() accesses the x attribute on the current user element.
  • The print operation processes the current user's values before the loop advances.

Choosing find or findall

MethodResultTypical use in this pattern
find()The first matching Element, or None if no match existsLocate one child such as id or name inside the current user
findall()A Python list containing all matching Element objectsCollect every user under the selected path before looping

The important difference is whether the operation produces one matching element or a list of all matching elements.

find()one Element or NoneElementone matchfindall()all matchesPython listmultiple Element objectsreturnsreturns
What is the difference between receiving one matching Element from find() and a list of matching Elements from findall()?

Use find() when the operation is intended to retrieve one matching element, such as a child of the current user. Use findall() when the operation must collect multiple matches for later iteration. A common mistake is to treat the list returned by findall() as if it were a single Element. Instead, first loop through the list; then use find(), get(), or .text on the individual Element held by the loop variable.

Checking the Search Result

Best practice

Always verify that the path matches the XML hierarchy. After calling findall(), check the length of the returned list with len(lst) to confirm that matches were found. If the list is empty, inspect the path and compare it with the actual nesting of the XML elements.

python

Common Looping Mistakes

  • Common mistake

    Using find() when every matching user is needed

    What it looks like
    user = root.find("users/user")
    Why it fails

    find() retrieves only the first matching element or None if no match exists.

    Do this instead

    Use root.findall("users/user") to receive a list of all matching user elements, then iterate through that list.

  • Common mistake

    Trying to access child data on the whole list

    What it looks like
    matches = root.findall("users/user") followed by matches.find("name")
    Why it fails

    findall() returns a Python list, while find() is called on an individual Element.

    Do this instead

    Loop through matches and call user.find("name") inside the loop.

  • Common mistake

    Using the wrong path for the hierarchy

    What it looks like

    root.findall("user") when the user elements are nested inside users

    Why it fails

    The selected path must match the XML structure being searched.

    Do this instead

    Picture the hierarchy and use the path that travels through users to its user children.

  • Common mistake

    Forgetting that the loop variable is an Element object

    What it looks like

    Treating user as if it were already the name text

    Why it fails

    Each loop item is a complete Element object that still needs a child lookup or attribute access.

    Do this instead

    Use user.find(), user.get(), or the relevant text access inside the loop.

Practice the Pattern

MEDIUM

Assume root refers to an XML tree containing a users element with multiple user children. Write the two-stage pattern that collects every user and then processes each user in a for loop. Inside the loop, retrieve the id child text, the name child text, and the x attribute.

Hints
  • Use the path users/user with findall().
  • The loop variable should represent one user Element.
  • Use find() followed by .text for id and name.
  • Use get() for the x attribute.
Worked example

Tracing a User Collection

Explain what happens when root.findall("users/user") is assigned to matches and a for loop iterates through matches.

  1. 1
    Select the path

    The path users/user directs the search through the users container to the user elements inside it.

  2. 2
    Build the collection

    findall() returns a Python list containing every matching user Element object.

  3. 3
    Start the loop

    The for loop takes the first Element from the list and assigns it to the loop variable.

  4. 4
    Read the current record

    Inside the loop, find() can locate id or name, .text can access child text content, and get() can access the x attribute.

  5. 5
    Continue

    After the current user is processed, the loop repeats for the next Element in the list.

Result

The collection is retrieved once, and each user subtree is processed individually during the loop.

Key Takeaways

  1. 1
    findall() returns a Python list of all Element objects matching a path.
  2. 2
    A for loop processes the returned list one Element at a time.
  3. 3
    Each loop item is a complete Element object that can be queried with find(), get(), and text access.
  4. 4
    find() retrieves one matching Element or None, while findall() retrieves all matching Elements in a list.
  5. 5
    Check the path syntax and use len() on the returned list when verifying that matches were found.

At a glance

  • Use findall() when an XML path may match multiple elements.
  • Iterate through the returned list with a for loop.
  • Inside the loop, the current item is an Element object representing one XML subtree.
  • Use find() for a single child lookup, get() for an attribute, and text access for element content.
  • Verify the path and check the list length when no matches are found.

Related concepts

More concepts starting with N