Motif Regex à faire correspondre, sauf quand… / sauf entre

108

--Modifier-- Les réponses actuelles ont quelques idées utiles mais je veux quelque chose de plus complet que je puisse comprendre et réutiliser à 100%; c'est pourquoi j'ai mis une prime. De plus, les idées qui fonctionnent partout sont meilleures pour moi que la syntaxe standard comme\K

Cette question porte sur la façon dont je peux faire correspondre un modèle, sauf dans certaines situations s1 s2 s3. Je donne un exemple spécifique pour montrer ma signification mais je préfère une réponse générale que je peux comprendre à 100% pour pouvoir la réutiliser dans d'autres situations.

Exemple

Je veux faire correspondre cinq chiffres en utilisant \b\d{5}\bmais pas dans trois situations s1 s2 s3:

s1: Pas sur une ligne qui se termine par un point comme cette phrase.

s2: Pas n'importe où dans les parens.

s3: pas dans un bloc qui commence par if(et se termine par//endif

Je sais comment résoudre n'importe lequel des s1 s2 s3 avec une anticipation et un regard en arrière, en particulier en C # lookbehind ou \Ken PHP.

Par exemple

s1 (?m)(?!\d+.*?\.$)\d+

s3 avec C # lookbehind (?<!if\(\D*(?=\d+.*?//endif))\b\d+\b

s3 avec PHP \ K (?:(?:if\(.*?//endif)\D*)*\K\d+

Mais le mélange des conditions fait exploser ma tête. Encore plus de mauvaises nouvelles, c'est que je devrai peut-être ajouter d'autres conditions s4 s5 à un autre moment.

La bonne nouvelle est que je m'en fiche si je traite les fichiers en utilisant les langages les plus courants comme PHP, C #, Python ou la machine à laver de mon voisin. :) Je suis à peu près un débutant en Python et Java mais intéressé à savoir s'il a une solution.

Je suis donc venu ici pour voir si quelqu'un pense à une recette flexible.

Les indices sont corrects: vous n'avez pas besoin de me donner le code complet. :)

Je vous remercie.

Hans Schindler
la source
1
\KIl n'y a pas de syntaxe php spéciale. Veuillez préciser et clarifier ce que vous voulez dire. Si vous souhaitez nous dire que vous n'avez pas besoin d'une solution «compliquée», vous devez dire ce qui est compliqué pour vous et pourquoi.
hakre
@hakre Vous voulez dire parce que ruby ​​l'utilise maintenant et que ça a commencé en perl?
Hans Schindler
1
Non, car c'est PCRE qui n'est pas PHP (ni Ruby). Perl est différent mais PCRE vise à être compatible avec Perl Regex.
hakre
Vos exigences s2 et s3 semblent contradictoires. s2 implique que les parenthèses sont toujours mises en correspondance et peuvent être imbriquées, mais s3 exige que le: "if("open paren soit fermé, non pas avec a ")", mais plutôt avec a "//endif":? Et si pour s3 vous vouliez vraiment dire que la clause if doit être fermée avec "//endif)":, alors l'exigence s3 est un sous-ensemble de s2.
ridgerunner
@hakre Oui je connais PCRE mais pour expliquer, la question porte sur le langage de programmation ... ça dit especially in C# lookbehind or \K in PHP... Mais C # regarde en arrière pas seulement C # c'est .NET donc vous pouvez vous plaindre aussi je dis C # pas .NET :) Et en réponse je dis Ruby pas Onigurama c'est mauvais aussi ... Y a-t-il un autre langage qui utilise PCRE? Ne pas parler de Notepad ++ ou des outils de serveur, c'est une question sur l'utilisation de la fonctionnalité dans le langage, j'espère l'expliquer et désolé si cela semble faux
Hans Schindler

Réponses:

205

Hans, je vais prendre l'appât et étoffer ma réponse précédente. Vous avez dit que vous vouliez «quelque chose de plus complet», alors j'espère que la longue réponse ne vous dérangera pas - juste essayer de plaire. Commençons par un peu de contexte.

Tout d'abord, c'est une excellente question. Il y a souvent des questions sur la correspondance de certains modèles, sauf dans certains contextes (par exemple, dans un bloc de code ou entre parenthèses). Ces questions donnent souvent lieu à des solutions assez délicates. Votre question sur plusieurs contextes est donc un défi particulier.

Surprise

De manière surprenante, il existe au moins une solution efficace, générale, facile à mettre en œuvre et agréable à entretenir. Il fonctionne avec toutes les versions de regex qui vous permettent d'inspecter les groupes de capture dans votre code. Et il se trouve qu'il répond à un certain nombre de questions courantes qui peuvent au premier abord sembler différentes des vôtres: "correspond à tout sauf Donuts", "remplace tout sauf ...", "correspond à tous les mots sauf ceux de la liste noire de ma mère", "ignorer balises "," correspondent à la température sauf en italique "...

Malheureusement, la technique n'est pas bien connue: j'estime que sur vingt questions SO qui pourraient l'utiliser, une seule a une réponse qui la mentionne - ce qui signifie peut-être une réponse sur cinquante ou soixante. Voir mon échange avec Kobi dans les commentaires. La technique est décrite en détail dans cet article qui l'appelle (avec optimisme) le "meilleur truc de regex jamais créé". Sans entrer dans les détails, je vais essayer de vous donner une idée précise du fonctionnement de la technique. Pour plus de détails et des exemples de code dans différentes langues, je vous encourage à consulter cette ressource.

Une variante mieux connue

Il existe une variante utilisant la syntaxe spécifique à Perl et PHP qui accomplit la même chose. Vous le verrez sur SO entre les mains de maîtres regex tels que CasimiretHippolyte et HamZa . Je vous en dirai plus à ce sujet ci-dessous, mais je me concentre ici sur la solution générale qui fonctionne avec toutes les saveurs de regex (tant que vous pouvez inspecter les groupes de capture dans votre code).

Merci pour tout le fond, zx81 ... Mais quelle est la recette?

Fait clé

La méthode renvoie la correspondance dans la capture du groupe 1. Il ne se soucie pas du tout du match global.

En fait, l'astuce est de faire correspondre les différents contextes que nous ne voulons pas (enchaîner ces contextes à l'aide du |OU / alternance) pour les «neutraliser». Après avoir établi tous les contextes indésirables, la dernière partie de l'alternance correspond à ce que nous ne voulons et le capture au Groupe 1.

La recette générale est

Not_this_context|Not_this_either|StayAway|(WhatYouWant)

Cela correspondra Not_this_context, mais dans un sens, cette correspondance va dans une poubelle, car nous ne regarderons pas les correspondances globales: nous ne regardons que les captures du groupe 1.

Dans votre cas, avec vos chiffres et vos trois contextes à ignorer, nous pouvons faire:

s1|s2|s3|(\b\d+\b)

Notez que parce que nous faisons correspondre s1, s2 et s3 au lieu d'essayer de les éviter avec des lookarounds, les expressions individuelles pour s1, s2 et s3 peuvent rester claires comme le jour. (Ce sont les sous-expressions de chaque côté de a |)

L'expression entière peut être écrite comme ceci:

(?m)^.*\.$|\([^\)]*\)|if\(.*?//endif|(\b\d+\b)

Regardez cette démo (mais concentrez-vous sur les groupes de capture dans le volet inférieur droit.)

Si vous essayez mentalement de diviser cette expression régulière à chaque |délimiteur, il ne s'agit en fait que d'une série de quatre expressions très simples.

Pour les saveurs qui prennent en charge l'espacement libre, cela se lit particulièrement bien.

(?mx)
      ### s1: Match line that ends with a period ###
^.*\.$  
|     ### OR s2: Match anything between parentheses ###
\([^\)]*\)  
|     ### OR s3: Match any if(...//endif block ###
if\(.*?//endif  
|     ### OR capture digits to Group 1 ###
(\b\d+\b)

Ceci est exceptionnellement facile à lire et à maintenir.

Extension de l'expression régulière

Lorsque vous souhaitez ignorer plus de situations s4 et s5, vous les ajoutez en plusieurs alternances sur la gauche:

s4|s5|s1|s2|s3|(\b\d+\b)

Comment cela marche-t-il?

Les contextes que vous ne voulez pas sont ajoutés à une liste d'alternatives sur la gauche: ils correspondent, mais ces correspondances globales ne sont jamais examinées, donc les faire correspondre est un moyen de les mettre dans une "poubelle".

Cependant, le contenu souhaité est capturé dans le groupe 1. Vous devez ensuite vérifier par programme que le groupe 1 est défini et non vide. C'est une tâche de programmation triviale (et nous parlerons plus tard de la façon dont cela est fait), d'autant plus qu'elle vous laisse avec une simple expression régulière que vous pouvez comprendre en un coup d'œil et réviser ou étendre si nécessaire.

Je ne suis pas toujours fan des visualisations, mais celle-ci montre à quel point la méthode est simple. Chaque «ligne» correspond à une correspondance potentielle, mais seule la ligne du bas est capturée dans le groupe 1.

Visualisation des expressions régulières

Démo Debuggex

Variation Perl / PCRE

Contrairement à la solution générale ci-dessus, il existe une variation pour Perl et PCRE qui est souvent vue sur SO, au moins entre les mains de regex Gods tels que @CasimiretHippolyte et @HamZa. C'est:

(?:s1|s2|s3)(*SKIP)(*F)|whatYouWant

Dans ton cas:

(?m)(?:^.*\.$|\([^()]*\)|if\(.*?//endif)(*SKIP)(*F)|\b\d+\b

Cette variante est un peu plus facile à utiliser car le contenu correspondant dans les contextes s1, s2 et s3 est simplement ignoré, vous n'avez donc pas besoin d'inspecter les captures du groupe 1 (notez que les parenthèses ont disparu). Les matchs ne contiennent quewhatYouWant

Notez que (*F), (*FAIL)et (?!)sont tous la même chose. Si vous vouliez être plus obscur, vous pourriez utiliser(*SKIP)(?!)

démo pour cette version

Applications

Voici quelques problèmes courants que cette technique peut souvent résoudre facilement. Vous remarquerez que le choix des mots peut faire que certains de ces problèmes sonnent différemment alors qu'ils sont pratiquement identiques.

  1. Comment puis-je faire correspondre foo sauf n'importe où dans une balise comme <a stuff...>...</a>?
  2. Comment puis-je faire correspondre foo sauf dans une <i>balise ou un extrait de code javascript (plus de conditions)?
  3. Comment puis-je faire correspondre tous les mots qui ne figurent pas sur cette liste noire?
  4. Comment puis-je ignorer quoi que ce soit à l'intérieur d'un bloc SUB ... END SUB?
  5. Comment puis-je faire correspondre tout sauf ... s1 s2 s3?

Comment programmer les captures du groupe 1

Vous n'avez pas comme pour le code, mais, pour la complétion ... Le code à inspecter dans le groupe 1 dépendra évidemment de la langue de votre choix. En tout cas, il ne devrait pas ajouter plus de quelques lignes au code que vous utiliseriez pour inspecter les correspondances.

En cas de doute, je vous recommande de consulter la section des exemples de code de l'article mentionné précédemment, qui présente du code pour un certain nombre de langues.

Alternatives

En fonction de la complexité de la question et du moteur regex utilisé, il existe plusieurs alternatives. Voici les deux qui peuvent s'appliquer à la plupart des situations, y compris plusieurs conditions. À mon avis, ni l'un ni l'autre n'est aussi attrayant que la s1|s2|s3|(whatYouWant)recette, ne serait-ce que parce que la clarté l'emporte toujours.

1. Remplacez puis Match.

Une bonne solution qui semble piratée mais qui fonctionne bien dans de nombreux environnements est de travailler en deux étapes. Une première regex neutralise le contexte que vous souhaitez ignorer en remplaçant les chaînes potentiellement conflictuelles. Si vous souhaitez uniquement faire correspondre, vous pouvez remplacer par une chaîne vide, puis exécuter votre correspondance dans la deuxième étape. Si vous souhaitez remplacer, vous pouvez d'abord remplacer les chaînes à ignorer par quelque chose de distinctif, par exemple en entourant vos chiffres d'une chaîne de largeur fixe de @@@. Après ce remplacement, vous êtes libre de remplacer ce que vous vouliez vraiment, puis vous devrez rétablir vos @@@chaînes distinctives .

2. Lookarounds.

Votre message d'origine montrait que vous compreniez comment exclure une seule condition à l'aide de méthodes d'analyse. Vous avez dit que C # est parfait pour cela, et vous avez raison, mais ce n'est pas la seule option. Les saveurs .NET regex trouvées dans C #, VB.NET et Visual C ++ par exemple, ainsi que le regexmodule encore expérimental à remplacer reen Python, sont les deux seuls moteurs que je connais qui prennent en charge le lookbehind de largeur infinie. Avec ces outils, une condition dans un regard en arrière peut prendre en charge non seulement de regarder en arrière, mais aussi le match et au-delà du match, évitant ainsi le besoin de se coordonner avec une anticipation. Plus de conditions? Plus de lookarounds.

En recyclant le regex que vous aviez pour s3 en C #, tout le modèle ressemblerait à ceci.

(?!.*\.)(?<!\([^()]*(?=\d+[^)]*\)))(?<!if\(\D*(?=\d+.*?//endif))\b\d+\b

Mais maintenant vous savez que je ne recommande pas cela, non?

Suppressions

@HamZa et @Jerry ont suggéré de mentionner une astuce supplémentaire pour les cas où vous cherchez simplement à supprimer WhatYouWant. Vous vous souvenez que la recette à associer WhatYouWant(la capturer dans le groupe 1) était s1|s2|s3|(WhatYouWant), non? Pour supprimer toutes les instances de WhatYouWant, vous changez l'expression régulière en

(s1|s2|s3)|WhatYouWant

Pour la chaîne de remplacement, vous utilisez $1. Ce qui se passe ici, c'est que pour chaque instance de s1|s2|s3cette correspondance, le remplacement $1remplace cette instance par lui-même (référencé par $1). En revanche, quand WhatYouWantest mis en correspondance, il est remplacé par un groupe vide et rien d'autre - et donc supprimé. Regardez cette démo , merci @HamZa et @Jerry d'avoir suggéré ce merveilleux ajout.

Remplaçants

Cela nous amène aux remplacements, sur lesquels je vais aborder brièvement.

  1. Lors du remplacement par rien, voir l'astuce "Suppressions" ci-dessus.
  2. Lors du remplacement, si vous utilisez Perl ou PCRE, utilisez la (*SKIP)(*F)variante mentionnée ci-dessus pour correspondre exactement à ce que vous voulez et effectuez un remplacement direct.
  3. Dans d'autres versions, dans l'appel de fonction de remplacement, inspectez la correspondance à l'aide d'un rappel ou d'un lambda, et remplacez-la si le groupe 1 est défini. Si vous avez besoin d'aide, l'article déjà référencé vous donnera du code en différentes langues.

S'amuser!

Non, attendez, il y a plus!

Ah, non, je garde ça pour mes mémoires en vingt volumes, à paraître au printemps prochain.

zx81
la source
2
@Kobi Réponse en deux parties. Oui, je me suis emporté en écrivant la nuit dernière et j'ai écrit en bas que je dormirais dessus et rangerais plus tard. :) Oui, le truc est simple mais je ne partage pas votre perception selon laquelle il est "basique" car il ne semble pas faire partie des outils courants que les gens utilisent pour résoudre les problèmes d'exclusion. Quand j'ai recherché sur Google des problèmes «sauf» ou «à moins» ou «pas à l'intérieur» sur SO, une seule réponse (sans vote) l'a suggéré, aucune des autres ne l'a fait. Je n'avais pas vu vos réponses, d'ailleurs, qui sont formidables. :)
zx81
2
Désolé, mais le "meilleur truc" de Rex ne fonctionne tout simplement pas (de manière fiable ). Dites que vous voulez faire correspondre Tarzan, mais pas à l’intérieur de guillemets doubles. Le /no|no|(yes)/regex : trick serait quelque chose comme: /"[^"]*"|Tarzan/(ignorant les caractères échappés). Cela fonctionne pour de nombreux cas, mais a complètement échoué lorsqu'il est appliqué au texte JavaScript valide suivant: var bug1 = 'One " quote here. Should match this Tarzan'; var bug2 = "Should not match this Tarzan";. L'astuce de Rex ne fonctionne que lorsque TOUTES les structures possibles sont mises en correspondance - en d'autres termes - vous devez analyser complètement le texte pour garantir une précision de 100%.
ridgerunner
1
Désolé si j'avais l'air dur - ce n'était certainement pas mon intention. Mon point (comme dans mon deuxième commentaire à la question originale ci-dessus) est qu'une solution correcte dépend fortement du texte cible recherché. Mon exemple a le code source JavaScript comme texte cible qui a un guillemet double entouré d'une chaîne entre guillemets simples. Cela aurait pu être tout aussi facilement un RegExp littéral tel que: var bug1 = /"[^"]*"|(Tarzan)/gi;et avoir le même effet (et ce deuxième exemple n'est certainement pas un cas limite). Je pourrais citer de nombreux autres exemples où cette technique ne fonctionne pas de manière fiable.
ridgerunner
1
@ridgerunner J'apprécie toujours avoir de vos nouvelles, cela me semble injustement dur. Lorsque nous savons que nos chaînes peuvent contenir des «fausses alertes», nous ajustons tous nos modèles. Par exemple, pour faire correspondre une chaîne qui peut contenir des guillemets échappés qui pourraient rejeter une correspondance de chaîne, vous pouvez utiliser (?<!\\)"(?:\\"|[^"\r\n])*+" Vous ne tirez pas les gros canons à moins que vous n'ayez une raison. Le principe de la solution est toujours valable. Si nous ne sommes pas en mesure d'exprimer un motif à mettre sur le côté gauche, c'est une autre histoire, nous avons besoin d'une solution différente. Mais la solution fait ce qu'elle annonce.
zx81
1
Cette réponse a été ajoutée à la FAQ sur les expressions régulières Stack Overflow par l'utilisateur @funkwurm.
aliteralmind
11

Faites trois correspondances différentes et gérez la combinaison des trois situations à l'aide de la logique conditionnelle dans le programme. Vous n'avez pas besoin de tout gérer dans une seule regex géante.

EDIT: laissez-moi développer un peu car la question est devenue plus intéressante :-)

L'idée générale que vous essayez de capturer ici est de faire correspondre un certain modèle d'expression régulière, mais pas quand il y a certains autres modèles (qui pourraient être n'importe quel nombre) présents dans la chaîne de test. Heureusement, vous pouvez tirer parti de votre langage de programmation: gardez les expressions régulières simples et utilisez simplement un conditionnel composé. Une meilleure pratique serait de capturer cette idée dans un composant réutilisable, alors créons une classe et une méthode qui l'implémentent:

using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;

public class MatcherWithExceptions {
  private string m_searchStr;
  private Regex m_searchRegex;
  private IEnumerable<Regex> m_exceptionRegexes;

  public string SearchString {
    get { return m_searchStr; }
    set {
      m_searchStr = value;
      m_searchRegex = new Regex(value);
    }
  }

  public string[] ExceptionStrings {
    set { m_exceptionRegexes = from es in value select new Regex(es); }
  }

  public bool IsMatch(string testStr) {
    return (
      m_searchRegex.IsMatch(testStr)
      && !m_exceptionRegexes.Any(er => er.IsMatch(testStr))
    );
  }
}

public class App {
  public static void Main() {
    var mwe = new MatcherWithExceptions();

    // Set up the matcher object.
    mwe.SearchString = @"\b\d{5}\b";
    mwe.ExceptionStrings = new string[] {
      @"\.$"
    , @"\(.*" + mwe.SearchString + @".*\)"
    , @"if\(.*" + mwe.SearchString + @".*//endif"
    };

    var testStrs = new string[] {
      "1." // False
    , "11111." // False
    , "(11111)" // False
    , "if(11111//endif" // False
    , "if(11111" // True
    , "11111" // True
    };

    // Perform the tests.
    foreach (var ts in testStrs) {
      System.Console.WriteLine(mwe.IsMatch(ts));
    }
  }
}

Donc ci-dessus, nous avons configuré la chaîne de recherche (les cinq chiffres), plusieurs chaînes d'exception (vos s1 , s2 et s3 ), puis essayons de faire correspondre plusieurs chaînes de test. Les résultats imprimés doivent être comme indiqué dans les commentaires à côté de chaque chaîne de test.

Yawar
la source
2
Vous voulez dire peut-être comme match trois regex de suite? Regex 1 éliminer la situation 1 (peut-être simplement supprimer le mauvais chiffre), r2 supprimer s2, r3 supprimer s3 et faire correspondre les chiffres à gauche? C'est une idée intéressante.
Hans Schindler
Ha, bien sûr, c'est pourquoi je vous ai voté pour. :) Ne vous méprenez pas, je pense toujours que dans ce cas particulier, ma réponse est plus efficace et maintenable. Avez-vous vu la version à espacement libre que j'ai ajoutée hier? C'est en un seul passage et exceptionnellement facile à lire et à maintenir. Mais j'aime votre travail et votre réponse élargie. Désolé, je ne peux pas voter à nouveau, sinon je le ferais. :)
zx81
2

Votre exigence selon laquelle ce n'est pas à l'intérieur des parens est impossible à satisfaire dans tous les cas. À savoir, si vous pouvez d'une manière ou d'une autre trouver un (à gauche et )à droite, cela ne signifie pas toujours que vous êtes à l'intérieur des parens. Par exemple.

(....) + 55555 + (.....)- pas encore à l'intérieur des parens (et )à gauche et à droite

Maintenant, vous pourriez vous penser intelligent et ne chercher (à gauche que si vous ne rencontrez pas )avant et vice versa à droite. Cela ne fonctionnera pas dans ce cas:

((.....) + 55555 + (.....))- à l'intérieur des parens même s'il y a des fermetures )et (à gauche et à droite.

Il est impossible de savoir si vous êtes à l'intérieur de parens en utilisant regex, car regex ne peut pas compter combien de parens ont été ouvertes et combien fermées.

Considérez cette tâche plus simple: en utilisant regex, découvrez si toutes les parens (éventuellement imbriquées) dans une chaîne sont fermées, c'est-à-dire pour toutes celles que (vous devez trouver ). Vous découvrirez qu'il est impossible à résoudre et si vous ne pouvez pas résoudre cela avec regex, vous ne pouvez pas déterminer si un mot est à l'intérieur de parens dans tous les cas, car vous ne pouvez pas trouver une certaine position dans la chaîne si tous les précédents (ont un correspondant ).

RokL
la source
2
Personne n'a rien dit à propos des parenthèses imbriquées, et votre cas n ° 1 est bien géré par la réponse de zx81.
Dan Bechard
Merci pour les belles pensées :) mais les parenthèses imbriquées ne m'inquiètent pas pour cette question c'est plus l'idée de mauvaises situations s1 s2 s3
Hans Schindler
Bien sûr, ce n'est pas impossible! C'est exactement pourquoi vous devez suivre le niveau de parenté dans lequel vous analysez actuellement.
MrWonderful
Eh bien, si vous analysez une sorte de CFG comme OP semble le faire, vous êtes mieux servi en générant un analyseur LALR ou similaire qui n'a pas de problèmes avec cela.
RokL
2

Hans si cela ne vous dérange pas, j'ai utilisé la machine à laver de votre voisin appelée perl :)

Modifié: sous un pseudo code:

  loop through input
  if line contains 'if(' set skip=true
        if skip= true do nothing
        else
           if line match '\b\d{5}\b' set s0=true
           if line does not match s1 condition  set s1=true
           if line does not match s2 condition  set s2=true
           if s0,s1,s2 are true print line 
  if line contains '//endif' set skip=false

Compte tenu du fichier input.txt:

tiago@dell:~$ cat input.txt 
this is a text
it should match 12345
if(
it should not match 12345
//endif 
it should match 12345
it should not match 12345.
it should not match ( blabla 12345  blablabla )
it should not match ( 12345 )
it should match 12345

Et le script validator.pl:

tiago@dell:~$ cat validator.pl 
#! /usr/bin/perl
use warnings;
use strict;
use Data::Dumper;

sub validate_s0 {
    my $line = $_[0];
    if ( $line =~ \d{5/ ){
        return "true";
    }
    return "false";
}

sub validate_s1 {
    my $line = $_[0];
    if ( $line =~ /\.$/ ){
        return "false";
    }
    return "true";
}

sub validate_s2 {
    my $line = $_[0];
    if ( $line =~ /.*?\(.*\d{5.*?\).*/ ){
        return "false";
    }
    return "true";
}

my $skip = "false";
while (<>){
    my $line = $_; 

    if( $line =~ /if\(/ ){
       $skip = "true";  
    }

    if ( $skip eq "false" ) {
        my $s0_status = validate_s0 "$line"; 
        my $s1_status = validate_s1 "$line";
        my $s2_status = validate_s2 "$line";

        if ( $s0_status eq "true"){
            if ( $s1_status eq "true"){
                if ( $s2_status eq "true"){
                    print "$line";
                }
            }
        }
    } 

    if ( $line =~ /\/\/endif/) {
        $skip="false";
    }
}

Exécution:

tiago @ dell: ~ $ cat input.txt | validateur perl.pl
il devrait correspondre 12345
il devrait correspondre 12345
il devrait correspondre 12345
Tiago Lopo
la source
2

Je ne sais pas si cela vous aiderait ou non, mais je propose une solution en tenant compte des hypothèses suivantes -

  1. Vous avez besoin d'une solution élégante pour vérifier toutes les conditions
  2. Les conditions peuvent changer à l'avenir et à tout moment.
  3. Une condition ne doit pas dépendre des autres.

Cependant, j'ai également considéré ce qui suit -

  1. Le fichier donné contient des erreurs minimes. Si c'est le cas, mon code pourrait avoir besoin de quelques modifications pour faire face à cela.
  2. J'ai utilisé Stack pour suivre les if(blocs.

Ok, voici la solution -

J'ai utilisé C # et avec lui MEF (Microsoft Extensibility Framework) pour implémenter les analyseurs configurables. L'idée est d'utiliser un seul analyseur pour analyser et une liste de classes de validateur configurables pour valider la ligne et retourner vrai ou faux en fonction de la validation. Ensuite, vous pouvez ajouter ou supprimer n'importe quel validateur à tout moment ou en ajouter de nouveaux si vous le souhaitez. Jusqu'à présent, j'ai déjà implémenté pour S1, S2 et S3 que vous avez mentionnés, vérifiez les classes au point 3. Vous devez ajouter des classes pour s4, s5 si vous en avez besoin à l'avenir.

  1. Tout d'abord, créez les interfaces -

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace FileParserDemo.Contracts
    {
        public interface IParser
        {
            String[] GetMatchedLines(String filename);
        }
    
        public interface IPatternMatcher
        {
            Boolean IsMatched(String line, Stack<string> stack);
        }
    }
  2. Puis vient le lecteur et le vérificateur de fichiers -

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using FileParserDemo.Contracts;
    using System.ComponentModel.Composition.Hosting;
    using System.ComponentModel.Composition;
    using System.IO;
    using System.Collections;
    
    namespace FileParserDemo.Parsers
    {
        public class Parser : IParser
        {
            [ImportMany]
            IEnumerable<Lazy<IPatternMatcher>> parsers;
            private CompositionContainer _container;
    
            public void ComposeParts()
            {
                var catalog = new AggregateCatalog();
                catalog.Catalogs.Add(new AssemblyCatalog(typeof(IParser).Assembly));
                _container = new CompositionContainer(catalog);
                try
                {
                    this._container.ComposeParts(this);
                }
                catch
                {
    
                }
            }
    
            public String[] GetMatchedLines(String filename)
            {
                var matched = new List<String>();
                var stack = new Stack<string>();
                using (StreamReader sr = File.OpenText(filename))
                {
                    String line = "";
                    while (!sr.EndOfStream)
                    {
                        line = sr.ReadLine();
                        var m = true;
                        foreach(var matcher in this.parsers){
                            m = m && matcher.Value.IsMatched(line, stack);
                        }
                        if (m)
                        {
                            matched.Add(line);
                        }
                     }
                }
                return matched.ToArray();
            }
        }
    }
  3. Vient ensuite l'implémentation de vérificateurs individuels, les noms de classe sont explicites, donc je ne pense pas qu'ils aient besoin de plus de descriptions.

    using FileParserDemo.Contracts;
    using System;
    using System.Collections.Generic;
    using System.ComponentModel.Composition;
    using System.Linq;
    using System.Text;
    using System.Text.RegularExpressions;
    using System.Threading.Tasks;
    
    namespace FileParserDemo.PatternMatchers
    {
        [Export(typeof(IPatternMatcher))]
        public class MatchAllNumbers : IPatternMatcher
        {
            public Boolean IsMatched(String line, Stack<string> stack)
            {
                var regex = new Regex("\\d+");
                return regex.IsMatch(line);
            }
        }
    
        [Export(typeof(IPatternMatcher))]
        public class RemoveIfBlock : IPatternMatcher
        {
            public Boolean IsMatched(String line, Stack<string> stack)
            {
                var regex = new Regex("if\\(");
                if (regex.IsMatch(line))
                {
                    foreach (var m in regex.Matches(line))
                    {
                        //push the if
                        stack.Push(m.ToString());
                    }
                    //ignore current line, and will validate on next line with stack
                    return true;
                }
                regex = new Regex("//endif");
                if (regex.IsMatch(line))
                {
                    foreach (var m in regex.Matches(line))
                    {
                        stack.Pop();
                    }
                }
                return stack.Count == 0; //if stack has an item then ignoring this block
            }
        }
    
        [Export(typeof(IPatternMatcher))]
        public class RemoveWithEndPeriod : IPatternMatcher
        {
            public Boolean IsMatched(String line, Stack<string> stack)
            {
                var regex = new Regex("(?m)(?!\\d+.*?\\.$)\\d+");
                return regex.IsMatch(line);
            }
        }
    
    
        [Export(typeof(IPatternMatcher))]
        public class RemoveWithInParenthesis : IPatternMatcher
        {
            public Boolean IsMatched(String line, Stack<string> stack)
            {
                var regex = new Regex("\\(.*\\d+.*\\)");
                return !regex.IsMatch(line);
            }
        }
    }
  4. Le programme -

    using FileParserDemo.Contracts;
    using FileParserDemo.Parsers;
    using System;
    using System.Collections.Generic;
    using System.ComponentModel.Composition;
    using System.IO;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace FileParserDemo
    {
        class Program
        {
            static void Main(string[] args)
            {
                var parser = new Parser();
                parser.ComposeParts();
                var matches = parser.GetMatchedLines(Path.GetFullPath("test.txt"));
                foreach (var s in matches)
                {
                    Console.WriteLine(s);
                }
                Console.ReadLine();
            }
        }
    }

Pour les tests, j'ai pris le fichier d'exemple de @ Tiago avec les Test.txtlignes suivantes -

this is a text
it should match 12345
if(
it should not match 12345
//endif 
it should match 12345
it should not match 12345.
it should not match ( blabla 12345  blablabla )
it should not match ( 12345 )
it should match 12345

Donne la sortie -

it should match 12345
it should match 12345
it should match 12345

Je ne sais pas si cela vous aiderait ou non, je me suis bien amusé à jouer avec .... :)

La meilleure partie est que, pour ajouter une nouvelle condition, tout ce que vous avez à faire est de fournir une implémentation de IPatternMatcher, elle sera automatiquement appelée et donc validera.

codeur sans cervelle
la source
2

Identique à @ zx81 (*SKIP)(*F)mais avec une assertion de recherche négative.

(?m)(?:if\(.*?\/\/endif|\([^()]*\))(*SKIP)(*F)|\b\d+\b(?!.*\.$)

DEMO

En python, je ferais facilement comme ça,

import re
string = """cat 123 sat.
I like 000 not (456) though 111 is fine
222 if(  //endif if(cat==789 stuff  //endif   333"""
for line in string.split('\n'):                                  # Split the input according to the `\n` character and then iterate over the parts.
    if not line.endswith('.'):                                   # Don't consider the part which ends with a dot.
        for i in re.split(r'\([^()]*\)|if\(.*?//endif', line):   # Again split the part by brackets or if condition which endswith `//endif` and then iterate over the inner parts.
            for j in re.findall(r'\b\d+\b', i):                  # Then find all the numbers which are present inside the inner parts and then loop through the fetched numbers.
                print(j)                                         # Prints the number one ny one.

Production:

000
111
222
333
Avinash Raj
la source