2009-12-17 5 views
15

Je suis à la recherche d'un moyen simple et efficace pour convertir des chaînes en CamelCase pour souligner la notation (c.-à-MyClassName -> my_class_name) et retour à l'objectif C.CamelCase à underscores et retour en Objective-C

Mon La solution actuelle implique beaucoup de , characterAtIndex, et opérations sur NSMutableStrings, et est tout simplement laide comme l'enfer :) Il semble qu'il doit y avoir une meilleure solution, mais je ne suis pas sûr de quoi il s'agit.

Je préfère ne pas importer une bibliothèque regex juste pour ce cas d'utilisation, bien que ce soit une option si tout le reste échoue.

Répondre

10

La suggestion de Chris de RegexKitLite est bonne. C'est une excellente boîte à outils, mais cela peut être fait assez facilement avec NSScanner. Utilisez -scanCharactersFromSet:intoString: en alternant entre +uppercaseLetterCharacterSet et +lowercaseLetterCharacterSet. Pour revenir en arrière, vous utiliseriez plutôt -scanUpToCharactersFromSet:, en utilisant un jeu de caractères avec juste un trait de soulignement.

+1

Merci Rob - mon manque d'expérience en utilisant NSScanner est ce qui m'a fait négliger cette solution, mais elle est beaucoup plus propre que ce que j'avais. –

4

Si votre préoccupation concerne uniquement la visibilité de votre code, vous pouvez créer une catégorie pour NSString en utilisant les méthodes que vous avez déjà conçues. De cette façon, vous ne voyez que le désordre laide une fois. ;)

Par exemple:

@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: Après une recherche rapide sur Google, je ne pouvais pas trouver un moyen de le faire, même dans la plaine C. Cependant, j'ai trouvé un cadre pourrait être utile. Ça s'appelle RegexKitLite. Il utilise la bibliothèque ICU intégrée, donc il ajoute seulement environ 20K au binaire final.

+1

Chris, merci beaucoup pour le pointeur RegexKitLite. Je vais certainement l'utiliser dans de futurs projets! –

+0

est environ 100 000 ans plus tard que lorsque cette réponse a été publiée en termes de développement iOS; Si quelqu'un d'autre trébuche ici: n'utilisez pas RegexKitLite. 'NSRegularExpression' est apparu dans iOS 4, environ six mois après la publication de cette réponse, apportant la même bibliothèque ICU dans les frameworks standards. – Tommy

9

Que diriez-vous ces:

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; 
} 

Certains inconvénients sont que ils utilisent des chaînes temporaires pour convertir entre majuscules et minuscules, et ils n'ont pas de logique pour les acronymes, donc myURL se traduira par my_u_r_l.

4

Voilà ma mise en œuvre de la réponse de Rob:

@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

pourquoi ne pas utiliser un NSMutableString en tant que constructeur? –

+0

Serait mieux, en effet. :-) – MonsieurDart

0

J'ai combiné les réponses trouvées ici dans ma bibliothèque refactoring, es_ios_utils. Voir NSCategories.h:

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

Utilisation:

@"my_string".asCamelCaseFromUnderscores 

rendements @ "myString"

S'il vous plaît pousser des améliorations!

3

Voici encore une autre version basée sur tout ce qui précède. Cette version gère des formulaires supplémentaires. En particulier, testé avec les éléments suivants:

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

va ici

- (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' n'est jamais lu et pourrait être supprimé. –

0

Je suis tombé sur cette question à la recherche d'un moyen de convertir Camel cas à une distance, l'utilisateur chaîne affichable.Voici ma solution qui a fonctionné mieux que remplacer @ "_" avec @ »"

- (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

On dirait déjà plutôt bien, mais: 1. que se passe-t-il si l'entrée est nulle? 2. comment fonctionne le retour de la conversion? 3. Votre boucle ne pourrait-elle pas commencer par 'i = 1' rendant le' i> 1'obsolete? – Trinimon

+0

1. Si l'entrée est nulle alors nul est retourné car le message de longueur envoyé à zéro retournera 0 2. Bon point, je n'ai pas besoin de cela dans mon application 3. J'aime ça, l'a édité dans ma réponse –

9

Essayez cette magie:

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); 

sortie: my_bundle_version

+3

Si la chaîne est dans le cas de Pascal c'est à dire MyBundleVersion, elle produira _my_bundle_version –

+0

J'aimerais voir une version avec des underscores principaux fixes et une qui convertit l'autre manière aussi. –

+1

@PeterDeWeese, Comme [celui-ci] (https://regex101.com/r/zM4dD2/1)? –

0

OK les gars. Voici une toute réponse regex, que je considère comme le seul vrai:

Vu:

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"]; 

Rendements fooBar.

inverse:

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]; 

Rendement: foo_bar.

\ U 2 $ remplace deuxième groupe de capture avec la version majuscule d'elle-même: D

\ L 1 $ Cependant, curieusement, ne remplace pas le premier groupe de capture avec une version minuscule d'elle-même :(Je ne sais pas pourquoi, il devrait fonctionner:./

1

Si vous êtes préoccupé par la vitesse de votre code que vous voulez probablement écrire une version plus performante du code:

- (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; 
} 
Questions connexes