objective-cafnetworkingkissxml

Parsing XML response from a website


Currently I am writing an online login for my iOS app, I did some research about what library I should use for handeling the HTTP-Request and the XML-Parsing. I ended up with AFNetworking and KissXML. I also use the KissXML addition for AFNetworking. The request to the website is successfully, but I am kinda stuck at the parsing of the recieved XML.

The response looks like this:

<login>
    <response status="success" result="correct"/>
    <data>
        <username>testusername</username>
        <country>Germany</country>
    </data>
</login>

In my code I do a HTTPRequest to the website recieve the XML and then try to check if the userdatas are valid or not. If they were valid I want to recieve the elements like username and country. But if an error occured I want to give it back. My code sofar:

{
__block BOOL success = NO;
__block NSError *localerror = nil;
__block NSString *domain = @"de.FranzBusch.Searchlight.ErrorDomain";

//HTTP Request
NSURL *url = [NSURL URLWithString:@"http://www.example.de/login.php"];
AFHTTPClient *client = [[AFHTTPClient alloc] initWithBaseURL:url];

NSDictionary *params = [NSDictionary dictionaryWithObjectsAndKeys:
                        email, @"email",
                        password, @"password", nil];

NSMutableURLRequest *request = [client requestWithMethod:@"POST" path:@"" parameters:params];

//Parse XML-Response
AFKissXMLRequestOperation *operation = [AFKissXMLRequestOperation XMLDocumentRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, DDXMLDocument *XMLDocument)
{
    NSArray *resultNodes = nil;
    NSError *responseError = nil;
    resultNodes = [XMLDocument nodesForXPath:@"//response" error:&responseError];

    if (responseError || ([resultNodes count] != 1))
    {
        localerror = [NSError errorWithDomain:domain code:-101 userInfo:[self generateErrorDictionary:@"XMLError"]];
        success = NO;
    }
    else
    {
        for (DDXMLElement *element in resultNodes)
        {
            DDXMLNode *node = [element attributeForName:@"status"];
            NSString *status = [node stringValue];

            if ([status isEqualToString:@"fail"])
            {
                success = NO;
                localerror = [NSError errorWithDomain:domain code:-101 userInfo:[self generateErrorDictionary:[[element attributeForName:@"result"] stringValue]]];
            }
            else
            {
                NSError *usernameError = nil;
                NSArray *dataNodes = [XMLDocument nodesForXPath:@"//data//username" error:&usernameError];

                if (usernameError || ([dataNodes count] != 1))
                {
                    localerror = [NSError errorWithDomain:domain code:-101 userInfo:[self generateErrorDictionary:@"XMLError"]];
                    success = NO;
                }
                else
                {
                    for (DDXMLELement *dataElement in dataNodes)
                    {

                    }
                }
            }
        }
    }

    }failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, DDXMLDocument *XMLDocument)
    {
        NSLog(@"failure");
    }];
[operation start];
return success;
}

Can anybody give me a hint on how to improve the code and how to do it right. Because if i want to assign the localerror to the error I always get an error :(


Solution

  • If I'm understanding you correctly, you're having problems parsing the XML response. I usually just use Apple's stream XML parser, NSXMLParser. It's simple, fast and easy to use.

    I did a small example project for you (using ARC), parsing the XML response you describe, with NSXMLParser: https://github.com/erikt/ETXMLParseSOExample

    You can play with it and run the project, but the important part is the NSXMLParser delegate:

    @interface ETLoginInfoXMLParser ()
    @property (strong,nonatomic) NSMutableString *currentElementValue;
    @property (strong,nonatomic) ETLoginInfo *loginInfo;
    @end
    
    @implementation ETLoginInfoXMLParser
    
    #pragma mark - NSXMLParserDelegate
    - (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict
    {
        if ([elementName isEqualToString:@"login"]) {
            if (!self.loginInfo) {
                self.loginInfo = [[ETLoginInfo alloc] init];
            }
            return;
        }
        
        if ([elementName isEqualToString:@"response"]) {
            self.loginInfo.responseStatus = [attributeDict objectForKey:@"status"];
            self.loginInfo.responseResult = [attributeDict objectForKey:@"result"];
        }
    }
    
    - (void) parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
        if (!self.currentElementValue) {
            self.currentElementValue = [[NSMutableString alloc] initWithString:string];
        } else {
            [self.currentElementValue appendString:string];
        }
    }
    
    - (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
    {
        if ([elementName isEqualToString:@"username"]) {
            NSString *trimmedValue = [self.currentElementValue stringByTrimmingCharactersInSet:
                                      [NSCharacterSet whitespaceAndNewlineCharacterSet]];
            self.loginInfo.username = trimmedValue;
        }
        
        if ([elementName isEqualToString:@"country"]) {
            NSString *trimmedValue = [self.currentElementValue stringByTrimmingCharactersInSet:
                                      [NSCharacterSet whitespaceAndNewlineCharacterSet]];
            self.loginInfo.country = trimmedValue;
        }
        
        self.currentElementValue = nil;
    }
    
    
    /** Parse XML login response */ 
    +(ETLoginInfo *)parseXMLLoginResponse:(NSString *)xml {
        ETLoginInfoXMLParser *loginInfoParser = [[ETLoginInfoXMLParser alloc] init];
        NSXMLParser *xmlParser = [[NSXMLParser alloc] initWithData:[xml dataUsingEncoding:NSUTF8StringEncoding]];
        [xmlParser setDelegate:loginInfoParser];
        BOOL success = [xmlParser parse];
        
        if (success) {
            return loginInfoParser.loginInfo;
        } else {
            NSLog(@"Error parsing login information");
            return nil;
        }
    }
    
    @end
    

    I wired it up to a silly iOS app to show the parsing of this XML response:

    <login>
      <response status="success" result="correct"/>
      <data>
        <username>Willy Wonka</username>
        <country>Germany</country>
      </data>
    </login>
    

    enter image description here enter image description here

    You would want a more interesting model hierarchy as I—for simplicity's sake—just put everything in the same entity