오늘은 어디로 갈까...

한글(Hangul) 0x00 본문

낙서

한글(Hangul) 0x00

剛宇 2009. 2. 20. 12:17
문자하면 빼 놓을 수 없는게 한글이다.(한국에 사니...)
오늘은 한글에 대해서 알아보도록 하자.
지난번 시간에 자바는 유니코드 기반이라고 했으니, 유니코드 사이트(http://www.unicode.org/)에 가서 열심히 읽어보자.

유니코드 한국어 명세서(Unicode Korean specific)를 보면 네개의 영역이 있다.
 Hangul Jamo 한글 자모 영역
  0x1100-0x11F9  U1100.pdf
 Hangul Compatibility Jamo 한글 호환 자모 영역
 0x3130-0x318E  U3130.pdf
 Hangul Syllables 한글 영역
 0xAC00-0xD7A3  UAC00.pdf
 Halfwidth Jamo 반각 자모
 0xFF00-0xFFEF  UAC00.pdf

첫번째, 0x1100-0x11F9 까지의 한글 자모 영역은 한글에서 사용되는 자모들을 초성 자음/중성 모음/종성 자음으로 각각 나누어 한글자씩 대응시킨것으로 이 영역에 있는 글자들을 이용하면 한글 고어까지 표현이 가능하나, 다른 캐릭터셋(ChracterSet)으로 변환이 어렵다는 등의 문제를 가지고 있다.
두번째, 0x3130-0x318E 까지의 한글 호환 자모 영역은 초/중/종성을 구분하지 않고 그냥 사용되는 모든 자모들을 한데 묶여 놓은것이다. 이 영역은 0x3130-0x314E(현대자음 40개), 0x314F-0x3163(현대모음 21개), 0x3164(채움코드), 0x3165-0x318E(옛글자모로) 나눌 수 있다.
세번째, 0xAC00-0xD7A3 까지의 한글 영역은 현대 한글 자모로 표현 가능한 모든 한글 문자들(11172자=19*21*28))이 들어 있습니다.
네번째, 0xFF00-0xFFEF 까지의 반각 자모 영역이다.
여기서는 두번째와 세번째 영역을 사용해서 처리하도록한다.

자, 뭔소리인지 이해가 안가는가? 그럼 직접 출력해보자.
        // 한글 호환 자모 영역
        for (int i = 0x3130;i <= 0x318E; i++) {
            System.out.print((char)i);
        }
        System.out.println();
        // 한글 영역
        for (int i = 0xAC00;i <= 0xD7A3; i++) {
            System.out.print((char)i);
        }

이젠 느낌이 오는가? 그러면 달려보자~


1. Hangul & HangulTest 클래스 만들기
 - Hangul 클래스 만들기

 - HangulTest 클래스 만들기

2. 한글인지 판단하기.
 - 입력한 문자가 한글인지 판단하는 메소드를 만들어보자.
package kr.kangwoo.util.hangul;

public class Hangul {

	private static final char HANGUL_SYLLABLES_BEGIN = 0xAC00;
	private static final char HANGUL_SYLLABLES_END = 0xD7A3;
	
	private static final char HANGUL_COMPATIBILITY_JAMO_BEGIN = 0x3130;
	private static final char HANGUL_COMPATIBILITY_JAMO_END = 0x318E;

	/**
	 * <p>입력한 문자가 한글인지 판단한다.</p>
	 * <p>(코드값이 한글 영역(0xAC00-0xD7A3)인지 판단)</p>
	 * 
	 * @param c
	 * @return 한글이면 <code>true</code>, 아니면 <code>false</code>
	 */
	public static boolean isHangulSyllables(char ch) {
		return ch >= HANGUL_SYLLABLES_BEGIN && ch <= HANGUL_SYLLABLES_END;
	}
	
	/**
	 * <p>입력한 문자가 한글 호환 자모인지 판단한다.</p>
	 * <p>(코드값이 한글 호환 자모 영역(0x1100-0x318E)인지 판단)</p>
	 * 
	 * @param ch
	 * @return 한글 호환 자모이면 <code>true</code>, 아니면 <code>false</code>
	 */
	public static boolean isHangulCompatibilityJamo(char ch) {
		return ch >= HANGUL_COMPATIBILITY_JAMO_BEGIN && ch <= HANGUL_COMPATIBILITY_JAMO_END;
	}

}
 - 입력한 문자가 한글인지 판단하는 메소드가 정상 작동하는지 테스트해보자.
package kr.kangwoo.util.hangul;


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

public class HangulTest {

	@Before
	public void setUp() throws Exception {
	}

	@After
	public void tearDown() throws Exception {
	}

	@Test
	public void isHangulSyllables() {
		Assert.assertEquals(Hangul.isHangulSyllables('ㄱ'), false);
		Assert.assertEquals(Hangul.isHangulSyllables('ㅏ'), false);
		Assert.assertEquals(Hangul.isHangulSyllables('A'), false);
		Assert.assertEquals(Hangul.isHangulSyllables('a'), false);
		Assert.assertEquals(Hangul.isHangulSyllables('Z'), false);
		Assert.assertEquals(Hangul.isHangulSyllables('z'), false);
		Assert.assertEquals(Hangul.isHangulSyllables('1'), false);
		Assert.assertEquals(Hangul.isHangulSyllables('\"'), false);
		Assert.assertEquals(Hangul.isHangulSyllables('가'), true);
	}
	
	@Test
	public void isHangulCompatibilityJamo() {
		Assert.assertEquals(Hangul.isHangulCompatibilityJamo('ㄱ'), true);
		Assert.assertEquals(Hangul.isHangulCompatibilityJamo('ㅀ'), true);
		Assert.assertEquals(Hangul.isHangulCompatibilityJamo('ㅎ'), true);
		Assert.assertEquals(Hangul.isHangulCompatibilityJamo('ㅏ'), true);
		Assert.assertEquals(Hangul.isHangulCompatibilityJamo('ㅣ'), true);
		Assert.assertEquals(Hangul.isHangulCompatibilityJamo('ㆌ'), true);
		Assert.assertEquals(Hangul.isHangulCompatibilityJamo('A'), false);
		Assert.assertEquals(Hangul.isHangulCompatibilityJamo('a'), false);
		Assert.assertEquals(Hangul.isHangulCompatibilityJamo('Z'), false);
		Assert.assertEquals(Hangul.isHangulCompatibilityJamo('z'), false);
		Assert.assertEquals(Hangul.isHangulCompatibilityJamo('1'), false);
		Assert.assertEquals(Hangul.isHangulCompatibilityJamo('\"'), false);
		Assert.assertEquals(Hangul.isHangulCompatibilityJamo('가'), false);
	}
}

2. 한글에서 초성/중성/종성을 추출하기.
 - 한글 자모 영역(0x3130-0x318E)과 한글 영역(0xAC00-0xD7A3) 간단한 계산식으로 쉽게 변환이 가능하다.
 - 한글(한문자) = 0xAC00 + (초성_Index * 21 * 28) + (중성_Index * 28) + 종성_Index
 - 초성_Index = (한글 - 0xAC00) / (21 * 28)
 - 중성_Index = ((한글 - 0xAC00) % (21 * 28)) / 28
 - 종성_Index = ((한글 - 0xAC00) % (21 * 28)) % 28
 - 구현해보도록 하자
	/**
	 * 'ㄱ', 'ㄲ', 'ㄴ', 'ㄷ', 'ㄸ', 'ㄹ', 'ㅁ', 'ㅂ', 'ㅃ', 'ㅅ', 'ㅆ', 'ㅇ', 'ㅈ', 'ㅉ', 'ㅊ', 'ㅋ', 'ㅌ', 'ㅍ', 'ㅎ'
	 */
	private static final char[] HANGUL_CHOSEONG = {0x3131, 0x3132, 0x3134, 0x3137, 0x3138, 0x3139, 0x3141, 0x3142, 0x3143, 0x3145, 0x3146, 0x3147, 0x3148, 0x3149, 0x314a, 0x314b, 0x314c, 0x314d, 0x314e};
	/**
	 * 'ㅏ', 'ㅐ', 'ㅑ', 'ㅒ', 'ㅓ', 'ㅔ', 'ㅕ', 'ㅖ', 'ㅗ', 'ㅘ', 'ㅙ', 'ㅚ', 'ㅛ', 'ㅜ', 'ㅝ', 'ㅞ', 'ㅟ', 'ㅠ', 'ㅡ', 'ㅢ', 'ㅣ'
	 */
	private static final char[] HANGUL_JUNGSEONG = {0x314f, 0x3150, 0x3151, 0x3152, 0x3153, 0x3154, 0x3155, 0x3156, 0x3157, 0x3158, 0x3159, 0x315a, 0x315b, 0x315c, 0x315d, 0x315e, 0x315f, 0x3160, 0x3161, 0x3162, 0x3163};
	/**
	 * ' ', 'ㄱ', 'ㄲ', 'ㄳ', 'ㄴ', 'ㄵ', 'ㄶ', 'ㄷ', 'ㄹ', 'ㄺ', 'ㄻ', 'ㄼ', 'ㄽ', 'ㄾ', 'ㄿ', 'ㅀ', 'ㅁ', 'ㅂ', 'ㅄ', 'ㅅ', 'ㅆ', 'ㅇ', 'ㅈ', 'ㅊ', 'ㅋ', 'ㅌ', 'ㅍ', 'ㅎ'
	 */
	private static final char[] HANGUL_JONGSEONG = {0x0000, 0x3131, 0x3132, 0x3133, 0x3134, 0x3135, 0x3136, 0x3137, 0x3139, 0x313a, 0x313b, 0x313c, 0x313d, 0x313e, 0x313f, 0x3140, 0x3141, 0x3142, 0x3144, 0x3145, 0x3146, 0x3147, 0x3148, 0x314a, 0x314b, 0x314c, 0x314d, 0x314e};

	public static final int HANGUL_CHOSEONG_SIZE = HANGUL_CHOSEONG.length;	// 19
	public static final int HANGUL_JUNGSEONG_SIZE = HANGUL_JUNGSEONG.length;	// 21
	public static final int HANGUL_JONGSEONG_SIZE = HANGUL_JONGSEONG.length;	// 28
	
	private static final Map<Character, Integer> HANGUL_CHOSEONG_CODE_TABLE = new HashMap<Character, Integer>();
	private static final Map<Character, Integer> HANGUL_JUNGSEONG_CODE_TABLE = new HashMap<Character, Integer>();
	private static final Map<Character, Integer> HANGUL_JONGSEONG_CODE_TABLE = new HashMap<Character, Integer>();
	
	static {
		for (int i = 0; i < HANGUL_CHOSEONG.length; i++) {
			HANGUL_CHOSEONG_CODE_TABLE.put(HANGUL_CHOSEONG[i], i);
		}
		for (int i = 0; i < HANGUL_JUNGSEONG.length; i++) {
			HANGUL_JUNGSEONG_CODE_TABLE.put(HANGUL_JUNGSEONG[i], i);
		}
		for (int i = 0; i < HANGUL_JONGSEONG.length; i++) {
			HANGUL_JONGSEONG_CODE_TABLE.put(HANGUL_JONGSEONG[i], i);
		}
	}
	
	/**
	 * <p>초성을 추출한다.</p>
	 * 
	 * @param ch
	 * @return
	 */
	public static char getChoseong(char ch) {
		if (isHangulSyllables(ch) == false) {
			throw new IllegalArgumentException("입력값이 잘못되었습니다. (" + ch + ")");
		}
		int hCode = (ch - HANGUL_SYLLABLES_BEGIN) / (HANGUL_JUNGSEONG_SIZE * HANGUL_JONGSEONG_SIZE);
		return HANGUL_CHOSEONG[hCode];
	}
	
	/**
	 * <p>중성을 추출한다.</p>
	 * 
	 * @param ch
	 * @return
	 */
	public static char getJungseong(char ch) {
		if (isHangulSyllables(ch) == false) {
			throw new IllegalArgumentException("입력값이 잘못되었습니다. (" + ch + ")");
		}
		int hCode = ((ch - HANGUL_SYLLABLES_BEGIN) % (HANGUL_JUNGSEONG_SIZE * HANGUL_JONGSEONG_SIZE)) / HANGUL_JONGSEONG_SIZE;
		return HANGUL_JUNGSEONG[hCode];
	}
	
	/**
	 * <p>종성을 추출한다.</p>
	 * 
	 * @param ch
	 * @return
	 */
	public static char getJongseong(char ch) {
		if (isHangulSyllables(ch) == false) {
			throw new IllegalArgumentException("입력값이 잘못되었습니다. (" + ch + ")");
		}
		int hCode = ((ch - HANGUL_SYLLABLES_BEGIN) % (HANGUL_JUNGSEONG_SIZE * HANGUL_JONGSEONG_SIZE)) % HANGUL_JONGSEONG_SIZE;
		return HANGUL_JONGSEONG[hCode];
	}
	
	/**
	 * <p>초/중/종성을 추출한다.
	 * </p>
	 * @param ch
	 * @return [초성, 중성, 종성] or [초성, 중성]
	 */
	public static char[] getJamo(char ch) {
		char[] jamo = new char[3];
		jamo[0] = getChoseong(ch);
		jamo[1] = getJungseong(ch);
		jamo[2] = getJongseong(ch);
		if (jamo[2] == HANGUL_JONGSEONG[0]) {
			char[] temp = new char[2];
			temp[0] = jamo[0];
			temp[1] = jamo[1];
			jamo = temp;
		}
		return jamo;
	}
	
	/**
	 * <p>종성이 존재하는지 여부를 판단한다.</p>
	 * 
	 * @param ch
	 * @return 종성이 존재하면 <code>true</code>, 아니면 <code>false</code>
	 */
	public static boolean hasJongseong(char ch) {
		return getJongseong(ch) != HANGUL_JONGSEONG[0];
	}
 - 테스트 해보자
	@Test
	public void getChoseong() {
		Assert.assertEquals(Hangul.getChoseong('한'), 'ㅎ');
		Assert.assertEquals(Hangul.getChoseong('지'), 'ㅈ');
		Assert.assertEquals(Hangul.getChoseong('나'), 'ㄴ');
		try {
			Assert.assertEquals(Hangul.getChoseong('a'), 'a');
			Assert.fail("Failed");
		} catch(IllegalArgumentException e) {
			Assert.assertTrue(true);
		}
	}
	
	@Test
	public void getJungseong() {
		Assert.assertEquals(Hangul.getJungseong('한'), 'ㅏ');
		Assert.assertEquals(Hangul.getJungseong('글'), 'ㅡ');
		try {
			Hangul.getJungseong('a');
			Assert.fail("한글이 아닐 경우 검증 오류");
		} catch(IllegalArgumentException e) {
			Assert.assertTrue(true);
		}
	}
	
	@Test
	public void getJongseong() {
		Assert.assertEquals(Hangul.getJongseong('한'), 'ㄴ');
		Assert.assertEquals(Hangul.getJongseong('글'), 'ㄹ');
		Assert.assertEquals(Hangul.getJongseong('사'), (char)0x0000);
		try {
			Hangul.getJongseong('a');
			Assert.fail("한글이 아닐 경우 검증 오류");
		} catch(IllegalArgumentException e) {
			Assert.assertTrue(true);
		}
		try {
			Hangul.getJongseong('ㄱ');
			Assert.fail("한글이 아닐 경우 검증 오류");
		} catch(IllegalArgumentException e) {
			Assert.assertTrue(true);
		}
		
	}
	
	@Test
	public void getJamo() {
		Assert.assertArrayEquals(Hangul.getJamo('한'), new char[] {'ㅎ', 'ㅏ', 'ㄴ'});
		Assert.assertArrayEquals(Hangul.getJamo('지'), new char[] {'ㅈ', 'ㅣ'});
		
		try {
			Hangul.getJamo('a');
			Assert.fail("Failed");
		} catch(IllegalArgumentException e) {
			Assert.assertTrue(true);
		}
	}
	
	@Test
	public void hasJongseong() {
		Assert.assertTrue(Hangul.hasJongseong('한'));
		Assert.assertFalse(Hangul.hasJongseong('지'));
	}

3. 초성/중성/초성을 결합하여 한글자 만들기.
 - 이번에 나눠진 자모들을 합쳐서 글자를 만들어보자.
	/**
	 * <p>초성, 중성을 겹합하여 한글자로 만든다.</p>
	 * 
	 * @param choseong 초성
	 * @param jungseong 중성
	 * @return
	 */
	public static char toHangul(char choseong, char jungseong) {
		return toHangul(choseong, jungseong, HANGUL_JONGSEONG[0]);
	}
	
	/**
	 * <p>초성, 중성, 종성을 결합하여 한글자로 만든다.</p>
	 * 
	 * @param choseong 초성
	 * @param jungseong 중성
	 * @param jongseong 종성
	 * @return
	 */
	public static char toHangul(char choseong, char jungseong, char jongseong) {
		Integer choseongIndex = HANGUL_CHOSEONG_CODE_TABLE.get(choseong);
		if (choseongIndex == null) {
			throw new IllegalArgumentException("초성이 잘못되었습니다. (" + choseong + ")");
		}
		Integer jungseongIndex = HANGUL_JUNGSEONG_CODE_TABLE.get(jungseong);
		if (jungseongIndex == null) {
			throw new IllegalArgumentException("중성이 잘못되었습니다. (" + jungseong + ")");
		}
		if (jongseong == ' ') {
			jongseong = HANGUL_JONGSEONG[0];
		}
		Integer jongseongIndex = HANGUL_JONGSEONG_CODE_TABLE.get(jongseong);
		if (jongseongIndex == null) {
			throw new IllegalArgumentException("종성이 잘못되었습니다. (" + jongseong + ")");
		}
		return (char)(HANGUL_SYLLABLES_BEGIN + (choseongIndex * (HANGUL_JUNGSEONG_SIZE * HANGUL_JONGSEONG_SIZE)) + (jungseongIndex * HANGUL_JONGSEONG_SIZE) + jongseongIndex);
	}

 - 잘 작동할까? 테스트해보자.
	@Test
	public void toHangul() {
		Assert.assertEquals(Hangul.toHangul('ㅈ', 'ㅣ'), '지');
		Assert.assertEquals(Hangul.toHangul('ㄴ', 'ㅏ'), '나');
	}
	
	@Test
	public void toHangul2() {
		Assert.assertEquals(Hangul.toHangul('ㅎ', 'ㅏ', 'ㄴ'), '한');
		Assert.assertEquals(Hangul.toHangul('ㄱ', 'ㅡ', 'ㄹ'), '글');
	}

4. 한글 조사 처리
 - 위에서 만든 메소드를 가지고 조사(은/는, 이/가, 와/과 등...) 처리를 해보는것을 만들어보자.
	/**
	 * <p>글자에 알맞는 조사를 선택한다.</p>
	 * 
	 * <pre>
	 * Hangul.getJosa('한', '은', '는') = '은'
	 * Hangul.getJosa('지', '이', '가') = '가'
	 * </pre>
	 * 
	 * @param ch
	 * @param aux1 종성이 있을때 붙일 조사
	 * @param aux2 종성이 없을때 붙일 조사
	 * @return
	 */
	public static char getJosa(char ch, char aux1, char aux2) {
		return hasJongseong(ch) ? aux1 : aux2;
	}

	/**
	 * <p>글자에 알맞는 조사를 선택한다.</p>
	 * 
	 * <pre>
	 * Hangul.getJosa('한', "은", "는") = "은"
	 * Hangul.getJosa('지', "이", "가") = "가"
	 * </pre>
	 * 
	 * @param ch
	 * @param aux1 종성이 있을때 붙일 조사
	 * @param aux2 종성이 없을때 붙일 조사
	 * @return
	 */
	public static String getJosa(char ch, String aux1, String aux2) {
		return hasJongseong(ch) ? aux1 : aux2;
	}

 - 이것도 테스트해보도록 하자. "마 멋진 나라이다. 수 여행을 간다."
	@Test
	public void getJosa() {
		Assert.assertEquals(Hangul.getJosa('한', '은', '는'), '은');
		Assert.assertEquals(Hangul.getJosa('지', '이', '가'), '가');
	}


	@Test
	public void getJosa2() {
		Assert.assertEquals(Hangul.getJosa('한', "은", "는"), "은");
		Assert.assertEquals(Hangul.getJosa('지', "이", "가"), "가");
	}