1

我正在尝试编写一个正则表达式来匹配和拆分 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}

摘要:语法可以归结为带有可选参数类型列表或映射(不是两者)的键。

下面是我到目前为止的正则表达式,它有一些问题,即它不能正确处理所有空格,在 .NET 中我没有得到我期望的分割。例如,在第一个示例中,我返回了一个匹配的“{LanguageId}{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
        ";
4

3 回答 3

3

您可能希望将其实现为 Finate-State Machine 而不是正则表达式,主要是为了速度目的。http://en.wikipedia.org/wiki/Finite-state_machine

编辑:实际上,准确地说,您想查看确定性有限状态机:http ://en.wikipedia.org/wiki/Deterministic_finite-state_machine

于 2009-08-29T23:49:43.850 回答
3

你试图用一个正则表达式做太多事情。我建议您将任务分解为多个步骤,首先是对看起来像变量的事物进行简单匹配。该正则表达式可能很简单:

\{\s*([^{}]+?)\s*\}

这将您的整个变量/命令字符串保存在第 1 组中,减去大括号和周围的空格。之后,您可以根据需要拆分冒号,然后是管道,然后"=>"是序列。不要将所有复杂性压缩到一个怪物正则表达式中;如果您设法编写了正则表达式,那么当您的需求稍后发生变化时,您会发现无法维护。

还有一件事:现在,您专注于在输入正确时让代码工作,但是当用户输入错误时呢?你不想给他们有用的反馈吗?正则表达式很糟糕;他们严格通过/失败。正则表达式可能非常有用,但与任何其他工具一样,您必须先了解它们的局限性,然后才能充分利用它们的力量。

于 2009-08-30T02:43:29.540 回答
2

这真的应该被解析。

例如,我想使用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 : 英语 | 西班牙语 | 法语 }
F:\Some Thing\{ LanguageId : English => D:\path\english.xml | 西班牙语 => D:\path\spanish.xml }
C:\{@command}
c:\{@command :arg}
c:\{ @command : arg1 | 参数2}

结果是:

---
'': '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:\路径\英文\某人
E:\英文
F:\Some Thing\[D:\path\english.xml]
C:\[@command]
c:\[@command :arg]
c:\[@command : arg1 | 参数2]
于 2009-08-30T07:12:50.790 回答