我正在尝试为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个不同的潜在分隔符做了它将变得非常难看。每个分隔符都可能是一个regexp特殊字符,导致我自己的正则表达式中有很多转义。
有没有办法避免这种情况,可能会将一个特殊的正则表达式传递给我的split()调用?
答案 0 :(得分:7)
my @split = split( /(?<!\Q$escape\E)\Q$delim\E/, $edi);
将为您执行拆分,但您必须单独删除转义字符:
s/\Q$escape$delim\E/$delim/g for @split;
更新:允许转义字符转义任何字符,包括其自身,而不仅仅是分隔符需要不同的方法。这是一种方式:
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
答案 1 :(得分:2)
尝试Text::CSV
。
答案 2 :(得分: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字符。
答案 3 :(得分:1)
这是一个自定义函数 - 它比ysth的答案更长,但在我看来,它更容易分解成有用的部分(不是所有的正则表达式),它还能够处理你要求的多个分隔符
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