Hans, vou morder a isca e detalhar minha resposta anterior. Você disse que queria "algo mais completo", então espero que não se importe com a resposta longa - apenas tentando agradar. Vamos começar com alguns antecedentes.
Em primeiro lugar, esta é uma excelente pergunta. Freqüentemente, há dúvidas sobre como combinar certos padrões, exceto em certos contextos (por exemplo, dentro de um bloco de código ou entre parênteses). Essas perguntas freqüentemente dão origem a soluções bastante estranhas. Portanto, sua pergunta sobre múltiplos contextos é um desafio especial.
Surpresa
Surpreendentemente, existe pelo menos uma solução eficiente que é geral, fácil de implementar e agradável de manter. Ele funciona com todos os sabores de regex que permitem que você inspecione grupos de captura em seu código. E acontece para responder a uma série de perguntas comuns que podem à primeira vista parecer diferentes das suas: "corresponder a tudo, exceto Donuts", "substituir tudo menos ...", "corresponder a todas as palavras, exceto as da lista negra da minha mãe", "ignorar tags "," correspondem à temperatura, a menos que estejam em itálico "...
Infelizmente, a técnica não é bem conhecida: estimo que em vinte perguntas do SO que poderiam usá-la, apenas uma tem uma resposta que a menciona - o que significa talvez uma em cinquenta ou sessenta respostas. Veja minha troca com Kobi nos comentários. A técnica é descrita com alguma profundidade neste artigo, que a chama (otimisticamente) de "melhor truque de regex de todos os tempos". Sem entrar em tantos detalhes, tentarei dar a você uma noção firme de como a técnica funciona. Para obter mais detalhes e exemplos de código em vários idiomas, encorajo você a consultar esse recurso.
Uma variação mais conhecida
Existe uma variação usando sintaxe específica para Perl e PHP que realiza o mesmo. Você o verá no SO nas mãos de mestres de regex, como CasimiretHippolyte e HamZa . Contarei mais sobre isso abaixo, mas meu foco aqui é a solução geral que funciona com todos os tipos de regex (contanto que você possa inspecionar grupos de captura em seu código).
Obrigado por todo o background, zx81 ... Mas qual é a receita?
Fato Chave
O método retorna a correspondência na captura do Grupo 1. Ele não se preocupa com o jogo geral.
Na verdade, o truque é combinar os vários contextos que não queremos (encadeando esses contextos usando o |
OR / alternância) de modo a "neutralizá-los". Depois de combinar todos os contextos indesejados, a parte final da alternância coincide com o que nós não queremos e captura-o ao grupo 1.
A receita geral é
Not_this_context|Not_this_either|StayAway|(WhatYouWant)
Isso vai coincidir Not_this_context
, mas de certa forma essa partida vai para uma lata de lixo, porque não vamos olhar para as correspondências gerais: olhamos apenas para as capturas do Grupo 1.
No seu caso, com seus dígitos e seus três contextos para ignorar, podemos fazer:
s1|s2|s3|(\b\d+\b)
Observe que, como realmente combinamos s1, s2 e s3 em vez de tentar evitá-los com lookarounds, as expressões individuais para s1, s2 e s3 podem permanecer claras como o dia. (São as subexpressões de cada lado de a |
)
Toda a expressão pode ser escrita assim:
(?m)^.*\.$|\([^\)]*\)|if\(.*?//endif|(\b\d+\b)
Veja esta demonstração (mas concentre-se nos grupos de captura no painel inferior direito).
Se você tentar dividir mentalmente esse regex em cada |
delimitador, na verdade é apenas uma série de quatro expressões muito simples.
Para sabores que suportam espaçamento livre, essa leitura é particularmente boa.
(?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)
Isso é excepcionalmente fácil de ler e manter.
Estendendo o regex
Quando você deseja ignorar mais situações S4 e S5, você as adiciona em mais alternâncias à esquerda:
s4|s5|s1|s2|s3|(\b\d+\b)
Como é que isso funciona?
Os contextos que você não quer são adicionados a uma lista de alternâncias à esquerda: eles vão combinar, mas essas combinações gerais nunca são examinadas, então combiná-los é uma maneira de colocá-los em uma "lata de lixo".
O conteúdo que você deseja, no entanto, é capturado para o Grupo 1. Em seguida, você deve verificar programaticamente se o Grupo 1 está definido e não vazio. Esta é uma tarefa de programação trivial (e falaremos mais tarde sobre como ela é feita), especialmente considerando que ela deixa você com uma regex simples que você pode entender rapidamente e revisar ou estender conforme necessário.
Nem sempre sou um fã de visualizações, mas este faz um bom trabalho em mostrar como o método é simples. Cada "linha" corresponde a uma correspondência potencial, mas apenas a linha inferior é capturada no Grupo 1.
Demonstração Debuggex
Variação Perl / PCRE
Em contraste com a solução geral acima, existe uma variação para Perl e PCRE que é freqüentemente vista no SO, pelo menos nas mãos de Deuses de regex como @CasimiretHippolyte e @HamZa. Isto é:
(?:s1|s2|s3)(*SKIP)(*F)|whatYouWant
No seu caso:
(?m)(?:^.*\.$|\([^()]*\)|if\(.*?//endif)(*SKIP)(*F)|\b\d+\b
Esta variação é um pouco mais fácil de usar porque o conteúdo correspondente nos contextos s1, s2 e s3 é simplesmente ignorado, então você não precisa inspecionar as capturas do Grupo 1 (observe que os parênteses sumiram). As partidas contêm apenaswhatYouWant
Note-se que (*F)
, (*FAIL)
e (?!)
são todos a mesma coisa. Se você quiser ser mais obscuro, pode usar(*SKIP)(?!)
demo para esta versão
Formulários
Aqui estão alguns problemas comuns que essa técnica pode facilmente resolver. Você notará que a escolha de palavras pode fazer alguns desses problemas soarem diferentes, embora na verdade sejam virtualmente idênticos.
- Como posso combinar foo, exceto em qualquer lugar em uma tag como
<a stuff...>...</a>
?
- Como posso corresponder foo, exceto em uma
<i>
tag ou snippet de javascript (mais condições)?
- Como posso combinar todas as palavras que não estão nesta lista negra?
- Como posso ignorar qualquer coisa dentro de um bloco SUB ... END SUB?
- Como posso combinar tudo exceto ... s1 s2 s3?
Como programar as capturas do Grupo 1
Você não gostou do código, mas, para completar ... O código para inspecionar o Grupo 1 obviamente dependerá do idioma de sua escolha. De qualquer forma, ele não deve adicionar mais do que algumas linhas ao código que você usaria para inspecionar correspondências.
Em caso de dúvida, recomendo que você dê uma olhada na seção de exemplos de código do artigo mencionado anteriormente, que apresenta código para algumas linguagens.
Alternativas
Dependendo da complexidade da questão e do mecanismo regex usado, existem várias alternativas. Aqui estão os dois que podem se aplicar à maioria das situações, incluindo várias condições. Em minha opinião, nenhum dos dois é tão atraente quanto a s1|s2|s3|(whatYouWant)
receita, até porque a clareza sempre vence.
1. Substitua e depois Combine.
Uma boa solução que parece hacky, mas funciona bem em muitos ambientes, é trabalhar em duas etapas. Uma primeira regex neutraliza o contexto que você deseja ignorar, substituindo strings potencialmente conflitantes. Se você deseja apenas corresponder, pode substituir por uma string vazia e, em seguida, executar a correspondência na segunda etapa. Se você quiser substituir, você pode primeiro substituir as strings a serem ignoradas por algo distinto, por exemplo, cercar seus dígitos com uma cadeia de largura fixa de @@@
. Após essa substituição, você está livre para substituir o que realmente deseja e, em seguida, terá que reverter suas @@@
cordas distintas .
2. Lookarounds.
Sua postagem original mostrou que você entende como excluir uma única condição usando soluções alternativas. Você disse que o C # é ótimo para isso e está certo, mas não é a única opção. Os tipos de regex do .NET encontrados em C #, VB.NET e Visual C ++, por exemplo, bem como o regex
módulo ainda experimental a ser substituído re
em Python, são os únicos dois mecanismos que conheço que suportam lookbehind de largura infinita. Com essas ferramentas, uma condição em um olhar para trás pode cuidar não apenas de olhar para trás, mas também para o fósforo e além dele, evitando a necessidade de coordenar com um olhar para frente. Mais condições? Mais alternativas.
Reciclando a regex que você tinha para s3 em C #, todo o padrão ficaria assim.
(?!.*\.)(?<!\([^()]*(?=\d+[^)]*\)))(?<!if\(\D*(?=\d+.*?//endif))\b\d+\b
Mas agora você sabe que não estou recomendando isso, certo?
Exclusões
@HamZa e @Jerry sugeriram que eu mencionasse um truque adicional para casos em que você deseja apenas excluir WhatYouWant
. Você se lembra que a receita para combinar WhatYouWant
(capturá-lo no Grupo 1) era s1|s2|s3|(WhatYouWant)
, certo? Para excluir todas as instâncias de WhatYouWant
, você altera o regex para
(s1|s2|s3)|WhatYouWant
Para a string de substituição, você usa $1
. O que acontece aqui é que para cada instância s1|s2|s3
que é correspondida, a substituição $1
substitui essa instância por ela mesma (referenciada por $1
). Por outro lado, quando WhatYouWant
é correspondido, ele é substituído por um grupo vazio e nada mais - e, portanto, excluído. Veja esta demonstração , obrigado @HamZa e @Jerry por sugerirem esta adição maravilhosa.
Substituições
Isso nos leva a substituições, nas quais tocarei brevemente.
- Ao substituir por nada, consulte o truque "Exclusões" acima.
- Ao substituir, se estiver usando Perl ou PCRE, use a
(*SKIP)(*F)
variação mencionada acima para corresponder exatamente ao que você deseja e faça uma substituição direta.
- Em outros sabores, na chamada da função de substituição, inspecione a correspondência usando um retorno de chamada ou lambda e substitua se o Grupo 1 estiver definido. Se precisar de ajuda com isso, o artigo já referenciado fornecerá o código em várias linguagens.
Diverta-se!
Não, espere, tem mais!
Ah, nah, vou guardar isso para minhas memórias em vinte volumes, a serem lançados na próxima primavera.
\K
não há sintaxe php especial. Elabore e esclareça o que você quer dizer. Se você pretende nos dizer que não precisa de uma solução "complicada", precisa dizer o que é complicado para você e por quê.