Istnieją dwa etapy przetwarzania tekstu Unicode. Pierwszym z nich jest „jak mogę go wprowadzić i wyprowadzić bez utraty informacji”. Drugi to „jak traktować tekst zgodnie z lokalnymi konwencjami językowymi”.
Post tchrista obejmuje oba te elementy, ale druga część to 99% tekstu jego postu. Większość programów nawet nie obsługuje poprawnie I / O, dlatego ważne jest, aby zrozumieć, że zanim zaczniesz martwić się o normalizację i zestawianie.
Ten post ma na celu rozwiązanie tego pierwszego problemu
Kiedy wczytujesz dane do Perla, nie ma znaczenia, jakie to kodowanie. Przydziela część pamięci i chowa tam bajty. Jeśli powieszprint $str
, po prostu przenosi te bajty do twojego terminala, który prawdopodobnie jest ustawiony tak, aby zakładać, że wszystko, co jest w nim zapisane, to UTF-8, a twój tekst się pojawi.
Cudowny.
Tyle że nie. Jeśli spróbujesz traktować dane jako tekst, zobaczysz, że dzieje się coś złego. Nie musisz iść dalej niż length
zobaczyć, co Perl myśli o twoim sznurku i co myślisz o sznurku się nie zgadza. Napisz jedno linijkę, taką jak: perl -E 'while(<>){ chomp; say length }'
i wpisz, 文字化け
a otrzymasz 12 ... nieprawidłowa odpowiedź, 4.
To dlatego, że Perl zakłada, że twój ciąg nie jest tekstem. Musisz powiedzieć, że to tekst, zanim da ci właściwą odpowiedź.
To dość łatwe; moduł Encode ma do tego odpowiednie funkcje. Ogólny punkt wejścia to Encode::decode
(lubuse Encode qw(decode)
oczywiście). Ta funkcja pobiera ciąg znaków ze świata zewnętrznego (to, co nazwiemy „oktetami”, wymyślny sposób na powiedzenie „8-bitowych bajtów”), i zamienia go w tekst, który Perl zrozumie. Pierwszy argument to nazwa kodująca znak, na przykład „UTF-8” lub „ASCII” lub „EUC-JP”. Drugi argument to ciąg. Zwracana wartość to skalar Perl zawierający tekst.
(Jest też Encode::decode_utf8
, który zakłada kodowanie UTF-8).
Jeśli przepiszemy jedną linijkę:
perl -MEncode=decode -E 'while(<>){ chomp; say length decode("UTF-8", $_) }'
Wpisujemy 文字 化 け i otrzymujemy „4” jako wynik. Sukces.
To właśnie jest rozwiązanie 99% problemów z Unicode w Perlu.
Kluczem jest to, że za każdym razem, gdy jakiś tekst pojawia się w twoim programie, musisz go odkodować. Internet nie może przesyłać znaków. Pliki nie mogą przechowywać znaków. W bazie danych nie ma znaków. Są tylko oktety i nie można traktować oktetów jako znaków w Perlu. Musisz zdekodować zakodowane oktety na znaki Perla za pomocą modułu Encode.
Druga połowa problemu to pobieranie danych z programu. To łatwe; po prostu mówiszuse Encode qw(encode)
, zdecyduj, w jakim kodowaniu będą twoje dane (UTF-8 do terminali, które rozumieją UTF-8, UTF-16 dla plików w systemie Windows itp.), a następnie wypisz wynik encode($encoding, $data)
zamiast po prostu wypisywać $data
.
Ta operacja przekształca znaki Perla, na których działa Twój program, w oktety, które mogą być używane przez świat zewnętrzny. Byłoby o wiele łatwiej, gdybyśmy mogli po prostu wysyłać znaki przez Internet lub do naszych terminali, ale nie możemy: tylko oktety. Musimy więc przekonwertować znaki na oktety, w przeciwnym razie wyniki nie zostaną zdefiniowane.
Podsumowując: zakoduj wszystkie wyjścia i odkoduj wszystkie wejścia.
Teraz porozmawiamy o trzech kwestiach, które sprawiają, że jest to trochę trudne. Pierwsza to biblioteki. Czy poprawnie obsługują tekst? Odpowiedź brzmi ... próbują. Jeśli pobierzesz stronę internetową, LWP zwróci ci wynik jako tekst. Jeśli wywołasz odpowiednią metodę w wyniku, to znaczy (i tak się składa decoded_content
, że niecontent
, to tylko strumień oktetów, który otrzymał z serwera). Sterowniki bazy danych mogą być niestabilne; jeśli użyjesz DBD :: SQLite tylko z Perlem, to zadziała, ale jeśli jakieś inne narzędzie umieściło w bazie danych tekst zapisany jako kodowanie inne niż UTF-8 ... cóż ... to nie będzie poprawnie obsługiwane dopóki nie napiszesz kodu, aby poprawnie go obsłużyć.
Wyprowadzanie danych jest zwykle łatwiejsze, ale jeśli widzisz „szeroki znak w druku”, to wiesz, że gdzieś psujesz kodowanie. To ostrzeżenie oznacza „hej, próbujesz wyciec postacie Perla do świata zewnętrznego i to nie ma żadnego sensu”. Twój program wydaje się działać (ponieważ drugi koniec zwykle poprawnie obsługuje nieprzetworzone znaki Perla), ale jest bardzo zepsuty i może przestać działać w dowolnym momencie. Napraw to wyraźnie Encode::encode
!
Drugi problem to kod źródłowy zakodowany w UTF-8. O ile nie powiesz use utf8
na górze każdego pliku, Perl nie przyjmie, że kod źródłowy to UTF-8. Oznacza to, że za każdym razem, gdy mówisz coś takiego my $var = 'ほげ'
, wstrzykujesz śmieci do swojego programu, który całkowicie psuje wszystko okropnie. Nie musisz „używać utf8”, ale jeśli nie, to należy nie używać żadnych znaków spoza ASCII w swoim programie.
Trzeci problem dotyczy tego, jak Perl radzi sobie z przeszłością. Dawno temu nie było czegoś takiego jak Unicode, a Perl założył, że wszystko jest tekstem Latin-1 lub binarnym. Więc kiedy dane przychodzą do twojego programu i zaczynasz traktować je jak tekst, Perl traktuje każdy oktet jako znak Latin-1. Właśnie dlatego, gdy poprosiliśmy o długość „文字 化 け”, otrzymaliśmy 12. Perl założył, że działamy na łańcuchu Latin-1 „æååã” (który ma 12 znaków, z których niektóre nie są drukowane).
Nazywa się to „niejawnym uaktualnieniem” i jest to całkowicie rozsądne, ale nie jest to pożądane, jeśli tekst nie jest w języku łacińskim-1. Dlatego tak ważne jest jawne odkodowanie danych wejściowych: jeśli tego nie zrobisz, Perl zrobi to i może zrobić to źle.
Ludzie wpadają w kłopoty, gdy połowa ich danych to ciąg znaków, a niektóre nadal są binarne. Perl zinterpretuje część, która wciąż jest binarna, tak jakby to był tekst Latin-1, a następnie połączy ją z poprawnymi danymi znakowymi. To sprawi, że będzie wyglądało na to, że prawidłowe zarządzanie postaciami zepsuło twój program, ale w rzeczywistości po prostu nie naprawiłeś go wystarczająco.
Oto przykład: masz program, który czyta plik tekstowy zakodowany w UTF-8, wstawiasz Unicode PILE OF POO
do każdej linii i drukujesz go. Piszecie tak:
while(<>){
chomp;
say "$_ 💩";
}
Następnie uruchom niektóre dane zakodowane w UTF-8, takie jak:
perl poo.pl input-data.txt
Drukuje dane UTF-8 ze kupką na końcu każdej linii. Idealnie, mój program działa!
Ale nie, po prostu robisz binarną konkatenację. Czytasz oktety z pliku, usuwasz \n
z chomp, a następnie dodajesz bajty w reprezentacji PILE OF POO
znaku UTF-8 . Kiedy poprawisz swój program, aby zdekodować dane z pliku i zakodować dane wyjściowe, zauważysz, że zamiast śmieci kupujesz śmieci („ð ©”). Doprowadzi cię to do przekonania, że dekodowanie pliku wejściowego jest niewłaściwe. To nie jest.
Problem polega na tym, że kupa jest domyślnie aktualizowana jako latin-1. Jeśli use utf8
utworzysz dosłowny tekst zamiast binarny, to znowu zadziała!
(To jest problem numer jeden, który widzę, gdy pomagam ludziom z Unicode. Rozstali się dobrze i to zepsuło ich program. To jest smutne z powodu nieokreślonych wyników: możesz mieć działający program przez długi czas, ale kiedy zaczniesz go naprawiać, psuje się. Nie martw się; jeśli dodajesz do swojego programu instrukcje kodowania / dekodowania i psuje się, oznacza to, że masz więcej pracy do zrobienia. Następnym razem, kiedy projektujesz z myślą o Unicode od samego początku, będzie to dużo łatwiej!)
To naprawdę wszystko, co musisz wiedzieć o Perlu i Unicode. Jeśli powiesz Perlowi, jakie są twoje dane, ma najlepszą obsługę Unicode spośród wszystkich popularnych języków programowania. Jeśli zakładasz, że magicznie będzie wiedział, jaki rodzaj tekstu podajesz, to nieodwracalnie usuniesz swoje dane. To, że Twój program działa dzisiaj na terminalu UTF-8, nie oznacza, że będzie działać jutro na pliku zakodowanym w UTF-16. Więc teraz to bezpieczne i oszczędzaj sobie kłopotów z usuwaniem danych użytkowników!
Łatwą częścią obsługi Unicode jest kodowanie danych wyjściowych i dekodowanie danych wejściowych. Trudność polega na znalezieniu wszystkich danych wejściowych i wyjściowych oraz ustaleniu, jakie to kodowanie. Ale dlatego dostajesz duże pieniądze :)