How to Parse XML with XMLParser in Swift

In this article, we will explain how to read XML data in Swift using XMLParser.

Sample XML String and Struct Definition

Here we will introduce how to use XMLParser to read XML strings into Swift structs.

We will use the following XML string as an example:

<?xml version="1.0" encoding="UTF-8"?>
<Orders>
  <Order OrderNo="PO000001">
    <VendorName>AAA Company</VendorName>
    <OrderDetails>
      <OrderDetail>
        <ItemName>Item A</ItemName>
        <OrderQty>10</OrderQty>
      </OrderDetail>
      <OrderDetail>
        <ItemName>Item B</ItemName>
        <OrderQty>5</OrderQty>
      </OrderDetail>
    </OrderDetails>
  </Order>
  <Order OrderNo="PO000002">
    <VendorName>BBB Company</VendorName>
    <OrderDetails>
      <OrderDetail>
        <ItemName>Item C</ItemName>
        <OrderQty>3</OrderQty>
      </OrderDetail>
    </OrderDetails>
  </Order>
</Orders>

We define the following structs to hold the XML data. The parsed data will be stored into an array of Order objects.

struct Order {
    var orderNo: String
    var vendorName: String
    var orderDetails: [OrderDetail]
}

struct OrderDetail {
    var itemName: String
    var orderQty: Int?
}

What is XMLParser in Swift?

Here, we use XMLParser in Swift to read XML strings.

XMLParser is an event-driven parser that triggers events while reading XML documents.

It is our job to handle these events and store the data as needed.


By conforming to XMLParserDelegate and running parse() with XMLParser, you can receive events as the XML is read and extract data.

In this example, we will use the following three methods to generate orders data from the XML:

parser(_:didStartElement:namespaceURI:qualifiedName:attributes:)
parser(_:foundCharacters:)
parser(_:didEndElement:namespaceURI:qualifiedName:)

The first method is called when an element starts, the second when characters are found inside the current element, and the third when an element ends.


How to Parse an XML String with XMLParser

Now let's explain how to parse an XML string in Swift using XMLParser.

Although you can try this in a Playground, debugging is easier in an iOS app, so we'll use the ViewController class here.


Here is how you can parse an XML string into an array of Order structs using XMLParser:

import UIKit

struct Order {
    var orderNo: String
    var vendorName: String
    var orderDetails: [OrderDetail]
}

struct OrderDetail {
    var itemName: String
    var orderQty: Int?
}

class ViewController: UIViewController, XMLParserDelegate {
    
    var elementName: String = ""
    var orders: [Order] = []
    var orderNo: String = ""
    var vendorName: String = ""
    var orderDetails:[OrderDetail] = []
    var itemName: String = ""
    var orderQty: Int?
    
    let xmlString = """
    <?xml version="1.0" encoding="UTF-8"?>
    <Orders>
    <Order OrderNo="PO000001">
        <VendorName>AAA Company</VendorName>
        <OrderDetails>
        <OrderDetail>
            <ItemName>Item A</ItemName>
            <OrderQty>10</OrderQty>
        </OrderDetail>
        <OrderDetail>
            <ItemName>Item B</ItemName>
            <OrderQty>5</OrderQty>
        </OrderDetail>
        </OrderDetails>
    </Order>
    <Order OrderNo="PO000002">
        <VendorName>BBB Company</VendorName>
        <OrderDetails>
        <OrderDetail>
            <ItemName>Item C</ItemName>
            <OrderQty>3</OrderQty>
        </OrderDetail>
        </OrderDetails>
    </Order>
    </Orders>
    """
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let parser = XMLParser(data: xmlString.data(using: .utf8)!)
        parser.delegate = self
        parser.parse()
        
        dump(orders)
    }
    
    func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String] = [:]) {
        switch elementName {
        case "Order":
            if let no = attributeDict["OrderNo"] {
                orderNo = no
            } else {
                orderNo = ""
            }
            vendorName = ""
            orderDetails = []
        case "OrderDetail":
            itemName = ""
            orderQty = nil
        default:
            break
        }
        
        self.elementName = elementName
    }
    
    func parser(_ parser: XMLParser, foundCharacters string: String) {
        let value = string.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
        if (!value.isEmpty) {
            switch self.elementName {
            case "VendorName":
                vendorName = value
            case "ItemName":
                itemName = value
            case "OrderQty":
                if let qty = Int(value) {
                    orderQty = qty
                }
            default:
                break
            }
        }
    }
 
    func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
        switch elementName {
        case "Order":
            let o = Order(orderNo: orderNo, vendorName: vendorName, orderDetails: orderDetails)
            orders.append(o)
        case "OrderDetail":
            let od = OrderDetail(itemName: itemName, orderQty: orderQty)
            orderDetails.append(od)
        default:
            break
        }
    }
}

When executed, the following will be displayed in the output window, confirming that the XML data has been successfully loaded into the orders object:

How to Parse XML with XMLParser in Swift 1


Now let's walk through the code step by step.

class ViewController: UIViewController, XMLParserDelegate {
    
    var elementName: String = ""
    var orders: [Order] = []
    var orderNo: String = ""
    var vendorName: String = ""
    var orderDetails:[OrderDetail] = []
    var itemName: String = ""
    var orderQty: Int?

In line 14, the ViewController class conforms to XMLParserDelegate.

In lines 16–22, we define variables needed to store XML data.

elementName holds the current element name. Finally, all parsed data will be stored in orders.


override func viewDidLoad() {
    super.viewDidLoad()
    
    let parser = XMLParser(data: xmlString.data(using: .utf8)!)
    parser.delegate = self
    parser.parse()

    dump(orders)
}

When the iOS app runs, viewDidLoad() is executed.

In line 55, an XMLParser is created from the XML string data.

Alternatively, you can create an XMLParser from a document by specifying a URL with XMLParser(contentsOf: URL).

In line 56, parser.delegate = self allows this class to receive XMLParser events.

Line 57 executes the parsing process.

Line 59 dumps the contents of the parsed orders object to the output window.


func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String] = [:]) {
    switch elementName {
    case "Order":
        if let no = attributeDict["OrderNo"] {
            orderNo = no
        } else {
            orderNo = ""
        }
        vendorName = ""
        orderDetails = []
    case "OrderDetail":
        itemName = ""
        orderQty = nil
    default:
        break
    }
    
    self.elementName = elementName
}

parser(_:didStartElement:namespaceURI:qualifiedName:attributes:) is called when an element starts.

When an Order or OrderDetail element begins, the relevant variables are reset.

To retrieve attribute values, you can use attributeDict as shown in line 65.

Line 79 stores the current element name in self.elementName.


func parser(_ parser: XMLParser, foundCharacters string: String) {
    let value = string.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
    if (!value.isEmpty) {
        switch self.elementName {
        case "VendorName":
            vendorName = value
        case "ItemName":
            itemName = value
        case "OrderQty":
            if let qty = Int(value) {
                orderQty = qty
            }
        default:
            break
        }
    }
}

parser(_:foundCharacters:) is called when characters are found inside the current element.

In line 83, whitespace and newlines are trimmed from the found string.

Line 84 ensures only non-empty values are processed in the switch statement.

When the current element name is VendorName, ItemName, or OrderQty, the corresponding values are assigned to variables.

OrderQty is only stored if the value is an integer.


func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
    switch elementName {
    case "Order":
        let o = Order(orderNo: orderNo, vendorName: vendorName, orderDetails: orderDetails)
        orders.append(o)
    case "OrderDetail":
        let od = OrderDetail(itemName: itemName, orderQty: orderQty)
        orderDetails.append(od)
    default:
        break
    }
}

parser(_:didEndElement:namespaceURI:qualifiedName:) is called when an element ends.

When the element is Order or OrderDetail, new Order and OrderDetail objects are created from stored values and appended to the orders and orderDetails arrays.

This way, when parsing finishes, the XML data is fully loaded into the orders array.


That wraps up how to read XML data in Swift using XMLParser.