Unicode-spracovanie problémy v Perl a ako sa s tým vyrovnať

link: http://ahinea.com/en/tech/perl-unicode-struggle.html

Banda perldoc manpages načrtnúť a vysvetliť Ktorá je podpora unicode. perluniintro, perlunicode, Encode modul, binmode() funkcie. A zoznam nie je kompletný. Hlavný problém s týmto dokumentom je jej objem. Väčšina programátorov ani nemusíte čítať to všetko, pretože ak chcete začať pracovať s Unicode stačí vedieť niektoré základné fakty a pravidlá.

Čo som zažil niekoľko druhov problémy s Unicode v Perl, na viacerých projektoch. Dva hlavné problémy, ktoré som videl, sú:

  • UTF-8 dáta dostať dvakrát kódovaný alebo iné kódovanie dát dostať skomolené
  • “Wide character in print” warning(“Wide character v tlači”) varovanie

Tieto dva problémy sú úzko prepojené a často rieši podobné pohyby.

Čítanie alebo aspoň prehliadanie  related manpages je stále dobrý spôsob, ako pochopiť a vyriešiť váš Unicode problémy. Ak nemáte čas na to teraz, čítajte ďalej.

Problém  showcase (predviesť): príklad

Predstavte si dve jednoduché premenné s Unicode text v ňom. Chcete vytlačiť týchto premenných na štandardný výstup. Čo môže byť jednoduchšie?.

#!/usr/bin/perl

my $ustring1 = "Hello \x{263A}!\n";  
my $ustring2 = <DATA>;

print "$ustring1$ustring2";
__DATA__
Hello ☺!

source

Obe premenné tu obsahujú rovnaké údaje: reťazec "Hello ", " nasleduje znak Unicode WHITE SMILING FACE U+263A, výkričník a nový-line charakter. __DATA__ časť ($ustring2) je v UTF-8 kódovaní.

Ale keď sme tlač, prvý vyjde v poriadku a druhá príde skomolené. Je to preto, Perl vie, že prvý reťazec je reťazec Unicode, a je vnútorne uložené v UTF-8. Ale to nevie kódovanie druhého. Keď ho stavia väčšie reťazec pre tlač, to re-kóduje druhý do UTF-8, nesprávne.

Okrem toho, že vypíše upozornenie: Wide character in print at unitest1.pl line 6, <DATA> line 1.Pozrieme sa na to  later,, po tom, čo sme opraviť svoj výstup.

Mohli by ste zrejme opraviť veci tým, že sa zabráni zreťazenie:

#!/usr/bin/perl

my $ustring1 = "Hello \x{263A}!\n";  
my $ustring2 = <DATA>;

print $ustring1, $ustring2;
__DATA__
Hello ☺!

source

Ale to nie je riešenie. Niekedy si jednoducho nemôže vyhnúť zreťazenie; to je taká základná prevádzka. Okrem toho, že je náchylné a nie budúcnosť dôkaz.

Prečo sa problém stane

Po prvé, niektoré základné fakty.

Existuje rozdiel medzi bytov a znaky. Znaky sú znaky Unicode. Jeden znak, môže byť zastúpený niekoľkých bajtov, keď uložené, vytlačené alebo odoslané prostredníctvom siete. Ako presne znak je prevedený do bytov závisí na kódovanie používa. UTF-8 je len jeden zo spôsobov, ako urobiť predstavujú znaky Unicode.

Perl má “utf8” vlajky pre každý skalárnym hodnotu, ktorá môže byť “on” alebo “off”. “O” štátnej vlajky hovorí, perl na liečbu hodnotu ako reťazec Unicode znaky. Inak, je to len banda bajtov.

Ak budete mať reťazec s utf8 vlajka off a zřetězit to s reťazec, ktorý má utf8 vlajkou, ktorá konvertuje prvé Unicode.

Môže to znieť v poriadku a zrejmé. Ale potom si myslíte, že: Ako? Perl bude vedieť, kódovanie údajov string pred konverziou. A perl bude snažiť hádať to. A to je zvyčajný zdrojom problémov.

Algoritmus perl používa, keď sa hádať, je zdokumentované (používa niektoré predvolené a možno kontroly miestne), ale moja firma návrh je: nikdy nedovoľte, aby perl urobiť. Inak, existuje VEĽKÁ šanca, že budete mať dvakrát kódované v UTF-8 reťazce, alebo inak nesprávnosť údajov.

Riešením: vždy sa údaje kódovanie explicitné, ako pre vstup a výstup.

Riešenie #1: Previesť reťazec Unicode

Jedno riešenie by sa mohlo povedať, perl, že $ustring2 obsahuje údaje vo formáte Unicode UTF-8 kódovaní. Existuje niekoľko spôsobov, ako to urobiť; ortodoxný spôsob je cez Kódovanie decode_utf8() funkcie:

#!/usr/bin/perl

use Encode;
my $ustring1 = "Hello \x{263A}!\n";  
my $ustring2 = <DATA>;
$ustring2 = decode_utf8( $ustring2 );

print "$ustring1$ustring2";
__DATA__
Hello ☺!

source

V tomto jednoduchom prípade oboch spôsobov, ako by sa robiť svoju prácu, ale môže dostať dosť únavné, ak vaše vstupy sú bohaté. A stále tlačí “Wide character” varovanie.

Ale to je to, čo vždy by ste mali urobiť pre medzinárodné údaje dostanete od iných Perl moduly, ako z databáz.

Nemali by ste zabudnúť, napriek tomu, že nie každá postupnosť bajtov, ktoré je platné UTF-8. Takže decode_utf8() operácia môže zlyhať. Pozri Encode perldoc pre spracovanie chýb detaily.

(Ďalší spôsob ako to urobiť, nech perl prijať UTF-8 dáta, ako je to napríklad s  pack “U0C*”, unpack “C*” hack. Ale asi nemali robiť, že.)

Ak ste si dáta v inom kódovaní (nie UTF-8), previesť na Unicode explicitne. Opäť, Kódovanie modul,  decode() funkcie:

require Encode;
my $ustring = Encode::decode( 'iso-8859-1', $input );

Iný príklad: UTF-8 dáta z CGI

V ACIS vyrábame HTML stránky v UTF-8. Očakávame, že v HTML forme textu ak chcete byť v UTF-8 rovnako. Manipulovať to, by sme povedať, perl o kódovanie:

require Encode;
require CGI;
my $query = CGI ->new;
my $form_input = {};  
foreach my $name ( $query ->param ) {
  my @val = $query ->param( $name );
  foreach ( @val ) {
    $_ = Encode::decode_utf8( $_ );
  }
  $name = Encode::decode_utf8( $name );
  if ( scalar @val == 1 ) {   
    $form_input ->{$name} = $val[0];
  } else {                      
    $form_input ->{$name} = \@val;  # save value as an array ref
  }
}

To stavia ready – a bezpečné na použitie hash vstupných parametrov.

Riešenie #2: Určte IO kódovanie vrstvy pre váš filehandles

Počnúc verziou 5.8 v Perl a filehandle môže mať kódovanie špecifikované za to. Perl potom bude previesť všetky vstup zo súboru automaticky do jeho vnútorného kódovanie Unicode. To bude znamenať hodnoty čítať z nej podľa utf8 vlajky. Rovnako, perl možno previesť výstup na špecifické kódovanie pre filehandle. Okrem toho, perl kontroly že dáta, ktoré ste výstup je platný pre filehandle kódovanie.

Takže, ak budete čítať údaje zo súboru alebo iného vstupného prúdu, a môžete očakávať, že UTF-8 dáta tam, varovať perl:

if ( open( FILE, "<:utf8", $fname ) ) {
  . . . 
}

alebo, v prípade našej jednoduchý test,

#!/usr/bin/perl

my $ustring1 = "Hello \x{263A}!\n";  
binmode DATA, ":utf8";
my $ustring2 = <DATA>;

print "$ustring1$ustring2";
__DATA__
Hello ☺!

source

To by malo tlačiť dva rovnaké riadky, ale to by ešte urobiť nepríjemné varovanie. Je to preto, lebo sme stále tlač unicode-obsahujúce hodnotu popisovač súboru, ktorý nie je pripravený na to, že: STDOUT. (A to sa stane, implicitne, pretože tlačiť vytlačí sa štandardne.) Skok tu vidieť fix pre varovanie práve teraz.

Podobne, ak máte otvorený súbor, ako:

open FILE, "<:encoding(iso-8859-7)", $filename;

je to obsah sa predpokladá, že bude v iso-8859-7 kódovanie. Perl bude používať tento súbor interpretuje údaje správne, t.j. previesť do vnútorného UTF-8.

(Tu a nižšie, ISO-8859-7 kódovanie je len príklad. Akékoľvek perl-podporované kódovania, ktoré môžu byť použité.)

Riešenie #3: Global Unicode nastavenie v Perl

A je ešte jeden spôsob, ako pristupovať k vašej kódovanie/kódovanie problémy. To je príkaz perl na liečbu všetkých váš program vstup a výstup, ako používali UTF-8. -C je perl spínač, ktorý vám umožní urobiť. Stačí dať -CS na perl príkazového riadku.

Prípadne použite PERL_UNICODE premennej prostredia. To musí byť nastavený v prostredí, kde môžete vykonať perl, napríklad:

[email protected]:~$ PERL_UNICODE=S perl script.pl

By príkaz perl predpokladať, UTF-8 vo všetkých vstupných a výstupných filehandles v skriptu a použité moduly, v predvolenom nastavení. (Žiaľ, a na rozdiel od mojich očakávaní to nemá vplyv na špeciálne DATA filehandle. Takže to nie je riešenie nášho problému predviesť skriptu.)

Môžete tiež zadať UTF-8-ness len pre vaše stdin, alebo len stdout, alebo len stderr. Prečítajte si sekciu na -C  v perlrun podrobnosti.

Široký charakteru v tlačenej varovanie (Wide character in print warning)

Varovanie sa stane, keď si výstup Unicode reťazec non-unicode filehandle. Čo je “non-unicode filehandle?”, sa pýtate. To je jeden s č unicode-kompatibilné IO vrstva na to (pozri Riešenie #2 oddiel vyššie.)

Správny spôsob, ako vyriešiť tento problém je určiť výstupný kódovanie explicitne, s binmode() funkcia alebo v otvorených (volať). Napríklad, otvorte súbor týmto spôsobom:

open FILE, ">:utf8", $filename;

Ak chcete tlačiť UTF-8 na štandardný výstup (alebo štandardná chyba), ako v našom prípade, môžeme urobiť:

#!/usr/bin/perl

my $ustring1 = "Hello \x{263A}!\n";  
binmode DATA, ":utf8";
my $ustring2 = <DATA>;
binmode STDOUT, ":utf8";
print "$ustring1$ustring2";
__DATA__
Hello ☺!

source

Teraz, že by mal konečne tlač dva rovnaké riadky (správne) a produkovať žiadne varovanie!

Nesprávny spôsob, ako sa vyhnúť varovanie vypnutie utf8 vlajky na vašom k-byť-tlačené údaje. Potom znaky, ktoré sa bude zase do bytov, a perl bude tlačiť ich do bytov-filehandle hladko. Ale nemusíte to, naozaj.

Na druhej strane, ak máte otvorený súbor, ako:

open FILE, ">:encoding(iso-8859-7)", $filename;

veci, ktoré tlače bude mať výstup v iso-8859-7 kódovanie, transcoded automaticky. ISO-8859-7 nie je Unicode-kompatibilné charset, takže nebudete môcť výstup znaky Unicode sa na to bez varovania.

Správnu stratégiu: zhrnutie

Ak môžete, použite kódovanie Unicode (ako UTF-8) na ukladanie a spracovanie vašich údajov. Vždy, uistite sa, perl vie, ktoré kódovanie vaše údaje príde a prísť. Uistite sa, že všetky vaše Unicode-obsahujúce scalars, utf8 vlajkou. Potom môžete bezpečne zřetězit reťazce. Potom môžete použiť Unicode-súvisiace regulárne výrazy, ktoré vám dáva veľké právomoci pre medzinárodné (multi-language) na spracovanie textov.

Na dosiahnutie tohto cieľa, budete musieť poznať všetky spôsoby údaje sa dostane do vášho programu. Hneď, ako si získať niektoré vstupné, označiť ju ako Unicode, alebo ho previesť na Unicode a dobre spať.

Niekedy údaje prichádza do svojho programu už v Unicode a nemali by ste sa ničoho báť. Napríklad, XML symboly vráti reťazec hodnoty s utf8 flag “na”. (Pokiaľ vás niečo podivné, ako sa dostať ho v pôvodnej podobe z parser, ktoré by ste nemali robiť rovnako.) Vo vyššie uvedenom príklade sme explicitne zahrnúť znakov unicode na reťazec ($ustring1) a perl vie, jeho kódovanie.

Ale keď budete čítať údaje z vstupné prúdy, z databázy alebo z premenné prostredia (ako parametre v CGI), budete musieť určiť, perl o svoje kódovanie.

Použitie PERL_UNICODE premennej prostredia do platnosti UTF-8 IO vrstiev na váš vstup, a/alebo výstupný filehandles.