Swift and XML Episode 1: Defining XML using Swift


XML is a very simple language at heart. It contains elements, i.e. those types that are primary to a tag, e.g. <paragraph> or <p>, and which are also used to close tags, e.g. </paragraph> or </p>, it then has attributes, which are secondary but which are also contained within the boundaries of the the tag, e.g. class="regular", which is contained like so <p class="regular">. Finally we have the content, which can be one of two things: text or another element nested within the first.

Since everything begins in XML with the top-level element, that's what will start with too:
struct Element {
    let name:String
    let attributes:[Atrribute]
    let content:[Content]
    var self_closing:Bool {
        return content.isEmpty
    }
}
We have now the most important thing, our ability to create instances of an Element, we also have the ability to be aware if the element we are dealing with has no content and will hence need self-closing, but that's just making things easier for us later. The thing we need to do now is to define what an Attribute is and what Content looks like in Swift. An Attribute is the easiest one:
struct Attribute {
    let name:String
    let value:String
}
For the Content I'm going to employ an enum with associated values:
enum Content {
    case element(Element), text(String)
}
Now we have everything we need to impose a generic XML structure on our Swift code. For example:
let a = Element(name:"a", attributes: [Attribute(name:"href", value:"https://blogspot.sketchytech.co.uk")], content:[.text("visit sketchyTech today!")])
let p = Element(name:"p", attributes: [], content:[.text("It's time to "), .element(a), .text(" You won't regret it!"])
And I guess that since we've come this far we might as well write a method for transforming our XML-structured Swift into, well you know, XML. So to do that:
extension Element {
    func outputXML() -> String {
        var str = "<\(name)"
        for a in attributes {
            str += "\(a.name)="\(a.value)\""
        }
        if self_closing {
            str += " />"
        }
        else {
            str += ">"
            for c in content {
                switch c {
                case .element(let e):
                    str += e.outputXML()
                case .text(let t):
                    str += text
                }
            }
            str += "</\(name)>"
        }
        return str
    }
}
We can try this on the Element instances we already created by simply writing:
let xml = p.outputXML()
print(xml)
and we can also test the self-closing:
let i = Element(name:"img", attributes: [Attribute(name:"src", value:"image.png")], content:[])
let xml = i.outputXML()
print(xml)
You should see that all is working well. Next time, I'll be leveraging the Codable protocol in order to transform our XML into JSON and transform that JSON back into XML.

Comments