Swift で XMLParser を使って XML を読み込む方法

ここでは Swift で XMLParser を使って XML データを読み込む方法についてご説明します。

サンプル XML 文字列と読み込む struct の定義

今回は XMLParser を使って、XML の文字列から Swift の struct のオブジェクに読み込む方法をご紹介します。

次のような XML 文字列を読み込みます。

<?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>

上の XML の情報を保持するための struct を次のように定義し、読み込んだデータを Order の配列に保存していきます。

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

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

Swift の XMLParser とは?

今回は Swift で XMLParser を使って、XML 文字列を読み込みます。

XMLParser はイベントドリブンのパーサーで XML ドキュメントを読み込みながらイベントを発生します。

発生したイベント内でデータを保存していくのはこちらの仕事です。


XMLParserDelegate をコンフォームして XMLParser で parse() すると、XML を読み込みながら、データを取得するためのイベントを受け取ることができます。

今回は以下の三つのメソッド使って、XML データから orders のデータを生成していきます。

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

一つ目はエレメントが始まった時に呼ばれるイベント、二つ目は現在のエレメント内で見つかった文字をお知らせするイベント、三つ目はエレメントが終わった時に呼ばれるイベントです。


XMLParser で XML 文字列を読み込む

それでは、Swift で XMLParser を使って、XML 文字列を読み込む方法をご説明します。

Playground でもできますが、デバッグしにくいので、今回は iOS アプリの ViewController クラスを使います。


XML 文字列を XMLParser を使って、Order struct の配列に読み込むには次のようにできます。

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
        }
    }
}

実行すると output ウィンドウに次のように表示され、XML のデータが orders オブジェクトに読み込めているのがわかります。

Swift で XMLParser を使って XML を読み込む方法 1


コードを順番にご説明していきます。

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?

14 行目では、ViewController クラスで XMLParserDelegate をコンフォームしています。

16 ~ 22 行目では、XML データを保存していくのに必要な変数を定義しています。

elementName には現在のエレメント名が入ります。最終的には orders に読み込んだ全てのデータが入ります。


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

    dump(orders)
}

iOS アプリを実行した時に、viewDidLoad() が実行されます。

55 行目で XML 文字列から Data を生成して指定して XMLParser を生成しています。

今回は data を指定して XMLParser を生成しましたが、XMLParser(contentsOf url: URL) で ドキュメントの URL を指定して XML ファイルから XMLParser を生成することも可能です。

56 行目で parser.delegate = self として、このクラスで XMLParser が起こすイベントを受けとれるようにしています。

57 行目の parse() でパースを実行しています。

59 行目でパースした結果の orders オブジェクトの中身を output ウィンドウに dump しています。


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:) はエレメントが始まった時に呼ばれるイベントです。

Order エレメントと OrderDetail エレメントが開始した時に、それぞれ情報を保持する変数をリセットしています。

アトリビュートの値を取得したい時は、65 行目のようにして、attributeDict にアトリビュート名を指定して取得できます。

79 行目で 現在の elementName を 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:) は現在のエレメント内で見つかった文字をお知らせするイベントです。

83 行目では、みつかった文字列から空白と改行をトリムしています。

84 行目で文字列が空でない時のみ、switch 文を実行するようにしています。

現在のエレメント名が VendorName、ItemName、OrderQty だった時に、値をそれぞれ vendorName、itemName、orderQty に代入しています。

OrderQty のみ、値が整数の時のみ orderQty に代入するようにしています。


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:) はエレメントが終わった時に呼ばれるイベントです。

終わったエレメント名が Order か OrderDetail の時は、変数に保持されている値を元に Order と OrderDetail オブジェクトを生成し、それぞれ orders 配列、orderDetails 配列に append しています。

これで、パースが終わった時に XML のデータが orders に読み込まれているはずです。


以上、Swift で XMLParser を使って XML データを読み込む方法についてご説明しました。

© 2024 iOS 開発入門