Parsing XML for iPhone (aMule downloads/ search results) - iphone

I am currently working on a software to control aMule status of my server through the iPhone,
i created a socket that spits out xml which should be parsed out, but because NSXMLParser is event-drive, i'm having problems in understanding how this could work...
I thought of this type of XML structure, if you have ideas of a better way to structure it please tell me!! :D
<root type="donwloads"> <-- specifies downloads or search results
<file name="Ubuntu_9_10.iso" status="[11,6%]" />
<file name="Fedora 12.iso" status="[56,2%]" />
</root>
What i was thinking is, as i want to put this in a tableview, most probably i will need a NSMutableArray with lots of NSDictionaries based on the results, every dict should be a file.. what do you guys propose?? how should i handle this situation?
Thanks

Write a parser class that turns nodes into Core Data managed objects and saves them to the managed object context, when a parser callback event is fired.
Use an NSFetchedResultsController to access the Core Data store. As the managed objects come in and are saved, the results controller updates the table view with whatever results it fetches.

An NSMutableArray of NSDictionary seems like a reasonable approach for your in-memory data structure.
You'll basically have a series of callbacks that build up that array as NSXMLParser runs through your XML file:
- (void) parseXML:(NSString *) filename {
NSURL *xmlURL = [NSURL fileURLWithPath:filename];
NSXMLParser *xmlParser = [[NSXMLParser alloc] initWithContentsOfURL:xmlURL];
[xmlParser setDelegate:self];
[xmlParser parse];
// Check for errors.
NSError *errorCode = [xmlParser parserError];
if (errorCode) {
// handle error here
NSLog(#"%#", [errorCode localizedDescription]);
}
[xmlParser release];
}
And your main delegate:
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)qualifiedName attributes:(NSDictionary *)attributeDict
{
// If certain elements are found initialize the object
if ([elementName isEqualToString:"#file"]) {
NSMutableDictionary *currentFile = [[NSMutableDictionary alloc] init];
// Look through the attributes add stuff to your dictionary
// Add it to your array.
}
}
Since all of your data is returned in attributes you can do it this way. Otherwise you'd need to store the file and build it up (the foundCharacters delegate) finally adding it to your array when the file's tag occurs in the didEndElement delegate.

Thanks a lot for your answers :D fortunately i resolved the problem 10 minutes after :D
ill post what i did:
XML:
<root>
<downloads>
<file type="text" name="fdsdf" />
<file type="text" name="sdfsdfssds" />
</downloads>
</root>
NSXMLParser delegates:
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributeDict{
if([elementName isEqualToString:#"downloads"] || [elementName isEqualToString:#"results"]){
NSLog(#"starting or downloads or results");
if(xmlArray){
xmlArray= nil;
}
self.xmlArray= [[NSMutableArray alloc] init];
}
else if([elementName isEqualToString:#"file"]){
NSLog(#"found file...");
[self.xmlArray addObject:attributeDict];
}
}
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName{
if([elementName isEqualToString:#"downloads"] || [elementName isEqualToString:#"results"]){
if([elementName isEqualToString:#"downloads"]){
NSLog(#"downloads found: %#... reloading table", xmlArray);
}
}
}
I hope this can possibly help someone which has my same problem :D

Related

NSXMLParser replaces é characters with \U00e9

I'm using an xml parser, NSXMLParser to parse asn xml an return some url in an NSMutableArray.Everything is giong great except the fact that the french é is replaced by \U00e9.
Here is my code:
- (void)parseXMLFileAtURL:(NSString *)URL
{
NSURL *xmlURL = [NSURL URLWithString:URL];
xmlParser = [NSXMLParser alloc] initWithContentsOfURL:xmlURL];
// Set self as the delegate of the parser so that it will receive the parser delegate methods callbacks.
[xmlParser setDelegate:self];
// Depending on the XML document you're parsing, you may want to enable these features of NSXMLParser.
[xmlParser setShouldProcessNamespaces:NO];
[xmlParser setShouldReportNamespacePrefixes:NO];
[xmlParser setShouldResolveExternalEntities:NO];
[xmlParser parse];
}
- (void) parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict{
currentElement = [elementName copy];
if ([elementName isEqualToString:#"catalogue"]) {
// clear out our story item caches...
currentCatalogue = [[Catalogue alloc] init];
}
if ([elementName isEqualToString:#"partenaire"]) {
// clear out our story item caches...
currentPartenaire = [[Partenaire alloc] init];
}
}
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName{
if ([elementName isEqualToString:#"catalogue"]) {
// Add currentCatalogue to array
[catalogueList addObject: currentCatalogue];
NSString *urls=[catalogueList valueForKey:#"url"];
NSLog(#"Current catalogue: urls=%#", urls);
}
if ([elementName isEqualToString:#"partenaire"]) {
// Add currentPartenaire to array
[partenaireList addObject: currentPartenaire];
/*NSLog(#"Current partenaire: raison_sociale=%#, lat=%#, lng=%#", currentPartenaire.raison_sociale, currentPartenaire.lat, currentPartenaire.lng);*/
}
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string{
// Catalogue setup
if ([currentElement isEqualToString:#"id_model"])
currentCatalogue.id_model = string;
if ([currentElement isEqualToString:#"url"])
{
if (currentCatalogue.url)
{
currentCatalogue.url = [NSString stringWithFormat: #"%#%#", currentCatalogue.url, string];
// NSLog(#"Valoare url in data handler %#", currentCatalogue.url);
}
else
currentCatalogue.url = string;
}
}
Anyone any idea how to fix this?
TESTED CODE : 100 % WORKS
NSString* inputString =[NSString stringWithFormat:#"\40_TTRS_Coup\u00e9_TTRS_Roadster_Tarifs_20110428.pdf"];
NSLog(#"inputString is: %# \n\n",inputString);
OUTPUT:
inputString is: _TTRS_Coupé_TTRS_Roadster_Tarifs_20110428.pdf
Assuming your XML has an explicit encoding, as follows:
<?xml version="1.0" encoding="UTF-8"?>
then there are 2 problems you should address first. These may or may not fix your direct problem, but if not they will make it easier to narrow in on the problem you are seeing. Here are the problems you should fix:
Remove the code that calls 2 different initializer methods on a single NSXMLParser instance. That will have undefined results and its impossible to know what is going on until you fix that.
Change how you implement the parser:foundCharacters: method. As NSXMLParser documentations states, this may be called multiple times for a given set of characters within an XML element. Instead of just accepting the string and storing its value away, your delegate class should have a mutable character buffer that you append to each time foundCharacters gets called. Then in parser:didElementEnd you can grab the contents of the buffer and do what you need to with that value.
Try it out with these fixes and see if it works. If not, update your post with a corrected version of your code and it might be more obvious what the problem is.

Parsing XML file with NSXMLParser - getting values

I've got a XML file which contains some data I would like to use:
<?xml version="1.0" encoding="UTF-8" ?>
<items>
<item name="product" price="19.95" where="store">
This is the first product.
</item>
<item name="product2" price="39.95" where="online">
This is the second product.
</item>
<item name="product3" price="99.95" where="garagesale">
This is the third product.
</item>
</items>
If I made 4 arrays, one for the name, one for the price, one for where it was bought and one for its description, how would I get the data into the arrays?
I figured using NSXMLParser, but couldn't get name, price, where or the description.
I'm stuck on how to do this.
Any help appreciated.
First you need to create an object that does the parsing. It will instatiate the NSXMLParser instance, set itself as the delegate for the parser and then call the parse message. It can also be responsible for storing your four result arrays:
NSXMLParser * parser = [[NSXMLParser alloc] initWithData:_data];
[parser setDelegate:self];
BOOL result = [parser parse];
The message you are most interested in implementing in your delegate objects is didStartElement. This guy gets called for each element in your XML file. In this callback you can add your name, price & where attributes to their respective arrays.
- (void)parser:(NSXMLParser *)parser
didStartElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qualifiedName
attributes:(NSDictionary *)attributeDict
{
// just do this for item elements
if(![elementName isEqual:#"item"])
return;
// then you just need to grab each of your attributes
NSString * name = [attributeDict objectForKey:#"name"];
// ... get the other attributes
// when we have our various attributes, you can add them to your arrays
[m_NameArray addObject:name];
// ... same for the other arrays
}
To get the value between the tags (e.g. "This is the first product.") you can override - (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
in the follwing method
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributeDict {
if([elementName isEqualToString:#"item"]) {
NSString *name=[attributeDict objectForKey:#"name"];
NSString *price=[attributeDict objectForKey:#"price"];
NSString *where=[attributeDict objectForKey:#"where"];
}
}
you have to consider the dictionary of item tag as an array and three tag (name,price and where)as the object at index 0,1,2

NSXMLParser Memory Leak from Second Parse Call

Update: While the Leaks instrument points to the first call to [parser parse];, I have identified that the leak only occurs when I make this call a second time. I don't see anything in the NSXMLParser class reference or documentation which indicates that one can't re-parse. Am I missing something? Should I create a new NSXMLParser for each time that I wish to parse the document?
I am currently trying to track down a leak in my xml parsing code for an iPhone app. I've searched through other posts on leaks with NSXMLParser, but haven't found any answers that I could use, so I would really appreciate help.
The instruments Leaks tool is detecting a leak in the following code:
NSData* data = [NSData dataWithContentsOfFile: [[NSBundle mainBundle] pathForResource: #"collisionGraph" ofType: #"graphml"]];
NSXMLParser* parser = [[[NSXMLParser alloc] initWithData: data] autorelease];
[parser setDelegate:self];
[parser parse]; <<<<< leak here according to Leaks
self.currentPass++;
...
[parser parse];
self.currentPass++;
I removed all of the code from my delegate callback methods, but this didn't stop the leak.
-(void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qualifiedName
attributes:(NSDictionary *)attributeDict
{
}
-(void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
}
-(void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName
{
}
Here is the view in Instruments http://i22.photobucket.com/albums/b311/Erithil/ParserLeak.png (linked because I don't have the rep to post images).
I'm really stumped by this, so any suggestions are appreciated. Thanks in advance.
It could be a leak within NSXMLParser (if e.g. xmlCleanupParser() is not called on the underlying libxml) or reusable memory mistakenly flagged as a leak. I think it's what typically happens with parsers due to them reusing (and not properly releasing?) their allocated memory.

how to parse xml

i am having an output xml which looks something like this,
<?xml version="1.0"?>
<root>
<apple><apple1>A</apple1></apple>
<ball><ball1>B</ball1></ball>
<cat><cat1>C</cat1></cat>
<dog><dog1>D</dog1></dog>
<ele><ele1>E</ele1></ele>
<root>
i am using NSXmlParser, i m confused, is tgis a gud xml structure?>//
secondly i dont knw how to parse this xml , i mean how should i write condition in parser didSTart and didEndElement ???
i m not to able properly navigate through each node, from apple to ele. i just want to assign the returned value of say apple to a string.
same for each node.
i m so confused in if / else condition.
Suggestions are always appreciated
regards
You need to have exactly one root element, like so:
<?xml version="1.0"?>
<root>
<apple><apple1>A</apple1></apple>
<ball><ball1>B</ball1></ball>
<cat><cat1>C</cat1></cat>
<dog><dog1>D</dog1></dog>
<ele><ele1>E</ele1></ele>
</root>
I don't know about the specifics of the parser you are using, but this is an intrinsic XML issue.
You can use below sample code.
resultArray = [[NSMutableArray alloc] init];
NSXMLParser *parser = [[NSXMLParser alloc] initWithData:webData];
[parser setDelegate:self];
[parser parse];
delegate methods to parse XML data
- (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError{
NSLog(#"error occured %#",parseError);
}
- (void)parserDidStartDocument:(NSXMLParser *)parser{
NSLog(#"Did Start Document");
}
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributeDict{
self.currentElement = elementName;
if([elementName isEqualToString:#"root"] && !itemDictionary){
self.itemDictionary = [[NSMutableDictionary alloc] init];
}
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string{
[itemDictionary setObject:string forKey:currentElement];
}
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName{
if([elementName isEqualToString:#"root"] && itemDictionary)){
[resultArray addObject:itemDictionary];
currentItem = nil;
}
}
- (void)parserDidEndDocument:(NSXMLParser *)parser{
NSLog("After parsing : %#", resultArray);
}
itemDictionary is a NSMutableDictionary.
I have edited my previous answer. (currentItem is itemDictionary).
In this sample code, itemDictionary is to store all values with key (when we parsing the data).
itemDictionary - {
apple1 = A
ball1 = B
cat1 = C
dog1 = D
ele1 = E
}
//--------------------------
<?xml version="1.0"?>
<root>
<apple id='10'><apple1>A</apple1></apple>
<ball><ball1>B</ball1></ball>
<cat><cat1>C</cat1></cat>
<dog><dog1>D</dog1></dog>
<ele><ele1>E</ele1></ele>
<root>
In didStartElement method, you have to use below sample code.
if([elementName isEqualToString:#"apple"]){
NSString *attributeValue = [attributeDict objectForKey:#"id"];
NSLog("Attribute value : %#", attributeValue);
}
I hope, it will help you.

How do I parse an NSString containing XML in Objective-C?

In my iPhone application, I have the following NSString:
NSString *myxml=#"<students>
<student><name>Raju</name><age>25</age><address>abcd</address>
</student></students>";
How would I parse the XML content of this string?
Download:
https://github.com/bcaccinolo/XML-to-NSDictionary
Then you simply do :
NSDictionary *dic = [XMLReader dictionaryForXMLString:myxml error:nil];
Result is a NSDictionary *dic with dictionaries, arrays and strings inside, depending of the XML:
{
students = {
student = {
address = abcd;
age = 25;
name = Raju;
};
};
}
You should use the NSXMLParser class
Here's a link to the documentation for that class:
http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSXMLParser_Class/Reference/Reference.html
Your code should look something like this:
#implementation MyClass
- (void)startParsing {
NSData *xmlData = (Get XML as NSData)
NSXMLParser *parser = [[[NSXMLParser alloc] initWithData:xmlData] autorelease];
[parser setDelegate:self];
[parser parse];
}
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributeDict {
NSLog(#"Started %#", elementName);
}
Another answer is: Don't use XML. Use a plist instead, which is written using XML but more easily parsable in Objective-C into distinct data types (NSArray for example has a method to convert a file or NSData plist into an NSArray).
Like #Jon Hess mentioned, just create a wrapping class for the "optional" methods of the NSXMLParserDelegate. These methods help you separate the tasks that you might find useful when you parse your xml.
One really good online journal file I found is Elegant XML parsing with Objective-C. Phil Nash really took his time to show the basics of the parsing options at your reach. It can take a new programmer and guide him/her through the whole setup.
Loading the xml can be a modification of #Jon Hess method.
You can setup the:
-(void)parser:(NSXMLParser *)parser
didStartElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName
attributes:(NSDictionary *)attributeDict{
}
to handle events on certain elements.
Also implement the:
-(void)parser:(NSXMLParser *)parser
foundCharacters:(NSString *)string {
}
to place the strings found into a collection of objects.
I think the best equivalent to XMLDocument is AbacigilXQ Library. You should look at it. I'm using it.
http://code.google.com/p/abacigilxq-library/