정규식

# 학습 과정 소개

정규식(Regular expression)을 활용하면 텍스트를 유연하고 효율적으로 처리할 수 있습니다. 메이플스토리 월드에서는 기본적인 루아 문자열 패턴 일치 외에도 정규식을 지원합니다. 정규식의 패턴 일치 표기법을 사용하면 많은 양의 텍스트 구문을 분석하여 아래의 항목을 수행할 수 있습니다.

  * 특정 문자 패턴 찾기
  * 텍스트 유효성을 검사하여 미리 정의된 패턴과 일치하는지 확인
  * 텍스트 하위 문자열을 추출, 편집, 변경, 삭제할 수 있음



단, 정규식은 다소 무겁기 때문에 간단한 정규식 작업을 할 때에는 루아 문자열 패턴 일치 기능을 활용하는 것이 좋습니다. 복잡한 작업이 필요한 경우에 아래 가이드를 참고하여 정규식을 활용해 봅시다.

##### 참고 API Reference

[Regex](/apiReference/Misc/Regex)

##### 참고 가이드

[정규식 언어](/docs/?postId=1043)

# 생성자

`Regex(String pattern)`로 특정 문자열 패턴을 가진 정규식 객체를 생성할 수 있습니다. 예를 들어 "word"를 찾는 정규식 객체는 아래와 같이 생성합니다.
    
    
    @Component
    script WordRegexInitializer extends Component
        @ExecSpace("ServerOnly")
        method void OnBeginPlay()
            -- "word" 를 찾는 정규식 객체
            local regex = Regex("word")
        end
    end
    

  
또한 정규식 객체를 생성할 때 `RegexOption`을 사용하면 여러 가지 필요한 옵션을 지정할 수 있습니다. 예를 들어, 아래와 같이 `RegexOption.IgnoreCase`를 설정하면 지정한 문자열 패턴을 대/소문자를 구분하지 않고 찾을 수 있습니다. ```lua @Component script WordPatternDefinition extends Component @ExecSpace("ServerOnly") method void OnBeginPlay() \-- 대/소문자를 구분하지 않고 "word" 를 검색 local regex = Regex("word", RegexOption.IgnoreCase) end end ``` 

# 프로퍼티

boolean 타입의 IsRightToLeft 프로퍼티가 있습니다. 이 프로퍼티로 정규식을 오른쪽에서 왼쪽으로 매칭하는지 여부를 가져옵니다.

# 함수

### IsMatch

`IsMatch(String origin)`는 지정된 정규식 패턴과 일치하는 항목이 입력 문자열(String origin)에 존재하는지 여부를 확인합니다.

  * 정규식 패턴과 일치하는 항목이 있으면 true를 반환합니다.
  * 정규식 패턴과 일치하는 항목이 없거나 제한 시간을 초과하여 정규식이 실패하면 false를 반환합니다.


    
    
    @Component
    script RegexPatternValidator extends Component
        @ExecSpace("ServerOnly")
        method void OnBeginPlay()
            -- 일반
            local pattern = "^[A-Z0-9]\\d{2}[A-Z0-9](-\\d{3}){2}[A-Z0-9]$"
            local regex = Regex(pattern, RegexOption.IgnoreCase)
            
            log(regex:IsMatch("1298-673-4192"))    --  true
            log(regex:IsMatch("A08Z-931-468a"))    --  true
            
            log(regex:IsMatch("_A90-123-129X"))    --  false
            log(regex:IsMatch("12345-KKA-1230"))    --  false
            log(regex:IsMatch("0919-2893-1256"))    --  false
        end
    end
    

  
`IsMatch(String origin, Int32 startIndex)`는 입력 문자열의 탐색 시작 위치(startIndex)부터 지정된 정규식 패턴과 일치하는 항목이 존재하는지 여부를 확인합니다. ```lua @Component script RegexPatternMatcher extends Component @ExecSpace("ServerOnly") method void OnBeginPlay() \-- startIndex 지정 local pattern = "[a-zA-Z0-9]\\\d{2}[a-zA-Z0-9](-\\\d{3}){2}[A-Za-z0-9]$" local regex = Regex(pattern) 
    
    
        
        log(regex:IsMatch("Part Number: 1298-673-4192", 10))  -- true
        log(regex:IsMatch("Part Number: 1298-673-4192", 15))  -- false
    end
    

end
    
    
    ### Match
    `Match(String origin)`는 입력 문자열에서 정규식 패턴과 일치하는 첫 항목을 검색합니다.
    

@Component script RegexPatternMatcher extends Component @ExecSpace("ServerOnly") method void OnBeginPlay() local input = "One car red car blue car" local pattern = "(\w+)\s+(car)"
    
    
        
        local regex = Regex(pattern, RegexOption.IgnoreCase)
        
        local match = regex:Match(input)
        
        while match.Success do
            log(match.Text)
            match = match:NextMatch()
        end
        
        -- (결과)
        -- One car
        -- red car
        -- blue car
    end
    

end
    
    
      
    
    `Match(String origin, Int32 startIndex)`는 입력 문자열의 탐색 시작 위치부터 정규식 패턴과 일치하는 첫 번째 항목을 검색합니다. 정규식이 실패하면 nil이 반환됩니다.
    

@Component script RegexMatcher extends Component @ExecSpace("ServerOnly") method void OnBeginPlay() \-- startIndex 지정 local origin = "123456789" local pattern = "\d"
    
    
        
        local regex = Regex(pattern)
        
        log(regex:Match(origin, 1).Text)      -- 1
        log(regex:Match(origin, 3).Text)      -- 3
        log(regex:Match(origin, 5).Text)      -- 5
    end
    

end
    
    
      
    
    `Match(String origin, Int32 startIndex, Int32 length)`는 입력 문자열의 탐색 시작 위치부터 지정된 길이의 문자열 내에서 정규식 패턴과 일치하는 첫 번째 항목을 검색합니다. 정규식이 실패하면 nil이 반환됩니다.
    

@Component script RegexMatcher extends Component @ExecSpace("ServerOnly") method void OnBeginPlay() \-- startIndex와 길이 지정 local origin = "123456789" local pattern = "\d.."
    
    
        
        local regex = Regex(pattern)
        
        log(regex:Match(origin, 1, 5).Text)   -- 12345
        log(regex:Match(origin, 3, 3).Text)   -- 345
        log(regex:Match(origin, 5, 1).Text)   -- 5
    end
    

end
    
    
    ### Matches
    `Matches(String origin)`는 입력 문자열에서 정규식 패턴과 일치하는 모든 항목을 검색합니다.
    

@Component script RegexMatcher extends Component @ExecSpace("ServerOnly") method void OnBeginPlay() local origin = "Who writes these notes and uses our paper?" local pattern = "\b\w+es\b"
    
    
        
        local regex = Regex(pattern)
        
        local matches = regex:Matches(origin)
        
        log(#matches)     -- 3
        
        for i, match in pairs(matches) do
            log(match.Text)
        end
        
        -- (결과)
        -- writes
        -- notes
        -- uses
    end
    

end
    
    
      
    
    `Matches(String origin, Int32 startIndex)`는 입력 문자열의 탐색 시작 위치부터 정규식 패턴과 일치하는 모든 항목을 검색합니다.
    

@Component script RegexSequenceFinder extends Component @ExecSpace("ServerOnly") method void OnBeginPlay() \-- startIndex 지정 local origin = "Who writes these notes and uses our paper?" local pattern = "\b\w+es\b"
    
    
        
        local regex = Regex(pattern)
        
        local firstMatch = regex:Match(origin)
        
        local matches = regex:Matches(origin, firstMatch.Index + firstMatch.Length)
        
        log(#matches)     -- 2
        
        for i, match in pairs(matches) do
            log(match.Text)
        end
        
        -- (결과)
        -- notes
        -- uses
    end
    

end
    
    
    ### Replace
    `Replace(String origin, String replacement)`는 입력 문자열에서 정규식 패턴과 일치하는 모든 항목을 대체 문자열(replacement)로 변경합니다.
    

@Component script WhitespaceNormalizer extends Component @ExecSpace("ServerOnly") method void OnBeginPlay() \-- 일반 local origin = "This is text with far too much white space." local pattern = "\s.."
    
    
        
        local regex = Regex(pattern)
        
        local result = regex:Replace(origin, " ")
        
        log(result) -- "This is text with far too much white space."
    end
    

end
    
    
      
    
    `Replace(String origin, String replacement, Int32 count)`는 입력 문자열에서 정규식 패턴과 일치하는 항목을 지정된 개수까지 대체 문자열로 변경합니다. 정규식이 실패하면 nil이 반환됩니다.
    

@Component script WhitespaceReducer extends Component @ExecSpace("ServerOnly") method void OnBeginPlay() local origin = "This is text with far too much white space." local pattern = "\s.."
    
    
        
        local regex = Regex(pattern)
        
        local result = regex:Replace(origin, " ", 4)
        
        log(result) -- "This is text with far  too   much   white space."
    end
    

end
    
    
      
    
    `Replace(String origin, String replacement, Int32 count, Int32 startIndex)`는 탐색 시작 위치부터 정규식 패턴과 일치하는 항목을 지정된 개수까지 대체 문자열로 변경합니다. 정규식이 실패하면 nil이 반환됩니다.
    

@Component script RegexMatchReplacer extends Component @ExecSpace("ServerOnly") method void OnBeginPlay() \-- 최대 개수와 시작 위치 설정 local origin = "1 3 5 7 9 h e l l o w o r l d" local pattern = "\s.."
    
    
        
        local regex = Regex(pattern)
        
        local result = regex:Replace(origin, "", 4, 11)
        
        log(result) -- "1 3 5 7 9 hello w o r l d"
    end
    

end
    
    
    ### ReplaceByEvaluator
    `ReplaceByEvaluator(String origin, function→string evaluator)`는 정규식 패턴과 일치하는 모든 항목을 evaluator로 반환된 문자열로 변경합니다. 정규식이 실패하면 nil이 반환됩니다.
    

@Component script WordLengthAnnotator extends Component @ExecSpace("ServerOnly") method void OnBeginPlay() local origin = "letter alphabetical missing lack release penchant slack acryllic laundry cease" local pattern = "\w.. "
    
    
        
        local regex = Regex(pattern, RegexOption.IgnorePatternWhitespace)
        
        ---@param match RegexMatch
        ---@return string
        local function evaluator(match)
            return match.Text.."("..tostring(match.Text:len())..")"
        end
        
        local result = regex:ReplaceByEvaluator(origin, evaluator)
        
        log(result)   
        -- "letter(6) alphabetical(12) missing(7) lack(4) release(7) penchant(8) slack(5) acryllic(8) laundry(7) cease(5)"
    end
    

end
    
    
      
    
    `ReplaceByEvaluator(String origin, function→string evaluator, Int32 count)`는 정규식 패턴과 일치하는 항목을 지정된 개수까지 evaluator 로 반환된 문자열로 변경합니다. 정규식이 실패하면 nil이 반환됩니다.
    

@Component script WordLengthReplacer extends Component @ExecSpace("ServerOnly") method void OnBeginPlay() local origin = "letter alphabetical missing lack release penchant slack acryllic laundry cease" local pattern = "\w.. "
    
    
        
        local regex = Regex(pattern, RegexOption.IgnorePatternWhitespace)
        
        ---@param match RegexMatch
        ---@return string
        local function evaluator(match)
            return match.Text.."("..tostring(match.Text:len())..")"
        end
        
        local result = regex:ReplaceByEvaluator(origin, evaluator, 5)
        
        log(result)  
        -- "letter(6) alphabetical(12) missing(7) lack(4) release(7) penchant slack acryllic laundry cease"
    end
    

end
    
    
      
    
    `ReplaceByEvaluator(String origin, function→string evaluator, Int32 count, Int32 startIndex)`는 탐색 시작 위치부터 정규식 패턴과 일치하는 항목을 지정된 개수까지 evaluator 로 반환된 문자열로 변경합니다. 정규식이 실패하면 nil이 반환됩니다.
    

@Component script MatchLengthAppender extends Component @ExecSpace("ServerOnly") method void OnBeginPlay() local origin = "letter alphabetical missing lack release penchant slack acryllic laundry cease" local pattern = "\w.. "
    
    
        
        local regex = Regex(pattern, RegexOption.IgnorePatternWhitespace)
        
        ---@param match RegexMatch
        ---@return string
        local function evaluator(match)
            return match.Text.."("..tostring(match.Text:len())..")"
        end
        
        local result = regex:ReplaceByEvaluator(origin, evaluator, 5, 21)
        
        log(result)   
        -- "letter alphabetical missing(7) lack(4) release(7) penchant(8) slack(5) acryllic laundry cease"
    end
    

end
    
    
    ### Split
    `Split(String origin)`는 정규식 패턴과 일치하는 항목을 기준으로 입력 문자열을 분할합니다. 정규식이 실패하면 nil이 반환됩니다.
    

@Component script RegexStringSplitter extends Component @ExecSpace("ServerOnly") method void OnBeginPlay() local origin = "plum-bear-pear" local pattern = "-"
    
    
        
        local regex = Regex(pattern)
        
        local texts = regex:Split(origin)
        
        log(#texts) -- 3
        
        for i, text in pairs(texts) do
            log(text)
        end
        
        -- (결과)
        -- plum
        -- bear
        -- pear
    end
    

end
    
    
      
    
    `Split(String origin, Int32 count)`는 정규식 패턴과 일치하는 항목을 기준으로 입력 문자열을 분할하되, 최대 지정 개수(count)까지만 분할합니다. 정규식이 실패하면 nil이 반환됩니다.
    

@Component script RegexSplitLimiter extends Component @ExecSpace("ServerOnly") method void OnBeginPlay() \-- count 지정 local origin = "apple-apricot-plum-pear-banana" local pattern = "-"
    
    
        
        local regex = Regex(pattern)
        
        local texts = regex:Split(origin, 4)
        
        log(#texts) -- 4
        
        for i, text in pairs(texts) do
            log(text)
        end
        
        -- (결과)
        -- apple
        -- apricot
        -- plum
        -- pear-banana
    end
    

end
    
    
      
    
    `Split(String origin, Int32 count, Int32 startIndex)`는 입력 문자열의 탐색 시작 위치부터 정규식 패턴과 일치하는 항목을 기준으로 최대 지정 개수까지만 분할합니다. 정규식이 실패하면 nil이 반환됩니다.
    

@Component script FixedPartSplitter extends Component @ExecSpace("ServerOnly") method void OnBeginPlay() local origin = "apple-apricot-plum-pear-banana" local pattern = "-"
    
    
        
        local regex = Regex(pattern)
        
        local texts = regex:Split(origin, 4, 7)
        
        log(#texts) -- 4
        
        for i, text in pairs(texts) do
            log(text)
        end
        
        -- (결과)
        -- apple-apricot
        -- plum
        -- pear
        -- banana
    end
    

end
    
    
    # 정규식 옵션
    
    `RegexOption`을 활용하면 정규식에 옵션을 설정할 수 있습니다.
    
    | Option | Description |
    | :---: | --- |
    | IgnoreCase | 대/소문자를 구분하지 않고 일치하는 항목을 검색하도록 합니다. |
    | IgnorePatternWhitespace | 이스케이프되지 않은 공백을 패턴에서 무시합니다. |
    | RightToLeft | 검색 방향을 변경합니다. 왼쪽→오른쪽이 아니라 오른쪽→왼쪽으로 검색하도록 합니다.  |
    | ExplicitCapture | 명명되지 않은 그룹을 캡처하지 않습니다. 명시적으로 명명되거나 번호가 매겨진 그룹만 유효한 캡처가 되도록 지정합니다. |
    | Multiline | 여러 줄 모드를 사용합니다. 전체 문자열의 시작과 끝 뿐만 아니라 모든 줄의 시작과 끝에서 각각 일치하도록 ^과 $의 의미를 변경합니다. |
    | Singleline | 한 줄 모드를 사용합니다. "\n을 제외한 모든 문자"가 아닌 "\n을 포함한 모든 문자"와 일치하도록 점(.)의 의미를 변경합니다. |
    
    각 옵션의 예제를 통해 사용 방법을 살펴봅시다.
    
    ### IgnoreCase
    대/소문자를 구분하지 않고 일치하는 항목을 검색합니다.
    

@Component script RegexCaseMatcher extends Component @ExecSpace("ServerOnly") method void OnBeginPlay() \-- IgnoreCase 옵션 설정하지 않음 local pattern = "\bthe\w*\b" local input = "The man then told them about that event."
    
    
        
        local noOptionRegex = Regex(pattern)
        local matches = noOptionRegex:Matches(input)
        log(#matches) -- 2
        
        -- IgnoreCase 옵션 설정함
        local optionRegex = Regex(pattern, RegexOption.IgnoreCase)
        matches = optionRegex:Matches(input)
        log(#matches) -- 3
    end
    

end
    
    
    ### IgnorePatternWhitespace
    이스케이프되지 않은 공백을 패턴에서 무시합니다.
    

@Component script RegexOptionDemonstrator extends Component @ExecSpace("ServerOnly") method void OnBeginPlay() \-- IgnorePatternWhitespace 옵션 설정하지 않음 local pattern = "\b \\(? ( (?>\w+) ,?\s? )+ [\\.!?] \\)? # Matches an entire sentence." local input = "This is the first sentence. Is it the beginning of a literary masterpiece? I think not. Instead, it is a nonsensical paragraph."
    
    
        
        local noOptionRegex = Regex(pattern)
        local matches = noOptionRegex:Matches(input)
        log(#matches) -- 0
        
        -- IgnorePatternWhitespace 옵션 설정함
        local optionRegex = Regex(pattern, RegexOption.IgnorePatternWhitespace)
        matches = optionRegex:Matches(input)
        log(#matches) -- 4
    end
    

end
    
    
    ### RightToLeft
    검색 방향을 변경합니다. 왼쪽→오른쪽이 아니라 오른쪽→왼쪽으로 검색하도록 합니다.
    

@Component script RegexRightToLeftDemo extends Component @ExecSpace("ServerOnly") method void OnBeginPlay() \-- RightToLeft 옵션 설정하지 않음 local pattern = "\bb\w+\s" local input = "build11 band22 tab"
    
    
        
        local noOptionRegex = Regex(pattern)
        local matches = noOptionRegex:Matches(input)
        
        log(noOptionRegex.IsRightToLeft)  -- false
        
        for i, match in pairs(matches) do
            log(match.Text)
        end
        
        -- (결과)
        -- build11
        -- band22
        
        -- RightToLeft 옵션 설정함 
        local optionRegex = Regex(pattern, RegexOption.RightToLeft)
        matches = optionRegex:Matches(input)
        
        log(optionRegex.IsRightToLeft)    -- true
        
        for i, match in pairs(matches) do
            log(match.Text)
        end
        
        -- (결과)
        -- band22
        -- build11
    end
    

end
    
    
    ### ExplicitCapture
    기본적으로 캡처할 그룹은 괄호를 사용하여 정의합니다. 그룹을 정의할 때 `(?...)` 형식으로 그룹 이름을 정할 수 있습니다. `ExplicitCapture` 옵션을 사용하면 명명되지 않은 그룹은 캡처하지 않습니다. 명시적으로 `(?...)` 형식으로 명명되거나 번호가 매겨진 그룹만 캡처합니다.
    

@Component script ExplicitCaptureDemo extends Component @ExecSpace("ServerOnly") method void OnBeginPlay() \-- ExplicitCapture 옵션 설정하지 않음 local pattern = "^(?\d{3})-(\d{4})-(\d{4})-(\d{4})-(\d{4})-(\d{4})-(\d{4})-(?\d{3})$" local input = "010-1111-2222-3333-4444-5555-6666-777"
    
    
        
        local noOptionRegex = Regex(pattern)
        local match = noOptionRegex:Match(input)
        
        ---@type table
        local groups = match:GetGroups()
        
        for j, group in pairs(groups) do
            log(group.Name)
        end
        
        -- (결과)
        -- 1
        -- 2
        -- 3
        -- 4
        -- 5
        -- 6
        -- 7
        -- first
        -- last
        
        -- ExplicitCapture 옵션 설정함
        local optionRegex = Regex(pattern, RegexOption.ExplicitCapture)
        match = optionRegex:Match(input)
        ---@type table
        groups = match:GetGroups()
        
        for j, group in pairs(groups) do
            log(group.Name)
        end
        
        -- (결과)
        -- 1
        -- first
        -- last
    end
    

end
    
    
    ### Multiline
    여러 줄로 구성된 입력 문자열을 처리할 수 있습니다. 
    원래 `^`와 `$`는 전체 입력 문자열의 시작과 끝 부분을 의미합니다. 하지만 `Multiline` 옵션을 사용하면 `^`와 `$`가 전체 입력 문자열이 아니라 각 줄의 시작과 끝 부분과 매치하게 됩니다. 각 줄은 줄 바꿈 문자 `\n`으로 구분합니다.
    

@Component script RegexMultilineOptionExample extends Component @ExecSpace("ServerOnly") method void OnBeginPlay() \-- Multiline 옵션 사용하지 않음 local pattern = "^(?\w+)\s(?\d+)$" local input = "Joe 164\nSam 208\nAllison 211\nGwen 171\n"
    
    
        
        local noOptionRegex = Regex(pattern)
        local matches = noOptionRegex:Matches(input)
        
        log(#matches) -- 0
        
        for i, match in pairs(matches) do
        
            ---@type table
            local groups = match:GetGroups()
        
            local nameCapture = groups["Name"]:GetCaptures()[1]
            local numberCapture = groups["Number"]:GetCaptures()[1]
        
            log("Name : "..nameCapture.Text..", Number : "..numberCapture.Text)
        end
        
        -- (결과)
        -- (없음)
        
        -- Multiline 옵션 사용
        local optionRegex = Regex(pattern, RegexOption.Multiline)
        matches = optionRegex:Matches(input)
        
        log(#matches) -- 4
        
        for i, match in pairs(matches) do
        
            ---@type table
            local groups = match:GetGroups()
        
            local nameCapture = groups["Name"]:GetCaptures()[1]
            local numberCapture = groups["Number"]:GetCaptures()[1]
        
            log("Name : "..nameCapture.Text..", Number : "..numberCapture.Text)
        end
        
        -- (결과)
        -- Name : Joe, Number : 164
        -- Name : Sam, Number : 208
        -- Name : Allision, Number : 211
        -- Name : Gwen, Number : 171
    end
    

end
    
    
    ### Singleline
    입력된 문자열을 한 줄로 구성된 것처럼 처리합니다.
    기본적으로 `Match()`는 `\n`을 만나면 끝나지만 `Singleline` 옵션을 사용하면 `\n`을 만나도 계속 진행합니다.
    

@Component script RegexSinglelineExample extends Component @ExecSpace("ServerOnly") method void OnBeginPlay() \-- Singleline 옵션 사용하지 않음 local pattern = "^.+" local input = "This is one line and\nthis is the second."
    
    
        
        local noOptionRegex = Regex(pattern)
        local match = noOptionRegex:Match(input)
        
        log(match.Text)   -- "This is one line and"
        
        -- Singleline 옵션 사용함 
        local optionRegex = Regex(pattern, RegexOption.Singleline)
        match = optionRegex:Match(input)
        
        log(match.Text)   -- "This is one line and\nthis is the second."
    end
    

end
    
    
    ### 다중 옵션 적용
    `RegexOption`은 한 번에 여러 가지를 적용할 수 있습니다.
    

@Component script RegexTextExtractor extends Component @ExecSpace("ServerOnly") method void OnBeginPlay() \-- [Commented: RegexOption bitwise OR not supported] local regex = Regex("line\w..", RegexOption.Singleline | RegexOption.RightToLeft | RegexOption.IgnoreCase)
    
    
        
        -- [Commented: Uses unsupported Regex] local matches = regex:Matches("lineFirst \n LineSecond \n LineLast")
        
        for i, match in pairs(matches) do
            log(match.Text)
        end
        
        -- (결과)
        -- LineLast
        -- LineSecond
        -- lineFirst
    end
    

end