How do I insert an XML fragment into another XML tree using Python and lxml?

I have a fragment of XML (the output from an XSL transform using lxml.etree), which I want to add to another XML tree.  I have
  <newsection>
    <newitem name="fred" attr1="7"/> 
    <newitem name="george" attr1="6"/>
  </newsection>

Open in new window

and
<oldparent>
  <oldsection>
    <oldchild>
      <oldgc name="sally"/>
    </oldchild>
  </oldsection>
  <othersect>
    <otherch name="alice"/>
  </othersect>
</oldparent>

Open in new window

Essentially, I need to insert the first fragment within the second so that "<newsection" is at the same level as "<oldsection>" and "<othersect>".  the location doesn't matter.

So far, I have tried inserting the first fragment entry by entry, but it ends up in the wrong order.  For example, using
#!/usr/bin/env python

import lxml.etree as ET

sd = ET.parse('file_containing_first_fragment.xml')
ft = ET.parse('extract_first_fragment.xsl')
transform = ET.XSLT(ft)
rx = transform(sd)
# "rx" now contains just the first fragment

st = ET.parse('second_fragment.xml')
root = st.getroot()
for e in rx.getiterator():
	root.append(e)
print(ET.tostring(st))

Open in new window

This does insert the first fragment, but the ordering is wrong - I get the "<newsection>" open and close before any of the "<newitem>" entries:
<oldparent>
  <oldsection>
    <oldchild>
      <oldgc name="sally"/>
    </oldchild>
  </oldsection>
  <othersect>
    <otherch name="alice"/>
  </othersect>
<newsection>
       </newsection>
<newitem name="fred" attr1="7"/> 
    <newitem name="george" attr1="6"/>
</oldparent>

Open in new window

How can I do this and have that "<newsection>" fragment in the right order?
LVL 20
simon3270Asked:
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

peprCommented:
The following code is with the standard xml.etree.ElementTree, but it should be the same with the lxml:
#!python3

import xml.etree.ElementTree as ET

rx = ET.fromstring('''
<newsection>
  <newitem name="fred" attr1="7"/> 
  <newitem name="george" attr1="6"/>
</newsection>''')
# "rx" now contains just the first fragment

root = ET.fromstring('''
<oldparent>
  <oldsection>
    <oldchild>
      <oldgc name="sally"/>
    </oldchild>
  </oldsection>
  <othersect>
    <otherch name="alice"/>
  </othersect>
</oldparent>''')

root.append(rx)
ET.dump(root)

Open in new window

It prints
<oldparent>
  <oldsection>
    <oldchild>
      <oldgc name="sally" />
    </oldchild>
  </oldsection>
  <othersect>
    <otherch name="alice" />
  </othersect>
<newsection>
  <newitem attr1="7" name="fred" />
  <newitem attr1="6" name="george" />
</newsection></oldparent>

Open in new window

The root stores the element 'oldparent' as the list of its children. You want to append the rx element as another child (as a whole).
0

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
simon3270Author Commented:
Thanks, @pepr, you pointed me in the right direction.

Your example works fine with lxml.etree, but my code didn't - I got:
  File "./tstsd.py", line 13, in <module>
    root.append(rx)
AttributeError: 'lxml.etree._ElementTree' object has no attribute 'append'

My problem turned out to be that ET.parse (which I was using to read the XML file) returns an ElementTree, which doesn't have an "append" attribute. ET.fromstring (which you used) returns an Element, which does have one.

The fix was to make use the string attributes to make ElementTrees into Elements, so I did
    root = ET.parse('second_fragment.xml')
    root = ET.tostring(ET.fromstring(root))
Not pretty, but it worked!
0
peprCommented:
No, no! It is known that ET.parse() returns a tree object. The root element object is obtained from the tree object via calling its .getroot() method -- as you did in your code for example here:
...
st = ET.parse('second_fragment.xml')
root = st.getroot()

Open in new window

0
simon3270Author Commented:
Aha, even easier!  And certainly prettier. Many thanks.
0
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
Python

From novice to tech pro — start learning today.

Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.