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:&currentChar 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, однако, как ни странно, не заменяет первую группу захвата самой младшей версией самого себя: (Не уверен, почему это должно сработать.:/