From: Josh Micich Date: Sat, 13 Sep 2008 05:14:26 +0000 (+0000) Subject: Refactored TextFunctions. Some minor fixes - test cases added. X-Git-Tag: REL_3_2_FINAL~61 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=af3829250516d91c1c0a57c73ea69c7e12154a09;p=poi.git Refactored TextFunctions. Some minor fixes - test cases added. git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@694877 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/src/java/org/apache/poi/hssf/record/formula/eval/ConcatEval.java b/src/java/org/apache/poi/hssf/record/formula/eval/ConcatEval.java index 8d6729352b..6755cfafef 100644 --- a/src/java/org/apache/poi/hssf/record/formula/eval/ConcatEval.java +++ b/src/java/org/apache/poi/hssf/record/formula/eval/ConcatEval.java @@ -44,7 +44,7 @@ public final class ConcatEval implements OperationEval { if (ve instanceof StringValueEval) { StringValueEval sve = (StringValueEval) ve; sb.append(sve.getStringValue()); - } else if (ve instanceof BlankEval) { + } else if (ve == BlankEval.INSTANCE) { // do nothing } else { // must be an error eval throw new RuntimeException("Unexpected value type (" diff --git a/src/java/org/apache/poi/hssf/record/formula/eval/FunctionEval.java b/src/java/org/apache/poi/hssf/record/formula/eval/FunctionEval.java index 719349a1c8..e68b8ee67d 100644 --- a/src/java/org/apache/poi/hssf/record/formula/eval/FunctionEval.java +++ b/src/java/org/apache/poi/hssf/record/formula/eval/FunctionEval.java @@ -105,8 +105,8 @@ public abstract class FunctionEval implements OperationEval { retval[28] = new Lookup(); // LOOKUP retval[29] = new Index(); // INDEX retval[30] = new Rept(); // REPT - retval[31] = new Mid(); // MID - retval[32] = new Len(); // LEN + retval[31] = TextFunction.MID; + retval[32] = TextFunction.LEN; retval[33] = new Value(); // VALUE retval[34] = new True(); // TRUE retval[35] = new False(); // FALSE @@ -185,13 +185,13 @@ public abstract class FunctionEval implements OperationEval { retval[109] = NumericFunction.LOG; retval[110] = new Exec(); // EXEC retval[111] = new Char(); // CHAR - retval[112] = new Lower(); // LOWER - retval[113] = new Upper(); // UPPER + retval[112] = TextFunction.LOWER; + retval[113] = TextFunction.UPPER; retval[114] = new Proper(); // PROPER - retval[115] = new Left(); // LEFT - retval[116] = new Right(); // RIGHT - retval[117] = new Exact(); // EXACT - retval[118] = new Trim(); // TRIM + retval[115] = TextFunction.LEFT; + retval[116] = TextFunction.RIGHT; + retval[117] = TextFunction.EXACT; + retval[118] = TextFunction.TRIM; retval[119] = new Replace(); // REPLACE retval[120] = new Substitute(); // SUBSTITUTE retval[121] = new Code(); // CODE @@ -394,7 +394,7 @@ public abstract class FunctionEval implements OperationEval { retval[332] = new Tinv(); // TINV retval[334] = new NotImplementedFunction(); // MOVIECOMMAND retval[335] = new NotImplementedFunction(); // GETMOVIE - retval[336] = new Concatenate(); // CONCATENATE + retval[336] = TextFunction.CONCATENATE; retval[337] = NumericFunction.POWER; retval[338] = new NotImplementedFunction(); // PIVOTADDDATA retval[339] = new NotImplementedFunction(); // GETPIVOTTABLE diff --git a/src/java/org/apache/poi/hssf/record/formula/eval/OperandResolver.java b/src/java/org/apache/poi/hssf/record/formula/eval/OperandResolver.java index 87f45236bf..09be70477f 100755 --- a/src/java/org/apache/poi/hssf/record/formula/eval/OperandResolver.java +++ b/src/java/org/apache/poi/hssf/record/formula/eval/OperandResolver.java @@ -272,12 +272,7 @@ public final class OperandResolver { StringValueEval sve = (StringValueEval) ve; return sve.getStringValue(); } - if (ve instanceof NumberEval) { - NumberEval neval = (NumberEval) ve; - return neval.getStringValue(); - } - - if (ve instanceof BlankEval) { + if (ve == BlankEval.INSTANCE) { return ""; } throw new IllegalArgumentException("Unexpected eval class (" + ve.getClass().getName() + ")"); @@ -289,7 +284,7 @@ public final class OperandResolver { */ public static Boolean coerceValueToBoolean(ValueEval ve, boolean stringsAreBlanks) throws EvaluationException { - if (ve == null || ve instanceof BlankEval) { + if (ve == null || ve == BlankEval.INSTANCE) { // TODO - remove 've == null' condition once AreaEval is fixed return null; } @@ -297,7 +292,7 @@ public final class OperandResolver { return Boolean.valueOf(((BoolEval) ve).getBooleanValue()); } - if (ve instanceof BlankEval) { + if (ve == BlankEval.INSTANCE) { return null; } diff --git a/src/java/org/apache/poi/hssf/record/formula/functions/Concatenate.java b/src/java/org/apache/poi/hssf/record/formula/functions/Concatenate.java deleted file mode 100644 index be961c151e..0000000000 --- a/src/java/org/apache/poi/hssf/record/formula/functions/Concatenate.java +++ /dev/null @@ -1,60 +0,0 @@ -/* -* 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. -*/ -/* - * Created on May 15, 2005 - * - */ -package org.apache.poi.hssf.record.formula.functions; - -import org.apache.poi.hssf.record.formula.eval.BlankEval; -import org.apache.poi.hssf.record.formula.eval.ErrorEval; -import org.apache.poi.hssf.record.formula.eval.Eval; -import org.apache.poi.hssf.record.formula.eval.StringEval; -import org.apache.poi.hssf.record.formula.eval.StringValueEval; -import org.apache.poi.hssf.record.formula.eval.ValueEval; - -/** - * @author Amol S. Deshmukh < amolweb at ya hoo dot com > - * - */ -public class Concatenate extends TextFunction { - - - public Eval evaluate(Eval[] operands, int srcCellRow, short srcCellCol) { - ValueEval retval = null; - StringBuffer sb = new StringBuffer(); - - for (int i=0, iSize=operands.length; i MID returns a specific number of - * characters from a text string, starting at the specified position.

- * - * Syntax:
MID(text, start_num, - * num_chars)
- * - * @author Manda Wilson < wilson at c bio dot msk cc dot org > - */ -public class Mid implements Function { - /** - * Returns a specific number of characters from a text string, starting at - * the position you specify, based on the number of characters you specify. - * - * @see org.apache.poi.hssf.record.formula.eval.Eval - */ - public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) { - if (args.length != 3) { - return ErrorEval.VALUE_INVALID; - } - - String text; - int startIx; // zero based - int numChars; - - try { - ValueEval evText = OperandResolver.getSingleValue(args[0], srcCellRow, srcCellCol); - text = OperandResolver.coerceValueToString(evText); - int startCharNum = evaluateNumberArg(args[1], srcCellRow, srcCellCol); - numChars = evaluateNumberArg(args[2], srcCellRow, srcCellCol); - startIx = startCharNum - 1; // convert to zero-based - } catch (EvaluationException e) { - return e.getErrorEval(); - } - - int len = text.length(); - if (startIx < 0) { - return ErrorEval.VALUE_INVALID; - } - if (numChars < 0) { - return ErrorEval.VALUE_INVALID; - } - if (numChars < 0 || startIx > len) { - return new StringEval(""); - } - int endIx = startIx + numChars; - if (endIx > len) { - endIx = len; - } - String result = text.substring(startIx, endIx); - return new StringEval(result); - - } - - private static int evaluateNumberArg(Eval arg, int srcCellRow, short srcCellCol) throws EvaluationException { - ValueEval ev = OperandResolver.getSingleValue(arg, srcCellRow, srcCellCol); - // Note - for start_num arg, blank/zero causes error(#VALUE!), - // but for num_chars causes empty string to be returned. - return OperandResolver.coerceValueToInt(ev); - } -} \ No newline at end of file diff --git a/src/java/org/apache/poi/hssf/record/formula/functions/Replace.java b/src/java/org/apache/poi/hssf/record/formula/functions/Replace.java index 95413f0823..1dfc4f8f05 100644 --- a/src/java/org/apache/poi/hssf/record/formula/functions/Replace.java +++ b/src/java/org/apache/poi/hssf/record/formula/functions/Replace.java @@ -1,112 +1,70 @@ -/* -* 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. -*/ -/* - * Created on May 15, 2005 - * - */ +/* ==================================================================== + 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.hssf.record.formula.functions; import org.apache.poi.hssf.record.formula.eval.ErrorEval; import org.apache.poi.hssf.record.formula.eval.Eval; -import org.apache.poi.hssf.record.formula.eval.NumericValueEval; +import org.apache.poi.hssf.record.formula.eval.EvaluationException; import org.apache.poi.hssf.record.formula.eval.StringEval; -import org.apache.poi.hssf.record.formula.eval.StringValueEval; import org.apache.poi.hssf.record.formula.eval.ValueEval; /** - * An implementation of the REPLACE function: - * Replaces part of a text string based on the number of characters - * you specify, with another text string. + * An implementation of the Excel REPLACE() function

: + * Replaces part of a text string based on the number of characters + * you specify, with another text string.
+ * + * Syntax:
+ * REPLACE(oldText, startNum, numChars, newText)

+ * + * oldText The text string containing characters to replace
+ * startNum The position of the first character to replace (1-based)
+ * numChars The number of characters to replace
+ * newText The new text value to replace the removed section
+ * * @author Manda Wilson < wilson at c bio dot msk cc dot org > */ -public class Replace extends TextFunction { +public final class Replace extends TextFunction { + + protected ValueEval evaluateFunc(Eval[] args, int srcCellRow, short srcCellCol) + throws EvaluationException { + if (args.length != 4) { + return ErrorEval.VALUE_INVALID; + } - /** - * Replaces part of a text string based on the number of characters - * you specify, with another text string. - * - * @see org.apache.poi.hssf.record.formula.eval.Eval - */ - public Eval evaluate(Eval[] operands, int srcCellRow, short srcCellCol) { - Eval retval = null; - String oldStr = null; - String newStr = null; - int startNum = 0; - int numChars = 0; - - switch (operands.length) { - default: - retval = ErrorEval.VALUE_INVALID; - case 4: - // first operand is text string containing characters to replace - // second operand is position of first character to replace - // third operand is the number of characters in the old string - // you want to replace with new string - // fourth operand is the new string - ValueEval firstveval = singleOperandEvaluate(operands[0], srcCellRow, srcCellCol); - ValueEval secondveval = singleOperandEvaluate(operands[1], srcCellRow, srcCellCol); - ValueEval thirdveval = singleOperandEvaluate(operands[2], srcCellRow, srcCellCol); - ValueEval fourthveval = singleOperandEvaluate(operands[3], srcCellRow, srcCellCol); - if (firstveval instanceof StringValueEval - && secondveval instanceof NumericValueEval - && thirdveval instanceof NumericValueEval - && fourthveval instanceof StringValueEval) { - - StringValueEval oldStrEval = (StringValueEval) firstveval; - oldStr = oldStrEval.getStringValue(); - - NumericValueEval startNumEval = (NumericValueEval) secondveval; - // NOTE: it is safe to cast to int here - // because in Excel =REPLACE("task", 2.7, 3, "est") - // returns test - // so 2.7 must be truncated to 2 - // and =REPLACE("task", 1, 1.9, "") returns ask - // so 1.9 must be truncated to 1 - startNum = (int) startNumEval.getNumberValue(); - - NumericValueEval numCharsEval = (NumericValueEval) thirdveval; - numChars = (int) numCharsEval.getNumberValue(); - - StringValueEval newStrEval = (StringValueEval) fourthveval; - newStr = newStrEval.getStringValue(); - } else { - retval = ErrorEval.VALUE_INVALID; - } - } - - if (retval == null) { - if (startNum < 1 || numChars < 0) { - retval = ErrorEval.VALUE_INVALID; - } else { - StringBuffer strBuff = new StringBuffer(oldStr); - // remove any characters that should be replaced - if (startNum <= oldStr.length() && numChars != 0) { - strBuff.delete(startNum - 1, startNum - 1 + numChars); - } - // now insert (or append) newStr - if (startNum > strBuff.length()) { - strBuff.append(newStr); - } else { - strBuff.insert(startNum - 1, newStr); - } - retval = new StringEval(strBuff.toString()); - } - } - return retval; - } + String oldStr = evaluateStringArg(args[0], srcCellRow, srcCellCol); + int startNum = evaluateIntArg(args[1], srcCellRow, srcCellCol); + int numChars = evaluateIntArg(args[2], srcCellRow, srcCellCol); + String newStr = evaluateStringArg(args[3], srcCellRow, srcCellCol); + if (startNum < 1 || numChars < 0) { + return ErrorEval.VALUE_INVALID; + } + StringBuffer strBuff = new StringBuffer(oldStr); + // remove any characters that should be replaced + if (startNum <= oldStr.length() && numChars != 0) { + strBuff.delete(startNum - 1, startNum - 1 + numChars); + } + // now insert (or append) newStr + if (startNum > strBuff.length()) { + strBuff.append(newStr); + } else { + strBuff.insert(startNum - 1, newStr); + } + return new StringEval(strBuff.toString()); + } } diff --git a/src/java/org/apache/poi/hssf/record/formula/functions/Right.java b/src/java/org/apache/poi/hssf/record/formula/functions/Right.java deleted file mode 100644 index 771d045734..0000000000 --- a/src/java/org/apache/poi/hssf/record/formula/functions/Right.java +++ /dev/null @@ -1,108 +0,0 @@ -/* -* 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. -*/ -/* - * Created on May 15, 2005 - * - */ -package org.apache.poi.hssf.record.formula.functions; - -import org.apache.poi.hssf.record.formula.eval.BoolEval; -import org.apache.poi.hssf.record.formula.eval.ErrorEval; -import org.apache.poi.hssf.record.formula.eval.Eval; -import org.apache.poi.hssf.record.formula.eval.NumberEval; -import org.apache.poi.hssf.record.formula.eval.RefEval; -import org.apache.poi.hssf.record.formula.eval.StringEval; -import org.apache.poi.hssf.record.formula.eval.ValueEval; - -/** - * @author Amol S. Deshmukh < amolweb at ya hoo dot com > - * - */ -public class Right extends TextFunction { - - public Eval evaluate(Eval[] operands, int srcCellRow, short srcCellCol) { - Eval retval = ErrorEval.VALUE_INVALID; - int index = 1; - switch (operands.length) { - default: - break; - case 2: - Eval indexEval = operands[1]; - index = evaluateAsInteger(indexEval); - if (index < 0) { - break; - } - case 1: - ValueEval veval = singleOperandEvaluate(operands[0], srcCellRow, srcCellCol); - String str = null; - if (veval instanceof StringEval) { - StringEval stringEval = (StringEval) veval; - str = stringEval.getStringValue(); - } - else if (veval instanceof BoolEval) { - BoolEval beval = (BoolEval) veval; - str = beval.getBooleanValue() ? "TRUE" : "FALSE"; - } - else if (veval instanceof NumberEval) { - NumberEval neval = (NumberEval) veval; - str = neval.getStringValue(); - } - if (null != str) { - int strlen = str.length(); - str = str.substring(Math.max(0, strlen-index)); - retval = new StringEval(str); - } - } - return retval; - } - - protected int evaluateAsInteger(Eval eval) { - int numval = -1; - if (eval instanceof NumberEval) { - NumberEval neval = (NumberEval) eval; - double d = neval.getNumberValue(); - numval = (int) d; - } - else if (eval instanceof StringEval) { - StringEval seval = (StringEval) eval; - String s = seval.getStringValue(); - try { - double d = Double.parseDouble(s); - numval = (int) d; - } - catch (Exception e) { - } - } - else if (eval instanceof BoolEval) { - BoolEval beval = (BoolEval) eval; - numval = beval.getBooleanValue() ? 1 : 0; - } - else if (eval instanceof RefEval) { - numval = evaluateAsInteger(xlateRefEval((RefEval) eval)); - } - return numval; - } - - protected Eval xlateRefEval(RefEval reval) { - Eval retval = reval.getInnerValueEval(); - - if (retval instanceof RefEval) { - retval = xlateRefEval((RefEval) retval); - } - return retval; - } -} diff --git a/src/java/org/apache/poi/hssf/record/formula/functions/Substitute.java b/src/java/org/apache/poi/hssf/record/formula/functions/Substitute.java index 9d2e9ce361..b00d7510e3 100644 --- a/src/java/org/apache/poi/hssf/record/formula/functions/Substitute.java +++ b/src/java/org/apache/poi/hssf/record/formula/functions/Substitute.java @@ -1,30 +1,26 @@ -/* -* 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. -*/ -/* - * Created on May 15, 2005 - * - */ +/* ==================================================================== + 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.hssf.record.formula.functions; import org.apache.poi.hssf.record.formula.eval.ErrorEval; import org.apache.poi.hssf.record.formula.eval.Eval; -import org.apache.poi.hssf.record.formula.eval.NumericValueEval; +import org.apache.poi.hssf.record.formula.eval.EvaluationException; import org.apache.poi.hssf.record.formula.eval.StringEval; -import org.apache.poi.hssf.record.formula.eval.StringValueEval; import org.apache.poi.hssf.record.formula.eval.ValueEval; /** @@ -32,86 +28,75 @@ import org.apache.poi.hssf.record.formula.eval.ValueEval; * Substitutes text in a text string with new text, some number of times. * @author Manda Wilson < wilson at c bio dot msk cc dot org > */ -public class Substitute extends TextFunction { - private static final int REPLACE_ALL = -1; - - /** - *Substitutes text in a text string with new text, some number of times. - * - * @see org.apache.poi.hssf.record.formula.eval.Eval - */ - public Eval evaluate(Eval[] operands, int srcCellRow, short srcCellCol) { - Eval retval = null; - String oldStr = null; - String searchStr = null; - String newStr = null; - int numToReplace = REPLACE_ALL; - - switch (operands.length) { - default: - retval = ErrorEval.VALUE_INVALID; - case 4: - ValueEval fourthveval = singleOperandEvaluate(operands[3], srcCellRow, srcCellCol); - if (fourthveval instanceof NumericValueEval) { - NumericValueEval numToReplaceEval = (NumericValueEval) fourthveval; - // NOTE: it is safe to cast to int here - // because in Excel =SUBSTITUTE("teststr","t","T",1.9) - // returns Teststr - // so 1.9 must be truncated to 1 - numToReplace = (int) numToReplaceEval.getNumberValue(); - } else { - retval = ErrorEval.VALUE_INVALID; - } - case 3: - // first operand is text string containing characters to replace - // second operand is text to find - // third operand is replacement text - ValueEval firstveval = singleOperandEvaluate(operands[0], srcCellRow, srcCellCol); - ValueEval secondveval = singleOperandEvaluate(operands[1], srcCellRow, srcCellCol); - ValueEval thirdveval = singleOperandEvaluate(operands[2], srcCellRow, srcCellCol); - if (firstveval instanceof StringValueEval - && secondveval instanceof StringValueEval - && thirdveval instanceof StringValueEval) { - - StringValueEval oldStrEval = (StringValueEval) firstveval; - oldStr = oldStrEval.getStringValue(); - - StringValueEval searchStrEval = (StringValueEval) secondveval; - searchStr = searchStrEval.getStringValue(); - - StringValueEval newStrEval = (StringValueEval) thirdveval; - newStr = newStrEval.getStringValue(); - } else { - retval = ErrorEval.VALUE_INVALID; - } - } - - if (retval == null) { - if (numToReplace != REPLACE_ALL && numToReplace < 1) { - retval = ErrorEval.VALUE_INVALID; - } else if (searchStr.length() == 0) { - retval = new StringEval(oldStr); - } else { - StringBuffer strBuff = new StringBuffer(); - int startIndex = 0; - int nextMatch = -1; - for (int leftToReplace = numToReplace; - (leftToReplace > 0 || numToReplace == REPLACE_ALL) - && (nextMatch = oldStr.indexOf(searchStr, startIndex)) != -1; - leftToReplace--) { - // store everything from end of last match to start of this match - strBuff.append(oldStr.substring(startIndex, nextMatch)); - strBuff.append(newStr); - startIndex = nextMatch + searchStr.length(); +public final class Substitute extends TextFunction { + + protected ValueEval evaluateFunc(Eval[] args, int srcCellRow, short srcCellCol) + throws EvaluationException { + if (args.length < 3 || args.length > 4) { + return ErrorEval.VALUE_INVALID; + } + + String oldStr = evaluateStringArg(args[0], srcCellRow, srcCellCol); + String searchStr = evaluateStringArg(args[1], srcCellRow, srcCellCol); + String newStr = evaluateStringArg(args[2], srcCellRow, srcCellCol); + + String result; + switch (args.length) { + default: + throw new IllegalStateException("Cannot happen"); + case 4: + int instanceNumber = evaluateIntArg(args[3], srcCellRow, srcCellCol); + if (instanceNumber < 1) { + return ErrorEval.VALUE_INVALID; } + result = replaceOneOccurrence(oldStr, searchStr, newStr, instanceNumber); + break; + case 3: + result = replaceAllOccurrences(oldStr, searchStr, newStr); + } + return new StringEval(result); + } + + private static String replaceAllOccurrences(String oldStr, String searchStr, String newStr) { + StringBuffer sb = new StringBuffer(); + int startIndex = 0; + int nextMatch = -1; + while (true) { + nextMatch = oldStr.indexOf(searchStr, startIndex); + if (nextMatch < 0) { // store everything from end of last match to end of string - if (startIndex < oldStr.length()) { - strBuff.append(oldStr.substring(startIndex)); - } - retval = new StringEval(strBuff.toString()); + sb.append(oldStr.substring(startIndex)); + return sb.toString(); + } + // store everything from end of last match to start of this match + sb.append(oldStr.substring(startIndex, nextMatch)); + sb.append(newStr); + startIndex = nextMatch + searchStr.length(); + } + } + + private static String replaceOneOccurrence(String oldStr, String searchStr, String newStr, int instanceNumber) { + if (searchStr.length() < 1) { + return oldStr; + } + int startIndex = 0; + int nextMatch = -1; + int count=0; + while (true) { + nextMatch = oldStr.indexOf(searchStr, startIndex); + if (nextMatch < 0) { + // not enough occurrences found - leave unchanged + return oldStr; + } + count++; + if (count == instanceNumber) { + StringBuffer sb = new StringBuffer(oldStr.length() + newStr.length()); + sb.append(oldStr.substring(0, nextMatch)); + sb.append(newStr); + sb.append(oldStr.substring(nextMatch + searchStr.length())); + return sb.toString(); } - } - return retval; - } - + startIndex = nextMatch + searchStr.length(); + } + } } diff --git a/src/java/org/apache/poi/hssf/record/formula/functions/TextFunction.java b/src/java/org/apache/poi/hssf/record/formula/functions/TextFunction.java index 9da1776127..df7db7ad32 100644 --- a/src/java/org/apache/poi/hssf/record/formula/functions/TextFunction.java +++ b/src/java/org/apache/poi/hssf/record/formula/functions/TextFunction.java @@ -1,31 +1,29 @@ -/* -* 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. -*/ -/* - * Created on May 22, 2005 - * - */ +/* ==================================================================== + 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.hssf.record.formula.functions; -import org.apache.poi.hssf.record.formula.eval.AreaEval; -import org.apache.poi.hssf.record.formula.eval.BlankEval; +import org.apache.poi.hssf.record.formula.eval.BoolEval; import org.apache.poi.hssf.record.formula.eval.ErrorEval; import org.apache.poi.hssf.record.formula.eval.Eval; -import org.apache.poi.hssf.record.formula.eval.RefEval; -import org.apache.poi.hssf.record.formula.eval.StringValueEval; +import org.apache.poi.hssf.record.formula.eval.EvaluationException; +import org.apache.poi.hssf.record.formula.eval.NumberEval; +import org.apache.poi.hssf.record.formula.eval.OperandResolver; +import org.apache.poi.hssf.record.formula.eval.StringEval; import org.apache.poi.hssf.record.formula.eval.ValueEval; /** @@ -33,75 +31,164 @@ import org.apache.poi.hssf.record.formula.eval.ValueEval; * */ public abstract class TextFunction implements Function { - - protected static final String EMPTY_STRING = ""; - - protected ValueEval singleOperandEvaluate(Eval eval, int srcRow, short srcCol) { - ValueEval retval; - if (eval instanceof AreaEval) { - AreaEval ae = (AreaEval) eval; - if (ae.contains(srcRow, srcCol)) { // circular ref! - retval = ErrorEval.CIRCULAR_REF_ERROR; - } - else if (ae.isRow()) { - if (ae.containsColumn(srcCol)) { - ValueEval ve = ae.getValueAt(ae.getFirstRow(), srcCol); - retval = attemptXlateToText(ve); - } - else { - retval = ErrorEval.VALUE_INVALID; - } - } - else if (ae.isColumn()) { - if (ae.containsRow(srcRow)) { - ValueEval ve = ae.getValueAt(srcRow, ae.getFirstColumn()); - retval = attemptXlateToText(ve); - } - else { - retval = ErrorEval.VALUE_INVALID; - } - } - else { - retval = ErrorEval.VALUE_INVALID; - } - } - else { - retval = attemptXlateToText((ValueEval) eval); - } - return retval; - } - - - /** - * converts from Different ValueEval types to StringEval. - * Note: AreaEvals are not handled, if arg is an AreaEval, - * the returned value is ErrorEval.VALUE_INVALID - * @param ve - */ - protected ValueEval attemptXlateToText(ValueEval ve) { - ValueEval retval; - if (ve instanceof StringValueEval) { - retval = ve; - } - else if (ve instanceof RefEval) { - RefEval re = (RefEval) ve; - ValueEval ive = re.getInnerValueEval(); - if (ive instanceof StringValueEval) { - retval = ive; - } - else if (ive instanceof BlankEval) { - retval = ive; - } - else { - retval = ErrorEval.VALUE_INVALID; - } - } - else if (ve instanceof BlankEval) { - retval = ve; - } - else { - retval = ErrorEval.VALUE_INVALID; - } - return retval; - } + + protected static final String EMPTY_STRING = ""; + + protected static final String evaluateStringArg(Eval eval, int srcRow, short srcCol) throws EvaluationException { + ValueEval ve = OperandResolver.getSingleValue(eval, srcRow, srcCol); + return OperandResolver.coerceValueToString(ve); + } + protected static final int evaluateIntArg(Eval arg, int srcCellRow, short srcCellCol) throws EvaluationException { + ValueEval ve = OperandResolver.getSingleValue(arg, srcCellRow, srcCellCol); + return OperandResolver.coerceValueToInt(ve); + } + + public final Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) { + try { + return evaluateFunc(args, srcCellRow, srcCellCol); + } catch (EvaluationException e) { + return e.getErrorEval(); + } + } + + protected abstract ValueEval evaluateFunc(Eval[] args, int srcCellRow, short srcCellCol) throws EvaluationException; + + /* ---------------------------------------------------------------------- */ + + private static abstract class SingleArgTextFunc extends TextFunction { + + protected SingleArgTextFunc() { + // no fields to initialise + } + protected ValueEval evaluateFunc(Eval[] args, int srcCellRow, short srcCellCol) + throws EvaluationException { + if (args.length != 1) { + return ErrorEval.VALUE_INVALID; + } + String arg = evaluateStringArg(args[0], srcCellRow, srcCellCol); + return evaluate(arg); + } + protected abstract ValueEval evaluate(String arg); + } + + public static final Function LEN = new SingleArgTextFunc() { + protected ValueEval evaluate(String arg) { + return new NumberEval(arg.length()); + } + }; + public static final Function LOWER = new SingleArgTextFunc() { + protected ValueEval evaluate(String arg) { + return new StringEval(arg.toLowerCase()); + } + }; + public static final Function UPPER = new SingleArgTextFunc() { + protected ValueEval evaluate(String arg) { + return new StringEval(arg.toUpperCase()); + } + }; + /** + * An implementation of the TRIM function: + * Removes leading and trailing spaces from value if evaluated operand + * value is string. + * @author Manda Wilson < wilson at c bio dot msk cc dot org > + */ + public static final Function TRIM = new SingleArgTextFunc() { + protected ValueEval evaluate(String arg) { + return new StringEval(arg.trim()); + } + }; + + /** + * An implementation of the MID function
+ * MID returns a specific number of + * characters from a text string, starting at the specified position.

+ * + * Syntax:
MID(text, start_num, + * num_chars)
+ * + * @author Manda Wilson < wilson at c bio dot msk cc dot org > + */ + public static final Function MID = new TextFunction() { + + protected ValueEval evaluateFunc(Eval[] args, int srcCellRow, short srcCellCol) + throws EvaluationException { + if (args.length != 3) { + return ErrorEval.VALUE_INVALID; + } + + String text = evaluateStringArg(args[0], srcCellRow, srcCellCol); + int startCharNum = evaluateIntArg(args[1], srcCellRow, srcCellCol); + int numChars = evaluateIntArg(args[2], srcCellRow, srcCellCol); + int startIx = startCharNum - 1; // convert to zero-based + + // Note - for start_num arg, blank/zero causes error(#VALUE!), + // but for num_chars causes empty string to be returned. + if (startIx < 0) { + return ErrorEval.VALUE_INVALID; + } + if (numChars < 0) { + return ErrorEval.VALUE_INVALID; + } + int len = text.length(); + if (numChars < 0 || startIx > len) { + return new StringEval(""); + } + int endIx = Math.min(startIx + numChars, len); + String result = text.substring(startIx, endIx); + return new StringEval(result); + } + }; + + private static final class LeftRight extends TextFunction { + + private final boolean _isLeft; + protected LeftRight(boolean isLeft) { + _isLeft = isLeft; + } + protected ValueEval evaluateFunc(Eval[] args, int srcCellRow, short srcCellCol) + throws EvaluationException { + if (args.length != 2) { + return ErrorEval.VALUE_INVALID; + } + String arg = evaluateStringArg(args[0], srcCellRow, srcCellCol); + int index = evaluateIntArg(args[1], srcCellRow, srcCellCol); + + String result; + if (_isLeft) { + result = arg.substring(0, Math.min(arg.length(), index)); + } else { + result = arg.substring(Math.max(0, arg.length()-index)); + } + return new StringEval(result); + } + } + + public static final Function LEFT = new LeftRight(true); + public static final Function RIGHT = new LeftRight(false); + + public static final Function CONCATENATE = new TextFunction() { + + protected ValueEval evaluateFunc(Eval[] args, int srcCellRow, short srcCellCol) + throws EvaluationException { + StringBuffer sb = new StringBuffer(); + for (int i=0, iSize=args.length; i