How to do selective parsing with NSXMLParser - objective-c

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.

Related

Parsing XML elements for a TableView

At the moment I am designing a simple app that will read news feeds. The XML feed I am using is this... http://feeds.foxnews.com/foxnews/world?format=xml .
What I am looking to do is essentially take the 'title' element and the description' elements from this page, and use them in a TableView.
This is what I currently have:
-(void)parserDidStartDocument:(NSXMLParser *)parser{
_elementsArray = [[NSMutableArray alloc]init];
}
-(void)parser:(NSXMLParser*)parser foundCharacters:(NSString *)string{
if (!_currentString) {
_currentString = [[NSMutableString alloc]init];
}
[_currentString appendString:string];
}
-(void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName{
if ([elementName isEqualToString:#"title"]) {
[_elementsArray addObject:_currentString];
_currentString=nil;
return;
}
if ([elementName isEqualToString:#"description"]) {
[_elementsArray addObject:_currentString];
_currentString=nil;
return;
}
_currentString = nil;
}
-(void)parserDidEndDocument:(NSXMLParser *)parser{
for (NSString*string in _elementsArray) {
NSLog(#"%#",string);
}
_elementsArray = nil;
}
I do get an output of this, that is not a problem.
My problem is that each element is added in a new NSArray entry. How would I add both elements to one entry? ( would it be possible to use keys ? )
You could use a temporary NSDictionary to store your title and description together for each item, and add the dictionary to the _elementsArray.
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributeDict {
if ([elementName isEqualToString:#"item"]) {
_currentDict = [NSMutableDictionary dictionary];
return;
}
}
-(void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName{
if ([elementName isEqualToString:#"item"]) {
[_elementsArray addObject:_currentDict];
_currentDict=nil;
return;
}
if ([elementName isEqualToString:#"title"]) {
_currentDict[#"title"] = _currentString;
_currentString=nil;
return;
}
if ([elementName isEqualToString:#"description"]) {
_currentDict[#"description"] = _currentString;
_currentString=nil;
return;
}
_currentString = nil;
}
// Other methods left unchanged
This should output something like:
NSArray (
NSDictionary {
#"title": #"First title",
#"description": #"A news article"
},
NSDictionary {
#"title": #"Second title",
#"description": #"Another interesting article"
}
)
Untested, but I hope you get the idea.
Note also that parsing an XML (or RSS) file into native ObjC objects is a very recurring problem, and lots of open source libs out there already do most of the heavy lifting for you.

Parsing XML into NSManagedObjects using categories and what to do with properties in categories?

Based on the excellent example "Parsing XML with NSXMLParser" in the book "The Big Nerd Ranch Guide" (3rd ed.), I haved added categories to my NSManagedObjects for which I want to add XML parsing. These categories provide only parsing functionality.
This is how I have implemented these categories:
.h:
#import "IBCompany.h"
#interface IBCompany (Xml) <NSXMLParserDelegate>
- (void)parseXmlString:(NSString*)xmlStr withCompletion:(void(^)(NSError *error))completionBlock;
#end
.m:
#implementation IBCompany (Xml)
- (void)parseXmlString:(NSString*)xmlStr withCompletion:(void(^)(NSError *error))completionBlock;
{
NSData *xmlData = [xmlStr dataUsingEncoding:NSUTF8StringEncoding];
NSXMLParser *parser = [[NSXMLParser alloc]initWithData:xmlData];
parser.delegate = self;
[parser parse];
xmlData = nil;
NSError *error;
completionBlock(error);
}
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict
{
if ([elementName isEqualToString:#"Issue"]) {
IBIssue *issue = [NSEntityDescription insertNewObjectForEntityForName:#"IBIssue" inManagedObjectContext:self.managedObjectContext];
issue.company = self;
issue.parentParserDelegate = self;
parser.delegate = issue;
}
As you can see in this code snippet, I switch the parser delegate to other subclasses / XML child elements to have them further process the next XML elements, which belong to them until the end of the XML element is reached and the delegate is set back to the parent.
This is why I need to store the parent delegate in the child. However, ivars and properties are not allowed in categories.
I came up with this solution which seems to circumvent this problem:
Child element, h:
#import "IBIssue.h"
#interface IBIssue (Xml) <NSXMLParserDelegate>
#property id parentParserDelegate;
#end
#import "IBIssue+Xml.h"
#implementation IBIssue (Xml)
NSMutableString *currentString;
NSString *currentXmlDocument;
id _parentParserDelegate;
- (id)parentParserDelegate
{
return _parentParserDelegate;
}
- (void)setParentParserDelegate:(id)parentParserDelegate;
{
_parentParserDelegate = parentParserDelegate;
}
- (NSDateFormatter*)dateFormatter
{
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:#"en_US_POSIX"]];
[dateFormatter setDateFormat:#"yyy-MM-dd"];
[dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT: 0]];
return dateFormatter;
}
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict
{
if ([elementName isEqualToString:#"IssueID"]) {
currentString = [[NSMutableString alloc]init];
if ([attributeDict[#"Type"] isEqualToString:#"Ticker"]) self.ticker = currentString;
else if ([attributeDict[#"Type"] isEqualToString:#"Name"]) self.issueName = currentString;
else if ([attributeDict[#"Type"] isEqualToString:#"CUSIP"]) self.cusip = currentString;
else if ([attributeDict[#"Type"] isEqualToString:#"ISIN"]) self.isin = currentString;
else if ([attributeDict[#"Type"] isEqualToString:#"RIC"]) self.ric = currentString;
else if ([attributeDict[#"Type"] isEqualToString:#"SEDOL"]) self.sedol = currentString;
else if ([attributeDict[#"Type"] isEqualToString:#"DisplayRIC"]) self.displayRic = currentString;
else if ([attributeDict[#"Type"] isEqualToString:#"InstrumentPI"]) ; //
else if ([attributeDict[#"Type"] isEqualToString:#"QuotePI"]) ; //
} else if ([elementName isEqualToString:#"Exchange"]) {
currentString = [[NSMutableString alloc]init];
self.exchangeCode = attributeDict[#"Code"];
self.exchangeCountry = attributeDict[#"Country"];
self.exchange = currentString;
} else if ([elementName isEqualToString:#"MostRecentSplit"]) {
currentString = [[NSMutableString alloc]init];
self.mostRecentSplitDate = [self.dateFormatter dateFromString:attributeDict[#"Date"]];
// self.mostRecentSplit = [NSNumber numberWithFloat: currentString.floatValue];
}
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
// NSLog(#"appendString: %#", string);
[currentString appendString:string];
}
-(void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
if ([elementName isEqualToString:#"Issue"]) {
parser.delegate = self.parentParserDelegate;
} else if ([elementName isEqualToString:#"MostRecentSplit"]) {
self.mostRecentSplit = [NSNumber numberWithFloat: currentString.floatValue];
}
currentString = nil;
}
#end
I save the delegate to the parent in a variable _parentDelegate which is declared outside the ivar declaration block and does not seem to be a real ivar.
This code works well in my tests and I wonder if I missed something which will turn out to become a problem later in the development process or if this design is ok.
What are your thoughts on that?
Thank you!
I'm not sure how the compiler will treat that variable. Could it be allocated so that only one variable is shared by all objects of this type? If your XML is parsed such that more than one IBCompany exists at a point in time it could cause a problem. I'd write a test that allocated two IBCompany objects, cause them both to write different values to _parentDelegate, then assert the values are different.
Or ignore the issue if there is no possibility that two IBCompany objects are parsed in parallel. You'd have to ensure that the XML can't have an IBCompany inside another IBCompany, multiple parts of the XML will not be processed in parallel, and that multiple XML documents will not be processed in parallel.
I don't see the need for a category. Categories are useful when you shouldn't write a subclass to an existing class, such as adding functionality to classes in the Cocoa framework. You are writing a custom subclass, so why not add the ivar to your subclass? You can have additional ivars in managed objects that are not saved in the Core Data backing stores. At most I'd just use an extension to segregate XML parsing code from the rest of the managed object.

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.

Parsing XML using NSXML for iPhone

I have an XML which has a structure like this:
<data>
<books>
<item>
<title>book1</title>
<author>author1</author>
</item>
<item>
<title>book2</title>
<author>author2</author>
</item>
</books>
<magazines>
<item>
<title>Mag1</title>
<author>author1</author>
</item>
</magazines>
<journals>
<item>
<title>Journal1</title>
<author>author1</author>
</item>
<item>
<title>Journal2</title>
<author>author2</author>
</item>
</journals>
</data>
I am using a table view to display the items of each category. I plan to have a table view with columns books, magazines & journals and clicking on them should display only the items under them. That is, if I click on books, it should only display the book entries.
I have this for xml parsing
- (void)parser:(NSXMLParser *)parser
didStartElement:(NSString *) elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName
attributes:(NSDictionary *)attributeDict
{
if ([elementName isEqual:#"books"]) {
flag=1;
}
if ([elementName isEqual:#"title"]) {
NSLog(#"found title!");
parsedString = [[NSMutableString alloc] init];
}
if ([elementName isEqual:#"author"]) {
NSLog(#"description found");
parsedString = [[NSMutableString alloc] init];
}
}
and in
- (void)parser:(NSXMLParser *)parser
didEndElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName
{
if (flag==1){
if ([elementName isEqual:#"title"]) {
[headlines addObject:parseString];
[parseString release];
parseString = nil;
}
if ([elementName isEqual:#"author"]) {
[authors addObject:parsedString];
[parseString release];
parseString = nil;
flag=2;
}
}
}
But this gets only the first entry of books and nothing else.
Instead of setting flags is there a way to parse only between the <books> and
</books> tags ?
Thank you.
The reason it is only getting the first item is that you are setting flag to 2 when you see the end element for the author tag. I suspect you will get closer to what you want if you update this flag in the end element for the books tag, as follows:
- (void)parser:(NSXMLParser *)parser
didEndElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName
{
if (flag==1){
if ([elementName isEqual:#"title"]) {
[headlines addObject:parseString];
[parseString release];
parseString = nil;
}
if ([elementName isEqual:#"author"]) {
[authors addObject:parsedString];
[parseString release];
parseString = nil;
}
if ([elementName isEqual:#"books"]) {
flag=2;
}
}
}
Just cancel parsing when the books tag ends:
- (void)parser:(NSXMLParser *)parser
didEndElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName
{
if ([elementName isEqual:#"books"]) {
[parser abortParsing];
}
....
}

How to handle HTML Strings in 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.