BLOG ARTICLE 자바 | 2 ARTICLE FOUND

  1. 2009.03.02 다국어 코드 적용하기
  2. 2008.07.01 자바에서 바이트 단위길이로 문자열 자르기


대단한 프로그램은 아니고,
이번에 프로그램을 한참 개발하고 다국어 버전으로 변경할 일이 있었는데
30여개의 소스 파일을 일일이 뒤지면서 한글로 만들어 놓은 코드를 다국어 처리코드로 바꾸는 게 여간 힘든 작업이 아니었다.
(눈과 팔이 너무 아팠다. ^^;)
만만하게 보고 그냥 무식하게 하려다가, 결국 반 정도를 한 후에 프로그램을 만들었다. ^^;

아래에 있는 소스가 자바로 만든 변환 프로그램.

[code java]
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;

public class I18NReplace {
    private static final String I18N_CODE_PREFIX = "{resourceManager.getString('rb','";
    private static final String I18N_CODE_POSTFIX = "')}";

    /**
     * @param args
     */
    public static void main(String[] args) throws Exception {
        HashMap map = new HashMap();
        BufferedReader br = null;
        BufferedReader brSrc = null;

        try {
            // 다국어 코드, 값이 정의된 리소스번들 파일
            br = new BufferedReader(new InputStreamReader(
                                    new FileInputStream("code.properties"), "UTF-8"));
            // 다국어 버전으로 변환할 소스 파일
            brSrc = new BufferedReader(new InputStreamReader(
                                    new FileInputStream("test.mxml"), "UTF-8"));
           
            String line = null;
            // 다국어 코드, 값을 HashMap에 저장
            while ( (line = br.readLine()) != null ) {
                String[] arr = line.split("=");
                if (arr.length == 2) {
                    map.put(arr[0], arr[1]);
                }
            }
           
            Object[] keys  = map.keySet().toArray();
            // 길이가 긴 문자열부터 변환하기 위해 문자열 길이 내림차순으로 키를 정렬
            Arrays.sort(keys, new KeyOrderComparator());
           
            String srcLine = null;
            while ( (srcLine = brSrc.readLine()) != null ) {
                // 주석 라인은 skip
                if (!srcLine.trim().startsWith("//") &&
                    !srcLine.trim().startsWith("/*") &&
                    !srcLine.trim().startsWith("<!--")) {
                    for (int i = 0; i < keys.length; i++) {
                        if (srcLine.indexOf((String)keys[i]) > -1) {
                            srcLine = srcLine.replaceAll((String)keys[i],
                                    I18N_CODE_PREFIX + (String)map.get(keys[i]) + I18N_CODE_POSTFIX);
                        }
                    }
                }
               
                System.out.println(srcLine);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (br != null)
                    br.close();
            } catch (Exception e) {}
           
            try {
                if (brSrc != null)
                    brSrc.close();
            } catch (Exception e) {}
        }
    }
}

class KeyOrderComparator implements Comparator {
    /**
     * 문자열의 길이에 따라 내림차순 정렬
     */
    public int compare(Object key1, Object key2) {
        return ((String)key2).length() - ((String)key1).length();
    }
}
[/code]
I18NReplace.java

[code]
데이터=data
[/code]
code.properties

[code xml]
<?xml version="1.0" encoding="utf-8"?>
<mx:TitleWindow xmlns:mx="http://www.adobe.com/2006/mxml" title="데이터">
...
</mx:TitleWindow>
[/code]
test.mxml


사용법이 복잡하진 않지만, 급하게 대강 만든 프로그램이라 GUI 등의 친절한 기능은 없다.

1. code.properties는 코드와 값을 넣은 건데 일반적인 순서와는 반대다.
이유는, 다국어 적용하기 전에 '데이터'라고 코딩을 했는데 이를 다국어 처리하기 위해서는
한글용 리소스번들 파일에 'data=데이터' 라는 내용이 있어야 한다.
그리고, '데이터' 대신에 다국어 처리 코드( Flex를 예로 들면, {resourceManager.getString('rb','data')} )를 넣으면
언어 설정에 따라 한국어일 때는 '데이터', 영어일 때는 'Data'라는 내용이 보이게 된다.
그런데, 지금 할 일이 '데이터'라고 하드코딩되어 있는 부분을 {resourceManager.getString('rb','data')}로 바꾸는 것이니까
code.properties 파일에는 '데이터=data' 즉, message=key 형태로 되어 있어야 한다.

2. test.mxml은 '데이터'라는 내용이 하드코딩되어 있는 소스 파일

3. I18N_CODE_PREFIX는 다국어처리코드에서 key 앞에 붙는 코드, I18N_CODE_POSTFIX는 key 뒤에 붙는 코드

참고로, 중간에 Arrays.sort 는 문자열길이에 따라 내림차순으로 정렬하는 건데,
그래야 '데이터 관리'라는 문자열이 소스에서 나왔을 때, '데이터 관리'라는 게 code.properties에 있을 때 우선적으로 처리하고
이게 없으면 '데이터'나 '관리'를 따로 변환하게 되어 원치 않는 결과가 나오게 된다.
이렇게 '데이터 관리'를 하나의 단어로 정의하는 것은 언어에 따라 어순이 바뀌거나,
두 단어 이상이 합쳐져서 만들어진 단어의 경우 언어에 따라 다른 의미가 될 수 있어서이다.

또, 참고로 파일을 읽을 때 "UTF-8"라는 설정이 있는데 이것은 "UTF-8" 인코딩으로 저장한 파일을 읽기 위해 사용한 것이다.
소스 파일이나 리소스번들 파일의 인코딩 형태에 따라 빼거나 고쳐서 사용하면 된다.


이렇게 설정한 상태에서 이 I18NReplace을 실행하면 치환된 결과가 표준출력으로 나오고,
이 결과를 원래 소스에 붙인 다음에 잘못 된 부분을 수정해서 저장하면 다국어 처리가 끝난다.

꼭 다국어 처리 변환에만 사용할 필요는 없고, 어떤 규칙에 따라 문자열을 치환해야 하는 경우 사용하면 되겠다.
당연한 얘기지만, code.properties 파일 정의시 띄어쓰기는 정확히 맞춰 주어야 한다.
AND

모 이런 거 어딘가 있다는 얘길 들었는데, 내가 만들면 더 잘 할 수 있을 거 같아서 한번 만들어 봤다.
하다가 귀찮아져서 겨우 문제 없이 돌아가는 수준에서 멈췄으니 고쳐 쓸 사람은 얼마든지 고쳐서 쓰시길...

용도는 아무래도 바이트 단위의 길이로 정의된 DB 컬럼에 데이타를 넣을 때,
어쩔 수 없이 잘라서 넣어야 하는 경우가 생기는데 이럴 때 이 메소드 한방으로 해결.
바이트 단위라는 부분에서 냄새를 맡았겠지만, 한글을 비롯한 한글자가 2바이트 이상을 사용할 때에 유용하겠다.
오라클 같은 경우 VARCHAR2 길이가 4000바이트 밖에 안 되다 보니 긴 내용을 넣기에 좀 부족한 느낌이 있는데,
그렇다고 LONG이나 CLOB를 쓰는 건 성능도 안 좋고, 다루기도 불편하니
이런 식으로 잘라서 VARCHAR2 컬럼에 넣는 방법을 종종 사용한다고 한다.

첫번째 메소드는 원하는 바이트수만큼만으로 이루어진 String 리턴
getMaxByteString(str, 400) 을 호출하면 str이라는 문자열에서 앞에서 최대 400바이트만큼 잘라서 문자열으로 만들어 리턴
즉, 리턴한 문자열을 보면 str이 400바이트 이상이면 399바이트 아니면 400바이트이겠고, 그보다 짧으면 통째로 리턴할 것이다.
[code java] // str이라는 문자열의 맨앞부터 최대 maxLen만큼의 바이트수로 이루어진 문자열을 잘라서 리턴 public static StringgetMaxByteString(String str, int maxLen) { StringBuilder sb = new StringBuilder(); int curLen = 0; String curChar; for (int i = 0; i < str.length(); i++) { curChar = str.substring(i, i+1); curLen += curChar.getBytes().length; if (curLen > maxLen) break; else sb.append(curChar); } return sb.toString(); } [/code]
두번 째 메소드는 원하는 바이트수(maxLen)만큼만으로 이루어진 String 배열을 str 문자열 끝까지 처리해서 리턴
getMaxByteStringArray(str, 400) 을 호출하면 str이라는 문자열에서 앞에서 최대 400바이트만큼 잘라서 문자열로 만들고
그 후에 문자열이 또 있으면 또 이어서 최대 400바이트만큼 잘라서 문자열을 만들고. 이걸 문자열 끝까지...
만들어진 문자열을 배열로 만들어서 리턴
별도 테이블을 만들어서 각 문자열을 하나의 로우에 저장하도록 구성한다면 이것도 괜찮을 듯.
[code java] // str이라는 문자열의 맨앞부터 끝까지 최대 maxLen만큼의 바이트수로 이루어진 문자열을 // 연속으로 잘라서 String 배열로 리턴 public static String[]getMaxByteStringArray(String str, int maxLen) { return getMaxByteStringArray(str, maxLen, -1); } [/code]
세번째 메소드는 원하는 바이트수(maxLen)만큼만으로 이루어진 String 배열을 최대 maxArrays만큼 길이로 리턴
getMaxByteStringArray(str, 400, 3) 을 호출하면 str이라는 문자열에서 앞에서 최대 400바이트만큼 잘라서 문자열로 만들고
그 후에 문자열이 또 있으면 또 이어서 최대 400바이트만큼 잘라서 문자열을 만들고. 이걸 문자열을 최대 3번까지...
만들어진 문자열을 배열로 만들어서 리턴
한 테이블의 3개의 컬럼에 문자열을 쪼개서 저장한다면 유용할 듯.
[code java] // str이라는 문자열의 맨앞부터 최대 maxLen만큼의 바이트수로 이루어진 문자열을 // 연속으로 잘라서 최대 maxArrays 길이의 배열로 리턴 public static String[]getMaxByteStringArray(String str, int maxLen, int maxArrays) { StringBuilder sb = new StringBuilder(); ArrayList strList = new ArrayList(); int curLen = 0; String curChar; for (int i = 0; i < str.length(); i++) { curChar = str.substring(i, i+1); curLen += curChar.getBytes().length; if (curLen > maxLen) { if (maxArrays == -1 || strList.size() <= maxArrays-2) { strList.add(sb.toString()); sb = new StringBuilder(); curLen = 0; i--; } else break; } else sb.append(curChar); } strList.add(sb.toString()); String[] strArr = new String[strList.size()]; for (int i = 0; i < strList.size(); i++) { strArr[i] = strList.get(i); } return strArr; } [/code]
아래는 위의 메소드를 호출하는 샘플 코드
이 코드 돌려 보고 싶으면, 위의 메소드들과 아래의 main 메소드 합쳐서 한 클래스에 넣고 돌리면 된다.
[code java] public static void main(String[] args) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < 1000; i++) { sb.append("codePointAt을 사용하면 문자의 유니코드값을 구할 수 있잖아요.. 근데.. 문자의 유니코드값을 다시 문자로 변환해"); } Stringstr = sb.toString(); String newStr = getMaxByteString(str, 4000); System.out.println(newStr + ":" + newStr.getBytes().length); System.out.println("-----------------------------------------"); String[] strArr = getMaxByteStringArray(str, 4000); for (int i = 0; i < strArr.length; i++) { System.out.println(strArr[i] + ":" + strArr[i].getBytes().length); } System.out.println("-----------------------------------------"); String[] strArr2 = getMaxByteStringArray(str, 4000, 3); for (int i = 0; i < strArr2.length; i++) { System.out.println(strArr2[i] + ":" + strArr2[i].getBytes().length); } } [/code]
한 글자씩 잘라 붙이기라서 오래 걸릴 줄 알았는데, 꽤 긴 문자열도 0.1초 이내에 잘라 내니 쓸 만하군...

'Java_Web' 카테고리의 다른 글

오라클에서 SYSDATE를 이용한 시간 연산  (0) 2009.02.12
Derby 사용하기  (2) 2008.09.29
ASM - 자바 바이트코드 분석하기  (1) 2008.03.07
클래스 위치 찾아 주는 프로그램  (0) 2007.08.15
놀라운 발견  (0) 2007.08.12
AND