在Kotlin中的正则表达式匹配

如何在字符串中匹配secret_code_data:

xeno://soundcloud/?code=secret_code_data# 

我试过了

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

没有运气。 我猜测 ? 在代码可能是问题之前,我应该以某种方式逃避它。 你能帮我吗?

这里有三个选项,第一个提供了一个好的正则表达式,它可以做你想做的,另外两个用于解析URL,使用Regex的替代方法来正确处理URL组件的编码/解码。

使用正则表达式解析

注: Regex方法在大多数使用情况下是不安全的,因为它没有正确地将URL解析为组件,然后单独解码每个组件。 通常你不能把整个URL解码成一个字符串,然后安全地解析,因为一些编码字符可能会在后面使用正则表达式。 这与使用正则表达式解析XHTML相似(如此处所述 )。 见下面的正则表达式的替代。

这是一个清理正则表达式作为一个单元测试用例,安全地处理更多的URL。 在这篇文章的最后是一个可以用于每种方法的单元测试。

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

这个正则表达式处理这些情况:

  • 有和没有尾随/在路径
  • 有和没有片段
  • 参数列表中的第一个,中间或最后一个参数
  • 参数作为唯一的参数

请注意,在Kotlin中创建正则表达式的惯用方法是someString.toRegex() 。 它和其他扩展方法可以在Kotlin API Reference中找到 。

使用UriBuilder或类似的类进行解析

下面是一个使用Kltter库的UriBuilder的例子。 这个版本处理的编码/解码包括更多的现代JavaScript unicode编码没有处理的Java标准的URI类( 它有很多问题 )。 这是安全的,容易的,你不必担心任何特殊情况。

执行:

 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 } 

Klutter uy.klutter:klutter-core-jdk6:$klutter_version工件很小,并且包括一些其他扩展包括现代化的URL编码/解码。 (对于$klutter_version使用最新版本 )。

使用JDK URI类进行解析

这个版本稍微长一些,说明你需要自己解析原始查询字符串,解析后解码,然后找到查询参数:

 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 } 

这可以写成URI类本身的扩展:

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

在主体中删除parsed变量,并使用this因为你已经有了URI,那么你就是URI。 然后打电话使用:

 val secretCode = URI(myTestUrl).findSecretCode() 

单元测试

鉴于上述任何功能,运行这个测试来证明它的工作原理:

 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") } } 

在第一个问号之前添加一个转义,因为它有特殊的含义

 ? 

 \? 

您也正在捕获第一组中的密码。 不知道下面的kotlin代码是提取第一个组。