Ответ 1
Вот три варианта, первый из которых содержит хорошее Regex, которое делает то, что вы хотите, и два других для синтаксического анализа URL, используя альтернативу Regex, которые правильно обрабатывают кодирование/декодирование URL-адреса.
Анализ с использованием Regex
ПРИМЕЧАНИЕ.. Метод Regex небезопасен в большинстве случаев использования, поскольку он неправильно анализирует URL-адрес в компонентах, а затем декодирует каждый компонент отдельно. Обычно вы не можете декодировать весь URL-адрес в одну строку, а затем безопасно разбираться, потому что некоторые закодированные символы могут запутать Regex позже. Это похоже на разбор XHTML с использованием regex (как описанного здесь). См. Альтернативы Regex ниже.
Вот очищенное регулярное выражение как случай unit test, который безопасно обрабатывает больше URL-адресов. В конце этого сообщения есть unit test, который вы можете использовать для каждого метода.
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()
. Это и другие методы расширения можно найти в Справочник по API Kotlin.
Анализ с использованием UriBuilder или аналогичного класса
Вот пример, используя UriBuilder
из библиотека Klutter для Котлина. Эта версия обрабатывает кодирование/декодирование, включая более современные кодировки jQi-кода JavaScript, которые не обрабатываются классом 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
используйте самую последнюю версию).
Разбор с классом URI JDK
Эта версия немного длиннее и показывает, что вам нужно самостоятельно разобрать необработанную строку запроса, декодировать после разбора, а затем найти параметр запроса:
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, ну, вы ARE 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")
}
}