안녕하세요. 졸린 개발자입니다.
정규표현식에 대해서는 개발을 하다보면 한 번씩 들어보셨을 거라 생각합니다.
그만큼, 정규표현식이 많은 곳에 쓰이는 것이겠죠.
오늘은 정규표현식 자체를 설명하는 것은 아니고,
java에서 사용하는 정규표현식 엔진에서 사용하는 Flag에 대해서 설명하려고 합니다.
따라서 본 글을 읽는 독자는 정규 표현식에 대해 어느정도 이해한다고
가정하고, java에서의 Flag에 대해 설명하려고 합니다.
정규표현식에서 Flag란?
정규표현식 엔진에서 어떻게 정규 표현식을 해석할지에 대한 힌트입니다.
간단한 예를 들어보죠.
[a-z] 가 있습니다.
위의 표현은 소문자 영어로 한개의 글자가 있는 문자열이 해당되겠죠?
이때 정규표현식 엔진에 case insensitive하게 해석을 해달라고 힌트를 주면 어떻게 될까요?
[a-z] == [A-Za-z]
가 될겁니다.
이렇게 다양하게 정규표현식을 어떻게 해석하면 좋을지에 대해 알려주는 것이 바로 Flag입니다.
이러한 Flag는 사용하는 엔진에 따라 지원하는 Flag들이 다르기 때문에,
Documentation을 잘 참고하시는 것이 중요합니다.
물론 공통적으로 지원하는 Flag들도 많기는 하지만요
Java에서 Flag를 사용하는 방법
public static void main(String[] args) {
Pattern pattern = Pattern.compile("[a-z]", Pattern.CANON_EQ);
Matcher matcher = pattern.matcher("a");
System.out.println(matcher.matches());
}
Pattern.compile에 두번째 인자로 Flag를 넣어주시면 됩니다.
그럼 두개의 Flag는 어떻게 넣을 수 있을까요?
public static void main(String[] args) {
Pattern pattern = Pattern.compile("[a-z]", Pattern.CANON_EQ | Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher("a");
System.out.println(matcher.matches());
}
이렇게 두개의 Flag에 |(Bar)를 넣으시면 됩니다
Flag자체가 int형으로 되어있고, bit 연산자로 조합할 수 있습니다.
Java에서 어떤 Flag가 정의되어있을까요?
위의 코드에서 보여드렸듯이, Pattern class에 상수로 정의되어 있습니다.
그럼 한번 볼까요?
공식문서에 따르면 이렇게 9개의 Flag가 있습니다.
위의 사진은 java 7의 공식문서이고, java 17공식문서에서도 동일하게 정의되어 있는 것을 확인하였습니다.
그럼 하나하나 살펴보죠
CANON_EQ
위의 문자를 풀어보면
canonical equivalence입니다.
설명은 나중에하고, 먼저 눈으로 보시죠.
é와 é는 서로 같은 문자처럼 보이죠?
(수정 : 위의 문자는 폰트에 따라 다르게 보일 수도 있습니다)
하지만, 서로 다른 코드값입니다.
뭔가 이상해보이죠?
그럼 직접 코드로 보시죠
public static void main(String[] args) {
Pattern pattern = Pattern.compile("\u00e9");
Matcher matcher = pattern.matcher("e\u0301");
System.out.println(matcher.matches());
System.out.println("\u00e9");
System.out.println("e\u0301");
}
보시면 알겠지만, 서로 다른 유니코드 값이기 때문에, 당연히 서로 다릅니다.
하지만, 눈으로 봤을 때는 같은 문자로 보이죠.
그럼 왜 이럴까요?
유니코드에서는 언어마다 특정한 범위가 지정됩니다.
만약, 똑같은 a라는 문자를 쓰더라도, 서로 다른 언어에서 이러한 문자를 쓴다면,
서로 다른 범위에서 코드값이 지정되기 때문에, 값이 달라지겠죠?
그렇기 때문에, 유니코드에서는 같은 문자처럼 보이지만, 서로 다른 코드값을 가진 문자들이 있습니다.
하지만, 눈으로 보이는 문자를 같은 문자로 보고 싶을 때도 있을지도 모르니,
이런 Flag가 있는 것입니다.
그리고 이러한 동등성을 처음에 설명한 canonical equivalence라고 합니다.
마지막으로, 코드를 보시죠
public static void main(String[] args) {
Pattern pattern = Pattern.compile("\u00e9", Pattern.CANON_EQ);
Matcher matcher = pattern.matcher("e\u0301");
System.out.println(matcher.matches());
System.out.println("\u00e9");
System.out.println("e\u0301");
}
CASE_INSENSITIVE
ASCII code상에서의 대소문자를 구별하지 않는 Flag입니다.
간단하니, 코드를 보시고 넘어가시죠
public static void main(String[] args) {
Pattern pattern = Pattern.compile("A", Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher("a");
System.out.println(matcher.matches());
}
COMMENTS
정규표현식에 comments, 즉 주석을 달 수 있습니다.
#로 달 수 있고, #뒤의 문자는 전부 무시됩니다.
코드를 보시죠
public static void main(String[] args) {
Pattern pattern = Pattern.compile("a#asddfasdf", Pattern.COMMENTS);
Matcher matcher = pattern.matcher("a");
System.out.println(matcher.matches());
}
DOTALL
정규표현식의 .(dot)을 모든 문자에 대응하도록 설정합니다.
원래 .(dot)은 line break, 즉, 라인을 다음 라인으로 넘어가는 문자를 제외한
모든 문자를 대응합니다.
그러나, .(dot)을 모든 문자에 대응하고 싶을 때도 있겠죠?
그럴때 쓰이는 플래그입니다.
코드를 보시죠
public static void main(String[] args) {
Pattern pattern = Pattern.compile("....");
Matcher matcher = pattern.matcher("a\nbc");
System.out.println(matcher.matches());
}
라인을 넘어가는 \n을 제외한 모든 문자가 .(dot)에 대응하기 때문에
false가 나오는 것을 보실 수 있습니다.
하지만 DOTALL을 설정하면?
public static void main(String[] args) {
Pattern pattern = Pattern.compile("....", Pattern.DOTALL);
Matcher matcher = pattern.matcher("a\nbc");
System.out.println(matcher.matches());
}
true가 나오는 것을 보실 수 있습니다.
LITERAL
literally, 문자 그대로 라는 뜻입니다.
말보다는 코드로 보시죠.
public static void main(String[] args) {
Pattern pattern = Pattern.compile("....", Pattern.LITERAL);
Matcher matcher = pattern.matcher("....");
System.out.println(matcher.matches());
}
public static void main(String[] args) {
Pattern pattern = Pattern.compile("....", Pattern.LITERAL);
Matcher matcher = pattern.matcher("abcd");
System.out.println(matcher.matches());
}
정규 표현식에서 .(dot)은 위에서도 설명했듯이 line break 문자를 제외한 모든 문자에 대응합니다.
하지만 LITERAL Flag를 사용하면, 문자 그대로를 반영합니다.
즉, .(dot)문자 자체가 같아야 true가 됩니다.
MULTILINE
정규 표현식을 여러 라인에 걸쳐서 적용합니다.
컴퓨터에서는, 여러 라인이라는 개념이 없습니다.
예를 들어보죠.
a
b
c
위의 문자열이 눈에는 서로 다른 라인으로 보이지만
실제로는 a\nb\nc
이런 식으로 같은 line에 저장되게 됩니다.
그래서 정규표현식을 적용할 때도 마찬가지인데요.
예를 들어보면
^[a-z]$
가 있을때
a
b
c
는 당연히 match되지 않을 것입니다.
왜냐하면, a\nb\nc이기 때문이죠.
하지만, MULTILINE을 적용하면 어떨까요?
한 line, 즉 \n전까지의 문자열마다 매칭시키기 떄문에
a
b
c
에서 a, b, c모두 match가 될 것입니다.
코드로 보시죠
public static void main(String[] args) {
Pattern pattern = Pattern.compile("^[a-z]$");
Matcher matcher = pattern.matcher("a\nb\nc");
while(matcher.find()) {
System.out.println(matcher.group());
}
System.out.println("end");
}
public static void main(String[] args) {
Pattern pattern = Pattern.compile("^[a-z]$", Pattern.MULTILINE);
Matcher matcher = pattern.matcher("a\nb\nc");
while(matcher.find()) {
System.out.println(matcher.group());
}
System.out.println("end");
}
결국, ^$이 전체 문자열이 아니라, 라인마다 생기는 것이라고 봐도 되겠네요.
UNICODE_CASE
CASE_INSENSITIVE와 함께 사용할 때, unicode상에서의 대소문자를 구별하지 않습니다.
CASE_INSENSITIVE만 사용하면, ascii code에서의 대소문자만 구별하게 됩니다.
하지만 이 Flag를 같이 사용하면, unicode의 대소문자도 같이 구별하게 됩니다.
다른 나라의 문자를 사용할 때 사용할 일이 있을 것 같은데,
우리나라에서는, 대부분 영어와 한글 2가지만을 사용하기 때문에
영어는 기본적으로 ASCII에서 대소문자를 구별하고,
한글은 대소문자가 없기 때문에
평소에 쓸일이 없어보입니다.
그렇기 때문에, 이 Flag는 코드를 생략하도록 하겠습니다.
UNICODE_CHARACTER_CLASS
미리 정의된 문자와, POSIX문자들이 unicode와 호환성을 가지게 됩니다
이것도 쓸일이 거의 없어보이네요. 코드는 생략하겠습니다.
UNIX_LINES
말 그대로 unix의 line만 취급한다는 얘기입니다.
windows 같은 경우는 line break가 \n\r로 이루어져 있습니다.
하지만, unix의 line break는 \n만으로 이루어지기 때문에
쉽게 말해서 \r을 line바꾸기가 아니라, 그냥 문자열로 인식한다는 것과 같습니다
이것도 특별한 경우가 아니면, 굳이 사용할 필요가 없어보이네요.
그래서 코드는 생략하겠습니다.
결론
정규 표현식을 공부하다가 이런 flag가 있다는 것을 알고
한번 정리해 봤는데, 생각보다 많은 flag가 있군요.
하지만 실질적으로 사용하는 것은
CASE_INSENSITIVE, DOTALL, MULTILINE
정도밖에 없겠네요
번외
javascript에는 global이라는 flag가 있는데
왜 java에는 없을까요?
바로 자바의 Matcher class에서 기본적으로
matcher.find(), matcher.group()을 사용하기 때문입니다.
그래서 이 함수를 활용하면 global이라는 flag를
활성화 시킨것과 같기 때문에, 굳이 만들지 않은 것으로 보이네요.
출처
https://stackoverflow.com/questions/18332117/set-two-flags-in-java-regex-pattern
https://docs.oracle.com/javase/7/docs/api/java/util/regex/Pattern.html
https://dzone.com/articles/canonical-equivalence-in-unicode-pattern-matching
'Java' 카테고리의 다른 글
데이터를 식별하기 위한 Unique한 ID생성하기 (트위터 스노우플레이크의 java구현) (0) | 2022.10.12 |
---|---|
Java classpath란? (0) | 2022.05.18 |