Regex для сопоставления индивидуального синтаксиса

StackOverflow https://stackoverflow.com/questions/1352657

Вопрос

Я пытаюсь написать регулярное выражение, чтобы соответствовать и разделить индивидуальный синтаксис переменной в C#. Идея здесь - это пользовательское форматирование значений строковых значений, очень похожее на стиль .net string.format/{0} форматирования форматирования строки.

Например, пользователь определит формат строки, который будет оцениваться во время выполнения, как SO:

D:\Path\{LanguageId}\{PersonId}\ 

Значение «LanguageId» соответствует полю объекта данных, и его текущее значение заменяет.

Вещи становятся сложными, когда необходимо передать аргументы в поле форматирования. Например:

{LanguageId:English|Spanish|French}

Это имело бы значение выполнения какой -то условной логики, если бы значение «языковой» было равным одному из аргументов.

Наконец, мне нужно было бы поддерживать такие аргументы карты, как это:

{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 я не получаю раскол, которые я ожидаю. Например, в первом примере я возвращался один матч '{ranguagyD} {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, за исключением брекетов и окружающих пробелов. После этого вы можете разделить на колонсах, затем трубы, затем "=>" последовательности в зависимости от необходимости. Не сжимайте всю сложность в одну режиму монстра; Если вам когда -нибудь удастся выписать корпорацию, вы найдете невозможным поддерживать, когда ваши требования изменятся позже.

И еще одна вещь: прямо сейчас вы сосредоточены на том, чтобы заставить код работать, когда ввод правильный, но как насчет того, когда пользователи ошибаются? Разве вы не хотели бы дать им полезные отзывы? Реферы сосут это; Они строго проходят/терпят неудачу. Руководители могут быть удивительно полезны, но, как и любой другой инструмент, вы должны изучить их ограничения, прежде чем вы сможете использовать их полную мощность.

Другие советы

Возможно, вы захотите взглянуть на реализацию этого как машину по финансированию, а не на корпусе, в основном для скоростных пуропсов. 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 ]
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top