Соответствие регулярных выражений в Котлине

Как сопоставить secret_code_data в строке:

xeno://soundcloud/?code=secret_code_data# 

я пробовал

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

без везения. Я подозреваю ? прежде чем код может быть проблемой, я должен каким-то образом избежать этого. Вы можете помочь?

Вот три варианта: первый, обеспечивающий хорошее Regex, который делает то, что вы хотите, а два других – для анализа URL-адресов, используя альтернативу Regex, которые правильно обрабатывают кодирование / декодирование URL-адреса.

Анализ с использованием Regex

ПРИМЕЧАНИЕ. Метод Regex небезопасен в большинстве случаев использования, поскольку он неправильно анализирует URL-адрес в компонентах, а затем декодирует каждый компонент отдельно. Обычно вы не можете декодировать весь URL-адрес в одну строку, а затем безопасно разбираться, потому что некоторые закодированные символы могут запутать Regex позже. Это похоже на разбор XHTML с использованием регулярного выражения (как описано здесь ). См. Альтернативы Regex ниже.

Вот очищенное регулярное выражение как единичный тестовый пример, который безопасно обрабатывает больше 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() . Это и другие методы расширения можно найти в Справочнике API Kotlin .

Анализ с использованием UriBuilder или аналогичного класса

Вот пример использования UriBuilder из библиотеки Klutter для Kotlin . Эта версия обрабатывает кодирование / декодирование, включая более современные кодировки jQi-кода JavaScript, которые не обрабатываются стандартным классом URI Java ( который имеет много проблем ). Это безопасно, легко, и вам не нужно беспокоиться о каких-либо особых случаях.

Реализация:

 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 } 

Clutter uy.klutter:klutter-core-jdk6:$klutter_version артефакт uy.klutter:klutter-core-jdk6:$klutter_version мал и включает в себя некоторые другие расширения, включая модернизированное кодирование / декодирование URL. (Для $klutter_version используйте самую $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, хорошо вы являетесь 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, который следует, – это извлечение первой группы.