From bd881e297b4209d6867af6bb1ef3ae972c1e6de7 Mon Sep 17 00:00:00 2001 From: Helge Hess Date: Fri, 27 Apr 2018 12:45:30 +0200 Subject: [PATCH] Update code to be Swift 4 compatible ... it should still work w/ Swift 2, but I suppose we can drop this eventually :-) --- README.md | 14 +- SwiftyExpat.xcodeproj/project.pbxproj | 25 +- .../xcschemes/SwiftyExpat.xcscheme | 4 +- SwiftyExpat/Expat.swift | 275 +++++++++++++----- SwiftyExpat/SwiftExtensions.swift | 5 + SwiftyExpatTests/SwiftyExpatTests.swift | 26 +- 6 files changed, 254 insertions(+), 95 deletions(-) diff --git a/README.md b/README.md index 571b9a5..0de4b58 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,11 @@ -SwiftyExpat -=========== +# SwiftyExpat + Simple wrapper for the Expat XML parser. -###Targets +### Targets + +2018-04-27: Updated to use Swift 4.0.3 (aka Xcode 9.2). Updated to use Swift v0.2b5 (aka Xcode 7b5). @@ -17,7 +19,7 @@ The project includes two targets: I suggest you start by looking at the SwiftyExpatTests. -####SwiftyExpat +#### SwiftyExpat This is a tiny framework wth a small Swift class to make the API nicer. Though this is not really necessary - Expat is reasonably easy to use from @@ -55,10 +57,10 @@ Note: The closures in the raw API cannot capture variables. If you need to pass around context (very likely ...), you need to fill the regular Expat 'user data' field (which the wrapper does, if you need an example). -####SwiftyExpatTests +#### SwiftyExpatTests Just a tiny demo on how to invoke the parser. -###Contact +### Contact [@helje5](http://twitter.com/helje5) | helge@alwaysrightinstitute.com diff --git a/SwiftyExpat.xcodeproj/project.pbxproj b/SwiftyExpat.xcodeproj/project.pbxproj index ac81c66..31089ef 100644 --- a/SwiftyExpat.xcodeproj/project.pbxproj +++ b/SwiftyExpat.xcodeproj/project.pbxproj @@ -286,7 +286,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0700; - LastUpgradeCheck = 0700; + LastUpgradeCheck = 0920; ORGANIZATIONNAME = "Always Right Institute"; TargetAttributes = { E8FB773D1971609A00E0557D = { @@ -390,13 +390,21 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; @@ -405,6 +413,7 @@ ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", @@ -422,6 +431,7 @@ ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 4.0; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; @@ -435,13 +445,21 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = YES; @@ -450,6 +468,7 @@ ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; @@ -459,6 +478,8 @@ MACOSX_DEPLOYMENT_TARGET = 10.9; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 4.0; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; @@ -479,6 +500,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "de.alwaysrightinstitute.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; + SWIFT_VERSION = 4.0; }; name = Debug; }; @@ -497,6 +519,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "de.alwaysrightinstitute.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; + SWIFT_VERSION = 4.0; }; name = Release; }; diff --git a/SwiftyExpat.xcodeproj/xcshareddata/xcschemes/SwiftyExpat.xcscheme b/SwiftyExpat.xcodeproj/xcshareddata/xcschemes/SwiftyExpat.xcscheme index 74f7512..bac9dc6 100644 --- a/SwiftyExpat.xcodeproj/xcshareddata/xcschemes/SwiftyExpat.xcscheme +++ b/SwiftyExpat.xcodeproj/xcshareddata/xcschemes/SwiftyExpat.xcscheme @@ -1,6 +1,6 @@ world") * p.close() */ -public final class Expat : OutputStreamType, BooleanType { +public final class Expat { public let nsSeparator : Character @@ -32,15 +32,20 @@ public final class Expat : OutputStreamType, BooleanType { let sepUTF8 = ("" + String(self.nsSeparator)).utf8 let separator = sepUTF8[sepUTF8.startIndex] - parser = encoding.withCString { cs in + let parser = encoding.withCString { cs in // if I use parser, swiftc crashes (if Expat is a class) // FIXME: use String for separator, and codepoints to get the Int? XML_ParserCreateNS(cs, XML_Char(separator)) } assert(parser != nil) - + self.parser = parser + // TBD: what is the better way to do this? - let ud = unsafeBitCast(self, UnsafeMutablePointer.self) + #if swift(>=4.0) + let ud = unsafeBitCast(self, to: UnsafeMutableRawPointer.self) + #else + let ud = unsafeBitCast(self, UnsafeMutablePointer.self) + #endif XML_SetUserData(parser, ud) registerCallbacks() @@ -63,11 +68,15 @@ public final class Expat : OutputStreamType, BooleanType { /* feed the parser */ public func feedRaw - (cs: UnsafePointer, final: Bool = false) -> ExpatResult + (_ cs: UnsafePointer, final: Bool = false) -> ExpatResult { // v4: for some reason this accepts a 'String', but for such it doesn't // actually work - let cslen = cs != nil ? strlen(cs) : 0 // cs? checks for a NULL C string + #if swift(>=4.0) + let cslen = strlen(cs) // cs? checks for a NULL C string + #else + let cslen = cs != nil ? strlen(cs) : 0 // cs? checks for a NULL C string + #endif let isFinal : Int32 = final ? 1 : 0 //dumpCharBuf(cs, Int(cslen)) @@ -84,13 +93,13 @@ public final class Expat : OutputStreamType, BooleanType { return ExpatResult.Error(error) } } - public func feed(s: String, final: Bool = false) -> ExpatResult { + public func feed(_ s: String, final: Bool = false) -> ExpatResult { return s.withCString { cs -> ExpatResult in return self.feedRaw(cs, final: final) } } - public func write(s: String) { + public func write(_ s: String) { let result = self.feed(s) // doesn't work with associated value?: assert(ExpatResult.OK == result) @@ -115,10 +124,16 @@ public final class Expat : OutputStreamType, BooleanType { func registerCallbacks() { XML_SetStartElementHandler(parser) { ud, name, attrs in - let me = unsafeBitCast(ud, Expat.self) - guard let cb = me.cbStartElement else { return } + #if swift(>=4.0) + let me = unsafeBitCast(ud, to: Expat.self) + guard let cb = me.cbStartElement else { return } + let sName = name != nil ? String(cString: name!) : "" + #else + let me = unsafeBitCast(ud, Expat.self) + guard let cb = me.cbStartElement else { return } + let sName = String.fromCString(name)! // unwrap, must be set + #endif - let sName = String.fromCString(name)! // unwrap, must be set // FIXME: we should not copy stuff, but have a wrapper which works on the // attrs structure 'on demand' @@ -127,27 +142,46 @@ public final class Expat : OutputStreamType, BooleanType { } XML_SetEndElementHandler(parser) { ud, name in - let me = unsafeBitCast(ud, Expat.self) - guard let cb = me.cbEndElement else { return } - - let sName = String.fromCString(name)! // unwrap, must be set - cb(sName) + #if swift(>=4.0) + let me = unsafeBitCast(ud, to: Expat.self) + guard let cb = me.cbEndElement else { return } + let sName = String(cString: name!) // force unwrap, must be set + cb(sName) + #else + let me = unsafeBitCast(ud, Expat.self) + guard let cb = me.cbEndElement else { return } + let sName = String.fromCString(name)! // force unwrap, must be set + cb(sName) + #endif } XML_SetStartNamespaceDeclHandler(parser) { ud, prefix, uri in - let me = unsafeBitCast(ud, Expat.self) - guard let cb = me.cbStartNS else { return } - - let sPrefix = String.fromCString(prefix) - let sURI = String.fromCString(uri)! - cb(sPrefix, sURI) + #if swift(>=4.0) + let me = unsafeBitCast(ud, to: Expat.self) + guard let cb = me.cbStartNS else { return } + let sPrefix = prefix != nil ? String(cString: prefix!) : nil + let sURI = String(cString: uri!) + cb(sPrefix, sURI) + #else + let me = unsafeBitCast(ud, Expat.self) + guard let cb = me.cbStartNS else { return } + let sPrefix = String.fromCString(prefix) + let sURI = String.fromCString(uri)! + cb(sPrefix, sURI) + #endif } XML_SetEndNamespaceDeclHandler(parser) { ud, prefix in - let me = unsafeBitCast(ud, Expat.self) - guard let cb = me.cbEndNS else { return } - - let sPrefix = String.fromCString(prefix) - cb(sPrefix) + #if swift(>=4.0) + let me = unsafeBitCast(ud, to: Expat.self) + guard let cb = me.cbEndNS else { return } + let sPrefix = prefix != nil ? String(cString: prefix!) : nil + cb(sPrefix) + #else + let me = unsafeBitCast(ud, Expat.self) + guard let cb = me.cbEndNS else { return } + let sPrefix = String.fromCString(prefix) + cb(sPrefix) + #endif } XML_SetCharacterDataHandler(parser) { ud, cs, cslen in @@ -156,16 +190,26 @@ public final class Expat : OutputStreamType, BooleanType { // println("CS: \(cs[0]) len \(cslen)") guard cslen > 0 else { return } - let me = unsafeBitCast(ud, Expat.self) - guard let cb = me.cbCharacterData else { return } + #if swift(>=4.0) + let me = unsafeBitCast(ud, to: Expat.self) + guard let cb = me.cbCharacterData else { return } - guard let s = String.fromCString(cs, length: Int(cslen)) else { - print("ERROR: could not convert CString to String?! (len=\(cslen))") - dumpCharBuf(cs, len: Int(cslen)) - return - } - - cb(s) + let cs2 = UnsafeRawPointer(cs!).assumingMemoryBound(to: UInt8.self) + let bp = UnsafeBufferPointer(start: cs2, count: Int(cslen)) + let s = String(decoding: bp, as: UTF8.self) + cb(s) + #else + let me = unsafeBitCast(ud, Expat.self) + guard let cb = me.cbCharacterData else { return } + + guard let s = String.fromCString(cs, length: Int(cslen)) else { + print("ERROR: could not convert CString to String?! (len=\(cslen))") + dumpCharBuf(cs, len: Int(cslen)) + return + } + + cb(s) + #endif } } @@ -197,33 +241,63 @@ public final class Expat : OutputStreamType, BooleanType { var cbCharacterData : CDataHandler? var cbError : ErrorHandler? - public func onStartElement(cb: StartElementHandler)-> Self { - cbStartElement = cb - return self - } - public func onEndElement(cb: EndElementHandler) -> Self { - cbEndElement = cb - return self - } + #if swift(>=3.0) + public func onStartElement(cb: @escaping StartElementHandler)-> Self { + cbStartElement = cb + return self + } + public func onEndElement(cb: @escaping EndElementHandler) -> Self { + cbEndElement = cb + return self + } - public func onStartNamespace(cb: StartNamespaceHandler) -> Self { - cbStartNS = cb - return self - } - public func onEndNamespace(cb: EndNamespaceHandler) -> Self { - cbEndNS = cb - return self - } + public func onStartNamespace(cb: @escaping StartNamespaceHandler) -> Self { + cbStartNS = cb + return self + } + public func onEndNamespace(cb: @escaping EndNamespaceHandler) -> Self { + cbEndNS = cb + return self + } - public func onCharacterData(cb: CDataHandler) -> Self { - cbCharacterData = cb - return self - } + public func onCharacterData(cb: @escaping CDataHandler) -> Self { + cbCharacterData = cb + return self + } - public func onError(cb: ErrorHandler) -> Self { - cbError = cb - return self - } + public func onError(cb: @escaping ErrorHandler) -> Self { + cbError = cb + return self + } + #else + public func onStartElement(cb: StartElementHandler)-> Self { + cbStartElement = cb + return self + } + public func onEndElement(cb: EndElementHandler) -> Self { + cbEndElement = cb + return self + } + + public func onStartNamespace(cb: StartNamespaceHandler) -> Self { + cbStartNS = cb + return self + } + public func onEndNamespace(cb: EndNamespaceHandler) -> Self { + cbEndNS = cb + return self + } + + public func onCharacterData(cb: CDataHandler) -> Self { + cbCharacterData = cb + return self + } + + public func onError(cb: ErrorHandler) -> Self { + cbError = cb + return self + } + #endif } @@ -233,24 +307,44 @@ public extension Expat { // Namespaces ( String, String, [String : String] ) -> Void public typealias EndElementNSHandler = ( String, String ) -> Void - public func onStartElementNS(cb: StartElementNSHandler) -> Self { - let sep = self.nsSeparator // so that we don't capture 'self' (necessary?) - return onStartElement { - let comps = $0.characters.split(sep, maxSplit: 1, allowEmptySlices: false) - .map { String($0) } - cb(comps[0], comps[1], $1) + #if swift(>=3.2) + public func onStartElementNS(cb: @escaping StartElementNSHandler) -> Self { + let sep = self.nsSeparator // so that we don't capture 'self' (necessary?) + return onStartElement { + // split(separator:maxSplits:omittingEmptySubsequences:) + let comps = $0.split(separator: sep, maxSplits: 1, + omittingEmptySubsequences: true) + cb(String(comps[0]), String(comps[1]), $1) + } } - } - public func onEndElementNS(cb: EndElementNSHandler) -> Self { - let sep = self.nsSeparator // so that we don't capture 'self' (necessary?) - return onEndElement { - let comps = $0.characters.split(sep, maxSplit: 1, allowEmptySlices: false) - .map { String($0) } - cb(comps[0], comps[1]) + public func onEndElementNS(cb: @escaping EndElementNSHandler) -> Self { + let sep = self.nsSeparator // so that we don't capture 'self' (necessary?) + return onEndElement { + let comps = $0.split(separator: sep, maxSplits: 1, + omittingEmptySubsequences: true) + cb(String(comps[0]), String(comps[1])) + } + } + #else + public func onStartElementNS(cb: StartElementNSHandler) -> Self { + let sep = self.nsSeparator // so that we don't capture 'self' (necessary?) + return onStartElement { + let comps = $0.characters.split(sep, maxSplit: 1, allowEmptySlices: false) + .map { String($0) } + cb(comps[0], comps[1], $1) + } } - } + public func onEndElementNS(cb: EndElementNSHandler) -> Self { + let sep = self.nsSeparator // so that we don't capture 'self' (necessary?) + return onEndElement { + let comps = $0.characters.split(sep, maxSplit: 1, allowEmptySlices: false) + .map { String($0) } + cb(comps[0], comps[1]) + } + } + #endif } @@ -275,7 +369,7 @@ extension XML_Error : CustomStringConvertible { } } -public enum ExpatResult : CustomStringConvertible, BooleanType { +public enum ExpatResult : CustomStringConvertible { case OK case Suspended @@ -300,18 +394,41 @@ public enum ExpatResult : CustomStringConvertible, BooleanType { /* debug */ -func dumpCharBuf(buf: UnsafePointer, len : Int) { +func dumpCharBuf(_ buf: UnsafePointer, len : Int) { print("*-- buffer (len=\(len))") for i in 0 ..< len { let cp = Int(buf[i]) - let c = Character(UnicodeScalar(cp)) + #if swift(>=3.0) + let c = Character(UnicodeScalar(cp)!) + #else + let c = Character(UnicodeScalar(cp)) + #endif print(" [\(i)]: \(cp) \(c)") } print("---") } +#if swift(>=3.0) + func makeAttributesDictionary - (attrs : UnsafeMutablePointer>) + (_ attrs : UnsafeMutablePointer?>?) + -> [ String : String ] +{ + var sAttrs = [ String : String ]() + guard let attrs = attrs else { return sAttrs } + var i = 0 + while attrs[i] != nil { + let name = String(cString: attrs[i]!) + let value = attrs[i + 1] != nil ? String(cString: attrs[i + 1]!) : "" + sAttrs[name] = value + i += 2 + } + return sAttrs +} +#else + +func makeAttributesDictionary + (_ attrs : UnsafeMutablePointer>) -> [ String : String ] { var sAttrs = [ String : String ]() @@ -326,3 +443,5 @@ func makeAttributesDictionary } return sAttrs } + +#endif diff --git a/SwiftyExpat/SwiftExtensions.swift b/SwiftyExpat/SwiftExtensions.swift index 7575bac..8b04c1c 100644 --- a/SwiftyExpat/SwiftExtensions.swift +++ b/SwiftyExpat/SwiftExtensions.swift @@ -6,6 +6,10 @@ // Copyright (c) 2014 Always Right Institute. All rights reserved. // +#if swift(>=4.0) + +#else + // Those are mostly dirty hacks to get what I need :-) // I would be very interested in better way to do those things, W/O using // Foundation. @@ -36,3 +40,4 @@ extension String { } } +#endif // \(name)") } - .onStartNamespace { prefix, uri in print("+NS[\(prefix)] = \(uri)") } - .onEndNamespace { prefix in print("-NS[\(prefix)]") } - .onCharacterData { content in print("TEXT: \(content)") } + .onStartNamespace { prefix, uri in print("+NS[\(prefix ?? "")] = \(uri)")} + .onEndNamespace { prefix in print("-NS[\(prefix ?? "")]") } + .onCharacterData { content in print("TEXT: \(content ?? "")") } .onError { error in print("ERROR \(error)") } } @@ -38,11 +38,11 @@ class SwiftyExpatTests: XCTestCase { let testXML = "world" result = p.feed(testXML) - XCTAssert(result) + XCTAssert(result.boolValue) result = p.close() // EOF print("Feed result: \(result)") - XCTAssert(result) + XCTAssert(result.boolValue) } func testErrorHandling() { @@ -53,10 +53,10 @@ class SwiftyExpatTests: XCTestCase { result = p.feed(testXML) print("Feed result: \(result)") - XCTAssert(!result) + XCTAssert(!result.boolValue) result = p.close() // EOF - XCTAssert(!result) + XCTAssert(!result.boolValue) } func testRawAPI() { @@ -66,11 +66,19 @@ class SwiftyExpatTests: XCTestCase { defer { XML_ParserFree(p); } XML_SetStartElementHandler(p) { _, name, attrs in - let nameString = String.fromCString(name)! + #if swift(>=3.0) + let nameString = String(cString: name!) + #else + let nameString = String.fromCString(name)! + #endif print("start tag \(nameString)") } XML_SetEndElementHandler (p) { _, name in - let nameString = String.fromCString(name)! + #if swift(>=3.0) + let nameString = String(cString: name!) + #else + let nameString = String.fromCString(name)! + #endif print("end tag \(nameString)") }