2009-12-17 10 views
15

Ich bin für eine einfache, effiziente Weise suchen Strings in Camelcase umwandeln zu unterstreichen Notation (dh MyClassName -> my_class_name) Mein und wieder zurück in Objective C.Camelcase zu Unterstrichen und zurück in Objective-C

aktuelle Lösung umfasst viele rangeOfString, characterAtIndex und Operationen auf NSMutableStrings, und ist einfach nur hässlich wie Hölle :) Es scheint, dass es eine bessere Lösung, aber ich bin mir nicht sicher, was es ist.

Ich würde lieber nicht eine Regex-Bibliothek nur für diesen einen Anwendungsfall importieren, obwohl das eine Option ist, wenn alles andere fehlschlägt.

Antwort

10

Chris Vorschlag von RegexKitLite ist gut. Es ist ein hervorragendes Toolkit, aber das könnte mit NSScanner ziemlich einfach gemacht werden. Verwenden Sie -scanCharactersFromSet:intoString: abwechselnd zwischen +uppercaseLetterCharacterSet und +lowercaseLetterCharacterSet. Für den Rückweg würden Sie stattdessen -scanUpToCharactersFromSet: verwenden und einen Zeichensatz mit nur einem Unterstrich verwenden.

+1

Danke Rob - mein Mangel an Erfahrung mit NSScanner hat mich dazu gebracht, diese Lösung zu übersehen, aber es ist viel sauberer als das, was ich hatte. –

4

Wenn Ihr Anliegen nur die Sichtbarkeit Ihres Codes ist, können Sie unter Verwendung der von Ihnen entwickelten Methoden eine Kategorie für NSString erstellen. So siehst du nur einmal das hässliche Chaos. ;)

Zum Beispiel:

@interface NSString(Conversions) { 
    - (NSString *)asCamelCase; 
    - (NSString *)asUnderscored; 
} 

@implementation NSString(Conversions) { 
    - (NSString *)asCamelCase { 
      // whatever you came up with 
    } 
    - (NSString *)asUnderscored { 
      // whatever you came up with 
    } 
} 

EDIT: Nach einer schnellen Google-Suche, ich irgendeine Art und Weise, dies zu tun, auch in einfachem C nicht jedoch finden konnte, habe ich einen Rahmen finden, dass könnte nützlich sein. Es heißt RegexKitLite. Es verwendet die integrierte ICU-Bibliothek, so dass nur etwa 20 KB zur endgültigen Binärdatei hinzugefügt werden.

+1

Chris, dank viel für den RegexKitLite Zeiger. Ich werde es definitiv in zukünftigen Projekten verwenden! –

+0

It ist ungefähr 100.000 Jahre später als wenn diese Antwort in iOS-Entwicklungsbegriffen gepostet wurde; sollte jemand anders hier reinkommen: RegexKitLite nicht benutzen. "NSRegularExpression" tauchte etwa sechs Monate nach der Veröffentlichung dieser Antwort in iOS 4 auf und brachte dieselbe ICU-Bibliothek in die Standard-Frameworks. – Tommy

9

Wie wäre es diese:

NSString *MyCamelCaseToUnderscores(NSString *input) { 
    NSMutableString *output = [NSMutableString string]; 
    NSCharacterSet *uppercase = [NSCharacterSet uppercaseLetterCharacterSet]; 
    for (NSInteger idx = 0; idx < [input length]; idx += 1) { 
     unichar c = [input characterAtIndex:idx]; 
     if ([uppercase characterIsMember:c]) { 
      [output appendFormat:@"_%@", [[NSString stringWithCharacters:&c length:1] lowercaseString]]; 
     } else { 
      [output appendFormat:@"%C", c]; 
     } 
    } 
    return output; 
} 

NSString *MyUnderscoresToCamelCase(NSString *underscores) { 
    NSMutableString *output = [NSMutableString string]; 
    BOOL makeNextCharacterUpperCase = NO; 
    for (NSInteger idx = 0; idx < [underscores length]; idx += 1) { 
     unichar c = [underscores characterAtIndex:idx]; 
     if (c == '_') { 
      makeNextCharacterUpperCase = YES; 
     } else if (makeNextCharacterUpperCase) { 
      [output appendString:[[NSString stringWithCharacters:&c length:1] uppercaseString]]; 
      makeNextCharacterUpperCase = NO; 
     } else { 
      [output appendFormat:@"%C", c]; 
     } 
    } 
    return output; 
} 

Einige Nachteile sind, dass sie temporäre Strings verwenden tun zwischen Groß- und Kleinschreibung zu konvertieren, und sie haben keine Logik für Akronyme, so myURL in my_u_r_l führen.

4

Hier ist meine Implementierung von Rob Antwort:

@implementation NSString (CamelCaseConversion) 

// Convert a camel case string into a dased word sparated string. 
// In case of scanning error, return nil. 
// Camel case string must not start with a capital. 
- (NSString *)fromCamelCaseToDashed { 

    NSScanner *scanner = [NSScanner scannerWithString:self]; 
    scanner.caseSensitive = YES; 

    NSString *builder = [NSString string]; 
    NSString *buffer = nil; 
    NSUInteger lastScanLocation = 0; 

    while ([scanner isAtEnd] == NO) { 

     if ([scanner scanCharactersFromSet:[NSCharacterSet lowercaseLetterCharacterSet] intoString:&buffer]) { 

      builder = [builder stringByAppendingString:buffer]; 

      if ([scanner scanCharactersFromSet:[NSCharacterSet uppercaseLetterCharacterSet] intoString:&buffer]) { 

       builder = [builder stringByAppendingString:@"-"]; 
       builder = [builder stringByAppendingString:[buffer lowercaseString]]; 
      } 
     } 

     // If the scanner location has not moved, there's a problem somewhere. 
     if (lastScanLocation == scanner.scanLocation) return nil; 
     lastScanLocation = scanner.scanLocation; 
    } 

    return builder; 
} 

@end 
+0

Warum verwenden Sie keinen NSMutableString als Builder? –

+0

Wäre in der Tat besser. :-) – MonsieurDart

0

ich die Antworten hier kombiniert haben in meine Refactoring Bibliothek gefunden, es_ios_utils. Siehe NSCategories.h:

@property(nonatomic, readonly) NSString *asCamelCaseFromUnderscores; 
@property(nonatomic, readonly) NSString *asUnderscoresFromCamelCase; 

Verbrauch:

@"my_string".asCamelCaseFromUnderscores 

Ausbeuten @ "myString"

Bitte drücken Verbesserungen!

3

Hier ist noch eine andere Version basierend auf all dem oben genannten. Diese Version behandelt zusätzliche Formulare. Insbesondere mit der folgenden getestet:

camelCase => camel_case 
camelCaseWord => camel_case_word 
camelURL => camel_url 
camelURLCase => camel_url_case 
CamelCase => camel_case 

Hier

- (NSString *)fromCamelCaseToDashed3 { 
    NSMutableString *output = [NSMutableString string]; 
    NSCharacterSet *uppercase = [NSCharacterSet uppercaseLetterCharacterSet]; 
    BOOL previousCharacterWasUppercase = FALSE; 
    BOOL currentCharacterIsUppercase = FALSE; 
    unichar currentChar = 0; 
    unichar previousChar = 0; 
    for (NSInteger idx = 0; idx < [self length]; idx += 1) { 
     previousChar = currentChar; 
     currentChar = [self characterAtIndex:idx]; 
     previousCharacterWasUppercase = currentCharacterIsUppercase; 
     currentCharacterIsUppercase = [uppercase characterIsMember:currentChar]; 

     if (!previousCharacterWasUppercase && currentCharacterIsUppercase && idx > 0) { 
      // insert an _ between the characters 
      [output appendString:@"_"]; 
     } else if (previousCharacterWasUppercase && !currentCharacterIsUppercase) { 
      // insert an _ before the previous character 
      // insert an _ before the last character in the string 
      if ([output length] > 1) { 
       unichar charTwoBack = [output characterAtIndex:[output length]-2]; 
       if (charTwoBack != '_') { 
        [output insertString:@"_" atIndex:[output length]-1]; 
       } 
      } 
     } 
     // Append the current character lowercase 
     [output appendString:[[NSString stringWithCharacters:&currentChar length:1] lowercaseString]]; 
    } 
    return output; 
} 
+0

'previousChar' wird nie gelesen und könnte entfernt werden. –

0

geht ich auf diese Frage geschah nach einem Weg, Kamel-Fall auf einen Abstand, Benutzer darstellbare Zeichenfolge zu konvertieren.Hier ist meine Lösung, die besser funktioniert als Ersatz @ "_" mit @“"

- (NSString *)fromCamelCaseToSpaced:(NSString*)input { 
    NSCharacterSet* lower = [NSCharacterSet lowercaseLetterCharacterSet]; 
    NSCharacterSet* upper = [NSCharacterSet uppercaseLetterCharacterSet]; 

    for (int i = 1; i < input.length; i++) { 
     if ([upper characterIsMember:[input characterAtIndex:i]] && 
      [lower characterIsMember:[input characterAtIndex:i-1]]) 
     { 
      NSString* soFar = [input substringToIndex:i]; 
      NSString* left = [input substringFromIndex:i]; 
      return [NSString stringWithFormat:@"%@ %@", soFar, [self fromCamelCaseToSpaced:left]]; 
     } 
    } 
    return input; 
} 
+0

Sieht schon ziemlich gut aus, aber: 1. Was passiert, wenn die Eingabe null ist? 2. Wie funktioniert die Umstellung zurück? 3. Könnte Ihre Schleife nicht mit "i = 1" beginnen, was "i> 1" veraltet macht? – Trinimon

+0

1. Wenn Eingabe ist Null, dann wird Nil zurückgegeben, da die Länge Nachricht an Null gesendet wird 0 2. Guter Punkt, ich brauchte dies nicht in meiner App 3. Ich mag das, bearbeitet es in meine Antwort –

9

diese Magie Versuchen:

NSString* camelCaseString = @"myBundleVersion"; 
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"(?<=[a-z])([A-Z])|([A-Z])(?=[a-z])" options:0 error:nil]; 
NSString *underscoreString = [[regex stringByReplacingMatchesInString:camelCaseString options:0 range:NSMakeRange(0, camelCaseString.length) withTemplate:@"_$1$2"] lowercaseString]; 
NSLog(@"%@", underscoreString); 

Ausgang: my_bundle_version

+3

Wenn Die Zeichenfolge ist in Pascal Fall, dh MyBundleVersion, wird es produzieren _my_bundle_version –

+0

Ich würde gerne eine Version mit führenden Unterstrichen fixed und eine, die umgekehrt konvertiert auch sehen. –

+1

@PeterDeWese, Wie [dieser] (https://regex101.com/r/zM4dD2/1)? –

0

OK Jungs. Hier ist eine alles regex Antwort, die ich den einzig wahren Weg betrachten:

Gegeben:

NSString *MYSTRING = "foo_bar"; 

NSRegularExpression *_toCamelCase = [NSRegularExpression 
    regularExpressionWithPattern:@"(_)([a-z])" 
    options:NSRegularExpressionCaseInsensitive error:&error]; 

NSString *camelCaseAttribute = [_toCamelCase 
    stringByReplacingMatchesInString:MYSTRING options:0 
    range:NSMakeRange(0, attribute.length) 
    withTemplate:@"\\U$2"]; 

Yields fooBar.

Umgekehrt:

NSString *MYSTRING = "fooBar"; 


NSRegularExpression *camelCaseTo_ = [NSRegularExpression 
    regularExpressionWithPattern:@"([A-Z])" 
    options:0 error:&error]; 

NSString *underscoreParsedAttribute = [camelCaseTo_ 
    stringByReplacingMatchesInString:MYSTRING 
    options:0 range:NSMakeRange(0, attribute.length) 
    withTemplate:@"_$1"]; 
underscoreParsedAttribute = [underscoreParsedAttribute lowercaseString]; 

Ausbeuten: foo_bar.

\ U $ 2 zweite Capture-Gruppe mit Großbuchstaben Version von mir selbst ersetzt: D

\ L $ 1 jedoch seltsam, ersetzt nicht die erste Capture-Gruppe mit einer Kleinen Version von mir selbst :(Nicht sicher warum sollte es funktionieren./

1

Wenn Sie mit der Geschwindigkeit des Codes betroffen sind Sie wahrscheinlich wollen eine leistungsfähigere Version des Codes schreiben:

- (nonnull NSString *)camelCaseToSnakeCaseString { 
    if ([self length] == 0) { 
     return @""; 
    } 
    NSMutableString *output = [NSMutableString string]; 
    NSCharacterSet *digitSet = [NSCharacterSet decimalDigitCharacterSet]; 
    NSCharacterSet *uppercaseSet = [NSCharacterSet uppercaseLetterCharacterSet]; 
    NSCharacterSet *lowercaseSet = [NSCharacterSet lowercaseLetterCharacterSet]; 

    for (NSInteger idx = 0; idx < [self length]; idx += 1) { 
     unichar c = [self characterAtIndex:idx]; 

     // if it's the last one then just append lowercase of character 
     if (idx == [self length] - 1) { 
      if ([uppercaseSet characterIsMember:c]) { 
       [output appendFormat:@"%@", [[NSString stringWithCharacters:&c length:1] lowercaseString]]; 
      } 
      else { 
       [output appendFormat:@"%C", c]; 
      } 
      continue; 
     } 

     unichar nextC = [self characterAtIndex:(idx+1)]; 
     // this logic finds the boundaries between lowercase/uppercase/digits and lets the string be split accordingly. 
     if ([lowercaseSet characterIsMember:c] && [uppercaseSet characterIsMember:nextC]) { 
      [output appendFormat:@"%@_", [[NSString stringWithCharacters:&c length:1] lowercaseString]]; 
     } 
     else if ([lowercaseSet characterIsMember:c] && [digitSet characterIsMember:nextC]) { 
      [output appendFormat:@"%@_", [[NSString stringWithCharacters:&c length:1] lowercaseString]]; 
     } 
     else if ([digitSet characterIsMember:c] && [uppercaseSet characterIsMember:nextC]) { 
      [output appendFormat:@"%@_", [[NSString stringWithCharacters:&c length:1] lowercaseString]]; 
     } 
     else { 
      // Append lowercase of character 
      if ([uppercaseSet characterIsMember:c]) { 
       [output appendFormat:@"%@", [[NSString stringWithCharacters:&c length:1] lowercaseString]]; 
      } 
      else { 
       [output appendFormat:@"%C", c]; 
      } 
     } 
    } 
    return output; 
} 
Verwandte Themen