Eliminar etiquetas HTML de un NSString en el iPhone

106

Hay un par de formas diferentes de eliminar HTML tagsun archivo NSStringin Cocoa.

Una forma es convertir la cadena en un NSAttributedStringy luego tomar el texto renderizado.

Otra forma es usar NSXMLDocument's- objectByApplyingXSLTStringmétodo para aplicar una XSLTtransformación que lo haga.

Desafortunadamente, el iPhone no es compatible con NSAttributedStringo NSXMLDocument. Hay demasiados casos extremos y HTMLdocumentos mal formados para que me sienta cómodo usando expresiones regulares o NSScanner. ¿Alguien tiene una solución para esto?

Una sugerencia ha sido simplemente buscar caracteres de etiqueta de apertura y cierre, este método no funcionará excepto en casos muy triviales.

Por ejemplo, estos casos (del capítulo del libro de recetas de Perl sobre el mismo tema) romperían este método:

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

<!-- <A comment> -->

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

<![INCLUDE CDATA [ >>>>>>>>>>>> ]]>
lfalin
fuente
Podría agregar un poco de lógica para tener en cuenta las comillas y los apóstrofos ... CDATA requeriría un poco más de trabajo, pero el objetivo de HTML es que el analizador puede ignorar las etiquetas desconocidas; si trata TODAS las etiquetas como desconocidas, entonces solo debería obtener texto sin formato.
Ben Gottlieb
Me gustaría comentar que una expresión regular buena (pero básica) definitivamente no se romperá con sus ejemplos. Ciertamente no si puede garantizar un XHTML bien formado. Sé que dijiste que no puedes, pero me pregunto por qué ;-)
Jake
1
Hay una buena respuesta para esta pregunta. Aplanar HTML usando Objective c
vipintj
Desafortunadamente, usar NSScanner es muy lento.
steipete
Aún más desafortunadamente, el ejemplo de NSScanner vinculado solo funciona para html trivial. Falla en todos los casos de prueba que mencioné en mi publicación.
lfalin

Respuestas:

309

Una solución rápida y "sucia" (elimina todo entre <y>), funciona con 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;
}

Tengo esto declarado como una categoría de NSString.

m.kocikowski
fuente
4
@James Para usar el método publicado en la solución. Tienes que crear una categoría para NSString. Busque "Categoría Objective-C" en Google. Luego, agrega ese método en el archivo my el prototipo en el archivo h. Cuando todo está configurado, para usarlo todo lo que tiene que hacer es tener un objeto de cadena (Ejemplo: NSString * myString = ...) y llamar a ese método en su objeto de cadena (NSString * strippedString = [myString stringByStrippingHTML]; ).
Roberto
3
+1 Excelente uso para expresiones regulares, pero desafortunadamente no cubre muchos casos.
matm
3
De hecho, rápido y sucio .... Esta función causa una gran pérdida de memoria en mi aplicación ... Bueno, en su defensa, estoy usando grandes cantidades de datos ....
EZFrag
5
En mi aplicación, esta solución causó problemas de rendimiento. Cambié a una solución con NSScanner en lugar de NSRegularExpressionSearch. Ahora los problemas de rendimiento se han ido
carmen_munich
2
Es muy muy muy memoria y consume mucho tiempo. ¡Úselo solo con pequeñas cantidades de html!
ullstrm
29

Esta NSStringcategoría utiliza NSXMLParserpara eliminar con precisión cualquier HTMLetiqueta de un NSString. Se trata de un único .my .harchivos que se pueden incluir en su proyecto fácilmente.

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

Luego te desnudas htmlhaciendo lo siguiente:

Importar el encabezado:

#import "NSString_stripHtml.h"

Y luego llame a stripHtml:

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

Esto también funciona con malformados HTMLque técnicamente no lo son XML.

Leigh McCulloch
fuente
3
Si bien la expresión regular (como dijo m.kocikowski) es rápida y sucia, esta es más robusta. Cadena de ejemplo: @ "Mi prueba <span font = \" font> name \ "> cadena html". Esta respuesta devuelve: Mi cadena html de prueba. La expresión regular devuelve: My test name "> html string. Si bien esto no es tan común, es más robusto.
DonnaLea
1
Excepto si tiene una cadena como "S&P 500", eliminará todo después del signo comercial y simplemente devolverá la cadena "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];

funciona bien para mi

MANCHIKANTI KRISHNAKISHORE
fuente
1
Tengo un problema de codificación con esta solución
KIDdAe
Probablemente la mejor solución, pero es inútil para un UILabel :-(
Zeb
9

Puedes usar como a continuación

-(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.
fuente
8

utilizar este

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

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

no olvide incluir esto en su código: #import "RegexKitLite.h" aquí está el enlace para descargar esta API: http://regexkit.sourceforge.net/#Downloads

Mohamed AHDIDOU
fuente
7

Eche un vistazo a NSXMLParser. Es un analizador de estilo SAX. Debería poder usarlo para detectar etiquetas u otros elementos no deseados en el documento XML e ignorarlos, capturando solo texto puro.

Colin Barrett
fuente
6

Aquí hay una solución más eficiente que la respuesta aceptada:

- (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 NSStringcategoría anterior usa una expresión regular para encontrar todas las etiquetas coincidentes, hace una copia de la cadena original y finalmente elimina todas las etiquetas en su lugar iterando sobre ellas en orden inverso. Es más eficiente porque:

  • La expresión regular se inicializa solo una vez.
  • Se utiliza una única copia de la cadena original.

Esto funcionó bastante bien para mí, pero el uso de una solución NSScannerpodría ser más eficiente.

Al igual que la respuesta aceptada, esta solución no aborda todos los casos fronterizos solicitados por @lfalin. Esos requerirían un análisis mucho más costoso que el caso de uso promedio probablemente no necesite.

hpique
fuente
5

Sin bucle (al menos de nuestro lado):

- (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
fuente
Esta debería ser la respuesta aceptada. El actual es ridículamente derrochador.
Adlai Holler
5
NSAttributedString *str=[[NSAttributedString alloc] initWithData:[trimmedString dataUsingEncoding:NSUTF8StringEncoding] options:@{NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType, NSCharacterEncodingDocumentAttribute: [NSNumber numberWithInt:NSUTF8StringEncoding]} documentAttributes:nil error:nil];
Pavan episodio
fuente
Cuando tenemos los metadatos con etiquetas HTML y queremos aplicar esas etiquetas, esa vez debemos aplicar el código anterior para lograr el resultado deseado.
Pavan Sisodio
4
#import "RegexKitLite.h"

string text = [html stringByReplacingOccurrencesOfRegex:@"<[^>]+>" withString:@""]
Jim Liu
fuente
2
HTML no es un lenguaje regular, por lo que no debería intentar analizarlo / eliminarlo con una expresión regular. stackoverflow.com/questions/1732348/…
csaunders
3

Extendí la respuesta de m.kocikowski y traté de hacerla un poco más eficiente utilizando NSMutableString. También lo estructuré para usarlo en una clase de Utils estática (aunque sé que una Categoría es probablemente el mejor diseño), y eliminé el lanzamiento automático para que se compile en un proyecto ARC.

Incluido aquí por si alguien lo encuentra útil.

.h

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

.metro

+ (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
fuente
Este método es útil pero, si necesito no quitar alguna etiqueta como el enlace <a>, ¿quién puedo actualizar este método para cumplir con esto?
wod
@wod, luego simplemente cambie la expresión regular a <(?>/?)(?!a).+?>esto eliminará todas las etiquetas, excepto las etiquetas de apertura <a> y cierre </a>.
Ashoor
3

Si desea obtener el contenido sin las etiquetas html de la página web (documento HTML), utilice este código dentro del método UIWebViewDidfinishLoading delegado .

  NSString *myText = [webView stringByEvaluatingJavaScriptFromString:@"document.documentElement.textContent"];
Biranchi
fuente
<br> está siendo reemplazado por nada ... lo cual es indeseable.
Nishant
2

Me imagino que la forma más segura sería analizar para <> s, ¿no? Recorra toda la cadena y copie todo lo que no esté entre <> sa una nueva cadena.

Ben Gottlieb
fuente
2

Esta es la modernización de la respuesta de m.kocikowski que elimina los espacios en blanco:

@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
fuente
2

la siguiente es la respuesta aceptada, pero en lugar de la categoría, es un método de ayuda simple con una cadena pasada. (gracias 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
fuente
2

Aquí está la versión rápida:

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
fuente
El hombre, que stringByReplacingOccurrencesOfStringusa fuera del ciclo es una codificación porcentual y debe corregirse de una manera correcta.
Vyachaslav Gerchicov
0

Si está dispuesto a usar el marco Three20 , tiene una categoría en NSString que agrega el método stringByRemovingHTMLTags. Consulte NSStringAdditions.h en el subproyecto Three20Core.

jarnoan
fuente
26
Por el amor de Dios, no uses Three20 para nada. El framework más hinchado y mal comentado jamás.
kompozer
0

Ampliando esto más de las respuestas de m.kocikowski y Dan J con más explicación para los novatos

1 # Primero tienes que crear categorías-c-objetivo para que el código sea utilizable en cualquier clase.

.h

@interface NSString (NAME_OF_CATEGORY)

- (NSString *)stringByStrippingHTML;

@end

.metro

@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 # Luego simplemente importe el archivo .h de la clase de categoría que acaba de crear, por ejemplo

#import "NSString+NAME_OF_CATEGORY.h"

3 # Llamar al método.

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

El resultado es NSString del que quiero quitar las etiquetas.

Ashoor
fuente
0

Seguí la respuesta aceptada por m.kocikowski y la modifiqué ligeramente para hacer uso de un autoreleasepool para limpiar todas las cadenas temporales creadas por stringByReplacingCharactersInRange

En el comentario de este método dice, / * Reemplaza los caracteres en el rango con la cadena especificada, devolviendo una nueva cadena. * /

Por lo tanto, dependiendo de la longitud de su XML, es posible que esté creando una gran pila de nuevas cadenas de liberación automática que no se limpian hasta el final del siguiente @autoreleasepool. Si no está seguro de cuándo puede suceder eso o si la acción de un usuario podría desencadenar repetidamente muchas llamadas a este método antes, puede resumir esto en un @autoreleasepool. Estos incluso se pueden anidar y usar dentro de bucles siempre que sea posible.

La referencia de Apple en @autoreleasepool dice lo siguiente ... "Si escribe un bucle que crea muchos objetos temporales. Puede usar un bloque de grupo de liberación automática dentro del bucle para deshacerse de esos objetos antes de la siguiente iteración. Usar un bloque de grupo de liberación automática en el bucle ayuda a reducir la huella máxima de memoria de la aplicación ". No lo he usado en el ciclo, pero al menos este método se limpia después de sí mismo ahora.

- (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
fuente
0

Otra forma:

Interfaz:

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

Implementación

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

Realización

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

o simple

NSString *myClearStr = [self stringByStrippingHTML:rudeStr];

Nik Kov
fuente
este método es eliminar etiquetas html. pero quiero analizar la cadena html. qué hacer
Krutarth Patel
me salvé la solución time.Nice
Krutarth Patel
0

Una respuesta actualizada para @ m.kocikowski que funciona en versiones recientes de iOS.

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

}

Ahmed Awad
fuente
-3

Aquí hay una publicación de blog que analiza un par de bibliotecas disponibles para eliminar HTML http://sugarmaplesoftware.com/25/strip-html-tags/ Tenga en cuenta los comentarios donde se ofrecen otras soluciones.

micco
fuente
Este es el conjunto exacto de comentarios al que me vinculé en mi pregunta como un ejemplo de lo que no funcionaría.
lfalin