<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>빛을 담고 세상 넓히기 &#187; 한글</title>
	<atom:link href="http://fantazic.com/archives/tag/%ed%95%9c%ea%b8%80/feed" rel="self" type="application/rss+xml" />
	<link>http://fantazic.com</link>
	<description>마음의 빛으로 넓은 세상을 비추고 싶다. JAVA, 고양이, 사진들이 있는 곳.</description>
	<lastBuildDate>Fri, 20 Aug 2010 14:40:50 +0000</lastBuildDate>
	
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>Java에서 한글을 정규표현식으로 찾기</title>
		<link>http://fantazic.com/archives/513</link>
		<comments>http://fantazic.com/archives/513#comments</comments>
		<pubDate>Tue, 20 Jul 2010 01:38:33 +0000</pubDate>
		<dc:creator>따지크</dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[정규표현식]]></category>
		<category><![CDATA[한글]]></category>

		<guid isPermaLink="false">http://fantazic.com/?p=513</guid>
		<description><![CDATA[
Tweet

문서를 읽어서 한글을 제외한 나머지 문자를 제거하기 위해 정규표현식을 사용했다. 이 과정에서 이해할 수 없는 경험들을 하게 되어 이곳에 정리한다.
처음에는  [^가-힣ㄱ-ㅎㅏ-ㅣ]  형태로 한글을 범위로 해서 찾았는데, 이렇게 하니 윈도우 기반 로컬 머신에서는 잘 동작하던 모듈이 linux 머신에서는 동작하지 않았다. 다른점을 살펴보니 linux 머신은 utf-8 기반이였고, 한글 정규표현식이 다르게 동작하는 것으로 보였다.
그래서 현재는 [^\\u3131-\\u318E\\uAC00-\\uD7A3] 와 같이 [...]]]></description>
			<content:encoded><![CDATA[<div style="display:block;margin-left: 10px; margin-bottom: 10px;">
<a href="http://twitter.com/share" class="twitter-share-button" data-url="http://fantazic.com/archives/513" data-text="Java에서 한글을 정규표현식으로 찾기" data-count="horizontal" >Tweet</a><script type="text/javascript" src="http://platform.twitter.com/widgets.js"></script>
</div>
<p>문서를 읽어서 한글을 제외한 나머지 문자를 제거하기 위해 정규표현식을 사용했다. 이 과정에서 이해할 수 없는 경험들을 하게 되어 이곳에 정리한다.</p>
<p>처음에는  [^가-힣ㄱ-ㅎㅏ-ㅣ]  형태로 한글을 범위로 해서 찾았는데, 이렇게 하니 윈도우 기반 로컬 머신에서는 잘 동작하던 모듈이 linux 머신에서는 동작하지 않았다. 다른점을 살펴보니 linux 머신은 utf-8 기반이였고, 한글 정규표현식이 다르게 동작하는 것으로 보였다.</p>
<p>그래서 현재는 [^\\u3131-\\u318E\\uAC00-\\uD7A3] 와 같이 유니코드로 범위를 표시해서 한글 이외의 문자를 제거하고 있다. Java가 내부적으로 UTF-16을 사용하는 것으로 알고 있는데 정규표현식에서 서로 다르게 동작할 수 있다는 사실을 처음 알게 됐고, 이렇게 해결하긴 했지만 동작 원리를 정확히 이해하지는 못하고 있다.</p>
<p>(?u)[^가-힣ㄱ-ㅎㅏ-ㅣ] 이렇게 유티코드 옵션을 주고 실행해도 정상 동작하지 않는다. <a href="http://bugs.sun.com/bugdatabase/view_bug.do;jsessionid=f738da6fb66a12421f628d5423?bug_id=6487160">찾아보니 UNICODE_CASE 일 때 범위 표현은 사용할 수 없다고 한다.</a></p>
<p>또 하나 고민은 (&lt;/?[bB][rR][ ]*/?&gt;|&lt;/?[pP]/?&gt;|&lt;/?[dD][iI][vV]/?&gt;|&lt;/?[lL][iI]/?&gt;|&lt;/?[tT][dD]/?&gt;) 와 같은 형태로 html 태그 중에서 단락을 구분지어 주는 태그를 약속된 구분자로 치환하려는데 이때 2가지 문제가 발생한다.</p>
<p>첫번째는 분석하려는 본문이 길 때 치환에 시간이 많이 걸린다는 것이고, 또 하나는 가끔 StackOverflowError가 발생한다<a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6337993">. 검색을 해보니 복잡한 정규표현식이나 분석하려는 대상이 길 때 발생할 수 있고 해결되지 않은 문제</a>라고 한다. 정규표현식을 좀 더 명확하고 단순하게  개선하면 에러 발생을 줄일 수 있다는데 어떻게 하면 될까?</p>
<p>우선은 (?i)(&lt;/?br */?&gt;|&lt;/?p/?&gt;|&lt;/?div/?&gt;|&lt;/?li/?&gt;|&lt;/?td/?&gt;) 대소문자 구분을 하지 않게 하고 필요 없는 표현을 제거했다. 실행 속도는 거의 변화가 없고 StackOverflowError가 줄어들지는 지켜봐야 한다.</p>
<p>(회사 동료분의 힌트로 다시 한번 수정. 현재는 (?i)&lt;/?(br *|p|div|li|td)/?&gt; 형태로 더 간단해졌다.)</p>
]]></content:encoded>
			<wfw:commentRss>http://fantazic.com/archives/513/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>톰캣 한글 인코딩 문제와 관련된 코드들</title>
		<link>http://fantazic.com/archives/255</link>
		<comments>http://fantazic.com/archives/255#comments</comments>
		<pubDate>Fri, 10 Jul 2009 06:03:17 +0000</pubDate>
		<dc:creator>따지크</dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[인코딩]]></category>
		<category><![CDATA[톰캣]]></category>
		<category><![CDATA[한글]]></category>

		<guid isPermaLink="false">http://fantazic.com/?p=255</guid>
		<description><![CDATA[
Tweet

Java로 6년째 개발을 하면서 한글 문제는 더 이상 새로운 것이 없겠지 생각을 하지만 매번 알 수 없는 상황에 부딪히게 된다.
EUC-KR 기반의 아파치-톰캣 시스템에서 브라우져 주소창의 한글을 처리하려고 할 때 다음과 같은 문제가 발생했다.
주소창에 &#8216;http://v.daum.net/search?q=한글&#8217;을 직접 입력하는 경우 크게 3가지 경우가 발생했다.
1) &#8216;한글&#8217;이라는 파라미터 값이 &#8216;EUC-KR&#8217;로 URLEncoding 되서 넘어오는 경우
2) &#8216;한글&#8217;이라는 파라미터 값이 &#8216;UTF-8&#8242;로 URLEncoding 되서 [...]]]></description>
			<content:encoded><![CDATA[<div style="display:block;margin-left: 10px; margin-bottom: 10px;">
<a href="http://twitter.com/share" class="twitter-share-button" data-url="http://fantazic.com/archives/255" data-text="톰캣 한글 인코딩 문제와 관련된 코드들" data-count="horizontal" >Tweet</a><script type="text/javascript" src="http://platform.twitter.com/widgets.js"></script>
</div>
<p>Java로 6년째 개발을 하면서 한글 문제는 더 이상 새로운 것이 없겠지 생각을 하지만 매번 알 수 없는 상황에 부딪히게 된다.</p>
<p>EUC-KR 기반의 아파치-톰캣 시스템에서 브라우져 주소창의 한글을 처리하려고 할 때 다음과 같은 문제가 발생했다.</p>
<p>주소창에 &#8216;http://v.daum.net/search?q=한글&#8217;을 직접 입력하는 경우 크게 3가지 경우가 발생했다.</p>
<p>1) &#8216;한글&#8217;이라는 파라미터 값이 &#8216;EUC-KR&#8217;로 URLEncoding 되서 넘어오는 경우<br />
2) &#8216;한글&#8217;이라는 파라미터 값이 &#8216;UTF-8&#8242;로 URLEncoding 되서 넘어오는 경우<br />
3) &#8216;한글&#8217;이 bytes로 변환되서 넘어오는 경우</p>
<p>이와 유사한 문제가 아파치 rewrite module을 사용할 때도 발생했다. 예를 들어 &#8216;http://v.daum.net/한글&#8217;을 &#8216;http://v.daum.net/search?q=한글&#8217;로 재작성하는 경우 &#8216;한글&#8217;이 bytes로 변환된다.</p>
<p>이에 대한 해결방법은 여러가지가 있을 수 있는데, 다음과 같은 아이디어를 기반으로 문제를 해결했다.</p>
<p>1. URLEncoding된 문자열의 헥사값을 bytes로 변환할 수 있다.<br />
2. bytes의 인코딩을 예측할 수 있다.</p>
<p>(구현된 코드는 톰캣 내부의 파라미터 처리 부분과 Daum 검색개발팀의 황재석님의 코드에 도움을 받았습니다.)</p>
<p>&#8216;getParameterSafely(request.getQueryString(), &#8220;q&#8221;);&#8217;와 같이 사용하면 됩니다. 단, 파라미터에 &#8216;%&#8217; 문자가 포함된 경우 오작동하게 됩니다.</p>
<pre>
/* public query parsing method */
public static String getParamterSafely(String queryString, String key) {
  if (StringUtils.isEmpty(queryString)) {
    return "";
  }

  try {
    String safeQueryString = urlDecodeSafely(queryString);
    return getParameter(safeQueryString, key);
  } catch (UnsupportedEncodingException e) {
    //ignore
    e.printStackTrace();
  }
  return "";
}

/* URLDecoding 수행 */
private static String urlDecodeSafely(String queryString)
  throws UnsupportedEncodingException {
  if (StringUtils.isEmpty(queryString))
    return "";
  byte[] src = queryString.getBytes("ISO-8859-1");
  byte[] bytes = urlDecode(src);
  <del datetime="2009-07-17T07:26:45+00:00">String charset = guessCharset(bytes);</del>
  String charset = CharsetDetector.detect(bytes);
  return new String(bytes, charset);
}

/* 헥사코드를 bytes로 변환 */
private static byte[] urlDecode(byte[] data)
  throws UnsupportedEncodingException {
  int ix = 0;
  int ox = 0;
  while (ix < data.length) {
    byte c = data[ix++];
    switch ((char) c) {
      case '+':
        data[ox++] = (byte) ' ';
        break;
      case '%':
        data[ox++] = (byte) ((convertHexDigit(data[ix++]) << 4)
          + convertHexDigit(data[ix++]));
        break;
      default:
        data[ox++] = c;
    }
  }

  return ArrayUtils.subarray(data, 0, ox);
}

/* 헥사코드 변환 */
private static byte convertHexDigit(byte b) {
  if ((b >= '0') &#038;&#038; (b <= '9'))
    return (byte) (b - '0');
  if ((b >= 'a') &#038;&#038; (b <= 'f'))
    return (byte) (b - 'a' + 10);
  if ((b >= 'A') &#038;&#038; (b <= 'F'))
    return (byte) (b - 'A' + 10);
  return 0;
}

<del datetime="2009-07-17T07:26:45+00:00">/* 재석님의 캐릭터셋 추측하는 메소드 */
private static String guessCharset(byte[] bytes) {
  try {
    CharsetDecoder decoder = Charset.forName("MS949").newDecoder();
    ByteBuffer bb = ByteBuffer.wrap(bytes);
    decoder.decode(bb);
    bb.clear();
    return "MS949";
  } catch (Exception e) {
    return "UTF-8";
  }
}</del>

/* 쿼리 문자열에서 특정 키의 값을 가져온다. */
private static String getParameter(String safeQueryString, String key) {
  String parameters[] = safeQueryString.split("&#038;");
  if (parameters != null) {
    for (String parameter : parameters) {
      if (parameter.startsWith(key + "=")) {
        return parameter.replaceFirst(key + "=", "");
      }
    }
  }
  return "";
}
</pre>
<p><a href="http://dmlim.egloos.com/3966342">임동문님의 블로그</a>를 참고해서 캐릭터셋 판별 부분을 보완했습니다.</p>
<pre>
public class CharsetDetector {
  private static final String DEFAULT_CHARSET = "MS949";
  private static final int MS949_NORMAL = 0;
  private static final int MS949_2BYTE = 1;
  private static final int MS949_KSC_2BYTE = 2;
  private static final int UTF8_NORMAL = 0;
  private static final int UTF8_2BYTE = 1;
  private static final int UTF8_3BYTE = 2;
  private static final int UTF8_4BYTE = 3;

  public static String detect(byte[] bytes) {
    if (isUTF8(bytes))
      return "UTF-8";
    if (isMS949(bytes))
      return "MS949";
    return DEFAULT_CHARSET;
  }

  public static boolean isMS949(byte[] bytes) {
    int status = MS949_NORMAL;
    for (int ch : bytes) {
      if (status < 0)
        return false;

      switch (status) {
        case MS949_NORMAL:
          if (ch < 0x80)
            status = MS949_NORMAL;
          else if (0x81 <= ch &#038;&#038; ch <= 0xc5)
            status = MS949_2BYTE;
          else if (0xc5 < ch &#038;&#038; ch <= 0xfe)
            status = MS949_KSC_2BYTE;
          else
            status = -1;
          break;
        case MS949_2BYTE:
          if ((0x41 <= ch &#038;&#038; ch <= 0x5a) || (0x61 <= ch &#038;&#038; ch <= 0x7a)
            || (0x81 <= ch &#038;&#038; ch <= 0xfe))
            status = MS949_NORMAL;
          else
            status = -1;
          break;
        case MS949_KSC_2BYTE:
          if (0xa1 <= ch &#038;&#038; ch <= 0xfe)
            status = MS949_NORMAL;
          else
            status = -1;
          break;
        default:
          break;
      }
    }
    return true;
  }

  public static boolean isUTF8(byte[] bytes) {
    int status = UTF8_NORMAL;

    for (int ch : bytes) {
      if (status < 0)
        return false;

      switch (status) {
        case UTF8_NORMAL:
          if ((ch &#038; 0x80) == 0)
            status = UTF8_NORMAL;
          else if (((ch &#038; 0xe0) ^ 0xc0) == 0)
            status = UTF8_2BYTE;
          else if (((ch &#038; 0xf0) ^ 0xe0) == 0)
            status = UTF8_3BYTE;
          else if (((ch &#038; 0xf8) ^ 0xf0) == 0)
            status = UTF8_4BYTE;
          else
            status = -1;
          break;
        case UTF8_2BYTE:
          if (((ch &#038; 0xc0) ^ 0x80) == 0)
            status = UTF8_NORMAL;
          else
            status = -1;
          break;
        case UTF8_3BYTE:
          if (((ch &#038; 0xc0) ^ 0x80) == 0)
            status = UTF8_2BYTE;
          else
            status = -1;
          break;
        case UTF8_4BYTE:
          if (((ch &#038; 0xc0) ^ 0x80) == 0)
            status = UTF8_3BYTE;
          else
            status = -1;
          break;
        default:
          break;
      }
    }
    return true;
  }
}
</pre>
]]></content:encoded>
			<wfw:commentRss>http://fantazic.com/archives/255/feed</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Ajax 통신에서 한글 문제</title>
		<link>http://fantazic.com/archives/26</link>
		<comments>http://fantazic.com/archives/26#comments</comments>
		<pubDate>Wed, 15 Feb 2006 04:07:52 +0000</pubDate>
		<dc:creator>따지크</dc:creator>
				<category><![CDATA[Software & Developer]]></category>
		<category><![CDATA[Ajax]]></category>
		<category><![CDATA[한글]]></category>

		<guid isPermaLink="false">http://fantazic.com/?p=26</guid>
		<description><![CDATA[
Tweet

Ajax 통신에는 XMLHttpRequest 객체가 사용되는데, 기존 html에서 form을 사용해 통신할 때 URL-Encoding이 되는 것과 달리 XMLHttpRequest는 자체적으로 인코딩을 수행하지 않는다. 그래서 한글이나 특수문자를 사용하는 경우 인자값을 인코딩해서 보내줘야 한다.
javascript에서 인코딩하는 방식에는 크게 escape()와 encodeURIComponent() 방식이 있는데, escape()는 유니코드로 인코딩을 하고 encodeURIComponent()는 UTF-8로 인코딩한다. (참고 XMLHttpRequest 사용시 한글 파라미터 전송 방법)
일반적인 경우 encodeURIComponent()를 사용해서 인코딩한 후 [...]]]></description>
			<content:encoded><![CDATA[<div style="display:block;margin-left: 10px; margin-bottom: 10px;">
<a href="http://twitter.com/share" class="twitter-share-button" data-url="http://fantazic.com/archives/26" data-text="Ajax 통신에서 한글 문제" data-count="horizontal" >Tweet</a><script type="text/javascript" src="http://platform.twitter.com/widgets.js"></script>
</div>
<p>Ajax 통신에는 XMLHttpRequest 객체가 사용되는데, 기존 html에서 form을 사용해 통신할 때 URL-Encoding이 되는 것과 달리 XMLHttpRequest는 자체적으로 인코딩을 수행하지 않는다. 그래서 한글이나 특수문자를 사용하는 경우 인자값을 인코딩해서 보내줘야 한다.</p>
<p>javascript에서 인코딩하는 방식에는 크게 escape()와 encodeURIComponent() 방식이 있는데, escape()는 유니코드로 인코딩을 하고 encodeURIComponent()는 UTF-8로 인코딩한다. (참고 <a href="http://javacan.madvirus.net/main/content/read.tle?contentId=113">XMLHttpRequest 사용시 한글 파라미터 전송 방법</a>)</p>
<p>일반적인 경우 encodeURIComponent()를 사용해서 인코딩한 후 서버쪽에서 UTF-8로 처리하면 되는데, ASP에서 응답받는 서버쪽을 Response.CharSet=&#8221;utf-8&#8243; 로 세팅한 경우 일부 한글이 깨지는 문제가 발생한다. 그래서 결국 escape()를 사용해야 하는데 escape()는 몇몇 문자를 인코딩하지 않는 문제가 있다. 그래서 가능하면 escape() 대신 encodeURIComponent()를 사용하도록 권고하고 있다. (참고 <a href="http://xkr.us/articles/javascript/encode-compare/">Comparing escape(), encodeURI(), and encodeURIComponent()</a>)</p>
<p>결국 escape()를 사용할 때, 문자열에 &#8216;+&#8217;이 들어가면 빈칸으로 인식되는 문제가 발생한다. 그래서 prototype.js의 Form 객체에 escape() 기능을 따로 정의해서 사용하게 되었다.</p>
<pre>escape: function(value) {
 return escape(value).replace(/\+/g, '%2B');
}</pre>
<p>prototype.js 내부에서도 Form.serialize()에서 사용된 encodeURIComponent()를 Form.escape()를 사용하도록 수정해서 문제는 해결됐다.</p>
<p>공개된 라이브러리, 특히 영어권에서 만들어진 라이브러리는 항상 유니코드 사용에 문제가 발생하는 경우가 많다. 씁쓸하지만 고쳐서 쓸 수 밖에. 국내에서 좋은 오픈소스 라이브러리가 많이 만들어지는 세상을 기대해본다. *^^*</p>
]]></content:encoded>
			<wfw:commentRss>http://fantazic.com/archives/26/feed</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
	</channel>
</rss>
