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.
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.
- Comment puis-je faire correspondre foo sauf n'importe où dans une balise comme
<a stuff...>...</a>
?
- Comment puis-je faire correspondre foo sauf dans une
<i>
balise ou un extrait de code javascript (plus de conditions)?
- Comment puis-je faire correspondre tous les mots qui ne figurent pas sur cette liste noire?
- Comment puis-je ignorer quoi que ce soit à l'intérieur d'un bloc SUB ... END SUB?
- 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 regex
module encore expérimental à remplacer re
en 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|s3
cette correspondance, le remplacement $1
remplace cette instance par lui-même (référencé par $1
). En revanche, quand WhatYouWant
est 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.
- Lors du remplacement par rien, voir l'astuce "Suppressions" ci-dessus.
- 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.
- 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.
\K
Il 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.