정규식으로 일치하는 부분 문자열을 검색하는 방법
먼저 NSRegularExpression 개체를 만들고, 그것의 메서드에 NSString 객체를 전달하는 형태로 쓴다. 아무튼 설명하는 것보다 코드 보는 것이 빠르다.
NSString *string = @"「そんな正規表現で大丈夫か?」「大丈夫だ、問題ない」"; NSError *error = nil; NSRegularExpression *regexp = [NSRegularExpression regularExpressionWithPattern:@"「そんな(.+)で大丈夫か?」「(.+)」" options:0 error:&error]; if (error != nil) { NSLog(@"%@", error); } else { NSTextCheckingResult *match = [regexp firstMatchInString:string options:0 range:NSMakeRange(0, string.length)]; NSLog(@"%d", match.numberOfRanges); // 3개 // 0번 index : 일치하는 모든 문자열 NSLog(@"%@", [string substringWithRange:[match rangeAtIndex:0]]); // 1번 index : 첫번째 패턴 "正規表現" NSLog(@"%@", [string substringWithRange:[match rangeAtIndex:1]]); // 2번 index : 두번쨰 패턴 "大丈夫だ、問題ない" NSLog(@"%@", [string substringWithRange:[match rangeAtIndex:2]]); } |
다소 까다롭다. Ruby 쓰면 이렇게 끝나는데.
# coding: utf-8 if "「そんな正規表現で大丈夫か?」「大丈夫だ、問題ない」" =~ /「そんな(.+)で大丈夫か?」「(.+)」/ puts $& puts $1 puts $2 end |
아무튼 Ruby나 Perl과 비교하는 것은 (적어도 문자열 조작이나 정규식에 관해서 말하면) 공정하지 않지만! 어쨌든 정규표현식으로 패턴을 찾을 수 있다.
덧붙여서 - firstMatchInString : options : range :라는 메서드 이름에서 알 수 있지만
이것은 첫번째로 일치하는 부분 밖에 가지고 오지 않는다. 일치하는 부분 모두 원한다면 - matchesInString : options : range :를 사용하면 NSTextCheckingResult 가 들어간 NSArray 가 되돌아 온다. 별도 반환값은 계속 가지고 있어야 아니라 일치하는 매순간 무언가 처리하려면 - enumerateMatchesInString : options : range : usingBlock : 사용할 수있다. 아까 - firstMatchInString : options : range :를 다시 이렇게 된다NSRegularExpressionOptions options = 0; NSRange range = NSMakeRange(0, string.length); id block = ^(NSTextCheckingResult *match, NSMatchingFlags flag, BOOL *stop){ NSLog(@"%d", match.numberOfRanges); NSLog(@"%@", [string substringWithRange:[match rangeAtIndex:0]]); NSLog(@"%@", [string substringWithRange:[match rangeAtIndex:1]]); NSLog(@"%@", [string substringWithRange:[match rangeAtIndex:2]]); }; [regexp enumerateMatchesInString:string options:options range:range usingBlock:block]; |
Blocks 사용법은 이전 쓴 기사 를 읽어보셨는지 모르겠지만 그 기사를 쓴 시점에서 제대로 사용할 수있는 것이 Snow Leopard 뿐이였지만, 지금은라면 iPhone / iPad 모두 iOS4.0 을 전제로 만들 수 있고, 원래 NSRegularExpression 자체가 iOS4.0 이후 밖에 없기 때문에 NSRegularExpression를 사용할 수있는 환경이라면 Block 사용할 수 있으므로 문제 없다.
치환하기
정규식을 사용할 수있다면 가장하고 싶은 것은 Replace 일것이다. - stringByReplacingMatchesInString : options : range : withTemplate : 이용하면 된다
NSString *string = @"「そんな正規表現で大丈夫か?」「大丈夫だ、問題ない」"; NSString *template = @"$0\n→($2砕け散る)\n→「神は言っている、ここで死ぬ運命ではないと」\n→「$1」「一番いいのを頼む」"; NSRegularExpression *regexp = [NSRegularExpression regularExpressionWithPattern:@"「(そんな(.+)で大丈夫か?)」「.+」" options:0 error:nil]; NSString *replaced = [regexp stringByReplacingMatchesInString:string options:0 range:NSMakeRange(0,string.length) withTemplate:template]; NSLog(@"%@",replaced); |
처음에는 제대로 실행되지 않았다 しれと $ 0 라든지 $ 1이나 사용하고 있지만,
물론 제대로 치환된 문자열에서 캡처된 부분 문자열을 참조할 수 있을 것입니다.
그냥 - stringByReplacingMatchesInString : options : range : withTemplate : 는 문자열 자체를 치환하고있는 것이 아니라 인수 NSString 객체를 copy하여 치환후 반환된다. 그래서 원래 string 은 아무것도 변하지 않기 때문에 바꿀용도로 사용하려고 하면 실행이 안되는거 같고, 매번 문자열의 복사본을하기 때문에 때로는 자원이 낭비된다.
이 경우 - replaceMatchesInString : options : range : withTemplate : 를 쓴다. 기본적으로 - stringByReplacingMatchesInString : options : range : withTemplate : 같지만, 다음 사항을 다르다.
- 인수 NSString은 없고 NSMutableString 을 가지고
- 인수 개체의 복사본이 아니라 인수 개체 자체를 바꾸려면
- 반환값은 바꿀 문자열이 아닌 정수 값으로 대체 개체 의 수를 반환
그래서, 몇몇 정규식 치환 문자열을 또 다른 정규 표현식으로 대체하여 같은 것을 할 경우는 아래 메서드를 사용해야한다.
참고로 위의 두 메소드는 일치하는 부분을 모두 바꾼다.
예를 들면 아래와 같은 코드로 하면 "大丈夫(か|だ)" 가 모두 교체된다.
NSString *string = @"「そんな正規表現で大丈夫か?」「大丈夫だ、問題ない」"; NSString *template = @"チョ☆チョニッシーナ☆まっソコぶれっシュ☆エスボグリバンバーベーコンさんだね!"; NSRegularExpression *regexp = [NSRegularExpression regularExpressionWithPattern:@"大丈夫(か|だ)、?" options:0 error:nil]; NSString *replaced = [regexp stringByReplacingMatchesInString:string options:0 range:NSMakeRange(0,string.length) withTemplate:template]; NSLog(@"%@",replaced); |
만약 일치하는 부분 내의 특정 부분을 치환하고 싶다면
- firstMatchInString : options : range : 나
- matchesInString : options : range :의
NSTextCheckingResult 개체를 가져와 두었다가
- replacementStringForResult : inString : offset : template : 을 사용하면 되지만
좀 귀찮다.
RegexKitLite or NSRegularExpression
모두 써 본 소감을 말하자면, 개인적으로 RegexKitLite 의 NSString 에 메서드로 처리하는
방식의 API 가 사용하기 편했다. CoreFoundation 통해 쓰고 있기 때문에 성능도 나쁘지 않고, 비교적 초기에 Blocks 에 적응했거나 활발히 개발하고 있고 이미 RegexKitLite를 사용하고 있다면 무리해서 NSRegularExpression 로 갈아 탈 필요는 없다고 생각한다.iOS4.0 이전 버전도 대상으로 한다면 다른 선택의 여지가 없고, 나중 왠지 NSRegularExpression 클래스는 iOS 밖에 없어 MacOSX 에서는 사용할 수 없다고 생각되기 때문에, iOS와 Mac에서도 동작과 같은 코드를 쓸 경우 역시 NSRegularExpression 사용할 수 없지 않을까..
NSRegularExpression 는 Foundation의 일부이므로, 정규식 치환을 사용하고 이 목적을 위해 외부의 코드로 분리하려고 프로젝트에 포함하거나, libicucore에 반드시 연결 ... 한다던지, 만일 iOS의 내부 구현이 바뀌거나 따위의 약관이 바뀌거나해서 아마 다시 갱신되지 않을까 생각되기도 한다 (안정성 때문이라던지), 때문에 앞으로 만들 애플 리케이션에서 4.0 이상만을 대상으로 하는 경우 NSRegularExpression 로 쓰는 걸까라고 생각되기도 한다.