Parsing NSXMLNode Attributes in Cocoa - objective-c

Given the following XML file:
<?xml version="1.0" encoding="UTF-8"?>
<application name="foo">
<movie name="tc" english="tce.swf" chinese="tcc.swf" a="1" b="10" c="20" />
<movie name="tl" english="tle.swf" chinese="tlc.swf" d="30" e="40" f="50" />
</application>
How can I access the attributes ("english", "chinese", "name", "a", "b", etc.) and their associated values of the MOVIE nodes? I currently have in Cocoa the ability to traverse these nodes, but I'm at a loss at how I can access the data in the MOVIE NSXMLNodes.
Is there a way I can dump all of the values from each NSXMLNode into a Hashtable and retrieve values that way?
Am using NSXMLDocument and NSXMLNodes.

YES! I answered my own question somehow.
When iterating through the XML document, instead of assigning each child node as an NSXMLNode, assign it as an NSXMLElement. You can then use the attributeForName function, which returns an NSXMLNode, to which you can use stringValue on to get the attribute's value.
Since I'm bad at explaining things, here's my commented code. It might make more sense.
//make sure that the XML doc is valid
if (xmlDoc != nil) {
//get all of the children from the root node into an array
NSArray *children = [[xmlDoc rootElement] children];
int i, count = [children count];
//loop through each child
for (i=0; i < count; i++) {
NSXMLElement *child = [children objectAtIndex:i];
//check to see if the child node is of 'movie' type
if ([child.name isEqual:#"movie"]) {
{
NSXMLNode *movieName = [child attributeForName:#"name"];
NSString *movieValue = [movieName stringValue];
//verify that the value of 'name' attribute of the node equals the value we're looking for, which is 'tc'
if ([movieValue isEqual:#"tc"]) {
//do stuff here if name's value for the movie tag is tc.
}
}
}
}

There are two options. If you continue to use NSXMLDocment and you have an NSXMLNode * for the a movie element, you can do this:
if ([movieNode kind] == NSXMLElementKind)
{
NSXMLElement *movieElement = (NSXMLElement *) movieNode;
NSArray *attributes = [movieElement attributes];
for (NSXMLNode *attribute in attributes)
{
NSLog (#"%# = %#", [attribute name], [attribute stringValue]);
}
}
Otherwise, you can switch to using an NSXMLParser instead. This is an event driven parser that informs a delegate when it has parsed elements (among other things). The method you're after is parser:didStartElement:namespaceURI:qualifiedName:attributes:
- (void) loadXMLFile
{
NSXMLParser *parser = [NSXMLParser parserWithContentsOfURL:#"file:///Users/jkem/test.xml"];
[parser setDelegate:self];
[parser parse];
}
// ... later ...
- (void) parser:(NSXMLParser *)parser
didStartElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qualifiedName
attributes:(NSDictionary *)attributeDict
{
if ([elementName isEqualToString:#"movie"])
{
NSLog (#"%#", [attributeDict objectForKey:#"a"]);
NSLog (#"%d", [[attributeDict objectForKey:#"b"] intValue]);
}
}

Related

XML Parser organizing the elements in the keys

I'm reading the existing elements within my xml file and organizing within a NSMutableDictionary with keys and objects, the structure of my xml file is as follows:
<?xml version="1.0"?>
<root>
<elements>
<element1>something 0</element1>
<element2>somethingelse 0</element2>
<element3>anotherthing 0</element3>
</elements>
<elements>
<element1>something 1</element1>
<element2>somethingelse 1</element2>
<element3>anotherthing 1</element3>
</elements>
<elements>
<element1>something 2</element1>
<element2>somethingelse 2</element2>
<element3>anotherthing 2</element3>
</elements>
</root>
My code is bellow:
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string{
NSString *trim = [string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
if(!currentString){
currentString = [[NSMutableString alloc] init];
}
[currentString appendString:trim];
}
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName{
if([elementName isEqualToString:#"root"]){
currentString = nil;
return;
}
if([elementName isEqualToString:#"elements"]){
key++;
currentString = nil;
}else{
NSString *keyp = [NSString stringWithFormat:#"%d",key];
NSMutableArray *listForelements = elementsArray[keyp];
if (keyp != nil) {
listForelements = [NSMutableArray array];
[elementsArray setValue:currentString forKey:keyp];
}
[listForelements addObject:currentString];
currentString = nil;
return;
}
currentString = nil;
The array elementsArray is returning me the following values:
0 = "anotherthing 0";
1 = "anotherthing 1";
2 = "anotherthing 2";
Instead of:
0 =
"something 0"
"somethingelse 0"
"anotherthing 0"
1 =
"something 1"
"somethingelse 1"
"anotherthing 1"
...
How I can solve this Problem?
After working with a significant amount of XML recently, I'd say your best bet is to look at a library like Ono. Parsing your XML is possible using the built in NSXMLParser and delegate methods but there is an unnecessary amount of things to keep track of compared to the following solution with Ono.
This solution makes use of a single class method that accepts the XML as NSData and returns an NSDictionary. This method has been created in a dedicated parsing class for whatever it's worth.
#import "Ono.h"
+ (NSDictionary *)parseData:(NSData *)data
{
NSError *docError;
ONOXMLDocument *document = [ONOXMLDocument XMLDocumentWithData:data error:&docError];
if (docError) {
// handle document creation error
NSLog(#"%#", docError.localizedDescription);
}
NSMutableDictionary *parsedData = [NSMutableDictionary dictionary];
[document enumerateElementsWithXPath:#"//elements" usingBlock:^(ONOXMLElement *element, NSUInteger idx, BOOL *stop) {
NSString *key = [NSString stringWithFormat:#"%d", idx];
NSMutableArray *items = [NSMutableArray array];
[element.children enumerateObjectsUsingBlock:^(ONOXMLElement *obj, NSUInteger idx, BOOL *stop) {
NSString *trimmedString = [obj.stringValue stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
[items addObject:trimmedString];
}];
[parsedData setObject:[items copy] forKey:key];
// [parsedData setObject:[items copy] forKey:#(idx)]; if you wanted to use an NSNumber as the key
[items removeAllObjects];
}];
return parsedData;
}
We first create an ONOXMLDocument using the +XMLDocumentWithData:error: method. Next we want to check that the document was created correctly and handle an error appropriately.
The NSMutableDictionary is created to store the parsed data and is returned by the method.
With regards to parsing, we first enumerate all of the ONOXMLElement instances that have the XPath elements. Using the XML provided, this gives us 3 items. We then create a key for the contained arrays to be stored by using the index provided by the enumeration block. We could also use an NSNumber as commented in the code.
Next, we create an NSMutableArray that will hold the content strings we intend to parse. We need to enumerate the children (element1, element2, element3) of each elements node. For convenience, we use ONOXMElement as the type of the argument obj so we can use the stringValue property which returns the content of the node.
As above, it's trimmed according to the desired character set, and then we add it to our items array. After enumerating the children of an element, we add a copy of the items array to the dictionary (since it's immutable the contents would change on the next iteration) and then remove all the objects so it is empty for our next iteration.
Once all iterations are complete, we return the dictionary of parsed XML.

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.

XML Parsing issue in Xcode?

I am making a mac application, and I need it to parse a local xml file and display it into a tableView. For some reason, I get a blank row in my tableView, which makes no sense, as it has found characters that are in my xml. Here is my code:
- (void)parserDidStartDocument:(NSXMLParser *)parser{
NSLog(#"found file and started parsing");
}
- (void)parseXMLFileAtURL:(NSString *)URL
{
//you must then convert the path to a proper NSURL or it won't work
NSURL *xmlURL = [NSURL URLWithString:URL];
// here, for some reason you have to use NSClassFromString when trying to alloc NSXMLParser, otherwise you will get an object not found error
// this may be necessary only for the toolchain
rssParser = [[NSXMLParser alloc] initWithContentsOfURL:xmlURL];
// Set self as the delegate of the parser so that it will receive the parser delegate methods callbacks.
[rssParser setDelegate: self];
// Depending on the XML document you're parsing, you may want to enable these features of NSXMLParser.
[rssParser setShouldProcessNamespaces:NO];
[rssParser setShouldReportNamespacePrefixes:NO];
[rssParser setShouldResolveExternalEntities:NO];
[rssParser parse];
}
- (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError {
NSString * errorString = [NSString stringWithFormat:#"Unable to download XML feed (Error code %i )", [parseError code]];
NSLog(#"Error parsing XML: %#", errorString);
}
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict{
//NSLog(#"found this element: %#", elementName);
currentElement = [elementName copy];
if ([elementName isEqualToString:#"event"]) {
// clear out our story item caches...
item = [[NSMutableDictionary alloc] init];
date = [[NSMutableString alloc] init];
opponent = [[NSMutableString alloc] init];
location = [[NSMutableString alloc] init];
time = [[NSMutableString alloc] init];
games = [[NSMutableString alloc] init];
}
}
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName{
NSLog(#"ended element: %#", elementName);
if ([elementName isEqualToString:#"event"]) {
// save values to an item, then store that item into the array...
[item setObject:date forKey:#"date"];
[item setObject:opponent forKey:#"opponent"];
[item setObject:location forKey:#"location"];
[item setObject:time forKey:#"time"];
[item setObject:games forKey:#"games"];
NSMutableArray *stories = [[NSMutableArray alloc] init];
[stories addObject:[item copy]];
[arrayController addObject:[NSDictionary dictionaryWithObjectsAndKeys:
date, #"date",
opponent, #"opponent",
location, #"location",
time, #"time",
games, #"games"
, nil]];
}
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string{
//NSLog(#"found characters: %#", string);
// save the characters for the current item...
if ([date isEqualToString:#"date"]) {
[date appendString:string];
} else if ([opponent isEqualToString:#"opponent"]) {
[opponent appendString:string];
} else if ([location isEqualToString:#"location"]) {
[location appendString:string];
} else if ([time isEqualToString:#"time"]) {
[time appendString:string];
} else if ([games isEqualToString:#"games"]) {
[games appendString:string];
}
}
- (void)parserDidEndDocument:(NSXMLParser *)parser {
NSLog(#"all done!");
[tabelview reloadData];
}
When i remove my adding part, where it adds item to the arraycontroller, and add
[arrayController addObject:stories]; I get a buch of ('s
If there is anything else you need, do not just down-vote, and instead tell me. Thanks!
Here is my xml:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xmlData>
<event>
<date>date here</date>
<opponent>opponent here</opponent>
<location>location here</location>
<time>time here</time>
<games>games here</games>
</event>
<event>
<date>date here</date>
<opponent>opponent here</opponent>
<location>location here</location>
<time>time here</time>
<games>games here</games>
</event>
<event>
<date>date here</date>
<opponent>opponent here</opponent>
<location>location here</location>
<time>time here</time>
<games>games here</games>
</event>
<event>
<date>date here</date>
<opponent>opponent here</opponent>
<location>location here</location>
<time>time here</time>
<games>games here</games>
</event>
</xmlData>
The error is in your parser. Please revise the logic. You are not using your object item when filling your table view array. Also, you are not catching the text in between the XML elements and are not assigning them to the appropriate variables.
Note the following:
When you enter an element, keep track of which element you are in currently
When you find characters you have to fill the appropriate attribute variable according to which element you are in
When you finish the event element, you should add your item with all its filled in keys into your data array.
You need to know how to use array. You are allocating one array stories. But then not using. Please Check your didEndElement method.
Make one class of Event, create .h and .m file and then create properties of your all element and then add whole object of Event class into an array. That array you can in appHandler or Single ton class.
Check this thing. May it help you.

How to avoid the XML parser go to the nested tag, or only allow the outer tag only in Objective C?

Here is a simple XML:
<MSG>
<ID>123<ID>
<Node>
<ID>456<ID>
</Node>
</MSG>
And I got a parser which is subclass of ParentParser and implements NSXMLParserDelegate
The ParentParser is something like this:
- (id)initWithXmlString:(NSString *)xmlString
{
if ( (self = [super init]) ) {
NSString *str = [[NSString alloc] initWithString:xmlString];
self.xml = str;
[str release];
self.storingData = NO;
self.receiveString = [NSMutableString string];
self.elementsToParse = [NSArray array];
}
return self;
}
And this is how I parse the XML:
- (void)parser:(NSXMLParser *)parser
didEndElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName{
if (self.m_oMyObject) {
if (self.storingData) {
NSString *tempStr = [self.receiveString stringByTrimmingCharactersInSet:
[NSCharacterSet whitespaceAndNewlineCharacterSet]];
[self.receiveString setString:#""];
if ([elementName isEqualToString:kID]) {
//Go in twice
}
//============ Code skips ===================
As you can see, the kID, String ID had been parse twice. But I would like to have the 123 only, not the 456, which is inside the <Node> tag, how can I fix it? Thanks.
I don't think that you can make NSXMLParser skip nested elements, but you could keep track of the current level by incrementing an instance variable level in parser:didStartElement:... and decrementing it in parser:didEndElement:....

iOS: Parsing a xml from HTTP using NSXMLParser for

It's first time using NSXMLParser and wondering if you give me some direction of parsing the returned xml from an http request that looks like:
<?xml version="1.0" ?>
<theresponse>
<status>OK</status>
<pricing currency="USD" symbol="$">
<price class="items">24.00</price>
<price class="shipping">6.00</price>
<price class="tax">1.57</price>
</pricing>
</theresponse>
I know the basic of parsing delegate methods, I just want to know what the code would look like in didEndElement/foundCharacters/didStartElement for retreiving above items(currency/items/shipping/tax)? any help greatly appreciated.
This is a little more tricky than some standard NSXMLParser code; Because essentially when you are looking for the "shipping" you want "6.00" but those two pieces of data are returned to you in different delegate methods, which is normal. But usually the element would be named "shipping" so in parser:didEndElement:namespaceURI:qualifiedName: you would automatically have the element name as it was passed into the method.
The solution would seem simple, have a _currentAttributes ivar and in parser:didStartElement:namespaceURI:qualifiedName:attributes: do something like _currentAttributes = attributeDict; and then handle this in the didEndElement: method. However this style would easily break, even on this moderately simple XML.
My way of handling this would be to store the attributes dictionary passed into the didStartElement: and set it in a dictionary as the object for the key of the element name. Combining this style with the standard use of an NSMutableString as a characterBuffer of sorts allows you to put all of your logic into the didEndElement: method.
Side note: I am also quite fond of having my NSXMLParserDelegate classes be NSXMLParser subclasses, as this one is. However the delegate methods would be identical if it were not.
ItemParser.h
#import <Foundation/Foundation.h>
#interface ItemParser : NSXMLParser <NSXMLParserDelegate>
#property (readonly) NSDictionary *itemData;
#end
ItemParser.m
#import "ItemParser.h"
#implementation ItemParser {
NSMutableDictionary *_itemData;
NSMutableDictionary *_attributesByElement;
NSMutableString *_elementString;
}
-(NSDictionary *)itemData{
return [_itemData copy];
}
-(void)parserDidStartDocument:(NSXMLParser *)parser{
_itemData = [[NSMutableDictionary alloc] init];
_attributesByElement = [[NSMutableDictionary alloc] init];
_elementString = [[NSMutableString alloc] init];
}
-(void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict{
// Save the attributes for later.
if (attributeDict) [_attributesByElement setObject:attributeDict forKey:elementName];
// Make sure the elementString is blank and ready to find characters
[_elementString setString:#""];
}
-(void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string{
// Save foundCharacters for later
[_elementString appendString:string];
}
-(void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName{
if ([elementName isEqualToString:#"status"]){
// Element status only contains a string i.e. "OK"
// Simply set a copy of the element value string in the itemData dictionary
[_itemData setObject:[_elementString copy] forKey:elementName];
} else if ([elementName isEqualToString:#"pricing"]) {
// Pricing has an interesting attributes dictionary
// So copy the entries to the item data
NSDictionary *attributes = [_attributesByElement objectForKey:#"pricing"];
[_itemData addEntriesFromDictionary:attributes];
} else if ([elementName isEqualToString:#"price"]) {
// The element price occurs multiple times.
// The meaningful designation occurs in the "class" attribute.
NSString *class = [[_attributesByElement objectForKey:elementName] objectForKey:#"class"];
if (class) [_itemData setObject:[_elementString copy] forKey:class];
}
[_attributesByElement removeObjectForKey:elementName];
[_elementString setString:#""];
}
-(void)parserDidEndDocument:(NSXMLParser *)parser{
_attributesByElement = nil;
_elementString = nil;
}
-(void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError{
NSLog(#"%# with error %#",NSStringFromSelector(_cmd),parseError.localizedDescription);
}
-(BOOL)parse{
self.delegate = self;
return [super parse];
}
#end
And so to test I stored the XML you posted above into a file named "ItemXML.xml". And tested it using this code:
NSURL *url = [[NSBundle mainBundle] URLForResource:#"ItemXML" withExtension:#"xml"];
ItemParser *parser = [[ItemParser alloc] initWithContentsOfURL:url];
[parser parse];
NSLog(#"%#",parser.itemData);
The result I got was:
{
currency = USD;
items = "24.00";
shipping = "6.00";
status = OK;
symbol = "$";
tax = "1.57";
}