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")) { 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..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,6 +21,11 @@ import org.hisp.dhis.lib.expression.syntax.Fragment.Companion.constant */ object ExpressionGrammar { + 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) */ @@ -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..b247fe1 --- /dev/null +++ b/src/commonTest/kotlin/org/hisp/dhis/lib/expression/function/ConditionTest.kt @@ -0,0 +1,60 @@ +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 + +/** + * Tests the `d2:condition` function + * + * @author Jan Bernitt + */ +class ConditionTest : AbstractVariableBasedTest() { + + @Test + fun testD2_Condition() { + 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)") + } + + @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