Removing Element from XML by python if contains specific subelement

Viewed 38

I'm trying to remove whole element when it contains certain subelement. I Figured out how to and my code works fine on small sample files, but when i try it over big vile (over 20Mb) it removes just some elements. If I run the code repeatedly over results, for some reason it after several runs removes all the elements I expected in first run, which is just random luck I guess, that the code does not give exception or some error in a first place.

XML sample:

<root>
<element1>
  <sub1>
    <subsub1>
    </subsub1>
  </sub1>
  <sub2>
    <subsub2>
       <foo STyp="foo">
         <bar>
         </bar>
       </foo>
    </subsub2>
  </sub2>
</element1>
</root>

I want to delete the whole element1 if it contains <foo STyp="foo"></foo>.

code:

import xml.etree.ElementTree as ET

tree = ET.parse("my.xml")

for root in tree.iter():
    for element1 in root:
            el = element1.find('foo')
            if el is not None:
                root.remove(element1)

tree.write('1.xml')

I'm just puzzled it works on smaller file

1 Answers

from Element.iter() documentation:

If the tree structure is modified during iteration, the result is undefined.

your problem is you are modifying the tree while iterating its elements. you can use XPath to find grandparents of your desiered elements. then find their parents in it and remove the parent element from grandparent element. (because python xml doesn't allow you to access parents directly.)

for grandparent_of_foo in root.findall(".//foo[@STyp='foo']/../../."):
  for parent_of_foo in grandparent_of_foo.findall("*/foo[@STyp='foo']/../."):
    grandparent_of_foo.remove(parent_of_foo)

for this input:

<root>
<element1>
  <sub1>
    <subsub1>
      <subsub4>
        <foo STyp="foo">
        </foo>
      </subsub4>
    </subsub1>
  </sub1>
  <sub2>
    <subsub2>
       <foo STyp="foo">
         <bar>
         </bar>
       </foo>
    </subsub2>
    <subsub3>
      <bar>
      </bar>
    </subsub3>
  </sub2>
</element1>
</root>

printing the root node would produce this result:

print(ET.tostring(root, encoding='utf-8').decode())
<root>
<element1>
  <sub1>
    <subsub1>
      </subsub1>
  </sub1>
  <sub2>
    <subsub3>
      <bar>
      </bar>
    </subsub3>
  </sub2>
</element1>
</root>

you can check out Xpath syntax here.

Related