Przykład "czterech wyników" prawie na pewno jest danymi zakodowanymi podwójnie. Wygląda na to, albo:
danych
- CP1252, który był prowadzony przez CP1252 do procesu utf8 dwukrotnie lub
- utf8 danych, który był prowadzony przez CP1252 do procesu utf8
(Naturalnie obu przypadkach wyglądają identycznie)
Teraz tego właśnie się spodziewałeś, więc dlaczego twój kod nie zadziałał?
Po pierwsze, chciałbym odnieść się do this table, która pokazuje konwersję z cp1252 do Unicode. Ważną rzeczą, którą chciałbym zauważyć, jest to, że istnieje kilka bajtów (takich jak 0x9D), które nie są prawidłowe w cp1252.
Kiedy wyobrażam sobie napisanie konwertera cp1252 do utf8, muszę zrobić coś z tymi bajtami, których nie ma w cp1252. Jedyną rozsądną rzeczą, jaką mogę wymyślić jest przekształcenie nieznanych bajtów w znaki Unicode o tej samej wartości. W rzeczywistości wydaje się, że to się stało. Weźmy przykład "cztery punkty" z powrotem o jeden krok na raz.
Po pierwsze, ponieważ jest on ważny UTF-8, niech dekodować z:
$ 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
Daje to ciąg punktów kodowych Unicode:
e2 20ac 153 66 6f 75 72 20 73 63 6f 72 65 e2 20ac 9d
("FMT" to komenda UNIX że po prostu formatujemy tekst tak, abyśmy mieli ładną linię z długimi danymi)
Teraz, przedstawmy każdy z nich jako bajt w cp1252, ale gdy znaku Unicode nie można przedstawić w cp1252, to ju st zamień go na bajt o tej samej wartości liczbowej. (Zamiast domyślnego, którym jest zastąpienie go znakiem zapytania) Powinniśmy wtedy, jeśli mamy rację co do tego, co stało się z danymi, mieć poprawny strumień bajtów 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
Trzeci argument do zakodowania - kiedy jest podrzędny - mówi, co zrobić z nieodkrywalnymi znakami.
Daje:
e2 80 9c 66 6f 75 72 20 73 63 6f 72 65 e2 80 9d
Teraz jest to prawidłowy strumień utf8 bajtów. Nie możesz tego stwierdzić przez inspekcję? Cóż, zapytać Perl zdekodować ten strumień bajtów jako 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
Przechodząc „1” jako trzeci argument do dekodowania zapewnia, że nasz kod będzie rechot jeśli strumień bajtów jest nieprawidłowy. Daje to:
201c 66 6f 75 72 20 73 63 6f 72 65 201d
lub drukowanych:
$ 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”
Więc myślę, że pełny algorytm powinien być tak:
- Grab strumień bajtów z mysql. Przypisz to do $ bytestream.
- Podczas $ bytestream jest prawidłowy strumień utf8 bajt:
- Przypisanie bieżącej wartości $ bytestream do $ dobry
- Jeśli $ bytestream jest all-ASCII (czyli każdy bajt jest mniej niż 0x80), przerwa z tej pętli "while ... valid utf8".
- Ustaw $ bytestream na wynik "demangle ($ bytestream)", gdzie demangle podano poniżej. Ta procedura rozwiązuje konwerter cp1252-to-utf8, który, jak sądzimy, ucierpiał z powodu tych danych.
- Umieść $ good z powrotem w bazie danych, jeśli nie jest to niepotrzebne. Jeśli $ good nigdy nie zostało przypisane, załóżmy, że $ bytestream było strumieniem bajtów cp1252 i przekonwertowało go na utf8. (Oczywiście, zoptymalizuj i nie rób tego, jeśli pętla w kroku 2 niczego nie zmieniła, itp.)
.
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])});
}
}
ta opiera się na założeniu, że jest to w rzeczywistości bardzo rzadko zdarza się, że ciąg nie jest all-ASCII jest poprawnym UTF-8 bajtów strumienia chyba że naprawdę jest UTF-8. To znaczy, nie jest to coś, co dzieje się przypadkowo.
edytowane ADD:
pamiętać, że ta technika nie pomaga zbytnio swoim przykładzie „Boba”, niestety. Myślę, że ten ciąg również przeszedł dwie rundy konwersji cp1252-to-utf8, ale niestety było również pewne uszkodzenie. Stosując tę samą technikę, jak poprzednio, najpierw odczytać sekwencję bajtów jako utf8 i spojrzeć na sekwencji Unicode referencji znakowych otrzymujemy:
$ perl -CO -MEncode -e '$a=decode("utf-8",
"bob\xC3\xAF\xC2\xBF\xC2\xBDs");
for $c (split(//,$a)) {printf "%x ",ord($c);}' | fmt
Daje:
62 6f 62 ef bf bd 73
Teraz, po prostu tak się dzieje dla trzech bajtów ef bf bd zgadzają się unicode i cp1252. Zatem reprezentowanie tej sekwencji punktów kodu unicode w cp1252 jest po prostu:
62 6f 62 ef bf bd 73
To jest ta sama sekwencja cyfr. Teraz, to jest w istocie ważny UTF-8 bajtów strumienia, ale co to dekoduje się może cię zaskoczyć:
$ 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
Oznacza to, że bajt strumień UTF-8, chociaż uzasadnionego UTF-8 bajtów strumienia, zakodowany znak 0xFFFD, który jest ogólnie używany dla "nieprzetłumaczalnego znaku". Podejrzewam, że to, co się tutaj wydarzyło, to to, że pierwsza transformacja * -to-utf8 zobaczyła postać, której nie rozpoznała i zastąpiła ją "nieprzetłumaczalną". Nie ma sposobu, aby programowo odzyskać oryginalną postać.
Konsekwencją jest to, że nie można wykryć, czy strumień bajtów jest poprawny utf8 (potrzebne dla tego algorytmu, który podałem powyżej) po prostu przez dekodowanie, a następnie szukanie 0xFFFD. Zamiast tego powinieneś użyć czegoś takiego:
sub is_valid_utf8 {
defined(eval { decode("utf-8", $_[0], 1) })
}