diff --git a/README.md b/README.md
index 6464f697..37b7cb76 100644
--- a/README.md
+++ b/README.md
@@ -188,7 +188,7 @@ Given:
The below will return "123".
```swift
-xml["root"]["catalog"]["book"][1].element?.attributes["id"]
+xml["root"]["catalog"]["book"][1].element?.attribute(by: "id")?.text
```
Alternatively, you can look up an element with specific attributes. The below will return "John".
@@ -290,17 +290,17 @@ Given:
```xml
-
+
Book A
12.5
2015
-
+
Book B
10
1988
-
+
Book C
8.33
1990
@@ -317,13 +317,15 @@ struct Book: XMLIndexerDeserializable {
let price: Double
let year: Int
let amount: Int?
+ let isbn: Int
static func deserialize(node: XMLIndexer) throws -> Book {
return try Book(
title: node["title"].value(),
price: node["price"].value(),
year: node["year"].value(),
- amount: node["amount"].value()
+ amount: node["amount"].value(),
+ isbn: node.value(ofAttribute: "isbn")
)
}
}
@@ -337,9 +339,11 @@ let books: [Book] = try xml["root"]["books"]["book"].value()
-Built-in, leaf-nodes converters support `Int`, `Double`, `Float`, `Bool`, and `String` values (both non- and -optional variants). Custom converters can be added by implementing `XMLElementDeserializable`.
+You can convert any XML to your custom type by implementing `XMLIndexerDeserializable` for any non-leaf node (e.g. `` in the example above).
+
+For leaf nodes (e.g. `` in the example above), built-in converters support `Int`, `Double`, `Float`, `Bool`, and `String` values (both non- and -optional variants). Custom converters can be added by implementing `XMLElementDeserializable`.
-You can convert any XML to your custom type by implementing `XMLIndexerDeserializable`.
+For attributes (e.g. `isbn=` in the example above), built-in converters support the same types as above, and additional converters can be added by implementing `XMLAttributeDeserializable`.
Types conversion supports error handling, optionals and arrays. For more examples, look into `SWXMLHashTests.swift` or play with types conversion directly in the Swift playground.
diff --git a/Source/Info.plist b/Source/Info.plist
index 78122054..d2035fbf 100644
--- a/Source/Info.plist
+++ b/Source/Info.plist
@@ -15,7 +15,7 @@
CFBundlePackageType
FMWK
CFBundleShortVersionString
- 1.0
+ 2.0
CFBundleSignature
????
CFBundleVersion
diff --git a/Source/SWXMLHash+TypeConversion.swift b/Source/SWXMLHash+TypeConversion.swift
index a4e1beff..0394f866 100644
--- a/Source/SWXMLHash+TypeConversion.swift
+++ b/Source/SWXMLHash+TypeConversion.swift
@@ -6,6 +6,8 @@
//
//
+// swiftlint:disable file_length
+
import Foundation
// MARK: - XMLIndexerDeserializable
@@ -55,9 +57,135 @@ public extension XMLElementDeserializable {
}
}
+// MARK: - XMLAttributeDeserializable
+
+/// Provides XMLAttribute deserialization / type transformation support
+public protocol XMLAttributeDeserializable {
+ static func deserialize(attribute: XMLAttribute) throws -> Self
+}
+
+/// Provides XMLAttribute deserialization / type transformation support
+public extension XMLAttributeDeserializable {
+ /**
+ A default implementation that will throw an error if it is called
+
+ - parameters:
+ - attribute: The XMLAttribute to be deserialized
+ - throws: an XMLDeserializationError.ImplementationIsMissing if no implementation is found
+ - returns: this won't ever return because of the error being thrown
+ */
+ static func deserialize(attribute: XMLAttribute) throws -> Self {
+ throw XMLDeserializationError.ImplementationIsMissing(
+ method: "XMLAttributeDeserializable(element: XMLAttribute)")
+ }
+}
+
+// MARK: - XMLIndexer Extensions
public extension XMLIndexer {
+ // MARK: - XMLAttributeDeserializable
+
+ /**
+ Attempts to deserialize the value of the specified attribute of the current XMLIndexer
+ element to `T`
+
+ - parameter attr: The attribute to deserialize
+ - throws: an XMLDeserializationError if there is a problem with deserialization
+ - returns: The deserialized `T` value
+ */
+ func value(ofAttribute attr: String) throws -> T {
+ switch self {
+ case .Element(let element):
+ return try element.value(ofAttribute: attr)
+ case .Stream(let opStream):
+ return try opStream.findElements().value(ofAttribute: attr)
+ default:
+ throw XMLDeserializationError.NodeIsInvalid(node: self)
+ }
+ }
+
+ /**
+ Attempts to deserialize the value of the specified attribute of the current XMLIndexer
+ element to `T?`
+
+ - parameter attr: The attribute to deserialize
+ - returns: The deserialized `T?` value, or nil if the attribute does not exist
+ */
+ func value(ofAttribute attr: String) -> T? {
+ switch self {
+ case .Element(let element):
+ return element.value(ofAttribute: attr)
+ case .Stream(let opStream):
+ return opStream.findElements().value(ofAttribute: attr)
+ default:
+ return nil
+ }
+ }
+
+ /**
+ Attempts to deserialize the value of the specified attribute of the current XMLIndexer
+ element to `[T]`
+
+ - parameter attr: The attribute to deserialize
+ - throws: an XMLDeserializationError if there is a problem with deserialization
+ - returns: The deserialized `[T]` value
+ */
+ func value(ofAttribute attr: String) throws -> [T] {
+ switch self {
+ case .List(let elements):
+ return try elements.map { try $0.value(ofAttribute: attr) }
+ case .Element(let element):
+ return try [element].map { try $0.value(ofAttribute: attr) }
+ case .Stream(let opStream):
+ return try opStream.findElements().value(ofAttribute: attr)
+ default:
+ throw XMLDeserializationError.NodeIsInvalid(node: self)
+ }
+ }
+
+ /**
+ Attempts to deserialize the value of the specified attribute of the current XMLIndexer
+ element to `[T]?`
+
+ - parameter attr: The attribute to deserialize
+ - throws: an XMLDeserializationError if there is a problem with deserialization
+ - returns: The deserialized `[T]?` value
+ */
+ func value(ofAttribute attr: String) throws -> [T]? {
+ switch self {
+ case .List(let elements):
+ return try elements.map { try $0.value(ofAttribute: attr) }
+ case .Element(let element):
+ return try [element].map { try $0.value(ofAttribute: attr) }
+ case .Stream(let opStream):
+ return try opStream.findElements().value(ofAttribute: attr)
+ default:
+ return nil
+ }
+ }
+
+ /**
+ Attempts to deserialize the value of the specified attribute of the current XMLIndexer
+ element to `[T?]`
+
+ - parameter attr: The attribute to deserialize
+ - throws: an XMLDeserializationError if there is a problem with deserialization
+ - returns: The deserialized `[T?]` value
+ */
+ func value(ofAttribute attr: String) throws -> [T?] {
+ switch self {
+ case .List(let elements):
+ return elements.map { $0.value(ofAttribute: attr) }
+ case .Element(let element):
+ return [element].map { $0.value(ofAttribute: attr) }
+ case .Stream(let opStream):
+ return try opStream.findElements().value(ofAttribute: attr)
+ default:
+ throw XMLDeserializationError.NodeIsInvalid(node: self)
+ }
+ }
+
// MARK: - XMLElementDeserializable
/**
@@ -71,7 +199,7 @@ public extension XMLIndexer {
case .Element(let element):
return try T.deserialize(element)
case .Stream(let opStream):
- return try! opStream.findElements().value()
+ return try opStream.findElements().value()
default:
throw XMLDeserializationError.NodeIsInvalid(node: self)
}
@@ -88,7 +216,7 @@ public extension XMLIndexer {
case .Element(let element):
return try T.deserialize(element)
case .Stream(let opStream):
- return try! opStream.findElements().value()
+ return try opStream.findElements().value()
default:
return nil
}
@@ -107,7 +235,7 @@ public extension XMLIndexer {
case .Element(let element):
return try [element].map { try T.deserialize($0) }
case .Stream(let opStream):
- return try! opStream.findElements().value()
+ return try opStream.findElements().value()
default:
return []
}
@@ -126,7 +254,7 @@ public extension XMLIndexer {
case .Element(let element):
return try [element].map { try T.deserialize($0) }
case .Stream(let opStream):
- return try! opStream.findElements().value()
+ return try opStream.findElements().value()
default:
return nil
}
@@ -145,7 +273,7 @@ public extension XMLIndexer {
case .Element(let element):
return try [element].map { try T.deserialize($0) }
case .Stream(let opStream):
- return try! opStream.findElements().value()
+ return try opStream.findElements().value()
default:
return []
}
@@ -165,7 +293,7 @@ public extension XMLIndexer {
case .Element:
return try T.deserialize(self)
case .Stream(let opStream):
- return try! opStream.findElements().value()
+ return try opStream.findElements().value()
default:
throw XMLDeserializationError.NodeIsInvalid(node: self)
}
@@ -182,7 +310,7 @@ public extension XMLIndexer {
case .Element:
return try T.deserialize(self)
case .Stream(let opStream):
- return try! opStream.findElements().value()
+ return try opStream.findElements().value()
default:
return nil
}
@@ -201,7 +329,7 @@ public extension XMLIndexer {
case .Element(let element):
return try [element].map { try T.deserialize( XMLIndexer($0) ) }
case .Stream(let opStream):
- return try! opStream.findElements().value()
+ return try opStream.findElements().value()
default:
throw XMLDeserializationError.NodeIsInvalid(node: self)
}
@@ -220,7 +348,7 @@ public extension XMLIndexer {
case .Element(let element):
return try [element].map { try T.deserialize( XMLIndexer($0) ) }
case .Stream(let opStream):
- return try! opStream.findElements().value()
+ return try opStream.findElements().value()
default:
throw XMLDeserializationError.NodeIsInvalid(node: self)
}
@@ -235,31 +363,73 @@ public extension XMLIndexer {
func value() throws -> [T?] {
switch self {
case .List(let elements):
- return try elements.map { try T.deserialize( XMLIndexer($0) ) }
+ return try elements.map { try T.deserialize( XMLIndexer($0) ) }
case .Element(let element):
return try [element].map { try T.deserialize( XMLIndexer($0) ) }
case .Stream(let opStream):
- return try! opStream.findElements().value()
+ return try opStream.findElements().value()
default:
throw XMLDeserializationError.NodeIsInvalid(node: self)
}
}
}
-private extension XMLElement {
- func nonEmptyTextOrThrow() throws -> String {
+// MARK: - XMLElement Extensions
+
+extension XMLElement {
+
+ /**
+ Attempts to deserialize the specified attribute of the current XMLElement to `T`
+
+ - parameter attr: The attribute to deserialize
+ - throws: an XMLDeserializationError if there is a problem with deserialization
+ - returns: The deserialized `T` value
+ */
+ public func value(ofAttribute attr: String) throws -> T {
+ if let attr = self.attribute(by: attr) {
+ return try T.deserialize(attr)
+ } else {
+ throw XMLDeserializationError.AttributeDoesNotExist(element: self, attribute: attr)
+ }
+ }
+
+ /**
+ Attempts to deserialize the specified attribute of the current XMLElement to `T?`
+
+ - parameter attr: The attribute to deserialize
+ - returns: The deserialized `T?` value, or nil if the attribute does not exist.
+ */
+ public func value(ofAttribute attr: String) -> T? {
+ if let attr = self.attribute(by: attr) {
+ return try? T.deserialize(attr)
+ } else {
+ return nil
+ }
+ }
+
+ /**
+ Gets the text associated with this element, or throws an exception if the text is empty
+
+ - throws: XMLDeserializationError.NodeHasNoValue if the element text is empty
+ - returns: The element text
+ */
+ private func nonEmptyTextOrThrow() throws -> String {
if let text = self.text where !text.characters.isEmpty {
return text
} else { throw XMLDeserializationError.NodeHasNoValue }
}
}
+// MARK: - XMLDeserializationError
+
/// The error that is thrown if there is a problem with deserialization
public enum XMLDeserializationError: ErrorType, CustomStringConvertible {
case ImplementationIsMissing(method: String)
case NodeIsInvalid(node: XMLIndexer)
case NodeHasNoValue
case TypeConversionFailed(type: String, element: XMLElement)
+ case AttributeDoesNotExist(element: XMLElement, attribute: String)
+ case AttributeDeserializationFailed(type: String, attribute: XMLAttribute)
/// The text description for the error thrown
public var description: String {
@@ -272,6 +442,10 @@ public enum XMLDeserializationError: ErrorType, CustomStringConvertible {
return "This node is empty"
case .TypeConversionFailed(let type, let node):
return "Can't convert node \(node) to value of type \(type)"
+ case .AttributeDoesNotExist(let element, let attribute):
+ return "Element \(element) does not contain attribute: \(attribute)"
+ case .AttributeDeserializationFailed(let type, let attribute):
+ return "Can't convert attribute \(attribute) to value of type \(type)"
}
}
}
@@ -279,7 +453,7 @@ public enum XMLDeserializationError: ErrorType, CustomStringConvertible {
// MARK: - Common types deserialization
-extension String: XMLElementDeserializable {
+extension String: XMLElementDeserializable, XMLAttributeDeserializable {
/**
Attempts to deserialize XML element content to a String
@@ -289,15 +463,24 @@ extension String: XMLElementDeserializable {
- returns: the deserialized String value
*/
public static func deserialize(element: XMLElement) throws -> String {
- guard let text = element.text
- else {
+ guard let text = element.text else {
throw XMLDeserializationError.TypeConversionFailed(type: "String", element: element)
}
return text
}
+
+ /**
+ Attempts to deserialize XML Attribute content to a String
+
+ - parameter attribute: the XMLAttribute to be deserialized
+ - returns: the deserialized String value
+ */
+ public static func deserialize(attribute: XMLAttribute) -> String {
+ return attribute.text
+ }
}
-extension Int: XMLElementDeserializable {
+extension Int: XMLElementDeserializable, XMLAttributeDeserializable {
/**
Attempts to deserialize XML element content to a Int
@@ -307,13 +490,30 @@ extension Int: XMLElementDeserializable {
- returns: the deserialized Int value
*/
public static func deserialize(element: XMLElement) throws -> Int {
- guard let value = Int(try element.nonEmptyTextOrThrow())
- else { throw XMLDeserializationError.TypeConversionFailed(type: "Int", element: element) }
+ guard let value = Int(try element.nonEmptyTextOrThrow()) else {
+ throw XMLDeserializationError.TypeConversionFailed(type: "Int", element: element)
+ }
+ return value
+ }
+
+ /**
+ Attempts to deserialize XML attribute content to an Int
+
+ - parameter attribute: The XMLAttribute to be deserialized
+ - throws: an XMLDeserializationError.AttributeDeserializationFailed if the attribute cannot be
+ deserialized
+ - returns: the deserialized Int value
+ */
+ public static func deserialize(attribute: XMLAttribute) throws -> Int {
+ guard let value = Int(attribute.text) else {
+ throw XMLDeserializationError.AttributeDeserializationFailed(
+ type: "Int", attribute: attribute)
+ }
return value
}
}
-extension Double: XMLElementDeserializable {
+extension Double: XMLElementDeserializable, XMLAttributeDeserializable {
/**
Attempts to deserialize XML element content to a Double
@@ -323,15 +523,30 @@ extension Double: XMLElementDeserializable {
- returns: the deserialized Double value
*/
public static func deserialize(element: XMLElement) throws -> Double {
- guard let value = Double(try element.nonEmptyTextOrThrow())
- else {
+ guard let value = Double(try element.nonEmptyTextOrThrow()) else {
throw XMLDeserializationError.TypeConversionFailed(type: "Double", element: element)
}
return value
}
+
+ /**
+ Attempts to deserialize XML attribute content to a Double
+
+ - parameter attribute: The XMLAttribute to be deserialized
+ - throws: an XMLDeserializationError.AttributeDeserializationFailed if the attribute cannot be
+ deserialized
+ - returns: the deserialized Double value
+ */
+ public static func deserialize(attribute: XMLAttribute) throws -> Double {
+ guard let value = Double(attribute.text) else {
+ throw XMLDeserializationError.AttributeDeserializationFailed(
+ type: "Double", attribute: attribute)
+ }
+ return value
+ }
}
-extension Float: XMLElementDeserializable {
+extension Float: XMLElementDeserializable, XMLAttributeDeserializable {
/**
Attempts to deserialize XML element content to a Float
@@ -341,19 +556,37 @@ extension Float: XMLElementDeserializable {
- returns: the deserialized Float value
*/
public static func deserialize(element: XMLElement) throws -> Float {
- guard let value = Float(try element.nonEmptyTextOrThrow())
- else { throw XMLDeserializationError.TypeConversionFailed(type: "Float", element: element) }
+ guard let value = Float(try element.nonEmptyTextOrThrow()) else {
+ throw XMLDeserializationError.TypeConversionFailed(type: "Float", element: element)
+ }
+ return value
+ }
+
+ /**
+ Attempts to deserialize XML attribute content to a Float
+
+ - parameter attribute: The XMLAttribute to be deserialized
+ - throws: an XMLDeserializationError.AttributeDeserializationFailed if the attribute cannot be
+ deserialized
+ - returns: the deserialized Float value
+ */
+ public static func deserialize(attribute: XMLAttribute) throws -> Float {
+ guard let value = Float(attribute.text) else {
+ throw XMLDeserializationError.AttributeDeserializationFailed(
+ type: "Float", attribute: attribute)
+ }
return value
}
}
-extension Bool: XMLElementDeserializable {
+extension Bool: XMLElementDeserializable, XMLAttributeDeserializable {
+ // swiftlint:disable line_length
/**
- Attempts to deserialize XML element content to a Bool. This uses NSString's 'boolValue' described
- [here](https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/#//apple_ref/occ/instp/NSString/boolValue)
+ Attempts to deserialize XML element content to a Bool. This uses NSString's 'boolValue'
+ described [here](https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/#//apple_ref/occ/instp/NSString/boolValue)
- parameters:
- - element: the XMLElement to be deserialized
+ - element: the XMLElement to be deserialized
- throws: an XMLDeserializationError.TypeConversionFailed if the element cannot be deserialized
- returns: the deserialized Bool value
*/
@@ -361,4 +594,19 @@ extension Bool: XMLElementDeserializable {
let value = Bool(NSString(string: try element.nonEmptyTextOrThrow()).boolValue)
return value
}
+
+ /**
+ Attempts to deserialize XML attribute content to a Bool. This uses NSString's 'boolValue'
+ described [here](https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/#//apple_ref/occ/instp/NSString/boolValue)
+
+ - parameter attribute: The XMLAttribute to be deserialized
+ - throws: an XMLDeserializationError.AttributeDeserializationFailed if the attribute cannot be
+ deserialized
+ - returns: the deserialized Bool value
+ */
+ public static func deserialize(attribute: XMLAttribute) throws -> Bool {
+ let value = Bool(NSString(string: attribute.text).boolValue)
+ return value
+ }
+ // swiftlint:enable line_length
}
diff --git a/Source/SWXMLHash.swift b/Source/SWXMLHash.swift
index da75fc99..dd9e4f52 100644
--- a/Source/SWXMLHash.swift
+++ b/Source/SWXMLHash.swift
@@ -416,18 +416,15 @@ public enum XMLIndexer: SequenceType {
let match = opStream.findElements()
return try match.withAttr(attr, value)
case .List(let list):
- if let elem = list.filter({$0.attributes[attr] == value}).first {
+ if let elem = list.filter({$0.attribute(by: attr)?.text == value}).first {
return .Element(elem)
}
throw Error.AttributeValue(attr: attr, value: value)
case .Element(let elem):
- if let attr = elem.attributes[attr] {
- if attr == value {
- return .Element(elem)
- }
- throw Error.AttributeValue(attr: attr, value: value)
+ if elem.attribute(by: attr)?.text == value {
+ return .Element(elem)
}
- fallthrough
+ throw Error.AttributeValue(attr: attr, value: value)
default:
throw Error.Attribute(attr: attr)
}
@@ -614,23 +611,46 @@ extension XMLIndexer.Error: CustomStringConvertible {
}
/// Models content for an XML doc, whether it is text or XML
-public protocol XMLContent: CustomStringConvertible {
-}
+public protocol XMLContent: CustomStringConvertible { }
/// Models a text element
public class TextElement: XMLContent {
+ /// The underlying text value
public let text: String
init(text: String) {
self.text = text
}
}
+public struct XMLAttribute {
+ public let name: String
+ public let text: String
+ init(name: String, text: String) {
+ self.name = name
+ self.text = text
+ }
+}
+
/// Models an XML element, including name, text and attributes
public class XMLElement: XMLContent {
/// The name of the element
public let name: String
+
/// The attributes of the element
- public var attributes = [String:String]()
+ @available(*, deprecated, message="See `allAttributes` instead, which introduces the XMLAttribute type over a simple String type")
+ public var attributes: [String:String] {
+ var attrMap = [String: String]()
+ for (name, attr) in allAttributes {
+ attrMap[name] = attr.text
+ }
+ return attrMap
+ }
+
+ public var allAttributes = [String:XMLAttribute]()
+
+ public func attribute(by name: String) -> XMLAttribute? {
+ return allAttributes[name]
+ }
/// The inner text of the element, if it exists
public var text: String? {
@@ -676,8 +696,8 @@ public class XMLElement: XMLContent {
for (keyAny, valueAny) in attributes {
if let key = keyAny as? String,
- let value = valueAny as? String {
- element.attributes[key] = value
+ value = valueAny as? String {
+ element.allAttributes[key] = XMLAttribute(name: key, text: value)
}
}
@@ -698,17 +718,17 @@ extension TextElement: CustomStringConvertible {
}
}
+extension XMLAttribute: CustomStringConvertible {
+ /// The textual representation of an `XMLAttribute` instance.
+ public var description: String {
+ return "\(name)=\"\(text)\""
+ }
+}
+
extension XMLElement: CustomStringConvertible {
/// The tag, attributes and content for a `XMLElement` instance (content)
public var description: String {
- var attributesStringList = [String]()
- if !attributes.isEmpty {
- for (key, val) in attributes {
- attributesStringList.append("\(key)=\"\(val)\"")
- }
- }
-
- var attributesString = attributesStringList.joinWithSeparator(" ")
+ var attributesString = allAttributes.map { $0.1.description }.joinWithSeparator(" ")
if !attributesString.isEmpty {
attributesString = " " + attributesString
}
diff --git a/Tests/LazyTypesConversionTests.swift b/Tests/LazyTypesConversionTests.swift
index c2012f89..1525fa07 100644
--- a/Tests/LazyTypesConversionTests.swift
+++ b/Tests/LazyTypesConversionTests.swift
@@ -40,6 +40,7 @@ class LazyTypesConversionTests: XCTestCase {
" the name of basic item" +
" 99.14" +
" " +
+ " " +
""
override func setUp() {
@@ -50,4 +51,9 @@ class LazyTypesConversionTests: XCTestCase {
let value: String = try! parser!["root"]["string"].value()
XCTAssertEqual(value, "the string value")
}
+
+ func testShouldConvertAttributeToNonOptional() {
+ let value: Int = try! parser!["root"]["attribute"].value(ofAttribute: "int")
+ XCTAssertEqual(value, 1)
+ }
}
diff --git a/Tests/LazyXMLParsingTests.swift b/Tests/LazyXMLParsingTests.swift
index 8998c12a..6278d459 100644
--- a/Tests/LazyXMLParsingTests.swift
+++ b/Tests/LazyXMLParsingTests.swift
@@ -49,6 +49,7 @@ class LazyXMLParsingTests: XCTestCase {
func testShouldBeAbleToParseAttributes() {
XCTAssertEqual(xml!["root"]["catalog"]["book"][1].element?.attributes["id"], "bk102")
+ XCTAssertEqual(xml!["root"]["catalog"]["book"][1].element?.attribute(by: "id")?.text, "bk102")
}
func testShouldBeAbleToLookUpElementsByNameAndAttribute() {
diff --git a/Tests/TypeConversionArrayOfNonPrimitiveTypesTests.swift b/Tests/TypeConversionArrayOfNonPrimitiveTypesTests.swift
index 07877921..8fd3d482 100644
--- a/Tests/TypeConversionArrayOfNonPrimitiveTypesTests.swift
+++ b/Tests/TypeConversionArrayOfNonPrimitiveTypesTests.swift
@@ -44,7 +44,8 @@ class TypeConversionArrayOfNonPrimitiveTypesTests: XCTestCase {
" item 3" +
" 3" +
" " +
- "" +
+ "" +
+ "" +
" " +
" item 1" +
" 1" +
@@ -57,13 +58,29 @@ class TypeConversionArrayOfNonPrimitiveTypesTests: XCTestCase {
" 3" +
" " +
"" +
+ "" +
+ " " +
+ " " +
+ " " +
+ "" +
+ "" +
+ " " +
+ " " + // it's missing the name attribute
+ " " +
+ "" +
""
let correctBasicItems = [
BasicItem(name: "item 1", price: 1),
BasicItem(name: "item 2", price: 2),
- BasicItem(name: "item 3", price: 3),
- ]
+ BasicItem(name: "item 3", price: 3)
+ ]
+
+ let correctAttributeItems = [
+ AttributeItem(name: "attr 1", price: 1.1),
+ AttributeItem(name: "attr 2", price: 2.2),
+ AttributeItem(name: "attr 3", price: 3.3)
+ ]
override func setUp() {
parser = SWXMLHash.parse(xmlWithArraysOfTypes)
@@ -110,4 +127,46 @@ class TypeConversionArrayOfNonPrimitiveTypesTests: XCTestCase {
}
}
}
+
+ func testShouldConvertArrayOfGoodAttributeItemsToNonOptional() {
+ let value: [AttributeItem] = try! parser!["root"]["arrayOfGoodAttributeItems"]["attributeItem"].value()
+ XCTAssertEqual(value, correctAttributeItems)
+ }
+
+ func testShouldConvertArrayOfGoodAttributeItemsToOptional() {
+ let value: [AttributeItem]? = try! parser!["root"]["arrayOfGoodAttributeItems"]["attributeItem"].value()
+ XCTAssertEqual(value!, correctAttributeItems)
+ }
+
+ func testShouldConvertArrayOfGoodAttributeItemsToArrayOfOptionals() {
+ let value: [AttributeItem?] = try! parser!["root"]["arrayOfGoodAttributeItems"]["attributeItem"].value()
+ XCTAssertEqual(value.flatMap({ $0 }), correctAttributeItems)
+ }
+
+ func testShouldThrowWhenConvertingArrayOfBadAttributeItemsToNonOptional() {
+ XCTAssertThrowsError(try (parser!["root"]["arrayOfBadAttributeItems"]["attributeItem"].value() as [AttributeItem])) { error in
+ guard error is XMLDeserializationError else {
+ XCTFail("Wrong type of error")
+ return
+ }
+ }
+ }
+
+ func testShouldThrowWhenConvertingArrayOfBadAttributeItemsToOptional() {
+ XCTAssertThrowsError(try (parser!["root"]["arrayOfBadAttributeItems"]["attributeItem"].value() as [AttributeItem]?)) { error in
+ guard error is XMLDeserializationError else {
+ XCTFail("Wrong type of error")
+ return
+ }
+ }
+ }
+
+ func testShouldThrowWhenConvertingArrayOfBadAttributeItemsToArrayOfOptionals() {
+ XCTAssertThrowsError(try (parser!["root"]["arrayOfBadAttributeItems"]["attributeItem"].value() as [AttributeItem?])) { error in
+ guard error is XMLDeserializationError else {
+ XCTFail("Wrong type of error")
+ return
+ }
+ }
+ }
}
diff --git a/Tests/TypeConversionBasicTypesTests.swift b/Tests/TypeConversionBasicTypesTests.swift
index c8bd6392..226e19d7 100644
--- a/Tests/TypeConversionBasicTypesTests.swift
+++ b/Tests/TypeConversionBasicTypesTests.swift
@@ -41,6 +41,8 @@ class TypeConversionBasicTypesTests: XCTestCase {
" the name of basic item" +
" 99.14" +
" " +
+ " " +
+ " " +
""
override func setUp() {
@@ -81,6 +83,30 @@ class TypeConversionBasicTypesTests: XCTestCase {
XCTAssertNil(value)
}
+ func testShouldConvertAttributeToNonOptional() {
+ let value: String = try! parser!["root"]["attr"].value(ofAttribute: "string")
+ XCTAssertEqual(value, "stringValue")
+ }
+
+ func testShouldConvertAttributeToOptional() {
+ let value: String? = parser!["root"]["attr"].value(ofAttribute: "string")
+ XCTAssertEqual(value, "stringValue")
+ }
+
+ func testShouldThrowWhenConvertingMissingAttributeToNonOptional() {
+ XCTAssertThrowsError(try (parser!["root"]["attr"].value(ofAttribute: "missing") as String)) { error in
+ guard error is XMLDeserializationError else {
+ XCTFail("Wrong type of error")
+ return
+ }
+ }
+ }
+
+ func testShouldConvertMissingAttributeToOptional() {
+ let value: String? = parser!["root"]["attr"].value(ofAttribute: "missing")
+ XCTAssertNil(value)
+ }
+
func testIntShouldConvertValueToNonOptional() {
let value: Int = try! parser!["root"]["int"].value()
XCTAssertEqual(value, 100)
@@ -123,6 +149,16 @@ class TypeConversionBasicTypesTests: XCTestCase {
XCTAssertNil(value)
}
+ func testIntShouldConvertAttributeToNonOptional() {
+ let value: Int = try! parser!["root"]["attr"].value(ofAttribute: "int")
+ XCTAssertEqual(value, 200)
+ }
+
+ func testIntShouldConvertAttributeToOptional() {
+ let value: Int? = parser!["root"]["attr"].value(ofAttribute: "int")
+ XCTAssertEqual(value, 200)
+ }
+
func testDoubleShouldConvertValueToNonOptional() {
let value: Double = try! parser!["root"]["double"].value()
XCTAssertEqual(value, 100.45)
@@ -165,6 +201,16 @@ class TypeConversionBasicTypesTests: XCTestCase {
XCTAssertNil(value)
}
+ func testDoubleShouldConvertAttributeToNonOptional() {
+ let value: Double = try! parser!["root"]["attr"].value(ofAttribute: "double")
+ XCTAssertEqual(value, 200.15)
+ }
+
+ func testDoubleShouldConvertAttributeToOptional() {
+ let value: Double? = parser!["root"]["attr"].value(ofAttribute: "double")
+ XCTAssertEqual(value, 200.15)
+ }
+
func testFloatShouldConvertValueToNonOptional() {
let value: Float = try! parser!["root"]["float"].value()
XCTAssertEqual(value, 44.12)
@@ -207,6 +253,16 @@ class TypeConversionBasicTypesTests: XCTestCase {
XCTAssertNil(value)
}
+ func testFloatShouldConvertAttributeToNonOptional() {
+ let value: Float = try! parser!["root"]["attr"].value(ofAttribute: "float")
+ XCTAssertEqual(value, 205.42)
+ }
+
+ func testFloatShouldConvertAttributeToOptional() {
+ let value: Float? = parser!["root"]["attr"].value(ofAttribute: "float")
+ XCTAssertEqual(value, 205.42)
+ }
+
func testBoolShouldConvertValueToNonOptional() {
let value1: Bool = try! parser!["root"]["bool1"].value()
let value2: Bool = try! parser!["root"]["bool2"].value()
@@ -253,6 +309,16 @@ class TypeConversionBasicTypesTests: XCTestCase {
XCTAssertNil(value)
}
+ func testBoolShouldConvertAttributeToNonOptional() {
+ let value: Bool = try! parser!["root"]["attr"].value(ofAttribute: "bool1")
+ XCTAssertEqual(value, false)
+ }
+
+ func testBoolShouldConvertAttributeToOptional() {
+ let value: Bool? = parser!["root"]["attr"].value(ofAttribute: "bool2")
+ XCTAssertEqual(value, true)
+ }
+
let correctBasicItem = BasicItem(name: "the name of basic item", price: 99.14)
func testBasicItemShouldConvertBasicitemToNonOptional() {
@@ -296,6 +362,50 @@ class TypeConversionBasicTypesTests: XCTestCase {
let value: BasicItem? = try! parser!["root"]["missing"].value()
XCTAssertNil(value)
}
+
+ let correctAttributeItem = AttributeItem(name: "the name of attribute item", price: 19.99)
+
+ func testAttributeItemShouldConvertAttributeItemToNonOptional() {
+ let value: AttributeItem = try! parser!["root"]["attributeItem"].value()
+ XCTAssertEqual(value, correctAttributeItem)
+ }
+
+ func testAttributeItemShouldThrowWhenConvertingEmptyToNonOptional() {
+ XCTAssertThrowsError(try (parser!["root"]["empty"].value() as AttributeItem)) { error in
+ guard error is XMLDeserializationError else {
+ XCTFail("Wrong type of error")
+ return
+ }
+ }
+ }
+
+ func testAttributeItemShouldThrowWhenConvertingMissingToNonOptional() {
+ XCTAssertThrowsError(try (parser!["root"]["missing"].value() as AttributeItem)) { error in
+ guard error is XMLDeserializationError else {
+ XCTFail("Wrong type of error")
+ return
+ }
+ }
+ }
+
+ func testAttributeItemShouldConvertAttributeItemToOptional() {
+ let value: AttributeItem? = try! parser!["root"]["attributeItem"].value()
+ XCTAssertEqual(value, correctAttributeItem)
+ }
+
+ func testAttributeItemShouldConvertEmptyToOptional() {
+ XCTAssertThrowsError(try (parser!["root"]["empty"].value() as AttributeItem?)) { error in
+ guard error is XMLDeserializationError else {
+ XCTFail("Wrong type of error")
+ return
+ }
+ }
+ }
+
+ func testAttributeItemShouldConvertMissingToOptional() {
+ let value: AttributeItem? = try! parser!["root"]["missing"].value()
+ XCTAssertNil(value)
+ }
}
struct BasicItem: XMLIndexerDeserializable {
@@ -315,3 +425,21 @@ extension BasicItem: Equatable {}
func == (a: BasicItem, b: BasicItem) -> Bool {
return a.name == b.name && a.price == b.price
}
+
+struct AttributeItem: XMLElementDeserializable {
+ let name: String
+ let price: Double
+
+ static func deserialize(element: XMLElement) throws -> AttributeItem {
+ return try AttributeItem(
+ name: element.value(ofAttribute: "name"),
+ price: element.value(ofAttribute: "price")
+ )
+ }
+}
+
+extension AttributeItem: Equatable {}
+
+func == (a: AttributeItem, b: AttributeItem) -> Bool {
+ return a.name == b.name && a.price == b.price
+}
diff --git a/Tests/TypeConversionComplexTypesTests.swift b/Tests/TypeConversionComplexTypesTests.swift
index a4fea34f..9a8c2f78 100644
--- a/Tests/TypeConversionComplexTypesTests.swift
+++ b/Tests/TypeConversionComplexTypesTests.swift
@@ -47,6 +47,11 @@ class TypeConversionComplexTypesTests: XCTestCase {
" 3" +
" " +
" " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
" " +
" " +
""
@@ -58,6 +63,11 @@ class TypeConversionComplexTypesTests: XCTestCase {
BasicItem(name: "item 1", price: 1),
BasicItem(name: "item 2", price: 2),
BasicItem(name: "item 3", price: 3),
+ ],
+ attrs: [
+ AttributeItem(name: "attr1", price: 1.1),
+ AttributeItem(name: "attr2", price: 2.2),
+ AttributeItem(name: "attr3", price: 3.3),
]
)
@@ -112,12 +122,14 @@ struct ComplexItem: XMLIndexerDeserializable {
let name: String
let priceOptional: Double?
let basics: [BasicItem]
+ let attrs: [AttributeItem]
static func deserialize(node: XMLIndexer) throws -> ComplexItem {
return try ComplexItem(
name: node["name"].value(),
priceOptional: node["price"].value(),
- basics: node["basicItems"]["basicItem"].value()
+ basics: node["basicItems"]["basicItem"].value(),
+ attrs: node["attributeItems"]["attributeItem"].value()
)
}
}
@@ -125,5 +137,5 @@ struct ComplexItem: XMLIndexerDeserializable {
extension ComplexItem: Equatable {}
func == (a: ComplexItem, b: ComplexItem) -> Bool {
- return a.name == b.name && a.priceOptional == b.priceOptional && a.basics == b.basics
+ return a.name == b.name && a.priceOptional == b.priceOptional && a.basics == b.basics && a.attrs == b.attrs
}
diff --git a/Tests/TypeConversionPrimitypeTypesTests.swift b/Tests/TypeConversionPrimitypeTypesTests.swift
index f751d22b..566f38a8 100644
--- a/Tests/TypeConversionPrimitypeTypesTests.swift
+++ b/Tests/TypeConversionPrimitypeTypesTests.swift
@@ -39,6 +39,9 @@ class TypeConversionPrimitypeTypesTests: XCTestCase {
"" +
" 0 boom 2 3" +
"" +
+ "" +
+ " " +
+ "" +
"" +
""
@@ -115,6 +118,21 @@ class TypeConversionPrimitypeTypesTests: XCTestCase {
}
}
+ func testShouldConvertArrayOfAttributeIntsToNonOptional() {
+ let value: [Int] = try! parser!["root"]["arrayOfAttributeInts"]["int"].value(ofAttribute: "value")
+ XCTAssertEqual(value, [0, 1, 2, 3])
+ }
+
+ func testShouldConvertArrayOfAttributeIntsToOptional() {
+ let value: [Int]? = try! parser!["root"]["arrayOfAttributeInts"]["int"].value(ofAttribute: "value")
+ XCTAssertEqual(value!, [0, 1, 2, 3])
+ }
+
+ func testShouldConvertArrayOfAttributeIntsToArrayOfOptionals() {
+ let value: [Int?] = try! parser!["root"]["arrayOfAttributeInts"]["int"].value(ofAttribute: "value")
+ XCTAssertEqual(value.flatMap({ $0 }), [0, 1, 2, 3])
+ }
+
func testShouldConvertEmptyArrayOfIntsToNonOptional() {
let value: [Int] = try! parser!["root"]["empty"]["int"].value()
XCTAssertEqual(value, [])
diff --git a/Tests/XMLParsingTests.swift b/Tests/XMLParsingTests.swift
index 274dc38c..a7ce8ee2 100644
--- a/Tests/XMLParsingTests.swift
+++ b/Tests/XMLParsingTests.swift
@@ -49,6 +49,7 @@ class XMLParsingTests: XCTestCase {
func testShouldBeAbleToParseAttributes() {
XCTAssertEqual(xml!["root"]["catalog"]["book"][1].element?.attributes["id"], "bk102")
+ XCTAssertEqual(xml!["root"]["catalog"]["book"][1].element?.attribute(by: "id")?.text, "bk102")
}
func testShouldBeAbleToLookUpElementsByNameAndAttribute() {