2012-05-25 5 views
5

Wenn ich Perl oder C verwenden, um einige Daten zu printf, habe ich versucht, ihr Format die Breite jeder Spalte zu steuern, wieUTF-8 Breite Display-Ausgabe von chinesischen Schriftzeichen

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

Aber wenn str chinesische Zeichen enthält, dann stimmt die Spalte nicht wie erwartet überein. siehe Anhang Bild.

Die Zeichensatzcodierung meines ubuntu ist zh_CN.utf8, so weit ich weiß, hat utf-8 encoding 1 ~ 4 Bytelänge. Chinesisches Schriftzeichen hat 3 Bytes. In meinem Test habe ich festgestellt, dass die Formatsteuerung von printf ein chinesisches Zeichen als 3 zählt, aber es wird tatsächlich als 2 ASCII Breite angezeigt.

Die eigentliche Anzeigebreite ist keine Konstante, wie erwartet, sondern eine Variable für die Anzahl der chinesischen Zeichen bezogen, dh

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

w die Breite Grenze erwartet wird, x ist die Anzahl der chinesischen Schriftzeichen, Sw (x) ist die tatsächliche Anzeigebreite.

Je mehr chinesisches Zeichen str enthält, desto kürzer wird es angezeigt.

Wie kann ich bekommen, was ich will? Zählen Sie die chinesischen Zeichen vor dem Ausdruck?

Soweit ich weiß, alle chinesischen oder sogar alle breiten Zeichen, die ich als 2 Breite anzeigen, dann warum printf es als 3 zählen? Die Codierung von UTF-8 hat nichts mit der Anzeigelänge zu tun.

+0

Mit anderen Worten, Sie suchen nach einer Multibyte-fähigen Version von 'printf' für Perl und/oder C? – deceze

+0

Ich habe noch nie utf8 in C dekodiert, aber hier ist ein Go-Code, der Runen in einer utf-8-Zeichenfolge zählt: http://golang.org/src/pkg/unicode/utf8/utf8.go?s=4824:4876 # L202 –

+1

@dystyroy Es ist nicht nur eine Frage des Zählens der Codepunkte (dh Runen). Es wird vielmehr in Betracht gezogen, dass verschiedene Codepunkte 0, 1 oder 2 Druckspalten pro UAX # 11 darstellen, und dies ist ziemlich subtil, insbesondere mit den Zeichen 'East_Asian_Width = Ambiguous'. Ich kenne keine Go-Bibliothek, die sich damit beschäftigt, wie die in meiner Antwort beschriebene Perl-Bibliothek tut, aber wenn es so etwas für Go gibt, würde ich gerne etwas darüber erfahren! Vielen Dank. – tchrist

Antwort

6

Ja, das ist ein Problem mit allen mir bekannten Versionen von printf. Ich diskutiere kurz die Angelegenheit in this answer und auch in this one.

Für C, ich weiß nicht von einer Bibliothek, die dies für Sie tun, aber wenn jemand es hat, wäre es ICU.

Für Perl müssen Sie das Unicode::GCString Modulformular CPAN verwenden, um die Anzahl der Druckspalten zu berechnen, die eine Unicode-Zeichenfolge aufnehmen wird. Dies berücksichtigt Unicode Standard Annex #11: East Asian Width.

Zum Beispiel nehmen einige Codepunkte 1 Spalte ein und andere belegen 2 Spalten. Es gibt sogar einige, die überhaupt keine Spalten aufnehmen, wie die Kombination von Zeichen und unsichtbaren Steuerzeichen. Die Klasse verfügt über eine columns-Methode, die angibt, wie viele Spalten die Zeichenfolge belegt.

Ich habe ein Beispiel für die vertikale Ausrichtung von Unicode-Text here. Es wird eine Reihe von Unicode-Zeichenfolgen sortieren, einschließlich einiger Zeichen und "breiter" asiatischer Ideogramme (CJK-Zeichen), und Sie können Objekte vertikal ausrichten.

sample terminal output

-Code für das kleine umenu Demo-Programm, das druckt, die gut Ausgabe ausgerichtet, unten enthalten ist.

Sie könnten auch interessiert sein, die weit ambitionierteren Unicode::LineBreak-Modul, von denen die oben genannten Unicode::GCString Klasse ist nur eine kleinere Komponente. Dieses Modul ist viel cooler und berücksichtigt Unicode Standard Annex #14: Unicode Line Breaking Algorithm.

Hier ist der Code für die kleine umenu Demo, getestet auf 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; 
} 

Wie Sie der Schlüssel zu sehen, um es in diesem speziellen Programm machen zu arbeiten, ist diese Codezeile, die nur andere Funktionen, die oben definiert ruft, und verwendet das Modul I diskutiert wurde:

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

Dadurch wird das Element auf die angegebene Breite aufgefüllt, wobei Punkte als Füllzeichen verwendet werden.

Ja, es ist viel weniger bequem, dass printf, aber zumindest ist es möglich.

Verwandte Themen