Hey,
I have to parse XML in my iOS app. I took Apple's SeismicXML Sample as my base, but I'm experiencing a really strange behaviour.
These are my parser methodes:
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName
attributes:(NSDictionary *)attributeDict {
if ([elementName isEqualToString:kEntryElementName]) {
Photo *photo = [[Photo alloc] init];
self.currentPhotoObject = photo;
[photo release];
} else if ([elementName isEqualToString:kTitleElementName] ||
[elementName isEqualToString:kLocationElementName] ||
[elementName isEqualToString:kAuthorElementName]) {
accumulatingParsedCharacterData = YES;
[currentParsedCharacterData setString:#""];
}
}
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName {
if ([elementName isEqualToString:kEntryElementName]) {
NSLog(#"Did End - Titel:%#", self.currentPhotoObject.titleText);
NSLog(#"Did End - Location:%#", self.currentPhotoObject.locationText);
NSLog(#"Did End - Author:%#", self.currentPhotoObject.author);
[self.currentParseBatch addObject:self.currentPhotoObject];
parsedPhotosCounter++;
if ([self.currentParseBatch count] >= kMaximumNumberOfPhotosToParse) {
[self performSelectorOnMainThread:#selector(addPhotosToList:)
withObject:self.currentParseBatch
waitUntilDone:NO];
self.currentParseBatch = [NSMutableArray array];
}
}
else if ([elementName isEqualToString:kTitleElementName]) {
self.currentPhotoObject.titleText = self.currentParsedCharacterData;
}
else if ([elementName isEqualToString:kAuthorElementName]) {
self.currentPhotoObject.author = self.currentParsedCharacterData;
}
else if ([elementName isEqualToString:kLocationElementName]) {
self.currentPhotoObject.locationText = self.currentParsedCharacterData;
}
accumulatingParsedCharacterData = NO;
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
if (accumulatingParsedCharacterData) {
// If the current element is one whose content we care about, append 'string'
// to the property that holds the content of the current element.
//
[self.currentParsedCharacterData appendString:string];
}
}
Everything works great, the XML Data comes correctly. The parser parses everything as it should.
The problem is in the parser didEndElement methode.
else if ([elementName isEqualToString:kTitleElementName]) {
self.currentPhotoObject.titleText = self.currentParsedCharacterData;
}
When I get "self.currentPhotoObject.titleText" via NSLog, I get the right parsed Data. But then:
else if ([elementName isEqualToString:kAuthorElementName]) {
self.currentPhotoObject.author = self.currentParsedCharacterData;
}
When I get the NSLog of "self.currentPhotoObject.titleText" and from "self.currentPhotoObject.author" here, both give me the author.
In the third parsed methode it is the same. All three properties (titleText, author and locationText) are the locationText.
I have no idea why .titleText e.g. is changed when the parser sets .author.
I have doublechecked everything at least 10 times and compared it to the SeismicXML sample but I can't find the problem.
Please help me. I'm thankfull for every hint !
Greets Sebastian
ps: My properties in the .m file:
#interface ParseOperation () <NSXMLParserDelegate>
#property (nonatomic, retain) Photo *currentPhotoObject;
#property (nonatomic, retain) NSMutableArray *currentParseBatch;
#property (nonatomic, retain) NSMutableString *currentParsedCharacterData;
#end
#implementation ParseOperation
#synthesize photoData, currentPhotoObject, currentParsedCharacterData, currentParseBatch;
It's because you assign same NSMutableString instance to all this properties.
1) Declare author, titleText, locationText properties as copy to avoid this in future.
2) Make a copy each time you want to return value of NSMutableString or assign it to something
self.currentPhotoObject.titleText = [[self.currentParsedCharacterData copy] autorelease];
Related
I am parsing XML file with RSS from an URL. In the XML, there is a tag as description.
<description>
<![CDATA[
<img width="150" height="150" src="http://www.ipadia.co/wp-content/uploads/2012/02/sayfada-bul-150x150.png" class="attachment-thumbnail wp-post-image" alt="sayfada bul" title="sayfada bul" />Yine çok kişinin dikkatinden kaçan ama faydalı bir araç Bulunduğunuz sayfada arama yapmak bazen çok gerekli olabiliyor Bunun için google arama tabınıkullanacağız Google aramatab ına tıklayin Resimdeki gibi Arama listesinin sonunda bu sayfada bul çıkacak Arama yapmak istediğiniz kelimeyi yazın … Continue reading <span class="meta-nav">→</span>
]]>
</description>
I am using this code as NSString. As you see, there is an image link at the beginning of description tag. I want to take the image link from this string. But when I parse this, It is giving me just the text, not HTML code. When I write this with NSLog, I can't see the image link in it. In short, Xcode doesn't give me the HTML code in the string.
I tried to load this code in UIWebView assuming that it can show the image. But the image didn't be shown in WebView either. Just the text was displayed.
When I look the RSS in browser, I can see the image code in it, but in Xcode, I can't see HTML code, it just gives me the plain text. How can I solve this problem?
Thank you very much.
EDIT: I think the CDATA may cause the issue. How can I challenge with that?
These are my XMLParser methods.
-(void) parser: (NSXMLParser *) parser didStartElement: (NSString *) elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict
{
if([elementName isEqualToString:#"channel"])
if (!ItemA)
{
ItemA = [[NSMutableArray alloc] init];
}
if([elementName isEqualToString:#"item"]){
// NSLog(#"ITEM OLUŞTURULDU.");
anItem= [[Item alloc] init];
}
}
-(void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
if (!currentElementValue)
currentElementValue = [[NSMutableString alloc] initWithString:string];
else
[currentElementValue appendString:string];
// NSLog(#"PRCOSSING VALUE :%#", currentElementValue);
}
-(void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
if([elementName isEqualToString:#"channel"])
return;
if ([elementName isEqualToString:#"item"])
{
// NSLog(#"qqqqqq:%#", anItem.title);
[ItemA addObject:anItem];
counter++;
// NSLog(#"kkk:%i", ItemA.count);
// NSLog(#" -*-*-*-*-* COUNTER: %d",counter);
anItem = nil;
return;
}
if ([elementName isEqualToString:#"title"])
{
anItem.title=currentElementValue;
// NSLog(#"kkk-title:%#", anItem.title);
}
if ([elementName isEqualToString:#"description"])
{
anItem.description=currentElementValue;
// NSLog(#"kkkkkk-desc:%#", anItem.description);
}
if ([elementName isEqualToString:#"link"])
{
anItem.link=currentElementValue;
// NSLog(#"kkkkkk-link:%#", anItem.link);
}
if ([elementName isEqualToString:#"pubDate"])
{
anItem.pubDate=currentElementValue;
// NSLog(#"kkkkkk-pubdate:%#", anItem.pubDate);
}
if ([elementName isEqualToString:#"category"])
{
anItem.category=currentElementValue;
// NSLog(#"kkkkkk-category:%#", anItem.category);
}
currentElementValue = nil;
}
I have an xml file that looks like this:
<result>
<trip duration="03:30">
<takeoff date="2010-06-19" time="18:40" city="Moscow"/>
<landing date="2010-06-19" time="20:10" city="Novgorod"/>
<flight carrier="Rossiya" number="8395" eq="320"/>
<price>13429.00</price>
</trip>
<trip duration="03:40">
<takeoff date="2010-06-19" time="09:20" city="Omsk"/>
<landing date="2010-06-19" time="11:15" city="Paris"/>
<flight carrier="AirFrance" number="1145" eq="320"/>
<price>13229.00</price>
</trip>
<trip duration="03:50">
<takeoff date="2010-06-19" time="07:20" city="Omsk"/>
<landing date="2010-06-19" time="14:15" city="Barcelona"/>
<flight carrier="AirFrance" number="1100" eq="320"/>
<price>13329.00</price>
</trip>
</result>
I'd like to get all the parameters and place them in 3 UITableViewCells. As you can see there are 3 flights and the data about them.
I've been trying to parse it in a TableView but I only managed to get the <price> thing going.
How would you deal with parsing complex-structured parameters within an XML file? I mean how would I get takeoff date parameter and so on and so forth?
As far as I could get you can't apply the tactics I'm using like this one:
- (void) parser:(NSXMLParser *)parser didEndElement:(NSString *)elementname namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
if ([elementname isEqualToString:#"price"])
{
currentTweet.dateCreated = currentNodeContent;
}
Any help would be highly appreciated. Thanks in advance.
This is the code that I frequently use to build a dictionary from an XML file that follows this basic model of some well defined repeating element (in this case, "trip"), and a series of data elements within that, some of which I'm reading the attributes of the tag (in this case, "takeoff", "landing" and "flight"), and others I'm reading the data between the opening and closing tags (in this case, just "price").
I have the following ivars:
#interface XmlParserViewController () <NSXMLParserDelegate>
{
NSMutableArray *trips;
NSMutableDictionary *currentTrip;
NSMutableString *currentElement;
}
#end
And then the code looks like:
- (void)viewDidLoad
{
[super viewDidLoad];
trips = [[NSMutableArray alloc] init];
// I'm getting my xml from my bundle. You get it however you're currently getting it.
NSString *filename = [[NSBundle mainBundle] pathForResource:#"results" ofType:#"xml"];
NSData *data = [NSData dataWithContentsOfFile:filename];
NSXMLParser *parser = [[NSXMLParser alloc] initWithData:data];
parser.delegate = self;
[parser parse];
}
#pragma mark - NSXMLParserDelegate methods
#define kRowElementTag #"trip"
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributeDict
{
NSArray *attributeElementNames = #[#"takeoff", #"landing", #"flight"];
NSArray *foundCharacterElementNames = #[#"price"];
if ([elementName isEqualToString:kRowElementTag])
{
currentTrip = [[NSMutableDictionary alloc] init];
[trips addObject:currentTrip];
if (attributeDict)
[currentTrip setObject:attributeDict forKey:elementName];
}
else if (currentTrip)
{
if ([attributeElementNames containsObject:elementName])
{
if (attributeDict)
[currentTrip setObject:attributeDict forKey:elementName];
}
else if ([foundCharacterElementNames containsObject:elementName] && currentElement == nil)
{
// you can change this to just grab a few fields ... add whatever fields you want to this
currentElement = [[NSMutableString alloc] init];
[currentTrip setObject:currentElement forKey:elementName];
}
}
}
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
if ([elementName isEqualToString:kRowElementTag])
{
currentTrip = nil;
}
else if (currentElement)
{
currentElement = nil;
}
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
if (currentElement)
{
[currentElement appendString:string];
}
}
- (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError
{
NSLog(#"%s error=%#", __FUNCTION__, parseError);
// we should handle the error here
}
- (void)parserDidEndDocument:(NSXMLParser *)parser
{
NSLog(#"%s trips=%#", __FUNCTION__, trips);
// generally I immediately kick off the reload of the table, but maybe
// you want to grok the trips dictionary first.
//
// [self.tableView reloadData];
}
As you can guess, I'm trying to end up with that sort of nested array/dictionary structure that we've gotten used to parsing JSON files. Clearly, I don't like the fact that I have to identify some of the structure of the XML file up front in my code (the fact that the outer array has "trip" tags, that "takeoff", "landing", and "flight" have attributes but "price"` doesn't), etc. But this is a little better than my first attempts at XML parsing that hardcoded values all over the place. Sigh.
You have to manage didStartElement too. This is a sample metacode could be good for you:
-(void)parser:(NSXMLParser*)parser didStartElement:(NSString *)elementName namespaceURI:(NSString*)namespaceURI qualifiedName:(NSString*)qualifiedName attributes:(NSDictionary*)attributeDict {
if([elementName isEqualToString:#"trip"]) {
currentTweet.tripDuration = [attributeDict objectForKey:#"duration"];
} else if ([elementName isEqualToString:#"takeoff"]) {
currentTweet.takeoffDate = [attributeDict objectForKey:#"date"];
currentTweet.takeoffTime = [attributeDict objectForKey:#"time"];
currentTweet.takeoffCity = [attributeDict objectForKey:#"city"];
} else if ([elementName isEqualToString:#"landing"]) {
...............
} else if ...........
}
I have a custom class and I set specific properties of that class based on the xml node name coming in.
if([elementName isEqualToString:#"strap"])
{
parcelObj.strap = nodeContent;
}
else if([elementName isEqualToString:#"owner"])
{
parcelObj.owner=nodeContent;
}
etc. etc...
I wanna know if I can lose the else if's all together and do this (somehow)
parcelObj."elementName" = nodeContent;
Make sense? I am not sure how to pass the "elementName" as the property ya know?
This may be a bit more info then you are looking for, but check out my custom xml parser JHXMLParser class. It parses an xml file and creates an array of dictionaries. Each dictionary has the xml tags name as a key for the text value inside the tag. So, if the xml data looked like this:
<PHOTOS>
<PHOTO>
<ID>177</ID>
<THUMB><![CDATA[http://www.blah.com/thumbs/489650795.jpg]]></THUMB>
<IMAGE><![CDATA[http://www.blah.com/images/489650795.jpg]]></IMAGE>
</PHOTO>
<PHOTO>
<ID>178</ID>
<THUMB><![CDATA[http://www.blah.com/thumbs/489650798.jpg]]></THUMB>
<IMAGE><![CDATA[http://www.blah.com/images/489650798.jpg]]></IMAGE>
</PHOTO>
</PHOTOS>
It would create an array with 2 dictionary elements. The first dictionary would have 3 keys ("ID", "THUMB", "IMAGE") and 3 values ("117", "http://www.blah.com/thumbs/489650795.jpg", and "http://www.blah.com/images/489650795.jpg") respectively.
Here are the important methods (_key, _previousTag, _currentTag, _elementText, and _parsedData are ivars and _key is a string that would be #"PHOTO" for the above XML):
- (void)parserDidStartDocument:(NSXMLParser *)parser {
_parsedData = [[NSMutableArray alloc] init];
}
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributeDict {
_currentTag = [[NSString alloc] initWithString:elementName];
if ([elementName isEqualToString:_key]) {
NSMutableDictionary *tmpDict = [[NSMutableDictionary alloc] init];
[_parsedData addObject:tmpDict];
[tmpDict release];
}
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
if ([_previousTag isEqualToString:_currentTag]) {
[_elementText appendString:[string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]];
} else {
_elementText = [[NSMutableString alloc] initWithString:[string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]];
}
if (![_previousTag isEqualToString:_currentTag]) {
_previousTag = [[NSString alloc] initWithString:_currentTag];
}
}
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
if ([_previousTag isEqualToString:elementName]) {
[[_parsedData lastObject] setObject:_elementText forKey:elementName];
} else {
_previousTag = [[NSString alloc] initWithFormat:#""];
}
}
Ok, my problem is that whenever i collect data from the parser into an array where the string contains Swedish ÅÄÖ characters. In my example the
[schemaInfoArray objectAtIndex:3]
is supposed to be #"Lördag" but is saved as #"L" and the
[schemaInfoArray objectAtIndex:4]
contains the rest of the string that gets presented as
#"ördag"
-(void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
tempStrang = string;
[schemaInfoArray insertObject:tempStrang atIndex:uppraknare];
uppraknare++;
}
-(void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
if ( [elementName isEqualToString:#"schemaInfo"] )
{
}
if ( [elementName isEqualToString:#"modfromtid"] )
{
frommodarbtid = [schemaInfoArray objectAtIndex:0];
}
if ([elementName isEqualToString:#"modtomtid"] )
{
tommodarbtid = [schemaInfoArray objectAtIndex:1];
}
if ([elementName isEqualToString:#"modrast"] )
{
modrast = [schemaInfoArray objectAtIndex:2];
}
if ([elementName isEqualToString:#"benamning"] )
{
benamning = [schemaInfoArray objectAtIndex:3];
}
if ([elementName isEqualToString:#"fromnormarb"] )
{
fromnormarbtid = [schemaInfoArray objectAtIndex:4];
}
if ([elementName isEqualToString:#"tomnormarb"] )
{
tomnormarbtid = [schemaInfoArray objectAtIndex:5];
}
if ([elementName isEqualToString:#"rast"] )
{
normrast = [schemaInfoArray objectAtIndex:6];
}
}
Does anyone have any thoughts about how to actually get #"Lördag" to be saved into ONE index instead of getting split into several indexes? This really destroys the structure of things that is supposed to be presented.
This is a documented design choice from Apple, and has nothing to do with Swedish characters:
Because string may be only part of
the total character content for the
current element, you should append it
to the current accumulation of
characters until the element changes.
So you should do just as they say: use a NSMutableString to accumulate the results, and when the element changes, save the buffer to a permanent, (preferrably) immutable NSString.
As requested, here's an example. It was written without any kind of IDE, so chances are that it'll work, but there's no guarantee that it will either compile or work.
#interface Foo : NSObject<NSXMLParserDelegate> {
NSMutableString* accumulator;
NSMutableArray* schemaInfoArray;
int uppraknare; // whatever 'uppraknare' means
}
/* snip */
#end
#implementation Foo
-(void)parser:(NSXMLParser*)parser foundCharacters:(NSString*)string
{
// only accumulate characters, until we get notified that we went through
// the whole XML element
[accumulator appendString:string];
}
-(void)parser:(NSXMLParser*)parser didEndElement:(NSString*)elementName namespaceURI:(NSString*)nsuri qualifiedName:(NSString*)qName
{
// we went through the whole element! time to save!
NSString* immutableResult = [accumulator copy];
[schemaInfoArray insertObject:immutableResult atIndex:uppraknare];
uppraknare++;
[immutableResult release];
// clear the accumulator for the next element
[accumulator deleteCharactersInRange:NSMakeRange(0, [accumulator length])];
/* the rest of your code here */
}
#end
-(void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string is not guaranteed to contain the complete contents of the string. You need to have a class instance variable that is a NSMutableString that can append all of foundCharacters between the calls to didStartElement and didEndElement. Inside of didEndElement add the the string to the schemaInfoArray.
OK this should be an easy one but still im breaking my head here:
In my root view controller I have a NSString called "entry" and is working perfectly. I NSLogged it and it works.
I have another class called ´ParseOperation´ and in it i have a NSStringcalled "localEntry" and im trying to send to "ParseOperation" the variable "entry" from "RootViewController" this is my RootViewController code for that:
RootViewController.m
ParseOperation *parseOperation = [[ParseOperation alloc] init];
parseOperation.localEntry = entry;
It just doesn't work. If I NSLog in my ParseOperation.m it returns "null", but if i do it on my RootViewController it returns the correct variable. and yes i did imported the ParseOperation.h
Here is the ParseOperation code (only the part that uses localEntry):
ParseOperation.h
#interface ParseOperation : NSOperation <NSXMLParserDelegate>
{
NSString *localEntry;
}
#property (nonatomic, retain) NSString *localEntry;
#end
ParseOperation.m
#synthesize localEntry;
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName
attributes:(NSDictionary *)attributeDict
{
//NSLog(#"entrada %#",localEntry);
if ([elementName isEqualToString:localEntry])
{
self.workingEntry = [[[AppRecord alloc] init] autorelease];
}
storingCharacterData = [elementsToParse containsObject:elementName];
}
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName
{
if (self.workingEntry)
{
if (storingCharacterData)
{
NSString *trimmedString = [workingPropertyString stringByTrimmingCharactersInSet:
[NSCharacterSet whitespaceAndNewlineCharacterSet]];
[workingPropertyString setString:#""]; // clear the string for next time
if ([elementName isEqualToString:kIDStr])
{
self.workingEntry.appURLString = trimmedString;
}
else if ([elementName isEqualToString:kNameStr])
{
self.workingEntry.appName = trimmedString;
}
else if ([elementName isEqualToString:kImageStr])
{
self.workingEntry.imageURLString = trimmedString;
}
else if ([elementName isEqualToString:kArtistStr])
{
self.workingEntry.artist = trimmedString;
}
}
else if ([elementName isEqualToString:localEntry])
{
[self.workingArray addObject:self.workingEntry];
self.workingEntry = nil;
}
}
}
THANKS!
In all likelihood, rootViewController is nil. When you declare and synthesize a property, it only creates the getter/setter methods for you. It does not initialize the variable to anything.
Since objective-c allows you to message nil, you won't crash when you write:
NSString *localentry = rootViewController.entry;
Messaging nil just returns nil. So if rootViewController is nil, then localentry will be nil as well.
Make sure you're actually setting rootViewController for this class. For example,
ParseOperation *myOperation = [[ParseOperation alloc] init];
[myOperation setRootViewController:rootViewController];
Or, make sure you've established the outlet connection in Interface Builder. In any case, I'd suspect rootViewController is nil. (You can test this with NSLog statements).
Are you sure, since it is an IBOutlet, that you connected to it in interface builder?
To answer my own question I just had to connect the viewController and the ParseOperation programmatically by adding the following to my header in the parseOperation:
#class RootViewController;
RootViewController *rootViewController;
#property (nonatomic, retain) IBOutlet RootViewController *rootViewController;
And the following on the m file of the ParseOperation:
#import "RootViewController.h"
rootViewController = [[RootViewController alloc]init];
After that in the parse operation I just declared:
localEntry= rootViewContoller.entry;