csv - 如何定义 Raku 语法来解析 TSV 文本?

标签 csv grammar raku

我有一些 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/

相关文章:

bash - 拆分 CSV 并使用 awk 添加标题和索引列

python - 使用 Python 检查文件是否为 CSV 格式

whitespace - 什么时候空格在 Perl6 语法中真正重要?

grammar - 为上下文敏感语言抽引理?

raku - perl6 冲刺 vs 文件句柄

python - 写入 CSV 文件

Excel 废墟 CSV

python - 使用 Python 的语法分析器并从树中构建文件

linux - 通过 Proc::Async 执行的命令不是正确的 "tapped"

raku - perl6 精度 Base4 转换