Token NSString di Objective-C

144

Apa cara terbaik untuk tokenize / split NSString di Objective-C?

Ned Batchelder
sumber

Jawaban:

274

Temukan ini di http://borkware.com/quickies/one?topic=NSString (tautan berguna):

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

Semoga ini membantu!

Adam

Adam Alexander
sumber
39
Sebagai referensi untuk pembaca masa depan, saya ingin mencatat bahwa yang terjadi adalah sebaliknya [anArray componentsJoinedByString:@":"];.
Ivan Vučica
2
terima kasih, tetapi bagaimana cara membagi NSString yang dipisahkan oleh lebih banyak token? (Jika Anda tahu apa yang saya maksud, bahasa Inggris saya tidak terlalu baik)
@Adam
2
@ Adam, saya pikir apa yang Anda inginkan componentsSeparatedByCharactersInSet. Lihat jawaban di bawah ini.
Wienke
32

Semua orang telah menyebutkan componentsSeparatedByString:tetapi Anda juga dapat menggunakan CFStringTokenizer(ingat bahwa NSStringdan CFStringdapat dipertukarkan) yang juga akan mengubah bahasa alami (seperti Cina / Jepang yang tidak membagi kata pada spasi).

Matt Gallagher
sumber
7
Dan, di Mac OS X 10.6 dan yang lebih baru, NSString memiliki metode enumerateLinesUsingBlock:dan enumerateSubstringsInRange:options:usingBlock:, yang terakhir adalah versi berbasis blok dari CFStringTokenizer. developer.apple.com/mac/library/documentation/Cocoa/Reference/… : developer.apple.com/mac/library/documentation/Cocoa/Reference/… :
Peter Hosey
1
The enumeratemetode yang tersedia di iOS 4 dan kemudian, juga.
bugloaf
21

Jika Anda hanya ingin membagi string, gunakan -[NSString componentsSeparatedByString:]. Untuk tokenisasi yang lebih kompleks, gunakan kelas NSScanner.

Chris Hanson
sumber
7

Jika kebutuhan tokenization Anda lebih kompleks, lihat toolkit tokenizing / parsing string open source Cocoa saya: ParseKit:

http://parsekit.com

Untuk pemisahan string menggunakan char pembatas (seperti ':'), ParseKit pasti akan berlebihan. Tetapi sekali lagi, untuk kebutuhan tokenization yang kompleks, ParseKit sangat kuat / fleksibel.

Lihat juga dokumentasi Tokenisasi ParseKit .

Todd Ditchendorf
sumber
Apakah ini masih berfungsi? Saya mencobanya dan mendapat beberapa kesalahan. Saya ragu untuk mencoba memperbaiki diri.
griotspeak
Hm? Hidup? Proyek ParseKit dikelola secara aktif, ya. Namun, komentar di sini bukan tempat yang tepat untuk melaporkan bug pada proyek. Ada di Google Code dan Github jika Anda perlu mengajukan bug.
Todd Ditchendorf
Kedengarannya bagus, tapi sekarang saya tidak bisa menghapus downvote saya sampai Anda mengedit jawabannya entah bagaimana (aturan situs). Mungkin Anda bisa mencatat versi apa yang bekerja pada, atau apakah itu menggunakan ARC, dll? Atau Anda bisa menambahkan spasi di suatu tempat, terserah Anda :)
Dan Rosenstark
6

Jika Anda ingin tokenize pada banyak karakter, Anda dapat menggunakan NSString componentsSeparatedByCharactersInSet. NSCharacterSet memiliki beberapa set pre-made yang berguna seperti whitespaceCharacterSetdan illegalCharacterSet. Dan memiliki inisialisasi untuk rentang Unicode.

Anda juga dapat menggabungkan set karakter dan menggunakannya untuk tokenize, seperti ini:

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

Ketahuilah bahwa componentsSeparatedByCharactersInSetakan menghasilkan string kosong jika menjumpai lebih dari satu anggota charSet berturut-turut, jadi Anda mungkin ingin menguji untuk panjang kurang dari 1.

Wienke
sumber
Tidak membahas bahasa di mana spasi putih tidak memisahkan semua token logis sama sekali. Solusi yang buruk.
uchuugaka
@uchuugaka Dalam hal ini, Anda akan menggunakan set karakter atau set yang berbeda untuk tokenize. Saya hanya menggunakan contoh-contoh spesifik untuk menggambarkan konsep umum.
Wienke
5

Jika Anda ingin menandai string ke dalam istilah pencarian sambil mempertahankan "frasa yang dikutip", berikut adalah NSStringkategori yang menghormati berbagai jenis pasangan kutipan:"" '' ‘’ “”

Pemakaian:

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

Kode:

@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
Air Terjun Michael
sumber
1

Jika Anda mencari fitur linguistik dari string (Kata, paragraf, karakter, kalimat, dan baris), gunakan enumerasi 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' 

Api ini bekerja dengan bahasa lain di mana spasi tidak selalu merupakan pembatas (misalnya Jepang). Juga menggunakan NSStringEnumerationByComposedCharacterSequencesadalah cara yang tepat untuk menghitung lebih dari karakter, karena banyak karakter non-barat lebih dari satu byte.

Robert
sumber
0

Saya punya kasus di mana saya harus membagi output konsol setelah permintaan LDAP dengan ldapsearch. Pertama-tama mengatur dan menjalankan NSTask (saya menemukan contoh kode yang baik di sini: Jalankan perintah terminal dari aplikasi Cocoa ). Tapi kemudian saya harus membagi dan mengurai output sehingga hanya mengekstrak nama server cetak dari Ldap-query-output. Sayangnya itu adalah manipulasi string yang membosankan yang tidak akan menjadi masalah sama sekali jika kita ingin memanipulasi C-string / array dengan operasi C-array yang sederhana. Jadi di sini adalah kode saya menggunakan objek kakao. Jika Anda memiliki saran yang lebih baik, beri tahu saya.

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

Saya sendiri menemukan contoh di mana tidak cukup hanya memisahkan string dengan komponen banyak tugas seperti
1) Mengkategorikan token menjadi tipe
2) Menambahkan token baru
3) Memisahkan string antara penutupan kustom seperti semua kata antara "{" dan "} "
Untuk persyaratan seperti itu saya menemukan Parse Kit penyelamat.

Saya menggunakannya untuk mem-parsing file .PGN (notasi game prtable) berhasil dengan sangat cepat dan ringan.

amar
sumber