A Beginner’s Guide to XML Parsing in Swift

Tons of document formats using XML syntax had been developed, like RSS, Atom, SOAP and XHTML, so it’s good to know, how to work with them. If you are not familiar with XML, it’s basically a precisely formatted text or string, which can be parsed into an array of objects containing the precious information. A good tutorial about XML can be found here.

Here I will show you the usage of the NSXMLParser (part of the iOS SDK) and SWXMLHash from GitHub. …hence the awesome wallpaper:

xml parsing in swift NSXMLParser vs. SWXMLHash

The sample XML, which will help us now is the following:

Let’s start with the NSXMLParser.

First of all, we need to create our custom objects:

As you can see, we created a separate class for every member, which can get multiple values within its parent (so we can make arrays from them).

To use NSXMLParser, our class needs to conform to the protocol NSXMLParserDelegate (eg. class ViewController: UIViewController, NSXMLParserDelegate {})

Now we need to make our XML file digestible for Xcode, create a parser and set it’s delegate (let the name of our sample data be xmlString):

The delegate contains many functions, but only 4 of them are interesting to us:

  1. func parser(didStartElement): is called every time the parser finds a <key>: In our example, it will be called when the parser reaches: <items>, <item>, <author>, <tag>…

  2. func parser(didEndElement): is called every time the parser finds a </key>: In our example it will be called when the parser reaches: </items>, </item>, </author>…

  3. func parser(foundCharacters): is called every time the parser enters a <key> and it will stop on line breaks and “special characters” (eg. í, ö): The second part looks like a problem, but with the most common usage of the parser this problem is totally eliminated (more on that later).

  4. func parserDidEndDocument: is called when the parser finished the document: The parser runs through the whole document once. This means that the parser will start with the <items> key, next <item> and <author> (calls didStartElement on all of them), following foundCharacters called  on “Robi”, didEndElement </author>… The last function called will be didEndElement on </items>.

Now, that we know how our parser works, we need to parse the elements.
For this we will need a new Item array, an empty string for the found characters and a global Item variable (we need it because the delegate’s methods are called many-many times and we need to save the parsed values until we finish):

and use the delegate’s methods:

As you can see, if we have an in-line value (<tag name = “Olympics” count = “3”/>) we cannot use the didEndElement, since there is no </key> value. Luckily, the attributeDict [String: String] dictionary is here to help us. You just need to tell which value you want to get and set.

Another thing to mention is the foundCharacters variable. As I mentioned above, the parser(foundCharacters) function can be interrupted, but the parser parses the whole document synchronously, so it continues from where it stopped.

You should notice, that we empty our foundCharacters value when the parser reached the end of an element. Therefore the fragments will give us the string we wanted: problem solved :).

When the parser finishes the document and calls the parserDidEndDocument, we log our newly created item list:

SWXMLHash

To use this framework, I recommend using CocoaPods and then importing SWXMLHash in your class using it.

The custom objects are the same as before:

Using SWXMLHash, you will need to make an XMLIndexer from your xmlString by parsing it (you can use configurations, they can be found on GitHub:

Create a new Item array that you want to fill:

Now, that you have an indexer, you can get the values by simply stating the key values the parser should search for:

As you can see, if you want to get values from deeper in the array, just write the name of the next key. After that you can get the value of the element by calling its text attribute (it will return optional value), eg. the text of <author> key:

Note that you have to define the name of the key you want to get.
The indexer behaves like an array, so you can iterate through it:

Note that you don’t need to use foundCharacters, since you got the whole text between <key> and </key>.
Don’t forget, that in this case the variable elem will be an Item, not the values <Item> key contains.
If you have in-line value, you have to call attributes[“key”] instead of the element. The attributes is a [String: String] dictionary, so it will return a String.

Let’s see the whole function with a logger:

and our output is:

Conclusion

As you can see, you will need to know the structure of your XML in both cases. Then you can get the data from it pretty easily.
The downside of the NSXMLParser is that you will have to work with 3-4 delegate functions, but they will be relatively short. Plus you can use native protocol.
With SWXMLHash you can parse your whole XML in one function and therefore it will be pretty long with a more complex structure.

If you liked this post, you might like another one I wrote about converting projects to Swift 3.0.
Also: if you’d like to be updated of our future posts, follow us on Linkedin or like us on Fb.

Róbert Klacso

Róbert Klacso

My favorite language and coding style are the same, swift.

Latest posts by Róbert Klacso (see all)
  • Gabe Spound

    How do I get that XMLString with SWXMLHASH?

    • Klacso Róbert

      Hello!

      If you mean the xmlString from here: parse(self.xmlString), then this is the sample XML written under the picture.
      If you mean something else, please give me more informations.

      Cheers,
      Robert

      • Gabe Spound

        I figured that much. I have my own xmlFile I am trying to apply this lesson to. In the third code example he sets xmlData to xmlString.dataUsingEncoding(NSUTF8StringEncoding)! However, I don’t see him initialize a variable called xmlString prior to this. Where does it come from?

        • Klacso Róbert

          You are right, the initialization of that is missing. I created a simple string variable inside the viewcontroller class, after the foundCharacters section. So the first part looks like this:

          var items = [Item]();
          var item = Item();
          var foundCharacters = “”;
          let xmlString = “RobiMy first article about OlypmicsRobiI can’t wait Spa-Francorchamps!!”

          • Gabe Spound

            Ok, so the XmlString is just my xml file copy and pasted without spaces and indentations?

          • Klacso Róbert

            Basically yes, but I don’t think that spaces and indentations will ruin the parsing, just change the output format.

          • Gabe Spound

            So did you physically copy and paste the xml file into xcode before hand, or use .Parse() on an xml file?

          • Klacso Róbert

            In the example the xml string is just a string, without a file, so we can say that it’s copy-pasted. I am pretty sure, that you can’t call .parse() on a file directly, so you will need to read the contents of your file into a string variable, and use it as the xmlString is used in the post.

          • Gabe Spound

            Ok I get it. Is there a way to read from an xml file and make it a string?

          • Klacso Róbert

            Sure, you have to describe the filename, convert it to a filepath (actually a string), after that you need NSURL from that path and read the data as String. In Xcode 2.3:
            let file = “XmlFile.xml”
            let filePath = NSBundle.mainBundle().pathForResource(file, ofType: nil)!
            let fileUrl = NSURL.fileURLWithPath(filePath)
            do {
            let xmlString = try String(contentsOfFile: fileUrl.path!, encoding: NSUTF8StringEncoding)

            } catch {
            //Error handling
            }
            where XmlFile.xml is the file, that contains the xml.

  • crazy squirrel

    I’m trying to use one of the XML parsers, but for a CLI program, so I think I don’ t need to use UIViewController – but then what do I delegate to?