Search code examples
swiftxml-parsingswxmlhash

How to deserialize nested element with SWXMLHash?


I have been trying to deserialized a nested XML with SWXMLHash, I was wordering if someone could help me ? I apologize in advance, I'm new to this stuff. Thanks in advance for helping.

here is an extract of the XML :



<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<kml
    xmlns="http://www.opengis.net/kml/2.2"
    xmlns:gx="http://www.google.com/kml/ext/2.2"
    xmlns:xal="urn:oasis:names:tc:ciq:xsdschema:xAL:2.0"
    xmlns:atom="http://www.w3.org/2005/Atom">
    <Document>
        <name>Stations</name>
        <Style id="AVAILABLE">
            <IconStyle>
                <scale>1.0</scale>
                <heading>0.0</heading>
                <Icon>
                    <href>http://maps.google.com/mapfiles/kml/paddle/grn-circle.png</href>
                    <refreshInterval>0.0</refreshInterval>
                    <viewRefreshTime>0.0</viewRefreshTime>
                    <viewBoundScale>0.0</viewBoundScale>
                </Icon>
            </IconStyle>
        </Style>
        <Style id="UNAVAILABLE">
            <IconStyle>
                <scale>1.0</scale>
                <heading>0.0</heading>
                <Icon>
                    <href>http://maps.google.com/mapfiles/kml/paddle/red-circle.png</href>
                    <refreshInterval>0.0</refreshInterval>
                    <viewRefreshTime>0.0</viewRefreshTime>
                    <viewBoundScale>0.0</viewBoundScale>
                </Icon>
            </IconStyle>
        </Style>
        <Placemark>
            <name>Esch-sur-Alzette - Parking souterrain Brill - Place de la Résistance</name>
            <visibility>1</visibility>
            <address>Rue Louis Pasteur, L-4276 Esch-sur-Alzette Luxembourg</address>
            <description>&lt;span&gt;&lt;b&gt;4&lt;/b&gt; connectors with 22kW and Type 2 connector&lt;span&gt;&lt;br/&gt;&lt;span&gt;&lt;b&gt;4&lt;/b&gt; available connectors&lt;span&gt;&lt;br/&gt;&lt;span&gt;&lt;b&gt;0&lt;/b&gt; occupied connectors&lt;span&gt;&lt;br/&gt;</description>
            <styleUrl>#AVAILABLE</styleUrl>
            <ExtendedData>
                <Data name="CPnum">
                    <displayName>Number of chargingpoints</displayName>
                    <value>4</value>
                </Data>
                <Data name="chargingdevice">
                    <displayName>Charging device</displayName>
                    <value>{"id":10644,"name":"CP2500","numberOfConnectors":2,"connectors":[{"id":59985,"name":"CP2500 - 1","maxchspeed":22.08,"connector":1,"description":"AVAILABLE"},{"id":59986,"name":"CP2500 - 2","maxchspeed":22.08,"connector":2,"description":"AVAILABLE"}]}</value>
                </Data>
                <Data name="chargingdevice">
                    <displayName>Charging device</displayName>
                    <value>{"id":10645,"name":"CP2501","numberOfConnectors":2,"connectors":[{"id":59987,"name":"CP2501 - 1","maxchspeed":22.08,"connector":1,"description":"AVAILABLE"},{"id":59988,"name":"CP2501 - 2","maxchspeed":22.08,"connector":2,"description":"AVAILABLE"}]}</value>
                </Data>
            </ExtendedData>
            <Point>
                <altitudeMode>clampToGround</altitudeMode>
                <coordinates>5.97622,49.492728</coordinates>
            </Point>
        </Placemark>

here is the struct and the function I'm using :

`

struct XMLPlacemark: XMLObjectDeserialization {
    var name: String
    var visibility: Int
    var address: String
    var description: String
    var styleUrl: String
    var cpnumber : Int
    var chargingdevice : [String]
    
    static func deserialize(_ node: XMLIndexer) throws -> XMLPlacemark {
        return try XMLPlacemark(
            name: node["name"].value(),
            visibility: node["visibility"].value(),
            address: node["address"].value(),
            description: node["description"].value(),
            styleUrl: node["styleUrl"].value(),
            cpnumber: node["ExtendedData"]["Data"].withAttribute("name", "CPnum")["value"].value(),
            chargingdevice: node["ExtendedData"]["Data"].withAttribute("name","chargingdevice")["value"].value()   
        )
    }
}

func loadtest() {
    let xml = XMLHash.parse(stationXML)
    if let station: [XMLPlacemark] = try? xml["kml"]["Document"]["Placemark"].value() {
        station.forEach {station in
            print("\(station.chargingdevice)")
            
        }
    }
}

Here is the result I get :

["{\"id\":10644,\"name\":\"CP2500\",\"numberOfConnectors\":2,\"connectors\":[{\"id\":59985,\"name\":\"CP2500 - 1\",\"maxchspeed\":22.08,\"connector\":1,\"description\":\"AVAILABLE\"},{\"id\":59986,\"name\":\"CP2500 - 2\",\"maxchspeed\":22.08,\"connector\":2,\"description\":\"AVAILABLE\"}]}"]

I'm missing the other value of the node :

<value>{"id":10645,"name":"CP2501",....

Any idea ?


Solution

  • Okay, so I understand what the issue here. The withAttribute method only returns a single attribute match, not all matches. That's why it is only returning the first match. You can see the implementation here: https://github.com/drmohundro/SWXMLHash/blob/main/Source/XMLIndexer.swift#L159.

    Because you're wanting to filter on these types, you'll need to tweak the implementation slightly. Here is a test I wrote that works the way you're expecting:

        struct XMLPlacemark: XMLObjectDeserialization {
            var name: String
            var visibility: Int
            var address: String
            var description: String
            var styleUrl: String
            var cpnumber : Int
            var chargingdevice : [String]
            
            static func deserialize(_ node: XMLIndexer) throws -> XMLPlacemark {
                var devices: [String] = []
                for dataElem in node["ExtendedData"]["Data"].all {
                    if (dataElem.element?.attribute(by: "name")?.text == "chargingdevice") {
                        devices.append(try dataElem["value"].value())
                    }
                }
                
                return try XMLPlacemark(
                    name: node["name"].value(),
                    visibility: node["visibility"].value(),
                    address: node["address"].value(),
                    description: node["description"].value(),
                    styleUrl: node["styleUrl"].value(),
                    cpnumber: node["ExtendedData"]["Data"].withAttribute("name", "CPnum")["value"].value(),
                    chargingdevice: devices
                )
            }
        }
    
        func testLoading() {
            let xml = XMLHash.parse(xmlWithComplexType)
            do {
                let station: [XMLPlacemark] = try xml["kml"]["Document"]["Placemark"].value()
                station.forEach {station in
                    print("hello")
                    print("\(station.chargingdevice)")
                    
                }
            } catch {
                print("failure? \(error)")
            }
        }