Parsing XML with ElementTree to get List of Tuples

Viewed 44

I am new to Python and trying to parse an XML document using Element-Tree (with xpaths) and trying to produce a list of Tuples as output.

I need to generate 2 types of output depending on a simple Python variable called norm which will hold either "True" or "False".

Expected Output 1 (when norm = True):

[('Liechtenstein', 1, 2008, 'Residence', 'Fern Road', 'NY', 'Office', 'Camac Street', 'DE'),
 ('Singapore', 4, 2011, 'Residence', 'Lansdown Road', 'CA', 'Office', 'Lord Street', 'WA'),
 ('Panama', 68, 2011, 'Residence', 'Tobin Road', 'MI', 'Office', 'Broad Street', 'MD')
]

Expected Output 2 (when norm = False):

[('Liechtenstein', 1, 2008, 'Residence', 'Fern Road', 'NY'), ('Liechtenstein', 1, 2008, 'Office', 'Camac Street', 'DE'),
 ('Singapore', 4, 2011, 'Residence', 'Lansdown Road', 'CA'), (Singapore', 4, 2011, 'Office', 'Lord Street', 'WA'),
 ('Panama', 68, 2011, 'Residence', 'Tobin Road', 'MI'), ('Panama', 68, 2011, 'Office', 'Broad Street', 'MD')
]

I have the following xml:

<?xml version="1.0"?>
<data>
    <country name="Liechtenstein">
        <rank>1</rank>
        <year>2008</year>
        <gdppc>141100</gdppc>
        <rank2><rank>New rank1</rank></rank2>
        <Addresses>
            <Address type="Residence">
                <Street>Fern Road</Street>
                <Block>A</Block>
                <City>NY</City>
            </Address>
            <Address type="Office">
                <Street>Camac Street</Street>
                <Block>B2</Block>
                <City>DE</City>
            </Address>
        </Addresses>
        <Street>Invalid</Street>
        <neighbor name="Austria" direction="E"/>
        <neighbor name="Switzerland" direction="W"/>
    </country>
    <country name="Singapore">
        <rank>4</rank>
        <year>2011</year>
        <gdppc>59900</gdppc>
        <rank2><rank>New rank2</rank></rank2>
        <Addresses>
            <Address type="Residence">
                <Street>Lansdown Road</Street>
                <Block>K</Block>
                <City>CA</City>
            </Address>
            <Address type="Office">
                <Street>Lord Street</Street>
                <Block>L32</Block>
                <City>WA</City>
            </Address>
        </Addresses>
        <Street>Invalid</Street>
        <neighbor name="Malaysia" direction="N"/>
    </country>
    <country name="Panama">
        <rank>68</rank>
        <year>2011</year>
        <gdppc>13600</gdppc>
        <rank2><rank>New rank3</rank></rank2>
        <Addresses>
            <Address type="Residence">
                <Street>Tobin Road</Street>
                <Block>T</Block>
                <City>MI</City>
            </Address>
            <Address type="Office">
                <Street>Broad Street</Street>
                <Block>B7</Block>
                <City>MD</City>
            </Address>
        </Addresses>
        <Street>Invalid</Street>
        <neighbor name="Costa Rica" direction="W"/>
        <neighbor name="Colombia" direction="E"/>
    </country>
</data>

Please note that, the "Street" value should be extracted only from the specified hierarchy i.e. - Country -> Addresses -> Address -> Street, since there is also another "Street" node directly under "Country" (with value as "Invalid") which is not required.

This is what I have tried so far: I could get Output-1 to work, but struggling with Output-2:

import xml.etree.cElementTree as ET

lst = []
myTree = ET.parse(r"D:\test.xml")
myRoot = myTree.getroot()
all_vals = myRoot.findall("./country")

norm = "True"
super_lst = []
main_lst = []
sub_lst = []
dict = [{"path": "Street"}, {"path": "City"}]
final_lst = []
for c in all_vals:
    val = c.find('./rank').text
    super_lst.append(val)
    for sub in c.iterfind("./Addresses/Address"):
        for path in dict:
            sub_lst.append(sub.find(path["path"]).text)
    super_lst = super_lst + sub_lst
    final_lst.append(tuple(super_lst))
    sub_lst.clear()
    super_lst.clear()
print(final_lst)

I want to use XPaths (since I plan on passing the xpath values as inputs to the program). Can anyone please help me.

Thanks

1 Answers

Consider list comprehensions, one of which uses a merge tuple and other a tuple build.

if norm == True:
    all_vals = [    
        (cnty.attrib["name"], cnty.find("rank").text, cnty.find("year").text) +
        sum(
            [(addr.find("Street").text, addr.find("Block").text, addr.find("City").text)
            for addr in cnty.find(".//Addresses")], 
            ()
        )
        for cnty in myRoot.findall("country")    
    ]
    all_vals
    # [('Liechtenstein', '1', '2008', 'Fern Road', 'A', 'NY', 'Camac Street', 'B2', 'DE'),
    #  ('Singapore', '4', '2011', 'Lansdown Road', 'K', 'CA', 'Lord Street', 'L32',  'WA'),
    #  ('Panama', '68', '2011', 'Tobin Road', 'T', 'MI', 'Broad Street', 'B7', 'MD')]        

if norm == False:
    all_vals = [
        (cnty.attrib["name"], cnty.find("rank").text, cnty.find("year").text,
         addr.find("Street").text, addr.find("Block").text, addr.find("City").text)
          for cnty in myRoot.findall("country")
          for addr in cnty.find(".//Addresses")
    ]
    all_vals
    # [('Liechtenstein', '1', '2008', 'Fern Road', 'A', 'NY'),
    #  ('Liechtenstein', '1', '2008', 'Camac Street', 'B2', 'DE'),
    #  ('Singapore', '4', '2011', 'Lansdown Road', 'K', 'CA'),
    #  ('Singapore', '4', '2011', 'Lord Street', 'L32', 'WA'),
    #  ('Panama', '68', '2011', 'Tobin Road', 'T', 'MI'),
    #  ('Panama', '68', '2011', 'Broad Street', 'B7', 'MD')]
Related