Supprimer les balises HTML d'une NSString sur l'iPhone


106

Il existe plusieurs façons de supprimer HTML tagsun fichier NSStringin Cocoa.

Une façon est de rendre la chaîne dans un NSAttributedString, puis de récupérer le texte rendu.

Une autre façon est d'utiliser NSXMLDocument's- objectByApplyingXSLTStringmethod pour appliquer une XSLTtransformation qui le fait.

Malheureusement, l'iPhone ne prend pas en charge NSAttributedStringou NSXMLDocument. Il y a trop de cas de bord et de HTMLdocuments mal formés pour que je me sente à l'aise avec l'utilisation de regex ou NSScanner. Quelqu'un a-t-il une solution à cela?

Une suggestion a été de rechercher simplement les caractères d'ouverture et de fermeture des balises, cette méthode ne fonctionnera pas sauf dans des cas très triviaux.

Par exemple, ces cas (du chapitre Perl Cookbook sur le même sujet) casseraient cette méthode:

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

<!-- <A comment> -->

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

<![INCLUDE CDATA [ >>>>>>>>>>>> ]]>

Vous pourriez ajouter un peu de logique pour prendre en compte les guillemets et les apostrophes ... CDATA prendrait un peu plus de travail, mais tout l'intérêt du HTML est que les balises inconnues peuvent être ignorées par l'analyseur; si vous traitez TOUTES les balises comme inconnues, alors vous devriez juste obtenir du texte brut.
Ben Gottlieb

Je voudrais faire remarquer qu'une bonne expression régulière (mais basique) ne cassera certainement pas vos exemples. Certainement pas si vous pouvez garantir un XHTML bien formé. Je sais que vous avez dit que vous ne pouvez pas, mais je me demande pourquoi ;-)
Jake

1
Il y a une bonne réponse à cette question. Aplatir le HTML avec Objective c
vipintj

Malheureusement, l'utilisation de NSScanner est extrêmement lente.
steipete

Encore plus malheureusement, l'exemple de NSScanner lié ne fonctionne que pour le html trivial. Il échoue pour chaque cas de test que j'ai mentionné dans mon message.
lfalin le

Réponses:


309

Une solution rapide et "sale" (supprime tout entre <et>), fonctionne avec 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;
}

Je l'ai déclaré comme catégorie os NSString.


4
@James Pour utiliser la méthode publiée dans la solution. Vous devez créer une catégorie pour NSString. Recherchez "Catégorie Objective-C" dans Google. Ensuite, vous ajoutez cette méthode dans le fichier m et le prototype dans le fichier h. Lorsque tout cela est configuré, pour l'utiliser, tout ce que vous avez à faire est d'avoir un objet chaîne (Exemple: NSString * myString = ...) et vous appelez cette méthode sur votre objet chaîne (NSString * strippedString = [myString stringByStrippingHTML]; ).
Roberto

3
+1 Excellente utilisation pour les expressions régulières, mais ne couvre malheureusement pas beaucoup de cas.
matm

3
Rapide et sale en effet .... Cette fonction provoque une énorme fuite de mémoire dans mon application ... Eh bien, pour sa défense, j'utilise de grandes quantités de données ....
EZFrag

5
Dans mon application, cette solution a causé des problèmes de performances. Je suis passé à une solution avec NSScanner à la place NSRegularExpressionSearch. Maintenant, les problèmes de performances ont disparu
carmen_munich

2
Cela demande beaucoup de temps et de mémoire. N'utilisez cela qu'avec de petites quantités de HTML!
ullstrm

29

Cette NSStringcatégorie utilise NSXMLParserpour supprimer avec précision toutes les HTMLbalises d'un NSString. Ceci est un seul .met .hfichier qui peut être inclus dans votre projet facilement.

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

Vous vous déshabillez ensuite htmlen procédant comme suit:

Importez l'en-tête:

#import "NSString_stripHtml.h"

Et puis appelez stripHtml:

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

Cela fonctionne également avec malformé HTMLqui ne l'est pas techniquement XML.


3
Alors que l'expression régulière (comme le dit m.kocikowski) est rapide et sale, elle est plus robuste. Exemple de chaîne: @ "My test <span font = \" font> name \ "> html string". Cette réponse renvoie: Ma chaîne html de test. L'expression régulière renvoie: Mon nom de test "> chaîne html. Bien que ce ne soit pas si courant, c'est juste plus robuste.
DonnaLea

1
Sauf si vous avez une chaîne comme "S&P 500", elle supprimera tout après l'esperluette et renverra simplement la chaîne "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];

fonctionne bien pour moi


1
J'ai un problème d'encodage avec cette solution
KIDdAe

Probablement la meilleure solution, mais elle est inutile pour un UILabel :-(
Zeb

9

Vous pouvez utiliser comme ci-dessous

-(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;
 }

8

utilisez ceci

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

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

n'oubliez pas de l'inclure dans votre code: #import "RegexKitLite.h" voici le lien pour télécharger cette API: http://regexkit.sourceforge.net/#Downloads


7

Jetez un œil à NSXMLParser. C'est un analyseur de type SAX. Vous devriez pouvoir l'utiliser pour détecter les balises ou autres éléments indésirables dans le document XML et les ignorer, en capturant uniquement du texte pur.


6

Voici une solution plus efficace que la réponse acceptée:

- (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;
}

La NSStringcatégorie ci-dessus utilise une expression régulière pour trouver toutes les balises correspondantes, fait une copie de la chaîne d'origine et finalement supprime toutes les balises en place en les itérant dans l'ordre inverse. C'est plus efficace car:

  • L'expression régulière n'est initialisée qu'une seule fois.
  • Une seule copie de la chaîne d'origine est utilisée.

Cela a fonctionné assez bien pour moi, mais une solution utilisant NSScannerpourrait être plus efficace.

Comme la réponse acceptée, cette solution ne répond pas à tous les cas de frontière demandés par @lfalin. Celles-ci nécessiteraient une analyse beaucoup plus coûteuse dont le cas d'utilisation moyen n'a probablement pas besoin.


5

Sans boucle (du moins de notre côté):

- (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:@""];
}

Cela devrait être la réponse acceptée. L'actuel est un gaspillage ridicule.
Adlai Holler

5
NSAttributedString *str=[[NSAttributedString alloc] initWithData:[trimmedString dataUsingEncoding:NSUTF8StringEncoding] options:@{NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType, NSCharacterEncodingDocumentAttribute: [NSNumber numberWithInt:NSUTF8StringEncoding]} documentAttributes:nil error:nil];

Lorsque nous avons les métadonnées avec des balises HTML et que nous voulons appliquer ces balises, nous devrions appliquer le code ci-dessus pour obtenir la sortie souhaitée.
Pavan Sisode


3

J'ai étendu la réponse par m.kocikowski et essayé de la rendre un peu plus efficace en utilisant un NSMutableString. Je l'ai également structuré pour une utilisation dans une classe Utils statique (je sais qu'une catégorie est probablement la meilleure conception cependant), et j'ai supprimé la version automatique pour qu'elle se compile dans un projet ARC.

Inclus ici au cas où quelqu'un le trouverait utile.

.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; 
}

Cette méthode est utile mais, si j'ai besoin de
supprimer

@wod puis changez simplement l'expression régulière en <(?>/?)(?!a).+?>ceci supprimera toutes les balises à l'exception des balises d'ouverture <a> et de fermeture </a>.
Ashoor

3

Si vous souhaitez obtenir le contenu sans les balises html de la page Web (document HTML), utilisez ce code dans la méthode UIWebViewDidfinishLoading déléguée .

  NSString *myText = [webView stringByEvaluatingJavaScriptFromString:@"document.documentElement.textContent"];

<br> n'est remplacé par rien ... ce qui n'est pas souhaitable.
Nishant le

2

J'imagine que le moyen le plus sûr serait simplement d'analyser les <> s, non? Faites une boucle sur toute la chaîne et copiez tout ce qui n'est pas compris entre <> s dans une nouvelle chaîne.


2

Il s'agit de la modernisation de la réponse m.kocikowski qui supprime les espaces:

@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

2

Voici la réponse acceptée, mais au lieu de la catégorie, il s'agit d'une méthode d'assistance simple avec une chaîne passée. (merci 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;
}

2

Voici la version rapide:

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
}

Man, stringByReplacingOccurrencesOfStringu utiliser en dehors du cycle est un encodage en pourcentage et doit être corrigé de manière correcte.
Vyachaslav Gerchicov

0

Si vous souhaitez utiliser le framework Three20 , il a une catégorie sur NSString qui ajoute la méthode stringByRemovingHTMLTags. Voir NSStringAdditions.h dans le sous-projet Three20Core.


26
Pour l'amour de Dieu, n'utilisez pas Three20 pour quoi que ce soit. Le cadre le plus gonflé et le plus mal commenté de tous les temps.
kompozer

0

Étendre davantage cela à partir des réponses de m.kocikowski et Dan J avec plus d'explications pour les débutants

1 # Vous devez d'abord créer des catégories objective-c pour rendre le code utilisable dans n'importe quelle classe.

.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 # Ensuite, importez simplement le fichier .h de la classe de catégorie que vous venez de créer, par exemple

#import "NSString+NAME_OF_CATEGORY.h"

3 # Appel de la méthode.

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

le résultat est NSString dont je veux supprimer les balises.


0

J'ai suivi la réponse acceptée de m.kocikowski et modifiée est légèrement d'utiliser un autoreleasepool pour nettoyer toutes les chaînes temporaires créées par stringByReplacingCharactersInRange

Dans le commentaire de cette méthode, il indique, / * Remplace les caractères de la plage par la chaîne spécifiée, en retournant une nouvelle chaîne. * /

Ainsi, selon la longueur de votre XML, vous pouvez créer une énorme pile de nouvelles chaînes de libération automatique qui ne seront pas nettoyées avant la fin du prochain @autoreleasepool. Si vous ne savez pas quand cela peut se produire ou si une action de l'utilisateur pourrait déclencher à plusieurs reprises de nombreux appels à cette méthode auparavant, vous pouvez simplement conclure cela dans un @autoreleasepool. Ceux-ci peuvent même être imbriqués et utilisés dans des boucles lorsque cela est possible.

La référence d'Apple sur @autoreleasepool indique ceci ... "Si vous écrivez une boucle qui crée de nombreux objets temporaires. Vous pouvez utiliser un bloc de pool de libération automatique à l'intérieur de la boucle pour supprimer ces objets avant l'itération suivante. Utilisation d'un bloc de pool de libération automatique dans la boucle contribue à réduire l'empreinte mémoire maximale de l'application. " Je ne l'ai pas utilisé dans la boucle, mais au moins cette méthode nettoie après elle-même maintenant.

- (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;
}

0

Une autre façon:

Interface:

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

la mise en oeuvre

(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;
}

La concrétisation

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

ou simple

NSString *myClearStr = [self stringByStrippingHTML:rudeStr];


cette méthode supprime les balises html.Mais je veux analyser la chaîne html.ce qu'il faut faire
Krutarth Patel

a sauvé ma solution time.nice
Krutarth Patel

0

Une réponse mise à jour pour @ m.kocikowski qui fonctionne sur les versions récentes d'iOS.

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

}


-3

Voici un article de blog qui traite de quelques bibliothèques disponibles pour le décapage HTML http://sugarmaplesoftware.com/25/strip-html-tags/ Notez les commentaires où d'autres solutions sont proposées.


C'est l'ensemble exact des commentaires que j'ai liés dans ma question comme exemple de ce qui ne fonctionnerait pas.
lfalin
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.