Swiftpack.co - Package - gcharita/XMLMapper

XMLMapper

CI Status Version License Platform Swift Package Manager compatible Carthage compatible

Example

To run the example project, clone the repo, and run pod install from the Example directory first.

Requirements

  • iOS 8.0+ / macOS 10.9+ / tvOS 9.0+ / watchOS 2.0+
  • Xcode 8.3+
  • Swift 3.1+

How to use

To map XML to a class (or the reverse) the class must implement the XMLMappable protocol:

var nodeName: String! { get set }
init(map: XMLMap)
mutating func mapping(map: XMLMap)

XMLMapper uses the <- operator to map properties to and from XML elements:

class Food: XMLMappable {
    var nodeName: String!

    var name: String!
    var price: Float!
    var description: String?
    var calories: Int?

    required init(map: XMLMap) {

    }

    func mapping(map: XMLMap) {
        name <- map["name"]
        price <- map["price"]
        description <- map["description"]
        calories <- map["calories"]
    }
}

Basic XML mapping

Convert easily an XML string to XMLMappable:

let food = Food(XMLString: xmlString)

Or an XMLMappable object to XML string:

let xmlString = food.toXMLString()

XMLMapper.swift can also provide the same functionality:

let food = XMLMapper<Food>().map(XMLString: xmlString)

let xmlString = XMLMapper().toXMLString(food)

Advanced mapping

Set nodeName property of your class to change the element's name:

food.nodeName = "myFood"
<myFood>
  <name>Belgian Waffles</name>
  <price>5.95</price>
  <description>
    Two of our famous Belgian Waffles with plenty of real maple syrup
  </description>
  <calories>650</calories>
</myFood>

Map easily XML attributes using the attributes property of the XMLMap:

<food name="Belgian Waffles">
</food>
func mapping(map: XMLMap) {
    name <- map.attributes["name"]
}

Map arrays of elements:

<breakfast_menu>
  <food>
    <name>Belgian Waffles</name>
    <price>5.95</price>
    <description>
      Two of our famous Belgian Waffles with plenty of real maple syrup
    </description>
    <calories>650</calories>
  </food>
  <food>
    <name>Strawberry Belgian Waffles</name>
    <price>7.95</price>
    <description>
      Light Belgian waffles covered with strawberries and whipped cream
    </description>
    <calories>900</calories>
  </food>
</breakfast_menu>
func mapping(map: XMLMap) {
    foods <- map["food"]
}

Map nested XML elements by separating names with a dot:

<food>
  <name>Belgian Waffles</name>
  <details>
    <price>5.95</price>
    <description>
      Two of our famous Belgian Waffles with plenty of real maple syrup
    </description>
    <calories>650</calories>
  </details>
</food>
func mapping(map: XMLMap) {
    price <- map["details.price"]
}

Or create your own custom transform type by implementing the XMLTransformType protocol:

public protocol XMLTransformType {
    associatedtype Object
    associatedtype XML

    func transformFromXML(_ value: Any?) -> Object?
    func transformToXML(_ value: Object?) -> XML?
}

and use it in mapping:

func mapping(map: XMLMap) {
    startTime <- (map["starttime"], XMLDateTransform())
}

XML Mapping example

map XML:

 <?xml version="1.0" encoding="UTF-8"?>
 <root>
    <TestElementXMLMappable testAttribute="enumValue">
       <testString>Test string</testString>
       <testList>
          <element>
             <testInt>1</testInt>
             <testDouble>1.0</testDouble>
          </element>
          <element>
             <testInt>2</testInt>
             <testDouble>2.0</testDouble>
          </element>
          <element>
             <testInt>3</testInt>
             <testDouble>3.0</testDouble>
          </element>
          <element>
             <testInt>4</testInt>
             <testDouble>4.0</testDouble>
          </element>
       </testList>
    </TestElementXMLMappable>
 </root>

to classes:

class TestXMLMappable: XMLMappable {
    var nodeName: String!

    var testElement: TestElementXMLMappable!

    required init(map: XMLMap) {

    }

    func mapping(map: XMLMap) {
        testElement <- map["TestElementXMLMappable"]
    }
}

enum EnumTest: String {
    case theEnumValue = "enumValue"
}

class TestElementXMLMappable: XMLMappable {
    var nodeName: String!

    var testString: String?
    var testAttribute: EnumTest?
    var testList: [Element]?

    required init(map: XMLMap) {

    }

    func mapping(map: XMLMap) {
        testString <- map["testString"]
        testAttribute <- map.attributes["testAttribute"]
        testList <- map["testList.element"]
    }
}

class Element: XMLMappable {
    var nodeName: String!

    var testInt: Int?
    var testDouble: Float?

    required init(map: XMLMap) {

    }

    func mapping(map: XMLMap) {
        testInt <- map["testInt"]
        testDouble <- map["testDouble"]
    }
}

Requests subspec

Create and send easily request with XML body using Alamofire (added missing XMLEncoding struct)

Alamofire.request(url, method: .post, parameters: xmlMappableObject.toXML(), encoding: XMLEncoding.default)

Also map XML responses to XMLMappable objects using the Alamofire extension. For example a URL returns the following CD catalog:

<CATALOG>
    <CD>
        <TITLE>Empire Burlesque</TITLE>
        <ARTIST>Bob Dylan</ARTIST>
        <COUNTRY>USA</COUNTRY>
        <COMPANY>Columbia</COMPANY>
        <PRICE>10.90</PRICE>
        <YEAR>1985</YEAR>
    </CD>
    <CD>
        <TITLE>Hide your heart</TITLE>
        <ARTIST>Bonnie Tyler</ARTIST>
        <COUNTRY>UK</COUNTRY>
        <COMPANY>CBS Records</COMPANY>
        <PRICE>9.90</PRICE>
        <YEAR>1988</YEAR>
    </CD>
</CATALOG>

Map the response as follows:

Alamofire.request(url).responseXMLObject { (response: DataResponse<CDCatalog>) in
    let catalog = response.result.value
    print(catalog?.cds?.first?.title ?? "nil")
}

The CDCatalog object will look something like this:

class CDCatalog: XMLMappable {
    var nodeName: String!

    var cds: [CD]?

    required init(map: XMLMap) {

    }

    func mapping(map: XMLMap) {
        cds <- map["CD"]
    }

}

class CD: XMLMappable {
    var nodeName: String!

    var title: String!
    var artist: String?
    var country: String?
    var company: String?
    var price: Double?
    var year: Int?

    required init(map: XMLMap) {

    }

    func mapping(map: XMLMap) {
        title <- map["TITLE"]
        artist <- map["ARTIST"]
        country <- map["COUNTRY"]
        company <- map["COMPANY"]
        price <- map["PRICE"]
        year <- map["YEAR"]
    }

}

Last but not least, create easily and send SOAP requests, again using Alamofire:

let soapMessage = SOAPMessage(soapAction: "ActionName", nameSpace: "ActionNameSpace")
let soapEnvelope = SOAPEnvelope(soapMessage: soapMessage)

Alamofire.request(url, method: .post, parameters: soapEnvelope.toXML(), encoding: XMLEncoding.soap(withAction: "ActionNameSpace#ActionName"))

The request will look something like this:

POST / HTTP/1.1
Host: <The url>
Content-Type: text/xml; charset="utf-8"
Connection: keep-alive
SOAPAction: ActionNameSpace#ActionName
Accept: */*
User-Agent: XMLMapper_Example/1.0 (org.cocoapods.demo.XMLMapper-Example; build:1; iOS 11.0.0) Alamofire/4.5.1
Accept-Language: en;q=1.0
Content-Length: 251
Accept-Encoding: gzip;q=1.0, compress;q=0.5

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
    <soap:Body>
        <m:ActionName xmlns:m="ActionNameSpace"/>
    </soap:Body>
</soap:Envelope>

Adding action parameters is as easy as subclassing the SOAPMessage class.

class MySOAPMessage: SOAPMessage {

    // Custom properties

    override func mapping(map: XMLMap) {
        super.mapping(map: map)

        // Map the custom properties
    }
}

Also specify the SOAP version that the endpoint use as follows:

let soapMessage = SOAPMessage(soapAction: "ActionName", nameSpace: "ActionNameSpace")
let soapEnvelope = SOAPEnvelope(soapMessage: soapMessage, soapVersion: .version1point2)

Alamofire.request(url, method: .post, parameters: soapEnvelope.toXML(), encoding: XMLEncoding.soap(withAction: "ActionNameSpace#ActionName", soapVersion: .version1point2))

and the request will change to this:

POST / HTTP/1.1
Host: <The url>
Content-Type: application/soap+xml;charset=UTF-8;action="ActionNameSpace#ActionName"
Connection: keep-alive
Accept: */*
User-Agent: XMLMapper_Example/1.0 (org.cocoapods.demo.XMLMapper-Example; build:1; iOS 11.0.0) Alamofire/4.5.1
Accept-Language: en;q=1.0
Content-Length: 248
Accept-Encoding: gzip;q=1.0, compress;q=0.5

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope/" soap:encodingStyle="http://www.w3.org/2003/05/soap-encoding">
    <soap:Body>
        <m:ActionName xmlns:m="ActionNameSpace"/>
    </soap:Body>
</soap:Envelope>

Unfortunately, there isn't an easy way to map SOAP response, other than creating your own XMLMappable objects (at least not for the moment)

Communication

  • If you need help, use Stack Overflow. (Tag 'xmlmapper')
  • If you'd like to ask a general question, use Stack Overflow.
  • If you found a bug, open an issue.
  • If you have a feature request, open an issue.

Installation

CocoaPods

XMLMapper is available through CocoaPods. To install it, simply add the following line to your Podfile:

pod 'XMLMapper'

To install the Requests subspec add the following line to your Podfile:

pod 'XMLMapper/Requests'

Carthage

To integrate XMLMapper into your Xcode project using Carthage, add the following line to your Cartfile:

github "gcharita/XMLMapper" ~> 1.4

Swift Package Manager

To add XMLMapper to a Swift Package Manager based project, add the following:

.package(url: "https://github.com/gcharita/XMLMapper.git", from: "1.4.0")

to the dependencies value of your Package.swift.

Special thanks

License

XMLMapper is available under the MIT license. See the LICENSE file for more info.

Github

link
Stars: 21
Help us keep the lights on

Dependencies

Used By

Total: 0

Releases

1.5.0 - Jun 20, 2018

  • Added support for Swift 4.2 and Xcode 10. Fixed invalid redeclaration errors. (warnings will remain in Swift 4.1 compiler, due to the fact that IUO do not behave the same way as in Swift 4.2 compiler)
  • Fixed flatMap deprecation warnings.
  • Fixed support for Swift 3.0 and Xcode 8.3.
  • Fixed XMLSerialization to support XMLs with XML declaration
  • Improved XMLMapper to support mapping of dictionary of XMLMappable and dictionary of arrays of XMLMappable objects.
  • XMLSerialization can now return array of dictionaries
  • Added support to map enums with rawValue of LosslessStringConvertible.
  • Added support to map array of Any with single element.
  • Added tests that cover more than half of the project.

1.4.4 - Apr 1, 2018

  • Fixed #5. Mapping Array of single object.
  • Fixed #8. Threading problem in XMLObjectParser causing crash.

1.4.3 - Feb 16, 2018

  • Fixed #2. Wrong XML String from nodes with attributes only.

1.4.2 - Feb 4, 2018

  • Fixed changes that broke Swift 3.1 and Xcode 8.3 support
  • Added innerText property in XMLMap to map directly the text of current XML node #1
  • General code improvements

1.4.1 - Jan 25, 2018

  • Added Carthage support