From c74c23cf7a9fbf90f83cfdb58dbba329e86e8af8 Mon Sep 17 00:00:00 2001 From: airatmeister Date: Thu, 15 Dec 2022 12:06:55 +0300 Subject: [PATCH 1/3] INTERNAL-377: Placeholder generation from regex source expression --- text-processing/build.gradle | 1 + .../roboswag/textprocessing/Constants.kt | 9 ++ .../roboswag/textprocessing/TextFormatter.kt | 51 +++----- .../generators/DecoroMaskGenerator.kt | 27 ++++ .../generators/PlaceHolderGenerator.kt | 93 ++++++++++++++ .../regexgenerator/PCREGeneratorItem.kt | 6 + .../regexgenerator/PCREGeneratorListener.kt | 77 +++++++++++ .../PCREGeneratorListenerExt.kt | 121 ++++++++++++++++++ .../regexgenerator/RegexReplaceGenerator.kt | 21 +++ .../validators/CustomValidator.kt | 16 +++ .../textprocessing/TextFormatterTest.kt | 5 + 11 files changed, 393 insertions(+), 34 deletions(-) create mode 100644 text-processing/src/main/java/ru/touchin/roboswag/textprocessing/Constants.kt create mode 100644 text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/DecoroMaskGenerator.kt create mode 100644 text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/PlaceHolderGenerator.kt create mode 100644 text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/regexgenerator/PCREGeneratorItem.kt create mode 100644 text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/regexgenerator/PCREGeneratorListener.kt create mode 100644 text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/regexgenerator/PCREGeneratorListenerExt.kt create mode 100644 text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/regexgenerator/RegexReplaceGenerator.kt create mode 100644 text-processing/src/main/java/ru/touchin/roboswag/textprocessing/validators/CustomValidator.kt diff --git a/text-processing/build.gradle b/text-processing/build.gradle index b586ae0..0da999d 100644 --- a/text-processing/build.gradle +++ b/text-processing/build.gradle @@ -3,5 +3,6 @@ apply from: "../android-configs/lib-config.gradle" dependencies { implementation 'org.antlr:antlr4:4.9.2' implementation 'org.antlr:antlr4-runtime:4.9.2' + implementation 'ru.tinkoff.decoro:decoro:1.5.1' testImplementation 'junit:junit:4.13.2' } diff --git a/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/Constants.kt b/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/Constants.kt new file mode 100644 index 0000000..49377d8 --- /dev/null +++ b/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/Constants.kt @@ -0,0 +1,9 @@ +package ru.touchin.roboswag.textprocessing + +object Constants { + internal const val digits = "1234567890" + internal const val charsRuUpper = "АБВГДЕЖЗИЙКЛМНОПРСТУФЧЦЧЭЮЯЪЬЫШ" + internal const val charsEngUpper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + internal const val charsRuLower = "абвгдежзийклмнопрстуфчцэюяъьыш" + internal const val charsEngLower = "abcdefghijklmnopqrstuvwxyz" +} \ No newline at end of file diff --git a/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/TextFormatter.kt b/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/TextFormatter.kt index db37040..64ef9c4 100644 --- a/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/TextFormatter.kt +++ b/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/TextFormatter.kt @@ -1,47 +1,30 @@ package ru.touchin.roboswag.textprocessing -import org.antlr.v4.runtime.CharStreams -import org.antlr.v4.runtime.CommonTokenStream -import org.antlr.v4.runtime.tree.ParseTreeWalker -import ru.touchin.roboswag.textprocessing.pcre.parser.PCREBaseListener -import ru.touchin.roboswag.textprocessing.pcre.parser.PCRELexer -import ru.touchin.roboswag.textprocessing.pcre.parser.PCREParser +import android.widget.TextView +import ru.touchin.roboswag.textprocessing.generators.DecoroMaskGenerator +import ru.touchin.roboswag.textprocessing.generators.PlaceholderGenerator +import ru.touchin.roboswag.textprocessing.generators.regexgenerator.RegexReplaceGenerator class TextFormatter(private val regex: String) { - private var currentGroupIndex = 1 - private var regexReplaceString = "" + private val regexReplaceGenerator = RegexReplaceGenerator() + private val decoroMaskGenerator = DecoroMaskGenerator() + private val pcreGeneratorItem = regexReplaceGenerator.regexToRegexReplace(regex) + private val regexReplaceString = pcreGeneratorItem.regexReplaceString + private val listForPlaceholder = pcreGeneratorItem.listForPlaceholder + private val placeholderGenerator = PlaceholderGenerator(listForPlaceholder) - init { - regexToRegexReplace(regex) - } + fun getFormatText(inputText: String) = inputText.replace(Regex(regex), regexReplaceString) - fun getFormatText(inputText: String): String { - return inputText.replace(Regex(regex), regexReplaceString) - } + fun getPlaceholder() = placeholderGenerator.getPlaceholder() fun getRegexReplace() = regexReplaceString - private fun regexToRegexReplace(regex: String) { - val stringStream = CharStreams.fromString(regex) - val lexer = PCRELexer(stringStream) - val parser = PCREParser(CommonTokenStream(lexer)) - val parseContext = parser.parse() - val walker = ParseTreeWalker() - walker.walk( - object : PCREBaseListener() { - override fun enterCapture(ctx: PCREParser.CaptureContext) { - super.enterCapture(ctx) - regexReplaceString += "\$$currentGroupIndex" - currentGroupIndex++ - } - - override fun enterLiteral(ctx: PCREParser.LiteralContext) { - super.enterLiteral(ctx) - regexReplaceString += ctx.shared_literal().text - } - }, - parseContext + fun mask(textView: TextView) { + val formatWatcher = decoroMaskGenerator.mask( + placeholderGenerator.getPlaceholder(), + listForPlaceholder ) + formatWatcher.installOn(textView) } } \ No newline at end of file diff --git a/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/DecoroMaskGenerator.kt b/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/DecoroMaskGenerator.kt new file mode 100644 index 0000000..3cf3841 --- /dev/null +++ b/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/DecoroMaskGenerator.kt @@ -0,0 +1,27 @@ +package ru.touchin.roboswag.textprocessing.generators + +import ru.tinkoff.decoro.MaskImpl +import ru.tinkoff.decoro.slots.PredefinedSlots +import ru.tinkoff.decoro.slots.Slot +import ru.tinkoff.decoro.watchers.MaskFormatWatcher +import ru.touchin.roboswag.textprocessing.validators.CustomValidator + +class DecoroMaskGenerator { + + /** Генерация маски и слотов на основе возможных символов для placeholder, + * если возможные символы не более одного, то символ хардкодится в слот + * **/ + fun mask(placeholder: String, listForPlaceholder: List): MaskFormatWatcher { + val slots = mutableListOf() + for (i in placeholder.indices) { + slots.add( + if (listForPlaceholder[i].length == 1) { + PredefinedSlots.hardcodedSlot(placeholder[i]) + } else { + CustomValidator.customSlot(listForPlaceholder[i]) + } + ) + } + return MaskFormatWatcher(MaskImpl.createTerminated(slots.toTypedArray())) + } +} \ No newline at end of file diff --git a/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/PlaceHolderGenerator.kt b/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/PlaceHolderGenerator.kt new file mode 100644 index 0000000..ca6cd3e --- /dev/null +++ b/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/PlaceHolderGenerator.kt @@ -0,0 +1,93 @@ +package ru.touchin.roboswag.textprocessing.generators + +import ru.touchin.roboswag.textprocessing.Constants.charsEngLower +import ru.touchin.roboswag.textprocessing.Constants.charsEngUpper +import ru.touchin.roboswag.textprocessing.Constants.charsRuLower +import ru.touchin.roboswag.textprocessing.Constants.charsRuUpper +import ru.touchin.roboswag.textprocessing.Constants.digits + +class PlaceholderGenerator( + listForPlaceholder: List +) { + + private var placeholder: String = "" + + init { + /** Индексы для всех возможных вариантов: + * Цифр, Ангийских букв верхнего регистра, + * Русских букв верхнего регистра, + * Английских букв нижнего регистра, + * Русских букв верхнего и нижнего регистра, + * Английских букв верхнего и нижнего регистра, + * На случай остальных повторений + * **/ + var indexDigits = 0 + var indexCharsEngUpper = 0 + var indexCharsRuUpper = 0 + var indexCharsEngLower = 0 + var indexCharsRuLower = 0 + var indexUpNLowerRu = 0 + var indexUpNLowerEng = 0 + var indexElse = 0 + for (str in listForPlaceholder) { + if (str.isEmpty()) continue + /** Если элемент без повторений **/ + if (str.length == 1) { + placeholder += str + continue + } + when { + /** Если элемент повторяет цифры **/ + digits.contains(str) -> { + if (str.length <= indexDigits) indexDigits = 0 + placeholder += str[indexDigits] + indexDigits++ + } + /** Если элемент повторяет Английские буквы верхнего регистра **/ + charsEngUpper.contains(str) -> { + if (str.length <= indexCharsEngUpper) indexCharsEngUpper = 0 + placeholder += str[indexCharsEngUpper] + indexCharsEngUpper++ + } + /** Если элемент повторяет Английские буквы нижнего регистра **/ + charsEngLower.contains(str) -> { + if (str.length <= indexCharsEngLower) indexCharsEngLower = 0 + placeholder += str[indexCharsEngLower] + indexCharsEngLower++ + } + /** Если элемент повторяет Русские буквы верхнего регистра **/ + charsRuUpper.contains(str) -> { + if (str.length <= indexCharsRuUpper) indexCharsRuUpper = 0 + placeholder += str[indexCharsRuUpper] + indexCharsRuUpper++ + } + /** Если элемент повторяет Русские буквы нижнего регистра **/ + charsRuLower.contains(str) -> { + if (str.length <= indexCharsRuLower) indexCharsRuLower = 0 + placeholder += str[indexCharsRuLower] + indexCharsRuLower++ + } + /** Если элемент повторяет Русские буквы верхнего и нижнего регистра **/ + charsRuLower.contains(str[str.length - 1]) -> { + if (str.length <= indexUpNLowerRu) indexUpNLowerRu = 0 + placeholder += str[indexUpNLowerRu] + indexUpNLowerRu++ + } + /** Если элемент повторяет Аглийские буквы верхнего и нижнего регистра **/ + charsEngLower.contains(str[str.length - 1]) -> { + if (str.length <= indexUpNLowerEng) indexUpNLowerEng = 0 + placeholder += str[indexUpNLowerEng] + indexUpNLowerEng++ + } + /** Если элемент повторяет Другие символы **/ + else -> { + if (str.length <= indexElse) indexElse = 0 + placeholder += str[indexElse] + indexElse++ + } + } + } + } + + fun getPlaceholder() = placeholder +} \ No newline at end of file diff --git a/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/regexgenerator/PCREGeneratorItem.kt b/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/regexgenerator/PCREGeneratorItem.kt new file mode 100644 index 0000000..ba5d315 --- /dev/null +++ b/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/regexgenerator/PCREGeneratorItem.kt @@ -0,0 +1,6 @@ +package ru.touchin.roboswag.textprocessing.generators.regexgenerator + +class PCREGeneratorItem( + val regexReplaceString: String, + val listForPlaceholder: List +) \ No newline at end of file diff --git a/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/regexgenerator/PCREGeneratorListener.kt b/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/regexgenerator/PCREGeneratorListener.kt new file mode 100644 index 0000000..119450e --- /dev/null +++ b/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/regexgenerator/PCREGeneratorListener.kt @@ -0,0 +1,77 @@ +package ru.touchin.roboswag.textprocessing.generators.regexgenerator + +import ru.touchin.roboswag.textprocessing.Constants +import ru.touchin.roboswag.textprocessing.pcre.parser.PCREBaseListener +import ru.touchin.roboswag.textprocessing.pcre.parser.PCREParser + +class PCREGeneratorListener : PCREBaseListener() { + /** + * Лист для placeholder, где индекс - номер буквы для placeholder + * значение - возможные символы для placeholder + * **/ + private var mutableList = mutableListOf() + private var currentGroupIndex = 1 + private var regexReplaceString = "" + + /** Элемент поиска с регулярного выражения + * В себе может содержать возможные элементы регулярного выражения, + * например: + * [1-2], \\d, [A-B], а так же элементы не относящиеся к регулярным выражениям + * или экранизированые + * **/ + private var charsAtom = "" + + override fun enterCapture(ctx: PCREParser.CaptureContext) { + super.enterCapture(ctx) + regexReplaceString += "\$$currentGroupIndex" + currentGroupIndex++ + } + + override fun enterShared_atom(ctx: PCREParser.Shared_atomContext) { + super.enterShared_atom(ctx) + /** Найдено соответствие цифр \\d **/ + charsAtom = Constants.digits + mutableList.add(charsAtom) + } + + override fun enterCharacter_class(ctx: PCREParser.Character_classContext) { + super.enterCharacter_class(ctx) + /** Проверка на количество диапазонов + * true - если, например [А-дD-f] + * false - если, например [А-д] + * **/ + if (ctx.cc_atom().size > 1) { + charsAtom = "" + val firstChar = ctx.CharacterClassStart().text + val endChar = ctx.CharacterClassEnd()[0].text + for (i in 0 until ctx.cc_atom().size) { + charsAtom += checkRules(firstChar + ctx.cc_atom()[i].text + endChar) + } + } else { + charsAtom = checkRules(ctx.text) + } + mutableList.add(charsAtom) + } + + /** Дублирование повторений для placeholder при их наличии, например [A-B]{6}, где 6 - повторения **/ + override fun enterDigits(ctx: PCREParser.DigitsContext) { + super.enterDigits(ctx) + repeat(ctx.text.toInt() - 1) { + mutableList.add(charsAtom) + } + } + + override fun enterLiteral(ctx: PCREParser.LiteralContext) { + super.enterLiteral(ctx) + regexReplaceString += ctx.shared_literal().text + charsAtom = ctx.text + mutableList.add(charsAtom) + } + + fun toPCREGeneratorItem() = PCREGeneratorItem( + regexReplaceString, + mutableList.map { + it.replace("\\", "") + }.toMutableList() + ) +} \ No newline at end of file diff --git a/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/regexgenerator/PCREGeneratorListenerExt.kt b/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/regexgenerator/PCREGeneratorListenerExt.kt new file mode 100644 index 0000000..00587e7 --- /dev/null +++ b/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/regexgenerator/PCREGeneratorListenerExt.kt @@ -0,0 +1,121 @@ +package ru.touchin.roboswag.textprocessing.generators.regexgenerator + +import ru.touchin.roboswag.textprocessing.Constants + +data class IndexesItem(val firstIndex: Int, val lastIndex: Int) + +internal fun PCREGeneratorListener.checkRules(ctxText: String): String { + var atomStr = ctxText + var resultCharsAtom = "" + /** endAtomStr index of atomStr.length - 2 вычисляется потому что с поиском, например, + * в [A-B] должен проверяться последний допуск для строки в данном случае это В + * startAtomStr = atomStr[1] - потому что должен проверяться первый допуск для строки + * в [A-B] это будет А + * **/ + val endAtomStr = atomStr[atomStr.length - 2] + val startAtomStr = atomStr[1] + when { + atomStr.contains('-') -> { + when { + /** Проверка соответствия начала диапазона Русских букв на верхний регистр **/ + Constants.charsRuUpper.contains(startAtomStr) -> { + when { + /** Проверка соответствия диапазона Русских букв с верхним и нижнем регистром **/ + Constants.charsRuLower.contains(endAtomStr) -> { + atomStr = atomStr.uppercase() + val (firstIndex, lastIndex) = findFirstAndLastIndex(atomStr, Constants.charsRuUpper) + resultCharsAtom = Constants.charsRuUpper.substring(firstIndex, lastIndex) + + Constants.charsRuLower.substring(firstIndex, lastIndex) + } + /** Проверка соответствия диапазона Русских букв с верхним регистром **/ + else -> { + atomStr = atomStr.uppercase() + val (firstIndex, lastIndex) = findFirstAndLastIndex(atomStr, Constants.charsRuUpper) + resultCharsAtom = Constants.charsRuUpper.substring(firstIndex, lastIndex) + } + } + } + /** Проверка соответствия начала диапазона Русских букв на нижний регистр **/ + Constants.charsRuLower.contains(startAtomStr) -> { + when { + /** Проверка соответствия диапазона Русских букв с нижним и верхнем регистром **/ + Constants.charsRuUpper.contains(endAtomStr) -> { + atomStr = atomStr.lowercase() + val (firstIndex, lastIndex) = findFirstAndLastIndex(atomStr, Constants.charsRuLower) + resultCharsAtom = Constants.charsRuLower.substring(firstIndex, lastIndex) + + Constants.charsRuUpper.substring(firstIndex, lastIndex) + } + /** Проверка соответствия диапазона Русских букв с нижним регистром **/ + else -> { + atomStr = atomStr.lowercase() + val (firstIndex, lastIndex) = findFirstAndLastIndex(atomStr, Constants.charsRuLower) + resultCharsAtom = Constants.charsRuLower.substring(firstIndex, lastIndex) + } + } + } + /** Проверка соответствия начала диапазона Английских букв на верхний регистр **/ + Constants.charsEngUpper.contains(startAtomStr) -> { + when { + /** Проверка соответствия диапазона Английских букв с верхним и нижнем регистром **/ + Constants.charsEngLower.contains(endAtomStr) -> { + atomStr = atomStr.uppercase() + val (firstIndex, lastIndex) = findFirstAndLastIndex(atomStr, Constants.charsEngUpper) + resultCharsAtom = Constants.charsEngUpper.substring(firstIndex, lastIndex) + + Constants.charsEngLower.substring(firstIndex, lastIndex) + } + /** Проверка соответствия диапазона Аглийских букв с верхним регистром **/ + else -> { + atomStr = atomStr.uppercase() + val (firstIndex, lastIndex) = findFirstAndLastIndex(atomStr, Constants.charsEngUpper) + resultCharsAtom = Constants.charsEngUpper.substring(firstIndex, lastIndex) + } + } + } + /** Проверка соответствия начала диапазона Английских букв на нижний регистр **/ + Constants.charsEngLower.contains(startAtomStr) -> { + when { + /** Проверка соответствия диапазона Английских букв с нижним и верхнем регистром **/ + Constants.charsEngUpper.contains(endAtomStr) -> { + atomStr = atomStr.lowercase() + val (firstIndex, lastIndex) = findFirstAndLastIndex(atomStr, Constants.charsEngLower) + resultCharsAtom = Constants.charsEngLower.substring(firstIndex, lastIndex) + + Constants.charsEngUpper.substring(firstIndex, lastIndex) + } + /** Проверка соответствия диапазона Аглийских букв с нижним регистром **/ + else -> { + atomStr = atomStr.lowercase() + val (firstIndex, lastIndex) = findFirstAndLastIndex(atomStr, Constants.charsEngLower) + resultCharsAtom = Constants.charsEngLower.substring(firstIndex, lastIndex) + } + } + } + /** Проверка соответствия диапазона Цифр букв с нижним регистром **/ + Constants.digits.contains(startAtomStr) -> { + /** Регулярки проверяют с 0, а placeholder с 1 **/ + val digitsRegex = "0123456789" + val (firstIndex, lastIndex) = findFirstAndLastIndex(atomStr, digitsRegex) + resultCharsAtom = digitsRegex.substring(firstIndex, lastIndex) + } + } + } + /** Результат при остальных соответствиях **/ + else -> { + resultCharsAtom = atomStr.substring(1, atomStr.length - 1) + } + } + return resultCharsAtom +} + +/** atomStr в себе должен содержать atomStr(описание выше) + * chars в себе должен содержать элементы по порядку алфавитному или возрастанию + * тех символов, которые находятся в atomStr + * **/ +internal fun PCREGeneratorListener.findFirstAndLastIndex(atomStr: String, chars: String): IndexesItem { + var firstIndex = 0 + var lastIndex = chars.length - 1 + for (i in chars.indices) { + if (chars[i] == atomStr[1]) firstIndex = i + if (chars[i] == atomStr[atomStr.length - 2]) lastIndex = i + 1 + } + return IndexesItem(firstIndex, lastIndex) +} \ No newline at end of file diff --git a/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/regexgenerator/RegexReplaceGenerator.kt b/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/regexgenerator/RegexReplaceGenerator.kt new file mode 100644 index 0000000..146b181 --- /dev/null +++ b/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/regexgenerator/RegexReplaceGenerator.kt @@ -0,0 +1,21 @@ +package ru.touchin.roboswag.textprocessing.generators.regexgenerator + +import org.antlr.v4.runtime.CharStreams +import org.antlr.v4.runtime.CommonTokenStream +import org.antlr.v4.runtime.tree.ParseTreeWalker +import ru.touchin.roboswag.textprocessing.pcre.parser.PCRELexer +import ru.touchin.roboswag.textprocessing.pcre.parser.PCREParser + +class RegexReplaceGenerator { + + fun regexToRegexReplace(regex: String): PCREGeneratorItem { + val stringStream = CharStreams.fromString(regex) + val lexer = PCRELexer(stringStream) + val parser = PCREParser(CommonTokenStream(lexer)) + val parseContext = parser.parse() + val walker = ParseTreeWalker() + val pcreGeneratorListener = PCREGeneratorListener() + walker.walk(pcreGeneratorListener, parseContext) + return pcreGeneratorListener.toPCREGeneratorItem() + } +} \ No newline at end of file diff --git a/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/validators/CustomValidator.kt b/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/validators/CustomValidator.kt new file mode 100644 index 0000000..910c23f --- /dev/null +++ b/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/validators/CustomValidator.kt @@ -0,0 +1,16 @@ +package ru.touchin.roboswag.textprocessing.validators + +import ru.tinkoff.decoro.slots.Slot + +class CustomValidator private constructor( + private val slotItems: String +) : Slot.SlotValidator { + + companion object { + fun customSlot(slotItems: String) = Slot(null, CustomValidator(slotItems)) + } + + override fun validate(value: Char): Boolean { + return slotItems.contains(value) + } +} \ No newline at end of file diff --git a/text-processing/src/test/java/ru/touchin/roboswag/textprocessing/TextFormatterTest.kt b/text-processing/src/test/java/ru/touchin/roboswag/textprocessing/TextFormatterTest.kt index 55cc78b..3db4c5a 100644 --- a/text-processing/src/test/java/ru/touchin/roboswag/textprocessing/TextFormatterTest.kt +++ b/text-processing/src/test/java/ru/touchin/roboswag/textprocessing/TextFormatterTest.kt @@ -12,6 +12,7 @@ class TextFormatterTest { val item = TextFormatter(regex) Assert.assertEquals("$1\\/$2", item.getRegexReplace()) Assert.assertEquals("06/22", item.getFormatText(inputText)) + Assert.assertEquals("12/34", item.getPlaceholder()) } @Test @@ -21,6 +22,7 @@ class TextFormatterTest { val item = TextFormatter(regex) Assert.assertEquals("\$1 \$2 \$3 \$4", item.getRegexReplace()) Assert.assertEquals("1234 3456 1235 3534", item.getFormatText(inputText)) + Assert.assertEquals("1234 5678 9012 3456", item.getPlaceholder()) } @Test @@ -30,6 +32,7 @@ class TextFormatterTest { val item = TextFormatter(regex) Assert.assertEquals("\\+7 \\($1\\) $2 $3 $4", item.getRegexReplace()) Assert.assertEquals("+7 (909) 134 44 22", item.getFormatText(inputText)) + Assert.assertEquals("+7 (123) 456 78 90", item.getPlaceholder()) } @Test @@ -39,6 +42,7 @@ class TextFormatterTest { val item = TextFormatter(regex) Assert.assertEquals("\$1-\$2 № \$3", item.getRegexReplace()) Assert.assertEquals("IV-БЮ № 349823", item.getFormatText(inputText)) + Assert.assertEquals("AB-АБ № 123456", item.getPlaceholder()) } @Test @@ -48,5 +52,6 @@ class TextFormatterTest { val item = TextFormatter(regex) Assert.assertEquals("\$1\$2 ₽", item.getRegexReplace()) Assert.assertEquals("5332.4 ₽", item.getFormatText(inputText)) + Assert.assertEquals("1.2 ₽", item.getPlaceholder()) } } \ No newline at end of file From 393ba8d8cb8111cca65fabb2a277e35ddd43e81c Mon Sep 17 00:00:00 2001 From: airatmeister Date: Tue, 20 Dec 2022 16:14:37 +0300 Subject: [PATCH 2/3] INTERNAL-377: Placeholder generation from regex source expression --- .../roboswag/textprocessing/Constants.kt | 9 -- .../roboswag/textprocessing/TextFormatter.kt | 2 +- .../generators/DecoroMaskGenerator.kt | 4 +- .../generators/PlaceHolderGenerator.kt | 91 +++---------- .../regexgenerator/PCREGeneratorItem.kt | 2 +- .../regexgenerator/PCREGeneratorListener.kt | 57 ++++++--- .../PCREGeneratorListenerExt.kt | 121 ------------------ .../validators/CustomValidator.kt | 4 +- 8 files changed, 63 insertions(+), 227 deletions(-) delete mode 100644 text-processing/src/main/java/ru/touchin/roboswag/textprocessing/Constants.kt delete mode 100644 text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/regexgenerator/PCREGeneratorListenerExt.kt diff --git a/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/Constants.kt b/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/Constants.kt deleted file mode 100644 index 49377d8..0000000 --- a/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/Constants.kt +++ /dev/null @@ -1,9 +0,0 @@ -package ru.touchin.roboswag.textprocessing - -object Constants { - internal const val digits = "1234567890" - internal const val charsRuUpper = "АБВГДЕЖЗИЙКЛМНОПРСТУФЧЦЧЭЮЯЪЬЫШ" - internal const val charsEngUpper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - internal const val charsRuLower = "абвгдежзийклмнопрстуфчцэюяъьыш" - internal const val charsEngLower = "abcdefghijklmnopqrstuvwxyz" -} \ No newline at end of file diff --git a/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/TextFormatter.kt b/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/TextFormatter.kt index 64ef9c4..b74a02a 100644 --- a/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/TextFormatter.kt +++ b/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/TextFormatter.kt @@ -11,7 +11,7 @@ class TextFormatter(private val regex: String) { private val decoroMaskGenerator = DecoroMaskGenerator() private val pcreGeneratorItem = regexReplaceGenerator.regexToRegexReplace(regex) private val regexReplaceString = pcreGeneratorItem.regexReplaceString - private val listForPlaceholder = pcreGeneratorItem.listForPlaceholder + private val listForPlaceholder = pcreGeneratorItem.list private val placeholderGenerator = PlaceholderGenerator(listForPlaceholder) fun getFormatText(inputText: String) = inputText.replace(Regex(regex), regexReplaceString) diff --git a/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/DecoroMaskGenerator.kt b/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/DecoroMaskGenerator.kt index 3cf3841..4c73360 100644 --- a/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/DecoroMaskGenerator.kt +++ b/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/DecoroMaskGenerator.kt @@ -11,11 +11,11 @@ class DecoroMaskGenerator { /** Генерация маски и слотов на основе возможных символов для placeholder, * если возможные символы не более одного, то символ хардкодится в слот * **/ - fun mask(placeholder: String, listForPlaceholder: List): MaskFormatWatcher { + fun mask(placeholder: String, listForPlaceholder: List>): MaskFormatWatcher { val slots = mutableListOf() for (i in placeholder.indices) { slots.add( - if (listForPlaceholder[i].length == 1) { + if (listForPlaceholder[i].size == 1) { PredefinedSlots.hardcodedSlot(placeholder[i]) } else { CustomValidator.customSlot(listForPlaceholder[i]) diff --git a/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/PlaceHolderGenerator.kt b/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/PlaceHolderGenerator.kt index ca6cd3e..0431032 100644 --- a/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/PlaceHolderGenerator.kt +++ b/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/PlaceHolderGenerator.kt @@ -1,90 +1,29 @@ package ru.touchin.roboswag.textprocessing.generators -import ru.touchin.roboswag.textprocessing.Constants.charsEngLower -import ru.touchin.roboswag.textprocessing.Constants.charsEngUpper -import ru.touchin.roboswag.textprocessing.Constants.charsRuLower -import ru.touchin.roboswag.textprocessing.Constants.charsRuUpper -import ru.touchin.roboswag.textprocessing.Constants.digits - class PlaceholderGenerator( - listForPlaceholder: List + listForPlaceholder: List> ) { private var placeholder: String = "" init { - /** Индексы для всех возможных вариантов: - * Цифр, Ангийских букв верхнего регистра, - * Русских букв верхнего регистра, - * Английских букв нижнего регистра, - * Русских букв верхнего и нижнего регистра, - * Английских букв верхнего и нижнего регистра, - * На случай остальных повторений - * **/ - var indexDigits = 0 - var indexCharsEngUpper = 0 - var indexCharsRuUpper = 0 - var indexCharsEngLower = 0 - var indexCharsRuLower = 0 - var indexUpNLowerRu = 0 - var indexUpNLowerEng = 0 - var indexElse = 0 - for (str in listForPlaceholder) { - if (str.isEmpty()) continue + val hashMap = hashMapOf, Int>() + for (listOfChar in listForPlaceholder) { + hashMap[listOfChar] = 0 + } + for (listOfChar in listForPlaceholder) { + if (listOfChar.isEmpty()) continue /** Если элемент без повторений **/ - if (str.length == 1) { - placeholder += str + if (listOfChar.size == 1) { + placeholder += listOfChar[0] continue } - when { - /** Если элемент повторяет цифры **/ - digits.contains(str) -> { - if (str.length <= indexDigits) indexDigits = 0 - placeholder += str[indexDigits] - indexDigits++ - } - /** Если элемент повторяет Английские буквы верхнего регистра **/ - charsEngUpper.contains(str) -> { - if (str.length <= indexCharsEngUpper) indexCharsEngUpper = 0 - placeholder += str[indexCharsEngUpper] - indexCharsEngUpper++ - } - /** Если элемент повторяет Английские буквы нижнего регистра **/ - charsEngLower.contains(str) -> { - if (str.length <= indexCharsEngLower) indexCharsEngLower = 0 - placeholder += str[indexCharsEngLower] - indexCharsEngLower++ - } - /** Если элемент повторяет Русские буквы верхнего регистра **/ - charsRuUpper.contains(str) -> { - if (str.length <= indexCharsRuUpper) indexCharsRuUpper = 0 - placeholder += str[indexCharsRuUpper] - indexCharsRuUpper++ - } - /** Если элемент повторяет Русские буквы нижнего регистра **/ - charsRuLower.contains(str) -> { - if (str.length <= indexCharsRuLower) indexCharsRuLower = 0 - placeholder += str[indexCharsRuLower] - indexCharsRuLower++ - } - /** Если элемент повторяет Русские буквы верхнего и нижнего регистра **/ - charsRuLower.contains(str[str.length - 1]) -> { - if (str.length <= indexUpNLowerRu) indexUpNLowerRu = 0 - placeholder += str[indexUpNLowerRu] - indexUpNLowerRu++ - } - /** Если элемент повторяет Аглийские буквы верхнего и нижнего регистра **/ - charsEngLower.contains(str[str.length - 1]) -> { - if (str.length <= indexUpNLowerEng) indexUpNLowerEng = 0 - placeholder += str[indexUpNLowerEng] - indexUpNLowerEng++ - } - /** Если элемент повторяет Другие символы **/ - else -> { - if (str.length <= indexElse) indexElse = 0 - placeholder += str[indexElse] - indexElse++ - } + hashMap[listOfChar]?.let { j -> + var i = j + if (listOfChar.size <= i) i = 0 + placeholder += listOfChar[i] + i++ + hashMap[listOfChar] = i } } } diff --git a/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/regexgenerator/PCREGeneratorItem.kt b/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/regexgenerator/PCREGeneratorItem.kt index ba5d315..d9f7dd3 100644 --- a/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/regexgenerator/PCREGeneratorItem.kt +++ b/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/regexgenerator/PCREGeneratorItem.kt @@ -2,5 +2,5 @@ package ru.touchin.roboswag.textprocessing.generators.regexgenerator class PCREGeneratorItem( val regexReplaceString: String, - val listForPlaceholder: List + val list: List> ) \ No newline at end of file diff --git a/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/regexgenerator/PCREGeneratorListener.kt b/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/regexgenerator/PCREGeneratorListener.kt index 119450e..c371bf0 100644 --- a/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/regexgenerator/PCREGeneratorListener.kt +++ b/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/regexgenerator/PCREGeneratorListener.kt @@ -1,6 +1,5 @@ package ru.touchin.roboswag.textprocessing.generators.regexgenerator -import ru.touchin.roboswag.textprocessing.Constants import ru.touchin.roboswag.textprocessing.pcre.parser.PCREBaseListener import ru.touchin.roboswag.textprocessing.pcre.parser.PCREParser @@ -9,7 +8,7 @@ class PCREGeneratorListener : PCREBaseListener() { * Лист для placeholder, где индекс - номер буквы для placeholder * значение - возможные символы для placeholder * **/ - private var mutableList = mutableListOf() + private var mutableListOfListChar = mutableListOf>() private var currentGroupIndex = 1 private var regexReplaceString = "" @@ -19,7 +18,7 @@ class PCREGeneratorListener : PCREBaseListener() { * [1-2], \\d, [A-B], а так же элементы не относящиеся к регулярным выражениям * или экранизированые * **/ - private var charsAtom = "" + private var charsList = mutableListOf() override fun enterCapture(ctx: PCREParser.CaptureContext) { super.enterCapture(ctx) @@ -30,8 +29,8 @@ class PCREGeneratorListener : PCREBaseListener() { override fun enterShared_atom(ctx: PCREParser.Shared_atomContext) { super.enterShared_atom(ctx) /** Найдено соответствие цифр \\d **/ - charsAtom = Constants.digits - mutableList.add(charsAtom) + charsList = '1'.rangeTo('9').toMutableList().apply { add('0') } + mutableListOfListChar.add(charsList) } override fun enterCharacter_class(ctx: PCREParser.Character_classContext) { @@ -41,37 +40,65 @@ class PCREGeneratorListener : PCREBaseListener() { * false - если, например [А-д] * **/ if (ctx.cc_atom().size > 1) { - charsAtom = "" + charsList = mutableListOf() val firstChar = ctx.CharacterClassStart().text val endChar = ctx.CharacterClassEnd()[0].text for (i in 0 until ctx.cc_atom().size) { - charsAtom += checkRules(firstChar + ctx.cc_atom()[i].text + endChar) + charsList += checkRules(firstChar + ctx.cc_atom()[i].text + endChar) } } else { - charsAtom = checkRules(ctx.text) + charsList = checkRules(ctx.text) } - mutableList.add(charsAtom) + mutableListOfListChar.add(charsList) } /** Дублирование повторений для placeholder при их наличии, например [A-B]{6}, где 6 - повторения **/ override fun enterDigits(ctx: PCREParser.DigitsContext) { super.enterDigits(ctx) repeat(ctx.text.toInt() - 1) { - mutableList.add(charsAtom) + mutableListOfListChar.add(charsList) } } override fun enterLiteral(ctx: PCREParser.LiteralContext) { super.enterLiteral(ctx) regexReplaceString += ctx.shared_literal().text - charsAtom = ctx.text - mutableList.add(charsAtom) + charsList = mutableListOf() + for (s in ctx.text) { + charsList.add(s) + } + mutableListOfListChar.add(charsList) } fun toPCREGeneratorItem() = PCREGeneratorItem( regexReplaceString, - mutableList.map { - it.replace("\\", "") - }.toMutableList() + mutableListOfListChar.map { it -> + it.filter { + it != '\\' + } + } ) + + private fun checkRules(ctxText: String): MutableList { + /** endAtomStr index of atomStr.length - 2 вычисляется потому что с поиском, например, + * в [A-B] должен проверяться последний допуск для строки в данном случае это В + * startAtomStr = atomStr[1] - потому что должен проверяться первый допуск для строки + * в [A-B] это будет А + * **/ + val endAtomStr = ctxText[ctxText.length - 2] + val startAtomStr = ctxText[1] + return if (startAtomStr.isLetterOrDigit() && endAtomStr.isLetterOrDigit()) { + getListRangeChars(ctxText).filter { + it.isLetterOrDigit() + }.toMutableList() + } else { + mutableListOf(startAtomStr, endAtomStr) + } + } + + private fun getListRangeChars(atomStr: String): MutableList { + val startRange = atomStr[1] + val endRange = atomStr[atomStr.length - 2] + return startRange.rangeTo(endRange).toMutableList() + } } \ No newline at end of file diff --git a/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/regexgenerator/PCREGeneratorListenerExt.kt b/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/regexgenerator/PCREGeneratorListenerExt.kt deleted file mode 100644 index 00587e7..0000000 --- a/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/regexgenerator/PCREGeneratorListenerExt.kt +++ /dev/null @@ -1,121 +0,0 @@ -package ru.touchin.roboswag.textprocessing.generators.regexgenerator - -import ru.touchin.roboswag.textprocessing.Constants - -data class IndexesItem(val firstIndex: Int, val lastIndex: Int) - -internal fun PCREGeneratorListener.checkRules(ctxText: String): String { - var atomStr = ctxText - var resultCharsAtom = "" - /** endAtomStr index of atomStr.length - 2 вычисляется потому что с поиском, например, - * в [A-B] должен проверяться последний допуск для строки в данном случае это В - * startAtomStr = atomStr[1] - потому что должен проверяться первый допуск для строки - * в [A-B] это будет А - * **/ - val endAtomStr = atomStr[atomStr.length - 2] - val startAtomStr = atomStr[1] - when { - atomStr.contains('-') -> { - when { - /** Проверка соответствия начала диапазона Русских букв на верхний регистр **/ - Constants.charsRuUpper.contains(startAtomStr) -> { - when { - /** Проверка соответствия диапазона Русских букв с верхним и нижнем регистром **/ - Constants.charsRuLower.contains(endAtomStr) -> { - atomStr = atomStr.uppercase() - val (firstIndex, lastIndex) = findFirstAndLastIndex(atomStr, Constants.charsRuUpper) - resultCharsAtom = Constants.charsRuUpper.substring(firstIndex, lastIndex) + - Constants.charsRuLower.substring(firstIndex, lastIndex) - } - /** Проверка соответствия диапазона Русских букв с верхним регистром **/ - else -> { - atomStr = atomStr.uppercase() - val (firstIndex, lastIndex) = findFirstAndLastIndex(atomStr, Constants.charsRuUpper) - resultCharsAtom = Constants.charsRuUpper.substring(firstIndex, lastIndex) - } - } - } - /** Проверка соответствия начала диапазона Русских букв на нижний регистр **/ - Constants.charsRuLower.contains(startAtomStr) -> { - when { - /** Проверка соответствия диапазона Русских букв с нижним и верхнем регистром **/ - Constants.charsRuUpper.contains(endAtomStr) -> { - atomStr = atomStr.lowercase() - val (firstIndex, lastIndex) = findFirstAndLastIndex(atomStr, Constants.charsRuLower) - resultCharsAtom = Constants.charsRuLower.substring(firstIndex, lastIndex) + - Constants.charsRuUpper.substring(firstIndex, lastIndex) - } - /** Проверка соответствия диапазона Русских букв с нижним регистром **/ - else -> { - atomStr = atomStr.lowercase() - val (firstIndex, lastIndex) = findFirstAndLastIndex(atomStr, Constants.charsRuLower) - resultCharsAtom = Constants.charsRuLower.substring(firstIndex, lastIndex) - } - } - } - /** Проверка соответствия начала диапазона Английских букв на верхний регистр **/ - Constants.charsEngUpper.contains(startAtomStr) -> { - when { - /** Проверка соответствия диапазона Английских букв с верхним и нижнем регистром **/ - Constants.charsEngLower.contains(endAtomStr) -> { - atomStr = atomStr.uppercase() - val (firstIndex, lastIndex) = findFirstAndLastIndex(atomStr, Constants.charsEngUpper) - resultCharsAtom = Constants.charsEngUpper.substring(firstIndex, lastIndex) + - Constants.charsEngLower.substring(firstIndex, lastIndex) - } - /** Проверка соответствия диапазона Аглийских букв с верхним регистром **/ - else -> { - atomStr = atomStr.uppercase() - val (firstIndex, lastIndex) = findFirstAndLastIndex(atomStr, Constants.charsEngUpper) - resultCharsAtom = Constants.charsEngUpper.substring(firstIndex, lastIndex) - } - } - } - /** Проверка соответствия начала диапазона Английских букв на нижний регистр **/ - Constants.charsEngLower.contains(startAtomStr) -> { - when { - /** Проверка соответствия диапазона Английских букв с нижним и верхнем регистром **/ - Constants.charsEngUpper.contains(endAtomStr) -> { - atomStr = atomStr.lowercase() - val (firstIndex, lastIndex) = findFirstAndLastIndex(atomStr, Constants.charsEngLower) - resultCharsAtom = Constants.charsEngLower.substring(firstIndex, lastIndex) + - Constants.charsEngUpper.substring(firstIndex, lastIndex) - } - /** Проверка соответствия диапазона Аглийских букв с нижним регистром **/ - else -> { - atomStr = atomStr.lowercase() - val (firstIndex, lastIndex) = findFirstAndLastIndex(atomStr, Constants.charsEngLower) - resultCharsAtom = Constants.charsEngLower.substring(firstIndex, lastIndex) - } - } - } - /** Проверка соответствия диапазона Цифр букв с нижним регистром **/ - Constants.digits.contains(startAtomStr) -> { - /** Регулярки проверяют с 0, а placeholder с 1 **/ - val digitsRegex = "0123456789" - val (firstIndex, lastIndex) = findFirstAndLastIndex(atomStr, digitsRegex) - resultCharsAtom = digitsRegex.substring(firstIndex, lastIndex) - } - } - } - /** Результат при остальных соответствиях **/ - else -> { - resultCharsAtom = atomStr.substring(1, atomStr.length - 1) - } - } - return resultCharsAtom -} - -/** atomStr в себе должен содержать atomStr(описание выше) - * chars в себе должен содержать элементы по порядку алфавитному или возрастанию - * тех символов, которые находятся в atomStr - * **/ -internal fun PCREGeneratorListener.findFirstAndLastIndex(atomStr: String, chars: String): IndexesItem { - var firstIndex = 0 - var lastIndex = chars.length - 1 - for (i in chars.indices) { - if (chars[i] == atomStr[1]) firstIndex = i - if (chars[i] == atomStr[atomStr.length - 2]) lastIndex = i + 1 - } - return IndexesItem(firstIndex, lastIndex) -} \ No newline at end of file diff --git a/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/validators/CustomValidator.kt b/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/validators/CustomValidator.kt index 910c23f..9a27c90 100644 --- a/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/validators/CustomValidator.kt +++ b/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/validators/CustomValidator.kt @@ -3,11 +3,11 @@ package ru.touchin.roboswag.textprocessing.validators import ru.tinkoff.decoro.slots.Slot class CustomValidator private constructor( - private val slotItems: String + private val slotItems: List ) : Slot.SlotValidator { companion object { - fun customSlot(slotItems: String) = Slot(null, CustomValidator(slotItems)) + fun customSlot(slotItems: List) = Slot(null, CustomValidator(slotItems)) } override fun validate(value: Char): Boolean { From 1d5e8f0c01deda82649b941fff269e2aa471c27b Mon Sep 17 00:00:00 2001 From: airatmeister Date: Tue, 20 Dec 2022 21:00:42 +0300 Subject: [PATCH 3/3] INTERNAL-377: Placeholder generation from regex source expression --- .../roboswag/textprocessing/TextFormatter.kt | 6 ++-- .../generators/DecoroMaskGenerator.kt | 8 ++--- .../textprocessing/generators/Matrix.kt | 3 ++ .../generators/PlaceHolderGenerator.kt | 32 ----------------- .../generators/PlaceholderGenerator.kt | 30 ++++++++++++++++ .../regexgenerator/PCREGeneratorItem.kt | 4 ++- .../regexgenerator/PCREGeneratorListener.kt | 35 +++++++++---------- .../validators/CustomValidator.kt | 6 ++-- 8 files changed, 63 insertions(+), 61 deletions(-) create mode 100644 text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/Matrix.kt delete mode 100644 text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/PlaceHolderGenerator.kt create mode 100644 text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/PlaceholderGenerator.kt diff --git a/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/TextFormatter.kt b/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/TextFormatter.kt index b74a02a..99eb9ea 100644 --- a/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/TextFormatter.kt +++ b/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/TextFormatter.kt @@ -11,8 +11,8 @@ class TextFormatter(private val regex: String) { private val decoroMaskGenerator = DecoroMaskGenerator() private val pcreGeneratorItem = regexReplaceGenerator.regexToRegexReplace(regex) private val regexReplaceString = pcreGeneratorItem.regexReplaceString - private val listForPlaceholder = pcreGeneratorItem.list - private val placeholderGenerator = PlaceholderGenerator(listForPlaceholder) + private val matrixOfSymbols = pcreGeneratorItem.matrixOfSymbols + private val placeholderGenerator = PlaceholderGenerator(matrixOfSymbols) fun getFormatText(inputText: String) = inputText.replace(Regex(regex), regexReplaceString) @@ -23,7 +23,7 @@ class TextFormatter(private val regex: String) { fun mask(textView: TextView) { val formatWatcher = decoroMaskGenerator.mask( placeholderGenerator.getPlaceholder(), - listForPlaceholder + matrixOfSymbols ) formatWatcher.installOn(textView) } diff --git a/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/DecoroMaskGenerator.kt b/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/DecoroMaskGenerator.kt index 4c73360..c5178a4 100644 --- a/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/DecoroMaskGenerator.kt +++ b/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/DecoroMaskGenerator.kt @@ -9,16 +9,16 @@ import ru.touchin.roboswag.textprocessing.validators.CustomValidator class DecoroMaskGenerator { /** Генерация маски и слотов на основе возможных символов для placeholder, - * если возможные символы не более одного, то символ хардкодится в слот + * если возможный символ всего один, то символ хардкодится в слот * **/ - fun mask(placeholder: String, listForPlaceholder: List>): MaskFormatWatcher { + fun mask(placeholder: String, matrixOfSymbols: Matrix): MaskFormatWatcher { val slots = mutableListOf() for (i in placeholder.indices) { slots.add( - if (listForPlaceholder[i].size == 1) { + if (matrixOfSymbols[i].size == 1) { PredefinedSlots.hardcodedSlot(placeholder[i]) } else { - CustomValidator.customSlot(listForPlaceholder[i]) + CustomValidator.customSlot(matrixOfSymbols[i]) } ) } diff --git a/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/Matrix.kt b/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/Matrix.kt new file mode 100644 index 0000000..1d20b16 --- /dev/null +++ b/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/Matrix.kt @@ -0,0 +1,3 @@ +package ru.touchin.roboswag.textprocessing.generators + +typealias Matrix = List> \ No newline at end of file diff --git a/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/PlaceHolderGenerator.kt b/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/PlaceHolderGenerator.kt deleted file mode 100644 index 0431032..0000000 --- a/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/PlaceHolderGenerator.kt +++ /dev/null @@ -1,32 +0,0 @@ -package ru.touchin.roboswag.textprocessing.generators - -class PlaceholderGenerator( - listForPlaceholder: List> -) { - - private var placeholder: String = "" - - init { - val hashMap = hashMapOf, Int>() - for (listOfChar in listForPlaceholder) { - hashMap[listOfChar] = 0 - } - for (listOfChar in listForPlaceholder) { - if (listOfChar.isEmpty()) continue - /** Если элемент без повторений **/ - if (listOfChar.size == 1) { - placeholder += listOfChar[0] - continue - } - hashMap[listOfChar]?.let { j -> - var i = j - if (listOfChar.size <= i) i = 0 - placeholder += listOfChar[i] - i++ - hashMap[listOfChar] = i - } - } - } - - fun getPlaceholder() = placeholder -} \ No newline at end of file diff --git a/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/PlaceholderGenerator.kt b/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/PlaceholderGenerator.kt new file mode 100644 index 0000000..3e9033a --- /dev/null +++ b/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/PlaceholderGenerator.kt @@ -0,0 +1,30 @@ +package ru.touchin.roboswag.textprocessing.generators + +class PlaceholderGenerator(matrixOfSymbols: Matrix) { + + private var placeholder: String = "" + + init { + val indexes = hashMapOf, Int>() + for (listOfSymbols in matrixOfSymbols) { + indexes[listOfSymbols] = 0 + } + for (listOfSymbols in matrixOfSymbols) { + if (listOfSymbols.isEmpty()) continue + /** Если элемент без повторений **/ + if (listOfSymbols.size == 1) { + placeholder += listOfSymbols[0] + continue + } + indexes[listOfSymbols]?.let { + var index = it + if (listOfSymbols.size <= index) index = 0 + placeholder += listOfSymbols[index] + index++ + indexes[listOfSymbols] = index + } + } + } + + fun getPlaceholder() = placeholder +} \ No newline at end of file diff --git a/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/regexgenerator/PCREGeneratorItem.kt b/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/regexgenerator/PCREGeneratorItem.kt index d9f7dd3..5b57d8b 100644 --- a/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/regexgenerator/PCREGeneratorItem.kt +++ b/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/regexgenerator/PCREGeneratorItem.kt @@ -1,6 +1,8 @@ package ru.touchin.roboswag.textprocessing.generators.regexgenerator +import ru.touchin.roboswag.textprocessing.generators.Matrix + class PCREGeneratorItem( val regexReplaceString: String, - val list: List> + val matrixOfSymbols: Matrix ) \ No newline at end of file diff --git a/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/regexgenerator/PCREGeneratorListener.kt b/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/regexgenerator/PCREGeneratorListener.kt index c371bf0..b42fb1d 100644 --- a/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/regexgenerator/PCREGeneratorListener.kt +++ b/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/generators/regexgenerator/PCREGeneratorListener.kt @@ -8,7 +8,7 @@ class PCREGeneratorListener : PCREBaseListener() { * Лист для placeholder, где индекс - номер буквы для placeholder * значение - возможные символы для placeholder * **/ - private var mutableListOfListChar = mutableListOf>() + private val matrixOfSymbols = mutableListOf>() private var currentGroupIndex = 1 private var regexReplaceString = "" @@ -18,7 +18,7 @@ class PCREGeneratorListener : PCREBaseListener() { * [1-2], \\d, [A-B], а так же элементы не относящиеся к регулярным выражениям * или экранизированые * **/ - private var charsList = mutableListOf() + private var listOfSymbols = mutableListOf() override fun enterCapture(ctx: PCREParser.CaptureContext) { super.enterCapture(ctx) @@ -29,8 +29,8 @@ class PCREGeneratorListener : PCREBaseListener() { override fun enterShared_atom(ctx: PCREParser.Shared_atomContext) { super.enterShared_atom(ctx) /** Найдено соответствие цифр \\d **/ - charsList = '1'.rangeTo('9').toMutableList().apply { add('0') } - mutableListOfListChar.add(charsList) + listOfSymbols = '1'.rangeTo('9').toMutableList().apply { add('0') } + matrixOfSymbols.add(listOfSymbols) } override fun enterCharacter_class(ctx: PCREParser.Character_classContext) { @@ -40,50 +40,49 @@ class PCREGeneratorListener : PCREBaseListener() { * false - если, например [А-д] * **/ if (ctx.cc_atom().size > 1) { - charsList = mutableListOf() + listOfSymbols = mutableListOf() val firstChar = ctx.CharacterClassStart().text val endChar = ctx.CharacterClassEnd()[0].text for (i in 0 until ctx.cc_atom().size) { - charsList += checkRules(firstChar + ctx.cc_atom()[i].text + endChar) + listOfSymbols += availableSymbolsToList(firstChar + ctx.cc_atom()[i].text + endChar) } } else { - charsList = checkRules(ctx.text) + listOfSymbols = availableSymbolsToList(ctx.text) } - mutableListOfListChar.add(charsList) + matrixOfSymbols.add(listOfSymbols) } /** Дублирование повторений для placeholder при их наличии, например [A-B]{6}, где 6 - повторения **/ override fun enterDigits(ctx: PCREParser.DigitsContext) { super.enterDigits(ctx) repeat(ctx.text.toInt() - 1) { - mutableListOfListChar.add(charsList) + matrixOfSymbols.add(listOfSymbols) } } override fun enterLiteral(ctx: PCREParser.LiteralContext) { super.enterLiteral(ctx) regexReplaceString += ctx.shared_literal().text - charsList = mutableListOf() + listOfSymbols = mutableListOf() for (s in ctx.text) { - charsList.add(s) + listOfSymbols.add(s) } - mutableListOfListChar.add(charsList) + matrixOfSymbols.add(listOfSymbols) } fun toPCREGeneratorItem() = PCREGeneratorItem( regexReplaceString, - mutableListOfListChar.map { it -> + matrixOfSymbols.map { it -> it.filter { it != '\\' } } ) - private fun checkRules(ctxText: String): MutableList { - /** endAtomStr index of atomStr.length - 2 вычисляется потому что с поиском, например, - * в [A-B] должен проверяться последний допуск для строки в данном случае это В - * startAtomStr = atomStr[1] - потому что должен проверяться первый допуск для строки - * в [A-B] это будет А + private fun availableSymbolsToList(ctxText: String): MutableList { + /** startAtomStr = atomStr[1] - потому что должен проверяться первый допуск для строки + * endAtomStr index of atomStr.length - 2 вычисляется потому что с поиском, + * например, [A-B], endAtomStr = "B", startAtomStr = "A" * **/ val endAtomStr = ctxText[ctxText.length - 2] val startAtomStr = ctxText[1] diff --git a/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/validators/CustomValidator.kt b/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/validators/CustomValidator.kt index 9a27c90..ccab533 100644 --- a/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/validators/CustomValidator.kt +++ b/text-processing/src/main/java/ru/touchin/roboswag/textprocessing/validators/CustomValidator.kt @@ -3,14 +3,14 @@ package ru.touchin.roboswag.textprocessing.validators import ru.tinkoff.decoro.slots.Slot class CustomValidator private constructor( - private val slotItems: List + private val slotSymbols: List ) : Slot.SlotValidator { companion object { - fun customSlot(slotItems: List) = Slot(null, CustomValidator(slotItems)) + fun customSlot(slotSymbols: List) = Slot(null, CustomValidator(slotSymbols)) } override fun validate(value: Char): Boolean { - return slotItems.contains(value) + return slotSymbols.contains(value) } } \ No newline at end of file