diff --git a/src/main/java/org/javarosa/xpath/expr/XPathFuncExpr.java b/src/main/java/org/javarosa/xpath/expr/XPathFuncExpr.java index d3edd19d2..fdb5e62c0 100644 --- a/src/main/java/org/javarosa/xpath/expr/XPathFuncExpr.java +++ b/src/main/java/org/javarosa/xpath/expr/XPathFuncExpr.java @@ -970,15 +970,15 @@ public static Object ifThenElse(DataInstance model, EvaluationContext ec, XPathE } /** - * This provides a method of indexing fields stored in prior repeat groups. + * Provides a method of indexing fields stored in a repeat without writing an XPath filter expression. *

- * args[0] = generic XPath expression to index - * args[1] = generic XPath expression for group to index - * args[2] = index number for group - * args[3] = generic XPath expression for add'l group to index (if 5 or 7 parameters passed) - * args[4] = index number for group (if 5 or 7 parameters passed) - * args[5] = generic XPath expression for add'l group to index (if 7 parameters passed) - * args[6] = index number for group (if 7 parameters passed) + * args[0] = reference to a field inside one or more repeats + * args[1] = generic XPath expression for repeat to index + * args[2] = index number for repeat instance + * args[3] = generic XPath expression for add'l repeat to index (if 5 or 7 parameters passed) + * args[4] = index number for repeat (if 5 or 7 parameters passed) + * args[5] = generic XPath expression for add'l repeat to index (if 7 parameters passed) + * args[6] = index number for repeat (if 7 parameters passed) */ public static Object indexedRepeat(DataInstance model, EvaluationContext ec, XPathExpression[] args, Object[] argVals) throws XPathTypeMismatchException { // initialize target and context references @@ -994,7 +994,7 @@ public static Object indexedRepeat(DataInstance model, EvaluationContext ec, XPa for (int pathargi = 1, idxargi = 2; idxargi < args.length; pathargi += 2, idxargi += 2) { // confirm that we were passed an XPath if (!(args[pathargi] instanceof XPathPathExpr)) { - throw new XPathTypeMismatchException("indexed-repeat(): parameter " + (pathargi + 1) + " must be XPath repeat-group reference"); + throw new XPathTypeMismatchException("indexed-repeat(): parameter " + (pathargi + 1) + " must be a reference to a repeat"); } // confirm that the passed XPath is a parent of our overall target path // Ensure we can deal with relative group refs by contextualizing with the EC's context ref diff --git a/src/test/java/org/javarosa/xpath/expr/IndexedRepeatTest.java b/src/test/java/org/javarosa/xpath/expr/IndexedRepeatTest.java new file mode 100644 index 000000000..fc5fd9f74 --- /dev/null +++ b/src/test/java/org/javarosa/xpath/expr/IndexedRepeatTest.java @@ -0,0 +1,135 @@ +package org.javarosa.xpath.expr; + +import org.javarosa.test.Scenario; +import org.javarosa.xpath.XPathTypeMismatchException; +import org.junit.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.instanceOf; +import static org.javarosa.core.test.AnswerDataMatchers.stringAnswer; +import static org.javarosa.test.BindBuilderXFormsElement.bind; +import static org.javarosa.test.XFormsElement.body; +import static org.javarosa.test.XFormsElement.group; +import static org.javarosa.test.XFormsElement.head; +import static org.javarosa.test.XFormsElement.html; +import static org.javarosa.test.XFormsElement.input; +import static org.javarosa.test.XFormsElement.mainInstance; +import static org.javarosa.test.XFormsElement.model; +import static org.javarosa.test.XFormsElement.repeat; +import static org.javarosa.test.XFormsElement.t; +import static org.javarosa.test.XFormsElement.title; +import static org.junit.Assert.fail; + +public class IndexedRepeatTest { + @Test + public void firstArgNotChildOfRepeat_throwsException() throws Exception { + try { + Scenario.init("indexed-repeat", html( + head( + title("indexed-repeat"), + model( + mainInstance(t("data id=\"indexed-repeat\"", + t("outside"), + t("repeat", + t("inside")), + t("calc") + )), + bind("/data/calc").calculate("indexed-repeat(/data/outside, /data/repeat, 1)") + ) + ), + body( + input("/data/outside"), + repeat("/data/repeat", + input("/data/repeat/inside")) + )) + ); + + fail("RuntimeException caused by XPathTypeMismatchException expected"); + } catch (RuntimeException e) { + assertThat(e.getCause(), instanceOf(XPathTypeMismatchException.class)); + } + } + + @Test + public void getsIndexedValueInSingleRepeat() throws Exception { + Scenario scenario = Scenario.init("indexed-repeat", html( + head( + title("indexed-repeat"), + model( + mainInstance(t("data id=\"indexed-repeat\"", + t("index"), + t("outer_group", // included to clarify intended evaluation context for index references + t("repeat", + t("inside"))), + t("calc") + )), + bind("/data/calc").calculate("indexed-repeat(/data/outer_group/repeat/inside, /data/outer_group/repeat, ../index)") + ) + ), + body( + input("/data/index"), + group("/data/outer_group", + repeat("/data/outer_group/repeat", + input("/data/outer_group/repeat/inside"))) + )) + ); + + scenario.createNewRepeat("/data/outer_group[1]/repeat"); + scenario.answer("/data/outer_group[1]/repeat[1]/inside", "index1"); + + scenario.createNewRepeat("/data/outer_group[1]/repeat"); + scenario.answer("/data/outer_group[1]/repeat[2]/inside", "index2"); + + scenario.createNewRepeat("/data/outer_group[1]/repeat"); + scenario.answer("/data/outer_group[1]/repeat[3]/inside", "index3"); + + scenario.answer("/data/index", "2"); + assertThat(scenario.answerOf("/data/calc"), is(stringAnswer("index2"))); + + scenario.answer("/data/index", "1"); + assertThat(scenario.answerOf("/data/calc"), is(stringAnswer("index1"))); + } + + @Test + public void getsIndexedValueUsingParallelRepeatPosition() throws Exception { + Scenario scenario = Scenario.init("indexed-repeat", html( + head( + title("indexed-repeat"), + model( + mainInstance(t("data id=\"indexed-repeat\"", + t("repeat1", + t("inside1")), + + t("repeat2", + t("inside2"), + t("from_repeat1")) + )), + bind("/data/repeat2/from_repeat1").calculate("indexed-repeat(/data/repeat1/inside1, /data/repeat1, position(..))") + ) + ), + body( + repeat("/data/repeat1", + input("/data/repeat1/inside1")), + + repeat("/data/repeat2", + input("/data/repeat2/inside2")) + )) + ); + + scenario.createNewRepeat("/data/repeat1"); + scenario.createNewRepeat("/data/repeat2"); + scenario.answer("/data/repeat1[1]/inside1", "index1"); + + scenario.createNewRepeat("/data/repeat1"); + scenario.createNewRepeat("/data/repeat2"); + scenario.answer("/data/repeat1[2]/inside1", "index2"); + + scenario.createNewRepeat("/data/repeat1"); + scenario.createNewRepeat("/data/repeat2"); + scenario.answer("/data/repeat1[3]/inside1", "index3"); + + assertThat(scenario.answerOf("/data/repeat2[1]/from_repeat1"), is(stringAnswer("index1"))); + assertThat(scenario.answerOf("/data/repeat2[2]/from_repeat1"), is(stringAnswer("index2"))); + } +}