Dav*_*fer 3 perl encoding locale ncurses utf-8
我正在尝试使用 Bryan Henderson 对 ncurses 库的 Perl 接口:Curses
对于一个简单的练习,我尝试获取在屏幕上输入的单个字符。这直接基于NCURSES Programming HOWTO并进行了修改。
当我调用 Perl 库的 时getchar(),我希望收到一个字符,可能是多字节的(正如库手册页的这一部分所解释的那样,它有点复杂,因为必须处理功能键和没有输入的特殊情况,但这只是通常的卷发)。
它是read1ch()下面代码中的子程序。
这适用于 ASCII 字符,但不适用于 0x7F 以上的字符。例如,当点击è(Unicode 0x00E8, UTF-8 : 0xC3, 0xA8) 时,我实际上获得了代码 0xE8 而不是 UTF-8 编码的东西。将其打印到LANG=en_GB.UTF-8无法正常工作的终端,无论如何我期待 0xC3A8。
我需要更改什么才能使其工作,即获取è正确的字符或 Perl 字符串?
剪断C代码getchar()是在这里顺便说一句。也许它只是没有用C_GET_WCHset编译?如何发现?
与设置尝试binmode使用
binmode STDERR, ':encoding(UTF-8)';
binmode STDOUT, ':encoding(UTF-8)';
Run Code Online (Sandbox Code Playgroud)
这应该解决任何编码问题,因为终端期望并发送 UTF-8,但这没有帮助。
还尝试使用 open设置流编码(不太确定此方法与上述方法之间的区别),但这也无济于事
use open qw(:std :encoding(UTF-8));
Run Code Online (Sandbox Code Playgroud)
Perl Curses 垫片的联机帮助页说:
如果
wget_wch()不可用(即 Curses 库不理解宽字符),则调用wgetch()[从curses 窗口获取 1 字节字符],但仍返回上述值。这可能是一个问题,因为对于像 UTF-8 这样的多字节字符编码,您将收到两个单字符的字符串来表示两字节字符(例如,“Ô和“¤”表示“ä”)。
这可能是这里的情况,但wget_wch()在这个系统上确实存在。
试图查看 C 代码的作用,并fprintf直接在 的多字节处理代码中添加了一个curses/Curses-1.36/CursesFunWide.c,重新编译,并没有设法Curses.so用我自己的 via覆盖系统LD_LIBRARY_PATH(为什么不呢?为什么一切只在一半的时间内工作?),所以直接替换了系统库(拿去吧!)。
#ifdef C_GET_WCH
wint_t wch;
int ret = wget_wch(win, &wch);
if (ret == OK) {
ST(0) = sv_newmortal();
fprintf(stderr,"Obtained win_t 0x%04lx\n", wch);
c_wchar2sv(ST(0), wch);
XSRETURN(1);
} else if (ret == KEY_CODE_YES) {
XST_mUNDEF(0);
ST(1) = sv_newmortal();
sv_setiv(ST(1), (IV)wch);
XSRETURN(2);
} else {
XSRETURN_UNDEF;
}
#else
Run Code Online (Sandbox Code Playgroud)
这只是一个肥胖的 NOPE,当按下ü一个看到:
Obtained win_t 0x00fc
Run Code Online (Sandbox Code Playgroud)
所以正确的代码是 run,但数据是ISO-8859-1,而不是 UTF-8 。所以它的wget_wch行为很糟糕。所以这是一个curses配置问题。呵呵。
让我震惊的是,可能ncurses是假设默认语言环境,即C. 为了使其ncurses与宽字符一起工作,必须“初始化语言环境”,这可能意味着将状态从“未设置”(从而ncurses回退到C)移动到“设置为系统指示的内容”(这应该是在LANG环境变量)。手册页ncurses说:
库使用调用程序已初始化的语言环境。这通常是通过 setlocale 完成的:
设置区域设置(LC_ALL,“”);
如果区域设置未初始化,则库假定字符可打印,如 ISO-8859-1 中所示,以与某些遗留程序一起使用。当区域设置尚未设置时,您应该初始化区域设置,而不是依赖库的特定细节。
这也不起作用,但我觉得解决方案就在这条路上。
来自 的win_t(显然与 相同wchar_t)转换代码CursesWide.c将接收到的wint_t(此处视为wchar_t)wget_wch()转换为 Perl 字符串。SV是“标量值”类型。
另见:https : //perldoc.perl.org/perlguts.html
这里用两个fprintf插入看看是怎么回事:
static void
c_wchar2sv(SV * const sv,
wchar_t const wc) {
/*----------------------------------------------------------------------------
Set SV to a one-character (not -byte!) Perl string holding a given wide
character
-----------------------------------------------------------------------------*/
if (wc <= 0xff) {
char s[] = { wc, 0 };
fprintf(stderr,"Not UTF-8 string: %02x %02x\n", ((int)s[0])&0xFF, ((int)s[1])&0xFF);
sv_setpv(sv, s);
SvPOK_on(sv);
SvUTF8_off(sv);
} else {
char s[UTF8_MAXBYTES + 1] = { 0 };
char *s_end = (char *)UVCHR_TO_UTF8((U8 *)s, wc);
*s_end = 0;
fprintf(stderr,"UTF-8 string: %02x %02x %02x\n", ((int)s[0])&0xFF, ((int)s[1])&0xFF, ((int)s[2])&0xFF);
sv_setpv(sv, s);
SvPOK_on(sv);
SvUTF8_on(sv);
}
}
Run Code Online (Sandbox Code Playgroud)
如果您尝试,请按 BACKSPACE 退出循环,因为不再解释 CTRL-C。
下面的代码很多,但关键区域标有----- Testing:
#ifdef C_GET_WCH
wint_t wch;
int ret = wget_wch(win, &wch);
if (ret == OK) {
ST(0) = sv_newmortal();
fprintf(stderr,"Obtained win_t 0x%04lx\n", wch);
c_wchar2sv(ST(0), wch);
XSRETURN(1);
} else if (ret == KEY_CODE_YES) {
XST_mUNDEF(0);
ST(1) = sv_newmortal();
sv_setiv(ST(1), (IV)wch);
XSRETURN(2);
} else {
XSRETURN_UNDEF;
}
#else
Run Code Online (Sandbox Code Playgroud)
[此答案假定 libncursesw 可用并正在使用。尝试在没有宽字符支持的情况下输出“宽字符”是没有意义的:) ]
简答
getchar工作正常。它返回一串 Unicode 代码点(又名解码文本),这是理想的。
printw 已损坏,但可以通过将以下内容添加到程序中,使其接受一串 Unicode 代码点(又名解码文本):
{
# Add wide character support to printw.
# This only modifies the current package (main),
# so it won't affect any code by ours.
no warnings qw( redefine );
sub printw { addstring(sprintf shift, @_) }
}
Run Code Online (Sandbox Code Playgroud)
有问题getchar吗?
所以你认为getchar. 让我们尝试通过检查getchar返回的内容来确认这一点。我们将通过添加以下内容来做到这一点:
printw("String received from getchar: %vX\n", $ch);
Run Code Online (Sandbox Code Playgroud)
(%vX将以十六进制打印字符串的每个字符的值,并用句点连接。)
当按下e(U+0065),一个 7 位字符时,会看到:
String received from getchar: 65
Run Code Online (Sandbox Code Playgroud)当按下é(U+00E9),一个 8 位字符时,会看到:
String received from getchar: E9
Run Code Online (Sandbox Code Playgroud)当按下?(U+0113),一个 9 位字符时,会看到:
String received from getchar: 113
Run Code Online (Sandbox Code Playgroud)在所有三种情况下,我们都会得到一个长度正好为一个字符的字符串,并且该字符由输入的 Unicode 代码点组成。[1]这正是我们想要的。应用和移除字符编码应该在外围完成,这样程序的主要逻辑就不必担心编码,而这正在完成。
结论:没有问题getchar。
有问题printw吗?
所以问题一定出在输出上。为了确认这一点,我在您的程序中添加了以下内容:
sub _d { utf8::downgrade( my $s = shift ); $s }
sub _u { utf8::upgrade( my $s = shift ); $s }
for (
[ "7-bit, UTF8=0" => _d(chr(0x65)) ], # Expect e
[ "7-bit, UTF8=1" => _u(chr(0x65)) ], # Expect e
[ "8-bit, UTF8=0" => _d(chr(0xE9)) ], # Expect é
[ "8-bit, UTF8=1" => _u(chr(0xE9)) ], # Expect é
[ "9-bit, UTF8=1" => chr(0x113) ], # Expect ?
) {
my ($name, $chr) = @$_;
printw("%s: %s\n", $name, $chr);
}
Run Code Online (Sandbox Code Playgroud)
输出:
7-bit, UTF8=0: e
7-bit, UTF8=1: e
8-bit, UTF8=0:
8-bit, UTF8=1: é
9-bit, UTF8=1: S
Run Code Online (Sandbox Code Playgroud)
综上所述,我们观察到:
_d(chr(0xE9))和_u(chr(0xE9)),即使标量都包含相同的字符串(_d(chr(0xE9)) eq _u(chr(0xE9))是真的)。因此,此功能会受到 Unicode 错误的影响。chr(0x113)。结论:存在重大问题printw。
解决问题 printw
解决 Unicode 错误很容易,但缺乏对 0xFF 以上字符的支持是一个障碍。让我们深入研究代码。
好的,我们不必费力寻找问题。我们看到它printw是根据 , 定义的addstr,并且addstr早于宽字符支持。 addstring是宽字符支持对方,所以我们要尽量printw使用addstring替代addstr。
{
# Add wide character support to printw.
# This only modifies the current package (main),
# so it won't affect any code by ours.
no warnings qw( redefine );
sub printw { addstring(sprintf shift, @_) }
}
Run Code Online (Sandbox Code Playgroud)
输出:
7-bit, UTF8=0: e
7-bit, UTF8=1: e
8-bit, UTF8=0: é
8-bit, UTF8=1: é
9-bit, UTF8=1: ?
Run Code Online (Sandbox Code Playgroud)
答对了!
综上所述,我们观察到:
UTF8=0测试结果与其相应UTF8=1测试之间的差异。因此,此功能不受 Unicode 错误的影响。这正是我们期望/渴望的。
getchar不会像您认为的那样返回输入的 iso-8859-1 编码。这种混淆是可以理解的,因为 Unicode 是 iso-8859-1 的扩展。