xml.etree.ElementTree insert into child of child node creating infinity loop Python

Viewed 543

I am attempting to insert an element at a specific child node of a child node in a large XML document (~2000 lines) below is my code:

import xml.etree.cElementTree as ET
class Kapow_commands:
    tree = ET.parse('location/of/xml/file')
    root = tree.getroot()
    seq_id = 39

    def __init__(self):
        pass

    def append(self, block):
        node_num=0 
        for node in Kapow_commands.root[13][1]:
            node_num=node_num+1
            if node.get('class') == 'End':
                node.attrib['id'] = str(Kapow_commands.seq_id + 1)
                print(node.attrib)
                print(node_num)
                Kapow_commands.root[13][1].insert(node_num -1, block)


block = ET.Element("test")    

Kapow_commands().append(block)

this code scans the XML file at a specific node for class = 'End' then it adds 1 to that elements id and inserts another element before it. However when I run this it seems to create an infinity loop as it doesn't stop inserting elements at this position. Does anyone have any idea why this might be happening? Or any idea on how to properly insert this element at the desired position?

2 Answers

I had a similar issue.

The solution was to put an initialization of the element you wanted to append inside the loop.

So in your case, I would pass a name as an argument and have the code look like this:

def append(self, blockName): # here comes the name
    node_num=0 
    for node in Kapow_commands.root[13][1]:
        block = ET.Element(name) # and there goes the init
        node_num=node_num+1
        if node.get('class') == 'End':
            node.attrib['id'] = str(Kapow_commands.seq_id + 1)
            print(node.attrib)
            print(node_num)
            Kapow_commands.root[13][1].insert(node_num -1, block)

For my it did the trick. Unfortunately, I don't know the reason behind this magic.

The fix

As mentioned in your comment, inserting a break statement after inserting the new element resolves the issue of an infinite loop.

Why does the code produce an infinite loop

As for why we get an infinite loop, we need to understand what happens when we call a for loop in python. From the docs we see that a for loop creates an iterator over your iterable object, in this case Kapow_commands.root[13][1], that terminates when we break out of it, when the sequence is empty, or when the iterator raises a StopIteration exception. Effectively, if we expand for node in Kapow_commands.root[13][1]: out, we get something along the lines of

iterator = iter(Kapow_commands.root[13][1])
while True:
    try:
        node  = iterator.__next__()
        # Body of for loop
    except StopIteration:
        break

Here's where the magic comes in. Let's call the node with a class of 'End' End Node. If our current node is End Node, we perform Kapow_commands.root[13][1].insert(node_num -1, block). This inserts block into the current position in our iterable, which means that the next item in the iterable becomes End Node once again. In the next iteration of the for loop, iterator.__next__() gives us End Node and we're back where we started, producing an infinite loop.

Related