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
.
한 가지 작은 권장 사항은 토큰에 줄 바꿈을 포함시키지 않는 것입니다. 또한 교대 연산자를 사용 할 수 있습니다 %
또는 %%
그들이 이런 종류의 작업을 처리하기 위해 설계된 것 같이 :
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 ParserActions {
method headerRow ($/) { ... }
method valueRow ($/) { ... }
method TOP ($/) { ... }
}
각 메소드에는 ($/)
정규식 일치 변수 인 서명 이 있습니다. 이제 각 토큰에서 원하는 정보를 물어 보겠습니다. 헤더 행에서 각 헤더 값을 행으로 원합니다. 그래서:
method headerRow ($/) {
my @headers = $<header>.map: *.Str
make @headers;
}
그것에 한정 기호와 모든 토큰은으로 간주됩니다 Positional
우리는 또한 각 개별 헤더 경기에 액세스 할 수 있도록, $<header>[0]
, $<header>[1]
, 등을하지만 그이 일치하는 객체 우리가 신속하게 캐릭터 라인 화 있도록. 이 make
명령을 사용하면 다른 토큰이 우리가 만든이 특수 데이터에 액세스 할 수 있습니다.
$<value>
토큰이 우리의 관심사 이기 때문에 우리의 가치 행은 동일하게 보일 것 입니다.
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
단지 요소를 intercolates, 해시 할당은 우리가 무엇을 의미하는지에 관해 않지만, 당신이 원하는 해시의 배열을 얻을 수있는 다른 방법이 있습니다.
완료되면, 당신은 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
답변
TL; DR : 당신은하지 않습니다. Text::CSV
모든 형식을 처리 할 수 있는를 사용하십시오 .
나는 몇 살이 Text::CSV
유용한 지 보여줄 것이다 :
use Text::CSV;
my $text = q:to/EOF/;
ID Name Email
1 test test@email.com
321 stan stan@nowhere.net
EOF
my @data = $text.lines.map: *.split(/\t/).list;
say @data.perl;
my $csv = csv( in => @data, key => "ID");
print $csv.perl;
여기서 중요한 부분은 초기 파일을 배열로 변환하는 데이터 녹이는 것입니다 (in @data
). 그러나 csv
명령이 문자열을 처리 할 수 없기 때문에 필요합니다 . 데이터가 파일에 있으면 계속 진행하십시오.
마지막 줄이 인쇄됩니다 :
${" 1" => ${:Email("test\@email.com"), :ID(" 1"), :Name("test")}, " 321" => ${:Email("stan\@nowhere.net"), :ID(" 321"), :Name("stan")}}%
ID 필드는 해시의 핵심이되고 모든 것은 해시 배열이됩니다.
답변
TL; DR regex
의 역 추적. token
하지 않습니다. 그렇기 때문에 패턴이 일치하지 않습니다. 이 답변은이를 설명하고 문법을 간단하게 수정하는 방법에 중점을 둡니다. 그러나 raku 정규식에 대해 배우기보다는 TSV를 구문 분석하려는 경우 반드시 수행해야 할 것입니다.
근본적인 오해?
raku의 정규 표현식에 대한 근본적인 것을 오해하고 있다고 생각합니다.
( “regexes”라는 용어가 매우 모호한 용어라는 것을 이미 알고 있다면이 섹션을 건너 뛰는 것이 좋습니다.)
당신이 오해 할 수있는 한 가지 근본적인 것은 “정규”라는 단어의 의미입니다. 다음은 사람들이 가정하는 일반적인 의미입니다.
-
공식적인 정규 표현식.
-
펄 정규식.
-
Perl 호환 정규 표현식 (PCRE).
-
“regexes”라는 텍스트 패턴 일치 표현식으로, 위와 유사하고 비슷한 것을 수행합니다.
이러한 의미 중 어느 것도 서로 호환되지 않습니다.
Perl 정규식은 의미 상 공식적인 정규 표현식의 상위 집합이지만 여러 가지면에서 훨씬 유용하지만 병리학 적 역 추적에 더 취약합니다 .
Perl 호환 정규식은 원래 1990 년대 후반의 표준 Perl 정규식과 동일 하다는 의미에서 Perl과 호환 가능하지만 Perl 은 PCRE 엔진을 포함한 플러그 가능한 정규식 엔진을 지원한다는 점에서 PCRE 정규식 구문은 표준과 동일하지 않습니다. 2020 년에 Perl이 기본적으로 사용하는 Perl 정규식.
“regexes”라는 텍스트 패턴 일치 표현식은 일반적으로 서로 비슷해 보이고 텍스트와 모두 일치하지만 수십, 수백 가지의 구문 변형 및 심지어 동일한 구문에 대한 의미론이 있습니다.
Raku 텍스트 패턴 일치 표현식은 일반적으로 “rules”또는 “regexes”라고합니다. “regexes”라는 용어는 구문이 정리되었지만 다른 regex와 다소 비슷하다는 사실을 전달합니다. “규칙”이라는 용어는 이들이 구문 분석 (및 그 이상)으로 확장 되는 훨씬 광범위한 기능 및 도구 세트의 일부라는 사실을 전달합니다 .
빠른 수정
위의 “regexes”라는 단어의 근본적인 측면을 벗어나서 이제는 “regex” 동작 의 기본 측면으로 전환 할 수 있습니다 .
token
선언자 에 대한 문법의 세 가지 패턴을 선언자로 전환하면 regex
문법이 의도 한대로 작동합니다.
grammar Parser {
regex TOP { <headerRow><valueRow>+ }
regex headerRow { [\s*<header>]+\n }
token header { \S+ }
regex valueRow { [\s*<value>]+\n? }
token value { \S+ }
}
사이의 유일한 차이 token
와는 regex
이다 regex
반면 역 추적이 token
되지 않습니다. 그러므로:
say 'ab' ~~ regex { [ \s* a ]+ b } # 「ab」
say 'ab' ~~ token { [ \s* a ]+ b } # 「ab」
say 'ab' ~~ regex { [ \s* \S ]+ b } # 「ab」
say 'ab' ~~ token { [ \s* \S ]+ b } # Nil
마지막 패턴을 처리하는 동안 (이것은 종종 “regex”라고 할 수 있지만 실제 선언자는 token
이 아닙니다 regex
) 이전 줄에서 정규식을 처리하는 동안 일시적으로했던 것처럼을 \S
삼킬 'b'
것입니다. 그러나 패턴이로 선언되어 있기 때문에 token
규칙 엔진 (일명 “regex engine”) 은 역 추적하지 않으므로 전체 일치가 실패합니다.
이것이 OP에서 진행중인 일입니다.
올바른 수정
일반적으로 더 나은 해결책은 악의적으로 구성된 문자열이나 실수로 불행한 문자 조합이있는 문자열과 일치 할 때 느리고 심지어 매우 느리게 (프로그램 중단으로 인해 예상 할 수없는) 역 추적 동작 을 가정 하는 것입니다.
때때로 regex
s가 적합합니다. 예를 들어, 일회용을 작성하고 정규식이 작업을 수행하면 완료됩니다. 괜찮아. 이것이 / ... /
raku의 구문이 다음과 같이 역 추적 패턴을 선언하는 이유의 일부입니다 regex
. (그런 다음 다시 당신이 쓸 수있는 / :r ... /
당신이에 전환 할 경우 래 치트 – “래칫은”그래서, “철수”의 반대 의미 :r
로 정규식 스위치를 token
의미합니다.)
때때로 역 추적은 여전히 구문 분석 컨텍스트에서 역할을합니다. 예를 들어, raku의 문법은 일반적으로 역 추적을 피하고 대신 수백 개의 rule
s 및 token
s를 갖지만 그럼에도 불구하고 여전히 3 regex
s를 갖습니다 .
@ user0721090601 ++의 답변이 유용하기 때문에 upvoted했습니다. 또한 코드에서 관용적으로 벗어난 것처럼 보였고 중요한 것은 token
s에 붙어있는 몇 가지 사항을 설명합니다 . 그것은 당신이 선호하는 대답 일 것입니다.