XML Parser - coolsamson7/inject GitHub Wiki
Overview
Usually there are two types of xml parsers:
- callback based ( SAX parser )
- dom based
While handling with callbacks is a mess ( unless you really have memory problems), the dom approach is also not the best solution, since dealing with anonymous elements is still pretty clumsy. The approach here offers a simple mapping from xml to user defined classes, which can be traversed in a second step.
XMParser
The class
XMLParser
offers the function
register(classes : ClassDefinition...) -> Self
which takes a number of ClassDefinition instances that describe the mapping of an individual class to xml. ClassDefinition is constructed by calling the class func
func mapping(clazz: AnyClass, element : String) -> ClassDefinition
where element is the xml element name.
This class in turn offers the func
func property(property : String, xml : String? = nil, conversion : Conversion? = nil) throws -> ClassDefinition
that is used to define property mappings. The parameters are
property: the property name of the corresponding classxml: the xml name. If not specified, it will be the property nameconversion: An optional conversion between a string and the expected property type
If not provided a number of internal conversions are applied. This covers
- all numeric types
- boolean type
Declared properties are recognized both as elements or attributes!
Once the parser is configured the following func will trigger the parsing and return the root object
public func parse(data : NSData) throws -> AnyObject?
Lets look at a simple example:
class Define : NSObject {
// MARK: instance data
var namespace : String?
var key : String?
var type : String?
var value : String?
}
...
let parser = XMLParser()
parser.register(
mapping(Define.self, element: "configuration:define")
.property("namespace")
.property("key")
.property("type")
.property("value")
)
let data = ...
let define = parser.parse(data) as! Define.self
A number of protocols are defined that - attached to classes - will control the parse process. These are
public protocol Ancestor {
func addChild(child : AnyObject) -> Void
}
If this protocol is implemented, any child nodes are attached to the direct parent
public protocol OriginAware {
var origin : Origin? { get set }
}
If this protocol is implemented, the line and column information - in form of a Origin struct - is passed to the class.
public protocol AttributeContainer {
subscript(name: String) -> AnyObject { get set }
}
If this protocol is implemented, all attributes which are not mapped to properties will be passed to the setter!
Example:
class Configuration : NSObject, Ancestor, NamespaceAware, OriginAware {
// MARK: instance data
var _namespace : String?
var _origin : Origin?
var configurationNamespace : String?
var definitions = [Define]()
// Ancestor
func addChild(child : AnyObject) -> Void {
if let define = child as? Define {
definitions.append(define)
}
}
// OriginAware
var origin : Origin? {
get {
return _origin
}
set {
_origin = newValue
}
}
// NamespaceAware
var namespace : String? {
get {
return _namespace
}
set {
_namespace = newValue
}
}
}