How to handle HTML Strings in Cocoa Touch - cocoa-touch

I'm using a RSS Reader and works fine when I tap the UITableViewCell to load the <link> either in a UIWebView or to open Safari on that link.
But I really want to learn how to load the Topic content into the application instead showing the entire site or jump to Safari
In the RSS feed per each <item> there is a <body> tag (and a <Description> that contains the same but encoded) that contains the topic content, like the image below shows:
alt text http://www.balexandre.com/temp/2010-04-13_0953.png
So, instead of catching the <link> I'm assigning the <body>. Problem is that it does not work correctly :-(
for this example I only get the content until the first <br> nothing more.
I'm using a NSString as I would use in C#, should I use any other object, is there a better object to use on such data?
Should I use an UITextView to load this information (as it has scroll already) or I should use a UIWebView instead?
Thank you.
added
- (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:#"item"]) {
// clear out our story item caches...
item = [[NSMutableDictionary alloc] init];
currentTitle = [[NSMutableString alloc] init];
currentDate = [[NSMutableString alloc] init];
currentSummary = [[NSMutableString alloc] init];
currentLink = [[NSMutableString alloc] init];
currentBody = [NSMutableString new]; // added by me
currentCreator = [NSMutableString new]; // added by me
}
}
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName{
//NSLog(#"ended element: %#", elementName);
if ([elementName isEqualToString:#"item"]) {
// save values to an item, then store that item into the array...
[item setObject:currentTitle forKey:#"title"];
[item setObject:currentLink forKey:#"link"];
[item setObject:currentSummary forKey:#"summary"];
[item setObject:currentDate forKey:#"date"];
[item setObject:currentBody forKey:#"body"]; // <----
[item setObject:currentCreator forKey:#"dc:creator"];
[stories addObject:[item copy]];
NSLog(#"adding story: %#", currentTitle);
}
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string{
//NSLog(#"found characters: %#", string);
// save the characters for the current item...
if ([currentElement isEqualToString:#"title"]) {
[currentTitle appendString:string];
} else if ([currentElement isEqualToString:#"link"]) {
[currentLink appendString:string];
} else if ([currentElement isEqualToString:#"summary"]) {
[currentSummary appendString:string];
} else if ([currentElement isEqualToString:#"pubDate"]) {
[currentDate appendString:string];
} else if ([currentElement isEqualToString:#"body"]) {
[currentBody appendString:string]; // <----
} else if ([currentElement isEqualToString:#"dc:creator"]) {
[currentCreator appendString:string];
}
}
and to pass to my WebBrowser View I have:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// Navigation logic
int storyIndex = [indexPath indexAtPosition: [indexPath length] - 1];
NSString * storyContent = [[stories objectAtIndex: storyIndex] objectForKey: #"body"];
// load web view
[self showWebPage:storyContent]; // <-- Here (storyContent variable) I only have the content until the first <br> :-(
}

Take a look at the SeismicXML sample project from Apple. It gives you a general idea of how to use NSXMLParser, and is also written to parse the XML data in its own thread to allow the user to still interact with the UI. If you are looking for a nested tag (i.e. <body> within <item>), you will want a flag to tell if you are currently within the <item> tag or not. Otherwise you will parse all <body> tags.
To address your second question, it depends on how you want to present this information to the user. If you want the text styled, you will need to use a UIWebView. Otherwise you can strip out what you need and have that spread out through a UITableView, or a custom view you create in Interface Builder.
Edit: After seeing your last comment, you need to create a flag (i.e. insideBody) and check for that inside of foundCharacters:
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string{
//NSLog(#"found characters: %#", string);
// save the characters for the current item...
if(insideBody == YES) {
[currentBody appendString:string];
} else if ([currentElement isEqualToString:#"title"]) {
[currentTitle appendString:string];
} else if ([currentElement isEqualToString:#"link"]) {
[currentLink appendString:string];
} else if ([currentElement isEqualToString:#"summary"]) {
[currentSummary appendString:string];
} else if ([currentElement isEqualToString:#"pubDate"]) {
[currentDate appendString:string];
} else if ([currentElement isEqualToString:#"dc:creator"]) {
[currentCreator appendString:string];
}
}
You will probably have to do the same in didStartElement: and didEndElement:, as getting data from foundCharacters: will only give you the content of tags and not the tags themselves.

Use NSXMLParser to detect the <body> and </body> tags, and just grab everything between them into a string.
Now that I can see your code, I can see your bug. When you encounter the <br /> tag you change the currentElement to #"br", which means that you stop adding characters to currentBody.

Related

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.

Parsing XML files with special characters

I try to parse a list of persons and pollute a UITableView with the names. But the persons I want to parse have special character (ä, ö, ü). Now if I start parsing the name "Gött" it is "ött" afterwards. Really strange, any ideas? Thanks a lot!
-(id) loadXMLByURL:(NSString *)urlString
{
tweets = [[NSMutableArray alloc] init];
NSURL *url = [NSURL URLWithString:urlString];
NSData *data = [[NSData alloc] initWithContentsOfURL:url];
parser = [[NSXMLParser alloc] initWithData:data];
parser.delegate = self;
[parser parse];
return self;
}
- (void) parser:(NSXMLParser *)parser didStartElement:(NSString *)elementname namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict
{
if ([elementname isEqualToString:#"lehrer"])
{
currentTweet = [Tweet alloc];
}
}
- (void) parser:(NSXMLParser *)parser didEndElement:(NSString *)elementname namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
if ([elementname isEqualToString:#"name"])
{
currentTweet.content = currentNodeContent;
}
if ([elementname isEqualToString:#"vorname"])
{
currentTweet.vorname = currentNodeContent;
}
if ([elementname isEqualToString:#"created_at"])
{
currentTweet.dateCreated = currentNodeContent;
}
if ([elementname isEqualToString:#"lehrer"])
{
[tweets addObject:currentTweet];
[currentTweet release];
currentTweet = nil;
[currentNodeContent release];
currentNodeContent = nil;
}
}
- (void) parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
currentNodeContent = (NSMutableString *) [string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
}
- (void) dealloc
{
[parser release];
[super dealloc];
}
#end
This is normal behaviour - parser:foundCharacters can be called multiple times for one string (and tends to be for accented characters). Your string isn't complete until the end of the element, so store them and use the full string when you get to the end of the block.
It is in the documentation for foundCharacters
Apple developer docs on NSXMLParser
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.
Edit as per question:
the code in general is fine but in the characters function, do
- (void) parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
if(nil == currentNodeContent)
currentNodeContent = [[NSMutableString alloc] initWithString:string];
else
[currentNodeContent appendString:string];
}
then in both didStart and didEnd call a method that checks to see if the string is nil, do whatever it was you were going to do with it in the first place, and then release the string (and null it).
The string is ended at both the start of a new element (ie, the text before an opening <), and at the end (the bit of text before the
As per Woody's answer, this is completely expected. You will need to concatenate the strings from the multiple - (void) parser:(NSXMLParser *)parser foundCharacters:(NSString *)string calls.
The correct way to do this is as follows:
- (void) parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
if (currentElementContent== nil)
currentElementContent = [[NSMutableString alloc] initWithString:string];
else
currentElementContent = [currentElementContent stringByAppendingString:string];
}
You should always be setting the currentElementContent to nil at the very end of the didEndElement method anyway. An example for this is below:
- (void) parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
// Do what you want with the parser here
// Set element content variable to nil
currentElementContent = nil;
}
You may need to replace the variable: currentElementContent with whatever variable you have used in your parser to house the content found between the start and end tags.

How to do selective parsing with NSXMLParser

I have the following XML:
<data>
<title>Bookstore</title>
<site>www.bookstore.com</site>
<lastUpdate>11/17/2011</lastUpdate>
<books>
<unit>
<title>
Beginning iPhone 4 Development: Exploring the iOS SDK
</title>
<author>David Mark, Jack Nutting, Jeff LaMarche</author>
<isbn10>143023024X</isbn10>
<isbn13>978-1430230243</isbn13>
<pubDate>January 28, 2011</pubDate>
<price>23.99</price>
<description>
Beginning iPhone 4 Development is a complete course in iOS development. You'll master techniques that work on iPhone, iPad, and iPod touch. We start with the basics showing you how to download and install the tools you'll need, and how to create your first simple application. Next you'll learn to integrate all the interface elements iOS users have come to know and love, such as buttons, switches, pickers, toolbars, and sliders.
</description>
</unit>
<unit>...</unit>
<unit>...</unit>
</books>
<clothes>
<unit>
<title>T-Shirt</title>
<size>M</size>
<color>Red</color>
<price>10.99</price>
<description>
100% cotton T-shirt, wash in cold water with like colors
</description>
</unit>
<unit>...</unit>
<unit>...</unit>
<unit>...</unit>
</clothes>
<accessories>
<unit>
<title>Mug - Large</title>
<color>Black</color>
<price>6.99</price>
<description>
Large 16-ounce ceramic coffee mug, with witty use of IIT name on it.
</description>
</unit>
<unit>...</unit>
</accessories>
</data>
This is the code I've got. I am checking if the tag encountered is "books".
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName
attributes:(NSDictionary *)attributeDict {
if([elementName isEqualToString:#"books"]) {
appDelegate.books = [[NSMutableArray alloc] init];
}
else if([elementName isEqualToString:#"unit"]) {
aBook = [[Book alloc] init];
}
NSLog(#"Processing Element: %#", elementName);
}
.
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
if(!currentElementValue)
currentElementValue = [[NSMutableString alloc] initWithString:string];
else
[currentElementValue appendString:string];
NSLog(#"Processing Value: %#", currentElementValue);
}
Here I am trying to assign the values in each tag to a variable in the Book object.
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
if (qName) {
elementName = qName;
}
if (aBook) {
if ([elementName isEqualToString:#"title"]) {
aBook.title = currentElementValue;
} else if ([elementName isEqualToString:#"author"]) {
aBook.author = currentElementValue;
} else if ([elementName isEqualToString:#"isbn10"]) {
aBook.isbn10 = currentElementValue;
} else if ([elementName isEqualToString:#"isbn13"]) {
aBook.isbn13 = currentElementValue;
} else if ([elementName isEqualToString:#"pubDate"]) {
aBook.pubDate = currentElementValue;
} else if ([elementName isEqualToString:#"price"]) {
aBook.price = currentElementValue;
} else if ([elementName isEqualToString:#"description"]) {
aBook.description = currentElementValue;
}
[appDelegate.books addObject:aBook];
[aBook release];
aBook = nil;
}
[currentElementValue release];
currentElementValue = nil;
}
I have a UITableView to display a the list of books,
Book *aBook = [appDelegate.books objectAtIndex:indexPath.row];
[[cell textLabel] setText:[aBook title]];
The problem is all the items are displayed in the table view, not just books. How do I limit the items displayed to be just from books. I think I'm going wrong in Parser DidStartElement method.
Thanks.
In parser:didEndElement:namespaceURI:qualifiedName: you need to check for the </books> tag, and ignore any <unit> tag after that.
One way to do that would be to use self.books instead of appDelegate.books inside your parser, and then only when you reach the </books> tag set appDelegate.books to self.books and then set self.books to nil. If you reach a <unit> tag and self.books is nil, then you ignore than unit.

Parsing simple XML in Objective-C

I have the following very simple XML returned from a webserver which I use ASIHttpRequest to connect to:
<element1>something</element1>
<element2>somethingelse</element2>
<element3>anotherthing</element3>
ASIHttpRequest can return it as NSData or NSString. I need to parse the information, what is the easiest way to do so?
Thanks
There are some XML parsers available for iOS NSXMLParser, libxml2 (DOM and SAX),TBXML,KissXML. You can refer http://www.raywenderlich.com/553/how-to-chose-the-best-xml-parser-for-your-iphone-project to choose best XML Parser (Speed and memory footprint). Easiest would be TBXML. NSXMLParser is easy as well.
NSXMLParser* xmlParser = [[NSXMLParser alloc] initWithData:receivedXMLData];//init NSXMLParser with receivedXMLData
[xmlParser setDelegate:self]; // Set delegate for NSXMLParser
[xmlParser parse]; //Start parsing
[xmlParser release];
//Delegate Methods
//Have a instance variable NSMutableString* currentString; to hold data between elements and NSMutableArray* elementsArray; to hold parsed data
- (void)parserDidStartDocument:(NSXMLParser *)parser
{
elementsArray = [[NSMutableArray alloc] init];
}
//store all found characters between elements in currentString mutable string
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
if(!currentString)
{
currentString = [[NSMutableString alloc] init];
}
[currentString appendString:string];
}
//When end of XML tag is found this method gets notified
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
if([elementName isEqualToString:#"element1"])
{
[elementsArray addObject:currentString];
[currentString release],currentString=nil;
return;
}
if([elementName isEqualToString:#"element2"])
{
[elementsArray addObject:currentString];
[currentString release],currentString=nil;
return;
}
if([elementName isEqualToString:#"element3"])
{
[elementsArray addObject:currentString];
[currentString release],currentString=nil;
return;
}
[currentString release],currentString =nil;
}
//Parsing has ended
- (void)parserDidEndDocument:(NSXMLParser *)parser
{
NSLog(#"Content of Elements Array: %#",elementsArray);
[elementsArray release],elementsArray=nil;
}
- (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError
{
UIAlertView* parseErrorAlert = [[UIAlertView alloc] initWithTitle:#"Parse Error" message:[NString stringWithFormat:"%#",[parseError localizedDescription]] delegate:nil cancelButtonTitle:#"Ok" otherButtonTitles:nil];
[parseErrorAlert show];
[parseErrorAlert release];
}
You have other delegate methods too like parseErrorOccured. Refer http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSXMLParser_Class/Reference/Reference.html
For TBXML : http://tbxml.co.uk/TBXML/API.html
Updated: Implemented parseError delegate method and valid xml example
XML You posted in code is not a valid XML (You can check XML validation online: http://validator.w3.org/#validate_by_input) so it will throw an parseError. Here is valid XML for XML you posted in question:
<?xml version="1.0"?>
<root>
<element1>something</element1>
<element2>somethingelse</element2>
<element3>anotherthing</element3>
</root>
Try also XMLObject. This may helps if you want to keep your cocoa style in your project.

Can't copy mutable array value into string

i really appreciate for immediate reply and i submit my xml parser methods to you and at last problem is mention.
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict
{
currentElement = [elementName copy];
if([elementName isEqualToString:#"alluser"])
{
objUser = [[Users alloc] init];
}
}
-(void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
if([elementName isEqualToString:#"username"])
{
objUser.userName =[currentElementValue stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
NSLog(#"%#",objUser.userName);
}
if([elementName isEqualToString:#"password"])
{
objUser.passWord = [currentElementValue stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
NSLog(#"%#",objUser.passWord);
}
if([elementName isEqualToString:#"user"])
{
[usersArray addObject:objUser];
[objUser release];
}
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
if(!currentElementValue)
{
currentElementValue = [[NSMutableString alloc] initWithString:string];
[currentElementValue stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
}
else
{
[currentElementValue appendString:string];
[currentElementValue stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
}
if([currentElementValue isEqualToString:#"User"])
{
[currentuser appendString:string];
}
if([currentElementValue isEqualToString:#"Pass"])
{
[currentpass appendString:string];
}
}
-(IBAction)backgroundclick:(id)sender
{
[txtpass resignFirstResponder];
[txtusername resignFirstResponder];
}
-(IBAction)returnPressed:(id)sender
{
[sender resignFirstResponder];
}
-(IBAction)LoginClicked:(id)sender
{
int cnt=0;
NSString *currUser = txtusername.text;
// NSString *passWord = txtpass.text;
// int i=0;
for(Users *objUser in usersArray )
{
/// how to get value and how to compare string...
}
return;
}
my mutable array is in this code is (usersArray). i got parse data into the array and i want to check username and password with textfields value if it match with array the certain action is performed. but somehow i cant access data into the string.
You don't give much detail as to what the problem is, so just guessing... For your loop in LoginClicked() you can compare using:
[currUser isEqualToString:objUser.userName]
Or any other string comparison method. But maybe your problem is something else? In your "parser:didStartElement..." method did you mean to check for the "user" element rather than "allUser"? As it is you allocate only one Users object but attempt to parse many and insert them into an array.