Update code to be Swift 4 compatible

... it should still work w/ Swift 2, but I suppose
we can drop this eventually :-)
This commit is contained in:
Helge Hess 2018-04-27 12:45:30 +02:00
parent e00c5b7983
commit bd881e297b
6 changed files with 254 additions and 95 deletions

View File

@ -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

View File

@ -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;
};

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0700"
LastUpgradeVersion = "0920"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@ -26,6 +26,7 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
@ -55,6 +56,7 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"

View File

@ -20,7 +20,7 @@
* p.write("<hello>world</hello>")
* 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<Void>.self)
#if swift(>=4.0)
let ud = unsafeBitCast(self, to: UnsafeMutableRawPointer.self)
#else
let ud = unsafeBitCast(self, UnsafeMutablePointer<Void>.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<CChar>, final: Bool = false) -> ExpatResult
(_ cs: UnsafePointer<CChar>, 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<CChar>, len : Int) {
func dumpCharBuf(_ buf: UnsafePointer<CChar>, 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<UnsafePointer<XML_Char>>)
(_ attrs : UnsafeMutablePointer<UnsafePointer<XML_Char>?>?)
-> [ 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<UnsafePointer<XML_Char>>)
-> [ String : String ]
{
var sAttrs = [ String : String ]()
@ -326,3 +443,5 @@ func makeAttributesDictionary
}
return sAttrs
}
#endif

View File

@ -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 // <Swift 4

View File

@ -20,9 +20,9 @@ class SwiftyExpatTests: XCTestCase {
p = Expat()
.onStartElement { name, attrs in print("<\(name) \(attrs)") }
.onEndElement { name in print(">\(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 = "<hello xmlns='YoYo' a='5'><x>world</x></hello>"
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)")
}