CamelCase, чтобы подчеркнуть и вернуться в Objective-C
Я ищу простой и эффективный способ преобразования строк в CamelCase, чтобы подчеркнуть нотацию (т.е. MyClassName → my_class_name) и обратно в Objective C.
Мое текущее решение включает в себя множество операций rangeOfString
, characterAtIndex
и replaceCharactersInRange
на NSMutableStrings, и это просто уродливо, как черт:) Кажется, что должно быть лучшее решение, но я не уверен что это.
Я бы предпочел не импортировать библиотеку регулярных выражений только для этого одного варианта использования, хотя это вариант, если все остальное не работает.
Ответы
Ответ 1
Предложение Криса RegexKitLite хорошее. Это отличный инструментарий, но это можно сделать довольно легко с помощью NSScanner. Используйте -scanCharactersFromSet:intoString:
, чередующиеся между +uppercaseLetterCharacterSet
и +lowercaseLetterCharacterSet
. Для возврата вы использовали бы -scanUpToCharactersFromSet:
вместо этого, используя набор символов с только подчеркиванием в нем.
Ответ 2
Как насчет этих:
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;
}
Некоторые недостатки в том, что они используют временные строки для преобразования между верхним и нижним регистром, и у них нет никакой логики для сокращений, поэтому myURL приведет к my_u_r_l.
Ответ 3
Попробуйте эту магию:
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);
Выход: my_bundle_version
Ответ 4
Если ваша забота - это просто видимость вашего кода, вы можете создать категорию для NSString
, используя уже разработанные вами методы. Таким образом, вы видите только уродливый беспорядок один раз.;)
Например:
@interface NSString(Conversions) {
- (NSString *)asCamelCase;
- (NSString *)asUnderscored;
}
@implementation NSString(Conversions) {
- (NSString *)asCamelCase {
// whatever you came up with
}
- (NSString *)asUnderscored {
// whatever you came up with
}
}
ИЗМЕНИТЬ. После быстрого поиска Google я не мог найти никакого способа сделать это, даже на простом C. Однако я нашел фреймворк, который может быть полезен. Он называется RegexKitLite. Он использует встроенную библиотеку ICU, поэтому он добавляет только около 20K к окончательному двоичному файлу.
Ответ 5
Здесь моя реализация 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 a problem somewhere.
if (lastScanLocation == scanner.scanLocation) return nil;
lastScanLocation = scanner.scanLocation;
}
return builder;
}
@end
Ответ 6
Здесь еще одна версия, основанная на всем вышеперечисленном. Эта версия обрабатывает дополнительные формы. В частности, протестировано со следующим:
camelCase => camel_case
camelCaseWord => camel_case_word
camelURL => camel_url
camelURLCase => camel_url_case
CamelCase => camel_case
Здесь
- (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:¤tChar length:1] lowercaseString]];
}
return output;
}
Ответ 7
Если вы обеспокоены скоростью вашего кода, вы, вероятно, захотите написать более эффективную версию кода:
- (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 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;
}
Ответ 8
Я собрал ответы, найденные здесь, в мою библиотеку рефакторинга, es_ios_utils. См. NSCategories.h:
@property(nonatomic, readonly) NSString *asCamelCaseFromUnderscores;
@property(nonatomic, readonly) NSString *asUnderscoresFromCamelCase;
Использование:
@"my_string".asCamelCaseFromUnderscores
дает @ "myString"
Пожалуйста, нажмите улучшения!
Ответ 9
Я столкнулся с этим вопросом, ища способ конвертировать Camel Case в разнесенную, отображаемую пользователем строку. Вот мое решение, которое работало лучше, чем замена @ "_" на @""
- (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;
}
Ответ 10
ОК, ребята. Вот полный ответ регулярного выражения, который я считаю единственным истинным способом:
Дано:
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"];
Выход fooBar.
И наоборот:
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];
Допустимые значения: foo_bar.
\ U $2 заменяет вторую группу захвата самой версией самого себя: D
\ L $1, однако, как ни странно, не заменяет первую группу захвата самой младшей версией самого себя: (Не уверен, почему это должно сработать.:/