Search code examples
lxmllxml.objectify

String interpolation while inserting multiple elements into xml from inside a for loop


For some reason, I found it difficult to generate an XML file by inserting multiple nodes from inside a for loop. The following does achieve that, but a couple of questions remain.

Given a list of dictionaries

dict_list = [{'x': 'abc', 'y': 'efg'}, \
            {'x': 'hij', 'y': 'klm'}]

I can use objectify() to generate the desired xml:

from lxml import etree, objectify
end_game = etree.XML('<final_root/>')

E = objectify.ElementMaker(annotate=False)
tmp_root = E.entry
for d in dict_list:
    att_values = [val for val in d.values()]    
    doc = tmp_root(   
        x = att_values[0],
        y = att_values[1]
     )    
    end_game.append(doc)
print(etree.tostring(end_game, pretty_print=True).decode())

Output, as desired:

<final_root>
  <entry x="abc" y="efg"/>
  <entry x="hij" y="klm"/>
</final_root>

The problem is that I need to hardwire the attribute names x and y into the loop. Attempting to use string interpolation fails. For instance:

for d in dict_list:
     att_items = [item for item in d.items()] 
        doc = tmp_root(   
            att_items[0][0] = att_items[0][1],
            att_items[1][0] = att_items[1][1]
         )

raises

SyntaxError: expression cannot contain assignment, perhaps you meant "=="?

Same error is raised using f-strings ('f'{att_items[0][0]}' = att_items[0][1]), or format ({}.format(att_items[0][0]) = att_items[0][1]).

So, the obvious question: is there a way to avoid having to manually insert attribute names? Alternatively: Is it possible to duplicate the outcome (and maybe avoid the issue) using lxml.etree instead?


Solution

  • Since etree Elements carry attributes as a dict, you should be able to just pass in the dict when constructing the Element...

    from lxml import etree
    
    end_game = etree.XML('<final_root/>')
    
    dict_list = [{'x': 'abc', 'y': 'efg'},
                 {'x': 'hij', 'y': 'klm'}]
    
    for meta in dict_list:
        end_game.append(etree.Element('item', meta))
    
    print(etree.tostring(end_game, pretty_print=True).decode())
    

    printed output...

    <final_root>
      <item x="abc" y="efg"/>
      <item x="hij" y="klm"/>
    </final_root>