I have an XML parser and i want to trim whitespace and new lines before it goes to the app delegate. I know it only works with string, but how to do it for the elements inside the object. More important is it smart to do this or is it better to do a separate trimming
newString =[menu.enable stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
In
- (void) parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName{
NSString *trimmedValue=[currentElementValue stringByTrimmingCharactersInSet:
[NSCharacterSet whitespaceAndNewlineCharacterSet]];
//NSLog(#"current value:%#",currentElementValue);
[aFile setValue:trimmedValue forKey:elementName];
}
it will trim every element before saving it to an object. Here, aFile is object
I've had my run ins with this issue myself, and it's not trivial. SriPriya's solution works but only if there are no newlines in the element content. This:
<foo>
hello
hi
</foo>
would (IIRC) come out as
#"hello\n hi"
when trimmed that way.
The solution I came up with to solve this (and there may be more elegant solutions out there - I'm all ears) is the following:
Presuming you're using NSXMLParser with a delegate class that handles the actual parsing (as in SriPriya's example above), where the -parser:foundCharacters: method is located, you would do:
- (NSString *)removeNewlinesAndTabulation:(NSString *)fromString appending:(BOOL)appending
{
NSArray *a = [fromString componentsSeparatedByString:#"\n"];
NSMutableString *res = [NSMutableString stringWithString:appending ? #" " : #""];
for (NSString *s in a) {
s = [s stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
if (s.length > 0
&& res.length > (appending ? 1 : 0)) [res appendString:#" "];
[res appendString:s];
}
return res;
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
if (! currentElementValue) {
currentElementValue = [[NSMutableString alloc] initWithString:[self removeNewlinesAndTabulation:string appending:NO]];
} else {
[currentElementValue appendString:[self removeNewlinesAndTabulation:string appending:currentElementValue.length > 0]];
}
}
I know it looks like a lot of code for something this simple, but it will correctly turn
<foo>
hi there all
i am typing some
stuff
</foo>
into
#"hi there all i am typing some stuff"
which sounds like what you're looking for.
Related
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 am sending a query to a server and getting response using this code
NSString *urlString = #"myPHPQuery";
NSURL *parserUrl = [[[NSURL alloc] initWithString:urlString] autorelease];
NSXMLParser *parser = [[[NSXMLParser alloc] initWithContentsOfURL:parserUrl] autorelease];
[parser setDelegate:self];
[parser parse];
I can get this type of response
<users>
<username>nothan</username>
<score>1000</score>
</users>
<users>
<username>nothan</username>
<score>1000</score>
</users>
I am using this code to parse the data in Mutable Array
int arrayCount = 0;
NSString *elementname;
NSInteger * count;
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict
{
elementname = elementName;
}
-(void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string{
if([elementname isEqualToString:#"username"])
{
if ( count == 0 )
{
NSLog(#"%#" , string);
[self.playerNames replaceObjectAtIndex:arrayCount withObject:string];
arrayCount = arrayCount + 1;
count = count + 1 ;
}
else
count = 0;
}
else if([elementname isEqualToString:#"score"])
{
if ( count == 0 )
{
NSLog(#"%#" , string);
[self.scores replaceObjectAtIndex:arrayCount withObject:string];
count = count + 1 ;
}
else
count = 0;
}
}
- (void)parserDidEndDocument:(NSXMLParser *)parser
{
for (int i = 0 ; i <10 ; i++)
{
NSLog(#"%d - %#" , i,[playerNames objectAtIndex:i]);
}
}
The problem with this code is, it retrieve only one
<users> .... </users> How can I change this code to make it retrieve more users.
Best Regards
For the sample XML you posted above, the parser ran correctly.
Extensible Markup Language (XML) 1.0 (Fifth Edition) §2.1 Well-Formed XML Documents
There is exactly one element, called the root, or document element
After the first <users>…</users> element is parsed, the document has finished and the parser stops.
To have a list if things in an XML document, you must wrap them in an outer element.
<allusers>
<users>
<username>nothan</username>
<score>1000</score>
</users>
<users>
<username>nothan</username>
<score>1000</score>
</users>
</allusers>
When the parser foundCharacters method is called the int count is checked. If it is zero then the first entry in the array is replaced, and count is incremented. On the next call to this method count is not zero, so nothing is replaced, and then count is set back to zero. For the next call to this method the count is zero.....and so on.
Also bear in mind that when the data being retrieved off the net is large enough to be split into blocks by tcp/ip then the initWithContentsOfURL may not return all of it. It is better to use NSURLConnection to build up the received data as an NSData object and then pass that to the parser. In addition there may be more than one call to foundCharacters for a given element and so the element received should be built up and the placing into the array should be done at the delegate didEndElement method.
I am using NSXML Parser to do parsing in my iPhone app. Now everything works fine except when data comes in French language.
For example, data from server comes as Ch\u00e9rie FM.
Now under the string argument of foundCharacters method, I only get string as 'Ch' rest of the characters don't come up. So finally my string is truncated only to 'Ch' intead of the whole Cherie fm
What could be done?
Code:
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName
attributes:(NSDictionary *)attributeDict
{
if (appDelegate.objPlayer.fromFlickrorRecommend == TRUE)
{
if([elementName isEqualToString:#"outline"] && [[attributeDict valueForKey:#"text"] isEqualToString:#"You may also like"])
{
flagCheck = 1;
}
else if ([elementName isEqualToString:#"outline"] && [[attributeDict valueForKey:#"text"] isEqualToString:#"Genres"])
{
flagCheck = 0;
}
if (flagCheck == 1 && [elementName isEqualToString:#"outline"])
{
if([[attributeDict valueForKey:#"type"] isEqualToString:#"audio"])
{
[appDelegate.objPlayer.recommendDataArray addObject:attributeDict];
}
}
}
else
{
if ([elementName isEqualToString:#"location"])
{
flagCheck = 2;
}
else if ([elementName isEqualToString:#"url"])
{
flagCheck = 3;
}
else if ([elementName isEqualToString:#"name"])
{
flagCheck = 4;
}
}
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
if (flagCheck == 2)
{
flagCheck = -1;
appDelegate.objPlayer.flickrCity = string;
}
else if(flagCheck == 3)
{
flagCheck = -1;
appDelegate.objPlayer.stationURL = string;
}
else if(flagCheck == 4)
{
flagCheck = -1;
appDelegate.playStationName = string;
}
//else if(flagCheck == 0) // change
// {
// appDelegate.playStationName = string;
// }
}
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
//if (flagCheck == 1 && [elementName isEqualToString:#"outline"])
// {
// [appDelegate.objPlayer.recommendDataArray addObject:dataDictionary];
// dataDictionary = nil;
// [dataDictionary release];
// }
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string may be called multiple times so you need to accumulate the characters found into an NSMutableString. There is an example of how to implement this in the Event-Driven XML Programming Guide.
The parser object may send the delegate several
parser:foundCharacters: messages to report the characters of an
element. 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.
Now \u00e9 is UTF-16 for é so the data must be properly encoded to parse past \u00. So if your data was initially a string you can get the data from it like this.
NSString *text = #"<node>Ch\u00e9rie</node>";
//Important or the parser will stop after Ch
NSData *utf16encode = [text dataUsingEncoding:NSUTF16StringEncoding];
NSXMLParser *parser = [[NSXMLParser alloc] initWithData:utf16encode];
Got the Answer:
This link helped while I was going through stackoverflow for the questions similar to my problem.
Why does arrays handle strings containing swedish ÅÄÖ characters by using two or more indexes?
Hope this helps all who are looking out for a solution. :)
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.
I am reading a xml and finaly just need to remove the CDATA Infos in my results
For example: I get:
"<![CDATA[iPhone 4-Rückgaberecht: Deutsche Telekom kulant]]>"
just need "iPhone 4-Rückgaberecht: Deutsche Telekom kulant"
thx
chris
Edit to your answers:
I am not using NSXMLParser (thats the reason I make my own parser)
Found some suggestions with:
- (NSString *)stringByDecodingXMLEntities;
but dont know how to implement. I always get
> YourController may not respond to '-stringByDecodingXMLEntities" <
Ok, i solved it with that:
NSMutableString* resultString;
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)s {
resultString = [[NSMutableString alloc] init];
[resultString appendString:s];
}
- (NSString*)convertEntiesInString:(NSString*)s {
if(s == nil) {
NSLog(#"ERROR : Parameter string is nil");
}
NSString* xmlStr = [NSString stringWithFormat:#"<d>%#</d>", s];
NSData *data = [xmlStr dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES];
NSXMLParser* xmlParse = [[NSXMLParser alloc] initWithData:data];
[xmlParse setDelegate:self];
[xmlParse parse];
NSString* returnStr = [[NSString alloc] initWithFormat:#"%#",resultString];
return returnStr;
}
call: myConvertedString = [self convertEntiesInString:myOriginalString];
use
(void)parser:(NSXMLParser *)parser foundCDATA:(NSData *)CDATABlock
method instead of
(void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
thats it
you could try a regex
replace <!\[CDATA\[(.*)\]\]> with $1
If you already have the String in String format with the you can remove it like so:
//Declare what you wish to remove
NSString * suffixTorRemove = #"<![CDATA[";
NSString * prefixToRemove = #"]]>";
//Now create a new string which uses your existing string and removes the declared occurrences above
NSString * newString = [yourString stringByReplacingOccurrencesOfString:suffixTorRemove withString:#""];
//Now the first part has changed, time to remove the second part
NSString * newString2 = [newString stringByReplacingOccurrencesOfString:prefixTorRemove withString:#""];
Quick and simple :-)