태그 보관물: 정규식

RegularExpression-swift

Swift3에서 문자열 확장과 재명명된 API를 이용하여 간결하게 문자열 내의 특정 범위를 찾고, 문자열을 치환하는 테크닉에 대해 살펴본다.

Swift의 정규식

Swift는 언어 자체에서 정규식을 지원하지 않고 FoundationNSRegularExpression 클래스를 이용한다.

  1. NSRegulareExpressioninitthrows이기 때문에 try와 같이 사용되어야 한다.
  2. 매치 결과는 TextCheckingResult 클래스의 인스턴스를 얻게 된다. 이는 매치영역 및 영역 내 각 매치 그룹의 범위를 NSRange값으로 가지고 있다.
  3. 문제는 Swift 문자열의 부분문자열은 Index<String.Index>에 의해서 얻을 수 있지, NSRange를 이용할 수 없다. 따라서 이를 컨버팅하는 편의함수나 타입 확장을 이용해야 한다.

Continue reading “RegularExpression-swift” »

Objective-C / Swift :: NSRegularExpression

NSRegularExpression

Foundation은 유니코드 문자열에 대해서 정규식을 적용할 수 있는 NSRegularExpression 클래스를 제공한다. 이 클래스의 인스턴스는 컴파일된 정규식 패턴을 나타낸다. 여기서 사용되는 정규식 표현 패턴은 ICU의 안을 따르고 있다. (파이썬 정규식과 거의 유사하다.)

http://userguide.icu-project.org/strings/regexp

기본적으로 정규식 객체는 자신의 패턴을 문자열에 적용해서 매치 결과들에 대해서 실행되는 블럭 이터레이터를 제공한다. 그 외에도 매치 결과를 배열로 리턴하거나, 매치의 수를 찾거나, 첫 매치를 찾는 등의 편의 메소드도 제공한다.

각각의 매치 결과는 NSTextCheckingResult 객체인데, 이는 전체 매치의 범위(NSRange) 및 개별 캡쳐 그룹의 범위값을 가지고 있다.

객체 생성

기본적인 정규식 객체 생성 방법은 다음과 같다.

NSError *error = nil;
NSRegularExpression *regex = [NSRegularExpression 
            regularExpressionWithPattern:@"\\b(a|b)(c|d)\\b"
            options:NSRegularExpressionCaseInsensitive
            error:&error];

NSRegularExpressionCaseInsensitive는 대소문자 구분없는 매칭을 위한 옵션이다. swift에서는 .CaseInsensitive로 간소화되었다.(NSRegularExpressionOptions)

매치개수 세기

매치되는 부분의 개수는 numberOfMatchesinString:options:range:로 셀 수 있다. 여기서 옵션은 NSMatchingOptions의 값으로, 거의 nil을 쓴다고 보면 된다.

NSUInteger numberOfMatches = [regex numberOfMatchesInString:str
                                    options:0
                                    range:NSMakeRange(0, [str length])];

첫번째 매치 결과에 관심이 있다면 rangeOfFirstMatchInString:options:range:를 쓰면 된다.

NSRange rangeOfFirstMatch = [regex rangeOfFirstMatchInString:str options:0 range:NSMakeRange(0, [str length])];
if(!NSEqualRanges(rangeOfFirstMatch, NSMakeRange(NSNotFound, 0))) {
    NSString *substringForFirstMatch = [str substringWithRange:rangeOfFirstMatch];
}

모든 매치 결과는 matchesInString:options:range:로 구한다.

NSArray<NSTextCheckingResult> *matches = [regex matchesInString:str
                                options:0 range:NSMakeRange(0, [str length])];
for (NSTextCheckingResult *match in matches) {
    NSRange matchRange = [match range];
    NSRange firstHalfRange = [match rangeAtIndex:0];
    NSRange secondHalfRange = [match rangeAtIndex:1];
}

매치 결과의 범위 값은 NSTextCheckingResult-range로 구하는데, 만약 캡쳐링 그룹이 있다면 -rangeAtIndex:로 구한다. 그룹번호를 넣으면 되고, 그룹번호가 0인 경우에는 매치 전체의 범위가 된다.

편의상 첫번째 매치를 구하는 함수도 제공한다. -firstMatchInString:options:range:이고 사용법은 동일하다.

블럭이터레이터

-enumerateMatchesInString:options:range:usingBlock:은 각각의 매치에 대해서 블럭을 실행시킬 수 있는 메소드이다.

__block NSUInteger count = 0;
[regex enumerateMatchesInString:str options:0 range:NSMakeRange(0, [str length] usingBlock:^(NSTextCheckingResult *match, NSMatchingFlags flags, BOOl *stop){
    NSRange matchRange = [match range]; // 각 매치 전체의 범위
    NSRange firstHalfRange = [match rangeAtIndex:1]; // 첫 그룹
    NSRange secondHalfRange = [match rangeAtIndex:2]; // 두 번째 그룹
    if (count++ >= 100) *stop = YES; // 100번째 매치를 만나면 그만둔다.
})]

바꾸기

예시로 …

NSString *modifiedString = [regex stringByReplacingMatchesInString:str
                            options:0 range:NSMakeRange(0, [str length])
                            withTemplate:@"$2$1"];