NSString (with method call : stringWithContentsOfFile) - iphone

NSBundle *bundle = [NSBundle bundleForClass : [self class]];
NSString *f_path = nil;
if ((f_path = [bundle pathForResource : #"about_screen" ofType:#"html" inDirectory:#"html"]) != nil)
{
NSLog(#" f_path found" );
NSString *ns_string = [NSString stringWithContentsOfFile : f_path
encoding : NSUTF8StringEncoding
error : NULL
];
NSLog(#" string = %#", ns_string);
}
else
{
NSLog(#" f_path not found" );
}
// *** if the following assignment is commented off, there will be an error. ***
ns_string =
#"<!DOCTYPE html PUBLIC \"-//IETF//DTD HTML 2.0//EN\"><HTML><HEAD><TITLE>minimal test </TITLE></HEAD><BODY bgcolor = \"silver\"><H1>Hi</H1><P>This is very minimal \"hello world\" test document.</P> </BODY></HTML>";
[web_view loadHTMLString : ns_string baseURL : nil];
Consider the above code segment.
For testing purpose I have set the content of the file "about_screen.html" to be the same as the string assigned to ns_string in the code above. So if "NSString stringWithContentsOfFile" works as expected, the ns_string "in-line" assignment can be commented off without making any difference.
My problem is : the in-line assignment works as expected but without it, there will be a run time error.
The error is :
-[About_Screen dataUsingEncoding:]: unrecognized selector sent to instance 0x6eddf70'
Also note that the statement :
NSLog(#" string = %#", ns_string);
always outputs the correct string, so the embedded html file is found and is being read correctly.
Hope that somebody familiar with this could help.

This answer actually comes from #bbamhart's comment. I thought the problem was due to the NSUTF8StringEncoding parameter since it shows up frequently from googling on related topics.
The real cause of the problem is about variable scope. ns_string should not be declared in any conditional blocks. (since the variable would then normally not be accessible outside the block.)
The bottom-line solution is simply move the declaration out of the block.

Related

Why does SBJson JSON parsing only get the last key of interest?

I am using the following JSON: http://www.kb.dk/tekst/mobil/aabningstider_en.json
When I try to parse it by the key "location" as such:
// get response in the form of a utf-8 encoded json string
NSString *jsonString = [[[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding] stringByTrimmingCharactersInSet:[NSCharacterSet newlineCharacterSet]];
// get most parent node from json string
NSDictionary *json = [jsonString JSONValue];
// get key-path from jason up to the point of json object
NSDictionary *locations = [json objectForKey:#"location"];
NSLog( #"%#", locations );
// iterate through all of the location objects in the json
for (NSDictionary *loc in locations )
{
// pull library name from the json object
NSString *name = [loc valueForKey:#"name"];
// add library data table arrays respectively
[ libraryNames addObject: ( ( name == nil | name.length > 0 ) ? name : #"UnNamed" ) ];
}
When I print the the object locations via NSLog:
{
address = "Universitetsparken 4, 3. etage, 2100 K\U00f8benhavn \U00d8";
desc = "";
lastUpdated = "";
latlng = "55.703124,12.559596";
link = "http://www.farma.ku.dk/index.php?id=3742";
name = "Faculty of Pharmaceutical Sciences Library";
parts = {
part = {
hour = {
day = "5.June Constitution Day (Denmark)";
open = Closed;
};
hours = {
hour = {
day = Friday;
open = "10-16";
};
};
name = main;
};
};
}
Which is only the last value for the "location" keys. Am I doing something wrong?
I tried validating the JSON via http://jsonlint.com/, however when I'd put in the JSON URL as above, it said "valid" - still only the last "locations" key was shown", however if I copy-paste it, it will not validate the JSON, and has to be fixed by removing new-lines from the string.
Also, when i try to parse the JSON and get the "name" fields, I get the following exception:
2012-05-08 15:37:04.941 iPhone App Tabbed[563:f803] *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<__NSCFString 0x68bfe70> valueForUndefinedKey:]: this class is not key value coding-compliant for the key name.'
*** First throw call stack:
(0x13dc052 0x156dd0a 0x13dbf11 0x9d2f0e 0x941841 0x940ca9 0x4593 0xf964e 0x114b89 0x1149bd 0x112f8a 0x112e2f 0x1148f4 0x13ddec9 0x365c2 0x3655a 0x25b569 0x13ddec9 0x365c2 0x3655a 0xdbb76 0xdc03f 0xdbbab 0x25dd1f 0x13ddec9 0x365c2 0x3655a 0xdbb76 0xdc03f 0xdb2fe 0x5ba30 0x5bc56 0x42384 0x35aa9 0x12c6fa9 0x13b01c5 0x1315022 0x131390a 0x1312db4 0x1312ccb 0x12c5879 0x12c593e 0x33a9b 0x281d 0x2785)
terminate called throwing an exception(lldb)
It would make more sense if the "locations" tag was an array object enclosed by square brackets ([]), however right now it's only an sequence of normal key-value pairs... Sadly, that's the JSON I have to work with.
Please help and thanks a great deal! :)
Sincerely,
Piotr.
The JSON you've got to work with may be valid, but it doesn't make much sense. It has one big dictionary with the location key repeated many times. Most JSON parser will simply return the last value for the repeated key. It would be best if you could change the structure to use an array instead, but if you cannot there's still hope. You can read the stream and stuff the values from the location keys into an array as they come out of it. This is how you'd do that:
#interface BadJsonHelper : NSObject
#property(strong) NSMutableArray *accumulator;
#end
#implementation BadJsonHelper
- (void)parser:(SBJsonStreamParser *)parser foundArray:(NSArray *)array {
// void
}
- (void)parser:(SBJsonStreamParser *)parser foundObject:(NSDictionary *)dict {
[accumulator addObject:dict];
}
#end
You can drop that little helper class at the top of your file, outside the #implementation section of the class where you're doing your work. (There's no need for the #interface and #implementation being in different files.)
In your code, you would use it like this:
BadJsonHelper *helper = [[BadJsonHelper alloc] init];
helper.accumulator = [NSMutableArray array];
SBJsonStreamParserAdapter *adapter = [[SBJsonStreamParserAdapter new] init];
adapter.delegate = helper;
adapter.levelsToSkip = 1;
SBJsonStreamParser *parser = [[SBJsonStreamParser alloc] init];
parser.delegate = adapter;
switch ([parser parse: responseData]) {
case SBJsonStreamParserComplete:
NSLog(#"%#", helper.accumulator);
break;
case SBJsonStreamParserWaitingForData:
NSLog(#"Didn't get all the JSON yet...");
break;
case SBJsonStreamParserError:
NSLog(#"Error: %#", parser.error);
break;
}
This example was originally adapted from the following test:
https://github.com/stig/json-framework/blob/master/Tests/StreamParserIntegrationTest.m
Update: I created a fully functional example project that loads the JSON asynchronously and parses it. This is available from github.
The JSON is valid, however there is a basic problem regarding the definition of the array of items.
Instead of defining an array of locations using brackets, the JSON redefines the same location key/value pair over and over again. In other words JSON initially says the value of location is the collection with name "The Black Diamond", but immediately after it redefines it with the collection with name "Faculty Library of Humanities" and so on till the last location Faculty of Pharmaceutical Sciences Library".
The same is true for parts and hours.
If you can't fix the result of the JSON and you really need to get it working you may want to modify the JSON removing the "location" keys and adding brackets properly.
Edit
Alternatively you may use an NSScanner and process the JSON result manually. Kinda hacky but it will work as long as the JSON format doesn't change significantly.
Edit
This snipped of code should do the work...
NSString *jsonString = [[[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding] stringByTrimmingCharactersInSet:[NSCharacterSet newlineCharacterSet]];
int indx = 1;
for (;;)
{
NSRange locationRange = [jsonString rangeOfString:#"\"location\":"];
if (locationRange.location == NSNotFound) break;
jsonString = [jsonString stringByReplacingCharactersInRange:locationRange
withString:[NSString stringWithFormat:#"\"location%d\":", indx++]];
}
NSDictionary *locations = [json objectForKey:#"location"];
As you can see, the result of JSON parsing by SBJson is a NSDictionary. A dictionary contains key/value pairs, and the keys are unique identifiers for the pairs.
The JSON data you need to handle is valid but not a good one. Per RFC 4627 - 2.2:
An object structure is represented as a pair of curly brackets surrounding zero or more name/value pairs (or members). A name is a string. A single colon comes after each name, separating the name from the value. A single comma separates a value from a following name. The names within an object SHOULD be unique.
Things like jQuery can parse the JSON also, but the result is the same as SBJson (the last one as the one). See Do JSON keys need to be unique?.
It is not a MUST, but it's still not a good practice. It would be much easier if you are able to change the structure of the JSON data on the server side (or even on the client side after receiving it) rather than parsing it as is.

ShareKit Api Change

I am trying to change ShareKit API code of attachment to this code below:
dialog.attachment = [NSString stringWithFormat:#"{\"name\":\"%#\",\"href\"
:\"%#\",\"media\":[{\"type\":\"image\",\"src\":\"http://example.com/example.png\"
,\"href\": \"http://example.com/\"}]} ",item.title == nil ? SHKEncodeURL(item.URL)
SHKEncode(item.title),SHKEncodeURL(item.URL)];
Xcode Keeps telling me prefix error.
What am I missing?
I'm sorry to say, but your code is one big piece of spaghetti code. It is hard to read, prone to mistakes and hard to maintain (not only for you, but for your fellow coders as well).
I'd like to suggest breaking down the code a bit - for example:
// one way to define constant strings...
NSString * const kSHKAttachmentTemplateString = #"{\"name\":\"%#\",\"href\":\"%#\",\"media\":[{\"type\":\"image\",\"src\":\"http://example.com/example.png\",\"href\":\"http://example.com/\"}]}"
// and another one that uses a #defined constant string at the beginning of header file of .m file
#define SHK_ATTACHMENT_TEMPLATE_STR #"{\"name\":\"%#\",\"href\":\"%#\",\"media\":[{\"type\":\"image\",\"src\":\"http://example.com/example.png\",\"href\":\"http://example.com/\"}]}"
// assuming item is an valid object
NSString *itemTitleOrURL = (item.title == nil) ? SHKEncodeURL(item.URL) : SHKEncode(item.title);
dialog.attachment = [NSString stringWithFormat:SHK_ATTACHMENT_TEMPLATE_STR, itemTitleOrURL, SHKEncodeURL(item.URL)];
Following a readable code style will definitely save you time while solving similar syntax errors.
You are missing an colon in the three-way conditional;
The original code looks like this:
dialog.attachment = [NSString stringWithFormat:#"{\"name\":\"%#\",\"href\"
:\"%#\",\"media\":[{\"type\":\"image\",\"src\":\"http://example.com/example.png\"
,\"href\": \"http://example.com/\"}]} ",item.title == nil ? SHKEncodeURL(item.URL)
SHKEncode(item.title),SHKEncodeURL(item.URL)];
Change it to this
dialog.attachment = [NSString stringWithFormat:#"{\"name\":\"%#\",\"href\"
:\"%#\",\"media\":[{\"type\":\"image\",\"src\":\"http://example.com/example.png\"
,\"href\": \"http://example.com/\"}]} ",item.title == nil ? SHKEncodeURL(item.URL):
SHKEncode(item.title),SHKEncodeURL(item.URL)];
The error is at the end of the third line.
You should have seen this in the XCode editor, with a little yellow charat under the position where the error is.

Very strange problem about NSString

A very strange behavior about this function,
void* getNSString(const NSString* str){
str = #"This is new test";
//NSString* str1 = #"so strange test";
return;
}
Then
NSLog(#"%#",getNSString(#"test"));
The result will be
This is new test
if uncomment
NSString* str1 = #"so strange test";
My understanding is that nothing is returned, so that should be NULL , why print out those string ?
Then result will be
so strange test
I don't believe that nothing is returned. I believe that it's undefined. In other words, anything could be returned.
In this case, it looks like it's returning whatever happened to be on the stack at a given location. That happens to be one of the strings you modified or created but I can assure you this is a fortunate accident (or unfortunate since a crash would probably be better).
If you want nothing to be returned, you need to change:
void* getNSString(const NSString* str){
to:
void getNSString(const NSString* str){
cocoa/objective c does this way.... I also face this type problem before,
for example :
-(NSObject*) someMethod ()
{ ...
NSString* result =#"some value";
// because too busy ,I just forgot write the return statement during dev :)
}
although nothing was return like above code, but the app also can return the result value ....
that is very funny....

Using va_list and getting EXC_BAD_ACCESS

Similar to how NSLog takes variable argument list, I want to create my own method.
I have my method declared like this but when I try to access the 'args' variable, I get an EXEC_BAD_ACCESS. What is that I'm not doing correctly here?
- (void)info:(NSString *)formatString, ...
{
va_list args;
va_start(args, formatString);
NSLog(#"formatString value: %#", formatString);
// The following line causes the EXEC_BAD_ACCESS
NSLog(#"args value: %#", args);
// This is what I'm trying to do:
NSLog(formatString, args);
va_end(args);
}
I was following the 'va_list in Cocoa' section from this blog:
http://cocoawithlove.com/2009/05/variable-argument-lists-in-cocoa.html
There are a couple of mistakes in your code. Firstly, args cannot be printed directly as is. It is a reference to several arguments, and trying to print it using NSLog("%#") will not work. What you can do is use NSLogv() to print it (e.g. NSLogv(format, args);) instead.
Or you can do what I do, and use this function:
void print (NSString *format, ...) {
va_list args;
va_start(args, format);
fputs([[[[NSString alloc] initWithFormat:format arguments:args] autorelease] UTF8String], stdout);
va_end(args);
}
The "%#" format directive takes an argument, interprets it as an Objective-C object and sends it the "description" selector. That needs to return a NSString which is printed.
So you code is trying to execute ‘[args description]‘, but args is not a Objective-C object, it's of type ‘va_list‘. Hence the exception.
See your link, the implementation of "setContentByAppendingStrings:" shows how do get the arguments out of your va_list.
Morning lads,
I just have been confronted with the very similar issue. Here is what I was doing:
+ (void) l:(D3LogLevel)p_logLevel s:(NSString *)p_format, ...
{
if (p_logLevel >= logLevel) {
va_list v_args;
va_start(v_args, p_format);
NSLog(#"[%d] %#", p_logLevel, [NSString stringWithFormat:p_format, v_args]);
va_end(v_args);
}
}
Which, as DarkDust here has described accurately, uses v_args as an Objective-C object when it is not. Here is the culprit call:
[NSString stringWithFormat:p_format, v_args]
Hence the modification to take a va_list:
[[NSString alloc] initWithFormat:p_format arguments:v_args]
Making use of the appropriate method initWithFormat which signature is:
- (id)initWithFormat:(NSString *)format arguments:(va_list)argList
We can see that the type is right and everything becomes crystal clear. The full rewritten method is then:
+ (void) l:(D3LogLevel)p_logLevel s:(NSString *)p_format, ...
{
if (p_logLevel >= logLevel) {
va_list v_args;
va_start(v_args, p_format);
NSLog(#"[%d] %#", p_logLevel, [[NSString alloc] initWithFormat:p_format arguments:v_args]);
va_end(v_args);
}
}
It works like a charm!

Open strings file from recource folder

I have a language file downloaded from a web service located in my resources folder of the project. The file name is "no.properties" and I need to load the file into a string to make further use of it..
The content of my file is displayed like this:
CMD_EXIT = Avslutt
CMD_PAUSE = Pause
CMD_SCAN = Skann
ect.....
Here is my code of loading the file into a string:
NSString* path = [[NSBundle mainBundle] pathForResource:[defaults objectForKey:#"language"]
ofType:#"properties"];
NSString* content = [NSString stringWithContentsOfFile:path
encoding:NSUTF8StringEncoding
error:NULL];
The problem is that the "content" string is null even though the file contains a lot of data. Anyone know what might be the problem?
note: I´ve checked that the value received from userdefaults equals "no". I have also tried to rename the file to "no.txt", but still the "content" string is null.
First, you could try to pass a NSError object to stringWithContentsOfFile:::.
You should receive a Cocoa error number, that (hopefully) tells you more about the problem.
NSError* err = nil;
NSString* string = [NSString stringWithContentsOfFile:path encoding:NSUTF16BigEndianStringEncoding error:&err];
As you seem to work with language files, I suspect that you are specifying the wrong encoding when reading in the strings.