2009-05-10 2 views
5

내 Perl 앱과 MySQL 데이터베이스가 들어오는 UTF-8 데이터를 올바르게 처리하지만 기존 데이터를 변환해야합니다. 일부 데이터는 CP-1252로 인코딩되어 UTF-8로 인코딩되기 전에 디코딩되지 않고 MySQL에 저장됩니다. O'Reilly의 기사 Turning MySQL data in latin1 to utf8 utf-8을 읽었는데 자주 언급 되긴하지만 결정적인 해결책은 아닙니다.저장된 오 인코딩 된 데이터를 어떻게 변환합니까?

나는 Encode::DoubleEncodedUTF8Encoding::FixLatin을 보았지만 어느 것도 내 데이터에서 작동하지 않았습니다.

이것은 내가 지금까지 한 일이다

대부분의 경우 수정하지만 proplerly 인코딩 된 기록에 대해 실행하는 경우, 그것은을 미치게
#Return the $bytes from the DB using BINARY() 
my $characters = decode('utf-8', $bytes); 
my $good = decode('utf-8', encode('cp-1252', $characters)); 

. Encode::GuessEncode::Detect을 사용해 보았지만 제대로 인코딩 된 레코드와 오 인코딩 된 레코드를 구별 할 수 없습니다. 따라서 전환 후 \x{FFFD} character이 발견되면 전환 만 취소됩니다.

일부 레코드는 부분적으로 만 변환됩니다. 다음은 왼쪽 중괄호 따옴표가 제대로 변환되지만 오른쪽 중괄호 따옴표는 잘리지 않는 예제입니다.

perl -CO -MEncode -e 'print decode("utf-8", encode("cp-1252", decode("utf-8", "bob\xC3\xAF\xC2\xBF\xC2\xBDs")))' 

내가 여기 이중 인코딩 된 데이터를 처리하고 있는가 :

perl -CO -MEncode -e 'print decode("utf-8", encode("cp-1252", decode("utf-8", "\xC3\xA2\xE2\x82\xAC\xC5\x93four score\xC3\xA2\xE2\x82\xAC\xC2\x9D")))' 

그리고 여기가 바로 따옴표 변환하지 않은 예입니다? 이 기록을 변환하기 위해 더해야 할 일은 무엇입니까?

답변

6

"4 점"예제에서는 거의 확실하게 이중으로 인코딩 된 데이터입니다.

(물론 UTF8 프로세스에 CP1252을 통해 실행 된 두 번 UTF8 과정에 CP1252을 통해 실행 된

  • CP1252 데이터,
  • UTF8 데이터, 두 경우 모두 : 그것은 같은 중 하나를 찾습니다 동일하게 보임)

    자, 예상했던대로입니다. 그렇다면 왜 코드가 작동하지 않았습니까?

    먼저, this table을 참조하십시오. 여기서는 cp1252에서 유니 코드로의 변환을 보여줍니다. 중요한 점은 cp1252에서 유효하지 않은 바이트 (예 : 0x9D)가 있다는 것입니다.

    따라서 cp1252를 utf8 변환기로 작성한다고 상상할 때 cp1252에없는 바이트로 작업해야합니다. 내가 생각할 수있는 유일한 감각적 인 것은 미지의 바이트를 같은 값으로 유니 코드 문자로 변환하는 것입니다. 실제로, 이것은 일어난 것 같습니다. 한 번에 한 단계 씩 "네 점수"예제를 사용합시다.

    $ perl -CO -MEncode -e '$a=decode("utf-8", 
        "\xC3\xA2\xE2\x82\xAC\xC5\x93" . 
        "four score" . 
        "\xC3\xA2\xE2\x82\xAC\xC2\x9D"); 
        for $c (split(//,$a)) {printf "%x ",ord($c);}' | fmt 
    

    이 유니 코드 코드 포인트의 순서 산출 :이 때문에

    먼저, 유효한 UTF-8의 함께 디코딩 할 수

    e2 20ac 153 66 6f 75 72 20 73 63 6f 72 65 e2 20ac 9d 
    

    ("FMT는"유닉스 명령입니다 그

    이제는 cp1252에서 이들 각각을 바이트로 나타내지 만, 유니 코드 문자를 cp1252에서 표현할 수없는 경우에는 ju 동일한 숫자 값을 가진 바이트로 바꿉니다. (기본값 대신 물음표로 바꾸는 것이 기본값입니다.) 데이터에 어떤 문제가 발생했는지 확인하려면 유효한 utf8 바이트 스트림이 있어야합니다.

    $ perl -CO -MEncode -e '$a=decode("utf-8", 
        "\xC3\xA2\xE2\x82\xAC\xC5\x93" . 
        "four score" . 
        "\xC3\xA2\xE2\x82\xAC\xC2\x9D"); 
        $a=encode("cp-1252", $a, sub { chr($_[0]) }); 
        for $c (split(//,$a)) {printf "%x ",ord($c);}' | fmt 
    

    encode의 세 번째 인수 - 하위 일 때 - 설명 할 수없는 문자로 수행 할 작업을 알려줍니다.

    이 수율 :

    e2 80 9c 66 6f 75 72 20 73 63 6f 72 65 e2 80 9d 
    

    지금이 유효한 UTF8 바이트 스트림이다. 검사로 알 수 없습니까? 음, UTF8로이 바이트 스트림을 디코딩하는 펄을 물어 보자 :

    $ perl -CO -MEncode -e '$a=decode("utf-8", 
        "\xC3\xA2\xE2\x82\xAC\xC5\x93" . 
        "four score" . 
        "\xC3\xA2\xE2\x82\xAC\xC2\x9D"); 
        $a=encode("cp-1252", $a, sub { chr($_[0]) }); 
        $a=decode("utf-8", $a, 1); 
        for $c (split(//,$a)) {printf "%x ",ord($c);}' | fmt 
    

    전달 "1"디코딩에 세 번째 인수는 바이트 스트림이 잘못된 경우 우리의 코드는 까악 까악 것을 보장한다. 이 수율 :

    201c 66 6f 75 72 20 73 63 6f 72 65 201d 
    

    또는 인쇄 :

    $ perl -CO -MEncode -e '$a=decode("utf-8", 
        "\xC3\xA2\xE2\x82\xAC\xC5\x93" . 
        "four score" . 
        "\xC3\xA2\xE2\x82\xAC\xC2\x9D"); 
        $a=encode("cp-1252", $a, sub { chr($_[0]) }); 
        $a=decode("utf-8", $a, 1); 
        print "$a\n"' 
    “four score” 
    

    그래서 나는 전체 알고리즘이되어야한다고 생각한다

    1. 잡아 MySQL의에서 바이트 스트림. 이것을 $ bytestream에 할당하십시오.
    2. $의 바이트 스트림이 유효한 UTF8 바이트 스트림 동안
    3. :
      1. 좋은 $
      2. $의 바이트 스트림은 모든 ASCII (즉, 모든 바이트 미만 0x80으로이다)이면, 휴식 시간에 $ 바이트 스트림의 현재의 값을 할당 이 "while ... valid utf8"루프에서.
      3. "demangle ($ bytestream)"의 결과에 $ bytestream을 설정합니다. demangle은 아래에 나와 있습니다. 이 루틴은이 데이터가 겪고있는 cp1252-utf8 변환기를 실행 취소합니다.
    4. 미확인이 아닌 경우 $ good을 데이터베이스에 넣습니다. $ good이 할당되지 않은 경우 $ bytestream이 cp1252 바이트 스트림이고이를 utf8로 변환한다고 가정합니다. 물론 2 단계의 루프가 아무 것도 바뀌지 않는 경우 최적화 및 수행하지 마십시오.

    .

    sub demangle { 
        my($a) = shift; 
        eval { # the non-string form of eval just traps exceptions 
         # so that we return undef on exception 
        local $SIG{__WARN__} = sub {}; # No warning messages 
        $a = decode("utf-8", $a, 1); 
        encode("cp-1252", $a, sub {$_[0] <= 255 or die $_[0]; chr($_[0])}); 
        } 
    } 
    

    이것은 그것이 정말 UTF-8이 아닌 모든 ASCII가 유효한 UTF-8 바이트 스트림이어야한다 문자열을 실제로 매우 드문 있다는 가정에 기초한다. 즉, 실수로 발생하는 일이 아닙니다.

    편집 됨 추가하려면 :이 기술은 불행하게도, 당신의 "밥의"예제를 너무 많이 도움이되지 않습니다

    참고. 나는 그 문자열이 cp1252-to-utf8 변환의 두 라운드를 거쳤다 고 생각하지만 불행히도 약간의 손상도있었습니다. 이전과 동일한 기술을 사용하여, 우리는 먼저 UTF8로 바이트 시퀀스를 읽고 우리가 얻을 유니 코드 문자 참조의 순서를 보면 : 그것은 너무 일이

    62 6f 62 ef bf bd 73 
    

    지금 :

    $ perl -CO -MEncode -e '$a=decode("utf-8", 
        "bob\xC3\xAF\xC2\xBF\xC2\xBDs"); 
        for $c (split(//,$a)) {printf "%x ",ord($c);}' | fmt 
    

    이 수율을 3 바이트 ef bf bd의 경우, unicode와 cp1252가 일치합니다. 따라서 cp1252에서이 유니 코드 코드 포인트 시퀀스를 나타내는 것은 다음과 같습니다.

    62 6f 62 ef bf bd 73 
    

    즉, 동일한 일련 번호입니다. 자,이 실제로 유효한 UTF-8 바이트 스트림이지만, 그것은 당신에게 놀랄 수에 디코딩 무엇 :

    의 UTF-8 바이트 스트림, 합법적 인 UTF-8 바이트 스트림하지만, 인코딩입니다
    $ perl -CO -MEncode -e '$a=decode("utf-8", 
        "bob\xC3\xAF\xC2\xBF\xC2\xBDs"); 
        $a=encode("cp-1252", $a, sub { chr(shift) }); 
        $a=decode("utf-8", $a, 1); 
        for $c (split(//,$a)) {printf "%x ",ord($c);}' | fmt 
    
    62 6f 62 fffd 73 
    

    문자 "0xFFFD"는 일반적으로 "번역 할 수없는 문자"에 사용됩니다. 나는 여기서 일어난 일이 최초의 * ~ utf8 변형이 인식하지 못한 문자를 보았고 그것을 "번역 할 수없는"것으로 바꾸 었다는 것을 의심한다. 프로그래밍 방식으로 원본 문자를 복구 할 방법이 없습니다.

    결과적으로 디코드를 수행 한 다음 0xFFFD를 찾는 것만으로 바이트 스트림이 유효한 utf8 (위의 알고리즘에 필요함)을 감지 할 수 없습니다. 대신 다음과 같이 사용해야합니다.

    sub is_valid_utf8 { 
        defined(eval { decode("utf-8", $_[0], 1) }) 
    }