[iphone] Objective-C / Cocoa Touch에서 HTML 문자 디코딩

우선, 나는 이것을 발견했습니다. Objective C HTML escape / unescape 이지만 작동하지 않습니다.

내 인코딩 된 문자 (RSS 피드, btw에서 가져옴)는 다음과 같습니다. &

인터넷 전체를 검색하고 관련 토론을 찾았지만 특정 인코딩에 대한 수정 사항이 없어서 16 진수 문자라고 생각합니다.



답변

이를 문자 엔티티 참조 라고 합니다. 형식을 취할 때 숫자 엔티티 참조&#<number>; 라고 합니다. 기본적으로 대체되어야하는 바이트의 문자열 표현입니다. 의 경우 ISO-8859-1 문자 인코딩 체계에서 값이 38 인 문자를 나타냅니다 .&#038;&

앰퍼샌드가 RSS로 인코딩되어야하는 이유는 예약 된 특수 문자이기 때문입니다.

해야 할 일은 문자열을 파싱하고 엔티티를과 사이의 값 &#과 일치하는 바이트로 바꾸는 것 ;입니다. 객관적인 C에서이 작업을 수행하는 좋은 방법은 없지만 이 스택 오버플로 질문 이 도움이 될 수 있습니다.

편집 : 약 2 년 전에 이에 대한 답변 이후 몇 가지 훌륭한 솔루션이 있습니다. 아래 @Michael Waterfall의 답변을 참조하십시오.


답변

HTML에 대한NSString 카테고리를 확인하십시오 . 사용 가능한 방법은 다음과 같습니다.

- (NSString *)stringByConvertingHTMLToPlainText;
- (NSString *)stringByDecodingHTMLEntities;
- (NSString *)stringByEncodingHTMLEntities;
- (NSString *)stringWithNewLinesAsBRs;
- (NSString *)stringByRemovingNewLinesAndWhitespace;


답변

Daniel의 작품은 기본적으로 매우 훌륭하며 몇 가지 문제를 해결했습니다.

  1. NSSCanner의 건너 뛰는 문자를 제거했습니다 (그렇지 않으면 두 개의 연속 엔티티 사이의 공백이 무시됩니다.

    [스캐너 setCharactersToBeSkipped : nil];

  2. 분리 된 ‘&’기호가있을 때 구문 분석을 수정했습니다 (이에 대한 ‘올바른’출력이 무엇인지 모르겠습니다. 방금 파이어 폭스와 비교했습니다).

예 :

    &#ABC DF & B&#39;  & C&#39; 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:@"&amp;" intoString:NULL])
            [result appendString:@"&"];
        else if ([scanner scanString:@"&apos;" intoString:NULL])
            [result appendString:@"'"];
        else if ([scanner scanString:@"&quot;" intoString:NULL])
            [result appendString:@"\""];
        else if ([scanner scanString:@"&lt;" intoString:NULL])
            [result appendString:@"<"];
        else if ([scanner scanString:@"&gt;" 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:&amp];  //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부터는 NSAttributedStringwith NSHTMLTextDocumentType속성 을 사용하여 기본적으로 HTML 문자를 디코딩 할 수 있습니다 .

NSString *htmlString = @"&#63743; &amp; &#38; &lt; &gt; &trade; &copy; &hearts; &clubs; &spades; &diams;";
NSData *stringData = [htmlString dataUsingEncoding:NSUTF8StringEncoding];

NSDictionary *options = @{NSDocumentTypeDocumentAttribute:NSHTMLTextDocumentType};
NSAttributedString *decodedString;
decodedString = [[NSAttributedString alloc] initWithData:stringData
                                                 options:options
                                      documentAttributes:NULL
                                                   error:NULL];

디코딩 된 속성 문자열은 이제  & & <> ™ © ♥ ♣ ♠ ♦로 표시됩니다.

참고 : 이것은 메인 스레드에서 호출 된 경우에만 작동합니다.


답변

아무도 가장 간단한 옵션 중 하나 인 Mac 용 Google 도구 상자 를 언급하지 않는 것 같습니다
(이름에도 불구하고 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, '&amp;' becomes '&'
///  Handles &#32; and &#x32; cases as well
///
//  Returns:
//    Autoreleased NSString
//
- (NSString *)gtm_stringByUnescapingFromHTML;

그리고 프로젝트에 헤더, 구현 및 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:@"&amp;" intoString:NULL])
            [result appendString:@"&"];
        else if ([scanner scanString:@"&apos;" intoString:NULL])
            [result appendString:@"'"];
        else if ([scanner scanString:@"&quot;" intoString:NULL])
            [result appendString:@"\""];
        else if ([scanner scanString:@"&lt;" intoString:NULL])
            [result appendString:@"<"];
        else if ([scanner scanString:@"&gt;" 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;
}


답변

이것은 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;  

}

이것이 누군가를 도울 수 있기를 바랍니다.