]> source.dussan.org Git - poi.git/commitdiff
[github-181] make Value function work with arrays. Thanks to MiƂosz Rembisz. This...
authorPJ Fanning <fanningpj@apache.org>
Sat, 6 Jun 2020 09:30:30 +0000 (09:30 +0000)
committerPJ Fanning <fanningpj@apache.org>
Sat, 6 Jun 2020 09:30:30 +0000 (09:30 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1878541 13f79535-47bb-0310-9956-ffa450edef68

src/java/org/apache/poi/ss/formula/functions/Value.java
src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java
test-data/spreadsheet/TestValueAsArrayFunction.xls [new file with mode: 0644]

index 176a776906d1bda9a82bb9114c23828b96cf92ec..d2a1f24fc7c958eae5095c9193ee540a38fa19a9 100644 (file)
@@ -30,177 +30,189 @@ import java.time.DateTimeException;
  * Implementation for Excel VALUE() function.<p>
  *
  * <b>Syntax</b>:<br> <b>VALUE</b>(<b>text</b>)<br>
- *
+ * <p>
  * Converts the text argument to a number. Leading and/or trailing whitespace is
  * ignored. Currency symbols and thousands separators are stripped out.
  * Scientific notation is also supported. If the supplied text does not convert
  * properly the result is <b>#VALUE!</b> error. Blank string converts to zero.
  */
-public final class Value extends Fixed1ArgFunction {
-
-       /** "1,0000" is valid, "1,00" is not */
-       private static final int MIN_DISTANCE_BETWEEN_THOUSANDS_SEPARATOR = 4;
-       private static final Double ZERO = Double.valueOf(0.0);
-
-       public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0) {
-               ValueEval veText;
-               try {
-                       veText = OperandResolver.getSingleValue(arg0, srcRowIndex, srcColumnIndex);
-               } catch (EvaluationException e) {
-                       return e.getErrorEval();
-               }
-               String strText = OperandResolver.coerceValueToString(veText);
-               Double result = convertTextToNumber(strText);
-               if(result == null) result = parseDateTime(strText);
-               if (result == null) {
-                       return ErrorEval.VALUE_INVALID;
-               }
-               return new NumberEval(result.doubleValue());
-       }
-
-       /**
-        * TODO see if the same functionality is needed in {@link OperandResolver#parseDouble(String)}
-        *
-        * @return <code>null</code> if there is any problem converting the text
-        */
-       public static Double convertTextToNumber(String strText) {
-               boolean foundCurrency = false;
-               boolean foundUnaryPlus = false;
-               boolean foundUnaryMinus = false;
-               boolean foundPercentage = false;
-
-               int len = strText.length();
-               int i;
-               for (i = 0; i < len; i++) {
-                       char ch = strText.charAt(i);
-                       if (Character.isDigit(ch) || ch == '.') {
-                               break;
-                       }
-                       switch (ch) {
-                               case ' ':
-                                       // intervening spaces between '$', '-', '+' are OK
-                                       continue;
-                               case '$':
-                                       if (foundCurrency) {
-                                               // only one currency symbols is allowed
-                                               return null;
-                                       }
-                                       foundCurrency = true;
-                                       continue;
-                               case '+':
-                                       if (foundUnaryMinus || foundUnaryPlus) {
-                                               return null;
-                                       }
-                                       foundUnaryPlus = true;
-                                       continue;
-                               case '-':
-                                       if (foundUnaryMinus || foundUnaryPlus) {
-                                               return null;
-                                       }
-                                       foundUnaryMinus = true;
-                                       continue;
-                               default:
-                                       // all other characters are illegal
-                                       return null;
-                       }
-               }
-               if (i >= len) {
-                       // didn't find digits or '.'
-                       if (foundCurrency || foundUnaryMinus || foundUnaryPlus) {
-                               return null;
-                       }
-                       return ZERO;
-               }
-
-               // remove thousands separators
-
-               boolean foundDecimalPoint = false;
-               int lastThousandsSeparatorIndex = Short.MIN_VALUE;
-
-               StringBuilder sb = new StringBuilder(len);
-               for (; i < len; i++) {
-                       char ch = strText.charAt(i);
-                       if (Character.isDigit(ch)) {
-                               sb.append(ch);
-                               continue;
-                       }
-                       switch (ch) {
-                               case ' ':
-                                       String remainingTextTrimmed = strText.substring(i).trim();
+public final class Value extends Fixed1ArgFunction implements ArrayFunction {
+
+    /**
+     * "1,0000" is valid, "1,00" is not
+     */
+    private static final int MIN_DISTANCE_BETWEEN_THOUSANDS_SEPARATOR = 4;
+    private static final Double ZERO = Double.valueOf(0.0);
+
+    public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0) {
+        ValueEval veText;
+        try {
+            veText = OperandResolver.getSingleValue(arg0, srcRowIndex, srcColumnIndex);
+        } catch (EvaluationException e) {
+            return e.getErrorEval();
+        }
+        String strText = OperandResolver.coerceValueToString(veText);
+        Double result = convertTextToNumber(strText);
+        if (result == null) result = parseDateTime(strText);
+        if (result == null) {
+            return ErrorEval.VALUE_INVALID;
+        }
+        return new NumberEval(result.doubleValue());
+    }
+
+    @Override
+    public ValueEval evaluateArray(ValueEval[] args, int srcRowIndex, int srcColumnIndex) {
+        if (args.length != 1) {
+            return ErrorEval.VALUE_INVALID;
+        }
+        return evaluateOneArrayArg(args[0], srcRowIndex, srcColumnIndex, (valA) ->
+                evaluate(srcRowIndex, srcColumnIndex, valA)
+        );
+    }
+
+    /**
+     * TODO see if the same functionality is needed in {@link OperandResolver#parseDouble(String)}
+     *
+     * @return <code>null</code> if there is any problem converting the text
+     */
+    public static Double convertTextToNumber(String strText) {
+        boolean foundCurrency = false;
+        boolean foundUnaryPlus = false;
+        boolean foundUnaryMinus = false;
+        boolean foundPercentage = false;
+
+        int len = strText.length();
+        int i;
+        for (i = 0; i < len; i++) {
+            char ch = strText.charAt(i);
+            if (Character.isDigit(ch) || ch == '.') {
+                break;
+            }
+            switch (ch) {
+                case ' ':
+                    // intervening spaces between '$', '-', '+' are OK
+                    continue;
+                case '$':
+                    if (foundCurrency) {
+                        // only one currency symbols is allowed
+                        return null;
+                    }
+                    foundCurrency = true;
+                    continue;
+                case '+':
+                    if (foundUnaryMinus || foundUnaryPlus) {
+                        return null;
+                    }
+                    foundUnaryPlus = true;
+                    continue;
+                case '-':
+                    if (foundUnaryMinus || foundUnaryPlus) {
+                        return null;
+                    }
+                    foundUnaryMinus = true;
+                    continue;
+                default:
+                    // all other characters are illegal
+                    return null;
+            }
+        }
+        if (i >= len) {
+            // didn't find digits or '.'
+            if (foundCurrency || foundUnaryMinus || foundUnaryPlus) {
+                return null;
+            }
+            return ZERO;
+        }
+
+        // remove thousands separators
+
+        boolean foundDecimalPoint = false;
+        int lastThousandsSeparatorIndex = Short.MIN_VALUE;
+
+        StringBuilder sb = new StringBuilder(len);
+        for (; i < len; i++) {
+            char ch = strText.charAt(i);
+            if (Character.isDigit(ch)) {
+                sb.append(ch);
+                continue;
+            }
+            switch (ch) {
+                case ' ':
+                    String remainingTextTrimmed = strText.substring(i).trim();
                     // support for value[space]%
                     if (remainingTextTrimmed.equals("%")) {
-                        foundPercentage= true;
+                        foundPercentage = true;
                         break;
                     }
                     if (remainingTextTrimmed.length() > 0) {
-                                               // intervening spaces not allowed once the digits start
-                                               return null;
-                                       }
-                                       break;
-                               case '.':
-                                       if (foundDecimalPoint) {
-                                               return null;
-                                       }
-                                       if (i - lastThousandsSeparatorIndex < MIN_DISTANCE_BETWEEN_THOUSANDS_SEPARATOR) {
-                                               return null;
-                                       }
-                                       foundDecimalPoint = true;
-                                       sb.append('.');
-                                       continue;
-                               case ',':
-                                       if (foundDecimalPoint) {
-                                               // thousands separators not allowed after '.' or 'E'
-                                               return null;
-                                       }
-                                       int distanceBetweenThousandsSeparators = i - lastThousandsSeparatorIndex;
-                                       // as long as there are 3 or more digits between
-                                       if (distanceBetweenThousandsSeparators < MIN_DISTANCE_BETWEEN_THOUSANDS_SEPARATOR) {
-                                               return null;
-                                       }
-                                       lastThousandsSeparatorIndex = i;
-                                       // don't append ','
-                                       continue;
-
-                               case 'E':
-                               case 'e':
-                                       if (i - lastThousandsSeparatorIndex < MIN_DISTANCE_BETWEEN_THOUSANDS_SEPARATOR) {
-                                               return null;
-                                       }
-                                       // append rest of strText and skip to end of loop
-                                       sb.append(strText.substring(i));
-                                       i = len;
-                                       break;
+                        // intervening spaces not allowed once the digits start
+                        return null;
+                    }
+                    break;
+                case '.':
+                    if (foundDecimalPoint) {
+                        return null;
+                    }
+                    if (i - lastThousandsSeparatorIndex < MIN_DISTANCE_BETWEEN_THOUSANDS_SEPARATOR) {
+                        return null;
+                    }
+                    foundDecimalPoint = true;
+                    sb.append('.');
+                    continue;
+                case ',':
+                    if (foundDecimalPoint) {
+                        // thousands separators not allowed after '.' or 'E'
+                        return null;
+                    }
+                    int distanceBetweenThousandsSeparators = i - lastThousandsSeparatorIndex;
+                    // as long as there are 3 or more digits between
+                    if (distanceBetweenThousandsSeparators < MIN_DISTANCE_BETWEEN_THOUSANDS_SEPARATOR) {
+                        return null;
+                    }
+                    lastThousandsSeparatorIndex = i;
+                    // don't append ','
+                    continue;
+
+                case 'E':
+                case 'e':
+                    if (i - lastThousandsSeparatorIndex < MIN_DISTANCE_BETWEEN_THOUSANDS_SEPARATOR) {
+                        return null;
+                    }
+                    // append rest of strText and skip to end of loop
+                    sb.append(strText.substring(i));
+                    i = len;
+                    break;
                 case '%':
                     foundPercentage = true;
                     break;
-                               default:
-                                       // all other characters are illegal
-                                       return null;
-                       }
-               }
-               if (!foundDecimalPoint) {
-                       if (i - lastThousandsSeparatorIndex < MIN_DISTANCE_BETWEEN_THOUSANDS_SEPARATOR) {
-                               return null;
-                       }
-               }
-               double d;
-               try {
-                       d = Double.parseDouble(sb.toString());
-               } catch (NumberFormatException e) {
-                       // still a problem parsing the number - probably out of range
-                       return null;
-               }
+                default:
+                    // all other characters are illegal
+                    return null;
+            }
+        }
+        if (!foundDecimalPoint) {
+            if (i - lastThousandsSeparatorIndex < MIN_DISTANCE_BETWEEN_THOUSANDS_SEPARATOR) {
+                return null;
+            }
+        }
+        double d;
+        try {
+            d = Double.parseDouble(sb.toString());
+        } catch (NumberFormatException e) {
+            // still a problem parsing the number - probably out of range
+            return null;
+        }
         double result = foundUnaryMinus ? -d : d;
-        return foundPercentage ? result/100. : result;
-       }
+        return foundPercentage ? result / 100. : result;
+    }
 
-       public static Double parseDateTime(String pText) {
+    public static Double parseDateTime(String pText) {
 
-               try {
-                       return DateUtil.parseDateTime(pText);
-               } catch (DateTimeException e) {
-                       return null;
-               }
+        try {
+            return DateUtil.parseDateTime(pText);
+        } catch (DateTimeException e) {
+            return null;
+        }
 
-       }
+    }
 }
index d0529220d2f492bd4033c8c0a5d3729042e9d91b..7d7650de25dcb6e81698ecff49ec55c09f05f8c7 100644 (file)
@@ -2903,6 +2903,21 @@ public final class TestBugs extends BaseTestBugzillaIssues {
     public void test63819() throws IOException {
         simpleTest("63819.xls");
     }
+
+    /**
+     * Test that VALUE behaves properly as array function and its result is handled by aggregate function
+     */
+    @Test
+    public void testValueAsArrayFunction() throws IOException {
+        try (final Workbook wb = openSampleWorkbook("TestValueAsArrayFunction.xls")) {
+            wb.getCreationHelper().createFormulaEvaluator().evaluateAll();
+            Sheet sheet = wb.getSheetAt(0);
+            Row row = sheet.getRow(0);
+            Cell cell = row.getCell(0);
+            assertEquals(6.0, cell.getNumericCellValue(), 0.0);
+        }
+    }
+
     // a simple test which rewrites the file once and evaluates its formulas
     private void simpleTest(String fileName) throws IOException {
         simpleTest(fileName, null);
diff --git a/test-data/spreadsheet/TestValueAsArrayFunction.xls b/test-data/spreadsheet/TestValueAsArrayFunction.xls
new file mode 100644 (file)
index 0000000..2b9b11f
Binary files /dev/null and b/test-data/spreadsheet/TestValueAsArrayFunction.xls differ