【问题标题】:Regex for Matching Custom Syntax用于匹配自定义语法的正则表达式
【发布时间】:2009-08-29 23:45:27
【问题描述】:

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

【问题讨论】:

    标签: c# regex syntax string.format


    【解决方案1】:

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

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

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

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

    【讨论】:

    • 嘿艾伦,这正是我需要的正则表达式。我已经准备好所有代码来解析各种参数排列并按照您的建议处理错误。所以这很有帮助。再次感谢
    【解决方案2】:

    您可能希望将其实现为有限状态机而不是正则表达式,主要是为了提高速度。 http://en.wikipedia.org/wiki/Finite-state_machine

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

    【讨论】:

    • 更不用说所有相关人员的理智了。虽然,正则表达式被编译为 FSM,所以它应该是可能的......虽然我真的不想读它。上面大多数行中都有 2-3 个字符集的注释这一事实说明了我的观点。
    【解决方案3】:

    这确实应该被解析。

    例如,我想使用 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:\Path\English\someone
    E:\英文
    F:\Some Thing\[D:\path\english.xml]
    C:\[@command]
    c:\[@command :arg]
    c:\[@command : arg1 |参数2]
    

    【讨论】:

    • +1 以获得出色的演示,但我认为 Perl 6 的语法尚未移植到 .NET。 :P
    • 实际上,它并不完全是 Perl 6 语法。它只是松散地基于它。
    • 另外,我想写它,所以我写了。
    • perl 的 regexp::grammars 摇滚。 .NET 有类似的东西吗?
    猜你喜欢
    • 2012-04-15
    • 2021-04-07
    • 1970-01-01
    • 2018-04-29
    • 2010-10-30
    • 1970-01-01
    • 2020-06-13
    • 2018-07-13
    • 2018-05-03
    相关资源
    最近更新 更多