2

[Python の人々: 私の質問は最後にあります :-)]

読みやすさとメンテナンスを容易にするために、C 文字列リテラル内で UTF-8 を使用したいと考えています。ただし、これは普遍的に移植可能ではありません。私の解決策は、0x80 以上のバイトの代わりにエスケープ シーケンスが含まれるようにfoo.c.in、小さな perl スクリプトによってファイルに変換されるファイルを作成することです。foo.c\xXX

簡単にするために、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 で書かれた同様のコードを指摘してくれれば幸いです。

4

3 に答える 3

4
  1. 使わない方がいいと思います:raw。テキストを処理しているため、適切にデコードおよびエンコードする必要があります。これにより、エラーが発生しにくくなり、必要に応じてパーサーが定義済みの文字クラスを使用できるようになります。

  2. リテラルにスラッシュがあるかのように解析しますが、エスケープするときは完全に無視します。そのため、あなたはで終わる可能性があります"...\\xC3\xA3..."。デコードされたテキストを扱うこともここで役に立ちます。

「パーリッシュ」は忘れてください。実際にバグを修正してみましょう。

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;
}
于 2013-08-11T14:37:10.703 に答える
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");
  }
}

The(my $copy = $str) =~ s/foo/bar/は、文字列のコピーで置換を実行するための標準的なイディオムです。5.14 では修飾子を使用することもできました/rが、パターンが一致したかどうかがわからないため、カウントに頼る必要がありました。

この関数はUnicode や UTF-8 とは関係がないことに注意してください。はutf8::downgrade($string, $fail_ok)、文字列が 1 バイトを使用して表現できることを確認します。これができない場合 (そして 2 番目の引数が true の場合)、false 値を返します。

正規表現演算子\p{...}と否定\P{...}は、特定の Unicode プロパティを持つコードポイントに一致します。たとえば\P{ASCII}、範囲内にないすべての文字に[\x00-\x7F]一致\P{Print}し、表示されないすべての文字に一致します。たとえば、空白などの制御コード\x00は空白ではありません。

あなたのwhile (<>)ループは間違いなくバグがあります: これは必ずしも STDIN を反復処理しません。むしろ、@ARGV(コマンドライン引数) にリストされているファイルの内容を反復するか、その配列が空の場合はデフォルトで STDIN になります。:rawからのファイルに対してレイヤが宣言されないことに注意してください@ARGV。可能な解決策:

  • openプラグマを使用して、すべてのファイルハンドルのデフォルト レイヤーを宣言できます。
  • できwhile (<STDIN>)ます。

パーリッシュって知ってる?モジュールの使用。たまたま、String::Escape必要な機能の多くを既に実装しています。

于 2013-08-11T07:03:06.463 に答える
1

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')
    ...
于 2013-08-11T05:49:16.730 に答える