1

I would like help to create an input masking using find and replace regexes for the following use case:

The user types 11 digits of his identification number and the output will be formatted with this pattern:

\d\d\d.\d\d\d.\d\d\d-\d\d

First attempt

Find: (\d{3})(\d{3})(\d{3})(\d{2})

Replace: $1.$2.$3-$4

This only works after the user types all the 11 digits. However, I want the dots and dashes to appear while the user types.

Second attempt

Find: (\d{1,3})(\d{1,3})(\d{1,3})(\d{1,2})

Replace: $1.$2.$3-$4

As soon as the user types the fourth digit, but the result ends up being like this \d.\d.\d-\d

Third attempt

Find: (\d{3})(\d{0,3})(\d{0,3})(\d{0,2})

Replace: $1.$2.$3-$4

As soon as the user types the third digit, but the result ends up being like this \d\d\d\..-

Code:

A brief description of the code:

fun transformation(input: String, findRegex: String, replaceRegex: String): String =
    input.replace(findRegex.toRegex(), replaceRegex)


fun main() {
    
    val input = "01121212"
    
    val findRegex = """(\d{3})(\d{3})(\d{3})(\d{2})"""

    val replaceRegex = """$1.$2.$3-$4"""
    
    val result = transformation(input, findRegex, replaceRegex)
    
    println(result)
}

https://pl.kotl.in/dPRrIMIVm

Full code can be found here: https://gitlab.com/pertence/masked-textinput

  • 1
    You forgot to use capturing groups that are referenced with the backreferences. – Wiktor Stribiżew Jul 16 '21 at 16:28
  • This answer https://stackoverflow.com/a/37681441/4770439 works and is the same as my first attempt, but I want I want the dots and dashes to appear while the user is typing. – Augusto Pertence Jul 16 '21 at 16:42
  • 1
    Sorry, the regex works on plain text. When user is typing, there is some code that processes the input and applies the regex. What is your code? Also, see [Input mask doesn't work when value is deleted](https://stackoverflow.com/questions/60870518/input-mask-doesnt-work-when-value-is-deleted/60871203#60871203) – Wiktor Stribiżew Jul 16 '21 at 16:48
  • I put the code sample in the question. I know there are other questions about input masking with android compose, but I want to create a class that does not need a custom logic for each mask pattern. – Augusto Pertence Jul 17 '21 at 11:30
  • You cannot make it generic because for each regex pattern you will need a specific replacement. Note `01121212` does not match your pattern. `01121212000` [will match](https://ideone.com/WIXK0d). If you need to replace "partial" matches, you will need a callable in the replacement. – Wiktor Stribiżew Jul 17 '21 at 11:35
  • Thanks for the help, but what would mean by callable? May KCallable be used to implement a callable? https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.reflect/-k-callable/ – Augusto Pertence Jul 17 '21 at 13:18
  • 1
    I think you need [this kind of solution](https://ideone.com/CA5AOO). – Wiktor Stribiżew Jul 17 '21 at 14:12

1 Answers1

1

You can use

fun transformation(input: String, findRegex: String, replaceRegex: String): String =
    input.replace("""\D+""".toRegex(), "").replace(findRegex.toRegex(), {
        it.groupValues[1].toString() + 
          (if (!it.groupValues[2].isNullOrEmpty()) ".${it.groupValues[2].toString()}"  else "") + 
          (if (!it.groupValues[3].isNullOrEmpty()) ".${it.groupValues[3].toString()}"  else "") + 
          (if (!it.groupValues[4].isNullOrEmpty()) "-${it.groupValues[4].toString()}"  else "")
    })
 
fun main(args: Array<String>) {
    val input = "0112121234" // 011.212.123-4
    // val input = "011" //  011
    // val input = "01121212" // 011.212.12
    // val input = "011212" // 011.212
    // val input = "01121" //  011.21
    // val input = "0112" //  011.2
    val findRegex = """^(\d{3})(\d{1,3})?(\d{1,3})?(\d{1,2})?$"""
    val replaceRegex = """$1.$2.$3-$4"""
    val result = transformation(input, findRegex, replaceRegex)
    println(result)
}

See the online Kotlin demo.

NOTES:

  • .replace("""\D+""".toRegex(), "") - removes all non-digits
  • .replace(findRegex.toRegex(), { it.groupValues[1].toString() + (if (!it.groupValues[2].isNullOrEmpty()) ".${it.groupValues[2].toString()}" else "") + (if (!it.groupValues[3].isNullOrEmpty()) ".${it.groupValues[3].toString()}" else "") + (if (!it.groupValues[4].isNullOrEmpty()) "-${it.groupValues[4].toString()}" else "") }) - matches a pattern and replaces the match with different replacements depending on whether a group matched or not.

The regex is

^(\d{3})(\d{1,3})?(\d{1,3})?(\d{1,2})?$

See the regex demo. Details:

  • ^ - start of string
  • (\d{3}) - Group 1: three digits
  • (\d{1,3})? - an optional Group 2 that matches one, two or three digits
  • (\d{1,3})? - an optional Group 3 that matches one, two or three digits
  • (\d{1,2})? - an optional Group 4 that matches one or two digits
  • $ - end of string.
Wiktor Stribiżew
  • 607,720
  • 39
  • 448
  • 563