most efficent way to parse XML With NSXMLParser - iphone

I am trying to figure out the best approach to parsing large datasets of xml with the xmlparser delegate.. (basically what the heck do i put the data into?)
the xml data will look like this.
<Rows>
<Row ID="76" MANF="JARD" ISMANUF="F" ISSER="F"/>
<Row ID="38" MANF ="SANBIN" ISMANUF ="F" ISSER ="T"/>
<Rows>
I am looking for a high level responses so I can go away and do more research as their is obviously several different ways of going about this.. I would like to know the best/most efficient to store the data coming back from NSXMLParser and also would like something that I will have the ability to cache...
Thus far I have been looking at NSMutabledictionarys however have heard this might not be sutible so I have now started to look at creating my own object.. but the data coming back from my parsing delegate is only compatible with strings.. so if i have a bool value I cannot put it into my object.. any help would be greatly appreciated as I am at a bit of a loss.

I've implemented an efficient enough XML-to-NSDictionary parser. You can return it as an NSMutableDictionary if you want to. I don't currently have a github or anything up, so I'll post the code inline here. It makes use of the TBXML XML parsing library (no XPath support, and read-only parsing, but fairly efficient and high-level).
EDIT: I just realised, my parser was made to parse element names and text inside the elements, but not element attributes, which is how your XML dataset is laid out. Unfortunately, the below code won't parse the attributes. You've got a starting point though, you can change the code which uses ->firstChild and ->nextSibling to read attributes.
XML.h
#interface XML : NSObject
/**
* Constructs an NSDictionary from the provided XML tree.
*
* Uses the default prefix of 'config'.
*/
+ (NSDictionary *)dictionaryForXMLTree:(TBXMLElement *)tree;
/**
* Constructs an NSDictionary from the provided XML tree.
*
* The format of the dictionary keys is:
* section/[subsection/.../]optionName
*/
+ (NSDictionary *)dictionaryForXMLTree:(TBXMLElement *)tree
withPrefix:(NSString *)keyPrefix;
/**
* Iteratively parses configuration areas from the provided XML document.
*
* If an 'list' area is encountered, its immediate children are added to
* the dictionary as a numbered list (i.e list/1/..., list/2/...).
*/
+ (NSDictionary *)dictionaryFromXML:(TBXML *)xmlDoc;
#end
XML.m
NSString *stripHTML(const char* xmlString);
NSString* stripHTML(const char* xmlString)
{
return [[[NSString stringWithUTF8String:xmlString]
stringByReplacingOccurrencesOfString:#"&"
withString:#"&"]
stringByReplacingOccurrencesOfString:#"
"
withString:#""];
}
#implementation XML
#synthesize configDict;
#pragma mark - XML parsing
+ (NSDictionary *)itemisedDictionaryForXMLTree:(TBXMLElement *)tree
withPrefix:(NSString *)keyPrefix
{
NSMutableDictionary *returnValues =
[[NSMutableDictionary alloc] init];
NSUInteger itemNumber = 1;
for (TBXMLElement *option = tree->firstChild;
option != nil;
option = option->nextSibling)
{
if(option->text == NULL)
option->text = "";
NSString *childPrefix = [NSString stringWithFormat:#"%#/%u",
keyPrefix, itemNumber++];
[returnValues setObject:stripHTML(option->text)
forKey:childPrefix];
[returnValues addEntriesFromDictionary:
[self dictionaryForXMLTree:option withPrefix:childPrefix]];
}
return [returnValues autorelease];
}
+ (NSDictionary *)dictionaryForXMLTree:(TBXMLElement *)tree
withPrefix:(NSString *)keyPrefix
{
NSMutableDictionary *returnValues =
[[NSMutableDictionary alloc] init];
for (TBXMLElement *option = tree->firstChild;
option != nil;
option = option->nextSibling)
{
if(option->text == NULL)
option->text = "";
NSString *childPrefix = [NSString stringWithFormat:#"%#/%s",
keyPrefix,
option->name];
[returnValues setObject:stripHTML(option->text)
forKey:childPrefix];
[returnValues addEntriesFromDictionary:
[self dictionaryForXMLTree:option withPrefix:childPrefix]];
}
return [returnValues autorelease];
}
+ (NSDictionary *)dictionaryForXMLTree:(TBXMLElement *)tree
{
return [self dictionaryForXMLTree:tree withPrefix:#"config"];
}
+ (NSDictionary *)dictionaryFromXML:(TBXML *)xmlDoc
{
NSMutableDictionary *config = [[NSMutableDictionary alloc] init];
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
TBXMLElement *rootElement = [xmlDoc rootXMLElement];
if(rootElement != nil)
{
for(TBXMLElement *configArea = rootElement->firstChild;
configArea != nil;
configArea = configArea->nextSibling)
{
NSString *areaName = [NSString stringWithFormat:#"%s",
configArea->name];
if([areaName
isEqualToString:#"list"]) // multiple children with the same name
{
[config addEntriesFromDictionary:
[self itemisedDictionaryForXMLTree:configArea
withPrefix:areaName]];
} else {
[config addEntriesFromDictionary:
[self dictionaryForXMLTree:configArea
withPrefix:areaName]];
}
}
}
[pool release];
return [config autorelease];
}
+ (NSDictionary *)fetchConfig:(NSURL *)atURL
{
TBXML *xmlDoc = [TBXML tbxmlWithURL:atURL];
return [XML dictionaryFromXML:xmlDoc];
}
+ (NSDictionary *)parseConfigFromXMLString:(NSString *)xmlString
{
TBXML *xmlDoc = [TBXML tbxmlWithXMLString:xmlString];
return [XML dictionaryFromXML:xmlDoc];
}

Related

how to display string combination in desire format in objective c

hi i want to display string combination on nslog like this format
abc
bca
acb
and so on
but
my program show me it like this format
permutations =
(
(
c,
b,
a
),
(
b,
c,
a
),
(
c,
a,
b
),
(
a,
c,
b
),
(
b,
a,
c
),
(
a,
b,
c
)
)
this is the code i am using
NSArray *array = [NSArray arrayWithObjects:#"a",#"b",#"c",nil];
NSMutableArray *permutations = nil;
int i = 0;
for (i = 0; i < array.count ; i++){
if (!permutations){
permutations = [NSMutableArray array];
for (NSString *character in array){
[permutations addObject:[NSArray arrayWithObject:character]];
}
} else {
//make copy of permutations array and clean og array
NSMutableArray *aCopy = [permutations copy] ;
[permutations removeAllObjects];
for (NSString *character in array){
//loop through the copy
for (NSArray *oldArray in aCopy){
//check if old string contains looping char..
if ([oldArray containsObject:character] == NO){
//update array
NSMutableArray *newArray = [NSMutableArray arrayWithArray:oldArray];
[newArray addObject:character];
//add to permutations
[permutations addObject:newArray];
}
}
}
}
}
NSLog(#"permutations = \n %#",permutations);
}
kindly tell me how i can display it in my require format
Try this way :
NSString *stringPermuted=[NSString new];
for (NSArray *array in permutations) {
for (NSString *string in array) {
stringPermuted=[stringPermuted stringByAppendingFormat:#"%#",string];
}
stringPermuted=[stringPermuted stringByAppendingFormat:#"\n"];
}
NSLog(#"permutations=\n%#",stringPermuted);
Another Way :
NSString *stringPermuted=[NSString new];
for (NSArray *array in permutations) {
stringPermuted=[stringPermuted stringByAppendingFormat:#"%#\n",[array componentsJoinedByString:#""]];
}
NSLog(#"permutations=\n%#",stringPermuted);
Create a simple method for displaying
- (NSString *)formatPermutations:(NSArray *)permutations {
NSString * formattedPermutations = #"";
for (NSArray * permutation in permutations) {
formattedPermutations = [formattedPermutations stringByAppendingFormat:#"%#\n", [self formatPermutation:permutation]];
}
return formattedPermutation;
}
- (NSString *)formatPermutation:(NSArray *)permutation {
NSString * formattedPermutation = #"";
for (NSString * letter in permutation) {
formattedPermutation = [formattedPermutation stringByAppendingString:letter];
}
return formattedPermutation;
}
and use it
NSLog(#"permutations = \n %#",[self formatPermutations:permutations]);
Another option (and maybe preferable option) would be to create your own classes and PermutationArray and Permutation and override their description method.
Such method is the equivalent of a Java toString and it gets called whenever NSLog needs to get a NSString representation out of an object.
With your last line NSLog(#"permutations = \n %#",permutations); you are logging the entire NSMutableArray in the console. Xcode is formatting it like that to make it more readable.
Try logging your results this way:
Updated version:
for (NSArray *permutation in permutations){
NSMutableString *tempString = [[NSMutableString alloc] initWithString:#""];
for(NSString *character in permutation){
[tempString appendString:character]
}
NSLog(#"%#\n",tempString);
}
Your permutations object is an NSArray of NSArrays. When using NSLog to log it, NSLog will invoke the description method of NSArray and this uses linebreaks to split up eacho f the objects in the array.
If you want to change the way it is printed you can either use your own NSArray subclass and overwrite the description method or just write a slightly more complicated log statement like so:
Quick and Dirty
for (NSArray *arrayOfStrings in permutations) {
for (NSString *oneCharacterString in arrayOfStrings) {
printf("%s", [oneCharacterString UTF8String]);
}
printf(" ");
}
Little bit less quick a lot less dirty
// with linebreaks after each permutation
// If you don't want those, move the NSLog out of the for loop construct one big string to log in the format you like
for (NSArray *arrayOfStrings in permutations) {
NSMutableString *permutationString = [NSMutableString string];
for (NSString *oneCharacterString in arrayOfStrings) {
[permutationString appendString:oneCharacterString]
}
NSLog(permutationString);
}
Other comments: If you want to save only onecharacter anyway you could also use an NSNumber object (create it using [NSNumber numberWithChar:]).
Or you could use NSString or NSMutableString instead of the inner arrays.

How to get original special characters from SQLite db using iPhone SDK?

I am inserting HTML content (which has special characters like bullets, etc) into the SQLite database.
When I try to get the content on a view, it does not show the special characters correctly. It shows me junk text.
How can I ensure that whatever text I insert in database, it is displayed correctly on the view.
Thanks!
My Insertion code:
// This query method implementation is in different file
- (NSArray *)executeQuery:(NSString *)sql arguments:(NSArray *)args {
sqlite3_stmt *sqlStmt;
if (![self prepareSql:sql inStatament:(&sqlStmt)])
return nil;
int i = 0;
int queryParamCount = sqlite3_bind_parameter_count(sqlStmt);
while (i++ < queryParamCount)
[self bindObject:[args objectAtIndex:(i - 1)] toColumn:i inStatament:sqlStmt];
NSMutableArray *arrayList = [[NSMutableArray alloc] init]; // By Devang
int columnCount = sqlite3_column_count(sqlStmt);
while ([self hasData:sqlStmt]) {
NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] init];
for (i = 0; i < columnCount; ++i) {
id columnName = [self columnName:sqlStmt columnIndex:i];
id columnData = [self columnData:sqlStmt columnIndex:i];
[dictionary setObject:columnData forKey:columnName];
}
[arrayList addObject:dictionary];
//[arrayList addObject:[dictionary autorelease]];
}
sqlite3_finalize(sqlStmt);
return arrayList;
}
// now call this method by make object for this file
NSString *inserQuery =[NSString stringWithFormat:#"insert into feedtest (title,summary,image,id) values ('%#','%#','%#',%d)",cell.textLabel.text,source,returnURL,indexPath.row];
NSLog(#"query - %#",inserQuery);
[database executeQuery:inserQuery];
// Retrive the data
NSString *sd=[NSString stringWithFormat:#"Select title,summary from feedtest"];
NSMutableArray *p=[[NSMutableArray alloc]init];
p=[[database executeQuery:sd ] mutableCopy];
[database close];
NSString *titleHTML = [[p objectAtIndex:i]valueForKey:#"title"];
NSString *postHTML =[[p objectAtIndex:i]valueForKey:#"summary"];
NSLog(#"%#",titleHTML);
NSLog(#"%#",postHTML);
You can check your local database using FireFox plugin SQLite. But, sometimes on retrieving we faced strange problem like what is present in the storage not coming properly and sometime, there is crash. So my suggestion is what you should check encoding scheme(normally, it's not matter more) and while getting data use this:
[NSString stringWithFormat:#"%s",(const char*)sqlite3_column_text(statement, 4)] ;
instead of:
[NSString stringWithUTF8String:(const char*)sqlite3_column_text(statement, 4)];
Hope, this is what you're looking for. Any concern get back to me. :)

When I use the TFHpple parser HTML on iPhone , and already find the node ,but the content return is NULL?

the Code:
NSString *linkStr=#"http://www.voanews.com/content/obama_pledges_aid_to_drought_stricken_farmers/1484380.html";
NSData *data = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:linkStr]];
// Create parser
TFHpple *xpathParser = [[TFHpple alloc] initWithHTMLData:data];
//Get all the cells of the 2nd row of the 3rd table
NSArray *elements = [xpathParser searchWithXPathQuery:#"//p[#class='article_date']"];
// Access the first cell
if ([elements count] > 0)
{
TFHppleElement *element = [elements objectAtIndex:0];
// Get the text within the cell tag
NSString *content = [element content];
NSLog(#"VOA = %#",content); //Result : print NULL
}
[xpathParser release];
[data release];
but I use the XPath Helper query the "//p[#class='article_date']" ,it's ok, but in my code the content is null
Running your code example, if I change [element content] for [element text], my output is:
VOA = August 11, 2012
In its Github repo, they mention (at USAGE section):
[e text]; // The text inside the HTML element (the content of the
first text node)
And looking at the source code of the CONTENT method it uses objectForKey, where TFHppleContentKey = "nodeContent". See:
static NSString * const TFHppleNodeContentKey = #"nodeContent"
// Returns this tag's innerHTML content.
- (NSString *) content
{
return [node objectForKey:TFHppleNodeContentKey];
}
It seems that it's safe to use [element text] instead of [element content] in your example.
I hope it helps.

How to display Xpath on the iPhone

I'm trying to extract the weather information from here using Xpath on the iPhone. As of now it parses all the data but I'm stuck on how to extract the content and display it in a table.
This is what I have so far:
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:[ #"http://aviationweather.gov/adds/metars/?station_ids=1234&std_trans=translated&chk_metars=on&hoursStr=most+recent+only&submitmet=Submit"stringByReplacingOccurrencesOfString:#"1234" withString:self.title]]];
TFHpple * doc = [[TFHpple alloc] initWithHTMLData:data];
NSArray * elements = [doc searchWithXPathQuery:#"//table[1]//tr"];
NSLog(#"%#", elements);
TFHppleElement * element = [elements objectAtIndex:0];
[element content]; // Tag's innerHTML
[element tagName]; // "a"
[element attributes]; // NSDictionary of href, class, id, etc.
[element objectForKey:#"href"]; // Easy access to single attribute
If anybody needs to see what its outputting so far, let me know.
Thanks,
Andrew
I had the same issue I got to the point your at and didn't no where to go but I end up implementing this code. Hope it helps there is still little bits need to make it work correctly but do to the nature of the app I have developed this is all I can give you. its not much more its just the actual implementation into your code that you need really.
#import "XPathQuery.h"
NSMutableArray *weatherArray = [[NSMutableArray arrayWithArray:0]retain]; // Initilize the NSMutableArray can also be done with just an NSArray but you will have to change the populateArray method.
NSString *xPathLookupQuery = #"//table[1]//tr"; // Path in xml
nodes = PerformXMLXPathQuery(data, xPathLookupQuery); // Pass the data in that you need to search through
[self populateArray:weatherArray fromNodes:nodes]; // To populate multiple values into array.
session = [[self fetchContent:nodes] retain]; // To populate a single value and returns value.
- (void)populateArray:(NSMutableArray *)array fromNodes:(NSArray *)nodes
{
for (NSDictionary *node in nodes) {
for (id key in node) {
if ([key isEqualToString:#"nodeContent"]) {
[array addObject:[node objectForKey:key]];
}
}
}
}
You only need either the above code or below code unless you want both.
- (NSString *)fetchContent:(NSArray *)nodes
{
NSString *result = #"";
for (NSDictionary *node in nodes) {
for (id key in node) {
if([key isEqualToString:#"nodeContent"]) {
result = [node objectForKey:key];
}
}
}
return result;
}

markup text parser like stackoverflow's formatter in Objective-C

I'm in the process of creating a markup editor in Objective C. I require the following functionality:
Recognise the demarcation of a block eg **block**
Delete the start and end "tags" eg "The next text is **bold**" becomes "The next text is bold"
Determine the start and end positions of the marked-up text in the new context: "The next text is bold"
Edit:
As I may expand the syntax in the future (it will be very limited at the moment), it is important that parsing be top-down such that the start and end positions of the text always correspond with the resulting text. For this reason regex may not be the best solution.
What is the best way to do this?
In the end went for regex approach using RegexKitLite
The code below is not fully tested but does work with the case St3fan pointed out.
- (NSArray *) scanContent:(NSMutableString **)content {
NSMutableArray *tokens = [[NSMutableArray alloc] init];
NSArray *captureRegex = [[NSArray alloc] initWithObjects:
#"\\[\\[(.*?)\\]\\]",#"\\*\\*(.*?)\\*\\*", nil];
NSArray *tokenID = [[NSArray alloc] initWithObjects:
#"Italic",#"Bold", nil];
int index = 0;
for (NSString*capture in captureRegex) {
NSRange captureRange;
NSRange stringRange;
stringRange.location = 0;
stringRange.length = [*content length];
do {
captureRange = [*content rangeOfRegex:capture inRange:stringRange];
if ( captureRange.location != NSNotFound ) {
NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] init];
[dictionary setObject:[tokenID objectAtIndex:index] forKey:#"Token"];
[dictionary setObject:[NSNumber numberWithInt:captureRange.location]
forKey:#"Start"];
[dictionary setObject:[NSNumber numberWithInt:captureRange.length]
forKey:#"Length"];
[tokens addObject:dictionary];
for (NSMutableDictionary *dict in tokens) {
NSNumber *nRange = [dict objectForKey:#"Start"];
int start = [nRange intValue];
if (start > captureRange.location) {
nRange = [NSNumber numberWithInt:start - 4]; // Removing 4 characters
[dict setObject:nRange forKey:#"Start"];
}
if (start == captureRange.location) {
NSString *data = [*content stringByMatching:capture options:RKLMultiline inRange:captureRange capture:1 error:NULL];
NSLog(#"data: %#",data);
[*content replaceOccurrencesOfRegex:capture withString:data range:captureRange];
NSLog(#"Replaced Content: %#",*content);
}
}
stringRange.location = captureRange.location + captureRange.length -4;
stringRange.length = [*content length] - stringRange.location;
}
}
while ( captureRange.location != NSNotFound );
index++;
}
return tokens;
}
MarkDown Sharp, the markdown processor used on the StackExchange websites, is open source. Take a look at the file, perhaps you can see how they do it or port it to objective-c.
Perhaps better yet, take a look at this question: "What is the simplest implementation of Markdown for a Cocoa application?"
It links to an open source application called MarkdownLive which uses a C implementation of Markdown called discount, and also provides an objective-c wrapper for it.