3

How can I replace an element during iteration in an elementtree? I'm writing a treeprocessor for markdown and would like to wrap an element.

<pre class='inner'>...</pre>

Should become

<div class='wrapper'><pre class='inner'>...</pre></div>

I use getiterator('pre') to find the elements, but I don't know how to wrap it. The trouble point is replacing the found element with the new wrapper, but preserving the existing one as the child.

edA-qa mort-ora-y
  • 30,295
  • 39
  • 137
  • 267

4 Answers4

10

This is a bit of a tricky one. First, you'll need to get the parent element as described in this previous question.

parent_map = dict((c, p) for p in tree.getiterator() for c in p)

If you can get markdown to use lxml, this is a little easier -- I believe that lxml elements know their parents already.

Now, when you get your element from iterating, you can also get the parent:

for elem in list(tree.getiterator('pre')):
    parent = parent_map[elem]
    wrap_elem(parent, elem)

Note that I've turned the iterator from the tree into a list -- We don't want to modify the tree while iterating over it. That could be trouble.

Finally, you're in position to move the element around:

def wrap_elem(parent, elem)
    parent_index = list(parent).index(elem)
    parent.remove(elem)
    new_elem = ET.Element('div', attrib={'class': 'wrapper'})
    parent.insert(parent_index, new_elem)
    new_elem.append(elem)

*Note that I haven't tested this code exactly... let me know if you find any bugs.

Community
  • 1
  • 1
mgilson
  • 300,191
  • 65
  • 633
  • 696
  • This looks like it should work. Though I tried a variant of doing a double iteration instead, iterate over all parents, then inner-loop of children. I didn't want that a second/later pass might not have all the parents set. – edA-qa mort-ora-y Jan 05 '14 at 09:49
1

In my experience, you can use the method below to get what you want:
xml.etree.ElementTree.SubElement( I will just call it ET.Subelement) http://docs.python.org/2/library/xml.etree.elementtree.html#xml.etree.ElementTree.SubElement

Here is the steps:
Before your iteration, you should get the parent element of these iterated element first, store it into variable parent.

Then,
1, store the element <pre class='inner'>...</pre> into a variable temp

2, add a new subelement div into parent:

div = ET.SubElement(parent, 'div')

and set the attrib of div:

div.set('class','wrapper')

3, add the element in step 1 as a subelement of div,

ET.SubElement(div, temp)  

4, delete the element in step 1:

parent.remove(temp)
Lyfing
  • 1,778
  • 1
  • 19
  • 20
0

Something like this works for one:

for i, element in enumerate(parent):
    if is_the_one_you_want_to_replace(element):
        parent.remove(element)
        parent.insert(i, new_element)
        break

Something like this works for many:

replacement_map = {}

for i, element in enumerate(parent):
    if is_an_element_you_want_to_replace(element):
        replacement_map[i] = el_to_remove, el_to_add

for index, (el_to_remove, el_to_add) in replacement_map.items():
    parent.remove(el_to_remove)
    parent.insert(index, el_to_add)
Caveman
  • 2,527
  • 1
  • 17
  • 18
-1

Another solution that works for me, similar to lyfing's. Copy the element into a temp; retag the original element with the wanted outer tag and clear it, then append the copy into the original.

import copy

temp = copy.deepcopy(elem)
elem.tag = "div"
elem.set("class","wrapper")
elem.clear()
elem.append(temp)