Git Product home page Git Product logo

xmlmapper's Introduction

XMLMapper

CI Status Version License Platform Swift Package Manager compatible Carthage compatible

XMLMapper is a framework written in Swift that makes it easy for you to convert your model objects (classes and structs) to and from XML.

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 9.1+
  • Swift 3.1+

Definition of the protocols

XMLBaseMappable Protocol

var nodeName: String! { get set }

This property is where the name of the XML node is being mapped

mutating func mapping(map: XMLMap)

This function is where all mapping definitions should go. When parsing XML, this function is executed after successful object creation. When generating XML, it is the only function that is called on the object.

Note: This protocol should not be implemented directly. XMLMappable or XMLStaticMappable should be used instead

XMLMappable Protocol (sub protocol of XMLBaseMappable)

init?(map: XMLMap)

This failable initializer is used by XMLMapper for object creation. It can be used by developers to validate XML prior to object serialization. Returning nil within the function will prevent the mapping from occuring. You can inspect the XML stored within the XMLMap object to do your validation:

required init?(map: XMLMap) {
    // check if a required "id" element exists within the XML.
    if map.XML["id"] == nil {
        return nil
    }
}

XMLStaticMappable Protocol (sub protocol of XMLBaseMappable)

XMLStaticMappable is an alternative to XMLMappable. It provides developers with a static function that is used by XMLMapper for object initialization instead of init?(map: XMLMap).

static func objectForMapping(map: XMLMap) -> XMLBaseMappable?

XMLMapper uses this function to get objects to use for mapping. Developers should return an instance of an object that conforms to XMLBaseMappable in this function. This function can also be used to:

  • validate XML prior to object serialization
  • provide an existing cached object to be used for mapping
  • return an object of another type (which also conforms to XMLBaseMappable) to be used for mapping. For instance, you may inspect the XML to infer the type of object that should be used for mapping

If you need to implement XMLMapper in an extension, you will need to adopt this protocol instead of XMLMappable.

How to use

To support mapping, a class or struct just needs to implement the XMLMappable protocol:

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

XMLMapper uses the <- operator to define how each property maps to and from XML:

<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>
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"]
    }
}

XMLMapper can map classes or structs composed of the following types:

  • Int
  • Bool
  • Double
  • Float
  • String
  • RawRepresentable (Enums)
  • Array<Any>
  • Dictionary<String, Any>
  • Object<T: XMLBaseMappable>
  • Array<T: XMLBaseMappable>
  • Set<T: XMLBaseMappable>
  • Dictionary<String, T: XMLBaseMappable>
  • Dictionary<String, Array<T: XMLBaseMappable>>
  • Optionals and Implicitly Unwrapped Optionals of all the above

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 class 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 array 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"]
}

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())
}

Map nested XML elements by separating names with a dot:

<food>
  <details>
    <price>5.95</price>
  </details>
</food>
func mapping(map: XMLMap) {
    price <- map["details.price"]
}

Note: Nested mapping is currently supported only:

  • for elements that are composed of only innerText (like the above example) and
  • for attributes

This means that in order to map the actual price of the food in the following XML:

<food>
  <details>
    <price currency="euro">5.95</price>
  </details>
</food>

You need to use an XMLMappable object instead of a Float:

class Price: XMLMappable {
    var nodeName: String!

    var currency: String!
    var actualPrice: Float!

    required init?(map: XMLMap) {}

    func mapping(map: XMLMap) {
        currency <- map.attributes["currency"]
        actualPrice <- map.innerText
    }
}

Because of currency attribute existence. The same applies to the following XML:

<food>
  <details>
    <price>
      5.95
      <currency>euro</currency>
  </details>
</food>

You need to use an XMLMappable object like:

class Price: XMLMappable {
    var nodeName: String!

    var currency: String!
    var actualPrice: Float!

    required init?(map: XMLMap) {}

    func mapping(map: XMLMap) {
        currency <- map["currency"]
        actualPrice <- map.innerText
    }
}

Because of currency element existence.


Swift 4.2 and unordered XML elements

Starting from Swift 4.2, XML elements are highly likely to have different order each time you run your app. (This happens because they are represented by a Dictionary)

For this, since version 1.5.2 of the XMLMapper you can map and change the order of the nodes that appear inside another node using nodesOrder property of XMLMap:

class TestOrderedNodes: XMLMappable {
    var nodeName: String!

    var id: String?
    var name: String?
    var nodesOrder: [String]?

    init() {}
    required init?(map: XMLMap) {}

    func mapping(map: XMLMap) {
        id <- map["id"]
        name <- map["name"]
        nodesOrder <- map.nodesOrder
    }
}

let testOrderedNodes = TestOrderedNodes()
testOrderedNodes.id = "1"
testOrderedNodes.name = "the name"
testOrderedNodes.nodesOrder = ["id", "name"]
print(testOrderedNodes.toXMLString() ?? "nil")

Note: If you want to change the ordering of the nodes, make sure that you include, in the nodesOrder array, all the node names that you want to appear in the XML string

Map CDATA wrapped values

Since version 2.0.0 of XMLMapper, CDATA support has added. CDATA wrapped strings now are mapped as an Array<Data> by default, instead of String which was the case in the previous versions. That had as a side effect the disability to serialize CDATA wrapped values.

For example using the following code:

class Food: XMLMappable {
    var nodeName: String!
    
    var description: String?
    
    init() {}
    
    required init?(map: XMLMap) {}
        
    func mapping(map: XMLMap) {
        description <- map["description"]
    }
}

let food = Food()
food.nodeName = "Food"
food.description = "Light Belgian waffles covered with strawberries & whipped cream"
print(food.toXMLString() ?? "nil")

Your result was always:

<Food>
    <description>
        Light Belgian waffles covered with strawberries &amp; whipped cream
    </description>
</Food>

In version 2.0.0 we introduce the build in XMLCDATATransform type, which can be used like this:

class Food: XMLMappable {
    var nodeName: String!
    
    var description: String?
    
    init() {}
    
    required init?(map: XMLMap) {}
        
    func mapping(map: XMLMap) {
        description <- (map["description"], XMLCDATATransform())
    }
}

let food = Food()
food.nodeName = "Food"
food.description = "Light Belgian waffles covered with strawberries & whipped cream"
print(food.toXMLString() ?? "nil")

and the result will be:

<Food>
    <description>
        <![CDATA[
            Light Belgian waffles covered with strawberries & whipped cream
        ]]>
    </description>
</Food>

The breaking change here is that the deserialization of CDATA wrapped values cannot achieved, unless you use XMLCDATATransform type. For example if you try to map the above XML to the following model class:

class Food: XMLMappable {
    var nodeName: String!
    
    var description: String?
    
    required init?(map: XMLMap) {}
        
    func mapping(map: XMLMap) {
        description <- map["description"]
    }
}

You will end up with nil as the value of description property.


Note: That default behaviour can be changed if you run xmlObject(withString:encoding:options:) function of XMLSerialization yourself and pass as options the default set, including cdataAsString option.

For example, the following code will work:

class Food: XMLMappable {
    var nodeName: String!
    
    var description: String?
    
    required init?(map: XMLMap) {}
        
    func mapping(map: XMLMap) {
        description <- map["description"]
    }
}

let xmlString = """
<Food>
    <description>
        <![CDATA[
            Light Belgian waffles covered with strawberries & whipped cream
        ]]>
    </description>
</Food>
"""
let data = Data(xmlString.utf8) // Data for deserialization (from XML to object)
do {
    let xml = try XMLSerialization.xmlObject(with: data, options: [.default, .cdataAsString])
    let food = XMLMapper<Food>().map(XMLObject: xml)
} catch {
    print(error)
}

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>
        <someTag>
            <someOtherTag>
                <nestedTag testNestedAttribute="nested attribute">
                </nestedTag>
            </someOtherTag>
        </someTag>
    </TestElementXMLMappable>
 </root>

to classes:

class TestXMLMappable: XMLMappable {
    var nodeName: String!

    var testElement: TestElementXMLMappable!
    var testNestedAttribute: String?

    required init?(map: XMLMap) {}

    func mapping(map: XMLMap) {
        testElement <- map["TestElementXMLMappable"]
        testNestedAttribute <- map.attributes["TestElementXMLMappable.someTag.someOtherTag.nestedTag.testNestedAttribute"]
    }
}

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

class TestElementXMLMappable: XMLMappable {
    var nodeName: String!

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

    required init?(map: XMLMap) {}

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

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

Note: Requests subspec has different minimum deployment targets due to Alamofire dependency. (currently iOS 10.0+ / macOS 10.12+ / tvOS 10.0+ / watchOS 3.0+)

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.6

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.6.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.

xmlmapper's People

Contributors

gcharita avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

xmlmapper's Issues

XMLObjectParser.swift problem

In some rare cases i have crash in XMLObjectParser.swift
2018-03-29 13 35 45
I believe it have some connections with threading.
Removing private static shared XMLObjectParser and make it local constant in class func dictionary seems solves the problem.
2018-03-29 13 40 25

Duplicate tag but different data

Hi Gcharita,

I have an issue, I tried to search a lot of closed and open issues but couldn't find any results.

I have the xml like

<feed class="abcs-js">
	<script/>
	<startIndex>0</startIndex>
	<itemCount>3</itemCount>
	<totalCount>3</totalCount>
	<item sdImg="https://i.image.com/sd.jpg" hdImg="https://i.image.com/hd.jpg">
		<title>TITLE DEMO</title>
		<description>Description demo</description>
		<feed type="epose">https://abc.xml</feed>
	</item>
	<item sdImg="https://i.image.com/sd1.jpg" hdImg="https://i.image.com/hd1.jpg">
		<title>TITLE DEMO</title>
		<description>description demo</description>
		<feed>https://xyz.xml</feed>
	</item>
	<item sdImg="https://i.image.com/sd2.jpg" hdImg="https://i.image.com/hd2.jpg">
		<title>TITLE DEMO1</title>
		<description>Description demo</description>
		<feed type="grid">https://cyz.xml</feed>
	</item>
</feed>

You can see, i have 2 tag <feed> and my model like:

class Data: XMLMappable {
    required init?(map: XMLMap) {}
    
    var nodeName: String!
    
    func mapping(map: XMLMap) {
        startIndex <- map["startIndex"]
        itemCount <- map["itemCount"]
        totalCount <- map["totalCount"]
        items <- map["item"]
    }
    
    var startIndex, itemCount, totalCount: Int?
    var items: [Item]?
}
class Item: XMLMappable {
    var title: String?
    var feed: String?
    var description: String?
    
    var type: String?
    
    var sdImg: String?
    var hdImg: String?
    
    var nodeName: String!
    
    required init?(map: XMLMap) {}
    
    func mapping(map: XMLMap) {
        title <- map["title"]
        titlel <- map["titlel"]
        feed <- map["feed"]
        type <- map.attributes["type"]
        description <- map["description"]        
        
        sdImg <- map.attributes["sdImg"]
        hdImg <- map.attributes["hdImg"]
    }
}

I used XMLMapper/Requests and converted it to Data class.
But feed in my Item class always nil.

thanks in advance,
Jin

Privacy Manifest

Hello,
I am an employee of Monstarlab company. I have a question about the Privacy Manifest

At WWDC23 Apple announced that apps and SDKs that make use of certain "required reason" APIs etc will need to provide a privacy manifest.
Does XML Mapper need to include this manifest?
Please let me know about your upcoming plans to apply the privacy manifest.

Here are some useful references:

https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_data_use_in_privacy_manifests

https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_use_of_required_reason_api

https://developer.apple.com/videos/play/wwdc2023/10060/

Thanks

toXMLString() tries to modify original object?

I'm having an issue when using toXMLString() on objects that are in a Realm DB.

It seems that by calling toXMLString() on an object that is XMLMappable, the mapping(map: XMLMap) method for the object is called, which tries to modify the object (even if it is keeping the values the same).

The issue, as far as I know it right now, is that when mapping is called, the primary key of my object is attempting to be changed, and that causes Realm to crash.

Is there a way to call toXMLString() without it attempting to modify any of the existing data?

Problem parsing XML with single or multiple elements

Take the following for example:

<foo>
    <bar>
         <item></item>
   <bar>
   <bar>
         <item></item>
         <item></item>
   </bar>
</foo>

You have to make a choice to either parse as a single item or an array [item]. Both options are not available.

How can I use Requests classes with Swift Package Manager?

Hi,

I’m trying to use the XMLEncoding that is part of the Request target but I'm seeing that it is excluded in Package.swift
I know I can achieve this with CocoaPods but is it possible to achieve the same with SPM?

Thanks in advance.

Alamofire incompatability?

I've just cloned the alamofire git as a sibmodule in my project along with your project, when I compile I get a load of errors including "DataRequest.stringResponseSerializer" not being found and so on....

Is this only compatible with a specific maor version?

Parse failing when illegal characters are present

I have an XML Mappable Object that I use to map responses from our API:

`class CreateAccountResponse: XMLMappable {
var nodeName: String!

var Result: String!
var ResultExplanation: String!

required init?(map: XMLMap) {
    nodeName = ApiConstants.XMLRootTag
}

func mapping(map: XMLMap) {
    Result <- map["Result"]
    ResultExplanation <- map["ResultExplanation"]
}`

and I'm using the Alamofire Requests subspec:

Alamofire.request(ApiProvider.buildUrl(), method: .post, parameters: requestObject.toXML(), encoding: XMLEncoding.default, headers: ApiConstants.Header) .responseXMLObject { (resp: DataResponse<CreateAccountResponse>) in {...}

And this works just perfect. Here is a example of and XML response that works:

<API3G> <Result>999</Result> <ResultExplanation>Email already in use</ResultExplanation> </API3G>

But the same endpoint with the same XML structure, but different values fails to map:

<API3G> <Result>902</Result> <ResultExplanation>Data mismatch in one of the fields - CustomerPassword. Please enter at least 6 characters (letters, numbers and !@#$%&()?)</ResultExplanation> </API3G>

The error message I'm getting is as follow: "XMLMapper failed to serialize response."

Invalid XML string for tags with XML encoded values

Mapping the following XML:

<?xml version="1.0" encoding="utf-8"?>
<root>
    <test text="Foo &amp; bar"/>
</root>

to the model:

class Root: XMLMappable {
    var nodeName: String!
    
    var testText: String?
    
    required init?(map: XMLMap) {}
    
    func mapping(map: XMLMap) {
        testText <- map.attributes["test.text"]
    }
}

and converting back the string calling toXMLString() function, prints XML string but without XML encoding the attribute values:

<?xml version="1.0" encoding="utf-8"?>
<root>
    <test text="Foo & bar"/>
</root>

Save XML data to UserDefaults

Hello @gcharita , thanks for this great library. It is well appreciated.

I will like to ask how best I can store data to my UserDefaults.

I have already tried

func saveListData(podList: [RSSFeeds]){
        let podData = NSKeyedArchiver.archivedData(withRootObject: podList)
        UserDefaults.standard.set(podData, forKey: "podData")
    }

but i am getting the unrecognized selector sent to instance 0x600000c23040 error.

Any insight to getting this resolved will be well appreciated.

Regards,

crashes most of times

Hi guys,

Almost of times the app crash with random xmls on this line.
func addText(_ additionalText: String) { if case nil = text?.append(additionalText) { text = additionalText } }

It throws this exception on console:
(17940,0x70000e800000) malloc: *** error for object 0x600000862ac0: Non-aligned pointer being freed (2) *** set a breakpoint in malloc_error_break to debug

Could you help me? :)

Cannot correctly parse elements with innerText and optional attributes

It is impossible to correctly parse elements that use innerText and optional attributes.

Take the example in the README:

<food>
  <details>
    <price currency="euro">5.95</price>
  </details>
</food>
class Price: XMLMappable {
    var nodeName: String!

    var currency: String!
    var actualPrice: Float!

    required init?(map: XMLMap) {}

    func mapping(map: XMLMap) {
        currency <- map.attributes["currency"]
        actualPrice <- map.innerText
    }
}

In this case, if the currency is optional so the input could include either <price currency="euro">5.95</price> or <price>5.95</price>, you cannot correctly parse the latter example. This is because the collapseTextNodes functionality in XMLObjectParser moves the 5.95 value to the parent dictionary if there are no attributes on the node. This leaves you with no reasonable way to parse both inputs with the same mapping object.

Question regarding dots in value

Hi Gcharita,

  • I'm having the following issue, a have a mapping where i have a dot in between a single value. Is this a bug or not? if so, is there a workaround?

This code is not working, there is a dot in the middle of LINE.NAME

func mapping(map: XMLMap) {
            lineName <- map["LINE.NAME"]

This code is fully working. There is no dot in the middle of LINENAME

func mapping(map: XMLMap) {
            lineName <- map["LINENAME"]

thanks in advance,

Patrick

Serialization for CDATA wrapped values

CDATA deserialization works great out of the box. It would be great to create some approach to serialize CDATA wrapped values.

For example:

class MyData: XMLMappable {
    var nodeName: String!
    var cdataValue: String?
    ...
    func mapping(map: XMLMap) {
        cdataValue <- map.attributes["cdataValue"]
    }
}
let myData = MyData()
myData.cdataValue = "actualValue"
print(myData.toXMLString())
<cdataValue><![CDATA[ actualValue ]]></cdataValue>

Request subspec on Alamofire 4.9

I'm a hobbyist, since XMLMapper 1.6.1 is compatabile with Alamofire 5.2, I was wondering if the Request Subspec is compatible with 5.2 or oly 4.9?

EDIT: podfile has
pod 'Alamofire', '> 5.2',
pod 'XMLMapper', '
> 1.6.1',
pod 'XMLMapper/Requests',

as well, when trying with XCode Swift package manager it does the same thing when switching to the enhancements/alamofire5 support tree. Request subspec is not optional

Alamofire 5.2 cannot convert value of type

Hi GCharita,

Since alamofire 5.2 i cannot convert DataResponse to expected type (DataResponse<T, AFError>).

Please find attached the used code and a screenshot of the error. Can you help me throught with this issue?

best regards,

Patrick

Alamofire 5.2
XMLMapper 2.0.0
Xcode 12.3

Schermafbeelding 2020-12-29 om 15 59 23

class AllCnannelModel : XMLMappable {
            
            var nodeName: String!
            
            var  id : Int?
            var  name: String?
            var  url : URL?
            var  picture : URL?
            var  category_id: Int?

            required init?(map: XMLMap) {}

            func mapping(map: XMLMap) {
                id<-map["PERSONID"]
                name<-map["NAME"]
                url<-map["url"]
                picture<-map["picture"]
                category_id<-map["category_id"]
            }
        }
        
        let getallpersonsquery = GetAllPersonsQuery()
        getallpersonsquery.nodeName = "query"
        getallpersonsquery.sql = "SELECT personid, name FROM person where personid = 150"
        
        AF.request(RequestUrl, method: .post, parameters: getallpersonsquery.toXML(), encoding: XMLEncoding.default, headers: headers)
            .redirect(using: redirector)
            .responseXMLObject(completionHandler: (DataResponse<[AllCnannelModel], AFError>) -> Void) in
        }

    }

XMLParserHelper xmlString, nodes with attributes only

I think there is problem in XMLParserHelper class func xmlString in nodes with attributes only.
Suggest change string format "<%1$@>%2$@</%1$@>" to "<%1$@%2$@/>" in

if !innerXML.isEmpty { return String(format: "<%1$@%2$@>%3$@</%1$@>", nodeName, attributeString, innerXML) } else { return String(format: "<%1$@>%2$@</%1$@>", nodeName, attributeString)}

Mapping XML element of basic type with attributes

Mapping an XML element that have other elements of basic type and attributes:

<Person gender="male">
    <FirstName>First</FirstName>
    <LastName>Last</LastName>
</Person>

is easy:

class Person: XMLMappable {
    var nodeName: String!
    
    var gender: String?
    var firstName: String?
    var lastName: String?
    
    required init(map: XMLMap) {
        
    }
    
    func mapping(map: XMLMap) {
        gender <- map.attributes["gender"]
        firstName <- map["FirstName"]
        lastName <- map["LastName"]
    }
}

But if I try to map an XML element of basic type (like Person below) that have attributes:

<root>
    <Person gender="male">First Last</Person>
</root>

Using this:

class Root: XMLMappable {
    var nodeName: String!
    
    var person: String?
    
    required init(map: XMLMap) {
        
    }
    
    func mapping(map: XMLMap) {
        person <- map["Person"]
    }
}

I am getting nil in person property and using this:

class Root: XMLMappable {
    var nodeName: String!
    
    var person: Person?
    
    required init(map: XMLMap) {
        
    }
    
    func mapping(map: XMLMap) {
        person <- map["Person"]
    }
}

class Person: XMLMappable {
    var nodeName: String!
    
    var gender: String?
    var name: String?
    
    required init(map: XMLMap) {
        
    }
    
    func mapping(map: XMLMap) {
        gender <- map.attributes["gender"]
        //name <- get somehow innertext
    }
}

I am getting correctly the gender attribute but I don't know how to get Person's name.

Parse special characters

I have problem parsing this special characters (&nbsp;<br />). In the xml tags there are also these symbols and html code. Can You help me?

Map to a bool based on the existence of a tag

I have an xml file that has an element that is a flag. For example, <hasThing/>. If that element exists I want to map it to true, otherwise it should be mapped to false. I didn't see anything in the readme. Is there a way to do this?

Thanks.

Body is located before Header (Unordered tags)

Hello, I am trying to compose a SOAP request and have encountered a problem - for some reason in my request Body is located before Header and I don't understand how I can change it.

This is how I create SOAPEnvelope where is information has header data and soapMessage has body data:
let soapEnvelope = SOAPEnvelope(soapMessage: soapMessage, soapInformation: information, soapVersion: .version1point2)

And for example when I try to print SOAPEnvelope (soapEnvelope.toXMLString()) I receive this string:

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

The problem is that I work with the ONVIF service and it is sensitive to the order of the header / body.

Thanks

mapArray(XMLObject: Any?) -> [N]? bug

If we have XML something like
<Array>
<XMLMappable/>
</Array>
and try to map it like
var array = [XMLMappable]()
array <- map[Array.XMLMappable]
we have empty array instead of array with 1 item.

If think problem method is mapArray(XMLObject: Any?) -> [N]? in Core/XMLMapper.swift
Suggest add else if statement like this.

Before:
public func mapArray(XMLObject: Any?) -> [N]? {
if let XMLArray = XMLObject as? [[String: Any]] {
return mapArray(XMLArray: XMLArray)
}
return nil
}

After:
public func mapArray(XMLObject: Any?) -> [N]? {
if let XMLArray = XMLObject as? [[String: Any]] {
return mapArray(XMLArray: XMLArray)
} else if let object = XMLObject as? [String: Any] {
return mapArray(XMLArray: [object])
}
return nil
}

Issue with optional attributes in simple tags

There is an issue on mapping simple tags (a tag with just inner text) that have optional attributes.

When there is at least one attribute present, everything work fine:

<root>
    <tag optionalAttribute="attribute value">innetText</testString>
</root>

But when there is no attribute present:

<root>
    <tag>innetText</testString>
</root>

mapping using the same model produces nil:

class Root: XMLMappable {
    var nodeName: String!
    
    var tag: Tag?
    
    required init?(map: XMLMap) {}
    
    func mapping(map: XMLMap) {
        tag <- map["tag"]
    }
}

class Tag: XMLMappable {
    var nodeName: String!
    
    var optionalAttribute: String?
    var text: String?
    
    required init?(map: XMLMap) {}
    
    func mapping(map: XMLMap) {
        optionalAttribute <- map.attributes["optionalAttribute"]
        text <- map.innerText
    }
}

Catch exceptions

There is any way to catch the parsing errors? I can assume sometimes a xml can't be downloaded but i can't assume the app will crash anytime the xml format is wrong.

Ordered XML Attributes

When I write to XML string, I want to be in order attributes. How do I do this?

example:

class TestOrderedAttributes: XMLMappable {
   var NodeName: String!
   var attribute1: String?
   var attribute2: String?
   var attribute3: String?

   init() {}
   required init?(map:XMLMap) {}

   func mapping(map: XMLMap) {
      attribute1 <- map.attributes["attribute1"]
      attribute2 <- map.attributes["attribute2"]
      attribute3 <- map.attributes["attribute3"]
   }
}

let testOrderedAttributes = TestOrderedAttributes()
testOrderedAttributes.attribute1 = "1"
testOrderedAttributes.attribute2= "2"
testOrderedAttributes.attribute3 = "3"
testOrderedAttributes.attributesOrder = ["attribute3", "attribute2", "attribute1"]

foobar2000 returns artist twice - once with just text, second time with attribute - XMLMapper can't access second array item

Good day,
I found a problem when trying to connect to a foobar2000 server.

The server returns:

<upnp:artist>The 54 All-Stars</upnp:artist>
<upnp:artist role="AlbumArtist">Various Artists</upnp:artist>

And that's ok, because XMLMapper treats it like an array.

However, I can't access the second item, no matter what I try.

If I map it to a String, I just get the first item. Ok, that's fine, but the second item gets lost.
So then I tried creating a new class:

class SOAPItemAlbumArtist: XMLMappable {
	required init?(map: XMLMap) {}
	
	var nodeName: String!

	var artistName: String?
	var artistRole: String?

	func mapping(map: XMLMap) {
		artistName <- map.innerText
		artistRole <- map.attributes["role"]
	}
}

And then the definition is as such:

var artist: [SOAPItemAlbumArtist]?

That causes a crash in XMLMapper.swift:90

if var object = klass.init(map: map) as? N {

I tried everything I could to figure this out, including tracing through the source code but I'm not familiar enough with it to determine exactly how to fix it, or if I'm just setting up my mapping incorrectly.

Has anyone seen this issue? Is it a bug, or user error?

Getting nil for array of elements

Trying to map the following xml:

<apps>
	<app id="tvinput.dtv" type="tvin" version="1.0.0">Antenna TV</app>
	<app id="tvinput.hdmi2" type="tvin" version="1.0.0">Apple TV</app>
	<app id="12" subtype="ndka" type="appl" version="4.2.81179021">Netflix</app>
	<app id="2531" subtype="rsga" type="appl" version="4.3.2018073101">NHL</app>
</apps>

using

class Apps: XMLMappable {
    var nodeName: String!
    
    var apps: [App]!
    
    required init?(map: XMLMap) {}
    
    func mapping(map: XMLMap) {
        apps <- map["App"]
    }
}

class App: XMLMappable {
    var nodeName: String!
    var name: String?
    var id: String?
    
    required init(map: XMLMap) {
        
    }
    
    func mapping(map: XMLMap) {
        name <- map.innertext
        id <- map.attributes["id"]
    }
}

and getting nil from Apps(XMLString: string)!.apps where variable string has the correct xml.
Do you have any pointer how could I get an array containing all "app" nodes (name and id) as separate items?

Recursive XML elements

Hi!

How would I go about handling recursive XML elements?

E.g.

<item>
  <id>1</id>
  <item>
    <id>2</id>
  </item>
</item>

responseXMLObject Returns nil

Hi I'm using your library to map SOAP responses. I'm trying to call a PING function, but it always returns me nil

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

Alamofire.request(Url, method: .post, parameters: soapEnvelope.toXML(), encoding: XMLEncoding.soap(withAction: "\(actionNameSpace)#PingResponse", soapVersion: .version1point2)).responseXMLObject { (response: DataResponse<PingResponse>) in
            print(response.result.value?.PingResult) 
        }

This is the request (tested with SoapUI)

<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:tem="http://tempuri.org/">
   <soap:Header/>
   <soap:Body>
      <tem:Ping/>
   </soap:Body>
</soap:Envelope>

This is the response

<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <PingResponse xmlns="http://tempuri.org/">
      <PingResult>true</PingResult>
    </PingResponse>
  </soap:Body>
</soap:Envelope>

Can you help me?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.