From ac2e818b191cdc716a5386fecce7bdfbd4be2a74 Mon Sep 17 00:00:00 2001 From: Jan Bernitt Date: Tue, 28 Oct 2025 15:40:59 +0100 Subject: [PATCH 1/5] fix: parsing of d2:condition arg0 into root AST --- .../dhis/lib/expression/eval/Calculator.kt | 2 +- .../expression/syntax/ExpressionGrammar.kt | 7 +++++- .../lib/expression/function/ConditionTest.kt | 25 +++++++++++++++++++ .../dhis/lib/expression/function/IfTest.kt | 1 + 4 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 src/commonTest/kotlin/org/hisp/dhis/lib/expression/function/ConditionTest.kt 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 d5494cd..b2db5a8 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 @@ -60,7 +60,7 @@ internal class Calculator( NamedFunction.containsItems -> functions.containsItems(evalToString(fn.child(0)), evalToStrings(fn.children()).drop(1)) NamedFunction.firstNonNull -> functions.firstNonNull(evalToMixed(fn.children())) NamedFunction.greatest -> functions.greatest(evalToNumbers(fn.children())) - NamedFunction.ifThenElse -> functions.ifThenElse( + NamedFunction.ifThenElse, NamedFunction.d2_condition -> functions.ifThenElse( evalToBoolean(fn.child(0)), evalToMixed(fn.child(1)), evalToMixed(fn.child(2))) diff --git a/src/commonMain/kotlin/org/hisp/dhis/lib/expression/syntax/ExpressionGrammar.kt b/src/commonMain/kotlin/org/hisp/dhis/lib/expression/syntax/ExpressionGrammar.kt index 9f8261f..043a2a9 100644 --- a/src/commonMain/kotlin/org/hisp/dhis/lib/expression/syntax/ExpressionGrammar.kt +++ b/src/commonMain/kotlin/org/hisp/dhis/lib/expression/syntax/ExpressionGrammar.kt @@ -21,6 +21,11 @@ import org.hisp.dhis.lib.expression.syntax.Fragment.Companion.constant */ object ExpressionGrammar { + private val STRING_PARSED = Fragment {expr, ctx -> run { + val nestedExpr = Literals.parseString(expr); // consumes the literal in parent expr + Expr.parse(nestedExpr, ctx, false); + }} + /* Terminals (simple building blocks) */ @@ -93,7 +98,7 @@ object ExpressionGrammar { fn(NamedFunction.variance, expr) ) private val CommonD2Functions = listOf( // (alphabetical) - fn(NamedFunction.d2_condition, STRING, expr, expr), + fn(NamedFunction.d2_condition, STRING_PARSED, expr, expr), fn(NamedFunction.d2_contains, expr, expr.plus()), fn(NamedFunction.d2_containsItems, expr, expr.plus()), fn(NamedFunction.d2_count, dataItem), diff --git a/src/commonTest/kotlin/org/hisp/dhis/lib/expression/function/ConditionTest.kt b/src/commonTest/kotlin/org/hisp/dhis/lib/expression/function/ConditionTest.kt new file mode 100644 index 0000000..df4857c --- /dev/null +++ b/src/commonTest/kotlin/org/hisp/dhis/lib/expression/function/ConditionTest.kt @@ -0,0 +1,25 @@ +package org.hisp.dhis.lib.expression.function + +import org.hisp.dhis.lib.expression.Expression +import org.hisp.dhis.lib.expression.ExpressionMode +import kotlin.test.Test +import kotlin.test.assertEquals + +/** + * Tests the `d2:condition` function + * + * @author Jan Bernitt + */ +class ConditionTest { + + @Test + fun testD2_Condition() { + assertEquals(1.0, evaluate("d2:condition('true', 1, 0)")) + assertEquals(true, evaluate("d2:condition('42 < 100', true, false)")) + assertEquals("yes", evaluate("d2:condition('if(true, true, false)', 'yes', 'no')")) + } + + private fun evaluate(expression: String): Any? { + return Expression(expression, ExpressionMode.ANDROID_CUSTOM_INTENT_EXPRESSION).evaluate() + } +} \ No newline at end of file diff --git a/src/commonTest/kotlin/org/hisp/dhis/lib/expression/function/IfTest.kt b/src/commonTest/kotlin/org/hisp/dhis/lib/expression/function/IfTest.kt index 1b8dffa..288afac 100644 --- a/src/commonTest/kotlin/org/hisp/dhis/lib/expression/function/IfTest.kt +++ b/src/commonTest/kotlin/org/hisp/dhis/lib/expression/function/IfTest.kt @@ -1,6 +1,7 @@ package org.hisp.dhis.lib.expression.function import org.hisp.dhis.lib.expression.Expression +import org.hisp.dhis.lib.expression.ExpressionMode import org.hisp.dhis.lib.expression.spi.IllegalExpressionException import kotlin.test.Test import kotlin.test.assertEquals From c0a856b2133bcfb79bd28bfb5bd40271aded411f Mon Sep 17 00:00:00 2001 From: Jan Bernitt Date: Tue, 28 Oct 2025 15:41:58 +0100 Subject: [PATCH 2/5] chore: undo change in unrelated file --- .../kotlin/org/hisp/dhis/lib/expression/function/IfTest.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/src/commonTest/kotlin/org/hisp/dhis/lib/expression/function/IfTest.kt b/src/commonTest/kotlin/org/hisp/dhis/lib/expression/function/IfTest.kt index 288afac..1b8dffa 100644 --- a/src/commonTest/kotlin/org/hisp/dhis/lib/expression/function/IfTest.kt +++ b/src/commonTest/kotlin/org/hisp/dhis/lib/expression/function/IfTest.kt @@ -1,7 +1,6 @@ package org.hisp.dhis.lib.expression.function import org.hisp.dhis.lib.expression.Expression -import org.hisp.dhis.lib.expression.ExpressionMode import org.hisp.dhis.lib.expression.spi.IllegalExpressionException import kotlin.test.Test import kotlin.test.assertEquals From 54540479d825928b3b79931016a065a25d9c708e Mon Sep 17 00:00:00 2001 From: Tony Valle Date: Wed, 29 Oct 2025 13:41:06 +0100 Subject: [PATCH 3/5] test: add more tests --- .../lib/expression/function/ConditionTest.kt | 47 ++++++++++++++++--- 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/src/commonTest/kotlin/org/hisp/dhis/lib/expression/function/ConditionTest.kt b/src/commonTest/kotlin/org/hisp/dhis/lib/expression/function/ConditionTest.kt index df4857c..b247fe1 100644 --- a/src/commonTest/kotlin/org/hisp/dhis/lib/expression/function/ConditionTest.kt +++ b/src/commonTest/kotlin/org/hisp/dhis/lib/expression/function/ConditionTest.kt @@ -2,6 +2,8 @@ package org.hisp.dhis.lib.expression.function import org.hisp.dhis.lib.expression.Expression import org.hisp.dhis.lib.expression.ExpressionMode +import org.hisp.dhis.lib.expression.spi.ValueType +import org.hisp.dhis.lib.expression.spi.VariableValue import kotlin.test.Test import kotlin.test.assertEquals @@ -10,16 +12,49 @@ import kotlin.test.assertEquals * * @author Jan Bernitt */ -class ConditionTest { +class ConditionTest : AbstractVariableBasedTest() { @Test fun testD2_Condition() { - assertEquals(1.0, evaluate("d2:condition('true', 1, 0)")) - assertEquals(true, evaluate("d2:condition('42 < 100', true, false)")) - assertEquals("yes", evaluate("d2:condition('if(true, true, false)', 'yes', 'no')")) + assertCondition(1.0, "d2:condition('true', 1, 0)") + assertCondition(true, "d2:condition('42 < 100', true, false)") + assertCondition("yes", "d2:condition('if(true, true, false)', 'yes', 'no')") + assertCondition("no", "d2:condition('d2:condition(\"true\", (\".\"), (\",\")) == (\",\")', 'yes', 'no')") + assertCondition(1.0,"d2:condition('((1 == 1) && (2 == 2))', 1, 0)") } - private fun evaluate(expression: String): Any? { - return Expression(expression, ExpressionMode.ANDROID_CUSTOM_INTENT_EXPRESSION).evaluate() + @Test + fun expression_with_variables() { + assertCondition("d2:condition('#{xxx1} * 100/ #{xxx2} >= 80', 100, 0)", mapOf( + "xxx1" to VariableValue(ValueType.NUMBER).copy(value = "10"), + "xxx2" to VariableValue(ValueType.NUMBER).copy(value = "13")), 0.0) + assertCondition("d2:condition('#{xxx1} * 100/ #{xxx2} >= 80', 100, 0)", mapOf( + "xxx1" to VariableValue(ValueType.NUMBER).copy(value = "11"), + "xxx2" to VariableValue(ValueType.NUMBER).copy(value = "13")), 100.0) + } + + @Test + fun expression_with_countWithVariables() { + val values = mapOf( + "xxx1" to VariableValue(ValueType.NUMBER).copy(candidates = listOf("1", "2", "1")), + "xxx2" to VariableValue(ValueType.NUMBER).copy(candidates = listOf("1", "2", "3"))) + assertCountWithVariables(values,0, 0, false) + assertCountWithVariables(values,1, 0, true) + assertCountWithVariables(values,2, 0, false) + assertCountWithVariables(values,2, 2, true) + assertCountWithVariables(values,2, 3, true) + assertCountWithVariables(values,3, 3, false) + } + + private fun assertCondition(expected: Any, expression: String) { + assertEquals(expected, Expression(expression, ExpressionMode.ANDROID_CUSTOM_INTENT_EXPRESSION).evaluate()) + } + + private fun assertCondition(expression: String, values: Map, expected: Any) { + assertEquals(expected, evaluate(expression, values)) + } + + private fun assertCountWithVariables(values: Map, a: Number, b: Number, expected: Boolean) { + assertCondition("d2:condition('d2:countIfValue(#{xxx1},\"${a}\") + d2:countIfValue(#{xxx2},\"${b}\") >= 2', true, false)", values, expected) } } \ No newline at end of file From be6449374b787e522617caa68cf0e828fcd8f643 Mon Sep 17 00:00:00 2001 From: Tony Valle Date: Wed, 29 Oct 2025 14:05:43 +0100 Subject: [PATCH 4/5] fix: review comments --- .../hisp/dhis/lib/expression/syntax/ExpressionGrammar.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/commonMain/kotlin/org/hisp/dhis/lib/expression/syntax/ExpressionGrammar.kt b/src/commonMain/kotlin/org/hisp/dhis/lib/expression/syntax/ExpressionGrammar.kt index 043a2a9..a462709 100644 --- a/src/commonMain/kotlin/org/hisp/dhis/lib/expression/syntax/ExpressionGrammar.kt +++ b/src/commonMain/kotlin/org/hisp/dhis/lib/expression/syntax/ExpressionGrammar.kt @@ -21,10 +21,10 @@ import org.hisp.dhis.lib.expression.syntax.Fragment.Companion.constant */ object ExpressionGrammar { - private val STRING_PARSED = Fragment {expr, ctx -> run { - val nestedExpr = Literals.parseString(expr); // consumes the literal in parent expr - Expr.parse(nestedExpr, ctx, false); - }} + private val STRING_PARSED = Fragment {expr, ctx -> + val nestedExpr = Literals.parseString(expr) // consumes the literal in parent expr + Expr.parse(nestedExpr, ctx, false) + } /* Terminals (simple building blocks) From bfccc5e09bc371e04ee6ff41e44ca1f45fb36a7f Mon Sep 17 00:00:00 2001 From: Victor Garcia Date: Thu, 13 Nov 2025 11:09:59 +0100 Subject: [PATCH 5/5] Bump to version 1.2.2 --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index b761bea..687f421 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -9,7 +9,7 @@ repositories { mavenCentral() } -version = "1.2.1-SNAPSHOT" +version = "1.2.2-SNAPSHOT" group = "org.hisp.dhis.lib.expression" if (project.hasProperty("removeSnapshotSuffix")) {