Move SSD source name helpers to typed Kotlin

ober

8eb9b75ba5d1d674ee5ece339eec4f31e089d48a

diff --git a/templates/ssd-review.ss b/templates/ssd-review.ss
index 9c98320..9346a13 100644
--- a/templates/ssd-review.ss
+++ b/templates/ssd-review.ss
@@ -458,7 +458,7 @@
     (typed-kotlin-file "com/sfb/ssdreview/UiTextHelpers.kt"
       (typed-library (com sfb ssdreview)
         (export normalizeFiringArc normalizeLabel isGenericGuessLabel isSsdPdfName
-                isErrorStatus)
+                sourceBaseName canonicalSourceName isErrorStatus)
         (def (normalizeFiringArc (value : String)) : String
           (string-uppercase (string-trim value)))
         (def (normalizeLabel (value : String)) : String
@@ -478,6 +478,17 @@
           (let ((lower (string-lowercase name)))
             (and (string-ends-with? lower ".pdf")
                  (string-contains? lower "ssd"))))
+        (def (sourceBaseName (value : String)) : String
+          (string-lowercase
+            (string-replace-regex
+              (string-replace-regex value "\\?.*$" "")
+              "^.*/"
+              "")))
+        (def (canonicalSourceName (value : String)) : String
+          (string-replace-regex
+            (sourceBaseName value)
+            " \\(\\d+\\)(?=\\.pdf$)"
+            ""))
         (def (isErrorStatus (text : String)) : Bool
           (let ((lower (string-lowercase text)))
             (or (or (string-contains? lower "failed")
@@ -4819,12 +4830,6 @@
        "    private fun urlEncode(value: String): String ="
        "        URLEncoder.encode(value, \"UTF-8\")"
        ""
-       "    private fun sourceBaseName(value: String): String ="
-       "        value.substringBefore('?').substringAfterLast('/').lowercase()"
-       ""
-       "    private fun canonicalSourceName(value: String): String ="
-       "        sourceBaseName(value).replace(Regex(\"\"\" \\(\\d+\\)(?=\\.pdf$)\"\"\"), \"\")"
-       ""
        "    private fun appendJsonLine(file: File, json: JSONObject) {"
        "        val previous = if (file.exists()) readLocalText(file) else \"\""
        "        val next = previous + json.toString() + \"\\n\""