Skip to content

Commit

Permalink
Merge pull request #4 from PerfectlySoft/sax
Browse files Browse the repository at this point in the history
Sax
  • Loading branch information
kjessup committed Mar 15, 2018
2 parents a25038b + f1232c7 commit 77920b7
Show file tree
Hide file tree
Showing 3 changed files with 293 additions and 3 deletions.
229 changes: 229 additions & 0 deletions Sources/PerfectXML/SAX.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
//
// SAX.swift
// PerfectXML
//
// Created by Kyle Jessup on 2018-03-13.
//

import libxml2

public struct SAXError: Error {
public let description: String
public init(_ d: String) {
description = d
}
}

public protocol SAXDelegate {
func startDocument()
func endDocument()
func processingInstruction(target: String, data: String)
func entityDecl(name: String, type: Int, pubicId: String, systemId: String, content: String)
func unparsedEntityDecl(name: String, pubicId: String, systemId: String, notationName: String)
func notationDecl(name: String, pubicId: String, systemId: String)
func attributeDecl(elem: String, fullName: String, type: Int, def: Int, defaultValue: String?, tree: xmlEnumerationPtr?)
func elementDecl(name: String, type: Int, content: xmlElementContentPtr?)
func reference(name: String)
func comment(_ c: String)
func startElementNs(localName: String,
prefix: String?,
uri: String?,
namespaces: [SAXDelegateNamespace],
attributes: [SAXDelegateAttribute])
func endElementNs(localName: String,
prefix: String?,
uri: String?)
func characters(_ c: String)
func ignorableWhitespace(_ c: String)
func cdataBlock(_ c: String)
}

public struct SAXDelegateNamespace {
public let prefix: String?
public let uri: String
}

public struct SAXDelegateAttribute {
public let localName: String
public let prefix: String?
public let nsUri: String?
public let value: String
}

/// Default implimentations do nothing
public extension SAXDelegate {
func startDocument() {}
func endDocument() {}
func processingInstruction(target: String, data: String) {}
func entityDecl(name: String, type: Int, pubicId: String, systemId: String, content: String) {}
func unparsedEntityDecl(name: String, pubicId: String, systemId: String, notationName: String) {}
func notationDecl(name: String, pubicId: String, systemId: String) {}
func attributeDecl(elem: String, fullName: String, type: Int, def: Int, defaultValue: String?, tree: xmlEnumerationPtr?) {}
func elementDecl(name: String, type: Int, content: xmlElementContentPtr?) {}
func reference(name: String) {}
func comment(_ c: String) {}
func startElementNs(localName: String,
prefix: String?,
uri: String?,
namespaces: [SAXDelegateNamespace],
attributes: [SAXDelegateAttribute]) {}
func endElementNs(localName: String,
prefix: String?,
uri: String?) {}
func characters(_ c: String) {}
func ignorableWhitespace(_ c: String) {}
func cdataBlock(_ c: String) {}
}

public class SAXParser {
var handler = xmlSAXHandler()
var delegate: SAXDelegate
var parserCtxt: xmlParserCtxtPtr?
public init(delegate d: SAXDelegate) {
delegate = d

}
deinit {
if let c = parserCtxt {
xmlFreeParserCtxt(c)
}
}
private func getCtxt() throws -> xmlParserCtxtPtr {
if let c = parserCtxt {
return c
}
try setHandlerFuncs()
guard let c = xmlCreatePushParserCtxt(&handler,
asContext(self), nil, 0, nil) else {
throw SAXError("Unable to allocate XML parser.")
}
parserCtxt = c
return c
}
public func pushData(_ d: [UInt8]) throws {
let ctx = try getCtxt()
let code = UnsafePointer(d).withMemoryRebound(to: Int8.self, capacity: d.count) {
return xmlParseChunk(ctx, $0, Int32(d.count), 0)
}
guard 0 == code else {
throw SAXError("Error parsing chunk: \(code).")
}
}
public func finish() throws {
xmlParseChunk(try getCtxt(), nil, 0, 0)
}

private static func ptr2AryNamespaces(_ ptr: UnsafeMutablePointer<UnsafePointer<xmlChar>?>?, count: Int) -> [SAXDelegateNamespace] {
guard let ptr = ptr else {
return []
}
var ret: [SAXDelegateNamespace] = []
for i in stride(from: 0, to: count*2, by: 2) {
let ptr1 = ptr[i]
let ptr2 = ptr[i+1]
ret.append(.init(prefix: String(ptr1), uri: String(ptr2, default: "")))
}
return ret
}

private static func ptr2AryAttributes(_ ptr: UnsafeMutablePointer<UnsafePointer<xmlChar>?>?, count: Int) -> [SAXDelegateAttribute] {
guard let ptr = ptr else {
return []
}
var ret: [SAXDelegateAttribute] = []
for i in stride(from: 0, to: count*5, by: 5) {
let namePtr = ptr[i]
let prefixPtr = ptr[i+1]
let nsUri = ptr[i+2]
let value: String
if let valueStartPtr = ptr[i+3],
let valueEndPtr = ptr[i+4] {
if valueEndPtr.pointee == 0 {
value = String(valueStartPtr, default: "")
} else {
let valueLen = valueEndPtr - valueStartPtr
value = String(valueStartPtr, count: valueLen, default: "")
}
} else {
value = ""
}
ret.append(.init(
localName: String(namePtr, default: ""),
prefix: String(prefixPtr),
nsUri: String(nsUri),
value: value))
}
return ret
}

private func setHandlerFuncs() throws {
handler.startElement = nil
handler.endElement = nil
handler.startElementNs = {
a, b, c, d, e, f, g, h, i in
fromContext(SAXParser.self, a)?.delegate.startElementNs(localName: String(b, default: ""),
prefix: String(c),
uri: String(d),
namespaces: SAXParser.ptr2AryNamespaces(f, count: Int(e)),
attributes: SAXParser.ptr2AryAttributes(i, count: Int(g)))
}
handler.endElementNs = {
fromContext(SAXParser.self, $0)?.delegate.endElementNs(
localName: String($1) ?? "no name",
prefix: String($2),
uri: String($3))
}
handler.serror = nil
handler.internalSubset = { _, _, _, _ in }
handler.externalSubset = { _, _, _, _ in }
handler.isStandalone = { _ in return 1 }
handler.hasInternalSubset = { _ in return 0 }
handler.hasExternalSubset = { _ in return 0 }
handler.resolveEntity = { _, _, _ in return nil }
handler.getEntity = { xmlGetPredefinedEntity($1) }
handler.getParameterEntity = { _, _ in return nil }
handler.entityDecl = {
fromContext(SAXParser.self, $0)?.delegate.entityDecl(name: String($1, default: ""),
type: Int($2),
pubicId: String($3, default: ""),
systemId: String($4, default: ""),
content: String($5, default: ""))
}
handler.attributeDecl = {
fromContext(SAXParser.self, $0)?.delegate.attributeDecl(elem: String($1, default: ""),
fullName: String($2, default: ""),
type: Int($3),
def: Int($4),
defaultValue: String($5),
tree: $6)
}
handler.elementDecl = {
fromContext(SAXParser.self, $0)?.delegate.elementDecl(name: String($1, default: ""), type: Int($2), content: $3)
}
handler.notationDecl = {
fromContext(SAXParser.self, $0)?.delegate.notationDecl(name: String($1, default: ""), pubicId: String($2, default: ""), systemId: String($3, default: ""))
}
handler.unparsedEntityDecl = {
fromContext(SAXParser.self, $0)?.delegate.unparsedEntityDecl(name: String($1, default: ""),
pubicId: String($2, default: ""),
systemId: String($3, default: ""),
notationName: String($4, default: ""))
}
handler.setDocumentLocator = { _, _ in }
handler.startDocument = { fromContext(SAXParser.self, $0)?.delegate.startDocument() }
handler.endDocument = { fromContext(SAXParser.self, $0)?.delegate.endDocument() }
handler.reference = { fromContext(SAXParser.self, $0)?.delegate.reference(name: String($1, default: "")) }
handler.characters = { fromContext(SAXParser.self, $0)?.delegate.characters(String($1, count: Int($2), default: "")) }
handler.cdataBlock = { fromContext(SAXParser.self, $0)?.delegate.cdataBlock(String($1, count: Int($2), default: "")) }
handler.ignorableWhitespace = { fromContext(SAXParser.self, $0)?.delegate.ignorableWhitespace(String($1, default: "")) ; _ = $2 };
handler.processingInstruction = { fromContext(SAXParser.self, $0)?.delegate.processingInstruction(target: String($1, default: ""), data: String($2, default: "")) }
handler.comment = { fromContext(SAXParser.self, $0)?.delegate.comment(String($1, default: "")) }
handler.warning = nil//xmlParserWarning;
handler.error = nil//xmlParserError;
handler.fatalError = nil//xmlParserError;

handler.initialized = 0xDEEDBEAF
}
}


35 changes: 32 additions & 3 deletions Sources/PerfectXML/XMLStream.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,37 @@ extension String {
guard let n = p else {
return nil
}
guard let s = n.withMemoryRebound(to: Int8.self, capacity: 0, {
return String(validatingUTF8: $0)
}) else {
guard let s = n.withMemoryRebound(to: Int8.self, capacity: 0, { String(validatingUTF8: $0) }) else {
return nil
}
self = s
}
init(_ p: UnsafePointer<xmlChar>?, default: String) {
guard let n = p else {
self = `default`
return
}
guard let s = n.withMemoryRebound(to: Int8.self, capacity: 0, { String(validatingUTF8: $0) }) else {
self = `default`
return
}
self = s
}
init(_ p: UnsafePointer<xmlChar>?, count: Int, default: String) {
guard let n = p else {
self = `default`
return
}
let a = (0..<count).map { Int8(n[$0]) } + [0]
guard let s = String(validatingUTF8: a) else {
self = `default`
return
}
self = s
}
}


func asContext(_ a: AnyObject) -> UnsafeMutableRawPointer {
return Unmanaged.passUnretained(a).toOpaque()
}
Expand All @@ -42,6 +64,13 @@ func fromContext<A: AnyObject>(_ type: A.Type, _ context: UnsafeMutableRawPointe
return Unmanaged<A>.fromOpaque(context).takeUnretainedValue()
}

func fromContext<A: AnyObject>(_ type: A.Type, _ context: UnsafeMutableRawPointer?) -> A? {
guard let context = context else {
return nil
}
return Unmanaged<A>.fromOpaque(context).takeUnretainedValue()
}

//xmlTextReaderGetParserColumnNumber

public class XMLStream {
Expand Down
32 changes: 32 additions & 0 deletions Tests/PerfectXMLTests/PerfectXMLTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,38 @@ class PerfectXMLTests: XCTestCase {
}
}

func testSAX() {
do {
class TestDelegate: SAXDelegate {
var opens = ["A", "B", "C", "D", "E"]
var closes = ["B", "C", "E", "D", "A"]

func startElementNs(localName: String, prefix: String?, uri: String?, namespaces: [SAXDelegateNamespace], attributes: [SAXDelegateAttribute]) {
XCTAssertEqual(opens.removeFirst(), localName)
if localName == "B" {
XCTAssertEqual("a", attributes.first?.localName)
}
}
func endElementNs(localName: String, prefix: String?, uri: String?) {
XCTAssertEqual(closes.removeFirst(), localName)
}
}
let d = TestDelegate()
let sax = SAXParser(delegate: d)
let bytes = Array("<A><foo:B xmlns:foo=\"123\" foo:a=\"value\">CONTENT</foo:B><C/><D><E/></D></A>".utf8)
for n in stride(from: 0, to: bytes.count, by: 4) {
let upper = min(n+4, bytes.count)
let range = bytes[n..<upper]
try sax.pushData(Array(range))
}
try sax.finish()
XCTAssert(d.opens.isEmpty)
XCTAssert(d.closes.isEmpty)
} catch {
XCTFail("\(error)")
}
}

static var allTests : [(String, (PerfectXMLTests) -> () throws -> Void)] {
return [
("testDocParse1", testDocParse1),
Expand Down

0 comments on commit 77920b7

Please sign in to comment.