summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/java/org/apache/poi/ss/formula/functions/Value.java324
-rw-r--r--src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java15
-rw-r--r--test-data/spreadsheet/TestValueAsArrayFunction.xlsbin0 -> 5632 bytes
3 files changed, 183 insertions, 156 deletions
diff --git a/src/java/org/apache/poi/ss/formula/functions/Value.java b/src/java/org/apache/poi/ss/formula/functions/Value.java
index 176a776906..d2a1f24fc7 100644
--- a/src/java/org/apache/poi/ss/formula/functions/Value.java
+++ b/src/java/org/apache/poi/ss/formula/functions/Value.java
@@ -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;
+ }
- }
+ }
}
diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java b/src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java
index d0529220d2..7d7650de25 100644
--- a/src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java
+++ b/src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java
@@ -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
index 0000000000..2b9b11f9ca
--- /dev/null
+++ b/test-data/spreadsheet/TestValueAsArrayFunction.xls
Binary files differ