将Objective-C中的CamelCase转换为下划线,并将下划线转换回CamelCase

16

我正在寻找一种简单高效的方法将CamelCase字符串转换为下划线表示法(例如,MyClassName -> my_class_name),并在Objective C中进行反向转换。

我目前的解决方案涉及大量的rangeOfStringcharacterAtIndexreplaceCharactersInRange操作NSMutableStrings,非常丑陋。似乎必须有更好的解决方案,但我不确定是什么。

如果所有其他方法都失败了,我宁愿不导入regex库,尽管这也是一种选择。

10个回答

11

克里斯建议使用RegexKitLite是不错的选择。这是一个优秀的工具包,但使用NSScanner也可以很容易地完成此操作。交替使用-scanCharactersFromSet:intoString:+uppercaseLetterCharacterSet以及+lowercaseLetterCharacterSet即可。要返回原始值,您需要使用-scanUpToCharactersFromSet:,并使用只包含下划线的字符集。


1
谢谢Rob——我缺乏使用NSScanner的经验,这让我忽视了这个解决方案,但它比我原来的方法更加简洁。 - John Biesnecker

9
这些怎么样:

如何评价这些:

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。


9

试试这个神奇的方法:

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

输出:我的捆绑版本。

3
如果字符串是帕斯卡命名法,即MyBundleVersion,它将生成_my_bundle_version。 - Mark Horgan
我希望能看到一个修复了前导下划线的版本,并且也能进行另一种转换。 - Peter DeWeese
@IulianOnofrei 它无法处理像 URLRequest 这样的名称。 - Anton Matosov
我的解决方案很简单 [underscoreString stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"_"]] - Anton Matosov
@AntonMatosov,您的解决方案删除了下划线,并且没有将驼峰字符串转换为下划线字符串。 - Iulian Onofrei
我的代码行是在这篇文章的正则表达式解决方案中的补充。它只是解决了前导下划线的问题。 - Anton Matosov

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
     }
}
编辑:经过快速的谷歌搜索,我无法找到任何一种方法来做到这一点,即使在纯C中也是如此。然而,我发现了一个可能有用的框架。它叫做RegexKitLite。它使用内置的ICU库,所以它只会在最终二进制文件中增加约20K的大小。

1
Chris,非常感谢你提供的RegexKitLite指针。我肯定会在未来的项目中使用它! - John Biesnecker
这篇回答发布时距今已经过去了大约100,000年,以iOS开发的术语来说;如果有其他人偶然进入这里:不要使用RegexKitLite。在这篇回答发布大约六个月后,iOS 4中出现了NSRegularExpression,将相同的ICU库引入标准框架中。 - Tommy

4
这是我对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

为什么不使用NSMutableString作为构建器? - cheng yang
确实会更好,没错。 :-) - MonsieurDart

3

这是另一种基于以上所有版本的版本。该版本处理了额外的表格。具体而言,已经测试过以下内容:

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

Here goes

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

1
如果您关心代码的速度,您可能想编写更高效的代码版本:
- (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;
}

0

我已将此处找到的答案合并到我的重构库es_ios_utils中。请参见NSCategories.h

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

使用方法:

@"my_string".asCamelCaseFromUnderscores

返回 @"myString"

请推动改进!


0

大家好。这里是一个全正则表达式答案,我认为这是唯一正确的方法:

给定:

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 奇怪的是,不会用其小写版本替换第一个捕获组 :( 不确定为什么,它应该可以工作。 :/


0
我偶然发现这个问题,正在寻找一种将驼峰命名法转换为可显示的空格分隔字符串的方法。以下是我的解决方案,比用@"_"替换更好。
- (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;
}

看起来已经很不错了,但是:1. 如果输入为空怎么办?2. 回转换如何工作?3. 你的循环是否可以从 i = 1 开始,使得 i > 1 变得无用? - Trinimon
  1. 如果输入为nil,则返回nil作为发送给nil的长度消息将返回0。
  2. 很好,我在我的应用程序中不需要这个。
  3. 我喜欢那个,把它编辑进了我的答案。
- Danny

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接