nsmutablearray elements to nsstring - objective-c

I want to retrieve elements that are parsed in a NSMutableArray and store them into a NSString variable and then store them in NSMutableArray as NSString (because I want to display the content in a NSComboBox). I tried this but it dosen't work. Can you fix the problem, I can't fix it:
//--this is the parsing code :
- (void)parser:(NSXMLParser *)parser
didStartElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qualifiedName
attributes:(NSDictionary *)attributeDict {
if ([elementName isEqualToString:#"user"]) {
NSLog(#"user element found – create a new instance of User class...");
if(currentElementValue == nil)
currentElementValue = [NSMutableString string];
else
[currentElementValue setString:#""];
}
else {
currentElementValue = nil;
}
user = [[User alloc] init];
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
if (!currentElementValue) {
// init the ad hoc string with the value
currentElementValue = [[NSMutableString alloc] initWithString:string];
} else {
// append value to the ad hoc string
[currentElementValue appendString:string];
if (currentElementValue)
{
currentElementValue = nil;
}
}
NSLog(#"Processing value for : %#", string);
}
- (void)parser:(NSXMLParser *)parser
didEndElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName {
if ([elementName isEqualToString:#"users"]) {
// We reached the end of the XML document
return;
NSLog(#"QUIT");
}
if ([elementName isEqualToString:#"userName"]) {
[[self user] setUserName:currentElementValue];
NSLog(#"final step for value: %#", user.userName);
NSLog(#"currentElementName content : %#", currentElementValue);
[currentElementValue release];
NSLog(#"release : %#", currentElementValue);
currentElementValue = nil;
NSLog(#"release : %#", currentElementValue);
}
if ([elementName isEqualToString:#"firstName"]) {
[[self user] setFirstName:currentElementValue];
[currentElementValue release];
currentElementValue = nil;
}
if ([elementName isEqualToString:#"lastName"]) {
[[self user] setLastName:currentElementValue];
[currentElementValue release];
currentElementValue = nil;
}
if ([elementName isEqualToString:#"user"]) {
NSLog(#"\n user=%# \n",user);
[users addObject:user];
NSLog(#"userName test : %#", users);
[user release];
user = nil;
}
}
-(BOOL)parseDocumentWithData:(NSData *)data {
if (data == nil)
return NO;
NSXMLParser *xmlparser = [[NSXMLParser alloc] initWithData:data];
[xmlparser setDelegate:self];
[xmlparser setShouldResolveExternalEntities:NO];
BOOL ok = [xmlparser parse];
if (ok == NO)
NSLog(#"error");
else
NSLog(#"OK");
[xmlparser release];
return ok;
}
// this is the xml file :
<users>
<user>
<userName>mspeller</userName>
<firstName>Mike</firstName>
<lastName>Speller</lastName>
</user>
<user>
<userName>mgdan</userName>
<firstName>Mila</firstName>
<lastName>Gdan</lastName>
</user>
</users>
//-------
NSMutableArray *tabletest= [[NSMutableArray alloc] init];
NSMutableString * result = [[NSMutableString alloc] init];
int i;
for(i=0; i < [users count]; i++){
[result appendString:[NSString stringWithFormat:#"%#",[[users objectAtIndex:i] valueForKey:#"userName"]] ];
NSLog(#"result==%#",result);
[tabletest addObject:result];
}

Based on your link in the comment section I think you're accessing the "userName" property the wrong way. You're trying to access it, as users contains NSDictionary objects. As far as I can see you're adding User objects to the NSMutableArray.
Try the following (I took the liberty to beautify the code a bit):
NSMutableArray *tabletest= [NSMutableArray array];
for (User* user in users)
{
NSString* result = [NSString stringWithFormat:#"%#", user.userName];
NSLog(#"result==%#",result);
[tabletest addObject:result];
}
Please correct me if I totally misunderstood your design.

I don't follow what your intention is, but what your code does at the moment is add the same string [user count] time to the array tabletest as follows:
The line:
[result appendString:[NSString stringWithFormat:#"%#",[[users objectAtIndex:i] valueForKey:#"userName"]] ];
accumulates into result the result of appending each [[users objectAtIndex:i] valueForKey:#"userName"] together - each iteration of the loop adds the next item to the end of result.
The line:
[tabletest addObject:result];
Adds the object referenced by result into the array. This is done once per iteration so the array ends up with [users count] references to the same object. Placing a reference to a mutable string into an array does not place a copy of its current value, just a reference to the string - mutate the string and the mutation is visible through the reference stored in the array.
So the final result of your code is an array of [users count] references to the same mutable string, and that string is the concatenation of all the [[users objectAtIndex:i] valueForKey:#"userName"] values.
What was your intent?
If you are trying to create an array of string representations of [[users objectAtIndex:i] valueForKey:#"userName"] then change the code to:
NSMutableArray *tabletest= [[NSMutableArray alloc] init];
for(int i = 0; i < [users count]; i++)
{
// create a string representation of userName
NSString *result = [NSString stringWithFormat:#"%#",[[users objectAtIndex:i] objectForKey:#"userName"]];
// add the string to the array
[tabletest addObject:result];
}
But maybe your intent is something else?

Related

NSMutableArray loses data

There is lots of help regarding this issue already here but none of the implementations have worked. i.e.: creating (nonatomic, retain) + using self.myArray, Using a dictionary instead of arrays.
What I am doing is parsing information from an xml document, filter out unwanted entries, and try to dynamically store the information for that entry.
When I try to store the info into 2 mutableArrays the information of ONE of them gets lost when trying to access the info outside of my 'parser' method.
Some background code. Not full code. (this also has the dictionary as well)
.h
#interface WikiView : UIViewController {
//scrollview
UIScrollView *ScrollView;
//init
int isIpad;
int orientation;
int parseCount;
//parse data constructs
NSString *subplant;
NSString *element;
NSMutableString *text;
NSString *oldElement;
NSMutableDictionary *dataHolder;
NSMutableArray *dataGroup;
NSMutableArray *dataText;
}
#end
.m
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
//inits
dataGroup = [[NSMutableArray alloc] init];
dataText = [[NSMutableArray alloc] init];
dataHolder = [[NSMutableDictionary alloc] initWithCapacity:1];
text = [[NSMutableString alloc] init];
//parse the info
[self loadDataFromXML:xmlpath];
//When I call the values for dataText here they are all null
//also when called the objects for dataHolder are null as well
//this outputs the correct array
for (int i = 0; i<[dataGroup count]; i++) {
NSLog(#"%#",dataGroup[i]);
}
//this outputs an array of null objects
for (int i = 0; i<[dataText count]; i++) {
NSLog(#"HI.....%#",dataText[i]);
}
}
//parse function
//method to retrieve data
- (void)loadDataFromXML:(NSString *)xmlpath {
//data is parsed
NSData* data = [NSData dataWithContentsOfFile: xmlpath];
NSXMLParser* parser = [[NSXMLParser alloc] initWithData: data];
[parser setDelegate:self];
[parser parse];
[parser release];
}
//on found characters
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
if ([subplant isEqualToString:plantid]) {
NSString *s2 = [string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
if (![oldElement isEqualToString:element]) {
if (oldElement != nil) {
if (parseCount >= 1) {
//Here I store the values into the proper places
NSLog(#"%#: %#",oldElement,text);
[dataGroup addObject:oldElement];
[dataText addObject:text];
[dataHolder setObject:text forKey:oldElement];
//The values are correct here
}
parseCount++;
}
//if (new tag) reset string
[text setString:s2];
}
else{
//if not new tag append string (takes care of &apos;)
[text appendString:s2];
}
oldElement = element;
}
}
//on did start element
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict {
//accessing tags of this element
if ([elementName isEqualToString:#"plant"]) {
subplant = [attributeDict valueForKey:#"plant"];
}
element = elementName;
}
-(void)dealloc{
[super dealloc];
[text release]; [dataGroup release]; [dataText release]; [dataHolder release];
}
I create dataGroup and dataText the exact same way but only dataText loses its value.
Any help is appreciated, and if any part of my code is unclear please let me know.
EDIT:
Found the source of the problem.
When I write to the dataText array I rewrite every entry to be the last entry to be entered. In my test case the last entry was the string #"null" creating an array of nulls.
Will be back with solution when found.
EDIT2:
#RuslanSoldatenko Noticed I did not create a new instance of my text string after I set the object in the array. Look at the comments for help.

Create an Array from the xml file

My XML format is like this:
<Rows>
<Row>
<style>String</style?
<thumbnail>String</thumbnail>
</Row>
<Row>
<style>String</style?
<thumbnail>String</thumbnail>
</Row>
</Rows>
How do i create an NSMutableArray like this ({style = "value"; thumbnail = "value";}.....)
Here is my code:
-(void) parser:(NSXMLParser *) parser didStartElement:(NSString *) elementName namespaceURI:(NSString *) namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict {
if ([elementName isEqualToString:#"style"]) {
element = [NSString stringWithString:elementName];
if (!soapResults) {
soapResults = [[NSMutableString alloc] init];
}
elementFound = YES;
}
if ([elementName isEqualToString:#"thumbnail1"]) {
element = [NSString stringWithString:elementName];
if (!soapResults) {
soapResults = [[NSMutableString alloc] init];
}
elementFound = YES;
}
}
-(void)parser:(NSXMLParser *) parser foundCharacters:(NSString *)string
{
if (elementFound) {
[soapResults appendString:string];
}
}
-(void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI: (NSString*)namespaceURI qualifiedName:(NSString *)qName
{
if ([elementName isEqualToString:#"style"]) {
[rowDict setValue:soapResults forKey:#"style"];
elementFound = NO;
soapResults = nil;
}
if ([elementName isEqualToString:#"thumbnail1"]) {
[rowDict setValue:soapResults forKey:#"thumbnail1"];
elementFound = NO;
soapResults = nil;
}
}
I'm getting wrong format if I'm doing like this please help me out
Thanks in Advance.
create one object class let say SampleObj
*SampleObj.h*
#interface SampleObj : NSObject
{
NSString *style;
NSString *thumbnail;
}
#property (nonatomic,retain) NSString *style,*thumbnail;
*SampleObj.m*
#implementation SampleObj
#synthesize style,thumbnail;
//dealloc your synthesized value;
do parsing like this.
NSArray *array=[theXML componentsSeparatedByString:#"<Rows>"];
for(int i=1;i<[array count];i++)
{
NSArray *arrayRow=[theXML componentsSeparatedByString:#"<Row>"];
for(int j=1;j<[arrayRow count];j++)
{
SampleObj *custobj = [[SampleObj alloc] init];
NSString *str=[arrayRow objectAtIndex:i];
NSArray *arr1=[str componentsSeparatedByString:#"<style>"];
NSString *data=[arr1 objectAtIndex:1];
NSRange ranfrom=[data rangeOfString:#"</style>"];
custobj.style =[data substringToIndex:ranfrom.location];
arr1=[str componentsSeparatedByString:#"< thumbnail >"];
data=[arr1 objectAtIndex:1];
ranfrom=[data rangeOfString:#"</thumbnail >"];
custobj.thumbnail=[data substringToIndex:ranfrom.location];
[yourarray addObject:custobj];
[custobj release]
}
}
if you want to use the object:
SampleObj *custobj = [yourarray objectAtIndex:rowPosition];
NSLog(#"style : %#", custobj.style);
NSLog(#"thumbnail : %#", custobj.thumbnail);
Hope it will help. sorry for any typo.

Doesn't geting data in right form from a 3 level xml service when using NSXMLParser class

I have to parse a 3 level xml which showing below:-
<Navigation>
<parent>
<parentheader>
<![CDATA[ Home ]]>
</parentheader>
<url>my-profile</url>
<Content>...</Content>
</parent>
<parent>
<parentheader>
<![CDATA[ Exhibiton ]]>
</parentheader>
<child>
<childheader>
<![CDATA[ London Exhibition ]]>
</childheader>
<subchild>London Sub
<url>ezone</url>
<Content>...</Content>
</subchild>
</child>
<child>
<childheader>
<![CDATA[ Asia Exhibition ]]>
</childheader>
<url>exhibition-asia-tour</url>
<Content>...</Content>
</child>
</parent>
</Navigation>
i am implementing the NSXMLParser class and delegates method below is the code:-
.h File
#interface NavigationXMLParser : NSObject<NSXMLParserDelegate>
{
NSXMLParser *xmlParser;
NSMutableDictionary *item,*childDict,*subChildDict;
NSMutableArray *nodesArr,*childArr,*subChildArr;
NSMutableString *parent, *url,*child,*subchild;
NSString *currentElement;
BOOL childBool;
}
-(void) fetchXMLData;
#end
.m implementation file code:-
#implementation NavigationXMLParser
-(void) fetchXMLData
{
xmlParser = [[NSXMLParser alloc] initWithContentsOfURL:[NSURL URLWithString:#"http://exhibitors.gastechkorea.com/admin/XMl_APP_Navigation.aspx"]];
[xmlParser setDelegate:self];
[xmlParser setShouldResolveExternalEntities:NO];
[xmlParser setShouldProcessNamespaces:NO];
[xmlParser setShouldReportNamespacePrefixes:NO];
[xmlParser parse];
}
- (void)parserDidStartDocument:(NSXMLParser *)parser
{
nodesArr =[[NSMutableArray alloc] init];
//[sharedSQLiteObj createFavoriteAgendaTableNamed:#"" withField1:#"" withField2:#"" withField3:#"" withField4:#""];
}
- (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError
{
NSString * errorString = [NSString stringWithFormat:#"Unable to download story feed from web site (Error code %i )", [parseError code]];
UIAlertView * errorAlert = [[UIAlertView alloc] initWithTitle:#"Error loading content" message:errorString delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil];
[errorAlert show];
}
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributeDict
{
{
currentElement = [elementName copy];
if ([elementName isEqualToString:#"parent"])
{
item = [[NSMutableDictionary alloc] init];
parent = [[NSMutableString alloc]init];
url = [[NSMutableString alloc]init];
}
else if ([elementName isEqualToString:#"child"])
{
child = [[NSMutableString alloc]init];
childArr = [[NSMutableArray alloc] init];
childDict = [[NSMutableDictionary alloc] init];
url = [[NSMutableString alloc]init];
}
else if ([elementName isEqualToString:#"subchild"])
{
subchild = [[NSMutableString alloc]init];
subChildArr = [[NSMutableArray alloc] init];
subChildDict = [[NSMutableDictionary alloc] init];
url = [[NSMutableString alloc]init];
}
}
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
if ([currentElement isEqualToString:#"parentheader"])
{
[parent appendString:string];
}
else if ([currentElement isEqualToString:#"url"])
{
[url appendString:string];
if (!(child.length ==0))
{
if (subchild.length==0)
{
[childDict setObject:string forKey:#"url"];
}
else{
[subChildDict setObject:string forKey:#"url"];
}
}
}
else if ([currentElement isEqualToString:#"childheader"])
{
[child appendString:string];
if (!(string.length ==0))
{
[childDict setObject:string forKey:#"childheader"];
}
}
else if ([currentElement isEqualToString:#"subchild"])
{
[subchild appendString:[string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]];
if (!([string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]].length ==0))
{
[subChildDict setObject:string forKey:#"subchild"];
[subChildArr addObject:[subChildDict copy]];
[childDict setObject:subChildArr forKey:#"subchild"];
subChildDict = nil;
};
}
}
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
if ([elementName isEqualToString:#"child"])
{
if (!(childDict.count ==0)) {
[childArr addObject:[childDict copy]];
childDict = nil;
}
}
if ([elementName isEqualToString:#"parent"])
{
[item setObject:[parent stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] forKey:#"parent"];
[item setObject:[url stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] forKey:#"url"];
if (!(childArr.count ==0))
{
[item setObject:childArr forKey:#"child"];
}
else
{
[item setObject:#"" forKey:#"child"];
}
//[item setObject:[subchild stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] forKey:#"subchild"];
[nodesArr addObject:[item copy]];
}
}
- (void)parserDidEndDocument:(NSXMLParser *)parser
{
NSLog(#"nodesArr---%#",nodesArr);
}
#end
i got the responsed array from xmlparser class:-
(
{
child = "";
parent = Home;
url = "my-profile";
},
{
child = (
{
childheader = "\n";
}
);
parent = Exhibiton;
url = "exhibition-asia-tour";
},
{
child = (
{
childheader = "\n";
}
);
parent = Calender;
url = "http://www.google.com";
}
)
i am not getting the data in right structure i am wrong some where but didn't find the solution.
i want to get data in the below structure:--
(
{ parent="…."
child=""
url="……"
content="…."
}
{parent ="……"
child = ({ child="……";
subchild= ({
name= "….."
url="….."
content="….."
}
{
…………………….
…..……………..
})
}
{
child="……"
………………
})
)
}
Thanks in advace for your help!!!
it's all about how to make use of the NSXMLParserDelegate methods.
Your XML file can provide you the following informations:
The characters between [CDATA], and the characters between elements.
So you need to implement the following Delegate methods to retrive the informations from the XML file:
This method will get you every string between the [CDATA]:
-(void)parser:(NSXMLParser *)parser foundCDATA:(NSData *)CDATABlock
{
NSMutableString *str=[[NSMutableString alloc]initWithData:CDATABlock encoding:NSUTF8StringEncoding];
[SomeArray addObject:str];
}
And this method will get you every string between elements:
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
// here you need to alloc your string
NSMutableString *parent = [[NSMutableString alloc]init];
[parent appendString:string];
[parent release];
}
after you retrive your data in NSMutableArray and NSMutableString, you can filter your data as you need. But thats how to Parse your XML file.
It's just not that complicated. Good luck ^_^

String Replacement operations in objective c

I got the result which is in the form of
<ZmaterialGroupList><Matkl>001</Matkl><Text>Metal processing</Text></ZmaterialGroupList>
I need to get the result as 001 Metal processing when i apply the string replacement function upon this it give an exception.Please help me.
Added code from comment:
for(int i=0; i<[soapArray.items count]; i++) {
NSString *str = [soapArray.items objectAtIndex:i];
str = [str stringByReplacingOccurrencesOfString:#"<Matkl>" withString:#""];
}
In this way I wrote but I got an exception like
Invalid argument pass at str
You might do better to use NSXMLParser rather than trying to replace pieces of the XML.
If you update your question to include a bit more explaining your code (specifically the code that deals with soapArray), I should be able explain a bit more as to why your code doesn't work as it is.
Using NSXMLParser
It's important to remember that NSXMLParser just reads the data you give it sequentially, it doesn't use a DOM structure.
Setup
Primarily you need to give your parser something to parse! In my example I get a resource from the bundle and convert it to NSData. There is also however another option to initWithContentsOfURL. Make sure you don't forget to set your delegate!
-(void) parse
{
NSString *file = #"myXMLFile.xml";
NSData *data = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:[file stringByDeletingPathExtension] ofType:[file pathExtension]]];
//If you already have a string:
//NSData* data=[xmlString dataUsingEncoding:NSUTF8StringEncoding];
if (data.length == 0)
{
//No data
return nil;
}
...
}
NSXMLParser *parser = [[NSXMLParser alloc] initWithData:data];
parser.delegate = self;
[parser parse];
Parsing
-(void) parserDidStartDocument:(NSXMLParser *)parser
{
//Here you set up any variables you might need while parsing
}
-(void) parserDidEndDocument:(NSXMLParser *)parser
{
//I usually don't have to do anything here but you might need to release some variables here for example.
}
-(void) parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict
{
//Here is where the bulk of the parsing is done if you are using attributes. I prefer to use attributes as it looks cleaner both in the XML file and the parser.
if ([elementName isEqualToString:#"element1"])
{
//Just an example of what you might want to do
int index = [[attributeDict valueForKey:#"attribute1"] intValue];
NSString *name = [attributeDict valueForKey:#"n"];
[exampleDictionary setValue:name forKey:[NSString stringWithFormat:#"%d", index]];
}
if ([elementName isEqualToString:#"element2"])
{
//We need to know that the next piece of information (from foundCharacters) is for element 2
currentElement = ELEMENT_2;
}
}
//If you haven't used attributes you might find that you have a lot of parsing to do here instead.
-(void) parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
//Check what this info is for?
if(currentElement == ELEMENT_2)
{
element2Data = [NSString stringWithString:string];
}
}
-(void) parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
if ([elementName isEqualToString:#"element2"])
{
myObject.somethingThatNeedsElement2 = element2;
}
}
Finishing
After finishing it's a good idea to check that nothing went wrong and free up the parser memory. So in your parse method, add this after the call to [parser parse].
if ([parser parserError] != nil)
{
[[[[UIAlertView alloc] initWithTitle:#"Error parsing XML" message:[[parser parserError] localizedDescription] delegate:nil cancelButtonTitle:#"Done" otherButtonTitles:nil] autorelease] show];
}
[parser release];
Consider using an XML parser or you can use NSScanner:
Example:
NSString *wanted;
NSString *fullMessage;
NSString *xml = #"<ZmaterialGroupList><Matkl>001</Matkl><Text>Metal processing</Text></ZmaterialGroupList>";
NSScanner *scanner = [NSScanner scannerWithString:xml];
[scanner scanUpToString:#"<Matkl>" intoString:nil];
[scanner scanString:#"<Matkl>" intoString:nil];
[scanner scanUpToString:#"</Matkl>" intoString:&wanted];
fullMessage = wanted;
[scanner scanUpToString:#"<Text>" intoString:nil];
[scanner scanString:#"<Text>" intoString:nil];
[scanner scanUpToString:#"</Text>" intoString:&wanted];
fullMessage = [fullMessage stringByAppendingFormat:#" %#", wanted];
NSLog(#"fullMessage: '%#'", fullMessage);
NSLog output:
fullMessage: '001 Metal processing'

How can I improve the XML parsing performance of my iOS code?

This may have been asked a lot but I'm still lost. I need to parse an XML file that I retrieve from Google Reader's API. Basically, it contains objects such as below :
<object>
<string name="id">feed/http://developer.apple.com/news/rss/news.rss</string>
<string name="title">Apple Developer News</string>
<list name="categories">
<object>
<string name="id">user/17999068807557229152/label/Apple</string>
<string name="label">Apple</string>
</object>
</list>
<string name="sortid">DB67AFC7</string>
<number name="firstitemmsec">1317836072018</number>
<string name="htmlUrl">http://developer.apple.com/news/</string>
</object>
I have tried with NSXMLParser and it works but it is really slow. Maybe my code is not the most efficient but still, it can take more than 10 second to parse and save an object into Core Data. I also have taken a look a several other libraries but their use seem a bit complicated and heavy for such a small XML file.
What do you think I should use ?
Thank you.
EDIT
Here the parser code:
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict {
if([elementName isEqualToString:#"list"] && [[attributeDict objectForKey:#"name"] isEqualToString:#"subscriptions"]){
subscriptionListFound = YES;
}
if(subscriptionListFound){
if([elementName isEqualToString:#"list"] && [[attributeDict objectForKey:#"name"] isEqualToString:#"categories"]){
categoryFound = YES;
currentCategoryId = [[[NSMutableString alloc] init] autorelease];
currentCategoryLabel = [[[NSMutableString alloc] init] autorelease];
}
if([elementName isEqualToString:#"object"] && !subscriptionFound && !categoryFound){
subscriptionFound = YES;
currentSubscriptionTitle = [[[NSMutableString alloc] init] autorelease];
currentSubscriptionId = [[[NSMutableString alloc] init] autorelease];
currentSubscriptionHtmlURL = [[[NSMutableString alloc] init] autorelease];
}
if([elementName isEqualToString:#"string"] && [[attributeDict objectForKey:#"name"] isEqualToString:#"id"]){
if(categoryFound){
categoryIdFound = YES;
}
else{
subscriptionIdFound = YES;
}
}
if([elementName isEqualToString:#"string"] && [[attributeDict objectForKey:#"name"] isEqualToString:#"title"]){
subscriptionTitleFound = YES;
}
if([elementName isEqualToString:#"string"] && [[attributeDict objectForKey:#"name"] isEqualToString:#"label"]){
categoryLabelFound = YES;
}
if([elementName isEqualToString:#"string"] && [[attributeDict objectForKey:#"name"] isEqualToString:#"htmlUrl"]){
subscriptionHtmlURLFound = YES;
}
}
}
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
if([elementName isEqualToString:#"list"] && !categoryFound){
subscriptionListFound = NO;
}
if([elementName isEqualToString:#"list"] && categoryFound){
categoryFound = NO;
}
if([elementName isEqualToString:#"object"] && !categoryFound && subscriptionFound){
[self saveSubscription];
[[NSNotificationCenter defaultCenter] postNotificationName:#"currentSubscriptionNotification" object:currentSubscriptionTitle];
subscriptionFound = NO;
}
if([elementName isEqualToString:#"string"]){
if(subscriptionIdFound == YES) {
[currentSubscriptionId appendString:self.currentParsedCharacterData];
subscriptionIdFound = NO;
}
if(subscriptionTitleFound == YES) {
[currentSubscriptionTitle appendString:self.currentParsedCharacterData];
subscriptionTitleFound = NO;
}
if(subscriptionHtmlURLFound == YES) {
[currentSubscriptionHtmlURL appendString:self.currentParsedCharacterData];
subscriptionHtmlURLFound = NO;
}
if(categoryIdFound == YES) {
[currentCategoryId appendString:self.currentParsedCharacterData];
categoryIdFound = NO;
}
if(categoryLabelFound == YES) {
[currentCategoryLabel appendString:self.currentParsedCharacterData];
categoryLabelFound = NO;
}
}
[self.currentParsedCharacterData setString:#""];
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
[self.currentParsedCharacterData appendString:string];
}
Here the code to save by means of CoreData:
- (void) saveSubscription {
NSFetchRequest *fetchRequest = [[[NSFetchRequest alloc] init] autorelease];
[fetchRequest setEntity:
[NSEntityDescription entityForName:#"Group" inManagedObjectContext:context]];
[fetchRequest setPredicate: [NSPredicate predicateWithFormat: #"(id == %#)",self.currentCategoryId]];
[fetchRequest setSortDescriptors: [NSArray arrayWithObject:
[[[NSSortDescriptor alloc] initWithKey: #"id"
ascending:YES] autorelease]]];
NSError *error2 = nil;
NSArray *foundGroups = [context executeFetchRequest:fetchRequest error:&error2];
if ([foundGroups count] > 0) {
self.currentGroupObject = [foundGroups objectAtIndex:0];
}
else {
self.currentGroupObject = [NSEntityDescription insertNewObjectForEntityForName:#"Group" inManagedObjectContext:context];
[self.currentGroupObject setId:self.currentCategoryId];
[self.currentGroupObject setLabel:self.currentCategoryLabel];
}
fetchRequest = [[[NSFetchRequest alloc] init] autorelease];
[fetchRequest setEntity:
[NSEntityDescription entityForName:#"Subscription" inManagedObjectContext:context]];
[fetchRequest setPredicate: [NSPredicate predicateWithFormat: #"(id == %#)", self.currentSubscriptionId]];
[fetchRequest setSortDescriptors: [NSArray arrayWithObject:
[[[NSSortDescriptor alloc] initWithKey: #"id"
ascending:YES] autorelease]]];
error2 = nil;
NSArray *foundSubscriptions = [context executeFetchRequest:fetchRequest error:&error2];
if ([foundSubscriptions count] > 0) {
self.currentSubscriptionObject = [foundSubscriptions objectAtIndex:0];
}
else {
self.currentSubscriptionObject = [NSEntityDescription insertNewObjectForEntityForName:#"Subscription" inManagedObjectContext:context];
[self.currentSubscriptionObject setId:self.currentSubscriptionId];
[self.currentSubscriptionObject setTitle:self.currentSubscriptionTitle];
[self.currentSubscriptionObject setHtmlURL:self.currentSubscriptionHtmlURL];
NSString *faviconURL = [self favIconUrlStringFromURL:self.currentSubscriptionHtmlURL];
NSString *faviconPath = [self saveFavicon:self.currentSubscriptionTitle url:faviconURL];
[self.currentSubscriptionObject setFaviconPath:faviconPath];
[self.currentSubscriptionObject setGroup:self.currentGroupObject];
[self.currentGroupObject addSubscriptionObject:self.currentSubscriptionObject];
}
NSError *error;
if (![context save:&error]) {
NSLog(#"Whoops, couldn't save: %#", [error localizedDescription]);
}
}
Your parsing logic is quite inefficient - you are doing the same test over and over again by saying
if (string and x) do this
if (string and y) do this
if (string and z) do this
Instead of
if (string)
if (x) do this
if (y) do this
if (z) do this
All those unnecessary string comparisons are probably why your parsing is so slow. Same goes for all the object lookups. If you need a value multiple times, get it once and then store it in a variable.
Objective C method calls are relatively slow and can't be optimised away by the compiler, so if the value doesn't change you should call the method once and then store it.
So for example, this:
if([elementName isEqualToString:#"string"] && [[attributeDict objectForKey:#"name"] isEqualToString:#"id"]){
if(categoryFound){
categoryIdFound = YES;
}
else{
subscriptionIdFound = YES;
}
}
if([elementName isEqualToString:#"string"] && [[attributeDict objectForKey:#"name"] isEqualToString:#"title"]){
subscriptionTitleFound = YES;
}
if([elementName isEqualToString:#"string"] && [[attributeDict objectForKey:#"name"] isEqualToString:#"label"]){
categoryLabelFound = YES;
}
if([elementName isEqualToString:#"string"] && [[attributeDict objectForKey:#"name"] isEqualToString:#"htmlUrl"]){
subscriptionHtmlURLFound = YES;
}
Could be rewritten as this:
NSString *name = [attributeDict objectForKey:#"name"];
if([elementName isEqualToString:#"string"])
{
if ([name isEqualToString:#"id"])
{
if(categoryFound){
categoryIdFound = YES;
}
else{
subscriptionIdFound = YES;
}
}
else if ([name isEqualToString:#"title"])
{
subscriptionTitleFound = YES;
}
else if ([name isEqualToString:#"label"])
{
categoryLabelFound = YES;
}
else if ([name isEqualToString:#"htmlUrl"])
{
subscriptionHtmlURLFound = YES;
}
}
Which is way more efficient.
I suggest you to use GDataXML. It's quite simple to use and very fast. For further info you can read at how-to-read-and-write-xml-documents-with-gdataxml.
I've already replied to a similar question on how to read attribute with GDataXML in this Stack Overflow topic: get-xml-response-value-with-gdataxml.
I my opinion, the best library for parsing XML on iOS is TouchXML. It allows you to parse XML using xPaths and has advanced element parsing options. You can also parse XHTML documents with this.
Parsing is very easy:
NSData *xmlData = read your xml file
CXMLDocument *doc = [[CXMLDocument alloc] initWithData:xmlData options:0 error:nil]
NSArray *objects = [doc nodesForXPath:#"//object" error:nil];
for (CXMLElement *object in objects) {
NSArray *children = [object children];
for(CXMLElement *child in children) {
if([[child name] isEqualToString:#"string"]) {
// you are parsing <string> element.
// you can obtain element attribute by:
NSString *name = [[child attributeForName:#"name"] stringValue];
// you can obtain string between <></> tags via:
NSString *value = [child stringValue];
} else if([[child name] isEqualToString:#"list"]) {
// you are parsing <list> element.
} else if ...
}
}
After having developed a few apps with similar needs as yours, I would wholeheartedly recommend the AQToolkit
My usual setup for parsing XML is more or less like this:
Create a separate queue, using either GCD og NSOperationsQueue
Set up a input stream using HTTPMessage and AQGZipInputStream
Example Code:
HTTPMessage *message = [HTTPMessage requestMessageWithMethod:#"GET" url:url version:HTTPVersion1_1];
[message setUseGzipEncoding:YES];
AQGzipInputStream *inputstream = [[AQGzipInputStream alloc] initWithCompressedStream: [message inputStream]];
Hand the stream to a separate parser delegate, which creates a separate NSManagedObjectContext, and merges changes into main NSManagedObjectContext on save (NSManagedObject is not thread safe!)
Example code for initializing the context, and adding notifications for merging:
-(void)parserDidStartDocument:(AQXMLParser *)parser
{
self.ctx=[[NSManagedObjectContext alloc] init];
[self.ctx setMergePolicy: NSMergeByPropertyObjectTrumpMergePolicy];
[self.ctx setPersistentStoreCoordinator: [Database db].persistentStoreCoordinator];
NSNotificationCenter *dnc = [NSNotificationCenter defaultCenter];
[dnc addObserver:self selector:#selector(mergeContextChanges:) name:NSManagedObjectContextDidSaveNotification object:self.ctx];
parsedElements = 0;
}
- (void)mergeContextChanges:(NSNotification *)notification{
SEL selector = #selector(mergeHelper:);
[self performSelectorOnMainThread:selector withObject:notification waitUntilDone:YES];
}
- (void)mergeHelper:(NSNotification*)saveNotification
{
// Fault in all updated objects
NSArray* updates = [[saveNotification.userInfo objectForKey:#"updated"] allObjects];
for (NSInteger i = [updates count]-1; i >= 0; i--)
{
[[[Database db].managedObjectContext objectWithID:[[updates objectAtIndex:i] objectID]] willAccessValueForKey:nil];
}
// Merge
[[Database db].managedObjectContext mergeChangesFromContextDidSaveNotification:saveNotification];
}
In my mind, choosing the right parser is more critical for huge datasets. If your dataset is manageable, then you have a lot to gain from a decent implementation. Using any libxml based parser, and parsing chunks of data as you receive them will give you significant performance increases from parsing data after it is downloaded.
Depending on your datasource, libz might throw Z_BUF_ERROR (at least in the simulator). I've suggested a solution in a pull-request on the AQToolkit, but I'm quite sure there would be even better solutions out there!