NSString tokenize en Objective-C

Respuestas:

274

Encontré esto en http://borkware.com/quickies/one?topic=NSString (enlace útil):

NSString *string = @"oop:ack:bork:greeble:ponies";
NSArray *chunks = [string componentsSeparatedByString: @":"];

¡Espero que esto ayude!

Adán

Adam Alexander
fuente
39
Como referencia para futuros lectores, me gustaría señalar que lo contrario es [anArray componentsJoinedByString:@":"];.
Ivan Vučica
2
gracias, pero ¿cómo dividir un NSString que está separado por más tokens? (Si sabes a lo que me refiero, mi inglés no es muy bueno) @ Adam
11684
2
@ Adam, creo que lo que querías era componentsSeparatedByCharactersInSet. Ver la respuesta a continuación.
Wienke
32

Todos lo han mencionado, componentsSeparatedByString:pero también puede usar CFStringTokenizer(recuerde que an NSStringy CFStringson intercambiables) que tokenizarán los idiomas naturales también (como el chino / japonés que no divide las palabras en espacios).

Matt Gallagher
fuente
77
Y, en Mac OS X 10.6 y posterior, NSString tiene métodos enumerateLinesUsingBlock:y enumerateSubstringsInRange:options:usingBlock:, el último de los cuales es una versión basada en bloques de CFStringTokenizer. developer.apple.com/mac/library/documentation/Cocoa/Reference/… : developer.apple.com/mac/library/documentation/Cocoa/Reference/… :
Peter Hosey
1
Los enumeratemétodos también están disponibles en iOS 4 y versiones posteriores.
bugloaf
21

Si solo quiere dividir una cadena, use -[NSString componentsSeparatedByString:]. Para una tokenización más compleja, use la clase NSScanner.

Chris Hanson
fuente
7

Si sus necesidades de tokenización son más complejas, consulte mi kit de herramientas de tokenización / análisis de Cacao String de código abierto: ParseKit:

http://parsekit.com

Para la división simple de cadenas usando un delimitador char (como ':'), ParseKit definitivamente sería excesivo. Pero, de nuevo, para necesidades de tokenización complejas, ParseKit es extremadamente potente / flexible.

Consulte también la documentación de tokenización de ParseKit .

Todd Ditchendorf
fuente
¿Esto todavía funciona? Lo probé y obtuve un par de errores que desconfío de tratar de arreglarme.
griotspeak
Hm? ¿Viva? El Proyecto ParseKit se mantiene activamente, sí. Sin embargo, los comentarios aquí no son el lugar correcto para archivar errores en el proyecto. Está en Google Code y Github si necesita archivar errores.
Todd Ditchendorf
Suena bien, pero ahora no puedo eliminar mi voto negativo hasta que edite la respuesta de alguna manera (reglas del sitio). ¿Quizás podría notar qué versiones de lo que funciona, o si utiliza ARC, etc.? O simplemente podría agregar un espacio en algún lugar, eso depende de usted :)
Dan Rosenstark
6

Si desea tokenizar en varios caracteres, puede usar NSString componentsSeparatedByCharactersInSet. NSCharacterSet tiene algunos conjuntos prácticos prefabricados como el whitespaceCharacterSety elillegalCharacterSet . Y tiene inicializadores para rangos Unicode.

También puede combinar conjuntos de caracteres y usarlos para tokenizar, de esta manera:

// Tokenize sSourceEntityName on both whitespace and punctuation.
NSMutableCharacterSet *mcharsetWhitePunc = [[NSCharacterSet whitespaceAndNewlineCharacterSet] mutableCopy];
[mcharsetWhitePunc formUnionWithCharacterSet:[NSCharacterSet punctuationCharacterSet]];
NSArray *sarrTokenizedName = [self.sSourceEntityName componentsSeparatedByCharactersInSet:mcharsetWhitePunc];
[mcharsetWhitePunc release];

Tenga en cuenta que componentsSeparatedByCharactersInSetproducirá cadenas en blanco si encuentra más de un miembro de charSet en una fila, por lo que es posible que desee probar longitudes menores a 1.

Wienke
fuente
No aborda los idiomas donde el espacio en blanco no separa todos los tokens lógicos. Mala solución.
uchuugaka
@uchuugaka En ese caso, usaría un conjunto de caracteres diferente o conjuntos con los cuales tokenizar. Solo estoy usando ejemplos específicos para ilustrar un concepto general.
Wienke
5

Si está buscando convertir una cadena en términos de búsqueda al tiempo que conserva las "frases citadas", aquí hay una NSStringcategoría que respeta varios tipos de pares de citas:"" '' ‘’ “”

Uso:

NSArray *terms = [@"This is my \"search phrase\" I want to split" searchTerms];
// results in: ["This", "is", "my", "search phrase", "I", "want", "to", "split"]

Código:

@interface NSString (Search)
- (NSArray *)searchTerms;
@end

@implementation NSString (Search)

- (NSArray *)searchTerms {

    // Strip whitespace and setup scanner
    NSCharacterSet *whitespace = [NSCharacterSet whitespaceAndNewlineCharacterSet];
    NSString *searchString = [self stringByTrimmingCharactersInSet:whitespace];
    NSScanner *scanner = [NSScanner scannerWithString:searchString];
    [scanner setCharactersToBeSkipped:nil]; // we'll handle whitespace ourselves

    // A few types of quote pairs to check
    NSDictionary *quotePairs = @{@"\"": @"\"",
                                 @"'": @"'",
                                 @"\u2018": @"\u2019",
                                 @"\u201C": @"\u201D"};

    // Scan
    NSMutableArray *results = [[NSMutableArray alloc] init];
    NSString *substring = nil;
    while (scanner.scanLocation < searchString.length) {
        // Check for quote at beginning of string
        unichar unicharacter = [self characterAtIndex:scanner.scanLocation];
        NSString *startQuote = [NSString stringWithFormat:@"%C", unicharacter];
        NSString *endQuote = [quotePairs objectForKey:startQuote];
        if (endQuote != nil) { // if it's a valid start quote we'll have an end quote
            // Scan quoted phrase into substring (skipping start & end quotes)
            [scanner scanString:startQuote intoString:nil];
            [scanner scanUpToString:endQuote intoString:&substring];
            [scanner scanString:endQuote intoString:nil];
        } else {
            // Single word that is non-quoted
            [scanner scanUpToCharactersFromSet:whitespace intoString:&substring];
        }
        // Process and add the substring to results
        if (substring) {
            substring = [substring stringByTrimmingCharactersInSet:whitespace];
            if (substring.length) [results addObject:substring];
        }
        // Skip to next word
        [scanner scanCharactersFromSet:whitespace intoString:nil];
    }

    // Return non-mutable array
    return results.copy;

}

@end
Cascada de michael
fuente
1

Si está buscando dividir las características lingüísticas de una cadena (palabras, párrafos, caracteres, oraciones y líneas), use la enumeración de cadenas:

NSString * string = @" \n word1!    word2,%$?'/word3.word4   ";

[string enumerateSubstringsInRange:NSMakeRange(0, string.length)
                           options:NSStringEnumerationByWords
                        usingBlock:
 ^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) {
     NSLog(@"Substring: '%@'", substring);
 }];

 // Logs:
 // Substring: 'word1'
 // Substring: 'word2'
 // Substring: 'word3'
 // Substring: 'word4' 

Esta API funciona con otros idiomas donde los espacios no siempre son el delimitador (p. Ej., Japonés). También se usa NSStringEnumerationByComposedCharacterSequencesla forma correcta de enumerar los caracteres, ya que muchos caracteres no occidentales tienen más de un byte de longitud.

Robert
fuente
0

Tuve un caso en el que tuve que dividir la salida de la consola después de una consulta LDAP con ldapsearch. Primero configure y ejecute el NSTask (encontré una buena muestra de código aquí: ejecutar un comando de terminal desde una aplicación Cocoa ). Pero luego tuve que dividir y analizar la salida para extraer solo los nombres del servidor de impresión de la salida de consulta Ldap. Desafortunadamente, es bastante tedioso la manipulación de cadenas que no sería un problema en absoluto si tuviéramos que manipular cadenas / matrices C con operaciones simples de matriz C. Así que aquí está mi código usando objetos de cacao. Si tiene mejores sugerencias, hágamelo saber.

//as the ldap query has to be done when the user selects one of our Active Directory Domains
//(an according comboBox should be populated with print-server names we discover from AD)
//my code is placed in the onSelectDomain event code

//the following variables are declared in the interface .h file as globals
@protected NSArray* aDomains;//domain combo list array
@protected NSMutableArray* aPrinters;//printer combo list array
@protected NSMutableArray* aPrintServers;//print server combo list array

@protected NSString* sLdapQueryCommand;//for LDAP Queries
@protected NSArray* aLdapQueryArgs;
@protected NSTask* tskLdapTask;
@protected NSPipe* pipeLdapTask;
@protected NSFileHandle* fhLdapTask;
@protected NSMutableData* mdLdapTask;

IBOutlet NSComboBox* comboDomain;
IBOutlet NSComboBox* comboPrinter;
IBOutlet NSComboBox* comboPrintServer;
//end of interface globals

//after collecting the print-server names they are displayed in an according drop-down comboBox
//as soon as the user selects one of the print-servers, we should start a new query to find all the
//print-queues on that server and display them in the comboPrinter drop-down list
//to find the shares/print queues of a windows print-server you need samba and the net -S command like this:
// net -S yourPrintServerName.yourBaseDomain.com -U yourLdapUser%yourLdapUserPassWord -W adm rpc share -l
//which dispalays a long list of the shares

- (IBAction)onSelectDomain:(id)sender
{
    static int indexOfLastItem = 0; //unfortunately we need to compare this because we are called also if the selection did not change!

    if ([comboDomain indexOfSelectedItem] != indexOfLastItem && ([comboDomain indexOfSelectedItem] != 0))
    {

        indexOfLastItem = [comboDomain indexOfSelectedItem]; //retain this index for next call

    //the print-servers-list has to be loaded on a per univeristy or domain basis from a file dynamically or from AN LDAP-QUERY

    //initialize an LDAP-Query-Task or console-command like this one with console output
    /*

     ldapsearch -LLL -s sub -D "cn=yourLdapUser,ou=yourOuWithLdapUserAccount,dc=yourDomain,dc=com" -h "yourLdapServer.com" -p 3268 -w "yourLdapUserPassWord" -b "dc=yourBaseDomainToSearchIn,dc=com" "(&(objectcategory=computer)(cn=ps*))" "dn"

//our print-server names start with ps* and we want the dn as result, wich comes like this:

     dn: CN=PSyourPrintServerName,CN=Computers,DC=yourBaseDomainToSearchIn,DC=com

     */

    sLdapQueryCommand = [[NSString alloc] initWithString: @"/usr/bin/ldapsearch"];


    if ([[comboDomain stringValue] compare: @"firstDomain"] == NSOrderedSame) {

      aLdapQueryArgs = [NSArray arrayWithObjects: @"-LLL",@"-s", @"sub",@"-D", @"cn=yourLdapUser,ou=yourOuWithLdapUserAccount,dc=yourDomain,dc=com",@"-h", @"yourLdapServer.com",@"-p",@"3268",@"-w",@"yourLdapUserPassWord",@"-b",@"dc=yourFirstDomainToSearchIn,dc=com",@"(&(objectcategory=computer)(cn=ps*))",@"dn",nil];
    }
    else {
      aLdapQueryArgs = [NSArray arrayWithObjects: @"-LLL",@"-s", @"sub",@"-D", @"cn=yourLdapUser,ou=yourOuWithLdapUserAccount,dc=yourDomain,dc=com",@"-h", @"yourLdapServer.com",@"-p",@"3268",@"-w",@"yourLdapUserPassWord",@"-b",@"dc=yourSecondDomainToSearchIn,dc=com",@"(&(objectcategory=computer)(cn=ps*))",@"dn",nil];

    }


    //prepare and execute ldap-query task

    tskLdapTask = [[NSTask alloc] init];
    pipeLdapTask = [[NSPipe alloc] init];//instead of [NSPipe pipe]
    [tskLdapTask setStandardOutput: pipeLdapTask];//hope to get the tasks output in this file/pipe

    //The magic line that keeps your log where it belongs, has to do with NSLog (see /programming/412562/execute-a-terminal-command-from-a-cocoa-app and here http://www.cocoadev.com/index.pl?NSTask )
    [tskLdapTask setStandardInput:[NSPipe pipe]];

    //fhLdapTask  = [[NSFileHandle alloc] init];//would be redundand here, next line seems to do the trick also
    fhLdapTask = [pipeLdapTask fileHandleForReading];
    mdLdapTask  = [NSMutableData dataWithCapacity:512];//prepare capturing the pipe buffer which is flushed on read and can overflow, start with 512 Bytes but it is mutable, so grows dynamically later
    [tskLdapTask setLaunchPath: sLdapQueryCommand];
    [tskLdapTask setArguments: aLdapQueryArgs];

#ifdef bDoDebug
    NSLog (@"sLdapQueryCommand: %@\n", sLdapQueryCommand);
    NSLog (@"aLdapQueryArgs: %@\n", aLdapQueryArgs );
    NSLog (@"tskLdapTask: %@\n", [tskLdapTask arguments]);
#endif

    [tskLdapTask launch];

    while ([tskLdapTask isRunning]) {
      [mdLdapTask appendData: [fhLdapTask readDataToEndOfFile]];
    }
    [tskLdapTask waitUntilExit];//might be redundant here.

    [mdLdapTask appendData: [fhLdapTask readDataToEndOfFile]];//add another read for safety after process/command stops

    NSString* sLdapOutput = [[NSString alloc] initWithData: mdLdapTask encoding: NSUTF8StringEncoding];//convert output to something readable, as NSData and NSMutableData are mere byte buffers

#ifdef bDoDebug
    NSLog(@"LdapQueryOutput: %@\n", sLdapOutput);
#endif

    //Ok now we have the printservers from Active Directory, lets parse the output and show the list to the user in its combo box
    //output is formatted as this, one printserver per line
    //dn: CN=PSyourPrintServer,OU=Computers,DC=yourBaseDomainToSearchIn,DC=com

    //so we have to search for "dn: CN=" to retrieve each printserver's name
    //unfortunately splitting this up will give us a first line containing only "" empty string, which we can replace with the word "choose"
    //appearing as first entry in the comboBox

    aPrintServers = (NSMutableArray*)[sLdapOutput componentsSeparatedByString:@"dn: CN="];//split output into single lines and store it in the NSMutableArray aPrintServers

#ifdef bDoDebug
    NSLog(@"aPrintServers: %@\n", aPrintServers);
#endif

    if ([[aPrintServers objectAtIndex: 0 ] compare: @"" options: NSLiteralSearch] == NSOrderedSame){
      [aPrintServers replaceObjectAtIndex: 0 withObject: slChoose];//replace with localized string "choose"

#ifdef bDoDebug
      NSLog(@"aPrintServers: %@\n", aPrintServers);
#endif

    }

//Now comes the tedious part to extract only the print-server-names from the single lines
    NSRange r;
    NSString* sTemp;

    for (int i = 1; i < [aPrintServers count]; i++) {//skip first line with "choose". To get rid of the rest of the line, we must isolate/preserve the print server's name to the delimiting comma and remove all the remaining characters
      sTemp = [aPrintServers objectAtIndex: i];
      sTemp = [sTemp stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]];//remove newlines and line feeds

#ifdef bDoDebug
      NSLog(@"sTemp: %@\n", sTemp);
#endif
      r = [sTemp rangeOfString: @","];//now find first comma to remove the whole rest of the line
      //r.length = [sTemp lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
      r.length = [sTemp length] - r.location;//calculate number of chars between first comma found and lenght of string
#ifdef bDoDebug
      NSLog(@"range: %i, %i\n", r.location, r.length);
#endif

      sTemp = [sTemp stringByReplacingCharactersInRange:r withString: @"" ];//remove rest of line
#ifdef bDoDebug
      NSLog(@"sTemp after replace: %@\n", sTemp);
#endif

      [aPrintServers replaceObjectAtIndex: i withObject: sTemp];//put back string into array for display in comboBox

#ifdef bDoDebug
      NSLog(@"aPrintServer: %@\n", [aPrintServers objectAtIndex: i]);
#endif

    }

    [comboPrintServer removeAllItems];//reset combo box
    [comboPrintServer addItemsWithObjectValues:aPrintServers];
    [comboPrintServer setNumberOfVisibleItems:aPrintServers.count];
    [comboPrintServer selectItemAtIndex:0];

#ifdef bDoDebug
    NSLog(@"comboPrintServer reloaded with new values.");
#endif


//release memory we used for LdapTask
    [sLdapQueryCommand release];
    [aLdapQueryArgs release];
    [sLdapOutput release];

    [fhLdapTask release];

    [pipeLdapTask release];
//    [tskLdapTask release];//strangely can not be explicitely released, might be autorelease anyway
//    [mdLdapTask release];//strangely can not be explicitely released, might be autorelease anyway

    [sTemp release];

    }
}
Rosario Carcò
fuente
0

Me encontré con una instancia en la que no era suficiente separar la cadena por componente de muchas tareas, como
1) Categorizar el token en tipos
2) Agregar nuevos tokens
3) Separar la cadena entre cierres personalizados como todas las palabras entre "{" y "} "
Para tales requisitos, encontré Parse Kit un salvavidas.

Lo utilicé para analizar archivos .PGN (notación de juegos prtable) con éxito, es muy rápido y ligero.

amar
fuente