Repeating XML elements using lxml

Viewed 352

I've got to create an XML document which has to have the same information repeated in different parts. I'm creating some details as an etree.Element and trying to append it to a couple of secondary XML elements

from lxml import etree

top = etree.Element('Primary')
element1 = etree.Element('Secondary')
element2 = etree.Element('Secondary')

details = etree.Element('Details', somevalue='value')

element1.append(details)
element2.append(details)
top.append(element1)
top.append(element2)

print(etree.tostring(top, encoding="unicode", pretty_print=True))

The output I want is;

<Primary>
  <Secondary>
    <Details somevalue="value"/>
  </Secondary>
  <Secondary>
    <Details somevalue="value"/>
  </Secondary>
</Primary>

The output I get is;

<Primary>
  <Secondary/>
  <Secondary>
    <Details somevalue="value"/>
  </Secondary>
</Primary>

lxml seems to treat the details element only being allowed in one place. Is there any way to turn this off?

Thanks!

2 Answers

The following is extracted from the lxml documentation

Note that in the original ElementTree, a single Element object can sit in any number of places in any number of trees, which allows for the same copy operation as with lists. The obvious drawback is that modifications to such an Element will apply to all places where it appears in a tree, which may or may not be intended.

The upside of this difference is that an Element in lxml.etree always has exactly one parent, which can be queried through the getparent() method. This is not supported in the original ElementTree.

So an element in lxml can only have one parent unlike the original element from ElementTree. So its not possible to just append the same element to more than one parent using lxml. However the documentation suggests to handle this new design you should use deep copy to copy the element you want to append to another element if its already assigned somewhere.

This will create a new copy of the element and as such the new copy can be assigned to a different parent element. These are seperate copy's so changing one will not change the other.

from lxml import etree
from copy import deepcopy

top = etree.Element('Primary')
element1 = etree.Element('Secondary')
element2 = etree.Element('Secondary')
details = etree.Element('Details', somevalue='value')
element1.append(details)
element2.append(deepcopy(details))
top.append(element1)
top.append(element2)
print(etree.tostring(top, encoding="unicode", pretty_print=True))

OUTPUT

<Primary>
  <Secondary>
    <Details somevalue="value"/>
  </Secondary>
  <Secondary>
    <Details somevalue="value"/>
  </Secondary>
</Primary>

Updated with example for Jack

So in thie example Jack i set a variable num_secondarys that will create X secondary elements each with a details element and the values in the detail element will incrmement by 1. I create a single element of secondary and detail as a template, then use deepcopy to copy them as new elements.

from lxml import etree
from copy import deepcopy

top = etree.Element('Primary')
secondary = etree.Element('Secondary')
detail = etree.Element('Details', somevalue='value')
num_secondarys = 3
for i in range(1, num_secondarys + 1):
    this_secondary = deepcopy(secondary)
    this_detail = deepcopy(detail)
    this_detail.attrib['somevalue']+=str(i)
    this_secondary.append(this_detail)
    top.append(this_secondary)

print(etree.tostring(top, encoding="unicode", pretty_print=True))

OUTPUT

<Primary>
  <Secondary>
    <Details somevalue="value1"/>
  </Secondary>
  <Secondary>
    <Details somevalue="value2"/>
  </Secondary>
  <Secondary>
    <Details somevalue="value3"/>
  </Secondary>
</Primary>

Below (using ElementTree the builtin python XML library)

import xml.etree.ElementTree as ET

root = ET.Element('Primary')
for x in range(2):
  sec = ET.SubElement(root, 'Secondary')
  details = ET.Element('Details')
  details.attrib['somevalue'] = 'value'
  sec.append(details)
ET.dump(root)

output

<Primary>
   <Secondary>
      <Details somevalue="value" />
   </Secondary>
   <Secondary>
      <Details somevalue="value" />
   </Secondary>
</Primary>
Related