오늘은 어디로 갈까...

한자(Hanja) 0x00 본문

낙서

한자(Hanja) 0x00

剛宇 2009. 2. 22. 10:42
한글 처리도 해봤으니, 이젠 한자 처리를 해보도록 하자.
기능은 아주 단순하다. 한자를 입력받으면 한글로 출력해주는것이다.
동형이음(同形異音) 처리등, 제대로 할려면 아주 복잡해지니,
단순히 한자-한글을 1:1 대응시켜서 변환시켜주는 아주 단순 무식한 클래스를 만들어보자.
여기서 사용하는 한자-한글 맵핑표는 김진숙님이 만든것은 무단 도용(?)한 것임을 밝혀둔다.
일단, 유니코드 사이트에 가서 한자에 대해 알아보자

Han Ideographs
 Unified CJK Ideographs  4E00-9FCF  U4E00.pdf
 CJK Ideographs Ext. A  3400-4DBF  U3400.pdf
 CJK Ideographs Ext. B  20000-2A6DF  U20000.pdf
 CJK Compatibility Ideographs  F900-FAFF  UF900.pdf

Radicals and Strokes
 CJK Radicals(한자 부수)  2E80-2EFF  U2E80.pdf
 KangXi Radicals(강희자전 부수)  2F00-2FDF  U2F00.pdf


이처럼 한자에 관한 많은 코드가 있는데, 여기서는 한자 영역(CJK Ideographs)과 확장 한자 영역 A(CJK Ideographs Ext. A)과 호환 한자 영역(CJK Compatibility Ideographs) 일부를 변환 해주고 확장 B와 부수쪽은 처리를 하지 않겠다.

처음에 언급한것처럼 단순히 맵핑해서 글자를 변환시킨다.
즉, 0x4E00 한자어는 0xC77C 한글이 되는것이다.
일단, 한자-한글 맵핑표를 작성하보자.

1. 변환 클래스 만들기
자, 이제 맵핑 파일을 읽어와서 메모리에 적재하는 코드를 작성해보자.
(이렇게 파일을 따로 분리하지 않고, 단순히 변수로 선언해서 사용하는게 깔끔하다. 하지만 불행히도 코드 갯수가 너무 많아서 자바 스펙이 정의한 최대길이를 넘어서게된다. ~is exceeding the 65535 bytes란 에러를 뱉어내게 될것이다.)

package kr.kangwoo.util.hangul;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;

import kr.kangwoo.util.StringUtils;

public class Hanja {

	private static final Hanja instance = new Hanja();
	
	/**
	 * Unified CJK Ideographs : 4E00-9FCF
	 */
	private Map<Character, Character> cjkHangulMap = new HashMap<Character, Character>();

	/**
	 * CJK Ideographs Ext. A : 3400-4DBF
	 */
	private Map<Character, Character> cjkExtAHangulMap = new HashMap<Character, Character>();

	/**
	 * CJK Compatibility Ideographs : F900-FAFF
	 */
	private Map<Character, Character> cjkCompHangulMap = new HashMap<Character, Character>();
	
	private IllegalStateException exception = null;
	
	/**
	 * <p>생성자</p>
	 */
	private Hanja() {
		try {
			loadMap("CJK_Ideographs.txt", cjkHangulMap, (char)0x4E00);
			loadMap("CJK_Ideographs_Ext_A.txt", cjkExtAHangulMap, (char)0x3400);
			loadMap("CJK_Ideographs_Compatibility.txt", cjkCompHangulMap, (char)0xF900);
		} catch (IOException e) {
			exception = new IllegalStateException("한자-한글 맵핑 정보를 가져오는 중 에러가 발생했습니다.", e);
		}
	}
	
	/**
	 * <p>인스턴스를 가져온다.</p>
	 * 
	 * @return
	 */
	public static Hanja getInstance() {
		return instance;
	}
	
	/**
	 * <p>한자-한글 맵핑 정보를 읽어와 Map에 담는다.</p>
	 * 
	 * @param filename 파일명
	 * @param map
	 * @param beginChar
	 * @throws IOException
	 */
	private void loadMap(String filename, Map<Character, Character> map, char beginChar) throws IOException {
		BufferedReader reader = null;
		try {
			reader = new BufferedReader(new InputStreamReader(Hanja.class.getResourceAsStream(filename)));
			int index = 0;
			String read = null;
			while ( (read = reader.readLine()) != null) {
				read = StringUtils.trim(read);
				if (read.startsWith("/")) {
					// 주석이므로 무시한다.
					continue;
				}
				String[] codeArray = StringUtils.split(read, ",");
				if (codeArray != null && codeArray.length > 0) {
					for (String code : codeArray) {
						if (StringUtils.isNotBlank(code)) {
							map.put( (char)(beginChar + (index++)), (char)(Integer.decode(code.trim()).intValue()));	
						}
					}
				}
			}
		} finally {
			if (reader != null) try { reader.close(); } catch(IOException e) {}
		}
	}
		
	/**
	 * <p>한자를 한글로 변환한다.</p>
	 * 
	 * @param ch 한자
	 * @return
	 * @throws JaruException
	 */
	public char toHangul(char ch) throws IllegalStateException {
		if (exception != null) {
			throw exception;
		}
		if (ch >= 0x4E00 && ch <= 0x9FCF) {
			return cjkHangulMap.get(ch);
		} else if (ch >= 0x3400 && ch <= 0x4DBF) { 
			return cjkExtAHangulMap.get(ch);
		} else if (ch >= 0xF900 && ch <= 0xFAFF) { 
			return cjkCompHangulMap.get(ch);
		} else {
			return ch;
		}
	}
	
	/**
	 * <p>한자를 한글로 변환한다.</p>
	 * 
	 * @param str 한자
	 * @return
	 * @throws IllegalStateException
	 */
	public String toHangul(String str) throws IllegalStateException {
		if (str == null) {
			return null;
		}
		char[] hangulCharArray = new char[str.length()];
		for (int i = 0; i < hangulCharArray.length; i++) {
			hangulCharArray[i] = toHangul(str.charAt(i));
		}
		return new String(hangulCharArray);
	}
}

2. 테스트 케이스(김진숙님꺼 무단 도용)
package kr.kangwoo.util.hangul;

import junit.framework.Assert;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

public class HanjaTest {

	@Before
	public void setUp() throws Exception {
	}

	@After
	public void tearDown() throws Exception {
	}

	@Test
	public void testToHangulString() {
		Hanja hanja = Hanja.getInstance();
		Assert.assertEquals(hanja.toHangul("漢字 한글 變換 프로그램 試驗을 위한 文書입니다."), "한자 한글 변환 프로그램 시험을 위한 문서입니다.");
		Assert.assertEquals(hanja.toHangul("樂樂樂 --> 이것은 \"악락요\"입니다."), "악락요 --> 이것은 \"악락요\"입니다.");
		Assert.assertEquals(hanja.toHangul("惡惡 --> \"악오\"입니다"), "악오 --> \"악오\"입니다");
		Assert.assertEquals(hanja.toHangul("天字一號, 東敞 煽威隊 天字組 조장(組長)의 직함을 가지고 있는 천자일호  위류향魏留香은 차라리 잘 됐다고 생각했다."), 
							"천자일호, 동창 선위대 천자조 조장(조장)의 직함을 가지고 있는 천자일호  위류향위유향은 차라리 잘 됐다고 생각했다.");
		Assert.assertEquals(hanja.toHangul("위유향(魏留香) 위류향(魏留香)"), "위유향(위유향) 위류향(위류향)");
		Assert.assertEquals(hanja.toHangul("글쓴이 김진숙(金鎭淑)"), "글쓴이 김진숙(김진숙)");
	}

}


* 참고 사이트
UTF8 기반 한자한글 변환 : http://www.kristalinfo.com/K-Lab/unicode/Hanja2Hangul-kr.html
한국역사정보통합시스템 자료실 : http://www.koreanhistory.or.kr/addservice/boardmanage/listFreeBoard.jsp?pBoardType=001