Skip to content

Commit

Permalink
Merge pull request #207 from PeeJWeeJ/StringRawRepresentables
Browse files Browse the repository at this point in the history
String raw representables
  • Loading branch information
drmohundro authored Mar 6, 2019
2 parents c07f6d4 + ad550db commit 75a1add
Show file tree
Hide file tree
Showing 5 changed files with 368 additions and 7 deletions.
74 changes: 69 additions & 5 deletions Source/SWXMLHash.swift
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,6 @@ protocol SimpleXmlParser {
#if os(Linux)

extension XMLParserDelegate {

func parserDidStartDocument(_ parser: Foundation.XMLParser) { }
func parserDidEndDocument(_ parser: Foundation.XMLParser) { }

Expand Down Expand Up @@ -303,7 +302,6 @@ class LazyXMLParser: NSObject, SimpleXmlParser, XMLParserDelegate {
namespaceURI: String?,
qualifiedName qName: String?,
attributes attributeDict: [String: String]) {

elementStack.push(elementName)

if !onMatch() {
Expand Down Expand Up @@ -342,7 +340,6 @@ class LazyXMLParser: NSObject, SimpleXmlParser, XMLParserDelegate {
didEndElement elementName: String,
namespaceURI: String?,
qualifiedName qName: String?) {

let match = onMatch()

elementStack.drop()
Expand Down Expand Up @@ -400,7 +397,6 @@ class FullXMLParser: NSObject, SimpleXmlParser, XMLParserDelegate {
namespaceURI: String?,
qualifiedName qName: String?,
attributes attributeDict: [String: String]) {

let currentNode = parentStack
.top()
.addElement(elementName, withAttributes: attributeDict, caseInsensitive: self.options.caseInsensitive)
Expand All @@ -418,7 +414,6 @@ class FullXMLParser: NSObject, SimpleXmlParser, XMLParserDelegate {
didEndElement elementName: String,
namespaceURI: String?,
qualifiedName qName: String?) {

parentStack.drop()
}

Expand Down Expand Up @@ -1043,3 +1038,72 @@ fileprivate extension String {
return str1 == str2
}
}

// MARK: - XMLIndexer String RawRepresentables

/*: Provides XMLIndexer Serialization/Deserialization using String backed RawRepresentables
Added by [PeeJWeeJ](https://github.com/PeeJWeeJ) */
extension XMLIndexer {
/**
Allows for element lookup by matching attribute values
using a String backed RawRepresentables (E.g. `String` backed `enum` cases)

- Note:
Convenience for withAttribute(String, String)

- parameters:
- attr: should the name of the attribute to match on
- value: should be the value of the attribute to match on
- throws: an XMLIndexer.XMLError if an element with the specified attribute isn't found
- returns: instance of XMLIndexer
*/
public func withAttribute<A: RawRepresentable, V: RawRepresentable>(_ attr: A, _ value: V) throws -> XMLIndexer
where A.RawValue == String, V.RawValue == String {
return try withAttribute(attr.rawValue, value.rawValue)
}

/**
Find an XML element at the current level by element name
using a String backed RawRepresentable (E.g. `String` backed `enum` cases)

- Note:
Convenience for byKey(String)

- parameter key: The element name to index by
- returns: instance of XMLIndexer to match the element (or elements) found by key
- throws: Throws an XMLIndexingError.Key if no element was found
*/
public func byKey<K: RawRepresentable>(_ key: K) throws -> XMLIndexer where K.RawValue == String {
return try byKey(key.rawValue)
}

/**
Find an XML element at the current level by element name
using a String backed RawRepresentable (E.g. `String` backed `enum` cases)

- Note:
Convenience for self[String]

- parameter key: The element name to index by
- returns: instance of XMLIndexer to match the element (or elements) found by
*/
public subscript<K: RawRepresentable>(key: K) -> XMLIndexer where K.RawValue == String {
return self[key.rawValue]
}
}

// MARK: - XMLElement String RawRepresentables

/*: Provides XMLIndexer Serialization/Deserialization using String backed RawRepresentables
Added by [PeeJWeeJ](https://github.com/PeeJWeeJ) */
extension XMLElement {
/**
Find an attribute by name using a String backed RawRepresentable (E.g. `String` backed `enum` cases)

- Note:
Convenience for self[String]
*/
public func attribute<N: RawRepresentable>(by name: N) -> XMLAttribute? where N.RawValue == String {
return attribute(by: name.rawValue)
}
}
117 changes: 115 additions & 2 deletions Source/XMLIndexer+XMLIndexerDeserializable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,6 @@ public extension XMLAttributeDeserializable {
// MARK: - XMLIndexer Extensions

public extension XMLIndexer {

// MARK: - XMLAttributeDeserializable

/**
Expand Down Expand Up @@ -392,10 +391,89 @@ public extension XMLIndexer {
}
}

// MARK: - XMLAttributeDeserializable String RawRepresentable

/*: Provides XMLIndexer XMLAttributeDeserializable deserialization from String backed RawRepresentables
Added by [PeeJWeeJ](https://github.com/PeeJWeeJ) */
public extension XMLIndexer {
/**
Attempts to deserialize the value of the specified attribute of the current XMLIndexer
element to `T` using a String backed RawRepresentable (E.g. `String` backed `enum` cases)

- Note:
Convenience for value(ofAttribute: String)

- parameter attr: The attribute to deserialize
- throws: an XMLDeserializationError if there is a problem with deserialization
- returns: The deserialized `T` value
*/
func value<T: XMLAttributeDeserializable, A: RawRepresentable>(ofAttribute attr: A) throws -> T where A.RawValue == String {
return try value(ofAttribute: attr.rawValue)
}

/**
Attempts to deserialize the value of the specified attribute of the current XMLIndexer
element to `T?` using a String backed RawRepresentable (E.g. `String` backed `enum` cases)

- Note:
Convenience for value(ofAttribute: String)

- parameter attr: The attribute to deserialize
- returns: The deserialized `T?` value, or nil if the attribute does not exist
*/
func value<T: XMLAttributeDeserializable, A: RawRepresentable>(ofAttribute attr: A) -> T? where A.RawValue == String {
return value(ofAttribute: attr.rawValue)
}

/**
Attempts to deserialize the value of the specified attribute of the current XMLIndexer
element to `[T]` using a String backed RawRepresentable (E.g. `String` backed `enum` cases)

- Note:
Convenience for value(ofAttribute: String)

- parameter attr: The attribute to deserialize
- throws: an XMLDeserializationError if there is a problem with deserialization
- returns: The deserialized `[T]` value
*/
func value<T: XMLAttributeDeserializable, A: RawRepresentable>(ofAttribute attr: A) throws -> [T] where A.RawValue == String {
return try value(ofAttribute: attr.rawValue)
}

/**
Attempts to deserialize the value of the specified attribute of the current XMLIndexer
element to `[T]?` using a String backed RawRepresentable (E.g. `String` backed `enum` cases)

- Note:
Convenience for value(ofAttribute: String)

- parameter attr: The attribute to deserialize
- throws: an XMLDeserializationError if there is a problem with deserialization
- returns: The deserialized `[T]?` value
*/
func value<T: XMLAttributeDeserializable, A: RawRepresentable>(ofAttribute attr: A) throws -> [T]? where A.RawValue == String {
return try value(ofAttribute: attr.rawValue)
}

/**
Attempts to deserialize the value of the specified attribute of the current XMLIndexer
element to `[T?]` using a String backed RawRepresentable (E.g. `String` backed `enum` cases)

- Note:
Convenience for value(ofAttribute: String)

- parameter attr: The attribute to deserialize
- throws: an XMLDeserializationError if there is a problem with deserialization
- returns: The deserialized `[T?]` value
*/
func value<T: XMLAttributeDeserializable, A: RawRepresentable>(ofAttribute attr: A) throws -> [T?] where A.RawValue == String {
return try value(ofAttribute: attr.rawValue)
}
}

// MARK: - XMLElement Extensions

extension XMLElement {

/**
Attempts to deserialize the specified attribute of the current XMLElement to `T`

Expand Down Expand Up @@ -441,6 +519,41 @@ extension XMLElement {
}
}

// MARK: String RawRepresentable

/*: Provides XMLElement XMLAttributeDeserializable deserialization from String backed RawRepresentables
Added by [PeeJWeeJ](https://github.com/PeeJWeeJ) */
public extension XMLElement {
/**
Attempts to deserialize the specified attribute of the current XMLElement to `T`
using a String backed RawRepresentable (E.g. `String` backed `enum` cases)

- Note:
Convenience for value(ofAttribute: String)

- parameter attr: The attribute to deserialize
- throws: an XMLDeserializationError if there is a problem with deserialization
- returns: The deserialized `T` value
*/
public func value<T: XMLAttributeDeserializable, A: RawRepresentable>(ofAttribute attr: A) throws -> T where A.RawValue == String {
return try value(ofAttribute: attr.rawValue)
}

/**
Attempts to deserialize the specified attribute of the current XMLElement to `T?`
using a String backed RawRepresentable (E.g. `String` backed `enum` cases)

- Note:
Convenience for value(ofAttribute: String)

- parameter attr: The attribute to deserialize
- returns: The deserialized `T?` value, or nil if the attribute does not exist.
*/
public func value<T: XMLAttributeDeserializable, A: RawRepresentable>(ofAttribute attr: A) -> T? where A.RawValue == String {
return value(ofAttribute: attr.rawValue)
}
}

// MARK: - XMLDeserializationError

/// The error that is thrown if there is a problem with deserialization
Expand Down
81 changes: 81 additions & 0 deletions Tests/SWXMLHashTests/TypeConversionBasicTypesTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,48 @@ class TypeConversionBasicTypesTests: XCTestCase {
XCTAssertNil(value)
}

// swiftlint:disable nesting
func testShouldConvertAttributeToNonOptionalWithStringRawRepresentable() {
enum Keys: String {
case string
}
do {
let value: String = try parser!["root"]["attr"].value(ofAttribute: Keys.string)
XCTAssertEqual(value, "stringValue")
} catch {
XCTFail("\(error)")
}
}

func testShouldConvertAttributeToOptionalWithStringRawRepresentable() {
enum Keys: String {
case string
}
let value: String? = parser!["root"]["attr"].value(ofAttribute: Keys.string)
XCTAssertEqual(value, "stringValue")
}

func testShouldThrowWhenConvertingMissingAttributeToNonOptionalWithStringRawRepresentable() {
enum Keys: String {
case missing
}
XCTAssertThrowsError(try (parser!["root"]["attr"].value(ofAttribute: Keys.missing) as String)) { error in
guard error is XMLDeserializationError else {
XCTFail("Wrong type of error")
return
}
}
}

func testShouldConvertMissingAttributeToOptionalWithStringRawRepresentable() {
enum Keys: String {
case missing
}
let value: String? = parser!["root"]["attr"].value(ofAttribute: Keys.missing)
XCTAssertNil(value)
}
// swiftlint:enable nesting

func testIntShouldConvertValueToNonOptional() {
do {
let value: Int = try parser!["root"]["int"].value()
Expand Down Expand Up @@ -494,6 +536,7 @@ class TypeConversionBasicTypesTests: XCTestCase {
}

let correctAttributeItem = AttributeItem(name: "the name of attribute item", price: 19.99)
let correctAttributeItemStringRawRepresentable = AttributeItemStringRawRepresentable(name: "the name of attribute item", price: 19.99)

func testAttributeItemShouldConvertAttributeItemToNonOptional() {
do {
Expand All @@ -504,6 +547,15 @@ class TypeConversionBasicTypesTests: XCTestCase {
}
}

func testAttributeItemStringRawRepresentableShouldConvertAttributeItemToNonOptional() {
do {
let value: AttributeItemStringRawRepresentable = try parser!["root"]["attributeItem"].value()
XCTAssertEqual(value, correctAttributeItemStringRawRepresentable)
} catch {
XCTFail("\(error)")
}
}

func testAttributeItemShouldThrowWhenConvertingEmptyToNonOptional() {
XCTAssertThrowsError(try (parser!["root"]["empty"].value() as AttributeItem)) { error in
guard error is XMLDeserializationError else {
Expand Down Expand Up @@ -609,6 +661,30 @@ extension AttributeItem: Equatable {
}
}

struct AttributeItemStringRawRepresentable: XMLElementDeserializable {
private enum Keys: String {
case name
case price
}

let name: String
let price: Double

static func deserialize(_ element: SWXMLHash.XMLElement) throws -> AttributeItemStringRawRepresentable {
print("my deserialize")
return try AttributeItemStringRawRepresentable(
name: element.value(ofAttribute: Keys.name),
price: element.value(ofAttribute: Keys.price)
)
}
}

extension AttributeItemStringRawRepresentable: Equatable {
static func == (a: AttributeItemStringRawRepresentable, b: AttributeItemStringRawRepresentable) -> Bool {
return a.name == b.name && a.price == b.price
}
}

extension TypeConversionBasicTypesTests {
static var allTests: [(String, (TypeConversionBasicTypesTests) -> () throws -> Void)] {
return [
Expand All @@ -622,6 +698,10 @@ extension TypeConversionBasicTypesTests {
("testShouldConvertAttributeToOptional", testShouldConvertAttributeToOptional),
("testShouldThrowWhenConvertingMissingAttributeToNonOptional", testShouldThrowWhenConvertingMissingAttributeToNonOptional),
("testShouldConvertMissingAttributeToOptional", testShouldConvertMissingAttributeToOptional),
("testShouldConvertAttributeToNonOptionalWithStringRawRepresentable", testShouldConvertAttributeToNonOptionalWithStringRawRepresentable),
("testShouldConvertAttributeToOptionalWithStringRawRepresentable", testShouldConvertAttributeToOptionalWithStringRawRepresentable),
("testShouldThrowWhenConvertingMissingAttributeToNonOptionalWithStringRawRepresentable", testShouldThrowWhenConvertingMissingAttributeToNonOptionalWithStringRawRepresentable),
("testShouldConvertMissingAttributeToOptionalWithStringRawRepresentable", testShouldConvertMissingAttributeToOptionalWithStringRawRepresentable),
("testIntShouldConvertValueToNonOptional", testIntShouldConvertValueToNonOptional),
("testIntShouldThrowWhenConvertingEmptyToNonOptional", testIntShouldThrowWhenConvertingEmptyToNonOptional),
("testIntShouldThrowWhenConvertingMissingToNonOptional", testIntShouldThrowWhenConvertingMissingToNonOptional),
Expand Down Expand Up @@ -661,6 +741,7 @@ extension TypeConversionBasicTypesTests {
("testBasicItemShouldConvertEmptyToOptional", testBasicItemShouldConvertEmptyToOptional),
("testBasicItemShouldConvertMissingToOptional", testBasicItemShouldConvertMissingToOptional),
("testAttributeItemShouldConvertAttributeItemToNonOptional", testAttributeItemShouldConvertAttributeItemToNonOptional),
("testAttributeItemStringRawRepresentableShouldConvertAttributeItemToNonOptional", testAttributeItemStringRawRepresentableShouldConvertAttributeItemToNonOptional),
("testAttributeItemShouldThrowWhenConvertingEmptyToNonOptional", testAttributeItemShouldThrowWhenConvertingEmptyToNonOptional),
("testAttributeItemShouldThrowWhenConvertingMissingToNonOptional", testAttributeItemShouldThrowWhenConvertingMissingToNonOptional),
("testAttributeItemShouldConvertAttributeItemToOptional", testAttributeItemShouldConvertAttributeItemToOptional),
Expand Down
Loading

0 comments on commit 75a1add

Please sign in to comment.