Декодирование символов HTML в Objective-C/Cocoa Touch
Прежде всего, я нашел это: Objective C HTML escape/unescape, но он не " t для меня.
Мои закодированные символы (поступающие из RSS-канала, кстати) выглядят следующим образом: &
Я искал по всей сети и нашел связанные обсуждения, но никаких исправлений для моей конкретной кодировки, я думаю, что они называются шестнадцатеричными символами.
Ответы
Ответ 1
Те, которые называются Ссылки на характерные лица. Когда они принимают форму &#<number>;
, они называются числовыми ссылками на сущности. По сути, это строковое представление байта, которое должно быть заменено. В случае &
он представляет символ со значением 38 в схеме кодирования символов ISO-8859-1, который равен &
.
Причина, по которой амперсанд должен быть закодирован в RSS, является зарезервированным специальным символом.
Что вам нужно сделать, это проанализировать строку и заменить сущности байтом, соответствующим значению между &#
и ;
. Я не знаю никаких отличных способов сделать это в объекте C, но этот вопрос может быть полезен.
Изменить: так как ответ на этот вопрос два года назад есть несколько отличных решений; см. @Michael Waterfall ниже.
Ответ 2
Ознакомьтесь с моей категорией NSString для HTML. Вот доступные методы:
- (NSString *)stringByConvertingHTMLToPlainText;
- (NSString *)stringByDecodingHTMLEntities;
- (NSString *)stringByEncodingHTMLEntities;
- (NSString *)stringWithNewLinesAsBRs;
- (NSString *)stringByRemovingNewLinesAndWhitespace;
Ответ 3
Один из Даниэля в основном очень приятный, и я исправил там несколько вопросов:
-
удалено символ пропуска для NSSCanner (иначе пробелы между двумя непрерывными объектами будут игнорироваться
[набор сканеровCharactersToBeSkipped: nil];
-
зафиксировал синтаксический анализ, когда есть изолированные '&' символы (я не уверен, что это "правильный" вывод для этого, я просто сравнил его с firefox):
например.
&#ABC DF & B' & C' Items (288)
здесь находится модифицированный код:
- (NSString *)stringByDecodingXMLEntities {
NSUInteger myLength = [self length];
NSUInteger ampIndex = [self rangeOfString:@"&" options:NSLiteralSearch].location;
// Short-circuit if there are no ampersands.
if (ampIndex == NSNotFound) {
return self;
}
// Make result string with some extra capacity.
NSMutableString *result = [NSMutableString stringWithCapacity:(myLength * 1.25)];
// First iteration doesn't need to scan to & since we did that already, but for code simplicity sake we'll do it again with the scanner.
NSScanner *scanner = [NSScanner scannerWithString:self];
[scanner setCharactersToBeSkipped:nil];
NSCharacterSet *boundaryCharacterSet = [NSCharacterSet characterSetWithCharactersInString:@" \t\n\r;"];
do {
// Scan up to the next entity or the end of the string.
NSString *nonEntityString;
if ([scanner scanUpToString:@"&" intoString:&nonEntityString]) {
[result appendString:nonEntityString];
}
if ([scanner isAtEnd]) {
goto finish;
}
// Scan either a HTML or numeric character entity reference.
if ([scanner scanString:@"&" intoString:NULL])
[result appendString:@"&"];
else if ([scanner scanString:@"'" intoString:NULL])
[result appendString:@"'"];
else if ([scanner scanString:@""" intoString:NULL])
[result appendString:@"\""];
else if ([scanner scanString:@"<" intoString:NULL])
[result appendString:@"<"];
else if ([scanner scanString:@">" intoString:NULL])
[result appendString:@">"];
else if ([scanner scanString:@"&#" intoString:NULL]) {
BOOL gotNumber;
unsigned charCode;
NSString *xForHex = @"";
// Is it hex or decimal?
if ([scanner scanString:@"x" intoString:&xForHex]) {
gotNumber = [scanner scanHexInt:&charCode];
}
else {
gotNumber = [scanner scanInt:(int*)&charCode];
}
if (gotNumber) {
[result appendFormat:@"%C", (unichar)charCode];
[scanner scanString:@";" intoString:NULL];
}
else {
NSString *unknownEntity = @"";
[scanner scanUpToCharactersFromSet:boundaryCharacterSet intoString:&unknownEntity];
[result appendFormat:@"&#%@%@", xForHex, unknownEntity];
//[scanner scanUpToString:@";" intoString:&unknownEntity];
//[result appendFormat:@"&#%@%@;", xForHex, unknownEntity];
NSLog(@"Expected numeric character entity but got &#%@%@;", xForHex, unknownEntity);
}
}
else {
NSString *amp;
[scanner scanString:@"&" intoString:&]; //an isolated & symbol
[result appendString:amp];
/*
NSString *unknownEntity = @"";
[scanner scanUpToString:@";" intoString:&unknownEntity];
NSString *semicolon = @"";
[scanner scanString:@";" intoString:&semicolon];
[result appendFormat:@"%@%@", unknownEntity, semicolon];
NSLog(@"Unsupported XML character entity %@%@", unknownEntity, semicolon);
*/
}
}
while (![scanner isAtEnd]);
finish:
return result;
}
Ответ 4
Как и в iOS 7, вы можете декодировать HTML-символы изначально с помощью NSAttributedString
с атрибутом NSHTMLTextDocumentType
:
NSString *htmlString = @" & & < > ™ © ♥ ♣ ♠ ♦";
NSData *stringData = [htmlString dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *options = @{NSDocumentTypeDocumentAttribute:NSHTMLTextDocumentType};
NSAttributedString *decodedString;
decodedString = [[NSAttributedString alloc] initWithData:stringData
options:options
documentAttributes:NULL
error:NULL];
Декодированная атрибутная строка теперь будет отображаться как: && < > ™ © ♥ ♣ ♠ ♦.
Примечание: Это будет работать только при вызове основного потока.
Ответ 5
Никто не упоминает один из самых простых вариантов: Google Toolbox для Mac
(Несмотря на название, это работает и на iOS.)
https://github.com/google/google-toolbox-for-mac/blob/master/Foundation/GTMNSString%2BHTML.h
/// Get a string where internal characters that are escaped for HTML are unescaped
//
/// For example, '&' becomes '&'
/// Handles   and 2 cases as well
///
// Returns:
// Autoreleased NSString
//
- (NSString *)gtm_stringByUnescapingFromHTML;
И мне пришлось включить только три файла в проект: заголовок, реализация и GTMDefines.h
.
Ответ 6
Я должен опубликовать это на GitHub или что-то в этом роде. Это относится к категории NSString, использует NSScanner
для реализации и обрабатывает как шестнадцатеричные, так и десятичные числовые символьные сущности, а также обычные символические.
Кроме того, он обрабатывает некорректные строки (когда у вас есть некоторая некорректная последовательность символов), и это оказалось очень важным в моем выпущенном приложение, которое использует этот код.
- (NSString *)stringByDecodingXMLEntities {
NSUInteger myLength = [self length];
NSUInteger ampIndex = [self rangeOfString:@"&" options:NSLiteralSearch].location;
// Short-circuit if there are no ampersands.
if (ampIndex == NSNotFound) {
return self;
}
// Make result string with some extra capacity.
NSMutableString *result = [NSMutableString stringWithCapacity:(myLength * 1.25)];
// First iteration doesn't need to scan to & since we did that already, but for code simplicity sake we'll do it again with the scanner.
NSScanner *scanner = [NSScanner scannerWithString:self];
do {
// Scan up to the next entity or the end of the string.
NSString *nonEntityString;
if ([scanner scanUpToString:@"&" intoString:&nonEntityString]) {
[result appendString:nonEntityString];
}
if ([scanner isAtEnd]) {
goto finish;
}
// Scan either a HTML or numeric character entity reference.
if ([scanner scanString:@"&" intoString:NULL])
[result appendString:@"&"];
else if ([scanner scanString:@"'" intoString:NULL])
[result appendString:@"'"];
else if ([scanner scanString:@""" intoString:NULL])
[result appendString:@"\""];
else if ([scanner scanString:@"<" intoString:NULL])
[result appendString:@"<"];
else if ([scanner scanString:@">" intoString:NULL])
[result appendString:@">"];
else if ([scanner scanString:@"&#" intoString:NULL]) {
BOOL gotNumber;
unsigned charCode;
NSString *xForHex = @"";
// Is it hex or decimal?
if ([scanner scanString:@"x" intoString:&xForHex]) {
gotNumber = [scanner scanHexInt:&charCode];
}
else {
gotNumber = [scanner scanInt:(int*)&charCode];
}
if (gotNumber) {
[result appendFormat:@"%C", charCode];
}
else {
NSString *unknownEntity = @"";
[scanner scanUpToString:@";" intoString:&unknownEntity];
[result appendFormat:@"&#%@%@;", xForHex, unknownEntity];
NSLog(@"Expected numeric character entity but got &#%@%@;", xForHex, unknownEntity);
}
[scanner scanString:@";" intoString:NULL];
}
else {
NSString *unknownEntity = @"";
[scanner scanUpToString:@";" intoString:&unknownEntity];
NSString *semicolon = @"";
[scanner scanString:@";" intoString:&semicolon];
[result appendFormat:@"%@%@", unknownEntity, semicolon];
NSLog(@"Unsupported XML character entity %@%@", unknownEntity, semicolon);
}
}
while (![scanner isAtEnd]);
finish:
return result;
}
Ответ 7
Так я это делаю, используя RegexKitLite framework:
-(NSString*) decodeHtmlUnicodeCharacters: (NSString*) html {
NSString* result = [html copy];
NSArray* matches = [result arrayOfCaptureComponentsMatchedByRegex: @"\\&#([\\d]+);"];
if (![matches count])
return result;
for (int i=0; i<[matches count]; i++) {
NSArray* array = [matches objectAtIndex: i];
NSString* charCode = [array objectAtIndex: 1];
int code = [charCode intValue];
NSString* character = [NSString stringWithFormat:@"%C", code];
result = [result stringByReplacingOccurrencesOfString: [array objectAtIndex: 0]
withString: character];
}
return result;
}
Надеюсь, это поможет кому-то.
Ответ 8
эту проблему можно использовать только для этой проблемы.
+ (NSString*) decodeHtmlUnicodeCharactersToString:(NSString*)str
{
NSMutableString* string = [[NSMutableString alloc] initWithString:str]; // #&39; replace with '
NSString* unicodeStr = nil;
NSString* replaceStr = nil;
int counter = -1;
for(int i = 0; i < [string length]; ++i)
{
unichar char1 = [string characterAtIndex:i];
for (int k = i + 1; k < [string length] - 1; ++k)
{
unichar char2 = [string characterAtIndex:k];
if (char1 == '&' && char2 == '#' )
{
++counter;
unicodeStr = [string substringWithRange:NSMakeRange(i + 2 , 2)];
// read integer value i.e, 39
replaceStr = [string substringWithRange:NSMakeRange (i, 5)]; // #&39;
[string replaceCharactersInRange: [string rangeOfString:replaceStr] withString:[NSString stringWithFormat:@"%c",[unicodeStr intValue]]];
break;
}
}
}
[string autorelease];
if (counter > 1)
return [self decodeHtmlUnicodeCharactersToString:string];
else
return string;
}
Ответ 9
На самом деле великая структура MWFeedParser Майкла Водопада (упомянутая его ответ) была раздвоена rmchaara, которая обновила ее поддержкой ARC!
Вы можете найти его в Github здесь
Это действительно отлично работает, я использовал метод stringByDecodingHTMLEntities и работает безупречно.
Ответ 10
Вот быстрая версия Вальти Йенга ответ:
extension String {
static private let mappings = [""" : "\"","&" : "&", "<" : "<", ">" : ">"," " : " ","¡" : "¡","¢" : "¢","£" : " £","¤" : "¤","¥" : "¥","¦" : "¦","§" : "§","¨" : "¨","©" : "©","ª" : " ª","«" : "«","¬" : "¬","®" : "®","¯" : "¯","°" : "°","±" : "±","² " : "²","³" : "³","´" : "´","µ" : "µ","¶" : "¶","·" : "·","¸" : "¸","¹" : "¹","º" : "º","»" : "»&","frac14" : "¼","½" : "½","¾" : "¾","¿" : "¿","×" : "×","÷" : "÷","Ð" : "Ð","ð" : "ð","Þ" : "Þ","þ" : "þ","Æ" : "Æ","æ" : "æ","&OElig" : "Œ","&oelig" : "œ","Å" : "Å","Ø" : "Ø","Ç" : "Ç","ç" : "ç","ß" : "ß","Ñ" : "Ñ","ñ":"ñ",]
func stringByDecodingXMLEntities() -> String {
guard let _ = self.rangeOfString("&", options: [.LiteralSearch]) else {
return self
}
var result = ""
let scanner = NSScanner(string: self)
scanner.charactersToBeSkipped = nil
let boundaryCharacterSet = NSCharacterSet(charactersInString: " \t\n\r;")
repeat {
var nonEntityString: NSString? = nil
if scanner.scanUpToString("&", intoString: &nonEntityString) {
if let s = nonEntityString as? String {
result.appendContentsOf(s)
}
}
if scanner.atEnd {
break
}
var didBreak = false
for (k,v) in String.mappings {
if scanner.scanString(k, intoString: nil) {
result.appendContentsOf(v)
didBreak = true
break
}
}
if !didBreak {
if scanner.scanString("&#", intoString: nil) {
var gotNumber = false
var charCodeUInt: UInt32 = 0
var charCodeInt: Int32 = -1
var xForHex: NSString? = nil
if scanner.scanString("x", intoString: &xForHex) {
gotNumber = scanner.scanHexInt(&charCodeUInt)
}
else {
gotNumber = scanner.scanInt(&charCodeInt)
}
if gotNumber {
let newChar = String(format: "%C", (charCodeInt > -1) ? charCodeInt : charCodeUInt)
result.appendContentsOf(newChar)
scanner.scanString(";", intoString: nil)
}
else {
var unknownEntity: NSString? = nil
scanner.scanUpToCharactersFromSet(boundaryCharacterSet, intoString: &unknownEntity)
let h = xForHex ?? ""
let u = unknownEntity ?? ""
result.appendContentsOf("&#\(h)\(u)")
}
}
else {
scanner.scanString("&", intoString: nil)
result.appendContentsOf("&")
}
}
} while (!scanner.atEnd)
return result
}
}
Ответ 11
Как будто вам нужно другое решение! Это довольно просто и довольно эффективно:
@interface NSString (NSStringCategory)
- (NSString *) stringByReplacingISO8859Codes;
@end
@implementation NSString (NSStringCategory)
- (NSString *) stringByReplacingISO8859Codes
{
NSString *dataString = self;
do {
//*** See if string contains &# prefix
NSRange range = [dataString rangeOfString: @"&#" options: NSRegularExpressionSearch];
if (range.location == NSNotFound) {
break;
}
//*** Get the next three charaters after the prefix
NSString *isoHex = [dataString substringWithRange: NSMakeRange(range.location + 2, 3)];
//*** Create the full code for replacement
NSString *isoString = [NSString stringWithFormat: @"&#%@;", isoHex];
//*** Convert to decimal integer
unsigned decimal = 0;
NSScanner *scanner = [NSScanner scannerWithString: [NSString stringWithFormat: @"0%@", isoHex]];
[scanner scanHexInt: &decimal];
//*** Use decimal code to get unicode character
NSString *unicode = [NSString stringWithFormat:@"%C", decimal];
//*** Replace all occurences of this code in the string
dataString = [dataString stringByReplacingOccurrencesOfString: isoString withString: unicode];
} while (TRUE); //*** Loop until we hit the NSNotFound
return dataString;
}
@end
Ответ 12
Если у вас есть ссылка на объект символов в виде строки, например, @"2318"
, вы можете извлечь перекодированную NSString с правильным символом Юникода, используя strtoul
;
NSString *unicodePoint = @"2318"
unichar iconChar = (unichar) strtoul(unicodePoint.UTF8String, NULL, 16);
NSString *recoded = [NSString stringWithFormat:@"%C", iconChar];
NSLog(@"recoded: %@", recoded");
// prints out "recoded: ⌘"
Ответ 13
Свифт 3 версии ответа Jugale
extension String {
static private let mappings = [""" : "\"","&" : "&", "<" : "<", ">" : ">"," " : " ","¡" : "¡","¢" : "¢","£" : " £","¤" : "¤","¥" : "¥","¦" : "¦","§" : "§","¨" : "¨","©" : "©","ª" : " ª","«" : "«","¬" : "¬","®" : "®","¯" : "¯","°" : "°","±" : "±","² " : "²","³" : "³","´" : "´","µ" : "µ","¶" : "¶","·" : "·","¸" : "¸","¹" : "¹","º" : "º","»" : "»&","frac14" : "¼","½" : "½","¾" : "¾","¿" : "¿","×" : "×","÷" : "÷","Ð" : "Ð","ð" : "ð","Þ" : "Þ","þ" : "þ","Æ" : "Æ","æ" : "æ","&OElig" : "Œ","&oelig" : "œ","Å" : "Å","Ø" : "Ø","Ç" : "Ç","ç" : "ç","ß" : "ß","Ñ" : "Ñ","ñ":"ñ",]
func stringByDecodingXMLEntities() -> String {
guard let _ = self.range(of: "&", options: [.literal]) else {
return self
}
var result = ""
let scanner = Scanner(string: self)
scanner.charactersToBeSkipped = nil
let boundaryCharacterSet = CharacterSet(charactersIn: " \t\n\r;")
repeat {
var nonEntityString: NSString? = nil
if scanner.scanUpTo("&", into: &nonEntityString) {
if let s = nonEntityString as? String {
result.append(s)
}
}
if scanner.isAtEnd {
break
}
var didBreak = false
for (k,v) in String.mappings {
if scanner.scanString(k, into: nil) {
result.append(v)
didBreak = true
break
}
}
if !didBreak {
if scanner.scanString("&#", into: nil) {
var gotNumber = false
var charCodeUInt: UInt32 = 0
var charCodeInt: Int32 = -1
var xForHex: NSString? = nil
if scanner.scanString("x", into: &xForHex) {
gotNumber = scanner.scanHexInt32(&charCodeUInt)
}
else {
gotNumber = scanner.scanInt32(&charCodeInt)
}
if gotNumber {
let newChar = String(format: "%C", (charCodeInt > -1) ? charCodeInt : charCodeUInt)
result.append(newChar)
scanner.scanString(";", into: nil)
}
else {
var unknownEntity: NSString? = nil
scanner.scanUpToCharacters(from: boundaryCharacterSet, into: &unknownEntity)
let h = xForHex ?? ""
let u = unknownEntity ?? ""
result.append("&#\(h)\(u)")
}
}
else {
scanner.scanString("&", into: nil)
result.append("&")
}
}
} while (!scanner.isAtEnd)
return result
}
}