Mozilla Universal Charset Detect on C/C++/PHP

김정균의 이미지

아시는 분들은 다 알고 계신 Mozilla 의 Universal charset detect library 에 대한 글입니다. 보기 좋게 말해서 announce 이지만 실제로는 4일간의 삽질기라고 보시면 됩니다. --;

Mozilla Universal Charset Detect (줄여서 보통 chardet 이라고 합니다.) 은 꽤 많은 언어로 포팅이 되어 있습니다. 각 언어별로 정리를 해 보면

Mozilla (C++)        -> mxr.mozilla.org/mozilla-central/source/intl/chardet/src/ 
Nchardet (C#)        -> conceptdevelopment.net/Localization/NCharDet/ 
Jchardet (Java)      -> jchardet.sourceforge.net/ 
chardet (Python)     -> chardet.feedparser.org/ 
chardet (ryby)       -> rubyforge.org/projects/chardet/ 
Encode-Detect (perl) -> search.cpan.org/~jgmyers/Encode-Detect-1.01/Detector.pm 

와 같습니다. 이 중 Mozilla code 의 경우에는 브라우저 소스에서 분리되어 있지가 않아 사용하기가 좀 껄끄럽고, python 의 경우에는 chardet 을 pure python code 로 작성을 하여 속도가 느립니다. 또한 ruby 의 경우에는 python chardet code를 ruby로 포팅한 것이라 역시 마찬가지 이고요.

아마 c/c++ library 형태로 지원이 되었다면, 아마 python 이나 ruby 가 pure code 로 작성하지는 않았을까 생각해 보게 되더군요. Nchardet 하고 Jchardet 은 코드를 보지 않아서 어떻게 구현이 되어 있는지 모르겠군요.

보시는 바와 같이 php 만 지원하지 않아서, 한 2년전 부터 누군가 만들어 주겠지 하고 기다리고 있었는데, 결국은 현재까지 만드는 사람이 없더군요. (php를 하는 사람중에 guru가 부족한 것일까 하고 생각도 해 봤습니다.) 그래서 기다리다 지쳐서, php 로 만들려고 chardet c/c++ library를 검색해 봤는데 찾지 못하고, 일단은 icu-project 의 Conversion 기능중의 Charset detection API 와 Python C API를 이용해서 mozilla chardet 을 dual mode 로 작성을 했습니다. (http://my.oops.org/126 참조)

일단 만들고 나서 icu-project 의 기능은 detecting 능력이 떨어지고, Python C API는 속도가 느리고 왠지 마음에 들지 않더군요. 그러다가 그 방대한 양의 module 을 가지고 있는 perl 은 왜 얘기가 없지 하고 검색해 보니 Encode-Detect 라는 모듈이 존재하고, 이 소스를 보니, C++ code를 Mozilla code 에서 분리해서 사용을 하고 있더군요.

일단, 이 코드에 있는 C++ 코드를 가지고 와서, libchardet 을 만들었습니다. 이 라이브러리는 Encode-Detect 의 코드와 다른점은 다음과 같습니다.

1. Latin2(Hungarian), Win1250(Hungarian), TIS620(ThaiModel)
   를 detecting 하지 못하는 문제 해결
2. C wraping function 제공 (C/C++ support)
3. libtool 을 이용해서 so/a library 제공
4. chardet-config 명령을 이용한 빌드 환경 제공

libchardet 을 이용한 C/C++ 코딩은 다음과 같이 할 수 있습니다. 빌드시에 -lchardet -lstdc++ 을 링크해 주셔야 합니다. 또는 chardet-config --libs 의 결과를 link 옵션으로 주시면 됩니다.

/*
 * sample code with libchardet
 * author: JoungKyun.Kim <http://oops.org>
 * $Id: sample.c,v 1.1.1.1 2009/02/21 20:08:02 oops Exp $
 */
 
#include <chardet.h>
 
int main (void) {
    Detect * d;
    DetectObj *obj;
    int i;
 
    char *str[] = {
        "안녕",
        "안녕하세요",
        "안녕하세요 정말?",
        "그래 이정도면 판단 될까?",
        "좀더 길게 적어 볼까 얼마나 길게 해야!",
        "그래 그래 좀 더 길게 적어 보자 더 길게 적어 보야야 겠지...",
        "12345 abcde"
    };
 
    short arrayNum;
    arrayNum = sizeof (str) / sizeof (str[0]);
 
    if ( (d = detect_init ()) == NULL ) {
        fprintf (stderr, "chardet handle initialize failed\n");
        return CHARDET_MEM_ALLOCATED_FAIL;
    }
 
    for ( i=0; i<arrayNum; i++ ) {
        detect_reset (&d);
 
        obj = detect_obj_init ();
        if ( obj == NULL ) {
            fprintf (stderr, "On attemped \"%s\", memory allocation failed\n", str[i]);
            continue;
        }
 
        if ( detect_handledata (&d, str[i], &obj) == CHARDET_OUT_OF_MEMORY ) {
            fprintf (stderr, "On handle processing, occured out of memory\n");
            return CHARDET_OUT_OF_MEMORY;
        }
        printf ("## %s : %s : %f : %d\n", str[i], obj->encoding, obj->confidence, obj->status);
        detect_obj_free (&obj);
    }
 
    detect_destroy (&d);
 
    return 0;
}

또는 간단하게 detect API 를 이용하면 handle 을 열고 닫을 필요가 없이 간단히 사용 가능 합니다.

/*
 * sample code with libchardet
 * author: JoungKyun.Kim <http://oops.org>
 * $Id: sample1.c,v 1.1.1.1 2009/02/21 20:08:02 oops Exp $
 */
#include <chardet.h>
//#include "../src/chardet.h"
 
int main (void) {
    DetectObj *obj;
    int i;
 
    char *str[] = {
        "안녕",
        "안녕하세요",
        "안녕하세요 정말?",
        "그래 이정도면 판단 될까?",
        "좀더 길게 적어 볼까 얼마나 길게 해야!",
        "그래 그래 좀 더 길게 적어 보자 더 길게 적어 보야야 겠지...",
        "12345 abcde"
    };
 
    short arrayNum;
    arrayNum = sizeof (str) / sizeof (str[0]);
 
    for ( i=0; i<arrayNum; i++ ) {
        obj = detect_obj_init ();
        if ( obj == NULL ) {
            fprintf (stderr, "On attemped \"%s\", memory allocation failed\n", str[i]);
            continue;
        }
 
        if ( detect (str[i], &obj) == CHARDET_OUT_OF_MEMORY ) {
            fprintf (stderr, "On handle processing, occured out of memory\n");
            return CHARDET_OUT_OF_MEMORY;
        }
        printf ("## %s : %s : %f : %d\n", str[i], obj->encoding, obj->confidence, obj->status);
        detect_obj_free (&obj);
    }
 
    return 0;
}

libchardet 을 만들게 되어 php mod_chardet 은 아주 종합 선물 세트가 되었습니다. detect API 에서 CHARDET_MOZ 를 사용하면, libchardet 을 이용하고, CHARDTE_ICU 를 이용하면 icu API를 이용하고, CHARDET_PY 를 이용하면 Python C API를 이용해서 python chardet 의 결과를 호출하게 됩니다.

대충 아래와 같이 코드를 만들 수 있습니다.

<?
if ( ! extension_loaded ('chardet') )
    dl ('chardet.so');
 
$strings = array (
    '안녕하세요 abc는 영어고요, 가나다는 한글 입니다.',
    '안녕',
    '안녕하세요',
    '조금더 길게 적어 봅니다. 어느 정도가 필요할까요? 오호라.. 점점 길어지네',
);
 
$fp = chardet_open ();
 
$i=0;
foreach ( $strings as $s ) {
    #
    # proto object chardet_detect (stream handle, string[, mode])
    #   database handle : return value of chardet_open () API
    #   string          : strings for character set detecting
    #   mode            : optional
    #                     if support CHARDTE_MOZ, this value is
    #                     default, and don't support CHARDET_MOZ,
    #                     CHARDET_ICU is default.
    #
    #                     CHARDET_MOZ : libchardet library result
    #                     CHARDET_ICU : icu library result
    #                     CHARDET_PY  : python-chardet result
    #
    #                     if each CHARDET_(MOZ|ICU|PY) value is -1,
    #                     it means don't support each mode.
    #
    #  return value type : object -> encoding    : detecting charset name
    #                                langs       : charset language name
    #                                              Don't support on CHARDET_MOZ and CAHRDET_PY mode
    #                                confidence  : detecting confidence
    #                                status      : error code (0 is not error)
    #
    if ( CHARDET_MOZ != -1 )
        $moz = chardet_detect ($fp, $s);
    if ( CHARDET_ICU != -1 )
        $icu = chardet_detect ($fp, $s, CHARDET_ICU);
    if ( CHARDET_PY != -1 )
        $py = chardet_detect ($fp, $s, CHARDET_PY);
 
    echo "$s\n";
    if ( CHARDET_MOZ != -1 )
        printf ("MOZ : Encoding -> %-12s, Confidence -> %3d, Status -> %d\n",
                $moz->encoding, $moz->confidence, $moz->status);
    if ( CHARDET_ICU != -1 )
        printf ("ICU : Encoding -> %-12s, Confidence -> %3d, Status -> %d\n",
                $icu->encoding, $icu->confidence, $icu->status);
    if ( CHARDET_PY != -1 )
        printf ("PY  : Encoding -> %-12s, Confidence -> %3d, Status -> %d\n",
                $py->encoding, $py->confidence, $py->status);
    echo "\n";
 
    $i++;
}
 
chardet_close ($fp);
?>

관련 정보로는

http://my.oops.org/126http://my.oops.org/126#comment1094 를 참조하시면 추가 정보가 좀 더 있습니다.

혹시 python 이나 ruby 로 사용하시다가 속도가 느려서 속상하시던 분, php 에서는 사용할 수 없어서 속상하시는 분.. 다른 application 에서 사용하고 싶은데.. 방법이 없어서 Python C API 로 사용하셨던 분..들.. 이젠 맘 편하게 사용 하십시오.

이상.. 4일간의 삽질기였습니다.

댓글

kirrie의 이미지

와 김정균님 정말 그렇게 안봤는데, 남이 먼저 이미 작업해 놓은 것을 마치 자기가 한 것처럼...

.. 하고 생각하다가, http://my.oops.org/126 링크에 들어갔더니
며칠전 구독하고 있던 정균님 블로그에서 본 내용이군요. ㅎㅎㅎ;; 한순간 제 마음속에서 정균님
나쁜 사람으로 만들어버려서 죄송합니다. m(-_-)m

그런데 php에선 이미 mb_ 함수들만으로도 충분히 쓸만하지 않은가요? (물론 mb_ 함수들을 지원하는 버전이어야 하고
설치되어 있어야 한다는 전제가 있긴 하지만...) 얼마전에 utf8의 한글 문자열을 적당한 길이로
잘라야 할 경우가 있어서 이런저런 커스텀 함수들을 테스트 해봤는데, 뭔가 껄쩍지근한 느낌을
지울수가 없더라구요. 결국 mb_substr을 발견하고는 사용해봤는데, 아주 멋지게 잘 작동해서 놀랐었는데..
--->
데비안 & 우분투로 대동단결!

--->
데비안 & 우분투로 대동단결!

김정균의 이미지

chardet 하고, mb_ 함수들과는 성격이 조금 틀리죠. :-) utf8 의 장점이라면 여러가지 언어를 한꺼번에 표현을 할 수 있다는 것인데, 이 경우 charset detection 이 가능하다면, utf8 이 아닌 언어를 utf8로 converting 을 할 수 있으니까요. 즉 들어오는 string 에 대한 정보를 프로토콜에 담지 않는 한은 chardet 이 필요할 수 밖에 없겠죠.

제가 utf8 match 함수를 euc-kr / utf-8 match 만 하다가, 이를 확장 하려고 (그래서 이런 http://oops.org/SERVICE/jsboard/read.php?table=jsboard_oopsFAQ&no=592 문서가 남게 되었죠..) 했었는데, 문제는 ISO-8859-2~15 가 0x80 이상의 범위를 사용하는데, 이 범위와 EUC-KR 하고 겹치는 경우가 발생하게 되더군요. 즉, euc-kr 로 판단이 되어야 하는데, ISO-8859-2~15 사이에 하나로 판단이 되어 버리는 경우가 발생이 되어 결국에는 detection 에 관심을 가지게 된 것입니다.

한 2년전 즈음에 openlook 을 통해서 python chardet 을 알게 되었고, 그 당시 해 보려다가 실력 부족 겸.. 누가 해 주겠지라는 맘으로 기다리다가, 쓸일이 생겨서 결국에는 "필요하면 내가 할 수 밖에" 또는 "급한놈이 할 수 밖에" 라는 생각으로 작업을 한 것입니다.

누가 미리 해 놓았다면,, 정말 안습인 상황이 되는 거죠 T.T

김정균의 이미지

Encode-Detect 1.01 의 버그는 http://rt.cpan.org/Ticket/Display.html?id=43548 에 리포팅을 했지만 bug list 를 보니 고쳐지기는 힘들 것 같군요 :-)

7339989b62a014c4ce6e31b3540bc7b5f06455024f22753f6235c935e8e5의 이미지

pecl에 한번 도전(?)해보시죠? ;)

ka7713의 이미지

php mb_detect_encoding(libmbfl) 이걸 꽤 오래 전부터 써왔던걸로 기억하는데요.
(기억에는 mbstring 포함 되었을때부터 있었던듯..)

인식 능력이나 속도 등 장단점이 있으니 폄하하는 뜻은 아닙니다..

c++에서 libmbfl을 좀 써볼려고, 찾아보니 공식홈이 안보이네요.

php ext mbstring 디렉토리에 통채로 있는걸 써보긴 해봤습니다.

김정균의 이미지

mb_detect_encoding은 charset 우선 순위에 따라 치중된 결과를 보여줍니다. 예전에 roundcube 개발자와 charset이 지정되지 않은 메일처리 부분이 열악해서 논쟁을 할 때, roundcube 개발자가 mb_detect_encoding을 사용했는데, 이 때 big5를 가장 우선 순위에 두어 euc-kr이 big5나 euc-jp로 판단되는 경우가 많았습니다.

제가 만든다면 당연히 euc-kr에 우선순위를 둘 테니 결과가 잘 나올지 모르겠지만 국제화를 기준으로 한다면 아직은 좋은 선택은 아니라 보입니다.

shyblue의 이미지

고생하셨습니다. ^^

xml 처리할때 귀찮아서 그냥 icu를 사용해왔었는데, 사실 이게 정확도가 cjk쪽에서 상당히 떨어져서 항상 고민이었죠.
최근엔 쓸일이 없어서 머리 한쪽 구석에 쳐박아둔 고민이었는데, 새롭게 보게됐습니다.

다음에, 처리할 일이 있으면 정균님이 수정하신 라이브러리를 사용해봐야겠네여 ^^

時日也放聲大哭

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.