iosxml-parsingalamofireswift5xmlmapper

Parsing image from XML Content


I am using XMLMapper for mapping XML response. Below is a few lines from the response I am getting from API URL,

<feed xmlns:im="http://itunes.apple.com/rss" xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
<id>http://mzstoreservices-int.dslb.apple.com/WebObjects/MZStoreServices.woa/ws/RSS/topsongs/limit=25/xml</id>
<title>iTunes Store: Top Songs</title>
<updated>2021-04-06T19:18:54-07:00</updated>
<link rel="alternate" type="text/html" href="https://itunes.apple.com/WebObjects/MZStore.woa/wa/viewTop?cc=us&id=1&popId=1"/>
<link rel="self" href="http://mzstoreservices-int.dslb.apple.com/WebObjects/MZStoreServices.woa/ws/RSS/topsongs/limit=25/xml"/>
<icon>http://itunes.apple.com/favicon.ico</icon>
<author>
<name>iTunes Store</name>
<uri>http://www.apple.com/itunes/</uri>
</author>
<rights>Copyright 2008 Apple Inc.</rights>
<entry>
<updated>2021-04-06T19:18:54-07:00</updated>
<id im:id="1547525311">https://music.apple.com/us/album/astronaut-in-the-ocean/1547525310?i=1547525311&uo=2</id>
<title>Astronaut In The Ocean - Masked Wolf</title>
<im:name>Astronaut In The Ocean</im:name>
<link rel="alternate" type="text/html" href="https://music.apple.com/us/album/astronaut-in-the-ocean/1547525310?i=1547525311&uo=2"/>
<im:contentType term="Music" label="Music">
<im:contentType term="Track" label="Track"/>
</im:contentType>
<category im:id="18" term="Hip Hop/Rap" scheme="https://music.apple.com/us/genre/music-hip-hop-rap/id18?uo=2" label="Hip-Hop/Rap"/>
<link title="Preview" rel="enclosure" type="audio/x-m4a" href="https://audio-ssl.itunes.apple.com/itunes-assets/AudioPreview124/v4/81/b4/95/81b495ac-0475-928b-8a27-a4fe2682749b/mzaf_17183909416838915149.plus.aac.p.m4a" im:assetType="preview">
<im:duration>30000</im:duration>
</link>
<im:artist href="https://music.apple.com/us/artist/masked-wolf/1313094981?uo=2">Masked Wolf</im:artist>
<im:price amount="1.29000" currency="USD">$1.29</im:price>
<im:image height="55">https://is4-ssl.mzstatic.com/image/thumb/Music124/v4/af/ef/ff/afefff15-b936-3cb7-9101-166f8501e71a/075679793102.jpg/55x55bb.png</im:image>
<im:image height="60">https://is3-ssl.mzstatic.com/image/thumb/Music124/v4/af/ef/ff/afefff15-b936-3cb7-9101-166f8501e71a/075679793102.jpg/60x60bb.png</im:image>
<im:image height="170">https://is1-ssl.mzstatic.com/image/thumb/Music124/v4/af/ef/ff/afefff15-b936-3cb7-9101-166f8501e71a/075679793102.jpg/170x170bb.png</im:image>
<rights>℗ 2021 Elektra Records LLC</rights>
<im:releaseDate label="June 7, 2019">2019-06-07T05:00:00-07:00</im:releaseDate>
<im:collection>
<im:name>Astronaut In The Ocean - Single</im:name>
<link rel="alternate" type="text/html" href="https://music.apple.com/us/album/astronaut-in-the-ocean-single/1547525310?uo=2"/>
<im:contentType term="Music" label="Music">
<im:contentType term="Album" label="Album"/>
</im:contentType>
</im:collection>
<content type="html"><table border="0" width="100%"> <tr> <td> <table border="0" width="100%" cellspacing="0" cellpadding="0"> <tr valign="top" align="left"> <td align="center" width="166" valign="top"> <a href="https://music.apple.com/us/album/astronaut-in-the-ocean/1547525310?i=1547525311&uo=2"><img border="0" alt="Masked Wolf - Astronaut In The Ocean artwork" src="https://is1-ssl.mzstatic.com/image/thumb/Music124/v4/af/ef/ff/afefff15-b936-3cb7-9101-166f8501e71a/075679793102.jpg/100x100bb.png" /></a> </td> <td width="10"><img alt="" width="10" height="1" src="http://r.mzstatic.com/images/spacer.gif" /></td> <td width="95%"> <b><a href="https://music.apple.com/us/album/astronaut-in-the-ocean/1547525310?i=1547525311&uo=2">Astronaut In The Ocean</a></b><br/> <a href="https://music.apple.com/us/album/astronaut-in-the-ocean-single/1547525310?uo=2">Astronaut In The Ocean - Single</a><br/> <a href="https://music.apple.com/us/artist/masked-wolf/1313094981?uo=2">Masked Wolf</a> <font size="2" face="Helvetica,Arial,Geneva,Swiss,SunSans-Regular"> <br/> <b>Genre:</b> <a href="https://music.apple.com/us/genre/music-hip-hop-rap/id18?uo=2">Hip-Hop/Rap</a> <br/> <b>Price:</b> $1.29 <br/> <b>Release Date:</b> June 7, 2019 </font> </td> </tr> </table> </td> </tr> <tr> <td> <font size="2" face="Helvetica,Arial,Geneva,Swiss,SunSans-Regular"> &#169; ℗ 2021 Elektra Records LLC</font> </td> </tr> </table> </content>
</entry>

Used Almofire for calling api,

import Alamofire
import XMLMapper

func fetchData() {
        AF.request(URL(string: "http://ax.itunes.apple.com/WebObjects/MZStoreServices.woa/ws/RSS/topsongs/limit=20/xml")!, method: .get, parameters: [:], encoding: URLEncoding.default, headers: []).responseData { (response) in
        do{
              let xmlDictionary = try XMLSerialization.xmlObject(with: response.data!) as? [String: Any]
               print(xmlDictionary!)
               let rssFeed = XMLMapper<SongFeed>().map(XMLObject: xmlDictionary)
               print(rssFeed?.entry?[0].image ?? "")
          } catch {
               print("Serialization error occurred: \(error.localizedDescription)")
          }
    }
}

Here is my model class,

import XMLMapper

class SongFeed : XMLMappable {
    var nodeName: String!
    var title : String?
    var entry : [Entry]?
    
    required init?(map: XMLMap) {
    }

    func mapping(map: XMLMap) {
        title <- map["title"]
        entry <- map["entry"]
    }
}

class Entry : XMLMappable {
    var nodeName: String!
    var title : String?
    var link: URL?
    var image : URL?
    var name : String?
    
    required init?(map: XMLMap) {
    }
    
    func mapping(map: XMLMap) {
        title <- map["title"]
        link <- (map["link"], XMLURLTransform())
        image <- (map["im:image"], XMLURLTransform())
        name <- map["im:name"]
    }
}

I am unable to fetch image URL from the <im:image> tag from the XML response. Also I am confused as there are three <im:image> tags with different height parameters, how can I fetch one of them? Can anyone help?


Solution

  • To map the im:image elements you need to use an array of custom object, since there are more than one and they contain an attribute. (height)

    Following the same logic, to map the link elements you need to use an array of custom object also.

    The model that will work may be something like this:

    class SongFeed: XMLMappable {
        var nodeName: String!
        
        var title: String?
        var entry: [Entry]?
        
        required init?(map: XMLMap) {}
    
        func mapping(map: XMLMap) {
            title <- map["title"]
            entry <- map["entry"]
        }
    }
    
    class Entry: XMLMappable {
        var nodeName: String!
        
        var title: String?
        var link: [Link]?
        var image: [Image]?
        var name: String?
        
        required init?(map: XMLMap) {}
        
        func mapping(map: XMLMap) {
            title <- map["title"]
            link <- map["link"]
            image <- map["im:image"]
            name <- map["im:name"]
        }
    }
    
    class Link: XMLMappable {
        var nodeName: String!
        
        var title: String?
        var rel: String?
        var type: String?
        var href: URL?
        var assetType: String?
        var duration: Double?
        
        required init?(map: XMLMap) {}
        
        func mapping(map: XMLMap) {
            title <- map.attributes["title"]
            rel <- map.attributes["rel"]
            type <- map.attributes["type"]
            href <- (map.attributes["href"], XMLURLTransform())
            assetType <- map.attributes["im:assetType"]
            duration <- map["im:duration"]
        }
    }
    
    class Image: XMLMappable {
        var nodeName: String!
        
        var height: Int?
        var url: URL?
        
        required init?(map: XMLMap) {}
        
        func mapping(map: XMLMap) {
            height <- map.attributes["height"]
            url <- (map.innerText, XMLURLTransform())
        }
    }
    

    Also, please notice the attributes mapping in the mapping(map:) function. It is clearly distinguished from element mapping.