Re-encoding a NSString returns null - cocoa

So I have this piece of code :
if ([receivedPage hasPrefix:[NSString stringWithUTF8String:"\xC3\xAF\xC2\xBB\xC2\xBF"]]) // UTF-8 BOM 'EF BB BF' as UTF-16 chars
{
//DebugLog(#"converting calls list to UTF8");
receivedPage = [[[NSString alloc] initWithData:[receivedPage dataUsingEncoding:NSISOLatin1StringEncoding] encoding:NSUTF8StringEncoding] autorelease];
}
However sometimes when the if is true the receivedPage becomes null. why would this happen?
The received page is the returned value of this function:
NSURLResponse * response;
NSData * result = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:error];
if ([result length] > 0)
return [[[NSString alloc] initWithBytes: (const void*)[result bytes] length:[result length] encoding: encoding] autorelease];
else
{
if (error && *error)
DebugLog(#"URL request got error: %#",*error);
return nil;
}
The encoding here is NSISOLatin1StringEncoding (don't know why ,I'm debugging someone else's code).
Any idea why this would happen?

It looks like you're trying to treat strings (objects containing characters) as data (objects containing bytes). Keep the data you received from the connection, and check for the UTF-8 BOM (the proper three-byte version) in it, then use either NSUTF8StringEncoding or NSISOLatin1StringEncoding based on whether you find it.
Or, just use UTF-8 conditionally, if you can fix the server to do that as well.
Also, you should probably switch this code to use the NSURLConnection asynchronously. If the user's internet connection is slow, you're hanging your app here. Doing it asynchronously lets you keep the UI running, display progress if appropriate, and enable the user to cancel.

Related

NSDataAsset data requirements

So the new data assets class allows reading 'data', but there appears to be some content restrictions? I have a file like so test.csv (collapsed here for viewing as a single line but actually 1-line per string):
½f ⅛m 1½f ¼m 2½f ⅜m 3½f ½m 4½f ⅝m 5½f ¾m 6½f ⅞m 7½f 1m 1m ½f 1⅛m 1m 1½f 1¼m 1m 2½f 1⅜m 1m 3½f 1½m 1m 4½f 1⅝m 1m 5½f 1¾m 1m 6½f 1⅞m 1m 7½f 2m
The file I maintain via TextEdit, and read such like so (NSData category but various methods can return different inherent - to the 'type', data):
+ (id)assetWithName:(NSString *)name
{
NSDataAsset * asset = [[[NSDataAsset alloc] initWithName:name] autorelease];
NSData * data = [[[NSData alloc] initWithData:asset.data] autorelease];
NSAssert(data.length > 0, #"'%#' has zero data.length ", name);// Yoink
NSString * string = [[[NSString alloc] initWithData:data
encoding:NSUTF8StringEncoding] autorelease];
NSString * type = asset.typeIdentifier;
// default to Ascii when UTF8 doesn't work
if (!string.length)
{
string = [[[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding] autorelease];
}
if ([#"public.xml" isEqualToString:type])
{
return [self assetBook:string];
}
else
if ([#"public.comma-separated-values-text" isEqualToString:type])
{
return [self assetCSVs:string];
}
else
{
NSLog(#"'%#' has unknown asset type %#",name,type);
return [self assetCSVs:string];
}
}
All was dandy, until I altered the text.
I hated having the fractions like "½" stored as "1/2" so I took to replacing these by the single character equivalents.
However, once doing so, the assert fires, so the class appears to not like my edits. I've taken to in-lining the file as a single string (above) - yuck, which I pull apart (-componentsSeparatedByString:) to an array, but perhaps someone else can tell me what's wrong with the approach?
Overall I favor assets' data obfuscation but it appears to have limits.
Not sure why, but naming my text files with a ".dat" type works.
The asset type reported is "dyn.ah62d4rv4ge80k2py" which I'm guessing is like the old Finder first few file bytes contents but this type was the same for several .dat files of differing content so it's probably related the the method used.

NSAttributedString initWithHTML incorrect character encoding?

-[NSMutableAttributedString initWithHTML:documentAttributes:] seems to mangle special characters:
NSString *html = #"“Hello” World"; // notice the smart quotes
NSData *htmlData = [html dataUsingEncoding:NSUTF8StringEncoding];
NSMutableAttributedString *as = [[NSMutableAttributedString alloc] initWithHTML:htmlData documentAttributes:nil];
NSLog(#"%#", as);
That prints “Hello†World followed by some RTF commands. In my application, I convert the attributed string to RTF and display it in an NSTextView, but the characters are corrupted there, too.
According to the documentation, the default encoding is UTF-8, but I tried being explicit and the result is the same:
NSDictionary *attributes = #{NSCharacterEncodingDocumentAttribute: [NSNumber numberWithInt:NSUTF8StringEncoding]};
NSMutableAttributedString *as = [[NSMutableAttributedString alloc] initWithHTML:htmlData documentAttributes:&attributes];
Use [html dataUsingEncoding:NSUnicodeStringEncoding] when creating the NSData and set the matching encoding option when you parse the HTML into an attributed string:
The documentation for NSCharacterEncodingDocumentAttribute is slightly confusing:
NSNumber, containing an int specifying the NSStringEncoding for the
file; for reading and writing plain text files and writing HTML;
default for plain text is the default encoding; default for HTML is
UTF-8.
So, you code should be:
NSString *html = #"“Hello” World";
NSData *htmlData = [html dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *options = #{NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType,
NSCharacterEncodingDocumentAttribute: #(NSUTF8StringEncoding)};
NSMutableAttributedString *as =
[[NSMutableAttributedString alloc] initWithHTML:htmlData
options: options
documentAttributes:nil];
The previous answer here works, but mostly by accident.
Making an NSData with NSUnicodeStringEncoding will tend to work, because that constant is an alias for NSUTF16StringEncoding, and UTF-16 is pretty easy for the system to identify. Easier than UTF-8, which apparently was being identified as some other superset of ASCII (it looks like NSWindowsCP1252StringEncoding in your case, probably because it's one of the few ASCII-based encodings with mappings for 0x8_ and 0x9_).
That answer is mistaken in quoting the documentation for NSCharacterEncodingDocumentAttribute, because "attributes" are what you get out of -initWithHTML. That's why it's NSDictionary ** and not just NSDictionary *. You can pass in a pointer to an NSDictionary *, and you'll get out keys like TopMargin/BottomMargin/LeftMargin/RightMargin, PaperSize, DocumentType, UTI, etc. Any values you try to pass in through the "attributes" dictionary are ignored.
You need to use "options" for passing values in, and the relevant option key is NSTextEncodingNameDocumentOption, which has no documented default value. It's passing the bytes to WebKit for parsing, so if you don't specify an encoding, presumably you're getting WebKit's encoding-guessing heuristics.
To guarantee the encoding types match between your NSData and NSAttributedString, what you should do is something like:
NSString *html = #"“Hello” World";
NSData *htmlData = [html dataUsingEncoding:NSUTF8StringEncoding];
NSMutableAttributedString *as =
[[NSMutableAttributedString alloc] initWithHTML:htmlData
options:#{NSTextEncodingNameDocumentOption: #"UTF-8"}
documentAttributes:nil];
Swift version of accepted answer is:
let htmlString: String = "Hello world contains html</br>"
let data: Data = Data(htmlString.utf8)
let options: [NSAttributedString.DocumentReadingOptionKey: Any] = [
.documentType: NSAttributedString.DocumentType.html,
.characterEncoding: String.Encoding.utf8.rawValue
]
let attributedString = try? NSAttributedString(data: data,
options: options,
documentAttributes: nil)

initWithContentsOfURL often returns nil

NSError *error;
NSString *string = [[NSString alloc]
initWithContentsOfURL:URL
encoding:NSUTF8StringEncoding
error:&error];
When I test this on my iPhone it always works when I have wifi turned on. However when I'm on 3G I often get nil. If I try perhaps 15 times in a row (I have an update button for this) I finally get the desired result.
My question is, is this problem located at the server side or is my code unstable? Should I use a different approach to get a more secure fetch of data?
You haven't provided enough information to give anything but a vague answer, but you do have some options here.
Most importantly, you have an "error" parameter that you should be printing out the results of. There's also a slightly better API you could be using in the NSString class.
Change your code to something like this:
NSError *error = NULL;
NSStringEncoding actualEncoding;
// variable names in Objective-C should usually start with lower case letters, so change
// URL in your code to "url", or even something more descriptive, like "urlOfOurString"
NSString *string = [[NSString alloc] initWithContentsOfURL:urlOfOurString usedEncoding:&actualEncoding error:&error];
if(string)
{
NSLog( #"hey, I actually got a result of %#", string);
if(actualEncoding != NSUTF8StringEncoding)
{
// I also suspect the string you're trying to load really isn't UTF8
NSLog( #"and look at that, the actual encoding wasn't NSUTF8StringEncoding");
}
} else {
NSLog( #"error when trying to fetch from URL %# - %#", [urlOfOurString absoluteString], [error localizedDescription]);
}
I'm now using STHTTPRequest instead. I recommend this library very much, easy to use yet powerful.

strange behavior of xcode 4.2

Strange and funny think is going on
Code:
-(void)saveFile
{
NSFileManager *fileMng = [NSFileManager defaultManager];
if(![fileMng fileExistsAtPath:self.appFilesPath])
{
NSError *error = nil;
BOOL success = [fileMng createDirectoryAtPath:self.appFilesPath withIntermediateDirectories:YES attributes:nil error:&error];
if(!success)
{
NSLog([error localizedDescription]);
}
}
NSLog([NSString stringWithFormat:#"%#",self.appFilesPath]);
[fileMng createFileAtPath:self.fileFullPath contents:self.fileData attributes:nil];
[self.fileData writeToFile:self.fileFullPath atomically:YES];
}
and this line
NSLog([NSString stringWithFormat:#"%#",self.appFilesPath]);
should give me something like this
file://localhost/Users/user/Library/Application%20Support/iPhone%20Simulator/5.0/Applications/BF35B859-514B-45AA-8E3A-B2CE65BD82B6/Documents/AppFiles
Directory AppFiles should be created under ../Documents/ directory,
but it's not there...
and the nslog gives me something like this:
file://localhost/Users/user/Library/Application瑳楲杮楗桴潆浲瑡:敲敬獡e摡䕤瑮楲獥牆浯楄瑣潩慮祲:扯敪瑣潆䭲祥:汣獡s獩楋摮晏汃獡㩳氀湥瑧h畡潴敲敬獡e敳佴橢捥㩴潦䭲祥:敲潭敶扏敪瑣潆䭲祥:湩整敧噲污敵戀潯噬污敵爀浥癯䅥汬扏敪瑣s湩瑩猀慨敲䥤獮慴据e敲楧瑳牥敎睴牯䑫晥畡瑬䙳牯灁䥰㩄挀灯y摡佤橢捥㩴洀楡䉮湵汤e畢摮敬摉湥楴楦牥椀䕳畱污潔瑓楲杮:桳牡摥潃普杩牵瑡潩n潣湵牴䑹晥畡瑬潆..few more screens of this bush..帴㽻椽絩8筞㴿楩}ㅶ䀶㨰帴㽻椽絩椸㈱䀀䜢佅楐數偬楯瑮"㽻搽絤䀸㨰4㉶䀴㨰笴㴿摤㡽䀀倢剂煥敵瑳牥"ㅶ䀶㨰⌴匸㈱䀀㰢䕇协灵潰瑲摥楔敬敓獴敓癲牥牐硯䑹汥来瑡㹥"㍶䀶㨰笴㴿摤㡽㉤椴㈳瘀㘱぀㐺㡀ㅩ2㉶䀸㨰帴㽻搽絤䤸㈱ㅤ椶㐲瘀㈱぀㐺癞8ㅀ䀶㨰帴㡶ㅣ2筞硟捰损湯敮瑣潩彮㵳}ㅀ䀶㨰䀴常彻䍟剆湵潌灯紽㈱帀彻䍟剆湵潌灯紽䀸㨰4筞彟䙃畒䱮潯㵰}癞䀸㨰4ㅀ䀲㨰帴㡶瘀〲぀㐺㡀ㅀ䤲㘱䀀㰢华慃档䑥汥来瑡㹥"㉀䀸㨰䀴笸㴿摤ㅽ2㽻搽絤㠲぀㐺㽻搽絤常㉤4㽻∽慬楴畴敤搢氢湯楧畴敤搢}upport/iPhone 貌Š߈imulator/5.0/Applications/CAF90A92-5B85-4FC0-8482-3702C3E98F8D/Documents/AppFiles
when i run code second time, the if condision is skiped, but ../Documents/ directory is stil empty
来瑡㹥"㉀䀸 - i mean wtf, first time something like this,
restarting xcode, mac did't help,
simulator is all set to english,
it also happen in other projects,
keyboard setting ant nationalization looks ok,
so realy I have no clue what is wrong
any ideas?
You can not just NSLog a string. The string argument it takes is a format (like in printf) which interpeted specially. In your case, it is reading random memory because %20S means to interpret the next argument as pointer to null-terminated string of wide characters and print it right-aligned in 20 columns; and they are Chinese because most of Unicode is occupied by Chinese hieroglyphs.
This is the correct way to do it:
NSLog(#"%#", self.appFilesPath);

Core Data Transformable attributes NOT working with NSPredicate

I often use Transformable for Core Data attributes, so I can change them later.
However, it seems like, if I want to use NSPredicate to find a NSManagedObject, using "uniqueKey == %#", or "uniqueKey MATCHES[cd] %#", it's not working as it should.
It always misses matching objects, until I change the attributes of the uniqueKey of the matching object to have specific class like NSString, or NSNumber.
Can someone explain the limitation of using NSPredicate with Transformable attributes?
Note: I'm not sure when/if this has changed since 5/2011 (from Scott Ahten's accepted answer), but you can absolutely search with NSPredicate on transformable attributes. Scott correctly explained why your assumptions were broken, but if Can someone explain the limitation of using NSPredicate with Transformable attributes? was your question, he implied that it is not possible, and that is incorrect.
Since the is the first google hit for "Core Data transformable value search nspredicate" (what I searched for trying to find inspiration), I wanted to add my working answer.
How to use NSPredicate with transformable properties
Short, heady answer: you need to be smart about your data transformers. You need to transfrom the value to NSData that contains what I'll call "primitive identifying information", i.e. the smallest, most identifying set of bytes that can be used to reconstruct your object. Long answer, ...
Foremost, consider:
Did you actual mean to use a transformable attribute? If any supported data type -- even binary data -- will suffice, use it.
Do you understand what transformable attributes actually are? How they pack and unpack data to and from the store? Review Non-Standard Persistent Attributes in Apple's documentation.
After reading the above, ask: does custom code that hides a supported type "backing attribute" work for you? Possibly use that technique.
Now, past those considerations, transformable attributes are rather slick. Frankly, writing an NSValueTransformer "FooToData" for Foo instances to NSData seemed cleaner than writing a lot of adhoc custom code. I haven't found a case where Core Data doesn't know it needs to transform the data using the registered NSValueTransformer.
To proceed simply address these concerns:
Did you tell Core Data what transformer to use? Open the Core Data model in table view, click the entity, click the attribute, load the Data Model Inspector pane. Under "Attribute Type: Transformable", set "Name" to your transformer.
Use a default transformer (again, see the previous Apple docs) or write your own transformer -- transformedValue: must return NSData.
NSKeyedUnarchiveFromDataTransformerName is the default transformer and may not suffice, or may draw in somewhat-transient instance data that can make two similar objects be different when they are equal.
The transformed value should contain only -- what I'll call -- "primitive identifying information". The store is going to be comparing bytes, so every byte counts.
You may also register your transformer globally. I have to do this since I actually reuse them elsewhere in the app -- e.g. NSString *name = #"FooTrans"; [NSValueTransformer setValueTransformer:[NSClassFromString(name) new] forName:name];
You probably don't want to use transforms heavily queried data operations - e.g. a large import where the primary key information uses transformers - yikes!
And then in the end, I simply use this to test for equality for high-level object attributes on models with NSPredicates -- e.g. "%K == %#" -- and it works fine. I haven't tried some of the various matching terms, but I wouldn't be surprised if they worked sometimes, and others not.
Here's an example of an NSURL to NSData transformer. Why not just store the string? Yeah, that's fine -- that's a good example of custom code masking the stored attribute. This example illustrates that an extra byte is added to the stringified URL to record if it was a file URL or not -- allowing us to know what constructors to use when the object is unpacked.
// URLToDataTransformer.h - interface
extern NSString *const kURLToDataTransformerName;
#interface URLToDataTransformer : NSValueTransformer
#end
...
// URLToDataTransformer.m - implementation
#import "URLToDataTransformer.h"
NSString *const kURLToDataTransformerName = #"URLToDataTransformer";
#implementation URLToDataTransformer
+ (Class)transformedValueClass { return [NSData class]; }
+ (BOOL)allowsReverseTransformation { return YES; }
- (id)transformedValue:(id)value
{
if (![value isKindOfClass:[NSURL class]])
{
// Log error ...
return nil;
}
NSMutableData *data;
char fileType = 0;
if ([value isFileURL])
{
fileType = 1;
data = [NSMutableData dataWithBytes:&fileType length:1];
[data appendData:[[(NSURL *)value path] dataUsingEncoding:NSUTF8StringEncoding]];
}
else
{
fileType = -1;
data = [NSMutableData dataWithBytes:&fileType length:1];
[data appendData:[[(NSURL *)value absoluteString] dataUsingEncoding:NSUTF8StringEncoding]];
}
return data;
}
- (id)reverseTransformedValue:(id)value
{
if (![value isKindOfClass:[NSData class]])
{
// Log error ...
return nil;
}
NSURL *url = nil;
NSData *data = (NSData *)value;
char fileType = 0;
NSRange range = NSMakeRange(1, [data length]-1);
[data getBytes:&fileType length:1];
if (1 == fileType)
{
NSData *actualData = [data subdataWithRange:range];
NSString *str = [[NSString alloc] initWithData:actualData encoding:NSUTF8StringEncoding];
url = [NSURL fileURLWithPath:str];
}
else if (-1 == fileType)
{
NSData *actualData = [data subdataWithRange:range];
NSString *str = [[NSString alloc] initWithData:actualData encoding:NSUTF8StringEncoding];
url = [NSURL URLWithString:str];
}
else
{
// Log error ...
return nil;
}
return url;
}
#end
Transformable attributes are usually persisted as archived binary data. As such, you are attempting to compare an instance of NSData with an instance of NSString or NSNumber.
Since these classes interpret the same data in different ways, they are not considered a match.
you can try this way
NSExpression *exprPath = [NSExpression expressionForKeyPath:#"transformable_field"];
NSExpression *exprKeyword = [NSExpression expressionForConstantValue:nsdataValue];
NSPredicate *predicate = [NSComparisonPredicate predicateWithLeftExpression:exprPath rightExpression:exprKeyword modifier:NSDirectPredicateModifier type:NSEqualToPredicateOperatorType options:0];

Resources