From e0dadf9b3353d34b70f19dd363718014f31ce0d1 Mon Sep 17 00:00:00 2001 From: Vincent Esche Date: Fri, 5 Oct 2018 09:05:15 +0200 Subject: [PATCH 01/24] Added support for decoupled, type-dependent node-encoding strategies --- Sources/XMLParsing/Encoder/XMLEncoder.swift | 214 ++++++++++++------ .../Encoder/XMLReferencingEncoder.swift | 26 ++- 2 files changed, 167 insertions(+), 73 deletions(-) diff --git a/Sources/XMLParsing/Encoder/XMLEncoder.swift b/Sources/XMLParsing/Encoder/XMLEncoder.swift index 99c499c..f77cef7 100644 --- a/Sources/XMLParsing/Encoder/XMLEncoder.swift +++ b/Sources/XMLParsing/Encoder/XMLEncoder.swift @@ -32,6 +32,14 @@ open class XMLEncoder { @available(macOS 10.13, iOS 11.0, watchOS 4.0, tvOS 11.0, *) public static let sortedKeys = OutputFormatting(rawValue: 1 << 1) } + + /// A node's encoding tyoe + public enum NodeEncoding { + case attribute + case element + + public static let `default`: NodeEncoding = .element + } /// The strategy to use for encoding `Date` values. public enum DateEncodingStrategy { @@ -164,14 +172,26 @@ open class XMLEncoder { return result } } - - /// The strategy to use for encoding attributes on a node. - public enum AttributeEncodingStrategy { + + /// Set of strategies to use for encoding of nodes. + public enum NodeEncodingStrategies { /// Defer to `Encoder` for choosing an encoding. This is the default strategy. case deferredToEncoder - - /// Return true to encode the value as an attribute. - case custom((Encoder) -> Bool) + + /// Return a closure computing the desired node encoding for the value by its coding key. + case custom((Encodable.Type, Encoder) -> ((CodingKey) -> XMLEncoder.NodeEncoding)) + + internal func nodeEncodings( + forType codableType: Encodable.Type, + with encoder: Encoder + ) -> ((CodingKey) -> XMLEncoder.NodeEncoding) { + switch self { + case .deferredToEncoder: + return { _ in .default } + case .custom(let closure): + return closure(codableType, encoder) + } + } } /// The output format to produce. Defaults to `[]`. @@ -190,7 +210,7 @@ open class XMLEncoder { open var keyEncodingStrategy: KeyEncodingStrategy = .useDefaultKeys /// The strategy to use in encoding encoding attributes. Defaults to `.deferredToEncoder`. - open var attributeEncodingStrategy: AttributeEncodingStrategy = .deferredToEncoder + open var nodeEncodingStrategy: NodeEncodingStrategies = .deferredToEncoder /// The strategy to use in encoding strings. Defaults to `.deferredToString`. open var stringEncodingStrategy: StringEncodingStrategy = .deferredToString @@ -204,7 +224,7 @@ open class XMLEncoder { let dataEncodingStrategy: DataEncodingStrategy let nonConformingFloatEncodingStrategy: NonConformingFloatEncodingStrategy let keyEncodingStrategy: KeyEncodingStrategy - let attributeEncodingStrategy: AttributeEncodingStrategy + let nodeEncodingStrategy: NodeEncodingStrategies let stringEncodingStrategy: StringEncodingStrategy let userInfo: [CodingUserInfoKey : Any] } @@ -215,7 +235,7 @@ open class XMLEncoder { dataEncodingStrategy: dataEncodingStrategy, nonConformingFloatEncodingStrategy: nonConformingFloatEncodingStrategy, keyEncodingStrategy: keyEncodingStrategy, - attributeEncodingStrategy: attributeEncodingStrategy, + nodeEncodingStrategy: nodeEncodingStrategy, stringEncodingStrategy: stringEncodingStrategy, userInfo: userInfo) } @@ -233,7 +253,11 @@ open class XMLEncoder { /// - throws: `EncodingError.invalidValue` if a non-conforming floating-point value is encountered during encoding, and the encoding strategy is `.throw`. /// - throws: An error if any value throws an error during encoding. open func encode(_ value: T, withRootKey rootKey: String, header: XMLHeader? = nil) throws -> Data { - let encoder = _XMLEncoder(options: self.options) + let encoder = _XMLEncoder( + options: self.options, + nodeEncodings: [] + ) + encoder.nodeEncodings.append(self.options.nodeEncodingStrategy.nodeEncodings(forType: T.self, with: encoder)) guard let topLevel = try encoder.box_(value) else { throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: [], debugDescription: "Top-level \(T.self) did not encode any values.")) @@ -250,8 +274,9 @@ open class XMLEncoder { guard let element = _XMLElement.createRootElement(rootKey: rootKey, object: topLevel) else { throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: [], debugDescription: "Unable to encode the given top-level value to XML.")) } - - return element.toXMLString(with: header, withCDATA: stringEncodingStrategy != .deferredToString).data(using: .utf8, allowLossyConversion: true)! + + let withCDATA = stringEncodingStrategy != .deferredToString + return element.toXMLString(with: header, withCDATA: withCDATA, formatting: self.outputFormatting).data(using: .utf8, allowLossyConversion: true)! } } @@ -266,6 +291,8 @@ internal class _XMLEncoder: Encoder { /// The path to the current point in encoding. public var codingPath: [CodingKey] + + public var nodeEncodings: [(CodingKey) -> XMLEncoder.NodeEncoding] /// Contextual user-provided information for use during encoding. public var userInfo: [CodingUserInfoKey : Any] { @@ -275,10 +302,15 @@ internal class _XMLEncoder: Encoder { // MARK: - Initialization /// Initializes `self` with the given top-level encoder options. - internal init(options: XMLEncoder._Options, codingPath: [CodingKey] = []) { + internal init( + options: XMLEncoder._Options, + nodeEncodings: [(CodingKey) -> XMLEncoder.NodeEncoding], + codingPath: [CodingKey] = [] + ) { self.options = options self.storage = _XMLEncodingStorage() self.codingPath = codingPath + self.nodeEncodings = nodeEncodings } /// Returns whether a new element can be encoded at this coding path. @@ -308,7 +340,8 @@ internal class _XMLEncoder: Encoder { topContainer = container } - + + let container = _XMLKeyedEncodingContainer(referencing: self, codingPath: self.codingPath, wrapping: topContainer) return KeyedEncodingContainer(container) } @@ -383,8 +416,11 @@ fileprivate struct _XMLKeyedEncodingContainer : KeyedEncodingCont public mutating func encode(_ value: Bool, forKey key: Key) throws { self.encoder.codingPath.append(key) defer { self.encoder.codingPath.removeLast() } - switch self.encoder.options.attributeEncodingStrategy { - case .custom(let closure) where closure(self.encoder): + guard let strategy = self.encoder.nodeEncodings.last else { + preconditionFailure("Attempt to access node encoding strategy from empty stack.") + } + switch strategy(key) { + case .attribute: if let attributesContainer = self.container[_XMLElement.attributesKey] as? NSMutableDictionary { attributesContainer[_converted(key).stringValue] = self.encoder.box(value) } else { @@ -392,7 +428,7 @@ fileprivate struct _XMLKeyedEncodingContainer : KeyedEncodingCont attributesContainer[_converted(key).stringValue] = self.encoder.box(value) self.container[_XMLElement.attributesKey] = attributesContainer } - default: + case .element: self.container[_converted(key).stringValue] = self.encoder.box(value) } } @@ -400,8 +436,11 @@ fileprivate struct _XMLKeyedEncodingContainer : KeyedEncodingCont public mutating func encode(_ value: Int, forKey key: Key) throws { self.encoder.codingPath.append(key) defer { self.encoder.codingPath.removeLast() } - switch self.encoder.options.attributeEncodingStrategy { - case .custom(let closure) where closure(self.encoder): + guard let strategy = self.encoder.nodeEncodings.last else { + preconditionFailure("Attempt to access node encoding strategy from empty stack.") + } + switch strategy(key) { + case .attribute: if let attributesContainer = self.container[_XMLElement.attributesKey] as? NSMutableDictionary { attributesContainer[_converted(key).stringValue] = self.encoder.box(value) } else { @@ -409,7 +448,7 @@ fileprivate struct _XMLKeyedEncodingContainer : KeyedEncodingCont attributesContainer[_converted(key).stringValue] = self.encoder.box(value) self.container[_XMLElement.attributesKey] = attributesContainer } - default: + case .element: self.container[_converted(key).stringValue] = self.encoder.box(value) } } @@ -417,8 +456,11 @@ fileprivate struct _XMLKeyedEncodingContainer : KeyedEncodingCont public mutating func encode(_ value: Int8, forKey key: Key) throws { self.encoder.codingPath.append(key) defer { self.encoder.codingPath.removeLast() } - switch self.encoder.options.attributeEncodingStrategy { - case .custom(let closure) where closure(self.encoder): + guard let strategy = self.encoder.nodeEncodings.last else { + preconditionFailure("Attempt to access node encoding strategy from empty stack.") + } + switch strategy(key) { + case .attribute: if let attributesContainer = self.container[_XMLElement.attributesKey] as? NSMutableDictionary { attributesContainer[_converted(key).stringValue] = self.encoder.box(value) } else { @@ -426,7 +468,7 @@ fileprivate struct _XMLKeyedEncodingContainer : KeyedEncodingCont attributesContainer[_converted(key).stringValue] = self.encoder.box(value) self.container[_XMLElement.attributesKey] = attributesContainer } - default: + case .element: self.container[_converted(key).stringValue] = self.encoder.box(value) } } @@ -434,8 +476,11 @@ fileprivate struct _XMLKeyedEncodingContainer : KeyedEncodingCont public mutating func encode(_ value: Int16, forKey key: Key) throws { self.encoder.codingPath.append(key) defer { self.encoder.codingPath.removeLast() } - switch self.encoder.options.attributeEncodingStrategy { - case .custom(let closure) where closure(self.encoder): + guard let strategy = self.encoder.nodeEncodings.last else { + preconditionFailure("Attempt to access node encoding strategy from empty stack.") + } + switch strategy(key) { + case .attribute: if let attributesContainer = self.container[_XMLElement.attributesKey] as? NSMutableDictionary { attributesContainer[_converted(key).stringValue] = self.encoder.box(value) } else { @@ -443,7 +488,7 @@ fileprivate struct _XMLKeyedEncodingContainer : KeyedEncodingCont attributesContainer[_converted(key).stringValue] = self.encoder.box(value) self.container[_XMLElement.attributesKey] = attributesContainer } - default: + case .element: self.container[_converted(key).stringValue] = self.encoder.box(value) } } @@ -451,8 +496,11 @@ fileprivate struct _XMLKeyedEncodingContainer : KeyedEncodingCont public mutating func encode(_ value: Int32, forKey key: Key) throws { self.encoder.codingPath.append(key) defer { self.encoder.codingPath.removeLast() } - switch self.encoder.options.attributeEncodingStrategy { - case .custom(let closure) where closure(self.encoder): + guard let strategy = self.encoder.nodeEncodings.last else { + preconditionFailure("Attempt to access node encoding strategy from empty stack.") + } + switch strategy(key) { + case .attribute: if let attributesContainer = self.container[_XMLElement.attributesKey] as? NSMutableDictionary { attributesContainer[_converted(key).stringValue] = self.encoder.box(value) } else { @@ -460,7 +508,7 @@ fileprivate struct _XMLKeyedEncodingContainer : KeyedEncodingCont attributesContainer[_converted(key).stringValue] = self.encoder.box(value) self.container[_XMLElement.attributesKey] = attributesContainer } - default: + case .element: self.container[_converted(key).stringValue] = self.encoder.box(value) } } @@ -468,8 +516,11 @@ fileprivate struct _XMLKeyedEncodingContainer : KeyedEncodingCont public mutating func encode(_ value: Int64, forKey key: Key) throws { self.encoder.codingPath.append(key) defer { self.encoder.codingPath.removeLast() } - switch self.encoder.options.attributeEncodingStrategy { - case .custom(let closure) where closure(self.encoder): + guard let strategy = self.encoder.nodeEncodings.last else { + preconditionFailure("Attempt to access node encoding strategy from empty stack.") + } + switch strategy(key) { + case .attribute: if let attributesContainer = self.container[_XMLElement.attributesKey] as? NSMutableDictionary { attributesContainer[_converted(key).stringValue] = self.encoder.box(value) } else { @@ -477,7 +528,7 @@ fileprivate struct _XMLKeyedEncodingContainer : KeyedEncodingCont attributesContainer[_converted(key).stringValue] = self.encoder.box(value) self.container[_XMLElement.attributesKey] = attributesContainer } - default: + case .element: self.container[_converted(key).stringValue] = self.encoder.box(value) } } @@ -485,8 +536,11 @@ fileprivate struct _XMLKeyedEncodingContainer : KeyedEncodingCont public mutating func encode(_ value: UInt, forKey key: Key) throws { self.encoder.codingPath.append(key) defer { self.encoder.codingPath.removeLast() } - switch self.encoder.options.attributeEncodingStrategy { - case .custom(let closure) where closure(self.encoder): + guard let strategy = self.encoder.nodeEncodings.last else { + preconditionFailure("Attempt to access node encoding strategy from empty stack.") + } + switch strategy(key) { + case .attribute: if let attributesContainer = self.container[_XMLElement.attributesKey] as? NSMutableDictionary { attributesContainer[_converted(key).stringValue] = self.encoder.box(value) } else { @@ -494,7 +548,7 @@ fileprivate struct _XMLKeyedEncodingContainer : KeyedEncodingCont attributesContainer[_converted(key).stringValue] = self.encoder.box(value) self.container[_XMLElement.attributesKey] = attributesContainer } - default: + case .element: self.container[_converted(key).stringValue] = self.encoder.box(value) } } @@ -502,8 +556,11 @@ fileprivate struct _XMLKeyedEncodingContainer : KeyedEncodingCont public mutating func encode(_ value: UInt8, forKey key: Key) throws { self.encoder.codingPath.append(key) defer { self.encoder.codingPath.removeLast() } - switch self.encoder.options.attributeEncodingStrategy { - case .custom(let closure) where closure(self.encoder): + guard let strategy = self.encoder.nodeEncodings.last else { + preconditionFailure("Attempt to access node encoding strategy from empty stack.") + } + switch strategy(key) { + case .attribute: if let attributesContainer = self.container[_XMLElement.attributesKey] as? NSMutableDictionary { attributesContainer[_converted(key).stringValue] = self.encoder.box(value) } else { @@ -511,7 +568,7 @@ fileprivate struct _XMLKeyedEncodingContainer : KeyedEncodingCont attributesContainer[_converted(key).stringValue] = self.encoder.box(value) self.container[_XMLElement.attributesKey] = attributesContainer } - default: + case .element: self.container[_converted(key).stringValue] = self.encoder.box(value) } } @@ -519,8 +576,11 @@ fileprivate struct _XMLKeyedEncodingContainer : KeyedEncodingCont public mutating func encode(_ value: UInt16, forKey key: Key) throws { self.encoder.codingPath.append(key) defer { self.encoder.codingPath.removeLast() } - switch self.encoder.options.attributeEncodingStrategy { - case .custom(let closure) where closure(self.encoder): + guard let strategy = self.encoder.nodeEncodings.last else { + preconditionFailure("Attempt to access node encoding strategy from empty stack.") + } + switch strategy(key) { + case .attribute: if let attributesContainer = self.container[_XMLElement.attributesKey] as? NSMutableDictionary { attributesContainer[_converted(key).stringValue] = self.encoder.box(value) } else { @@ -528,7 +588,7 @@ fileprivate struct _XMLKeyedEncodingContainer : KeyedEncodingCont attributesContainer[_converted(key).stringValue] = self.encoder.box(value) self.container[_XMLElement.attributesKey] = attributesContainer } - default: + case .element: self.container[_converted(key).stringValue] = self.encoder.box(value) } } @@ -536,8 +596,11 @@ fileprivate struct _XMLKeyedEncodingContainer : KeyedEncodingCont public mutating func encode(_ value: UInt32, forKey key: Key) throws { self.encoder.codingPath.append(key) defer { self.encoder.codingPath.removeLast() } - switch self.encoder.options.attributeEncodingStrategy { - case .custom(let closure) where closure(self.encoder): + guard let strategy = self.encoder.nodeEncodings.last else { + preconditionFailure("Attempt to access node encoding strategy from empty stack.") + } + switch strategy(key) { + case .attribute: if let attributesContainer = self.container[_XMLElement.attributesKey] as? NSMutableDictionary { attributesContainer[_converted(key).stringValue] = self.encoder.box(value) } else { @@ -545,7 +608,7 @@ fileprivate struct _XMLKeyedEncodingContainer : KeyedEncodingCont attributesContainer[_converted(key).stringValue] = self.encoder.box(value) self.container[_XMLElement.attributesKey] = attributesContainer } - default: + case .element: self.container[_converted(key).stringValue] = self.encoder.box(value) } } @@ -553,8 +616,11 @@ fileprivate struct _XMLKeyedEncodingContainer : KeyedEncodingCont public mutating func encode(_ value: UInt64, forKey key: Key) throws { self.encoder.codingPath.append(key) defer { self.encoder.codingPath.removeLast() } - switch self.encoder.options.attributeEncodingStrategy { - case .custom(let closure) where closure(self.encoder): + guard let strategy = self.encoder.nodeEncodings.last else { + preconditionFailure("Attempt to access node encoding strategy from empty stack.") + } + switch strategy(key) { + case .attribute: if let attributesContainer = self.container[_XMLElement.attributesKey] as? NSMutableDictionary { attributesContainer[_converted(key).stringValue] = self.encoder.box(value) } else { @@ -562,7 +628,7 @@ fileprivate struct _XMLKeyedEncodingContainer : KeyedEncodingCont attributesContainer[_converted(key).stringValue] = self.encoder.box(value) self.container[_XMLElement.attributesKey] = attributesContainer } - default: + case .element: self.container[_converted(key).stringValue] = self.encoder.box(value) } } @@ -570,8 +636,11 @@ fileprivate struct _XMLKeyedEncodingContainer : KeyedEncodingCont public mutating func encode(_ value: String, forKey key: Key) throws { self.encoder.codingPath.append(key) defer { self.encoder.codingPath.removeLast() } - switch self.encoder.options.attributeEncodingStrategy { - case .custom(let closure) where closure(self.encoder): + guard let strategy = self.encoder.nodeEncodings.last else { + preconditionFailure("Attempt to access node encoding strategy from empty stack.") + } + switch strategy(key) { + case .attribute: if let attributesContainer = self.container[_XMLElement.attributesKey] as? NSMutableDictionary { attributesContainer[_converted(key).stringValue] = self.encoder.box(value) } else { @@ -579,7 +648,7 @@ fileprivate struct _XMLKeyedEncodingContainer : KeyedEncodingCont attributesContainer[_converted(key).stringValue] = self.encoder.box(value) self.container[_XMLElement.attributesKey] = attributesContainer } - default: + case .element: self.container[_converted(key).stringValue] = self.encoder.box(value) } } @@ -595,8 +664,11 @@ fileprivate struct _XMLKeyedEncodingContainer : KeyedEncodingCont // Since the double may be invalid and throw, the coding path needs to contain this key. self.encoder.codingPath.append(key) defer { self.encoder.codingPath.removeLast() } - switch self.encoder.options.attributeEncodingStrategy { - case .custom(let closure) where closure(self.encoder): + guard let strategy = self.encoder.nodeEncodings.last else { + preconditionFailure("Attempt to access node encoding strategy from empty stack.") + } + switch strategy(key) { + case .attribute: if let attributesContainer = self.container[_XMLElement.attributesKey] as? NSMutableDictionary { attributesContainer[_converted(key).stringValue] = try self.encoder.box(value) } else { @@ -604,29 +676,35 @@ fileprivate struct _XMLKeyedEncodingContainer : KeyedEncodingCont attributesContainer[_converted(key).stringValue] = try self.encoder.box(value) self.container[_XMLElement.attributesKey] = attributesContainer } - default: + case .element: self.container[_converted(key).stringValue] = try self.encoder.box(value) } } public mutating func encode(_ value: T, forKey key: Key) throws { + guard let strategy = self.encoder.nodeEncodings.last else { + preconditionFailure("Attempt to access node encoding strategy from empty stack.") + } self.encoder.codingPath.append(key) - defer { self.encoder.codingPath.removeLast() } - - if T.self == Date.self || T.self == NSDate.self { - switch self.encoder.options.attributeEncodingStrategy { - case .custom(let closure) where closure(self.encoder): - if let attributesContainer = self.container[_XMLElement.attributesKey] as? NSMutableDictionary { - attributesContainer[_converted(key).stringValue] = try self.encoder.box(value) - } else { - let attributesContainer = NSMutableDictionary() - attributesContainer[_converted(key).stringValue] = try self.encoder.box(value) - self.container[_XMLElement.attributesKey] = attributesContainer - } - default: - self.container[_converted(key).stringValue] = try self.encoder.box(value) + let nodeEncodings = self.encoder.options.nodeEncodingStrategy.nodeEncodings( + forType: T.self, + with: self.encoder + ) + self.encoder.nodeEncodings.append(nodeEncodings) + defer { + let _ = self.encoder.nodeEncodings.removeLast() + self.encoder.codingPath.removeLast() + } + switch strategy(key) { + case .attribute: + if let attributesContainer = self.container[_XMLElement.attributesKey] as? NSMutableDictionary { + attributesContainer[_converted(key).stringValue] = try self.encoder.box(value) + } else { + let attributesContainer = NSMutableDictionary() + attributesContainer[_converted(key).stringValue] = try self.encoder.box(value) + self.container[_XMLElement.attributesKey] = attributesContainer } - } else { + case .element: self.container[_converted(key).stringValue] = try self.encoder.box(value) } } diff --git a/Sources/XMLParsing/Encoder/XMLReferencingEncoder.swift b/Sources/XMLParsing/Encoder/XMLReferencingEncoder.swift index 9953165..30ce4c3 100644 --- a/Sources/XMLParsing/Encoder/XMLReferencingEncoder.swift +++ b/Sources/XMLParsing/Encoder/XMLReferencingEncoder.swift @@ -35,20 +35,36 @@ internal class _XMLReferencingEncoder : _XMLEncoder { // MARK: - Initialization /// Initializes `self` by referencing the given array container in the given encoder. - internal init(referencing encoder: _XMLEncoder, at index: Int, wrapping array: NSMutableArray) { + internal init( + referencing encoder: _XMLEncoder, + at index: Int, + wrapping array: NSMutableArray + ) { self.encoder = encoder self.reference = .array(array, index) - super.init(options: encoder.options, codingPath: encoder.codingPath) + super.init( + options: encoder.options, + nodeEncodings: encoder.nodeEncodings, + codingPath: encoder.codingPath + ) self.codingPath.append(_XMLKey(index: index)) } /// Initializes `self` by referencing the given dictionary container in the given encoder. - internal init(referencing encoder: _XMLEncoder, - key: CodingKey, convertedKey: CodingKey, wrapping dictionary: NSMutableDictionary) { + internal init( + referencing encoder: _XMLEncoder, + key: CodingKey, + convertedKey: CodingKey, + wrapping dictionary: NSMutableDictionary + ) { self.encoder = encoder self.reference = .dictionary(dictionary, convertedKey.stringValue) - super.init(options: encoder.options, codingPath: encoder.codingPath) + super.init( + options: encoder.options, + nodeEncodings: encoder.nodeEncodings, + codingPath: encoder.codingPath + ) self.codingPath.append(key) } From be560c1ef86b961901c1583fdb05349f6151d160 Mon Sep 17 00:00:00 2001 From: Vincent Esche Date: Mon, 1 Oct 2018 10:32:48 +0200 Subject: [PATCH 02/24] Added missing visibility declarations --- Sources/XMLParsing/XMLStackParser.swift | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/Sources/XMLParsing/XMLStackParser.swift b/Sources/XMLParsing/XMLStackParser.swift index 4af9d34..185cae3 100644 --- a/Sources/XMLParsing/XMLStackParser.swift +++ b/Sources/XMLParsing/XMLStackParser.swift @@ -14,17 +14,13 @@ import Foundation public struct XMLHeader { /// the XML standard that the produced document conforms to. - var version: Double? = nil + public let version: Double? /// the encoding standard used to represent the characters in the produced document. - var encoding: String? = nil - /// indicates whetehr a document relies on information from an external source. - var standalone: String? = nil - - init(version: Double? = nil) { - self.version = version - } - - init(version: Double?, encoding: String?, standalone: String? = nil) { + public let encoding: String? + /// indicates whether a document relies on information from an external source. + public let standalone: String? + + public init(version: Double? = nil, encoding: String? = nil, standalone: String? = nil) { self.version = version self.encoding = encoding self.standalone = standalone @@ -157,7 +153,7 @@ internal class _XMLElement { parentElement.children[key] = (parentElement.children[key] ?? []) + [element] } - func flatten() -> [String: Any] { + fileprivate func flatten() -> [String: Any] { var node: [String: Any] = attributes for childElement in children { @@ -192,7 +188,7 @@ internal class _XMLElement { return node } - func toXMLString(with header: XMLHeader? = nil, withCDATA cdata: Bool, ignoreEscaping: Bool = false) -> String { + internal func toXMLString(with header: XMLHeader? = nil, withCDATA cdata: Bool, ignoreEscaping: Bool = false) -> String { if let header = header, let headerXML = header.toXML() { return headerXML + _toXMLString(withCDATA: cdata) } else { @@ -237,7 +233,7 @@ internal class _XMLElement { } extension String { - func escape(_ characterSet: [(character: String, escapedCharacter: String)]) -> String { + internal func escape(_ characterSet: [(character: String, escapedCharacter: String)]) -> String { var string = self for set in characterSet { From f14b02b2d8b76cdf8c22f0792130d13b05c5480a Mon Sep 17 00:00:00 2001 From: Vincent Esche Date: Mon, 1 Oct 2018 10:37:54 +0200 Subject: [PATCH 03/24] Improved `.gitignore` and removed tracked `*.xcuserdata` files --- .gitignore | 156 +++++++++++++++++- .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcschemes/xcschememanagement.plist | 24 --- 3 files changed, 162 insertions(+), 26 deletions(-) create mode 100644 XMLParsing.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist delete mode 100644 XMLParsing.xcodeproj/xcuserdata/wilson.xcuserdatad/xcschemes/xcschememanagement.plist diff --git a/.gitignore b/.gitignore index ddbb1e0..f2551d4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,155 @@ + +# Created by https://www.gitignore.io/api/swift + +### Swift ### +# Xcode +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +## Build generated +build/ +DerivedData/ + +## Various settings +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata/ + +## Other +*.moved-aside +*.xccheckout +*.xcscmblueprint + +## Obj-C/Swift specific +*.hmap +*.ipa +*.dSYM.zip +*.dSYM + +## Playgrounds +timeline.xctimeline +playground.xcworkspace + +# Swift Package Manager +# +# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. +# Packages/ +# Package.pins +# Package.resolved +.build/ + +# CocoaPods +# +# We recommend against adding the Pods directory to your .gitignore. However +# you should judge for yourself, the pros and cons are mentioned at: +# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control +# +# Pods/ +# +# Add this line if you want to avoid checking in source code from the Xcode workspace +# *.xcworkspace + +# Carthage +# +# Add this line if you want to avoid checking in source code from Carthage dependencies. +# Carthage/Checkouts + +Carthage/Build + +# fastlane +# +# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the +# screenshots whenever they are needed. +# For more information about the recommended setup visit: +# https://docs.fastlane.tools/best-practices/source-control/#source-control + +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots/**/*.png +fastlane/test_output + +# Code Injection +# +# After new code Injection tools there's a generated folder /iOSInjectionProject +# https://github.com/johnno1962/injectionforxcode + +iOSInjectionProject/ + + +# End of https://www.gitignore.io/api/swift + +# Created by https://www.gitignore.io/api/xcode + +### Xcode ### +# Xcode +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +## User settings +xcuserdata/ + +## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) +*.xcscmblueprint +*.xccheckout + +## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) +build/ +DerivedData/ +*.moved-aside +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 + +### Xcode Patch ### +*.xcodeproj/* +!*.xcodeproj/project.pbxproj +!*.xcodeproj/xcshareddata/ +!*.xcworkspace/contents.xcworkspacedata +/*.gcno + + +# End of https://www.gitignore.io/api/xcode + +# Created by https://www.gitignore.io/api/macos + +### macOS ### +# General .DS_Store -/.build -/Packages +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + + +# End of https://www.gitignore.io/api/macos diff --git a/XMLParsing.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/XMLParsing.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/XMLParsing.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/XMLParsing.xcodeproj/xcuserdata/wilson.xcuserdatad/xcschemes/xcschememanagement.plist b/XMLParsing.xcodeproj/xcuserdata/wilson.xcuserdatad/xcschemes/xcschememanagement.plist deleted file mode 100644 index 984ac00..0000000 --- a/XMLParsing.xcodeproj/xcuserdata/wilson.xcuserdatad/xcschemes/xcschememanagement.plist +++ /dev/null @@ -1,24 +0,0 @@ - - - - - SchemeUserState - - XMLParsing-Package.xcscheme_^#shared#^_ - - orderHint - 0 - - XMLParsingPackageDescription.xcscheme - - orderHint - 1 - - XMLParsingPackageTests.xcscheme - - orderHint - 2 - - - - From 50c00a594366ff80d80dad070515d558cabb02ee Mon Sep 17 00:00:00 2001 From: Vincent Esche Date: Mon, 1 Oct 2018 11:19:25 +0200 Subject: [PATCH 04/24] Made `XMLEncoder.OutputFormatting.prettyPrinted` actually do something # Conflicts: # Sources/XMLParsing/XMLStackParser.swift --- Sources/XMLParsing/XMLStackParser.swift | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/Sources/XMLParsing/XMLStackParser.swift b/Sources/XMLParsing/XMLStackParser.swift index 185cae3..c0ba0d6 100644 --- a/Sources/XMLParsing/XMLStackParser.swift +++ b/Sources/XMLParsing/XMLStackParser.swift @@ -187,17 +187,19 @@ internal class _XMLElement { return node } - - internal func toXMLString(with header: XMLHeader? = nil, withCDATA cdata: Bool, ignoreEscaping: Bool = false) -> String { + + func toXMLString(with header: XMLHeader? = nil, withCDATA cdata: Bool, formatting: XMLEncoder.OutputFormatting, ignoreEscaping: Bool = false) -> String { if let header = header, let headerXML = header.toXML() { - return headerXML + _toXMLString(withCDATA: cdata) + return headerXML + _toXMLString(withCDATA: cdata, formatting: formatting) } else { - return _toXMLString(withCDATA: cdata) + return _toXMLString(withCDATA: cdata, formatting: formatting) } } - fileprivate func _toXMLString(indented level: Int = 0, withCDATA cdata: Bool, ignoreEscaping: Bool = false) -> String { - var string = String(repeating: " ", count: level * 4) + fileprivate func _toXMLString(indented level: Int = 0, withCDATA cdata: Bool, formatting: XMLEncoder.OutputFormatting, ignoreEscaping: Bool = false) -> String { + let prettyPrinted = formatting.contains(.prettyPrinted) + let indentation = String(repeating: " ", count: (prettyPrinted ? level : 0) * 4) + var string = indentation string += "<\(key)" for (key, value) in attributes { @@ -213,16 +215,16 @@ internal class _XMLElement { } string += "" } else if !children.isEmpty { - string += ">\n" + string += prettyPrinted ? ">\n" : ">" for childElement in children { for child in childElement.value { - string += child._toXMLString(indented: level + 1, withCDATA: cdata) - string += "\n" + string += child._toXMLString(indented: level + 1, withCDATA: cdata, formatting: formatting) + string += prettyPrinted ? "\n" : "" } } - string += String(repeating: " ", count: level * 4) + string += indentation string += "" } else { string += " />" From faddb3ff4b59e0c2d57475f763f2d6529535d02b Mon Sep 17 00:00:00 2001 From: Evan Coleman Date: Wed, 24 Oct 2018 18:26:03 -0400 Subject: [PATCH 05/24] Add tvOS deployment target to podspec --- XMLParsing.podspec | 1 + 1 file changed, 1 insertion(+) diff --git a/XMLParsing.podspec b/XMLParsing.podspec index 46af4a2..d69a33a 100644 --- a/XMLParsing.podspec +++ b/XMLParsing.podspec @@ -7,6 +7,7 @@ Pod::Spec.new do |s| s.license = { :type => "MIT", :file => "LICENSE" } s.author = { "Shawn Moore" => "sm5@me.com" } s.ios.deployment_target = "10.0" + s.tvos.deployment_target = "10.0" s.osx.deployment_target = "10.12" s.source = { :git => "https://github.com/ShawnMoore/XMLParsing.git", :tag => s.version.to_s } s.source_files = "Sources/XMLParsing/**/*.swift" From 3c713ef2ff989f4e1edf6084bdfeff5cf9159ad6 Mon Sep 17 00:00:00 2001 From: Jose Salavert Date: Mon, 22 Oct 2018 11:12:54 +0200 Subject: [PATCH 06/24] Fixing Carthage command --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9f0c394..dae383b 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ $ brew install carthage Inside of your `Cartfile`, specify XMLParsing: ```ogdl -github "ShawmMoore/XMLParsing" +github "ShawnMoore/XMLParsing" ``` Then, run the following command to build the framework: From f971d69b09a0ccad44f93a27b4323d7f573b2c2a Mon Sep 17 00:00:00 2001 From: Wolfgang Lutz Date: Tue, 16 Oct 2018 15:25:55 +0200 Subject: [PATCH 07/24] set deployment versions to allow older SDKs If these are the exactly correct versions needs to be tested --- XMLParsing.xcodeproj/project.pbxproj | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/XMLParsing.xcodeproj/project.pbxproj b/XMLParsing.xcodeproj/project.pbxproj index c0d74c5..565d72a 100644 --- a/XMLParsing.xcodeproj/project.pbxproj +++ b/XMLParsing.xcodeproj/project.pbxproj @@ -131,7 +131,7 @@ name = Products; sourceTree = BUILT_PRODUCTS_DIR; }; - OBJ_5 /* */ = { + OBJ_5 = { isa = PBXGroup; children = ( OBJ_6 /* Package.swift */, @@ -140,7 +140,6 @@ OBJ_26 /* Sample XML */, OBJ_27 /* Products */, ); - name = ""; sourceTree = ""; }; OBJ_7 /* Sources */ = { @@ -249,7 +248,7 @@ knownRegions = ( en, ); - mainGroup = OBJ_5 /* */; + mainGroup = OBJ_5; productRefGroup = OBJ_27 /* Products */; projectDirPath = ""; projectRoot = ""; @@ -324,6 +323,7 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_NS_ASSERTIONS = YES; GCC_OPTIMIZATION_LEVEL = 0; + IPHONEOS_DEPLOYMENT_TARGET = 9.3; MACOSX_DEPLOYMENT_TARGET = 10.10; ONLY_ACTIVE_ARCH = YES; OTHER_SWIFT_FLAGS = "-DXcode"; @@ -332,6 +332,7 @@ SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator appletvos appletvsimulator watchos watchsimulator"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = SWIFT_PACKAGE; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + TVOS_DEPLOYMENT_TARGET = 9.0; USE_HEADERMAP = NO; }; name = Debug; @@ -399,6 +400,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_OPTIMIZATION_LEVEL = s; + IPHONEOS_DEPLOYMENT_TARGET = 9.3; MACOSX_DEPLOYMENT_TARGET = 10.10; OTHER_SWIFT_FLAGS = "-DXcode"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -406,6 +408,7 @@ SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator appletvos appletvsimulator watchos watchsimulator"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = SWIFT_PACKAGE; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + TVOS_DEPLOYMENT_TARGET = 9.0; USE_HEADERMAP = NO; }; name = Release; From c7c8107a4cb75a69d4d1dfb258443a56ad791775 Mon Sep 17 00:00:00 2001 From: Wolfgang Lutz Date: Tue, 16 Oct 2018 15:26:19 +0200 Subject: [PATCH 08/24] Add Info.plist to allow Framework use in App Store Connect via Carthage --- Sources/XMLParsing/Info.plist | 24 ++++++++++++++++++++++++ XMLParsing.xcodeproj/project.pbxproj | 2 ++ 2 files changed, 26 insertions(+) create mode 100644 Sources/XMLParsing/Info.plist diff --git a/Sources/XMLParsing/Info.plist b/Sources/XMLParsing/Info.plist new file mode 100644 index 0000000..fbe1e6b --- /dev/null +++ b/Sources/XMLParsing/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSPrincipalClass + + + diff --git a/XMLParsing.xcodeproj/project.pbxproj b/XMLParsing.xcodeproj/project.pbxproj index 565d72a..5c16710 100644 --- a/XMLParsing.xcodeproj/project.pbxproj +++ b/XMLParsing.xcodeproj/project.pbxproj @@ -56,6 +56,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 8420DF4021761DF200757ECF /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = Sources/XMLParsing/Info.plist; sourceTree = ""; }; OBJ_10 /* DecodingErrorExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DecodingErrorExtension.swift; sourceTree = ""; }; OBJ_11 /* XMLDecoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XMLDecoder.swift; sourceTree = ""; }; OBJ_12 /* XMLDecodingStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XMLDecodingStorage.swift; sourceTree = ""; }; @@ -145,6 +146,7 @@ OBJ_7 /* Sources */ = { isa = PBXGroup; children = ( + 8420DF4021761DF200757ECF /* Info.plist */, OBJ_8 /* XMLParsing */, ); name = Sources; From 73f38304b488c1a6d3ff1d14d1dd9d5caf9b8bc8 Mon Sep 17 00:00:00 2001 From: Wolfgang Lutz Date: Wed, 17 Oct 2018 11:37:04 +0200 Subject: [PATCH 09/24] set CURRENT_PROJECT_VERSION --- XMLParsing.xcodeproj/project.pbxproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/XMLParsing.xcodeproj/project.pbxproj b/XMLParsing.xcodeproj/project.pbxproj index 5c16710..979be70 100644 --- a/XMLParsing.xcodeproj/project.pbxproj +++ b/XMLParsing.xcodeproj/project.pbxproj @@ -321,6 +321,7 @@ CLANG_ENABLE_OBJC_ARC = YES; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = dwarf; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_NS_ASSERTIONS = YES; @@ -399,6 +400,7 @@ CLANG_ENABLE_OBJC_ARC = YES; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = YES; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_OPTIMIZATION_LEVEL = s; From 6d147739c7aa52b6b6d7ec5fd0d58740397ee233 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Sat, 27 Oct 2018 21:44:30 +0100 Subject: [PATCH 10/24] Add convertFromCapitalized strategy, simple test --- Sources/XMLParsing/Decoder/XMLDecoder.swift | 15 ++++- .../Decoder/XMLKeyedDecodingContainer.swift | 4 ++ Tests/XMLParsingTests/XMLParsingTests.swift | 59 +++++++++++++++++-- 3 files changed, 72 insertions(+), 6 deletions(-) diff --git a/Sources/XMLParsing/Decoder/XMLDecoder.swift b/Sources/XMLParsing/Decoder/XMLDecoder.swift index 1602d85..dd63d96 100644 --- a/Sources/XMLParsing/Decoder/XMLDecoder.swift +++ b/Sources/XMLParsing/Decoder/XMLDecoder.swift @@ -119,13 +119,24 @@ open class XMLDecoder { /// /// - Note: Using a key decoding strategy has a nominal performance cost, as each string key has to be inspected for the `_` character. case convertFromSnakeCase - + + /// Convert from "CodingKey" to "codingKey" + case convertFromCapitalized + /// Provide a custom conversion from the key in the encoded JSON to the keys specified by the decoded types. /// The full path to the current decoding position is provided for context (in case you need to locate this key within the payload). The returned key is used in place of the last component in the coding path before decoding. /// If the result of the conversion is a duplicate key, then only one value will be present in the container for the type to decode from. case custom((_ codingPath: [CodingKey]) -> CodingKey) + + static func _convertFromCapitalized(_ stringKey: String) -> String { + guard !stringKey.isEmpty else { return stringKey } + var result = stringKey + let range = result.startIndex...result.index(after: result.startIndex) + result.replaceSubrange(range, with: result[range].lowercased()) + return result + } - internal static func _convertFromSnakeCase(_ stringKey: String) -> String { + static func _convertFromSnakeCase(_ stringKey: String) -> String { guard !stringKey.isEmpty else { return stringKey } // Find the first non-underscore character diff --git a/Sources/XMLParsing/Decoder/XMLKeyedDecodingContainer.swift b/Sources/XMLParsing/Decoder/XMLKeyedDecodingContainer.swift index 5c756ca..40fd4a0 100644 --- a/Sources/XMLParsing/Decoder/XMLKeyedDecodingContainer.swift +++ b/Sources/XMLParsing/Decoder/XMLKeyedDecodingContainer.swift @@ -38,6 +38,10 @@ internal struct _XMLKeyedDecodingContainer : KeyedDecodingContain self.container = Dictionary(container.map { key, value in (XMLDecoder.KeyDecodingStrategy._convertFromSnakeCase(key), value) }, uniquingKeysWith: { (first, _) in first }) + case .convertFromCapitalized: + self.container = Dictionary(container.map { + key, value in (XMLDecoder.KeyDecodingStrategy._convertFromCapitalized(key), value) + }, uniquingKeysWith: { (first, _) in first }) case .custom(let converter): self.container = Dictionary(container.map { key, value in (converter(decoder.codingPath + [_XMLKey(stringValue: key, intValue: nil)]).stringValue, value) diff --git a/Tests/XMLParsingTests/XMLParsingTests.swift b/Tests/XMLParsingTests/XMLParsingTests.swift index 77f2a3d..b017b38 100644 --- a/Tests/XMLParsingTests/XMLParsingTests.swift +++ b/Tests/XMLParsingTests/XMLParsingTests.swift @@ -1,12 +1,63 @@ import XCTest @testable import XMLParsing +let example = """ + + + + + +""" + +struct Relationships: Codable { + let items: [Relationship] + + enum CodingKeys: String, CodingKey { + case items = "relationship" + } +} + +struct Relationship: Codable { + enum SchemaType: String, Codable { + case officeDocument = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" + case extendedProperties = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties" + case coreProperties = "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" + } + + let id: String + let type: SchemaType + let target: String + + enum CodingKeys: CodingKey { + case type + case id + case target + } +} + class XMLParsingTests: XCTestCase { func testExample() { - // This is an example of a functional test case. - // Use XCTAssert and related functions to verify your tests produce the correct - // results. -// XCTAssertEqual(XMLParsing().text, "Hello, World!") + do { + guard let data = example.data(using: .utf8) else { return } + + let decoder = XMLDecoder() + decoder.keyDecodingStrategy = .convertFromCapitalized + + let rels = try decoder.decode(Relationships.self, from: data) + + XCTAssertEqual(rels.items[0].id, "rId1") + } catch { + XCTAssert(false, "failed to decode the example: \(error)") + } } From 292cea930f0d03ffd418a860ba55d41fd782e5a5 Mon Sep 17 00:00:00 2001 From: "Leonid S. Usov" Date: Fri, 23 Nov 2018 20:47:27 +0100 Subject: [PATCH 11/24] add some sources from CHDataStructures --- .../CHDataStructures/CHCircularBuffer.h | 70 +++ .../CHDataStructures/CHCircularBuffer.m | 589 ++++++++++++++++++ .../CHDataStructures/CHMutableDictionary.h | 45 ++ .../CHDataStructures/CHMutableDictionary.m | 161 +++++ .../CHDataStructures/CHOrderedDictionary.h | 257 ++++++++ .../CHDataStructures/CHOrderedDictionary.m | 163 +++++ Sources/XMLParsing/CHDataStructures/Util.h | 174 ++++++ Sources/XMLParsing/CHDataStructures/Util.m | 98 +++ .../XMLParsing-Bridging-Header.h | 4 + XMLParsing.xcodeproj/project.pbxproj | 58 +- 10 files changed, 1617 insertions(+), 2 deletions(-) create mode 100755 Sources/XMLParsing/CHDataStructures/CHCircularBuffer.h create mode 100755 Sources/XMLParsing/CHDataStructures/CHCircularBuffer.m create mode 100755 Sources/XMLParsing/CHDataStructures/CHMutableDictionary.h create mode 100755 Sources/XMLParsing/CHDataStructures/CHMutableDictionary.m create mode 100755 Sources/XMLParsing/CHDataStructures/CHOrderedDictionary.h create mode 100755 Sources/XMLParsing/CHDataStructures/CHOrderedDictionary.m create mode 100755 Sources/XMLParsing/CHDataStructures/Util.h create mode 100755 Sources/XMLParsing/CHDataStructures/Util.m create mode 100644 Sources/XMLParsing/CHDataStructures/XMLParsing-Bridging-Header.h diff --git a/Sources/XMLParsing/CHDataStructures/CHCircularBuffer.h b/Sources/XMLParsing/CHDataStructures/CHCircularBuffer.h new file mode 100755 index 0000000..b692569 --- /dev/null +++ b/Sources/XMLParsing/CHDataStructures/CHCircularBuffer.h @@ -0,0 +1,70 @@ +/* + CHDataStructures.framework -- CHCircularBuffer.h + + Copyright (c) 2009-2010, Quinn Taylor + + This source code is released under the ISC License. + + Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. + + The software is provided "as is", without warranty of any kind, including all implied warranties of merchantability and fitness. In no event shall the authors or copyright holders be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the software or the use or other dealings in the software. + */ + +#import "Util.h" + +/** + @file CHCircularBuffer.h + + A circular buffer array. + */ + +/** + A circular buffer is a structure that emulates a continuous ring of N data slots. This class uses a C array and tracks the indexes of the front and back elements in the buffer, such that the first element is treated as logical index 0 regardless of where it is actually stored. The buffer dynamically expands to accommodate added objects. This type of storage is ideal for scenarios where objects are added and removed only at one or both ends (such as a stack or queue) but still supports all normal NSMutableArray functionality. + + @note Any method inherited from NSArray or NSMutableArray is supported by this class and its children. Please see the documentation for those classes for details. +*/ +@interface CHCircularBuffer : NSMutableArray { + __strong id *array; // Primitive C array for storing collection contents. + NSUInteger arrayCapacity; // How many pointers @a array can accommodate. + NSUInteger count; // The number of objects currently in the buffer. + NSUInteger headIndex; // The array index of the first object. + NSUInteger tailIndex; // The array index after the last object. + unsigned long mutations; // Tracks mutations for NSFastEnumeration. +} + +// The following methods are undocumented since they are only reimplementations. +// Users should consult the API documentation for NSArray and NSMutableArray. + +- (id) init; +- (id) initWithArray:(NSArray*)anArray; +- (id) initWithCapacity:(NSUInteger)capacity NS_DESIGNATED_INITIALIZER; + +- (NSArray*) allObjects; +- (BOOL) containsObject:(id)anObject; +- (BOOL) containsObjectIdenticalTo:(id)anObject; +- (void) exchangeObjectAtIndex:(NSUInteger)idx1 withObjectAtIndex:(NSUInteger)idx2; +- (id) firstObject; +- (NSUInteger) indexOfObject:(id)anObject; +- (NSUInteger) indexOfObjectIdenticalTo:(id)anObject; +- (id) lastObject; +- (NSEnumerator*) objectEnumerator; +- (NSArray*) objectsAtIndexes:(NSIndexSet*)indexes; +- (void) removeAllObjects; +- (void) removeFirstObject; +- (void) removeLastObject; +- (void) removeObject:(id)anObject; +- (void) removeObjectIdenticalTo:(id)anObject; +- (void) removeObjectsAtIndexes:(NSIndexSet*)indexes; +- (void) replaceObjectAtIndex:(NSUInteger)index withObject:(id)anObject; +- (NSEnumerator*) reverseObjectEnumerator; + +#pragma mark Adopted Protocols + +- (void) encodeWithCoder:(NSCoder*)encoder; +- (id) initWithCoder:(NSCoder*)decoder; +- (id) copyWithZone:(NSZone*)zone; +- (NSUInteger) countByEnumeratingWithState:(NSFastEnumerationState*)state + objects:(id*)stackbuf + count:(NSUInteger)len; + +@end diff --git a/Sources/XMLParsing/CHDataStructures/CHCircularBuffer.m b/Sources/XMLParsing/CHDataStructures/CHCircularBuffer.m new file mode 100755 index 0000000..98db5f7 --- /dev/null +++ b/Sources/XMLParsing/CHDataStructures/CHCircularBuffer.m @@ -0,0 +1,589 @@ +/* + CHDataStructures.framework -- CHCircularBuffer.m + + Copyright (c) 2009-2010, Quinn Taylor + + This source code is released under the ISC License. + + Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. + + The software is provided "as is", without warranty of any kind, including all implied warranties of merchantability and fitness. In no event shall the authors or copyright holders be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the software or the use or other dealings in the software. + */ + +#import "CHCircularBuffer.h" + +#define DEFAULT_BUFFER_SIZE 16u + +#define transformIndex(index) ((headIndex + index) % arrayCapacity) +#define incrementIndex(index) (index = (index + 1) % arrayCapacity) +#define decrementIndex(index) (index = index ? index - 1 : arrayCapacity - 1) + +// Shift a group of elements within the underlying array; used to close up gaps. +// Guarantees that 'number' is in the correct range for the array capacity. +#define blockMove(dst, src, scan) \ +do { \ + NSUInteger itemsLeftToCopy = (scan - src + arrayCapacity) % arrayCapacity; \ + while (itemsLeftToCopy) { \ + NSUInteger size = MIN(itemsLeftToCopy, arrayCapacity - MAX(dst, src)); \ + memmove(&array[dst], &array[src], kCHPointerSize * size); \ + src = (src + size) % arrayCapacity; \ + dst = (dst + size) % arrayCapacity; \ + itemsLeftToCopy -= size; \ + } \ +} while(0) + +/** + An NSEnumerator for traversing a CHAbstractCircularBufferCollection subclass. + + Enumerators encapsulate their own state, and more than one may be active at once. + However, like an enumerator for a mutable data structure, any instances of this + enumerator become invalid if the underlying collection is modified. + */ +@interface CHCircularBufferEnumerator : NSEnumerator +{ + id *array; // Underlying circular buffer to be enumerated. + NSUInteger arrayCapacity; // Allocated capacity of @a array. + NSUInteger arrayCount; // Number of elements in @a array. + NSUInteger enumerationCount; // How many objects have been enumerated. + NSUInteger enumerationIndex; // Index of the next element to enumerate. + BOOL reverseEnumeration; // Whether to enumerate back-to-front. + unsigned long mutationCount; // Stores the collection's initial mutation. + unsigned long *mutationPtr; // Pointer for checking changes in mutation. +} + +/** + Create an enumerator which traverses a circular buffer in the specified order. + + @param anArray The C pointer array of the circular buffer being enumerated. + @param capacity The total capacity of the circular buffer being enumerated. + @param count The number of items currently in the circular buffer. + @param startIndex The index at which to begin enumerating (forward or reverse). + @param direction The direction in which to enumerate. (@c NSOrderedDescending is back-to-front). + @param mutations A pointer to the collection's mutation count for invalidation. + @return An initialized CHCircularBufferEnumerator which will enumerate objects in @a anArray in the order specified by @a direction. + */ +- (id) initWithArray:(id*)anArray + capacity:(NSUInteger)capacity + count:(NSUInteger)count + startIndex:(NSUInteger)startIndex + direction:(NSComparisonResult)direction + mutationPointer:(unsigned long*)mutations; + +/** + Returns an array of objects the receiver has yet to enumerate. + + @return An array of objects the receiver has yet to enumerate. + + Invoking this method exhausts the remainder of the objects, such that subsequent + invocations of #nextObject return @c nil. + */ +- (NSArray*) allObjects; + +/** + Returns the next object from the collection being enumerated. + + @return The next object from the collection being enumerated, or + @c nil when all objects have been enumerated. + */ +- (id) nextObject; + +@end + +@implementation CHCircularBufferEnumerator + +- (id) initWithArray:(id*)anArray + capacity:(NSUInteger)capacity + count:(NSUInteger)count + startIndex:(NSUInteger)startIndex + direction:(NSComparisonResult)direction + mutationPointer:(unsigned long*)mutations +{ + if ((self = [super init]) == nil) return nil; + array = anArray; + arrayCapacity = capacity; + arrayCount = count; + enumerationCount = 0; + enumerationIndex = startIndex; + reverseEnumeration = (direction == NSOrderedDescending); + if (reverseEnumeration) + decrementIndex(enumerationIndex); + mutationCount = *mutations; + mutationPtr = mutations; + return self; +} + +- (NSArray*) allObjects { + NSMutableArray *allObjects = [[NSMutableArray alloc] init]; + if (reverseEnumeration) { + while (enumerationCount++ < arrayCount) { + [allObjects addObject:array[enumerationIndex]]; + decrementIndex(enumerationIndex); + } + } + else { + while (enumerationCount++ < arrayCount) { + [allObjects addObject:array[enumerationIndex]]; + incrementIndex(enumerationIndex); + } + } + if (mutationCount != *mutationPtr) + CHMutatedCollectionException([self class], _cmd); + return [allObjects autorelease]; +} + +- (id) nextObject { + id object = nil; + if (enumerationCount++ < arrayCount) { + object = array[enumerationIndex]; + if (reverseEnumeration) { + decrementIndex(enumerationIndex); + } + else { + incrementIndex(enumerationIndex); + } + } + if (mutationCount != *mutationPtr) + CHMutatedCollectionException([self class], _cmd); + return object; +} + +@end + +#pragma mark - + +/** + @todo Reimplement @c removeObjectsAtIndexes: for efficiency with multiple objects. + + @todo Look at refactoring @c insertObject:atIndex: and @c removeObjectAtIndex: + to always shift the smaller chunk of elements and deal with wrapping around. + The current worst-case is that removing at index N-1 of N when the buffer wraps + (or inserting at index 1 of N when it doesn't) causes N-1 objects to be shifted + in memory, where it would obviously make more sense to shift only one object. + Being able to shift the shorter side would almost always move less total data. + - Shifting without wrapping requires only 1 memmove(), <= the current size. + - Shifting around the end requires 0-2 memmove()s and an assignment. + - 0 if inserting/removing just inside head or tail, causing them to (un)wrap. + - 1 if inserting in first/last array slot with 1+ items wrapped on other end. + - 2 if inserting/removing further inside with 1+ items on other end. + */ +@implementation CHCircularBuffer + +- (void) dealloc { + [self removeAllObjects]; + free(array); + [super dealloc]; +} + +// Note: Defined here since -init is not implemented in NS(Mutable)Array. +- (id) init { + return [self initWithCapacity:DEFAULT_BUFFER_SIZE]; +} + +- (id) initWithArray:(NSArray*)anArray { + NSUInteger capacity = DEFAULT_BUFFER_SIZE; + while (capacity <= [anArray count]) + capacity *= 2; + if ([self initWithCapacity:capacity] == nil) return nil; + for (id anObject in anArray) { + array[tailIndex++] = [anObject retain]; + } + count = [anArray count]; + return self; +} + +// This is the designated initializer for CHCircularBuffer. +- (id) initWithCapacity:(NSUInteger)capacity { + if ((self = [super init]) == nil) return nil; + arrayCapacity = capacity ? capacity : DEFAULT_BUFFER_SIZE; + array = malloc(kCHPointerSize * arrayCapacity); + return self; +} + +#pragma mark + +// Overridden from NSMutableArray to encode/decode as the proper class. +- (Class) classForKeyedArchiver { + return [self class]; +} + +- (id) initWithCoder:(NSCoder*)decoder { + return [self initWithArray:[decoder decodeObjectForKey:@"array"]]; +} + +- (void) encodeWithCoder:(NSCoder*)encoder { + [encoder encodeObject:[self allObjects] forKey:@"array"]; +} + +#pragma mark + +- (id) copyWithZone:(NSZone*)zone { + return [[[self class] allocWithZone:zone] initWithArray:[self allObjects]]; +} + +#pragma mark + +/* + Since this class uses a C array for storage, we can return a pointer to any spot in the array and a count greater than "len". This approach avoids copy overhead, and is also more efficient since this method will be called only 2 or 3 times, depending on whether the buffer wraps around the end of the array. (The last call always returns 0 and requires no extra processing.) + */ +- (NSUInteger) countByEnumeratingWithState:(NSFastEnumerationState*)state + objects:(id*)stackbuf + count:(NSUInteger)len +{ + if (state->state == 0) { + state->mutationsPtr = &mutations; + state->itemsPtr = array + headIndex; // pointer arithmetic for offset + // If the buffer wraps, only provide elements to the end of the array. + NSUInteger enumeratedCount = MIN(arrayCapacity - headIndex, count); + state->state = (unsigned long) enumeratedCount; + return enumeratedCount; + } + else if (state->state < count) { + // This means the buffer wrapped around; now return the wrapped segment. + state->itemsPtr = array; + NSUInteger enumeratedCount = (NSUInteger) state->state; + state->state = (unsigned long) count; + return (count - enumeratedCount); + } + else { + return 0; + } +} + +#pragma mark Querying Contents + +- (NSArray*) allObjects { + NSMutableArray *allObjects = [[NSMutableArray alloc] init]; + if (count > 0) { + for (id anObject in self) { + [allObjects addObject:anObject]; + } + } + return [allObjects autorelease]; +} + +- (BOOL) containsObject:(id)anObject { + NSUInteger iterationIndex = headIndex; + while (iterationIndex != tailIndex) { + if ([array[iterationIndex] isEqual:anObject]) + return YES; + incrementIndex(iterationIndex); + } + return NO; +} + +- (BOOL) containsObjectIdenticalTo:(id)anObject { + NSUInteger iterationIndex = headIndex; + while (iterationIndex != tailIndex) { + if (array[iterationIndex] == anObject) + return YES; + incrementIndex(iterationIndex); + } + return NO; +} + +// NSArray primitive method +- (NSUInteger) count { + return count; +} + +- (id) firstObject { + return (count > 0) ? array[headIndex] : nil; +} + +- (NSUInteger) hash { + return hashOfCountAndObjects(count, [self firstObject], [self lastObject]); +} + +- (id) lastObject { + return (count > 0) ? array[((tailIndex) ? tailIndex : arrayCapacity) - 1] : nil; +} + +- (NSUInteger) indexOfObject:(id)anObject { + return [self indexOfObject:anObject inRange:NSMakeRange(0, count)]; +} + +- (NSUInteger) indexOfObject:(id)anObject inRange:(NSRange)range { + NSUInteger onePastLastRelativeIndex = range.location + range.length; + if (onePastLastRelativeIndex > count) + CHIndexOutOfRangeException([self class], _cmd, onePastLastRelativeIndex, count); + NSUInteger iterationIndex = transformIndex(range.location); + NSUInteger relativeIndex = range.location; + while (relativeIndex < onePastLastRelativeIndex) { + if ([array[iterationIndex] isEqual:anObject]) + return relativeIndex; + incrementIndex(iterationIndex); + relativeIndex++; + } + return NSNotFound; +} + +- (NSUInteger) indexOfObjectIdenticalTo:(id)anObject { + return [self indexOfObjectIdenticalTo:anObject inRange:NSMakeRange(0, count)]; +} + +- (NSUInteger)indexOfObjectIdenticalTo:(id)anObject inRange:(NSRange)range { + NSUInteger onePastLastRelativeIndex = range.location + range.length; + if (onePastLastRelativeIndex > count) + CHIndexOutOfRangeException([self class], _cmd, onePastLastRelativeIndex, count); + NSUInteger iterationIndex = transformIndex(range.location); + NSUInteger relativeIndex = range.location; + while (relativeIndex < onePastLastRelativeIndex) { + if (array[iterationIndex] == anObject) + return relativeIndex; + incrementIndex(iterationIndex); + relativeIndex++; + } + return NSNotFound; +} + +// NSArray primitive method +- (id) objectAtIndex:(NSUInteger)index { + if (index >= count) + CHIndexOutOfRangeException([self class], _cmd, index, count); + return array[transformIndex(index)]; +} + +- (NSArray*) objectsAtIndexes:(NSIndexSet*)indexes { + if (indexes == nil) + CHNilArgumentException([self class], _cmd); + if ([indexes count] == 0) + return [NSArray array]; + NSMutableArray *objects = [NSMutableArray arrayWithCapacity:[indexes count]]; + NSUInteger index = [indexes firstIndex]; + while (index != NSNotFound) { + [objects addObject:[self objectAtIndex:index]]; + index = [indexes indexGreaterThanIndex:index]; + } + return objects; +} + +- (NSEnumerator*) objectEnumerator { + return [[[CHCircularBufferEnumerator alloc] + initWithArray:array + capacity:arrayCapacity + count:count + startIndex:headIndex + direction:NSOrderedAscending + mutationPointer:&mutations] autorelease]; +} + +- (NSEnumerator*) reverseObjectEnumerator { + return [[[CHCircularBufferEnumerator alloc] + initWithArray:array + capacity:arrayCapacity + count:count + startIndex:tailIndex + direction:NSOrderedDescending + mutationPointer:&mutations] autorelease]; +} + +#pragma mark Modifying Contents + +// NSMutableArray primitive method +- (void) addObject:(id)anObject { + [self insertObject:anObject atIndex:count]; +} + +// NSMutableArray primitive method +- (void) insertObject:(id)anObject atIndex:(NSUInteger)index { + if (index > count) + CHIndexOutOfRangeException([self class], _cmd, index, count); + if (anObject == nil) + CHNilArgumentException([self class], _cmd); + [anObject retain]; + if (count == 0 || index == count) { + // To append, just move the tail forward one slot (wrapping if needed) + array[tailIndex] = anObject; + incrementIndex(tailIndex); + } else if (index == 0) { + // To prepend, just move the head backward one slot (wrapping if needed) + decrementIndex(headIndex); + array[headIndex] = anObject; + } else { + NSUInteger actualIndex = transformIndex(index); + if (actualIndex > tailIndex) { + // Buffer wraps and 'index' is between head and end, so shift left. + memmove(&array[headIndex - 1], &array[headIndex], kCHPointerSize * index); + // These can't wrap around (we'll hit tail first) so just decrement. + --headIndex; + --actualIndex; + } + else { + // Otherwise, shift everything from given index onward to the right. + memmove(&array[actualIndex + 1], &array[actualIndex], kCHPointerSize * (tailIndex - actualIndex)); + incrementIndex(tailIndex); + } + array[actualIndex] = anObject; + } + ++count; + ++mutations; + // If this insertion filled the array to capacity, double its size and copy. + if (headIndex == tailIndex) { + array = realloc(array, kCHPointerSize * arrayCapacity * 2); + // Copy wrapped-around portion to end of queue and move tail index + memmove(array + arrayCapacity, array, kCHPointerSize * tailIndex); + bzero(array, kCHPointerSize * tailIndex); // Zero the source of the copy + tailIndex += arrayCapacity; + arrayCapacity *= 2; + } +} + +- (void) exchangeObjectAtIndex:(NSUInteger)idx1 withObjectAtIndex:(NSUInteger)idx2 { + if (idx1 >= count || idx2 >= count) + CHIndexOutOfRangeException([self class], _cmd, MAX(idx1,idx2), count); + if (idx1 != idx2) { + // Find the "real" equivalents of the provided indexes + NSUInteger realIdx1 = transformIndex(idx1); + NSUInteger realIdx2 = transformIndex(idx2); + // Swap the objects at the provided indexes + id tempObject = array[realIdx1]; + array[realIdx1] = array[realIdx2]; + array[realIdx2] = tempObject; + ++mutations; + } +} + +- (void) removeFirstObject { + if (count == 0) + return; + [array[headIndex] release]; + array[headIndex] = nil; // Let GC do its thing + incrementIndex(headIndex); + --count; + ++mutations; +} + +// NSMutableArray primitive method +- (void) removeLastObject { + if (count == 0) + return; + decrementIndex(tailIndex); + [array[tailIndex] release]; + array[tailIndex] = nil; // Let GC do its thing + --count; + ++mutations; +} + +// Private method that accepts a function pointer for testing object equality. +- (void) removeObject:(id)anObject withEqualityTest:(BOOL(*)(id,id))objectsMatch { + if (count == 0 || anObject == nil) + return; + // Strip off leading matches if any exist in the buffer. + while (headIndex != tailIndex && objectsMatch(array[headIndex], anObject)) { + [array[headIndex] release]; + array[headIndex] = nil; // Let GC do its thing + incrementIndex(headIndex); + } + // Scan ahead to find the next matching object to remove, if one exists. + NSUInteger scanIndex = headIndex; + while (scanIndex != tailIndex && !objectsMatch(array[scanIndex], anObject)) + incrementIndex(scanIndex); + // Scan other objects; release and skip matches, block copy objects to keep. + NSUInteger copySrcIndex = scanIndex; // index to copy FROM when closing gaps + NSUInteger copyDstIndex = scanIndex; // index to copy TO when closing gaps + while (scanIndex != tailIndex) { + if (objectsMatch(array[scanIndex], anObject)) { + [array[scanIndex] release]; + // If the object is preceded by 1+ not to remove, close the gap now. + // NOTE: blockMove advances src/dst indexes by the count of objects. + if (copySrcIndex != scanIndex) + blockMove(copyDstIndex, copySrcIndex, scanIndex); + incrementIndex(copySrcIndex); // Advance to where scanIndex will be. + } + incrementIndex(scanIndex); + } + blockMove(copyDstIndex, copySrcIndex, tailIndex); // fixes any trailing gaps + if (tailIndex != copyDstIndex) { + // Zero any now-unoccupied array elements if tail pointer moved left. + // Under GC, this prevents holding onto removed objects unnecessarily. + // Under retain-release, it promotes fail-fast behavior to reveal bugs. + if (tailIndex > copyDstIndex) { + bzero(array + copyDstIndex, kCHPointerSize * (tailIndex - copyDstIndex)); + } else { + bzero(array + copyDstIndex, kCHPointerSize * (arrayCapacity - copyDstIndex)); + bzero(array, kCHPointerSize * tailIndex); + } + tailIndex = copyDstIndex; + } + count = (tailIndex + arrayCapacity - headIndex) % arrayCapacity; + ++mutations; +} + +- (void) removeObject:(id)anObject { + [self removeObject:anObject withEqualityTest:&objectsAreEqual]; +} + +// NSMutableArray primitive method +- (void) removeObjectAtIndex:(NSUInteger)index { + if (index >= count) + CHIndexOutOfRangeException([self class], _cmd, index, count); + NSUInteger actualIndex = transformIndex(index); + [array[actualIndex] release]; + // Handle the simple cases of removing the first or last object first. + if (index == 0) { + array[actualIndex] = nil; // Prevents possible memory leak under GC + incrementIndex(headIndex); + } else if (index == count - 1) { + array[actualIndex] = nil; // Prevents possible memory leak under GC + decrementIndex(tailIndex); + } else { + // This logic is derived from http://www.javafaq.nu/java-article808.html + // For simplicity, this code doesnt shift elements around the array end. + // Consequently headIndex and tailIndex will not wrap past the end here. + if (actualIndex > tailIndex) { + // If the buffer wraps and index is in "the right side", shift right. + memmove(&array[headIndex+1], &array[headIndex], kCHPointerSize * index); + array[headIndex++] = nil; // Prevents possible memory leak under GC + } else { + // Otherwise, shift everything from index to tail one to the left. + memmove(&array[actualIndex], &array[actualIndex + 1], kCHPointerSize * (tailIndex - actualIndex - 1)); + array[--tailIndex] = nil; // Prevents possible memory leak under GC + } + } + --count; + ++mutations; +} + +- (void) removeObjectIdenticalTo:(id)anObject { + [self removeObject:anObject withEqualityTest:&objectsAreIdentical]; +} + +- (void) removeObjectsAtIndexes:(NSIndexSet*)indexes { + if (indexes == nil) + CHNilArgumentException([self class], _cmd); + if ([indexes count] > 0) { + NSUInteger index = [indexes lastIndex]; + while (index != NSNotFound) { + [self removeObjectAtIndex:index]; + index = [indexes indexLessThanIndex:index]; + } + } +} + +- (void) removeAllObjects { + if (count > 0) { + while (headIndex != tailIndex) { + [array[headIndex] release]; + incrementIndex(headIndex); + } + if (arrayCapacity > DEFAULT_BUFFER_SIZE) { + arrayCapacity = DEFAULT_BUFFER_SIZE; + // Shrink the size of allocated memory; calls realloc() under non-GC + array = realloc(array, kCHPointerSize * arrayCapacity); + } + } + headIndex = tailIndex = 0; + count = 0; + ++mutations; +} + +// NSMutableArray primitive method +- (void) replaceObjectAtIndex:(NSUInteger)index withObject:(id)anObject { + if (index >= count) + CHIndexOutOfRangeException([self class], _cmd, index, count); + [anObject retain]; + [array[transformIndex(index)] release]; + array[transformIndex(index)] = anObject; +} + +@end diff --git a/Sources/XMLParsing/CHDataStructures/CHMutableDictionary.h b/Sources/XMLParsing/CHDataStructures/CHMutableDictionary.h new file mode 100755 index 0000000..4276fe9 --- /dev/null +++ b/Sources/XMLParsing/CHDataStructures/CHMutableDictionary.h @@ -0,0 +1,45 @@ +/* + CHDataStructures.framework -- CHMutableDictionary.h + + Copyright (c) 2009-2010, Quinn Taylor + + This source code is released under the ISC License. + + Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. + + The software is provided "as is", without warranty of any kind, including all implied warranties of merchantability and fitness. In no event shall the authors or copyright holders be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the software or the use or other dealings in the software. + */ + +#import "Util.h" + +HIDDEN void createCollectableCFMutableDictionary(CFMutableDictionaryRef* dictionary, NSUInteger initialCapacity); + +/** + @file CHMutableDictionary.h + + A mutable dictionary class. + */ + +/** + A mutable dictionary class. + + A CFMutableDictionaryRef is used internally to store the key-value pairs. Subclasses may choose to add other instance variables to enable a specific ordering of keys, override methods to modify behavior, and add methods to extend existing behaviors. However, all subclasses should behave like a standard Cocoa dictionary as much as possible, and document clearly when they do not. + + @note Any method inherited from NSDictionary or NSMutableDictionary is supported by this class and its children. Please see the documentation for those classes for details. + + @todo Implement @c -copy and @c -mutableCopy differently (so users can actually obtain an immutable copy) and make mutation methods aware of immutability? + */ +@interface CHMutableDictionary : NSMutableDictionary { + CFMutableDictionaryRef dictionary; // A Core Foundation dictionary. +} + +- (id) initWithCapacity:(NSUInteger)numItems NS_DESIGNATED_INITIALIZER; + +- (NSUInteger) count; +- (NSEnumerator*) keyEnumerator; +- (id) objectForKey:(id)aKey; +- (void) removeAllObjects; +- (void) removeObjectForKey:(id)aKey; +- (void) setObject:(id)anObject forKey:(id)aKey; + +@end diff --git a/Sources/XMLParsing/CHDataStructures/CHMutableDictionary.m b/Sources/XMLParsing/CHDataStructures/CHMutableDictionary.m new file mode 100755 index 0000000..a119c61 --- /dev/null +++ b/Sources/XMLParsing/CHDataStructures/CHMutableDictionary.m @@ -0,0 +1,161 @@ +/* + CHDataStructures.framework -- CHMutableDictionary.m + + Copyright (c) 2009-2010, Quinn Taylor + + This source code is released under the ISC License. + + Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. + + The software is provided "as is", without warranty of any kind, including all implied warranties of merchantability and fitness. In no event shall the authors or copyright holders be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the software or the use or other dealings in the software. + */ + +#import "CHMutableDictionary.h" + +#pragma mark CFDictionary callbacks + +const void* CHDictionaryRetain(CFAllocatorRef allocator, const void *value) { + return [(id)value retain]; +} + +void CHDictionaryRelease(CFAllocatorRef allocator, const void *value) { + [(id)value release]; +} + +CFStringRef CHDictionaryCopyDescription(const void *value) { + return (CFStringRef)[[(id)value description] copy]; +} + +Boolean CHDictionaryEqual(const void *value1, const void *value2) { + return [(id)value1 isEqual:(id)value2]; +} + +CFHashCode CHDictionaryHash(const void *value) { + return (CFHashCode)[(id)value hash]; +} + +static const CFDictionaryKeyCallBacks kCHDictionaryKeyCallBacks = { + 0, // default version + CHDictionaryRetain, + CHDictionaryRelease, + CHDictionaryCopyDescription, + CHDictionaryEqual, + CHDictionaryHash +}; + +static const CFDictionaryValueCallBacks kCHDictionaryValueCallBacks = { + 0, // default version + CHDictionaryRetain, + CHDictionaryRelease, + CHDictionaryCopyDescription, + CHDictionaryEqual +}; + +HIDDEN void createCollectableCFMutableDictionary(CFMutableDictionaryRef* dictionary, NSUInteger initialCapacity) +{ + // Create a CFMutableDictionaryRef with callback functions as defined above. + *dictionary = CFDictionaryCreateMutable(kCFAllocatorDefault, + initialCapacity, + &kCHDictionaryKeyCallBacks, + &kCHDictionaryValueCallBacks); + // Hand the reference off to GC if it's enabled, perform a no-op otherwise. + CFMakeCollectable(*dictionary); +} + +#pragma mark - + +@implementation CHMutableDictionary + +- (void) dealloc { + CFRelease(dictionary); // The dictionary will never be null at this point. + [super dealloc]; +} + +// Note: Defined here since -init is not implemented in NS(Mutable)Dictionary. +- (id) init { + return [self initWithCapacity:0]; // The 0 means we provide no capacity hint +} + +// Note: This is the designated initializer for NSMutableDictionary and this class. +// Subclasses may override this as necessary, but must call back here first. +- (id) initWithCapacity:(NSUInteger)numItems { + if ((self = [super init]) == nil) return nil; + createCollectableCFMutableDictionary(&dictionary, numItems); + return self; +} + +#pragma mark + +// Overridden from NSMutableDictionary to encode/decode as the proper class. +- (Class) classForKeyedArchiver { + return [self class]; +} + +- (id) initWithCoder:(NSCoder*)decoder { + return [self initWithDictionary:[decoder decodeObjectForKey:@"dictionary"]]; +} + +- (void) encodeWithCoder:(NSCoder*)encoder { + [encoder encodeObject:(NSDictionary*)dictionary forKey:@"dictionary"]; +} + +#pragma mark + +- (id) copyWithZone:(NSZone*) zone { + // We could use -initWithDictionary: here, but it would just use more memory. + // (It marshals key-value pairs into two id* arrays, then inits from those.) + CHMutableDictionary *copy = [[[self class] allocWithZone:zone] init]; + [copy addEntriesFromDictionary:self]; + return copy; +} + +#pragma mark + +- (NSUInteger) countByEnumeratingWithState:(NSFastEnumerationState*)state + objects:(id*)stackbuf + count:(NSUInteger)len +{ + return [super countByEnumeratingWithState:state objects:stackbuf count:len]; +} + +#pragma mark Querying Contents + +- (NSUInteger) count { + return CFDictionaryGetCount(dictionary); +} + +- (NSString*) debugDescription { + CFStringRef description = CFCopyDescription(dictionary); + CFRelease([(id)description retain]); + return [(id)description autorelease]; +} + +- (NSEnumerator*) keyEnumerator { + return [(id)dictionary keyEnumerator]; +} + +- (NSEnumerator*) objectEnumerator { + return [(id)dictionary objectEnumerator]; +} + +- (id) objectForKey:(id)aKey { + return (id)CFDictionaryGetValue(dictionary, aKey); +} + +#pragma mark Modifying Contents + +- (void) removeAllObjects { + CFDictionaryRemoveAllValues(dictionary); +} + +- (void) removeObjectForKey:(id)aKey { + CFDictionaryRemoveValue(dictionary, aKey); +} + +- (void) setObject:(id)anObject forKey:(id)aKey { + if (anObject == nil || aKey == nil) + CHNilArgumentException([self class], _cmd); + CFDictionarySetValue(dictionary, [[aKey copy] autorelease], anObject); +} + +@end diff --git a/Sources/XMLParsing/CHDataStructures/CHOrderedDictionary.h b/Sources/XMLParsing/CHDataStructures/CHOrderedDictionary.h new file mode 100755 index 0000000..08b38c0 --- /dev/null +++ b/Sources/XMLParsing/CHDataStructures/CHOrderedDictionary.h @@ -0,0 +1,257 @@ +/* + CHDataStructures.framework -- CHOrderedDictionary.h + + Copyright (c) 2009-2010, Quinn Taylor + + This source code is released under the ISC License. + + Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. + + The software is provided "as is", without warranty of any kind, including all implied warranties of merchantability and fitness. In no event shall the authors or copyright holders be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the software or the use or other dealings in the software. + */ + +#import "CHMutableDictionary.h" + +/** + @file CHOrderedDictionary.h + + A dictionary which enumerates keys in the order in which they are inserted. + */ + +/** + A dictionary which enumerates keys in the order in which they are inserted. The following additional operations are provided to take advantage of the ordering: + - \link #firstKey\endlink + - \link #lastKey\endlink + - \link #keyAtIndex:\endlink + - \link #reverseKeyEnumerator\endlink + + Key-value entries are inserted just as in a normal dictionary, including replacement of values for existing keys, as detailed in \link #setObject:forKey: -setObject:forKey:\endlink. However, an additional structure is used in parallel to track insertion order, and keys are enumerated in that order. If a key to be added does not currently exist in the dictionary, it is added to the end of the list, otherwise the insertion order of the key does not change. + + Implementations of insertion-ordered dictionaries (aka "maps") in other languages include the following: + + - LinkedHashMap / LinkedMap (Java) + - LinkedMap (Lua) + - OrderedDict (Python) + - OrderedDictionary (.NET) + - OrderedDictionary (Flash) + + @note Any method inherited from NSDictionary or NSMutableDictionary is supported, but only overridden methods are listed here. + + @see CHOrderedSet + */ +@interface CHOrderedDictionary : CHMutableDictionary { + id keyOrdering; +} + +#pragma mark Querying Contents +/** @name Querying Contents */ +// @{ + +/** + Returns the first key in the receiver, according to insertion order. + + @return The first key in the receiver, or @c nil if the receiver is empty. + + @see keyAtIndex: + @see lastKey + */ +- (id) firstKey; + +/** + Returns the last key in the receiver, according to insertion order. + + @return The last key in the receiver, or @c nil if the receiver is empty. + + @see firstKey + @see keyAtIndex: + */ +- (id) lastKey; + +/** + Returns the index of a given key based on insertion order. + + @param aKey The key to search for in the receiver. + @return The index of @a akey based on insertion order. If the key does not exist in the receiver, @c NSNotFound is returned. + + @see firstKey + @see keyAtIndex: + @see lastKey + */ +- (NSUInteger) indexOfKey:(id)aKey; + +/** + Returns the key at the specified index, based on insertion order. + + @param index The insertion-order index of the key to retrieve. + @return The key at the specified index, based on insertion order. + + @throw NSRangeException if @a index exceeds the bounds of the receiver. + + @see \link NSDictionary#containsKey: -containsKey:\endlink + @see firstKey + @see indexOfKey: + @see lastKey + */ +- (id) keyAtIndex:(NSUInteger)index; + +/** + Returns an array containing the keys in the receiver at the indexes specified by a given index set. + + @param indexes A set of positions corresponding to keys to retrieve from the receiver. + @return A new array containing the keys in the receiver specified by @a indexes. + + @throw NSRangeException if any location in @a indexes exceeds the bounds of the receiver. + @throw NSInvalidArgumentException if @a indexes is @c nil. + + @see \link NSDictionary#allKeys -allKeys\endlink + @see orderedDictionaryWithKeysAtIndexes: + */ +- (NSArray*) keysAtIndexes:(NSIndexSet*)indexes; + +/** + Returns the value for the key at the specified index, based on insertion order. + + @param index The insertion-order index of the key for the value to retrieve. + @return The value for the key at the specified index, based on insertion order. + + @throw NSRangeException if @a index exceeds the bounds of the receiver. + + @see indexOfKey: + @see keyAtIndex: + @see \link NSDictionary#objectForKey: -objectForKey:\endlink + @see removeObjectForKeyAtIndex: + */ +- (id) objectForKeyAtIndex:(NSUInteger)index; + +/** + Returns the set of objects from the receiver corresponding to the keys at the indexes specified by a given index set. + + @param indexes A set of positions corresponding to objects to retrieve from the receiver. + @return A new array containing the objects in the receiver for the keys specified by @a indexes. + + @throw NSRangeException if any location in @a indexes exceeds the bounds of the receiver. + @throw NSInvalidArgumentException if @a indexes is @c nil. + + @see keysAtIndexes: + @see \link NSDictionary#objectsForKeys:notFoundMarker: -objectsForKeys:notFoundMarker:\endlink + @see removeObjectsForKeysAtIndexes: + */ +- (NSArray*) objectsForKeysAtIndexes:(NSIndexSet*)indexes; + +/** + Returns an ordered dictionary containing the entries in the receiver with keys at the indexes specified by a given index set. + + @param indexes A set of indexes for keys to retrieve from the receiver. + @return An array containing the entries in the receiver at the indexes specified by @a indexes. + + @throw NSRangeException if any location in @a indexes exceeds the bounds of the receiver. + @throw NSInvalidArgumentException if @a indexes is @c nil. + + @attention To retrieve entries in a given NSRange, pass [NSIndexSet indexSetWithIndexesInRange:range] as the parameter. + */ +- (CHOrderedDictionary*) orderedDictionaryWithKeysAtIndexes:(NSIndexSet*)indexes; + +/** + Returns an enumerator that lets you access each key in the receiver in reverse order. + + @return An enumerator that lets you access each key in the receiver in reverse order. The enumerator returned is never @c nil; if the dictionary is empty, the enumerator will always return @c nil for \link NSEnumerator#nextObject -nextObject\endlink and an empty array for \link NSEnumerator#allObjects -allObjects\endlink. + + @attention The enumerator retains the collection. Once all objects in the enumerator have been consumed, the collection is released. + @warning Modifying a collection while it is being enumerated is unsafe, and may cause a mutation exception to be raised. + + @note If you need to modify the entries concurrently, use \link NSDictionary#allKeys -allKeys\endlink to create a "snapshot" of the dictionary's keys and work from this snapshot to modify the entries. + + @see \link NSDictionary#allKeys -allKeys\endlink + @see \link NSDictionary#allKeysForObject: -allKeysForObject:\endlink + @see NSFastEnumeration protocol + */ +- (NSEnumerator*) reverseKeyEnumerator; + +// @} +#pragma mark Modifying Contents +/** @name Modifying Contents */ +// @{ + +/** + Exchange the keys in the receiver at given indexes. + + @param idx1 The index of the key to replace with the key at @a idx2. + @param idx2 The index of the key to replace with the key at @a idx1. + + @throw NSRangeException if @a idx1 or @a idx2 exceeds the bounds of the receiver. + + @see indexOfKey: + @see keyAtIndex: + */ +- (void) exchangeKeyAtIndex:(NSUInteger)idx1 withKeyAtIndex:(NSUInteger)idx2; + +/** + Adds a given key-value pair to the receiver, with the key at a given index in the ordering. + + @param anObject The value for @a aKey. The object receives a @c -retain message before being added to the receiver. Must not be @c nil. + @param aKey The key for @a anObject. The key is copied using @c -copyWithZone: so keys must conform to the NSCopying protocol. Must not be @c nil. + @param index The index in the receiver's key ordering at which to insert @a anObject. + + @throw NSRangeException if @a index exceeds the bounds of the receiver. + @throw NSInvalidArgumentException if @a aKey or @a anObject is @c nil. If you need to represent a @c nil value in the dictionary, use NSNull. + + @see indexOfKey: + @see keyAtIndex: + @see setObject:forKey: + */ +- (void) insertObject:(id)anObject forKey:(id)aKey atIndex:(NSUInteger)index; + +/** + Removes the key at a given index from the receiver. Elements on the non-wrapped end of the buffer are shifted one spot to fill the gap. + + @param index The index of the key to remove. + + @throw NSRangeException if @a index exceeds the bounds of the receiver. + + @see indexOfKey: + @see keyAtIndex: + @see objectForKeyAtIndex: + @see \link NSMutableDictionary#removeObjectForKey: -removeObjectForKey:\endlink + */ +- (void) removeObjectForKeyAtIndex:(NSUInteger)index; + +/** + Removes the keys at the specified indexes from the receiver. This method is similar to #removeObjectForKeyAtIndex: but allows you to efficiently remove multiple keys with a single operation. + + @param indexes The indexes of the keys to remove from the receiver. + + @throw NSRangeException if any location in @a indexes exceeds the bounds of the receiver. + @throw NSInvalidArgumentException if @a indexes is @c nil. + + @see objectsForKeysAtIndexes: + @see removeObjectForKeyAtIndex: + */ +- (void) removeObjectsForKeysAtIndexes:(NSIndexSet*)indexes; + +/** + Adds a given key-value pair to the receiver, with the key added at the end of the ordering. + + @param anObject The value for @a aKey. The object receives a @c -retain message before being added to the receiver. Must not be @c nil. + @param aKey The key for @a anObject. The key is copied using @c -copyWithZone: so keys must conform to the NSCopying protocol. Must not be @c nil. + + @see insertObject:forKey:atIndex: + @see setObject:forKeyAtIndex: + */ +- (void) setObject:(id)anObject forKey:(id)aKey; + +/** + Sets the value for the key at the specified index in the receiver. + + @param anObject The new value to be set for the key at @a index. The object receives a @c -retain message before being added to the receiver. Must not be @c nil. + @param index The index of the key for which to set the value. + + @throw NSInvalidArgumentException if @a anObject is @c nil. If you need to represent a @c nil value in the dictionary, use NSNull. + @throw NSRangeException if @a index exceeds the bounds of the receiver. + + @see insertObject:forKey:atIndex: + @see setObject:forKey: + */ +- (void) setObject:(id)anObject forKeyAtIndex:(NSUInteger)index; + +// @} +@end diff --git a/Sources/XMLParsing/CHDataStructures/CHOrderedDictionary.m b/Sources/XMLParsing/CHDataStructures/CHOrderedDictionary.m new file mode 100755 index 0000000..bb3f0dc --- /dev/null +++ b/Sources/XMLParsing/CHDataStructures/CHOrderedDictionary.m @@ -0,0 +1,163 @@ +/* + CHDataStructures.framework -- CHOrderedDictionary.m + + Copyright (c) 2009-2010, Quinn Taylor + + This source code is released under the ISC License. + + Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. + + The software is provided "as is", without warranty of any kind, including all implied warranties of merchantability and fitness. In no event shall the authors or copyright holders be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the software or the use or other dealings in the software. + */ + +#import "CHOrderedDictionary.h" +#import "CHCircularBuffer.h" + +@implementation CHOrderedDictionary + +- (void) dealloc { + [keyOrdering release]; + [super dealloc]; +} + +- (id) initWithCapacity:(NSUInteger)numItems { + if ((self = [super initWithCapacity:numItems]) == nil) return nil; + keyOrdering = [[CHCircularBuffer alloc] initWithCapacity:numItems]; + return self; +} + +- (id) initWithCoder:(NSCoder*)decoder { + if ((self = [super initWithCoder:decoder]) == nil) return nil; + [keyOrdering release]; + keyOrdering = [[decoder decodeObjectForKey:@"keyOrdering"] retain]; + return self; +} + +- (void) encodeWithCoder:(NSCoder*)encoder { + [super encodeWithCoder:encoder]; + [encoder encodeObject:keyOrdering forKey:@"keyOrdering"]; +} + +#pragma mark + +/** @test Add unit test. */ +- (NSUInteger) countByEnumeratingWithState:(NSFastEnumerationState*)state + objects:(id*)stackbuf + count:(NSUInteger)len +{ + return [keyOrdering countByEnumeratingWithState:state objects:stackbuf count:len]; +} + +#pragma mark Querying Contents + +- (id) firstKey { + return [keyOrdering firstObject]; +} + +- (NSUInteger) hash { + return [keyOrdering hash]; +} + +- (id) lastKey { + return [keyOrdering lastObject]; +} + +- (NSUInteger) indexOfKey:(id)aKey { + if (CFDictionaryContainsKey(dictionary, aKey)) + return [keyOrdering indexOfObject:aKey]; + else + return NSNotFound; +} + +- (id) keyAtIndex:(NSUInteger)index { + return [keyOrdering objectAtIndex:index]; +} + +- (NSArray*) keysAtIndexes:(NSIndexSet*)indexes { + return [keyOrdering objectsAtIndexes:indexes]; +} + +- (NSEnumerator*) keyEnumerator { + return [keyOrdering objectEnumerator]; +} + +- (id) objectForKeyAtIndex:(NSUInteger)index { + // Note: -keyAtIndex: will raise an exception if the index is invalid. + return [self objectForKey:[self keyAtIndex:index]]; +} + +- (NSArray*) objectsForKeysAtIndexes:(NSIndexSet*)indexes { + return [self objectsForKeys:[self keysAtIndexes:indexes] notFoundMarker:self]; +} + +- (CHOrderedDictionary*) orderedDictionaryWithKeysAtIndexes:(NSIndexSet*)indexes { + if (indexes == nil) + CHNilArgumentException([self class], _cmd); + if ([indexes count] == 0) + return [[self class] dictionary]; + CHOrderedDictionary* newDictionary = [[self class] dictionaryWithCapacity:[indexes count]]; + NSUInteger index = [indexes firstIndex]; + while (index != NSNotFound) { + id key = [self keyAtIndex:index]; + [newDictionary setObject:[self objectForKey:key] forKey:key]; + index = [indexes indexGreaterThanIndex:index]; + } + return newDictionary; +} + +- (NSEnumerator*) reverseKeyEnumerator { + return [keyOrdering reverseObjectEnumerator]; +} + +#pragma mark Modifying Contents + +- (void) exchangeKeyAtIndex:(NSUInteger)idx1 withKeyAtIndex:(NSUInteger)idx2 { + [keyOrdering exchangeObjectAtIndex:idx1 withObjectAtIndex:idx2]; +} + +- (void) insertObject:(id)anObject forKey:(id)aKey atIndex:(NSUInteger)index { + if (index > [self count]) + CHIndexOutOfRangeException([self class], _cmd, index, [self count]); + if (anObject == nil || aKey == nil) + CHNilArgumentException([self class], _cmd); + + id clonedKey = [[aKey copy] autorelease]; + if (!CFDictionaryContainsKey(dictionary, clonedKey)) { + [keyOrdering insertObject:clonedKey atIndex:index]; + } + CFDictionarySetValue(dictionary, clonedKey, anObject); +} + +- (void) removeAllObjects { + [super removeAllObjects]; + [keyOrdering removeAllObjects]; +} + +- (void) removeObjectForKey:(id)aKey { + if (CFDictionaryContainsKey(dictionary, aKey)) { + [super removeObjectForKey:aKey]; + [keyOrdering removeObject:aKey]; + } +} + +- (void) removeObjectForKeyAtIndex:(NSUInteger)index { + // Note: -keyAtIndex: will raise an exception if the index is invalid. + [super removeObjectForKey:[self keyAtIndex:index]]; + [keyOrdering removeObjectAtIndex:index]; +} + +- (void) removeObjectsForKeysAtIndexes:(NSIndexSet*)indexes { + NSArray* keysToRemove = [keyOrdering objectsAtIndexes:indexes]; + [keyOrdering removeObjectsAtIndexes:indexes]; + [(NSMutableDictionary*)dictionary removeObjectsForKeys:keysToRemove]; +} + +- (void) setObject:(id)anObject forKey:(id)aKey { + [self insertObject:anObject forKey:aKey atIndex:[self count]]; +} + +- (void) setObject:(id)anObject forKeyAtIndex:(NSUInteger)index { + [self insertObject:anObject forKey:[self keyAtIndex:index] atIndex:index]; +} + +@end diff --git a/Sources/XMLParsing/CHDataStructures/Util.h b/Sources/XMLParsing/CHDataStructures/Util.h new file mode 100755 index 0000000..b956efb --- /dev/null +++ b/Sources/XMLParsing/CHDataStructures/Util.h @@ -0,0 +1,174 @@ +/* + CHDataStructures.framework -- Util.h + + Copyright (c) 2008-2010, Quinn Taylor + + This source code is released under the ISC License. + + Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. + + The software is provided "as is", without warranty of any kind, including all implied warranties of merchantability and fitness. In no event shall the authors or copyright holders be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the software or the use or other dealings in the software. + */ + +#import + +/** + @file Util.h + A group of utility C functions for simplifying common exceptions and logging. + */ + +/** Macro for reducing visibility of symbol names not indended to be exported. */ +#define HIDDEN __attribute__((visibility("hidden"))) + +/** Macro for designating symbols as being unused to suppress compile warnings. */ +#define UNUSED __attribute__((unused)) + +#pragma mark - + +/** Global variable to store the size of a pointer only once. */ +OBJC_EXPORT size_t kCHPointerSize; + +/** + Simple function for checking object equality, to be used as a function pointer. + + @param o1 The first object to be compared. + @param o2 The second object to be compared. + @return [o1 isEqual:o2] + */ +HIDDEN BOOL objectsAreEqual(id o1, id o2); + +/** + Simple function for checking object identity, to be used as a function pointer. + + @param o1 The first object to be compared. + @param o2 The second object to be compared. + @return o1 == o2 + */ +HIDDEN BOOL objectsAreIdentical(id o1, id o2); + +/** + Determine whether two collections enumerate the equivalent objects in the same order. + + @param collection1 The first collection to be compared. + @param collection2 The second collection to be compared. + @return Whether the collections are equivalent. + + @throw NSInvalidArgumentException if one of both of the arguments do not respond to the @c -count or @c -objectEnumerator selectors. + */ +OBJC_EXPORT BOOL collectionsAreEqual(id collection1, id collection2); + +/** + Generate a hash for a collection based on the count and up to two objects. If objects are provided, the result of their -hash method will be used. + + @param count The number of objects in the collection. + @param o1 The first object to include in the hash. + @param o2 The second object to include in the hash. + @return An unsigned integer that can be used as a table address in a hash table structure. + */ +HIDDEN NSUInteger hashOfCountAndObjects(NSUInteger count, id o1, id o2); + +#pragma mark - + +/** + Convenience function for raising an exception for an invalid range (index). + + Currently, there is no support for calling this function from a C function. + + @param aClass The class object for the originator of the exception. Callers should pass the result of [self class] for this parameter. + @param method The method selector where the problem originated. Callers should pass @c _cmd for this parameter. + @param index The offending index passed to the receiver. + @param elements The number of elements present in the receiver. + + @throw NSRangeException + + @see \link NSException#raise:format: +[NSException raise:format:]\endlink + */ +OBJC_EXPORT void CHIndexOutOfRangeException(Class aClass, SEL method, + NSUInteger index, NSUInteger elements); + +/** + Convenience function for raising an exception on an invalid argument. + + Currently, there is no support for calling this function from a C function. + + @param aClass The class object for the originator of the exception. Callers should pass the result of [self class] for this parameter. + @param method The method selector where the problem originated. Callers should pass @c _cmd for this parameter. + @param str An NSString describing the offending invalid argument. + + @throw NSInvalidArgumentException + + @see \link NSException#raise:format: +[NSException raise:format:]\endlink + */ +OBJC_EXPORT void CHInvalidArgumentException(Class aClass, SEL method, NSString *str); + +/** + Convenience function for raising an exception on an invalid nil object argument. + + Currently, there is no support for calling this function from a C function. + + @param aClass The class object for the originator of the exception. Callers should pass the result of [self class] for this parameter. + @param method The method selector where the problem originated. Callers should pass @c _cmd for this parameter. + + @throw NSInvalidArgumentException + + @see CHInvalidArgumentException() + */ +OBJC_EXPORT void CHNilArgumentException(Class aClass, SEL method); + +/** + Convenience function for raising an exception when a collection is mutated. + + Currently, there is no support for calling this function from a C function. + + @param aClass The class object for the originator of the exception. Callers should pass the result of [self class] for this parameter. + @param method The method selector where the problem originated. Callers should pass @c _cmd for this parameter. + + @throw NSGenericException + + @see \link NSException#raise:format: +[NSException raise:format:]\endlink + */ +OBJC_EXPORT void CHMutatedCollectionException(Class aClass, SEL method); + +/** + Convenience function for raising an exception for un-implemented functionality. + + Currently, there is no support for calling this function from a C function. + + @param aClass The class object for the originator of the exception. Callers should pass the result of [self class] for this parameter. + @param method The method selector where the problem originated. Callers should pass @c _cmd for this parameter. + + @throw NSInternalInconsistencyException + + @see \link NSException#raise:format: +[NSException raise:format:]\endlink + */ +OBJC_EXPORT void CHUnsupportedOperationException(Class aClass, SEL method); + +/** + Provides a more terse alternative to NSLog() which accepts the same parameters. The output is made shorter by excluding the date stamp and process information which NSLog prints before the actual specified output. + + @param format A format string, which must not be nil. + @param ... A comma-separated list of arguments to substitute into @a format. + + Read Formatting String Objects and String Format Specifiers on this webpage for details about using format strings. Look for examples that use @c NSLog() since the parameters and syntax are idential. + */ +OBJC_EXPORT void CHQuietLog(NSString *format, ...); + +/** + A macro for including the source file and line number where a log occurred. + + @param format A format string, which must not be nil. + @param ... A comma-separated list of arguments to substitute into @a format. + + This is defined as a compiler macro so it can automatically fill in the file name and line number where the call was made. After printing these values in brackets, this macro calls #CHQuietLog with @a format and any other arguments supplied afterward. + + @see CHQuietLog + */ +#ifndef CHLocationLog +#define CHLocationLog(format,...) \ +{ \ + NSString *file = [[NSString alloc] initWithUTF8String:__FILE__]; \ + printf("[%s:%d] ", [[file lastPathComponent] UTF8String], __LINE__); \ + [file release]; \ + CHQuietLog((format),##__VA_ARGS__); \ +} +#endif diff --git a/Sources/XMLParsing/CHDataStructures/Util.m b/Sources/XMLParsing/CHDataStructures/Util.m new file mode 100755 index 0000000..8ad2b5b --- /dev/null +++ b/Sources/XMLParsing/CHDataStructures/Util.m @@ -0,0 +1,98 @@ +/* + CHDataStructures.framework -- Util.m + + Copyright (c) 2008-2010, Quinn Taylor + + This source code is released under the ISC License. + + Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. + + The software is provided "as is", without warranty of any kind, including all implied warranties of merchantability and fitness. In no event shall the authors or copyright holders be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the software or the use or other dealings in the software. + */ + +#import "Util.h" + +size_t kCHPointerSize = sizeof(void*); // A variable declared extern in Util.h + +BOOL objectsAreEqual(id o1, id o2) { + return [o1 isEqual:o2]; +} + +BOOL objectsAreIdentical(id o1, id o2) { + return (o1 == o2); +} + +BOOL collectionsAreEqual(id collection1, id collection2) { + if ((collection1 && ![collection1 respondsToSelector:@selector(count)]) || + (collection2 && ![collection2 respondsToSelector:@selector(count)])) + { + [NSException raise:NSInvalidArgumentException + format:@"Parameter does not respond to -count selector."]; + } + if (collection1 == collection2) + return YES; + if ([collection1 count] != [collection2 count]) + return NO; + NSEnumerator *otherObjects = [collection2 objectEnumerator]; + for (id anObject in collection1) { + if (![anObject isEqual:[otherObjects nextObject]]) + return NO; + } + return YES; +} + +NSUInteger hashOfCountAndObjects(NSUInteger count, id object1, id object2) { + NSUInteger hash = 17 * count ^ (count << 16); + return hash ^ (31*[object1 hash]) ^ ((31*[object2 hash]) << 4); +} + +#pragma mark - + +void CHIndexOutOfRangeException(Class aClass, SEL method, + NSUInteger index, NSUInteger count) { + [NSException raise:NSRangeException + format:@"[%@ %s] -- Index (%lu) beyond bounds for count (%lu)", + aClass, sel_getName(method), index, count]; +} + +void CHInvalidArgumentException(Class aClass, SEL method, NSString *string) { + [NSException raise:NSInvalidArgumentException + format:@"[%@ %s] -- %@", + aClass, sel_getName(method), string]; +} + +void CHNilArgumentException(Class aClass, SEL method) { + CHInvalidArgumentException(aClass, method, @"Invalid nil argument"); +} + +void CHMutatedCollectionException(Class aClass, SEL method) { + [NSException raise:NSGenericException + format:@"[%@ %s] -- Collection was mutated during enumeration", + aClass, sel_getName(method)]; +} + +void CHUnsupportedOperationException(Class aClass, SEL method) { + [NSException raise:NSInternalInconsistencyException + format:@"[%@ %s] -- Unsupported operation", + aClass, sel_getName(method)]; +} + +void CHQuietLog(NSString *format, ...) { + if (format == nil) { + printf("(null)\n"); + return; + } + // Get a reference to the arguments that follow the format parameter + va_list argList; + va_start(argList, format); + // Do format string argument substitution, reinstate %% escapes, then print + NSMutableString *string = [[NSMutableString alloc] initWithFormat:format + arguments:argList]; + va_end(argList); + NSRange range; + range.location = 0; + range.length = [string length]; + [string replaceOccurrencesOfString:@"%%" withString:@"%%%%" options:0 range:range]; + printf("%s\n", [string UTF8String]); + [string release]; +} diff --git a/Sources/XMLParsing/CHDataStructures/XMLParsing-Bridging-Header.h b/Sources/XMLParsing/CHDataStructures/XMLParsing-Bridging-Header.h new file mode 100644 index 0000000..1b2cb5d --- /dev/null +++ b/Sources/XMLParsing/CHDataStructures/XMLParsing-Bridging-Header.h @@ -0,0 +1,4 @@ +// +// Use this file to import your target's public headers that you would like to expose to Swift. +// + diff --git a/XMLParsing.xcodeproj/project.pbxproj b/XMLParsing.xcodeproj/project.pbxproj index 979be70..89c1380 100644 --- a/XMLParsing.xcodeproj/project.pbxproj +++ b/XMLParsing.xcodeproj/project.pbxproj @@ -21,6 +21,14 @@ /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ + B3C3F0CC21A88F89000DC7C8 /* CHMutableDictionary.m in Sources */ = {isa = PBXBuildFile; fileRef = B3C3F0CA21A88F89000DC7C8 /* CHMutableDictionary.m */; }; + B3C3F0CD21A88F89000DC7C8 /* CHMutableDictionary.m in Sources */ = {isa = PBXBuildFile; fileRef = B3C3F0CA21A88F89000DC7C8 /* CHMutableDictionary.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + B3C3F0D021A88FDF000DC7C8 /* Util.m in Sources */ = {isa = PBXBuildFile; fileRef = B3C3F0CF21A88FDF000DC7C8 /* Util.m */; }; + B3C3F0D121A88FDF000DC7C8 /* Util.m in Sources */ = {isa = PBXBuildFile; fileRef = B3C3F0CF21A88FDF000DC7C8 /* Util.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + B3C3F0D421A88FF1000DC7C8 /* CHCircularBuffer.m in Sources */ = {isa = PBXBuildFile; fileRef = B3C3F0D321A88FF0000DC7C8 /* CHCircularBuffer.m */; }; + B3C3F0D521A88FF1000DC7C8 /* CHCircularBuffer.m in Sources */ = {isa = PBXBuildFile; fileRef = B3C3F0D321A88FF0000DC7C8 /* CHCircularBuffer.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + B3C3F0D821A88FFD000DC7C8 /* CHOrderedDictionary.m in Sources */ = {isa = PBXBuildFile; fileRef = B3C3F0D621A88FFD000DC7C8 /* CHOrderedDictionary.m */; }; + B3C3F0D921A88FFD000DC7C8 /* CHOrderedDictionary.m in Sources */ = {isa = PBXBuildFile; fileRef = B3C3F0D621A88FFD000DC7C8 /* CHOrderedDictionary.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; OBJ_35 /* Package.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_6 /* Package.swift */; }; OBJ_41 /* XMLParsingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_25 /* XMLParsingTests.swift */; }; OBJ_43 /* XMLParsing.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = "XMLParsing::XMLParsing::Product" /* XMLParsing.framework */; }; @@ -57,6 +65,15 @@ /* Begin PBXFileReference section */ 8420DF4021761DF200757ECF /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = Sources/XMLParsing/Info.plist; sourceTree = ""; }; + B3C3F0C921A88F88000DC7C8 /* XMLParsing-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "XMLParsing-Bridging-Header.h"; sourceTree = ""; }; + B3C3F0CA21A88F89000DC7C8 /* CHMutableDictionary.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CHMutableDictionary.m; sourceTree = ""; }; + B3C3F0CB21A88F89000DC7C8 /* CHMutableDictionary.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CHMutableDictionary.h; sourceTree = ""; }; + B3C3F0CE21A88FDF000DC7C8 /* Util.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Util.h; sourceTree = ""; }; + B3C3F0CF21A88FDF000DC7C8 /* Util.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Util.m; sourceTree = ""; }; + B3C3F0D221A88FF0000DC7C8 /* CHCircularBuffer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CHCircularBuffer.h; sourceTree = ""; }; + B3C3F0D321A88FF0000DC7C8 /* CHCircularBuffer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CHCircularBuffer.m; sourceTree = ""; }; + B3C3F0D621A88FFD000DC7C8 /* CHOrderedDictionary.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CHOrderedDictionary.m; sourceTree = ""; }; + B3C3F0D721A88FFD000DC7C8 /* CHOrderedDictionary.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CHOrderedDictionary.h; sourceTree = ""; }; OBJ_10 /* DecodingErrorExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DecodingErrorExtension.swift; sourceTree = ""; }; OBJ_11 /* XMLDecoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XMLDecoder.swift; sourceTree = ""; }; OBJ_12 /* XMLDecodingStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XMLDecodingStorage.swift; sourceTree = ""; }; @@ -95,6 +112,22 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + B3C3F0C821A88F27000DC7C8 /* CHDataStructures */ = { + isa = PBXGroup; + children = ( + B3C3F0D721A88FFD000DC7C8 /* CHOrderedDictionary.h */, + B3C3F0D621A88FFD000DC7C8 /* CHOrderedDictionary.m */, + B3C3F0D221A88FF0000DC7C8 /* CHCircularBuffer.h */, + B3C3F0D321A88FF0000DC7C8 /* CHCircularBuffer.m */, + B3C3F0CE21A88FDF000DC7C8 /* Util.h */, + B3C3F0CF21A88FDF000DC7C8 /* Util.m */, + B3C3F0CB21A88F89000DC7C8 /* CHMutableDictionary.h */, + B3C3F0CA21A88F89000DC7C8 /* CHMutableDictionary.m */, + B3C3F0C921A88F88000DC7C8 /* XMLParsing-Bridging-Header.h */, + ); + path = CHDataStructures; + sourceTree = ""; + }; OBJ_15 /* Encoder */ = { isa = PBXGroup; children = ( @@ -155,6 +188,7 @@ OBJ_8 /* XMLParsing */ = { isa = PBXGroup; children = ( + B3C3F0C821A88F27000DC7C8 /* CHDataStructures */, OBJ_9 /* Decoder */, OBJ_15 /* Encoder */, OBJ_20 /* ISO8601DateFormatter.swift */, @@ -236,10 +270,10 @@ LastUpgradeCheck = 9999; TargetAttributes = { "XMLParsing::XMLParsing" = { - LastSwiftMigration = 1000; + LastSwiftMigration = 0940; }; "XMLParsing::XMLParsingTests" = { - LastSwiftMigration = 1000; + LastSwiftMigration = 0940; }; }; }; @@ -277,6 +311,10 @@ buildActionMask = 0; files = ( OBJ_41 /* XMLParsingTests.swift in Sources */, + B3C3F0CC21A88F89000DC7C8 /* CHMutableDictionary.m in Sources */, + B3C3F0D021A88FDF000DC7C8 /* Util.m in Sources */, + B3C3F0D821A88FFD000DC7C8 /* CHOrderedDictionary.m in Sources */, + B3C3F0D421A88FF1000DC7C8 /* CHCircularBuffer.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -289,8 +327,12 @@ OBJ_52 /* XMLDecodingStorage.swift in Sources */, OBJ_53 /* XMLKeyedDecodingContainer.swift in Sources */, OBJ_54 /* XMLUnkeyedDecodingContainer.swift in Sources */, + B3C3F0D121A88FDF000DC7C8 /* Util.m in Sources */, + B3C3F0D921A88FFD000DC7C8 /* CHOrderedDictionary.m in Sources */, OBJ_55 /* EncodingErrorExtension.swift in Sources */, OBJ_56 /* XMLEncoder.swift in Sources */, + B3C3F0D521A88FF1000DC7C8 /* CHCircularBuffer.m in Sources */, + B3C3F0CD21A88F89000DC7C8 /* CHMutableDictionary.m in Sources */, OBJ_57 /* XMLEncodingStorage.swift in Sources */, OBJ_58 /* XMLReferencingEncoder.swift in Sources */, OBJ_59 /* ISO8601DateFormatter.swift in Sources */, @@ -361,6 +403,7 @@ OBJ_38 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_ENABLE_MODULES = YES; EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -371,6 +414,8 @@ LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks @loader_path/Frameworks"; OTHER_LDFLAGS = "$(inherited)"; OTHER_SWIFT_FLAGS = "$(inherited)"; + SWIFT_OBJC_BRIDGING_HEADER = "Sources/XMLParsing/CHDataStructures/XMLParsing-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 4.2; TARGET_NAME = XMLParsingTests; }; @@ -379,6 +424,7 @@ OBJ_39 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_ENABLE_MODULES = YES; EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -389,6 +435,7 @@ LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks @loader_path/Frameworks"; OTHER_LDFLAGS = "$(inherited)"; OTHER_SWIFT_FLAGS = "$(inherited)"; + SWIFT_OBJC_BRIDGING_HEADER = "Sources/XMLParsing/CHDataStructures/XMLParsing-Bridging-Header.h"; SWIFT_VERSION = 4.2; TARGET_NAME = XMLParsingTests; }; @@ -420,6 +467,8 @@ OBJ_47 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_ENABLE_MODULES = YES; + DEFINES_MODULE = YES; ENABLE_TESTABILITY = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -434,6 +483,8 @@ PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; + SWIFT_OBJC_BRIDGING_HEADER = "Sources/XMLParsing/CHDataStructures/XMLParsing-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 4.2; TARGET_NAME = XMLParsing; }; @@ -442,6 +493,8 @@ OBJ_48 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_ENABLE_MODULES = YES; + DEFINES_MODULE = YES; ENABLE_TESTABILITY = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -456,6 +509,7 @@ PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; + SWIFT_OBJC_BRIDGING_HEADER = "Sources/XMLParsing/CHDataStructures/XMLParsing-Bridging-Header.h"; SWIFT_VERSION = 4.2; TARGET_NAME = XMLParsing; }; From aa1d32e6d5ea197f7afb3f98e993795cce6405db Mon Sep 17 00:00:00 2001 From: "Leonid S. Usov" Date: Fri, 23 Nov 2018 21:02:00 +0100 Subject: [PATCH 12/24] Use CHOrderedDictionary instead of NSMutableDictionary --- .gitignore | 2 +- .../CHDataStructures/CHCircularBuffer.h | 6 ++ .../CHDataStructures/CHCircularBuffer.m | 2 +- .../CHDataStructures/CHMutableDictionary.h | 4 +- .../CHDataStructures/CHMutableDictionary.m | 8 +-- .../XMLParsing-Bridging-Header.h | 1 + Sources/XMLParsing/Encoder/XMLEncoder.swift | 72 +++++++++---------- .../Encoder/XMLEncodingStorage.swift | 4 +- .../Encoder/XMLReferencingEncoder.swift | 4 +- 9 files changed, 56 insertions(+), 47 deletions(-) diff --git a/.gitignore b/.gitignore index f2551d4..daa5d97 100644 --- a/.gitignore +++ b/.gitignore @@ -130,7 +130,7 @@ DerivedData/ .LSOverride # Icon must end with two \r -Icon +Icon # Thumbnails ._* diff --git a/Sources/XMLParsing/CHDataStructures/CHCircularBuffer.h b/Sources/XMLParsing/CHDataStructures/CHCircularBuffer.h index b692569..5151377 100755 --- a/Sources/XMLParsing/CHDataStructures/CHCircularBuffer.h +++ b/Sources/XMLParsing/CHDataStructures/CHCircularBuffer.h @@ -37,8 +37,14 @@ - (id) init; - (id) initWithArray:(NSArray*)anArray; + +// This is the designated initializer for CHCircularBuffer. - (id) initWithCapacity:(NSUInteger)capacity NS_DESIGNATED_INITIALIZER; +#pragma mark + +- (id) initWithCoder:(NSCoder*)decoder; + - (NSArray*) allObjects; - (BOOL) containsObject:(id)anObject; - (BOOL) containsObjectIdenticalTo:(id)anObject; diff --git a/Sources/XMLParsing/CHDataStructures/CHCircularBuffer.m b/Sources/XMLParsing/CHDataStructures/CHCircularBuffer.m index 98db5f7..5e3bd93 100755 --- a/Sources/XMLParsing/CHDataStructures/CHCircularBuffer.m +++ b/Sources/XMLParsing/CHDataStructures/CHCircularBuffer.m @@ -183,7 +183,7 @@ - (id) initWithArray:(NSArray*)anArray { NSUInteger capacity = DEFAULT_BUFFER_SIZE; while (capacity <= [anArray count]) capacity *= 2; - if ([self initWithCapacity:capacity] == nil) return nil; + if ((self = [self initWithCapacity:capacity]) == nil) return nil; for (id anObject in anArray) { array[tailIndex++] = [anObject retain]; } diff --git a/Sources/XMLParsing/CHDataStructures/CHMutableDictionary.h b/Sources/XMLParsing/CHDataStructures/CHMutableDictionary.h index 4276fe9..adc5ed8 100755 --- a/Sources/XMLParsing/CHDataStructures/CHMutableDictionary.h +++ b/Sources/XMLParsing/CHDataStructures/CHMutableDictionary.h @@ -33,7 +33,9 @@ HIDDEN void createCollectableCFMutableDictionary(CFMutableDictionaryRef* diction CFMutableDictionaryRef dictionary; // A Core Foundation dictionary. } -- (id) initWithCapacity:(NSUInteger)numItems NS_DESIGNATED_INITIALIZER; +- (nonnull instancetype) init; +- (nullable instancetype) initWithCoder:(NSCoder*)decoder; +- (nonnull instancetype) initWithCapacity:(NSUInteger)numItems NS_DESIGNATED_INITIALIZER; - (NSUInteger) count; - (NSEnumerator*) keyEnumerator; diff --git a/Sources/XMLParsing/CHDataStructures/CHMutableDictionary.m b/Sources/XMLParsing/CHDataStructures/CHMutableDictionary.m index a119c61..856d56e 100755 --- a/Sources/XMLParsing/CHDataStructures/CHMutableDictionary.m +++ b/Sources/XMLParsing/CHDataStructures/CHMutableDictionary.m @@ -23,7 +23,7 @@ void CHDictionaryRelease(CFAllocatorRef allocator, const void *value) { } CFStringRef CHDictionaryCopyDescription(const void *value) { - return (CFStringRef)[[(id)value description] copy]; + return (CFStringRef)[[(id)value description] copy]; } Boolean CHDictionaryEqual(const void *value1, const void *value2) { @@ -72,13 +72,13 @@ - (void) dealloc { } // Note: Defined here since -init is not implemented in NS(Mutable)Dictionary. -- (id) init { +- (nonnull instancetype) init { return [self initWithCapacity:0]; // The 0 means we provide no capacity hint } // Note: This is the designated initializer for NSMutableDictionary and this class. // Subclasses may override this as necessary, but must call back here first. -- (id) initWithCapacity:(NSUInteger)numItems { +- (nonnull instancetype) initWithCapacity:(NSUInteger)numItems { if ((self = [super init]) == nil) return nil; createCollectableCFMutableDictionary(&dictionary, numItems); return self; @@ -91,7 +91,7 @@ - (Class) classForKeyedArchiver { return [self class]; } -- (id) initWithCoder:(NSCoder*)decoder { +- (nullable instancetype) initWithCoder:(NSCoder*)decoder { return [self initWithDictionary:[decoder decodeObjectForKey:@"dictionary"]]; } diff --git a/Sources/XMLParsing/CHDataStructures/XMLParsing-Bridging-Header.h b/Sources/XMLParsing/CHDataStructures/XMLParsing-Bridging-Header.h index 1b2cb5d..3409d09 100644 --- a/Sources/XMLParsing/CHDataStructures/XMLParsing-Bridging-Header.h +++ b/Sources/XMLParsing/CHDataStructures/XMLParsing-Bridging-Header.h @@ -2,3 +2,4 @@ // Use this file to import your target's public headers that you would like to expose to Swift. // +#import "CHOrderedDictionary.h" diff --git a/Sources/XMLParsing/Encoder/XMLEncoder.swift b/Sources/XMLParsing/Encoder/XMLEncoder.swift index f77cef7..a99d795 100644 --- a/Sources/XMLParsing/Encoder/XMLEncoder.swift +++ b/Sources/XMLParsing/Encoder/XMLEncoder.swift @@ -40,7 +40,7 @@ open class XMLEncoder { public static let `default`: NodeEncoding = .element } - + /// The strategy to use for encoding `Date` values. public enum DateEncodingStrategy { /// Defer to `Date` for choosing an encoding. This is the default strategy. @@ -293,7 +293,7 @@ internal class _XMLEncoder: Encoder { public var codingPath: [CodingKey] public var nodeEncodings: [(CodingKey) -> XMLEncoder.NodeEncoding] - + /// Contextual user-provided information for use during encoding. public var userInfo: [CodingUserInfoKey : Any] { return self.options.userInfo @@ -329,12 +329,12 @@ internal class _XMLEncoder: Encoder { // MARK: - Encoder Methods public func container(keyedBy: Key.Type) -> KeyedEncodingContainer { // If an existing keyed container was already requested, return that one. - let topContainer: NSMutableDictionary + let topContainer: CHOrderedDictionary if self.canEncodeNewValue { // We haven't yet pushed a container at this level; do so here. topContainer = self.storage.pushKeyedContainer() } else { - guard let container = self.storage.containers.last as? NSMutableDictionary else { + guard let container = self.storage.containers.last as? CHOrderedDictionary else { preconditionFailure("Attempt to push new keyed encoding container when already previously encoded at this path.") } @@ -379,7 +379,7 @@ fileprivate struct _XMLKeyedEncodingContainer : KeyedEncodingCont private let encoder: _XMLEncoder /// A reference to the container we're writing to. - private let container: NSMutableDictionary + private let container: CHOrderedDictionary /// The path of coding keys taken to get to this point in encoding. private(set) public var codingPath: [CodingKey] @@ -387,7 +387,7 @@ fileprivate struct _XMLKeyedEncodingContainer : KeyedEncodingCont // MARK: - Initialization /// Initializes `self` with the given references. - fileprivate init(referencing encoder: _XMLEncoder, codingPath: [CodingKey], wrapping container: NSMutableDictionary) { + fileprivate init(referencing encoder: _XMLEncoder, codingPath: [CodingKey], wrapping container: CHOrderedDictionary) { self.encoder = encoder self.codingPath = codingPath self.container = container @@ -421,10 +421,10 @@ fileprivate struct _XMLKeyedEncodingContainer : KeyedEncodingCont } switch strategy(key) { case .attribute: - if let attributesContainer = self.container[_XMLElement.attributesKey] as? NSMutableDictionary { + if let attributesContainer = self.container[_XMLElement.attributesKey] as? CHOrderedDictionary { attributesContainer[_converted(key).stringValue] = self.encoder.box(value) } else { - let attributesContainer = NSMutableDictionary() + let attributesContainer = CHOrderedDictionary() attributesContainer[_converted(key).stringValue] = self.encoder.box(value) self.container[_XMLElement.attributesKey] = attributesContainer } @@ -441,10 +441,10 @@ fileprivate struct _XMLKeyedEncodingContainer : KeyedEncodingCont } switch strategy(key) { case .attribute: - if let attributesContainer = self.container[_XMLElement.attributesKey] as? NSMutableDictionary { + if let attributesContainer = self.container[_XMLElement.attributesKey] as? CHOrderedDictionary { attributesContainer[_converted(key).stringValue] = self.encoder.box(value) } else { - let attributesContainer = NSMutableDictionary() + let attributesContainer = CHOrderedDictionary() attributesContainer[_converted(key).stringValue] = self.encoder.box(value) self.container[_XMLElement.attributesKey] = attributesContainer } @@ -461,10 +461,10 @@ fileprivate struct _XMLKeyedEncodingContainer : KeyedEncodingCont } switch strategy(key) { case .attribute: - if let attributesContainer = self.container[_XMLElement.attributesKey] as? NSMutableDictionary { + if let attributesContainer = self.container[_XMLElement.attributesKey] as? CHOrderedDictionary { attributesContainer[_converted(key).stringValue] = self.encoder.box(value) } else { - let attributesContainer = NSMutableDictionary() + let attributesContainer = CHOrderedDictionary() attributesContainer[_converted(key).stringValue] = self.encoder.box(value) self.container[_XMLElement.attributesKey] = attributesContainer } @@ -481,10 +481,10 @@ fileprivate struct _XMLKeyedEncodingContainer : KeyedEncodingCont } switch strategy(key) { case .attribute: - if let attributesContainer = self.container[_XMLElement.attributesKey] as? NSMutableDictionary { + if let attributesContainer = self.container[_XMLElement.attributesKey] as? CHOrderedDictionary { attributesContainer[_converted(key).stringValue] = self.encoder.box(value) } else { - let attributesContainer = NSMutableDictionary() + let attributesContainer = CHOrderedDictionary() attributesContainer[_converted(key).stringValue] = self.encoder.box(value) self.container[_XMLElement.attributesKey] = attributesContainer } @@ -501,10 +501,10 @@ fileprivate struct _XMLKeyedEncodingContainer : KeyedEncodingCont } switch strategy(key) { case .attribute: - if let attributesContainer = self.container[_XMLElement.attributesKey] as? NSMutableDictionary { + if let attributesContainer = self.container[_XMLElement.attributesKey] as? CHOrderedDictionary { attributesContainer[_converted(key).stringValue] = self.encoder.box(value) } else { - let attributesContainer = NSMutableDictionary() + let attributesContainer = CHOrderedDictionary() attributesContainer[_converted(key).stringValue] = self.encoder.box(value) self.container[_XMLElement.attributesKey] = attributesContainer } @@ -521,10 +521,10 @@ fileprivate struct _XMLKeyedEncodingContainer : KeyedEncodingCont } switch strategy(key) { case .attribute: - if let attributesContainer = self.container[_XMLElement.attributesKey] as? NSMutableDictionary { + if let attributesContainer = self.container[_XMLElement.attributesKey] as? CHOrderedDictionary { attributesContainer[_converted(key).stringValue] = self.encoder.box(value) } else { - let attributesContainer = NSMutableDictionary() + let attributesContainer = CHOrderedDictionary() attributesContainer[_converted(key).stringValue] = self.encoder.box(value) self.container[_XMLElement.attributesKey] = attributesContainer } @@ -541,10 +541,10 @@ fileprivate struct _XMLKeyedEncodingContainer : KeyedEncodingCont } switch strategy(key) { case .attribute: - if let attributesContainer = self.container[_XMLElement.attributesKey] as? NSMutableDictionary { + if let attributesContainer = self.container[_XMLElement.attributesKey] as? CHOrderedDictionary { attributesContainer[_converted(key).stringValue] = self.encoder.box(value) } else { - let attributesContainer = NSMutableDictionary() + let attributesContainer = CHOrderedDictionary() attributesContainer[_converted(key).stringValue] = self.encoder.box(value) self.container[_XMLElement.attributesKey] = attributesContainer } @@ -561,10 +561,10 @@ fileprivate struct _XMLKeyedEncodingContainer : KeyedEncodingCont } switch strategy(key) { case .attribute: - if let attributesContainer = self.container[_XMLElement.attributesKey] as? NSMutableDictionary { + if let attributesContainer = self.container[_XMLElement.attributesKey] as? CHOrderedDictionary { attributesContainer[_converted(key).stringValue] = self.encoder.box(value) } else { - let attributesContainer = NSMutableDictionary() + let attributesContainer = CHOrderedDictionary() attributesContainer[_converted(key).stringValue] = self.encoder.box(value) self.container[_XMLElement.attributesKey] = attributesContainer } @@ -581,10 +581,10 @@ fileprivate struct _XMLKeyedEncodingContainer : KeyedEncodingCont } switch strategy(key) { case .attribute: - if let attributesContainer = self.container[_XMLElement.attributesKey] as? NSMutableDictionary { + if let attributesContainer = self.container[_XMLElement.attributesKey] as? CHOrderedDictionary { attributesContainer[_converted(key).stringValue] = self.encoder.box(value) } else { - let attributesContainer = NSMutableDictionary() + let attributesContainer = CHOrderedDictionary() attributesContainer[_converted(key).stringValue] = self.encoder.box(value) self.container[_XMLElement.attributesKey] = attributesContainer } @@ -601,10 +601,10 @@ fileprivate struct _XMLKeyedEncodingContainer : KeyedEncodingCont } switch strategy(key) { case .attribute: - if let attributesContainer = self.container[_XMLElement.attributesKey] as? NSMutableDictionary { + if let attributesContainer = self.container[_XMLElement.attributesKey] as? CHOrderedDictionary { attributesContainer[_converted(key).stringValue] = self.encoder.box(value) } else { - let attributesContainer = NSMutableDictionary() + let attributesContainer = CHOrderedDictionary() attributesContainer[_converted(key).stringValue] = self.encoder.box(value) self.container[_XMLElement.attributesKey] = attributesContainer } @@ -621,10 +621,10 @@ fileprivate struct _XMLKeyedEncodingContainer : KeyedEncodingCont } switch strategy(key) { case .attribute: - if let attributesContainer = self.container[_XMLElement.attributesKey] as? NSMutableDictionary { + if let attributesContainer = self.container[_XMLElement.attributesKey] as? CHOrderedDictionary { attributesContainer[_converted(key).stringValue] = self.encoder.box(value) } else { - let attributesContainer = NSMutableDictionary() + let attributesContainer = CHOrderedDictionary() attributesContainer[_converted(key).stringValue] = self.encoder.box(value) self.container[_XMLElement.attributesKey] = attributesContainer } @@ -641,10 +641,10 @@ fileprivate struct _XMLKeyedEncodingContainer : KeyedEncodingCont } switch strategy(key) { case .attribute: - if let attributesContainer = self.container[_XMLElement.attributesKey] as? NSMutableDictionary { + if let attributesContainer = self.container[_XMLElement.attributesKey] as? CHOrderedDictionary { attributesContainer[_converted(key).stringValue] = self.encoder.box(value) } else { - let attributesContainer = NSMutableDictionary() + let attributesContainer = CHOrderedDictionary() attributesContainer[_converted(key).stringValue] = self.encoder.box(value) self.container[_XMLElement.attributesKey] = attributesContainer } @@ -669,10 +669,10 @@ fileprivate struct _XMLKeyedEncodingContainer : KeyedEncodingCont } switch strategy(key) { case .attribute: - if let attributesContainer = self.container[_XMLElement.attributesKey] as? NSMutableDictionary { + if let attributesContainer = self.container[_XMLElement.attributesKey] as? CHOrderedDictionary { attributesContainer[_converted(key).stringValue] = try self.encoder.box(value) } else { - let attributesContainer = NSMutableDictionary() + let attributesContainer = CHOrderedDictionary() attributesContainer[_converted(key).stringValue] = try self.encoder.box(value) self.container[_XMLElement.attributesKey] = attributesContainer } @@ -697,10 +697,10 @@ fileprivate struct _XMLKeyedEncodingContainer : KeyedEncodingCont } switch strategy(key) { case .attribute: - if let attributesContainer = self.container[_XMLElement.attributesKey] as? NSMutableDictionary { + if let attributesContainer = self.container[_XMLElement.attributesKey] as? CHOrderedDictionary { attributesContainer[_converted(key).stringValue] = try self.encoder.box(value) } else { - let attributesContainer = NSMutableDictionary() + let attributesContainer = CHOrderedDictionary() attributesContainer[_converted(key).stringValue] = try self.encoder.box(value) self.container[_XMLElement.attributesKey] = attributesContainer } @@ -710,7 +710,7 @@ fileprivate struct _XMLKeyedEncodingContainer : KeyedEncodingCont } public mutating func nestedContainer(keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer { - let dictionary = NSMutableDictionary() + let dictionary = CHOrderedDictionary() self.container[_converted(key).stringValue] = dictionary self.codingPath.append(key) @@ -804,7 +804,7 @@ fileprivate struct _XMLUnkeyedEncodingContainer : UnkeyedEncodingContainer { self.codingPath.append(_XMLKey(index: self.count)) defer { self.codingPath.removeLast() } - let dictionary = NSMutableDictionary() + let dictionary = CHOrderedDictionary() self.container.add(dictionary) let container = _XMLKeyedEncodingContainer(referencing: self.encoder, codingPath: self.codingPath, wrapping: dictionary) diff --git a/Sources/XMLParsing/Encoder/XMLEncodingStorage.swift b/Sources/XMLParsing/Encoder/XMLEncodingStorage.swift index cd551a0..3095829 100644 --- a/Sources/XMLParsing/Encoder/XMLEncodingStorage.swift +++ b/Sources/XMLParsing/Encoder/XMLEncodingStorage.swift @@ -29,8 +29,8 @@ internal struct _XMLEncodingStorage { return self.containers.count } - internal mutating func pushKeyedContainer() -> NSMutableDictionary { - let dictionary = NSMutableDictionary() + internal mutating func pushKeyedContainer() -> CHOrderedDictionary { + let dictionary = CHOrderedDictionary() self.containers.append(dictionary) return dictionary } diff --git a/Sources/XMLParsing/Encoder/XMLReferencingEncoder.swift b/Sources/XMLParsing/Encoder/XMLReferencingEncoder.swift index 30ce4c3..641a32a 100644 --- a/Sources/XMLParsing/Encoder/XMLReferencingEncoder.swift +++ b/Sources/XMLParsing/Encoder/XMLReferencingEncoder.swift @@ -21,7 +21,7 @@ internal class _XMLReferencingEncoder : _XMLEncoder { case array(NSMutableArray, Int) /// Referencing a specific key in a dictionary container. - case dictionary(NSMutableDictionary, String) + case dictionary(CHOrderedDictionary, String) } // MARK: - Properties @@ -56,7 +56,7 @@ internal class _XMLReferencingEncoder : _XMLEncoder { referencing encoder: _XMLEncoder, key: CodingKey, convertedKey: CodingKey, - wrapping dictionary: NSMutableDictionary + wrapping dictionary: CHOrderedDictionary ) { self.encoder = encoder self.reference = .dictionary(dictionary, convertedKey.stringValue) From ff41377260a7f5f56f72f8b6d09bcb58915dc82b Mon Sep 17 00:00:00 2001 From: "Leonid S. Usov" Date: Fri, 23 Nov 2018 21:58:31 +0100 Subject: [PATCH 13/24] Additional use CHOrderedDictionary instead of NSMutableDictionary --- Sources/XMLParsing/XMLStackParser.swift | 45 +++++---- Tests/XMLParsingTests/XMLParsingTests.swift | 103 ++++++++++++++++++++ XMLParsing.xcodeproj/project.pbxproj | 8 -- 3 files changed, 127 insertions(+), 29 deletions(-) diff --git a/Sources/XMLParsing/XMLStackParser.swift b/Sources/XMLParsing/XMLStackParser.swift index c0ba0d6..822a8db 100644 --- a/Sources/XMLParsing/XMLStackParser.swift +++ b/Sources/XMLParsing/XMLStackParser.swift @@ -58,13 +58,13 @@ internal class _XMLElement { var key: String var value: String? = nil var attributes: [String: String] = [:] - var children: [String: [_XMLElement]] = [:] + var children = CHOrderedDictionary() internal init(key: String, value: String? = nil, attributes: [String: String] = [:], children: [String: [_XMLElement]] = [:]) { self.key = key self.value = value self.attributes = attributes - self.children = children + self.children = CHOrderedDictionary(dictionary: children) } convenience init(key: String, value: Optional, attributes: [String: CustomStringConvertible] = [:]) { @@ -117,7 +117,7 @@ internal class _XMLElement { } if let parentElement = parentElement, let key = key { - parentElement.children[key] = (parentElement.children[key] ?? []) + [element] + parentElement.children[key] = (parentElement.children[key] as! [_XMLElement]? ?? []) + [element] } } @@ -140,46 +140,48 @@ internal class _XMLElement { fileprivate static func createElement(parentElement: _XMLElement, key: String, object: NSNumber) { let element = _XMLElement(key: key, value: object.description) - parentElement.children[key] = (parentElement.children[key] ?? []) + [element] + parentElement.children[key] = (parentElement.children[key] as! [_XMLElement]? ?? []) + [element] } fileprivate static func createElement(parentElement: _XMLElement, key: String, object: NSString) { let element = _XMLElement(key: key, value: object.description) - parentElement.children[key] = (parentElement.children[key] ?? []) + [element] + parentElement.children[key] = (parentElement.children[key] as! [_XMLElement]? ?? []) + [element] } fileprivate static func createElement(parentElement: _XMLElement, key: String, object: NSNull) { let element = _XMLElement(key: key) - parentElement.children[key] = (parentElement.children[key] ?? []) + [element] + parentElement.children[key] = (parentElement.children[key] as! [_XMLElement]? ?? []) + [element] } fileprivate func flatten() -> [String: Any] { var node: [String: Any] = attributes for childElement in children { - for child in childElement.value { + let value = childElement.value as! [_XMLElement] + let key = childElement.key as! String + for child in value { if let content = child.value { - if let oldContent = node[childElement.key] as? Array { - node[childElement.key] = oldContent + [content] + if let oldContent = node[key] as? Array { + node[key] = oldContent + [content] - } else if let oldContent = node[childElement.key] { - node[childElement.key] = [oldContent, content] + } else if let oldContent = node[key] { + node[key] = [oldContent, content] } else { - node[childElement.key] = content + node[key] = content } - } else if !child.children.isEmpty || !child.attributes.isEmpty { + } else if child.children.count() > 0 || !child.attributes.isEmpty { let newValue = child.flatten() - if let existingValue = node[childElement.key] { + if let existingValue = node[key] { if var array = existingValue as? Array { array.append(newValue) - node[childElement.key] = array + node[key] = array } else { - node[childElement.key] = [existingValue, newValue] + node[key] = [existingValue, newValue] } } else { - node[childElement.key] = newValue + node[key] = newValue } } } @@ -214,11 +216,11 @@ internal class _XMLElement { string += "\(value)" } string += "" - } else if !children.isEmpty { + } else if children.count() > 0 { string += prettyPrinted ? ">\n" : ">" for childElement in children { - for child in childElement.value { + for child in childElement.value as! [_XMLElement] { string += child._toXMLString(indented: level + 1, withCDATA: cdata, formatting: formatting) string += prettyPrinted ? "\n" : "" } @@ -292,8 +294,9 @@ internal class _XMLStackParser: NSObject, XMLParserDelegate { stack.append(node) if let currentNode = currentNode { - if currentNode.children[elementName] != nil { - currentNode.children[elementName]?.append(node) + if var childArray = currentNode.children[elementName] as? [_XMLElement] { + childArray.append(node) + currentNode.children[elementName] = childArray } else { currentNode.children[elementName] = [node] } diff --git a/Tests/XMLParsingTests/XMLParsingTests.swift b/Tests/XMLParsingTests/XMLParsingTests.swift index b017b38..2baf127 100644 --- a/Tests/XMLParsingTests/XMLParsingTests.swift +++ b/Tests/XMLParsingTests/XMLParsingTests.swift @@ -44,6 +44,109 @@ struct Relationship: Codable { } } + +struct Book: Codable { + var id: String + var author: String + var title: String + var genre: Genre + var price: Double + var publishDate: Date + var description: String + + enum CodingKeys: String, CodingKey { + case id, author, title, genre, price, description + + case publishDate = "publish_date" + } + + func encode(to encoder: Encoder) throws { + var c = encoder.container(keyedBy: CodingKeys.self) + + try c.encode(id, forKey: .id) + try c.encode(author, forKey: .author) + try c.encode(publishDate, forKey: .publishDate) + try c.encode(genre, forKey: .genre) + try c.encode(description, forKey: .description) + try c.encode(title, forKey: .title) + try c.encode(price, forKey: .price) + } +} + +enum Genre: String, Codable { + case computer = "Computer" + case fantasy = "Fantasy" + case romance = "Romance" + case horror = "Horror" + case sciFi = "Science Fiction" +} + +let bookXML = """ + + +Gambardella, Matthew +XML Developer's Guide +Computer +44.95 +2000-10-01 +An in-depth look at creating applications +with XML. + +""" + +// TEST FUNCTIONS +extension Book { + static func retrieveBook() -> Book? { + guard let data = bookXML.data(using: .utf8) else { + return nil + } + + let decoder = XMLDecoder() + + let formatter: DateFormatter = { + let formatter = DateFormatter() + formatter.dateFormat = "yyyy-MM-dd" + return formatter + }() + + decoder.dateDecodingStrategy = .formatted(formatter) + + let book: Book? + + do { + book = try decoder.decode(Book.self, from: data) + } catch { + print(error) + + book = nil + } + + return book + } + + func toXML() -> String? { + let encoder = XMLEncoder() + + let formatter: DateFormatter = { + let formatter = DateFormatter() + formatter.dateFormat = "yyyy-MM-dd" + return formatter + }() + + encoder.dateEncodingStrategy = .formatted(formatter) + + do { + let data = try encoder.encode(self, withRootKey: "book", header: XMLHeader(version: 1.0)) + + return String(data: data, encoding: .utf8) + } catch { + print(error) + + return nil + } + } +} + class XMLParsingTests: XCTestCase { func testExample() { do { diff --git a/XMLParsing.xcodeproj/project.pbxproj b/XMLParsing.xcodeproj/project.pbxproj index 89c1380..a150c7b 100644 --- a/XMLParsing.xcodeproj/project.pbxproj +++ b/XMLParsing.xcodeproj/project.pbxproj @@ -21,13 +21,9 @@ /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ - B3C3F0CC21A88F89000DC7C8 /* CHMutableDictionary.m in Sources */ = {isa = PBXBuildFile; fileRef = B3C3F0CA21A88F89000DC7C8 /* CHMutableDictionary.m */; }; B3C3F0CD21A88F89000DC7C8 /* CHMutableDictionary.m in Sources */ = {isa = PBXBuildFile; fileRef = B3C3F0CA21A88F89000DC7C8 /* CHMutableDictionary.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; - B3C3F0D021A88FDF000DC7C8 /* Util.m in Sources */ = {isa = PBXBuildFile; fileRef = B3C3F0CF21A88FDF000DC7C8 /* Util.m */; }; B3C3F0D121A88FDF000DC7C8 /* Util.m in Sources */ = {isa = PBXBuildFile; fileRef = B3C3F0CF21A88FDF000DC7C8 /* Util.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; - B3C3F0D421A88FF1000DC7C8 /* CHCircularBuffer.m in Sources */ = {isa = PBXBuildFile; fileRef = B3C3F0D321A88FF0000DC7C8 /* CHCircularBuffer.m */; }; B3C3F0D521A88FF1000DC7C8 /* CHCircularBuffer.m in Sources */ = {isa = PBXBuildFile; fileRef = B3C3F0D321A88FF0000DC7C8 /* CHCircularBuffer.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; - B3C3F0D821A88FFD000DC7C8 /* CHOrderedDictionary.m in Sources */ = {isa = PBXBuildFile; fileRef = B3C3F0D621A88FFD000DC7C8 /* CHOrderedDictionary.m */; }; B3C3F0D921A88FFD000DC7C8 /* CHOrderedDictionary.m in Sources */ = {isa = PBXBuildFile; fileRef = B3C3F0D621A88FFD000DC7C8 /* CHOrderedDictionary.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; OBJ_35 /* Package.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_6 /* Package.swift */; }; OBJ_41 /* XMLParsingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_25 /* XMLParsingTests.swift */; }; @@ -311,10 +307,6 @@ buildActionMask = 0; files = ( OBJ_41 /* XMLParsingTests.swift in Sources */, - B3C3F0CC21A88F89000DC7C8 /* CHMutableDictionary.m in Sources */, - B3C3F0D021A88FDF000DC7C8 /* Util.m in Sources */, - B3C3F0D821A88FFD000DC7C8 /* CHOrderedDictionary.m in Sources */, - B3C3F0D421A88FF1000DC7C8 /* CHCircularBuffer.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; From 9cc358f544e3e1186fb4c4519f1bad58e872b8ba Mon Sep 17 00:00:00 2001 From: "Leonid S. Usov" Date: Fri, 23 Nov 2018 22:14:42 +0100 Subject: [PATCH 14/24] Trying to get rid of the nullability warnings --- Sources/XMLParsing/CHDataStructures/CHMutableDictionary.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Sources/XMLParsing/CHDataStructures/CHMutableDictionary.h b/Sources/XMLParsing/CHDataStructures/CHMutableDictionary.h index adc5ed8..6fea706 100755 --- a/Sources/XMLParsing/CHDataStructures/CHMutableDictionary.h +++ b/Sources/XMLParsing/CHDataStructures/CHMutableDictionary.h @@ -12,6 +12,8 @@ #import "Util.h" +NS_ASSUME_NONNULL_BEGIN + HIDDEN void createCollectableCFMutableDictionary(CFMutableDictionaryRef* dictionary, NSUInteger initialCapacity); /** @@ -45,3 +47,5 @@ HIDDEN void createCollectableCFMutableDictionary(CFMutableDictionaryRef* diction - (void) setObject:(id)anObject forKey:(id)aKey; @end + +NS_ASSUME_NONNULL_END From e995c9efbe4651374487652de4a0b92b419c2fc8 Mon Sep 17 00:00:00 2001 From: "Leonid S. Usov" Date: Sat, 24 Nov 2018 17:20:28 +0100 Subject: [PATCH 15/24] [proj] Exclude the bridging header file from public framework headers --- XMLParsing.xcodeproj/project.pbxproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/XMLParsing.xcodeproj/project.pbxproj b/XMLParsing.xcodeproj/project.pbxproj index a150c7b..68f92c1 100644 --- a/XMLParsing.xcodeproj/project.pbxproj +++ b/XMLParsing.xcodeproj/project.pbxproj @@ -475,6 +475,7 @@ PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; + SWIFT_INSTALL_OBJC_HEADER = NO; SWIFT_OBJC_BRIDGING_HEADER = "Sources/XMLParsing/CHDataStructures/XMLParsing-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 4.2; @@ -501,6 +502,7 @@ PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; + SWIFT_INSTALL_OBJC_HEADER = NO; SWIFT_OBJC_BRIDGING_HEADER = "Sources/XMLParsing/CHDataStructures/XMLParsing-Bridging-Header.h"; SWIFT_VERSION = 4.2; TARGET_NAME = XMLParsing; From 48dbc32f8d69b4955fc37c428c9a556899b8eee4 Mon Sep 17 00:00:00 2001 From: "Leonid S. Usov" Date: Sun, 25 Nov 2018 00:01:19 +0100 Subject: [PATCH 16/24] [proj] dark magic with modulemap to hide internal usage of objc classes --- .../XMLParsing-Bridging-Header.h | 5 ----- .../CHDataStructures/module.modulemap | 4 ++++ Sources/XMLParsing/Decoder/XMLDecoder.swift | 1 + Sources/XMLParsing/Encoder/XMLEncoder.swift | 1 + .../Encoder/XMLEncodingStorage.swift | 1 + .../Encoder/XMLReferencingEncoder.swift | 1 + Sources/XMLParsing/XMLStackParser.swift | 1 + XMLParsing.xcodeproj/project.pbxproj | 18 +++++++++++++++--- 8 files changed, 24 insertions(+), 8 deletions(-) delete mode 100644 Sources/XMLParsing/CHDataStructures/XMLParsing-Bridging-Header.h create mode 100644 Sources/XMLParsing/CHDataStructures/module.modulemap diff --git a/Sources/XMLParsing/CHDataStructures/XMLParsing-Bridging-Header.h b/Sources/XMLParsing/CHDataStructures/XMLParsing-Bridging-Header.h deleted file mode 100644 index 3409d09..0000000 --- a/Sources/XMLParsing/CHDataStructures/XMLParsing-Bridging-Header.h +++ /dev/null @@ -1,5 +0,0 @@ -// -// Use this file to import your target's public headers that you would like to expose to Swift. -// - -#import "CHOrderedDictionary.h" diff --git a/Sources/XMLParsing/CHDataStructures/module.modulemap b/Sources/XMLParsing/CHDataStructures/module.modulemap new file mode 100644 index 0000000..3cf17be --- /dev/null +++ b/Sources/XMLParsing/CHDataStructures/module.modulemap @@ -0,0 +1,4 @@ +module XMLParsingPrivate { + header "CHOrderedDictionary.h" + export * +} diff --git a/Sources/XMLParsing/Decoder/XMLDecoder.swift b/Sources/XMLParsing/Decoder/XMLDecoder.swift index dd63d96..8dd672f 100644 --- a/Sources/XMLParsing/Decoder/XMLDecoder.swift +++ b/Sources/XMLParsing/Decoder/XMLDecoder.swift @@ -7,6 +7,7 @@ // import Foundation +import XMLParsingPrivate //===----------------------------------------------------------------------===// // XML Decoder diff --git a/Sources/XMLParsing/Encoder/XMLEncoder.swift b/Sources/XMLParsing/Encoder/XMLEncoder.swift index a99d795..fb05411 100644 --- a/Sources/XMLParsing/Encoder/XMLEncoder.swift +++ b/Sources/XMLParsing/Encoder/XMLEncoder.swift @@ -7,6 +7,7 @@ // import Foundation +import XMLParsingPrivate //===----------------------------------------------------------------------===// // XML Encoder diff --git a/Sources/XMLParsing/Encoder/XMLEncodingStorage.swift b/Sources/XMLParsing/Encoder/XMLEncodingStorage.swift index 3095829..41be209 100644 --- a/Sources/XMLParsing/Encoder/XMLEncodingStorage.swift +++ b/Sources/XMLParsing/Encoder/XMLEncodingStorage.swift @@ -8,6 +8,7 @@ // import Foundation +import XMLParsingPrivate // MARK: - Encoding Storage and Containers diff --git a/Sources/XMLParsing/Encoder/XMLReferencingEncoder.swift b/Sources/XMLParsing/Encoder/XMLReferencingEncoder.swift index 641a32a..b615f84 100644 --- a/Sources/XMLParsing/Encoder/XMLReferencingEncoder.swift +++ b/Sources/XMLParsing/Encoder/XMLReferencingEncoder.swift @@ -7,6 +7,7 @@ // import Foundation +import XMLParsingPrivate // MARK: - _XMLReferencingEncoder diff --git a/Sources/XMLParsing/XMLStackParser.swift b/Sources/XMLParsing/XMLStackParser.swift index 822a8db..c18fa85 100644 --- a/Sources/XMLParsing/XMLStackParser.swift +++ b/Sources/XMLParsing/XMLStackParser.swift @@ -7,6 +7,7 @@ // import Foundation +import XMLParsingPrivate //===----------------------------------------------------------------------===// // Data Representation diff --git a/XMLParsing.xcodeproj/project.pbxproj b/XMLParsing.xcodeproj/project.pbxproj index 68f92c1..3fb957e 100644 --- a/XMLParsing.xcodeproj/project.pbxproj +++ b/XMLParsing.xcodeproj/project.pbxproj @@ -62,6 +62,7 @@ /* Begin PBXFileReference section */ 8420DF4021761DF200757ECF /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = Sources/XMLParsing/Info.plist; sourceTree = ""; }; B3C3F0C921A88F88000DC7C8 /* XMLParsing-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "XMLParsing-Bridging-Header.h"; sourceTree = ""; }; + B321EFA821AA0D4A00AF7D94 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; B3C3F0CA21A88F89000DC7C8 /* CHMutableDictionary.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CHMutableDictionary.m; sourceTree = ""; }; B3C3F0CB21A88F89000DC7C8 /* CHMutableDictionary.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CHMutableDictionary.h; sourceTree = ""; }; B3C3F0CE21A88FDF000DC7C8 /* Util.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Util.h; sourceTree = ""; }; @@ -119,7 +120,7 @@ B3C3F0CF21A88FDF000DC7C8 /* Util.m */, B3C3F0CB21A88F89000DC7C8 /* CHMutableDictionary.h */, B3C3F0CA21A88F89000DC7C8 /* CHMutableDictionary.m */, - B3C3F0C921A88F88000DC7C8 /* XMLParsing-Bridging-Header.h */, + B321EFA821AA0D4A00AF7D94 /* module.modulemap */, ); path = CHDataStructures; sourceTree = ""; @@ -209,6 +210,16 @@ }; /* End PBXGroup section */ +/* Begin PBXHeadersBuildPhase section */ + B321EFA921AA0DED00AF7D94 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + /* Begin PBXNativeTarget section */ "XMLParsing::SwiftPMPackageDescription" /* XMLParsingPackageDescription */ = { isa = PBXNativeTarget; @@ -230,6 +241,7 @@ buildPhases = ( OBJ_49 /* Sources */, OBJ_62 /* Frameworks */, + B321EFA921AA0DED00AF7D94 /* Headers */, ); buildRules = ( ); @@ -475,8 +487,8 @@ PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; + SWIFT_INCLUDE_PATHS = "$(PROJECT_DIR)/Sources/XMLParsing/CHDataStructures/"; SWIFT_INSTALL_OBJC_HEADER = NO; - SWIFT_OBJC_BRIDGING_HEADER = "Sources/XMLParsing/CHDataStructures/XMLParsing-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 4.2; TARGET_NAME = XMLParsing; @@ -502,8 +514,8 @@ PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; + SWIFT_INCLUDE_PATHS = "$(PROJECT_DIR)/Sources/XMLParsing/CHDataStructures/"; SWIFT_INSTALL_OBJC_HEADER = NO; - SWIFT_OBJC_BRIDGING_HEADER = "Sources/XMLParsing/CHDataStructures/XMLParsing-Bridging-Header.h"; SWIFT_VERSION = 4.2; TARGET_NAME = XMLParsing; }; From bb11d6082697218633b79065e67f10fa304d5cd1 Mon Sep 17 00:00:00 2001 From: "Leonid S. Usov" Date: Sun, 25 Nov 2018 00:21:07 +0100 Subject: [PATCH 17/24] [proj] change iOS minimum deployment target --- XMLParsing.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/XMLParsing.xcodeproj/project.pbxproj b/XMLParsing.xcodeproj/project.pbxproj index 3fb957e..c967b37 100644 --- a/XMLParsing.xcodeproj/project.pbxproj +++ b/XMLParsing.xcodeproj/project.pbxproj @@ -372,7 +372,7 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_NS_ASSERTIONS = YES; GCC_OPTIMIZATION_LEVEL = 0; - IPHONEOS_DEPLOYMENT_TARGET = 9.3; + IPHONEOS_DEPLOYMENT_TARGET = 9.2; MACOSX_DEPLOYMENT_TARGET = 10.10; ONLY_ACTIVE_ARCH = YES; OTHER_SWIFT_FLAGS = "-DXcode"; @@ -455,7 +455,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_OPTIMIZATION_LEVEL = s; - IPHONEOS_DEPLOYMENT_TARGET = 9.3; + IPHONEOS_DEPLOYMENT_TARGET = 9.2; MACOSX_DEPLOYMENT_TARGET = 10.10; OTHER_SWIFT_FLAGS = "-DXcode"; PRODUCT_NAME = "$(TARGET_NAME)"; From b6dddd6b837fe21c28f31703c8623fb7bd968fc8 Mon Sep 17 00:00:00 2001 From: "Leonid S. Usov" Date: Sun, 25 Nov 2018 23:41:12 +0100 Subject: [PATCH 18/24] Install objc compat header (*-Swift.h) -> make Carthage happy --- XMLParsing.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/XMLParsing.xcodeproj/project.pbxproj b/XMLParsing.xcodeproj/project.pbxproj index c967b37..135bcaf 100644 --- a/XMLParsing.xcodeproj/project.pbxproj +++ b/XMLParsing.xcodeproj/project.pbxproj @@ -488,7 +488,7 @@ PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; SWIFT_INCLUDE_PATHS = "$(PROJECT_DIR)/Sources/XMLParsing/CHDataStructures/"; - SWIFT_INSTALL_OBJC_HEADER = NO; + SWIFT_INSTALL_OBJC_HEADER = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 4.2; TARGET_NAME = XMLParsing; @@ -515,7 +515,7 @@ PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; SWIFT_INCLUDE_PATHS = "$(PROJECT_DIR)/Sources/XMLParsing/CHDataStructures/"; - SWIFT_INSTALL_OBJC_HEADER = NO; + SWIFT_INSTALL_OBJC_HEADER = YES; SWIFT_VERSION = 4.2; TARGET_NAME = XMLParsing; }; From f9906cf243891af2ba0e2e0e4477e7c497771306 Mon Sep 17 00:00:00 2001 From: "Leonid S. Usov" Date: Sun, 2 Dec 2018 09:51:45 +0200 Subject: [PATCH 19/24] Fix testing project --- XMLParsing.xcodeproj/project.pbxproj | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/XMLParsing.xcodeproj/project.pbxproj b/XMLParsing.xcodeproj/project.pbxproj index 135bcaf..f438da8 100644 --- a/XMLParsing.xcodeproj/project.pbxproj +++ b/XMLParsing.xcodeproj/project.pbxproj @@ -418,7 +418,6 @@ LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks @loader_path/Frameworks"; OTHER_LDFLAGS = "$(inherited)"; OTHER_SWIFT_FLAGS = "$(inherited)"; - SWIFT_OBJC_BRIDGING_HEADER = "Sources/XMLParsing/CHDataStructures/XMLParsing-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 4.2; TARGET_NAME = XMLParsingTests; @@ -439,7 +438,6 @@ LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks @loader_path/Frameworks"; OTHER_LDFLAGS = "$(inherited)"; OTHER_SWIFT_FLAGS = "$(inherited)"; - SWIFT_OBJC_BRIDGING_HEADER = "Sources/XMLParsing/CHDataStructures/XMLParsing-Bridging-Header.h"; SWIFT_VERSION = 4.2; TARGET_NAME = XMLParsingTests; }; @@ -490,7 +488,7 @@ SWIFT_INCLUDE_PATHS = "$(PROJECT_DIR)/Sources/XMLParsing/CHDataStructures/"; SWIFT_INSTALL_OBJC_HEADER = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 4.2; + SWIFT_VERSION = 4.0; TARGET_NAME = XMLParsing; }; name = Debug; @@ -516,7 +514,7 @@ SKIP_INSTALL = YES; SWIFT_INCLUDE_PATHS = "$(PROJECT_DIR)/Sources/XMLParsing/CHDataStructures/"; SWIFT_INSTALL_OBJC_HEADER = YES; - SWIFT_VERSION = 4.2; + SWIFT_VERSION = 4.0; TARGET_NAME = XMLParsing; }; name = Release; From b6ef543bc43b27cf84463d105d417bf0542be602 Mon Sep 17 00:00:00 2001 From: "Leonid S. Usov" Date: Sun, 2 Dec 2018 11:31:55 +0200 Subject: [PATCH 20/24] Standard compliant treatment of present empty elements and support of the xsi:nil attribute --- Sources/XMLParsing/XMLStackParser.swift | 64 +++++++++++++++------ Tests/XMLParsingTests/XMLParsingTests.swift | 29 ++++++++++ 2 files changed, 76 insertions(+), 17 deletions(-) diff --git a/Sources/XMLParsing/XMLStackParser.swift b/Sources/XMLParsing/XMLStackParser.swift index c18fa85..75a563d 100644 --- a/Sources/XMLParsing/XMLStackParser.swift +++ b/Sources/XMLParsing/XMLStackParser.swift @@ -161,17 +161,7 @@ internal class _XMLElement { let value = childElement.value as! [_XMLElement] let key = childElement.key as! String for child in value { - if let content = child.value { - if let oldContent = node[key] as? Array { - node[key] = oldContent + [content] - - } else if let oldContent = node[key] { - node[key] = [oldContent, content] - - } else { - node[key] = content - } - } else if child.children.count() > 0 || !child.attributes.isEmpty { + if child.children.count() > 0 || !child.attributes.isEmpty { let newValue = child.flatten() if let existingValue = node[key] { @@ -184,6 +174,16 @@ internal class _XMLElement { } else { node[key] = newValue } + } else if let content = child.value { + if let oldContent = node[key] as? Array { + node[key] = oldContent + [content] + + } else if let oldContent = node[key] { + node[key] = [oldContent, content] + + } else { + node[key] = content + } } } } @@ -237,6 +237,10 @@ internal class _XMLElement { } } +enum XmlNamespace: String { + case xsi = "http://www.w3.org/2001/XMLSchema-instance" +} + extension String { internal func escape(_ characterSet: [(character: String, escapedCharacter: String)]) -> String { var string = self @@ -249,6 +253,8 @@ extension String { } } + + internal class _XMLStackParser: NSObject, XMLParserDelegate { var root: _XMLElement? var stack = [_XMLElement]() @@ -257,6 +263,11 @@ internal class _XMLStackParser: NSObject, XMLParserDelegate { var currentElementName: String? var currentElementData = "" + var nsPrefix = [String: String]() + var prefixNs = [String: String]() + + + static func parse(with data: Data) throws -> [String: Any] { let parser = _XMLStackParser() @@ -274,6 +285,8 @@ internal class _XMLStackParser: NSObject, XMLParserDelegate { func parse(with data: Data) throws -> _XMLElement? { let xmlParser = XMLParser(data: data) xmlParser.delegate = self + xmlParser.shouldProcessNamespaces = true + xmlParser.shouldReportNamespacePrefixes = true if xmlParser.parse() { return root @@ -284,6 +297,11 @@ internal class _XMLStackParser: NSObject, XMLParserDelegate { } } + private func popValueOf(attr: String, ns: XmlNamespace, from dict: inout [String: String]) -> String? { + guard let prefix = nsPrefix[ns.rawValue] else { return nil } + return dict.removeValue(forKey: "\(prefix):\(attr)") + } + func parserDidStartDocument(_ parser: XMLParser) { root = nil stack = [_XMLElement]() @@ -307,12 +325,13 @@ internal class _XMLStackParser: NSObject, XMLParserDelegate { func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) { if let poppedNode = stack.popLast(){ - if let content = poppedNode.value?.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) { - if content.isEmpty { - poppedNode.value = nil - } else { - poppedNode.value = content - } + if let nilAttr = popValueOf(attr: "nil", ns: .xsi, from: &poppedNode.attributes), nilAttr == "true" { + poppedNode.value = nil + } else if let content = poppedNode.value?.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) { + poppedNode.value = content + } else { + // an element which is present must be at least empty + poppedNode.value = "" } if (stack.isEmpty) { @@ -324,6 +343,17 @@ internal class _XMLStackParser: NSObject, XMLParserDelegate { } } + func parser(_ parser: XMLParser, didStartMappingPrefix prefix: String, toURI namespaceURI: String) { + prefixNs[prefix] = namespaceURI + nsPrefix[namespaceURI] = prefix + } + + func parser(_ parser: XMLParser, didEndMappingPrefix prefix: String) { + if let uri = prefixNs.removeValue(forKey: prefix) { + nsPrefix.removeValue(forKey: uri) + } + } + func parser(_ parser: XMLParser, foundCharacters string: String) { currentNode?.value = (currentNode?.value ?? "") + string } diff --git a/Tests/XMLParsingTests/XMLParsingTests.swift b/Tests/XMLParsingTests/XMLParsingTests.swift index 2baf127..7c6fa3e 100644 --- a/Tests/XMLParsingTests/XMLParsingTests.swift +++ b/Tests/XMLParsingTests/XMLParsingTests.swift @@ -147,6 +147,20 @@ extension Book { } } + +let nilXml = """ + + + + +""" + +struct NullTest: Decodable { + let empty: String + let null: String? +} + + class XMLParsingTests: XCTestCase { func testExample() { do { @@ -162,6 +176,21 @@ class XMLParsingTests: XCTestCase { XCTAssert(false, "failed to decode the example: \(error)") } } + + func testNull() { + do { + guard let data = nilXml.data(using: .utf8) else { return } + + let decoder = XMLDecoder() + + let null = try decoder.decode(NullTest.self, from: data) + + XCTAssertEqual(null.empty, "") + XCTAssertNil(null.null) + } catch { + XCTAssert(false, "failed to decode the example: \(error)") + } + } static var allTests = [ From cfc91177524f02c88f4d30aa34171abd948ea33f Mon Sep 17 00:00:00 2001 From: "Leonid S. Usov" Date: Sun, 2 Dec 2018 16:23:22 +0200 Subject: [PATCH 21/24] trying to fix the bundle signature issues --- XMLParsing.xcodeproj/XMLParsing_Info.plist | 39 +++++++++++----------- XMLParsing.xcodeproj/project.pbxproj | 5 ++- 2 files changed, 21 insertions(+), 23 deletions(-) diff --git a/XMLParsing.xcodeproj/XMLParsing_Info.plist b/XMLParsing.xcodeproj/XMLParsing_Info.plist index 57ada9f..fbe1e6b 100644 --- a/XMLParsing.xcodeproj/XMLParsing_Info.plist +++ b/XMLParsing.xcodeproj/XMLParsing_Info.plist @@ -1,25 +1,24 @@ + - CFBundleDevelopmentRegion - en - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - NSPrincipalClass - + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSPrincipalClass + diff --git a/XMLParsing.xcodeproj/project.pbxproj b/XMLParsing.xcodeproj/project.pbxproj index f438da8..36484cf 100644 --- a/XMLParsing.xcodeproj/project.pbxproj +++ b/XMLParsing.xcodeproj/project.pbxproj @@ -61,7 +61,6 @@ /* Begin PBXFileReference section */ 8420DF4021761DF200757ECF /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = Sources/XMLParsing/Info.plist; sourceTree = ""; }; - B3C3F0C921A88F88000DC7C8 /* XMLParsing-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "XMLParsing-Bridging-Header.h"; sourceTree = ""; }; B321EFA821AA0D4A00AF7D94 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; B3C3F0CA21A88F89000DC7C8 /* CHMutableDictionary.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CHMutableDictionary.m; sourceTree = ""; }; B3C3F0CB21A88F89000DC7C8 /* CHMutableDictionary.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CHMutableDictionary.h; sourceTree = ""; }; @@ -481,7 +480,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(TOOLCHAIN_DIR)/usr/lib/swift/macosx"; OTHER_LDFLAGS = "$(inherited)"; OTHER_SWIFT_FLAGS = "$(inherited)"; - PRODUCT_BUNDLE_IDENTIFIER = XMLParsing; + PRODUCT_BUNDLE_IDENTIFIER = "com.practi.opensource.xml-parsing"; PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; @@ -508,7 +507,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(TOOLCHAIN_DIR)/usr/lib/swift/macosx"; OTHER_LDFLAGS = "$(inherited)"; OTHER_SWIFT_FLAGS = "$(inherited)"; - PRODUCT_BUNDLE_IDENTIFIER = XMLParsing; + PRODUCT_BUNDLE_IDENTIFIER = "com.practi.opensource.xml-parsing"; PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; From c7def6a14e93af74336c85dae07a458d85b84475 Mon Sep 17 00:00:00 2001 From: "Leonid S. Usov" Date: Mon, 3 Dec 2018 15:04:51 +0200 Subject: [PATCH 22/24] ignore .idea folder --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index daa5d97..8b29c40 100644 --- a/.gitignore +++ b/.gitignore @@ -153,3 +153,4 @@ Temporary Items # End of https://www.gitignore.io/api/macos +.idea From 10dd707cc922e08a7cf96f73508b68112f817bc6 Mon Sep 17 00:00:00 2001 From: "Leonid S. Usov" Date: Mon, 3 Dec 2018 15:05:52 +0200 Subject: [PATCH 23/24] Add support for SNAKE_UPPERCASE key encoding / decoding strategy --- Sources/XMLParsing/Decoder/XMLDecoder.swift | 9 +++- .../Decoder/XMLKeyedDecodingContainer.swift | 12 +++++- Sources/XMLParsing/Encoder/XMLEncoder.swift | 17 +++++--- Tests/XMLParsingTests/XMLParsingTests.swift | 41 +++++++++++++++++++ 4 files changed, 70 insertions(+), 9 deletions(-) diff --git a/Sources/XMLParsing/Decoder/XMLDecoder.swift b/Sources/XMLParsing/Decoder/XMLDecoder.swift index 8dd672f..5df1273 100644 --- a/Sources/XMLParsing/Decoder/XMLDecoder.swift +++ b/Sources/XMLParsing/Decoder/XMLDecoder.swift @@ -120,6 +120,9 @@ open class XMLDecoder { /// /// - Note: Using a key decoding strategy has a nominal performance cost, as each string key has to be inspected for the `_` character. case convertFromSnakeCase + + /// Convert from "SNAKE_CASE_KEYS" to "camelCaseKeys" + case convertFromSnakeUpperCase /// Convert from "CodingKey" to "codingKey" case convertFromCapitalized @@ -137,8 +140,10 @@ open class XMLDecoder { return result } - static func _convertFromSnakeCase(_ stringKey: String) -> String { - guard !stringKey.isEmpty else { return stringKey } + static func _convertFromSnakeCase(_ originalStringKey: String, snakeUpperCase: Bool) -> String { + guard !originalStringKey.isEmpty else { return originalStringKey } + + let stringKey = snakeUpperCase ? originalStringKey.lowercased() : originalStringKey // Find the first non-underscore character guard let firstNonUnderscore = stringKey.index(where: { $0 != "_" }) else { diff --git a/Sources/XMLParsing/Decoder/XMLKeyedDecodingContainer.swift b/Sources/XMLParsing/Decoder/XMLKeyedDecodingContainer.swift index 40fd4a0..f0bbfcd 100644 --- a/Sources/XMLParsing/Decoder/XMLKeyedDecodingContainer.swift +++ b/Sources/XMLParsing/Decoder/XMLKeyedDecodingContainer.swift @@ -29,14 +29,18 @@ internal struct _XMLKeyedDecodingContainer : KeyedDecodingContain /// Initializes `self` by referencing the given decoder and container. internal init(referencing decoder: _XMLDecoder, wrapping container: [String : Any]) { self.decoder = decoder + var snakeUpperCase = false switch decoder.options.keyDecodingStrategy { case .useDefaultKeys: self.container = container + case .convertFromSnakeUpperCase: + snakeUpperCase = true + fallthrough case .convertFromSnakeCase: // Convert the snake case keys in the container to camel case. // If we hit a duplicate key after conversion, then we'll use the first one we saw. Effectively an undefined behavior with dictionaries. self.container = Dictionary(container.map { - key, value in (XMLDecoder.KeyDecodingStrategy._convertFromSnakeCase(key), value) + key, value in (XMLDecoder.KeyDecodingStrategy._convertFromSnakeCase(key, snakeUpperCase: snakeUpperCase), value) }, uniquingKeysWith: { (first, _) in first }) case .convertFromCapitalized: self.container = Dictionary(container.map { @@ -61,11 +65,15 @@ internal struct _XMLKeyedDecodingContainer : KeyedDecodingContain } private func _errorDescription(of key: CodingKey) -> String { + var uppercase = false switch decoder.options.keyDecodingStrategy { + case .convertFromSnakeUpperCase: + uppercase = true + fallthrough case .convertFromSnakeCase: // In this case we can attempt to recover the original value by reversing the transform let original = key.stringValue - let converted = XMLEncoder.KeyEncodingStrategy._convertToSnakeCase(original) + let converted = XMLEncoder.KeyEncodingStrategy._convertToSnakeCase(original, uppercased: uppercase) if converted == original { return "\(key) (\"\(original)\")" } else { diff --git a/Sources/XMLParsing/Encoder/XMLEncoder.swift b/Sources/XMLParsing/Encoder/XMLEncoder.swift index fb05411..e05c9b6 100644 --- a/Sources/XMLParsing/Encoder/XMLEncoder.swift +++ b/Sources/XMLParsing/Encoder/XMLEncoder.swift @@ -117,14 +117,17 @@ open class XMLEncoder { /// For example, `oneTwoThree` becomes `one_two_three`. `_oneTwoThree_` becomes `_one_two_three_`. /// /// - Note: Using a key encoding strategy has a nominal performance cost, as each string key has to be converted. - case convertToSnakeCase + case convertToSnakeLowerCase + + /// Convert from "camelCaseKeys" to "SNAKE_CASE_KEYS" before writing a key to XML payload. + case convertToSnakeUpperCase /// Provide a custom conversion to the key in the encoded XML from the keys specified by the encoded types. /// The full path to the current encoding position is provided for context (in case you need to locate this key within the payload). The returned key is used in place of the last component in the coding path before encoding. /// If the result of the conversion is a duplicate key, then only one value will be present in the result. case custom((_ codingPath: [CodingKey]) -> CodingKey) - internal static func _convertToSnakeCase(_ stringKey: String) -> String { + internal static func _convertToSnakeCase(_ stringKey: String, uppercased: Bool) -> String { guard !stringKey.isEmpty else { return stringKey } var words : [Range] = [] @@ -168,7 +171,7 @@ open class XMLEncoder { } words.append(wordStart.. : KeyedEncodingCont // MARK: - Coding Path Operations private func _converted(_ key: CodingKey) -> CodingKey { + var snakeUppercased = false switch encoder.options.keyEncodingStrategy { case .useDefaultKeys: return key - case .convertToSnakeCase: - let newKeyString = XMLEncoder.KeyEncodingStrategy._convertToSnakeCase(key.stringValue) + case .convertToSnakeUpperCase: + snakeUppercased = true + fallthrough + case .convertToSnakeLowerCase: + let newKeyString = XMLEncoder.KeyEncodingStrategy._convertToSnakeCase(key.stringValue, uppercased: snakeUppercased) return _XMLKey(stringValue: newKeyString, intValue: key.intValue) case .custom(let converter): return converter(codingPath + [key]) diff --git a/Tests/XMLParsingTests/XMLParsingTests.swift b/Tests/XMLParsingTests/XMLParsingTests.swift index 7c6fa3e..464be5f 100644 --- a/Tests/XMLParsingTests/XMLParsingTests.swift +++ b/Tests/XMLParsingTests/XMLParsingTests.swift @@ -161,6 +161,20 @@ struct NullTest: Decodable { } +let uppercasedXml = """ + +value + + +""" + +struct UpperTest: Codable { + let nonEmptyValue: String + let whatsUpYo: String? +} + + + class XMLParsingTests: XCTestCase { func testExample() { do { @@ -191,6 +205,33 @@ class XMLParsingTests: XCTestCase { XCTAssert(false, "failed to decode the example: \(error)") } } + + func testUppercase() { + do { + guard let data = uppercasedXml.data(using: .utf8) else { return } + + let decoder = XMLDecoder() + decoder.keyDecodingStrategy = .convertFromSnakeUpperCase + + let test = try decoder.decode(UpperTest.self, from: data) + + XCTAssertEqual(test.nonEmptyValue, "value") + XCTAssertNil(test.whatsUpYo) + + let encoder = XMLEncoder() + + encoder.keyEncodingStrategy = .convertToSnakeUpperCase + + let newData = try encoder.encode(test, withRootKey: "ROOT") + + let xmlText = String(data: newData, encoding: .utf8)! + + print(xmlText) + + } catch { + XCTAssert(false, "failed to decode / encode the example: \(error)") + } + } static var allTests = [ From c299fe72274e2c51e933f415e7374175f67cb3ec Mon Sep 17 00:00:00 2001 From: "Leonid S. Usov" Date: Mon, 17 Dec 2018 00:30:10 +0200 Subject: [PATCH 24/24] Improve working with numbers --- Sources/XMLParsing/Decoder/XMLDecoder.swift | 60 +++++++++++---------- 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/Sources/XMLParsing/Decoder/XMLDecoder.swift b/Sources/XMLParsing/Decoder/XMLDecoder.swift index 5df1273..ca0a80d 100644 --- a/Sources/XMLParsing/Decoder/XMLDecoder.swift +++ b/Sources/XMLParsing/Decoder/XMLDecoder.swift @@ -435,11 +435,11 @@ extension _XMLDecoder { guard let string = value as? String else { return nil } - guard let value = Float(string) else { + guard let value = Decimal(string: string) else { throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: string) } - let number = NSNumber(value: value) + let number = NSDecimalNumber(decimal: value) guard number !== kCFBooleanTrue, number !== kCFBooleanFalse else { throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) @@ -458,11 +458,11 @@ extension _XMLDecoder { guard let string = value as? String else { return nil } - guard let value = Float(string) else { + guard let value = Decimal(string: string) else { throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: string) } - let number = NSNumber(value: value) + let number = NSDecimalNumber(decimal: value) guard number !== kCFBooleanTrue, number !== kCFBooleanFalse else { throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) @@ -481,11 +481,11 @@ extension _XMLDecoder { guard let string = value as? String else { return nil } - guard let value = Float(string) else { + guard let value = Decimal(string: string) else { throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: string) } - let number = NSNumber(value: value) + let number = NSDecimalNumber(decimal: value) guard number !== kCFBooleanTrue, number !== kCFBooleanFalse else { throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) @@ -504,11 +504,11 @@ extension _XMLDecoder { guard let string = value as? String else { return nil } - guard let value = Float(string) else { + guard let value = Decimal(string: string) else { throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: string) } - let number = NSNumber(value: value) + let number = NSDecimalNumber(decimal: value) guard number !== kCFBooleanTrue, number !== kCFBooleanFalse else { throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) @@ -527,11 +527,11 @@ extension _XMLDecoder { guard let string = value as? String else { return nil } - guard let value = Float(string) else { + guard let value = Decimal(string: string) else { throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: string) } - let number = NSNumber(value: value) + let number = NSDecimalNumber(decimal: value) guard number !== kCFBooleanTrue, number !== kCFBooleanFalse else { throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) @@ -550,11 +550,11 @@ extension _XMLDecoder { guard let string = value as? String else { return nil } - guard let value = Float(string) else { + guard let value = Decimal(string: string) else { throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: string) } - let number = NSNumber(value: value) + let number = NSDecimalNumber(decimal: value) guard number !== kCFBooleanTrue, number !== kCFBooleanFalse else { throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) @@ -573,11 +573,11 @@ extension _XMLDecoder { guard let string = value as? String else { return nil } - guard let value = Float(string) else { + guard let value = Decimal(string: string) else { throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: string) } - let number = NSNumber(value: value) + let number = NSDecimalNumber(decimal: value) guard number !== kCFBooleanTrue, number !== kCFBooleanFalse else { throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) @@ -596,11 +596,11 @@ extension _XMLDecoder { guard let string = value as? String else { return nil } - guard let value = Float(string) else { + guard let value = Decimal(string: string) else { throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: string) } - let number = NSNumber(value: value) + let number = NSDecimalNumber(decimal: value) guard number !== kCFBooleanTrue, number !== kCFBooleanFalse else { throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) @@ -619,11 +619,11 @@ extension _XMLDecoder { guard let string = value as? String else { return nil } - guard let value = Float(string) else { + guard let value = Decimal(string: string) else { throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: string) } - let number = NSNumber(value: value) + let number = NSDecimalNumber(decimal: value) guard number !== kCFBooleanTrue, number !== kCFBooleanFalse else { throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) @@ -642,11 +642,11 @@ extension _XMLDecoder { guard let string = value as? String else { return nil } - guard let value = Float(string) else { + guard let value = Decimal(string: string) else { throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: string) } - let number = NSNumber(value: value) + let number = NSDecimalNumber(decimal: value) guard number !== kCFBooleanTrue, number !== kCFBooleanFalse else { throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) @@ -784,6 +784,8 @@ extension _XMLDecoder { throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) } + guard !string.isEmpty else { return Data() } + guard let data = Data(base64Encoded: string) else { throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "Encountered Data is not valid Base64.")) } @@ -803,8 +805,8 @@ extension _XMLDecoder { // Attempt to bridge from NSDecimalNumber. let doubleValue = try self.unbox(value, as: Double.self)! return Decimal(doubleValue) - } - + } + internal func unbox(_ value: Any, as type: T.Type) throws -> T? { let decoded: T if type == Date.self || type == NSDate.self { @@ -827,13 +829,13 @@ extension _XMLDecoder { } else if type == Decimal.self || type == NSDecimalNumber.self { guard let decimal = try self.unbox(value, as: Decimal.self) else { return nil } decoded = decimal as! T - } else { - self.storage.push(container: value) - defer { self.storage.popContainer() } - return try type.init(from: self) - } - + } else { + self.storage.push(container: value) + defer { self.storage.popContainer() } + return try type.init(from: self) + } + return decoded + } } -}