NSString-Token in Objective-C

144

Was ist der beste Weg, um einen NSString in Objective-C zu tokenisieren / zu teilen?

Ned Batchelder
quelle

Antworten:

274

Gefunden unter http://borkware.com/quickies/one?topic=NSString (nützlicher Link):

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

Hoffe das hilft!

Adam

Adam Alexander
quelle
39
Als Hinweis auf zukünftige Leser möchte ich darauf hinweisen, dass das Gegenteil der Fall ist [anArray componentsJoinedByString:@":"];.
Ivan Vučica
2
danke, aber wie kann man einen NSString teilen, der durch mehr Token getrennt ist? (Wenn Sie wissen, was ich meine, ist mein Englisch nicht sehr gut) @Adam
11684
2
@Adam, ich denke was du wolltest war componentsSeparatedByCharactersInSet. Siehe Antwort unten.
Wienke
32

Jeder hat es erwähnt, componentsSeparatedByString:aber Sie können auch CFStringTokenizer(denken Sie daran, dass ein NSStringund CFStringaustauschbar ist) verwenden, um auch natürliche Sprachen zu kennzeichnen (wie Chinesisch / Japanisch, die keine Wörter in Leerzeichen aufteilen).

Matt Gallagher
quelle
7
In Mac OS X 10.6 und höher verfügt NSString über Methoden enumerateLinesUsingBlock:und enumerateSubstringsInRange:options:usingBlock:letztere ist eine blockbasierte Version von CFStringTokenizer. developer.apple.com/mac/library/documentation/Cocoa/Reference/… : developer.apple.com/mac/library/documentation/Cocoa/Reference/… :
Peter Hosey
1
Die enumerateMethoden sind auch in iOS 4 und höher verfügbar.
Bugloaf
21

Wenn Sie nur eine Zeichenfolge teilen möchten, verwenden Sie -[NSString componentsSeparatedByString:]. Verwenden Sie für eine komplexere Tokenisierung die NSScanner-Klasse.

Chris Hanson
quelle
7

Wenn Ihre Tokenisierungsanforderungen komplexer sind, lesen Sie mein Open Source-Toolkit zum Tokenisieren / Parsen von Cocoa-Zeichenfolgen: ParseKit:

http://parsekit.com

Für das einfache Aufteilen von Zeichenfolgen mit einem Trennzeichen (wie ':') wäre ParseKit definitiv übertrieben. Aber auch für komplexe Tokenisierungsanforderungen ist ParseKit äußerst leistungsfähig / flexibel.

Siehe auch die Dokumentation zur ParseKit-Tokenisierung .

Todd Ditchendorf
quelle
Funktioniert das noch Ich habe es versucht und ein paar Fehler bekommen. Ich bin misstrauisch, mich selbst zu beheben.
Griotspeak
Hm? Am Leben? Das ParseKit-Projekt wird aktiv gepflegt, ja. Kommentare hier sind jedoch nicht der richtige Ort, um Fehler im Projekt zu melden. Es ist sowohl auf Google Code als auch auf Github verfügbar, wenn Sie Fehler melden müssen.
Todd Ditchendorf
Hört sich gut an, aber jetzt kann ich meine Downvote erst entfernen, wenn Sie die Antwort irgendwie bearbeitet haben (Regeln der Site). Vielleicht könnten Sie feststellen, auf welchen Versionen es funktioniert oder ob ARC usw. verwendet wird? Oder Sie könnten einfach irgendwo ein Leerzeichen hinzufügen, das liegt bei Ihnen :)
Dan Rosenstark
6

Wenn Sie mehrere Zeichen tokenisieren möchten, können Sie NSStrings verwenden componentsSeparatedByCharactersInSet. NSCharacterSet hat einige handliche vorgefertigte Sets wie das whitespaceCharacterSetund das illegalCharacterSet. Und es hat Initialisierer für Unicode-Bereiche.

Sie können Zeichensätze auch kombinieren und zum Tokenisieren wie folgt verwenden:

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

componentsSeparatedByCharactersInSetBeachten Sie, dass leere Zeichenfolgen erzeugt werden, wenn mehr als ein Mitglied des charSet hintereinander angetroffen wird. Daher möchten Sie möglicherweise auf Längen von weniger als 1 testen.

Wienke
quelle
Adressiert keine Sprachen, in denen Leerzeichen nicht alle logischen Token trennen. Schlechte Lösung.
Uchuugaka
@uchuugaka In diesem Fall würden Sie einen oder mehrere andere Zeichensätze zum Tokenisieren verwenden. Ich verwende nur spezifische Beispiele, um ein allgemeines Konzept zu veranschaulichen.
Wienke
5

Wenn Sie eine Zeichenfolge in Suchbegriffe unter Beibehaltung von "zitierten Phrasen" umwandeln möchten, finden Sie hier eine NSStringKategorie, die verschiedene Arten von Anführungszeichenpaaren berücksichtigt:"" '' ‘’ “”

Verwendung:

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 Wasserfall
quelle
1

Wenn Sie sprachliche Merkmale einer Zeichenfolge (Wörter, Absätze, Zeichen, Sätze und Zeilen) aufteilen möchten, verwenden Sie die Zeichenfolgenaufzählung:

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' 

Diese API funktioniert mit anderen Sprachen, in denen Leerzeichen nicht immer das Trennzeichen sind (z. B. Japanisch). Die Verwendung NSStringEnumerationByComposedCharacterSequencesist auch die richtige Methode zum Aufzählen von Zeichen, da viele nicht-westliche Zeichen mehr als ein Byte lang sind.

Robert
quelle
0

Ich hatte einen Fall, in dem ich die Konsolenausgabe nach einer LDAP-Abfrage mit ldapsearch aufteilen musste. Richten Sie zuerst die NSTask ein und führen Sie sie aus (ich habe hier ein gutes Codebeispiel gefunden: Führen Sie einen Terminalbefehl aus einer Cocoa-App aus ). Aber dann musste ich die Ausgabe teilen und analysieren, um nur die Druckservernamen aus der Ldap-Abfrage-Ausgabe zu extrahieren. Leider ist es eine ziemlich langwierige String-Manipulation, die überhaupt kein Problem wäre, wenn wir C-Strings / Arrays mit einfachen C-Array-Operationen manipulieren würden. Also hier ist mein Code mit Kakaobjekten. Wenn Sie bessere Vorschläge haben, lassen Sie es mich wissen.

//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ò
quelle
0

Ich bin selbst auf eine Instanz gestoßen, in der es nicht ausreichte, die Zeichenfolge nach Komponenten zu trennen. Viele Aufgaben wie
1) Kategorisieren von Token in Typen
2) Hinzufügen neuer Token
3) Trennen von Zeichenfolgen zwischen benutzerdefinierten Abschlüssen wie alle Wörter zwischen "{" und "} "
Für solche Anforderungen fand ich Parse Kit ein Lebensretter.

Ich habe es verwendet, um .PGN-Dateien (Prtable Gaming Notation) erfolgreich zu analysieren. Es ist sehr schnell und einfach.

amar
quelle