정규식이라고도 불리는 정규표현식은 특정한 규칙의 문자열의 집합을 표현하는 식이라고 할 수 있다. 불특정 문자열이 특정 조건에 만족하는지 판별할 때 주로 사용이 된다.
정규표현식은 크게 POSIX 과 PCRE 두 방식이 있으며 POSIX이 표준이고 PCRE는 Perl방식으로 확장된 기능이 많다.
여러 프로그래밍 언어가 이 정규식을 사용할 수 있도록 지원하고 있고 언어마다 조금씩 다르지만 큰 틀은 벗어나지 않는다.
정규식 검사 사이트 https://regexr.com/
정규표현식에서 사용하는 연산 기호를 메타 문자라고 한다. 이 메타문자들과 숫자, 단어의 조합으로 하나의 문자열 패턴을 완성시킬 수 있다.
\ ^ $ . | [] ( ) * + ? { }
등이 있으며, 메타 문자로 된 자원을 식 내에서 찾아야 하는 경우에는 다른 언어와 마찬가지로 앞에 역슬래시 \를 붙여 이스케이프 해주면 된다. 위 글 에서도 역슬래시()를 사용하였다.
^ : 문자열의 시작을 의미.
$ : 문자열의 끝을 의미.
. : 문자 한 개를 의미. '.'이 위치한 곳에 어떤 문자든지 1개의 문자가 들어감.
[ ] : 대괄호에 있는 문자 중 한 개를 의미. [abc]는 a, b, c 중 하나를 선택.
[^] : not의 의미로, 대괄호에서 쓴다면 [^abc] : a, b, c 제외하고 나머지를 의미.
| : or을 의미. a|b : a 또는 b.
() : 공통되는 부분을 묶을 때, 서브 패턴을 지정할 때 사용. abc|abd -> ab(c|d)로 바꿀 수 있음.
? : 문자가 0회 또는 1회 등장. a? b는 a가 나올 수도, 없을 수도 있음. ab, b.
: 문자가 0회 이상 등장. ab : b, ab, aaab, aaab..
+ : 문자가 1회 이상 등장. a+b : ab, aab, aaab..
{n} : 문자가 n개 나옴. a {2} b : aab
{n,} : 문자가 n개 이상 나옴. a {2,} b : aab, aaab, aaaab..
{n, m} : 문자가 n개 이상 m개 이하로 나옴. a {1,3 } b : ab, aab, aaab
\s : 공백 제거
\t : 탭
\d : 숫자, [0-9]와 동일
\b : 단어의 경계, 문자 사이의 공백
\w : 알파벳이나 숫자, [a-zA-Z0-9_]와 동일
위의 \s, \t, \d, \b, \w는 대문자로 바꾸면 반대 의미가 됩니다.
출처 https://yoon-dailylife.tistory.com/113
Regex 클래스는 정규식 문자열과 함께 객체 생성이 가능하다.
//2~20 한글 알파벳 소문자 대문자 띄어쓰기 가능
val regex = Regex("^[\\s가-힣a-zA-Z]{2,20}$")
String Extension Function 을 이용할 수 있다.
"문자열".toRegex()
// 0부터 9까지
val regex = "[0-9]".toRegex()
// 숫자 4개
val regex = """^\d{4}$""".toRegex()
Regex 클래스에 주어진 CharSequence를 가 정규식을 만족하는지 확인하는 함수가 존재한다.
matches(input: CharSequence): Boolean
그리고 코틀린에서 CharSequence를 인라인함수로 matches() 가 존재하고 이는 Regex의 matches 함수를 호출한다.
@kotlin.internal.InlineOnly
public inline infix fun CharSequence.matches(regex: Regex): Boolean = regex.matches(this)
이 matches()를 이용해 넘겨 받은 문자열이 특정 정규 표현식을 만족하는지 판단하는 함수를 만들 수 있다.
// 년도.월.일 정규식
fun verifyDateFormat(date: String) : Boolean{
return """^((?=(([13579][26])|([02468][048]))00\.02\.((0[1-9])|([1-2][0-9])))|(?=(\d\d00\.02\.((0[1-9])|([1-2][0-8]))))|(?=(\d\d(([13579][26])|([02468][048])))\.02\.((0[1-9])|([1-2][0-9])))|(?=\d\d\d\d\.02\.((0[1-9])|([1-2][0-8])))|(?=\d\d\d\d\.(0[469]|11)\.((0[1-9])|([1-2][0-9])|30))|(?=\d\d\d\d\.(0[13578]|1[02])\.((0[1-9])|([1-2][0-9])|3[01])))(\d{4}\.\d{2}\.\d{2})$""".toRegex()
.matches(date)
}
// 이메일
fun verifyEmail(email: String) : Boolean {
val regexEmail = """^([a-zA-Z0-9_\-\.]+)@([a-zA-Z0-9_\-\.]+)\.([a-zA-Z]{2,5})${'$'}""".toRegex()
return regexEmail.matches(email)
}
// 이름 (2~20 한글 알파벳 소문자 대문자 띄어쓰기 가능)
fun verifyName(newName: String) : Boolean = newName.matches(Regex("^[\\s가-힣a-zA-Z]{2,20}$"))
이러한 정규식 함수들을 이용해서 EditTextView 컴포넌트의 입력값이 특정 조건이 만족하는지 확인할 수 있다.
💡 정규식 함수들을 따로 object로 빼서 관리하면 좀 더 편리하게 사용할 수 있다!!
XML에서 양방향 데이터바인딩을 통해 EditTextView의 값을 LiveData 변수에 바인딩한 후 Observing 하면 정규식이 만족하는지도 판단할 수 있다!!
//XML
android:text="@={viewModel.name}"
// ViewModel
val name by lazy { MutableLiveData<String>("") }
// Activity
viewModel.name.observe(this) { Regex.verifyName(name.value!!) }
// Regex Object
fun verifyName(newName: String) : Boolean = newName.matches(Regex("^[\\s가-힣a-zA-Z]{2,20}$"))