Entendendo de uma vez por todas Expressões Regulares

Capturando Grupos

Filhos(as) de Zion, tudo na paz? Continuando nossa saga nas Expressões Regulares, hoje iremos abordar uma funcionalidade que começa a dar vida e trazer uma real utilidade para as nossas expressões: Grupos de caracteres.

Então, liga teu white noise e teu pomodoro e vamo que vamo!


Grupos de Caracteres: o que são?

Grupos de caracteres em expressões regulares são formas de agrupar expressões. Com eles, podemos selecionar o que queremos ou não receber nos resultados.

Até agora, estamos definindo expressões sem grupo e quando temos um match, é no resultado inteiro da expressão. Mas… e se quisermos pegar só um pedaço daquela expressão? Só uma parte em específico e o resto podemos até descartar?

É justamente pra isso que serve o grupo. Observe o target abaixo:

<h1>Meu Titulo</h1>

Vamos imaginar que queremos pegar o valor do título entre as tags. Assim, podemos pensar da seguinte forma:

  • Preciso definir que eu quero que venha um h1: <h1>
  • Como eu não sei o que virá no titulo, quero que apareça qualquer letra ou número (word character) \w e também espaços em branco \s, 1 ou mais vezes: [\w\s]+
  • Por fim, fechamos a tag h1: <\/h1>

Resultado:

<h1>[\w\s]+<\/h1>
full match!

Mas, não era bem o que a gente queria né? Queremos só o conteúdo do titulo e não as tags que ele está inserido.


Utilizando os grupos

Para definir um grupo de caracteres, basta escrever uma expressão entre parênteses (()). Dessa maneira, começaremos a receber como resultado os valores do grupo, não só o full match como estamos acostumados.

Continuando no exemplo do título, vamos separar cada bloco de código por grupos:

  • <h1> = (<h1>)
  • [\w\s]+ = ([\w\s]+)
  • <\/h1> = (<\/h1>)

Resultando em:

(<h1>)([\w\s]+)(<\/h1>)
Opa, novos resultados! \o

Viu só? Agora além de recebermos o valor full match da expressão, recebemos também valores de cada grupo que definimos. E é aqui que a coisa começa a ficar interessante pra valer!

Mas Raul, a gente não queria somente o valor do título? Por que tá vindo as tags ainda?

Calma Jovem! A gente vai descobrir isso agora.


Non-capturing group

Como vimos no nosso exemplo, agora estamos pegando os valores dos grupos. Mas como solicitar à Regex Engine para que NÃO traga o valor de algum grupo? Para faze-lo, basta no início do grupo digitar (?:).

Assim, corrigindo a nossa expressão, temos:

(?:<h1>)([\w\s]+)(?:<\/h1>)

E como resultado temos:

Finalmente! =D

Como escapamos o valor dos grupos das tags, agora só temos o full match e o resultado do conteúdo da nossa tag h1.


Diferentes cenários

Atingimos o nosso objetivo, mas, vamos um pouco mais afundo nisso.

Imagine que além de validar a tag h1, queremos considerar todos os tipos de titulo (de 1 a 6).

Bem, já sabemos fazer isso né? Basta utilizar uma classe de caractere, passando que queremos de 1 até 6:

<h[1-6]>

Alterando nossa expressão, temos:

(?:<h[1-6]>)([\w\s]+)(?:<\/h[1-6]>)

Resultando em:

Bem loco!

Conseguimos deixar um pouco mais dinâmico… Mas… como já diria o poeta:

“Algo errado, não está certo!” — desconhecido

Talvez você tenha percebido que temos uma possível falha em nossa expressão. Da maneira que está definida, dizemos que:

Queremos pegar os valores entre as tags h (de 1 a 6) de abertura e fechamento!

Porém, observe o caso abaixo:

<h1>Meu titulo inválido</h4>

Todos nós sabemos que a tag de fechamento deve ser a mesma de abertura. Caso não seja, é um elemento inválido! Entretanto, olha o resultado da nossa expressão:

Oi?
Chega, não da mais.
Chega, não da mais.

Calma, calma, calma. Tem solução!


Backreferences

Backreference é uma forma de fazer referência à um grupo que foi informado anteriormente, ou seja, é uma forma de dizer à engine que queremos fazer referência (ter o mesmo valor aqui) do grupo “n”.

Grupo n? Que história é essa?

Número do grupo

Cada grupo que definimos ganha um número, que no caso seria sua posição na expressão. Esse número, varia conforme a ordem de declaração, ou seja, o primeiro grupo ganha o número 1, o segundo ganha o número 2 e assim sucessivamente.

Ordem dos grupos

Vale deixar claro que o que determina a ordem e os números de cada grupo é a ordem de precedência, ou seja, se tivermos grupos dentro de grupos, a regex irá definir todas os grupos daquele aninhamento e depois continuará para os outros que estão fora:

aninhamento de grupos

Fazendo uma analogia, lembra das aulas de matemática, onde temos várias expressões aninhadas e precisamos resolver de fora pra dentro? É a mesma coisa:

1+((3*4)+2) = 1+(12+2) = 1+14 = 15

Atenção Vale comentar um comportamento que pode fazer a gente quebrar muito a cabeça e acreditar que estamos fazendo algo errado.

Quando definimos que o grupo será “non-capturing” (?:), além dele não aparecer nos resultados, ele também não ganha uma numeração. Ou seja, você não conseguirá fazer referências à ele. =/

Fazendo referência à um grupo

Sabendo como é definido os números, para podemos definir que queremos o mesmo valor do grupo “n”, basta saber o seu número e passarmos com a barra invertida (\).

Voltando pra resolver o último exemplo, queremos que quando a tag de abertura for h1, a tag de fechamento também seja h1. Com o conceito de backrefence em mente, definiremos que o h[1-6]:

(?:<(h[1-6])>)

Agora, faremos a referência a ele. Como na ordem de precedência ele é o grupo número 1 (devido ao fato de termos negado o pai), declaramos nosso que o backreference é igual a \1 :

([\w\s]+)(?:<\/\1>)

Juntando as duas partes:

(?:<(h[1-6])>)([\w\s]+)(?:<\/\1>)

Resultando em:

Tag de abertura e fechamento validada!

Perceba que agora blindamos a nossa regex contra um comportamento estranho. Infelizmente recebemos um grupo não desejado, mas é o preço da referência por números.

De qualquer forma, nosso objetivo foi alcançado!

Conseguimos, uhul
Conseguimos, uhul

Conclusão

Ainda há mais algumas formas de fazer referência, como por exemplo por nome. Entretanto, essa funcionalidade não é comum em todas as engines.

Grupos nos permitem dar uma complexidade e flexibilidade às nossas expressões. Assim, tenha em mente: Sempre que precisar pegar algum resultado específico, utilize grupos!

Espero que tenha aprendido sobre o tema e até a próxima! =D