diff --git a/src/documentation/content/xdocs/changes.xml b/src/documentation/content/xdocs/changes.xml index 1c6b2854a..183475c20 100644 --- a/src/documentation/content/xdocs/changes.xml +++ b/src/documentation/content/xdocs/changes.xml @@ -37,6 +37,8 @@ + 46065 - added implementation for VALUE function + 45966 - added implementation for FIND function 45778 - fixed ObjRecord to read ftLbsData properly 46053 - fixed evaluation cache dependency analysis when changing blank cells diff --git a/src/documentation/content/xdocs/spreadsheet/formula.xml b/src/documentation/content/xdocs/spreadsheet/formula.xml index d8ced8fb2..3dda9f181 100644 --- a/src/documentation/content/xdocs/spreadsheet/formula.xml +++ b/src/documentation/content/xdocs/spreadsheet/formula.xml @@ -99,7 +99,7 @@ org.apache.poi.hssf.record.formula.FormulaParser class. This class implements a hand written recursive descent parser.

-

Check out the javadocs for details. +

Check out the javadocs for details.

diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml index bfd9364ce..9088ef28b 100644 --- a/src/documentation/content/xdocs/status.xml +++ b/src/documentation/content/xdocs/status.xml @@ -34,6 +34,8 @@ + 46065 - added implementation for VALUE function + 45966 - added implementation for FIND function 45778 - fixed ObjRecord to read ftLbsData properly 46053 - fixed evaluation cache dependency analysis when changing blank cells diff --git a/src/java/org/apache/poi/hssf/dev/HSSF.java b/src/java/org/apache/poi/hssf/dev/HSSF.java index 07baa3a2b..c1af8a11e 100644 --- a/src/java/org/apache/poi/hssf/dev/HSSF.java +++ b/src/java/org/apache/poi/hssf/dev/HSSF.java @@ -15,7 +15,6 @@ limitations under the License. ==================================================================== */ - package org.apache.poi.hssf.dev; import java.io.FileInputStream; @@ -30,7 +29,7 @@ import org.apache.poi.hssf.usermodel.HSSFRichTextString; import org.apache.poi.hssf.usermodel.HSSFRow; import org.apache.poi.hssf.usermodel.HSSFSheet; import org.apache.poi.hssf.usermodel.HSSFWorkbook; -import org.apache.poi.hssf.util.CellRangeAddress; +import org.apache.poi.ss.util.CellRangeAddress; import org.apache.poi.poifs.filesystem.POIFSFileSystem; import org.apache.poi.ss.util.Region; @@ -222,7 +221,7 @@ public class HSSF if (args.length < 2) { -/* try + try { HSSF hssf = new HSSF(args[ 0 ]); @@ -231,26 +230,30 @@ public class HSSF for (int k = 0; k < wb.getNumberOfSheets(); k++) { - System.out.println("Sheet " + k); HSSFSheet sheet = wb.getSheetAt(k); int rows = sheet.getPhysicalNumberOfRows(); - + System.out.println("Sheet " + k + " \"" + + wb.getSheetName(k) + "\" has " + + rows + " row(s)."); for (int r = 0; r < rows; r++) { - HSSFRow row = sheet.getPhysicalRowAt(r); - int cells = row.getPhysicalNumberOfCells(); - - System.out.println("ROW " + row.getRowNum()); + HSSFRow row = sheet.getRow(r); + int cells = (row != null) ? row.getPhysicalNumberOfCells() : 0; + if (row != null) { + System.out.println("\nROW " + row.getRowNum() + + " has " + cells + " cell(s)."); + } for (int c = 0; c < cells; c++) { - HSSFCell cell = row.getPhysicalCellAt(c); + HSSFCell cell = row.getCell(c); String value = null; switch (cell.getCellType()) { case HSSFCell.CELL_TYPE_FORMULA : - value = "FORMULA "; + value = "FORMULA value=" + + cell.getCellFormula(); break; case HSSFCell.CELL_TYPE_NUMERIC : @@ -275,7 +278,7 @@ public class HSSF catch (Exception e) { e.printStackTrace(); - }*/ + } } else if (args.length == 2) { diff --git a/src/java/org/apache/poi/hssf/model/Sheet.java b/src/java/org/apache/poi/hssf/model/Sheet.java index c5e78af44..901402eff 100644 --- a/src/java/org/apache/poi/hssf/model/Sheet.java +++ b/src/java/org/apache/poi/hssf/model/Sheet.java @@ -68,7 +68,7 @@ import org.apache.poi.hssf.record.aggregates.RowRecordsAggregate; import org.apache.poi.hssf.record.aggregates.RecordAggregate.PositionTrackingVisitor; import org.apache.poi.hssf.record.aggregates.RecordAggregate.RecordVisitor; import org.apache.poi.hssf.record.formula.FormulaShifter; -import org.apache.poi.hssf.util.CellRangeAddress; +import org.apache.poi.ss.util.CellRangeAddress; import org.apache.poi.hssf.util.PaneInformation; import org.apache.poi.util.POILogFactory; import org.apache.poi.util.POILogger; diff --git a/src/java/org/apache/poi/hssf/record/CFHeaderRecord.java b/src/java/org/apache/poi/hssf/record/CFHeaderRecord.java index 708b20d85..762af3be7 100644 --- a/src/java/org/apache/poi/hssf/record/CFHeaderRecord.java +++ b/src/java/org/apache/poi/hssf/record/CFHeaderRecord.java @@ -51,8 +51,8 @@ public final class CFHeaderRecord extends Record { { field_1_numcf = in.readShort(); field_2_need_recalculation = in.readShort(); - field_3_enclosing_cell_range = new org.apache.poi.hssf.util.CellRangeAddress(in); - field_4_cell_ranges = new org.apache.poi.hssf.util.CellRangeAddressList(in); + field_3_enclosing_cell_range = new CellRangeAddress(in); + field_4_cell_ranges = new CellRangeAddressList(in); } public int getNumberOfConditionalFormats() diff --git a/src/java/org/apache/poi/hssf/record/MergeCellsRecord.java b/src/java/org/apache/poi/hssf/record/MergeCellsRecord.java index 7683c6b86..1b917a7fc 100644 --- a/src/java/org/apache/poi/hssf/record/MergeCellsRecord.java +++ b/src/java/org/apache/poi/hssf/record/MergeCellsRecord.java @@ -50,7 +50,7 @@ public final class MergeCellsRecord extends Record { int nRegions = in.readUShort(); CellRangeAddress[] cras = new CellRangeAddress[nRegions]; for (int i = 0; i < nRegions; i++) { - cras[i] = new org.apache.poi.hssf.util.CellRangeAddress(in); + cras[i] = new CellRangeAddress(in); } _numberOfRegions = nRegions; _startIndex = 0; diff --git a/src/java/org/apache/poi/hssf/record/aggregates/MergedCellsTable.java b/src/java/org/apache/poi/hssf/record/aggregates/MergedCellsTable.java index bbf5c4d0a..588276274 100644 --- a/src/java/org/apache/poi/hssf/record/aggregates/MergedCellsTable.java +++ b/src/java/org/apache/poi/hssf/record/aggregates/MergedCellsTable.java @@ -22,8 +22,8 @@ import java.util.List; import org.apache.poi.hssf.model.RecordStream; import org.apache.poi.hssf.record.MergeCellsRecord; -import org.apache.poi.hssf.util.CellRangeAddress; -import org.apache.poi.hssf.util.CellRangeAddressList; +import org.apache.poi.ss.util.CellRangeAddress; +import org.apache.poi.ss.util.CellRangeAddressList; /** * @@ -51,7 +51,8 @@ public final class MergedCellsTable extends RecordAggregate { MergeCellsRecord mcr = (MergeCellsRecord) rs.getNext(); int nRegions = mcr.getNumAreas(); for (int i = 0; i < nRegions; i++) { - temp.add(mcr.getAreaAt(i)); + CellRangeAddress cra = mcr.getAreaAt(i); + temp.add(cra); } } } @@ -102,7 +103,8 @@ public final class MergedCellsTable extends RecordAggregate { private void addMergeCellsRecord(MergeCellsRecord mcr) { int nRegions = mcr.getNumAreas(); for (int i = 0; i < nRegions; i++) { - _mergedRegions.add(mcr.getAreaAt(i)); + CellRangeAddress cra = mcr.getAreaAt(i); + _mergedRegions.add(cra); } } @@ -130,5 +132,4 @@ public final class MergedCellsTable extends RecordAggregate { public int getNumberOfMergedRegions() { return _mergedRegions.size(); } - } diff --git a/src/java/org/apache/poi/hssf/record/formula/eval/StringOperationEval.java b/src/java/org/apache/poi/hssf/record/formula/eval/StringOperationEval.java deleted file mode 100644 index e2a3c72a4..000000000 --- a/src/java/org/apache/poi/hssf/record/formula/eval/StringOperationEval.java +++ /dev/null @@ -1,96 +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 14, 2005 - * - */ -package org.apache.poi.hssf.record.formula.eval; - -/** - * @author Amol S. Deshmukh < amolweb at ya hoo dot com > - * - */ -public abstract class StringOperationEval implements OperationEval { - - - - /** - * Returns an instanceof StringValueEval or ErrorEval or BlankEval - * - * @param eval - * @param srcRow - * @param srcCol - */ - 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 = internalResolveEval(eval); - } - else { - retval = ErrorEval.NAME_INVALID; - } - } - else if (ae.isColumn()) { - if (ae.containsRow(srcRow)) { - ValueEval ve = ae.getValueAt(srcRow, ae.getFirstColumn()); - retval = internalResolveEval(eval); - } - else { - retval = ErrorEval.NAME_INVALID; - } - } - else { - retval = ErrorEval.NAME_INVALID; - } - } - else { - retval = internalResolveEval(eval); - } - return retval; - } - - private ValueEval internalResolveEval(Eval eval) { - ValueEval retval; - if (eval instanceof StringValueEval) { - retval = (StringValueEval) eval; - } - else if (eval instanceof RefEval) { - RefEval re = (RefEval) eval; - ValueEval tve = re.getInnerValueEval(); - if (tve instanceof StringValueEval || tve instanceof BlankEval) { - retval = tve; - } - else { - retval = ErrorEval.NAME_INVALID; - } - } - else if (eval instanceof BlankEval) { - retval = (BlankEval) eval; - } - else { - retval = ErrorEval.NAME_INVALID; - } - return retval; - } -} diff --git a/src/java/org/apache/poi/hssf/record/formula/functions/Find.java b/src/java/org/apache/poi/hssf/record/formula/functions/Find.java index 7bd11ecf3..5621eb5ce 100644 --- a/src/java/org/apache/poi/hssf/record/formula/functions/Find.java +++ b/src/java/org/apache/poi/hssf/record/formula/functions/Find.java @@ -1,25 +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. -*/ -/* - * 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; -public class Find extends NotImplementedFunction { +import org.apache.poi.hssf.record.formula.eval.Eval; +import org.apache.poi.hssf.record.formula.eval.ErrorEval; +import org.apache.poi.hssf.record.formula.eval.ValueEval; +import org.apache.poi.hssf.record.formula.eval.NumberEval; +import org.apache.poi.hssf.record.formula.eval.EvaluationException; +/** + * Implementation of the FIND() function.

+ * + * Syntax:
+ * FIND(find_text, within_text, start_num)

+ * + * FIND returns the character position of the first occurrence of find_text inside + * within_text. The third parameter, start_num, is optional (default=1) + * and specifies where to start searching from. Character positions are 1-based.

+ * + * @author Torstein Tauno Svendsen (torstei@officenet.no) + */ +public class Find extends TextFunction { + + protected ValueEval evaluateFunc(Eval[] args, int srcCellRow, short srcCellCol) + throws EvaluationException { + + int nArgs = args.length; + if (nArgs < 2 || nArgs > 3) { + return ErrorEval.VALUE_INVALID; + } + String needle = evaluateStringArg(args[0], srcCellRow, srcCellCol); + String haystack = evaluateStringArg(args[1], srcCellRow, srcCellCol); + int startpos; + if (nArgs == 3) { + startpos = evaluateIntArg(args[2], srcCellRow, srcCellCol); + if (startpos <= 0) { + return ErrorEval.VALUE_INVALID; + } + startpos--; // convert 1-based to zero based + } else { + startpos = 0; + } + int result = haystack.indexOf(needle, startpos); + if (result == -1) { + return ErrorEval.VALUE_INVALID; + } + return new NumberEval(result + 1); + } } diff --git a/src/java/org/apache/poi/hssf/record/formula/functions/Value.java b/src/java/org/apache/poi/hssf/record/formula/functions/Value.java index 6772dcf28..84f61db98 100644 --- a/src/java/org/apache/poi/hssf/record/formula/functions/Value.java +++ b/src/java/org/apache/poi/hssf/record/formula/functions/Value.java @@ -1,25 +1,188 @@ -/* -* 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; -public class Value extends NotImplementedFunction { +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.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.ValueEval; +/** + * Implementation for Excel VALUE() function.

+ * + * Syntax:
VALUE(text)
+ * + * 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 #VALUE! error. Blank string converts to zero. + * + * @author Josh Micich + */ +public final class Value implements Function { + + /** "1,0000" is valid, "1,00" is not */ + private static final int MIN_DISTANCE_BETWEEN_THOUSANDS_SEPARATOR = 4; + private static final Double ZERO = new Double(0.0); + + public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) { + if (args.length != 1) { + return ErrorEval.VALUE_INVALID; + } + ValueEval veText; + try { + veText = OperandResolver.getSingleValue(args[0], srcCellRow, srcCellCol); + } catch (EvaluationException e) { + return e.getErrorEval(); + } + String strText = OperandResolver.coerceValueToString(veText); + Double result = convertTextToNumber(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 null if there is any problem converting the text + */ + private static Double convertTextToNumber(String strText) { + boolean foundCurrency = false; + boolean foundUnaryPlus = false; + boolean foundUnaryMinus = 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; + + StringBuffer sb = new StringBuffer(len); + for (; i < len; i++) { + char ch = strText.charAt(i); + if (Character.isDigit(ch)) { + sb.append(ch); + continue; + } + switch (ch) { + case ' ': + String remainingText = strText.substring(i); + if (remainingText.trim().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; + 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; + } + return new Double(foundUnaryMinus ? -d : d); + } } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java b/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java index 75568f4da..fd0e0a535 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java @@ -670,7 +670,7 @@ public class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet /** * @return the merged region at the specified index */ - public org.apache.poi.hssf.util.CellRangeAddress getMergedRegion(int index) { + public CellRangeAddress getMergedRegion(int index) { return sheet.getMergedRegionAt(index); } diff --git a/src/java/org/apache/poi/hssf/util/CellRangeAddress.java b/src/java/org/apache/poi/hssf/util/CellRangeAddress.java index cc67b8eb0..439db43db 100644 --- a/src/java/org/apache/poi/hssf/util/CellRangeAddress.java +++ b/src/java/org/apache/poi/hssf/util/CellRangeAddress.java @@ -18,12 +18,12 @@ package org.apache.poi.hssf.util; import org.apache.poi.hssf.record.RecordInputStream; import org.apache.poi.hssf.record.SelectionRecord; -import org.apache.poi.util.LittleEndian; /** * See OOO documentation: excelfileformat.pdf sec 2.5.14 - 'Cell Range Address'

* * Note - {@link SelectionRecord} uses the BIFF5 version of this structure + * @deprecated use {@link org.apache.poi.ss.util.CellRangeAddress} * @author Dragos Buleandra (dragos.buleandra@trade2b.ro) */ public class CellRangeAddress extends org.apache.poi.ss.util.CellRangeAddress { diff --git a/src/java/org/apache/poi/hssf/util/CellRangeAddressList.java b/src/java/org/apache/poi/hssf/util/CellRangeAddressList.java index 79ea50abb..5f22fb733 100644 --- a/src/java/org/apache/poi/hssf/util/CellRangeAddressList.java +++ b/src/java/org/apache/poi/hssf/util/CellRangeAddressList.java @@ -33,6 +33,8 @@ import org.apache.poi.util.LittleEndian; * range address (called an ADDR structure) contains 4 16-bit-values. *

* + * @deprecated use {@link org.apache.poi.ss.util.CellRangeAddressList} + * * @author Dragos Buleandra (dragos.buleandra@trade2b.ro) */ public class CellRangeAddressList extends org.apache.poi.ss.util.CellRangeAddressList { diff --git a/src/java/org/apache/poi/ss/util/CellRangeAddress.java b/src/java/org/apache/poi/ss/util/CellRangeAddress.java index 0d910c682..3cc70cf48 100644 --- a/src/java/org/apache/poi/ss/util/CellRangeAddress.java +++ b/src/java/org/apache/poi/ss/util/CellRangeAddress.java @@ -16,6 +16,7 @@ package org.apache.poi.ss.util; +import org.apache.poi.hssf.record.RecordInputStream; import org.apache.poi.hssf.record.SelectionRecord; import org.apache.poi.util.LittleEndian; @@ -42,6 +43,17 @@ public class CellRangeAddress extends CellRangeAddressBase { LittleEndian.putUShort(data, offset + 6, getLastColumn()); return ENCODED_SIZE; } + public CellRangeAddress(RecordInputStream in) { + super(readUShortAndCheck(in), in.readUShort(), in.readUShort(), in.readUShort()); + } + + private static int readUShortAndCheck(RecordInputStream in) { + if (in.remaining() < ENCODED_SIZE) { + // Ran out of data + throw new RuntimeException("Ran out of data reading CellRangeAddress"); + } + return in.readUShort(); + } public CellRangeAddress copy() { return new CellRangeAddress(getFirstRow(), getLastRow(), getFirstColumn(), getLastColumn()); diff --git a/src/java/org/apache/poi/ss/util/CellRangeAddressList.java b/src/java/org/apache/poi/ss/util/CellRangeAddressList.java index 72b588248..65474ef98 100644 --- a/src/java/org/apache/poi/ss/util/CellRangeAddressList.java +++ b/src/java/org/apache/poi/ss/util/CellRangeAddressList.java @@ -18,6 +18,8 @@ package org.apache.poi.ss.util; import java.util.ArrayList; import java.util.List; + +import org.apache.poi.hssf.record.RecordInputStream; import org.apache.poi.util.LittleEndian; /** @@ -51,7 +53,17 @@ public class CellRangeAddressList { this(); addCellRangeAddress(firstRow, firstCol, lastRow, lastCol); } + /** + * @param in the RecordInputstream to read the record from + */ + public CellRangeAddressList(RecordInputStream in) { + this(); + int nItems = in.readUShort(); + for (int k = 0; k < nItems; k++) { + _list.add(new CellRangeAddress(in)); + } + } /** * Get the number of following ADDR structures. The number of this * structures is automatically set when reading an Excel file and/or diff --git a/src/testcases/org/apache/poi/hssf/record/TestMergeCellsRecord.java b/src/testcases/org/apache/poi/hssf/record/TestMergeCellsRecord.java index c0a8f9fe7..b36da1aad 100644 --- a/src/testcases/org/apache/poi/hssf/record/TestMergeCellsRecord.java +++ b/src/testcases/org/apache/poi/hssf/record/TestMergeCellsRecord.java @@ -17,10 +17,18 @@ package org.apache.poi.hssf.record; +import java.util.ArrayList; +import java.util.List; + +import junit.framework.AssertionFailedError; import junit.framework.TestCase; +import org.apache.poi.hssf.model.RecordStream; +import org.apache.poi.hssf.record.aggregates.MergedCellsTable; +import org.apache.poi.hssf.record.aggregates.RecordAggregate.RecordVisitor; import org.apache.poi.ss.util.CellRangeAddress; + /** * Make sure the merge cells record behaves * @author Danny Mui (dmui at apache dot org) @@ -28,25 +36,45 @@ import org.apache.poi.ss.util.CellRangeAddress; */ public final class TestMergeCellsRecord extends TestCase { - /** - * Make sure when a clone is called, we actually clone it. - * @throws Exception - */ - public void testCloneReferences() throws Exception { - CellRangeAddress[] cras = { new CellRangeAddress(0, 1, 0, 2), }; - MergeCellsRecord merge = new MergeCellsRecord(cras, 0, cras.length); - MergeCellsRecord clone = (MergeCellsRecord)merge.clone(); + /** + * Make sure when a clone is called, we actually clone it. + */ + public void testCloneReferences() { + CellRangeAddress[] cras = { new CellRangeAddress(0, 1, 0, 2), }; + MergeCellsRecord merge = new MergeCellsRecord(cras, 0, cras.length); + MergeCellsRecord clone = (MergeCellsRecord)merge.clone(); + + assertNotSame("Merged and cloned objects are the same", merge, clone); + + CellRangeAddress mergeRegion = merge.getAreaAt(0); + CellRangeAddress cloneRegion = clone.getAreaAt(0); + assertNotSame("Should not point to same objects when cloning", mergeRegion, cloneRegion); + assertEquals("New Clone Row From doesnt match", mergeRegion.getFirstRow(), cloneRegion.getFirstRow()); + assertEquals("New Clone Row To doesnt match", mergeRegion.getLastRow(), cloneRegion.getLastRow()); + assertEquals("New Clone Col From doesnt match", mergeRegion.getFirstColumn(), cloneRegion.getFirstColumn()); + assertEquals("New Clone Col To doesnt match", mergeRegion.getLastColumn(), cloneRegion.getLastColumn()); - assertNotSame("Merged and cloned objects are the same", merge, clone); - - CellRangeAddress mergeRegion = merge.getAreaAt(0); - CellRangeAddress cloneRegion = clone.getAreaAt(0); - assertNotSame("Should not point to same objects when cloning", mergeRegion, cloneRegion); - assertEquals("New Clone Row From doesnt match", mergeRegion.getFirstRow(), cloneRegion.getFirstRow()); - assertEquals("New Clone Row To doesnt match", mergeRegion.getLastRow(), cloneRegion.getLastRow()); - assertEquals("New Clone Col From doesnt match", mergeRegion.getFirstColumn(), cloneRegion.getFirstColumn()); - assertEquals("New Clone Col To doesnt match", mergeRegion.getLastColumn(), cloneRegion.getLastColumn()); - - assertFalse(merge.getAreaAt(0) == clone.getAreaAt(0)); - } + assertFalse(merge.getAreaAt(0) == clone.getAreaAt(0)); + } + + private static final RecordVisitor dummyRecordVisitor = new RecordVisitor() { + public void visitRecord(Record r) { + // do nothing + } + }; + public void testMCTable_bug46009() { + MergedCellsTable mct = new MergedCellsTable(); + List recList = new ArrayList(); + CellRangeAddress[] cras = new CellRangeAddress[] { + new CellRangeAddress(0, 0, 0, 3), + }; + recList.add(new MergeCellsRecord(cras, 0, 1)); + RecordStream rs = new RecordStream(recList, 0); + mct.read(rs); + try { + mct.visitContainedRecords(dummyRecordVisitor); + } catch (ArrayStoreException e) { + throw new AssertionFailedError("Identified bug 46009"); + } + } } diff --git a/src/testcases/org/apache/poi/hssf/record/formula/functions/AllIndividualFunctionEvaluationTests.java b/src/testcases/org/apache/poi/hssf/record/formula/functions/AllIndividualFunctionEvaluationTests.java index c57c40b62..834b5281d 100755 --- a/src/testcases/org/apache/poi/hssf/record/formula/functions/AllIndividualFunctionEvaluationTests.java +++ b/src/testcases/org/apache/poi/hssf/record/formula/functions/AllIndividualFunctionEvaluationTests.java @@ -32,6 +32,7 @@ public final class AllIndividualFunctionEvaluationTests { result.addTestSuite(TestAverage.class); result.addTestSuite(TestCountFuncs.class); result.addTestSuite(TestDate.class); + result.addTestSuite(TestFind.class); result.addTestSuite(TestFinanceLib.class); result.addTestSuite(TestIndex.class); result.addTestSuite(TestIndexFunctionFromSpreadsheet.class); @@ -49,6 +50,7 @@ public final class AllIndividualFunctionEvaluationTests { result.addTestSuite(TestStatsLib.class); result.addTestSuite(TestTFunc.class); result.addTestSuite(TestTrim.class); + result.addTestSuite(TestValue.class); result.addTestSuite(TestXYNumericFunction.class); return result; } diff --git a/src/testcases/org/apache/poi/hssf/record/formula/functions/TestFind.java b/src/testcases/org/apache/poi/hssf/record/formula/functions/TestFind.java new file mode 100644 index 000000000..3a6df7354 --- /dev/null +++ b/src/testcases/org/apache/poi/hssf/record/formula/functions/TestFind.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.hssf.record.formula.functions; + +import junit.framework.TestCase; + +import org.apache.poi.hssf.usermodel.HSSFCell; +import org.apache.poi.hssf.usermodel.HSSFErrorConstants; +import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.ss.usermodel.CellValue; + +/** + * Tests for {@link Find} + * + * @author Torstein Svendsen (torstei@officenet.no) + */ +public final class TestFind extends TestCase { + + public void testFind() { + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFCell cell = wb.createSheet().createRow(0).createCell(0); + + HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(wb); + + confirmResult(fe, cell, "find(\"h\", \"haystack\")", 1); + confirmResult(fe, cell, "find(\"a\", \"haystack\",2)", 2); + confirmResult(fe, cell, "find(\"a\", \"haystack\",3)", 6); + + // number args converted to text + confirmResult(fe, cell, "find(7, 32768)", 3); + confirmResult(fe, cell, "find(\"34\", 1341235233412, 3)", 10); + confirmResult(fe, cell, "find(5, 87654)", 4); + + // Errors + confirmError(fe, cell, "find(\"n\", \"haystack\")", HSSFErrorConstants.ERROR_VALUE); + confirmError(fe, cell, "find(\"k\", \"haystack\",9)", HSSFErrorConstants.ERROR_VALUE); + confirmError(fe, cell, "find(\"k\", \"haystack\",#REF!)", HSSFErrorConstants.ERROR_REF); + confirmError(fe, cell, "find(\"k\", \"haystack\",0)", HSSFErrorConstants.ERROR_VALUE); + confirmError(fe, cell, "find(#DIV/0!, #N/A, #REF!)", HSSFErrorConstants.ERROR_DIV_0); + confirmError(fe, cell, "find(2, #N/A, #REF!)", HSSFErrorConstants.ERROR_NA); + } + + private static void confirmResult(HSSFFormulaEvaluator fe, HSSFCell cell, String formulaText, + int expectedResult) { + cell.setCellFormula(formulaText); + fe.notifyUpdateCell(cell); + CellValue result = fe.evaluate(cell); + assertEquals(result.getCellType(), HSSFCell.CELL_TYPE_NUMERIC); + assertEquals(expectedResult, result.getNumberValue(), 0.0); + } + + private static void confirmError(HSSFFormulaEvaluator fe, HSSFCell cell, String formulaText, + int expectedErrorCode) { + cell.setCellFormula(formulaText); + fe.notifyUpdateCell(cell); + CellValue result = fe.evaluate(cell); + assertEquals(result.getCellType(), HSSFCell.CELL_TYPE_ERROR); + assertEquals(expectedErrorCode, result.getErrorValue()); + } +} diff --git a/src/testcases/org/apache/poi/hssf/record/formula/functions/TestValue.java b/src/testcases/org/apache/poi/hssf/record/formula/functions/TestValue.java new file mode 100644 index 000000000..5f74fbf1a --- /dev/null +++ b/src/testcases/org/apache/poi/hssf/record/formula/functions/TestValue.java @@ -0,0 +1,94 @@ +/* ==================================================================== + 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 junit.framework.TestCase; + +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.StringEval; + +/** + * Tests for {@link Value} + * + * @author Josh Micich + */ +public final class TestValue extends TestCase { + + private static Eval invokeValue(String strText) { + Eval[] args = new Eval[] { new StringEval(strText), }; + return new Value().evaluate(args, -1, (short) -1); + } + + private static void confirmValue(String strText, double expected) { + Eval result = invokeValue(strText); + assertEquals(NumberEval.class, result.getClass()); + assertEquals(expected, ((NumberEval) result).getNumberValue(), 0.0); + } + + private static void confirmValueError(String strText) { + Eval result = invokeValue(strText); + assertEquals(ErrorEval.class, result.getClass()); + assertEquals(ErrorEval.VALUE_INVALID, result); + } + + public void testBasic() { + + confirmValue("100", 100); + confirmValue("-2.3", -2.3); + confirmValue(".5", 0.5); + confirmValue(".5e2", 50); + confirmValue(".5e-2", 0.005); + confirmValue(".5e+2", 50); + confirmValue("+5", 5); + confirmValue("$1,000", 1000); + confirmValue("100.5e1", 1005); + confirmValue("1,0000", 10000); + confirmValue("1,000,0000", 10000000); + confirmValue("1,000,0000,00000", 1000000000000.0); + confirmValue(" 100 ", 100); + confirmValue(" + 100", 100); + confirmValue("10000", 10000); + confirmValue("$-5", -5); + confirmValue("$.5", 0.5); + confirmValue("123e+5", 12300000); + confirmValue("1,000e2", 100000); + confirmValue("$10e2", 1000); + confirmValue("$1,000e2", 100000); + } + + public void testErrors() { + confirmValueError("1+1"); + confirmValueError("1 1"); + confirmValueError("1,00.0"); + confirmValueError("1,00"); + confirmValueError("$1,00.5e1"); + confirmValueError("1,00.5e1"); + confirmValueError("1,0,000"); + confirmValueError("1,00,000"); + confirmValueError("++100"); + confirmValueError("$$5"); + confirmValueError("-"); + confirmValueError("+"); + confirmValueError("$"); + confirmValueError(",300"); + confirmValueError("0.233,4"); + confirmValueError("1e2.5"); + } +}