2010.09.03 15:02

[강좌A13] 안드로이드 실전 개발 - Hangul2English 최종소스 공개 (한글 자모 분리, Manifest)




차주쯤 강좌를 쓸려고 했는데, 오늘 일이 한가한 바람에 시간이 남아서 마지막 소스 부분을 마무리 할까 합니다.
오늘의 강좌는 소스코드의 마지막 부분인 한타 -> 영타 변환 소스 부분과 메니페스트 부분에 대해서 설명하겠습니다. 오늘 강좌가 끝나면 전체 소스코드가 다 오픈되는 군요.

전체 강좌 목차

[강좌A01] Moteodev Studio를 이용한 안드로이드 개발 환경 구축 가이드
[강좌A02] 안드로이드 개발 참고 서적 소개
[강좌A03] Android 실전 개발 - 아이디어 / 기획 / Wireframe
[강좌A04] 안드로이드 실전 개발 - 아이콘 제작
[강좌A05] 안드로이드 실전 개발 - 레이아웃 및 리소스 : Part1
[강좌A06] 안드로이드 실전 개발 - 레이아웃 및 리소스 : Part2
[강좌A07] 안드로이드 실전 개발 - 리소스 해킹
[강좌A08] 안드로이드 실전 개발 - SQLite

[강좌A09] 안드로이드  실전 개발 - 데이터베이스 : Part1
[강좌A10] 안드로이드  실전 개발 - 데이터베이스 : Part2
[강좌A11] 안드로이드  실전 개발 - 데이터베이서 : Part3 (Motodev database)
[강좌A12] 안드로이드  실전 개발 - Main UI 소스, ListAdapter
[강좌A13] 안드로이드  실전 개발 - Hangul2English 최종 소스 공개 (한글 자모 분리, Manifest)


먼저, 한타를 영타로 변환하는 부분입니다.
저는 앞선 강좌에서 onTextChanged 이벤트를 잡아서 한글을 영문으로 변환 하도록 구성했습니다. 한글을 영문으로 변환하기 위해서는 여러 방법이 있겠지만, 저는 한글을 자소(초성/중성/종성) 분리하여 각 글자에 해당되는 영문을 찾아서 변환해 주는 방식을 사용했습니다.

먼저, 한글 자소 분리에 대한 기본 이론을 살펴보도록 하겠습니다. 이 이론을 잘 이해하시면 초성검색 구현도 별 어려움 없이 처리할 수 있습니다. 자소 분리나 초성검색은 이미 많으신 분들이 웹사이트에 구현코드를 많이 올려 놓으셨기 때문에 별도로 언급하지 않을까 하다가, 정리하는 차원에서 함께 설명합니다.

한글 유니코드 자소(초성,중성,종성) 분리 원리.

www.unicode.org/charts 에 들어가시면 East Asian Scripts 하위의 Hangul syllables 를 클릭하시면 한글 완성형 유니코드가 나옵니다. 첫번째 “가”는 유니코드가 0xAC00 이군요. 한글 완성형 코드는 0xAC00 ~ 0xD7A3까지의 코드 범위에 존재합니다.

유니코드에서 한글은 초성 19개, 중성 21개, 종성 28개의 모든 경우의 수에 따라 조합순으로 코드가 배열되어 있습니다.

초성  "ㄱ", "ㄲ", "ㄴ", "ㄷ", "ㄸ", "ㄹ", "ㅁ", "ㅂ", "ㅃ", "ㅅ", "ㅆ", "ㅇ", "ㅈ", "ㅉ", "ㅊ", "ㅋ", "ㅌ", "ㅍ", "ㅎ"

중성  "ㅏ", "ㅐ", "ㅑ", "ㅒ", "ㅓ", "ㅔ", "ㅕ", "ㅖ", "ㅗ", "ㅘ", "ㅙ", "ㅚ", "ㅛ", "ㅜ", "ㅝ", "ㅞ", "ㅟ", "ㅠ", "ㅡ", "ㅢ", "ㅣ"

종성  "", "ㄱ", "ㄲ", "ㄳ", "ㄴ", "ㄵ", "ㄶ", "ㄷ", "ㄹ", "ㄺ", "ㄻ", "ㄼ", "ㄽ", "ㄾ", "ㄿ", "ㅀ", "ㅁ", "ㅂ", "ㅄ", "ㅅ", "ㅆ", "ㅇ", "ㅈ", "ㅊ", "ㅋ", "ㅌ", "ㅍ", "ㅎ"

한글 유니코드를 초성,중성,종성으로 분리해 보면 다음과 같은 산식이 나옵니다.

0xAC00 + ( 초성순서 * 21 * 28 ) + ( 중성순서 * 28 ) + 종성순서 = 한글유니코드값.

다르게 표현하면

0xAC00 + ( ( 초성순서 * 21) + 중성순서 ) * 28 + 종성순서 = 한글유니코드값.

유니코드 표를 보시면 한글 “가”는 0xAC00 입니다. 즉, (초성 0번째 + 중성 0번째 + 종성 0번째) 로 구성되어 있습니다. 각 순서는 0번 인덱스부터 시작합니다.

“난” 이란 글자를 한번 분해해 보도록 하겠습니다.
한글 “난”은 초성 두번째(2) + 중성 0번째 + 종성 4번째로 구성되어 다음과 같은 산식이 성립됩니다.

“난” = ( 2[초성순서] * 21 * 28 ) + ( 0[중성순서] * 28 ) + ( 4[종성순서] ) + 0xAC00
    = 1180(십진수) + 0xAC00
    = 0x049C  + AC00
    = 0xB09C

즉 “난”이란 글자의 유니코드는 0xAC00 부터 1180번째 글자라 볼 수 있습니다.

이 수식을 기본으로 한글 유니코드를 초성/중성/종성으로 분리하도록 하겠습니다.

종성
종성은 한글유니코드값에서 0xAC00을 뺀 값에서 28로 나눈 나머지 값이 종성 순서 값이 됩니다.

종성 = (한글유니코드 – 0xAC00 ) % 28

“난”의 종성 “ㄴ”은 0xB09C – 0xAC00 = 1180(십진수) % 28 = 4
중성 배열 중 4번째 값인 “ㄴ”을 잦을수 있습니다.

중성

중성은 한글유니코드값에서 0xAC00을 빼고 거기서 다시 종성값을 뺀 후 28로 나눈 몫을 21로 나눈 나머지가 중성이 됩니다. 말로 하니 복잡하군요.

중성 = ( ( 한글유니코드 – 0xAC00 – 종성 ) / 28 ) % 21

“난” 의 중성 “ㅏ”는
“ㅏ” = ( ( 0xB09C – 0xAC00 – 4 ) / 28 ) % 21
    = ( 1176(십진수) / 28 ) % 21
    = 42 % 21 = 0
즉 중성 0번째 글자인 “ㅏ”로 분해됩니다.

초성

초성도 마찬가지로 산식을 이용하여 구할 수 있습니다.
초성 = ( ( ( 한글유니코드 – 0xAC00 – 종성 ) / 28 ) – 중성 ) ) / 21

“ㄴ”  = ( ( (0xB09C – 0xAC00 – 4 ) / 28 ) – 0 ) ) / 21
     = ( 1176(십진수) / 28 ) / 21
     = 42 / 21
     = 2
즉 초성 2번째 글자인 “ㄴ” 값을 구할 수 있습니다.

각 한글 자모 각각의 유니코드 값은 www.unicode.org/charts 에서 Hangul Compatibility Jamo 부분을 클릭하시면 pdf로 나오는데, 0x3131 ~ 0x318E 값의 범위를 가지고 있습니다.



이제 소스코드를 보겠습니다.
기본 자모 분석에 관한 소스는 웹에서 구한 소스입니다. 초성검색 때문에 많은 곳에 소스가 퍼져있고, 원 저작자 출처가 불분명해서 누가 최초로 만든 것이지 알 수는 없었습니다. 또한, 저도 소스를 사용한지 오래 되어 어느곳에서 가져다 왔는지 조차 기억이 안나는 군요. 가져온 소스에다가 제가 필요한 부분이 더 추가된 소스입니다.

//Hangul.java

package com.overoid.hangul2english.util;

 

public class Hangul {

   

                                //                                   

                                //                                   

                                //                                
                                //

final static char[] ChoSung   = { 0x3131, 0x3132, 0x3134, 0x3137, 0x3138, 0x3139,

                                  0x3141, 0x3142, 0x3143, 0x3145, 0x3146, 0x3147,

                                  0x3148, 0x3149, 0x314a, 0x314b, 0x314c, 0x314d,
                                  0x314e };

   

    final static String[] ChoSungEng = {"r", "R", "s", "e", "E", "f",

                                        "a", "q", "Q", "t", "T", "d",

                                        "w", "W", "c", "z", "x", "v", "g"};

 

                                   //                                   

                                   //                                 

                                   //                              

                                   //           

   final static char[] JwungSung = { 0x314f, 0x3150, 0x3151, 0x3152, 0x3153, 0x3154,

                                     0x3155, 0x3156, 0x3157, 0x3158, 0x3159, 0x315a,

                                     0x315b, 0x315c, 0x315d, 0x315e, 0x315f, 0x3160,

                                     0x3161, 0x3162, 0x3163 };

   

    final static String[] JwungSungEng = {"k", "o", "i", "O", "j", "p",

                                          "u", "P", "h", "hk", "ho", "hl",

                                          "y", "n", "nj", "np", "nl", "b",

                                          "m", "ml", "l"};

 

                                   //                                     

                                   //                             

                                   //                             

                                   //                                 

                                   //                   

   final static char[] JongSung  = { 0,      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 };

   

    final static String[] JongSungEng = {"", "r", "R", "rt", "s", "sw",

                                          "sg", "e", "f", "fr", "fa", "fq",

                                          "ft", "fx", "fv", "fg", "a", "q",

                                          "qt", "t", "T", "d", "w", "c",

                                          "z", "x", "v", "g"};

   

   

    public static String hangulToJaso(String s) {

       

        int a, b, c; // 자소 버퍼: 초성/중성/종성

        String result = "";

 

        for (int i = 0; i < s.length(); i++) {

            char ch = s.charAt(i);

 

            if (ch >= 0xAC00 && ch <= 0xD7A3) { // "AC00:" ~ "D7A3:" 속한 글자면 분해

                c = ch - 0xAC00;

                a = c / (21 * 28);

                c = c % (21 * 28);

                b = c / 28;

                c = c % 28;

 

                result = result + ChoSung[a] + JwungSung[b];

                if (c != 0) result = result + JongSung[c] ; // c 0 아니면, 받침이 있으면

            } else {

                result = result + ch;

            }

        }

        return result;

    }

   

    /**

     * 한글기준의 문자열을 입력받아서 한글의 경우에는 영타기준으로 변경한다.

     * @param s 한글/영문/특수문자가 합쳐진 문자열

     * @return 영타기준으로 변경된 문자열값

     */

    public static String convertToEnglish(String s) {

        // *****************************************

        // 0xAC00 + ( (초성순서 * 21) + 중성순서 ) * 28 + 종성순서 = 한글유니코드값

        // ( (초성순서 * 21) + 중성순서 ) * 28 + 종성순서 = 순수한글코드

        // 순수한글코드 % 28 = 종성

        // ( (순수한글코드 - 종성) / 28 ) % 21 = 중성

        // ( ( ( 순수한글코드 - 종성) / 28) - 중성) ) / 21 = 초성

        // *******************************************

       

        int a, b, c; // 자소 버퍼: 초성/중성/종성

        String result = "";

 

        for (int i = 0; i < s.length(); i++) {

            char ch = s.charAt(i);

 

            if (ch >= 0xAC00 && ch <= 0xD7A3) { // "AC00:" ~ "D7A3:" 속한 글자면 분해

                c = ch - 0xAC00;

                a = c / (21 * 28);

                c = c % (21 * 28);

                b = c / 28;

                c = c % 28;

 

                result = result + ChoSungEng[a] + JwungSungEng[b];

                if (c != 0) result = result + JongSungEng[c] ; // c 0 아니면, 받침이 있으면

            } else {

                result = result + ch;

            }

        }

       

        return result;

    }

   

    /*

     * 완성되지 않은 한글의 경우 영문 변환이 제대로 되지 않는다.

     * 잘못된 글자인 경우에도 영문으로 변환이 가능하도록 추가적으로 처리하는 함수

     * 글자가 초성, 중성, 종성을 구성하는 글자 배열을 루프돌면서 같은글자가 있는지

     * 확인한 해당 영문으로 변환함.

     */

    public static String convertToEnglishforSingleChar(String s) {

        String result = "";

        String temp = null;

       

        for (int i = 0; i < s.length(); i++) {

            char ch = s.charAt(i);

           

            if(ch >= 0x3131 && ch <= 0x3163) {

                temp = findChoSung(ch);

                if(temp != null) {

                    result = result + temp;

                } else {

                    temp = findJwungSung(ch);

                    if(temp != null) {

                        result = result + temp;

                    } else {

                        temp = findJongSung(ch);

                        if(temp != null) {

                            result = result + temp;

                        } else {

                            result = result + ch;

                        }

                    }

                }

            } else {

                result = result + ch;

            }

           

        }

       

        return result;

    }

   

    private static String findChoSung(char c) {

        String result = null;

        for(int i=0; i < ChoSung.length; i++) {

            if(ChoSung[i] == c) {

                result = ChoSungEng[i];

                break;

            }

        }

        return result;

    }

   

    private static String findJwungSung(char c) {

        String result = null;

        for(int i=0; i < JwungSung.length; i++) {

            if(JwungSung[i] == c) {

                result = JwungSungEng[i];

                break;

            }

        }

        return result;

    }

   

    private static String findJongSung(char c) {

        String result = null;

        for(int i=0; i < JongSung.length; i++) {

            if(JongSung[i] == c) {

                result = JongSungEng[i];

                break;

            }

        }

        return result;

    }

}



위 한글 자모 분석 로직을 이해하셨으면 소스는 이해가 쉽게 되실겁니다.

 

hangulToJaso 메소드는 카피한 소스에 있던 메소드인데, 한글 자모를 분리해서 string으로 리턴해주는 메소드입니다. 저희 소스에는 불필요한 부분이지만 나중에 쓸일이 있을지도 몰라서 그냥 그대로 두었습니다.

 

convertToEnglish 메소드가 저희 앱에서 사용하는 메소입니다. 한글을 초성/중성/종성으로 분리하여 순서값에 해당되는 영문자 배열을 리턴하면 간단히 변환이 끝났니다. 하지만, 사용자가 패스워드용 한글을 정상적인 완성형 한글만 사용하는 아니라 하ㄱㅏㅏ라고 자모만을 이용해서 만들 수도 있으므로 convertToEnglish 메소드는 이런 자모만으로 구성된 비정상적인 글자를 영문으로 변환할 수가 없어서 부분은 처리를 위한 convertToEnglishforSingleChar() 메소드를 추가로 두었습니다. 한글 자모 배열을 뒤져서 매칭되는 자모를 찾고, 동일한 배열값의 영문자를 리턴하는 메소드입니다.

 

Hangul Class 유틸성이라서 인스턴스 없이 메소드 호출을 하기 위해 모두 static으로 구현했습니다.

 

이로써 저희 Hangul2English 소스가 완성되었습니다.

 

끝으로 메니페스트 파일을 보도록 하겠습니다.


//AndroidManifest.xml

<?xml version="1.0" encoding="UTF-8"?>

<manifest android:versionCode="1" android:versionName="1.0"

          package="com.overoid.hangul2english" xmlns:android="http://schemas.android.com/apk/res/android">

    <application android:icon="@drawable/icon"

                 android:label="@string/app_name"

                 android:debuggable="false">

        <activity android:label="@string/app_name"

                  android:name=".MainActivity"

                  android:theme="@android:style/Theme.NoTitleBar"

                  >

            <intent-filter>

                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>

            </intent-filter>

        </activity>

    </application>

    <uses-sdk android:minSdkVersion="5"/>

</manifest>



다른 부분은 별개 없고,
Acticity의 속성으로 android:theme="@android:style/Theme.NoTitleBar" 를 기술하시면 앱 실행시 상단에 나오는 회색 타이틀 바가 없어집니다. 별도로 Custom TitleBar를 만드는 방법도 있긴 하지만, 그냥 디폴트 타이틀 바를 위 처럼 없애고 Layout 작성시 타이틀 처럼 보이는 레이아웃을 추가하면 될 것 같습니다. 저희도 별도로 타이틀 바 비스무리한게 있어서 디폴트 타이틀바는 빼버렸습니다.

Hangul2English를 실행해 보셨는지 모르겠습니다만, Hangul2English는 가로 회전시에도 디자인이 별 문제없이 잘 돌아갑니다. 만일, 개발하시려는 앱이 특정 가로나 세로방향만 지원해야 한다면 메니페스트 파일에 android:screenOrientation="portrait"  로 기술하시면 세로방향만, landscape로 지정하시면 가로방향으로만 앱이 실행됩니다.

그리고 application entity에 설정되어 있는 android:debuggable="false" 속성은 어플리케이션 개발 및 테스트 완료 후에 배포용 패키지를 만드실 때  설정하시면 됩니다. 부가적으로 배포용 패키지 만드실 때는 로그 기능을 빼야 합니다. 런타임시에 불필요한 로그 코드가 나오는 것은 안 좋겠죠.

로그에 대해 간단히 알려드리면 로그는 ERROR, WARN, INFO, DEBUG, VERBOSE 이 다섯개 레벨로 나눌 수 있습니다. Verbose로 설정된 로그(Log.v)는 개발용 컴파일이 아닌 배포용 패키지를 만드실 때 컴파일 단계에서 빠져버립니다. 디버그 레벨 로그는 컴파일은 되지만 런타임시에 빠져서 절대 런타임시에 출력될 일은 없습니다. 나머지 에러나 warning, info 레벨 로그는 개발시나 런타임시나 출력이 됩니다. 아주 중요한 정보만 ERROR, WARN, INFO 레벨 로그로 남기시고, 가능한 개발시에는 Verbose 레벨을 사용하시면 런타임시 부하를 줄일 수 있습니다.

아. 정말 코드가 끝났습니다.
그동안 많은 도움이 되었는지 모르겠습니다.

완성된 소스코드 첨부합니다. 개발하시는데 많은 도움이 되셨으면 좋겠네요^^



앞으로 남은 안드로이드 실전강좌는 다 만든 앱을 시장에 배포하는거에 관련된 강좌가 남은 것 같습니다. 그럼, 즐거운 하루 되시기 바랍니다.

저작자 표시 비영리 변경 금지
신고
Trackback 1 Comment 6
  1. zzl986 2011.03.24 22:15 신고 address edit & del reply

    강의 해주신내용에. 감동이였구요!
    필요한 내용이든 그렇치않은.내용이든.고생하신. 모습에. 박수드립니다!
    아자..화이팅!

  2. 김영호 2011.04.18 13:43 신고 address edit & del reply

    강의 잘 보았습니다 ^^+

    얕은 지식에 많은 보탬이 되었습니다.

    감사합니다. __+

  3. BlueSkygogo 2011.06.25 17:54 신고 address edit & del reply

    정말 좋은 내용으로 가득하네요...!
    개발하는 데 큰 도움이 될것 같습니다.
    감사드립니다....^^

  4. 민짜 2011.10.06 13:24 신고 address edit & del reply

    폰에서 글자를 입력받아 실시간으로 서버로 전송하는 프로그램을 개발중입니다. 자음/모음 분리하는것때문에 좌절하고 있었는데 올려주신 Hangul소스를 바탕으로 해결했습니다.정말 감사합니다.

  5. 미래소년 2012.04.05 23:39 신고 address edit & del reply

    감사합니다.. 강의 정말 잘 봤습니다.. 무지한 저도 이해할 수 있을만큼 잘 설명해 주셨네요..
    그런데 제가 지금 만들고 싶은 어플은 영타를 한글로 바꿔주는 것입니다..
    이 강좌와 반대로 말이죠.. 의외로 한글을 초성중성종성으로 분해할 필요가 없어 쉬울 것 같은데..
    코드 좀 부탁드려도 될까요.. 머리에서 돌기는 하는데 사실 자신이 없어서요..

  6. 2012.07.05 16:36 address edit & del reply

    비밀댓글입니다