2012-05-25 16 views
5

Quando uso Perl o C per printf alcuni dati, ho cercato il loro formato per controllare la larghezza di ogni colonna, comeUTF-8 larghezza di visualizzazione di Emissione dei caratteri cinesi

printf("%-30s", str); 

Ma quando str contiene caratteri cinesi, quindi la colonna non si allinea come previsto. vedere l'immagine dell'allegato.

La codifica del mio charset di ubuntu è zh_CN.utf8, per quanto ne so, la codifica utf-8 ha una lunghezza di 1 ~ 4 byte. Il carattere cinese ha 3 byte. Nel mio test, ho trovato che il controllo formato di printf conta un carattere cinese come 3, ma in realtà viene visualizzato come 2 larghezza ascii.

Così l'ampiezza reale del display non è una costante, come previsto, ma una variabile legata al numero di carattere cinese, vale a dire

Sw(x) = 1 * (w - 3x) + 2 * x = w - x 

w è il limite di larghezza previsto, x è il conteggio dei caratteri cinesi, Sw (x) è la larghezza reale del display.

Quindi più caratteri cinesi contiene, più corto è visualizzato.

Come posso ottenere quello che voglio? Contare i caratteri cinesi prima di printf?

Per quanto ne so, tutti i caratteri cinesi o anche tutti i caratteri larghi immagino, visualizzati come larghezza 2, quindi perché printf lo contano come 3? La codifica di UTF-8 non ha nulla a che fare con la lunghezza del display.

+0

In altre parole, stai cercando una versione multipla di 'printf' per Perl e/o C? – deceze

+0

Non ho mai eseguito la decodifica utf8 in C ma ecco un codice Go che conta le rune in una stringa utf-8: http://golang.org/src/pkg/unicode/utf8/utf8.go?s=4824:4876 # L202 –

+1

@dystroy Non si tratta solo di contare i punti del codice (es. Rune). Piuttosto, si tiene conto che diversi punti di codice rappresentano 0, 1 o 2 colonne di stampa per UAX # 11, e questo è abbastanza sottile, specialmente con i caratteri 'East_Asian_Width = Ambiguous'. Non conosco alcuna libreria Go che tratti questo come fa la libreria Perl descritta nella mia risposta, ma se c'è una cosa del genere per Go, mi piacerebbe conoscerla! Grazie. – tchrist

risposta

6

Sì, questo è un problema con tutte le versioni di printf di cui sono a conoscenza. Parlo brevemente della questione in this answer e anche in this one.

Per C, non conosco una libreria che farà questo per voi, ma se qualcuno ce l'ha, sarebbe ICU.

Per Perl, è necessario utilizzare il modulo CPAN Unicode::GCString per calcolare il numero di colonne di stampa occupate da una stringa Unicode. Questo prende in considerazione Unicode Standard Annex #11: East Asian Width.

Ad esempio, alcuni punti di codice occupano 1 colonna e altri occupano 2 colonne. Ci sono anche alcuni che non occupano affatto le colonne, come combinare caratteri e caratteri di controllo invisibili. La classe ha un metodo columns che restituisce quante colonne occupa la stringa.

Ho un esempio di utilizzo di questo per allineare il testo Unicode verticalmente here. Analizzerà un gruppo di stringhe Unicode, incluse alcune con la combinazione di caratteri e ideogrammi asiatici "larghi" (caratteri CJK) e consentirà di allineare le cose verticalmente.

sample terminal output

Codice per il piccolo programma umenu demo che le stampe che ben allineati in uscita, è incluso di seguito.

Potresti anche essere interessato al modulo molto più ambizioso Unicode::LineBreak, di cui la classe Unicode::GCString sopra menzionata è solo un componente più piccolo. Questo modulo è molto più interessante e prende in considerazione Unicode Standard Annex #14: Unicode Line Breaking Algorithm.

Ecco il codice per la piccola demo umenu, testata su Perl v5.14:

#!/usr/bin/env perl 
# umenu - demo sorting and printing of Unicode food 
# 
# (obligatory and increasingly long preamble) 
# 
use utf8; 
use v5.14;      # for locale sorting 
use strict; 
use warnings; 
use warnings qw(FATAL utf8); # fatalize encoding faults 
use open  qw(:std :utf8); # undeclared streams in UTF-8 
use charnames qw(:full :short); # unneeded in v5.16 

# std modules 
use Unicode::Normalize;   # std perl distro as of v5.8 
use List::Util qw(max);   # std perl distro as of v5.10 
use Unicode::Collate::Locale; # std perl distro as of v5.14 

# cpan modules 
use Unicode::GCString;   # from CPAN 

# forward defs 
sub pad($$$); 
sub colwidth(_); 
sub entitle(_); 

my %price = (
    "γύρος"    => 6.50, # gyros, Greek 
    "pears"    => 2.00, # like um, pears 
    "linguiça"   => 7.00, # spicy sausage, Portuguese 
    "xoriço"   => 3.00, # chorizo sausage, Catalan 
    "hamburger"   => 6.00, # burgermeister meisterburger 
    "éclair"   => 1.60, # dessert, French 
    "smørbrød"   => 5.75, # sandwiches, Norwegian 
    "spätzle"   => 5.50, # Bayerisch noodles, little sparrows 
    "包子"    => 7.50, # bao1 zi5, steamed pork buns, Mandarin 
    "jamón serrano"  => 4.45, # country ham, Spanish 
    "pêches"   => 2.25, # peaches, French 
    "シュークリーム" => 1.85, # cream-filled pastry like éclair, Japanese 
    "막걸리"   => 4.00, # makgeolli, Korean rice wine 
    "寿司"    => 9.99, # sushi, Japanese 
    "おもち"   => 2.65, # omochi, rice cakes, Japanese 
    "crème brûlée"  => 2.00, # tasty broiled cream, French 
    "fideuà"   => 4.20, # more noodles, Valencian (Catalan=fideuada) 
    "pâté"    => 4.15, # gooseliver paste, French 
    "お好み焼き"  => 8.00, # okonomiyaki, Japanese 
); 

my $width = 5 + max map { colwidth } keys %price; 

# So the Asian stuff comes out in an order that someone 
# who reads those scripts won't freak out over; the 
# CJK stuff will be in JIS X 0208 order that way. 
my $coll = new Unicode::Collate::Locale locale => "ja"; 

for my $item ($coll->sort(keys %price)) { 
    print pad(entitle($item), $width, "."); 
    printf " €%.2f\n", $price{$item}; 
} 

sub pad($$$) { 
    my($str, $width, $padchar) = @_; 
    return $str . ($padchar x ($width - colwidth($str))); 
} 

sub colwidth(_) { 
    my($str) = @_; 
    return Unicode::GCString->new($str)->columns; 
} 

sub entitle(_) { 
    my($str) = @_; 
    $str =~ s{ (?=\pL)(\S)  (\S*) } 
       { ucfirst($1) . lc($2) }xge; 
    return $str; 
} 

Come si vede, la chiave per farlo funzionare in quel particolare programma è questa riga di codice, che ha appena chiama altre funzioni definite sopra, e utilizza il modulo stavo discutendo:

print pad(entitle($item), $width, "."); 

Questo riempirà l'elemento della larghezza specificata usando i punti come carattere di riempimento.

Sì, è molto meno conveniente che printf, ma almeno è possibile.

Problemi correlati