6

The toComplie string contains all the definitions of the functions like sum, multiply, etc. appended by if ($a > 0) then (iaf:numeric-equal(iaf:numeric-multiply($b, $c), $d)) else (true())

The snippet executing this is :

XQueryExecutable queryExecutable = xqueryCompiler.compile(toCompile.toString());
XQueryEvaluator xqueryEvaluator = queryExecutable.load();

//setExternalVariables(): function used to set the variables for the test contains below line
        xqueryEvaluator.setExternalVariable(new QName(memberName), value);
setExternalVariables(xqueryEvaluator,assertionExpression);

xqueryResult = xqueryEvaluator.evaluate();

Which throws an exception as below:

XPTY0004: Required item type of the first operand of '>' is numeric; supplied value has item type xs:string


Please let me know if any more information is needed to understand the question. Is this because of the else part, or something else?

EDIT: In setExternalVariables(), I'm adding the variables using below line, using for-each loop. value variable is of type net.sf.saxon.s9api.XdmValue

xqueryEvaluator.setExternalVariable(new QName(memberName), value);

In setExternalVariables() method,

// FACT_VALUE_FORMAT:%s;%s --  where first string is value and second gives information about precision.
//current option
XdmAtomicValue atomicValue = new XdmAtomicValue(String.format(FACT_VALUE_FORMAT, fact.getValue(),getPrecision(fact.getDecimals())));
// alternative 1
atomicValue = new XdmAtomicValue(getDoubleValue(fact));
//alternative 2
atomicValue = new XdmAtomicValue(getStringValue(fact));

In getDoubleValue(),

    String precision = fact.getDecimals();
    BigDecimal value = new BigDecimal(fact.getValue());
    if((precision != null ) && (precision.equals(INF_STRING) == false )){
        if(Integer.parseInt(precision)>0){
            NumberFormat nf = NumberFormat.getNumberInstance(Locale.US);
            DecimalFormat df = (DecimalFormat)nf;

            // If the decimal value is greater than 0, then we need the decimal precision correct to n places of decimal
            df.setMaximumFractionDigits(Integer.parseInt(precision) + 1);
            double doublePrecision = Math.pow(10,-Integer.parseInt(precision))/2;
            df.setMaximumFractionDigits(Integer.parseInt(precision) + 1);
            precision = df.format(doublePrecision);
            System.out.println("doublePrecision\t:\t"+doublePrecision);
            return (double) Math.round(value.doubleValue() * doublePrecision) / doublePrecision;
        }else{

            int scale = (int) Math.pow(10, -Integer.parseInt(precision));
            System.out.println("scale\t:\t"+scale);
            return (double) Math.round(value.doubleValue() * scale) / scale;

        }
    }
    return value.doubleValue();

In getStringValue(),

    String value = fact.getValue();
    String decimal = fact.getDecimals();
    String DOT = "\\.";
    if(value.contains(".")){
        final int parseInt = Integer.parseInt(decimal);
        if(parseInt>0){
            String[]split = value.split(DOT);
            value = split[0];
            if(parseInt>=value.length()){
                return "0";
            }
            for (int i = 0; i < parseInt; i++) {
                char[] array =value.toCharArray();
                array[value.length()-i-1]="0".charAt(0);
                value = new String(array);
            }
        }else{
            final int parseNegativeInt = -Integer.parseInt(decimal);
            String[]split = value.split(DOT);
            String tempValue = split[1];
            if(tempValue.length()>parseNegativeInt){
                tempValue = tempValue.substring(0, parseNegativeInt);
            }
            value = split[0]+"."+tempValue;
        }
    }
    return value;

Current implementation and alternative(2) does not work for the rule mentioned above, and when I'm returning double, it transforms big numbers into expression containing char E, for e.g. 5.12344E12, which fails in other rules.

Error on line 199 of module with no systemId: FORG0001: Cannot convert string "1.089563E9" to xs:decimal: invalid character 'E' at iaf:splitValueThreshold() (module with no systemId#20) at iaf:numeric-equal() (module with no systemId#343)

Please suggest any other option.

VijayD
  • 826
  • 1
  • 11
  • 33
  • Well, where do you set, construct or create the variable `value`, do you want to pass in a string or a certain number type there? – Martin Honnen Mar 28 '18 at 14:57
  • You can construct an `xs:decimal` in your Java code using http://saxonica.com/html/documentation/dotnetdoc/Saxon/Api/XdmAtomicValue.html#XdmAtomicValue(decimal) and pass that in to `setExternalVariable`. Or you can do it on the XPath side using `if (xs:decimal($a) > 0) ...` or as the answer suggests using `if (number($a) > 0)...` although that creates an xs:double. – Martin Honnen Mar 29 '18 at 07:12
  • 1
    Sorry, I think I posted a link to the .NET API, the Java API is at http://saxonica.com/html/documentation/javadoc/net/sf/saxon/s9api/XdmAtomicValue.html#XdmAtomicValue-java.math.BigDecimal-. – Martin Honnen Mar 29 '18 at 07:18
  • If you don't have a number type in your Java code but a String instead then you can use http://saxonica.com/html/documentation/javadoc/net/sf/saxon/s9api/XdmAtomicValue.html#XdmAtomicValue-java.lang.String-net.sf.saxon.s9api.ItemType- to construct a certain number type – Martin Honnen Mar 29 '18 at 07:20
  • @MartinHonnen, I can not change the if condition as a business rule, so your second comment won't work I guess. Will check if the links you have shared are of any help! – VijayD Mar 29 '18 at 07:30
  • So with what kind of value do you start with on the Java side of your code? Do you have a Java String there or some kind of Java number value, which type exactly? We can only help using the right way with the Saxon API and the XSLT/XPath type system if we know which kind of value you have originally as the input in Java. – Martin Honnen Apr 06 '18 at 07:44
  • Right now in my JUnits the value for `$a` is **2179.125955** which gives an error when passed as String using `getStringValue()` method. If I use `getDoubleValue()`, it gives me an error with the result of **`$b`*`$c`**, values of `$b`,`$c` are **507.327,2179.125955**. And I have java String data type from values. – VijayD Apr 06 '18 at 10:01

3 Answers3

4

Typically XPath > implicitly converts a string operand to a number, but you can force the conversion using the number() XPath function.

if (number($a) > 0) then (iaf:numeric-equal(iaf:numeric-multiply($b, $c), $d)) else (true())
Mads Hansen
  • 63,927
  • 12
  • 112
  • 147
kjhughes
  • 106,133
  • 27
  • 181
  • 240
  • I can not change the rule to add `number` function. I tried passing the value as double in there, but if the value is too big for decimal to handle it includes "E" in the number. And this logic fails in other rules where "E" is not expected in the number. Please suggest! – VijayD Apr 04 '18 at 10:28
  • Unfortunately, I can not change the rule. Please find my updated question as I tried few more things to solve the issue. – VijayD Apr 05 '18 at 06:22
2

Assuming you have your numeric values on the Java side as Strings in e.g. String value = "2179.125955"; then I would suggest to pass them as xs:decimals to XQuery/XPath by using new XdmAtomicValue(new BigDecimal(value)) as the value you pass to setExternalVariable e.g.

xqueryEvaluator.setExternalVariable(new QName(memberName), new XdmAtomicValue(new BigDecimal(value)));
Martin Honnen
  • 160,499
  • 6
  • 90
  • 110
  • I will try this solution and will let you know if it works!! – VijayD Apr 09 '18 at 05:35
  • some rules, which were passing before implementing this, have started failing, need to debug more on this. Thank you for your help! – VijayD Apr 18 '18 at 05:35
1

I second Martin's suggestion (I had first overlooked BigDecimal vs. BigDecimalValue).

Some alternatives also come to my mind based on Saxon's documentation:

atomicValue = new XdmAtomicValue(
    new BigDecimalValue(getDoubleValue(fact))
);

or to avoid going through doubles or BigDecimal, something like:

atomicValue = new XdmAtomicValue(
    (BigDecimalValue)BigDecimalValue.makeDecimalValue(getStringValue(fact), true)
);
// potentially adding an "instance of BigDecimalValue" check in the middle
// or bypassing all checks with 'false' instead of 'true'

Note: I am not sure I fully understand the issue that arises in other rules if an xs:double is passed instead of an xs:decimal. I suspect that there may be some casting back to xs:string involved in these other rules, as this is where an E could be introduced. XPath comparisons between numeric values of any types should be seamless.

In general (but I am only guessing here as I do not know the details involved, apart from seeing usage of EBA's interval arithmetic functions in the rule), I think it is probably a nice idea to align the types used in or passed to XBRL Formula rules with the original XBRL concept types (use xs:decimal for xbrli:decimalItemType (r) or xbrli:monetaryItemType (m), but xs:double for xbrli:doubleItemType, etc), as the lexical spaces will then match those used in the XBRL instances.

Ghislain Fourny
  • 6,971
  • 1
  • 30
  • 37