Parsing Xml in iOS

Parsing XML from URL in Swift

The process is simple:

  1. Create XMLParser object, passing it the data.
  2. Specify the delegate for that parser.
  3. Initiate the parsing.

So, in Swift 3/4, that looks like:

let task = URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data, error == nil else {
print(error ?? "Unknown error")
return
}

let parser = XMLParser(data: data)
parser.delegate = self
if parser.parse() {
print(self.results ?? "No results")
}
}
task.resume()

The question is how do you implement the XMLParserDelegate methods. The three critical methods are didStartElement (where you prepare to receive characters), foundCharacters (where you handle the actual values parsed), and didEndElement (where you save you results).

You asked how to parse a single record (i.e. a single dictionary), but I'll show you a more general pattern for parsing a series of them, which is a far more common situation with XML. You can obviously see how to simplify this if you didn't need an array of values (or just grab the first one).

// a few constants that identify what element names we're looking for inside the XML

// a few constants that identify what element names we're looking for inside the XML

let recordKey = "record"
let dictionaryKeys = Set<String>(["EmpName", "EmpPhone", "EmpEmail", "EmpAddress", "EmpAddress1"])

// a few variables to hold the results as we parse the XML

var results: [[String: String]]? // the whole array of dictionaries
var currentDictionary: [String: String]? // the current dictionary
var currentValue: String? // the current value for one of the keys in the dictionary

And

extension ViewController: XMLParserDelegate {

// initialize results structure

func parserDidStartDocument(_ parser: XMLParser) {
results = []
}

// start element
//
// - If we're starting a "record" create the dictionary that will hold the results
// - If we're starting one of our dictionary keys, initialize `currentValue` (otherwise leave `nil`)

func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String]) {
if elementName == recordKey {
currentDictionary = [:]
} else if dictionaryKeys.contains(elementName) {
currentValue = ""
}
}

// found characters
//
// - If this is an element we care about, append those characters.
// - If `currentValue` still `nil`, then do nothing.

func parser(_ parser: XMLParser, foundCharacters string: String) {
currentValue? += string
}

// end element
//
// - If we're at the end of the whole dictionary, then save that dictionary in our array
// - If we're at the end of an element that belongs in the dictionary, then save that value in the dictionary

func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
if elementName == recordKey {
results!.append(currentDictionary!)
currentDictionary = nil
} else if dictionaryKeys.contains(elementName) {
currentDictionary![elementName] = currentValue
currentValue = nil
}
}

// Just in case, if there's an error, report it. (We don't want to fly blind here.)

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

currentValue = nil
currentDictionary = nil
results = nil
}

}

For Swift 2 rendition, see previous revision of this answer.

xml parsing in iOS swift

Here's some parsing code I wrote in Swift 3 based off of a Google News RSS reader I previously wrote in Swift 2.0. I have this code modified to handle a list of PrintLetterBarcodeData elements as well as a single one:

class BarcodeData {
var uid: String
var name: String
var gender: String
var yob: String
var co: String
var house: String
var street: String
var lm: String
var vtc: String
var po: String
var dist: String
var subdist: String
var state: String
var pc: String
var dob: String

init?(dictionary: [String : String]) {
guard let uid = dictionary["uid"],
let name = dictionary["name"],
let gender = dictionary["gender"],
let yob = dictionary["yob"],
let co = dictionary["co"],
let house = dictionary["house"],
let street = dictionary["street"],
let lm = dictionary["lm"],
let vtc = dictionary["vtc"],
let po = dictionary["po"],
let dist = dictionary["dist"],
let subdist = dictionary["subdist"],
let state = dictionary["state"],
let pc = dictionary["pc"],
let dob = dictionary["dob"] else {
return nil
}

self.uid = uid
self.name = name
self.gender = gender
self.yob = yob
self.co = co
self.house = house
self.street = street
self.lm = lm
self.vtc = vtc
self.po = po
self.dist = dist
self.subdist = subdist
self.state = state
self.pc = pc
self.dob = dob

}
}

class MyParser: NSObject {
var parser: XMLParser

var barcodes = [BarcodeData]()

init(xml: String) {
parser = XMLParser(data: xml.data(using: String.Encoding.utf8)!)
super.init()
parser.delegate = self
}

func parseXML() -> [BarcodeData] {
parser.parse()
return barcodes
}

}

extension MyParser: XMLParserDelegate {

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

if elementName == "PrintLetterBarcodeData" {

if let barcode = BarcodeData(dictionary: attributeDict) {
barcodes.append(barcode)
}
}
}
}

Usage:

let xmlString = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><PrintLetterBarcodeData uid=\"685860050795\" name=\"Sangeetha D\" gender=\"F\" yob=\"1989\" co=\"W/O: Dhanansekaran\" house=\"632\" street=\"saradhambal nagar\" lm=\"agaramel\" vtc=\"Nazarathpettai\" po=\"Nazarethpettai\" dist=\"Tiruvallur\" subdist=\"Poonamallee\" state=\"Tamil Nadu\" pc=\"600123\" dob=\"03/06/1989\"/>"

let parser = MyParser(xml: xmlString)
let barcodes = parser.parseXML() // array of barcodes
barcodes.first // your barcode

Parsing xml from from url in iOS Swift

It isn't entirely clear from your question what your goal is, but I assume it is to extract the attributes of the result elements contained in the CreditCheckMSResult element.

The values are attributes of the result element, not children. So, when you get the start of the result element in didStartElement, the values you want are in the attributes dictionary that is passed to that function.

A dictionary is rarely a good final data model. In this case I would suggest that you create an array of structs to contain your data.

struct CreditCheckResult {
let message: String
let ref: String
let network: String
}

The xmlns attribute is to detail the XML namespace that apples to the element, if there is one. You wouldn't want to store this.

Since you aren't interested in the children of the result element (it doesn't have any). You pretty much only need the didStart delegate methods.

let recordKey = "result"
let results = [CreditCheckResult]?
extension ViewController: XMLParserDelegate {

func parserDidStartDocument(_ parser: XMLParser) {
results = []
}

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

// We only care about "result" elements
guard elementName == recordKey else {
return
}

guard let message = attributes["message"],
let ref = attributes["ref"],
let network = attributes["network"] else {
print("Malformed result element = required attribute missing")
return
}

self.results?.append(CreditCheckResult(message: message, ref: ref, network:network))
}

func parser(_ parser: XMLParser, parseErrorOccurred parseError: Error) {
print(parseError)
results = []
}

}

Parse an XML file in chunks without waiting for complete download in Swift

Maybe the client/server has a problem with some Session-Cookies? Try to delete them after each request:

// Loops through each of the cookies and deletes them.
let cookieStore = HTTPCookieStorage.shared
for cookie in cookieStore.cookies ?? [] {
cookieStore.deleteCookie(cookie)
}

Parsing XML file in Swift (Xcode v 7.0.1) and retrieving values from dictionary


NOTE
I've put the whole thing in a gist which you can copy and paste into a playground.


Let's look at a simple example to get a start:

let xml = "<coord2 count=\"3\">"
+ "<markers>"
+ "<marker>"
+ "<item>marker1</item>"
+ "</marker>"
+ "<marker>"
+ "<item>marker2</item>"
+ "<lat>36</lat>"
+ "</marker>"
+ "</markers>"
+ "</coord2>"

A bit narrowed down, but Markers can have a item name (string) and lat value (int). A Coord2 will have an array of Markers, and a count (int) attribute.

To parse the above with custom classes, here's one approach.

First create a ParserBase class that does some ground work for us, namely accumulating foundCharacters so that it can be easily used by sub classes.
Also (more importantly) it has a parent property which is used to hold references to the parent container class [this is used for the way in which we will be parsing XML].

// Simple base class that is used to consume foundCharacters
// via the parser

class ParserBase : NSObject, NSXMLParserDelegate {

var currentElement:String = ""
var foundCharacters = ""
weak var parent:ParserBase? = nil

func parser(parser: NSXMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String]) {

currentElement = elementName
}

func parser(parser: NSXMLParser, foundCharacters string: String) {
self.foundCharacters += string
}

}

Since coord2 is our root tag, we will create a class that will map to that tag - it represents the root object, has an array of Markers, a count property, and is also the root delegate object for the XMLParser.

// Represents a coord2 tag
// It has a count attribute
// and a collection of markers

class Coord2 : ParserBase {

var count = 0
var markers = [Marker]()

override func parser(parser: NSXMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String]) {

print("processing <\(elementName)> tag from Coord")

if elementName == "coord2" {

// if we are processing a coord2 tag, we are at the root
// of this example
// extract the count value and set it
if let c = Int(attributeDict["count"]!) {
self.count = c
}
}

// if we found a marker tag, delegate further responsibility
// to parsing to a new instance of Marker

if elementName == "marker" {
let marker = Marker()
self.markers.append(marker)

// push responsibility
parser.delegate = marker

// let marker know who we are
// so that once marker is done XML processing
// it can return parsing responsibility back
marker.parent = self
}
}

}

The Marker class is as follows:

class Marker : ParserBase {

var item = ""
var lat = 0

func parser(parser: NSXMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {

print("processing <\(elementName)> tag from Marker")

// if we finished an item tag, the ParserBase parent
// would have accumulated the found characters
// so just assign that to our item variable
if elementName == "item" {
self.item = foundCharacters
}

// similarly for lat tags
// convert the lat to an int for example
else if elementName == "lat" {
if let l = Int(foundCharacters) {
self.lat = l
}
}

// if we reached the </marker> tag, we do not
// have anything further to do, so delegate
// parsing responsibility to parent
else if elementName == "marker" {
parser.delegate = self.parent
}

// reset found characters
foundCharacters = ""
}

}

Now on to parsing, extracting info, and printing something.

let xmlData = xml.dataUsingEncoding(NSUTF8StringEncoding)!
let parser = NSXMLParser(data: xmlData)

let coord = Coord2()
parser.delegate = coord

parser.parse()

print("coord has a count attribute of \(coord.count)")
print("coord has \(coord.markers.count) markers")

for marker in coord.markers {
print("marker item = \(marker.item) and lat = \(marker.lat)")
}

which outputs the following:

coord has a count attribute of 3
coord has 2 markers
marker item = marker1 and lat = 0
marker item = marker2 and lat = 36

iOS parsing entire XML elements

It is because the foundCharacters function will called when the parser found something between any tag.

In your case, your first tag is 'id', since it is not match the name, your currentName have not initialized and therefore foundCharacters does not append the value '2' as the currentName is nil at this time. Then the 2nd tag is name, so it match your condition and initialize the string. From this point, every characters found between tag will append to currentName.

If you would like to parse the name only, one way is to check if the current tag is 'name' before appending characters to currentName.

- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI  qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributeDict {

// SUPPOSE TO READ ALL XML FILE AND FIND ALL "name" ELEMENTS
if ([elementName isEqualToString:@"name"]) {
isNameTag = YES;
self.currentName = [[NSMutableString alloc] init];
}
}

- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
if (isNameTag) {
[self.currentName appendString:string]; //passing the value of the current elemtn to the string
}
}

- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {

if ([elementName isEqualToString:@"name"]) {
isNameTag = NO; // disable the flag
NSLog(@"%@", currentName); //show current element
[allNames addObject:currentName];//not only is not reading all xml "name" elements but also adding another elements not shown in above NSLog....
}
}

How can I parse an XML file in Objective C

You couldnt get the firstname,lastname,etc in your attributeDict. Attribute dictionary holds values like in the below format

<count n="1">

In the above example attributeDict holds the value for n

In order to parse the given xml, you can use the below code.

Declare the objects

Politician *politician;
NSString *curElement;
NSMutableArray *politicians;
BOOL isCongressNumbers;

Initialize the politicians in viewDidLoad

politicians = [[NSMutableArray alloc]init];

Add the delegate methods

#pragma mark - NSXMLParser Delegate

- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict
{
if ([elementName isEqualToString:@"item"]) {
politician = [[Politician alloc]init];

} else if ([elementName isEqualToString:@"congress_numbers"]) {
isCongressNumbers = YES;
}

}

- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
curElement = string;

}

- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
if ([elementName isEqualToString:@"item"] && !isCongressNumbers) {
[politicians addObject:politician];
} else if ([elementName isEqualToString:@"firstname"]) {
politician.name = curElement;
} else if ([elementName isEqualToString:@"lastname"]) {
politician.lName = curElement;
} else if ([elementName isEqualToString:@"birthday"]) {
politician.bDay = curElement;
} else if ([elementName isEqualToString:@"congress_numbers"]) {
isCongressNumbers = NO;
}
}

how to parse xml response in objective c

The data you are looking for are XML attributes and are returned in the attributes dictionary in didStartElement.

-(void)parser:(NSXMLParser *)parser didStartElement:(NSString *) elementName 
namespaceURI:(NSString *) namespaceURI
qualifiedName:(NSString *) qName
attributes:(NSDictionary *) attributeDict
{

xmlparserString = elementName;
NSLog(@"xmlparserString start -->%@",xmlparserString);

if ([xmlparserString isEqualToString:@"logindetails"]) {

// soapResultsString = [[NSMutableString alloc] init];
NSLog(@"logindetails attributes --> %@", attributeDict);

}

The JSON deserialization in didEndElement is wrong and cannot work. XML attributes are not JSON. Delete the entire if expression



Related Topics



Leave a reply



Submit