Search code examples
kotlinstring-substitution

How to use dynamic string substitution in Kotlin?


I'm looking for a Kotlin way to do a dynamic values substitution into a string. It is clear how to implement it, just want to check if there is something similar in standard library.

Could you help me to find a function which given template and data map returns a resulting string with all template keys replaced with their values?

fun format(template: String, data: Map<String, Any>): String { /* magic */ }

format("${a} ${b} ${a}", mapOf("a" to "Home", "b" to "Sweet))   // -> "Home Sweet Home"

Solution

  • I did not find any thing standard to solve the problem.

    So here is a balanced (readability/performance/extensibility) solution also handling cases when some substitutions are undefined in dataMap.

    makeString("\${a} # \${b} @ \${c}", mapOf("a" to 123, "c" to "xyz"))   // => "123 # ??? @ xyz"
    

    --

    object Substitutions {
        private val pattern = Pattern.compile("\\$\\{([^}]+)\\}")
    
        fun makeString(
            template: String, 
            dataMap: Map<String, Any?>, 
            undefinedStub: String = "???"
        ): String {
            val replacer = createReplacer(dataMap, undefinedStub)
            val messageParts = splitWithDelimiters(template, pattern, replacer)
            return messageParts.joinToString("")
        }
    
        private fun createReplacer(dataMap: Map<String, Any?>, stub: String): (Matcher) -> String {
            return { m ->
                val key = m.group(1)
                (dataMap[key] ?: stub).toString()
            }
        }
    
        private fun splitWithDelimiters(
            text: String,
            pattern: Pattern,
            matchTransform: (Matcher) -> String
        ): List<String> {
            var lastMatch = 0
            val items = mutableListOf<String>()
            val m = pattern.matcher(text)
    
            while (m.find()) {
                items.add(text.substring(lastMatch, m.start()))
                items.add(matchTransform(m))
                lastMatch = m.end()
            }
    
            items.add(text.substring(lastMatch))
    
            return items
        }
    }