mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-08 23:54:39 +00:00
Merge pull request #3299 from vitorpamplona/claude/url-parser-punctuation-brvq35
Keep balanced closing delimiters in URLs
This commit is contained in:
+28
@@ -321,6 +321,34 @@ class UrlParserTest {
|
||||
Urls(withScheme = setOf("http://[2a01:5cc0:1:2::4]")),
|
||||
)
|
||||
|
||||
@Test
|
||||
fun testUrlWithBalancedParenthesis() =
|
||||
test(
|
||||
"https://en.wikipedia.org/wiki/Bitcoin_(disambiguation)",
|
||||
Urls(withScheme = setOf("https://en.wikipedia.org/wiki/Bitcoin_(disambiguation)")),
|
||||
)
|
||||
|
||||
@Test
|
||||
fun testUrlWithCommaAndBalancedParenthesis() =
|
||||
test(
|
||||
"https://memory-alpha.fandom.com/wiki/Scorpion,_Part_II_(episode)",
|
||||
Urls(withScheme = setOf("https://memory-alpha.fandom.com/wiki/Scorpion,_Part_II_(episode)")),
|
||||
)
|
||||
|
||||
@Test
|
||||
fun testUrlWithBalancedParenthesisInSentence() =
|
||||
test(
|
||||
"Read https://en.wikipedia.org/wiki/Bitcoin_(disambiguation) for context.",
|
||||
Urls(withScheme = setOf("https://en.wikipedia.org/wiki/Bitcoin_(disambiguation)")),
|
||||
)
|
||||
|
||||
@Test
|
||||
fun testUrlWrappedInParenthesisDropsCloser() =
|
||||
test(
|
||||
"(see https://test.com)",
|
||||
Urls(withScheme = setOf("https://test.com")),
|
||||
)
|
||||
|
||||
@Test
|
||||
fun testBlossom() {
|
||||
val blossom = "blossom:b1674191a88ec5cdd733e4240a81803105dc412d6c6708d53ab94fc248f4f553.pdf?xs=cdn.satellite.earth"
|
||||
|
||||
+38
-2
@@ -649,8 +649,11 @@ class UrlDetector(
|
||||
// if the url is valid and greater then 0
|
||||
if (state == ReadEndState.ValidUrl && buffer.isNotEmpty()) {
|
||||
var url = buffer.toString()
|
||||
if (url.lastOrNull() in CANNOT_END_URLS_WITH) url = url.dropLast(1)
|
||||
urlList.add(currentUrlMarker.createUrl(url))
|
||||
val last = url.lastOrNull()
|
||||
if (last != null && last in CANNOT_END_URLS_WITH && !url.endsOnBalancedCloser(last)) {
|
||||
url = url.dropLast(1)
|
||||
}
|
||||
if (url.isNotEmpty()) urlList.add(currentUrlMarker.createUrl(url))
|
||||
}
|
||||
|
||||
// clear out the buffer.
|
||||
@@ -667,6 +670,37 @@ class UrlDetector(
|
||||
return state == ReadEndState.ValidUrl
|
||||
}
|
||||
|
||||
/**
|
||||
* Decides whether a trailing closing delimiter ([last]) should be kept as part of the URL.
|
||||
*
|
||||
* Closing parenthesis, braces and brackets are common inside real URLs (e.g. Wikipedia's
|
||||
* `…/Bitcoin_(disambiguation)`), so we keep a trailing closer when the URL also contains its
|
||||
* matching opener — i.e. the delimiters are balanced. When the closer is unbalanced it is
|
||||
* almost always wrapping/sentence punctuation (e.g. `(see example.com)` or `http://test.com)`)
|
||||
* and is stripped. Any opening delimiter or other punctuation is never balanced, so it falls
|
||||
* through to the normal trailing strip.
|
||||
*/
|
||||
private fun String.endsOnBalancedCloser(last: Char): Boolean {
|
||||
val opener =
|
||||
when (last) {
|
||||
')' -> '('
|
||||
'}' -> '{'
|
||||
']' -> '['
|
||||
else -> return false
|
||||
}
|
||||
|
||||
var depth = 0
|
||||
for (c in this) {
|
||||
if (c == opener) {
|
||||
depth++
|
||||
} else if (c == last) {
|
||||
depth--
|
||||
}
|
||||
}
|
||||
// depth >= 0 means every closer (including the trailing one) has a matching opener.
|
||||
return depth >= 0
|
||||
}
|
||||
|
||||
companion object {
|
||||
val VALID_SCHEMES_NO_SLASHES: List<String> =
|
||||
listOf(
|
||||
@@ -694,6 +728,7 @@ class UrlDetector(
|
||||
'!',
|
||||
')',
|
||||
'}',
|
||||
']',
|
||||
'(',
|
||||
'{',
|
||||
'\u3002',
|
||||
@@ -711,6 +746,7 @@ class UrlDetector(
|
||||
':',
|
||||
')',
|
||||
'}',
|
||||
']',
|
||||
'(',
|
||||
'{',
|
||||
'\u3002',
|
||||
|
||||
+43
@@ -839,6 +839,49 @@ class UriDetectionTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testBalancedClosingDelimitersAreKept() {
|
||||
// Wikipedia-style urls whose path legitimately ends in a balanced ")".
|
||||
runTest(
|
||||
"https://en.wikipedia.org/wiki/Bitcoin_(disambiguation)",
|
||||
"https://en.wikipedia.org/wiki/Bitcoin_(disambiguation)",
|
||||
)
|
||||
// also exercises a comma without surrounding spaces inside the path.
|
||||
runTest(
|
||||
"https://memory-alpha.fandom.com/wiki/Scorpion,_Part_II_(episode)",
|
||||
"https://memory-alpha.fandom.com/wiki/Scorpion,_Part_II_(episode)",
|
||||
)
|
||||
|
||||
// a balanced closer is kept even when followed by sentence punctuation/words.
|
||||
runTest(
|
||||
"See https://en.wikipedia.org/wiki/Bitcoin_(disambiguation).",
|
||||
"https://en.wikipedia.org/wiki/Bitcoin_(disambiguation)",
|
||||
)
|
||||
runTest(
|
||||
"read https://en.wikipedia.org/wiki/Bitcoin_(disambiguation) now",
|
||||
"https://en.wikipedia.org/wiki/Bitcoin_(disambiguation)",
|
||||
)
|
||||
|
||||
// balanced braces/brackets inside the path are kept as well.
|
||||
runTest("https://example.com/a[b]c", "https://example.com/a[b]c")
|
||||
runTest("https://example.com/a{b}c", "https://example.com/a{b}c")
|
||||
|
||||
// commas without surrounding spaces stay part of the url.
|
||||
runTest("https://example.com/a,b,c", "https://example.com/a,b,c")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testUnbalancedClosingDelimitersAreStripped() {
|
||||
// a closer that wraps the url (no matching opener inside it) is still removed.
|
||||
runTest("(see example.com)", "example.com")
|
||||
runTest("look [example.com]", "example.com")
|
||||
runTest("note {example.com}", "example.com")
|
||||
runTest("https://example.com/path)", "https://example.com/path")
|
||||
|
||||
// a trailing comma is sentence punctuation and is still removed.
|
||||
runTest("visit example.com,", "example.com")
|
||||
}
|
||||
|
||||
private fun runTest(
|
||||
text: String,
|
||||
vararg expected: String?,
|
||||
|
||||
Reference in New Issue
Block a user