XMLMapper 是一个用 Swift 编写的框架,它可以让你轻松地将模型对象(类和结构体)转换为 XML,以及从 XML 转换回来。
要运行示例项目,请克隆代码仓库,然后首先从 Example 目录运行 pod install
。
此属性用于映射 XML 节点的名称
此函数是所有映射定义应该存在的地方。 在解析 XML 时,此函数在成功创建对象后执行。 生成 XML 时,它是对象上调用的唯一函数。
注意:不应直接实现此协议。 应使用 XMLMappable
或 XMLStaticMappable
XMLMapper 使用此可失败的初始化程序来创建对象。 开发人员可以使用它来验证 XML,然后再进行对象序列化。 在函数中返回 nil 将阻止映射发生。 您可以检查存储在 XMLMap
对象中的 XML
来进行验证
required init?(map: XMLMap) {
// check if a required "id" element exists within the XML.
if map.XML["id"] == nil {
return nil
}
}
XMLStaticMappable
是 XMLMappable
的替代方案。 它为开发人员提供了一个静态函数,XMLMapper 使用该函数进行对象初始化,而不是 init?(map: XMLMap)
。
XMLMapper 使用此函数来获取用于映射的对象。 开发人员应在此函数中返回符合 XMLBaseMappable
的对象的实例。 此函数还可以用于
XMLBaseMappable
)以用于映射。 例如,您可以检查 XML 以推断应该用于映射的对象类型如果您需要在扩展中实现 XMLMapper,则需要采用此协议,而不是 XMLMappable
。
为了支持映射,类或结构只需要实现 XMLMappable
协议
var nodeName: String! { get set }
init?(map: XMLMap)
mutating func mapping(map: XMLMap)
XMLMapper 使用 <-
运算符来定义每个属性如何映射到 XML 以及从 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>
Object<T: XMLBaseMappable>
Array<T: XMLBaseMappable>
Set<T: XMLBaseMappable>
Dictionary<String, T: XMLBaseMappable>
Dictionary<String, Array<T: XMLBaseMappable>>
轻松地将 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>
使用 XMLMap
的 attributes
属性轻松映射 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"]
}
注意:目前仅支持嵌套映射
这意味着为了映射以下 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 元素的顺序很可能不同。(发生这种情况是因为它们由一个 Dictionary
表示)
因此,从 XMLMapper 的 1.5.2 版本开始,你可以使用 XMLMap
的 nodesOrder
属性来映射和更改出现在另一个节点内部的节点的顺序
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 字符串中的节点名称
从 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 & 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"]
}
}
你最终会将 nil
作为 description
属性的值。
注意:如果你自己运行 XMLSerialization
的 xmlObject(withString:encoding:options:)
函数,并将 options
作为 default
设置,包括 cdataAsString
选项,则可以更改默认行为。
例如,以下代码将有效
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 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"]
}
}
注意:由于 Alamofire
依赖项,Requests
子模块具有不同的最低部署目标。(目前为 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>
不幸的是,除了创建你自己的 XMLMappable 对象之外,没有简单的方法来映射 SOAP 响应(至少目前还没有)
XMLMapper 可通过 CocoaPods 获得。 要安装它,只需将以下行添加到你的 Podfile
中
pod 'XMLMapper'
要安装 Requests
子模块,请将以下行添加到你的 Podfile
中
pod 'XMLMapper/Requests'
要使用 Carthage 将 XMLMapper 集成到你的 Xcode 项目中,请将以下行添加到你的 Cartfile
中
github "gcharita/XMLMapper" ~> 1.6
要将 XMLMapper 添加到基于 Swift Package Manager 的项目中,请添加以下内容
.package(url: "https://github.com/gcharita/XMLMapper.git", from: "1.6.0")
到你的 Package.swift
的 dependencies
值中。
XMLMapper 在 MIT 许可证下可用。 有关更多信息,请参阅 LICENSE 文件。