맞춤 구문 일치에 대한 Regex
-
20-09-2019 - |
문제
C#에서 맞춤형 변수 구문을 일치시키고 분할하기 위해 정규 표현식을 작성하려고합니다. 여기서 아이디어는 문자열 형식의 .net string.format/{0} 스타일과 매우 유사한 문자열 값의 사용자 정의 형식입니다.
예를 들어, 사용자는 런타임에서 평가할 문자열 형식을 다음과 같이 정의합니다.
D:\Path\{LanguageId}\{PersonId}\
값 'languageId'는 데이터 객체 필드와 일치하며 현재 값이 대체됩니다.
서식 필드에 인수를 전달할 필요가있을 때 상황이 까다로워집니다. 예를 들어:
{LanguageId:English|Spanish|French}
'languageId'의 값이 인수 중 하나와 같으면 조건부 논리를 실행하는 의미가 있습니다.
마지막으로 다음과 같은지도 인수를 지원해야합니다.
{LanguageId:English=>D:\path\english.xml|Spanish=>D:\path\spansih.xml}
다음은 가능한 모든 값의 열거입니다.
명령은 없다: 특별한 일을하십시오
{@Date}
명령 단일 인수 :
{@Date:yyyy-mm-dd}
논쟁 없음 :
{LanguageId}
단일 인수 목록 :
{LanguageId:English}
멀티 인수 목록 :
{LanguageId:English|Spanish}
단일 인수 맵 :
{LanguageId:English=>D:\path\english.xml}
다중 인수 맵 :
{LanguageId:English=>D:\path\english.xml|Spanish=>D:\path\spansih.xml}
요약 : 구문은 옵션 매개 변수 유형 목록 또는 맵 (둘 다가 아님)을 사용하여 키로 끓일 수 있습니다.
아래는 지금까지 몇 가지 문제가있는 Regex입니다. 즉, 모든 공백을 올바르게 처리하지는 않습니다. .NET에서는 내가 기대하는 분할을 얻지 못합니다. 예를 들어 첫 번째 예에서는 두 개의 별개의 일치 대신 '{language} {personid}'의 단일 경기를 반환했습니다. 또한 파일 시스템 경로를 처리하거나 인용 한 문자열을 다루지 않을 것이라고 확신합니다. 나를 혹을 극복하는 데 도움이 될 것입니다. 또는 모든 권장 사항.
private const string RegexMatch = @"
\{ # opening curly brace
[\s]* # whitespace before command
@? # command indicator
(.[^\}\|])+ # string characters represening command or metadata
( # begin grouping of params
: # required param separater
( # begin select list param type
( # begin group of list param type
.+[^\}\|] # string of characters for the list item
(\|.+[^\}\|])* # optional multiple list items with separator
) # end select list param type
| # or select map param type
( # begin group of map param type
.+[^\}\|]=>.+[^\}\|] # string of characters for map key=>value pair
(\|.+[^\}\|]=>.+[^\}\|])* # optional multiple param map items
) # end group map param type
) # end select map param type
) # end grouping of params
? # allow at most 1 param group
\s*
\} # closing curly brace
";
해결책
당신은 한 번의 정규식으로 너무 많은 일을하려고합니다. 나는 당신이 작업을 단계로 나누는 것이 좋습니다. 첫 번째는 변수처럼 보이는 것과의 단순한 일치입니다. 그 선수는 다음과 같이 간단 할 수 있습니다.
\{\s*([^{}]+?)\s*\}
이는 전체 변수/명령 문자열을 그룹 #1, 브레이스 및 주변 공백을 빼고 저장합니다. 그 후에는 콜론, 파이프를 쪼개고 나서 "=>"
적절한 시퀀스. 모든 복잡성을 하나의 몬스터 Regex로 압축하지 마십시오. REGEX를 작성할 수 있다면 나중에 요구 사항이 변경 될 때 유지하는 것이 불가능하다는 것을 알게 될 것입니다.
그리고 또 다른 것 : 지금, 당신은 입력이 정확할 때 코드가 작동하도록하는 데 집중하고 있지만, 사용자가 잘못되면 어떨까요? 도움이되는 피드백을주고 싶지 않습니까? Regexes는 그것에 빠진다. 그들은 엄격하게 통과/실패합니다. Regexes는 놀랍도록 유용 할 수 있지만 다른 도구와 마찬가지로 전체 힘을 활용하려면 한계를 배워야합니다.
다른 팁
주로 속도 퓨 러스를 위해이를 선별 대신 환불 상태 기계로 구현하는 것을 살펴볼 수 있습니다. http://en.wikipedia.org/wiki/finite-state_machine
편집 : 실제로 정확하게 말하면 결정 론적 유한 상태 기계를보고 싶습니다. http://en.wikipedia.org/wiki/deterministic_finite-state_machine
이것은 실제로 구문 분석해야합니다.
예를 들어, 나는 이것을 사용하여 이것을 구문 분석하고 싶었다 Regexp::Grammars
.
길이를 실례합니다.
#! /opt/perl/bin/perl
use strict;
use warnings;
use 5.10.1;
use Regexp::Grammars;
my $grammar = qr{
^<Path>$
<objtoken: My::Path>
<drive=([a-zA-Z])>:\\ <[elements=PathElement]> ** (\\) \\?
<rule: PathElement>
(?:
<MATCH=BlockPathElement>
|
<MATCH=SimplePathElement>
)
<token: SimplePathElement>
(?<= \\ ) <MATCH=([^\\]+)>
<rule: My::BlockPathElement>
(?<=\\){ \s*
(?|
<MATCH=Command>
|
<MATCH=Variable>
)
\s* }
<objrule: My::Variable>
<name=(\w++)> <options=VariableOptionList>?
<rule: VariableOptionList>
:
<[MATCH=VariableOptionItem]> ** ([|])
<token: VariableOptionItem>
(?:
<MATCH=VariableOptionMap>
|
<MATCH=( [^{}|]+? )>
)
<objrule: My::VariableOptionMap>
\s*
<name=(\w++)> => <value=([^{}|]+?)>
\s*
<objrule: My::Command>
@ <name=(\w++)>
(?:
: <[arg=CommandArg]> ** ([|])
)?
<token: CommandArg>
<MATCH=([^{}|]+?)> \s*
}x;
테스트 :
use YAML;
while( my $line = <> ){
chomp $line;
local %/;
if( $line =~ $grammar ){
say Dump \%/;
}else{
die "Error: $line\n";
}
}
샘플 데이터 :
D:\Path\{LanguageId}\{PersonId} E:\{ LanguageId : English | Spanish | French } F:\Some Thing\{ LanguageId : English => D:\path\english.xml | Spanish => D:\path\spanish.xml } C:\{@command} c:\{@command :arg} c:\{ @command : arg1 | arg2 }
결과 :
---
'': 'D:\Path\{LanguageId}\{PersonId}'
Path: !!perl/hash:My::Path
'': 'D:\Path\{LanguageId}\{PersonId}'
drive: D
elements:
- Path
- !!perl/hash:My::Variable
'': LanguageId
name: LanguageId
- !!perl/hash:My::Variable
'': PersonId
name: PersonId
---
'': 'E:\{ LanguageId : English | Spanish | French }'
Path: !!perl/hash:My::Path
'': 'E:\{ LanguageId : English | Spanish | French }'
drive: E
elements:
- !!perl/hash:My::Variable
'': 'LanguageId : English | Spanish | French'
name: LanguageId
options:
- English
- Spanish
- French
---
'': 'F:\Some Thing\{ LanguageId : English => D:\path\english.xml | Spanish => D:\path\spanish.xml }'
Path: !!perl/hash:My::Path
'': 'F:\Some Thing\{ LanguageId : English => D:\path\english.xml | Spanish => D:\path\spanish.xml }'
drive: F
elements:
- Some Thing
- !!perl/hash:My::Variable
'': 'LanguageId : English => D:\path\english.xml | Spanish => D:\path\spanish.xml '
name: LanguageId
options:
- !!perl/hash:My::VariableOptionMap
'': 'English => D:\path\english.xml '
name: English
value: D:\path\english.xml
- !!perl/hash:My::VariableOptionMap
'': 'Spanish => D:\path\spanish.xml '
name: Spanish
value: D:\path\spanish.xml
---
'': 'C:\{@command}'
Path: !!perl/hash:My::Path
'': 'C:\{@command}'
drive: C
elements:
- !!perl/hash:My::Command
'': '@command'
name: command
---
'': 'c:\{@command :arg}'
Path: !!perl/hash:My::Path
'': 'c:\{@command :arg}'
drive: c
elements:
- !!perl/hash:My::Command
'': '@command :arg'
arg:
- arg
name: command
---
'': 'c:\{ @command : arg1 | arg2 }'
Path: !!perl/hash:My::Path
'': 'c:\{ @command : arg1 | arg2 }'
drive: c
elements:
- !!perl/hash:My::Command
'': '@command : arg1 | arg2 '
arg:
- arg1
- arg2
name: command
샘플 프로그램 :
my %ARGS = qw'
LanguageId English
PersonId someone
';
while( my $line = <> ){
chomp $line;
local %/;
if( $line =~ $grammar ){
say $/{Path}->fill( %ARGS );
}else{
say 'Error: ', $line;
}
}
{
package My::Path;
sub fill{
my($self,%args) = @_;
my $out = $self->{drive}.':';
for my $element ( @{ $self->{elements} } ){
if( ref $element ){
$out .= '\\' . $element->fill(%args);
}else{
$out .= "\\$element";
}
}
return $out;
}
}
{
package My::Variable;
sub fill{
my($self,%args) = @_;
my $name = $self->{name};
if( exists $args{$name} ){
$self->_fill( $args{$name} );
}else{
my $lc_name = lc $name;
my @possible = grep {
lc $_ eq $lc_name
} keys %args;
die qq'Cannot find argument for variable "$name"\n' unless @possible;
if( @possible > 1 ){
my $die = qq'Cannot determine which argument matches "$name" closer:\n';
for my $possible( @possible ){
$die .= qq' "$possible"\n';
}
die $die;
}
$self->_fill( $args{$possible[1]} );
}
}
sub _fill{
my($self,$opt) = @_;
# This is just an example.
unless( exists $self->{options} ){
return $opt;
}
for my $element ( @{$self->{options}} ){
if( ref $element ){
return '['.$element->value.']' if lc $element->name eq lc $opt;
}elsif( lc $element eq lc $opt ){
return $opt;
}
}
my $name = $self->{name};
my $die = qq'Invalid argument "$opt" for "$name" :\n';
for my $valid ( @{$self->{options}} ){
$die .= qq' "$valid"\n';
}
die $die;
}
}
{
package My::VariableOptionMap;
sub name{
my($self) = @_;
return $self->{name};
}
}
{
package My::Command;
sub fill{
my($self,%args) = @_;
return '['.$self->{''}.']';
}
}
{
package My::VariableOptionMap;
sub name{
my($self) = @_;
return $self->{name};
}
sub value{
my($self) = @_;
return $self->{value};
}
}
예제 데이터를 사용한 출력 :
D:\Path\English\someone E:\English F:\Some Thing\[D:\path\english.xml] C:\[@command] c:\[@command :arg] c:\[@command : arg1 | arg2 ]