Juego Regex en Kotlin

¿Cómo puedo unir secret_code_data en una cadena?

xeno://soundcloud/?code=secret_code_data# 

He intentado

 val regex = Regex("""xeno://soundcloud/?code=(.*?)#""") field = regex.find(url)?.value ?: "" 

sin suerte Sospecho ? antes el código podría ser el problema, ¿debería escaping de alguna manera? ¿Puede usted ayudar?

Aquí hay tres opciones, la primera que proporciona un buen Regex que hace lo que desea, y las otras dos para analizar el URL usando una alternativa a Regex que maneja la encoding / deencoding del componente URL correctamente.

Análisis usando Regex

NOTA: El método Regex no es seguro en la mayoría de los casos de uso ya que no analiza correctamente el URL en los componentes, luego decodifica cada componente por separado. Normalmente no se puede decodificar toda la URL en una cadena y luego analizar de forma segura porque algunos caracteres codificados pueden confundir la Regex más tarde. Esto es similar a analizar XHTML usando expresiones regulares (como se describe aquí ). Vea alternativas a Regex a continuación.

Aquí hay una expresión regular limpia como un caso de testing unitario que maneja más URL de manera segura. Al final de esta publicación hay una testing de unidad que puede usar para cada método.

 private val SECRET_CODE_REGEX = """xeno://soundcloud[/]?.*[\?&]code=([^#&]+).*""".toRegex() fun findSecretCode(withinUrl: String): String? = SECRET_CODE_REGEX.matchEntire(withinUrl)?.groups?.get(1)?.value 

Esta expresión regular maneja estos casos:

  • con y sin trailing / in path
  • con y sin fragment
  • parámetro como primer, medio o último en la list de parameters
  • parámetro como único parámetro

Tenga en count que la forma idiomática de hacer una expresión regular en Kotlin es someString.toRegex() . Este y otros methods de extensión se pueden encontrar en la Referencia de la API de Kotlin .

Análisis usando UriBuilder o class similar

Aquí hay un ejemplo usando UriBuilder de la biblioteca Klutter para Kotlin . Esta versión maneja la encoding / deencoding incluyendo codificaciones unicode más modernas de JavaScript no manejadas por la class URI estándar de Java ( que tiene muchos problemas ). Esto es seguro, fácil y no necesita preocuparse por ningún caso especial.

Implementación:

 fun findSecretCode(withinUrl: String): String? { fun isValidUri(uri: UriBuilder): Boolean = uri.scheme == "xeno" && uri.host == "soundcloud" && (uri.encodedPath == "/" || uri.encodedPath.isNullOrBlank()) val parsed = buildUri(withinUrl) return if (isValidUri(parsed)) parsed.decodedQueryDeduped?.get("code") else null } 

El artefacto Klutter uy.klutter:klutter-core-jdk6:$klutter_version es pequeño e incluye algunas otras extensiones que incluyen la encoding / deencoding URL modernizada. (Para $klutter_version usa la versión más reciente ).

Análisis con JDK URI Class

Esta versión es un poco más larga, y muestra que necesita analizar la cadena de consulta sin procesar usted mismo, descodificar después del análisis, luego search el parámetro de consulta:

 fun findSecretCode(withinUrl: String): String? { fun isValidUri(uri: URI): Boolean = uri.scheme == "xeno" && uri.host == "soundcloud" && (uri.rawPath == "/" || uri.rawPath.isNullOrBlank()) val parsed = URI(withinUrl) return if (isValidUri(parsed)) { parsed.getRawQuery().split('&').map { val parts = it.split('=') val name = parts.firstOrNull() ?: "" val value = parts.drop(1).firstOrNull() ?: "" URLDecoder.decode(name, Charsets.UTF_8.name()) to URLDecoder.decode(value, Charsets.UTF_8.name()) }.firstOrNull { it.first == "code" }?.second } else null } 

Esto podría escribirse como una extensión en la class URI en sí:

 fun URI.findSecretCode(): String? { ... } 

En el cuerpo, elimine la variable parsed y parsed porque ya tiene el URI, así usted es el URI. Luego llame usando:

 val secretCode = URI(myTestUrl).findSecretCode() 

Pruebas unitarias

Dada cualquiera de las funciones anteriores, ejecute esta testing para demostrar que funciona:

 class TestSo34594605 { @Test fun testUriBuilderFindsCode() { // positive test cases val testUrls = listOf("xeno://soundcloud/?code=secret_code_data#", "xeno://soundcloud?code=secret_code_data#", "xeno://soundcloud/?code=secret_code_data", "xeno://soundcloud?code=secret_code_data", "xeno://soundcloud?code=secret_code_data&other=fish", "xeno://soundcloud?cat=hairless&code=secret_code_data&other=fish", "xeno://soundcloud/?cat=hairless&code=secret_code_data&other=fish", "xeno://soundcloud/?cat=hairless&code=secret_code_data", "xeno://soundcloud/?cat=hairless&code=secret_code_data&other=fish#fragment" ) testUrls.forEach { test -> assertEquals("secret_code_data", findSecretCode(test), "source URL: $test") } // negative test cases, don't get things on accident val badUrls = listOf("xeno://soundcloud/code/secret_code_data#", "xeno://soundcloud?hiddencode=secret_code_data#", "http://www.soundcloud.com/?code=secret_code_data") badUrls.forEach { test -> assertNotEquals("secret_code_data", findSecretCode(test), "source URL: $test") } } 

Agregue un escape antes del primer signo de interrogación, ya que tiene un significado especial

 ? 

se convierte

 \? 

También está capturando el código secreto en el primer grupo. Sin embargo, no estoy seguro de que el código de kotlin que sigue esté extrayendo el primer grupo.

  • Kotlin: ¿Cómo get un grupo de captura de la primera línea que coincida?
  • Diseño de expresiones regulares multilínea
  • Regex: coincide con palabras que no son cadenas
  • Regex Match en una string