Thursday, January 12, 2023
HomeWeb DevelopmentXML parsing in Swift: Tutorial with examples

XML parsing in Swift: Tutorial with examples


Though the XML file format sounds outdated in comparison with the JSON file format, XML could be very a lot alive. JSON could be extra in style in internet growth, however XML has many options that JSON lacks, equivalent to namespace assist that’s helpful to keep away from identify collision for components and attributes. XML additionally has a schema that may power an XML doc to stick to a sure customary.

You don’t have to search for a third-party library to control an XML file with Swift. The Swift customary library has XMLParser for all of your XML wants.

Leap forward:

Organising XMLParser and XMLParserDelegate

Create a Playground undertaking in XCode and identify it XMLParsingPlayground. Check with this text to study extra about this step.

To parse the XML knowledge, it’s essential to convert an XML string to Knowledge. Add the next code:

let xmlContent =
"""
<?xml model="1.0" encoding="UTF-8"?>
<root>
    <article>
        <title>Getting Began with Swift</title>
    </article>
    <article>
        <title> Parse XML with Rust</title>
    </article>
</root>
""";

let xmlData = Knowledge(xmlContent.utf8)

Within the code above, you initialized a Knowledge occasion with an XML string. Now, it’s essential to initialize an XMLParser occasion with this Knowledge occasion:

let xmlParser = XMLParser(knowledge: xmlData)

XMLParser will delegate the parsing logic to its delegation class. You’ll have to implement this class that inherits NSObject and XMLParserDelegate. Add the next code to create the category and its occasion:

class Parser1 : NSObject, XMLParserDelegate {

    func parserDidStartDocument(_ parser: XMLParser) {
        print("Begin of the doc")
        print("Line quantity: (parser.lineNumber)")
    }

    func parserDidEndDocument(_ parser: XMLParser) {
        print("Finish of the doc")
        print("Line quantity: (parser.lineNumber)")
    }

}
let parser1 = Parser1()

Within the delegated class, you arrange a logic to do one thing once you hit the beginning of the doc and the tip of the doc within the parsing course of. For those who encounter these, you’ll print some strings and a line quantity the place the parser is correct now. Each strategies settle for an XMLParser occasion within the arguments. Many strategies within the delegated class settle for an XMLParser occasion of their arguments.

When you get the parser delegation occasion executed, you need to set it because the XMLParser occasion’s delegation class:

xmlParser.delegate = parser1

The final step is to parse it:

xmlParser.parse()

For those who compiled and ran this system, it is best to get the next output:

Begin of the doc
Line quantity: 1
Finish of the doc
Line quantity: 9

The code above implies that when the parser met the occasion of the begin of the doc, it was in line 1. Then it went to the content material of the doc. It traveled by way of the weather however since you didn’t implement strategies to deal with this, nothing occurred. Lastly, when it hit the occasion of the finish of the doc, it knowledgeable us by way of the parserDidEndDocument methodology that you just’ve carried out.

Dealing with components of the XML knowledge

When parsing XML, it’s essential to implement strategies to deal with the nodes or occasions that you just’re fascinated about. For those who’re fascinated about feedback, it’s essential to implement a way when the parser meets feedback within the XML knowledge.

Now, you wish to implement strategies for when a parser meets components like article and title. The tactic that it’s essential to implement is the parser methodology with the next signature:

func parser(
        _ parser: XMLParser,
        didStartElement elementName: String,
        namespaceURI: String?,
        qualifiedName qName: String?,
        attributes attributeDict: [String : String] = [:]
    )

Create a brand new parser delegated class:

class Parser2 : NSObject, XMLParserDelegate {

    func parser(
        _ parser: XMLParser,
        didStartElement elementName: String,
        namespaceURI: String?,
        qualifiedName qName: String?,
        attributes attributeDict: [String : String] = [:]
    ) {
        print(elementName)
    }

}

Within the new delegated class, you didn’t implement parserDidStartDocument and parserDidEndDocument since you’re solely centered on parsing components.

As regular, it’s essential to set the delegated class occasion of a brand new XMLParser occasion and name the parse methodology once more:

let parser2 = Parser2()
let xmlParser2 = XMLParser(knowledge: xmlData)
xmlParser2.delegate = parser2

xmlParser2.parse()

Compile and run this system, and it’ll print the weather’ names:

root
article
title
article
title

As you possibly can see, there are 5 components. If you wish to differentiate title components as a result of there are two of them, it’s essential to add extra logic. One of many examples is to trace what number of article components the parse has met.

Create one other delegated class:

class Parser3 : NSObject, XMLParserDelegate {

    var articleNth = 0

    func parser(
        _ parser: XMLParser,
        didStartElement elementName: String,
        namespaceURI: String?,
        qualifiedName qName: String?,
        attributes attributeDict: [String : String] = [:]
    ) {
        if (elementName=="article") {
            articleNth += 1
        } else if (elementName=="title") {
            print("'(elementName)' within the article component quantity (articleNth)")
        }
    }

}

This time, you tracked what number of article components you’ve met so once you meet the title component, you already know which article component you’re in.

Create a brand new XMLParser occasion and set the delegated class to Parser3:

let parser3 = Parser3()
let xmlParser3 = XMLParser(knowledge: xmlData)
xmlParser3.delegate = parser3

xmlParser3.parse()

For those who compile and run this system, it is best to see this output:

'title' within the article component number one
'title' within the article component quantity 2

Dealing with the attributes of components of the XML knowledge

Components within the XML knowledge can have attributes as properly. You would possibly wish to question the attributes. The latest methodology that you just’ve executed has one other argument that refers back to the attributes. However first, you need to create different XML knowledge as a result of the info proper now doesn’t have the attributes:

let xmlContent_attributes =
"""
<?xml model="1.0" encoding="UTF-8"?>
<root>
    <article id="1" tag="swift">
        <title>Getting Began with Swift</title>
    </article>
    <article id="2" tag="rust">
        <title> Parse XML with Rust</title>
    </article>
</root>
""";
let xmlData_attributes = Knowledge(xmlContent_attributes.utf8)

This time, you added attributes on the article components. The attributes are id and tag.

Now, you need to create the delegated class to deal with the attributes:

class Parser4 : NSObject, XMLParserDelegate {

    func parser(
        _ parser: XMLParser,
        didStartElement elementName: String,
        namespaceURI: String?,
        qualifiedName qName: String?,
        attributes attributeDict: [String : String] = [:]
    ) {
        for (attr_key, attr_val) in attributeDict {
            print("Key: (attr_key), worth: (attr_val)")
        }
    }

}

The tactic is identical as the strategy you beforehand carried out. However this time, you didn’t ignore the attributeDict argument.

We’re not completed but! Now, you need to create a brand new XMLParser occasion that makes use of this delegated class occasion:

let parser4 = Parser4()
let xmlParser4 = XMLParser(knowledge: xmlData_attributes)
xmlParser4.delegate = parser4

xmlParser4.parse()

For those who compiled and ran this system, you’d see the attributes:

Key: id, worth: 1
Key: tag, worth: swift
Key: id, worth: 2
Key: tag, worth: rust

Dealing with the namespace within the XML knowledge

Now, there are two arguments you haven’t handled within the parser methodology. They’re namespaceURI and qName, that are associated to one another.

First, it’s essential to add namespaces into the XML knowledge:

let xmlContent_namespace =
"""
<?xml model="1.0" encoding="UTF-8"?>
<root xmlns:t="http://logrocket.com/tech" xmlns:m="http://logrocket.com/advertising">
    <t:article>
        <t:title>Getting Began with Swift</t:title>
    </t:article>
    <m:article>
        <m:title> Parse XML with Rust</m:title>
    </m:article>
</root>
""";

let xmlData_namespace = Knowledge(xmlContent_namespace.utf8)

There are two namespaces on this XML knowledge: http://logrocket.com/tech and http://logrocket.com/advertising. Components inside the foundation component use namespaces. It’s not article anymore, however t:article or m:article.

The t refers to xmlns:t, which has the worth http://logrocket.com/tech. The m refers to xmlns:m, which has the worth http://logrocket.com/advertising.

Then it’s essential to create the delegated class to deal with the namespace:

class Parser5 : NSObject, XMLParserDelegate {

    func parser(
        _ parser: XMLParser,
        didStartElement elementName: String,
        namespaceURI: String?,
        qualifiedName qName: String?,
        attributes attributeDict: [String : String] = [:]
    ) {
        print("Namespace URI: (namespaceURI!), certified identify: (qName!)")
    }

}

To get the namespace URI, for instance, http://logrocket.com/tech, you should utilize the namespaceURI argument. To get the certified identify, for instance, t:article, you should utilize the qName argument.

The following step is vital. Create a brand new XMLParser occasion to deal with the namespace:

let parser5 = Parser5()
let xmlParser5 = XMLParser(knowledge: xmlData_namespace)
xmlParser5.delegate = parser5
xmlParser5.shouldProcessNamespaces = true

xmlParser5.parse()

Earlier than you compile and run this system, discover there’s a line that tells the parser to course of the namespace. To allow the namespace within the XML knowledge, it’s essential to set the shouldProcessNamespaces property of the parser to true. In any other case, the namespaces shall be ignored.

Compile and run this system. Then you’ll get the namespace URIs and the certified names:

Namespace URI: , certified identify: root
Namespace URI: http://logrocket.com/tech, certified identify: t:article
Namespace URI: http://logrocket.com/tech, certified identify: t:title
Namespace URI: http://logrocket.com/advertising, certified identify: m:article
Namespace URI: http://logrocket.com/advertising, certified identify: m:title

Getting the textual content content material when parsing the XML knowledge

Now, it’s time to get the textual content content material from the XML knowledge. The tactic that it’s essential to implement is the parser methodology with the next signature:

    func parser(
        _ parser: XMLParser,
        foundCharacters string: String
    )

Add the next XML knowledge:

let xmlContent_text =
"""
<?xml model="1.0" encoding="UTF-8"?>
<root>
    <article>
        <title>Getting Began with Swift</title>
        <revealed>true</revealed>
    </article>
    <article>
        <title> Parse XML with Rust</title>
        <revealed>false</revealed>
    </article>
</root>
""";
let xmlData_text = Knowledge(xmlContent_text.utf8)

Then it’s essential to implement the delegated class that has a way to deal with the textual content content material:

class Parser6 : NSObject, XMLParserDelegate {

    func parser(
        _ parser: XMLParser,
        foundCharacters string: String
    ) {
        if (string.trimmingCharacters(in: .whitespacesAndNewlines) != "") {
            print(string)
        }
    }

}

You’re solely within the textual content content material that has some characters. For those who didn’t implement the filter, it will end in plenty of clean textual content content material. The textual content context is outlined because the textual content between component nodes. That features the clean textual content between the article component and the title component as properly:

    <article>
        <title> Parse XML with Rust</title>

It’s not simply white areas, however there’s a new line between article and title. That’s why you trimmed it first after which checked whether or not it was an empty string or not.

Lastly, create an XMLParser occasion to deal with the textual content content material:

let parser6 = Parser6()
let xmlParser6 = XMLParser(knowledge: xmlData_text)
xmlParser6.delegate = parser6

xmlParser6.parse()

For those who compiled and ran this system, it is best to get this textual content content material:

Getting Began with Swift
true
 Parse XML with Rust
false

Dealing with errors when parsing the XML knowledge

Life just isn’t supreme. Typically you get imperfect XML knowledge and the info is corrupted. However don’t fear! You may deal with errors with the parser methodology with the next signature:

    func parser(
        _ parser: XMLParser,
        parseErrorOccurred parseError: Error
    )

However first, it’s essential to write the corrupted XML knowledge:

let xmlContent_corrupted =
"""
<?xml model="1.0" encoding="UTF-8"?>
<root>
    <article>
        <title>Getting Began with Swift</title>
    </article>
    <article>
        <title> Parse XML with Rust
    </article>
</root>
""";
let xmlData_corrupted = Knowledge(xmlContent_corrupted.utf8)

Discover that there’s a lacking closing title tag.

Then, create a delegated class to deal with the error:

class Parser7 : NSObject, XMLParserDelegate {

    func parser(
        _ parser: XMLParser,
        parseErrorOccurred parseError: Error
    ) {
        print(parseError)
    }

}

This methodology gave the parseError argument that has the details about the error. On this case, you printed the error itself.

What does the error appear like? It’s good to create the XMLParser occasion to take this delegated class:

let parser7 = Parser7()
let xmlParser7 = XMLParser(knowledge: xmlData_corrupted)
xmlParser7.delegate = parser7

xmlParser7.parse()

For those who compiled and ran this system, it is best to see the error:

Error Area=NSXMLParserErrorDomain Code=76 "(null)" UserInfo={NSXMLParserErrorColumn=15, NSXMLParserErrorLineNumber=8, NSXMLParserErrorMessage=Opening and ending tag mismatch: title line 7 and article
}

You bought the road quantity and column quantity the place the error occurred. You additionally obtained the error message, which advised you that the opening and ending tags have been mismatched.

Conclusion

On this article, you discovered parse XML knowledge. You began by creating the XMLParser occasion. You then created a delegated XMLParserDelegate class to deal with the logic of the parsing course of. From there, you dealt with components, attributes, and textual content content material. You additionally managed an error within the parsing course of and configured the parser to deal with the namespace.


Extra nice articles from LogRocket:


This text solely scratches the floor of XML parsing in Swift. You may study extra concerning the XML parsing API in the documentation right here. The code for this text is obtainable on this GitHub repository.

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

- Advertisment -
Google search engine

Most Popular

Recent Comments