NSString tokenize dans Objective-C

144

Quelle est la meilleure façon de tokeniser / diviser une NSString en Objective-C?

Ned Batchelder
la source

Réponses:

274

J'ai trouvé ceci sur http://borkware.com/quickies/one?topic=NSString (lien utile):

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

J'espère que cela t'aides!

Adam

Adam Alexander
la source
39
En référence aux futurs lecteurs, j'aimerais noter que c'est le contraire [anArray componentsJoinedByString:@":"];.
Ivan Vučica
2
merci, mais comment diviser une NSString qui est séparée par plus de jetons? (Si vous voyez ce que je veux dire, mon anglais n'est pas très bon) @Adam
11684
2
@Adam, je pense que vous vouliez componentsSeparatedByCharactersInSet. Voir la réponse ci-dessous.
Wienke
32

Tout le monde l'a mentionné, componentsSeparatedByString:mais vous pouvez également utiliser CFStringTokenizer(rappelez-vous que un NSStringet CFStringsont interchangeables) qui symboliseront également les langues naturelles (comme le chinois / japonais qui ne divise pas les mots sur des espaces).

Matt Gallagher
la source
7
Et, dans Mac OS X 10.6 et versions ultérieures, NSString dispose de méthodes enumerateLinesUsingBlock:et enumerateSubstringsInRange:options:usingBlock:, cette dernière étant une version par blocs de CFStringTokenizer. developer.apple.com/mac/library/documentation/Cocoa/Reference/… : developer.apple.com/mac/library/documentation/Cocoa/Reference/… :
Peter Hosey
1
Les enumerateméthodes sont également disponibles dans iOS 4 et versions ultérieures.
bugloaf
21

Si vous souhaitez simplement diviser une chaîne, utilisez -[NSString componentsSeparatedByString:]. Pour une tokenisation plus complexe, utilisez la classe NSScanner.

Chris Hanson
la source
7

Si vos besoins de tokenisation sont plus complexes, consultez ma boîte à outils open source de tokenisation / analyse de chaîne Cocoa: ParseKit:

http://parsekit.com

Pour le fractionnement simple des chaînes en utilisant un caractère délimiteur (comme ':'), ParseKit serait certainement exagéré. Mais encore une fois, pour les besoins de tokenisation complexes, ParseKit est extrêmement puissant / flexible.

Consultez également la documentation relative à la tokenisation ParseKit .

Todd Ditchendorf
la source
Cela fonctionne-t-il toujours? Je l'ai essayé et j'ai eu quelques erreurs que je crains d'essayer de me réparer.
griotspeak
Hm? Vivant? Le projet ParseKit est activement maintenu, oui. Cependant, les commentaires ici ne sont pas le bon endroit pour signaler des bogues sur le projet. C'est à la fois sur Google Code et Github si vous devez signaler des bogues.
Todd Ditchendorf le
Cela semble bien, mais maintenant je ne peux pas supprimer mon vote défavorable tant que vous n'aurez pas modifié la réponse (règles du site). Peut-être pourriez-vous noter sur quelles versions de quoi il fonctionne, ou s'il utilise ARC, etc.? Ou vous pouvez simplement ajouter un espace quelque part, c'est à vous de
décider
6

Si vous souhaitez tokeniser sur plusieurs caractères, vous pouvez utiliser NSString componentsSeparatedByCharactersInSet. NSCharacterSet propose des ensembles prédéfinis pratiques comme le whitespaceCharacterSetet leillegalCharacterSet . Et il a des initialiseurs pour les plages Unicode.

Vous pouvez également combiner des jeux de caractères et les utiliser pour tokeniser, comme ceci:

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

Sachez que componentsSeparatedByCharactersInSetcela produira des chaînes vides s'il rencontre plus d'un membre du charSet à la suite, vous pouvez donc tester les longueurs inférieures à 1.

Wienke
la source
Ne traite pas des langues dans lesquelles les espaces ne séparent pas du tout tous les jetons logiques. Mauvaise solution.
uchuugaka
@uchuugaka Dans ce cas, vous utiliseriez un ou plusieurs jeux de caractères différents avec lesquels tokenize. J'utilise simplement des exemples spécifiques pour illustrer un concept général.
Wienke
5

Si vous souhaitez convertir une chaîne en termes de recherche tout en préservant les "phrases entre guillemets", voici une NSStringcatégorie qui respecte différents types de paires de citations:"" '' ‘’ “”

Usage:

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

Code:

@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
Michael Waterfall
la source
1

Si vous cherchez à diviser les caractéristiques linguistiques d'une chaîne (mots, paragraphes, caractères, phrases et lignes), utilisez l'énumération de chaînes:

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' 

Cette API fonctionne avec d'autres langues où les espaces ne sont pas toujours le délimiteur (par exemple le japonais). L'utilisation NSStringEnumerationByComposedCharacterSequencesest également la bonne façon d'énumérer des caractères, car de nombreux caractères non occidentaux ont plus d'un octet.

Robert
la source
0

J'ai eu un cas où j'ai dû diviser la sortie de la console après une requête LDAP avec ldapsearch. Commencez par configurer et exécuter le NSTask (j'ai trouvé un bon exemple de code ici: exécutez une commande de terminal à partir d'une application Cocoa ). Mais ensuite, j'ai dû diviser et analyser la sortie afin d'extraire uniquement les noms de serveur d'impression de la sortie Ldap-query-output. Malheureusement, il s'agit d'une manipulation de chaînes assez fastidieuse qui ne poserait aucun problème si nous devions manipuler des chaînes C / tableaux avec de simples opérations C-array. Voici donc mon code utilisant des objets cacao. Si vous avez de meilleures suggestions, faites-le moi savoir.

//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ò
la source
0

Je me suis retrouvé dans une instance où il ne suffisait pas de séparer simplement chaîne par composant de nombreuses tâches telles que
1) Catégoriser le jeton en types
2) Ajouter de nouveaux jetons
3) Séparer une chaîne entre des fermetures personnalisées comme tous les mots entre "{" et "} «
Pour toutes ces exigences, j'ai trouvé Parse Kit une bouée de sauvetage.

Je l'ai utilisé pour analyser avec succès les fichiers .PGN (notation de jeu prtable), c'est très rapide et léger.

amar
la source