diff --git a/README.md b/README.md
index c977c97..a582ac3 100644
--- a/README.md
+++ b/README.md
@@ -1,205 +1,51 @@
-SwiftyHTTP
-==========
+SwiftyExpat
+===========
-A simple GCD based HTTP library for Swift.
-
-SwiftyHTTP is kind of a demo on how to integrate Swift with raw C APIs. More
-for stealing Swift coding ideas than for actually using the code in a real
-project. In most real world Swift apps you have access to Cocoa, use it.
-
-**Note**: This is just my second [Swift](https://developer.apple.com/swift/)
-project. Any suggestions on how to improve the code are welcome. I expect
-lots and lots :-)
+Simple wrapper for the Expat XML parser. Which had to be adjusted to use
+blocks instead of function pointer callbacks.
###Targets
-The project includes three targets:
-- SwiftyHTTP
-- SwiftyServer
-- SwiftyClient
+The project includes two targets:
+- SwiftyExpat
+- SwiftyExpatTests
-I suggest you start out looking at the SwiftyServer.
+I suggest you start out looking at the SwiftyExpatTests.
-####SwiftyHTTP
+####SwiftyExpat
-A framework containing the HTTP classes and relevant extensions. It has a few
-'subprojects':
-- Foundation
-- Sockets
-- Parser
-- HTTP
+This is a tiny framework containing the modified Expat parser. Plus a small
+Swift class to make the API nicer, though this is not really necessary - the
+block based Expat is reasonably easy to use from Swift.
-#####Foundation
-
-This has just the 'RawByteBuffer' class. Which is kinda like a RawByte array.
-I bet there are better ways to implement this! Please suggest some! :-)
-
-Also a few - highly inefficient - extensions to convert between String's and
-CString's. I would love some suggestions on those as well.
-
-But remember: NSxyz is forbidden for this venture! :-)
-
-#####Sockets
-
-Just a local copy of the SwiftSockets project - I wish GIT had proper externals
-;-) (https://github.com/AlwaysRightInstitute/SwiftSockets)
-
-#####Parser
-
-This uses the C HTTP parser which is also used in Node.JS. I couldn't
-directly use it being C callback driven - a feature (currently?)
-unsupported by Swift.
-The fix was to rewrite the parser to use C blocks instead of function pointers.
-Along that way I also removed some great features of the parser, like no
-malloc() at all :->
-
-It also contains the main request/response classes: HTTPRequest and
-HTTPResponse, both subclasses of HTTPMessage.
-And enums for HTTP status values (like `💰Required`) and request methods (GET
-etc).
-
-#####HTTP
-
-HTTPConnectionPool is an abstract base class and manages open connections,
-either incoming or outgoing. The HTTPConnection sits on top of the SwiftSockets
-and manages one HTTP connection.
-
-HTTPServer is the server class. Uses the SwiftSockets to listen for incoming
-connections. Sample:
```Swift
-let httpd = HTTPServer()
- .onRequest {
- rq, res, con in
- res.bodyAsString = "
Always Right, Never Wrong!
"
- con.sendResponse(res)
- }
- .listen(1337)
-```
-That's it.
-
-As a bonus - this also has a tiny Connect class - which is modelled after the
-Node.JS Connect thingy (which in turn is apparently modelled after RoR Rack).
-It allows you to hook up a set of blocks for request processing, instead of
-having just a single entry point.
-Not sure I like that stuff, but it seems to fit into Swift quite well.
-
-It works like this:
-```Swift
-let httpd = Connect()
- .use { rq, res, _, next in
- println("\(rq.method) \(rq.url) \(res.status)")
- next()
- }
- .use("/hello") { rq, res, con, next in
- res.bodyAsString = "Hello!"
- con.sendResponse(res)
- }
- .use("/") { rq, res, con, next in
- res.bodyAsString = "Always almost sometimes"
- con.sendResponse(res)
- }
- .listen(1337)
+let p = Expat()
+ .onStartElement { name, attrs in println("<\(name) \(attrs)") }
+ .onEndElement { name in println(">\(name)") }
+ .onStartNamespace { prefix, uri in println("+NS[\(prefix)] = \(uri)") }
+ .onEndNamespace { prefix in println("-NS[\(prefix)]") }
+ .onError { error in println("ERROR: \(error)") }
+p.write("world")
+p.close()
```
-Finally there is a tiny HTTP client, use it like this:
+The raw Expat API works like this:
```Swift
- GET("http://www.apple.com/")
- .done {
- println()
- println("request \($0)")
- println("response \($1)")
- println("body:\n\($1.bodyAsString)")
- }
- .fail {
- println("failed \($0): \($1)")
- }
- .always { println("---") }
+var p = XML_ParserCreate("UTF-8")
+XML_SetStartElementHandler(p) { _, name, attrs in println("start tag \(name)") }
+XML_SetEndElementHandler (p) { _, name in println("end tag \(name)") }
+
+XML_Parse(parser, "", 8, 0)
+XML_Parse(parser, "", 0, 1)
+
+XML_ParserFree(p); p = nil
```
+You get the idea ...
-####SwiftyServer
+####SwiftyExpatTests
-Great httpd server - great in counting the requests it got sent. This is not
-actually serving any files ;-) Comes along as a Cocoa app. Compile it, run it,
-then connect to it in the browser via http://127.0.0.1:1337/Awesome-O!
-
-![](http://i.imgur.com/4ShGZXS.png)
-
-####SwiftyClient
-
-Just a demo on how to do HTTP requests via SwiftyHTTP. No, it doesn't do JSON
-decoding and such.
-
-Again: You do NOT want to use it in a real iOS/OSX app! Use NSURLSession and
-companions - it gives you plenty of extra features you want to have for realz.
-
-![](http://i.imgur.com/ny0PSKH.png)
-
-###Goals
-
-- [x] Max line length: 80 characters
-- [ ] Great error handling
- - [x] PS style great error handling
- - [x] println() error handling
- - [ ] Real error handling
-- [x] Twisted (no blocking reads or writes)
- - [x] Async reads and writes
- - [x] Never block on reads
- - [x] Never block on listen
- - [ ] Async connect()
-- [x] No NS'ism
-- [ ] Use as many language features Swift provides
- - [x] Generics
- - [x] Generic function
- - [x] typealias
- - [x] Closures
- - [x] weak self
- - [x] trailing closures
- - [x] implicit parameters
- - [x] Unowned
- - [x] Extensions on structs
- - [x] Extensions to organize classes
- - [x] Protocols on structs
- - [x] Tuples
- - [x] Trailing closures
- - [ ] @Lazy
- - [x] Pure Swift weak delegates via @class
- - [x] Optionals
- - [x] Convenience initializers
- - [x] Class variables on structs
- - [x] CConstPointer, CConstVoidPointer
- - [x] withCString {}
- - [x] UnsafePointer
- - [x] sizeof()
- - [x] Standard Protocols
- - [x] Printable
- - [x] LogicValue
- - [x] OutputStream
- - [x] Equatable
- - [x] Hashable
- - [x] Sequence (GeneratorOf)
- - [x] Left shift AND right shift
- - [x] Enums on steroids
- - [ ] Dynamic type system, reflection
- - [x] Operator overloading
- - [x] UCS-4 identifiers (🐔🐔🐔)
- - [ ] ~~RTF source code with images and code sections in different fonts~~
- - [x] Nested classes/types
- - [ ] Patterns
- - [x] Use wildcard pattern to ignore value
- - [ ] @auto-closure
- - [ ] reinterpretCast()
-
-###Why?!
-
-This is an experiment to get acquainted with Swift. To check whether something
-real can be implemented in 'pure' Swift. Meaning, without using any Objective-C
-Cocoa classes (no NS'ism).
-Or in other words: Can you use Swift without writing all the 'real' code in
-wrapped Objective-C? :-)
+Just a tiny demo on how to invoke the parser.
###Contact
[@helje5](http://twitter.com/helje5) | helge@alwaysrightinstitute.com
-
-![](http://www.alwaysrightinstitute.com/ARI.png)
-
diff --git a/SwiftyExpat.xcodeproj/project.pbxproj b/SwiftyExpat.xcodeproj/project.pbxproj
index de95c8f..b4a0122 100644
--- a/SwiftyExpat.xcodeproj/project.pbxproj
+++ b/SwiftyExpat.xcodeproj/project.pbxproj
@@ -8,6 +8,7 @@
/* Begin PBXBuildFile section */
E8C667EC19757405004EFA0C /* Expat.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8C667EB19757405004EFA0C /* Expat.swift */; };
+ E8C667EE19757B9D004EFA0C /* SwiftExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8C667ED19757B9D004EFA0C /* SwiftExtensions.swift */; };
E8FB77441971609A00E0557D /* SwiftyExpat.h in Headers */ = {isa = PBXBuildFile; fileRef = E8FB77431971609A00E0557D /* SwiftyExpat.h */; settings = {ATTRIBUTES = (Public, ); }; };
E8FB77721971667F00E0557D /* ascii.h in Headers */ = {isa = PBXBuildFile; fileRef = E8FB775F1971667F00E0557D /* ascii.h */; };
E8FB77731971667F00E0557D /* asciitab.h in Headers */ = {isa = PBXBuildFile; fileRef = E8FB77601971667F00E0557D /* asciitab.h */; };
@@ -65,6 +66,8 @@
/* Begin PBXFileReference section */
E8C667EB19757405004EFA0C /* Expat.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Expat.swift; sourceTree = ""; };
+ E8C667ED19757B9D004EFA0C /* SwiftExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftExtensions.swift; sourceTree = ""; };
+ E8C667EF19757C40004EFA0C /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; };
E8FB773E1971609A00E0557D /* SwiftyExpat.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SwiftyExpat.framework; sourceTree = BUILT_PRODUCTS_DIR; };
E8FB77421971609A00E0557D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
E8FB77431971609A00E0557D /* SwiftyExpat.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SwiftyExpat.h; sourceTree = ""; };
@@ -72,7 +75,7 @@
E8FB77601971667F00E0557D /* asciitab.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = asciitab.h; sourceTree = ""; };
E8FB77611971667F00E0557D /* Changes */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Changes; sourceTree = ""; };
E8FB77621971667F00E0557D /* COPYING */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = COPYING; sourceTree = ""; };
- E8FB77631971667F00E0557D /* expat.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = expat.h; sourceTree = ""; };
+ E8FB77631971667F00E0557D /* expat.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = expat.h; path = expat/expat.h; sourceTree = ""; };
E8FB77641971667F00E0557D /* expat_config.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = expat_config.h; sourceTree = ""; };
E8FB77651971667F00E0557D /* expat_external.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = expat_external.h; sourceTree = ""; };
E8FB77661971667F00E0557D /* iasciitab.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = iasciitab.h; sourceTree = ""; };
@@ -116,6 +119,7 @@
E8FB77341971609A00E0557D = {
isa = PBXGroup;
children = (
+ E8C667EF19757C40004EFA0C /* README.md */,
E8FB77401971609A00E0557D /* SwiftyExpat */,
E8FB77901971767500E0557D /* SwiftyExpatTests */,
E8FB773F1971609A00E0557D /* Products */,
@@ -135,6 +139,8 @@
isa = PBXGroup;
children = (
E8C667EB19757405004EFA0C /* Expat.swift */,
+ E8C667ED19757B9D004EFA0C /* SwiftExtensions.swift */,
+ E8FB77631971667F00E0557D /* expat.h */,
E8FB77431971609A00E0557D /* SwiftyExpat.h */,
E8FB775E1971667F00E0557D /* expat */,
E8FB77411971609A00E0557D /* Supporting Files */,
@@ -156,7 +162,6 @@
children = (
E8FB77621971667F00E0557D /* COPYING */,
E8FB77611971667F00E0557D /* Changes */,
- E8FB77631971667F00E0557D /* expat.h */,
E8FB7787197166E600E0557D /* char tables */,
E8FB779F1971E5EE00E0557D /* implementation */,
);
@@ -340,6 +345,7 @@
files = (
E8FB77811971667F00E0557D /* xmltok.c in Sources */,
E8FB777E1971667F00E0557D /* xmlparse.c in Sources */,
+ E8C667EE19757B9D004EFA0C /* SwiftExtensions.swift in Sources */,
E8C667EC19757405004EFA0C /* Expat.swift in Sources */,
E8FB777F1971667F00E0557D /* xmlrole.c in Sources */,
);
diff --git a/SwiftyExpat/Expat.swift b/SwiftyExpat/Expat.swift
index 118404f..cbc197a 100644
--- a/SwiftyExpat/Expat.swift
+++ b/SwiftyExpat/Expat.swift
@@ -12,9 +12,9 @@
*
* Done as a class as this is no value object (and struct's have no deinit())
*/
-class Expat : OutputStream {
+class Expat : OutputStream, LogicValue {
- let parser : XML_Parser
+ var parser : XML_Parser = nil
var isClosed = false
init(encoding: String = "UTF-8", nsSeparator: Character = ":") {
@@ -32,30 +32,64 @@ class Expat : OutputStream {
parser = newParser
}
deinit {
- println("freeing parser ...")
- XML_ParserFree(parser)
+ if parser {
+ XML_ParserFree(parser)
+ }
+ }
+
+
+ /* valid? */
+
+ func getLogicValue() -> Bool {
+ return parser != nil
}
/* feed the parser */
- func write(cs: CString) {
- let cslen = strlen(cs)
- XML_Parse(parser, cs, Int32(cslen), 0)
+ func feed(cs: CString, final: Bool = false) -> ExpatResult {
+ let cslen = cs ? strlen(cs) : 0 // cs? checks for a NULL C string
+ let isFinal : Int32 = final ? 1 : 0
+ let status : XML_Status = XML_Parse(parser, cs, Int32(cslen), isFinal)
+
+ switch status.value { // the Expat enum's don't work?
+ case 1: return ExpatResult.OK
+ case 2: return ExpatResult.Suspended
+ default:
+ let error = XML_GetErrorCode(parser)
+ if let cb = errorCB {
+ cb(error)
+ }
+ return ExpatResult.Error(error)
+ }
+ }
+
+ func feed(s: String) -> ExpatResult {
+ return s.withCString { cs in self.feed(cs) }
}
func write(s: String) {
- s.withCString { cs in self.write(cs) }
+ let result = feed(s)
+
+ // doesn't work with associated value?: assert(ExpatResult.OK == result)
+ switch result {
+ case .OK: break
+ default: assert(false)
+ }
}
- func close() {
- if isClosed { return }
+ func close() -> ExpatResult {
+ if isClosed { return ExpatResult.OK /* do not complain */ }
- let isFinal : Int32 = 1
- XML_Parse(parser, "", 0, isFinal)
+ let result = feed("", final: true)
resetCallbacks()
isClosed = true
+
+ XML_ParserFree(parser)
+ parser = nil
+
+ return result
}
func resetCallbacks() {
@@ -119,6 +153,7 @@ class Expat : OutputStream {
}
return self
}
+
func onEndNamespace(cb: ( String? ) -> Void) -> Self {
XML_SetEndNamespaceDeclHandler(parser) {
_, prefix in
@@ -128,4 +163,65 @@ class Expat : OutputStream {
return self
}
+ func onCharacterData(cb: ( String ) -> Void) -> Self {
+ //const XML_Char *s, int len);
+ XML_SetCharacterDataHandler(parser) {
+ _, cs, cslen in
+ assert(cslen > 0)
+ if cslen > 0 {
+ let s = String.fromCString(cs, length: Int(cslen))!
+ cb(s)
+ }
+ }
+ return self
+ }
+
+ func onError(cb: ( XML_Error ) -> Void) -> Self {
+ errorCB = cb
+ return self
+ }
+ var errorCB : (( XML_Error ) -> Void)? = nil
+}
+
+extension XML_Error : Printable {
+
+ var description: String {
+ switch self.value {
+ // doesn't work?: case .XML_ERROR_NONE: return "OK"
+ case 0 /* XML_ERROR_NONE */: return "OK"
+ case 1 /* XML_ERROR_NO_MEMORY */: return "XMLError::NoMemory"
+ case 2 /* XML_ERROR_SYNTAX */: return "XMLError::Syntax"
+ case 3 /* XML_ERROR_NO_ELEMENTS */: return "XMLError::NoElements"
+ case 4 /* XML_ERROR_INVALID_TOKEN */: return "XMLError::InvalidToken"
+ case 5 /* XML_ERROR_UNCLOSED_TOKEN */: return "XMLError::UnclosedToken"
+ case 6 /* XML_ERROR_PARTIAL_CHAR */: return "XMLError::PartialChar"
+ case 7 /* XML_ERROR_TAG_MISMATCH */: return "XMLError::TagMismatch"
+ case 8 /* XML_ERROR_DUPLICATE_ATTRIBUTE */: return "XMLError::DupeAttr"
+ // FIXME: complete me
+ default:
+ return "XMLError(\(self.value))"
+ }
+ }
+}
+
+enum ExpatResult : Printable, LogicValue {
+
+ case OK
+ case Suspended
+ case Error(XML_Error) // we cannot make this XML_Error, fails swiftc
+
+ var description: String {
+ switch self {
+ case .OK: return "OK"
+ case .Suspended: return "Suspended"
+ case .Error(let error): return "XMLError(\(error))"
+ }
+ }
+
+ func getLogicValue() -> Bool {
+ switch self {
+ case .OK: return true
+ default: return false
+ }
+ }
}
diff --git a/SwiftyExpat/SwiftExtensions.swift b/SwiftyExpat/SwiftExtensions.swift
index a5a3696..120f9b9 100644
--- a/SwiftyExpat/SwiftExtensions.swift
+++ b/SwiftyExpat/SwiftExtensions.swift
@@ -2,8 +2,34 @@
// SwiftExtensions.swift
// SwiftyExpat
//
-// Created by Helge Heß on 7/15/14.
+// Created by Helge Heß on 6/18/14.
// Copyright (c) 2014 Always Right Institute. All rights reserved.
//
-import Foundation
+// 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.
+
+extension String {
+
+ static func fromCString(cs: CString, length: Int?) -> String? {
+ if length == .None { // no length given, use \0 standard variant
+ return String.fromCString(cs)
+ }
+
+ // hh: this is really lame, there must be a better way :-)
+ // Also: it could be a constant string! So we probably need to copy ...
+ if let buf = cs.persist() {
+ return buf.withUnsafePointerToElements {
+ (p: UnsafePointer) in
+ let old = p[length!]
+ p[length!] = 0
+ let s = String.fromCString(CString(p))
+ p[length!] = old
+ return s
+ }
+ }
+ return nil
+ }
+
+}
diff --git a/SwiftyExpatTests/SwiftyExpatTests.swift b/SwiftyExpatTests/SwiftyExpatTests.swift
index e621e18..10eb56c 100644
--- a/SwiftyExpatTests/SwiftyExpatTests.swift
+++ b/SwiftyExpatTests/SwiftyExpatTests.swift
@@ -19,9 +19,11 @@ class SwiftyExpatTests: XCTestCase {
p = Expat()
.onStartElement { name, attrs in println("<\(name) \(attrs)") }
- .onEndElement { name in println(">\(name)") }
+ .onEndElement { name in println(">\(name)") }
.onStartNamespace { prefix, uri in println("+NS[\(prefix)] = \(uri)") }
- .onEndNamespace { prefix in println("-NS[\(prefix)]") }
+ .onEndNamespace { prefix in println("-NS[\(prefix)]") }
+ .onCharacterData { content in println("TEXT: \(content)") }
+ .onError { error in println("ERROR \(error)") }
}
override func tearDown() {
@@ -31,18 +33,28 @@ class SwiftyExpatTests: XCTestCase {
func testSimpleParsing() {
XCTAssert(true, "Pass")
-
+
+ var result : ExpatResult
let testXML = "world"
- p.write(testXML)
- p.close() // EOF
+
+ result = p.feed(testXML)
+ XCTAssert(result)
+
+ result = p.close() // EOF
+ XCTAssert(result)
}
-
- /*
- func testPerformanceExample() {
- // This is an example of a performance test case.
- self.measureBlock() {
- // Put the code you want to measure the time of here.
- }
+
+ func testErrorHandling() {
+ XCTAssert(true, "Pass")
+
+ var result : ExpatResult
+ let testXML = "x>world"
+
+ result = p.feed(testXML)
+ println("Feed result: \(result)")
+ XCTAssert(!result)
+
+ result = p.close() // EOF
+ XCTAssert(!result)
}
- */
}