python lxml append element after another element

28,222

Solution 1

Instead of appending to contentnav, go up to the parent (contentdiv) and insert the new div at a particular index. To find that index, use contentdiv.index(contentnav), which gives the index of contentnav within contentdiv. Adding one to that gives the desired index.

import lxml.etree as ET

content='''\
<div id="contents">
    <div id="content_nav">
        something goes here
    </div>
    <p>
        some contents
    </p>   
</div>
'''
tree = ET.fromstring(content, parser=ET.HTMLParser())
contentnav = tree.find(".//div[@id='content_nav']")
contentdiv = contentnav.getparent()
contentdiv.insert(contentdiv.index(contentnav)+1,
                  ET.XML("<div style='clear: both'></div>"))
print(ET.tostring(tree))

yields

<html><body><div id="contents">
    <div id="content_nav">
        something goes here
    </div>
    <div style="clear: both"/><p>
        some contents
    </p>   
</div></body></html>

Solution 2

Use addprevious and addnext for prepending and appending siblings.

An lxml.etree _Element has two methods: addprevious and addnext for doing exactly what you want.

import lxml.etree as ET

content='''\
<div id="contents">
    <div id="content_nav">
        something goes here
    </div>
    <p>
        some contents
    </p>   
</div>
'''
tree = ET.fromstring(content, parser=ET.HTMLParser())
contentnav = tree.find(".//div[@id='content_nav']")
contentnav.addnext(ET.XML("<div style='clear: both'></div>"))
print(ET.tostring(tree))

Output:

<html><body><div id="contents">
    <div id="content_nav">
        something goes here
    </div><div style="clear: both"/>
    <p>
        some contents
    </p>   
</div>
</body></html>

Solution 3

I believe that a generic function addressing the question "insert an element after another element" might be useful, even if it's just a reformulation of the accepted answer:

def insert_after(element, new_element):
    parent = element.getparent()
    parent.insert(parent.index(element)+1, new_element)

which allows to insert a new_element after an existing element with just

insert_after(element, new_element)
Share:
28,222
Tu Hoang
Author by

Tu Hoang

I am a novice programmer ...

Updated on July 09, 2022

Comments

  • Tu Hoang
    Tu Hoang almost 2 years

    I have the following HTML markup

    <div id="contents">
        <div id="content_nav">
            something goes here
        </div>
        <p>
            some contents
        </p>   
    </div>
    

    To fix some CSS issue, I want to append a div tag <div style="clear:both"></div> after the content_nav div like this

    <div id="contents">
        <div id="content_nav">
            something goes here
        </div>
    
        <div style="clear:both"></div>
    
        <p>
            some contents
        </p>   
    </div>
    

    I am doing it this way:

    import lxml.etree
    
    tree = lxml.etree.fromString(inputString, parser=lxml.etree.HTMLParser())
    
    contentnav = tree.find(".//div[@id='content_nav']")
    contentnav.append(lxml.etree.XML("<div style='clear: both'></div>"))
    

    But that doesn't append the new div right after content_nav div but inside.

    <div id="content_nav">
        something goes here
        <div style="clear:both"></div>
    </div>
    

    Is there any way to add a div in the middle of content_nav div and some p like that inside contents?

    Thanks

  • Tu Hoang
    Tu Hoang over 12 years
    Yeah, I did the same thing after asking the question. :)
  • shrewmouse
    shrewmouse over 6 years
    All that you've done is re-implement element.appendnext().
  • mmj
    mmj over 6 years
    @Shrewmouse I guess you mean element.addnext(). I don't know when it was added to the API but now it is definitively the best solution.
  • pguardiario
    pguardiario almost 5 years
    Should probably be ET.HTML in this case