Most of the examples of how to invoke the NSXMLParser are contained within complex projects involving Apps. What does a simple example that demonstrates the callbacks look l
As part of exploring the NSXMLParser I created the following really simple code.
main.m
int main(int argc, const char * argv[])
{
@autoreleasepool {
NSLog(@"Main Started");
NSError *error = nil;
// Load the file and check the result
NSData *data = [NSData dataWithContentsOfFile:@"/Users/Tim/Documents/MusicXml/Small.xml"
options:NSDataReadingUncached
error:&error];
if(error) {
NSLog(@"Error %@", error);
return 1;
}
// Create a parser and point it at the NSData object containing the file we just loaded
NSXMLParser *parser = [[NSXMLParser alloc] initWithData:data];
// Create an instance of our parser delegate and assign it to the parser
MyXmlParserDelegate *parserDelegate = [[MyXmlParserDelegate alloc] init];
[parser setDelegate:parserDelegate];
// Invoke the parser and check the result
[parser parse];
error = [parser parserError];
if(error)
{
NSLog(@"Error %@", error);
return 1;
}
// All done
NSLog(@"Main Ended");
}
return 0;
}
MyXmlParserDelegate.h
#import <Foundation/Foundation.h>
@interface MyXmlParserDelegate : NSObject <NSXMLParserDelegate>
@end
MyXmlParserDelegate.m
#import "MyXmlParserDelegate.h"
@implementation MyXmlParserDelegate
- (void) parserDidStartDocument:(NSXMLParser *)parser {
NSLog(@"parserDidStartDocument");
}
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict {
NSLog(@"didStartElement --> %@", elementName);
}
-(void) parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
NSLog(@"foundCharacters --> %@", string);
}
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
NSLog(@"didEndElement --> %@", elementName);
}
- (void) parserDidEndDocument:(NSXMLParser *)parser {
NSLog(@"parserDidEndDocument");
}
@end
I've posted it in the hope that it helps someone else.
#import <Cocoa/Cocoa.h>
@interface AppDelegate : NSObject <NSApplicationDelegate, NSXMLParserDelegate>
@property (nonatomic, strong) NSMutableDictionary *dictXML;
@property (nonatomic,strong) NSMutableArray *arrOfUpdateDictsByVersion;
@property (nonatomic,strong) NSString *strElementBeingParsed;
@property (nonatomic,strong) NSString *strElementFinishedParsing;
@end
#import "AppDelegate.h"
@interface AppDelegate ()
@property (weak) IBOutlet NSWindow *window;
@end
@implementation AppDelegate
@synthesize dictXML;
@synthesize arrOfUpdateDictsByVersion;
@synthesize strElementBeingParsed;
@synthesize strElementFinishedParsing;
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
[self initializeTheArray];
[self startParsingXML];
}
-(void)initializeTheArray{
self.dictXML = [[NSMutableDictionary alloc] init];
self.arrOfUpdateDictsByVersion = [[NSMutableArray alloc] init];
}
-(void)startParsingXML{
// NSXMLParser *xmlparser = [[NSXMLParser alloc] initWithContentsOfURL:[NSURL URLWithString:@"http://cdn.example.com/databaseupdate.xml"]];
NSXMLParser *xmlparser = [[NSXMLParser alloc] initWithContentsOfURL:[NSURL fileURLWithPath:@"/Users/vkrmsinha/Desktop/xmlParse.xml"]];
[xmlparser setDelegate:self];
[xmlparser parse];
}
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict;
{
// initial tag comes in here
self.strElementBeingParsed = elementName;
/*if([elementName isEqualToString:@"Version"]){
}
else if ([elementName isEqualToString:@"Update"]){
}*/
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string;
{
if(([string rangeOfCharacterFromSet:[NSCharacterSet newlineCharacterSet]].location != NSNotFound) && ![self.strElementBeingParsed isEqualToString:@"Update"])
return;
// middle part from between the start and end tags comes here
if ([self.strElementBeingParsed isEqualToString:@"Update"]){
NSMutableDictionary *dictUpdate = [NSMutableDictionary dictionary];
[self.arrOfUpdateDictsByVersion addObject:dictUpdate];
}
else if ([self.strElementBeingParsed isEqualToString:@"UpdateType"]){
NSMutableDictionary *dictUpdate = [self.arrOfUpdateDictsByVersion lastObject];
NSMutableDictionary *dictUpd = [NSMutableDictionary dictionary];
[dictUpd setValue:string forKey:@"UpdateType"];
[dictUpdate setValue:dictUpd forKey:@"update"];
}
else if([self.strElementBeingParsed isEqualToString:@"Version"]){
NSMutableDictionary *dictUpdate = [self.arrOfUpdateDictsByVersion lastObject];
// WARNING: ASK IF NO TWO VERSION WILL BE SAME IN FUTURE
[dictUpdate setValue:string forKey:@"version"];
}
else if ([self.strElementBeingParsed isEqualToString:@"FileName"]){
NSMutableDictionary *dictUpdate = [self.arrOfUpdateDictsByVersion lastObject];
NSMutableDictionary *dict = [dictUpdate objectForKey:@"update"];
[dict setValue:string forKey:@"FileName"];
}
else if ([self.strElementBeingParsed isEqualToString:@"Hash"]){
NSMutableDictionary *dictUpdate = [self.arrOfUpdateDictsByVersion lastObject];
NSMutableDictionary *dict = [dictUpdate objectForKey:@"update"];
[dict setValue:string forKey:@"Hash"];
}
else if ([self.strElementBeingParsed isEqualToString:@"DownloadURL"]){
NSMutableDictionary *dictUpdate = [self.arrOfUpdateDictsByVersion lastObject];
NSMutableDictionary *dict = [dictUpdate objectForKey:@"update"];
[dict setValue:string forKey:@"DownloadURL"];
}
else if ([self.strElementBeingParsed isEqualToString:@"Size"]){
NSMutableDictionary *dictUpdate = [self.arrOfUpdateDictsByVersion lastObject];
NSMutableDictionary *dict = [dictUpdate objectForKey:@"update"];
[dict setValue:string forKey:@"Size"];
}
}
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName;
{
// ending of tag comes in here
self.strElementFinishedParsing = elementName;
if([elementName isEqualToString:@"Update"]){
[self.arrOfUpdateDictsByVersion sortUsingDescriptors:[NSArray arrayWithObjects:[NSSortDescriptor sortDescriptorWithKey:@"self.version" ascending:YES], nil]];
NSLog(@"%@", [self.arrOfUpdateDictsByVersion lastObject]);
}
if([elementName isEqualToString:@"UpdateDetails"]){
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
[dict setValue:[[self.arrOfUpdateDictsByVersion lastObject] objectForKey:@"version"] forKey:@"latestVer"];
[dict setValue:[[self.arrOfUpdateDictsByVersion firstObject] objectForKey:@"version"] forKey:@"oldestVer"];
[dict setValue:self.arrOfUpdateDictsByVersion forKey:@"arrOfUpdsByVer"];
NSLog(@"%@", dict);
}
}
- (void)applicationWillTerminate:(NSNotification *)aNotification {
// Insert code here to tear down your application
}
<UpdateDetails xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<DatabaseUpdates>
<Update>
<UpdateType>CompleteDatabase</UpdateType>
<Version>1</Version>
<FileName>1completedatabase.zip</FileName>
<Hash>ad94431d2fe4cd60eb3347fadaa45d88</Hash>
<DownloadURL>
http://www.example.com/download/new.xml
</DownloadURL>
<Size>2367008</Size>
</Update>
</DatabaseUpdates>
</UpdateDetails>
Swift 4 Example - Parsing Xib-file.
import Foundation
class XMLTransformer: NSObject {
private let parser: XMLParser
private var stack = [Node]()
private var tree: Node?
init(data: Data) {
parser = XMLParser(data: data)
super.init()
parser.delegate = self
}
}
extension XMLTransformer {
func transform() throws -> Node? {
parser.parse()
if let e = parser.parserError {
throw e
}
assert(stack.isEmpty)
assert(tree != nil)
return tree
}
}
extension XMLTransformer: XMLParserDelegate {
func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String] = [:]) {
guard let tag = Tag(rawValue: elementName) else {
return
}
let node = Node(tag: tag, attributes: attributeDict, nodes: [])
stack.append(node)
}
func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
guard let tag = Tag(rawValue: elementName) else {
return
}
let lastElement = stack.removeLast()
assert(lastElement.tag == tag)
if let last = stack.last {
last.nodes += [lastElement]
} else {
tree = lastElement
}
}
}
extension XMLTransformer {
enum Tag: String {
case document
case objects
case tableViewCell, tableViewCellContentView
case subviews
case mapView
case constraints, constraint
case connections, outlet
}
}
extension XMLTransformer {
class Node {
let tag: Tag
let attributes: [String : String]
var nodes: [Node]
init(tag: Tag, attributes: [String : String], nodes: [Node] = []) {
self.tag = tag
self.attributes = attributes
self.nodes = nodes
}
}
}
Usage:
let data = try xib(named: "MapTableViewCell")
let c = XMLTransformer(data: data)
let tree = try c.transform() // Here you have parsed XML in a Tree representation.
Here is a Swift version of the original Objective-C code below.
It was built and tested using XCode 7.3. In writing the delegate I found it quite handy to copy the function prototypes from the documentation. It is worth noting that Swift is currently a fairly rapidly moving target.
main.swift
import Foundation
// Let's go
print("Main: Started")
// Try to load the file. Display the description of the error if one occurs
var xmlData : NSData
do {
xmlData = try NSData(contentsOfFile: "/Users/amt/Documents/TestXml/Test.xml",
options: .DataReadingMappedIfSafe)
}
catch let error as NSError {
print("Main: \(error.description)")
exit(1)
}
// Create a parser and point it at the NSData object containing
// the file we just loaded
var parser : NSXMLParser! = NSXMLParser(data: xmlData)
// Create a parser delegate object and assign it to the parser
// Beware the "weak" reference and don't try to combine the two lines
// of code into one unless you like EXC_BAD_ACCESS exceptions
var parserDelegate : MyXmlParserDelegate = MyXmlParserDelegate()
parser.delegate = parserDelegate
// This example also illustrates some of the namespace functions defined in
// the delegate protocol so enable namespace reporting to see them invoked
parser.shouldReportNamespacePrefixes = true
// Parse the document
if !parser.parse() {
// If parse() returned false then an error occurred so display is location
// and details
let error = parser.parserError
let line = parser.lineNumber
let col = parser.columnNumber
print("Parsing failed at \(line):\(col): \(error?.localizedDescription)")
}
// All done
print("Main: Ended")
exit(0)
MyXmlParserDelegate.swift
import Foundation
class MyXmlParserDelegate:NSObject, NSXMLParserDelegate {
@objc func parserDidStartDocument(parser: NSXMLParser) {
print("parserDidStartDocument")
}
@objc func parser(parser: NSXMLParser, didStartElement elementName: String,
namespaceURI: String?, qualifiedName qName: String?,
attributes attributeDict: [String : String]) {
print("didStartElement --> \(elementName)")
}
@objc func parser(parser: NSXMLParser, foundCharacters string: String) {
print("foundCharacters --> \(string)")
}
@objc func parser(parser: NSXMLParser, didEndElement elementName: String,
namespaceURI: String?, qualifiedName qName: String?) {
print("didEndElement --> \(elementName)")
}
@objc func parser(parser: NSXMLParser, didStartMappingPrefix prefix: String,
toURI namespaceURI: String) {
print("didStartMappingPrefix --> Prefix: \(prefix) toURI: \(namespaceURI)")
}
@objc func parser(parser: NSXMLParser, didEndMappingPrefix prefix: String) {
print("didEndMappingPrefix --> Prefix: \(prefix)")
}
@objc func parserDidEndDocument(parser: NSXMLParser) {
print("parserDidEndDocument")
}
}