/ / corresponde ao regex com look-behind de comprimento variável de uma palavra e look-behind negativo de comprimento variável de outra palavra? - .net, regex, lookbehind, negative-lookahead, balancing-groups

regex de correspondência com look-behind de comprimento variável de uma palavra e look-behind negativo de comprimento variável de outra palavra? - .net, regex, lookbehind, lookahead negativo, grupos de equilíbrio

Eu tenho uma expressão regular que captura um padrão UMA apenas se a string contiver um padrão B em algum lugar antes UMA.

Digamos, por uma questão de simplicidade, que UMA é bd{3}b (ou seja, três dígitos) e B é a palavra "foo".

Portanto, o Regex que tenho é (?<=b(?:foo)b.*?)(?<A>bd{3}b).

(?<=               # look-behind
b(?:foo)b    # pattern B
.*?            # variable length
)
(?<A>bd{3}b)    # pattern A

Por exemplo, para a corda

"foo text 111, 222 and not bar something 333 but foo 444 and better 555"

captura

(111, 222, 333, 444, 555)

Recebi um novo requisito e agora tenho que excluir as capturas que são precedidas por padrão C, vamos dizer que C é a palavra "bar". O que eu quero construir é um regex que expressa

(?<=               # look-behind
b(?:foo)b    # pattern B
???????????    # anything that does not contains pattern C
)
(?<A>bd{3}b)    # pattern A

Então, na string de exemplo, terei que capturar

(111, 222, 444, 555)

Claro algo como (?<=b(?:foo)b.*?)(?<!b(?:bar)b.*?)(?<A>bd{3}b)

(?<=               # look-behind
b(?:foo)b    # pattern B
.*?
)
(?<!               # negative look-behind
b(?:bar)b    # pattern C
.*?
)
(?<A>bd{3}b)    # pattern A

não funcionará, pois excluirá tudo após a primeira aparição de "barra" e a captura será

(111, 222)

O regex (?<=b(?:foo)b(?!.*?(?:bbarb)).*?)(?<A>bd{3}b)

(?<=                     # look-behind
b(?:foo)b          # pattern B
(?!                  # negative lookahead
.*?              # variable lenght
(?:bbarb)      # pattern C
)
.*?                  # variable lenght
)
(?<A>bd{3}b)          # pattern A

também não funcionará porque para o primeiro "foo" na minha string de teste, ele sempre encontrará a "barra" como um sufixo e só irá capturar

(444, 55)

Até agora, usando Correspondência condicional de expressões e (agora) sabendo que enquanto dentro de um lookbehind, .net corresponde e captura da direita para a esquerda, Consegui criar o seguinte regex (?<=(?(C)(?!)| (?:bfoob))(?:(?<!bbar)s|(?<C>bbars)|[^s])*)(?<A>bd{3}b)

(?<=                     # look-behind
(?(C)                # if capture group C is not empty
(?!)             # fail (pattern C was found)
|                # else
(?:bfoob)      # pattern B
)
(?:
(?<!bbar)s     # space not preceeded by pattern C (consume the space)
|
(?<C>bbars)    # pattern C followed by space (capture in capture group C)
|
[^s]            # anything but space (just consume)
)*                   # repeat as needed
)
(?<A>bd{3}b)          # pattern A

que funciona, mas é muito complexo como os padrões UMA, B e C são muito mais complexos do que os exemplos que postei aqui.

É possível simplificar este regex? Talvez usando grupos de equilíbrio?

Respostas:

2 para resposta № 1

Uma opção simples é muito semelhante ao segundo padrão de Casimir et Hippolyte:

foo(?>(?<A>bd{3}b)|(?!bar).)+
  • Começar com foo
  • (?>|(?!bar).)+ - Pare de combinar se você viu bar.
  • (?<A>bd{3}b) e capture todos os A "s que você vir ao longo do caminho.
  • Grupo atômico (?>) não é necessário neste caso, o retrocesso não iria bagunçar tudo de qualquer maneira.

Exemplo de trabalho

Da mesma forma, ele pode ser convertido em um lookbehind:

(?<=foo(?:(?!bar).)*?)(?<A>bd{3}b)

Isso tem a vantagem de combinar apenas os números. O lookbehind afirma que há um foo antes de A, mas não há um bar.
Exemplo de trabalho

Ambos assumem que B e C são um tanto simples.


3 para resposta № 2

Você pode usar um padrão baseado no G âncora que corresponde à posição após a partida anterior:

(?:G(?!A)|bfoob)(?:(?!b(?:bar|d{3})b).)*(d{3})

demonstração

detalhes:

(?:
G(?!A) # contiguous to a previous match and not at the start of the string
|        # OR
bfoob  # foo: the condition for the first match
)
(?:(?!b(?:bar|d{3})b).)* # all that is not "bar" or a 3 digit number (*)
(d{3})

(*) Observe que, se você puder usar um subpadrão melhor (ou seja,que não testa cada caractere com um lookahead contendo uma alternância) para sua situação real, não hesite em mudá-la. (por exemplo, algo baseado em classes de personagens: [^bd]*(?>(?:B[bd]+|b(?!arb)|d(?!ddb))[^bd]*)*)


Outra maneira: como o mecanismo de regex .net é capaz de armazenar capturas repetidas, você também pode escrever isso:

bfoob(?:(?:(?!b(?:bar|d{3})b).)*(d{3}))+

Mas desta vez, você precisa fazer um loop sobre cada ocorrência de foo para extrair os resultados no grupo 1. É menos prático, mas o padrão é mais rápido, pois não começa com uma alternância.

Note que se "bar" e "d{3}" começa e termina com caracteres de palavra, você pode escrever o padrão de uma forma mais eficiente:

bfoo(?:W+(?>(?!barb)w+W+)*?(d{3}))+b

Outro jeito:divida sua string em "foo" e "bar" (preserve o delimitador), faça um loop sobre cada parte. Quando a parte for "foo" defina um sinalizador como verdadeiro, quando a parte for "bar" defina-o como falso e quando não for "t" foo "ou" bar "extraia os números se o sinalizador for verdadeiro.


2 para resposta № 3

Já que você perguntou, é possível com grupos de equilíbrio, mas provavelmente não é necessário.

A                    # Match from the start of the string
(?>                   # Atomic group. no backsies.
(?<B>(?<-B>)?foo)            # If we see "foo", push it to stack B.
# (?<-B>)? ensures B only has one item - if there are two,
# one is popped.
|(?<-B>bar)                  # When we see a bar, reset the foo.
|(?(B)(?<A>bd{3}b)|(?!))  # If foo is set, we are allowed to capture A.
|.                           # Else, just advance by one character.
)+
z                    # Match until the end of the string.

Exemplo de trabalho

Se quisermos ser mais inteligentes (o que provavelmente não queremos), podemos combinar a maioria dos ramos no condicional:

A
(?>
(?(B)
(?:(?<A>bd{3}b)|(?<-B>bar))
| # else
(?<B>foo)
)
|.
)+
z

Exemplo de trabalho

Mais uma vez, é possível, mas balancear grupos não é a melhor opção aqui, principalmente porque não estamos balanceando nada, apenas verificando se um sinalizador está definido ou não.