Usuń tagi HTML z NSString na iPhonie

106

Istnieje kilka różnych sposobów usuwania HTML tagsplików NSStringw formacie in Cocoa.

Jednym ze sposobów jest wyrenderowanie ciągu znaków NSAttributedStringi pobranie wyrenderowanego tekstu.

Innym sposobem jest użycie NSXMLDocument's- objectByApplyingXSLTStringmetody zastosowania XSLTtransformacji, która to robi.

Niestety iPhone nie obsługuje NSAttributedStringlub NSXMLDocument. Jest zbyt wiele przypadków skrajnych i źle sformułowanych HTMLdokumentów, abym czuł się komfortowo używając wyrażeń regularnych lub NSScanner. Czy ktoś ma na to rozwiązanie?

Jedną z sugestii było po prostu szukanie znaków otwierających i zamykających znaczników, ta metoda nie zadziała z wyjątkiem bardzo trywialnych przypadków.

Na przykład te przypadki (z rozdziału książki kucharskiej Perla na ten sam temat) zepsują tę metodę:

<IMG SRC = "foo.gif" ALT = "A > B">

<!-- <A comment> -->

<script>if (a<b && a>c)</script>

<![INCLUDE CDATA [ >>>>>>>>>>>> ]]>
lfalin
źródło
Mógłbyś dodać trochę logiki, aby wziąć pod uwagę cudzysłowy i apostrofy ... CDATA wymagałoby trochę więcej pracy, ale celem HTML jest to, że nieznane znaczniki mogą być ignorowane przez parser; jeśli traktujesz WSZYSTKIE tagi jako nieznane, powinieneś po prostu otrzymać surowy tekst.
Ben Gottlieb
Chciałbym skomentować, że dobre (ale podstawowe) wyrażenie regularne na pewno nie zepsuje twoich przykładów. Z pewnością nie, jeśli możesz zagwarantować dobrze uformowany XHTML. Wiem, że powiedziałeś, że nie możesz, ale zastanawiam się dlaczego ;-)
Jake
1
Jest dobra odpowiedź na to pytanie. Spłaszcz kod HTML za pomocą Objective c
vipintj
Niestety, używanie NSScanner jest cholernie wolne.
steipete
Co gorsza, połączony przykład NSScanner działa tylko w przypadku trywialnego HTML. Zawodzi w przypadku każdego przypadku testowego, o którym wspomniałem w moim poście.
lfalin

Odpowiedzi:

309

Szybkie i "brudne" (usuwa wszystko między <i>) rozwiązanie, działa z iOS> = 3.2:

-(NSString *) stringByStrippingHTML {
  NSRange r;
  NSString *s = [[self copy] autorelease];
  while ((r = [s rangeOfString:@"<[^>]+>" options:NSRegularExpressionSearch]).location != NSNotFound)
    s = [s stringByReplacingCharactersInRange:r withString:@""];
  return s;
}

Mam to zadeklarowane jako kategoria w NSString.

m.kocikowski
źródło
4
@James Aby użyć metody zamieszczonej w rozwiązaniu. Musisz utworzyć kategorię dla NSString. Wyszukaj w Google „Kategoria celu-C”. Następnie dodajesz tę metodę w pliku m, a prototyp w pliku h. Kiedy to wszystko jest skonfigurowane, wszystko, co musisz zrobić, to mieć obiekt typu string (przykład: NSString * myString = ...) i wywołać tę metodę na obiekcie ciągu (NSString * strippedString = [myString stringByStrippingHTML]; ).
Roberto
3
+1 Świetne zastosowanie w przypadku wyrażeń regularnych, ale niestety nie obejmuje wielu przypadków.
matm
3
Rzeczywiście szybka i brudna ... Ta funkcja powoduje ogromny wyciek pamięci w mojej aplikacji ... Cóż, w jej obronie używam dużych ilości danych ...
EZFrag
5
W mojej aplikacji to rozwiązanie spowodowało problemy z wydajnością. Przerzuciłem się na rozwiązanie z NSScanner zamiast NSRegularExpressionSearch. Teraz problemy z wydajnością zniknęły
carmen_munich
2
Jest to bardzo, bardzo bardzo pamięciowe i czasochłonne. Używaj tego tylko z niewielkimi ilościami html!
ullstrm
29

Ta NSStringkategoria używa rozszerzeniaNSXMLParser do dokładnego usuwania wszelkich HTMLtagów z NSString. To jest singiel .mi .hplik, który można łatwo dołączyć do projektu.

https://gist.github.com/leighmcculloch/1202238

Następnie rozbierzesz htmlsię, wykonując następujące czynności:

Zaimportuj nagłówek:

#import "NSString_stripHtml.h"

A następnie wywołaj stripHtml:

NSString* mystring = @"<b>Hello</b> World!!";
NSString* stripped = [mystring stripHtml];
// stripped will be = Hello World!!

Działa to również w przypadku zniekształceń, HTMLktóre technicznie nie są XML.

Leigh McCulloch
źródło
3
Podczas gdy wyrażenie regularne (jak powiedział m.Kocikowski) jest szybkie i brudne, jest solidniejsze. Przykładowy ciąg: @ "Mój test <span font = \" font> nazwa \ "> ciąg html". Ta odpowiedź zwraca: Mój testowy ciąg html. Zwraca wyrażenie regularne: Moja nazwa testu "> ciąg html. Chociaż nie jest to tak powszechne, jest po prostu bardziej niezawodne.
DonnaLea
1
Z wyjątkiem przypadku, gdy masz ciąg, taki jak „S&P 500”, usunie on wszystko po znaku ampersand i po prostu zwróci ciąg „S”.
Joshua Gross,
11
UITextView *textview= [[UITextView alloc]initWithFrame:CGRectMake(10, 130, 250, 170)];
NSString *str = @"This is <font color='red'>simple</font>";
[textview setValue:str forKey:@"contentToHTMLString"];
textview.textAlignment = NSTextAlignmentLeft;
textview.editable = NO;
textview.font = [UIFont fontWithName:@"vardana" size:20.0];
[UIView addSubview:textview];

działa dobrze dla mnie

MANCHIKANTI KRISHNAKISHORE
źródło
1
Mam problem z kodowaniem w tym rozwiązaniu
KIDdAe
Prawdopodobnie najlepsze rozwiązanie, ale jest bezużyteczne dla UILabel :-(
Zeb
9

Możesz użyć jak poniżej

-(void)myMethod
 {

 NSString* htmlStr = @"<some>html</string>";
 NSString* strWithoutFormatting = [self stringByStrippingHTML:htmlStr];

 }

 -(NSString *)stringByStrippingHTML:(NSString*)str
 {
   NSRange r;
   while ((r = [str rangeOfString:@"<[^>]+>" options:NSRegularExpressionSearch]).location     != NSNotFound)
  {
     str = [str stringByReplacingCharactersInRange:r withString:@""];
 }
  return str;
 }
Kirtikumar A.
źródło
8

Użyj tego

NSString *myregex = @"<[^>]*>"; //regex to remove any html tag

NSString *htmlString = @"<html>bla bla</html>";
NSString *stringWithoutHTML = [hstmString stringByReplacingOccurrencesOfRegex:myregex withString:@""];

nie zapomnij umieścić tego w swoim kodzie: #import „RegexKitLite.h” tutaj jest łącze do pobrania tego interfejsu API: http://regexkit.sourceforge.net/#Downloads

Mohamed AHDIDOU
źródło
7

Spójrz na NSXMLParser. To parser w stylu SAX. Powinieneś móc go używać do wykrywania znaczników lub innych niechcianych elementów w dokumencie XML i ignorowania ich, przechwytując tylko czysty tekst.

Colin Barrett
źródło
6

Oto skuteczniejsze rozwiązanie niż przyjęta odpowiedź:

- (NSString*)hp_stringByRemovingTags
{
    static NSRegularExpression *regex = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        regex = [NSRegularExpression regularExpressionWithPattern:@"<[^>]+>" options:kNilOptions error:nil];
    });

    // Use reverse enumerator to delete characters without affecting indexes
    NSArray *matches =[regex matchesInString:self options:kNilOptions range:NSMakeRange(0, self.length)];
    NSEnumerator *enumerator = matches.reverseObjectEnumerator;

    NSTextCheckingResult *match = nil;
    NSMutableString *modifiedString = self.mutableCopy;
    while ((match = [enumerator nextObject]))
    {
        [modifiedString deleteCharactersInRange:match.range];
    }
    return modifiedString;
}

Powyższa NSStringkategoria używa wyrażenia regularnego, aby znaleźć wszystkie pasujące tagi, tworzy kopię oryginalnego ciągu i ostatecznie usuwa wszystkie tagi w miejscu, wykonując po nich iterację w odwrotnej kolejności. Jest bardziej wydajna, ponieważ:

  • Wyrażenie regularne jest inicjowane tylko raz.
  • Używana jest pojedyncza kopia oryginalnego ciągu.

To działało dla mnie wystarczająco dobrze, ale rozwiązanie wykorzystujące NSScannermoże być bardziej wydajne.

Podobnie jak przyjęta odpowiedź, to rozwiązanie nie dotyczy wszystkich przypadków granicznych, o które prosił @lfalin. Wymagałoby to znacznie droższego parsowania, którego przeciętny przypadek użycia najprawdopodobniej nie potrzebuje.

hpique
źródło
5

Bez pętli (przynajmniej po naszej stronie):

- (NSString *)removeHTML {

    static NSRegularExpression *regexp;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        regexp = [NSRegularExpression regularExpressionWithPattern:@"<[^>]+>" options:kNilOptions error:nil];
    });

    return [regexp stringByReplacingMatchesInString:self
                                            options:kNilOptions
                                              range:NSMakeRange(0, self.length)
                                       withTemplate:@""];
}
Rémy
źródło
To powinna być akceptowana odpowiedź. Obecny jest absurdalnie marnotrawny.
Adlai Holler
5
NSAttributedString *str=[[NSAttributedString alloc] initWithData:[trimmedString dataUsingEncoding:NSUTF8StringEncoding] options:@{NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType, NSCharacterEncodingDocumentAttribute: [NSNumber numberWithInt:NSUTF8StringEncoding]} documentAttributes:nil error:nil];
Pavan Sisode
źródło
Kiedy mamy metadane z tagami HTML i chcemy zastosować te tagi, wtedy powinniśmy zastosować powyższy kod, aby uzyskać pożądane wyjście.
Pavan Sisode
4
#import "RegexKitLite.h"

string text = [html stringByReplacingOccurrencesOfRegex:@"<[^>]+>" withString:@""]
Jim Liu
źródło
2
HTML nie jest zwykłym językiem, więc nie powinieneś próbować analizować / usuwać go za pomocą wyrażenia regularnego. stackoverflow.com/questions/1732348/…
csaunders
3

Rozszerzyłem odpowiedź m.kocikowskiego i próbowałem uczynić ją nieco bardziej wydajną za pomocą NSMutableString. Skonstruowałem go również do użycia w statycznej klasie Utils (wiem, że kategoria jest prawdopodobnie najlepszym projektem) i usunąłem autorelease, więc kompiluje się w projekcie ARC.

Zawarte tutaj na wypadek, gdyby ktoś uznało to za przydatne.

.h

+ (NSString *)stringByStrippingHTML:(NSString *)inputString;

.m

+ (NSString *)stringByStrippingHTML:(NSString *)inputString 
{
  NSMutableString *outString;

  if (inputString)
  {
    outString = [[NSMutableString alloc] initWithString:inputString];

    if ([inputString length] > 0)
    {
      NSRange r;

      while ((r = [outString rangeOfString:@"<[^>]+>" options:NSRegularExpressionSearch]).location != NSNotFound)
      {
        [outString deleteCharactersInRange:r];
      }      
    }
  }

  return outString; 
}
Dan J
źródło
Ta metoda jest przydatna, ale jeśli muszę usunąć tag, taki jak link <a>, kto może zaktualizować tę metodę, aby to spełnić
wod
@wod, a następnie po prostu zmień wyrażenie regularne na <(?>/?)(?!a).+?>to spowoduje usunięcie wszystkich tagów z wyjątkiem tagów otwierających <a> i zamykających </a>.
Ashoor,
3

Jeśli chcesz pobrać zawartość bez tagów HTML ze strony internetowej (dokumentu HTML), użyj tego kodu wewnątrz metody UIWebViewDidfinishLoading delegata .

  NSString *myText = [webView stringByEvaluatingJavaScriptFromString:@"document.documentElement.textContent"];
Biranchi
źródło
<br> nie jest zastępowane przez nic ... co jest niepożądane.
Nishant,
2

Wyobrażam sobie, że najbezpieczniejszym sposobem byłoby po prostu przeanalizować <> s, nie? Przejdź przez cały ciąg i skopiuj wszystko, co nie jest zawarte w <> s do nowego ciągu.

Ben Gottlieb
źródło
2

Oto unowocześnienie odpowiedzi m.kocikowskiego, która usuwa spacje:

@implementation NSString (StripXMLTags)

- (NSString *)stripXMLTags
{
    NSRange r;
    NSString *s = [self copy];
    while ((r = [s rangeOfString:@"<[^>]+>\\s*" options:NSRegularExpressionSearch]).location != NSNotFound)
        s = [s stringByReplacingCharactersInRange:r withString:@""];
    return s;
}

@end
digipeople
źródło
2

poniżej jest akceptowana odpowiedź, ale zamiast kategorii jest to prosta metoda pomocnicza z przekazanym ciągiem znaków. (dziękuję m.kocikowski)

-(NSString *) stringByStrippingHTML:(NSString*)originalString {
    NSRange r;
    NSString *s = [originalString copy];
    while ((r = [s rangeOfString:@"<[^>]+>" options:NSRegularExpressionSearch]).location != NSNotFound)
        s = [s stringByReplacingCharactersInRange:r withString:@""];
    return s;
}
tmr
źródło
2

Oto szybka wersja:

func stripHTMLFromString(string: String) -> String {
  var copy = string
  while let range = copy.rangeOfString("<[^>]+>", options: .RegularExpressionSearch) {
    copy = copy.stringByReplacingCharactersInRange(range, withString: "")
  }
  copy = copy.stringByReplacingOccurrencesOfString("&nbsp;", withString: " ")
  copy = copy.stringByReplacingOccurrencesOfString("&amp;", withString: "&")
  return copy
}
JohnVanDijk
źródło
Człowieku, stringByReplacingOccurrencesOfStringużywasz poza cyklem kodowania procentowego i powinno być naprawione w odpowiedni sposób.
Vyachaslav Gerchicov
0

Jeśli chcesz użyć frameworka Three20 , ma on kategorię na NSString, która dodaje metodę stringByRemovingHTMLTags. Zobacz NSStringAdditions.h w podprojekcie Three20Core.

jarnoan
źródło
26
Na litość boską, do niczego nie używaj Three20. Najbardziej rozdęty i źle skomentowany framework w historii.
kompozer
0

Rozszerzając to bardziej na odpowiedzi m.Kocikowskiego i Dana J z dodatkowymi wyjaśnieniami dla początkujących

1 # Najpierw musisz utworzyć kategorie z celem, aby kod był użyteczny w dowolnej klasie.

.h

@interface NSString (NAME_OF_CATEGORY)

- (NSString *)stringByStrippingHTML;

@end

.m

@implementation NSString (NAME_OF_CATEGORY)

- (NSString *)stringByStrippingHTML
{
NSMutableString *outString;
NSString *inputString = self;

if (inputString)
{
    outString = [[NSMutableString alloc] initWithString:inputString];

    if ([inputString length] > 0)
    {
        NSRange r;

        while ((r = [outString rangeOfString:@"<[^>]+>" options:NSRegularExpressionSearch]).location != NSNotFound)
        {
            [outString deleteCharactersInRange:r];
        }
    }
}

return outString;
}

@end

2 # Następnie po prostu zaimportuj plik .h klasy kategorii, którą właśnie utworzyłeś, np

#import "NSString+NAME_OF_CATEGORY.h"

3 # Wywołanie metody.

NSString* sub = [result stringByStrippingHTML];
NSLog(@"%@", sub);

wynik to NSString, z którego chcę usunąć tagi.

Ashoor
źródło
0

Postępowałem zgodnie z zaakceptowaną odpowiedzią autorstwa m.kocikowskiego i nieznacznie zmodyfikowałem, aby użyć puli autoreleasepool do czyszczenia wszystkich tymczasowych ciągów, które są tworzone przez stringByReplacingCharactersInRange

W komentarzu do tej metody stwierdza się, / * Zamień znaki w zakresie na określony ciąg, zwracając nowy ciąg. * /

Tak więc, w zależności od długości pliku XML, możesz tworzyć ogromny stos nowych ciągów autorelease, które nie są czyszczone do końca następnej puli @autoreleasepool. Jeśli nie masz pewności, kiedy to może się zdarzyć lub jeśli akcja użytkownika mogłaby wielokrotnie wyzwolić wiele wywołań tej metody wcześniej, możesz po prostu zapakować to w @autoreleasepool. W miarę możliwości można je nawet zagnieżdżać i używać w pętlach.

Dokument Apple w @autoreleasepool stwierdza to ... „Jeśli napiszesz pętlę, która tworzy wiele obiektów tymczasowych. Możesz użyć bloku puli autorelease wewnątrz pętli, aby pozbyć się tych obiektów przed następną iteracją. Używanie bloku puli autorelease w pętli pomaga zmniejszyć maksymalne zużycie pamięci przez aplikację. ” Nie używałem go w pętli, ale przynajmniej ta metoda czyści się teraz po sobie.

- (NSString *) stringByStrippingHTML {
    NSString *retVal;
    @autoreleasepool {
        NSRange r;
        NSString *s = [[self copy] autorelease];
        while ((r = [s rangeOfString:@"<[^>]+>" options:NSRegularExpressionSearch]).location != NSNotFound) {
            s = [s stringByReplacingCharactersInRange:r withString:@""];
        }
        retVal = [s copy];
    } 
    // pool is drained, release s and all temp 
    // strings created by stringByReplacingCharactersInRange
    return retVal;
}
jcpennypincher
źródło
0

Inny sposób:

Berło:

-(NSString *) stringByStrippingHTML:(NSString*)inputString;

Realizacja

(NSString *) stringByStrippingHTML:(NSString*)inputString
{ 
NSAttributedString *attrString = [[NSAttributedString alloc] initWithData:[inputString dataUsingEncoding:NSUTF8StringEncoding] options:@{NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType,NSCharacterEncodingDocumentAttribute: @(NSUTF8StringEncoding)} documentAttributes:nil error:nil];
NSString *str= [attrString string]; 

//you can add here replacements as your needs:
    [str stringByReplacingOccurrencesOfString:@"[" withString:@""];
    [str stringByReplacingOccurrencesOfString:@"]" withString:@""];
    [str stringByReplacingOccurrencesOfString:@"\n" withString:@""];

    return str;
}

Realizacja

cell.exampleClass.text = [self stringByStrippingHTML:[exampleJSONParsingArray valueForKey: @"key"]];

lub proste

NSString *myClearStr = [self stringByStrippingHTML:rudeStr];

Nik Kov
źródło
ta metoda polega na usuwaniu tagów html. ale chcę przeanalizować ciąg html. co robić
Krutarth Patel
save my time.nice rozwiązanie
Krutarth Patel
0

Zaktualizowana odpowiedź dla @ m.kocikowski, która działa na najnowszych wersjach iOS.

-(NSString *) stringByStrippingHTMLFromString:(NSString *)str {
NSRange range;
while ((range = [str rangeOfString:@"<[^>]+>" options:NSRegularExpressionSearch]).location != NSNotFound)
    str = [str stringByReplacingCharactersInRange:range withString:@""];
return str;

}

Ahmed Awad
źródło
-3

Oto post na blogu omawiający kilka bibliotek dostępnych do usuwania kodu HTML http://sugarmaplesoftware.com/25/strip-html-tags/ Zwróć uwagę na komentarze, w których oferowane są inne rozwiązania.

micco
źródło
To jest dokładny zestaw komentarzy, do których dołączyłem w moim pytaniu, jako przykład tego, co nie zadziała.
lfalin