XMLMapper 2.0.0

XMLMapper 2.0.0

测试已测试
语言语言 SwiftSwift
许可证 MIT
发布最后发布2020年12月
SPM支持 SPM

Giorgos Charitakis维护。



XMLMapper 2.0.0

  • 作者:
  • gcharita

XMLMapper

CI Status Version License Platform Swift Package Manager compatible Carthage compatible

XMLMapper是一个用Swift编写的框架,它使您能够轻松地将模型对象(类和结构)转换为XML,并从XML进行转换。

示例

要运行示例项目,请首先克隆仓库,然后从Example目录中运行pod install

要求

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

协议定义

XMLBaseMappable 协议

var nodeName: String! { get set }

此属性是映射XML节点名称的位置

mutating func mapping(map: XMLMap)

所有映射定义应该放在此函数中。在解析XML时,会在此函数创建成功对象之后执行该函数。在生成XML时,该对象只有一个会被调用的函数。

注意:不应直接实现此协议。应使用 XMLMappableXMLStaticMappable

XMLMappable 协议(XMLBaseMappable 的子协议)

init?(map: XMLMap)

这个可失败初始化器用于XMLMapper创建对象。开发人员可以使用它来在对象序列化之前验证XML。在函数中返回nil将防止发生映射。您可以通过检查XML Map对象中的XMLMap对象来执行验证。

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

XMLStaticMappable 协议(XMLBaseMappable 的子协议)

XMLStaticMappableXMLMappable 的替代方案。它为开发人员提供了一个静态函数,该函数由XMLMapper用于对象初始化,而不是使用 init?(map: XMLMap)

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

XMLMapper使用此函数来获取用于映射的对象。在函数中,开发人员应返回符合XMLBaseMappable协议的对象实例。此函数还可以用于:

  • 在对象序列化之前验证XML
  • 提供一个现有的缓存对象来使用于映射
  • 返回用于映射的另一种类型的对象(也符合XMLBaseMappable)。例如,您可能需要检查XML来推断用于映射的对象类型

如果您需要在扩展中实现XMLMapper,您将需要采用此协议而不是XMLMappable

如何使用

为了支持映射,一个类或结构体只需实现XMLMappable协议即可

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

XMLMapper使用<-运算符来定义每个属性映射到和从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可以映射下列类型的类或结构体:

  • Int
  • Bool
  • Double
  • Float
  • String
  • RawRepresentable(枚举)
  • Array<Any>
  • Dictionary<String, Any>
  • T: XMLBaseMappableObject
  • T: XMLBaseMappableArray
  • T: XMLBaseMappableSet
  • String, T: XMLBaseMappableDictionary
  • String, T: XMLBaseMappableArray <T: XMLBaseMappable>
  • 上述所有类型的可选和隐式解包可选

基本的XML映射

轻松地将XML字符串转换为XMLMappable

let food = Food(XMLString: xmlString)

或将XMLMappable对象转换为XML字符串

let xmlString = food.toXMLString()

XMLMapper类也可以提供相同的功能

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

let xmlString = XMLMapper().toXMLString(food)

高级映射

设置类中的nodeName属性以更改元素名称

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>

利用XMLMapattributes属性轻松映射XML属性

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

映射元素数组

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

通过实现XMLTransformType协议创建您自己的自定义变换类型

public protocol XMLTransformType {
    associatedtype Object
    associatedtype XML

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

并在映射中使用它

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

通过使用点分隔名称来映射嵌套XML元素

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

注意:当前仅支持嵌套映射

  • 仅适用于仅包含innerText的元素(如上面的示例)和属性

这意味着为了将以下XML中的实际食品价格映射进去

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

您需要使用XMLMappable对象,而不是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
    }
}

因为存在currency属性。同样的情况适用于下面的XML

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

您需要使用类似以下的XMLMappable对象

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

因为存在currency元素。


Swift 4.2和未排序的XML元素

从Swift 4.2开始,XML元素在每次运行应用时很可能有不同的顺序。(这是因为它们由Dictionary表示)

因此,从XMLMapper版本1.5.2开始,您可以使用XMLMapnodesOrder属性映射并更改另一节点内部的节点顺序

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")

注意:如果您想更改节点顺序,请确保在nodesOrder数组中包含您希望在XML字符串中出现的所有节点名称

映射CDATA包装的值

自XMLMapper 2.0.0版本起,增加了对CDATA的支持。默认情况下,现在将CDATA包装的字符串映射为Array<Data>,而不是上一版本中的String。这导致了一个副作用,即不能序列化CDATA包装的值。

例如,使用以下代码

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")

您的结果总是

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

在版本2.0.0中,我们引入了内置的XMLCDATATransform类型,可以像这样使用

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")

并且结果将是

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

破坏性变更在于,除非您使用XMLCDATATransform类型,否则无法进行CDATA包裹值的反序列化。例如,如果您尝试将上述XML映射到以下模型类

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

您最终的description属性值将是nil


注意:如果您自己运行XMLSerializationxmlObject(withString:encoding:options:)函数,并将包括cdataAsString选项在内的default集合作为options传递,则可以更改默认行为。

例如,以下代码将有效:

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映射示例

映射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>

到类

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子规格

注意:由于依赖于AlamofireRequests子规格有不同的最低部署目标。目前为iOS 10.0+ / macOS 10.12+ / tvOS 10.0+ / watchOS 3.0+

使用Alamofire轻松创建并发送带有XML主体的请求(已添加缺失的XMLEncoding结构)

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

还使用Alamofire扩展将XML响应映射到XMLMappable对象。例如,一个URL返回以下CD目录

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

按照以下方式映射响应

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

CDCatalog对象看起来可能像这样

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

最后但同样重要的是,再次使用Alamofire轻松创建并发送SOAP请求

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"))

请求看起来可能像这样

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>

添加动作参数就像子类化SOAPMessage类一样简单。

class MySOAPMessage: SOAPMessage {

    // Custom properties

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

        // Map the custom properties
    }
}

还要指定端点使用的SOAP版本,如下所示

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

然后请求将变为这样

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>

不幸的是,目前没有简单的方法可以映射SOAP响应,除了创建您自己的XMLMappable对象(至少不是现在)

通讯

  • 如果您需要帮助,请使用Stack Overflow。(标签'xmlmapper')
  • 如果您想问一个通用问题,请使用Stack Overflow
  • 如果您发现了一个错误,请提交一个问题。
  • 如果您有一个功能请求,请提交一个问题。

安装

CocoaPods

XMLMapper 可以通过 CocoaPods 获取。要安装它,只需将以下行添加到您的 Podfile

pod 'XMLMapper'

要安装 Requests 子规格,请将以下行添加到您的 Podfile

pod 'XMLMapper/Requests'

Carthage

要使用 Carthage 将 XMLMapper 整合到您的 Xcode 项目中,请将以下行添加到您的 Cartfile

github "gcharita/XMLMapper" ~> 1.6

Swift Package Manager

要将 XMLMapper 添加到基于 Swift Package Manager 的项目,请添加以下内容

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

到您的 Package.swiftdependencies 值。

特别感谢

许可证

XMLMapper可在MIT许可证下使用。更多信息,请参阅许可证文件。