好的Perl风格:如何将UTF-8 C字符串文字转换为\ xXX序列

时间:2013-08-11 05:21:27

标签: python c perl utf-8 string-literals

[Python人:我的问题就在最后: - )]

我想在C字符串文字中使用UTF-8以提高可读性和易维护性。但是,这不是普遍可移植的。我的解决方案是创建一个文件foo.c.in,它由一个小的perl脚本转换为文件foo.c,以便它包含\xXX转义序列而不是大于或等于0x80的字节。

为简单起见,我假设C字符串在同一行开始和结束。

这是我创建的Perl代码。如果找到字节> = 0x80,则原始字符串也作为注释发出。

use strict;
use warnings;

binmode STDIN, ':raw';
binmode STDOUT, ':raw';


sub utf8_to_esc
{
  my $string = shift;
  my $oldstring = $string;
  my $count = 0;
  $string =~ s/([\x80-\xFF])/$count++; sprintf("\\x%02X", ord($1))/eg;
  $string = '"' . $string . '"';
  $string .= " /* " . $oldstring . " */" if $count;
  return $string;
}

while (<>)
{
  s/"((?:[^"\\]++|\\.)*+)"/utf8_to_esc($1)/eg;
  print;
}

例如,输入

"fööbär"

转换为

"f\xC3\xB6\xC3\xB6b\xC3\xA4r" /* fööbär */

最后,我的问题是:我在Perl中不是很好,我想知道是否有可能以更优雅(或更“Perlish”)的方式重写代码。我也想如果有人能指出用Python编写的类似代码。

3 个答案:

答案 0 :(得分:4)

  1. 我认为最好不要使用:raw。您正在处理文本,因此您应该正确解码和编码。这将更不容易出错,并且如果您愿意,它将允许您的解析器使用预定义的字符类。

  2. 你解析就好像你希望文字中的斜杠一样,但是当你逃避时你会完全忽略。因此,您可能最终得到"...\\xC3\xA3..."。使用已解码的文本也会有所帮助。

  3. 所以忘记“perlish”;让我们实际修复错误。

    use open ':std', ':locale';
    
    sub convert_char {
       my ($s) = @_;
       utf8::encode($s);
       $s = uc unpack 'H*', $s;
       $s =~ s/\G(..)/\\x$1/sg;
       return $s;
    }
    
    sub convert_literal {
       my $orig = my $s = substr($_[0], 1, -1);
    
       my $safe          = '\x20-\x7E';          # ASCII printables and space
       my $safe_no_slash = '\x20-\x5B\x5D-\x7E'; # ASCII printables and space, no \
       my $changed = $s =~ s{
          (?: \\? ( [^$safe] )
          |   ( (?: [$safe_no_slash] | \\[$safe] )+ )
          )
       }{
          defined($1) ? convert_char($1) : $2
       }egx;
    
       # XXX Assumes $orig doesn't contain "*/"
       return qq{"$s"} . ( $changed ? " /* $orig */" : '' );
    }
    
    while (<>) {
       s/(" (?:[^"\\]++|\\.)*+ ")/ convert_literal($1) /segx;
       print;
    }
    

答案 1 :(得分:3)

Re:更多Perlish方式。

您可以对引号运算符使用任意分隔符,因此您可以使用字符串插值而不是显式连接,这可以看起来更好。此外,计算替换次数是不必要的:标量上下文中的替换计算为匹配数。

我会把你的(错误的!)函数写成

use strict; use warnings;
use Carp;

sub escape_high_bytes {
  my ($orig) = @_;

  # Complain if the input is not a string of bytes.
  utf8::downgrade($orig, 1)
    or carp "Input must be binary data";

  if ((my $changed = $orig) =~ s/([\P{ASCII}\P{Print}])/sprintf '\\x%02X', ord $1/eg) {
    # TODO make sure $orig does not contain "*/"
    return qq("$changed" /* $orig */);
  } else {
    return qq("$orig");
  }
}

(my $copy = $str) =~ s/foo/bar/是在字符串副本中运行替换的标准习惯用法。使用5.14,我们也可以使用/r修饰符,但是我们不知道模式是否匹配,我们将不得不求助于计数。

请注意,此函数 nothing 与Unicode或UTF-8无关。 utf8::downgrade($string, $fail_ok)确保可以使用单个字节表示字符串。如果无法完成(并且第二个参数为true),则返回false值。

正则表达式运算符\p{...}和否定\P{...}匹配具有特定Unicode属性的代码点。例如。 \P{ASCII}匹配不在[\x00-\x7F]范围内的所有字符,\P{Print}匹配所有不可见的字符,例如控制代码如\x00但不是空格。

你的while (<>)循环可以说是错误的:这不一定会迭代STDIN。相反,它迭代@ARGV(命令行参数)中列出的文件的内容,或者如果该数组为空则默认为STDIN。请注意,不会为:raw的文件声明@ARGV图层。可能的解决方案:

  • 您可以使用open pragma为所有文件句柄声明默认图层。
  • 您可以while (<STDIN>)

你知道什么是Perlish吗?使用模块。碰巧的是,String::Escape已经实现了您想要的大部分功能。

答案 2 :(得分:1)

用Python编写的类似代码

Python 2.7

import re
import sys

def utf8_to_esc(matched):
    s = matched.group(1)
    s2 = s.encode('string-escape')
    result = '"{}"'.format(s2)
    if s != s2:
        result += ' /* {} */'.format(s)
    return result

sys.stdout.writelines(re.sub(r'"([^"]+)"', utf8_to_esc, line) for line in sys.stdin)

Python 3.x

def utf8_to_esc(matched):
    ...
    s2 = s.encode('unicode-escape').decode('ascii')
    ...