我有一些 TSV 数据
ID Name Email
1 test test@email.com
321 stan stan@nowhere.net
我想将其解析为哈希列表
@entities[0]<Name> eq "test";
@entities[1]<Email> eq "stan@nowhere.net";
我在使用换行元字符从值行分隔标题行时遇到问题。我的语法定义:
use v6;
grammar Parser {
token TOP { <headerRow><valueRow>+ }
token headerRow { [\s*<header>]+\n }
token header { \S+ }
token valueRow { [\s*<value>]+\n? }
token value { \S+ }
}
my $dat = q:to/EOF/;
ID Name Email
1 test test@email.com
321 stan stan@nowhere.net
EOF
say Parser.parse($dat);
但这又回来了
Nil
.我想我误解了 raku 中关于正则表达式的一些基本知识。
最佳答案
可能主要是因为 \s
匹配水平和垂直空间。要仅匹配水平空间,请使用 \h
, 并仅匹配垂直空间,\v
.
我提出的一个小建议是避免在 token 中包含换行符。您可能还想使用交替运算符 %
或 %%
,因为它们是为处理此类工作而设计的:
grammar Parser {
token TOP {
<headerRow> \n
<valueRow>+ %% \n
}
token headerRow { <.ws>* %% <header> }
token valueRow { <.ws>* %% <value> }
token header { \S+ }
token value { \S+ }
token ws { \h* }
}
Parser.parse($dat)
的结果原因如下:「ID Name Email
1 test test@email.com
321 stan stan@nowhere.net
」
headerRow => 「ID Name Email」
header => 「ID」
header => 「Name」
header => 「Email」
valueRow => 「 1 test test@email.com」
value => 「1」
value => 「test」
value => 「test@email.com」
valueRow => 「 321 stan stan@nowhere.net」
value => 「321」
value => 「stan」
value => 「stan@nowhere.net」
valueRow => 「」
这向我们表明语法已成功解析所有内容。但是,让我们关注问题的第二部分,即您希望它在变量中可用。为此,您需要提供一个对于该项目来说非常简单的操作类。您只需创建一个其方法与您的语法方法匹配的类(尽管非常简单的类,例如
value
/header
除了字符串化之外不需要特殊处理,可以忽略)。有一些更有创意/紧凑的方法来处理你的处理,但我会用一种相当基本的方法来说明。这是我们的类(class):class ParserActions {
method headerRow ($/) { ... }
method valueRow ($/) { ... }
method TOP ($/) { ... }
}
每个方法都有签名
($/)
这是正则表达式匹配变量。所以现在,让我们问问我们想要从每个 token 中获得什么信息。在标题行中,我们希望每个标题值都排成一行。所以: method headerRow ($/) {
my @headers = $<header>.map: *.Str
make @headers;
}
任何带有量词的标记都将被视为
Positional
,因此我们还可以使用 $<header>[0]
访问每个单独的 header 匹配项, $<header>[1]
,等等。但那些是匹配对象,所以我们只是快速地将它们字符串化。 make
命令允许其他 token 访问我们创建的这个特殊数据。我们的值行看起来是一样的,因为
$<value>
token 是我们关心的。 method valueRow ($/) {
my @values = $<value>.map: *.Str
make @values;
}
当我们使用最后一个方法时,我们将要创建带有哈希值的数组。
method TOP ($/) {
my @entries;
my @headers = $<headerRow>.made;
my @rows = $<valueRow>.map: *.made;
for @rows -> @values {
my %entry = flat @headers Z @values;
@entries.push: %entry;
}
make @entries;
}
在这里您可以看到我们如何访问我们在
headerRow()
中处理的内容。和 valueRow()
:您使用 .made
方法。因为有多个valueRows,要得到它们的每一个made
值,我们需要做一个映射(在这种情况下,我倾向于编写我的语法以在语法中简单地包含 <header><data>
,并将数据定义为多行,但这很简单,还不错)。现在我们有两个数组中的标题和行,只需将它们设为散列数组即可,我们在
for
中执行此操作。环形。 flat @x Z @y
只是插入元素,散列分配就是我们的意思,但还有其他方法可以让数组以您想要的散列形式存在。完成后,您只需
make
它,然后它将在 made
中可用解析:say Parser.parse($dat, :actions(ParserActions)).made
-> [{Email => test@email.com, ID => 1, Name => test} {Email => stan@nowhere.net, ID => 321, Name => stan} {}]
将这些包装成一个方法是很常见的,比如
sub parse-tsv($tsv) {
return Parser.parse($tsv, :actions(ParserActions)).made
}
这样你就可以说
my @entries = parse-tsv($dat);
say @entries[0]<Name>; # test
say @entries[1]<Email>; # stan@nowhere.net
关于csv - 如何定义 Raku 语法来解析 TSV 文本?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60510667/