NSXMLParser parse value based on element attribute - objective-c

I have XML sample:
<book id="bk101">
<title>XML Developer's Guide</title>
</book>
<book id="bk102">
<title>Midnight Rain</title>
</book>
How can I output title value for book with id "bk101"?
My code:
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributeDict
{
if ([elementName isEqualToString:#"book"])
{
NSString *codeValue = [attributeDict objectForKey:#"id"];
if ([codeValue isEqualToString:#"bk101"]) {
//NSLog(#"found"); // output book title here
}
}
}

Suppose structure XML such as in the example and tag has no child elements and text inside the tag does not have unicode or other special characters. Then for output to console (for example) "title" value for book with id "bk101" you should:
Declare codeValue as property.
#property (strong, nonatomic) NSString *codeValue;
Change your code like below:
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributeDict
{
if ([elementName isEqualToString:#"book"])
{
_codeValue = [attributeDict objectForKey:#"id"];
}
}
Add implementation foundCharacters: method like below:
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
if ([_codeValue isEqualToString:#"bk101"])
{
NSLog(#"found: %#", string); // output book title here
}
}
Add implementation didEndElement: method like below (selectively for the case where not all tags "book" will have an attribute):
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
if (_codeValue != nil)
{
_codeValue = nil
}
}
NOTE: If the conditions specified in the beginning of the post will be different - you may need a different implementation.
As alternative you can use XMLConverter and convert your XML (I did some changes):
<?xml version="1.0" encoding="UTF-8"?>
<books>
<book id="bk101">
<title>XML Developer's Guide</title>
</book>
<book id="bk102">
<title>Midnight Rain</title>
</book>
</books>
to NSDictionary like below:
2013-12-24 13:15:48.167 test[603:70b] {
books = {
book = (
{
"-id" = bk101;
title = "XML Developer's Guide";
},
{
"-id" = bk102;
title = "Midnight Rain";
}
);
};
}
and work with it.

Related

Parsing XML elements for a TableView

At the moment I am designing a simple app that will read news feeds. The XML feed I am using is this... http://feeds.foxnews.com/foxnews/world?format=xml .
What I am looking to do is essentially take the 'title' element and the description' elements from this page, and use them in a TableView.
This is what I currently have:
-(void)parserDidStartDocument:(NSXMLParser *)parser{
_elementsArray = [[NSMutableArray alloc]init];
}
-(void)parser:(NSXMLParser*)parser foundCharacters:(NSString *)string{
if (!_currentString) {
_currentString = [[NSMutableString alloc]init];
}
[_currentString appendString:string];
}
-(void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName{
if ([elementName isEqualToString:#"title"]) {
[_elementsArray addObject:_currentString];
_currentString=nil;
return;
}
if ([elementName isEqualToString:#"description"]) {
[_elementsArray addObject:_currentString];
_currentString=nil;
return;
}
_currentString = nil;
}
-(void)parserDidEndDocument:(NSXMLParser *)parser{
for (NSString*string in _elementsArray) {
NSLog(#"%#",string);
}
_elementsArray = nil;
}
I do get an output of this, that is not a problem.
My problem is that each element is added in a new NSArray entry. How would I add both elements to one entry? ( would it be possible to use keys ? )
You could use a temporary NSDictionary to store your title and description together for each item, and add the dictionary to the _elementsArray.
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributeDict {
if ([elementName isEqualToString:#"item"]) {
_currentDict = [NSMutableDictionary dictionary];
return;
}
}
-(void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName{
if ([elementName isEqualToString:#"item"]) {
[_elementsArray addObject:_currentDict];
_currentDict=nil;
return;
}
if ([elementName isEqualToString:#"title"]) {
_currentDict[#"title"] = _currentString;
_currentString=nil;
return;
}
if ([elementName isEqualToString:#"description"]) {
_currentDict[#"description"] = _currentString;
_currentString=nil;
return;
}
_currentString = nil;
}
// Other methods left unchanged
This should output something like:
NSArray (
NSDictionary {
#"title": #"First title",
#"description": #"A news article"
},
NSDictionary {
#"title": #"Second title",
#"description": #"Another interesting article"
}
)
Untested, but I hope you get the idea.
Note also that parsing an XML (or RSS) file into native ObjC objects is a very recurring problem, and lots of open source libs out there already do most of the heavy lifting for you.

parse multiple elements of xml in objective-c

I need to parse multiple elements (id1 and id2) in such kind of xml:
<name>
<id1>104634449</id2>
<id2>22014870</id2>
</name>
<name>
<id1>104634433</id2>
<id2>220143210</id2>
</name>
I use such code:
-(void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI: (NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict{
isName = [elementName isEqualToString:#"name"];
isId1 = [elementName isEqualToString:#"id1"];
isId2 = [elementName isEqualToString:#"id2"];
if(isName){
name = [Name new];
}
}
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
if (isName){
[names addObject:name];
}
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
if (isId1){
[name setId1:string];
}
if (isId2){
[name setId2:string];
}
}
But only one element (id1 or id2) is set in object name. What I do wrong?
In debug I get this:
id1 = #"\n"
id2 = #"22014870"
The problem is that when you finish to read id2 isName is set to NO, so you don't really add it to your array.
This is the sequence of actions:
The name element starts;
The id1 element starts;
The id1 element ends;
The id2 element starts;
The id2 element ends;
The name element ends.
At point 4 occurs the last assignment of isName, set to:
isName = [elementName isEqualToString:#"name"];
Which will result false.
The corrected code:
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
if ([elementName isEqualToString: #"name"){
[names addObject:name];
}
}
Before you get too deep in the bowels of XML parsing I would highly recommend using GDataXMLNode (also available as a CocoaPod)
Then, to get an idea of how to use it, check out this tutorial, How To Read and Write XML Documents with GDataXML
I know this doesn't directly answer your question but it is really so much simpler to use this library provided by Google.

count elements in xml using objective c

I've got an XML file and I need to count how many times the element appears in it using Objective-C. How should I do this?
<?xml version="1.0" encoding="ISO-8859-1"?>
<residents>
<resident id="1">
<name>
<first>David</first>
<last>Dollar</last>
</name>
</resident>
<resident id="2">
<name>
<first>Michael</first>
<last>Nipp</last>
</name>
</resident>
etc...
I would set your class as the delegate of the parser, then this class will receive parse events such as parser:didStartElement:, parser:foundCharacters: and parser:didEndElement:.
self.parser = [[NSXMLParser alloc] initWithData:xmlData];
[self.parser setDelegate:self];
[self.parser parse];
I would create a count variable in your parser delegate. Anytime an element is found, the didStartElement: function is called on the parser delegate. Check if it's the "resident" element and increment the count if so.
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict {
if ([elementName isEqualToString:#"resident"]) {
self.count += 1;
}
}

NSXMLParser to create nodes and NSMutableArray

i'm looking for a advise. I want to parse an XML file with NSXMLParser, and i wonder what i should do with tags and parameters.
Fo example i have:
<template>
<template name="default" layout="absolute">
<image tmpl="topbanner"/>
<list tmpl="list">
<font tmpl="listfont"/>
<item target="target1">
<text>Target1</text>
</item>
<item target="target2">
<text>Target2</text>
</item>
<item target="target3">
<text>Target3</text>
</item>
.
.
.
And later i want to create an objects base on this information. So - where should i store retrieve information from parser? In method:
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict
{
element = [NSMutableString string];
}
I see that i can simply recieve atributes ang tags, but should I in this point write it in NSMutableArray or NSDictionary?
I've read NSXMLParser how to pass the NSMutableDictionary to a NSMutableArray But is it the best way?
If you know that a <list> element contains an array of subelements, you'll probably want to create an array or dictionary at the start of that block:
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict
{
if ([elementName isEqualToString:#"list"]) {
self.list = [NSMutableDictionary dictionary];
self.listName = [attributeDict objectForKey:#"tmpl"]
}
else if ([elementName isEqualToString:#"item"]) {
self.itemKey = [attributeDict objectForKey:#"target"];
}
else if ([elementName isEqualToString:#"text"]) {
self.data = [NSMutableString string];
}
else if ([elementName isEqualToString:#"font"]) {
self.font = [attributeDict objectForKey:#"tmpl"];
}
}
Then you can add the simple <item> elements in your -parser:didEndElement:... method:
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
if (elementName isEqualToString:#"text") {
// no action needed here -- data already contains the text
}
else if (elementName isEqualToString:#"item"]) {
[self.list setObject:[self.data copy] forKey:self.itemKey;
self.itemKey = nil;
}
else if ([elementName isEqualToString:#"list"]) {
// do something appropriate with the list
[self.template setObject:self.items forKey:self.listName];
self.listName = nil;
self.list = nil;
}
}
This all presumes that you've got properties for font, data, itemKey, and so on... basically whatever you need to remember all the state you need until you can create the related object. Once you have the data you need, usually in the didEnd method, create the object, store it somewhere, and clear out the saved data.
This isn't the only approach to parsing the data. For example, you might want to go with a stack-based approach instead. But the idea illustrated above is probably the easiest to understand, and if the data isn't too complicated it's not hard to manage.
To get what's inside the tag use:
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
[element appendString:string];
}
Then check in :
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
To get the attributes :
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict {
if ([elementName isEqualToString:#"image"]) {
NSString *imageName = [attributeDict objectForKey:#"tmpl"];
}
}

Parsing XML using NSXML for iPhone

I have an XML which has a structure like this:
<data>
<books>
<item>
<title>book1</title>
<author>author1</author>
</item>
<item>
<title>book2</title>
<author>author2</author>
</item>
</books>
<magazines>
<item>
<title>Mag1</title>
<author>author1</author>
</item>
</magazines>
<journals>
<item>
<title>Journal1</title>
<author>author1</author>
</item>
<item>
<title>Journal2</title>
<author>author2</author>
</item>
</journals>
</data>
I am using a table view to display the items of each category. I plan to have a table view with columns books, magazines & journals and clicking on them should display only the items under them. That is, if I click on books, it should only display the book entries.
I have this for xml parsing
- (void)parser:(NSXMLParser *)parser
didStartElement:(NSString *) elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName
attributes:(NSDictionary *)attributeDict
{
if ([elementName isEqual:#"books"]) {
flag=1;
}
if ([elementName isEqual:#"title"]) {
NSLog(#"found title!");
parsedString = [[NSMutableString alloc] init];
}
if ([elementName isEqual:#"author"]) {
NSLog(#"description found");
parsedString = [[NSMutableString alloc] init];
}
}
and in
- (void)parser:(NSXMLParser *)parser
didEndElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName
{
if (flag==1){
if ([elementName isEqual:#"title"]) {
[headlines addObject:parseString];
[parseString release];
parseString = nil;
}
if ([elementName isEqual:#"author"]) {
[authors addObject:parsedString];
[parseString release];
parseString = nil;
flag=2;
}
}
}
But this gets only the first entry of books and nothing else.
Instead of setting flags is there a way to parse only between the <books> and
</books> tags ?
Thank you.
The reason it is only getting the first item is that you are setting flag to 2 when you see the end element for the author tag. I suspect you will get closer to what you want if you update this flag in the end element for the books tag, as follows:
- (void)parser:(NSXMLParser *)parser
didEndElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName
{
if (flag==1){
if ([elementName isEqual:#"title"]) {
[headlines addObject:parseString];
[parseString release];
parseString = nil;
}
if ([elementName isEqual:#"author"]) {
[authors addObject:parsedString];
[parseString release];
parseString = nil;
}
if ([elementName isEqual:#"books"]) {
flag=2;
}
}
}
Just cancel parsing when the books tag ends:
- (void)parser:(NSXMLParser *)parser
didEndElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName
{
if ([elementName isEqual:#"books"]) {
[parser abortParsing];
}
....
}