まず第一に、私はこれを見つけました: Objective C HTMLエスケープ/アンエスケープですが、私にはうまくいきません。
私のエンコードされた文字(RSSフィードからのbtw)は次のようになります。 &
私はネット全体を検索し、関連するディスカッションを見つけましたが、私の特定のエンコーディングに対する修正はありません。それらは16進文字と呼ばれていると思います。
8
です。
まず第一に、私はこれを見つけました: Objective C HTMLエスケープ/アンエスケープですが、私にはうまくいきません。
私のエンコードされた文字(RSSフィードからのbtw)は次のようになります。 &
私はネット全体を検索し、関連するディスカッションを見つけましたが、私の特定のエンコーディングに対する修正はありません。それらは16進文字と呼ばれていると思います。
8
です。
回答:
それらはCharacter Entity Referencesと呼ばれます。それらがその形を取るとき、&#<number>;
それらは数値実体参照と呼ばれます。基本的に、これは、置換されるバイトの文字列表現です。の場合&
、これは、ISO-8859-1文字コード化スキームでの値が38の文字、つまりを表し&
ます。
アンパサンドをRSSでエンコードする必要があるのは、予約された特殊文字だからです。
何をする必要がある文字列を解析し、間の値に一致するバイトを持つエンティティを置き換えている&#
と;
。目標Cでこれを行うための優れた方法は知りませんが、このスタックオーバーフローの質問が役立つかもしれません。
編集:2年前にこれに答えてから、いくつかの素晴らしい解決策があります。以下の@Michael Waterfallの回答を参照してください。
HTMLのNSStringカテゴリを確認してください。利用可能な方法は次のとおりです。
- (NSString *)stringByConvertingHTMLToPlainText;
- (NSString *)stringByDecodingHTMLEntities;
- (NSString *)stringByEncodingHTMLEntities;
- (NSString *)stringWithNewLinesAsBRs;
- (NSString *)stringByRemovingNewLinesAndWhitespace;
ダニエルによるものは基本的にとてもいいです、そして私はそこでいくつかの問題を修正しました:
NSSCannerのスキップ文字を削除しました(それ以外の場合、2つの連続したエンティティ間のスペースは無視されます)
[スキャナーsetCharactersToBeSkipped: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's 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;
}
iOS 7以降、次NSAttributedString
のNSHTMLTextDocumentType
属性を使用してHTML文字をネイティブにデコードできます。
NSString *htmlString = @" & & < > ™ © ♥ ♣ ♠ ♦";
NSData *stringData = [htmlString dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *options = @{NSDocumentTypeDocumentAttribute:NSHTMLTextDocumentType};
NSAttributedString *decodedString;
decodedString = [[NSAttributedString alloc] initWithData:stringData
options:options
documentAttributes:NULL
error:NULL];
デコードされた属性文字列は、次のように表示されます:&&<>™©♥♣♠♦。
注:これは、メインスレッドで呼び出された場合にのみ機能します。
最もシンプルなオプションの1つである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;
また、プロジェクトに含める必要があるのは、ヘッダー、実装、およびの3つのファイルのみですGTMDefines.h
。
これをGitHubか何かに投稿するべきです。これはNSStringのカテゴリに入り、NSScanner
、実装し、16進数と10進数の両方の数値エンティティと通常のシンボリックエンティティを処理します。
また、不正な形式の文字列(&の後に無効な文字シーケンスが続く場合)を比較的適切に処理します。これは、このコードを使用する私のリリース済みアプリでは非常に重要であることがわかりました。
- (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's 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;
}
goto
ひどいコードスタイルとしてsを使用することは避けてください。この行goto finish;
をに置き換える必要がありbreak;
ます。
これは、RegexKitLiteフレームワークを使用して行う方法です。
-(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;
}
これが誰かを助けることを願っています。
この関数だけを使用して、この問題を解決できます。
+ (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;
}
ウォルティヨンの回答の Swiftバージョンは次のとおりです。
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
}
}
実際、Michael Waterfallの優れたMWFeedParserフレームワーク(彼の回答を参照)は、ARCのサポートで更新したrmchaaraによってforkされました。
それは本当にうまく機能し、私はstringByDecodingHTMLEntitiesメソッドを使用して完璧に機能しました。
別のソリューションが必要であるかのように!これは非常にシンプルで非常に効果的です。
@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
たとえば、文字エンティティ参照を文字列として持っている場合@"2318"
、を使用して、正しいUnicode文字で再コード化された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: ⌘"
ジュガレの回答のSwift 3バージョン
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
}
}
&#...;
して、文字列内のを同等の文字で検索および置換します。