2013-07-02 13 views
8

Ho due file Json che provengono da diversi SO.Normalizzazione sui nomi file utf8 memorizzati in JSON con perl

Entrambi i file sono codificati in UTF-8 e contengono filenames codificato .

Un file proviene da OS X e il nome del file è in forma NFD: (od -bc)

0000160 166 145 164 154 141 314 201 057 110 157 165 163 145 040 155 145 
      v e t l a ́ ** / H o u s e  m e 

il secondo contiene lo stesso nome file ma in forma NFC:

000760 166 145 164 154 303 241 057 110 157 165 163 145 040 155 145 163 
      v e t l á ** / H o u s e  m e s 

come ho imparato , questo si chiama 'normalizzazione diversa', e c'è un modulo CPAN Unicode::Normalize per gestirlo.

Sto leggendo entrambi i file con il prossimo:

my $json1 = decode_json read_file($file1, {binmode => ':raw'}) or die "..." ; 
my $json2 = decode_json read_file($file2, {binmode => ':raw'}) or die "..." ; 

Il read_file è da File::Slurp e decode_json dal JSON::XS.

Lettura del JSON in struttura perl, da un file json il nome del file viene immesso nella posizione key e dal secondo file entra nello values. Devo cercare quando l'hash key dal primo hash è equivoco a un value dal secondo hash, quindi è necessario assicurarsi che siano "binari" identici.

provato il prossimo:

grep 'House' file1.json | perl -CSAD -MUnicode::Normalize -nlE 'print NFD($_)' | od -bc 

e

grep 'House' file2.json | perl -CSAD -MUnicode::Normalize -nlE 'print NFD($_)' | od -bc 

produce per me la stessa uscita.

Ora le domande:

  • Come semplicemente leggere entrambi i file JSON per ottenere lo stesso normalizzazione in entrambi $hashrefs?

o è necessario dopo il decode_json eseguire qualcosa come su entrambi gli hash?

while(my($k,$v) = each(%$json1)) { 
    $copy->{ NFD($k) } = NFD($v); 
} 

In breve:

  • Come leggere diversi file JSON per ottenere la stessa normalizzazione 'dentro' il perl $href? È possibile ottenere un po 'più bello come fare esplicitamente NFD su ogni keyvalue e creare un'altra copia NFD normalizzata (grande) degli hash?

Alcuni suggerimenti, suggerimenti - pleae ...

perché il mio inglese è molto cattivo, ecco una simulazione del problema

use 5.014; 
use warnings; 

use utf8; 
use feature qw(unicode_strings); 
use charnames qw(:full); 
use open qw(:std :utf8); 
use Encode qw(encode decode); 
use Unicode::Normalize qw(NFD NFC); 

use File::Slurp; 
use Data::Dumper; 
use JSON::XS; 

#Creating two files what contains different "normalizations" 
my($nfc, $nfd);; 
$nfc->{ NFC('key') } = NFC('vál'); 
$nfd->{ NFD('vál') } = 'something'; 

#save as NFC - this comes from "FreeBSD" 
my $jnfc = JSON::XS->new->encode($nfc); 
open my $fd, ">:utf8", "nfc.json" or die("nfc"); 
print $fd $jnfc; 
close $fd; 

#save as NFD - this comes from "OS X" 
my $jnfd = JSON::XS->new->encode($nfd); 
open $fd, ">:utf8", "nfd.json" or die("nfd"); 
print $fd $jnfd; 
close $fd; 

#now read them 
my $jc = decode_json read_file("nfc.json", { binmode => ':raw' }) or die "No file" ; 
my $jd = decode_json read_file("nfd.json", { binmode => ':raw' }) or die "No file" ; 

say $jd->{ $jc->{key} } // "NO FOUND"; #wanted to print "something" 

my $jc2; 
#is here a better way to DO THIS? 
while(my($k,$v) = each(%$jc)) { 
    $jc2->{ NFD($k) } = NFD($v); 
} 
say $jd->{ $jc2->{key} } // "NO FOUND"; #OK 

risposta

1

Mentre cercavo la soluzione giusta per la tua domanda ho scoperto: il software è c * rp :) Vedi: https://stackoverflow.com/a/17448888/632407.

In ogni caso, ha trovato la soluzione per la vostra domanda particolare - come leggere JSON con nomi di file indipendentemente dalla normalizzazione:

al posto del tuo:

#now read them 
my $jc = decode_json read_file("nfc.json", { binmode => ':raw' }) or die "No file" ; 
my $jd = decode_json read_file("nfd.json", { binmode => ':raw' }) or die "No file" ; 

utilizzare il seguente:

#now read them 
my $jc = get_json_from_utf8_file('nfc.json') ; 
my $jd = get_json_from_utf8_file('nfd.json') ; 
... 

sub get_json_from_utf8_file { 
    my $file = shift; 
    return 
     decode_json  #let parse the json to perl 
     encode 'utf8', #the decode_json want utf8 encoded binary string, encode it 
      NFC   #conv. to precomposed normalization - regardless of the source 
      read_file #your file contains utf8 encoded text, so read it correctly 
       $file, { binmode => ':utf8' } ; 
} 

Questo dovrebbe (almeno spero) garantire che non rispetta quale scomposizione utilizza il contenuto JSON, lo NFC lo convertirà in versione precomposta e il JSON: XS leggerà analizzarlo correttamente alla stessa struttura interna perl.

Così il vostro esempio stampe:

something 

senza muovere gli $json

L'idea nasce da Joseph Myers e Nemo;)

Forse alcuni programmatori più esperti daranno ulteriori suggerimenti.

+0

nel frattempo mi trovi esattamente questa soluzione. Grazie. – kobame

1

Anche se può essere importante in questo momento solo di convertire alcuni nomi di file per la stessa normalizzazione per il confronto, altri problemi imprevisti potrebbero sorgere quasi ovunque se i dati JSON hanno una diversa normalizzazione.

Quindi il mio suggerimento è di normalizzare l'intero input da entrambe le fonti come primo passo prima di eseguire qualsiasi analisi (ad esempio, allo stesso tempo si legge il file e prima di decode_json). Questo non dovrebbe corrompere nessuna delle tue strutture JSON poiché quelle sono delimitate usando caratteri ASCII. Quindi il codice perl esistente dovrebbe essere in grado di supporre ciecamente che tutti i caratteri UTF8 abbiano la stessa normalizzazione.

$rawdata1 = read_file($file1, {binmode => ':raw'}) or die "..."; 
$rawdata2 = read_file($file2, {binmode => ':raw'}) or die "..."; 

my $json1 = decode_json NFD($rawdata1); 
my $json2 = decode_json NFD($rawdata2); 

per rendere questo processo leggermente più veloce (che dovrebbe essere molto veloce già, dal momento che il modulo utilizza procedure XS veloci), è possibile scoprire se una delle due file di dati è già in una certa forma di normalizzazione, e quindi lascia il file invariato e converti l'altro file in quel modulo.

Ad esempio:

$rawdata1 = read_file($file1, {binmode => ':raw'}) or die "..."; 
$rawdata2 = read_file($file2, {binmode => ':raw'}) or die "..."; 

if (checkNFD($rawdata1)) { 
    # then you know $file1 is already in Normalization Form D 
    # (i.e., it was formed by canonical decomposition). 
    # so you only need to convert $file2 into NFD 
    $rawdata2 = NFD($rawdata2); 
} 
my $json1 = decode_json $rawdata1; 
my $json2 = decode_json $rawdata2; 

Naturalmente, si sarebbe naturalmente necessario sperimentare ora nel tempo di sviluppo per vedere se uno o l'altro dei file di input è già in forma normalizzata, e quindi nel vostro finale versione del codice, non sarebbe più necessaria un'istruzione condizionale, ma semplicemente convertire l'altro file di input nella stessa forma normalizzata.

Si noti inoltre che è consigliabile produrre output in formato NFC (se il programma produce un output che verrebbe memorizzato e utilizzato successivamente). Vedere qui, ad esempio: http://www.perl.com/pub/2012/05/perlunicookbook-unicode-normalization.html

+0

Sfortunatamente, il: 'decode_json NFD (read_file (" nfd.json ", {binmode => ': raw'}))' non funziona. Ottieni il messaggio di errore: 'Ampio carattere nella voce di subroutine nella riga read2.pl 18. Lamentando il' decode_json'. – kobame

+0

Sì, problemi come questo sono noiosi e spesso le soluzioni semplici che dovrebbero funzionare non funzionano. Permettimi di rimuginare su di esso e vedere se riesco a diagnosticarlo per te (sto cercando di destreggiarmi tra SO e un centro di tutoraggio in questo momento). Questa domanda è simile a quella con cui mi occupo quasi ogni settimana, un sacco di stranezze inaspettate che mostrano che il mondo non è ancora perfetto per quanto riguarda Unicode - sebbene quando indico quella realtà, ottengo solo un sacco di downvotes come qui :(http://stackoverflow.com/questions/16594636/using-html-symbol-entities-instead-of-the-actual-symbol/16594962#16594962. –

+1

Ti suggerisco di usare NFC come normalizzazione prima di passare per decode_json. Prova quello. –

1

Hm. Non posso consigliarti una soluzione "di programmazione" migliore. Ma perché semplicemente non correre

perl -CSDA -MUnicode::Normalize -0777 -nle 'print NFD($_)' <freebsd.json>bsdok.json 
perl -CSDA -MUnicode::Normalize -0777 -nle 'print NFD($_)' < osx.json  >osxok.json 

e ora lo script può leggere ed utilizzare, sia perché sono entrambi nella stessa normalizzazione? Quindi, invece di cercare la soluzione di programmazione som all'interno dello del tuo script, risolvi il problema prima di entrare nello script. (Il secondo comando non è necessario: conversione semplice a livello di file ... Certo è più facile come attraversare strutture dati ...