2012-05-25 13 views
5

Cuando uso Perl o C a printf algunos datos, que probé su formato para controlar el ancho de cada columna, comoUTF-8 ancho de la pantalla Edición de caracteres chinos

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

Pero cuando str contiene caracteres chinos, entonces la columna no se alinea como se esperaba. ver la imagen adjunta.

La codificación del conjunto de caracteres de mi ubuntu es zh_CN.utf8, hasta donde yo sé, la codificación utf-8 tiene 1 ~ 4 longitud de bytes. El carácter chino tiene 3 bytes. En mi prueba, encontré que el control de formato de printf cuenta un carácter chino como 3, pero en realidad se muestra como un ancho de 2 ascii.

lo tanto, el ancho de la pantalla real no es una constante como se esperaba, sino una variable relacionada con el número de caracteres chinos, es decir

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

W es el límite de ancho de espera, X es el recuento de los caracteres chinos, Sw (x) es el ancho real de la pantalla.

Por lo tanto, cuanto más caracteres chinos contiene str, más corto se muestra.

¿Cómo puedo obtener lo que quiero? Cuente los caracteres chinos antes de printf?

Por lo que yo sé, todos los caracteres chinos o incluso todos los anchos, supongo, se muestran con un ancho de 2, ¿por qué printf cuenta como 3? La codificación de UTF-8 no tiene nada que ver con la duración de la pantalla.

+0

En otras palabras, ¿está buscando una versión multibyte de 'printf' para Perl y/o C? – deceze

+0

Nunca he hecho la decodificación utf8 en C, pero aquí hay un código Go que cuenta las runas en una cadena utf-8: http://golang.org/src/pkg/unicode/utf8/utf8.go?s=4824:4876 # L202 –

+1

@dystroy No se trata solo de contar los puntos de código (es decir, runas). Por el contrario, se tiene en cuenta que diferentes puntos de código representan 0, 1 o 2 columnas de impresión por UAX # 11, y esto es bastante sutil, especialmente con los caracteres 'East_Asian_Width = Ambiguous'. No conozco ninguna biblioteca de Go que trate esto como lo describió la biblioteca de Perl en mi respuesta, pero si existe algo para Go, ¡me encantaría aprender sobre esto! Gracias. – tchrist

Respuesta

6

Sí, este es un problema con todas las versiones de printf que yo sepa. Discuto brevemente el asunto en this answer y también en this one.

Para C, no sé de una biblioteca que haga esto por usted, pero si alguien la tiene, sería ICU.

Para Perl, debe usar el módulo Unicode::GCString de CPAN para calcular el número de columnas de impresión que tomará una cadena Unicode. Esto tiene en cuenta Unicode Standard Annex #11: East Asian Width.

Por ejemplo, algunos puntos de código ocupan 1 columna y otros ocupan 2 columnas. Incluso hay algunos que no ocupan columnas en absoluto, como la combinación de caracteres y caracteres de control invisibles. La clase tiene un método columns que devuelve la cantidad de columnas que ocupa la cadena.

Tengo un ejemplo de cómo usar esto para alinear texto Unicode verticalmente here. Clasificará un conjunto de cadenas Unicode, incluidas algunas con la combinación de caracteres e ideogramas asiáticos "anchos" (caracteres CJK), y le permitirá alinear las cosas verticalmente.

Código

sample terminal output

para el pequeño programa de demostración umenu que imprime esa salida muy bien alineados, se incluye a continuación.

También le puede interesar el módulo Unicode::LineBreak, mucho más ambicioso, de los cuales la clase Unicode::GCString mencionada anteriormente es solo un componente más pequeño. Este módulo es mucho más fresco, y tiene en cuenta Unicode Standard Annex #14: Unicode Line Breaking Algorithm.

Aquí está el código para la pequeña demostración umenu, probado en 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; 
} 

Como se ve, la clave para hacer que funcione en ese programa en particular es esta línea de código, que sólo llama a otras funciones definidas anteriormente, y utiliza el módulo estaba discutiendo:

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

Eso rellenará el artículo con el ancho dado usando puntos como el carácter de relleno.

Sí, es mucho menos conveniente que printf, pero al menos es posible.

Cuestiones relacionadas