3

EDI データ形式用のパーサーを作成しようとしています。これは区切りテキストですが、ファイルの先頭に区切り文字が定義されています。

基本的には、コードの先頭で読み取った値に基づく一連の splits() です。問題は、次の区切り文字を無視する必要があることを示すカスタムの「エスケープ文字」もあります。

たとえば、* が区切り文字で、? はエスケープです、私は次のようなことをしています

use Data::Dumper;
my $delim = "*";
my $escape = "?";
my $edi = "foo*bar*baz*aster?*isk";

my @split = split("\\" . $delim, $edi);
print Dumper(\@split);

最後の要素として「aster*isk」を返す必要があります。

私の最初のアイデアは、split() 関数を呼び出す前に、エスケープ文字とそれに続く文字のすべてのインスタンスを、カスタム マップされた印刷不可能な ASCII シーケンスに置き換えてから、別の正規表現を使用してそれらを正しい値に戻すというものでした。

それは実行可能ですが、ハックのように感じられ、5 つの異なる潜在的な区切り文字すべてに対して実行すると、かなり見苦しくなります。各区切り文字は潜在的に正規表現の特殊文字でもあるため、私自身の正規表現で多くのエスケープが発生します。

おそらく私の split() 呼び出しに特別な正規表現を渡して、これを回避する方法はありますか?

4

4 に答える 4

7
my @split = split( /(?<!\Q$escape\E)\Q$delim\E/, $edi);

が分割を行いますが、エスケープ文字を個別に削除する必要があります。

s/\Q$escape$delim\E/$delim/g for @split;

更新:エスケープ文字が区切り文字だけでなく、それ自体を含む任意の文字をエスケープできるようにするには、別のアプローチが必要です。1 つの方法を次に示します。

my @split = $edi =~ /(?:\Q$delim\E|^)((?:\Q$escape\E.|(?!\Q$delim\E).)*+)/gs;
s/\Q$escape$delim\E/$delim/g for @split;

*+perl 5.10 以降が必要です。それ以前は、次のようになります。

/(?:\Q$delim\E|^)((?>(?:\Q$escape\E.|(?!\Q$delim\E).)*))/gs
于 2010-08-27T21:49:37.733 に答える
2

試してみてくださいText::CSV

于 2010-08-27T21:50:19.117 に答える
1

エスケープ文字がフィールドの最後の文字である場合を正しく処理したい場合、これは少し注意が必要です。これが1つの方法です:

# Process escapes to hide the following character:
$edi =~ s/\Q$escape\E(.)/sprintf '%s%d%s', $escape, ord $1, $escape/esg;

my @split = split( /\Q$delim\E/, $edi);

# Convert escape sequences into the escaped character:
s/\Q$escape\E(\d+)\Q$escape\E/chr $1/eg for @split;

これは、エスケープ文字も区切り文字も数字ではないことを前提としていますが、Unicode文字の全範囲をサポートしていることに注意してください。

于 2010-08-27T22:39:17.970 に答える
1

ここにカスタム関数があります-ysthの答えよりも長いですが、私の意見では、有用な部分に分解する方が簡単です(すべてが1つの正規表現であるわけではありません)。また、要求した複数の区切り文字に対処する機能もあります。

sub split_edi {
  my ($in, %args) = @_;
  die q/Usage: split_edi($input, escape => "#", delims => [ ... ]) /
    unless defined $in and defined $args{escape} and defined $args{delims};

  my $escape = quotemeta $args{escape};
  my $delims = join '|', map quotemeta, @{ $args{delims} };

  my ($cur, @ret);

  while ($in !~ /\G\z/cg) {
    if ($in =~ /\G$escape(.)/mcg) {
      $cur .= $1;
    } elsif ($in =~ /\G(?:$delims)/cg) {
      push @ret, $cur; 
      $cur = '';
    } elsif ($in =~ /\G((?:(?!$delims|$escape).)+)/mcg) {
      $cur .= $1;
    } else {
      die "hobbs can't write parsers";
    }
  }
  push @ret, $cur if defined $cur;
  @ret;
}

最初の行は、引数の解析、必要に応じてエスケープ文字のバックスラッシュ、区切り文字のいずれかに一致する正規表現フラグメントの構築です。

次に、マッチング ループが続きます。

  • エスケープが見つかった場合は、それをスキップして、次の文字を特別に処理するのではなく、出力のリテラル ビットとしてキャプチャします。
  • 区切り文字が見つかったら、新しいレコードを開始します。
  • それ以外の場合は、次のエスケープまたは区切り文字まで文字をキャプチャします。
  • 文字列の終わりに達したら停止します。

これは非常に単純であり、それでもかなり堅実なパフォーマンスを備えています。ysth の正規表現ソリューションと同様に、ラチェット式です。不必要にバックトラックしようとはしません。エスケープまたは区切り文字のいずれかが複数文字の場合、正確性は保証されませんが、実際にはほとんど正しいと思います:)

say for split_edi("foo*bar;baz*aster?*isk", delims => [qw(* ;)], escape => "?");
foo
bar
baz
aster*isk
于 2010-08-28T03:05:31.783 に答える