Использование Objective C/Cocoa для unescape символов Unicode, то есть\u1234
Некоторые сайты, из которых я извлекаю данные, возвращают строки UTF-8, а символы UTF-8 экранированы, то есть: \u5404\u500b\u90fd
Есть ли встроенная функция cocoa, которая может помочь в этом или мне придется написать собственный алгоритм декодирования.
Ответы
Ответ 1
Нет встроенной функции для выполнения функции C.
Вы можете немного обмануть с помощью NSPropertyListSerialization
, так как plist "старого текста" поддерживает C escaping через \Uxxxx
:
NSString* input = @"ab\"cA\"BC\\u2345\\u0123";
// will cause trouble if you have "abc\\\\uvw"
NSString* esc1 = [input stringByReplacingOccurrencesOfString:@"\\u" withString:@"\\U"];
NSString* esc2 = [esc1 stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""];
NSString* quoted = [[@"\"" stringByAppendingString:esc2] stringByAppendingString:@"\""];
NSData* data = [quoted dataUsingEncoding:NSUTF8StringEncoding];
NSString* unesc = [NSPropertyListSerialization propertyListFromData:data
mutabilityOption:NSPropertyListImmutable format:NULL
errorDescription:NULL];
assert([unesc isKindOfClass:[NSString class]]);
NSLog(@"Output = %@", unesc);
но помните, что это не очень эффективно. Это намного лучше, если вы напишете собственный парсер. (Кстати, вы расшифровываете строки JSON? Если да, вы можете использовать существующие парсеры JSON.)
Ответ 2
Правильно, что Cocoa не предлагает решение, но Core Foundation делает: CFStringTransform
.
CFStringTransform
живет в пыльном, удаленном уголке Mac OS (и iOS), и поэтому он немного знает драгоценный камень. Это интерфейс Apple совместимый с ICU механизм трансформации строк. Он может выполнять настоящую магию, такую как транслитерации между греческими и латинскими (или любыми известными сценариями), но также может использоваться для выполнения мирских задач, таких как unescaping strings с дерьмового сервера:
NSString *input = @"\\u5404\\u500b\\u90fd";
NSString *convertedString = [input mutableCopy];
CFStringRef transform = CFSTR("Any-Hex/Java");
CFStringTransform((__bridge CFMutableStringRef)convertedString, NULL, transform, YES);
NSLog(@"convertedString: %@", convertedString);
// prints: 各個都, tada!
Как я уже сказал, CFStringTransform
действительно мощный. Он поддерживает ряд предопределенных преобразований, таких как сопоставления событий, нормализации или преобразование имени символа юникода. Вы даже можете создавать свои собственные преобразования.
Я понятия не имею, почему Apple не делает его доступным из Cocoa.
Изменить 2015:
OS X 10.11 и iOS 9 добавьте следующий метод в Foundation:
- (nullable NSString *)stringByApplyingTransform:(NSString *)transform reverse:(BOOL)reverse;
Итак, пример сверху становится...
NSString *input = @"\\u5404\\u500b\\u90fd";
NSString *convertedString = [input stringByApplyingTransform:@"Any-Hex/Java"
reverse:YES];
NSLog(@"convertedString: %@", convertedString);
Спасибо @nschmidt за головы.
Ответ 3
Вот что я написал. Надеюсь, это поможет некоторым людям.
+ (NSString*) unescapeUnicodeString:(NSString*)string
{
// unescape quotes and backwards slash
NSString* unescapedString = [string stringByReplacingOccurrencesOfString:@"\\\"" withString:@"\""];
unescapedString = [unescapedString stringByReplacingOccurrencesOfString:@"\\\\" withString:@"\\"];
// tokenize based on unicode escape char
NSMutableString* tokenizedString = [NSMutableString string];
NSScanner* scanner = [NSScanner scannerWithString:unescapedString];
while ([scanner isAtEnd] == NO)
{
// read up to the first unicode marker
// if a string has been scanned, it a token
// and should be appended to the tokenized string
NSString* token = @"";
[scanner scanUpToString:@"\\u" intoString:&token];
if (token != nil && token.length > 0)
{
[tokenizedString appendString:token];
continue;
}
// skip two characters to get past the marker
// check if the range of unicode characters is
// beyond the end of the string (could be malformed)
// and if it is, move the scanner to the end
// and skip this token
NSUInteger location = [scanner scanLocation];
NSInteger extra = scanner.string.length - location - 4 - 2;
if (extra < 0)
{
NSRange range = {location, -extra};
[tokenizedString appendString:[scanner.string substringWithRange:range]];
[scanner setScanLocation:location - extra];
continue;
}
// move the location pas the unicode marker
// then read in the next 4 characters
location += 2;
NSRange range = {location, 4};
token = [scanner.string substringWithRange:range];
unichar codeValue = (unichar) strtol([token UTF8String], NULL, 16);
[tokenizedString appendString:[NSString stringWithFormat:@"%C", codeValue]];
// move the scanner past the 4 characters
// then keep scanning
location += 4;
[scanner setScanLocation:location];
}
// done
return tokenizedString;
}
+ (NSString*) escapeUnicodeString:(NSString*)string
{
// lastly escaped quotes and back slash
// note that the backslash has to be escaped before the quote
// otherwise it will end up with an extra backslash
NSString* escapedString = [string stringByReplacingOccurrencesOfString:@"\\" withString:@"\\\\"];
escapedString = [escapedString stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""];
// convert to encoded unicode
// do this by getting the data for the string
// in UTF16 little endian (for network byte order)
NSData* data = [escapedString dataUsingEncoding:NSUTF16LittleEndianStringEncoding allowLossyConversion:YES];
size_t bytesRead = 0;
const char* bytes = data.bytes;
NSMutableString* encodedString = [NSMutableString string];
// loop through the byte array
// read two bytes at a time, if the bytes
// are above a certain value they are unicode
// otherwise the bytes are ASCII characters
// the %C format will write the character value of bytes
while (bytesRead < data.length)
{
uint16_t code = *((uint16_t*) &bytes[bytesRead]);
if (code > 0x007E)
{
[encodedString appendFormat:@"\\u%04X", code];
}
else
{
[encodedString appendFormat:@"%C", code];
}
bytesRead += sizeof(uint16_t);
}
// done
return encodedString;
}
Ответ 4
простой код:
const char *cString = [unicodeStr cStringUsingEncoding:NSUTF8StringEncoding];
NSString *resultStr = [NSString stringWithCString:cString encoding:NSNonLossyASCIIStringEncoding];
from: fooobar.com/info/153357/...