NSString tokenize em Objective-C

Qual é a melhor maneira de tokenizar / dividir um NSString em Objective-C?

Encontrei isto em http://borkware.com/quickies/one?topic=NSString (link útil):

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

Espero que isto ajude!

Adão

Todos mencionaram componentsSeparatedByString: mas você também pode usar o CFStringTokenizer (lembre-se que um NSString e CFString são intercambiáveis) que também tokenizarão linguagens naturais (como chinês / japonês que não dividem palavras em espaços).

Se você quiser apenas dividir uma string, use -[NSString componentsSeparatedByString:] . Para uma tokenização mais complexa, use a class NSScanner.

Se as suas necessidades de tokenização forem mais complexas, confira meu kit de ferramentas de tokenização / análise de código aberto Cocoa String: ParseKit:

http://parsekit.com

Para a divisão simples de strings usando um caractere delimitador (como ‘:’), o ParseKit definitivamente seria um exagero. Mas, novamente, para necessidades complexas de tokenização, o ParseKit é extremamente poderoso / flexível.

Veja também a documentação do ParseKit Tokenization .

Se você quiser tokenizar em vários caracteres, você pode usar os componentsSeparatedByCharactersInSet do NSString. O NSCharacterSet possui alguns conjuntos pré-criados, como o whitespaceCharacterSet e o illegalCharacterSet . E tem inicializadores para intervalos Unicode.

Você também pode combinar conjuntos de caracteres e usá-los para tokenizar, assim:

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

Esteja ciente de que componentsSeparatedByCharactersInSet produzirá strings em branco se encontrar mais de um membro do charSet em uma linha, portanto, talvez você queira testar comprimentos menores que 1.

Se você estiver tentando separar uma string em termos de pesquisa, preservando “frases entre aspas”, aqui está uma categoria de NSString que respeita vários tipos de pares de citações: "" '' '' “”

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 

Se você estiver procurando dividir os resources lingüísticos de uma string (Palavras, parágrafos, caracteres, frases e linhas), use a enumeração de string:

 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 trabalha com outras linguagens onde os espaços nem sempre são o delimitador (por exemplo, japonês). Também usar NSStringEnumerationByComposedCharacterSequences é a maneira correta de enumerar os caracteres, pois muitos caracteres não ocidentais têm mais de um byte.

Eu tive um caso onde eu tive que dividir a saída do console após uma consulta LDAP com ldapsearch. Primeiro, configure e execute o NSTask (encontrei um bom exemplo de código aqui: Execute um comando de terminal a partir de um aplicativo Cocoa ). Mas então eu tive que dividir e analisar a saída para extrair apenas os nomes dos servidores de impressão da saída de consulta do Ldap. Infelizmente, é uma manipulação de strings bastante entediante, o que não seria problema se manipulássemos strings C / arrays com operações simples de array C. Então aqui está o meu código usando objects de cacau. Se você tiver sugestões melhores, me avise.

 //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 https://stackoverflow.com/questions/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]; } } 

Eu tenho a minha auto se deparar com instância em que não foi suficiente apenas separar string por componente muitas tarefas como
1) Categorizando o token em tipos
2) Adicionando novos tokens
3) Separar string entre closures personalizados como todas as palavras entre “{” e “}”
Para qualquer um desses requisitos, achei o Parse Kit um salva-vidas.

Eu usei isso para analisar arquivos .PGN (notação de jogos prtable) com sucesso é muito rápido e lite.