Ziel C HTML Escape / Unescape

74

Ich frage mich, ob es in Ziel C eine einfache Möglichkeit gibt, ein einfaches HTML-Escape / Unescape durchzuführen. Was ich möchte, ist so etwas wie dieser Pseudocode:

NSString *string = @"<span>Foo</span>";
[string stringByUnescapingHTML];

Welches kehrt zurück

<span>Foo</span>

Hoffentlich werden auch alle anderen HTML-Entitäten und sogar ASCII-Codes wie Ӓ und dergleichen entkoppelt.

Gibt es in Cocoa Touch / UIKit Methoden, um dies zu tun?

Alex Wayne
quelle
Der wahrscheinlich einfachste Weg mit iOS7 besteht darin, die Fähigkeit von NSAttributedString zu verwenden, HTML zu dekodieren und dann den NSAttributedString in einen NSString zu konvertieren - siehe meine Antwort unten.
Orj

Antworten:

30

Dieser Link enthält die folgende Lösung. Cocoa CF verfügt über die Funktion CFXMLCreateStringByUnescapingEntities, die auf dem iPhone jedoch nicht verfügbar ist.

@interface MREntitiesConverter : NSObject <NSXMLParserDelegate>{
    NSMutableString* resultString;
}

@property (nonatomic, retain) NSMutableString* resultString;

- (NSString*)convertEntitiesInString:(NSString*)s;

@end


@implementation MREntitiesConverter

@synthesize resultString;

- (id)init
{
    if([super init]) {
        resultString = [[NSMutableString alloc] init];
    }
    return self;
}

- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)s {
        [self.resultString appendString:s];
}

- (NSString*)convertEntitiesInString:(NSString*)s {
    if (!s) {
        NSLog(@"ERROR : Parameter string is nil");
    }
    NSString* xmlStr = [NSString stringWithFormat:@"<d>%@</d>", s];
    NSData *data = [xmlStr dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES];
    NSXMLParser* xmlParse = [[[NSXMLParser alloc] initWithData:data] autorelease];
    [xmlParse setDelegate:self];
    [xmlParse parse];
    return [NSString stringWithFormat:@"%@",resultString];
}

- (void)dealloc {
    [resultString release];
    [super dealloc];
}

@end
Andrew Grant
quelle
1
Wäre es nicht einfacher, dies als NSString-Kategorie zu implementieren, als als völlig separates Objekt? Außerdem wird die Rückgabezeichenfolge nicht automatisch freigegeben, aber der Anrufer sollte sie nicht besitzen, da sie vom Anrufer nicht explizit zugewiesen wurde.
Dreamlax
6
xmlParse leckt auch übrigens, fügen Sie einfach eine Autorelease hinzu und geben SieStr
Jarin Udom
1
Wenn Sie es zu einer NSString-Kategorie machen, benötigen Sie noch einen Delegaten für den Parser. Sie benötigen also ohnehin ein separates Objekt.
William Jockusch
4
Obwohl CFXMLCreateStringByUnescapingEntitieses unter iOS nicht verfügbar ist, können Sie die Definition aus CFXMLParser.c (aus dem Core Foundation-Quellcode) kopieren und in Ihrem Projekt verwenden. Ich habe es getestet und es funktioniert.
Chaitanya Gupta
2
Ich habe festgestellt, dass dieser Code alle HTML-Tags entfernt (zum Beispiel nur "Facebook" von "<a href="xxx"> Facebook </a>") und manchmal einfach nichts zurückgibt, wenn komplexes HTML übergeben wird. Also leider funktioniert nicht für meine Ziele.
Mike Keskinov
91

Überprüfen Sie meine NSString-Kategorie für XMLEntities . Es gibt Methoden zum Dekodieren von XML-Entitäten (einschließlich aller HTML-Zeichenreferenzen), zum Codieren von XML-Entitäten, zum Entfernen von Tags und zum Entfernen von Zeilenumbrüchen und Leerzeichen aus einer Zeichenfolge:

- (NSString *)stringByStrippingTags;
- (NSString *)stringByDecodingXMLEntities; // Including all HTML character references
- (NSString *)stringByEncodingXMLEntities;
- (NSString *)stringWithNewLinesAsBRs;
- (NSString *)stringByRemovingNewLinesAndWhitespace;
Michael Wasserfall
quelle
2
Scheint, dass es Kyrillisch nicht unterstützt. Haben Sie eine gesehen, die unterstützt?
Slatvick
Danke, ich habe deine Parses übrigens schon benutzt. Gute Arbeit!
Abramodj
Funktioniert wie Charme. Danke für diese tolle Kategorie!
DevZarak
9
Was ist mit der Funky-Lizenz los? Kann nicht für Tagebücher und Tagebücher verwendet werden?
Alltom
1
Diese Kategorie verwendet die Google Toolbox-Kategorie unter der Haube. Es ist besser, den Google Toolbox-Helfer direkt über Cocoapods zu installieren : pod "GTMNSStringHTMLAdditions". Siehe Travis 'Antwort vom September 2015.
Skensell
35

Eine weitere HTML-NSString-Kategorie von Google Toolbox für Mac
Trotz des Namens funktioniert dies auch unter iOS.

http://google-toolbox-for-mac.googlecode.com/svn/trunk/Foundation/GTMNSString+HTML.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;

Und ich musste nur drei Dateien in das Projekt aufnehmen: Header, Implementierung und GTMDefines.h.

Nikita Rybak
quelle
2
Erwähnenswert, dass , wenn Sie für das Gegenteil davon suchen, das heißt, '&'wird '&amp;', die auch in abgedeckt ist - (NSString *)gtm_stringByEscapingForHTML;, später in der Datei definiert.
Kristian
Bitte, können Sie einen Link fürGTMDefines.h
Almas Adilbek
Beachten Sie, dass diese Kategorie nicht mit ARC kompatibel ist, da sie Objective-C-Objekte in einer Struktur verwendet, die nicht unterstützt wird. Selbst das Setzen des -fno-objc-arcCompiler-Flags verhindert nicht, dass die Struktur als Fehler in Xcode markiert wird.
robotpukeko
@robotpukeko Das ist seltsam, weil ich ein ARC-Projekt mit dieser Kategorie kompilieren konnte, indem ich nur das Flag auf die .m-Datei setzte.
Timur Kuchkarov
Fügen Sie einfach -fno-objc-arc zu den Kompilierungsquellen hinzu. und es funktioniert gut.
Yong Ho
29

Dies ist eine unglaublich gehackte Lösung, die ich gemacht habe, aber wenn Sie einfach einer Zeichenfolge entkommen möchten, ohne sich Gedanken über das Parsen machen zu müssen, gehen Sie folgendermaßen vor:

-(NSString *)htmlEntityDecode:(NSString *)string
    {
        string = [string stringByReplacingOccurrencesOfString:@"&quot;" withString:@"\""];
        string = [string stringByReplacingOccurrencesOfString:@"&apos;" withString:@"'"];
        string = [string stringByReplacingOccurrencesOfString:@"&lt;" withString:@"<"];
        string = [string stringByReplacingOccurrencesOfString:@"&gt;" withString:@">"];
        string = [string stringByReplacingOccurrencesOfString:@"&amp;" withString:@"&"]; // Do this last so that, e.g. @"&amp;lt;" goes to @"&lt;" not @"<"

        return string;
    }

Ich weiß, dass es keineswegs elegant ist, aber es erledigt den Job. Sie können dann ein Element dekodieren, indem Sie Folgendes aufrufen:

string = [self htmlEntityDecode:string];

Wie ich schon sagte, es ist hacky, aber es funktioniert. Wenn Sie eine Zeichenfolge codieren möchten, kehren Sie einfach die Parameter stringByReplacingOccurencesOfString um.

Andrew Kozlik
quelle
5
Und wie wäre es mit Leistung? Sie gehen die Zeichenfolge 5 Mal durch. Es scheint nicht sehr effizient zu sein;)
HyLian
Es ist definitiv nicht die effizienteste Lösung, aber es funktioniert. Was wäre ein effizienterer Weg, dies zu tun?
Andrew Kozlik
6
Abhängig davon, wie oft dies verwendet wird und wie viel Zeit Sie tatsächlich sparen können, indem Sie dies effizienter gestalten, ist es möglicherweise nicht sinnvoll, hier eine Mikrooptimierung vorzunehmen. Da es sich hier um HTML handelt, ist es wahrscheinlich, dass irgendwo eine Netzwerkanforderung vorliegt, und die Rückkehr wird tausende Male länger dauern als die Ausführung des oben gezeigten Codes. Ich würde mich wahrscheinlich dazu neigen, diesen Code nicht zu optimieren.
Josh Brown
Die vorgeschlagene Methode weist eine schlechte Leistung auf, funktioniert jedoch einwandfrei, wenn Sie selten kurze Zeichenfolgen verarbeiten müssen. Vielen Dank für die Zeitersparnis bei der Implementierung dieser 10 Zeilen;)
Kostiantyn Sokolinskyi
@ Andrew Der effizientere Weg wäre die Implementierung eines eigenen String-Scanners, der alle diese XML-Zeichenentitätsreferenzen in einem String-Scan in entsprechende Zeichen konvertiert. Die Zeitkomplexität wird um das Fünffache sinken. Oder Sie können eine Bibliothek wie die unten von Nikita vorgeschlagene verwenden - stackoverflow.com/questions/659602/…
Kostiantyn Sokolinskyi
11

In iOS 7 können Sie die Fähigkeit von NSAttributedString verwenden, HTML zu importieren, um HTML-Entitäten in einen NSString zu konvertieren.

Z.B:

@interface NSAttributedString (HTML)
+ (instancetype)attributedStringWithHTMLString:(NSString *)htmlString;
@end

@implementation NSAttributedString (HTML)
+ (instancetype)attributedStringWithHTMLString:(NSString *)htmlString
{
    NSDictionary *options = @{ NSDocumentTypeDocumentAttribute : NSHTMLTextDocumentType,
                               NSCharacterEncodingDocumentAttribute :@(NSUTF8StringEncoding) };

    NSData *data = [htmlString dataUsingEncoding:NSUTF8StringEncoding];

    return [[NSAttributedString alloc] initWithData:data options:options documentAttributes:nil error:nil];
}

@end

Dann in Ihrem Code, wenn Sie die Entitäten bereinigen möchten:

NSString *cleanString = [[NSAttributedString attributedStringWithHTMLString:question.title] string];

Dies ist wahrscheinlich der einfachste Weg, aber ich weiß nicht, wie performant es ist. Sie sollten wahrscheinlich ziemlich sicher sein, dass der Inhalt Ihrer "Reinigung" keine <img>Tags oder ähnliches enthält, da diese Methode diese Bilder während der Konvertierung von HTML in NSAttributedString herunterlädt. :) :)

orj
quelle
Ich habe dazu eine Methode geschrieben, die den String nimmt, bereinigt und den bereinigten String zurückgibt. Sehen Sie es hier .
Adam Simpson
Diese Lösung entfernt auch alle vorhandenen HTML-Tags, z. B. this is testvon <b>this</b> is <a href='test'>test</a>.
Mike Keskinov
2
Nur einen Kopf hoch macht der NSAttributedString schreckliche Dinge im Konstruktor, wie das Drehen des Runloops. Ich konnte dies nicht für den Haupt-Thread verwenden, ohne UIKit sehr unglücklich zu machen.
Brian King
Das ist cool. Vielen Dank, wirkte wie ein Zauber für mich.
Tim Johnsen
5

Hier ist eine Lösung, die alle Zeichen neutralisiert (indem sie alle HTML-codierten Entitäten für ihren Unicode-Wert festgelegt werden) ... Verwendet diese für meine Anforderungen (um sicherzustellen, dass eine Zeichenfolge, die vom Benutzer stammt, aber in einer Webansicht platziert wurde, keine haben kann XSS-Angriffe):

Schnittstelle:

@interface NSString (escape)
- (NSString*)stringByEncodingHTMLEntities;
@end

Implementierung:

@implementation NSString (escape)

- (NSString*)stringByEncodingHTMLEntities {
    // Rather then mapping each individual entity and checking if it needs to be replaced, we simply replace every character with the hex entity

    NSMutableString *resultString = [NSMutableString string];
    for(int pos = 0; pos<[self length]; pos++)
        [resultString appendFormat:@"&#x%x;",[self characterAtIndex:pos]];
    return [NSString stringWithString:resultString];
}

@end

Anwendungsbeispiel:

UIWebView *webView = [[UIWebView alloc] init];
NSString *userInput = @"<script>alert('This is an XSS ATTACK!');</script>";
NSString *safeInput = [userInput stringByEncodingHTMLEntities];
[webView loadHTMLString:safeInput baseURL:nil];

Ihr Kilometerstand wird variieren.

BadPirate
quelle
Ihnen fehlt ein ';' Am Ende der Escape-Sequenz stellte ich außerdem in allen Dokumenten fest, dass die Länge einer Unicode-Zahl 4 mit führenden Nullen beträgt. Daher sollte Ihr Format anders lauten @"&#x%04x;", ich würde einen einfachen alphanumerischen Detektor hinzufügen und einfach kopieren solche Charaktere ohne zu entkommen.
Moshe Gottlieb
Interessanterweise funktioniert dieser Code für mich ohne das Semikolon einwandfrei. Wahrscheinlich ist nur das Webkit robust. Ich habe das hinzugefügt. Führen Sie% 04x jedoch nicht wie vorgeschlagen aus, da Sie sonst Probleme mit Einzelbyte-Mehrbyte-Unicode-Zeichen haben könnten. Mit% x wird die richtige Zahl für Einzel- und Mehrbyte (wie Japanisch) gedruckt.
BadPirate
4

Die am wenigsten invasive und leichteste Methode zum Codieren und Decodieren von HTML- oder XML-Zeichenfolgen ist die Verwendung des GTMNSStringHTMLAdditions CocoaPod .

Es ist einfach die Kategorie Google Toolbox für Mac NSString GTMNSString+HTML, von der die Abhängigkeit befreit ist GTMDefines.h. Alles, was Sie hinzufügen müssen, ist ein .h und ein .m, und Sie können loslegen.

Beispiel:

#import "GTMNSString+HTML.h"

// Encoding a string with XML / HTML elements
NSString *stringToEncode = @"<TheBeat>Goes On</TheBeat>";
NSString *encodedString = [stringToEncode gtm_stringByEscapingForHTML];

// encodedString looks like this now:
// &lt;TheBeat&gt;Goes On&lt;/TheBeat&gt;

// Decoding a string with XML / HTML encoded elements
NSString *stringToDecode = @"&lt;TheBeat&gt;Goes On&lt;/TheBeat&gt;";
NSString *decodedString = [stringToDecode gtm_stringByUnescapingFromHTML];

// decodedString looks like this now:
// <TheBeat>Goes On</TheBeat>
T Leer
quelle
2

Dies ist eine einfach zu verwendende Implementierung der NSString-Kategorie:

Es ist noch lange nicht vollständig, aber Sie können hier einige fehlende Entitäten hinzufügen: http://code.google.com/p/statz/source/browse/trunk/NSString%2BHTML.m

Verwendung:

#import "NSString+HTML.h"

NSString *raw = [NSString stringWithFormat:@"<div></div>"];
NSString *escaped = [raw htmlEscapedString];
Blago
quelle
Ich kann bestätigen, dass diese Kategorie perfekt funktioniert. Es ist perfekt geschrieben. Ich fordere alle auf, es zu benutzen - ich bezweifle, dass es da draußen eine bessere Lösung gibt! Wieder ist es total erstaunlich, dass dies noch nicht in iOS eingebaut ist .. bizarro. Danke @blago
Fattie
0

Diese einfachste Lösung besteht darin, eine Kategorie wie folgt zu erstellen:

Hier ist die Header-Datei der Kategorie:

#import <Foundation/Foundation.h>
@interface NSString (URLEncoding)
-(NSString *)urlEncodeUsingEncoding:(NSStringEncoding)encoding;
@end

Und hier ist die Implementierung:

#import "NSString+URLEncoding.h"
@implementation NSString (URLEncoding)
-(NSString *)urlEncodeUsingEncoding:(NSStringEncoding)encoding {
    return (NSString *)CFURLCreateStringByAddingPercentEscapes(NULL,
               (CFStringRef)self,
               NULL,
               (CFStringRef)@"!*'\"();:@&=+$,/?%#[]% ",
               CFStringConvertNSStringEncodingToEncoding(encoding));
}
@end

Und jetzt können wir das einfach machen:

NSString *raw = @"hell & brimstone + earthly/delight";
NSString *url = [NSString stringWithFormat:@"http://example.com/example?param=%@",
            [raw urlEncodeUsingEncoding:NSUTF8Encoding]];
NSLog(url);

Die Credits für diese Antwort gehen auf die folgende Website: -

http://madebymany.com/blog/url-encoding-an-nsstring-on-ios
Hashim Akhtar
quelle
Dies ist die URL-Codierung. Die Frage lautet, ob HTML nicht der URL-Codierung entgeht.
Tim Johnsen
-4

Warum nicht einfach benutzen?

NSData *data = [s dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES];
NSString *result = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease];
return result;

Noob Frage, aber in meinem Fall funktioniert es ...

kheraud
quelle
1
Warum sollte das funktionieren? Soweit ich das beurteilen kann, wird es einfach in Binärdaten und dann zurück in einen String konvertiert. Ich verstehe nicht, was hier ">" in "& gt;" und umgekehrt.
Alex Wayne
-5

Dies ist eine alte Antwort, die ich vor einigen Jahren gepostet habe. Meine Absicht war es nicht, eine "gute" und "respektable" Lösung anzubieten, sondern eine "hackige", die unter bestimmten Umständen nützlich sein könnte. Bitte verwenden Sie diese Lösung nur, wenn nichts anderes funktioniert.

Tatsächlich funktioniert es in vielen Situationen einwandfrei, in denen andere Antworten dies nicht tun, da UIWebView die ganze Arbeit erledigt. Und Sie können sogar Javascript injizieren (was gefährlich und / oder nützlich sein kann). Die Leistung sollte schrecklich sein, ist aber eigentlich nicht so schlecht.

Es gibt noch eine andere Lösung, die erwähnt werden muss. Erstellen Sie einfach eine UIWebView, laden Sie die codierte Zeichenfolge und erhalten Sie den Text zurück. Es entgeht den Tags "<>" und dekodiert auch alle HTML-Entitäten (z. B. "& gt;") und funktioniert möglicherweise dort, wo andere dies nicht tun (z. B. unter Verwendung von Kyrillik). Ich denke nicht, dass es die beste Lösung ist, aber es kann nützlich sein, wenn die oben genannten Lösungen nicht funktionieren.

Hier ist ein kleines Beispiel mit ARC:

@interface YourClass() <UIWebViewDelegate>

    @property UIWebView *webView;

@end

@implementation YourClass 

- (void)someMethodWhereYouGetTheHtmlString:(NSString *)htmlString {
    self.webView = [[UIWebView alloc] init];
    NSString *htmlString = [NSString stringWithFormat:@"<html><body>%@</body></html>", self.description];
    [self.webView loadHTMLString:htmlString baseURL:nil];
    self.webView.delegate = self;
}

- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {
    self.webView = nil;
}

- (void)webViewDidFinishLoad:(UIWebView *)webView {
    self.webView = nil;
    NSString *escapedString = [self.webView stringByEvaluatingJavaScriptFromString:@"document.body.textContent;"];
}

- (void)webViewDidStartLoad:(UIWebView *)webView {
    // Do Nothing
}

@end
FranMowinckel
quelle
Sarkasmus Ich denke, das ist groß in Leistung und Ressourcen / Sarkasmus
Dreamlab