Home > Net >  keeping track of injected variables when using string interpolation in kotlin
keeping track of injected variables when using string interpolation in kotlin

Time:05-26

I'm looking for a way to keep track of variables used when doing a string interpolation without parsing the string. For example, if I have a string:

val expStr = "${var1} some other useless text ${var2}"

I want to be able to identify the order of the variables used, again without parsing the string. In this case [var1, var2] would be an expected output. So far I've thought of defining a class where I pass it all of the variables. Then reference said variables through the class function grab.

val wrapper = ContainerClass(var1, var2)
val expStr = "${wrapper.grab(var1)} some other useless text ${wrapper.grab(var2)}"

inside ContainerClass is a array, each time a variable is referenced it is added to the array and outputted through getReferenceCalls

val whatIWant = wrapper.getReferenceCalls() // [var1, var2]

This works great until I introduce the injection of strings into strings.

val wrapper = ContainerClass(var1, var2, var3)
val snippet = "${wrapper.grab(var1)} some other useless text ${wrapper.grab(var2)}"
val expStr = "${wrapper.grab(var3)} ${snippet}"

val notWhatIWant = wrapper.getReferenceCalls() // [var1, var2, var3]

Here, I want to identify the order of the injected variables in the final expStr ie. [var3, var1, var2]. My question is, is this possible without parsing expStr? I did also think of a not so elegant solution of allowing my class to define any given "snippet" and the class identifies the variables referenced in the snippet. This works but becomes convoluted fast. What I really need is an eligant solution...if it exists.

CodePudding user response:

I have implemented a "ContainerClass" to achieve your goal. I uses String.format instead of string templates so that I don't need prior information of the input.

class StringNode(private val format: String, vararg args : Any) {
    private val argv = args

    override fun toString() : String = String.format(format,*argv)

    fun getFlatArgs() : List<Any> = argv.flatMap {
        if(it is StringNode){
            it.getFlatArgs()
        } else{
            listOf(it)
        }
    }
}

Usage:

fun main(){
        val sn1 = StringNode("1:%s 2:%s 3:%s","abc",123,"def")
        println(sn1)
        println(sn1.getFlatArgs())

        val sn2 = StringNode("foo:%s bar:%s","foo",sn1);
        println(sn2)
        println(sn2.getFlatArgs())

        val sn3 = StringNode("sn1:%s, sn2:%s",sn1,sn2);
        println(sn3)
        println(sn3.getFlatArgs())
}

Output:

1:abc 2:123 3:def
[abc, 123, def]
foo:foo bar:1:abc 2:123 3:def
[foo, abc, 123, def]
sn1:1:abc 2:123 3:def, sn2:foo:foo bar:1:abc 2:123 3:def
[abc, 123, def, foo, abc, 123, def]

CodePudding user response:

val var1 = "abc"
val var2 = "def"

val list = mutableListOf<String>()

val expStr = "${var1.also { list.add(it) }} some other useless text ${var2.also { list.add(it) }}"

println(expStr)   // Output: "abc some other useless text def"
println(list)     // Output: [abc, def]

Or:

val var1 = "abc"
val var2 = "def"

val list = mutableListOf<String>()
fun String.addTo(list: MutableList<String>) = this.also { list.add(it) }

val expStr = "${var1.addTo(list)} some other useless text ${var2.addTo(list)}"

println(expStr)   // Output: "abc some other useless text def"
println(list)     // Output: [abc, def]
  • Related