■ 응용 4: 정확하게 이메일 주소를 입력했는지 검사
가장 기본적인 이메일 패턴은 "example@gmail.com"입니다. 이에 대한 정규 패턴은 "^[a-zA-Z]+@[a-zA-Z.]+$" 입니다. 메타 기호 ^과 $는 각각 문자열의 시작과 끝을 가리킵니다. 이메일은 영어 대소문자 1개 이상으로 시작해야 합니다("^[a-zA-Z]+"). 이어서 기호 @가 있어야 하며, 영어 대소문자와 마침표(.)로 이루어진 문자열이 1개 이상 나타나고 끝나야 합니다("[a-zA-Z.]+$"). 아래 예에서 변수 email(example@gmail.com)이 이 패턴과 일치하지만, email4와 email5는 일치하지 않습니다. email4( example@gmailcom )는 기호 @ 다음에 마침표(.)가 없기 때문입니다.
이제 기본 패턴을 확장해서 email2와 email3을 검증된 이메일로 판별한 것인지 결정해야 합니다. @ 앞 문자열에 숫자([0-9])와 4개의 기호([ _ . + - ] )를 모두 허용하려면 아래 예처럼 수정해야 합니다. 아래 정규 표현은 @ 뒤 문자열에 숫자와 2개 기호([-.])를 허용합니다.
fun main() {
val emailPattern = "^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$".toRegex()
val email = "example@gmail.com"
val email2 = "_example@aqua2345.co.kr"
val email3 = "123.45.6@kor-comp.co.kr"
val email4 = "example@gmailcom"
val email5 = "example*gmail.com"
println(emailPattern.matches(email)) // true
println(emailPattern.matches(email2)) // true
println(emailPattern.matches(email3)) // true
println(emailPattern.matches(email4)) // false
println(emailPattern.matches(email5)) // false
}
■ 응용 5: 경로가 포함된 파일에서 파일 이름 추출
정규 표현(2)에서 다룬 응용 2의 코드를 잘 알고 있어야 합니다. 윈도우 탐색기의 파일 경로는 이중 역슬래스(\\)를 사용해 경로로를 구분합니다. 아래 정규 표현에서 이중 역슬래시(\\)를 기준으로 왼쪽과 오른쪽으로 2개 그룹(파일 경로와 파일 이름)으로 패턴을 정의합니다. 각 그룹에 대해 그룹 이름(path, fileName)을 지정했습니다. 메타 기호 '.'(dot)은 줄바꿈 문자(newline)를 제외한 모든 문자를 나타냅니다. ".+"는 길이가 1이상인 문자열입니다. 파일 이름(regex.hwp)에 대한 패턴 ".+\..+"에서 역슬래시(\)가 붙은 2번째 점(\.)은 메타 기호가 아니라 파일 확장자를 표시하기 위한 점(.)입니다.
fun extract(path: String) {
val pattern = """(?<path>C:\\.+)\\(?<fileName>.+\..+)""".toRegex()
val matchResult = pattern.find(path)
matchResult?.groups?.let {
println("path: ${it["path"]?.value}")
println("file name: ${it["fileName"]?.value}")
}
}
fun main() {
extract("C:\\Users\\Hong\\Desktop\\Work\\regex.hwp")
// path: C:\Users\Hong\Desktop\Work
// file name: regex.hwp
}
위 코드를 파일 확장자를 추출하는 코드로 수정해 볼까요? 코드가 복잡해지는 것을 피하기 위해 그룹 이름은 생략했습니다. 1개 파일 이름 그룹 "(.+\..+)"을 점(\.)을 기준으로 2개의 그룹("(.+)"과 "(.+)")으로 구분합니다. matchResult 객체의 속성 destructed를 사용해 3개의 그룹을 추출할 수 있습니다.
fun extract(path: String) {
val pattern = """(C:\\.+)\\(.+)\.(.+)""".toRegex()
val matchResult = pattern.find(path)
matchResult?.let {
val (dir, name, ext) = it.destructured
println("path: $dir, file name: $name, extension: $ext")
}
}
fun main() {
extract("C:\\Users\\Hong\\Desktop\\Work\\regex.hwp")
// path: C:\Users\Hong\Desktop\Work, file name: regex, extension: hwp
}
위 코드는 정규 표현을 사용하지 않고 String 클래스에서 제공하는 문자열 처리 함수를 사용해 구현할 수도 있습니다. substringBeforeLast("\\")는 마지막 이중 역슬래시 이전까지 부분문자열을 추출합니다. substringAfterLast("\\")는 마지막 이중 역슬래시 이후부터 시작하는 부분문자열을 추출합니다.
substringBeforeLast("\\")는 정규 표현 "(C:\\.+)\\(.+\..+)"에서 이중 역슬래시(\\) 앞에 놓인 (C:\\.+)와 같습니다. substringAfterLast("\\")는 이중 역슬래시 뒤에 놓인 \(.+\..+)에 해당합니다.
fun extract(path: String) {
val dir = path.substringBeforeLast("\\")
val fileName = path.substringAfterLast("\\")
println("path: $dir, file name: $fileName")
}
fun main() {
extract("C:\\Users\\Hong\\Desktop\\Work\\regex.hwp")
// Dir: C:\Users\Hong\Desktop\Work, name: regex, ext: hwp
}
정규 표현으로 패턴을 표현하는 것이 부담스럽겠지만, 정규 표현은 유연하게 패턴을 정의할 수 있는 장점이 있습니다. , 응용 4처럼 이메일 유효성을 검증할 때는 정규 표현이 다양한 조건을 쉽게 표현할 수 있습니다. 그러나, 응용 5는 String 클래스에서 제공하는 문자열 처리 함수를 사용하는 게 유리합니다. 응용 목적에 따라 어느 방법을 선택할 지 고민이 필요하겠죠. 한 가지 더. 정규 표현(1)에서 강조한 것처럼 프로그래밍 언어의 문법을 이해하려면 정규 표현을 알아야 합니다.
■ 마무리 연습(Wrap-up exercise)
이메일 검사 예제에서 이메일(example@gmail.com)로부터 이메일 아이디(example)와 도메인 이름(gmail.com)을 추출해 보세요. <Hint> 응용 5의 코드를 참고하면 쉽게 해결할 수 있습니다.
'코틀린' 카테고리의 다른 글
코틀린: 정규 표현(2) (0) | 2025.01.31 |
---|---|
코틀린: 정규 표현(1) (0) | 2025.01.27 |
쉬어가는 글: 람다 식은 어떻게 유래되었나요? (0) | 2025.01.27 |
코틀린: DSL(2/2) - CSV 빌더를 만들어 봅시다. (0) | 2025.01.17 |
코틀린: DSL(1/2) - CSV 빌더를 만들어 봅시다. (0) | 2025.01.17 |