Become a MacRumors Supporter for $50/year with no ads, ability to filter front page stories, and private forums.

Soulstorm

macrumors 68000
Original poster
Feb 1, 2005
1,887
1
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?
 

iSee

macrumors 68040
Oct 25, 2004
3,540
272
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.
 

kainjow

Moderator emeritus
Jun 15, 2000
7,958
7
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.
 

iSee

macrumors 68040
Oct 25, 2004
3,540
272
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.)
 

Soulstorm

macrumors 68000
Original poster
Feb 1, 2005
1,887
1
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?
 

iSee

macrumors 68040
Oct 25, 2004
3,540
272
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...
 

Soulstorm

macrumors 68000
Original poster
Feb 1, 2005
1,887
1
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.
 
Register on MacRumors! This sidebar will go away, and you'll see fewer ads.