SWXMLHash is a relatively simple way to parse XML in Swift. If you're familiar with NSXMLParser
, this library is a simple wrapper around it. Conceptually, it provides a translation from XML to a dictionary of arrays (aka hash).
The API takes a lot of inspiration from SwiftyJSON.
- iOS 8.0+ / Mac OS X 10.9+ / tvOS 9.0+ / watchOS 2.0+
- Xcode 7.1+
(note that Xcode 8 beta and Swift 3 support are being tracked in PR 78)
SWXMLHash can be installed using CocoaPods, Carthage, or manually.
To install CocoaPods, run:
$ gem install cocoapods
Then create a Podfile
with the following contents:
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '8.0'
pod 'SWXMLHash', '~> 2.3.0'
Finally, run the following command to install it:
$ pod install
To install Carthage, run (using Homebrew):
$ brew update
$ brew install carthage
Then add the following line to your Cartfile
:
github "drmohundro/SWXMLHash" ~> 2.3
To install manually, you'll need to clone the SWXMLHash repository. You can do this in a separate directory or you can make use of git submodules - in this case, git submodules are recommended so that your repository has details about which commit of SWXMLHash you're using. Once this is done, you can just drop the SWXMLHash.swift
file into your project.
NOTE: if you're targeting iOS 7, you'll have to install manually because embedded frameworks require a minimum deployment target of iOS 8 or OSX Mavericks.
If you're just getting started with SWXMLHash, I'd recommend cloning the repository down and opening the workspace. I've included a Swift playground in the workspace which makes it very easy to experiment with the API and the calls.
SWXMLHash allows for limited configuration in terms of its approach to parsing. To set any of the configuration options, you use the configure
method, like so:
let xml = SWXMLHash.config {
config in
// set any config options here
}.parse(xmlToParse)
The available options at this time are:
shouldProcessLazily
- This determines whether not to use lazy loading of the XML. It can significantly increase the performance of parsing if your XML is very large.
- Defaults to
false
shouldProcessNamespaces
- This setting is forwarded on to the internal
NSXMLParser
instance. It will return any XML elements without their namespace parts (i.e. "<h:table>" will be returned as "<table>") - Defaults to
false
- This setting is forwarded on to the internal
All examples below can be found in the included specs.
let xml = SWXMLHash.parse(xmlToParse)
Alternatively, if you're parsing a large XML file and need the best performance, you may wish to configure the parsing to be processed lazily. Lazy processing avoids loading the entire XML document into memory, so it could be preferable for performance reasons. See the error handling for one caveat regarding lazy loading.
let xml = SWXMLHash.config {
config in
config.shouldProcessLazily = true
}.parse(xmlToParse)
The above approach uses the new config method, but there is also a lazy
method directly off of SWXMLHash
.
let xml = SWXMLHash.lazy(xmlToParse)
Given:
<root>
<header>
<title>Foo</title>
</header>
...
</root>
Will return "Foo".
xml["root"]["header"]["title"].element?.text
Given:
<root>
...
<catalog>
<book><author>Bob</author></book>
<book><author>John</author></book>
<book><author>Mark</author></book>
</catalog>
...
</root>
The below will return "John".
xml["root"]["catalog"]["book"][1]["author"].element?.text
Given:
<root>
...
<catalog>
<book id="1"><author>Bob</author></book>
<book id="123"><author>John</author></book>
<book id="456"><author>Mark</author></book>
</catalog>
...
</root>
The below will return "123".
xml["root"]["catalog"]["book"][1].element?.attributes["id"]
Alternatively, you can look up an element with specific attributes. The below will return "John".
xml["root"]["catalog"]["book"].withAttr("id", "123")["author"].element?.text
Given:
<root>
...
<catalog>
<book><genre>Fiction</genre></book>
<book><genre>Non-fiction</genre></book>
<book><genre>Technical</genre></book>
</catalog>
...
</root>
The below will return "Fiction, Non-fiction, Technical" (note the all
method).
", ".join(xml["root"]["catalog"]["book"].all.map { elem in
elem["genre"].element!.text!
})
Alternatively, you can just iterate over the elements using for-in
directly against an element.
for elem in xml["root"]["catalog"]["book"] {
NSLog(elem["genre"].element!.text!)
}
Given:
<root>
<catalog>
<book>
<genre>Fiction</genre>
<title>Book</title>
<date>1/1/2015</date>
</book>
</catalog>
</root>
The below will NSLog
"root", "catalog", "book", "genre", "title", and "date" (note the children
method).
func enumerate(indexer: XMLIndexer) {
for child in indexer.children {
NSLog(child.element!.name)
enumerate(child)
}
}
enumerate(xml)
Using Swift 2.0's new error handling feature:
do {
try xml!.byKey("root").byKey("what").byKey("header").byKey("foo")
} catch let error as XMLIndexer.Error {
// error is an XMLIndexer.Error instance that you can deal with
}
Or using the existing indexing functionality (NOTE that the .Error
case has been renamed to .XMLError
so as to not conflict with the XMLIndexer.Error
error type):
switch xml["root"]["what"]["header"]["foo"] {
case .Element(let elem):
// everything is good, code away!
case .XMLError(let error):
// error is an XMLIndexer.Error instance that you can deal with
}
Note that error handling as shown above will not work with lazy loaded XML. The lazy parsing doesn't actually occur until the element
or all
method are called - as a result, there isn't any way to know prior to asking for an element if it exists or not.
Given:
<root>
<books>
<book>
<title>Book A</title>
<price>12.5</price>
<year>2015</year>
</book>
<book>
<title>Book B</title>
<price>10</price>
<year>1988</year>
</book>
<book>
<title>Book C</title>
<price>8.33</price>
<year>1990</year>
<amount>10</amount>
</book>
<books>
</root>
with Book
struct implementing XMLIndexerDeserializable
:
struct Book: XMLIndexerDeserializable {
let title: String
let price: Double
let year: Int
let amount: Int?
static func deserialize(node: XMLIndexer) throws -> Book {
return try Book(
title: node["title"].value(),
price: node["price"].value(),
year: node["year"].value(),
amount: node["amount"].value()
)
}
}
The below will return array of Book
structs:
let books: [Book] = try xml["root"]["books"]["book"].value()
Built-in, leaf-nodes converters support Int
, Double
, Float
, Bool
, and String
values (both non- and -optional variants). Custom converters can be added by implementing XMLElementDeserializable
.
You can convert any XML to your custom type by implementing XMLIndexerDeserializable
.
Types conversion supports error handling, optionals and arrays. For more examples, look into SWXMLHashTests.swift
or play with types conversion directly in the Swift playground.
No - SWXMLHash only handles parsing of XML. If you have a URL that has XML content on it, I'd recommend using a library like AlamoFire to download the content into a string and then parsing it.
No, not at the moment - SWXMLHash only supports parsing XML (via indexing, deserialization, etc.).
.value()
is used for deserialization - you have to have something that implements XMLIndexerDeserializable
and that can handle deserialization to the left-hand side of expression.
For example, given the following:
let dateValue: NSDate = try! xml["root"]["date"].value()
You'll get an error because there isn't any built-in deserializer for NSDate
. See the above documentation on adding your own deserialization support.
Chances are very good that your XML content has what is called a "byte order mark" or BOM. SWXMLHash uses NSXMLParser
for its parsing logic and there are issues with it and handling BOM characters. See issue #65 for more details. Others who have run into this problem have just rstripped the BOM out of their content prior to parsing.
Feel free to shoot me an email, post a question on StackOverflow, or open an issue if you think you've found a bug. I'm happy to try to help!
See CHANGELOG for a list of all changes and their corresponding versions.
See CONTRIBUTING for guidelines to contribute back to SWXMLHash.
SWXMLHash is released under the MIT license. See LICENSE for details.