diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml index 591696ece..202938cff 100644 --- a/src/documentation/content/xdocs/status.xml +++ b/src/documentation/content/xdocs/status.xml @@ -34,6 +34,7 @@ + 50607 - Added implementation for CLEAN(), CHAR() and ADDRESS() 50587 - Improved documentation on user-defined functions Inside ExtractorFactory, support finding embedded OOXML documents and providing extractors for them Partial HDGF LZW compression support diff --git a/src/java/org/apache/poi/ss/formula/eval/FunctionEval.java b/src/java/org/apache/poi/ss/formula/eval/FunctionEval.java index a7bc15cee..7c84a5860 100644 --- a/src/java/org/apache/poi/ss/formula/eval/FunctionEval.java +++ b/src/java/org/apache/poi/ss/formula/eval/FunctionEval.java @@ -130,6 +130,7 @@ public final class FunctionEval { retval[109] = NumericFunction.LOG; + retval[111] = TextFunction.CHAR; retval[112] = TextFunction.LOWER; retval[113] = TextFunction.UPPER; @@ -148,7 +149,7 @@ public final class FunctionEval { retval[130] = new T(); retval[ID.INDIRECT] = null; // Indirect.evaluate has different signature - + retval[162] = TextFunction.CLEAN; //Aniket Banerjee retval[169] = new Counta(); retval[183] = AggregateFunction.PRODUCT; @@ -161,7 +162,7 @@ public final class FunctionEval { retval[212] = NumericFunction.ROUNDUP; retval[213] = NumericFunction.ROUNDDOWN; - + retval[219] = new Address(); //Aniket Banerjee retval[220] = new Days360(); retval[221] = new Today(); diff --git a/src/java/org/apache/poi/ss/formula/functions/Address.java b/src/java/org/apache/poi/ss/formula/functions/Address.java new file mode 100644 index 000000000..288a88edc --- /dev/null +++ b/src/java/org/apache/poi/ss/formula/functions/Address.java @@ -0,0 +1,103 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ +package org.apache.poi.ss.formula.functions; + +import org.apache.poi.ss.formula.SheetNameFormatter; +import org.apache.poi.ss.formula.eval.*; +import org.apache.poi.ss.util.CellReference; + +/** + * Creates a text reference as text, given specified row and column numbers. + * + * @author Aniket Banerjee (banerjee@google.com) + */ +public class Address implements Function { + public static final int REF_ABSOLUTE = 1; + public static final int REF_ROW_ABSOLUTE_COLUMN_RELATIVE = 2; + public static final int REF_ROW_RELATIVE_RELATIVE_ABSOLUTE = 3; + public static final int REF_RELATIVE = 4; + + public ValueEval evaluate(ValueEval[] args, int srcRowIndex, + int srcColumnIndex) { + if(args.length < 2 || args.length > 5) { + return ErrorEval.VALUE_INVALID; + } + try { + boolean pAbsRow, pAbsCol; + + int row = (int)NumericFunction.singleOperandEvaluate(args[0], srcRowIndex, srcColumnIndex); + int col = (int)NumericFunction.singleOperandEvaluate(args[1], srcRowIndex, srcColumnIndex); + + int refType; + if(args.length > 2){ + refType = (int)NumericFunction.singleOperandEvaluate(args[2], srcRowIndex, srcColumnIndex); + } else { + refType = REF_ABSOLUTE; + } + switch (refType){ + case REF_ABSOLUTE: + pAbsRow = true; + pAbsCol = true; + break; + case REF_ROW_ABSOLUTE_COLUMN_RELATIVE: + pAbsRow = true; + pAbsCol = false; + break; + case REF_ROW_RELATIVE_RELATIVE_ABSOLUTE: + pAbsRow = false; + pAbsCol = true; + break; + case REF_RELATIVE: + pAbsRow = false; + pAbsCol = false; + break; + default: + throw new EvaluationException(ErrorEval.VALUE_INVALID); + } + + boolean a1; + if(args.length > 3){ + ValueEval ve = OperandResolver.getSingleValue(args[3], srcRowIndex, srcColumnIndex); + // TODO R1C1 style is not yet supported + a1 = ve == MissingArgEval.instance ? true : OperandResolver.coerceValueToBoolean(ve, false); + } else { + a1 = true; + } + + String sheetName; + if(args.length == 5){ + ValueEval ve = OperandResolver.getSingleValue(args[4], srcRowIndex, srcColumnIndex); + sheetName = ve == MissingArgEval.instance ? null : OperandResolver.coerceValueToString(ve); + } else { + sheetName = null; + } + + CellReference ref = new CellReference(row - 1, col - 1, pAbsRow, pAbsCol); + StringBuffer sb = new StringBuffer(32); + if(sheetName != null) { + SheetNameFormatter.appendFormat(sb, sheetName); + sb.append('!'); + } + sb.append(ref.formatAsString()); + + return new StringEval(sb.toString()); + + } catch (EvaluationException e){ + return e.getErrorEval(); + } + } +} diff --git a/src/java/org/apache/poi/ss/formula/functions/TextFunction.java b/src/java/org/apache/poi/ss/formula/functions/TextFunction.java index dc57b4277..86e1e3268 100644 --- a/src/java/org/apache/poi/ss/formula/functions/TextFunction.java +++ b/src/java/org/apache/poi/ss/formula/functions/TextFunction.java @@ -83,6 +83,25 @@ public abstract class TextFunction implements Function { protected abstract ValueEval evaluate(String arg); } + /** + * Returns the character specified by a number. + */ + public static final Function CHAR = new Fixed1ArgFunction() { + public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0) { + int arg; + try { + arg = evaluateIntArg(arg0, srcRowIndex, srcColumnIndex); + if (arg < 0 || arg >= 256) { + throw new EvaluationException(ErrorEval.VALUE_INVALID); + } + + } catch (EvaluationException e) { + return e.getErrorEval(); + } + return new StringEval(String.valueOf((char)arg)); + } + }; + public static final Function LEN = new SingleArgTextFunc() { protected ValueEval evaluate(String arg) { return new NumberEval(arg.length()); @@ -109,8 +128,43 @@ public abstract class TextFunction implements Function { return new StringEval(arg.trim()); } }; - + /** + * An implementation of the CLEAN function: + * In Excel, the Clean function removes all non-printable characters from a string. + * + * Author: Aniket Banerjee(banerjee@google.com) + */ + public static final Function CLEAN = new SingleArgTextFunc() { + protected ValueEval evaluate(String arg) { + StringBuilder result = new StringBuilder(); + for (int i = 0; i < arg.length(); i++) { + char c = arg.charAt(i); + if (isPrintable(c)) { + result.append(c); + } + } + return new StringEval(result.toString()); + } + + /** + * From Excel docs: The CLEAN function was designed to remove the first 32 nonprinting characters + * in the 7-bit ASCII code (values 0 through 31) from text. In the Unicode character set, + * there are additional nonprinting characters (values 127, 129, 141, 143, 144, and 157). By itself, + * the CLEAN function does not remove these additional nonprinting characters. To do this task, + * use the SUBSTITUTE function to replace the higher value Unicode characters with the 7-bit ASCII + * characters for which the TRIM and CLEAN functions were designed. + * + * @param c the character to test + * @return whether the character is printable + */ + private boolean isPrintable(char c){ + int charCode = (int)c ; + return charCode >= 32; + } + }; + + /** * An implementation of the MID function
* MID returns a specific number of * characters from a text string, starting at the specified position.

diff --git a/src/testcases/org/apache/poi/ss/formula/functions/AllIndividualFunctionEvaluationTests.java b/src/testcases/org/apache/poi/ss/formula/functions/AllIndividualFunctionEvaluationTests.java index 4e42389ec..05c8a9bbd 100644 --- a/src/testcases/org/apache/poi/ss/formula/functions/AllIndividualFunctionEvaluationTests.java +++ b/src/testcases/org/apache/poi/ss/formula/functions/AllIndividualFunctionEvaluationTests.java @@ -60,6 +60,8 @@ public final class AllIndividualFunctionEvaluationTests { result.addTestSuite(TestTrunc.class); result.addTestSuite(TestValue.class); result.addTestSuite(TestXYNumericFunction.class); + result.addTestSuite(TestAddress.class); + result.addTestSuite(TestClean.class); return result; } } diff --git a/src/testcases/org/apache/poi/ss/formula/functions/TestAddress.java b/src/testcases/org/apache/poi/ss/formula/functions/TestAddress.java new file mode 100644 index 000000000..ba25c4d7d --- /dev/null +++ b/src/testcases/org/apache/poi/ss/formula/functions/TestAddress.java @@ -0,0 +1,76 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ +package org.apache.poi.ss.formula.functions; + +import org.apache.poi.hssf.usermodel.HSSFCell; +import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.ss.usermodel.CellValue; + +import junit.framework.TestCase; +import org.apache.poi.ss.util.CellReference; + +public final class TestAddress extends TestCase { + + public void testAddress() { + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFCell cell = wb.createSheet().createRow(0).createCell(0); + HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(wb); + + String formulaText = "ADDRESS(1,2)"; + confirmResult(fe, cell, formulaText, "$B$1"); + + formulaText = "ADDRESS(22,44)"; + confirmResult(fe, cell, formulaText, "$AR$22"); + + formulaText = "ADDRESS(1,1)"; + confirmResult(fe, cell, formulaText, "$A$1"); + + formulaText = "ADDRESS(1,128)"; + confirmResult(fe, cell, formulaText, "$DX$1"); + + formulaText = "ADDRESS(1,512)"; + confirmResult(fe, cell, formulaText, "$SR$1"); + + formulaText = "ADDRESS(1,1000)"; + confirmResult(fe, cell, formulaText, "$ALL$1"); + + formulaText = "ADDRESS(1,10000)"; + confirmResult(fe, cell, formulaText, "$NTP$1"); + + formulaText = "ADDRESS(2,3)"; + confirmResult(fe, cell, formulaText, "$C$2"); + + formulaText = "ADDRESS(2,3,2)"; + confirmResult(fe, cell, formulaText, "C$2"); + + formulaText = "ADDRESS(2,3,2,,\"EXCEL SHEET\")"; + confirmResult(fe, cell, formulaText, "'EXCEL SHEET'!C$2"); + + formulaText = "ADDRESS(2,3,3,TRUE,\"[Book1]Sheet1\")"; + confirmResult(fe, cell, formulaText, "'[Book1]Sheet1'!$C2"); + } + + private static void confirmResult(HSSFFormulaEvaluator fe, HSSFCell cell, String formulaText, + String expectedResult) { + cell.setCellFormula(formulaText); + fe.notifyUpdateCell(cell); + CellValue result = fe.evaluate(cell); + assertEquals(result.getCellType(), HSSFCell.CELL_TYPE_STRING); + assertEquals(expectedResult, result.getStringValue()); + } +} diff --git a/src/testcases/org/apache/poi/ss/formula/functions/TestClean.java b/src/testcases/org/apache/poi/ss/formula/functions/TestClean.java new file mode 100644 index 000000000..ad717b162 --- /dev/null +++ b/src/testcases/org/apache/poi/ss/formula/functions/TestClean.java @@ -0,0 +1,65 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ +package org.apache.poi.ss.formula.functions; + +import org.apache.poi.hssf.usermodel.HSSFCell; +import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.ss.usermodel.CellValue; + +import junit.framework.TestCase; + +public final class TestClean extends TestCase { + + public void testClean() { + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFCell cell = wb.createSheet().createRow(0).createCell(0); + HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(wb); + + String[] asserts = { + "aniket\u0007\u0017\u0019", "aniket", + "\u0011aniket\u0007\u0017\u0010", "aniket", + "\u0011aniket\u0007\u0017\u007F", "aniket\u007F", + "\u2116aniket\u2211\uFB5E\u2039", "\u2116aniket\u2211\uFB5E\u2039", + }; + + for(int i = 0; i < asserts.length; i+= 2){ + String formulaText = "CLEAN(\"" + asserts[i] + "\")"; + confirmResult(fe, cell, formulaText, asserts[i + 1]); + } + + asserts = new String[] { + "CHAR(7)&\"text\"&CHAR(7)", "text", + "CHAR(7)&\"text\"&CHAR(17)", "text", + "CHAR(181)&\"text\"&CHAR(190)", "\u00B5text\u00BE", + "\"text\"&CHAR(160)&\"'\"", "text\u00A0'", + }; + for(int i = 0; i < asserts.length; i+= 2){ + String formulaText = "CLEAN(" + asserts[i] + ")"; + confirmResult(fe, cell, formulaText, asserts[i + 1]); + } + } + + private static void confirmResult(HSSFFormulaEvaluator fe, HSSFCell cell, String formulaText, + String expectedResult) { + cell.setCellFormula(formulaText); + fe.notifyUpdateCell(cell); + CellValue result = fe.evaluate(cell); + assertEquals(result.getCellType(), HSSFCell.CELL_TYPE_STRING); + assertEquals(expectedResult, result.getStringValue()); + } +}