Go Back   MacRumors Forums > Apple Systems and Services > Programming > Mac Programming

Reply
 
Thread Tools Search this Thread Display Modes
Old Feb 6, 2008, 10:17 AM   #1
Soulstorm
macrumors 68000
 
Soulstorm's Avatar
 
Join Date: Feb 2005
NSXMLParser Simple Problem

I have an xml document:

Code:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE addresses SYSTEM "addresses.dtd">
<addresses>
    <person>
        <lastName>Doe</lastName>
        <firstName>John</firstName>
        <email>jdoe@foo.com</email>
        <address>
            <street>100 Main Street</street>
            <city>Somewhere</city>
            <state>New Jersey</state>
            <zip>07670</zip>
        </address>
    </person> 
</addresses>
I have set up a cocoa application for testing purposes. The application consists of a single file. Inside that, this is the code. Header and Implementation file appears together below.
Code:

#import <Cocoa/Cocoa.h>
#import "ABPerson.h"

@interface SFXMLParser : NSObject {
	NSXMLParser *addressParser;
	
	NSMutableArray *addresses;
	ABPerson *currentPerson;
	
	NSMutableString *currentStringValue;
	
	NSMutableArray *dictProperties;
	
	NSString *currentName;
}
- (void)parseXMLFile:(NSString *)pathToFile;

-(IBAction)do:(id)sender;
@end





#import "SFXMLParser.h"


@implementation SFXMLParser
- (id) init
{
	self = [super init];
	if (self != nil) {
		
	}
	return self;
}


-(void)awakeFromNib
{
	NSString *str = [[NSString stringWithString:@"~/Desktop/info.xml"]stringByStandardizingPath];
	[self parseXMLFile:str];
}


- (void)parseXMLFile:(NSString *)pathToFile {
    BOOL success;
	
    NSURL *xmlURL = [NSURL fileURLWithPath:pathToFile];
    if (addressParser) // addressParser is an NSXMLParser instance variable
        [addressParser release];
    
	addressParser = [[NSXMLParser alloc] initWithContentsOfURL:xmlURL];
    [addressParser setDelegate:self];
    [addressParser setShouldResolveExternalEntities:YES];
    
	success = [addressParser parse]; // return value not used
	// if not successful, delegate is informed of error
	NSLog(@"parse!");
}


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

	if ( [elementName isEqualToString:@"addresses"]) {
        // addresses is an NSMutableArray instance variable
		NSLog(@"found addresses");
		if (!addresses)
			addresses = [[NSMutableArray alloc] init];
        return;
    }
	
	if ( [elementName isEqualToString:@"person"] ) {
		NSLog(@"found ABPerson");
        // currentPerson is an ABPerson instance variable
        currentPerson = [[ABPerson alloc] init];
        return;
    }
	if ( [elementName isEqualToString:@"lastName"] ) {
		NSLog(@"found lastName");
        // currentPerson is an ABPerson instance variable
        currentPerson = [[ABPerson alloc] init];
        return;
    }
	
}

- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
	NSLog(@"found characters:%@",string);
    if (!currentStringValue) {
        // currentStringValue is an NSMutableString instance variable
        currentStringValue = [[NSMutableString alloc] init];
    }
	NSLog(@"appendingstring...");
    [currentStringValue appendString:string];
}

- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName 
{
	if (( [elementName isEqualToString:@"addresses"]) ||
        ( [elementName isEqualToString:@"address"] )) return;
	
	if ([elementName isEqualToString:@"lastName"]) {
		 NSLog(@"lastname entering...%@",currentStringValue);
		 [currentPerson setLastName:currentStringValue];
	}
	
	if ([elementName isEqualToString:@"firstName"]) {
		NSLog(@"first name entering...");
		[currentPerson setFirstName:currentStringValue];
	}
	if ([elementName isEqualToString:@"email"]) {
		NSLog(@"email entering...");
		[currentPerson setEmail:currentStringValue];
	}
	
	if ( [elementName isEqualToString:@"person"] ) {
        [addresses addObject:currentPerson];
        [currentPerson release];
        return;
    }
	[currentStringValue release];
	currentStringValue = nil;
}

-(IBAction)do:(id)sender
{
	int i = 0;
	NSLog(@"first name:%@\nlast name:%@\nemail:%@",[[addresses objectAtIndex:i]firstName], [[addresses objectAtIndex:i]lastName], [[addresses objectAtIndex:i]email]);
}

- (void) dealloc
{
	[dictProperties release];
	[super dealloc];
}

@end
The problem is that when I press the "do" button (the one that activates the "do" IBAction) I get the following result:

Code:
2008-02-06 18:13:04.906 cocoaki2[582:10b] first name:
        John
last name:
    
        Doe
email:
        jdoe@foo.com
The application appears to give more new lines than those exists inside the file. Why all those '\n' characters?

Last edited by Soulstorm; Feb 6, 2008 at 10:24 AM.
Soulstorm is offline   0 Reply With Quote
Old Feb 6, 2008, 02:52 PM   #2
iSee
macrumors 68040
 
iSee's Avatar
 
Join Date: Oct 2004
I read over the code and it looks fine to me. You aren't inserting that white space in the code, anyway.

It's suspicious because notice that the extra white space includes indentation (spaces or tabs) in addition to newlines.

So either the parser is doing it (that would be a big bug). Or the white space really is in the source .xml file. If you haven't already, open ~/Desktop/info.xml in a plain text editor (not a high-level xml editor that may be hiding or adding white space) to verify its contents.
iSee is offline   0 Reply With Quote
Old Feb 6, 2008, 03:07 PM   #3
kainjow
Moderator emeritus
 
kainjow's Avatar
 
Join Date: Jun 2000
I think parser:foundCharacters: is getting called for every element, and you're appending the element value to your variable. I believe whitespace is an XML element and so the whitespace is being appended to your string. You should set a flag so that in that delegate method you're only appending to your variable when the flag is on.

Also, if you're targeting 10.4+, you can use NSXMLDocument and related classes and read the XML with XPath and other DOM related methods. I find that a much easier way for simple XML reading.
kainjow is offline   0 Reply With Quote
Old Feb 6, 2008, 04:08 PM   #4
iSee
macrumors 68040
 
iSee's Avatar
 
Join Date: Oct 2004
Ah, I see what you mean kainjow.

the currentStringValue used to set firstName, for example, includes the whitespace from the end of </lastName> to the start of <firstName>: a newline plus eight spaces.

So the OP could modify the code so that:
1. currentStringValue is not allocated in -foundCharacters[]. Instead -foundCharacters[] would do nothing when currentStringValue is nil.
2. currentStringValue is allocated and init'ed in didStartElement when an appropriate element name is passed in. It would be good to double-check that currentStringValue wasn't already non-nil.

(My solution uses whether or not currentStringValue is nil as the flag you mention in your post.)
iSee is offline   0 Reply With Quote
Old Feb 10, 2008, 10:23 AM   #5
Soulstorm
Thread Starter
macrumors 68000
 
Soulstorm's Avatar
 
Join Date: Feb 2005
I have another problem...

Seems the NSXMLParser fails to load nested variables. Can anyone help me with this? Those are the files:

ABPerson Definition File:
Code:
#import <Cocoa/Cocoa.h>
#import "ABAddress.h"

@interface ABPerson : NSObject {
	NSString *lastName;
	NSString *firstName;
	NSString *email;
	
	ABAddress *address;
}

- (NSString *)lastName;
- (NSString *)firstName;
- (NSString *)email;
- (ABAddress *)address;

- (void)setAddress:(ABAddress *)newAddress;
- (void)setLastName:(NSString *)newLastName;
- (void)setFirstName:(NSString *)newFirstName;
- (void)setEmail:(NSString *)newEmail;

@end
ABAddress definition file:
Code:
#import <Cocoa/Cocoa.h>


@interface ABAddress : NSObject {
	NSString *street;
	NSString *city;
	NSString *state;
	NSString *zipCode;
}

- (void)setStreet:(NSString *)aStreet;
- (void)setCity:(NSString *)aCity;
- (void)setState:(NSString *)aState;
- (void)setZipCode:(NSString *)aZipCode;

- (NSString *)street;
- (NSString *)city;
- (NSString *)state;
- (NSString *)zipCode;

@end
SFXMLParser definition and Implementation:
Code:
#import <Cocoa/Cocoa.h>
#import "ABPerson.h"

@interface SFXMLParser : NSObject {
	NSXMLParser *addressParser;
	
	NSMutableArray *addresses;
	ABPerson *currentPerson;
	
	NSMutableString *currentStringValue;
	
	NSMutableArray *dictProperties;
	NSString *currentName;
	
	ABAddress *currentAddress;
}
- (void)parseXMLFile:(NSString *)pathToFile;

-(IBAction)do:(id)sender;
@end



#import "SFXMLParser.h"


@implementation SFXMLParser
- (id) init
{
	self = [super init];
	if (self != nil) {
		
	}
	return self;
}


-(void)awakeFromNib
{
	NSString *str = [[NSString stringWithString:@"~/Desktop/info.xml"]stringByStandardizingPath];
	[self parseXMLFile:str];
}


- (void)parseXMLFile:(NSString *)pathToFile {
    BOOL success;
	
    NSURL *xmlURL = [NSURL fileURLWithPath:pathToFile];
    if (addressParser) // addressParser is an NSXMLParser instance variable
        [addressParser release];
    
	addressParser = [[NSXMLParser alloc] initWithContentsOfURL:xmlURL];
    [addressParser setDelegate:self];
    [addressParser setShouldResolveExternalEntities:YES];
    
	success = [addressParser parse]; // return value not used
	// if not successful, delegate is informed of error
	NSLog(@"parse!");
}


- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict 
{
	currentStringValue = [[NSMutableString alloc] init];
	if ( [elementName isEqualToString:@"addresses"]) {
        // addresses is an NSMutableArray instance variable
		NSLog(@"found addresses");
		if (!addresses)
			addresses = [[NSMutableArray alloc] init];
        return;
    }
	
	if ( [elementName isEqualToString:@"address"]) {
        // addresses is an NSMutableArray instance variable
		NSLog(@"found single address");
		if (!currentAddress)
			currentAddress = [[ABAddress alloc] init];
        return;
    }
	
	if ( [elementName isEqualToString:@"person"] ) {
		NSLog(@"found ABPerson");
        // currentPerson is an ABPerson instance variable
        currentPerson = [[ABPerson alloc] init];
        return;
    }
	
	
}

- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
	//NSLog(@"found characters:%@",string);
    if (!currentStringValue) {
        // currentStringValue is an NSMutableString instance variable
        //currentStringValue = [[NSMutableString alloc] init];
    }
	//NSLog(@"appendingstring...");
    [currentStringValue appendString:string];
}

- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName 
{
	if (( [elementName isEqualToString:@"addresses"]) ||
        ( [elementName isEqualToString:@"address"] )) return;
	
	if ([elementName isEqualToString:@"lastName"]) {
		 NSLog(@"lastname entering...%@",currentStringValue);
		 [currentPerson setLastName:currentStringValue];
	}
	
	if ([elementName isEqualToString:@"firstName"]) {
		NSLog(@"first name entering...");
		[currentPerson setFirstName:currentStringValue];
	}
	if ([elementName isEqualToString:@"email"]) {
		NSLog(@"email entering...");
		[currentPerson setEmail:currentStringValue];
	}
	
	if ([elementName isEqualToString:@"street"]) {
		NSLog(@"street entering...");
		[currentAddress setStreet:currentStringValue];
	}

	if ([elementName isEqualToString:@"city"]) {
		NSLog(@"city entering...");
		[currentAddress setCity:currentStringValue];
	}
	
	if ([elementName isEqualToString:@"state"]) {
		NSLog(@"state entering...");
		[currentAddress setState:currentStringValue];
	}
	
	if ([elementName isEqualToString:@"zip"]) {
		NSLog(@"zipcode entering...");
		[currentAddress setZipCode:currentStringValue];
	}
	
	if ([elementName isEqualToString:@"address"]) {
		NSLog(@"address finishing...");
		[currentPerson setAddress:currentAddress];
		[currentAddress release];
		return;
	}
	
	if ( [elementName isEqualToString:@"person"] ) {
        [addresses addObject:currentPerson];
        [currentPerson release];
        return;
    }
	[currentStringValue release];
	currentStringValue = nil;
}

-(IBAction)do:(id)sender
{
	int i = 0;
	//NSLog(@"first name:%@\nlast name:%@\nemail:%@",[[addresses objectAtIndex:i]firstName], [[addresses objectAtIndex:i]lastName], [[addresses objectAtIndex:i]email]);
	ABPerson *person = [addresses objectAtIndex:i];

	if ([[person address]state] == nil) {
		NSLog(@"fdssdfsd nooooooooooooooooooooooooooo!");
	}
	
	NSLog(@"it is: %@",[[person address]zipCode]);
}

- (void) dealloc
{
	[dictProperties release];
	[super dealloc];
}

@end
The problem is that the parser although it read correctly every ABPerson attribute, it won't give me any result for its ABAddress variable.

Any idea why this happens?

Last edited by Soulstorm; Feb 10, 2008 at 10:56 AM.
Soulstorm is offline   0 Reply With Quote
Old Feb 10, 2008, 10:42 AM   #6
iSee
macrumors 68040
 
iSee's Avatar
 
Join Date: Oct 2004
Ah ha, I see the problem. It's just a little copy & paste error.

Look at didStartElement, where you are allocating currentAddress.
Notice you are checking if addresses is nil, not currentAddress...
iSee is offline   0 Reply With Quote
Old Feb 10, 2008, 10:55 AM   #7
Soulstorm
Thread Starter
macrumors 68000
 
Soulstorm's Avatar
 
Join Date: Feb 2005
Quote:
Originally Posted by iSee View Post
Ah ha, I see the problem. It's just a little copy & paste error.

Look at didStartElement, where you are allocating currentAddress.
Notice you are checking if addresses is nil, not currentAddress...
That was correct, but the problem remains the same (I changed my original post to avoid confusing anyone):

Strangely, this is what I get from the result

Code:
2008-02-10 18:52:36.977 cocoaki2[1155:10b] found addresses
2008-02-10 18:52:36.983 cocoaki2[1155:10b] found ABPerson
2008-02-10 18:52:36.984 cocoaki2[1155:10b] lastname entering...Doe
2008-02-10 18:52:36.984 cocoaki2[1155:10b] first name entering...
2008-02-10 18:52:36.985 cocoaki2[1155:10b] email entering...
2008-02-10 18:52:36.985 cocoaki2[1155:10b] found single address
2008-02-10 18:52:36.986 cocoaki2[1155:10b] street entering...
2008-02-10 18:52:36.986 cocoaki2[1155:10b] city entering...
2008-02-10 18:52:36.986 cocoaki2[1155:10b] state entering...
2008-02-10 18:52:36.987 cocoaki2[1155:10b] zipcode entering...
2008-02-10 18:52:36.987 cocoaki2[1155:10b] person ending
2008-02-10 18:52:36.988 cocoaki2[1155:10b] parse!
2008-02-10 18:52:38.109 cocoaki2[1155:10b] it is:
Notice that the didEndElement function is NOT called when addresses end! Or, is it called and something messy is going on?

EDIT: Actually, this is getting weird. The didEndElement function is indeed called for the "address" field. However, it fails to go into this branch:

Code:
if ([elementName isEqualToString:@"address"]) {
		NSLog(@"address finishing... %@", [currentAddress state]);
		[currentPerson setAddress:currentAddress];
		[currentAddress release];
		return;
	}
It's as if the "if" function has something wrong, only I can't figure out what!
EDIT2: I am stupid. this line was the problem:

Code:
if (( [elementName isEqualToString:@"addresses"]) ||
        ( [elementName isEqualToString:@"address"] )) return;
I changed it to:
Code:
	if ( [elementName isEqualToString:@"addresses"])
		return;
and it worked,only I am experiencing a crash. It's a memory management thingie, since when I enable garbage collection, it works just fine.

Last edited by Soulstorm; Feb 10, 2008 at 11:04 AM.
Soulstorm is offline   0 Reply With Quote

Reply
MacRumors Forums > Apple Systems and Services > Programming > Mac Programming

Thread Tools Search this Thread
Search this Thread:

Advanced Search
Display Modes

Similar Threads
thread Thread Starter Forum Replies Last Post
NSXMLParser MickeyT iPhone/iPad Programming 3 Jun 18, 2014 04:29 PM
A serious problem, with a simple solution? NaughtyNoNo Mac Basics and Help 4 Aug 8, 2013 12:37 PM
NSXmlParser error leconteconte iPhone/iPad Programming 2 Feb 28, 2013 06:29 AM
Simple problem, need help! Sweetooth44 iPhone/iPad Programming 5 Sep 19, 2012 11:35 AM
Resolved: NSXMLParser fails after first element? ArtOfWarfare Mac Programming 2 Jul 17, 2012 07:30 PM

Forum Jump

All times are GMT -5. The time now is 10:25 PM.

Mac Rumors | Mac | iPhone | iPhone Game Reviews | iPhone Apps

Mobile Version | Fixed | Fluid | Fluid HD
Copyright 2002-2013, MacRumors.com, LLC