NSXMLPARSER fails an refuse to parse !!!

Discussion in 'iOS Programming' started by IPhontastic, Oct 17, 2010.

  1. IPhontastic macrumors newbie

    Oct 17, 2010
    Hi all

    for so many days i tried to parse a simple xml file but with no luck at all.

    here is how i do it...

    first i send the user name and the password from my app using the post method then i use nsxmlparser to parse the xml created by the php file and get the user ID..

    the user id and other user infos are stored in a table.

    this is the post method ...
    -(IBAction) login {
    		NSError        *error = nil;
    		NSHTTPURLResponse *response;
    		// this user name and password are stored in a table db 
    		NSString *userName = @"xxx";
    		NSString *passwordy = @"password";
    		// assemble the POST data
    		NSString *post = [NSString stringWithFormat:
    		NSData *postData = [post dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES];
    		NSString *postLength = [NSString stringWithFormat:@"%d", [postData length]];
    		NSMutableURLRequest *request = [[[NSMutableURLRequest alloc] init] autorelease];
    		[request setURL:[NSURL URLWithString:@"http://www.myserver.com/folder/logg.php"]];
    		[request setHTTPMethod:@"POST"];
    		[request setValue:postLength forHTTPHeaderField:@"Content-Length"];
    		[request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
    		[request setHTTPBody:postData];
    		// send it
    		NSData *serverReply = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
    		NSString *replyString = [[NSString alloc] initWithBytes:[serverReply bytes] length:[serverReply length] encoding: NSASCIIStringEncoding];
    		if([replyString isEqualToString:@"Invalid."]){ // i have not set the php code to output "invalid" so this will not work for now ...
    	else {
    		NSLog(@"%@",replyString); // what ever i echo out of the php file - this is just to capture the whole output ...
    		[self performSelectorInBackground:@selector(parseXML) withObject:nil]; // it'll invok the parser
    this is the php file ...

    	$strUserName = $_POST['strUserName'];
    	$strPassword = md5($_POST['strPassword']);
    	$connect = mysql_connect("localhost", "mxmxmxm", "bc5Fo0");
    	$sqllogin = "SELECT usr_ID FROM tbluser WHERE usr_UserName='$strUserName' AND usr_Password='$strPassword'";
    	$nResult = mysql_query($sqllogin);
    	if( mysql_num_rows( $nResult )  == 1) {
    		$rstRow = mysql_fetch_array($nResult);
    		$nID =  $rstRow['usr_ID'];
        	$xmlBody ='<?xml version="1.0" encoding="UTF-8"?>';
    		$xmlBody .='<XML>';
    		$xmlBody .='<Data>';
    		$xmlBody .='<ID>'.$nID.'</ID>';
    		$xmlBody .='</Data>';
    		$xmlBody .='</XML>';
    		echo $xmlBody;
    now you can see that the user id should be stored in the $nID variable but for some reason the parser can not read it.

    this is the parser code (.m file) :

    - (void)parserDidStartDocument:(NSXMLParser *)parser{	
    - (void)parseXMLFileAtURL:(NSString *)URL
        //you must then convert the path to a proper NSURL or it won't work
        NSURL *xmlURL = [NSURL URLWithString:URL];
        // here, for some reason you have to use NSClassFromString when trying to alloc NSXMLParser, otherwise you will get an object not found error
        // this may be necessary only for the toolchain
         rssParser = [[NSXMLParser alloc] initWithContentsOfURL:xmlURL];
        [rssParser setDelegate:self];
        //Depending on the XML document you're parsing, you may want to enable these features of NSXMLParser.
        [rssParser setShouldProcessNamespaces:YES];
        [rssParser setShouldReportNamespacePrefixes:YES];
        [rssParser setShouldResolveExternalEntities:YES];
        [rssParser parse];
    -(void) parseXML {
        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    	NSString * path = @"http://www.myserver.com/folder/logg.php";
    	[self parseXMLFileAtURL :path];
        [pool drain]; 
    - (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError {
    	NSLog(@"Error: %@", [parseError localizedDescription]);
    - (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict{			
        NSLog(@"found this element: %@", elementName);
    	currentElement = [elementName copy];
    	if ([elementName isEqualToString:@"Data"]) {
    		// clear out our rest item caches...
    		currentUserID = [[NSMutableString alloc] init]; // init & alloc here
    - (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName{     
    	NSLog(@"ended element: %@", elementName);
    	if ([elementName isEqualToString:@"ID"]) {
    		// save values to an item, then store that item into the array...
    - (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
    	//if (!currentElement) return;
    	NSLog(@"found characters: %@", string);
    	// save the characters for the current item...
    	if ([currentElement isEqualToString:@"ID"]) {
    		[currentUserID appendString:string];
    		NSLog(@"this is the found method the id is %@",currentUserID);
    - (void)parserDidEndDocument:(NSXMLParser *)parser {
    	NSLog(@"all done!");
    - (void)dealloc {
        [super dealloc];
    	[rssParser release];
    	[currentElement release];
    	[currentUserID release];

    this is the log and you can see that the replystring is capturing the xml created by the .php file but the parser is failing to work with it ...

    2010-10-17 18:04:44.068 usingPOST[597:207] <?xml version="1.0" encoding="UTF-8"?><XML><Data><ID>2</ID></Data></XML>
    2010-10-17 18:04:44.648 usingPOST[597:5907] Error: Operation could not be completed. (NSXMLParserErrorDomain error 5.)
    if i removed the If statement from the .php file then this is what i get ..

    2010-10-17 18:10:17.387 usingPOST[624:207] <?xml version="1.0" encoding="UTF-8"?><XML><Data><ID>2</ID></Data></XML>
    2010-10-17 18:10:17.934 usingPOST[624:5907] started
    2010-10-17 18:10:17.936 usingPOST[624:5907] found this element: XML
    2010-10-17 18:10:17.938 usingPOST[624:5907] found this element: Data
    2010-10-17 18:10:17.940 usingPOST[624:5907] found this element: ID
    2010-10-17 18:10:17.942 usingPOST[624:5907] ended element: ID
    2010-10-17 18:10:17.943 usingPOST[624:5907] ended element: Data
    2010-10-17 18:10:17.945 usingPOST[624:5907] ended element: XML
    2010-10-17 18:10:17.946 usingPOST[624:5907] all done!
    as you can see now, it reads the whole xml file but does not show the value of <ID> !!

    one more thing... if i hardcoded the user name and password in the .php file then the parser will work just fine !!

    	$strUserName = "xxx";
    	$strPassword = md5(password);
    i am really tired finding whats wrong and what mistake i am doing. i really appreciate any help and any suggestion.

    thank you all in advance.
  2. ianray macrumors 6502

    Jun 22, 2010
    The parsing code that you posted seems to work. I get the following output:
    2010-10-17 19:37:19.739 a.out[25303:903] didStartElement XML
    2010-10-17 19:37:19.740 a.out[25303:903] didStartElement Data
    2010-10-17 19:37:19.740 a.out[25303:903] didStartElement ID
    2010-10-17 19:37:19.740 a.out[25303:903] *** foundCharacters 2
    2010-10-17 19:37:19.741 a.out[25303:903] didEndElement ID
    2010-10-17 19:37:19.741 a.out[25303:903] didEndElement Data
    2010-10-17 19:37:19.742 a.out[25303:903] didEndElement XML
    2010-10-17 19:37:19.742 a.out[25303:903] after parsing 1
    From this simple program:
    // gcc -W -Wall -framework Foundation parse.m
    #import <Foundation/Foundation.h>
    @interface MyClass : NSObject <NSXMLParserDelegate> {
    - (void)parse;
    @implementation MyClass
    - (void)parse {
    	NSString *xml = @"<?xml version=\"1.0\" encoding=\"UTF-8\"?><XML><Data><ID>2</ID></Data></XML>";
            NSData *data = [xml dataUsingEncoding:NSUTF8StringEncoding];
    	NSXMLParser *myParser = [[NSXMLParser alloc] initWithData:data];
    	[myParser setDelegate:self];
    	[myParser setShouldResolveExternalEntities:YES];
    	[myParser autorelease];
    	BOOL success = [myParser parse];
    	NSLog(@"after parsing %d", success);
    - (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError {
    	NSLog(@"parseErrorOccurred %@", parseError);
    - (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributeDict {
    	NSLog(@"didStartElement %@", elementName);
    - (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
    	NSLog(@"didEndElement %@", elementName);
    - (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
    	NSLog(@"*** foundCharacters %@", string);
    int main (int argc, const char * argv[]) {
        NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    	MyClass *my = [[MyClass alloc] init];
    	[my parse];
    	[my release], my = nil;
        [pool drain];
        return 0;
  3. chown33 macrumors 604

    Aug 9, 2009
    Sailing beyond the sunset
    Since I'm certain this code will eventually be copy-pasted into someone's program, the entire blue-hilited section can be replaced by one line:
    NSData *data = [xml dataUsingEncoding:NSUTF8StringEncoding];
    And because the returned NSData is already autoreleased, also delete the red-hilited line.

    The string-encoding was changed to NSUTF8StringEncoding, because that's the encoding named in the <?xml> processing directive, not ASCII. It doesn't matter in this simple test, but again, because I'm certain this code will be copy-pasted, better to be safe.
  4. chown33 macrumors 604

    Aug 9, 2009
    Sailing beyond the sunset
    You have a serious logic flaw in this code.

    The red-hilited code logs the replyString from the URL request, but it then neglects to parse that replyString. Instead, it asks for the parseXML method to be run in the background. That method then makes another HTTP request (URL string hilited in blue), this time a simple GET with no parameters. It then goes on to parse that new reply, which I suspect has nothing at all in its <ID> element.

    You need to properly pass the received replyString to the parser, so it can parse that reply. You should only have ONE URL and ONE request, not two of them in different places. That means you need to refactor parseXMLFileAtURL: so it accepts a reply string that was previously received from the server. You should also change its name, because it won't be fetching a URL.

    I should also point out that it's silly to perform the URL request synchronously, then parse the reply in the background. The parsing is almost certain to finish hundreds of times faster than a URL request. Furthermore, you can't proceed from login until the parse is completed anyway, and you have no way to signal the completion (or success) of the XML parsing. Those may all be things you haven't yet designed, but you will still have to do them correctly if you expect it to work.

    I strongly suggest that you not do any parsing in the background, but simply invoke the parser directly with the received replyString. This is far easier to get right, and to see that it's right, than trying to coordinate two separate threads of execution, which is what you have now.

    And it wouldn't hurt you to learn how to use the debugger to set breakpoints and step through lines of code. If you stepped through parseXMLFileAtURL: it would become clear that the string being parsed isn't the replyString from the POST.
  5. ianray macrumors 6502

    Jun 22, 2010
    Well spotted :) This was, of course, old test code lurking on my machine :D

Share This Page