diff --git a/build.gradle.kts b/build.gradle.kts index aba53a1..d24ca67 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -11,7 +11,7 @@ repositories { mavenCentral() } -version = "1.4.1-SNAPSHOT" +version = "1.4.2-SNAPSHOT" group = "org.hisp.dhis.lib.expression" if (project.hasProperty("removeSnapshotSuffix")) { @@ -107,4 +107,4 @@ sonarqube { tasks.named("sonar").configure { dependsOn(":koverXmlReport") -} \ No newline at end of file +} diff --git a/src/commonMain/kotlin/org/hisp/dhis/lib/expression/eval/Calculator.kt b/src/commonMain/kotlin/org/hisp/dhis/lib/expression/eval/Calculator.kt index b2db5a8..57fc36e 100644 --- a/src/commonMain/kotlin/org/hisp/dhis/lib/expression/eval/Calculator.kt +++ b/src/commonMain/kotlin/org/hisp/dhis/lib/expression/eval/Calculator.kt @@ -2,6 +2,7 @@ package org.hisp.dhis.lib.expression.eval import kotlinx.datetime.LocalDate import org.hisp.dhis.lib.expression.ast.* +import org.hisp.dhis.lib.expression.ast.Nodes.Utf8StringNode import org.hisp.dhis.lib.expression.ast.UnaryOperator.Companion.negate import org.hisp.dhis.lib.expression.spi.* @@ -139,7 +140,7 @@ internal class Calculator( evalToInteger(fn.child(2))) NamedFunction.d2_validatePattern -> functions.d2_validatePattern( evalToString(fn.child(0)), - evalToString(fn.child(1))) + evalToRawString(fn.child(1))) NamedFunction.d2_weeksBetween -> functions.d2_weeksBetween( evalToDate(fn.child(0)), evalToDate(fn.child(1))) @@ -301,6 +302,15 @@ internal class Calculator( return eval(node, "String", Typed::toStringTypeCoercion) } + private fun evalToRawString(node: Node<*>): String? { + return when (node.getType()) { + NodeType.STRING -> node.getRawValue() + NodeType.ARGUMENT -> evalToRawString(node.child(0)) + NodeType.PAR -> evalToRawString(node.child(0)) + else -> evalToString(node) + } + } + fun evalToBoolean(node: Node<*>): Boolean? { return eval(node, "Boolean", Typed::toBooleanTypeCoercion) } diff --git a/src/commonMain/kotlin/org/hisp/dhis/lib/expression/spi/ExpressionFunctions.kt b/src/commonMain/kotlin/org/hisp/dhis/lib/expression/spi/ExpressionFunctions.kt index 5fb4fa7..007cbed 100644 --- a/src/commonMain/kotlin/org/hisp/dhis/lib/expression/spi/ExpressionFunctions.kt +++ b/src/commonMain/kotlin/org/hisp/dhis/lib/expression/spi/ExpressionFunctions.kt @@ -315,7 +315,7 @@ fun interface ExpressionFunctions { } fun d2_validatePattern(input: String?, regex: String?): Boolean { - return input != null && regex != null && input.matches(regex.toRegex()) + return input != null && regex != null && matchesPattern(input, regex) } fun d2_weeksBetween(start: LocalDate?, end: LocalDate?): Int { diff --git a/src/commonMain/kotlin/org/hisp/dhis/lib/expression/spi/RegexMatch.kt b/src/commonMain/kotlin/org/hisp/dhis/lib/expression/spi/RegexMatch.kt new file mode 100644 index 0000000..63928d0 --- /dev/null +++ b/src/commonMain/kotlin/org/hisp/dhis/lib/expression/spi/RegexMatch.kt @@ -0,0 +1,3 @@ +package org.hisp.dhis.lib.expression.spi + +internal expect fun matchesPattern(input: String, pattern: String): Boolean diff --git a/src/commonTest/kotlin/org/hisp/dhis/lib/expression/function/ValidatePatternTest.kt b/src/commonTest/kotlin/org/hisp/dhis/lib/expression/function/ValidatePatternTest.kt index 278129b..817d4a3 100644 --- a/src/commonTest/kotlin/org/hisp/dhis/lib/expression/function/ValidatePatternTest.kt +++ b/src/commonTest/kotlin/org/hisp/dhis/lib/expression/function/ValidatePatternTest.kt @@ -29,15 +29,17 @@ internal class ValidatePatternTest { fun testValidatePattern_Match() { assertTrue(evaluate("d2:validatePattern(\"124\", \"[0-9]+\")")) assertTrue(evaluate("d2:validatePattern(\"12x4\", \"[0-9x]+\")")) + assertTrue(evaluate("d2:validatePattern(\"John\",(\"[a-zA-Z0-9À-ȕ\\'\\-\\‘\\`\\’\\ ]+\"))")) } @Test fun testValidatePattern_NoMatch() { assertFalse(evaluate("d2:validatePattern(\"12x4\", \"[0-9]+\")")) assertFalse(evaluate("d2:validatePattern(\"ab0\", \"[0-9x]+\")")) + assertFalse(evaluate("d2:validatePattern(\"Иван\",(\"[a-zA-Z0-9À-ȕ\\'\\-\\‘\\`\\’\\ ]+\"))")) } private fun evaluate(expression: String): Boolean { return Expression(expression, ExpressionMode.RULE_ENGINE_ACTION).evaluate() as Boolean } -} \ No newline at end of file +} diff --git a/src/jsMain/kotlin/org/hisp/dhis/lib/expression/spi/RegexMatch.kt b/src/jsMain/kotlin/org/hisp/dhis/lib/expression/spi/RegexMatch.kt new file mode 100644 index 0000000..1219efe --- /dev/null +++ b/src/jsMain/kotlin/org/hisp/dhis/lib/expression/spi/RegexMatch.kt @@ -0,0 +1,7 @@ +package org.hisp.dhis.lib.expression.spi + +internal actual fun matchesPattern(input: String, pattern: String): Boolean { + // Avoid Kotlin stdlib's Regex wrapper which may add the JS `u` flag and reject unknown backslash + // escapes. Use RegExp directly with ^(?:...)$ anchoring to replicate full-string matching. + return js("new RegExp('^(?:' + pattern + ')$').test(input)") as Boolean +} diff --git a/src/jvmMain/kotlin/org/hisp/dhis/lib/expression/spi/RegexMatch.kt b/src/jvmMain/kotlin/org/hisp/dhis/lib/expression/spi/RegexMatch.kt new file mode 100644 index 0000000..e33e60c --- /dev/null +++ b/src/jvmMain/kotlin/org/hisp/dhis/lib/expression/spi/RegexMatch.kt @@ -0,0 +1,4 @@ +package org.hisp.dhis.lib.expression.spi + +internal actual fun matchesPattern(input: String, pattern: String): Boolean = + input.matches(pattern.toRegex()) diff --git a/src/nativeMain/kotlin/org/hisp/dhis/lib/expression/spi/RegexMatch.kt b/src/nativeMain/kotlin/org/hisp/dhis/lib/expression/spi/RegexMatch.kt new file mode 100644 index 0000000..e33e60c --- /dev/null +++ b/src/nativeMain/kotlin/org/hisp/dhis/lib/expression/spi/RegexMatch.kt @@ -0,0 +1,4 @@ +package org.hisp.dhis.lib.expression.spi + +internal actual fun matchesPattern(input: String, pattern: String): Boolean = + input.matches(pattern.toRegex())