Regex com capturas nomeadas no Ruby

Artigos - 27/Jan/2021 - por Henrique Morato

Em Ruby podemos usar o alguns métodos na classe String juntamente com expressões regulares (Regex) como match ou scan. Mas muitas vezes queremos recuperar vários dados dessa string e aí se torna uma confusão, principalmente na ordem em que as capturas aconteceram.

Para resolver esse problema, a gente pode usar a funcionalidade de capturas nominadas.

Como exemplo vamos usar um código ISBN-13 bastante utilizado no mundo. Neste código os três primeiros dígitos são o EAN, seguido de dois dígitos que representam o grupo, quatro dígitos da editora, três dígitos do número do livro e um dígito verificador.

Se fossemos fazer um regex (focado na legibilidade) para o livro abaixo, ficaria algo mais ou menos assim:

isbn = '9780134456478'
book_isbn = isbn.match(/(\d{3})(\d{2})(\d{4})(\d{3})(\d{1})/)
# => #<MatchData "9780134456478" 1:"978" 2:"01" 3:"3445" 4:"647" 5:"8">
book_isbn.captures
# => ["978", "01", "3445", "647", "8"]

Para salvar em variáveis poderíamos fazer algo assim:

isbn = '9780134456478'
ean, group, publisher, title, check = isbn.match(/(\d{3})(\d{2})(\d{4})(\d{3})(\d{1})/)&.captures
# => ["978", "01", "3445", "647", "8"]

Assim, temos os valores salvos em variáveis para utilizar. Até aí já está bem interessante, mas algumas vezes alocar variáveis demais ou o tratamento de capturas que não aconteceram podem se tornar um problema.

Então podemos trabalhar com capturas nominadas para facilitar atribuindo menos variáveis e dando mais valor à captura. Para isso, colocamos uma sintaxe antes do grupo da seguinte forma: (?<nome_da_captura>match_aqui).

Seguindo o exemplo acima ficaria assim:

isbn = '9780134456478'
book_isbn =
  isbn.match(/(?<ean>\d{3})(?<group>\d{2})(?<publisher>\d{4})(?<title>\d{3})(?<check>\d{1})/)
# => #<MatchData "9780134456478" ean:"978" group:"01" publisher:"3445" title:"647" check:"8">

book_isbn[:ean]
# => "978"
book_isbn[:group]
# => "01"
book_isbn[:publisher]
# => "3445"
book_isbn[:title]
# => "647"
book_isbn[:check]
# => "8"

Agora você tem o melhor dos dois mundos, um regex fácil de ler e uma sintaxe de hash disponível na MatchData que você pode utilizar no seu código.

Meu uso preferido seria algo assim:

ISBN_REGEX = /^(?<ean>\d{3})(?<group>\d{2})(?<publisher>\d{4})(?<title>\d{3})(?<check>\d{1})$/.freeze

book_isbn = ISBN_REGEX.match('9780134456478')
# => #<MatchData "9780134456478" ean:"978" group:"01" publisher:"3445" title:"647" check:"8">

Você pode testar o regex do exemplo aqui no Rubular que, diga-se de passagem, é um excelente lugar para testar regex com Ruby.

Por hoje é só, bom código!

Foto de perfil do autor
Henrique Morato

Dev