Newbie confused about parsing XML

Discussion in 'iOS Programming' started by jeremyapp, Jan 22, 2009.

  1. jeremyapp macrumors newbie

    Joined:
    Apr 30, 2008
    #1
    Hey everyone,

    I'm just starting to get the hang of obj-c, but I still have a long way to go.

    I have a pretty basic understanding of the terminology, but no matter what I've read I can't seem to find the answer to this question. To illustrate it best, let me give you a hypothetical situation:

    Let's say I have a remote XML file on some webserver with the following format:

    Code:
    <?xml version="1.0" encoding="ISO-8859-1"?>
    <quote>
         <id>1</id>
         <body>A penny saved is a penny earned.</body>
         <author>Unknown</author>
    </quote>
    
    In my interface, I have two text labels (one for quote, and one for author) and a button.

    In my viewController header file, I've declared the UILabels for both labels, as well as two NSString's. I've also added an IBAction called changeLabel:, which is linked to my button.

    What I would like to do is the following:

    When the button i pressed, I would like for the remote XML file to be parsed in such a way that I end up with the info from the <body> tags in one NSString and the info from the <author> tags in the other NSString. From there, I know exactly how to change the labels - it's just getting that data from the remote XML that I can't wrap my head around.

    If anyone could please offer some help, I would be very grateful. Thank you!
     
  2. dejo Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #2
    I would suggest looking at the code for the SeismicXML sample app. That should probably get you started in the right direction.
     
  3. jeremyapp thread starter macrumors newbie

    Joined:
    Apr 30, 2008
    #3
    That was the first place that I looked, but it didn't quite have what I was looking for. While it probably would have been very helpful for someone with a bit more cocoa knowledge, I can't seem to understand how to just get single elements from xml instead of a whole array to put into a table.
     
  4. mpramodjain macrumors regular

    Joined:
    Nov 20, 2008
    Location:
    Banglore
    #4
    found Characters.

    Get the current element name , in startElement callback, and in foundCharacters get that string into your variables.Thats all...
     
  5. jeremyapp thread starter macrumors newbie

    Joined:
    Apr 30, 2008
    #5
    Thank you for the help. I tried this, but I'm still just not getting it. Do you mind showing me what the code should look like? Thanks.
     
  6. drivefast macrumors regular

    Joined:
    Mar 13, 2008
    #6
    wherever you start the parser:
    Code:
    	NSXMLParser *pXML = [[NSXMLParser alloc] initWithData:yourXMLstring];
    	[pXML setDelegate:self];
    	[pXML parse];
    	if ([pXML parserError]) {
    		// handle error here
    	}
    	[pXML release];
    
    and since you set your delegate to self, these methods have to be declared in the same class:
    Code:
    - (void)parser:(NSXMLParser *)parser 
    	didEndElement:(NSString *)elementName 
    	namespaceURI:(NSString *)namespaceURI 
    	qualifiedName:(NSString *)qName
    {
    	if ([elementName isEqualToString:@"id"]) {
    		lblID.text = [NSString stringWithString:tagContents];
    	} else if ([elementName isEqualToString:@"body"]) {
    		lblBody.text = [NSString stringWithString:tagContents];
    	// ... and so on with each tag name
    	}
    }
    
    Code:
    - (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
    	[tagContents appendString:string];	
    }
    
    i use to declare tagContents as a NSMutableString and clear it off (set it to an empty string) in each parser:didStartElement:... pass.
     
  7. jeremyapp thread starter macrumors newbie

    Joined:
    Apr 30, 2008
    #7
    Thanks for the help again! I feel like I'm getting close now. It still isn't working, though. Here's what I have:

    Code:
    // Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
    - (void)viewDidLoad {
    	NSURL *xmlURL = [NSURL fileURLWithPath:@"http://www.quotestumbler.com/sample.xml"];
        NSXMLParser *pXML = [[ NSClassFromString(@"NSXMLParser") alloc] initWithContentsOfURL:xmlURL];
    	[pXML setDelegate:self];
    	[pXML parse];
    	if ([pXML parserError]) {
    		// handle error here
    	}
    	[pXML release];
    }
    
    - (void)parser:(NSXMLParser *)parser 
     didEndElement:(NSString *)elementName 
      namespaceURI:(NSString *)namespaceURI 
     qualifiedName:(NSString *)qName
    {
    	if ([elementName isEqualToString:@"id"]) {
    		quote.text = [NSString stringWithString:tagContents];
    	} else if ([elementName isEqualToString:@"body"]) {
    		author.text = [NSString stringWithString:tagContents];
    	}
    }
    
    - (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
    	[tagContents appendString:string];	
    }
    
    
    It's saying that tagContents is undeclared. What could I be doing wrong? Thanks!
     
  8. drivefast macrumors regular

    Joined:
    Mar 13, 2008
    #8
    tagContents should be your variable. declare it as a mutable string in the header file, and take care of emptying its contents when needed. so in the declarations part of your class (in your header file) you would have
    NSMutableString *tagContents;
    and right before you initialize the parser you would do
    tagContents = @"";
    to give it a fresh start. then you would clear again the contents after a tag is completely processed, for example at the end of the parser:didEndElement:... function.

    here's how the parser works. when you say [parser parse], the string you have given as an argument starts to be poured (streamed) in the parser, character by character. when a sequence that looks like "<mytag>" is encountered, the parser calls your parser:didStartElement:... function, and gives you a chance to prepare the stage for the tag in question. when you're done processing, the parser continues to pour the characters between <mytag> and </mytag>, occasionally calling the parser:foundCharacters: function. in your case, and in most of the cases, all you have to do is to concatenate the found characters into the value that you expect the tag to carry. finally, when the "</mytag>" construction is encountered, the parser calls your parser:didEndElement:... function, to give you a chance to do something with the data you've just put together. note that control is not given to the line after the [parser parse] command until after the whole xml document is done parsing; in other words, you can consider the "parse" command blocking. this is how a SAX parser works - there's another breed of parsers, called DOM, which swallows the whole xml doc and presents it in some form of a tree with node navigation methods, but as far as i know there is no DOM parser in the sdk.

    my final comment is actually a rant: the acceptable inputs to the xml parser offered by the sdk is either a file or a string. why in the world cant we have a stream as an input??... it kinda defeats the very purpose of having a sax parser.
     
  9. jeremyapp thread starter macrumors newbie

    Joined:
    Apr 30, 2008
    #9
    I'm so sorry about this, but it just still isn't working and I can't understand why! I feel really bad that I keep asking questions that are probably really simple and stupid, but this has been frustrating me to no ends. Here's my situation:

    I have a view-based application.

    In QuoteTestViewController.h:

    Code:
    //
    //  QuoteTestViewController.h
    
    #import <UIKit/UIKit.h>
    
    @interface QuoteTestViewController : UIViewController {
    	IBOutlet UITextView *quoteText;
    	IBOutlet UILabel *authorLabel;
    	
    	NSMutableString *tagContents;
    }
    
    @property (nonatomic, retain) UITextView *quoteText;
    @property (nonatomic, retain) UILabel *authorLabel;
    
    @end
    
    In QuoteTestViewController.m:

    Code:
    //
    //  QuoteTestViewController.m
    
    #import "QuoteTestViewController.h"
    
    @implementation QuoteTestViewController
    @synthesize authorLabel, quoteText;
    
    
    /*
    // The designated initializer. Override to perform setup that is required before the view is loaded.
    - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
        if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
            // Custom initialization
        }
        return self;
    }
    */
    
    /*
    // Implement loadView to create a view hierarchy programmatically, without using a nib.
    - (void)loadView {
    }
    */
    
    
    
    // Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
    - (void)viewDidLoad {
    	tagContents = @"";
        NSURL *xmlURL = [NSURL fileURLWithPath:@"http://www.quotestumbler.com/sample.xml"];
        NSXMLParser *pXML = [[ NSClassFromString(@"NSXMLParser") alloc] initWithContentsOfURL:xmlURL];
    	[pXML setDelegate:self];
    	[pXML parse];
    	if ([pXML parserError]) {
    		// handle error here
    	}
    	[pXML release];
    }
    
    - (void)parser:(NSXMLParser *)parser 
     didEndElement:(NSString *)elementName 
      namespaceURI:(NSString *)namespaceURI 
     qualifiedName:(NSString *)qName
    {
    	if ([elementName isEqualToString:@"author"]) {
    		authorLabel.text = [NSString stringWithString:tagContents];
    	} else if ([elementName isEqualToString:@"body"]) {
    		quoteText.text = [NSString stringWithString:tagContents];
    		// ... and so on with each tag name
    	}
    }
    
    - (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
    	[tagContents appendString:string];	
    }
    
    /*
    // Override to allow orientations other than the default portrait orientation.
    - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
        // Return YES for supported orientations
        return (interfaceOrientation == UIInterfaceOrientationPortrait);
    }
    */
    
    
    - (void)didReceiveMemoryWarning {
        [super didReceiveMemoryWarning]; // Releases the view if it doesn't have a superview
        // Release anything that's not essential, such as cached data
    }
    
    
    - (void)dealloc {
        [super dealloc];
    }
    
    @end
    
    
    My goal is to have it parse that xml file and put change the labels to the values of the "author" and "body" elements in my xml file. However, the labels aren't changing no matter what I try. Does it look like something is wrong here?

    Again, I can't tell you how much I appreciate this. Thanks!
     
  10. dejo Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #10
    Are the UITextView/UILabel text properties not getting updated or are they, but those changes to their values are not getting propogated to the display? (Put in a breakpoint when you update them to be sure). If the latter, you just may need to call something as simple as setNeedsDisplay on your UIView.
     
  11. jeremyapp thread starter macrumors newbie

    Joined:
    Apr 30, 2008
    #11
    To test if it was even able to find the elements in the xml, I made it so that it would write a line to the console if it had found the element we were looking for:

    Code:
    - (void)parser:(NSXMLParser *)parser 
     didEndElement:(NSString *)elementName 
      namespaceURI:(NSString *)namespaceURI 
     qualifiedName:(NSString *)qName
    {
    	if ([elementName isEqualToString:@"author"]) {
    		authorLabel.text = [NSString stringWithString:tagContents];
                     
                    // Let us know if we found the element
    
                    NSLog(@"Element found");
    	} else if ([elementName isEqualToString:@"body"]) {
    		quoteText.text = [NSString stringWithString:tagContents];
    		NSLog(@"Element found");
    	}
    }
    
    Nothing shows up in the console, so I think it's something with the parser.
     
  12. drivefast macrumors regular

    Joined:
    Mar 13, 2008
    #12
    as dejo suggested, put a breakpoint on the first line of the parser:didEndElement:... function and then step-debug (use the run menu to learn the commands). if your breakpoint is never touched, try implementing even a dummy parser:didStartElement:...function (although this is more of a shot in the dark).
     

Share This Page