【问题标题】:CamelCase to underscores and back in Objective-CCamelCase 下划线并返回到 Objective-C
【发布时间】:2009-12-17 01:43:31
【问题描述】:

我正在寻找一种简单、有效的方法来将 CamelCase 中的字符串转换为下划线表示法(即 MyClassName -> my_class_name)并在 Objective C 中再次转换回来。

我当前的解决方案在 NSMutableStrings 上涉及很多 rangeOfStringcharacterAtIndexreplaceCharactersInRange 操作,而且简直丑到要命 :) 似乎必须有更好的解决方案,但我不确定这是什么。

我宁愿不只为这个用例导入正则表达式库,但如果其他方法都失败了,这是一种选择。

【问题讨论】:

    标签: objective-c string nsstring


    【解决方案1】:

    Chris 对 RegexKitLite 的建议很好。这是一个出色的工具包,但使用NSScanner 可以轻松完成。在+uppercaseLetterCharacterSet+lowercaseLetterCharacterSet 之间交替使用-scanCharactersFromSet:intoString:。要返回,您可以改用 -scanUpToCharactersFromSet:,使用其中只有一个下划线的字符集。

    【讨论】:

    • 谢谢 Rob -- 我缺乏使用 NSScanner 的经验,这让我忽略了这个解决方案,但它比我拥有的要干净得多。
    【解决方案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

      【讨论】:

      • 如果字符串是Pascal大小写,即MyBundleVersion,它将产生_my_bundle_version
      • 我希望看到一个修正了前导下划线的版本,以及一个可以转换其他方式的版本。
      • @IulianOnofrei 它不处理 URLRequest 之类的名称。
      • 我的解决方案很简单[underscoreString stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"_"]]
      • @AntonMatosov,您的解决方案删除了​​下划线,并且不会将驼峰式字符串转换为下划线字符串。
      【解决方案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。

      【讨论】:

      • 克里斯,非常感谢 RegexKitLite 指针。我肯定会在未来的项目中使用它!
      • 这比这个答案在 iOS 开发术语中发布的时间晚了大约 100,000 年;如果其他人在这里绊倒:不要使用 RegexKitLite。 NSRegularExpression 在发布此答案大约六个月后出现在 iOS 4 中,将相同的 ICU 库带入标准框架。
      【解决方案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's a problem somewhere.
              if (lastScanLocation == scanner.scanLocation) return nil;
              lastScanLocation = scanner.scanLocation;
          }
      
          return builder;
      }
      
      @end
      

      【讨论】:

      • 为什么不使用 NSMutableString 作为构建器?
      • 确实会更好。 :-)
      【解决方案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'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;
        }
        

        【讨论】:

          【解决方案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;
            }
            

            【讨论】:

            • 看起来已经很不错了,但是: 1. 如果输入为空怎么办? 2.转换回来是如何工作的? 3. 你的循环不能以i = 1 开始,使i &gt; 1过时吗?
            • 1.如果输入为 nil,则返回 nil,因为发送到 nil 的长度消息将返回 0 2。好点,我的应用程序中不需要这个 3。我喜欢,将其编辑到我的答案中
            【解决方案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 然而,奇怪的是,它不会用自己的小写版本替换第一个捕获组:(不知道为什么,它应该可以工作。:/

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2011-02-26
              • 2016-03-25
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多