Search in sources :

Example 1 with XPathUnhandledException

use of org.javarosa.xpath.XPathUnhandledException in project javarosa by opendatakit.

the class XPathEvalTest method doTests.

public void doTests() {
    EvaluationContext ec = getFunctionHandlers();
    FormInstance instance = createTestInstance();
    FormInstance countNonEmptyInstance = createCountNonEmptyTestInstance();
    logTestCategory("counting");
    testEval("count(/data/path)", countNonEmptyInstance, null, 5.0);
    testEval("count-non-empty(/data/path)", countNonEmptyInstance, null, 3);
    logTestCategory("unsupporteds");
    testEval("/union | /expr", new XPathUnsupportedException());
    testEval("/descendant::blah", new XPathUnsupportedException());
    testEval("/cant//support", new XPathUnsupportedException());
    testEval("/text()", new XPathUnsupportedException());
    testEval("/namespace:*", new XPathUnsupportedException());
    testEval("(filter-expr)[5]", instance, null, new XPathUnsupportedException());
    testEval("(filter-expr)/data", instance, null, new XPathUnsupportedException());
    logTestCategory("numeric literals");
    testEval("5", 5.0);
    testEval("555555.555", 555555.555);
    testEval(".000555", 0.000555);
    testEval("0", 0.0);
    testEval("-5", -5.0);
    testEval("-0", -0.0);
    testEval("1230000000000000000000", 1.23e21);
    testEval("0.00000000000000000123", 1.23e-18);
    logTestCategory("string literals");
    testEval("''", "");
    testEval("'\"'", "\"");
    testEval("\"test string\"", "test string");
    testEval("'   '", "   ");
    logTestCategory("type conversions");
    testEval("true()", TRUE);
    testEval("false()", FALSE);
    testEval("boolean(true())", TRUE);
    testEval("boolean(false())", FALSE);
    testEval("boolean(1)", TRUE);
    testEval("boolean(-1)", TRUE);
    testEval("boolean(0.0001)", TRUE);
    testEval("boolean(0)", FALSE);
    testEval("boolean(-0)", FALSE);
    testEval("boolean(number('NaN'))", FALSE);
    testEval("boolean(1 div 0)", TRUE);
    testEval("boolean(-1 div 0)", TRUE);
    testEval("boolean('')", FALSE);
    testEval("boolean('asdf')", TRUE);
    testEval("boolean('  ')", TRUE);
    testEval("boolean('false')", TRUE);
    testEval("boolean(date('2000-01-01'))", TRUE);
    testEval("boolean(convertible())", null, ec, TRUE);
    testEval("boolean(inconvertible())", null, ec, new XPathTypeMismatchException());
    testEval("number(true())", 1.0);
    testEval("number(false())", 0.0);
    testEval("number('100')", 100.0);
    testEval("number('100.001')", 100.001);
    testEval("number('.1001')", 0.1001);
    testEval("number('1230000000000000000000')", 1.23e21);
    testEval("number('0.00000000000000000123')", 1.23e-18);
    testEval("number('0')", 0.0);
    testEval("number('-0')", -0.0);
    testEval("number(' -12345.6789  ')", -12345.6789);
    testEval("number('NaN')", NaN);
    testEval("number('not a number')", NaN);
    testEval("number('- 17')", NaN);
    testEval("number('  ')", NaN);
    testEval("number('')", NaN);
    testEval("number('Infinity')", NaN);
    testEval("number('1.1e6')", NaN);
    testEval("number('34.56.7')", NaN);
    testEval("number(10)", 10.0);
    testEval("number(0)", 0.0);
    testEval("number(-0)", -0.0);
    testEval("number(-123.5)", -123.5);
    testEval("number(number('NaN'))", NaN);
    testEval("number(1 div 0)", POSITIVE_INFINITY);
    testEval("number(-1 div 0)", NEGATIVE_INFINITY);
    testEval("number(date('1970-01-01'))", 0.0);
    testEval("number(date('1970-01-02'))", 1.0);
    testEval("number(date('1969-12-31'))", -1.0);
    testEval("number(date('2008-09-05'))", 14127.0);
    testEval("number(date('1941-12-07'))", -10252.0);
    testEval("number(convertible())", null, ec, 5.0);
    testEval("number(inconvertible())", null, ec, new XPathTypeMismatchException());
    testEval("string(true())", "true");
    testEval("string(false())", "false");
    testEval("string(number('NaN'))", "NaN");
    testEval("string(1 div 0)", "Infinity");
    testEval("string(-1 div 0)", "-Infinity");
    testEval("string(0)", "0");
    testEval("string(-0)", "0");
    testEval("string(123456.0000)", "123456");
    testEval("string(-123456)", "-123456");
    testEval("string(1)", "1");
    testEval("string(-1)", "-1");
    testEval("string(.557586)", "0.557586");
    // broken: testEval("string(1230000000000000000000)", "1230000000000000000000");
    // broken: testEval("string(0.00000000000000000123)", "0.00000000000000000123");
    testEval("string('')", "");
    testEval("string('  ')", "  ");
    testEval("string('a string')", "a string");
    testEval("string(date('1989-11-09'))", "1989-11-09");
    testEval("string(convertible())", null, ec, "hi");
    testEval("string(inconvertible())", null, ec, new XPathTypeMismatchException());
    logTestCategory("substring functions");
    testEval("substr('hello',0)", "hello");
    testEval("substr('hello',0,5)", "hello");
    testEval("substr('hello',1)", "ello");
    testEval("substr('hello',1,5)", "ello");
    testEval("substr('hello',1,4)", "ell");
    testEval("substr('hello',-2)", "lo");
    testEval("substr('hello',0,-1)", "hell");
    testEval("contains('a', 'a')", true);
    testEval("contains('a', 'b')", false);
    testEval("contains('abc', 'b')", true);
    testEval("contains('abc', 'bcd')", false);
    testEval("not(contains('a', 'b'))", true);
    testEval("starts-with('abc', 'a')", true);
    testEval("starts-with('', 'a')", false);
    testEval("starts-with('', '')", true);
    testEval("ends-with('abc', 'a')", false);
    testEval("ends-with('abc', 'c')", true);
    testEval("ends-with('', '')", true);
    logTestCategory("date functions");
    testEval("date('2000-01-01')", DateUtils.getDate(2000, 1, 1));
    testEval("date('1945-04-26')", DateUtils.getDate(1945, 4, 26));
    testEval("date('1996-02-29')", DateUtils.getDate(1996, 2, 29));
    testEval("date('1983-09-31')", new XPathTypeMismatchException());
    testEval("date('not a date')", new XPathTypeMismatchException());
    testEval("date(0)", DateUtils.getDate(1970, 1, 1));
    testEval("date(6.5)", DateUtils.getDate(1970, 1, 7));
    testEval("date(1)", DateUtils.getDate(1970, 1, 2));
    testEval("date(-1)", DateUtils.getDate(1969, 12, 31));
    testEval("date(14127)", DateUtils.getDate(2008, 9, 5));
    testEval("date(-10252)", DateUtils.getDate(1941, 12, 7));
    testEval("date(date('1989-11-09'))", DateUtils.getDate(1989, 11, 9));
    testEval("date(true())", new XPathTypeMismatchException());
    testEval("date(convertible())", null, ec, new XPathTypeMismatchException());
    // note: there are lots of time and timezone-like issues with dates that should be tested (particularly DST changes),
    // but it's just too hard and client-dependent, so not doing it now
    // basically:
    // dates cannot reliably be compared/used across time zones (an issue with the code)
    // any time-of-day or DST should be ignored when comparing/using a date (an issue with testing)
    /* other built-in functions */
    testEval("format-date('2018-01-02T10:20:30.123', \"%Y-%m-%e %H:%M:%S\")", "2018-01-2 10:20:30");
    logTestCategory("boolean functions");
    testEval("not(true())", FALSE);
    testEval("not(false())", TRUE);
    testEval("not('')", TRUE);
    testEval("boolean-from-string('true')", TRUE);
    testEval("boolean-from-string('false')", FALSE);
    testEval("boolean-from-string('whatever')", FALSE);
    testEval("boolean-from-string('1')", TRUE);
    testEval("boolean-from-string('0')", FALSE);
    testEval("boolean-from-string(1)", TRUE);
    testEval("boolean-from-string(1.0)", TRUE);
    testEval("boolean-from-string(1.0001)", FALSE);
    testEval("boolean-from-string(true())", TRUE);
    testEval("if(true(), 5, 'abc')", 5.0);
    testEval("if(false(), 5, 'abc')", "abc");
    testEval("if(6 > 7, 5, 'abc')", "abc");
    testEval("if('', 5, 'abc')", "abc");
    testEval("selected('apple baby crimson', 'apple')", TRUE);
    testEval("selected('apple baby crimson', 'baby')", TRUE);
    testEval("selected('apple baby crimson', 'crimson')", TRUE);
    testEval("selected('apple baby crimson', '  baby  ')", TRUE);
    testEval("selected('apple baby crimson', 'babby')", FALSE);
    testEval("selected('apple baby crimson', 'bab')", FALSE);
    testEval("selected('apple', 'apple')", TRUE);
    testEval("selected('apple', 'ovoid')", FALSE);
    testEval("selected('', 'apple')", FALSE);
    logTestCategory("math operators");
    testEval("5.5 + 5.5", 11.0);
    testEval("0 + 0", 0.0);
    testEval("6.1 - 7.8", -1.7);
    testEval("-3 + 4", 1.0);
    testEval("3 + -4", -1.0);
    testEval("1 - 2 - 3", -4.0);
    testEval("1 - (2 - 3)", 2.0);
    testEval("-(8*5)", -40.0);
    testEval("-'19'", -19.0);
    testEval("1.1 * -1.1", -1.21);
    testEval("-10 div -4", 2.5);
    testEval("2 * 3 div 8 * 2", 1.5);
    testEval("3 + 3 * 3", 12.0);
    testEval("1 div 0", POSITIVE_INFINITY);
    testEval("-1 div 0", NEGATIVE_INFINITY);
    testEval("0 div 0", NaN);
    testEval("3.1 mod 3.1", 0.0);
    testEval("5 mod 3.1", 1.9);
    testEval("2 mod 3.1", 2.0);
    testEval("0 mod 3.1", 0.0);
    testEval("5 mod -3", 2.0);
    testEval("-5 mod 3", -2.0);
    testEval("-5 mod -3", -2.0);
    testEval("5 mod 0", NaN);
    testEval("5 * (6 + 7)", 65.0);
    testEval("'123' * '456'", 56088.0);
    logTestCategory("math functions");
    testEval("abs(-3.5)", 3.5);
    // round, with a single argument
    testEval("round('14.29123456789')", 14.0);
    testEval("round('14.6')", 15.0);
    // round, with two arguments
    testEval("round('14.29123456789', 0)", 14.0);
    testEval("round('14.29123456789', 1)", 14.3);
    testEval("round('14.29123456789', 1.5)", 14.3);
    testEval("round('14.29123456789', 2)", 14.29);
    testEval("round('14.29123456789', 3)", 14.291);
    testEval("round('14.29123456789', 4)", 14.2912);
    testEval("round('12345.14',     1)", 12345.1);
    testEval("round('-12345.14',    1)", -12345.1);
    testEval("round('12345.12345',  0)", 12345.0);
    testEval("round('12345.12345', -1)", 12350.0);
    testEval("round('12345.12345', -2)", 12300.0);
    testEval("round('12350.12345', -2)", 12400.0);
    testEval("round('12345.12345', -3)", 12000.0);
    // round, with a comma instead of a decimal point
    testEval("round('4,6'            )", 5.0);
    // XPath specification tests
    testEval("round('1 div 0', 0)", NaN);
    testEval("round('14.5')", 15.0);
    testEval("round('NaN')", NaN);
    testEval("round('-NaN')", -NaN);
    testEval("round('0')", 0.0);
    testEval("round('-0')", -0.0);
    testEval("round('-0.5')", -0.0);
    // non US format
    testEval("round('14,6')", 15.0);
    // Java 8 tests deprecated by XPath 3.0 specification
    // See discussion at https://github.com/opendatakit/javarosa/pull/42#issuecomment-299527754
    testEval("round('12345.15',     1)", 12345.2);
    testEval("round('-12345.15',    1)", -12345.1);
    logTestCategory("strange operators");
    testEval("true() + 8", 9.0);
    testEval("date('2008-09-08') - date('1983-10-06')", 9104.0);
    testEval("true() and true()", TRUE);
    testEval("true() and false()", FALSE);
    testEval("false() and false()", FALSE);
    testEval("true() or true()", TRUE);
    testEval("true() or false()", TRUE);
    testEval("false() or false()", FALSE);
    testEval("true() or true() and false()", TRUE);
    testEval("(true() or true()) and false()", FALSE);
    // short-circuiting
    testEval("true() or date('')", TRUE);
    // short-circuiting
    testEval("false() and date('')", FALSE);
    testEval("'' or 17", TRUE);
    testEval("false() or 0 + 2", TRUE);
    testEval("(false() or 0) + 2", 2.0);
    testEval("4 < 5", TRUE);
    testEval("5 < 5", FALSE);
    testEval("6 < 5", FALSE);
    testEval("4 <= 5", TRUE);
    testEval("5 <= 5", TRUE);
    testEval("6 <= 5", FALSE);
    testEval("4 > 5", FALSE);
    testEval("5 > 5", FALSE);
    testEval("6 > 5", TRUE);
    testEval("4 >= 5", FALSE);
    testEval("5 >= 5", TRUE);
    testEval("6 >= 5", TRUE);
    testEval("-3 > -6", TRUE);
    logTestCategory("odd comparisons");
    testEval("true() > 0.9999", TRUE);
    // no string comparison: converted to number
    testEval("'-17' > '-172'", TRUE);
    // no string comparison: converted to NaN
    testEval("'abc' < 'abcd'", FALSE);
    testEval("date('2001-12-26') > date('2001-12-25')", TRUE);
    testEval("date('1969-07-20') < date('1969-07-21')", TRUE);
    testEval("false() and false() < true()", FALSE);
    testEval("(false() and false()) < true()", TRUE);
    testEval("6 < 7 - 4", FALSE);
    testEval("(6 < 7) - 4", -3.0);
    testEval("3 < 4 < 5", TRUE);
    testEval("3 < (4 < 5)", FALSE);
    testEval("true() = true()", TRUE);
    testEval("true() = false()", FALSE);
    testEval("true() != true()", FALSE);
    testEval("true() != false()", TRUE);
    testEval("3 = 3", TRUE);
    testEval("3 = 4", FALSE);
    testEval("3 != 3", FALSE);
    testEval("3 != 4", TRUE);
    // handle floating point rounding
    testEval("6.1 - 7.8 = -1.7", TRUE);
    testEval("'abc' = 'abc'", TRUE);
    testEval("'abc' = 'def'", FALSE);
    testEval("'abc' != 'abc'", FALSE);
    testEval("'abc' != 'def'", TRUE);
    testEval("'' = ''", TRUE);
    testEval("true() = 17", TRUE);
    testEval("0 = false()", TRUE);
    testEval("true() = 'true'", TRUE);
    testEval("17 = '17.0000000'", TRUE);
    testEval("'0017.' = 17", TRUE);
    testEval("'017.' = '17.000'", FALSE);
    testEval("date('2004-05-01') = date('2004-05-01')", TRUE);
    testEval("true() != date('1999-09-09')", FALSE);
    testEval("false() and true() != true()", FALSE);
    testEval("(false() and true()) != true()", TRUE);
    testEval("-3 < 3 = 6 >= 6", TRUE);
    logTestCategory("functions, including custom function handlers");
    testEval("true(5)", new XPathUnhandledException());
    testEval("number()", new XPathUnhandledException());
    testEval("string('too', 'many', 'args')", new XPathUnhandledException());
    testEval("not-a-function()", new XPathUnhandledException());
    testEval("testfunc()", null, ec, TRUE);
    testEval("add(3, 5)", null, ec, 8.0);
    testEval("add('17', '-14')", null, ec, 3.0);
    logTestCategory("proto");
    testEval("proto()", null, ec, new XPathTypeMismatchException());
    testEval("proto(5, 5)", null, ec, "[Double:5.0,Double:5.0]");
    testEval("proto(6)", null, ec, "[Double:6.0]");
    testEval("proto('asdf')", null, ec, "[Double:NaN]");
    // note: args treated as doubles because
    testEval("proto('7', '7')", null, ec, "[Double:7.0,Double:7.0]");
    // (double, double) prototype takes precedence and strings are convertible to doubles
    testEval("proto(1.1, 'asdf', true())", null, ec, "[Double:1.1,String:asdf,Boolean:true]");
    testEval("proto(false(), false(), false())", null, ec, "[Double:0.0,String:false,Boolean:false]");
    testEval("proto(1.1, 'asdf', inconvertible())", null, ec, new XPathTypeMismatchException());
    testEval("proto(1.1, 'asdf', true(), 16)", null, ec, new XPathTypeMismatchException());
    logTestCategory("raw");
    testEval("raw()", null, ec, "[]");
    testEval("raw(5, 5)", null, ec, "[Double:5.0,Double:5.0]");
    testEval("raw('7', '7')", null, ec, "[String:7,String:7]");
    // convertible to prototype
    testEval("raw('1.1', 'asdf', 17)", null, ec, "[Double:1.1,String:asdf,Boolean:true]");
    testEval("raw(get-custom(false()), get-custom(true()))", null, ec, "[CustomType:,CustomSubType:]");
    logTestCategory("concat");
    testEval("concat()", null, ec, "");
    testEval("concat('a')", null, ec, "a");
    testEval("concat('a','b','')", null, ec, "ab");
    testEval("concat('ab','cde','','fgh',1,false(),'ijklmnop')", null, ec, "abcdefgh1falseijklmnop");
    testEval("check-types(55, '55', false(), '1999-09-09', get-custom(false()))", null, ec, TRUE);
    testEval("check-types(55, '55', false(), '1999-09-09', get-custom(true()))", null, ec, TRUE);
    logTestCategory("regex");
    testEval("regex('12345','[0-9]+')", null, ec, TRUE);
    testEval("pow(2, 2)", 4.0);
    testEval("pow(2, 0)", 1.0);
    testEval("pow(0, 4)", 0.0);
    testEval("pow(2.5, 2)", 6.25);
    testEval("pow(0.5, 2)", .25);
    testEval("pow(-1, 2)", 1.0);
    testEval("pow(-1, 3)", -1.0);
    // So raising things to decimal powers is.... very hard
    // to evaluated exactly due to double floating point
    // precision. We'll try for things with clean answers
    testEval("pow(4, 0.5)", 2.0);
    testEval("pow(16, 0.25)", 2.0);
    logTestCategory("variable refs");
    EvaluationContext varContext = getVariableContext();
    testEval("$var_float_five", null, varContext, 5.0);
    testEval("$var_string_five", null, varContext, "five");
    testEval("$var_int_five", null, varContext, 5.0);
    testEval("$var_double_five", null, varContext, 5.0);
    logTestCategory("node referencing");
    // happy flow scenario where the index node is not blank
    FormInstance testInstance = createTestDataForIndexedRepeatFunction(1);
    XPathNodeset expected = createExpectedNodesetFromIndexedRepeatFunction(testInstance, 1, "name");
    testEval("indexed-repeat( /data/repeat/name , /data/repeat , /data/index1 )", testInstance, null, expected);
    // situation where the referenced index node is blank and the default value (0 which means the first repeat group) is used
    testInstance = createTestDataForIndexedRepeatFunction(null);
    expected = createExpectedNodesetFromIndexedRepeatFunction(testInstance, 0, "name");
    testEval("indexed-repeat( /data/repeat/name , /data/repeat , /data/index1 )", testInstance, null, expected);
    logTestCategory("crypto functions");
    // Support for all 5 supported digest algorithms (required and optional) and default base64 encoding
    testEval("digest('some text', 'MD5', 'base64')", "VS4hzUzZkYZ448Gg30kbww==");
    testEval("digest('some text', 'SHA-1', 'base64')", "N6pjx3OY2VRHMmLhoAV8HmMu2nc=");
    testEval("digest('some text', 'SHA-256', 'base64')", "uU9vElx546X/qoJvWEwQ1SraZp5nYgUbgmtVd20FrtI=");
    testEval("digest('some text', 'SHA-384', 'base64')", "zJTsPphzwLmnJIZEKVj2cQZ833e5QnQW0DFEDMYgQeLuE0RJhEfsDO2fcENGG9Hz");
    testEval("digest('some text', 'SHA-512', 'base64')", "4nMrrtyj6sFAeChjfeHbynAsP8ns4Wz1Nt241hOc2F3+dGS4I1spgm9gjM9KxkPimxnGN4WKPYcQpZER30LdtQ==");
    // Support for hexadecimal encoding
    testEval("digest('some text', 'MD5', 'hex')", "552e21cd4cd9918678e3c1a0df491bc3");
    // Support for optional third argument (defaults to 'base64')
    testEval("digest('some text', 'MD5')", "VS4hzUzZkYZ448Gg30kbww==");
    try {
        testEval("null-proto()", null, ec, new XPathUnhandledException());
        fail("Did not get expected null pointer");
    } catch (NullPointerException npe) {
    // expected
    }
    ec.addFunctionHandler(read);
    ec.addFunctionHandler(write);
    read.val = "testing-read";
    testEval("read()", null, ec, "testing-read");
    testEval("write('testing-write')", null, ec, TRUE);
    if (!"testing-write".equals(write.val))
        fail("Custom function handler did not successfully send data to external source");
}
Also used : XPathNodeset(org.javarosa.xpath.XPathNodeset) EvaluationContext(org.javarosa.core.model.condition.EvaluationContext) XPathTypeMismatchException(org.javarosa.xpath.XPathTypeMismatchException) FormInstance(org.javarosa.core.model.instance.FormInstance) XPathUnhandledException(org.javarosa.xpath.XPathUnhandledException) XPathUnsupportedException(org.javarosa.xpath.XPathUnsupportedException)

Example 2 with XPathUnhandledException

use of org.javarosa.xpath.XPathUnhandledException in project javarosa by opendatakit.

the class XPathFuncExpr method eval.

/**
 * Evaluate the function call.
 *
 * First check if the function is a member of the built-in function suite. If not, then check
 * for any custom handlers registered to handler the function. If not, throw and exception.
 *
 * Both function name and appropriate arguments are taken into account when finding a suitable
 * handler. For built-in functions, the number of arguments must match; for custom functions,
 * the supplied arguments must match one of the function prototypes defined by the handler.
 */
public Object eval(DataInstance model, EvaluationContext evalContext) {
    String name = id.toString();
    Object[] argVals = new Object[args.length];
    HashMap<String, IFunctionHandler> funcHandlers = evalContext.getFunctionHandlers();
    // TODO: Func handlers should be able to declare the desire for short circuiting as well
    if (name.equals("if")) {
        assertArgsCount(name, args, 3);
        return ifThenElse(model, evalContext, args, argVals);
    } else if (name.equals("coalesce")) {
        assertArgsCount(name, args, 2);
        argVals[0] = args[0].eval(model, evalContext);
        if (!isNull(argVals[0])) {
            return argVals[0];
        } else {
            // that was null, so try the other one...
            argVals[1] = args[1].eval(model, evalContext);
            return argVals[1];
        }
    } else if (name.equals("indexed-repeat")) {
        if ((args.length == 3 || args.length == 5 || args.length == 7 || args.length == 9 || args.length == 11)) {
            return indexedRepeat(model, evalContext, args, argVals);
        } else {
            throw new XPathUnhandledException("function \'" + name + "\' requires " + "3, 5, 7, 9 or 11 arguments. Only " + args.length + " provided.");
        }
    }
    for (int i = 0; i < args.length; i++) {
        argVals[i] = args[i].eval(model, evalContext);
    }
    // check built-in functions
    if (name.equals("true")) {
        assertArgsCount(name, args, 0);
        return Boolean.TRUE;
    } else if (name.equals("false")) {
        assertArgsCount(name, args, 0);
        return Boolean.FALSE;
    } else if (name.equals("boolean")) {
        assertArgsCount(name, args, 1);
        return toBoolean(argVals[0]);
    } else if (name.equals("number")) {
        assertArgsCount(name, args, 1);
        return toNumeric(argVals[0]);
    } else if (name.equals("int")) {
        // non-standard
        assertArgsCount(name, args, 1);
        return toInt(argVals[0]);
    } else if (name.equals("round")) {
        // Proximate XPath 3.0 and Excel-style round(value,decimal place)
        final int places;
        if (args.length == 1) {
            places = 0;
        } else {
            assertArgsCount(name, args, 2);
            places = toNumeric(argVals[1]).intValue();
        }
        return round(toNumeric(argVals[0]), places);
    } else if (name.equals("string")) {
        assertArgsCount(name, args, 1);
        return toString(argVals[0]);
    } else if (name.equals("date")) {
        // non-standard
        assertArgsCount(name, args, 1);
        return toDate(argVals[0], false);
    } else if (name.equals("date-time")) {
        // non-standard -- convert double/int/string to Date object
        assertArgsCount(name, args, 1);
        return toDate(argVals[0], true);
    } else if (name.equals("decimal-date-time")) {
        // non-standard -- convert string/date to decimal days off 1970-01-01T00:00:00.000-000
        assertArgsCount(name, args, 1);
        return toDecimalDateTime(argVals[0], true);
    } else if (name.equals("decimal-time")) {
        // non-standard -- convert string/date to decimal days off 1970-01-01T00:00:00.000-000
        assertArgsCount(name, args, 1);
        return toDecimalDateTime(argVals[0], false);
    } else if (name.equals("not")) {
        assertArgsCount(name, args, 1);
        return boolNot(argVals[0]);
    } else if (name.equals("boolean-from-string")) {
        assertArgsCount(name, args, 1);
        return boolStr(argVals[0]);
    } else if (name.equals("format-date")) {
        assertArgsCount(name, args, 2);
        return formatDateTime(argVals[0], argVals[1]);
    } else if (name.equals("abs")) {
        // XPath 3.0
        checkArity(name, 1, args.length);
        return Math.abs(toDouble(argVals[0]));
    } else if (name.equals("acos")) {
        // XPath 3.0
        checkArity(name, 1, args.length);
        return Math.acos(toDouble(argVals[0]));
    } else if (name.equals("asin")) {
        // XPath 3.0
        checkArity(name, 1, args.length);
        return Math.asin(toDouble(argVals[0]));
    } else if (name.equals("atan")) {
        // XPath 3.0
        checkArity(name, 1, args.length);
        return Math.atan(toDouble(argVals[0]));
    } else if (name.equals("atan2")) {
        // XPath 3.0
        checkArity(name, 2, args.length);
        return Math.atan2(toDouble(argVals[0]), toDouble(argVals[1]));
    } else if (name.equals("cos")) {
        // XPath 3.0
        checkArity(name, 1, args.length);
        return Math.cos(toDouble(argVals[0]));
    } else if (name.equals("exp")) {
        // XPath 3.0
        checkArity(name, 1, args.length);
        return Math.exp(toDouble(argVals[0]));
    } else if (name.equals("exp10")) {
        // XPath 3.0
        checkArity(name, 1, args.length);
        return Math.pow(10.0, toDouble(argVals[0]));
    } else if (name.equals("log")) {
        // XPath 3.0
        checkArity(name, 1, args.length);
        return Math.log(toDouble(argVals[0]));
    } else if (name.equals("log10")) {
        // XPath 3.0
        checkArity(name, 1, args.length);
        return Math.log10(toDouble(argVals[0]));
    } else if (name.equals("pi")) {
        // XPath 3.0
        checkArity(name, 0, args.length);
        return Math.PI;
    } else if (name.equals("sin")) {
        // XPath 3.0
        checkArity(name, 1, args.length);
        return Math.sin(toDouble(argVals[0]));
    } else if (name.equals("sqrt")) {
        // XPath 3.0
        checkArity(name, 1, args.length);
        return Math.sqrt(toDouble(argVals[0]));
    } else if (name.equals("tan")) {
        // XPath 3.0
        checkArity(name, 1, args.length);
        return Math.tan(toDouble(argVals[0]));
    } else if (name.equals("format-date-time")) {
        // non-standard
        assertArgsCount(name, args, 2);
        return formatDateTime(argVals[0], argVals[1]);
    } else if ((name.equals("selected") || name.equals("is-selected"))) {
        // non-standard
        assertArgsCount(name, args, 2);
        return multiSelected(argVals[0], argVals[1], name);
    } else if (name.equals("count-selected")) {
        // non-standard
        assertArgsCount(name, args, 1);
        return countSelected(argVals[0]);
    } else if (name.equals("selected-at")) {
        // non-standard
        assertArgsCount(name, args, 2);
        return selectedAt(argVals[0], argVals[1]);
    } else if (name.equals("position")) {
        // TODO: Technically, only the 0 length argument is valid here.
        if (args.length == 1) {
            XPathNodeset nodes = (XPathNodeset) argVals[0];
            if (nodes.size() == 0) {
                // Will likely cause an error downstream when used in an XPath.
                return (double) (1 + TreeReference.INDEX_UNBOUND);
            } else {
                // if or how this might manifest into a bug... .
                return position(nodes.getRefAt(0));
            }
        } else if (args.length == 0) {
            if (evalContext.getContextPosition() != -1) {
                return (double) (1 + evalContext.getContextPosition());
            }
            return position(evalContext.getContextRef());
        } else {
            throw new XPathUnhandledException("function \'" + name + "\' requires either exactly one argument or no arguments. Only " + args.length + " provided.");
        }
    } else if (name.equals("count")) {
        assertArgsCount(name, args, 1);
        return count(argVals[0]);
    } else if (name.equals("count-non-empty")) {
        assertArgsCount(name, args, 1);
        return countNonEmpty(argVals[0]);
    } else if (name.equals("sum")) {
        assertArgsCount(name, args, 1);
        if (argVals[0] instanceof XPathNodeset) {
            return sum(((XPathNodeset) argVals[0]).toArgList());
        } else {
            throw new XPathTypeMismatchException("not a nodeset");
        }
    } else if (name.equals("max")) {
        if (args.length == 1 && argVals[0] instanceof XPathNodeset) {
            return max(((XPathNodeset) argVals[0]).toArgList());
        } else {
            return max(argVals);
        }
    } else if (name.equals("min")) {
        if (args.length == 1 && argVals[0] instanceof XPathNodeset) {
            return min(((XPathNodeset) argVals[0]).toArgList());
        } else {
            return min(argVals);
        }
    } else if (name.equals("today")) {
        assertArgsCount(name, args, 0);
        return DateUtils.roundDate(new Date());
    } else if (name.equals("now")) {
        assertArgsCount(name, args, 0);
        return new Date();
    } else if (name.equals("concat")) {
        if (args.length == 1 && argVals[0] instanceof XPathNodeset) {
            return join("", ((XPathNodeset) argVals[0]).toArgList());
        } else {
            return join("", argVals);
        }
    } else if (name.equals("join") && args.length >= 1) {
        if (args.length == 2 && argVals[1] instanceof XPathNodeset) {
            return join(argVals[0], ((XPathNodeset) argVals[1]).toArgList());
        } else {
            return join(argVals[0], subsetArgList(argVals, 1));
        }
    } else if (name.equals("substr") && (args.length == 2 || args.length == 3)) {
        return substring(argVals[0], argVals[1], args.length == 3 ? argVals[2] : null);
    } else if (name.equals("contains") && args.length == 2) {
        return toString(argVals[0]).contains(toString(argVals[1]));
    } else if (name.equals("starts-with") && args.length == 2) {
        return toString(argVals[0]).startsWith(toString(argVals[1]));
    } else if (name.equals("ends-with") && args.length == 2) {
        return toString(argVals[0]).endsWith(toString(argVals[1]));
    } else if (name.equals("string-length")) {
        assertArgsCount(name, args, 1);
        return stringLength(argVals[0]);
    } else if (name.equals("checklist") && args.length >= 2) {
        // non-standard
        if (args.length == 3 && argVals[2] instanceof XPathNodeset) {
            return checklist(argVals[0], argVals[1], ((XPathNodeset) argVals[2]).toArgList());
        } else {
            return checklist(argVals[0], argVals[1], subsetArgList(argVals, 2));
        }
    } else if (name.equals("weighted-checklist") && args.length >= 2 && args.length % 2 == 0) {
        // non-standard
        if (args.length == 4 && argVals[2] instanceof XPathNodeset && argVals[3] instanceof XPathNodeset) {
            Object[] factors = ((XPathNodeset) argVals[2]).toArgList();
            Object[] weights = ((XPathNodeset) argVals[3]).toArgList();
            if (factors.length != weights.length) {
                throw new XPathTypeMismatchException("weighted-checklist: nodesets not same length");
            }
            return checklistWeighted(argVals[0], argVals[1], factors, weights);
        } else {
            return checklistWeighted(argVals[0], argVals[1], subsetArgList(argVals, 2, 2), subsetArgList(argVals, 3, 2));
        }
    } else if (name.equals("regex")) {
        // non-standard
        assertArgsCount(name, args, 2);
        return regex(argVals[0], argVals[1]);
    } else if (name.equals("depend") && args.length >= 1) {
        // non-standard
        return argVals[0];
    } else if (name.equals("random")) {
        // non-standard
        assertArgsCount(name, args, 0);
        // calculated expressions may be recomputed w/o warning! use with caution!!
        return MathUtils.getRand().nextDouble();
    } else if (name.equals("once")) {
        assertArgsCount(name, args, 1);
        XPathPathExpr currentFieldPathExpr = XPathPathExpr.fromRef(evalContext.getContextRef());
        Object currValue = currentFieldPathExpr.eval(model, evalContext).unpack();
        if (currValue == null || toString(currValue).length() == 0) {
            // this is the "once" case
            return argVals[0];
        } else {
            return currValue;
        }
    } else if (name.equals("uuid") && (args.length == 0 || args.length == 1)) {
        // calculated expressions may be recomputed w/o warning! use with caution!!
        if (args.length == 0) {
            return PropertyUtils.genUUID();
        }
        int len = toInt(argVals[0]).intValue();
        return PropertyUtils.genGUID(len);
    } else if (name.equals("version")) {
        // non-standard
        assertArgsCount(name, args, 0);
        final String formVersion = (model instanceof FormInstance) ? ((FormInstance) model).formVersion : "";
        return formVersion == null ? "" : formVersion;
    } else if (name.equals("property")) {
        // non-standard
        // return a property defined by the property manager.
        // NOTE: Property should be immutable.
        // i.e., does not work with 'start' or 'end' property.
        assertArgsCount(name, args, 1);
        String s = toString(argVals[0]);
        return PropertyManager._().getSingularProperty(s);
    } else if (name.equals("pow") && (args.length == 2)) {
        // XPath 3.0
        double a = toDouble(argVals[0]);
        double b = toDouble(argVals[1]);
        return Math.pow(a, b);
    } else if (name.equals("enclosed-area") || name.equals("area")) {
        assertArgsCount(name, args, 1);
        Object argVal = argVals[0];
        if (!(argVal instanceof XPathNodeset)) {
            throw new XPathUnhandledException("function \'" + name + "\' requires a field as the parameter.");
        }
        Object[] argList = ((XPathNodeset) argVal).toArgList();
        int repeatSize = argList.length;
        List<GeoUtils.GPSCoordinates> gpsCoordinatesList;
        if (repeatSize == 1) {
            // Try to determine if the argument is of type GeoShapeData
            try {
                GeoShapeData geoShapeData = new GeoShapeData().cast(new UncastData(toString(argList[0])));
                if (geoShapeData.points.size() <= 2) {
                    return 0d;
                } else {
                    gpsCoordinatesList = new ArrayList<GeoUtils.GPSCoordinates>();
                    for (GeoPointData point : geoShapeData.points) {
                        gpsCoordinatesList.add(new GeoUtils.GPSCoordinates(point.getPart(0), point.getPart(1)));
                    }
                }
            } catch (Exception e) {
                throw new XPathTypeMismatchException("The function \'" + name + "\' received a value that does not represent GPS coordinates: " + argList[0]);
            }
        } else {
            if (repeatSize <= 2) {
                return 0d;
            } else {
                // treat the input as a series of GeoPointData
                gpsCoordinatesList = new ArrayList<GeoUtils.GPSCoordinates>();
                for (Object arg : argList) {
                    try {
                        GeoPointData geoPointData = new GeoPointData().cast(new UncastData(toString(arg)));
                        gpsCoordinatesList.add(new GeoUtils.GPSCoordinates(geoPointData.getPart(0), geoPointData.getPart(1)));
                    } catch (Exception e) {
                        throw new XPathTypeMismatchException("The function \'" + name + "\' received a value that does not represent GPS coordinates: " + arg);
                    }
                }
            }
        }
        return GeoUtils.calculateAreaOfGPSPolygonOnEarthInSquareMeters(gpsCoordinatesList);
    } else if (name.equals("digest") && (args.length == 2 || args.length == 3)) {
        return DigestAlgorithm.from((String) argVals[1]).digest((String) argVals[0], args.length == 3 ? Encoding.from((String) argVals[2]) : Encoding.BASE64);
    } else {
        // check for custom handler
        IFunctionHandler handler = funcHandlers.get(name);
        if (handler != null) {
            return evalCustomFunction(handler, argVals, evalContext);
        } else {
            throw new XPathUnhandledException("function \'" + name + "\'");
        }
    }
}
Also used : ArrayList(java.util.ArrayList) GeoPointData(org.javarosa.core.model.data.GeoPointData) IFunctionHandler(org.javarosa.core.model.condition.IFunctionHandler) XPathUnhandledException(org.javarosa.xpath.XPathUnhandledException) Date(java.util.Date) XPathUnhandledException(org.javarosa.xpath.XPathUnhandledException) UnpivotableExpressionException(org.javarosa.core.model.condition.pivot.UnpivotableExpressionException) IOException(java.io.IOException) XPathTypeMismatchException(org.javarosa.xpath.XPathTypeMismatchException) XPathArityException(org.javarosa.xpath.XPathArityException) DeserializationException(org.javarosa.core.util.externalizable.DeserializationException) GeoUtils(org.javarosa.core.util.GeoUtils) GeoShapeData(org.javarosa.core.model.data.GeoShapeData) UncastData(org.javarosa.core.model.data.UncastData) XPathNodeset(org.javarosa.xpath.XPathNodeset) ArrayList(java.util.ArrayList) List(java.util.List) XPathTypeMismatchException(org.javarosa.xpath.XPathTypeMismatchException) FormInstance(org.javarosa.core.model.instance.FormInstance)

Aggregations

FormInstance (org.javarosa.core.model.instance.FormInstance)2 XPathNodeset (org.javarosa.xpath.XPathNodeset)2 XPathTypeMismatchException (org.javarosa.xpath.XPathTypeMismatchException)2 XPathUnhandledException (org.javarosa.xpath.XPathUnhandledException)2 IOException (java.io.IOException)1 ArrayList (java.util.ArrayList)1 Date (java.util.Date)1 List (java.util.List)1 EvaluationContext (org.javarosa.core.model.condition.EvaluationContext)1 IFunctionHandler (org.javarosa.core.model.condition.IFunctionHandler)1 UnpivotableExpressionException (org.javarosa.core.model.condition.pivot.UnpivotableExpressionException)1 GeoPointData (org.javarosa.core.model.data.GeoPointData)1 GeoShapeData (org.javarosa.core.model.data.GeoShapeData)1 UncastData (org.javarosa.core.model.data.UncastData)1 GeoUtils (org.javarosa.core.util.GeoUtils)1 DeserializationException (org.javarosa.core.util.externalizable.DeserializationException)1 XPathArityException (org.javarosa.xpath.XPathArityException)1 XPathUnsupportedException (org.javarosa.xpath.XPathUnsupportedException)1