Newsgroup: it.comp.lang.perl
Titolo: Re: STDIN in UTF-8... Che succede?

     read( STDIN, $buffer, $ENV{'CONTENT_LENGTH'} );
Mi sa che l'inghippo è qui: $ENV{CONTENT_LENGTH} è la lunghezza del 
message-body in *byte*, mentre il terzo parametro di read() è 
interpretato come numero di *caratteri*, che ovviamente non coincide con 
i byte, se prima hai impostato ':utf8' come layer di encoding di STDIN 
(o meglio, coinciderebbe solo nel caso avessi in input esclusivamente 
codici ASCII puri).
E' una monnezzata pazzesca (da non usare mai e poi mai in produzione) 
ma, giusto per toglierci lo sfizio (ora sono curioso anche io) potresti 
provare:
binmode STDIN, ':utf8';
read( STDIN, $buffer, $ENV{CONTENT_LENGTH}/2 );
Ho diviso per 2 perché ho supposto che tu abbia solo lettere latine (o 
greche, cirilliche etc.), altrimenti dividi per 3 o per 4 se hai altri 
piani di Unicode: insomma, lo sai *molto* meglio di me ;-)
Giusto per provare: fammi sapere quanto marcio è il mio torto ;-)
***
Invece, messo da parte il cazzeggio, la soluzione seria e corretta direi 
che è proprio quella che hai scritto tu nell'altro messaggio: leggi 
*sempre* sotto il layer ':bytes' (perché essendo $ENV{CONTENT_LENGTH} il 
numero di byte non puoi fare diversamente), e poi usi:
require Encode;
my $utf8_buffer = Encode::decode('utf8', $buffer)
potendo utilizzare $buffer (inalterato) per salvare successivamente in 
XML, e invece $utf8_buffer per le altre elaborazioni senza passare per 
il file.
Leggere STDIN direttamente in utf8 direi che bisogna proprio 
rinunciarvi, data la semantica di $ENV{CONTENT_LENGTH}; del resto mi 
sembra di ricordare che anche CGI.pm faccia così.
E del resto mi sembra che anche a te così già funzioni... ;-)
Fammi sapere.
Emanuele.
-- 
What does VISTA mean?
Viruses, Infections, Spyware, Trojan horses & Adware.

 Fammi sapere.
Pare che l'unica (parliamo sempre dello specifico caso di input in XML)
sia effettuare il parsing con XMLin() e *poi* effettuare il crawling
ricorsivo dell'albero e -su tutte le foglie- usare utf8::decode() :-(
Ty!
-- 
Splendente in età acerba di passione
rosso fiammante,
ma senza età matura
marcia impostura

 ricorsivo dell'albero e -su tutte le foglie- usare utf8::decode() :-(
E infatti...
# Da Perl lo leggo cosě:
    my $buffer;
    read( STDIN, $buffer, $ENV{'CONTENT_LENGTH'} );
# Lo passo in un XML:
    my $xml = new XML::Simple(
        RootName        => 'root',
        NumericEscape   => 0,
        XMLDecl         => "<?xml version='1.0' encoding='utf-8'?>",
        KeyAttr         => []
    );
    my $data = $xml->XMLin( $buffer );
# e poi, in place:
    blessInputXmlUtf( $data);
# dove:
sub blessInputXmlUtf {
    my $d = shift;
    my $r = ref $d;
    if ($r eq 'ARRAY') {
        foreach my $i (@d) {
            if (ref $i) {
                blessInputXmlUtf( $i );
            } else {
                utf8::decode( $i );
            }
        }
    } elsif ($r eq 'HASH') {
        foreach my $k (keys %$d) {
            if (ref $d->{$k}) {
                blessInputXmlUtf( $d->{$k} );
            } else {
                utf8::decode( $d->{$k} );
            }
        }
    } elsif ($r eq 'SCALAR') {
        utf8::decode( $$d );
    }
}
## Spero che serva a qualcun altro, io ci sono impazzito :-D
-- 
Oramai che i baci tuoi piu` non mi sazian
cerchero` un'altra donna dentro ai sogni di Tarzan

 ricorsivo dell'albero e -su tutte le foglie- usare utf8::decode() :-(
E infatti...
# Da Perl lo leggo cosě:
    my $buffer;
    read( STDIN, $buffer, $ENV{'CONTENT_LENGTH'} );
# Lo passo in un XML:
    my $xml = new XML::Simple(
        RootName        => 'root',
        NumericEscape   => 0,
        XMLDecl         => "<?xml version='1.0' encoding='utf-8'?>",
        KeyAttr         => []
    );
    my $data = $xml->XMLin( $buffer );
# e poi, in place:
    blessInputXmlUtf( $data);
# dove:
sub blessInputXmlUtf {
    my $d = shift;
    my $r = ref $d;
    if ($r eq 'ARRAY') {
        foreach my $i (@$d) {


 avrai intuito il perché non ci provo neanche... :-D
Francamente no... E difatti la prova te la faccio io ;-)
Preciso che in effetti il tuo problema forse non era quello, ma non 
perché leggere caratteri o byte sia la stessa cosa, bensì soltanto 
perché il tuo message-body *probabilmente* termina con un eof, per cui 
se provi a leggere mediante read() più byte di quanti ne siano 
disponibili su STDIN, non succede niente.
...Ma se il problema non era quello, in ogni caso ne hai (almeno) un 
altro, perché possono tranquillamente esserci degli extension-data dopo 
il request-body, per cui leggendo più byte di quanti dovresti (ne leggi 
1, 2 o 3 in più per ogni carattere esteso del message-body) vai a 
leggerti anche dei byte degli extension-data, mettendoli erroneamente 
nel message-body, e per giunta mutilando pure gli extension-data medesimi.
Difatti la rfc 3875 dice esplicitamente:
<<
The server MAY signal an end-of-file
condition after CONTENT_LENGTH bytes have been read or it MAY supply
extension data. Therefore, the script MUST NOT attempt to read more
than CONTENT_LENGTH bytes, even if more data is available.
                     ^^^^^
 >>
E preciso che le lettere maiuscole non le ho messe io ;-)
http://www.ietf.org/rfc/rfc3875
Comunque facciamo 'sta prova, che del resto è assolutamente ovvia e banale.
Uno script che stampa su STDOUT:
------------------------------------------------------------------------
# FILE: 
server.pl
# Il content-body contiene 4 byte (quindi CONTENT_LENGTH == 4)
# ma si tratta *solo* di 2 caratteri utf8 (2 smile).
my $content_body = "\x{263A}" x 2;
# Gli extension-data contengono altri 4 byte (2 car. estesi).
my $extension_data = "\x{26}\x{DF}\x{26}\x{DD}";
binmode STDOUT, ':utf8'; # Per sopprimere il warning.
print $content_body . $extension_data;
------------------------------------------------------------------------
Ed uno che legge da STDIN in layer utf8:
------------------------------------------------------------------------
# FILE: 
cgi_script.pl
my $content_length = 4;
binmode STDIN, ':utf8';
read STDIN, my $buffer, $content_length;
# CONTENT_LENGTH e` uguale a 4 byte, ma cosi` ho letto
# 4 caratteri, che in questo caso sono 8 byte!
binmode STDOUT, ':utf8'; # Per sopprimere il warning.
print "$buffer\n";
------------------------------------------------------------------------
Eseguiamoli connettendoli mediante una pipe:
perl 
server.pl
 | perl 
cgi_script.pl
e se volgiamo redirigiamo l'output su file:
perl 
server.pl
 | perl 
cgi_script.pl
 > dati_letti.txt
e vediamo che vengono stampati i 2 smile *più* 2 caratteri "spuri", 
perché appunto ho letto 4 caratteri (estesi) che sono 8 byte, e non 4 
byte, come avrei dovuto!
Ora dimezziamo CONTENT_LENGTH (come avevo suggerito di provare ;-) 
mettendo in 
cgi_script.pl
my $content_length = 2;
rieseguimao il comando di cui sopra, et-voilà, abbiamo soltanto i 2 
smile che volevamo :-) :-)
Ripeto, non so se fosse questo il tuo problema ma, come vedi, utilizzare 
$ENV{CONTENT_LENGTH} come parametro di read() in layer utf8 è comunque 
sbagliato, perché ti porta, in generale, a leggere più byte di quanti 
dovresti.
Per il resto l'altro suggerimento che avanzavo a me funziona 
perfettamente e, a dire il vero, mi sembra pure confermato dal tuo 
ultimo messaggio, per cui davvero non capisco...
Comunque, tant'è ;-)
Ciao,


 my $extension_data = "\x{26}\x{DF}\x{26}\x{DD}";
Mi scuso, questi non sono 2 caratteri estesi, ma è del tutto 
ininfluente: è sufficiente che ci siano dei byte (random) aggiuntivi.


 allora mi sa che non ti capisco più io... come dici di leggere STDIN?
Necessariamente in bytes (che in effetti è quello che facevi nel tuo 
primissimo esempio, e che hai fatto nell'ultimo) e non in utf8, perché 
non conosci il numero di caratteri da leggere, ma solo il numero di byte.
Nella forma più stringata possibile, mettendoci pure la decodifica utf8 
secondo me equivalente a quella di XML::Simple, sarebbe così:
# FILE: 
cgi_script.pl
read STDIN, my $buffer, 10; # Leggo 10 byte.
use Encode; my $utf8_buffer = Encode::decode_utf8($buffer);
# Ora $utf8_buffer ha il flag utf8 settato. Si puo` verificare con:
# print "OK!\n" if Encode::is_utf8($utf8_buffer);
binmode STDOUT, ':utf8';
print $utf8_buffer;
Ora, se ad esempio sparo a questo snippet 2 smile intercalati da 3 
caratteri ASCII e con un newline finale per un totale di 10 byte(*), li 
legge alla perfezione, senza alcuna alterazione né errore né warning:
perl -e 'use bytes; print "a\x{263A}b\x{263A}c\n"' | perl 
cgi_script.pl
Quindi alla fine abbiamo che $utf8_buffer :
1. ha i byte UTF-8 corretti;
2. ha il flag utf8 settato.
Per cui sarei portato a concludere che "non può non funzionare"™® ;-)
Non riuscendo peraltro a vedere cosa XML::Simple faccia che questo 
banale snippet non faccia.
Cioè in pratica sembra che XML::Simple faccia soltanto utf8::decode(), 
ma allora lo snippet dovrebbe essere equivalente, quindi deve esserci 
qualcos'altro: se riuscissi ad isolarlo, sarebbe interessante.
Ciao,
Emanuele.
(*) Sono un cane: nel mio messaggio precedente ho detto che il carattere 
smile (code-point Unicode 0x263A) occupava 2 byte: e` falso, la sua 
codifica UTF-8 occupa *3 byte*.
Questo non inficia minimamente gli esempi precedenti (perché leggevo 
soltanto caratteri e non byte), ma ciò non toglie che io sono un cane.


 Sempre un errore alla riga 186 di Encode.pm
Per evitare di ripetere ottusamente che secondo me questo codice 
dovrebbe funzionare, tenendo conto delle tue indicazioni, mi sono messo 
di buzzo buono per riprodurre il tuo errore di Encode.pm, e finalmente 
eccolo qui:
	use Encode;
	my $s = "\x{263A}" . chr 0b11101111;
	my $s_utf8 = Encode::decode_utf8($s);
$ Cannot decode string with wide characters at Encode.pm line 166.
(A parte il numero di riga, che però potrebbe anche dipendere dal fatto 
che abbiamo versioni diverse di Encode.pm)
Che (in quest'esempio) accade perché ho messo a fine stringa un byte 
(0b11101111) che costituisce il primo byte di una codifica UTF-8 
multibyte (in questo caso a 3 byte).
Leggendolo, Encode chiaramente si aspetta di trovare subito dopo i 2 
byte successivi del carattere UTF-8, e invece non li trova.
Insomma Encode schiatta (almeno in certe condizioni) se trova un prefix 
UTF-8 multibyte (legale) ma poi non trova i byte successivi (o perché 
non ci sono proprio o perché sono incompatibili con il prefix).
Aspetta, c'è una novità!
Ho upgradato ad Encode 2.18, e l'errore non me lo da più!
Va be', non ce ne frega niente (anzi, la versione precedente di Encode 
ci faceva pure comodo per il debugging), perché l'errore risiede 
ovviamente altrove, e in effetti si tratta proprio del fatto che ti 
ritrovi con qualche sequenza UTF-8 mutilata, forse per la lettura di un 
numero errato di byte...
O forse problemi di byte munging (problemi di endianness tra diverse 
piattaforme? Ovviamente sto sparando nel buio ;-)
Comunque in entrambi i casi, alterazione dei byte o troncamento 
dell'ultimo carattere, non si capisce perché con un semplice passaggio 
su file dovrebbe tornare tutto a posto!
Specialmente se avessimo un problema di numero errato di byte.
Perciò i byte ci sono sicuramente tutti (e soli), e il problema non può 
essere questo... Quindi cosa rimane?
XML! Deve essere qualcosa di inerente proprio al tuo XML, che da qualche 
parte adotta codifiche non UTF-8 compliant. Probabilmente la cosa è 
perfettamente legale, e quindi XML::Simple la sa gestire.
Io purtroppo la so troppo corta sull'XML (forse Mirod potrebbe aiutarci, 
se fosse in ascolto...)
In questo caso però, il semplice passaggio su file (senza XML::Simple) 
non dovrebbe risolvere il problema. Altrimenti è magia nera.
Ciao,
Emanuele.

 Unicode a 2 byte
Anche qui sono stato impreciso; sarebbe stato più esatto dire: "soltanto 
caratteri con codifica UTF-8 a 2 byte", perché non è per niente detto 
che un code-point Unicode a 16 bit abbia per forza codifica UTF-8 a 2 
byte (difatti più spesso ce l'ha a 3 byte).
Che sono un cane l'ho già detto.
Emanuele.

     my $s_utf8 = Encode::decode_utf8($s);
Con utf8 bisogna stare veramente attenti.
Per scrupolo ho provato a rieseguire quelle sole 3 righe di codice che 
ho postato (che in realtà facevano parte di uno script più esteso), e da 
sole non funzionano per niente come ho descritto.
Il mio script invece funzionava come descritto, perché prima c'era uno 
"use bytes;", che *non* può essere omesso, e quindi ce lo rimetto:
	use bytes;
	# ...
	use Encode;
	my $s = "\x{263A}" . chr 0b11101111;
	my $s_utf8 = Encode::decode_utf8($s);
Senza il pragma use bytes, quello che succede è che Perl trovando una 
concatenazione tra una stringa UTF-8 ed una non UTF-8, decodifica la 
nuova stringa in Latin-1 (con l'effetto che in questo caso
chr 0b11101111 verrebbe trasformato in 2 byte "legali", facendo saltare 
tutto il ragionamento).
http://perldoc.perl.org/perlunicode.html#Byte-and-Character-Semantics
Ciao,
Emanuele.
