2014년 6월 21일 토요일

자바/안드로이드 초성 검색 2.0

지난번 자바/안드로이드 초성 검색에서

가비지 컬렉션을 최소화함과 동시에 단순 불리언 매칭 대신 매칭된 문자열의 실제값과 위치까지 제공하는 새 API는 다음편에서 다루도록 하겠다.

고 했었는데, 다음편이 생각보다 너무 늘어져 버렸다. ^^ 일단 요즘 대세에 발맞춰 GitHub에 저장소를 만들었다. 소스는 필요없고 JAR 파일만 라이브러리로 쓸 분들은 여기를 눌러 받으면 된다. 아래는 안드로이드에서 초성 검색후 찾은 검색어만 하일라이트해주는 예:

KoreanTextMatcher

KoreanTextMatcher는 자바/안드로이드에서 한글 초성 매칭 검색을 가능하게 해주는 라이브러리다. 주요 기능과 특징은 다음과 같다:

  • 오픈 소스: 수정, 배포, 2차 저작 등 어떤 프로그램에도 자유롭게 사용 가능한 BSD 라이선스. 상용 프로그램에도 물론 아무 제약없이 사용할 수 있다(저작권 문구는 절대 지우지 마세요!).
  • 사용하기 쉬운 API
  • 100개 이상의 유닛 테스트 케이스, 99% 이상의 코드 커버리지 등을 통한 품질 검증
  • 가비지 컬렉션을 최소화하도록 세심하게 작성된 코드
  • 완전 쓰레드 세이프: 모든 타입이 이뮤터블
  • Unicode Hangul Jamo와 Unicode Hangul Compatibility Jamo 모두 지원
  • 단순 문자열 검색과 초성 매칭 검색 모두 지원
  • 한 문자열 내에서 여러 개의 패턴 검색
  • 정규식 앵커 ^$를 이용하여 패턴의 위치를 검색 대상 문자열의 시작과 끝으로 한정할 수 있음

사용법

KoreanTextMatcher는 KoreanChar, KoreanTextMatcher, KoreanTextMatch 세 클래스로 이루어져 있다.

KoreanChar

KoreanChar는 한글 문자에서 초성을 추출하는 데 쓰는 클래스다. isSyllable()은 입력 문자가 한글 음절인지, isCompatibilityChoseong()/isChoseong()은 입력 문자가 초성인지를 알려준다. getCompatibilityChoseong()/getChoseong()은 한글 음절에서 초성을 추출한다. 사용예는 다음과 같다:

char c = '한';
boolean isHangulChar = KoreanChar.isSyllable(c);
char compatChoseong = KoreanChar.getCompatibilityChoseong(c);
char choseong = KoreanChar.getChoseong(c);
boolean isCompatChoseong = KoreanChar.isCompatibilityChoseong(compatChoseong);
boolean isChoseong = KoreanChar.isChoseong(choseong);

getCompatibilityChoseong()/getChoseong()은 입력이 한글 음절이 아니면 '\0'을 리턴한다.

자음/모음으로만 구분되어 있는 Hangul Compatibility Jamo와 별개로 초성/중성/종성으로 구분되는 Hangul Jamo가 있지만 최소한 Windows와 Android에서는 후자를 쓰는 경우가 거의 없는 것 같다. 따라서 대부분은 xxxCompatibilityChoseong() API를 쓰면 될 것이다.

KoreanTextMatcher/KoreanTextMatch

한글 초성 매칭 검색을 가능케 해주는 클래스다. KoreanTextMatcher에 매칭 API가 들어 있고 매칭 결과가 KoreanTextMatch 인스턴스로 리턴된다. 기본적인 용법은 다음과 같다:

KoreanTextMatcher matcher = new KoreanTextMatcher(pattern);
KoreanTextMatch match = matcher.match(text);
if (match.success()) {
    System.out.format("%s: %s[%d]에서 시작, 길이 %d\n",
        match.value(), text, match.index(), match.length());
}

1회성으로 쓸 때에는 static 버전의 match()를 쓰는 쪽이 간편하다:

KoreanTextMatch match = KoreanTextMatcher.match(text, pattern);
if (match.success()) {
    System.out.format("%s: %s[%d]에서 시작, 길이 %d\n",
        match.value(), text, match.index(), match.length());
}

패턴이 문자열의 시작 또는 끝에 위치하는 경우만 매칭하려면 정규식에서 널리 쓰이는 ^$를 각각 쓰면 된다. 단순히 매칭 성공 여부만을 알려주는 isMatch()와 함께 쓰면 다음과 같다:

KoreanTextMatcher.isMatch("하늘", "^ㅎㄴ");  // true
KoreanTextMatcher.isMatch(" 하늘", "^ㅎㄴ"); // false

^$는 각각 String.startsWith()String.endsWith()를 대체하는 목적으로 쓸 수 있다. 둘을 한꺼번에 쓰면 String.equals()를 쓴 것과 동일한 효과를 얻을 수 있다.

KoreanTextMatcher.matches()는 문자열 내에서 매칭되는 패턴을 모두 찾아 List<KoreanTextMatch>로 돌려준다. 검색결과에서 검색어만 하일라이트할 필요가 있을 때 쓰면 편리하다. 아래는 맨 위에 소개한 안드로이드 데모 스크린샷의 실제 코드다:

String text = "바닥에 남은 차가운 껍질에 뜨거운 눈물을 부어";

TextView tv = new TextView(getBaseContext());
tv.setTextSize(25F);
tv.setText(text, TextView.BufferType.SPANNABLE);

Spannable spannedText = (Spannable) tv.getText();

KoreanTextMatcher matcher = new KoreanTextMatcher("ㄱㅇ");
for (KoreanTextMatch match : matcher.matches(text)) {
    spannedText.setSpan(new ForegroundColorSpan(Color.RED),
        match.index(), match.index() + match.length(),
        Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}

matches() 역시 match()처럼 인스턴스 버전과 static 버전이 있으므로 필요에 따라 골라 쓰면 된다. 대개는 static 쪽이 쓰기 편하다.

다음편에서는…

이 API들도 지난번 버전과 마찬가지로 C#을 자바로 변환하여 만든 것이다. API 디자인과 작성 및 테스트, C#을 자바로 변환하는 과정 등에 관한 이야기는 다음편에 계속.

댓글 없음:

댓글 쓰기

댓글을 입력하세요. 링크를 걸려면 <a href="">..</a> 태그를 쓰면 됩니다. <b>와 <i> 태그도 사용 가능합니다.

게시한지 14일이 지난 글에는 댓글이 등록되지 않습니다. 날짜를 반드시 확인해 주세요.