Expressões regulares em Kotlin
1. Introdução
Podemos encontrar uso (ou abuso) deregular expressions em praticamente todos os tipos de software, de scripts rápidos a aplicativos incrivelmente complexos.
Neste artigo, veremos como usar expressões regulares em Kotlin.
Não discutiremos a sintaxe da expressão regular; é necessária familiaridade com expressões regulares, em geral, para seguir o artigo de forma adequada, e o conhecimento doJava Pattern syntax especificamente é recomendado.
2. Configuração
Embora as expressões regulares não façam parte da linguagem Kotlin, elas vêm com sua biblioteca padrão.
Provavelmente já o temos como dependência do nosso projeto:
org.jetbrains.kotlin
kotlin-stdlib
1.2.21
Podemos encontrar a versão mais recente dekotlin-stdlib no Maven Central.
3. Criação de um objeto de expressão regular
Expressões regulares são instâncias da classekotlin.text.Regex. Podemos criar um de várias maneiras.
Uma possibilidade é chamar o construtorRegex:
Regex("a[bc]+d?")
ou podemos chamar o métodotoRegex em umString:
"a[bc]+d?".toRegex()
Finalmente, podemos usar um método estático de fábrica:
Regex.fromLiteral("a[bc]+d?")
Exceto pela diferença explicada na próxima seção, essas opções são equivalentes e equivalem à preferência pessoal. Lembre-se de ser consistente!
Dica: as expressões regulares geralmente contêm caracteres que seriam interpretados como sequências de escape em literaisString. Podemos, portanto, usarStrings brutos para esquecer os vários níveis de escape:
"""a[bc]+d?\W""".toRegex()
3.1. Opções de correspondência
Tanto o construtorRegex quanto o métodotoRegex nos permitem especificar uma única opção adicional ou um conjunto:
Regex("a(b|c)+d?", CANON_EQ)
Regex("a(b|c)+d?", setOf(DOT_MATCHES_ALL, COMMENTS))
"a(b|c)+d?".toRegex(MULTILINE)
"a(b|c)+d?".toRegex(setOf(IGNORE_CASE, COMMENTS, UNIX_LINES))
As opções são enumeradas na classeRegexOption, que importamos convenientemente estaticamente no exemplo acima:
-
IGNORE_CASE – ativa a correspondência sem distinção entre maiúsculas e minúsculas
-
MULTILINE - muda o significado de^e$ (vejaPattern)
-
LITERAL - faz com que metacaracteres ou sequências de escape no padrão não recebam nenhum significado especial
-
UNIX_LINES - neste modo, apenas o é reconhecido como um terminador de linha
-
COMMENTS - permite espaços em branco e comentários no padrão
-
DOT_MATCHES_ALL - faz com que o ponto corresponda a qualquer caractere, incluindo um terminador de linha
-
CANON_EQ - ativa a equivalência por decomposição canônica (verPattern)
4. Coincidindo
We use regular expressions primarily to match input Strings,e às vezes para extrair ou substituir partes deles.
Veremos agora em detalhes os métodos oferecidos pela classeRegex de Kotlin para combinarStrings.
4.1. Verificar correspondências parciais ou totais
Nesses casos de uso, estamos interessados emknowing whether a String or a portion of a String satisfies our regular expression.
Se precisarmos apenas de uma correspondência parcial, podemos usarcontainsMatchIn:
val regex = """a([bc]+)d?""".toRegex()
assertTrue(regex.containsMatchIn("xabcdy"))
Se quisermos que todo oString corresponda, usamosmatches:
assertTrue(regex.matches("abcd"))
Observe que também podemos usarmatches como um operador infixo:
assertFalse(regex matches "xabcdy")
4.2. Extraindo componentes correspondentes
Nesses casos de uso, queremosto match a String against a regular expression and extract parts of the String.
Podemos querer corresponder todo oString:
val matchResult = regex.matchEntire("abbccbbd")
Ou podemos encontrar a primeira substring que corresponda:
val matchResult = regex.find("abcbabbd")
Ou talvez para encontrar todas as substrings correspondentes de uma vez, como umSet:
val matchResults = regex.findAll("abcb abbd")
Em qualquer um dos casos, se a correspondência for bem-sucedida, o resultado será uma ou mais instâncias da classeMatchResult. Na próxima seção, veremos como usá-lo.
Se a correspondência não for bem-sucedida, em vez disso, esses métodos retornamnull ouSet vazio no caso defindAll.
4.3. A classeMatchResult
As instâncias da classeMatchResult representam correspondências bem-sucedidas de alguma string de entrada em relação a uma expressão regular; correspondências completas ou parciais (consulte a seção anterior).
Como tal, eles têm umvalue, que é oString ou substring correspondente:
val regex = """a([bc]+)d?""".toRegex()
val matchResult = regex.find("abcb abbd")
assertEquals("abcb", matchResult.value)
E eles têm umrange de índices para indicar que parte da entrada foi correspondida:
assertEquals(IntRange(0, 3), matchResult.range)
4.4. Grupos e desestruturação
Também podemos extrair grupos (substrings correspondentes) de instâncias deMatchResult.
Podemos obtê-los comoStrings:
assertEquals(listOf("abcb", "bcb"), matchResult.groupValues)
Ou também podemos vê-los como objetosMatchGroup consistindo emvalueerange:
assertEquals(IntRange(1, 3), matchResult.groups[1].range)
O grupo com índice 0 é sempre todos os índicesString. correspondidos maiores que 0, em vez disso, representam grupos na expressão regular, delimitados por parênteses, como([bc]+) em nosso exemplo.
Também podemos desestruturar instâncias deMatchResult em uma instrução de atribuição:
val regex = """([\w\s]+) is (\d+) years old""".toRegex()
val matchResult = regex.find("Mickey Mouse is 95 years old")
val (name, age) = matchResult!!.destructured
assertEquals("Mickey Mouse", name)
assertEquals("95", age)
4.5. Várias correspondências
MatchResult também tem um métodonext que podemos usarto obtain the next match of the input String against the regular expression, se houver:
val regex = """a([bc]+)d?""".toRegex()
var matchResult = regex.find("abcb abbd")
assertEquals("abcb", matchResult!!.value)
matchResult = matchResult.next()
assertEquals("abbd", matchResult!!.value)
matchResult = matchResult.next()
assertNull(matchResult)
Como podemos ver,next retorna nulo quando não há mais correspondências.
5. Substituindo
Outro uso comum de expressões regulares éreplacing matching substrings with other Strings.
Para esse fim, temos dois métodos prontamente disponíveis na biblioteca padrão.
Um,replace, é para substituir todas as ocorrências de umString: correspondente
val regex = """(red|green|blue)""".toRegex()
val beautiful = "Roses are red, Violets are blue"
val grim = regex.replace(beautiful, "dark")
assertEquals("Roses are dark, Violets are dark", grim)
O outro,replaceFirst, é para substituir apenas a primeira ocorrência:
val shiny = regex.replaceFirst(beautiful, "rainbow")
assertEquals("Roses are rainbow, Violets are blue", shiny)
5.1. Substituições Complexas
Paramore advanced scenarios, quando não queremos substituir as correspondências porStrings, constante, mas queremosto apply a transformation,Regex ainda nos dá o que precisamos.
Insira a sobrecarga dereplace fechando:
val reallyBeautiful = regex.replace(beautiful) {
m -> m.value.toUpperCase() + "!"
}
assertEquals("Roses are RED!, Violets are BLUE!", reallyBeautiful)
Como podemos ver, para cada correspondência, podemos calcular uma substituiçãoString usando essa correspondência.
6. Divisão
Finalmente, podemos quererto split a String into a list of substrings according to a regular expression. Novamente,Regex de Kotlin nos ajudou:
val regex = """\W+""".toRegex()
val beautiful = "Roses are red, Violets are blue"
assertEquals(listOf(
"Roses", "are", "red", "Violets", "are", "blue"), regex.split(beautiful))
Aqui, a expressão regular corresponde a um ou mais caracteres que não sejam palavras, portanto o resultado da operação de divisão é uma lista de palavras.
Também podemos colocar um limite no comprimento da lista resultante:
assertEquals(listOf("Roses", "are", "red", "Violets are blue"), regex.split(beautiful, 4))
7. Interoperabilidade Java
Se precisarmos passar nossa expressão regular para o código Java ou alguma outra API de linguagem JVM que espera uma instância dejava.util.regex.Pattern, podemos simplesmente converter nossoRegex:
regex.toPattern()
8. Conclusões
Neste artigo, examinamos o suporte a expressões regulares na biblioteca padrão Kotlin.
Para obter mais informações, consultethe Kotlin reference.
A implementação de todos esses exemplos e trechos de código pode ser encontrada emthe GitHub project - este é um projeto Maven, então deve ser fácil de importar e executar como está.