Parsing XML from URL in Swift
The process is simple:
- Create
XMLParser
object, passing it the data. - Specify the
delegate
for that parser. - 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
iOS Safari Memory Usage with "-Webkit-Transform"
Which Is the Best Way to Estimate Measure of Photographed Things
If No Table View Results, Display "No Results" on Screen
Uitextview Style Is Being Reset After Setting Text Property
How to Set the Uinavigationbar with Gradient Color
The Maximum Number of Apps for Free Development Profiles Has Been Reached. Xcode 11.5
How to Load Local PDF in Uiwebview in Swift
Why Safari Shows "No Inspectable Applications" During Remote Debugging with iOS 6 Device
Uitableview with Two Custom Cells (Multiple Identifiers)
How to Display Clickable Links in Uitextview
CSS Gradient Not Working on iOS
Ask for User Permission to Receive Uilocalnotifications in iOS 8
How to Open Maps App Programmatically with Coordinates in Swift
Getting Text from Image on iOS (Image Processing)
Wait for Asynchronous Operation to Complete in Swift