동일하게 보이는 문자열이 다르다고 판단되는 경우 Zero Width Space가 원인일 수도
'Zero Width Space(ZWSP)'란 단어 그대로 '폭이 없는 공백'을 뜻하는데요.
해당 포스팅에서는 zero width space로 인해 발생한 문제를 해결하는 과정에서 해당 개념과 더불어 whitespace characters에 대한 개념 및 정규식을 통한 whitespace character 제거 방법에 대해서도 정리하였습니다.
Zero Width Space 발견
시스템 로직을 통해 중복된 값이 들어가지 않도록 처리한 문자열 컬럼이 있었는데요.
관련된 데이터 쪽 문제가 있어 확인하던 중 해당 컬럼에 다음과 같이 중복된(사실은 중복이 아니라 동일한 것처럼 보이는) 값이 들어가 있는 것을 볼 수 있었습니다.
혹시나 해당 문자열 앞뒤로 공백이나 줄 바꿈이 들어가 있는가 확인해 봤지만 그 경우도 아니었는데요.
데이터를 이렇게 저렇게 살펴보던 중 해당 값을 복사해서 콘솔 창에서 확인해 보니 다음과 같이 문자열 마지막에 이상한? 특수문자가 들어가 있는 것을 확인할 수 있었습니다.
그리고 해당 데이터를 'ASCIISTR()' 함수를 사용해서 다시 확인해 본 결과 문자열 맨 뒤에 '\200B'라는 값이 붙어있는 것을 확인할 수 있었는데요.
* 'ASCIISTR()' 함수는 입력된 글자에 해당하는 유니코드 값을 16진수 형식으로 반환하는 함수입니다.
Unicode Explorer 사이트의 내용을 통해 Codepoint 'U+200B' (= '\200B')가 zero width space를 나타낸다는 것을 알게 되었습니다.
zero width space는 앞서 말한 것처럼 그 자체로는 아무것도 출력되지 않는 '폭이 없는 공백'을 뜻합니다.
줄 바꿈을 제어하는 데 주로 사용되거나, 중국어나 일본어와 같이 띄어쓰기가 없는 문자에서도 사용될 수 있다고 합니다.
다양한 공백문자 Whitespace Characters
ZWSP에 대해 알아보는 과정에서 일반적으로 사용되는 스페이스(Space / U+0020), 탭 문자(Tab / U+0009), 개행 문자(Newline / U+000A or U+000D), 비공백 문자(NBSP, U+00A0) 외에 유니코드 기준 다양한 공백 문자들이 있다는 것을 알게 되었는데요.
이러한 특수한 공백 문자가 들어오지 않도록 처리하는 것이 목적이기 때문에 각각의 whitespace character에 대한 설명은 생략하고 해당 문자를 제거하는 방법을 테스트해 보았습니다.
Whitespace Characters 제거 방법
먼저 'trim()' 메서드의 경우 'U+0020' 이하의 codepoint 공백만 제거하기 때문에 원하는 목적으로는 사용할 수 없습니다.
Java 11부터 도입된 'strip()' 메서드 역시 'Character.isWhitespace(int)'에 해당되는 공백을 제거하지만 ZWSP(U+200B) 등, 원하는 공백을 제거하지는 못한다는 것을 확인했습니다.
(+ StringUtils 클래스의 'trimAllWhitespace()' 메서드 역시 내부적으로 Character 클래스의 'isWhitespace()' 메서드를 통해 공백을 파악하기 때문에 원하는 기능에는 적합하지 않았습니다.)
@Test
public void whitespaceCharacterTest1() {
String SPACE = " ";
String NO_BREAK_SPACE = " "; //U+00A0
String EN_QUAD = " "; //U+2000
String EM_QUAD = " "; //U+2001
String EN_SPACE = " "; //U+2002
String EM_SPACE = " "; //U+2003
String THREE_PER_EM_SPACE = " "; //U+2004
String FORE_PER_EM_SPACE = " "; //U+2005
String SIX_PER_EM_SPACE = " "; //U+2006
String FIGURE_SPACE = " "; //U+2007
String PUNCTUATION_SPACE = " "; //U+2008
String THIN_SPACE = " "; //U+2009
String HAIR_SPACE = " "; //U+200A
String NARROW_NO_BREAK_SPACE = " "; //U+202F
String MEDIUM_MATHEMATICAL_SPACE = " "; //U+205F
String IDEOGRAPHIC_SPACE = " "; //U+3000
String MONGOLIAN_VOWEL_SEPARATOR = ""; //U+180E
String ZERO_WIDTH_SPACE = ""; //U+200B
String ZERO_WIDTH_NON_JOINER = ""; //U+200C
String ZERO_WIDTH_JOINER = ""; //U+200D
String WORD_JOINER = ""; //U+2060
Map<String ,String> whitespaceMap = new HashMap<>();
// ... Map whitespace put 부분 생략
String whitespaceCharacterRegex =
"[\\p{Zs}\\u00A0\\u2000-\\u200A\\u202F\\u205F\\u3000\\u180E\\u200B\\u200C\\u200D\\u2060]+";
for (String key: whitespaceMap.keySet()) {
String value = whitespaceMap.get(key);
String replaceValue = value.replaceAll(whitespaceCharacterRegex, "*");
log.info("Key is \"{}\" => Before Value:{} / After Value:{}", key, value, replaceValue);
}
}
결론적으로 다음과 같이 정규식을 통해 whitespace character를 제거하게 되었는데요.
비그래픽 문자를 찾을 때 사용하는 '\\P{Graph}' 정규식이나, 인쇄 불가능한 문자를 찾을 때 사용하는 '\\P{Print}' 정규식의 경우 한글 문자도 제거되는 문제가 있었기 때문에 정규식의 가독성 등을 고려하여 유니코드 공백 문자를 처리하는 '\\p{Zs}'를 기반으로 해당 정규식을 통해 제거할 수 없는 공백 문자를 직접 추가하는 방법을 사용하였습니다.
< 참고 자료 >
'IT Info' 카테고리의 다른 글
(network) SYN Flooding 공격이란? 대응 방법은? (2) | 2024.11.05 |
---|---|
eclipse 이클립스 자주 사용되는 단축키 정리 (2) | 2024.09.18 |
(티스토리) 오디세이 스킨, 글 제목 배경 제거하는 방법 (1) | 2024.03.05 |
아이폰 푸시 알림 안 오는 경우 해결 방법(위치 및 개인 정보 보호 재설정) (4) | 2023.12.23 |
GCP 암호화폐 채굴로 인한 리소스 중단 이슈 (your Google Cloud Project is engaging in cryptocurrency mining) (1) | 2023.07.02 |