web-dev-qa-db-fra.com

Regex pour l'opération "AND NOT"

Je cherche une construction regex générale pour faire correspondre tout le motif x SAUF correspond au motif y. C'est difficile à expliquer à la fois de manière complète et concise ... voir Material Nonimplication pour une définition formelle.

Par exemple, faites correspondre n'importe quel caractère Word (\w) SAUF 'p'. Remarque: je soustrais un petit ensemble (la lettre "p") d'un ensemble plus grand (tous les caractères Word). Je ne peux pas simplement dire [^p] car cela ne prend pas en compte le plus grand ensemble limitatif de caractères Word uniquement. Pour ce petit exemple, bien sûr, je pourrais reconstruire manuellement quelque chose comme [a-oq-zA-OQ-Z0-9_], ce qui est douloureux mais faisable. Mais je cherche une construction plus générale pour qu'au moins le grand ensemble positif puisse être une expression plus complexe. Comme match ((?<=(so|me|^))big(com?pl{1,3}ex([pA]t{2}ern) sauf quand il commence par "My".

Edit : Je me rends compte que c'était un mauvais exemple, car l'exclusion de trucs au début ou à la fin est une situation où les expressions d'anticipation négative et d'anticipation fonctionnent . (Bohème, je vous ai quand même donné une note positive pour illustrer cela). Alors ... qu'en est-il de l'exclusion des correspondances qui contiennent "My" quelque part au milieu? ... Je cherche toujours vraiment une construction générale, comme un équivalent regex du pseudo-sql suivant

select [captures] from [input]
where (
    input MATCHES [pattern1]
    AND NOT capture MATCHES [pattern2]
)

Si la réponse est "ça n'existe pas et voici pourquoi ..." J'aimerais le savoir aussi.

Edit 2 : Si je voulais définir ma propre fonction pour ce faire, ce serait quelque chose comme (voici une version C # LINQ):

public static Match[] RegexMNI(string input, 
                               string positivePattern, 
                               string negativePattern) {
    return (from Match m in Regex.Matches(input, positivePattern)
            where !Regex.IsMatch(m.Value, negativePattern)
            select m).ToArray();
}

Je me demande TOUJOURS s'il existe une construction regex native qui pourrait le faire.

24
Joshua Honig

Cela correspondra à tout caractère qui est un mot et est pas un p:

((?=[^p])\w)

Pour résoudre votre exemple, utilisez une anticipation négative pour "Mon" n'importe où dans l'entrée, c'est-à-dire (?!.*My):

^(?!.*My)((?<=(so|me|^))big(com?pl{1,3}ex([pA]t{2}ern)

Notez l'ancre au début de l'entrée ^ qui est nécessaire pour le faire fonctionner.

20
Bohemian

Je me demande pourquoi les gens essaient de faire des choses compliquées dans de grandes expressions régulières monolithiques?

Pourquoi ne pouvez-vous pas simplement décomposer le problème en sous-parties, puis créer des expressions régulières très faciles pour les faire correspondre individuellement? Dans ce cas, faites d'abord correspondre \w, alors correspondre [^p] si cette première correspondance réussit. Perl (et d'autres langages) permet de construire des expressions régulières d'apparence vraiment compliquée qui vous permettent de faire exactement ce que vous devez faire dans un grand blobby-regex (ou, comme cela peut très bien être, avec un crypto-regex court et accrocheur) , mais pour celui qui a besoin de lire (et de maintenir!) le code une fois que vous êtes parti, vous devez le documenter complètement. Mieux vaut donc le rendre facile à comprendre dès le départ.

Désolé, déclamez.

15
Kusalananda

Après vos modifications, c'est toujours l'anticipation négative, mais avec un quantificateur supplémentaire.

Si vous voulez vous assurer que la chaîne entière ne contient pas "My", vous pouvez le faire

(?!.*My)^.*$

Voir ici sur Regexr

Cela correspondra à n'importe quelle séquence de caractères (avec le .* à la fin) et le (?!.*My).* au début échouera s'il y a un "My" n'importe où dans la chaîne.

Si vous voulez faire correspondre quelque chose qui n'est pas exactement "My", utilisez des ancres

(?!^My$).*
5
stema

Donc, après avoir parcouru ces sujets sur RegEx: lookahead, lookbehind, nesting, AND operator, recursion, subroutines, conditionals, anchors, and groups, j'en suis arrivé à la conclusion qu'il y a pas de solution qui satisfait ce que vous demandez.

La raison pour laquelle l'anticipation ne fonctionne pas est qu'elle échoue dans ce cas relativement simple:

Trois mots sans Mon inclus comme un seul.

Regex:

^ (?!. * Mon. *) (\ B\w +\b\s\b\w +\b\s\b\w +\b)

Allumettes:

inclus comme un

Les trois premiers mots ne correspondent pas car My arrive après eux. Si "My" est à la fin de la chaîne entière, vous ne correspondrez à rien car chaque lookahead échouera car ils verront tous cela.

Le problème semble être que même si lookahead a une ancre implicite quant à l'endroit où il commence sa correspondance, il n'y a aucun moyen de terminer où lookahead termine sa recherche avec une ancre basée sur le résultat d'une autre partie du RegEx. Cela signifie que vous devez vraiment dupliquer tous les RegEx dans l'antichambre négatif pour créer manuellement l'ancre que vous recherchez.

C'est frustrant et douloureux. La "solution" semble être d'utiliser un langage de script pour effectuer deux expressions régulières. L'un au-dessus de l'autre. Je suis surpris que ce type de fonctionnalité ne soit pas mieux intégré aux moteurs d'expression régulière.

1
horta