Question

Je suis en train d'écrire une expression régulière pour correspondre et de diviser une syntaxe personnalisée variable en C #. L'idée ici est une mise en forme personnalisée des valeurs de chaîne très similaire à la String.Format .NET / {0} style de mise en forme de chaîne.

Par exemple, l'utilisateur doit définir un format de chaîne à évaluer lors de l'exécution comme ceci:

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

La valeur « languageId » correspond à un champ objet de données, et sa valeur actuelle remplace.

Les choses se compliquent quand il est nécessaire de transmettre des arguments au champ de formatage. Par exemple:

{LanguageId:English|Spanish|French}

Cela aurait le sens de l'exécution de la logique conditionnelle si la valeur de « languageId » était égal à l'un des arguments.

Enfin je aurais besoin pour appuyer les arguments de carte comme celle-ci:

{LanguageId:English=>D:\path\english.xml|Spanish=>D:\path\spansih.xml}

Voici une énumération de toutes les valeurs possibles:

Commande aucun argument : faire quelque chose de spécial

{@Date}

Commande seul argument:

{@Date:yyyy-mm-dd}

Aucun argument:

{LanguageId}

Un seul argument liste:

{LanguageId:English}

Argument liste multi:

{LanguageId:English|Spanish}

Argument-simple carte:

{LanguageId:English=>D:\path\english.xml}

Multi-Argument map:

{LanguageId:English=>D:\path\english.xml|Spanish=>D:\path\spansih.xml}

Résumé:. La syntaxe peut se résumer à une clé avec la liste de type de paramètre optionnel ou d'une carte (pas les deux)

est inférieure à la Regex je jusqu'à présent qui a quelques problèmes, à savoir qu'il ne marche pas gérer correctement tous les espaces, dans .NET Je n'obtiennent pas le grand écart que je attends. Par exemple, dans le premier exemple, je suis retourné une seule de '{} {languageId personID} au lieu de deux matchs distincts. Aussi je suis sûr qu'il ne marche pas gérer chemin du système de fichiers, ou délimités chaînes entre guillemets. Toute aide me faire sur la bosse serait appréciée. Ou toutes les recommandations.

    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
        ";
Était-ce utile?

La solution

Vous essayez de faire trop avec une expression régulière. Je vous suggère de diviser la tâche en étapes, le premier étant un simple match sur quelque chose qui ressemble à une variable. Ce regex pourrait être aussi simple que:

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

Cela permet de gagner votre variable toute / chaîne de commande dans le groupe n ° 1, moins les accolades et les espaces autour. Après cela, vous pouvez diviser sur deux points, puis des tuyaux, puis "=>" séquences selon le cas. Ne pas compresser toute la complexité en un monstre regex; si vous parvenez toujours à obtenir le regex écrit, vous trouverez qu'il est impossible de maintenir quand vos besoins changent plus tard.

Et une autre chose: en ce moment, vous êtes concentré sur l'obtention du code de travail lorsque l'entrée est correcte, mais que quand les utilisateurs se trompent? Ne voudriez-vous leur donner des commentaires utiles? Regexes sucent à cela; ils passent / échec strictement. Regexes peut être incroyablement utile, mais comme tout autre outil, vous devez apprendre leurs limites avant de pouvoir exploiter leur pleine puissance.

Autres conseils

Vous pouvez jeter un coup d'oeil dans la mise en œuvre de cette machine comme Finate-Etat au lieu d'une expression régulière, principalement pour puropses de vitesse. http://en.wikipedia.org/wiki/Finite-state_machine

Edit: En fait, pour être précis, vous voulez regarder les machines déterministes Finite State: http : //en.wikipedia.org/wiki/Deterministic_finite-state_machine

Cela devrait vraiment être analysé.

Pour un exemple, je voulais analyser cela en utilisant Regexp::Grammars .

S'il vous plaît excuser la longueur.

#! /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;

Test avec:

use YAML;
while( my $line = <> ){
  chomp $line;
  local %/;

  if( $line =~ $grammar ){
    say Dump \%/;
  }else{
    die "Error: $line\n";
  }
}

Avec des échantillons de données:

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 }

Résultats dans:

---
'': '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

Exemple de programme:

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};
  }
}

sortie en utilisant l'exemple de données:

D:\Path\English\someone
E:\English
F:\Some Thing\[D:\path\english.xml]
C:\[@command]
c:\[@command :arg]
c:\[@command : arg1 | arg2 ]
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top