web-dev-qa-db-ja.com

Kotlinの正規表現マッチング

Stringのsecret_code_dataを照合するにはどうすればよいですか:

xeno://soundcloud/?code=secret_code_data#

私はもう試した

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

運がなければ。疑う?コードが問題になる前に、どうにかしてエスケープする必要があります。手伝ってくれますか?

26
ssuukk

次の3つのオプションがあります。1つ目は、必要なことを行う優れた正規表現を提供し、2つ目は、URLコンポーネントのエンコード/デコードを正しく処理するRegexの代替を使用してURLを解析します。

正規表現を使用した解析

注:Regexメソッドはほとんどのユースケースで安全ではありませんURLをコンポーネントに適切に解析せず、各コンポーネントを個別にデコードします。通常、エンコードされた一部の文字が後でRegexを混乱させる可能性があるため、URL全体を1つの文字列にデコードしてから安全に解析することはできません。これは、正規表現を使用した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または同様のクラスを使用した解析

KotlinのKlutterライブラリUriBuilder を使用した例を次に示します。このバージョンは エンコード/デコード Java standard URI class(which多くの問題があります)。これは安全で簡単で、特別なケースを心配する必要はありません。

実装:

_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? { ... }
_

すでにURIを持っているので、本文でparsed変数を削除し、thisを使用します。次に使用して呼び出します:

_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")
        }
    }
_
39
Jayson Minard

特別な意味があるため、最初の疑問符の前にエスケープを追加します

? 

になる

\?

また、最初のグループのシークレットコードをキャプチャしています。ただし、後続のkotlinコードが最初のグループを抽出しているかどうかはわかりません。

0
buckley