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 class
  • xml: the xml name. If not specified, it will be the property name
  • conversion: 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
            }
        }
    }