Extension de la syntaxe des expressions régulières pour dire “ne contient pas de texte XYZ”

J’ai une application où les utilisateurs peuvent spécifier des expressions régulières à plusieurs endroits. Ceux-ci sont utilisés lors de l’exécution de l’application pour vérifier si le texte (par exemple, les URL et le HTML) correspond aux expressions rationnelles. Souvent, les utilisateurs veulent pouvoir dire où le texte correspond à ABC et ne correspond pas à XYZ . Pour leur faciliter la tâche, je songe à étendre la syntaxe des expressions régulières dans mon application avec un moyen de dire ” et ne contient pas de motif “. Des suggestions sur un bon moyen de faire cela?

Mon application est écrite en C # .NET 3.5.

Mon plan (avant d’avoir les réponses géniales à cette question …)

Actuellement, je pense à utiliser le caractère ¬: tout ce qui précède le caractère ¬ est une expression régulière normale, tout ce qui suit le caractère ¬ est une expression régulière qui ne peut pas correspondre au texte à tester.

Donc, je pourrais utiliser des expressions rationnelles comme cet exemple (artificiel):

on (this|that|these) day(s)?¬(every|all) day(s) ? 

Ce qui, par exemple, correspondrait ” ce jour-là, l’homme a dit … ” mais ne correspondait pas ” ce jour-là et tous les jours après, il y aura … “.

Dans mon code qui traite la regex, je vais simplement séparer les deux parties de la regex et les traiter séparément, par exemple:

  public bool IsMatchExtended(ssortingng textToTest, ssortingng extendedRegex) { int notPosition = extendedRegex.IndexOf('¬'); // Just a normal regex: if (notPosition==-1) return Regex.IsMatch(textToTest, extendedRegex); // Use a positive (normal) regex and a negative one ssortingng positiveRegex = extendedRegex.Subssortingng(0, notPosition); ssortingng negativeRegex = extendedRegex.Subssortingng(notPosition + 1, extendedRegex.Length - notPosition - 1); return Regex.IsMatch(textToTest, positiveRegex) && !Regex.IsMatch(textToTest, negativeRegex); } 

Des suggestions sur une meilleure façon de mettre en œuvre une telle extension? J’aurais besoin d’être un peu plus intelligent pour scinder la chaîne sur le caractère ¬ pour lui permettre d’être échappé. Par conséquent, n’utilisez pas simplement la division de Subssortingng () ci-dessus. Quelque chose à considérer?

Plan alternatif

En écrivant cette question, je suis également tombé sur cette réponse qui suggère d’utiliser quelque chose comme ceci:

 ^(?=(?:(?!negative pattern).)*$).*?positive pattern 

Je pourrais donc simplement conseiller aux gens d’utiliser un modèle tel que, au lieu de mon plan original, quand ils ne veulent PAS faire correspondre certains textes.

Est-ce que cela ferait l’équivalent de mon plan original? Je pense que c’est un moyen assez coûteux de le faire en termes de performances, et comme je suis parfois en train d’parsingr de gros documents HTML, cela peut poser problème, alors que je suppose que mon plan initial serait plus performant. Avez-vous des pensées (en dehors de l’évidence: “essayez les deux et mesurez-les!”)?

Peut-être pertinent pour la performance: parfois, il y aura plusieurs «mots» ou une regex plus complexe qui ne peut pas être dans le texte, comme (tous | tous) dans mon exemple ci-dessus, mais avec quelques variations supplémentaires.

Pourquoi!?

Je sais que mon approche initiale semble étrange, par exemple pourquoi ne pas simplement avoir deux regex!? Mais dans mon application particulière, les administrateurs fournissent les expressions régulières et il serait assez difficile de leur donner la possibilité de fournir deux expressions régulières partout où ils peuvent actuellement en fournir une. Il est beaucoup plus facile dans ce cas d’avoir une syntaxe pour NOT – faites-moi confiance sur ce point.

J’ai une application qui permet aux administrateurs de définir des expressions régulières à différents points de configuration. Les expressions régulières sont simplement utilisées pour vérifier si le texte ou les URL correspondent à un certain motif. les remplacements ne sont pas effectués et les groupes de capture ne sont pas utilisés. Cependant, ils souhaitent souvent spécifier un motif indiquant «où ABC n’est pas dans le texte». Il est notoirement difficile de ne PAS faire de correspondance dans les expressions régulières. Le moyen habituel est donc de créer deux expressions régulières: une pour spécifier un motif à faire correspondre et une autre pour spécifier un motif qui ne doit pas correspondre. Si le premier correspond et que le second ne l’est pas, le texte correspond. Dans mon application, append la possibilité d’avoir une deuxième expression régulière à chaque endroit que les utilisateurs peuvent maintenant en fournir constituerait un travail considérable. Je voudrais donc étendre la syntaxe des expressions régulières avec un moyen de dire ” et ne contient pas de motif “. .

Vous n’avez pas besoin d’introduire un nouveau symbole. Il existe déjà une assistance pour ce dont vous avez besoin dans la plupart des moteurs d’expression régulière. C’est juste une question d’apprendre et d’appliquer.

Vous avez des inquiétudes concernant les performances, mais l’avez-vous testé? Avez-vous mesuré et démontré ces problèmes de performance? Ce sera probablement bien.

Regex fonctionne pour beaucoup de gens, dans beaucoup de scénarios différents. Cela correspond probablement à vos besoins.

En outre, la regex compliquée que vous avez trouvée sur l’autre question SO peut être simplifiée. Il existe des expressions simples pour qualifier les aspects négatifs et positifs.
?! ? ?= ?<=


Quelques exemples

Supposons que l'exemple de texte soit

Albatross

Compte tenu des expressions rationnelles suivantes, voici les résultats que vous verrez:

  1. tr - match
  2. td - match
  3. ^td - pas de correspondance
  4. ^tr - pas de correspondance
  5. ^ - correspond
  6. ^
    .*

    - pas de correspondance

  7. ^.* - correspondance
  8. ^.*(?
    )

    - correspond

  9. ^.*(?) - pas de correspondance
  10. ^.*(? - match
  11. ^.*(? - pas de correspondance
  12. ^(?!.*Albatross.*).* - pas de correspondance

Des explications

Les deux premiers correspondent car l'expression rationnelle peut s'appliquer n'importe où dans la chaîne sample (ou test). Les deux autres ne correspondent pas, car ^ indique "commence par le début" et que la chaîne de test ne commence pas par td ou tr - elle commence par un crochet à gauche.

Le cinquième exemple correspond car la chaîne de test commence par . Le sixième ne le fait pas, car il veut que la chaîne d'échantillon commence par

, avec un crochet d'angle de fermeture suivant immédiatement le tr , mais dans la chaîne de test réelle, l'ouverture tr inclut l'atsortingbut valign . Par conséquent, tr est un espace. . La 7ème expression régulière montre comment autoriser l'espace et l'atsortingbut avec des caractères génériques.

La 8ème expression régulière applique une assertion de recherche positive à la fin de l'expression régulière, en utilisant ?< . Il dit, ne correspond à l'intégralité de la regex que si ce qui précède immédiatement le curseur dans la chaîne de test correspond à ce qu'il y a dans les parenthèses, après le ?< . Dans ce cas, ce qui suit est tr> . Après évaluation de `` ^. * , the cursor in the test ssortingng is positioned at the end of the test ssortingng. Therefore, the , the cursor in the test ssortingng is positioned at the end of the test ssortingng. Therefore, the tr> `correspond à la fin de la chaîne de test, qui est évaluée à TRUE. Par conséquent, le lookbehind positif est évalué à true, donc les expressions rationnelles globales correspondent.

Le neuvième exemple montre comment insérer une assertion de recherche négative en utilisant ? . En gros, il dit "permettre à la regex de correspondre si ce qui se trouve juste derrière le curseur à ce point ne correspond pas à ce qui suit ? Dans les parenthèses, qui est dans ce cas tr> . Le bit de regex précédant l'assertion, ^.* correspond à la fin de la chaîne incluse, car le motif tr> correspond à la fin de la chaîne. Mais il s'agit d'une assertion négative, elle est donc évaluée à FALSE, ce qui signifie que la neuvième Cet exemple n'est pas une correspondance.

Le dixième exemple utilise une autre assertion de regard négatif. En gros, il est dit "permet à la regex de correspondre si ce qui est juste derrière le curseur à ce point ne correspond pas à ce qui est dans les parens, dans ce cas, Albatross . Le bit de regex précédant l'assertion, ^.* correspond à la fin de la chaîne incluse. La sélection de "Albatros" par rapport à la fin de la chaîne donne une correspondance négative, car la chaîne de test se termine par . En effet, le motif à l'intérieur du parens négatif du lookbehind négatif NOT match, cela signifie que le lookbehind négatif est évalué à TRUE, ce qui signifie que le dixième exemple est une correspondance.

Le onzième exemple étend le regard négatif en arrière pour inclure des caractères génériques; en anglais, le résultat de la recherche négative est "ne correspond que si la chaîne précédente n'inclut pas le mot Albatross ". Dans ce cas, la chaîne de test NE COMPREND PAS le mot, le regard négatif derrière vaut FALSE et la 11ème expression régulière ne correspond pas.

Le 12ème exemple utilise une affirmation d'anticipation négative. Comme les éléments de regard, les points de vue ont une largeur nulle: ils ne déplacent pas le curseur dans la chaîne de test aux fins de la correspondance. Le lookahead dans ce cas, rejette la chaîne immédiatement, car .*Albatross.* Correspond; étant donné qu'il s'agit d'une prévision négative, la valeur est FALSE, ce qui signifie que la regex globale ne correspond pas, ce qui signifie que l'évaluation de la regex par rapport à la chaîne de test s'arrête là.

L'exemple 12 a toujours la même valeur booléenne que l'exemple 11, mais son comportement est différent lors de l'exécution. Dans l'ex 12, le contrôle négatif est effectué en premier, immédiatement après. Dans l'ex 11, l'expression rationnelle complète est appliquée et évaluée à TRUE avant que l'assertion lookbehind ne soit vérifiée. Ainsi, vous pouvez constater qu’il peut exister des différences de performances lors de la comparaison des indicateurs anticipés et indirects. Le choix qui vous convient dépend de ce sur quoi vous faites correspondre, ainsi que de la complexité relative du motif de "correspondance positive" et du motif de "correspondance négative".

Pour plus d'informations à ce sujet, consultez le site http://www.regular-expressions.info/

Ou procurez-vous un outil d'évaluation des expressions rationnelles et essayez quelques tests.

comme cet outil:
entrez la description de l'image ici

source et binary

Vous pouvez facilement atteindre vos objectives en utilisant une seule expression régulière. Voici un exemple qui montre une façon de le faire. Cette expression rationnelle correspond à une chaîne contenant "cat" ET "lion" ET "tiger" , mais ne contient PAS "dog" OU "wolf" OU "hyena" :

 if (Regex.IsMatch(text, @" # Match ssortingng containing all of one set of words but none of another. ^ # anchor to start of ssortingng. # Positive look ahead assertions for required subssortingngs. (?=.*? cat ) # Assert ssortingng has: 'cat'. (?=.*? lion ) # Assert ssortingng has: 'lion'. (?=.*? tiger ) # Assert ssortingng has: 'tiger'. # Negative look ahead assertions for not-allowed subssortingngs. (?!.*? dog ) # Assert ssortingng does not have: 'dog'. (?!.*? wolf ) # Assert ssortingng does not have: 'wolf'. (?!.*? hyena ) # Assert ssortingng does not have: 'hyena'. ", RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace)) { // Successful match } else { // Match attempt failed } 

Vous pouvez voir le modèle nécessaire. Lors de l’assemblage de la regex, veillez à exécuter chacune des sous-chaînes fournies par l’utilisateur par le biais de la méthode Regex.escape () afin d’échapper aux métacaractères qu’elle peut contenir (c’est-à-dire ( , ) , | etc.). De plus, l’expression rationnelle ci-dessus est écrite en mode espacement libre pour plus de lisibilité. Votre regex de production ne doit PAS utiliser ce mode, sinon les espaces dans les sous-chaînes de l’utilisateur seraient ignorés.

Vous voudrez peut-être append \b limites de mot avant et après chaque “mot” dans chaque assertion si les sous-chaînes sont composées uniquement de mots réels.

Notez également que l’assertion négative peut être un peu plus efficace en utilisant la syntaxe alternative suivante:

(?!.*?(?:dog|wolf|hyena))