ASF Bugzilla – Attachment 24731 Details for
Bug 48292
[PATCH] Support of array formulas
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
Remember
[x]
|
Forgot Password
Login:
[x]
last reworked patch
ArraySupportPatch.txt (text/plain), 322.32 KB, created by
Petr.Udalau
on 2009-12-18 06:38:37 UTC
(
hide
)
Description:
last reworked patch
Filename:
MIME Type:
Creator:
Petr.Udalau
Created:
2009-12-18 06:38:37 UTC
Size:
322.32 KB
patch
obsolete
>Index: src/java/org/apache/poi/hssf/record/ArrayRecord.java >=================================================================== >--- src/java/org/apache/poi/hssf/record/ArrayRecord.java (revision 892169) >+++ src/java/org/apache/poi/hssf/record/ArrayRecord.java (working copy) >@@ -1,87 +1,131 @@ >-/* ==================================================================== >- 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; >- >-import org.apache.poi.hssf.record.formula.Ptg; >-import org.apache.poi.ss.formula.Formula; >-import org.apache.poi.util.HexDump; >-import org.apache.poi.util.LittleEndianOutput; >- >-/** >- * ARRAY (0x0221)<p/> >- * >- * Treated in a similar way to SharedFormulaRecord >- * >- * @author Josh Micich >- */ >-public final class ArrayRecord extends SharedValueRecordBase { >- >- public final static short sid = 0x0221; >- private static final int OPT_ALWAYS_RECALCULATE = 0x0001; >- private static final int OPT_CALCULATE_ON_OPEN = 0x0002; >- >- private int _options; >- private int _field3notUsed; >- private Formula _formula; >- >- public ArrayRecord(RecordInputStream in) { >- super(in); >- _options = in.readUShort(); >- _field3notUsed = in.readInt(); >- int formulaTokenLen = in.readUShort(); >- int totalFormulaLen = in.available(); >- _formula = Formula.read(formulaTokenLen, in, totalFormulaLen); >- } >- >- public boolean isAlwaysRecalculate() { >- return (_options & OPT_ALWAYS_RECALCULATE) != 0; >- } >- public boolean isCalculateOnOpen() { >- return (_options & OPT_CALCULATE_ON_OPEN) != 0; >- } >- >- protected int getExtraDataSize() { >- return 2 + 4 >- + _formula.getEncodedSize(); >- } >- protected void serializeExtraData(LittleEndianOutput out) { >- out.writeShort(_options); >- out.writeInt(_field3notUsed); >- _formula.serialize(out); >- } >- >- public short getSid() { >- return sid; >- } >- >- public String toString() { >- StringBuffer sb = new StringBuffer(); >- sb.append(getClass().getName()).append(" [ARRAY]\n"); >- sb.append(" range=").append(getRange().toString()).append("\n"); >- sb.append(" options=").append(HexDump.shortToHex(_options)).append("\n"); >- sb.append(" notUsed=").append(HexDump.intToHex(_field3notUsed)).append("\n"); >- sb.append(" formula:").append("\n"); >- Ptg[] ptgs = _formula.getTokens(); >- for (int i = 0; i < ptgs.length; i++) { >- Ptg ptg = ptgs[i]; >- sb.append(ptg.toString()).append(ptg.getRVAType()).append("\n"); >- } >- sb.append("]"); >- return sb.toString(); >- } >-} >+/* ==================================================================== >+ 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; >+ >+import org.apache.poi.hssf.record.formula.AreaPtgBase; >+import org.apache.poi.hssf.record.formula.Ptg; >+import org.apache.poi.hssf.record.formula.RefPtgBase; >+import org.apache.poi.hssf.util.CellRangeAddress8Bit; >+import org.apache.poi.ss.formula.Formula; >+import org.apache.poi.util.HexDump; >+import org.apache.poi.util.LittleEndianOutput; >+ >+/** >+ * ARRAY (0x0221)<p/> >+ * >+ * Treated in a similar way to SharedFormulaRecord >+ * >+ * @author Josh Micich >+ * @author Vladimirs Abramovs(Vladimirs.Abramovs at exigenservices.com) - Array Formula support >+ */ >+public final class ArrayRecord extends SharedValueRecordBase { >+ >+ public final static short sid = 0x0221; >+ private static final int OPT_ALWAYS_RECALCULATE = 0x0001; >+ private static final int OPT_CALCULATE_ON_OPEN = 0x0002; >+ >+ private int _options; >+ private int _field3notUsed; >+ private Formula _formula; >+ >+ public ArrayRecord(RecordInputStream in) { >+ super(in); >+ _options = in.readUShort(); >+ _field3notUsed = in.readInt(); >+ int formulaTokenLen = in.readUShort(); >+ int totalFormulaLen = in.available(); >+ _formula = Formula.read(formulaTokenLen, in, totalFormulaLen); >+ } >+ >+ public ArrayRecord(Formula formula, CellRangeAddress8Bit range ) { >+ super(range); >+ _options = OPT_CALCULATE_ON_OPEN; >+ _field3notUsed = 0; >+ _formula = formula; >+ } >+ >+ public boolean isAlwaysRecalculate() { >+ return (_options & OPT_ALWAYS_RECALCULATE) != 0; >+ } >+ public boolean isCalculateOnOpen() { >+ return (_options & OPT_CALCULATE_ON_OPEN) != 0; >+ } >+ >+ protected int getExtraDataSize() { >+ return 2 + 4 >+ + _formula.getEncodedSize(); >+ } >+ protected void serializeExtraData(LittleEndianOutput out) { >+ out.writeShort(_options); >+ out.writeInt(_field3notUsed); >+ _formula.serialize(out); >+ } >+ >+ public short getSid() { >+ return sid; >+ } >+ >+ public String toString() { >+ StringBuffer sb = new StringBuffer(); >+ sb.append(getClass().getName()).append(" [ARRAY]\n"); >+ sb.append(" range=").append(getRange().toString()).append("\n"); >+ sb.append(" options=").append(HexDump.shortToHex(_options)).append("\n"); >+ sb.append(" notUsed=").append(HexDump.intToHex(_field3notUsed)).append("\n"); >+ sb.append(" formula:").append("\n"); >+ Ptg[] ptgs = _formula.getTokens(); >+ for (int i = 0; i < ptgs.length; i++) { >+ Ptg ptg = ptgs[i]; >+ sb.append(ptg.toString()).append(ptg.getRVAType()).append("\n"); >+ } >+ sb.append("]"); >+ return sb.toString(); >+ } >+ >+ /** >+ * @return the equivalent {@link Ptg} array that the formula would have, >+ * were it not shared. >+ */ >+ public Ptg[] getFormulaTokens() { >+ int formulaRow = this.getFirstRow(); >+ int formulaColumn = this.getLastColumn(); >+ >+ // Use SharedFormulaRecord static method to convert formula >+ >+ Ptg[] ptgs = _formula.getTokens(); >+ >+ // Convert from relative addressing to absolute >+ // because all formulas in array need to be referenced to the same >+ // ref/range >+ for (int i = 0; i < ptgs.length; i++) { >+ Ptg ptg = ptgs[i]; >+ if (ptg instanceof AreaPtgBase) { >+ AreaPtgBase aptg = (AreaPtgBase) ptg; >+ aptg.setFirstRowRelative(false); >+ aptg.setLastRowRelative(false); >+ aptg.setFirstColRelative(false); >+ aptg.setLastColRelative(false); >+ >+ } else if (ptg instanceof RefPtgBase) { >+ RefPtgBase rptg = (RefPtgBase) ptg; >+ rptg.setRowRelative(false); >+ rptg.setColRelative(false); >+ } >+ } >+ return SharedFormulaRecord.convertSharedFormulas(ptgs, formulaRow, formulaColumn); >+ } >+} >Index: src/java/org/apache/poi/hssf/record/aggregates/FormulaRecordAggregate.java >=================================================================== >--- src/java/org/apache/poi/hssf/record/aggregates/FormulaRecordAggregate.java (revision 892169) >+++ src/java/org/apache/poi/hssf/record/aggregates/FormulaRecordAggregate.java (working copy) >@@ -17,6 +17,7 @@ > > package org.apache.poi.hssf.record.aggregates; > >+import org.apache.poi.hssf.record.ArrayRecord; > import org.apache.poi.hssf.record.CellValueRecordInterface; > import org.apache.poi.hssf.record.FormulaRecord; > import org.apache.poi.hssf.record.Record; >@@ -25,13 +26,17 @@ > import org.apache.poi.hssf.record.StringRecord; > import org.apache.poi.hssf.record.formula.ExpPtg; > import org.apache.poi.hssf.record.formula.Ptg; >+import org.apache.poi.hssf.util.CellRangeAddress8Bit; > import org.apache.poi.hssf.util.CellReference; >+import org.apache.poi.ss.formula.Formula; >+import org.apache.poi.ss.util.CellRangeAddress; > > /** > * The formula record aggregate is used to join together the formula record and it's > * (optional) string record and (optional) Shared Formula Record (template reads, excel optimization). > * > * @author Glen Stampoultzis (glens at apache.org) >+ * @author Vladimirs Abramovs(Vladimirs.Abramovs at exigenservices.com) - Array Formula support > */ > public final class FormulaRecordAggregate extends RecordAggregate implements CellValueRecordInterface { > >@@ -181,10 +186,15 @@ > } > > public Ptg[] getFormulaTokens() { >- if (_sharedFormulaRecord == null) { >- return _formulaRecord.getParsedExpression(); >+ if (_sharedFormulaRecord != null) { >+ return _sharedFormulaRecord.getFormulaTokens(_formulaRecord); > } >- return _sharedFormulaRecord.getFormulaTokens(_formulaRecord); >+ CellReference expRef = _formulaRecord.getFormula().getExpReference(); >+ if (expRef != null) { >+ ArrayRecord arec = _sharedValueManager.getArrayRecord(expRef.getRow(), expRef.getCol()); >+ return arec.getFormulaTokens(); >+ } >+ return _formulaRecord.getParsedExpression(); > } > > /** >@@ -216,4 +226,36 @@ > _sharedValueManager.unlink(_sharedFormulaRecord); > } > } >+ public boolean isPartOfArrayFormula() { >+ if (_sharedFormulaRecord != null) { >+ return false; >+ } >+ return _formulaRecord.getFormula().getExpReference() != null; >+ } >+ >+ public CellRangeAddress getArrayFormulaRange() { >+ if (_sharedFormulaRecord != null) { >+ throw new IllegalStateException("not an array formula cell."); >+ } >+ CellReference expRef = _formulaRecord.getFormula().getExpReference(); >+ if (expRef == null) { >+ throw new IllegalStateException("not an array formula cell."); >+ } >+ ArrayRecord arec = _sharedValueManager.getArrayRecord(expRef.getRow(), expRef.getCol()); >+ CellRangeAddress8Bit a = arec.getRange(); >+ return new CellRangeAddress(a.getFirstRow(), a.getLastRow(), a.getFirstColumn(),a.getLastColumn()); >+ } >+ public void setArrayFormula(CellRangeAddress r, Ptg[] ptgs) { >+ >+ ArrayRecord arr = new ArrayRecord(Formula.create(ptgs), new CellRangeAddress8Bit(r.getFirstRow(), r.getLastRow(), r.getFirstColumn(), r.getLastColumn())); >+ _sharedValueManager.addArrayRecord(arr); >+ } >+ /** >+ * Removes an array formula >+ * @return the range of the array formula containing the specified cell. Never <code>null</code> >+ */ >+ public CellRangeAddress removeArrayFormula(int rowIndex, int columnIndex) { >+ CellRangeAddress8Bit a = _sharedValueManager.removeArrayFormula(rowIndex, columnIndex); >+ return new CellRangeAddress(a.getFirstRow(), a.getLastRow(), a.getFirstColumn(), a.getLastColumn()); >+ } > } >Index: src/java/org/apache/poi/hssf/record/aggregates/SharedValueManager.java >=================================================================== >--- src/java/org/apache/poi/hssf/record/aggregates/SharedValueManager.java (revision 892169) >+++ src/java/org/apache/poi/hssf/record/aggregates/SharedValueManager.java (working copy) >@@ -1,278 +1,322 @@ >-/* ==================================================================== >- 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.aggregates; >- >-import java.util.Arrays; >-import java.util.Comparator; >-import java.util.HashMap; >-import java.util.Map; >- >-import org.apache.poi.hssf.record.ArrayRecord; >-import org.apache.poi.hssf.record.FormulaRecord; >-import org.apache.poi.hssf.record.SharedFormulaRecord; >-import org.apache.poi.hssf.record.SharedValueRecordBase; >-import org.apache.poi.hssf.record.TableRecord; >-import org.apache.poi.hssf.record.formula.ExpPtg; >-import org.apache.poi.hssf.util.CellRangeAddress8Bit; >-import org.apache.poi.ss.util.CellReference; >- >-/** >- * Manages various auxiliary records while constructing a >- * {@link RowRecordsAggregate}: >- * <ul> >- * <li>{@link SharedFormulaRecord}s</li> >- * <li>{@link ArrayRecord}s</li> >- * <li>{@link TableRecord}s</li> >- * </ul> >- * >- * @author Josh Micich >- */ >-public final class SharedValueManager { >- >- private static final class SharedFormulaGroup { >- private final SharedFormulaRecord _sfr; >- private final FormulaRecordAggregate[] _frAggs; >- private int _numberOfFormulas; >- /** >- * Coordinates of the first cell having a formula that uses this shared formula. >- * This is often <i>but not always</i> the top left cell in the range covered by >- * {@link #_sfr} >- */ >- private final CellReference _firstCell; >- >- public SharedFormulaGroup(SharedFormulaRecord sfr, CellReference firstCell) { >- if (!sfr.isInRange(firstCell.getRow(), firstCell.getCol())) { >- throw new IllegalArgumentException("First formula cell " + firstCell.formatAsString() >- + " is not shared formula range " + sfr.getRange().toString() + "."); >- } >- _sfr = sfr; >- _firstCell = firstCell; >- int width = sfr.getLastColumn() - sfr.getFirstColumn() + 1; >- int height = sfr.getLastRow() - sfr.getFirstRow() + 1; >- _frAggs = new FormulaRecordAggregate[width * height]; >- _numberOfFormulas = 0; >- } >- >- public void add(FormulaRecordAggregate agg) { >- if (_numberOfFormulas == 0) { >- if (_firstCell.getRow() != agg.getRow() || _firstCell.getCol() != agg.getColumn()) { >- throw new IllegalStateException("shared formula coding error"); >- } >- } >- if (_numberOfFormulas >= _frAggs.length) { >- throw new RuntimeException("Too many formula records for shared formula group"); >- } >- _frAggs[_numberOfFormulas++] = agg; >- } >- >- public void unlinkSharedFormulas() { >- for (int i = 0; i < _numberOfFormulas; i++) { >- _frAggs[i].unlinkSharedFormula(); >- } >- } >- >- public SharedFormulaRecord getSFR() { >- return _sfr; >- } >- >- public final String toString() { >- StringBuffer sb = new StringBuffer(64); >- sb.append(getClass().getName()).append(" ["); >- sb.append(_sfr.getRange().toString()); >- sb.append("]"); >- return sb.toString(); >- } >- >- /** >- * Note - the 'first cell' of a shared formula group is not always the top-left cell >- * of the enclosing range. >- * @return <code>true</code> if the specified coordinates correspond to the 'first cell' >- * of this shared formula group. >- */ >- public boolean isFirstCell(int row, int column) { >- return _firstCell.getRow() == row && _firstCell.getCol() == column; >- } >- } >- >- public static final SharedValueManager EMPTY = new SharedValueManager( >- new SharedFormulaRecord[0], new CellReference[0], new ArrayRecord[0], new TableRecord[0]); >- private final ArrayRecord[] _arrayRecords; >- private final TableRecord[] _tableRecords; >- private final Map<SharedFormulaRecord, SharedFormulaGroup> _groupsBySharedFormulaRecord; >- /** cached for optimization purposes */ >- private SharedFormulaGroup[] _groups; >- >- private SharedValueManager(SharedFormulaRecord[] sharedFormulaRecords, >- CellReference[] firstCells, ArrayRecord[] arrayRecords, TableRecord[] tableRecords) { >- int nShF = sharedFormulaRecords.length; >- if (nShF != firstCells.length) { >- throw new IllegalArgumentException("array sizes don't match: " + nShF + "!=" + firstCells.length + "."); >- } >- _arrayRecords = arrayRecords; >- _tableRecords = tableRecords; >- Map<SharedFormulaRecord, SharedFormulaGroup> m = new HashMap<SharedFormulaRecord, SharedFormulaGroup>(nShF * 3 / 2); >- for (int i = 0; i < nShF; i++) { >- SharedFormulaRecord sfr = sharedFormulaRecords[i]; >- m.put(sfr, new SharedFormulaGroup(sfr, firstCells[i])); >- } >- _groupsBySharedFormulaRecord = m; >- } >- >- /** >- * @param firstCells >- * @param recs list of sheet records (possibly contains records for other parts of the Excel file) >- * @param startIx index of first row/cell record for current sheet >- * @param endIx one past index of last row/cell record for current sheet. It is important >- * that this code does not inadvertently collect <tt>SharedFormulaRecord</tt>s from any other >- * sheet (which could happen if endIx is chosen poorly). (see bug 44449) >- */ >- public static SharedValueManager create(SharedFormulaRecord[] sharedFormulaRecords, >- CellReference[] firstCells, ArrayRecord[] arrayRecords, TableRecord[] tableRecords) { >- if (sharedFormulaRecords.length + firstCells.length + arrayRecords.length + tableRecords.length < 1) { >- return EMPTY; >- } >- return new SharedValueManager(sharedFormulaRecords, firstCells, arrayRecords, tableRecords); >- } >- >- >- /** >- * @param firstCell as extracted from the {@link ExpPtg} from the cell's formula. >- * @return never <code>null</code> >- */ >- public SharedFormulaRecord linkSharedFormulaRecord(CellReference firstCell, FormulaRecordAggregate agg) { >- >- SharedFormulaGroup result = findFormulaGroup(getGroups(), firstCell); >- result.add(agg); >- return result.getSFR(); >- } >- >- private static SharedFormulaGroup findFormulaGroup(SharedFormulaGroup[] groups, CellReference firstCell) { >- int row = firstCell.getRow(); >- int column = firstCell.getCol(); >- // Traverse the list of shared formulas and try to find the correct one for us >- >- // perhaps this could be optimised to some kind of binary search >- for (int i = 0; i < groups.length; i++) { >- SharedFormulaGroup svg = groups[i]; >- if (svg.isFirstCell(row, column)) { >- return svg; >- } >- } >- // TODO - fix file "15228.xls" so it opens in Excel after rewriting with POI >- throw new RuntimeException("Failed to find a matching shared formula record"); >- } >- >- private SharedFormulaGroup[] getGroups() { >- if (_groups == null) { >- SharedFormulaGroup[] groups = new SharedFormulaGroup[_groupsBySharedFormulaRecord.size()]; >- _groupsBySharedFormulaRecord.values().toArray(groups); >- Arrays.sort(groups, SVGComparator); // make search behaviour more deterministic >- _groups = groups; >- } >- return _groups; >- } >- >- private static final Comparator<SharedFormulaGroup> SVGComparator = new Comparator<SharedFormulaGroup>() { >- >- public int compare(SharedFormulaGroup a, SharedFormulaGroup b) { >- CellRangeAddress8Bit rangeA = a.getSFR().getRange(); >- CellRangeAddress8Bit rangeB = b.getSFR().getRange(); >- >- int cmp; >- cmp = rangeA.getFirstRow() - rangeB.getFirstRow(); >- if (cmp != 0) { >- return cmp; >- } >- cmp = rangeA.getFirstColumn() - rangeB.getFirstColumn(); >- if (cmp != 0) { >- return cmp; >- } >- return 0; >- } >- }; >- >- /** >- * Gets the {@link SharedValueRecordBase} record if it should be encoded immediately after the >- * formula record contained in the specified {@link FormulaRecordAggregate} agg. Note - the >- * shared value record always appears after the first formula record in the group. For arrays >- * and tables the first formula is always the in the top left cell. However, since shared >- * formula groups can be sparse and/or overlap, the first formula may not actually be in the >- * top left cell. >- * >- * @return the SHRFMLA, TABLE or ARRAY record for the formula cell, if it is the first cell of >- * a table or array region. <code>null</code> if the formula cell is not shared/array/table, >- * or if the specified formula is not the the first in the group. >- */ >- public SharedValueRecordBase getRecordForFirstCell(FormulaRecordAggregate agg) { >- CellReference firstCell = agg.getFormulaRecord().getFormula().getExpReference(); >- // perhaps this could be optimised by consulting the (somewhat unreliable) isShared flag >- // and/or distinguishing between tExp and tTbl. >- if (firstCell == null) { >- // not a shared/array/table formula >- return null; >- } >- >- >- int row = firstCell.getRow(); >- int column = firstCell.getCol(); >- if (agg.getRow() != row || agg.getColumn() != column) { >- // not the first formula cell in the group >- return null; >- } >- SharedFormulaGroup[] groups = getGroups(); >- for (int i = 0; i < groups.length; i++) { >- // note - logic for finding correct shared formula group is slightly >- // more complicated since the first cell >- SharedFormulaGroup sfg = groups[i]; >- if (sfg.isFirstCell(row, column)) { >- return sfg.getSFR(); >- } >- } >- >- // Since arrays and tables cannot be sparse (all cells in range participate) >- // The first cell will be the top left in the range. So we can match the >- // ARRAY/TABLE record directly. >- >- for (int i = 0; i < _tableRecords.length; i++) { >- TableRecord tr = _tableRecords[i]; >- if (tr.isFirstCell(row, column)) { >- return tr; >- } >- } >- for (int i = 0; i < _arrayRecords.length; i++) { >- ArrayRecord ar = _arrayRecords[i]; >- if (ar.isFirstCell(row, column)) { >- return ar; >- } >- } >- return null; >- } >- >- /** >- * Converts all {@link FormulaRecord}s handled by <tt>sharedFormulaRecord</tt> >- * to plain unshared formulas >- */ >- public void unlink(SharedFormulaRecord sharedFormulaRecord) { >- SharedFormulaGroup svg = _groupsBySharedFormulaRecord.remove(sharedFormulaRecord); >- _groups = null; // be sure to reset cached value >- if (svg == null) { >- throw new IllegalStateException("Failed to find formulas for shared formula"); >- } >- svg.unlinkSharedFormulas(); >- } >-} >+/* ==================================================================== >+ 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.aggregates; >+ >+import java.util.ArrayList; >+import java.util.Arrays; >+import java.util.Comparator; >+import java.util.HashMap; >+import java.util.List; >+import java.util.Map; >+ >+import org.apache.poi.hssf.record.ArrayRecord; >+import org.apache.poi.hssf.record.FormulaRecord; >+import org.apache.poi.hssf.record.SharedFormulaRecord; >+import org.apache.poi.hssf.record.SharedValueRecordBase; >+import org.apache.poi.hssf.record.TableRecord; >+import org.apache.poi.hssf.record.formula.ExpPtg; >+import org.apache.poi.hssf.util.CellRangeAddress8Bit; >+import org.apache.poi.ss.util.CellReference; >+ >+/** >+ * Manages various auxiliary records while constructing a >+ * {@link RowRecordsAggregate}: >+ * <ul> >+ * <li>{@link SharedFormulaRecord}s</li> >+ * <li>{@link ArrayRecord}s</li> >+ * <li>{@link TableRecord}s</li> >+ * </ul> >+ * >+ * @author Josh Micich >+ * @author Vladimirs Abramovs(Vladimirs.Abramovs at exigenservices.com) - handling of ArrayRecords >+ */ >+public final class SharedValueManager { >+ >+ private static final class SharedFormulaGroup { >+ private final SharedFormulaRecord _sfr; >+ private final FormulaRecordAggregate[] _frAggs; >+ private int _numberOfFormulas; >+ /** >+ * Coordinates of the first cell having a formula that uses this shared formula. >+ * This is often <i>but not always</i> the top left cell in the range covered by >+ * {@link #_sfr} >+ */ >+ private final CellReference _firstCell; >+ >+ public SharedFormulaGroup(SharedFormulaRecord sfr, CellReference firstCell) { >+ if (!sfr.isInRange(firstCell.getRow(), firstCell.getCol())) { >+ throw new IllegalArgumentException("First formula cell " + firstCell.formatAsString() >+ + " is not shared formula range " + sfr.getRange().toString() + "."); >+ } >+ _sfr = sfr; >+ _firstCell = firstCell; >+ int width = sfr.getLastColumn() - sfr.getFirstColumn() + 1; >+ int height = sfr.getLastRow() - sfr.getFirstRow() + 1; >+ _frAggs = new FormulaRecordAggregate[width * height]; >+ _numberOfFormulas = 0; >+ } >+ >+ public void add(FormulaRecordAggregate agg) { >+ if (_numberOfFormulas == 0) { >+ if (_firstCell.getRow() != agg.getRow() || _firstCell.getCol() != agg.getColumn()) { >+ throw new IllegalStateException("shared formula coding error"); >+ } >+ } >+ if (_numberOfFormulas >= _frAggs.length) { >+ throw new RuntimeException("Too many formula records for shared formula group"); >+ } >+ _frAggs[_numberOfFormulas++] = agg; >+ } >+ >+ public void unlinkSharedFormulas() { >+ for (int i = 0; i < _numberOfFormulas; i++) { >+ _frAggs[i].unlinkSharedFormula(); >+ } >+ } >+ >+ public SharedFormulaRecord getSFR() { >+ return _sfr; >+ } >+ >+ public final String toString() { >+ StringBuffer sb = new StringBuffer(64); >+ sb.append(getClass().getName()).append(" ["); >+ sb.append(_sfr.getRange().toString()); >+ sb.append("]"); >+ return sb.toString(); >+ } >+ >+ /** >+ * Note - the 'first cell' of a shared formula group is not always the top-left cell >+ * of the enclosing range. >+ * @return <code>true</code> if the specified coordinates correspond to the 'first cell' >+ * of this shared formula group. >+ */ >+ public boolean isFirstCell(int row, int column) { >+ return _firstCell.getRow() == row && _firstCell.getCol() == column; >+ } >+ } >+ >+ public static final SharedValueManager EMPTY = new SharedValueManager( >+ new SharedFormulaRecord[0], new CellReference[0], new ArrayRecord[0], new TableRecord[0]); >+ private final List<ArrayRecord> _arrayRecords; >+ private final TableRecord[] _tableRecords; >+ private final Map<SharedFormulaRecord, SharedFormulaGroup> _groupsBySharedFormulaRecord; >+ /** cached for optimization purposes */ >+ private SharedFormulaGroup[] _groups; >+ >+ private SharedValueManager(SharedFormulaRecord[] sharedFormulaRecords, >+ CellReference[] firstCells, ArrayRecord[] arrayRecords, TableRecord[] tableRecords) { >+ int nShF = sharedFormulaRecords.length; >+ if (nShF != firstCells.length) { >+ throw new IllegalArgumentException("array sizes don't match: " + nShF + "!=" + firstCells.length + "."); >+ } >+ _arrayRecords = toList(arrayRecords); >+ _tableRecords = tableRecords; >+ Map<SharedFormulaRecord, SharedFormulaGroup> m = new HashMap<SharedFormulaRecord, SharedFormulaGroup>(nShF * 3 / 2); >+ for (int i = 0; i < nShF; i++) { >+ SharedFormulaRecord sfr = sharedFormulaRecords[i]; >+ m.put(sfr, new SharedFormulaGroup(sfr, firstCells[i])); >+ } >+ _groupsBySharedFormulaRecord = m; >+ } >+ >+ private static <Z> List<Z> toList(Z[] zz) { >+ List<Z> result = new ArrayList<Z>(zz.length); >+ for (int i = 0; i < zz.length; i++) { >+ result.add(zz[i]); >+ } >+ return result; >+ } >+ >+ /** >+ * @param firstCells >+ * @param recs list of sheet records (possibly contains records for other parts of the Excel file) >+ * @param startIx index of first row/cell record for current sheet >+ * @param endIx one past index of last row/cell record for current sheet. It is important >+ * that this code does not inadvertently collect <tt>SharedFormulaRecord</tt>s from any other >+ * sheet (which could happen if endIx is chosen poorly). (see bug 44449) >+ */ >+ public static SharedValueManager create(SharedFormulaRecord[] sharedFormulaRecords, >+ CellReference[] firstCells, ArrayRecord[] arrayRecords, TableRecord[] tableRecords) { >+ if (sharedFormulaRecords.length + firstCells.length + arrayRecords.length + tableRecords.length < 1) { >+ return EMPTY; >+ } >+ return new SharedValueManager(sharedFormulaRecords, firstCells, arrayRecords, tableRecords); >+ } >+ >+ >+ /** >+ * @param firstCell as extracted from the {@link ExpPtg} from the cell's formula. >+ * @return never <code>null</code> >+ */ >+ public SharedFormulaRecord linkSharedFormulaRecord(CellReference firstCell, FormulaRecordAggregate agg) { >+ >+ SharedFormulaGroup result = findFormulaGroup(getGroups(), firstCell); >+ result.add(agg); >+ return result.getSFR(); >+ } >+ >+ private static SharedFormulaGroup findFormulaGroup(SharedFormulaGroup[] groups, CellReference firstCell) { >+ int row = firstCell.getRow(); >+ int column = firstCell.getCol(); >+ // Traverse the list of shared formulas and try to find the correct one for us >+ >+ // perhaps this could be optimised to some kind of binary search >+ for (int i = 0; i < groups.length; i++) { >+ SharedFormulaGroup svg = groups[i]; >+ if (svg.isFirstCell(row, column)) { >+ return svg; >+ } >+ } >+ // TODO - fix file "15228.xls" so it opens in Excel after rewriting with POI >+ throw new RuntimeException("Failed to find a matching shared formula record"); >+ } >+ >+ private SharedFormulaGroup[] getGroups() { >+ if (_groups == null) { >+ SharedFormulaGroup[] groups = new SharedFormulaGroup[_groupsBySharedFormulaRecord.size()]; >+ _groupsBySharedFormulaRecord.values().toArray(groups); >+ Arrays.sort(groups, SVGComparator); // make search behaviour more deterministic >+ _groups = groups; >+ } >+ return _groups; >+ } >+ >+ private static final Comparator<SharedFormulaGroup> SVGComparator = new Comparator<SharedFormulaGroup>() { >+ >+ public int compare(SharedFormulaGroup a, SharedFormulaGroup b) { >+ CellRangeAddress8Bit rangeA = a.getSFR().getRange(); >+ CellRangeAddress8Bit rangeB = b.getSFR().getRange(); >+ >+ int cmp; >+ cmp = rangeA.getFirstRow() - rangeB.getFirstRow(); >+ if (cmp != 0) { >+ return cmp; >+ } >+ cmp = rangeA.getFirstColumn() - rangeB.getFirstColumn(); >+ if (cmp != 0) { >+ return cmp; >+ } >+ return 0; >+ } >+ }; >+ >+ /** >+ * Gets the {@link SharedValueRecordBase} record if it should be encoded immediately after the >+ * formula record contained in the specified {@link FormulaRecordAggregate} agg. Note - the >+ * shared value record always appears after the first formula record in the group. For arrays >+ * and tables the first formula is always the in the top left cell. However, since shared >+ * formula groups can be sparse and/or overlap, the first formula may not actually be in the >+ * top left cell. >+ * >+ * @return the SHRFMLA, TABLE or ARRAY record for the formula cell, if it is the first cell of >+ * a table or array region. <code>null</code> if the formula cell is not shared/array/table, >+ * or if the specified formula is not the the first in the group. >+ */ >+ public SharedValueRecordBase getRecordForFirstCell(FormulaRecordAggregate agg) { >+ CellReference firstCell = agg.getFormulaRecord().getFormula().getExpReference(); >+ // perhaps this could be optimised by consulting the (somewhat unreliable) isShared flag >+ // and/or distinguishing between tExp and tTbl. >+ if (firstCell == null) { >+ // not a shared/array/table formula >+ return null; >+ } >+ >+ >+ int row = firstCell.getRow(); >+ int column = firstCell.getCol(); >+ if (agg.getRow() != row || agg.getColumn() != column) { >+ // not the first formula cell in the group >+ return null; >+ } >+ SharedFormulaGroup[] groups = getGroups(); >+ for (int i = 0; i < groups.length; i++) { >+ // note - logic for finding correct shared formula group is slightly >+ // more complicated since the first cell >+ SharedFormulaGroup sfg = groups[i]; >+ if (sfg.isFirstCell(row, column)) { >+ return sfg.getSFR(); >+ } >+ } >+ >+ // Since arrays and tables cannot be sparse (all cells in range participate) >+ // The first cell will be the top left in the range. So we can match the >+ // ARRAY/TABLE record directly. >+ >+ for (TableRecord tr : _tableRecords) { >+ if (tr.isFirstCell(row, column)) { >+ return tr; >+ } >+ } >+ for (ArrayRecord ar : _arrayRecords) { >+ if (ar.isFirstCell(row, column)) { >+ return ar; >+ } >+ } >+ return null; >+ } >+ >+ /** >+ * Converts all {@link FormulaRecord}s handled by <tt>sharedFormulaRecord</tt> >+ * to plain unshared formulas >+ */ >+ public void unlink(SharedFormulaRecord sharedFormulaRecord) { >+ SharedFormulaGroup svg = _groupsBySharedFormulaRecord.remove(sharedFormulaRecord); >+ if (svg == null) { >+ throw new IllegalStateException("Failed to find formulas for shared formula"); >+ } >+ _groups = null; // be sure to reset cached value >+ svg.unlinkSharedFormulas(); >+ } >+ >+ /** >+ * Add specified Array Record. >+ */ >+ public void addArrayRecord(ArrayRecord ar) { >+ // could do a check here to make sure none of the ranges overlap >+ _arrayRecords.add(ar); >+ } >+ >+ /** >+ * Removes the {@link ArrayRecord} for the cell group containing the specified cell. >+ * The caller should clear (set blank) all cells in the returned range. >+ * @return the range of the array formula which was just removed. Never <code>null</code>. >+ */ >+ public CellRangeAddress8Bit removeArrayFormula(int rowIndex, int columnIndex) { >+ for (ArrayRecord ar : _arrayRecords) { >+ if (ar.isInRange(rowIndex, columnIndex)) { >+ _arrayRecords.remove(ar); >+ return ar.getRange(); >+ } >+ } >+ throw new IllegalArgumentException("Specified cell is not part of an array formula."); >+ } >+ >+ /** >+ * @return the shared ArrayRecord identified by (firstRow, firstColumn). never <code>null</code>. >+ */ >+ public ArrayRecord getArrayRecord(int firstRow, int firstColumn) { >+ for(ArrayRecord ar : _arrayRecords) { >+ if(ar.isFirstCell(firstRow, firstColumn)) { >+ return ar; >+ } >+ } >+ return null; >+ } >+} >Index: src/java/org/apache/poi/hssf/record/formula/functions/AggregateFunction.java >=================================================================== >--- src/java/org/apache/poi/hssf/record/formula/functions/AggregateFunction.java (revision 892169) >+++ src/java/org/apache/poi/hssf/record/formula/functions/AggregateFunction.java (working copy) >@@ -1,145 +1,150 @@ >-/* ==================================================================== >- 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.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; >- >-/** >- * @author Amol S. Deshmukh < amolweb at ya hoo dot com > >- */ >-public abstract class AggregateFunction extends MultiOperandNumericFunction { >- >- private static final class LargeSmall extends Fixed2ArgFunction { >- private final boolean _isLarge; >- protected LargeSmall(boolean isLarge) { >- _isLarge = isLarge; >- } >- >- public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, >- ValueEval arg1) { >- double dn; >- try { >- ValueEval ve1 = OperandResolver.getSingleValue(arg1, srcRowIndex, srcColumnIndex); >- dn = OperandResolver.coerceValueToDouble(ve1); >- } catch (EvaluationException e1) { >- // all errors in the second arg translate to #VALUE! >- return ErrorEval.VALUE_INVALID; >- } >- // weird Excel behaviour on second arg >- if (dn < 1.0) { >- // values between 0.0 and 1.0 result in #NUM! >- return ErrorEval.NUM_ERROR; >- } >- // all other values are rounded up to the next integer >- int k = (int) Math.ceil(dn); >- >- double result; >- try { >- double[] ds = ValueCollector.collectValues(arg0); >- if (k > ds.length) { >- return ErrorEval.NUM_ERROR; >- } >- result = _isLarge ? StatsLib.kthLargest(ds, k) : StatsLib.kthSmallest(ds, k); >- NumericFunction.checkValue(result); >- } catch (EvaluationException e) { >- return e.getErrorEval(); >- } >- >- return new NumberEval(result); >- } >- } >- private static final class ValueCollector extends MultiOperandNumericFunction { >- private static final ValueCollector instance = new ValueCollector(); >- public ValueCollector() { >- super(false, false); >- } >- public static double[] collectValues(ValueEval...operands) throws EvaluationException { >- return instance.getNumberArray(operands); >- } >- protected double evaluate(double[] values) { >- throw new IllegalStateException("should not be called"); >- } >- } >- >- protected AggregateFunction() { >- super(false, false); >- } >- >- public static final Function AVEDEV = new AggregateFunction() { >- protected double evaluate(double[] values) { >- return StatsLib.avedev(values); >- } >- }; >- public static final Function AVERAGE = new AggregateFunction() { >- protected double evaluate(double[] values) throws EvaluationException { >- if (values.length < 1) { >- throw new EvaluationException(ErrorEval.DIV_ZERO); >- } >- return MathX.average(values); >- } >- }; >- public static final Function DEVSQ = new AggregateFunction() { >- protected double evaluate(double[] values) { >- return StatsLib.devsq(values); >- } >- }; >- public static final Function LARGE = new LargeSmall(true); >- public static final Function MAX = new AggregateFunction() { >- protected double evaluate(double[] values) { >- return values.length > 0 ? MathX.max(values) : 0; >- } >- }; >- public static final Function MEDIAN = new AggregateFunction() { >- protected double evaluate(double[] values) { >- return StatsLib.median(values); >- } >- }; >- public static final Function MIN = new AggregateFunction() { >- protected double evaluate(double[] values) { >- return values.length > 0 ? MathX.min(values) : 0; >- } >- }; >- public static final Function PRODUCT = new AggregateFunction() { >- protected double evaluate(double[] values) { >- return MathX.product(values); >- } >- }; >- public static final Function SMALL = new LargeSmall(false); >- public static final Function STDEV = new AggregateFunction() { >- protected double evaluate(double[] values) throws EvaluationException { >- if (values.length < 1) { >- throw new EvaluationException(ErrorEval.DIV_ZERO); >- } >- return StatsLib.stdev(values); >- } >- }; >- public static final Function SUM = new AggregateFunction() { >- protected double evaluate(double[] values) { >- return MathX.sum(values); >- } >- }; >- public static final Function SUMSQ = new AggregateFunction() { >- protected double evaluate(double[] values) { >- return MathX.sumsq(values); >- } >- }; >-} >+/* ==================================================================== >+ 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.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; >+ >+/** >+ * @author Amol S. Deshmukh < amolweb at ya hoo dot com > >+ * @author Zahars Sulkins(Zahars.Sulkins at exigenservices.com) - array support; >+ */ >+public abstract class AggregateFunction extends MultiOperandNumericFunction { >+ >+ private static final class LargeSmall extends Fixed2ArgFunction implements FunctionWithArraySupport { >+ private final boolean _isLarge; >+ protected LargeSmall(boolean isLarge) { >+ _isLarge = isLarge; >+ } >+ >+ public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, >+ ValueEval arg1) { >+ double dn; >+ try { >+ ValueEval ve1 = OperandResolver.getSingleValue(arg1, srcRowIndex, srcColumnIndex); >+ dn = OperandResolver.coerceValueToDouble(ve1); >+ } catch (EvaluationException e1) { >+ // all errors in the second arg translate to #VALUE! >+ return ErrorEval.VALUE_INVALID; >+ } >+ // weird Excel behaviour on second arg >+ if (dn < 1.0) { >+ // values between 0.0 and 1.0 result in #NUM! >+ return ErrorEval.NUM_ERROR; >+ } >+ // all other values are rounded up to the next integer >+ int k = (int) Math.ceil(dn); >+ >+ double result; >+ try { >+ double[] ds = ValueCollector.collectValues(arg0); >+ if (k > ds.length) { >+ return ErrorEval.NUM_ERROR; >+ } >+ result = _isLarge ? StatsLib.kthLargest(ds, k) : StatsLib.kthSmallest(ds, k); >+ NumericFunction.checkValue(result); >+ } catch (EvaluationException e) { >+ return e.getErrorEval(); >+ } >+ >+ return new NumberEval(result); >+ } >+ >+ public boolean supportArray(int paramIndex) { >+ return paramIndex != 1; >+ } >+ } >+ private static final class ValueCollector extends MultiOperandNumericFunction { >+ private static final ValueCollector instance = new ValueCollector(); >+ public ValueCollector() { >+ super(false, false); >+ } >+ public static double[] collectValues(ValueEval...operands) throws EvaluationException { >+ return instance.getNumberArray(operands); >+ } >+ protected double evaluate(double[] values) { >+ throw new IllegalStateException("should not be called"); >+ } >+ } >+ >+ protected AggregateFunction() { >+ super(false, false); >+ } >+ >+ public static final Function AVEDEV = new AggregateFunction() { >+ protected double evaluate(double[] values) { >+ return StatsLib.avedev(values); >+ } >+ }; >+ public static final Function AVERAGE = new AggregateFunction() { >+ protected double evaluate(double[] values) throws EvaluationException { >+ if (values.length < 1) { >+ throw new EvaluationException(ErrorEval.DIV_ZERO); >+ } >+ return MathX.average(values); >+ } >+ }; >+ public static final Function DEVSQ = new AggregateFunction() { >+ protected double evaluate(double[] values) { >+ return StatsLib.devsq(values); >+ } >+ }; >+ public static final Function LARGE = new LargeSmall(true); >+ public static final Function MAX = new AggregateFunction() { >+ protected double evaluate(double[] values) { >+ return values.length > 0 ? MathX.max(values) : 0; >+ } >+ }; >+ public static final Function MEDIAN = new AggregateFunction() { >+ protected double evaluate(double[] values) { >+ return StatsLib.median(values); >+ } >+ }; >+ public static final Function MIN = new AggregateFunction() { >+ protected double evaluate(double[] values) { >+ return values.length > 0 ? MathX.min(values) : 0; >+ } >+ }; >+ public static final Function PRODUCT = new AggregateFunction() { >+ protected double evaluate(double[] values) { >+ return MathX.product(values); >+ } >+ }; >+ public static final Function SMALL = new LargeSmall(false); >+ public static final Function STDEV = new AggregateFunction() { >+ protected double evaluate(double[] values) throws EvaluationException { >+ if (values.length < 1) { >+ throw new EvaluationException(ErrorEval.DIV_ZERO); >+ } >+ return StatsLib.stdev(values); >+ } >+ }; >+ public static final Function SUM = new AggregateFunction() { >+ protected double evaluate(double[] values) { >+ return MathX.sum(values); >+ } >+ }; >+ public static final Function SUMSQ = new AggregateFunction() { >+ protected double evaluate(double[] values) { >+ return MathX.sumsq(values); >+ } >+ }; >+} >Index: src/java/org/apache/poi/hssf/record/formula/functions/ArrayMode.java >=================================================================== >--- src/java/org/apache/poi/hssf/record/formula/functions/ArrayMode.java (revision 0) >+++ src/java/org/apache/poi/hssf/record/formula/functions/ArrayMode.java (revision 0) >@@ -0,0 +1,30 @@ >+/* ==================================================================== >+ 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.ValueEval; >+ >+/** >+ * Interface for those functions that behaves differently in array formula >+ * >+ * @author Zahars Sulkins(Zahars.Sulkins at exigenservices.com) >+ */ >+public interface ArrayMode { >+ >+ ValueEval evaluateInArrayFormula(ValueEval[] args, int srcRowIndex, int srcColumnIndex); >+} >Index: src/java/org/apache/poi/hssf/record/formula/functions/BooleanFunction.java >=================================================================== >--- src/java/org/apache/poi/hssf/record/formula/functions/BooleanFunction.java (revision 892169) >+++ src/java/org/apache/poi/hssf/record/formula/functions/BooleanFunction.java (working copy) >@@ -36,7 +36,7 @@ > * > * @author Amol S. Deshmukh < amolweb at ya hoo dot com > > */ >-public abstract class BooleanFunction implements Function { >+public abstract class BooleanFunction implements FunctionWithArraySupport { > > public final ValueEval evaluate(ValueEval[] args, int srcRow, int srcCol) { > if (args.length < 1) { >@@ -51,6 +51,10 @@ > return BoolEval.valueOf(boolResult); > } > >+ public boolean supportArray(int paramIndex) { >+ return true; >+ } >+ > private boolean calculate(ValueEval[] args) throws EvaluationException { > > boolean result = getInitialResultValue(); >Index: src/java/org/apache/poi/hssf/record/formula/functions/Column.java >=================================================================== >--- src/java/org/apache/poi/hssf/record/formula/functions/Column.java (revision 892169) >+++ src/java/org/apache/poi/hssf/record/formula/functions/Column.java (working copy) >@@ -22,8 +22,9 @@ > 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.ValueEval; >+import org.apache.poi.ss.formula.ArrayEval; > >-public final class Column implements Function0Arg, Function1Arg { >+public final class Column implements Function0Arg, Function1Arg, ArrayMode { > > public ValueEval evaluate(int srcRowIndex, int srcColumnIndex) { > return new NumberEval(srcColumnIndex+1); >@@ -51,4 +52,16 @@ > } > return ErrorEval.VALUE_INVALID; > } >+ >+ public ValueEval evaluateInArrayFormula(ValueEval[] evals, int srcCellRow, int srcCellCol) { >+ if ((evals.length == 1) && (evals[0] instanceof AreaEval)) { >+ AreaEval ae = (AreaEval) evals[0]; >+ ValueEval[][] result = new ValueEval[1][ae.getWidth()]; >+ for (int c = 0; c < ae.getWidth(); c++) { >+ result[0][c] = new NumberEval(ae.getFirstColumn() + c + 1); >+ } >+ return new ArrayEval(result); >+ } >+ return evaluate(evals, srcCellRow, srcCellCol); >+ } > } >Index: src/java/org/apache/poi/hssf/record/formula/functions/Columns.java >=================================================================== >--- src/java/org/apache/poi/hssf/record/formula/functions/Columns.java (revision 892169) >+++ src/java/org/apache/poi/hssf/record/formula/functions/Columns.java (working copy) >@@ -28,7 +28,7 @@ > * > * @author Josh Micich > */ >-public final class Columns extends Fixed1ArgFunction { >+public final class Columns extends Fixed1ArgFunction implements FunctionWithArraySupport { > > public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0) { > >@@ -42,4 +42,8 @@ > } > return new NumberEval(result); > } >+ >+ public boolean supportArray(int paramIndex) { >+ return true; >+ } > } >Index: src/java/org/apache/poi/hssf/record/formula/functions/Count.java >=================================================================== >--- src/java/org/apache/poi/hssf/record/formula/functions/Count.java (revision 892169) >+++ src/java/org/apache/poi/hssf/record/formula/functions/Count.java (working copy) >@@ -34,7 +34,7 @@ > * TODO: Check this properly matches excel on edge cases > * like formula cells, error cells etc > */ >-public final class Count implements Function { >+public final class Count implements FunctionWithArraySupport { > > public ValueEval evaluate(ValueEval[] args, int srcCellRow, int srcCellCol) { > int nArgs = args.length; >@@ -74,4 +74,8 @@ > return false; > } > }; >+ >+ public boolean supportArray(int paramIndex) { >+ return true; >+ } > } >Index: src/java/org/apache/poi/hssf/record/formula/functions/Counta.java >=================================================================== >--- src/java/org/apache/poi/hssf/record/formula/functions/Counta.java (revision 892169) >+++ src/java/org/apache/poi/hssf/record/formula/functions/Counta.java (working copy) >@@ -32,7 +32,7 @@ > * > * @author Josh Micich > */ >-public final class Counta implements Function { >+public final class Counta implements FunctionWithArraySupport { > > public ValueEval evaluate(ValueEval[] args, int srcCellRow, int srcCellCol) { > int nArgs = args.length; >@@ -69,4 +69,8 @@ > return true; > } > }; >+ >+ public boolean supportArray(int paramIndex) { >+ return true; >+ } > } >Index: src/java/org/apache/poi/hssf/record/formula/functions/Countif.java >=================================================================== >--- src/java/org/apache/poi/hssf/record/formula/functions/Countif.java (revision 892169) >+++ src/java/org/apache/poi/hssf/record/formula/functions/Countif.java (working copy) >@@ -44,7 +44,7 @@ > * > * @author Josh Micich > */ >-public final class Countif extends Fixed2ArgFunction { >+public final class Countif extends Fixed2ArgFunction implements FunctionWithArraySupport { > > private static final class CmpOp { > public static final int NONE = 0; >@@ -525,4 +525,11 @@ > } > return null; > } >+ >+ public boolean supportArray(int paramIndex) { >+ if (paramIndex==0) { >+ return true; >+ } >+ return false; // TODO - countif does not allow first param as array - only as range >+ } > } >Index: src/java/org/apache/poi/hssf/record/formula/functions/FunctionWithArraySupport.java >=================================================================== >--- src/java/org/apache/poi/hssf/record/formula/functions/FunctionWithArraySupport.java (revision 0) >+++ src/java/org/apache/poi/hssf/record/formula/functions/FunctionWithArraySupport.java (revision 0) >@@ -0,0 +1,30 @@ >+/* ==================================================================== >+ 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; >+ >+/** >+ * TODO - Later this interface should be merged with Function interface >+ * @author Zahars Sulkins(Zahars.Sulkins at exigenservices.com) >+ */ >+public interface FunctionWithArraySupport extends Function { >+ >+ /** >+ * @return <code>true</code> if parameter accept array, <code>false</code> otherwise >+ */ >+ public boolean supportArray(int paramIndex); >+} >Index: src/java/org/apache/poi/hssf/record/formula/functions/Hlookup.java >=================================================================== >--- src/java/org/apache/poi/hssf/record/formula/functions/Hlookup.java (revision 892169) >+++ src/java/org/apache/poi/hssf/record/formula/functions/Hlookup.java (working copy) >@@ -39,7 +39,7 @@ > * > * @author Josh Micich > */ >-public final class Hlookup extends Var3or4ArgFunction { >+public final class Hlookup extends Var3or4ArgFunction implements FunctionWithArraySupport { > private static final ValueEval DEFAULT_ARG3 = BoolEval.TRUE; > > public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1, >@@ -77,4 +77,8 @@ > } > return LookupUtils.createRowVector(tableArray, rowIndex); > } >+ >+ public boolean supportArray(int paramIndex) { >+ return paramIndex == 1; >+ } > } >Index: src/java/org/apache/poi/hssf/record/formula/functions/Index.java >=================================================================== >--- src/java/org/apache/poi/hssf/record/formula/functions/Index.java (revision 892169) >+++ src/java/org/apache/poi/hssf/record/formula/functions/Index.java (working copy) >@@ -25,6 +25,7 @@ > import org.apache.poi.hssf.record.formula.eval.OperandResolver; > import org.apache.poi.hssf.record.formula.eval.RefEval; > import org.apache.poi.hssf.record.formula.eval.ValueEval; >+import org.apache.poi.ss.formula.ArrayEval; > import org.apache.poi.ss.formula.TwoDEval; > > /** >@@ -44,41 +45,48 @@ > * </p> > * > * @author Josh Micich >+ * @author Zahars Sulkins(Zahars.Sulkins at exigenservices.com) - array support > */ >-public final class Index implements Function2Arg, Function3Arg, Function4Arg { >+public final class Index implements Function2Arg, Function3Arg, Function4Arg, FunctionWithArraySupport, ArrayMode { > > public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1) { >+ return evaluateX(srcRowIndex, srcColumnIndex, arg0, arg1, false); >+ } >+ >+ >+ private ValueEval evaluateX(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1, boolean supportRowColumn) { >+ >+ if (arg0 instanceof ArrayEval) { >+ supportRowColumn = true; >+ } > TwoDEval reference = convertFirstArg(arg0); > >+ boolean colArgWasPassed = false; > int columnIx = 0; > try { > int rowIx = resolveIndexArg(arg1, srcRowIndex, srcColumnIndex); >- >- if (!reference.isColumn()) { >- if (!reference.isRow()) { >- // always an error with 2-D area refs >- // Note - the type of error changes if the pRowArg is negative >- return ErrorEval.REF_INVALID; >- } >- // When the two-arg version of INDEX() has been invoked and the reference >- // is a single column ref, the row arg seems to get used as the column index >- columnIx = rowIx; >- rowIx = 0; >- } >- >- return getValueFromArea(reference, rowIx, columnIx); >+ return getValueFromArea(reference, rowIx, columnIx, colArgWasPassed, srcRowIndex, srcColumnIndex, supportRowColumn); > } catch (EvaluationException e) { > return e.getErrorEval(); > } > } > public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1, > ValueEval arg2) { >+ return evaluateX(srcRowIndex, srcColumnIndex, arg0, arg1, arg2, false); >+ } >+ private ValueEval evaluateX(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1, >+ ValueEval arg2, boolean supportRowColumn) { >+ >+ if (arg0 instanceof ArrayEval) { >+ supportRowColumn = true; >+ } > TwoDEval reference = convertFirstArg(arg0); > >+ boolean colArgWasPassed = true; > try { > int columnIx = resolveIndexArg(arg2, srcRowIndex, srcColumnIndex); > int rowIx = resolveIndexArg(arg1, srcRowIndex, srcColumnIndex); >- return getValueFromArea(reference, rowIx, columnIx); >+ return getValueFromArea(reference, rowIx, columnIx, colArgWasPassed, srcRowIndex, srcColumnIndex, supportRowColumn); > } catch (EvaluationException e) { > return e.getErrorEval(); > } >@@ -110,60 +118,137 @@ > } > > public ValueEval evaluate(ValueEval[] args, int srcRowIndex, int srcColumnIndex) { >+ return evaluateX(args, srcRowIndex, srcColumnIndex, false); >+ } >+ >+ private ValueEval evaluateX(ValueEval[] args, int srcRowIndex, int srcColumnIndex, boolean supportRowColumn) { > switch (args.length) { > case 2: >- return evaluate(srcRowIndex, srcColumnIndex, args[0], args[1]); >+ return evaluateX(srcRowIndex, srcColumnIndex, args[0], args[1], supportRowColumn); > case 3: >- return evaluate(srcRowIndex, srcColumnIndex, args[0], args[1], args[2]); >+ return evaluateX(srcRowIndex, srcColumnIndex, args[0], args[1], args[2], supportRowColumn); > case 4: > return evaluate(srcRowIndex, srcColumnIndex, args[0], args[1], args[2], args[3]); > } > return ErrorEval.VALUE_INVALID; > } > >- private static ValueEval getValueFromArea(TwoDEval ae, int pRowIx, int pColumnIx) >- throws EvaluationException { >- assert pRowIx >= 0; >- assert pColumnIx >= 0; >- >- int width = ae.getWidth(); >- int height = ae.getHeight(); >- >- int relFirstRowIx; >- int relLastRowIx; > >- if ((pRowIx == 0)) { >- relFirstRowIx = 0; >- relLastRowIx = height-1; >- } else { >- // Slightly irregular logic for bounds checking errors >- if (pRowIx > height) { >- // high bounds check fail gives #REF! if arg was explicitly passed >- throw new EvaluationException(ErrorEval.REF_INVALID); >+ /** >+ * @param colArgWasPassed <code>false</code> if the INDEX argument list had just 2 items >+ * (exactly 1 comma). If anything is passed for the <tt>column_num</tt> argument >+ * (including {@link BlankEval} or {@link MissingArgEval}) this parameter will be >+ * <code>true</code>. This parameter is needed because error codes are slightly >+ * different when only 2 args are passed. >+ */ >+ private static ValueEval getValueFromArea(TwoDEval ae, int pRowIx, int pColumnIx, >+ boolean colArgWasPassed, int srcRowIx, int srcColIx, boolean supportRowColumn) throws EvaluationException { >+ boolean rowArgWasEmpty = pRowIx == 0; >+ boolean colArgWasEmpty = pColumnIx == 0; >+ int rowIx; >+ int columnIx; >+ >+ // implementation of this function isn't support all features of the Excel >+ // here I'm adding only support for return of entire row or columm >+ if (supportRowColumn >+ && (rowArgWasEmpty && !ae.isRow() && pColumnIx <= ae.getWidth() && !colArgWasEmpty || colArgWasEmpty >+ && !ae.isColumn() && pRowIx <= ae.getHeight() && !rowArgWasEmpty)) { >+ // return row or column >+ ValueEval[][] result; >+ if (rowArgWasEmpty ) { // entire column >+ result = new ValueEval[ae.getHeight()][1]; >+ for( int r=0; r<ae.getHeight(); r++ ) { >+ result[r][0] = ae.getValue(r, pColumnIx-1); >+ } >+ } else { //entire row >+ result = new ValueEval[1][ae.getWidth()]; >+ for (int c=0; c<ae.getWidth(); c++) { >+ result[0][c] = ae.getValue( pRowIx-1,c); >+ } > } >- int rowIx = pRowIx-1; >- relFirstRowIx = rowIx; >- relLastRowIx = rowIx; >+ return new ArrayEval(result); > } > >- int relFirstColIx; >- int relLastColIx; >- if ((pColumnIx == 0)) { >- relFirstColIx = 0; >- relLastColIx = width-1; >+ // when the area ref is a single row or a single column, >+ // there are special rules for conversion of rowIx and columnIx >+ if (ae.isRow()) { >+ if (ae.isColumn()) { >+ // single cell ref >+ rowIx = rowArgWasEmpty ? 0 : pRowIx-1; >+ columnIx = colArgWasEmpty ? 0 : pColumnIx-1; >+ } else { >+ if (colArgWasPassed) { >+ rowIx = rowArgWasEmpty ? 0 : pRowIx-1; >+ columnIx = pColumnIx-1; >+ } else { >+ // special case - row arg seems to get used as the column index >+ rowIx = 0; >+ // transfer both the index value and the empty flag from 'row' to 'column': >+ columnIx = pRowIx-1; >+ colArgWasEmpty = rowArgWasEmpty; >+ } >+ } >+ } else if (ae.isColumn()) { >+ if (rowArgWasEmpty) { >+ if (ae instanceof AreaEval) { >+ rowIx = srcRowIx - ((AreaEval) ae).getFirstRow(); >+ } else { >+ // TODO - ArrayEval >+ // rowIx = relative row of evaluating cell in its array formula cell group >+ throw new RuntimeException("incomplete code - "); >+ } >+ } else { >+ rowIx = pRowIx-1; >+ } >+ if (colArgWasEmpty) { >+ columnIx = 0; >+ } else { >+ columnIx = colArgWasEmpty ? 0 : pColumnIx-1; >+ } > } else { >- // Slightly irregular logic for bounds checking errors >- if (pColumnIx > width) { >- // high bounds check fail gives #REF! if arg was explicitly passed >- throw new EvaluationException(ErrorEval.REF_INVALID); >+ // ae is an area (not single row or column) >+ if (!colArgWasPassed) { >+ // always an error with 2-D area refs >+ // Note - the type of error changes if the pRowArg is negative >+ throw new EvaluationException(pRowIx < 0 ? ErrorEval.VALUE_INVALID : ErrorEval.REF_INVALID); > } >- int columnIx = pColumnIx-1; >- relFirstColIx = columnIx; >- relLastColIx = columnIx; >+ // Normal case - area ref is 2-D, and both index args were provided >+ // if either arg is missing (or blank) the logic is similar to OperandResolver.getSingleValue() >+ if (rowArgWasEmpty) { >+ if (ae instanceof AreaEval) { >+ rowIx = srcRowIx - ((AreaEval) ae).getFirstRow(); >+ } else { >+ // TODO - ArrayEval >+ // rowIx = relative row of evaluating cell in its array formula cell group >+ throw new RuntimeException("incomplete code - "); >+ } >+ } else { >+ rowIx = pRowIx-1; >+ } >+ if (colArgWasEmpty) { >+ if (ae instanceof AreaEval) { >+ columnIx = srcColIx - ((AreaEval) ae).getFirstColumn(); >+ } else { >+ // TODO - ArrayEval >+ // colIx = relative col of evaluating cell in its array formula cell group >+ throw new RuntimeException("incomplete code - "); >+ } >+ } else { >+ columnIx = pColumnIx-1; >+ } > } > >- AreaEval x = ((AreaEval) ae); >- return x.offset(relFirstRowIx, relLastRowIx, relFirstColIx, relLastColIx); >+ int width = ae.getWidth(); >+ int height = ae.getHeight(); >+ // Slightly irregular logic for bounds checking errors >+ if (!rowArgWasEmpty && rowIx >= height || !colArgWasEmpty && columnIx >= width) { >+ // high bounds check fail gives #REF! if arg was explicitly passed >+ throw new EvaluationException(ErrorEval.REF_INVALID); >+ } >+ if (rowIx < 0 || columnIx < 0 || rowIx >= height || columnIx >= width) { >+ throw new EvaluationException(ErrorEval.VALUE_INVALID); >+ } >+ return ae.getValue(rowIx, columnIx); > } > > >@@ -187,4 +272,13 @@ > } > return result; > } >+ >+ public ValueEval evaluateInArrayFormula(ValueEval[] args, int srcCellRow, int srcCellCol) { >+ // in array formula index(reference,row,0) and index(reference,0,col) should return entire row/column >+ return evaluateX(args, srcCellRow, srcCellCol, true); >+ } >+ >+ public boolean supportArray(int paramIndex) { >+ return paramIndex == 0; >+ } > } >Index: src/java/org/apache/poi/hssf/record/formula/functions/Lookup.java >=================================================================== >--- src/java/org/apache/poi/hssf/record/formula/functions/Lookup.java (revision 892169) >+++ src/java/org/apache/poi/hssf/record/formula/functions/Lookup.java (working copy) >@@ -36,8 +36,9 @@ > * <b>result_vector</b> Single row or single column area reference from which the result value is chosen.<br/> > * > * @author Josh Micich >+ * @author Zahars Sulkins(Zahars.Sulkins at exigenservices.com) - array support > */ >-public final class Lookup extends Var2or3ArgFunction { >+public final class Lookup extends Var2or3ArgFunction implements FunctionWithArraySupport { > > public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1) { > // complex rules to choose lookupVector and resultVector from the single area ref >@@ -73,4 +74,13 @@ > // extra complexity required to emulate the way LOOKUP can handles these abnormal cases. > throw new RuntimeException("non-vector lookup or result areas not supported yet"); > } >+ >+ public boolean supportArray(int paramIndex) { >+ switch (paramIndex) { >+ case 1: >+ case 2: >+ return true; >+ } >+ return false; >+ } > } >Index: src/java/org/apache/poi/hssf/record/formula/functions/Match.java >=================================================================== >--- src/java/org/apache/poi/hssf/record/formula/functions/Match.java (revision 892169) >+++ src/java/org/apache/poi/hssf/record/formula/functions/Match.java (working copy) >@@ -63,7 +63,7 @@ > * > * @author Josh Micich > */ >-public final class Match extends Var2or3ArgFunction { >+public final class Match extends Var2or3ArgFunction implements FunctionWithArraySupport { > > public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1) { > // default match_type is 1.0 >@@ -248,4 +248,8 @@ > } > return false; > } >+ >+ public boolean supportArray(int paramIndex) { >+ return paramIndex == 1; >+ } > } >Index: src/java/org/apache/poi/hssf/record/formula/functions/Mode.java >=================================================================== >--- src/java/org/apache/poi/hssf/record/formula/functions/Mode.java (revision 892169) >+++ src/java/org/apache/poi/hssf/record/formula/functions/Mode.java (working copy) >@@ -35,7 +35,7 @@ > * @author Amol S. Deshmukh < amolweb at ya hoo dot com > > * > */ >-public final class Mode implements Function { >+public final class Mode implements FunctionWithArraySupport { > > /** > * if v is zero length or contains no duplicates, return value is >@@ -130,4 +130,8 @@ > } > throw new RuntimeException("Unexpected value type (" + arg.getClass().getName() + ")"); > } >+ >+ public boolean supportArray(int paramIndex) { >+ return true; >+ } > } >Index: src/java/org/apache/poi/hssf/record/formula/functions/MultiOperandNumericFunction.java >=================================================================== >--- src/java/org/apache/poi/hssf/record/formula/functions/MultiOperandNumericFunction.java (revision 892169) >+++ src/java/org/apache/poi/hssf/record/formula/functions/MultiOperandNumericFunction.java (working copy) >@@ -34,7 +34,7 @@ > * classes that take variable number of operands, and > * where the order of operands does not matter > */ >-public abstract class MultiOperandNumericFunction implements Function { >+public abstract class MultiOperandNumericFunction implements FunctionWithArraySupport { > > private final boolean _isReferenceBoolCounted; > private final boolean _isBlankCounted; >@@ -194,4 +194,8 @@ > throw new RuntimeException("Invalid ValueEval type passed for conversion: (" > + ve.getClass() + ")"); > } >+ >+ public boolean supportArray(int paramIndex) { >+ return true; >+ } > } >Index: src/java/org/apache/poi/hssf/record/formula/functions/Offset.java >=================================================================== >--- src/java/org/apache/poi/hssf/record/formula/functions/Offset.java (revision 892169) >+++ src/java/org/apache/poi/hssf/record/formula/functions/Offset.java (working copy) >@@ -38,8 +38,9 @@ > * <b>width</b> (default same width as base reference) is the column count for the returned area reference.<br/> > * > * @author Josh Micich >+ * @author Zahars Sulkins(Zahars.Sulkins at exigenservices.com) - array support > */ >-public final class Offset implements Function { >+public final class Offset implements FunctionWithArraySupport { > // These values are specific to BIFF8 > private static final int LAST_VALID_ROW_INDEX = 0xFFFF; > private static final int LAST_VALID_COLUMN_INDEX = 0xFF; >@@ -224,4 +225,8 @@ > ValueEval ve = OperandResolver.getSingleValue(eval, srcCellRow, srcCellCol); > return OperandResolver.coerceValueToInt(ve); > } >+ >+ public boolean supportArray(int paramIndex) { >+ return paramIndex == 0; >+ } > } >Index: src/java/org/apache/poi/hssf/record/formula/functions/Row.java >=================================================================== >--- src/java/org/apache/poi/hssf/record/formula/functions/Row.java (revision 892169) >+++ src/java/org/apache/poi/hssf/record/formula/functions/Row.java (working copy) >@@ -22,8 +22,9 @@ > 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.ValueEval; >+import org.apache.poi.ss.formula.ArrayEval; > >-public final class Row implements Function0Arg, Function1Arg { >+public final class Row implements Function0Arg, Function1Arg, ArrayMode { > > public ValueEval evaluate(int srcRowIndex, int srcColumnIndex) { > return new NumberEval(srcRowIndex+1); >@@ -52,4 +53,16 @@ > return ErrorEval.VALUE_INVALID; > } > >+ public ValueEval evaluateInArrayFormula(ValueEval[] evals, int srcCellRow, int srcCellCol) { >+ if ((evals.length == 1) && (evals[0] instanceof AreaEval)) { >+ AreaEval ae = (AreaEval) evals[0]; >+ >+ ValueEval[][] result = new ValueEval[ae.getHeight()][1]; >+ for (int r = 0; r < ae.getHeight(); r++) { >+ result[r][0] = new NumberEval(ae.getFirstRow() + r + 1); >+ } >+ return new ArrayEval(result); >+ } >+ return evaluate(evals, srcCellRow, srcCellCol); >+ } > } >Index: src/java/org/apache/poi/hssf/record/formula/functions/Rows.java >=================================================================== >--- src/java/org/apache/poi/hssf/record/formula/functions/Rows.java (revision 892169) >+++ src/java/org/apache/poi/hssf/record/formula/functions/Rows.java (working copy) >@@ -28,7 +28,7 @@ > * > * @author Josh Micich > */ >-public final class Rows extends Fixed1ArgFunction { >+public final class Rows extends Fixed1ArgFunction implements FunctionWithArraySupport { > > public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0) { > >@@ -42,4 +42,8 @@ > } > return new NumberEval(result); > } >+ >+ public boolean supportArray(int paramIndex) { >+ return true; >+ } > } >Index: src/java/org/apache/poi/hssf/record/formula/functions/Sumif.java >=================================================================== >--- src/java/org/apache/poi/hssf/record/formula/functions/Sumif.java (revision 892169) >+++ src/java/org/apache/poi/hssf/record/formula/functions/Sumif.java (working copy) >@@ -37,8 +37,9 @@ > * </table><br/> > * </p> > * @author Josh Micich >+ * @author Zahars Sulkins(Zahars.Sulkins at exigenservices.com) - array support > */ >-public final class Sumif extends Var2or3ArgFunction { >+public final class Sumif extends Var2or3ArgFunction implements FunctionWithArraySupport { > > public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1) { > >@@ -124,4 +125,10 @@ > throw new EvaluationException(ErrorEval.VALUE_INVALID); > } > >+ public boolean supportArray(int paramIndex) { >+ if (paramIndex == 1) { // TODO - should throw exception if array instead of range >+ return false; >+ } >+ return true; >+ } > } >Index: src/java/org/apache/poi/hssf/record/formula/functions/Sumproduct.java >=================================================================== >--- src/java/org/apache/poi/hssf/record/formula/functions/Sumproduct.java (revision 892169) >+++ src/java/org/apache/poi/hssf/record/formula/functions/Sumproduct.java (working copy) >@@ -50,23 +50,24 @@ > * ) > * </p> > * @author Josh Micich >+ * @author Zahars Sulkins(Zahars.Sulkins at exigenservices.com) - array support > */ >-public final class Sumproduct implements Function { >+public final class Sumproduct implements FunctionWithArraySupport { > > > public ValueEval evaluate(ValueEval[] args, int srcCellRow, int srcCellCol) { > > int maxN = args.length; > >- if(maxN < 1) { >+ if (maxN < 1) { > return ErrorEval.VALUE_INVALID; > } > ValueEval firstArg = args[0]; > try { >- if(firstArg instanceof NumericValueEval) { >+ if (firstArg instanceof NumericValueEval) { > return evaluateSingleProduct(args); > } >- if(firstArg instanceof RefEval) { >+ if (firstArg instanceof RefEval) { > return evaluateSingleProduct(args); > } > if (firstArg instanceof TwoDEval) { >@@ -228,4 +229,8 @@ > throw new RuntimeException("Unexpected value eval class (" > + ve.getClass().getName() + ")"); > } >+ >+ public boolean supportArray(int paramIndex) { >+ return true; >+ } > } >Index: src/java/org/apache/poi/hssf/record/formula/functions/Vlookup.java >=================================================================== >--- src/java/org/apache/poi/hssf/record/formula/functions/Vlookup.java (revision 892169) >+++ src/java/org/apache/poi/hssf/record/formula/functions/Vlookup.java (working copy) >@@ -38,8 +38,9 @@ > * the lookup_value. If FALSE, only exact matches will be considered<br/> > * > * @author Josh Micich >+ * @author Zahars Sulkins(Zahars.Sulkins at exigenservices.com) - array support > */ >-public final class Vlookup extends Var3or4ArgFunction { >+public final class Vlookup extends Var3or4ArgFunction implements FunctionWithArraySupport { > private static final ValueEval DEFAULT_ARG3 = BoolEval.TRUE; > > public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1, >@@ -78,4 +79,8 @@ > } > return LookupUtils.createColumnVector(tableArray, colIndex); > } >+ >+ public boolean supportArray(int paramIndex) { >+ return paramIndex == 1; >+ } > } >Index: src/java/org/apache/poi/hssf/record/formula/functions/XYNumericFunction.java >=================================================================== >--- src/java/org/apache/poi/hssf/record/formula/functions/XYNumericFunction.java (revision 892169) >+++ src/java/org/apache/poi/hssf/record/formula/functions/XYNumericFunction.java (working copy) >@@ -28,7 +28,7 @@ > /** > * @author Amol S. Deshmukh < amolweb at ya hoo dot com > > */ >-public abstract class XYNumericFunction extends Fixed2ArgFunction { >+public abstract class XYNumericFunction extends Fixed2ArgFunction implements FunctionWithArraySupport { > > private static abstract class ValueArray implements ValueVector { > private final int _size; >@@ -174,4 +174,8 @@ > } > return new SingleCellValueArray(arg); > } >+ >+ public boolean supportArray(int paramIndex) { >+ return true; >+ } > } >Index: src/java/org/apache/poi/hssf/usermodel/HSSFCell.java >=================================================================== >--- src/java/org/apache/poi/hssf/usermodel/HSSFCell.java (revision 892169) >+++ src/java/org/apache/poi/hssf/usermodel/HSSFCell.java (working copy) >@@ -43,6 +43,7 @@ > import org.apache.poi.hssf.record.TextObjectRecord; > import org.apache.poi.hssf.record.UnicodeString; > import org.apache.poi.hssf.record.aggregates.FormulaRecordAggregate; >+import org.apache.poi.hssf.record.formula.ExpPtg; > import org.apache.poi.hssf.record.formula.Ptg; > import org.apache.poi.hssf.record.formula.eval.ErrorEval; > import org.apache.poi.ss.usermodel.Cell; >@@ -50,6 +51,7 @@ > import org.apache.poi.ss.usermodel.Comment; > import org.apache.poi.ss.usermodel.Hyperlink; > import org.apache.poi.ss.usermodel.RichTextString; >+import org.apache.poi.ss.util.CellRangeAddress; > import org.apache.poi.ss.util.NumberToTextConverter; > import org.apache.poi.ss.formula.FormulaType; > import org.apache.poi.ss.SpreadsheetVersion; >@@ -1160,4 +1162,30 @@ > } > return ((FormulaRecordAggregate)_record).getFormulaRecord().getCachedResultType(); > } >+ >+ void setCellArrayFormula(CellRangeAddress range) { >+ int row=_record.getRow(); >+ short col=_record.getColumn(); >+ short styleIndex=_record.getXFIndex(); >+ setCellType(CELL_TYPE_FORMULA, false, row, col, styleIndex); >+ >+ // Billet for formula in rec >+ Ptg[] ptgsForCell = { new ExpPtg(range.getFirstRow(), range.getFirstColumn()) }; >+ FormulaRecordAggregate agg = (FormulaRecordAggregate) _record; >+ agg.setParsedExpression(ptgsForCell); >+ } >+ >+ public CellRangeAddress getArrayFormulaRange() { >+ if (_cellType != CELL_TYPE_FORMULA) { >+ throw new IllegalArgumentException("Only formula cells can have array ranges"); >+ } >+ return ((FormulaRecordAggregate)_record).getArrayFormulaRange(); >+ } >+ >+ public boolean isPartOfArrayFormulaGroup() { >+ if (_cellType != CELL_TYPE_FORMULA) { >+ return false; >+ } >+ return ((FormulaRecordAggregate)_record).isPartOfArrayFormula(); >+ } > } >Index: src/java/org/apache/poi/hssf/usermodel/HSSFEvaluationCell.java >=================================================================== >--- src/java/org/apache/poi/hssf/usermodel/HSSFEvaluationCell.java (revision 892169) >+++ src/java/org/apache/poi/hssf/usermodel/HSSFEvaluationCell.java (working copy) >@@ -1,72 +1,77 @@ >-/* ==================================================================== >- 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.usermodel; >- >-import org.apache.poi.ss.formula.EvaluationCell; >-import org.apache.poi.ss.formula.EvaluationSheet; >-/** >- * HSSF wrapper for a cell under evaluation >- * >- * @author Josh Micich >- */ >-final class HSSFEvaluationCell implements EvaluationCell { >- >- private final EvaluationSheet _evalSheet; >- private final HSSFCell _cell; >- >- public HSSFEvaluationCell(HSSFCell cell, EvaluationSheet evalSheet) { >- _cell = cell; >- _evalSheet = evalSheet; >- } >- public HSSFEvaluationCell(HSSFCell cell) { >- this(cell, new HSSFEvaluationSheet(cell.getSheet())); >- } >- public Object getIdentityKey() { >- // save memory by just using the cell itself as the identity key >- // Note - this assumes HSSFCell has not overridden hashCode and equals >- return _cell; >- } >- >- public HSSFCell getHSSFCell() { >- return _cell; >- } >- public boolean getBooleanCellValue() { >- return _cell.getBooleanCellValue(); >- } >- public int getCellType() { >- return _cell.getCellType(); >- } >- public int getColumnIndex() { >- return _cell.getColumnIndex(); >- } >- public int getErrorCellValue() { >- return _cell.getErrorCellValue(); >- } >- public double getNumericCellValue() { >- return _cell.getNumericCellValue(); >- } >- public int getRowIndex() { >- return _cell.getRowIndex(); >- } >- public EvaluationSheet getSheet() { >- return _evalSheet; >- } >- public String getStringCellValue() { >- return _cell.getRichStringCellValue().getString(); >- } >-} >+/* ==================================================================== >+ 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.usermodel; >+ >+import org.apache.poi.ss.formula.EvaluationCell; >+import org.apache.poi.ss.formula.EvaluationSheet; >+import org.apache.poi.ss.util.CellRangeAddress; >+/** >+ * HSSF wrapper for a cell under evaluation >+ * >+ * @author Josh Micich >+ */ >+final class HSSFEvaluationCell implements EvaluationCell { >+ >+ private final EvaluationSheet _evalSheet; >+ private final HSSFCell _cell; >+ >+ public HSSFEvaluationCell(HSSFCell cell, EvaluationSheet evalSheet) { >+ _cell = cell; >+ _evalSheet = evalSheet; >+ } >+ public HSSFEvaluationCell(HSSFCell cell) { >+ this(cell, new HSSFEvaluationSheet(cell.getSheet())); >+ } >+ public Object getIdentityKey() { >+ // save memory by just using the cell itself as the identity key >+ // Note - this assumes HSSFCell has not overridden hashCode and equals >+ return _cell; >+ } >+ >+ public HSSFCell getHSSFCell() { >+ return _cell; >+ } >+ public boolean getBooleanCellValue() { >+ return _cell.getBooleanCellValue(); >+ } >+ public int getCellType() { >+ return _cell.getCellType(); >+ } >+ public int getColumnIndex() { >+ return _cell.getColumnIndex(); >+ } >+ public int getErrorCellValue() { >+ return _cell.getErrorCellValue(); >+ } >+ public double getNumericCellValue() { >+ return _cell.getNumericCellValue(); >+ } >+ public int getRowIndex() { >+ return _cell.getRowIndex(); >+ } >+ public EvaluationSheet getSheet() { >+ return _evalSheet; >+ } >+ public String getStringCellValue() { >+ return _cell.getRichStringCellValue().getString(); >+ } >+ >+ public boolean isArrayFormula() { >+ return _cell.isPartOfArrayFormulaGroup(); >+ } >+} >Index: src/java/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java >=================================================================== >--- src/java/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java (revision 892169) >+++ src/java/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java (working copy) >@@ -1,335 +1,433 @@ >-/* ==================================================================== >- 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.usermodel; >- >-import java.util.Iterator; >- >-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.NumberEval; >-import org.apache.poi.hssf.record.formula.eval.StringEval; >-import org.apache.poi.hssf.record.formula.eval.ValueEval; >-import org.apache.poi.hssf.record.formula.udf.UDFFinder; >-import org.apache.poi.ss.formula.CollaboratingWorkbooksEnvironment; >-import org.apache.poi.ss.formula.IStabilityClassifier; >-import org.apache.poi.ss.formula.WorkbookEvaluator; >-import org.apache.poi.ss.usermodel.Cell; >-import org.apache.poi.ss.usermodel.CellValue; >-import org.apache.poi.ss.usermodel.FormulaEvaluator; >-import org.apache.poi.ss.usermodel.Row; >- >-/** >- * Evaluates formula cells.<p/> >- * >- * For performance reasons, this class keeps a cache of all previously calculated intermediate >- * cell values. Be sure to call {@link #clearAllCachedResultValues()} if any workbook cells are changed between >- * calls to evaluate~ methods on this class. >- * >- * @author Amol S. Deshmukh < amolweb at ya hoo dot com > >- * @author Josh Micich >- */ >-public class HSSFFormulaEvaluator implements FormulaEvaluator { >- >- private WorkbookEvaluator _bookEvaluator; >- >- /** >- * @deprecated (Sep 2008) HSSFSheet parameter is ignored >- */ >- public HSSFFormulaEvaluator(HSSFSheet sheet, HSSFWorkbook workbook) { >- this(workbook); >- if (false) { >- sheet.toString(); // suppress unused parameter compiler warning >- } >- } >- public HSSFFormulaEvaluator(HSSFWorkbook workbook) { >- this(workbook, null); >- } >- /** >- * @param stabilityClassifier used to optimise caching performance. Pass <code>null</code> >- * for the (conservative) assumption that any cell may have its definition changed after >- * evaluation begins. >- */ >- public HSSFFormulaEvaluator(HSSFWorkbook workbook, IStabilityClassifier stabilityClassifier) { >- this(workbook, stabilityClassifier, null); >- } >- >- /** >- * @param udfFinder pass <code>null</code> for default (AnalysisToolPak only) >- */ >- private HSSFFormulaEvaluator(HSSFWorkbook workbook, IStabilityClassifier stabilityClassifier, UDFFinder udfFinder) { >- _bookEvaluator = new WorkbookEvaluator(HSSFEvaluationWorkbook.create(workbook), stabilityClassifier, udfFinder); >- } >- >- /** >- * @param stabilityClassifier used to optimise caching performance. Pass <code>null</code> >- * for the (conservative) assumption that any cell may have its definition changed after >- * evaluation begins. >- * @param udfFinder pass <code>null</code> for default (AnalysisToolPak only) >- */ >- public static HSSFFormulaEvaluator create(HSSFWorkbook workbook, IStabilityClassifier stabilityClassifier, UDFFinder udfFinder) { >- return new HSSFFormulaEvaluator(workbook, stabilityClassifier, udfFinder); >- } >- >- >- /** >- * Coordinates several formula evaluators together so that formulas that involve external >- * references can be evaluated. >- * @param workbookNames the simple file names used to identify the workbooks in formulas >- * with external links (for example "MyData.xls" as used in a formula "[MyData.xls]Sheet1!A1") >- * @param evaluators all evaluators for the full set of workbooks required by the formulas. >- */ >- public static void setupEnvironment(String[] workbookNames, HSSFFormulaEvaluator[] evaluators) { >- WorkbookEvaluator[] wbEvals = new WorkbookEvaluator[evaluators.length]; >- for (int i = 0; i < wbEvals.length; i++) { >- wbEvals[i] = evaluators[i]._bookEvaluator; >- } >- CollaboratingWorkbooksEnvironment.setup(workbookNames, wbEvals); >- } >- >- /** >- * Does nothing >- * @deprecated (Aug 2008) - not needed, since the current row can be derived from the cell >- */ >- public void setCurrentRow(HSSFRow row) { >- // do nothing >- if (false) { >- row.getClass(); // suppress unused parameter compiler warning >- } >- } >- >- /** >- * Should be called whenever there are major changes (e.g. moving sheets) to input cells >- * in the evaluated workbook. If performance is not critical, a single call to this method >- * may be used instead of many specific calls to the notify~ methods. >- * >- * Failure to call this method after changing cell values will cause incorrect behaviour >- * of the evaluate~ methods of this class >- */ >- public void clearAllCachedResultValues() { >- _bookEvaluator.clearAllCachedResultValues(); >- } >- /** >- * Should be called to tell the cell value cache that the specified (value or formula) cell >- * has changed. >- * Failure to call this method after changing cell values will cause incorrect behaviour >- * of the evaluate~ methods of this class >- */ >- public void notifyUpdateCell(HSSFCell cell) { >- _bookEvaluator.notifyUpdateCell(new HSSFEvaluationCell(cell)); >- } >- /** >- * Should be called to tell the cell value cache that the specified cell has just been >- * deleted. >- * Failure to call this method after changing cell values will cause incorrect behaviour >- * of the evaluate~ methods of this class >- */ >- public void notifyDeleteCell(HSSFCell cell) { >- _bookEvaluator.notifyDeleteCell(new HSSFEvaluationCell(cell)); >- } >- public void notifyDeleteCell(Cell cell) { >- _bookEvaluator.notifyDeleteCell(new HSSFEvaluationCell((HSSFCell)cell)); >- } >- >- /** >- * Should be called to tell the cell value cache that the specified (value or formula) cell >- * has changed. >- * Failure to call this method after changing cell values will cause incorrect behaviour >- * of the evaluate~ methods of this class >- */ >- public void notifySetFormula(Cell cell) { >- _bookEvaluator.notifyUpdateCell(new HSSFEvaluationCell((HSSFCell)cell)); >- } >- >- /** >- * If cell contains a formula, the formula is evaluated and returned, >- * else the CellValue simply copies the appropriate cell value from >- * the cell and also its cell type. This method should be preferred over >- * evaluateInCell() when the call should not modify the contents of the >- * original cell. >- * >- * @param cell may be <code>null</code> signifying that the cell is not present (or blank) >- * @return <code>null</code> if the supplied cell is <code>null</code> or blank >- */ >- public CellValue evaluate(Cell cell) { >- if (cell == null) { >- return null; >- } >- >- switch (cell.getCellType()) { >- case HSSFCell.CELL_TYPE_BOOLEAN: >- return CellValue.valueOf(cell.getBooleanCellValue()); >- case HSSFCell.CELL_TYPE_ERROR: >- return CellValue.getError(cell.getErrorCellValue()); >- case HSSFCell.CELL_TYPE_FORMULA: >- return evaluateFormulaCellValue(cell); >- case HSSFCell.CELL_TYPE_NUMERIC: >- return new CellValue(cell.getNumericCellValue()); >- case HSSFCell.CELL_TYPE_STRING: >- return new CellValue(cell.getRichStringCellValue().getString()); >- case HSSFCell.CELL_TYPE_BLANK: >- return null; >- } >- throw new IllegalStateException("Bad cell type (" + cell.getCellType() + ")"); >- } >- >- >- /** >- * If cell contains formula, it evaluates the formula, and saves the result of the formula. The >- * cell remains as a formula cell. If the cell does not contain formula, this method returns -1 >- * and leaves the cell unchanged. >- * >- * Note that the type of the <em>formula result</em> is returned, so you know what kind of >- * cached formula result is also stored with the formula. >- * <pre> >- * int evaluatedCellType = evaluator.evaluateFormulaCell(cell); >- * </pre> >- * Be aware that your cell will hold both the formula, and the result. If you want the cell >- * replaced with the result of the formula, use {@link #evaluateInCell(org.apache.poi.ss.usermodel.Cell)} >- * @param cell The cell to evaluate >- * @return -1 for non-formula cells, or the type of the <em>formula result</em> >- */ >- public int evaluateFormulaCell(Cell cell) { >- if (cell == null || cell.getCellType() != HSSFCell.CELL_TYPE_FORMULA) { >- return -1; >- } >- CellValue cv = evaluateFormulaCellValue(cell); >- // cell remains a formula cell, but the cached value is changed >- setCellValue(cell, cv); >- return cv.getCellType(); >- } >- >- /** >- * If cell contains formula, it evaluates the formula, and >- * puts the formula result back into the cell, in place >- * of the old formula. >- * Else if cell does not contain formula, this method leaves >- * the cell unchanged. >- * Note that the same instance of HSSFCell is returned to >- * allow chained calls like: >- * <pre> >- * int evaluatedCellType = evaluator.evaluateInCell(cell).getCellType(); >- * </pre> >- * Be aware that your cell value will be changed to hold the >- * result of the formula. If you simply want the formula >- * value computed for you, use {@link #evaluateFormulaCell(Cell)}} >- */ >- public HSSFCell evaluateInCell(Cell cell) { >- if (cell == null) { >- return null; >- } >- HSSFCell result = (HSSFCell) cell; >- if (cell.getCellType() == HSSFCell.CELL_TYPE_FORMULA) { >- CellValue cv = evaluateFormulaCellValue(cell); >- setCellValue(cell, cv); >- setCellType(cell, cv); // cell will no longer be a formula cell >- } >- return result; >- } >- private static void setCellType(Cell cell, CellValue cv) { >- int cellType = cv.getCellType(); >- switch (cellType) { >- case HSSFCell.CELL_TYPE_BOOLEAN: >- case HSSFCell.CELL_TYPE_ERROR: >- case HSSFCell.CELL_TYPE_NUMERIC: >- case HSSFCell.CELL_TYPE_STRING: >- cell.setCellType(cellType); >- return; >- case HSSFCell.CELL_TYPE_BLANK: >- // never happens - blanks eventually get translated to zero >- case HSSFCell.CELL_TYPE_FORMULA: >- // this will never happen, we have already evaluated the formula >- } >- throw new IllegalStateException("Unexpected cell value type (" + cellType + ")"); >- } >- >- private static void setCellValue(Cell cell, CellValue cv) { >- int cellType = cv.getCellType(); >- switch (cellType) { >- case HSSFCell.CELL_TYPE_BOOLEAN: >- cell.setCellValue(cv.getBooleanValue()); >- break; >- case HSSFCell.CELL_TYPE_ERROR: >- cell.setCellErrorValue(cv.getErrorValue()); >- break; >- case HSSFCell.CELL_TYPE_NUMERIC: >- cell.setCellValue(cv.getNumberValue()); >- break; >- case HSSFCell.CELL_TYPE_STRING: >- cell.setCellValue(new HSSFRichTextString(cv.getStringValue())); >- break; >- case HSSFCell.CELL_TYPE_BLANK: >- // never happens - blanks eventually get translated to zero >- case HSSFCell.CELL_TYPE_FORMULA: >- // this will never happen, we have already evaluated the formula >- default: >- throw new IllegalStateException("Unexpected cell value type (" + cellType + ")"); >- } >- } >- >- /** >- * Loops over all cells in all sheets of the supplied >- * workbook. >- * For cells that contain formulas, their formulas are >- * evaluated, and the results are saved. These cells >- * remain as formula cells. >- * For cells that do not contain formulas, no changes >- * are made. >- * This is a helpful wrapper around looping over all >- * cells, and calling evaluateFormulaCell on each one. >- */ >- public static void evaluateAllFormulaCells(HSSFWorkbook wb) { >- HSSFFormulaEvaluator evaluator = new HSSFFormulaEvaluator(wb); >- for(int i=0; i<wb.getNumberOfSheets(); i++) { >- HSSFSheet sheet = wb.getSheetAt(i); >- >- for (Iterator<Row> rit = sheet.rowIterator(); rit.hasNext();) { >- Row r = rit.next(); >- >- for (Iterator<Cell> cit = r.cellIterator(); cit.hasNext();) { >- Cell c = cit.next(); >- if (c.getCellType() == HSSFCell.CELL_TYPE_FORMULA) >- evaluator.evaluateFormulaCell(c); >- } >- } >- } >- } >- >- /** >- * Returns a CellValue wrapper around the supplied ValueEval instance. >- * @param eval >- */ >- private CellValue evaluateFormulaCellValue(Cell cell) { >- ValueEval eval = _bookEvaluator.evaluate(new HSSFEvaluationCell((HSSFCell)cell)); >- if (eval instanceof NumberEval) { >- NumberEval ne = (NumberEval) eval; >- return new CellValue(ne.getNumberValue()); >- } >- if (eval instanceof BoolEval) { >- BoolEval be = (BoolEval) eval; >- return CellValue.valueOf(be.getBooleanValue()); >- } >- if (eval instanceof StringEval) { >- StringEval ne = (StringEval) eval; >- return new CellValue(ne.getStringValue()); >- } >- if (eval instanceof ErrorEval) { >- return CellValue.getError(((ErrorEval)eval).getErrorCode()); >- } >- throw new RuntimeException("Unexpected eval class (" + eval.getClass().getName() + ")"); >- } >-} >+/* ==================================================================== >+ 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.usermodel; >+ >+import java.util.Iterator; >+ >+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.NumberEval; >+import org.apache.poi.hssf.record.formula.eval.StringEval; >+import org.apache.poi.hssf.record.formula.eval.ValueEval; >+import org.apache.poi.hssf.record.formula.udf.UDFFinder; >+import org.apache.poi.ss.formula.ArrayEval; >+import org.apache.poi.ss.formula.CollaboratingWorkbooksEnvironment; >+import org.apache.poi.ss.formula.IStabilityClassifier; >+import org.apache.poi.ss.formula.WorkbookEvaluator; >+import org.apache.poi.ss.usermodel.ArrayFormulaEvaluatorHelper; >+import org.apache.poi.ss.usermodel.Cell; >+import org.apache.poi.ss.usermodel.CellValue; >+import org.apache.poi.ss.usermodel.FormulaEvaluator; >+import org.apache.poi.ss.usermodel.Row; >+import org.apache.poi.ss.usermodel.Sheet; >+import org.apache.poi.ss.util.CellRangeAddress; >+ >+/** >+ * Evaluates formula cells.<p/> >+ * >+ * For performance reasons, this class keeps a cache of all previously calculated intermediate >+ * cell values. Be sure to call {@link #clearAllCachedResultValues()} if any workbook cells are changed between >+ * calls to evaluate~ methods on this class. >+ * >+ * @author Amol S. Deshmukh < amolweb at ya hoo dot com > >+ * @author Josh Micich >+ * @author Zahars Sulkins(Zahars.Sulkins at exigenservices.com) - Array Formula support >+ * @author Vladimirs Abramovs(Vladimirs.Abramovs at exigenservices.com) - Array Formula support >+ */ >+public class HSSFFormulaEvaluator implements FormulaEvaluator { >+ >+ private WorkbookEvaluator _bookEvaluator; >+ >+ /** >+ * @deprecated (Sep 2008) HSSFSheet parameter is ignored >+ */ >+ public HSSFFormulaEvaluator(HSSFSheet sheet, HSSFWorkbook workbook) { >+ this(workbook); >+ if (false) { >+ sheet.toString(); // suppress unused parameter compiler warning >+ } >+ } >+ public HSSFFormulaEvaluator(HSSFWorkbook workbook) { >+ this(workbook, null); >+ } >+ /** >+ * @param stabilityClassifier used to optimise caching performance. Pass <code>null</code> >+ * for the (conservative) assumption that any cell may have its definition changed after >+ * evaluation begins. >+ */ >+ public HSSFFormulaEvaluator(HSSFWorkbook workbook, IStabilityClassifier stabilityClassifier) { >+ this(workbook, stabilityClassifier, null); >+ } >+ >+ /** >+ * @param udfFinder pass <code>null</code> for default (AnalysisToolPak only) >+ */ >+ private HSSFFormulaEvaluator(HSSFWorkbook workbook, IStabilityClassifier stabilityClassifier, UDFFinder udfFinder) { >+ _bookEvaluator = new WorkbookEvaluator(HSSFEvaluationWorkbook.create(workbook), stabilityClassifier, udfFinder); >+ } >+ >+ /** >+ * @param stabilityClassifier used to optimise caching performance. Pass <code>null</code> >+ * for the (conservative) assumption that any cell may have its definition changed after >+ * evaluation begins. >+ * @param udfFinder pass <code>null</code> for default (AnalysisToolPak only) >+ */ >+ public static HSSFFormulaEvaluator create(HSSFWorkbook workbook, IStabilityClassifier stabilityClassifier, UDFFinder udfFinder) { >+ return new HSSFFormulaEvaluator(workbook, stabilityClassifier, udfFinder); >+ } >+ >+ >+ /** >+ * Coordinates several formula evaluators together so that formulas that involve external >+ * references can be evaluated. >+ * @param workbookNames the simple file names used to identify the workbooks in formulas >+ * with external links (for example "MyData.xls" as used in a formula "[MyData.xls]Sheet1!A1") >+ * @param evaluators all evaluators for the full set of workbooks required by the formulas. >+ */ >+ public static void setupEnvironment(String[] workbookNames, HSSFFormulaEvaluator[] evaluators) { >+ WorkbookEvaluator[] wbEvals = new WorkbookEvaluator[evaluators.length]; >+ for (int i = 0; i < wbEvals.length; i++) { >+ wbEvals[i] = evaluators[i]._bookEvaluator; >+ } >+ CollaboratingWorkbooksEnvironment.setup(workbookNames, wbEvals); >+ } >+ >+ /** >+ * Does nothing >+ * @deprecated (Aug 2008) - not needed, since the current row can be derived from the cell >+ */ >+ public void setCurrentRow(HSSFRow row) { >+ // do nothing >+ if (false) { >+ row.getClass(); // suppress unused parameter compiler warning >+ } >+ } >+ >+ /** >+ * Should be called whenever there are major changes (e.g. moving sheets) to input cells >+ * in the evaluated workbook. If performance is not critical, a single call to this method >+ * may be used instead of many specific calls to the notify~ methods. >+ * >+ * Failure to call this method after changing cell values will cause incorrect behaviour >+ * of the evaluate~ methods of this class >+ */ >+ public void clearAllCachedResultValues() { >+ _bookEvaluator.clearAllCachedResultValues(); >+ } >+ /** >+ * Should be called to tell the cell value cache that the specified (value or formula) cell >+ * has changed. >+ * Failure to call this method after changing cell values will cause incorrect behaviour >+ * of the evaluate~ methods of this class >+ */ >+ public void notifyUpdateCell(HSSFCell cell) { >+ _bookEvaluator.notifyUpdateCell(new HSSFEvaluationCell(cell)); >+ } >+ /** >+ * Should be called to tell the cell value cache that the specified cell has just been >+ * deleted. >+ * Failure to call this method after changing cell values will cause incorrect behaviour >+ * of the evaluate~ methods of this class >+ */ >+ public void notifyDeleteCell(HSSFCell cell) { >+ _bookEvaluator.notifyDeleteCell(new HSSFEvaluationCell(cell)); >+ } >+ public void notifyDeleteCell(Cell cell) { >+ _bookEvaluator.notifyDeleteCell(new HSSFEvaluationCell((HSSFCell)cell)); >+ } >+ >+ /** >+ * Should be called to tell the cell value cache that the specified (value or formula) cell >+ * has changed. >+ * Failure to call this method after changing cell values will cause incorrect behaviour >+ * of the evaluate~ methods of this class >+ */ >+ public void notifySetFormula(Cell cell) { >+ _bookEvaluator.notifyUpdateCell(new HSSFEvaluationCell((HSSFCell)cell)); >+ } >+ >+ /** >+ * If cell contains a formula, the formula is evaluated and returned, >+ * else the CellValue simply copies the appropriate cell value from >+ * the cell and also its cell type. This method should be preferred over >+ * evaluateInCell() when the call should not modify the contents of the >+ * original cell. >+ * >+ * @param cell may be <code>null</code> signifying that the cell is not present (or blank) >+ * @return <code>null</code> if the supplied cell is <code>null</code> or blank >+ */ >+ public CellValue evaluate(Cell cell) { >+ if (cell == null) { >+ return null; >+ } >+ >+ switch (cell.getCellType()) { >+ case HSSFCell.CELL_TYPE_BOOLEAN: >+ return CellValue.valueOf(cell.getBooleanCellValue()); >+ case HSSFCell.CELL_TYPE_ERROR: >+ return CellValue.getError(cell.getErrorCellValue()); >+ case HSSFCell.CELL_TYPE_FORMULA: >+ return evaluateFormulaCellValue(cell); >+ case HSSFCell.CELL_TYPE_NUMERIC: >+ return new CellValue(cell.getNumericCellValue()); >+ case HSSFCell.CELL_TYPE_STRING: >+ return new CellValue(cell.getRichStringCellValue().getString()); >+ case HSSFCell.CELL_TYPE_BLANK: >+ return null; >+ } >+ throw new IllegalStateException("Bad cell type (" + cell.getCellType() + ")"); >+ } >+ >+ >+ /** >+ * If cell contains formula, it evaluates the formula, and saves the result of the formula. The >+ * cell remains as a formula cell. If the cell does not contain formula, this method returns -1 >+ * and leaves the cell unchanged. >+ * >+ * Note that the type of the <em>formula result</em> is returned, so you know what kind of >+ * cached formula result is also stored with the formula. >+ * <pre> >+ * int evaluatedCellType = evaluator.evaluateFormulaCell(cell); >+ * </pre> >+ * Be aware that your cell will hold both the formula, and the result. If you want the cell >+ * replaced with the result of the formula, use {@link #evaluateInCell(org.apache.poi.ss.usermodel.Cell)} >+ * @param cell The cell to evaluate >+ * @return -1 for non-formula cells, or the type of the <em>formula result</em> >+ */ >+ public int evaluateFormulaCell(Cell cell) { >+ if (cell == null || cell.getCellType() != HSSFCell.CELL_TYPE_FORMULA) { >+ return -1; >+ } >+ // cell remains a formula cell, but the cached value is changed >+ CellValue cv; >+ if (cell.isPartOfArrayFormulaGroup()) { // Array Formula Context >+ CellValue[][] cvs = evaluateFormulaCellArrayValues((HSSFCell) cell); >+ int rowIndex = cell.getRowIndex() - cell.getArrayFormulaRange().getFirstRow(); >+ int colIndex = cell.getColumnIndex() - cell.getArrayFormulaRange().getFirstColumn(); >+ CellValue[][] values = setCellValues(cell, cvs); >+ cv = values[rowIndex][colIndex]; >+ } else { // Single Formula >+ >+ cv = evaluateFormulaCellValue(cell); >+ setCellValue(cell, cv); >+ } >+ return cv.getCellType(); >+ } >+ >+ /** >+ * If cell contains formula, it evaluates the formula, and >+ * puts the formula result back into the cell, in place >+ * of the old formula. >+ * Else if cell does not contain formula, this method leaves >+ * the cell unchanged. >+ * Note that the same instance of HSSFCell is returned to >+ * allow chained calls like: >+ * <pre> >+ * int evaluatedCellType = evaluator.evaluateInCell(cell).getCellType(); >+ * </pre> >+ * Be aware that your cell value will be changed to hold the >+ * result of the formula. If you simply want the formula >+ * value computed for you, use {@link #evaluateFormulaCell(Cell)}} >+ */ >+ public HSSFCell evaluateInCell(Cell cell) { >+ if (cell == null) { >+ return null; >+ } >+ HSSFCell result = (HSSFCell) cell; >+ if (cell.getCellType() == HSSFCell.CELL_TYPE_FORMULA) { >+ if (cell.isPartOfArrayFormulaGroup()) { // Array Formula Context >+ CellValue[][] cvs = evaluateFormulaCellArrayValues((HSSFCell) cell); >+ setCellValues(cell, cvs); >+ setCellsTypes(cell, cvs); // cells will no longer be a formula cell >+ } else { // Single Formula >+ CellValue cv = evaluateFormulaCellValue(cell); >+ setCellValue(cell, cv); >+ setCellType(cell, cv); // cell will no longer be a formula cell >+ } >+ } >+ return result; >+ } >+ private static void setCellType(Cell cell, CellValue cv) { >+ int cellType = cv.getCellType(); >+ switch (cellType) { >+ case HSSFCell.CELL_TYPE_BOOLEAN: >+ case HSSFCell.CELL_TYPE_ERROR: >+ case HSSFCell.CELL_TYPE_NUMERIC: >+ case HSSFCell.CELL_TYPE_STRING: >+ cell.setCellType(cellType); >+ return; >+ case HSSFCell.CELL_TYPE_BLANK: >+ // never happens - blanks eventually get translated to zero >+ case HSSFCell.CELL_TYPE_FORMULA: >+ // this will never happen, we have already evaluated the formula >+ } >+ throw new IllegalStateException("Unexpected cell value type (" + cellType + ")"); >+ } >+ >+ private static void setCellValue(Cell cell, CellValue cv) { >+ int cellType = cv.getCellType(); >+ switch (cellType) { >+ case HSSFCell.CELL_TYPE_BOOLEAN: >+ cell.setCellValue(cv.getBooleanValue()); >+ break; >+ case HSSFCell.CELL_TYPE_ERROR: >+ cell.setCellErrorValue(cv.getErrorValue()); >+ break; >+ case HSSFCell.CELL_TYPE_NUMERIC: >+ cell.setCellValue(cv.getNumberValue()); >+ break; >+ case HSSFCell.CELL_TYPE_STRING: >+ cell.setCellValue(new HSSFRichTextString(cv.getStringValue())); >+ break; >+ case HSSFCell.CELL_TYPE_BLANK: >+ // never happens - blanks eventually get translated to zero >+ case HSSFCell.CELL_TYPE_FORMULA: >+ // this will never happen, we have already evaluated the formula >+ default: >+ throw new IllegalStateException("Unexpected cell value type (" + cellType + ")"); >+ } >+ } >+ >+ private void setCellsTypes(Cell cell, CellValue[][] cvs) { >+ CellRangeAddress range = cell.getArrayFormulaRange(); >+ int rowStart = range.getFirstRow(); >+ int colStart = range.getFirstColumn(); >+ Sheet sheet = cell.getSheet(); >+ for(int i=rowStart;i<=range.getLastRow();i++ ) { >+ for(int j=colStart; j<=range.getLastColumn();j++) { >+ Row row = sheet.getRow(i); >+ Cell c = row.getCell(j); >+ if ((i-rowStart)<cvs.length && (j-colStart)<cvs[i-rowStart].length) { >+ setCellType(c,cvs[i-rowStart][j-colStart]); >+ } >+ } >+ } >+ } >+ >+ /** >+ * Set value in Range >+ * @param cell >+ * @param cvs >+ * @return >+ */ >+ private CellValue[][] setCellValues(Cell cell, CellValue[][] cvs) { >+ CellRangeAddress range = cell.getArrayFormulaRange(); >+ int rowStart = range.getFirstRow(); >+ int colStart = range.getFirstColumn(); >+ Sheet sheet = cell.getSheet(); >+ CellValue[][] answer = ArrayFormulaEvaluatorHelper.transformToRange(cvs, range); >+ for(int i=rowStart;i<=range.getLastRow();i++ ) { >+ for(int j=colStart; j<=range.getLastColumn();j++) { >+ Row row = sheet.getRow(i); >+ if (row == null) { >+ row = sheet.createRow(i); >+ } >+ Cell c = row.getCell(j); >+ if (c == null) { >+ c = row.createCell(j); >+ } >+ CellValue cellValue = answer[i-rowStart][j-colStart]; >+ setCellValue(c,cellValue); >+ } >+ } >+ return answer; >+ } >+ >+ /** >+ * Loops over all cells in all sheets of the supplied >+ * workbook. >+ * For cells that contain formulas, their formulas are >+ * evaluated, and the results are saved. These cells >+ * remain as formula cells. >+ * For cells that do not contain formulas, no changes >+ * are made. >+ * This is a helpful wrapper around looping over all >+ * cells, and calling evaluateFormulaCell on each one. >+ */ >+ public static void evaluateAllFormulaCells(HSSFWorkbook wb) { >+ HSSFFormulaEvaluator evaluator = new HSSFFormulaEvaluator(wb); >+ for(int i=0; i<wb.getNumberOfSheets(); i++) { >+ HSSFSheet sheet = wb.getSheetAt(i); >+ >+ for (Iterator<Row> rit = sheet.rowIterator(); rit.hasNext();) { >+ Row r = rit.next(); >+ >+ for (Iterator<Cell> cit = r.cellIterator(); cit.hasNext();) { >+ Cell c = cit.next(); >+ if (c.getCellType() == HSSFCell.CELL_TYPE_FORMULA) >+ evaluator.evaluateFormulaCell(c); >+ } >+ } >+ } >+ } >+ >+ /** >+ * Returns a CellValue wrapper around the supplied ValueEval instance. >+ * @param eval >+ */ >+ private CellValue evaluateFormulaCellValue(Cell cell) { >+ ValueEval eval = _bookEvaluator.evaluate(new HSSFEvaluationCell((HSSFCell)cell)); >+ if (eval instanceof ArrayEval) {// support of arrays >+ if (cell.isPartOfArrayFormulaGroup()) { >+ eval = ArrayFormulaEvaluatorHelper.dereferenceValue((ArrayEval) eval, cell); >+ } else { >+ eval = ((ArrayEval) eval).getValue(0, 0); >+ } >+ } >+ if (eval instanceof NumberEval) { >+ NumberEval ne = (NumberEval) eval; >+ return new CellValue(ne.getNumberValue()); >+ } >+ if (eval instanceof BoolEval) { >+ BoolEval be = (BoolEval) eval; >+ return CellValue.valueOf(be.getBooleanValue()); >+ } >+ if (eval instanceof StringEval) { >+ StringEval ne = (StringEval) eval; >+ return new CellValue(ne.getStringValue()); >+ } >+ if (eval instanceof ErrorEval) { >+ return CellValue.getError(((ErrorEval)eval).getErrorCode()); >+ } >+ throw new RuntimeException("Unexpected eval class (" + eval.getClass().getName() + ")"); >+ } >+ >+ /** >+ * Returns a Array CellValue wrapper around the supplied ArrayEval instance. >+ */ >+ private CellValue[][] evaluateFormulaCellArrayValues(HSSFCell cell) { >+ ValueEval eval = _bookEvaluator.evaluate(new HSSFEvaluationCell(cell)); >+ if (eval instanceof ArrayEval) {// support of arrays >+ ArrayEval ae = (ArrayEval) eval; >+ int rowCount = ae.getHeight(); >+ int ColCount = ae.getWidth(); >+ CellValue[][] answer = new CellValue[rowCount][ColCount]; >+ for (int i = 0; i < rowCount; i++) { >+ for (int j = 0; j < ColCount; j++) { >+ ValueEval val = ae.getValue(i, j); >+ answer[i][j] = ArrayFormulaEvaluatorHelper.evalToCellValue(val); >+ } >+ } >+ return answer; >+ } >+ // non-array (usually from aggregate function) >+ CellValue[][] answer = new CellValue[1][1]; >+ answer[0][0] = ArrayFormulaEvaluatorHelper.evalToCellValue(eval); >+ return answer; >+ } >+} >Index: src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java >=================================================================== >--- src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java (revision 892169) >+++ src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java (working copy) >@@ -31,6 +31,7 @@ > import java.util.TreeMap; > > import org.apache.poi.ddf.EscherRecord; >+import org.apache.poi.hssf.model.HSSFFormulaParser; > import org.apache.poi.hssf.model.Sheet; > import org.apache.poi.hssf.model.Workbook; > import org.apache.poi.hssf.record.CellValueRecordInterface; >@@ -44,10 +45,13 @@ > import org.apache.poi.hssf.record.WSBoolRecord; > import org.apache.poi.hssf.record.WindowTwoRecord; > import org.apache.poi.hssf.record.aggregates.DataValidityTable; >+import org.apache.poi.hssf.record.aggregates.FormulaRecordAggregate; > import org.apache.poi.hssf.record.aggregates.WorksheetProtectionBlock; > import org.apache.poi.hssf.record.formula.FormulaShifter; >+import org.apache.poi.hssf.record.formula.Ptg; > import org.apache.poi.hssf.util.PaneInformation; > import org.apache.poi.hssf.util.Region; >+import org.apache.poi.ss.formula.FormulaType; > import org.apache.poi.ss.usermodel.Cell; > import org.apache.poi.ss.usermodel.CellStyle; > import org.apache.poi.ss.usermodel.Row; >@@ -64,6 +68,7 @@ > * @author Shawn Laubach (slaubach at apache dot org) (Just a little) > * @author Jean-Pierre Paris (jean-pierre.paris at m4x dot org) (Just a little, too) > * @author Yegor Kozlov (yegor at apache.org) (Autosizing columns) >+ * @author Petr Udalau(Petr.Udalau at exigenservices.com) - set/remove array formulas > */ > public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { > private static final POILogger log = POILogFactory.getLogger(HSSFSheet.class); >@@ -1870,4 +1875,46 @@ > return wb.getSheetName(idx); > } > >+ public void setArrayFormula(String formula, CellRangeAddress range) { >+ // make sure the formula parses OK first >+ Ptg[] ptgs = HSSFFormulaParser.parse(formula, _workbook, FormulaType.ARRAY, _workbook.getSheetIndex(this)); >+ int firstRow = range.getFirstRow(); >+ int firstColumn = range.getFirstColumn(); >+ for (int rowIn = firstRow; rowIn <= range.getLastRow(); rowIn++) { >+ for (int colIn = firstColumn; colIn <= range.getLastColumn(); colIn++) { >+ HSSFRow row = getRow(rowIn); >+ if (row == null) { >+ row = createRow(rowIn); >+ } >+ HSSFCell cell = row.getCell(colIn); >+ if (cell == null) { >+ cell = row.createCell(colIn); >+ } >+ cell.setCellArrayFormula(range); >+ } >+ } >+ HSSFCell firstArrayFormulaCell = getRow(firstRow).getCell(firstColumn); >+ FormulaRecordAggregate agg = (FormulaRecordAggregate) firstArrayFormulaCell.getCellValueRecord(); >+ agg.setArrayFormula(range, ptgs); >+ } >+ >+ >+ public void removeArrayFormula(Cell cell) { >+ CellValueRecordInterface rec = ((HSSFCell) cell).getCellValueRecord(); >+ if (!(rec instanceof FormulaRecordAggregate)) { >+ throw new IllegalArgumentException("Specified cell is not a formula cell."); >+ } >+ FormulaRecordAggregate fra = (FormulaRecordAggregate) rec; >+ CellRangeAddress range = fra.removeArrayFormula(cell.getRowIndex(), cell.getColumnIndex()); >+ if (range == null) { >+ throw new IllegalArgumentException("Specified cell does not contain an array formula."); >+ } >+ // clear all cells in the range >+ for (int rowIn = range.getFirstRow(); rowIn <= range.getLastRow(); rowIn++) { >+ for (int colIn = range.getFirstColumn(); colIn <= range.getLastColumn(); colIn++) { >+ Cell rCell = getRow(rowIn).getCell(colIn); >+ rCell.setCellType(Cell.CELL_TYPE_BLANK); >+ } >+ } >+ } > } >Index: src/java/org/apache/poi/ss/formula/ArrayEval.java >=================================================================== >--- src/java/org/apache/poi/ss/formula/ArrayEval.java (revision 0) >+++ src/java/org/apache/poi/ss/formula/ArrayEval.java (revision 0) >@@ -0,0 +1,268 @@ >+/* ==================================================================== >+ 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; >+ >+import java.util.ArrayList; >+import java.util.List; >+ >+import org.apache.poi.hssf.record.constant.ErrorConstant; >+import org.apache.poi.hssf.record.formula.ArrayPtg; >+import org.apache.poi.hssf.record.formula.eval.AreaEval; >+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.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; >+import org.apache.poi.ss.util.NumberToTextConverter; >+ >+/** >+ * Class to support evaluated array of values >+ * >+ * @author Zahars Sulkins(Zahars.Sulkins at exigenservices.com) >+ */ >+public final class ArrayEval implements TwoDEval { >+ >+ private final ValueEval[][] _values; >+ private byte componentError = 0; >+ >+ public ArrayEval(ArrayPtg ptg) { >+ if (ptg == null) { >+ throw new IllegalArgumentException("ArrayPtg should not be null"); >+ } >+ Object[][] tokenValues = ptg.getTokenArrayValues(); >+ int nRows = tokenValues.length; >+ int nCols = tokenValues[0].length; >+ ValueEval[][] values = new ValueEval[nRows][nCols]; >+ for (int r=0; r< nRows; r++) { >+ Object[] tokenRow = tokenValues[r]; >+ ValueEval[] row = values[r]; >+ for (int c=0; c< nCols; c++) { >+ row[c] = constructEval(tokenRow[c]); >+ } >+ } >+ _values = values; >+ } >+ >+ public ArrayEval(ValueEval[][] values) { >+ if (values == null) { >+ throw new IllegalArgumentException("null is not allowed"); >+ } >+ int nRows = values.length; >+ int nCols = values[0].length; >+ for (int r=0; r< nRows; r++) { >+ ValueEval[] row = values[r]; >+ for (int c=0; c< nCols; c++) { >+ validateValueEval(row[c]); >+ } >+ } >+ >+ _values = values; >+ } >+ >+ private void validateValueEval(ValueEval valueEval) { >+ if (valueEval instanceof NumberEval) { >+ return; >+ } >+ if (valueEval instanceof StringEval) { >+ return; >+ } >+ if (valueEval instanceof BoolEval) { >+ return; >+ } >+ if (valueEval instanceof ErrorEval) { >+ return; >+ } >+ >+ if (valueEval == null) { >+ if (false) { >+ // TODO throw new IllegalArgumentException("Array elements cannot be null."); >+ } >+ return; >+ } >+ >+ if (valueEval instanceof RefEval) { >+ throw new IllegalArgumentException("Array elements cannot be of type RefEval"); >+ } >+ if (valueEval instanceof AreaEval) { >+ throw new IllegalArgumentException("Array elements cannot be of type AreaEval"); >+ } >+ throw new IllegalArgumentException("Unexpected eval type (" >+ + valueEval.getClass().getSimpleName() + ")."); >+ } >+ >+ public String toString() { >+ StringBuilder sb = new StringBuilder(); >+ sb.append(getClass().getName()).append(" ["); >+ sb.append("{"); >+ for (int r = 0; r < _values.length; r++) { >+ if (r > 0) { >+ sb.append(";"); >+ } >+ for (int c = 0; c < _values[r].length; c++) { >+ if (c > 0) { >+ sb.append(","); >+ } >+ Object o = _values[r][c]; >+ sb.append(getConstantText(o)); >+ } >+ } >+ sb.append("}]"); >+ return sb.toString(); >+ } >+ >+ /** >+ * TODO - remove this method >+ */ >+ public ValueEval[][] getArrayValues() { >+ return _values; >+ } >+ >+ /** >+ * get element of array as Value Eval >+ * @param row >+ * @param col >+ * @return >+ */ >+ public ValueEval getValue(int row, int col) { >+ return _values[row][col]; >+ } >+ >+ /** >+ * Convert Object to ValueEval >+ */ >+ private static ValueEval constructEval(Object o) { >+ if (o == null) { >+ throw new RuntimeException("Array item cannot be null"); >+ } >+ if (o instanceof String) { >+ return new StringEval( (String)o ); >+ } >+ if (o instanceof Double) { >+ return new NumberEval(((Double)o).doubleValue()); >+ } >+ if (o instanceof Boolean) { >+ return BoolEval.valueOf(((Boolean)o).booleanValue()); >+ } >+ if (o instanceof ErrorConstant) { >+ return ErrorEval.valueOf(((ErrorConstant)o).getErrorCode()); >+ } >+ throw new IllegalArgumentException("Unexpected constant class (" + o.getClass()); >+ } >+ >+ >+ /** >+ * get String contains object's value >+ * @param o >+ * @return >+ */ >+ private static String getConstantText(Object o) { >+ >+ if (o == null) { >+ return "Error - null"; >+// TODO throw new RuntimeException("Array item cannot be null"); >+ } >+ if (o instanceof StringEval) { >+ return "\"" + ((StringEval)o).getStringValue() + "\""; >+ } >+ if (o instanceof NumberEval) { >+ return NumberToTextConverter.toText(((NumberEval)o).getNumberValue()); >+ } >+ if (o instanceof Boolean) { >+ return ((Boolean)o).booleanValue() ? "TRUE" : "FALSE"; >+ } >+ if (o instanceof ErrorEval) { >+ return ErrorEval.getText(((ErrorEval)o).getErrorCode()); >+ } >+ throw new IllegalArgumentException("Unexpected constant class (" + o.getClass().getName() + ")"); >+ } >+ >+ /** >+ * return Array as ValueEval list >+ * @return >+ */ >+ public List<ValueEval> getArrayAsEval() { >+ >+ List<ValueEval> l = new ArrayList<ValueEval>(); >+ for(int r=0; r< _values.length; r++) { >+ ValueEval[] row = _values[r]; >+ for (int c=0; c<row.length; c++) { >+ l.add(row[c]); >+ } >+ } >+ return l; >+ } >+ >+ /** >+ * get row count >+ * @return >+ */ >+ public int getHeight() { >+ return _values.length; >+ } >+ >+ /** >+ * get column count >+ * @return >+ */ >+ public int getWidth() { >+ return _values[0].length; >+ } >+ >+ public boolean isRow() { >+ return _values.length == 1; >+ } >+ >+ public boolean isColumn() { >+ return _values[0].length == 1; >+ } >+ public enum BooleanContent{ONLY_FALSE,ONLY_TRUE,MIXED} >+ >+ /** >+ * Check if content of boolean array ONLY_FALSE, ONLY_TRUE or MIXED >+ * if content is not boolean then return MIXED >+ * @return >+ */ >+ public BooleanContent checkBooleanContent() { >+ try { >+ BoolEval first = (BoolEval) _values[0][0]; >+ for (int i = 0; i < _values.length; i++) { >+ for (int j = 0; j < _values[i].length; j++) { >+ if (first.equals(_values[i][j])) { >+ return BooleanContent.MIXED; >+ } >+ } >+ } >+ >+ if (first.getBooleanValue()) { >+ return BooleanContent.ONLY_TRUE; >+ } >+ return BooleanContent.ONLY_FALSE; >+ } catch (Exception e) { >+ return BooleanContent.MIXED; >+ } >+ } >+ >+ public byte getComponentError() { >+ return componentError; >+ } >+ >+ public void setComponentError(byte componentError) { >+ this.componentError = componentError; >+ } >+} >Index: src/java/org/apache/poi/ss/formula/EvaluationCell.java >=================================================================== >--- src/java/org/apache/poi/ss/formula/EvaluationCell.java (revision 892169) >+++ src/java/org/apache/poi/ss/formula/EvaluationCell.java (working copy) >@@ -1,45 +1,51 @@ >-/* ==================================================================== >- 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; >- >-import java.util.HashMap; >- >-/** >- * Abstracts a cell for the purpose of formula evaluation. This interface represents both formula >- * and non-formula cells.<br/> >- * >- * For POI internal use only >- * >- * @author Josh Micich >- */ >-public interface EvaluationCell { >- /** >- * @return an Object that identifies the underlying cell, suitable for use as a key in a {@link HashMap} >- */ >- Object getIdentityKey(); >- >- EvaluationSheet getSheet(); >- int getRowIndex(); >- int getColumnIndex(); >- int getCellType(); >- >- double getNumericCellValue(); >- String getStringCellValue(); >- boolean getBooleanCellValue(); >- int getErrorCellValue(); >-} >+/* ==================================================================== >+ 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; >+ >+import java.util.HashMap; >+ >+/** >+ * Abstracts a cell for the purpose of formula evaluation. This interface represents both formula >+ * and non-formula cells.<br/> >+ * >+ * For POI internal use only >+ * >+ * @author Josh Micich >+ * @author Petr Udalau(Petr.Udalau at exigenservices.com) - array formula support >+ */ >+public interface EvaluationCell { >+ /** >+ * @return an Object that identifies the underlying cell, suitable for use as a key in a {@link HashMap} >+ */ >+ Object getIdentityKey(); >+ >+ EvaluationSheet getSheet(); >+ int getRowIndex(); >+ int getColumnIndex(); >+ int getCellType(); >+ >+ double getNumericCellValue(); >+ String getStringCellValue(); >+ boolean getBooleanCellValue(); >+ int getErrorCellValue(); >+ >+ /** >+ * @return <code>true<code> if cell belongs to to range of Array Formula >+ */ >+ boolean isArrayFormula(); >+} >Index: src/java/org/apache/poi/ss/formula/OperandClassTransformer.java >=================================================================== >--- src/java/org/apache/poi/ss/formula/OperandClassTransformer.java (revision 892169) >+++ src/java/org/apache/poi/ss/formula/OperandClassTransformer.java (working copy) >@@ -1,288 +1,291 @@ >-/* ==================================================================== >- 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; >- >-import org.apache.poi.hssf.record.formula.AbstractFunctionPtg; >-import org.apache.poi.hssf.record.formula.AttrPtg; >-import org.apache.poi.hssf.record.formula.ControlPtg; >-import org.apache.poi.hssf.record.formula.FuncVarPtg; >-import org.apache.poi.hssf.record.formula.MemAreaPtg; >-import org.apache.poi.hssf.record.formula.MemFuncPtg; >-import org.apache.poi.hssf.record.formula.Ptg; >-import org.apache.poi.hssf.record.formula.RangePtg; >-import org.apache.poi.hssf.record.formula.UnionPtg; >-import org.apache.poi.hssf.record.formula.ValueOperatorPtg; >- >-/** >- * This class performs 'operand class' transformation. Non-base tokens are classified into three >- * operand classes: >- * <ul> >- * <li>reference</li> >- * <li>value</li> >- * <li>array</li> >- * </ul> >- * <p/> >- * >- * The final operand class chosen for each token depends on the formula type and the token's place >- * in the formula. If POI gets the operand class wrong, Excel <em>may</em> interpret the formula >- * incorrectly. This condition is typically manifested as a formula cell that displays as '#VALUE!', >- * but resolves correctly when the user presses F2, enter.<p/> >- * >- * The logic implemented here was partially inspired by the description in >- * "OpenOffice.org's Documentation of the Microsoft Excel File Format". The model presented there >- * seems to be inconsistent with observed Excel behaviour (These differences have not been fully >- * investigated). The implementation in this class has been heavily modified in order to satisfy >- * concrete examples of how Excel performs the same logic (see TestRVA).<p/> >- * >- * Hopefully, as additional important test cases are identified and added to the test suite, >- * patterns might become more obvious in this code and allow for simplification. >- * >- * @author Josh Micich >- */ >-final class OperandClassTransformer { >- >- private final int _formulaType; >- >- public OperandClassTransformer(int formulaType) { >- _formulaType = formulaType; >- } >- >- /** >- * Traverses the supplied formula parse tree, calling <tt>Ptg.setClass()</tt> for each non-base >- * token to set its operand class. >- */ >- public void transformFormula(ParseNode rootNode) { >- byte rootNodeOperandClass; >- switch (_formulaType) { >- case FormulaType.CELL: >- rootNodeOperandClass = Ptg.CLASS_VALUE; >- break; >- case FormulaType.NAMEDRANGE: >- case FormulaType.DATAVALIDATION_LIST: >- rootNodeOperandClass = Ptg.CLASS_REF; >- break; >- default: >- throw new RuntimeException("Incomplete code - formula type (" >- + _formulaType + ") not supported yet"); >- >- } >- transformNode(rootNode, rootNodeOperandClass, false); >- } >- >- /** >- * @param callerForceArrayFlag <code>true</code> if one of the current node's parents is a >- * function Ptg which has been changed from default 'V' to 'A' type (due to requirements on >- * the function return value). >- */ >- private void transformNode(ParseNode node, byte desiredOperandClass, >- boolean callerForceArrayFlag) { >- Ptg token = node.getToken(); >- ParseNode[] children = node.getChildren(); >- boolean isSimpleValueFunc = isSimpleValueFunction(token); >- >- if (isSimpleValueFunc) { >- boolean localForceArray = desiredOperandClass == Ptg.CLASS_ARRAY; >- for (int i = 0; i < children.length; i++) { >- transformNode(children[i], desiredOperandClass, localForceArray); >- } >- setSimpleValueFuncClass((AbstractFunctionPtg) token, desiredOperandClass, callerForceArrayFlag); >- return; >- } >- >- if (isSingleArgSum(token)) { >- // Need to process the argument of SUM with transformFunctionNode below >- // so make a dummy FuncVarPtg for that call. >- token = FuncVarPtg.SUM; >- // Note - the tAttrSum token (node.getToken()) is a base >- // token so does not need to have its operand class set >- } >- if (token instanceof ValueOperatorPtg || token instanceof ControlPtg >- || token instanceof MemFuncPtg >- || token instanceof MemAreaPtg >- || token instanceof UnionPtg) { >- // Value Operator Ptgs and Control are base tokens, so token will be unchanged >- // but any child nodes are processed according to desiredOperandClass and callerForceArrayFlag >- >- // As per OOO documentation Sec 3.2.4 "Token Class Transformation", "Step 1" >- // All direct operands of value operators that are initially 'R' type will >- // be converted to 'V' type. >- byte localDesiredOperandClass = desiredOperandClass == Ptg.CLASS_REF ? Ptg.CLASS_VALUE : desiredOperandClass; >- for (int i = 0; i < children.length; i++) { >- transformNode(children[i], localDesiredOperandClass, callerForceArrayFlag); >- } >- return; >- } >- if (token instanceof AbstractFunctionPtg) { >- transformFunctionNode((AbstractFunctionPtg) token, children, desiredOperandClass, callerForceArrayFlag); >- return; >- } >- if (children.length > 0) { >- if (token == RangePtg.instance) { >- // TODO is any token transformation required under the various ref operators? >- return; >- } >- throw new IllegalStateException("Node should not have any children"); >- } >- >- if (token.isBaseToken()) { >- // nothing to do >- return; >- } >- token.setClass(transformClass(token.getPtgClass(), desiredOperandClass, callerForceArrayFlag)); >- } >- >- private static boolean isSingleArgSum(Ptg token) { >- if (token instanceof AttrPtg) { >- AttrPtg attrPtg = (AttrPtg) token; >- return attrPtg.isSum(); >- } >- return false; >- } >- >- private static boolean isSimpleValueFunction(Ptg token) { >- if (token instanceof AbstractFunctionPtg) { >- AbstractFunctionPtg aptg = (AbstractFunctionPtg) token; >- if (aptg.getDefaultOperandClass() != Ptg.CLASS_VALUE) { >- return false; >- } >- int numberOfOperands = aptg.getNumberOfOperands(); >- for (int i=numberOfOperands-1; i>=0; i--) { >- if (aptg.getParameterClass(i) != Ptg.CLASS_VALUE) { >- return false; >- } >- } >- return true; >- } >- return false; >- } >- >- private byte transformClass(byte currentOperandClass, byte desiredOperandClass, >- boolean callerForceArrayFlag) { >- switch (desiredOperandClass) { >- case Ptg.CLASS_VALUE: >- if (!callerForceArrayFlag) { >- return Ptg.CLASS_VALUE; >- } >- // else fall through >- case Ptg.CLASS_ARRAY: >- return Ptg.CLASS_ARRAY; >- case Ptg.CLASS_REF: >- if (!callerForceArrayFlag) { >- return currentOperandClass; >- } >- return Ptg.CLASS_REF; >- } >- throw new IllegalStateException("Unexpected operand class (" + desiredOperandClass + ")"); >- } >- >- private void transformFunctionNode(AbstractFunctionPtg afp, ParseNode[] children, >- byte desiredOperandClass, boolean callerForceArrayFlag) { >- >- boolean localForceArrayFlag; >- byte defaultReturnOperandClass = afp.getDefaultOperandClass(); >- >- if (callerForceArrayFlag) { >- switch (defaultReturnOperandClass) { >- case Ptg.CLASS_REF: >- if (desiredOperandClass == Ptg.CLASS_REF) { >- afp.setClass(Ptg.CLASS_REF); >- } else { >- afp.setClass(Ptg.CLASS_ARRAY); >- } >- localForceArrayFlag = false; >- break; >- case Ptg.CLASS_ARRAY: >- afp.setClass(Ptg.CLASS_ARRAY); >- localForceArrayFlag = false; >- break; >- case Ptg.CLASS_VALUE: >- afp.setClass(Ptg.CLASS_ARRAY); >- localForceArrayFlag = true; >- break; >- default: >- throw new IllegalStateException("Unexpected operand class (" >- + defaultReturnOperandClass + ")"); >- } >- } else { >- if (defaultReturnOperandClass == desiredOperandClass) { >- localForceArrayFlag = false; >- // an alternative would have been to for non-base Ptgs to set their operand class >- // from their default, but this would require the call in many subclasses because >- // the default OC is not known until the end of the constructor >- afp.setClass(defaultReturnOperandClass); >- } else { >- switch (desiredOperandClass) { >- case Ptg.CLASS_VALUE: >- // always OK to set functions to return 'value' >- afp.setClass(Ptg.CLASS_VALUE); >- localForceArrayFlag = false; >- break; >- case Ptg.CLASS_ARRAY: >- switch (defaultReturnOperandClass) { >- case Ptg.CLASS_REF: >- afp.setClass(Ptg.CLASS_REF); >-// afp.setClass(Ptg.CLASS_ARRAY); >- break; >- case Ptg.CLASS_VALUE: >- afp.setClass(Ptg.CLASS_ARRAY); >- break; >- default: >- throw new IllegalStateException("Unexpected operand class (" >- + defaultReturnOperandClass + ")"); >- } >- localForceArrayFlag = (defaultReturnOperandClass == Ptg.CLASS_VALUE); >- break; >- case Ptg.CLASS_REF: >- switch (defaultReturnOperandClass) { >- case Ptg.CLASS_ARRAY: >- afp.setClass(Ptg.CLASS_ARRAY); >- break; >- case Ptg.CLASS_VALUE: >- afp.setClass(Ptg.CLASS_VALUE); >- break; >- default: >- throw new IllegalStateException("Unexpected operand class (" >- + defaultReturnOperandClass + ")"); >- } >- localForceArrayFlag = false; >- break; >- default: >- throw new IllegalStateException("Unexpected operand class (" >- + desiredOperandClass + ")"); >- } >- >- } >- } >- >- for (int i = 0; i < children.length; i++) { >- ParseNode child = children[i]; >- byte paramOperandClass = afp.getParameterClass(i); >- transformNode(child, paramOperandClass, localForceArrayFlag); >- } >- } >- >- private void setSimpleValueFuncClass(AbstractFunctionPtg afp, >- byte desiredOperandClass, boolean callerForceArrayFlag) { >- >- if (callerForceArrayFlag || desiredOperandClass == Ptg.CLASS_ARRAY) { >- afp.setClass(Ptg.CLASS_ARRAY); >- } else { >- afp.setClass(Ptg.CLASS_VALUE); >- } >- } >-} >+/* ==================================================================== >+ 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; >+ >+import org.apache.poi.hssf.record.formula.AbstractFunctionPtg; >+import org.apache.poi.hssf.record.formula.AttrPtg; >+import org.apache.poi.hssf.record.formula.ControlPtg; >+import org.apache.poi.hssf.record.formula.FuncVarPtg; >+import org.apache.poi.hssf.record.formula.MemAreaPtg; >+import org.apache.poi.hssf.record.formula.MemFuncPtg; >+import org.apache.poi.hssf.record.formula.Ptg; >+import org.apache.poi.hssf.record.formula.RangePtg; >+import org.apache.poi.hssf.record.formula.UnionPtg; >+import org.apache.poi.hssf.record.formula.ValueOperatorPtg; >+ >+/** >+ * This class performs 'operand class' transformation. Non-base tokens are classified into three >+ * operand classes: >+ * <ul> >+ * <li>reference</li> >+ * <li>value</li> >+ * <li>array</li> >+ * </ul> >+ * <p/> >+ * >+ * The final operand class chosen for each token depends on the formula type and the token's place >+ * in the formula. If POI gets the operand class wrong, Excel <em>may</em> interpret the formula >+ * incorrectly. This condition is typically manifested as a formula cell that displays as '#VALUE!', >+ * but resolves correctly when the user presses F2, enter.<p/> >+ * >+ * The logic implemented here was partially inspired by the description in >+ * "OpenOffice.org's Documentation of the Microsoft Excel File Format". The model presented there >+ * seems to be inconsistent with observed Excel behaviour (These differences have not been fully >+ * investigated). The implementation in this class has been heavily modified in order to satisfy >+ * concrete examples of how Excel performs the same logic (see TestRVA).<p/> >+ * >+ * Hopefully, as additional important test cases are identified and added to the test suite, >+ * patterns might become more obvious in this code and allow for simplification. >+ * >+ * @author Josh Micich >+ */ >+final class OperandClassTransformer { >+ >+ private final int _formulaType; >+ >+ public OperandClassTransformer(int formulaType) { >+ _formulaType = formulaType; >+ } >+ >+ /** >+ * Traverses the supplied formula parse tree, calling <tt>Ptg.setClass()</tt> for each non-base >+ * token to set its operand class. >+ */ >+ public void transformFormula(ParseNode rootNode) { >+ byte rootNodeOperandClass; >+ switch (_formulaType) { >+ case FormulaType.CELL: >+ rootNodeOperandClass = Ptg.CLASS_VALUE; >+ break; >+ case FormulaType.ARRAY: >+ rootNodeOperandClass = Ptg.CLASS_ARRAY; >+ break; >+ case FormulaType.NAMEDRANGE: >+ case FormulaType.DATAVALIDATION_LIST: >+ rootNodeOperandClass = Ptg.CLASS_REF; >+ break; >+ default: >+ throw new RuntimeException("Incomplete code - formula type (" >+ + _formulaType + ") not supported yet"); >+ >+ } >+ transformNode(rootNode, rootNodeOperandClass, false); >+ } >+ >+ /** >+ * @param callerForceArrayFlag <code>true</code> if one of the current node's parents is a >+ * function Ptg which has been changed from default 'V' to 'A' type (due to requirements on >+ * the function return value). >+ */ >+ private void transformNode(ParseNode node, byte desiredOperandClass, >+ boolean callerForceArrayFlag) { >+ Ptg token = node.getToken(); >+ ParseNode[] children = node.getChildren(); >+ boolean isSimpleValueFunc = isSimpleValueFunction(token); >+ >+ if (isSimpleValueFunc) { >+ boolean localForceArray = desiredOperandClass == Ptg.CLASS_ARRAY; >+ for (int i = 0; i < children.length; i++) { >+ transformNode(children[i], desiredOperandClass, localForceArray); >+ } >+ setSimpleValueFuncClass((AbstractFunctionPtg) token, desiredOperandClass, callerForceArrayFlag); >+ return; >+ } >+ >+ if (isSingleArgSum(token)) { >+ // Need to process the argument of SUM with transformFunctionNode below >+ // so make a dummy FuncVarPtg for that call. >+ token = FuncVarPtg.SUM; >+ // Note - the tAttrSum token (node.getToken()) is a base >+ // token so does not need to have its operand class set >+ } >+ if (token instanceof ValueOperatorPtg || token instanceof ControlPtg >+ || token instanceof MemFuncPtg >+ || token instanceof MemAreaPtg >+ || token instanceof UnionPtg) { >+ // Value Operator Ptgs and Control are base tokens, so token will be unchanged >+ // but any child nodes are processed according to desiredOperandClass and callerForceArrayFlag >+ >+ // As per OOO documentation Sec 3.2.4 "Token Class Transformation", "Step 1" >+ // All direct operands of value operators that are initially 'R' type will >+ // be converted to 'V' type. >+ byte localDesiredOperandClass = desiredOperandClass == Ptg.CLASS_REF ? Ptg.CLASS_VALUE : desiredOperandClass; >+ for (int i = 0; i < children.length; i++) { >+ transformNode(children[i], localDesiredOperandClass, callerForceArrayFlag); >+ } >+ return; >+ } >+ if (token instanceof AbstractFunctionPtg) { >+ transformFunctionNode((AbstractFunctionPtg) token, children, desiredOperandClass, callerForceArrayFlag); >+ return; >+ } >+ if (children.length > 0) { >+ if (token == RangePtg.instance) { >+ // TODO is any token transformation required under the various ref operators? >+ return; >+ } >+ throw new IllegalStateException("Node should not have any children"); >+ } >+ >+ if (token.isBaseToken()) { >+ // nothing to do >+ return; >+ } >+ token.setClass(transformClass(token.getPtgClass(), desiredOperandClass, callerForceArrayFlag)); >+ } >+ >+ private static boolean isSingleArgSum(Ptg token) { >+ if (token instanceof AttrPtg) { >+ AttrPtg attrPtg = (AttrPtg) token; >+ return attrPtg.isSum(); >+ } >+ return false; >+ } >+ >+ private static boolean isSimpleValueFunction(Ptg token) { >+ if (token instanceof AbstractFunctionPtg) { >+ AbstractFunctionPtg aptg = (AbstractFunctionPtg) token; >+ if (aptg.getDefaultOperandClass() != Ptg.CLASS_VALUE) { >+ return false; >+ } >+ int numberOfOperands = aptg.getNumberOfOperands(); >+ for (int i=numberOfOperands-1; i>=0; i--) { >+ if (aptg.getParameterClass(i) != Ptg.CLASS_VALUE) { >+ return false; >+ } >+ } >+ return true; >+ } >+ return false; >+ } >+ >+ private byte transformClass(byte currentOperandClass, byte desiredOperandClass, >+ boolean callerForceArrayFlag) { >+ switch (desiredOperandClass) { >+ case Ptg.CLASS_VALUE: >+ if (!callerForceArrayFlag) { >+ return Ptg.CLASS_VALUE; >+ } >+ // else fall through >+ case Ptg.CLASS_ARRAY: >+ return Ptg.CLASS_ARRAY; >+ case Ptg.CLASS_REF: >+ if (!callerForceArrayFlag) { >+ return currentOperandClass; >+ } >+ return Ptg.CLASS_REF; >+ } >+ throw new IllegalStateException("Unexpected operand class (" + desiredOperandClass + ")"); >+ } >+ >+ private void transformFunctionNode(AbstractFunctionPtg afp, ParseNode[] children, >+ byte desiredOperandClass, boolean callerForceArrayFlag) { >+ >+ boolean localForceArrayFlag; >+ byte defaultReturnOperandClass = afp.getDefaultOperandClass(); >+ >+ if (callerForceArrayFlag) { >+ switch (defaultReturnOperandClass) { >+ case Ptg.CLASS_REF: >+ if (desiredOperandClass == Ptg.CLASS_REF) { >+ afp.setClass(Ptg.CLASS_REF); >+ } else { >+ afp.setClass(Ptg.CLASS_ARRAY); >+ } >+ localForceArrayFlag = false; >+ break; >+ case Ptg.CLASS_ARRAY: >+ afp.setClass(Ptg.CLASS_ARRAY); >+ localForceArrayFlag = false; >+ break; >+ case Ptg.CLASS_VALUE: >+ afp.setClass(Ptg.CLASS_ARRAY); >+ localForceArrayFlag = true; >+ break; >+ default: >+ throw new IllegalStateException("Unexpected operand class (" >+ + defaultReturnOperandClass + ")"); >+ } >+ } else { >+ if (defaultReturnOperandClass == desiredOperandClass) { >+ localForceArrayFlag = false; >+ // an alternative would have been to for non-base Ptgs to set their operand class >+ // from their default, but this would require the call in many subclasses because >+ // the default OC is not known until the end of the constructor >+ afp.setClass(defaultReturnOperandClass); >+ } else { >+ switch (desiredOperandClass) { >+ case Ptg.CLASS_VALUE: >+ // always OK to set functions to return 'value' >+ afp.setClass(Ptg.CLASS_VALUE); >+ localForceArrayFlag = false; >+ break; >+ case Ptg.CLASS_ARRAY: >+ switch (defaultReturnOperandClass) { >+ case Ptg.CLASS_REF: >+ afp.setClass(Ptg.CLASS_REF); >+// afp.setClass(Ptg.CLASS_ARRAY); >+ break; >+ case Ptg.CLASS_VALUE: >+ afp.setClass(Ptg.CLASS_ARRAY); >+ break; >+ default: >+ throw new IllegalStateException("Unexpected operand class (" >+ + defaultReturnOperandClass + ")"); >+ } >+ localForceArrayFlag = (defaultReturnOperandClass == Ptg.CLASS_VALUE); >+ break; >+ case Ptg.CLASS_REF: >+ switch (defaultReturnOperandClass) { >+ case Ptg.CLASS_ARRAY: >+ afp.setClass(Ptg.CLASS_ARRAY); >+ break; >+ case Ptg.CLASS_VALUE: >+ afp.setClass(Ptg.CLASS_VALUE); >+ break; >+ default: >+ throw new IllegalStateException("Unexpected operand class (" >+ + defaultReturnOperandClass + ")"); >+ } >+ localForceArrayFlag = false; >+ break; >+ default: >+ throw new IllegalStateException("Unexpected operand class (" >+ + desiredOperandClass + ")"); >+ } >+ >+ } >+ } >+ >+ for (int i = 0; i < children.length; i++) { >+ ParseNode child = children[i]; >+ byte paramOperandClass = afp.getParameterClass(i); >+ transformNode(child, paramOperandClass, localForceArrayFlag); >+ } >+ } >+ >+ private void setSimpleValueFuncClass(AbstractFunctionPtg afp, >+ byte desiredOperandClass, boolean callerForceArrayFlag) { >+ >+ if (callerForceArrayFlag || desiredOperandClass == Ptg.CLASS_ARRAY) { >+ afp.setClass(Ptg.CLASS_ARRAY); >+ } else { >+ afp.setClass(Ptg.CLASS_VALUE); >+ } >+ } >+} >Index: src/java/org/apache/poi/ss/formula/OperationEvaluationContext.java >=================================================================== >--- src/java/org/apache/poi/ss/formula/OperationEvaluationContext.java (revision 892169) >+++ src/java/org/apache/poi/ss/formula/OperationEvaluationContext.java (working copy) >@@ -1,257 +1,263 @@ >-/* ==================================================================== >- 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; >- >-import org.apache.poi.hssf.record.formula.eval.AreaEval; >-import org.apache.poi.hssf.record.formula.eval.ErrorEval; >-import org.apache.poi.hssf.record.formula.eval.RefEval; >-import org.apache.poi.hssf.record.formula.eval.ValueEval; >-import org.apache.poi.hssf.record.formula.functions.FreeRefFunction; >-import org.apache.poi.ss.SpreadsheetVersion; >-import org.apache.poi.ss.formula.CollaboratingWorkbooksEnvironment.WorkbookNotFoundException; >-import org.apache.poi.ss.formula.EvaluationWorkbook.ExternalSheet; >-import org.apache.poi.ss.util.CellReference; >-import org.apache.poi.ss.util.CellReference.NameType; >- >-/** >- * Contains all the contextual information required to evaluate an operation >- * within a formula >- * >- * For POI internal use only >- * >- * @author Josh Micich >- */ >-public final class OperationEvaluationContext { >- public static final FreeRefFunction UDF = UserDefinedFunction.instance; >- private final EvaluationWorkbook _workbook; >- private final int _sheetIndex; >- private final int _rowIndex; >- private final int _columnIndex; >- private final EvaluationTracker _tracker; >- private final WorkbookEvaluator _bookEvaluator; >- >- public OperationEvaluationContext(WorkbookEvaluator bookEvaluator, EvaluationWorkbook workbook, int sheetIndex, int srcRowNum, >- int srcColNum, EvaluationTracker tracker) { >- _bookEvaluator = bookEvaluator; >- _workbook = workbook; >- _sheetIndex = sheetIndex; >- _rowIndex = srcRowNum; >- _columnIndex = srcColNum; >- _tracker = tracker; >- } >- >- public EvaluationWorkbook getWorkbook() { >- return _workbook; >- } >- >- public int getRowIndex() { >- return _rowIndex; >- } >- >- public int getColumnIndex() { >- return _columnIndex; >- } >- >- SheetRefEvaluator createExternSheetRefEvaluator(ExternSheetReferenceToken ptg) { >- return createExternSheetRefEvaluator(ptg.getExternSheetIndex()); >- } >- SheetRefEvaluator createExternSheetRefEvaluator(int externSheetIndex) { >- ExternalSheet externalSheet = _workbook.getExternalSheet(externSheetIndex); >- WorkbookEvaluator targetEvaluator; >- int otherSheetIndex; >- if (externalSheet == null) { >- // sheet is in same workbook >- otherSheetIndex = _workbook.convertFromExternSheetIndex(externSheetIndex); >- targetEvaluator = _bookEvaluator; >- } else { >- // look up sheet by name from external workbook >- String workbookName = externalSheet.getWorkbookName(); >- try { >- targetEvaluator = _bookEvaluator.getOtherWorkbookEvaluator(workbookName); >- } catch (WorkbookNotFoundException e) { >- throw new RuntimeException(e.getMessage()); >- } >- otherSheetIndex = targetEvaluator.getSheetIndex(externalSheet.getSheetName()); >- if (otherSheetIndex < 0) { >- throw new RuntimeException("Invalid sheet name '" + externalSheet.getSheetName() >- + "' in bool '" + workbookName + "'."); >- } >- } >- return new SheetRefEvaluator(targetEvaluator, _tracker, otherSheetIndex); >- } >- >- /** >- * @return <code>null</code> if either workbook or sheet is not found >- */ >- private SheetRefEvaluator createExternSheetRefEvaluator(String workbookName, String sheetName) { >- WorkbookEvaluator targetEvaluator; >- if (workbookName == null) { >- targetEvaluator = _bookEvaluator; >- } else { >- if (sheetName == null) { >- throw new IllegalArgumentException("sheetName must not be null if workbookName is provided"); >- } >- try { >- targetEvaluator = _bookEvaluator.getOtherWorkbookEvaluator(workbookName); >- } catch (WorkbookNotFoundException e) { >- return null; >- } >- } >- int otherSheetIndex = sheetName == null ? _sheetIndex : targetEvaluator.getSheetIndex(sheetName); >- if (otherSheetIndex < 0) { >- return null; >- } >- return new SheetRefEvaluator(targetEvaluator, _tracker, otherSheetIndex); >- } >- >- public SheetRefEvaluator getRefEvaluatorForCurrentSheet() { >- return new SheetRefEvaluator(_bookEvaluator, _tracker, _sheetIndex); >- } >- >- >- >- /** >- * Resolves a cell or area reference dynamically. >- * @param workbookName the name of the workbook containing the reference. If <code>null</code> >- * the current workbook is assumed. Note - to evaluate formulas which use multiple workbooks, >- * a {@link CollaboratingWorkbooksEnvironment} must be set up. >- * @param sheetName the name of the sheet containing the reference. May be <code>null</code> >- * (when <tt>workbookName</tt> is also null) in which case the current workbook and sheet is >- * assumed. >- * @param refStrPart1 the single cell reference or first part of the area reference. Must not >- * be <code>null</code>. >- * @param refStrPart2 the second part of the area reference. For single cell references this >- * parameter must be <code>null</code> >- * @param isA1Style specifies the format for <tt>refStrPart1</tt> and <tt>refStrPart2</tt>. >- * Pass <code>true</code> for 'A1' style and <code>false</code> for 'R1C1' style. >- * TODO - currently POI only supports 'A1' reference style >- * @return a {@link RefEval} or {@link AreaEval} >- */ >- public ValueEval getDynamicReference(String workbookName, String sheetName, String refStrPart1, >- String refStrPart2, boolean isA1Style) { >- if (!isA1Style) { >- throw new RuntimeException("R1C1 style not supported yet"); >- } >- SheetRefEvaluator sre = createExternSheetRefEvaluator(workbookName, sheetName); >- if (sre == null) { >- return ErrorEval.REF_INVALID; >- } >- // ugly typecast - TODO - make spreadsheet version more easily accessible >- SpreadsheetVersion ssVersion = ((FormulaParsingWorkbook)_workbook).getSpreadsheetVersion(); >- >- NameType part1refType = classifyCellReference(refStrPart1, ssVersion); >- switch (part1refType) { >- case BAD_CELL_OR_NAMED_RANGE: >- return ErrorEval.REF_INVALID; >- case NAMED_RANGE: >- throw new RuntimeException("Cannot evaluate '" + refStrPart1 >- + "'. Indirect evaluation of defined names not supported yet"); >- } >- if (refStrPart2 == null) { >- // no ':' >- switch (part1refType) { >- case COLUMN: >- case ROW: >- return ErrorEval.REF_INVALID; >- case CELL: >- CellReference cr = new CellReference(refStrPart1); >- return new LazyRefEval(cr.getRow(), cr.getCol(), sre); >- } >- throw new IllegalStateException("Unexpected reference classification of '" + refStrPart1 + "'."); >- } >- NameType part2refType = classifyCellReference(refStrPart1, ssVersion); >- switch (part2refType) { >- case BAD_CELL_OR_NAMED_RANGE: >- return ErrorEval.REF_INVALID; >- case NAMED_RANGE: >- throw new RuntimeException("Cannot evaluate '" + refStrPart1 >- + "'. Indirect evaluation of defined names not supported yet"); >- } >- >- if (part2refType != part1refType) { >- // LHS and RHS of ':' must be compatible >- return ErrorEval.REF_INVALID; >- } >- int firstRow, firstCol, lastRow, lastCol; >- switch (part1refType) { >- case COLUMN: >- firstRow =0; >- lastRow = ssVersion.getLastRowIndex(); >- firstCol = parseColRef(refStrPart1); >- lastCol = parseColRef(refStrPart2); >- break; >- case ROW: >- firstCol = 0; >- lastCol = ssVersion.getLastColumnIndex(); >- firstRow = parseRowRef(refStrPart1); >- lastRow = parseRowRef(refStrPart2); >- break; >- case CELL: >- CellReference cr; >- cr = new CellReference(refStrPart1); >- firstRow = cr.getRow(); >- firstCol = cr.getCol(); >- cr = new CellReference(refStrPart2); >- lastRow = cr.getRow(); >- lastCol = cr.getCol(); >- break; >- default: >- throw new IllegalStateException("Unexpected reference classification of '" + refStrPart1 + "'."); >- } >- return new LazyAreaEval(firstRow, firstCol, lastRow, lastCol, sre); >- } >- >- private static int parseRowRef(String refStrPart) { >- return CellReference.convertColStringToIndex(refStrPart); >- } >- >- private static int parseColRef(String refStrPart) { >- return Integer.parseInt(refStrPart) - 1; >- } >- >- private static NameType classifyCellReference(String str, SpreadsheetVersion ssVersion) { >- int len = str.length(); >- if (len < 1) { >- return CellReference.NameType.BAD_CELL_OR_NAMED_RANGE; >- } >- return CellReference.classifyCellReference(str, ssVersion); >- } >- >- public FreeRefFunction findUserDefinedFunction(String functionName) { >- return _bookEvaluator.findUserDefinedFunction(functionName); >- } >- >- public ValueEval getRefEval(int rowIndex, int columnIndex) { >- SheetRefEvaluator sre = getRefEvaluatorForCurrentSheet(); >- return new LazyRefEval(rowIndex, columnIndex, sre); >- } >- public ValueEval getRef3DEval(int rowIndex, int columnIndex, int extSheetIndex) { >- SheetRefEvaluator sre = createExternSheetRefEvaluator(extSheetIndex); >- return new LazyRefEval(rowIndex, columnIndex, sre); >- } >- public ValueEval getAreaEval(int firstRowIndex, int firstColumnIndex, >- int lastRowIndex, int lastColumnIndex) { >- SheetRefEvaluator sre = getRefEvaluatorForCurrentSheet(); >- return new LazyAreaEval(firstRowIndex, firstColumnIndex, lastRowIndex, lastColumnIndex, sre); >- } >- public ValueEval getArea3DEval(int firstRowIndex, int firstColumnIndex, >- int lastRowIndex, int lastColumnIndex, int extSheetIndex) { >- SheetRefEvaluator sre = createExternSheetRefEvaluator(extSheetIndex); >- return new LazyAreaEval(firstRowIndex, firstColumnIndex, lastRowIndex, lastColumnIndex, sre); >- } >-} >+/* ==================================================================== >+ 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; >+ >+import org.apache.poi.hssf.record.formula.eval.AreaEval; >+import org.apache.poi.hssf.record.formula.eval.ErrorEval; >+import org.apache.poi.hssf.record.formula.eval.RefEval; >+import org.apache.poi.hssf.record.formula.eval.ValueEval; >+import org.apache.poi.hssf.record.formula.functions.FreeRefFunction; >+import org.apache.poi.ss.SpreadsheetVersion; >+import org.apache.poi.ss.formula.CollaboratingWorkbooksEnvironment.WorkbookNotFoundException; >+import org.apache.poi.ss.formula.EvaluationWorkbook.ExternalSheet; >+import org.apache.poi.ss.util.CellReference; >+import org.apache.poi.ss.util.CellReference.NameType; >+ >+/** >+ * Contains all the contextual information required to evaluate an operation >+ * within a formula >+ * >+ * For POI internal use only >+ * >+ * @author Josh Micich >+ */ >+public final class OperationEvaluationContext { >+ public static final FreeRefFunction UDF = UserDefinedFunction.instance; >+ private final EvaluationWorkbook _workbook; >+ private final int _sheetIndex; >+ private final int _rowIndex; >+ private final int _columnIndex; >+ private final EvaluationTracker _tracker; >+ private final WorkbookEvaluator _bookEvaluator; >+ private final boolean _inArrayFormulaContext; >+ >+ public OperationEvaluationContext(WorkbookEvaluator bookEvaluator, EvaluationWorkbook workbook, int sheetIndex, int srcRowNum, >+ int srcColNum, EvaluationTracker tracker, boolean inArrayFormulaContext) { >+ _bookEvaluator = bookEvaluator; >+ _workbook = workbook; >+ _sheetIndex = sheetIndex; >+ _rowIndex = srcRowNum; >+ _columnIndex = srcColNum; >+ _tracker = tracker; >+ _inArrayFormulaContext = inArrayFormulaContext; >+ } >+ >+ public EvaluationWorkbook getWorkbook() { >+ return _workbook; >+ } >+ >+ public int getRowIndex() { >+ return _rowIndex; >+ } >+ >+ public int getColumnIndex() { >+ return _columnIndex; >+ } >+ >+ public boolean isInArrayFormulaContext() { >+ return _inArrayFormulaContext; >+ } >+ >+ SheetRefEvaluator createExternSheetRefEvaluator(ExternSheetReferenceToken ptg) { >+ return createExternSheetRefEvaluator(ptg.getExternSheetIndex()); >+ } >+ SheetRefEvaluator createExternSheetRefEvaluator(int externSheetIndex) { >+ ExternalSheet externalSheet = _workbook.getExternalSheet(externSheetIndex); >+ WorkbookEvaluator targetEvaluator; >+ int otherSheetIndex; >+ if (externalSheet == null) { >+ // sheet is in same workbook >+ otherSheetIndex = _workbook.convertFromExternSheetIndex(externSheetIndex); >+ targetEvaluator = _bookEvaluator; >+ } else { >+ // look up sheet by name from external workbook >+ String workbookName = externalSheet.getWorkbookName(); >+ try { >+ targetEvaluator = _bookEvaluator.getOtherWorkbookEvaluator(workbookName); >+ } catch (WorkbookNotFoundException e) { >+ throw new RuntimeException(e.getMessage()); >+ } >+ otherSheetIndex = targetEvaluator.getSheetIndex(externalSheet.getSheetName()); >+ if (otherSheetIndex < 0) { >+ throw new RuntimeException("Invalid sheet name '" + externalSheet.getSheetName() >+ + "' in bool '" + workbookName + "'."); >+ } >+ } >+ return new SheetRefEvaluator(targetEvaluator, _tracker, otherSheetIndex); >+ } >+ >+ /** >+ * @return <code>null</code> if either workbook or sheet is not found >+ */ >+ private SheetRefEvaluator createExternSheetRefEvaluator(String workbookName, String sheetName) { >+ WorkbookEvaluator targetEvaluator; >+ if (workbookName == null) { >+ targetEvaluator = _bookEvaluator; >+ } else { >+ if (sheetName == null) { >+ throw new IllegalArgumentException("sheetName must not be null if workbookName is provided"); >+ } >+ try { >+ targetEvaluator = _bookEvaluator.getOtherWorkbookEvaluator(workbookName); >+ } catch (WorkbookNotFoundException e) { >+ return null; >+ } >+ } >+ int otherSheetIndex = sheetName == null ? _sheetIndex : targetEvaluator.getSheetIndex(sheetName); >+ if (otherSheetIndex < 0) { >+ return null; >+ } >+ return new SheetRefEvaluator(targetEvaluator, _tracker, otherSheetIndex); >+ } >+ >+ public SheetRefEvaluator getRefEvaluatorForCurrentSheet() { >+ return new SheetRefEvaluator(_bookEvaluator, _tracker, _sheetIndex); >+ } >+ >+ >+ >+ /** >+ * Resolves a cell or area reference dynamically. >+ * @param workbookName the name of the workbook containing the reference. If <code>null</code> >+ * the current workbook is assumed. Note - to evaluate formulas which use multiple workbooks, >+ * a {@link CollaboratingWorkbooksEnvironment} must be set up. >+ * @param sheetName the name of the sheet containing the reference. May be <code>null</code> >+ * (when <tt>workbookName</tt> is also null) in which case the current workbook and sheet is >+ * assumed. >+ * @param refStrPart1 the single cell reference or first part of the area reference. Must not >+ * be <code>null</code>. >+ * @param refStrPart2 the second part of the area reference. For single cell references this >+ * parameter must be <code>null</code> >+ * @param isA1Style specifies the format for <tt>refStrPart1</tt> and <tt>refStrPart2</tt>. >+ * Pass <code>true</code> for 'A1' style and <code>false</code> for 'R1C1' style. >+ * TODO - currently POI only supports 'A1' reference style >+ * @return a {@link RefEval} or {@link AreaEval} >+ */ >+ public ValueEval getDynamicReference(String workbookName, String sheetName, String refStrPart1, >+ String refStrPart2, boolean isA1Style) { >+ if (!isA1Style) { >+ throw new RuntimeException("R1C1 style not supported yet"); >+ } >+ SheetRefEvaluator sre = createExternSheetRefEvaluator(workbookName, sheetName); >+ if (sre == null) { >+ return ErrorEval.REF_INVALID; >+ } >+ // ugly typecast - TODO - make spreadsheet version more easily accessible >+ SpreadsheetVersion ssVersion = ((FormulaParsingWorkbook)_workbook).getSpreadsheetVersion(); >+ >+ NameType part1refType = classifyCellReference(refStrPart1, ssVersion); >+ switch (part1refType) { >+ case BAD_CELL_OR_NAMED_RANGE: >+ return ErrorEval.REF_INVALID; >+ case NAMED_RANGE: >+ throw new RuntimeException("Cannot evaluate '" + refStrPart1 >+ + "'. Indirect evaluation of defined names not supported yet"); >+ } >+ if (refStrPart2 == null) { >+ // no ':' >+ switch (part1refType) { >+ case COLUMN: >+ case ROW: >+ return ErrorEval.REF_INVALID; >+ case CELL: >+ CellReference cr = new CellReference(refStrPart1); >+ return new LazyRefEval(cr.getRow(), cr.getCol(), sre); >+ } >+ throw new IllegalStateException("Unexpected reference classification of '" + refStrPart1 + "'."); >+ } >+ NameType part2refType = classifyCellReference(refStrPart1, ssVersion); >+ switch (part2refType) { >+ case BAD_CELL_OR_NAMED_RANGE: >+ return ErrorEval.REF_INVALID; >+ case NAMED_RANGE: >+ throw new RuntimeException("Cannot evaluate '" + refStrPart1 >+ + "'. Indirect evaluation of defined names not supported yet"); >+ } >+ >+ if (part2refType != part1refType) { >+ // LHS and RHS of ':' must be compatible >+ return ErrorEval.REF_INVALID; >+ } >+ int firstRow, firstCol, lastRow, lastCol; >+ switch (part1refType) { >+ case COLUMN: >+ firstRow =0; >+ lastRow = ssVersion.getLastRowIndex(); >+ firstCol = parseColRef(refStrPart1); >+ lastCol = parseColRef(refStrPart2); >+ break; >+ case ROW: >+ firstCol = 0; >+ lastCol = ssVersion.getLastColumnIndex(); >+ firstRow = parseRowRef(refStrPart1); >+ lastRow = parseRowRef(refStrPart2); >+ break; >+ case CELL: >+ CellReference cr; >+ cr = new CellReference(refStrPart1); >+ firstRow = cr.getRow(); >+ firstCol = cr.getCol(); >+ cr = new CellReference(refStrPart2); >+ lastRow = cr.getRow(); >+ lastCol = cr.getCol(); >+ break; >+ default: >+ throw new IllegalStateException("Unexpected reference classification of '" + refStrPart1 + "'."); >+ } >+ return new LazyAreaEval(firstRow, firstCol, lastRow, lastCol, sre); >+ } >+ >+ private static int parseRowRef(String refStrPart) { >+ return CellReference.convertColStringToIndex(refStrPart); >+ } >+ >+ private static int parseColRef(String refStrPart) { >+ return Integer.parseInt(refStrPart) - 1; >+ } >+ >+ private static NameType classifyCellReference(String str, SpreadsheetVersion ssVersion) { >+ int len = str.length(); >+ if (len < 1) { >+ return CellReference.NameType.BAD_CELL_OR_NAMED_RANGE; >+ } >+ return CellReference.classifyCellReference(str, ssVersion); >+ } >+ >+ public FreeRefFunction findUserDefinedFunction(String functionName) { >+ return _bookEvaluator.findUserDefinedFunction(functionName); >+ } >+ >+ public ValueEval getRefEval(int rowIndex, int columnIndex) { >+ SheetRefEvaluator sre = getRefEvaluatorForCurrentSheet(); >+ return new LazyRefEval(rowIndex, columnIndex, sre); >+ } >+ public ValueEval getRef3DEval(int rowIndex, int columnIndex, int extSheetIndex) { >+ SheetRefEvaluator sre = createExternSheetRefEvaluator(extSheetIndex); >+ return new LazyRefEval(rowIndex, columnIndex, sre); >+ } >+ public ValueEval getAreaEval(int firstRowIndex, int firstColumnIndex, >+ int lastRowIndex, int lastColumnIndex) { >+ SheetRefEvaluator sre = getRefEvaluatorForCurrentSheet(); >+ return new LazyAreaEval(firstRowIndex, firstColumnIndex, lastRowIndex, lastColumnIndex, sre); >+ } >+ public ValueEval getArea3DEval(int firstRowIndex, int firstColumnIndex, >+ int lastRowIndex, int lastColumnIndex, int extSheetIndex) { >+ SheetRefEvaluator sre = createExternSheetRefEvaluator(extSheetIndex); >+ return new LazyAreaEval(firstRowIndex, firstColumnIndex, lastRowIndex, lastColumnIndex, sre); >+ } >+} >Index: src/java/org/apache/poi/ss/formula/OperationEvaluatorFactory.java >=================================================================== >--- src/java/org/apache/poi/ss/formula/OperationEvaluatorFactory.java (revision 892169) >+++ src/java/org/apache/poi/ss/formula/OperationEvaluatorFactory.java (working copy) >@@ -42,6 +42,7 @@ > import org.apache.poi.hssf.record.formula.UnaryMinusPtg; > import org.apache.poi.hssf.record.formula.UnaryPlusPtg; > import org.apache.poi.hssf.record.formula.eval.ConcatEval; >+import org.apache.poi.hssf.record.formula.eval.ErrorEval; > import org.apache.poi.hssf.record.formula.eval.FunctionEval; > import org.apache.poi.hssf.record.formula.eval.IntersectionEval; > import org.apache.poi.hssf.record.formula.eval.PercentEval; >@@ -51,15 +52,19 @@ > import org.apache.poi.hssf.record.formula.eval.UnaryMinusEval; > import org.apache.poi.hssf.record.formula.eval.UnaryPlusEval; > import org.apache.poi.hssf.record.formula.eval.ValueEval; >+import org.apache.poi.hssf.record.formula.function.FunctionMetadata; > import org.apache.poi.hssf.record.formula.function.FunctionMetadataRegistry; >+import org.apache.poi.hssf.record.formula.functions.ArrayMode; > import org.apache.poi.hssf.record.formula.functions.Function; > import org.apache.poi.hssf.record.formula.functions.Indirect; >+import org.apache.poi.ss.usermodel.ArrayFormulaEvaluatorHelper; > > /** > * This class creates <tt>OperationEval</tt> instances to help evaluate <tt>OperationPtg</tt> > * formula tokens. > * > * @author Josh Micich >+ * @author Petr Udalau(Petr.Udalau at exigenservices.com) - evaluations of array formulas > */ > final class OperationEvaluatorFactory { > >@@ -110,16 +115,12 @@ > */ > public static ValueEval evaluate(OperationPtg ptg, ValueEval[] args, > OperationEvaluationContext ec) { >- if(ptg == null) { >+ if (ptg == null) { > throw new IllegalArgumentException("ptg must not be null"); > } >- Function result = _instancesByPtgClass.get(ptg); >- >- if (result != null) { >- return result.evaluate(args, ec.getRowIndex(), (short) ec.getColumnIndex()); >- } >- >- if (ptg instanceof AbstractFunctionPtg) { >+ Function func = _instancesByPtgClass.get(ptg); >+ FunctionMetadata functionMetaData = null; >+ if (func == null && ptg instanceof AbstractFunctionPtg) { > AbstractFunctionPtg fptg = (AbstractFunctionPtg)ptg; > int functionIndex = fptg.getFunctionIndex(); > switch (functionIndex) { >@@ -128,9 +129,46 @@ > case FunctionMetadataRegistry.FUNCTION_INDEX_EXTERNAL: > return UserDefinedFunction.instance.evaluate(args, ec); > } >- >- return FunctionEval.getBasicFunction(functionIndex).evaluate(args, ec.getRowIndex(), (short) ec.getColumnIndex()); >+ func = FunctionEval.getBasicFunction(functionIndex); >+ functionMetaData = FunctionMetadataRegistry.getFunctionByIndex(functionIndex); > } >+ if (func != null) { >+ if (func instanceof ArrayMode && ec.isInArrayFormulaContext()) { >+ return evaluateInSpecialModeForArrayFormulas((ArrayMode) func, args, ec); >+ } >+ return invokeOperation(func, functionMetaData, args, ec); >+ } > throw new RuntimeException("Unexpected operation ptg class (" + ptg.getClass().getName() + ")"); > } >+ >+ private static ValueEval evaluateInSpecialModeForArrayFormulas(ArrayMode function, ValueEval[] ops, >+ OperationEvaluationContext ec) { >+ return function.evaluateInArrayFormula(ops, ec.getRowIndex(), ec.getColumnIndex()); >+ } >+ >+ private static ValueEval invokeOperation(Function func, FunctionMetadata functionMetaData, ValueEval[] ops, OperationEvaluationContext ec) { >+ boolean isArrayFormula = ec.isInArrayFormulaContext(); >+ ArrayEval arrayResult = ArrayFormulaEvaluatorHelper.prepareEmptyResult(func,functionMetaData, ops, isArrayFormula); >+ int srcRowIndex = ec.getRowIndex(); >+ int srcColIndex = ec.getColumnIndex(); >+ if (arrayResult == null) { >+ return ArrayFormulaEvaluatorHelper.transferComponentError(func.evaluate(ops, srcRowIndex, srcColIndex), ops); >+ } >+ ValueEval[][] values = arrayResult.getArrayValues(); >+ for (int row = 0; row < values.length ; row++) { >+ for (int col = 0; col < values[row].length ; col++) { >+ if(values[row][col]instanceof ErrorEval) { // We just have set Error for this element so no need to invoke function >+ continue; >+ } >+ ValueEval[] singleInvocationArgs = ArrayFormulaEvaluatorHelper.prepareArgsForLoop(func,functionMetaData, ops, row, col,isArrayFormula); >+ ValueEval elemResult = func.evaluate(singleInvocationArgs, srcRowIndex, srcColIndex); >+ values[row][col] = elemResult; >+// values[row][col] = WorkbookEvaluator.dereferenceValue(elemResult, srcRowIndex, srcColIndex); >+ } >+ } >+ if(arrayResult.getComponentError() == 0){ >+ arrayResult = (ArrayEval)ArrayFormulaEvaluatorHelper.transferComponentError(arrayResult, ops); >+ } >+ return arrayResult; > } >+} >Index: src/java/org/apache/poi/ss/formula/WorkbookEvaluator.java >=================================================================== >--- src/java/org/apache/poi/ss/formula/WorkbookEvaluator.java (revision 892169) >+++ src/java/org/apache/poi/ss/formula/WorkbookEvaluator.java (working copy) >@@ -21,9 +21,11 @@ > import java.util.Map; > import java.util.Stack; > >+import org.apache.poi.hssf.record.formula.AbstractFunctionPtg; > import org.apache.poi.hssf.record.formula.Area3DPtg; > import org.apache.poi.hssf.record.formula.AreaErrPtg; > import org.apache.poi.hssf.record.formula.AreaPtg; >+import org.apache.poi.hssf.record.formula.ArrayPtg; > import org.apache.poi.hssf.record.formula.AttrPtg; > import org.apache.poi.hssf.record.formula.BoolPtg; > import org.apache.poi.hssf.record.formula.ControlPtg; >@@ -48,7 +50,6 @@ > import org.apache.poi.hssf.record.formula.StringPtg; > import org.apache.poi.hssf.record.formula.UnionPtg; > import org.apache.poi.hssf.record.formula.UnknownPtg; >-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; >@@ -58,9 +59,9 @@ > import org.apache.poi.hssf.record.formula.eval.NameXEval; > 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.RefEval; > import org.apache.poi.hssf.record.formula.eval.StringEval; > import org.apache.poi.hssf.record.formula.eval.ValueEval; >+import org.apache.poi.hssf.record.formula.function.FunctionMetadataRegistry; > import org.apache.poi.hssf.record.formula.functions.Choose; > import org.apache.poi.hssf.record.formula.functions.FreeRefFunction; > import org.apache.poi.hssf.record.formula.functions.If; >@@ -68,6 +69,7 @@ > import org.apache.poi.hssf.util.CellReference; > import org.apache.poi.ss.formula.CollaboratingWorkbooksEnvironment.WorkbookNotFoundException; > import org.apache.poi.ss.formula.eval.NotImplementedException; >+import org.apache.poi.ss.usermodel.ArrayFormulaEvaluatorHelper; > import org.apache.poi.ss.usermodel.Cell; > > /** >@@ -252,7 +254,8 @@ > if (!tracker.startEvaluate(cce)) { > return ErrorEval.CIRCULAR_REF_ERROR; > } >- OperationEvaluationContext ec = new OperationEvaluationContext(this, _workbook, sheetIndex, rowIndex, columnIndex, tracker); >+ OperationEvaluationContext ec = new OperationEvaluationContext(this, _workbook, sheetIndex, rowIndex, >+ columnIndex, tracker, srcCell.isArrayFormula()); > > try { > >@@ -334,6 +337,8 @@ > // visibility raised for testing > /* package */ ValueEval evaluateFormula(OperationEvaluationContext ec, Ptg[] ptgs) { > >+ Stack<Boolean> stackSkip = new Stack<Boolean>(); >+ Boolean isSkipActive = Boolean.TRUE; > Stack<ValueEval> stack = new Stack<ValueEval>(); > for (int i = 0, iSize = ptgs.length; i < iSize; i++) { > >@@ -348,11 +353,22 @@ > } > if (attrPtg.isOptimizedChoose()) { > ValueEval arg0 = stack.pop(); >+ if (arg0 instanceof ArrayEval && ((ArrayEval)arg0).checkBooleanContent()== ArrayEval.BooleanContent.MIXED) { >+ stack.push(arg0); >+ stackSkip.push(isSkipActive); >+ // Switch off skip option only for this level >+ isSkipActive = Boolean.FALSE; >+ continue; >+ } >+ ValueEval cond = arg0; >+ if (arg0 instanceof ArrayEval) { >+ cond = ((ArrayEval)arg0).getValue(0,0); >+ } > int[] jumpTable = attrPtg.getJumpTable(); > int dist; > int nChoices = jumpTable.length; > try { >- int switchIndex = Choose.evaluateFirstArg(arg0, ec.getRowIndex(), ec.getColumnIndex()); >+ int switchIndex = Choose.evaluateFirstArg(cond, ec.getRowIndex(), ec.getColumnIndex()); > if (switchIndex<1 || switchIndex > nChoices) { > stack.push(ErrorEval.VALUE_INVALID); > dist = attrPtg.getChooseFuncOffset() + 4; // +4 for tFuncFar(CHOOSE) >@@ -372,8 +388,19 @@ > if (attrPtg.isOptimizedIf()) { > ValueEval arg0 = stack.pop(); > boolean evaluatedPredicate; >+ if (arg0 instanceof ArrayEval && ((ArrayEval)arg0).checkBooleanContent()== ArrayEval.BooleanContent.MIXED) { >+ stack.push(arg0); >+ stackSkip.push(isSkipActive); >+ // Switch off skip option only for this level >+ isSkipActive = new Boolean(false); >+ continue; >+ } >+ ValueEval cond = arg0; >+ if (arg0 instanceof ArrayEval) { >+ cond = ((ArrayEval)arg0).getValue(0,0); >+ } > try { >- evaluatedPredicate = If.evaluateFirstArg(arg0, ec.getRowIndex(), ec.getColumnIndex()); >+ evaluatedPredicate = If.evaluateFirstArg(cond, ec.getRowIndex(), ec.getColumnIndex()); > } catch (EvaluationException e) { > stack.push(e.getErrorEval()); > int dist = attrPtg.getData(); >@@ -398,11 +425,13 @@ > continue; > } > if (attrPtg.isSkip()) { >- int dist = attrPtg.getData()+1; >- i+= countTokensToBeSkipped(ptgs, i, dist); >- if (stack.peek() == MissingArgEval.instance) { >- stack.pop(); >- stack.push(BlankEval.instance); >+ if (isSkipActive.booleanValue()) { >+ int dist = attrPtg.getData()+1; >+ i+= countTokensToBeSkipped(ptgs, i, dist); >+ if (stack.peek() == MissingArgEval.instance) { >+ stack.pop(); >+ stack.push(BlankEval.instance); >+ } > } > continue; > } >@@ -436,6 +465,9 @@ > } > // logDebug("invoke " + operation + " (nAgs=" + numops + ")"); > opResult = OperationEvaluatorFactory.evaluate(optg, ops, ec); >+ if (!isSkipActive.booleanValue() && isSkipSensitive(optg)) { >+ isSkipActive = stackSkip.pop(); >+ } > } else { > opResult = getEvalForPtg(ptg, ec); > } >@@ -450,10 +482,34 @@ > if (!stack.isEmpty()) { > throw new IllegalStateException("evaluation stack not empty"); > } >- return dereferenceResult(value, ec.getRowIndex(), ec.getColumnIndex()); >+ value = dereferenceResult(value, ec.getRowIndex(), ec.getColumnIndex()); >+ if (value == BlankEval.instance) { >+ // Note Excel behaviour here. A blank final final value is converted to zero. >+ return NumberEval.ZERO; >+ // Formulas _never_ evaluate to blank. If a formula appears to have evaluated to >+ // blank, the actual value is empty string. This can be verified with ISBLANK(). >+ } >+ return value; > } > > /** >+ * Has this function "optimized" form? >+ * @return <code>true</code> if the operation can be optimised with tAttr tokens (if, choose, skip) >+ */ >+ private boolean isSkipSensitive(OperationPtg optg) { >+ if (optg instanceof AbstractFunctionPtg){ >+ // Skip sensitive is only "optimized" function - just only "IF" and "choose" >+ AbstractFunctionPtg fptg = (AbstractFunctionPtg)optg; >+ switch (fptg.getFunctionIndex()) { >+ case FunctionMetadataRegistry.FUNCTION_INDEX_IF: >+ case FunctionMetadataRegistry.FUNCTION_INDEX_CHOOSE: >+ return true; >+ } >+ } >+ return false; >+ } >+ >+ /** > * Calculates the number of tokens that the evaluator should skip upon reaching a tAttrSkip. > * > * @return the number of tokens (starting from <tt>startIndex+1</tt>) that need to be skipped >@@ -474,8 +530,17 @@ > } > return index-startIndex; > } >- > /** >+ * Dereferences a single value from any ArrayEval evaluation result. >+ * @param evaluationResult >+ * @param cell >+ * @return >+ */ >+ private static ValueEval dereferenceValue(ArrayEval evaluationResult, EvaluationCell evalCell ) { >+ Cell cell = (Cell)evalCell.getIdentityKey(); >+ return ArrayFormulaEvaluatorHelper.dereferenceValue(evaluationResult,cell); >+ } >+ /** > * Dereferences a single value from any AreaEval or RefEval evaluation > * result. If the supplied evaluationResult is just a plain value, it is > * returned as-is. >@@ -565,7 +630,9 @@ > AreaPtg aptg = (AreaPtg) ptg; > return ec.getAreaEval(aptg.getFirstRow(), aptg.getFirstColumn(), aptg.getLastRow(), aptg.getLastColumn()); > } >- >+ if (ptg instanceof ArrayPtg) { >+ return new ArrayEval((ArrayPtg)ptg); >+ } > if (ptg instanceof UnknownPtg) { > // POI uses UnknownPtg when the encoded Ptg array seems to be corrupted. > // This seems to occur in very rare cases (e.g. unused name formulas in bug 44774, attachment 21790) >@@ -590,11 +657,15 @@ > /** > * Used by the lazy ref evals whenever they need to get the value of a contained cell. > */ >- /* package */ ValueEval evaluateReference(EvaluationSheet sheet, int sheetIndex, int rowIndex, >+ ValueEval evaluateReference(EvaluationSheet sheet, int sheetIndex, int rowIndex, > int columnIndex, EvaluationTracker tracker) { > > EvaluationCell cell = sheet.getCell(rowIndex, columnIndex); >- return evaluateAny(cell, sheetIndex, rowIndex, columnIndex, tracker); >+ ValueEval result = evaluateAny(cell, sheetIndex, rowIndex, columnIndex, tracker); >+ if (result instanceof ArrayEval && cell.isArrayFormula()) { >+ result = dereferenceValue((ArrayEval) result, cell); >+ } >+ return result; > } > public FreeRefFunction findUserDefinedFunction(String functionName) { > return _udfFinder.findFunction(functionName); >Index: src/java/org/apache/poi/ss/formula/eval/forked/ForkedEvaluationCell.java >=================================================================== >--- src/java/org/apache/poi/ss/formula/eval/forked/ForkedEvaluationCell.java (revision 892169) >+++ src/java/org/apache/poi/ss/formula/eval/forked/ForkedEvaluationCell.java (working copy) >@@ -1,132 +1,136 @@ >-/* ==================================================================== >- 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.eval.forked; >- >-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.NumberEval; >-import org.apache.poi.hssf.record.formula.eval.StringEval; >-import org.apache.poi.hssf.record.formula.eval.ValueEval; >-import org.apache.poi.hssf.usermodel.HSSFCell; >-import org.apache.poi.ss.formula.EvaluationCell; >-import org.apache.poi.ss.formula.EvaluationSheet; >-import org.apache.poi.ss.usermodel.Cell; >- >-/** >- * Represents a cell being used for forked evaluation that has had a value set different from the >- * corresponding cell in the shared master workbook. >- * >- * @author Josh Micich >- */ >-final class ForkedEvaluationCell implements EvaluationCell { >- >- private final EvaluationSheet _sheet; >- /** corresponding cell from master workbook */ >- private final EvaluationCell _masterCell; >- private boolean _booleanValue; >- private int _cellType; >- private int _errorValue; >- private double _numberValue; >- private String _stringValue; >- >- public ForkedEvaluationCell(ForkedEvaluationSheet sheet, EvaluationCell masterCell) { >- _sheet = sheet; >- _masterCell = masterCell; >- // start with value blank, but expect construction to be immediately >- setValue(BlankEval.instance); // followed by a proper call to setValue() >- } >- >- public Object getIdentityKey() { >- return _masterCell.getIdentityKey(); >- } >- >- public void setValue(ValueEval value) { >- Class<? extends ValueEval> cls = value.getClass(); >- >- if (cls == NumberEval.class) { >- _cellType = HSSFCell.CELL_TYPE_NUMERIC; >- _numberValue = ((NumberEval)value).getNumberValue(); >- return; >- } >- if (cls == StringEval.class) { >- _cellType = HSSFCell.CELL_TYPE_STRING; >- _stringValue = ((StringEval)value).getStringValue(); >- return; >- } >- if (cls == BoolEval.class) { >- _cellType = HSSFCell.CELL_TYPE_BOOLEAN; >- _booleanValue = ((BoolEval)value).getBooleanValue(); >- return; >- } >- if (cls == ErrorEval.class) { >- _cellType = HSSFCell.CELL_TYPE_ERROR; >- _errorValue = ((ErrorEval)value).getErrorCode(); >- return; >- } >- if (cls == BlankEval.class) { >- _cellType = HSSFCell.CELL_TYPE_BLANK; >- return; >- } >- throw new IllegalArgumentException("Unexpected value class (" + cls.getName() + ")"); >- } >- public void copyValue(Cell destCell) { >- switch (_cellType) { >- case Cell.CELL_TYPE_BLANK: destCell.setCellType(Cell.CELL_TYPE_BLANK); return; >- case Cell.CELL_TYPE_NUMERIC: destCell.setCellValue(_numberValue); return; >- case Cell.CELL_TYPE_BOOLEAN: destCell.setCellValue(_booleanValue); return; >- case Cell.CELL_TYPE_STRING: destCell.setCellValue(_stringValue); return; >- case Cell.CELL_TYPE_ERROR: destCell.setCellErrorValue((byte)_errorValue); return; >- } >- throw new IllegalStateException("Unexpected data type (" + _cellType + ")"); >- } >- >- private void checkCellType(int expectedCellType) { >- if (_cellType != expectedCellType) { >- throw new RuntimeException("Wrong data type (" + _cellType + ")"); >- } >- } >- public int getCellType() { >- return _cellType; >- } >- public boolean getBooleanCellValue() { >- checkCellType(HSSFCell.CELL_TYPE_BOOLEAN); >- return _booleanValue; >- } >- public int getErrorCellValue() { >- checkCellType(HSSFCell.CELL_TYPE_ERROR); >- return _errorValue; >- } >- public double getNumericCellValue() { >- checkCellType(HSSFCell.CELL_TYPE_NUMERIC); >- return _numberValue; >- } >- public String getStringCellValue() { >- checkCellType(HSSFCell.CELL_TYPE_STRING); >- return _stringValue; >- } >- public EvaluationSheet getSheet() { >- return _sheet; >- } >- public int getRowIndex() { >- return _masterCell.getRowIndex(); >- } >- public int getColumnIndex() { >- return _masterCell.getColumnIndex(); >- } >-} >+/* ==================================================================== >+ 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.eval.forked; >+ >+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.NumberEval; >+import org.apache.poi.hssf.record.formula.eval.StringEval; >+import org.apache.poi.hssf.record.formula.eval.ValueEval; >+import org.apache.poi.hssf.usermodel.HSSFCell; >+import org.apache.poi.ss.formula.EvaluationCell; >+import org.apache.poi.ss.formula.EvaluationSheet; >+import org.apache.poi.ss.usermodel.Cell; >+ >+/** >+ * Represents a cell being used for forked evaluation that has had a value set different from the >+ * corresponding cell in the shared master workbook. >+ * >+ * @author Josh Micich >+ */ >+final class ForkedEvaluationCell implements EvaluationCell { >+ >+ private final EvaluationSheet _sheet; >+ /** corresponding cell from master workbook */ >+ private final EvaluationCell _masterCell; >+ private boolean _booleanValue; >+ private int _cellType; >+ private int _errorValue; >+ private double _numberValue; >+ private String _stringValue; >+ >+ public ForkedEvaluationCell(ForkedEvaluationSheet sheet, EvaluationCell masterCell) { >+ _sheet = sheet; >+ _masterCell = masterCell; >+ // start with value blank, but expect construction to be immediately >+ setValue(BlankEval.instance); // followed by a proper call to setValue() >+ } >+ >+ public Object getIdentityKey() { >+ return _masterCell.getIdentityKey(); >+ } >+ >+ public void setValue(ValueEval value) { >+ Class<? extends ValueEval> cls = value.getClass(); >+ >+ if (cls == NumberEval.class) { >+ _cellType = HSSFCell.CELL_TYPE_NUMERIC; >+ _numberValue = ((NumberEval)value).getNumberValue(); >+ return; >+ } >+ if (cls == StringEval.class) { >+ _cellType = HSSFCell.CELL_TYPE_STRING; >+ _stringValue = ((StringEval)value).getStringValue(); >+ return; >+ } >+ if (cls == BoolEval.class) { >+ _cellType = HSSFCell.CELL_TYPE_BOOLEAN; >+ _booleanValue = ((BoolEval)value).getBooleanValue(); >+ return; >+ } >+ if (cls == ErrorEval.class) { >+ _cellType = HSSFCell.CELL_TYPE_ERROR; >+ _errorValue = ((ErrorEval)value).getErrorCode(); >+ return; >+ } >+ if (cls == BlankEval.class) { >+ _cellType = HSSFCell.CELL_TYPE_BLANK; >+ return; >+ } >+ throw new IllegalArgumentException("Unexpected value class (" + cls.getName() + ")"); >+ } >+ public void copyValue(Cell destCell) { >+ switch (_cellType) { >+ case Cell.CELL_TYPE_BLANK: destCell.setCellType(Cell.CELL_TYPE_BLANK); return; >+ case Cell.CELL_TYPE_NUMERIC: destCell.setCellValue(_numberValue); return; >+ case Cell.CELL_TYPE_BOOLEAN: destCell.setCellValue(_booleanValue); return; >+ case Cell.CELL_TYPE_STRING: destCell.setCellValue(_stringValue); return; >+ case Cell.CELL_TYPE_ERROR: destCell.setCellErrorValue((byte)_errorValue); return; >+ } >+ throw new IllegalStateException("Unexpected data type (" + _cellType + ")"); >+ } >+ >+ private void checkCellType(int expectedCellType) { >+ if (_cellType != expectedCellType) { >+ throw new RuntimeException("Wrong data type (" + _cellType + ")"); >+ } >+ } >+ public int getCellType() { >+ return _cellType; >+ } >+ public boolean getBooleanCellValue() { >+ checkCellType(HSSFCell.CELL_TYPE_BOOLEAN); >+ return _booleanValue; >+ } >+ public int getErrorCellValue() { >+ checkCellType(HSSFCell.CELL_TYPE_ERROR); >+ return _errorValue; >+ } >+ public double getNumericCellValue() { >+ checkCellType(HSSFCell.CELL_TYPE_NUMERIC); >+ return _numberValue; >+ } >+ public String getStringCellValue() { >+ checkCellType(HSSFCell.CELL_TYPE_STRING); >+ return _stringValue; >+ } >+ public EvaluationSheet getSheet() { >+ return _sheet; >+ } >+ public int getRowIndex() { >+ return _masterCell.getRowIndex(); >+ } >+ public int getColumnIndex() { >+ return _masterCell.getColumnIndex(); >+ } >+ >+ public boolean isArrayFormula() { >+ return _masterCell.isArrayFormula(); >+ } >+} >Index: src/java/org/apache/poi/ss/usermodel/ArrayFormulaEvaluatorHelper.java >=================================================================== >--- src/java/org/apache/poi/ss/usermodel/ArrayFormulaEvaluatorHelper.java (revision 0) >+++ src/java/org/apache/poi/ss/usermodel/ArrayFormulaEvaluatorHelper.java (revision 0) >@@ -0,0 +1,419 @@ >+/* ==================================================================== >+ 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.usermodel; >+ >+import org.apache.poi.hssf.record.formula.eval.AreaEval; >+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.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; >+import org.apache.poi.hssf.record.formula.function.FunctionMetadata; >+import org.apache.poi.hssf.record.formula.functions.Function; >+import org.apache.poi.hssf.record.formula.functions.FunctionWithArraySupport; >+import org.apache.poi.ss.formula.ArrayEval; >+import org.apache.poi.ss.formula.TwoDEval; >+import org.apache.poi.ss.formula.WorkbookEvaluator; >+import org.apache.poi.ss.util.CellRangeAddress; >+ >+/** >+ * Helper class to manipulate with array formula >+ * This class contains methods, common for HSSF and XSSF FormulaEvaluator >+ * Better solution - to have abstract class for FormulaEvaluator Interface Implementation >+ * All method need to be static >+ * >+ * @author Vladimirs Abramovs (Vladimirs.Abramovs at exigenservices.com) >+ */ >+/** >+ * @author vabramovs >+ * >+ */ >+public class ArrayFormulaEvaluatorHelper { >+ private static final CellValue CELL_VALUE_NA = CellValue.getError(ErrorConstants.ERROR_NA); >+ final static int SCALAR_TYPE = 0; >+ final static int ARRAY_TYPE = 1; >+ private static final ValueEval ERROR_EVAL_NA = ErrorEval.NA; >+ >+ >+ private ArrayFormulaEvaluatorHelper () { >+ // has no instance >+ } >+ >+ >+ /** Transform evaluated Array Values according aimed Range >+ * Depending on dimensions correlation, >+ * result array may be restricted >+ * or his single row/column will cloned >+ * or some cell will set to #N/A >+ * @param cvs >+ * @param range >+ * @return >+ */ >+ public static ValueEval[][] transformToRange(ValueEval[][] cvs, CellRangeAddress range) { >+ return (ValueEval[][]) transformToRange(cvs, range, false); >+ } >+ /** Transform evaluated Array Values according aimed Range >+ * Depending on dimensions correlation, >+ * result array may be restricted >+ * or his single row/column will cloned >+ * or some cell will set to #N/A >+ * @param cvs >+ * @param range >+ * @return >+ */ >+ public static CellValue[][] transformToRange(CellValue[][] cvs, CellRangeAddress range) { >+ return (CellValue[][]) transformToRange(cvs, range, true); >+ } >+ /** Transform evaluated Array Values according aimed Range >+ * Depending on dimensions correlation, >+ * result array may be restricted >+ * or his single row/column will cloned >+ * or some cell will set to #N/A >+ * @param cvs >+ * @param range >+ * @return >+ */ >+ private static Object[][] transformToRange(Object[][] cvs, CellRangeAddress range, >+ boolean isCellValue) { >+ >+ int nRows = range.getLastRow() - range.getFirstRow() + 1; >+ int nColumns = range.getLastColumn() - range.getFirstColumn() + 1; >+ Object[][] result = isCellValue ? new CellValue[nRows][nColumns] : new ValueEval[nRows][nColumns]; >+ int rowStart = range.getFirstRow(); >+ int colStart = range.getFirstColumn(); >+ for (int i = rowStart; i <= range.getLastRow(); i++) { >+ for (int j = colStart; j <= range.getLastColumn(); j++) { >+ Object value; >+ if (i - rowStart < cvs.length && j - colStart < cvs[i - rowStart].length) { >+ value = cvs[i - rowStart][j - colStart]; >+ } else { >+ boolean needClone = false; >+ int cloneRow = 0; >+ int cloneCol = 0; >+ if (cvs.length == 1) { // Need to clone first colm of cvs >+ cloneCol = j - colStart; >+ needClone = true; >+ } >+ if (cvs[0].length == 1) { // Need to clone first row of cvs >+ cloneRow = i - rowStart; >+ needClone = true; >+ } >+ if (needClone && cloneCol < cvs[0].length && cloneRow < cvs.length) { >+ value = cvs[cloneRow][cloneCol]; >+ } else { >+ // For other cases set cell value to #N/A >+ // For those cells we changes also their type to Error >+ value = isCellValue ? CELL_VALUE_NA : ErrorEval.NA; >+ } >+ } >+ result[i - rowStart][j - colStart] = value; >+ } >+ } >+ return result; >+ } >+ >+ /** >+ * Convert Eval value to CellValue >+ * @param val >+ * @return >+ */ >+ public static CellValue evalToCellValue(ValueEval val) { >+ if (val instanceof BoolEval) { >+ return CellValue.valueOf(((BoolEval)val).getBooleanValue()); >+ } >+ if (val instanceof NumberEval) { >+ return new CellValue(((NumberEval)val).getNumberValue()); >+ } >+ if (val instanceof StringEval) { >+ return new CellValue(((StringEval)val).getStringValue()); >+ } >+ if (val instanceof ErrorEval) { >+ return new CellValue(((ErrorEval)val).getErrorCode()); >+ } >+ throw new IllegalStateException("Unexpected value (" + val + ")"); >+ } >+ /** >+ * Get single value from ArrayEval for desired cell >+ * @param evaluationResult >+ * @param cell >+ * @return >+ */ >+ public static ValueEval dereferenceValue(ArrayEval evaluationResult, Cell cell) { >+ CellRangeAddress range = cell.getArrayFormulaRange(); >+ ValueEval[][] rangeVal =ArrayFormulaEvaluatorHelper.transformToRange(evaluationResult.getArrayValues(),range); >+ int rowInArray = cell.getRowIndex()- range.getFirstRow(); >+ int colInArray = cell.getColumnIndex() - range.getFirstColumn(); >+ return rangeVal[rowInArray][colInArray]; >+ } >+ /** >+ * Get type of parameter (SCALAR_TYPE or ARRAY_TYPE) which support function >+ * for given argument >+ * >+ * @param function >+ * @param functionMetaData >+ * @param argIndex >+ * @return >+ */ >+ public static int getParameterType(Function function, FunctionMetadata functionMetaData, int argIndex) { >+ int oldanswer = SCALAR_TYPE; >+ if (function instanceof FunctionWithArraySupport) { >+ // ask new interface(ZS) for argument type >+ if (((FunctionWithArraySupport) function).supportArray(argIndex)) { >+ oldanswer = ARRAY_TYPE; >+ } >+ } >+// int answer = SCALAR_TYPE;; >+// int index = argIndex; >+// if (functionMetaData != null) { >+// byte[] parameterClassCodes = functionMetaData.getParameterClassCodes(); >+// if(index >= parameterClassCodes.length){ // In case of unlimited Vargs >+// index = parameterClassCodes.length-1; >+// } >+// >+// // If function may accept reference it means that it may accept array as result of ref evaluation? >+//// if (parameterClassCodes[index] != OperationPtg.CLASS_VALUE) { >+// if (parameterClassCodes[index] == OperationPtg.CLASS_ARRAY) { >+//// return ARRAY_TYPE; >+// answer = ARRAY_TYPE; >+// } >+// } >+//// return SCALAR_TYPE; >+// if(answer != oldanswer){ >+// String oldtype = oldanswer==ARRAY_TYPE?"Array":"Scalar"; >+// System.out.println("Diff in function "+functionMetaData.getName()+";parameter "+(index+1)+" was "+ oldtype); >+// } >+ return oldanswer; >+ } >+ >+ /** >+ * Prepare empty template, which will keep result of evaluation >+ * A few arguments of function may be array. >+ * In this case result array will have dimension as such arguments union(not intersection). >+ * Union provides correct error setting, while arrays have no concordant dimensions . >+ * This method calculates result's dimension and prepare empty result's holder >+ * >+ * >+ * @param function >+ * @param args >+ * @param arrayFormula >+ * @return <code>null</code> if function returns a scalar result >+ */ >+ >+ public static ArrayEval prepareEmptyResult(Function function, FunctionMetadata functionMetaData,ValueEval[] args, boolean arrayFormula) { >+ boolean foundArrayArgThatNeedIterated = false; >+ boolean criticalError = false; >+ int rowCount = Integer.MIN_VALUE; >+ int colCount = Integer.MIN_VALUE; >+ byte aggregationError = 0; >+ >+ for (int i = 0; i < args.length; i++) { >+ int argRowCount = Integer.MIN_VALUE; >+ int argColCount = Integer.MIN_VALUE; >+ ValueEval arg = args[i]; >+ if (getParameterType(function,functionMetaData, i) == SCALAR_TYPE) { >+ if (arg instanceof ArrayEval) { >+ ArrayEval aa = (ArrayEval) arg; >+ argRowCount = aa.getHeight(); >+ argColCount = aa.getWidth(); >+ if(aa.getComponentError()!= 0) >+ aggregationError = aa.getComponentError(); >+ else{ >+ if(isArrayArgContainsRef(aa)) >+ aggregationError = ErrorConstants.ERROR_VALUE; >+ } >+ >+ } else if (arg instanceof AreaEval && arrayFormula) { >+ AreaEval aa = (AreaEval) arg; >+ argRowCount = aa.getHeight(); >+ argColCount = aa.getWidth(); >+ } else { >+ continue; // Arguments is not array - just skip it >+ } >+ foundArrayArgThatNeedIterated = true; >+ rowCount = Math.max(rowCount, argRowCount); >+ colCount = Math.max(colCount, argColCount); >+ } >+ else { // function accepts Array >+ >+ if (arg instanceof ArrayEval) { // Argument already array >+ ArrayEval aa = (ArrayEval) arg; >+ boolean thisArrayRequreIteration = false; >+ if(aa.getComponentError() !=0) >+ { >+ // This error situation - in this case we don't need to invoke function - simply fill returned array by error code, taking from from parameters >+ criticalError = true; >+ thisArrayRequreIteration = true; >+ aggregationError = aa.getComponentError(); >+ } >+ >+ if(isArrayArgContainsRef(aa)){ >+ // Element is reference that means that in future results will be "aggregate" only once >+ thisArrayRequreIteration = true; >+ aggregationError = ErrorConstants.ERROR_NA; >+ } >+ if(thisArrayRequreIteration){ >+ foundArrayArgThatNeedIterated = true; >+ argRowCount = aa.getHeight(); >+ argColCount = aa.getWidth(); >+ rowCount = Math.max(rowCount, argRowCount); >+ colCount = Math.max(colCount, argColCount); >+ } >+ >+ } // ArraEval >+ } // Array type of argument >+ } // by arguments >+ if (!foundArrayArgThatNeedIterated) { >+ return null; >+ } >+ >+ if(criticalError){ >+ ErrorEval[][] errorArray = new ErrorEval[rowCount][colCount]; >+ for(int row=0;row<rowCount;row++){ >+ for(int col=0;col<colCount;col++){ >+ errorArray[row][col] = ErrorEval.valueOf(aggregationError); >+ } >+ } >+ return new ArrayEval(errorArray); >+ } >+ >+ ValueEval[][] emptyArray = new ValueEval[rowCount][colCount]; >+ ArrayEval answer = new ArrayEval(emptyArray); >+ if(aggregationError != 0) >+ answer.setComponentError(aggregationError); >+ return answer; >+ } >+ >+/** >+ * Does array contain any reference ? >+ * Some Excel function change it's behaviour when it "array" argument contains reference >+ * @param aa >+ * @return >+ */ >+private static boolean isArrayArgContainsRef(ArrayEval aa){ >+ >+ for(int j=0;j<aa.getHeight();j++){ >+ for(int jj=0;jj<aa.getWidth();jj++){ >+ ValueEval elem = aa.getValue(j,jj); >+ if(elem instanceof AreaEval || elem instanceof RefEval ){ >+ // Element is reference that means that in future results will be "aggregate" only once >+ return true; >+ } >+ } >+ } >+ return false; >+} >+ >+ /** >+ * Prepare arguments for next iteration to call function >+ * Each argument may be scalar, full array or element(according iteration) of array >+ * >+ * @param function >+ * @param args >+ * @param i >+ * @param j >+ * @return >+ */ >+ public static ValueEval[] prepareArgsForLoop(Function function,FunctionMetadata functionMetaData, ValueEval[] args, int i, int j,boolean arrayFormula ) { >+ ValueEval[] answer = new ValueEval[args.length]; >+ for (int argIn = 0; argIn < args.length; argIn++) { >+ ValueEval arg = args[argIn]; >+ >+ if (getParameterType(function,functionMetaData, argIn) == SCALAR_TYPE ) { >+ if (arg instanceof TwoDEval) { >+ arg = getArrayValue((TwoDEval) arg, i, j); >+ } >+ } >+ else{ >+ if (arg instanceof ArrayEval) { >+ ValueEval elem = getArrayValue((TwoDEval) arg, i, j); >+ if(elem instanceof AreaEval){ >+ arg = WorkbookEvaluator.dereferenceResult(elem, ((AreaEval)elem).getFirstRow()+i, ((AreaEval)elem).getFirstColumn()+j); >+ } >+ else if(elem instanceof RefEval){ >+ arg = WorkbookEvaluator.dereferenceResult(elem, 0, 0); >+ } >+// System.out.println("Function "+functionMetaData.getName()+" got "+ (argIn+1)+" parameter as array"); >+ } >+ } >+ answer[argIn] = arg; >+ } >+ >+ return answer; >+ } >+ private static ValueEval getArrayValue(TwoDEval tde, int pRowIndex, int pColIndex) { >+ int rowIndex; >+ if (pRowIndex >= tde.getHeight()) { >+ if (!tde.isRow()) { >+ return ERROR_EVAL_NA; >+ } >+ rowIndex = 0; >+ } else { >+ rowIndex = pRowIndex; >+ } >+ int colIndex; >+ if (pColIndex >= tde.getWidth()) { >+ if (!tde.isColumn()) { >+ return ERROR_EVAL_NA; >+ } >+ colIndex = 0; >+ } else { >+ colIndex = pColIndex; >+ } >+ return tde.getValue(rowIndex, colIndex); >+ } >+ >+ >+ /** >+ * check if ops contain arrays and those should be iterated >+ * >+ * @param function >+ * @param ops >+ * @return >+ */ >+ public static boolean checkForArrays(Function function,FunctionMetadata functionMetaData, ValueEval[] ops) { >+ >+ for (int i = 0; i < ops.length; i++) { >+ if ((ops[i] instanceof ArrayEval) && (getParameterType(function,functionMetaData, i) == SCALAR_TYPE)) { >+ return true; >+ } >+ } >+ return false; >+ } >+ >+ /** >+ * Transfer component error (if any) from argument to result >+ * @param to >+ * @param ops >+ * @return >+ */ >+ public static ValueEval transferComponentError(ValueEval to, ValueEval[] ops) { >+ ValueEval result = to; >+ if(result instanceof ArrayEval){ >+ for(int i=0;i<ops.length;i++){ >+ if(ops[i] instanceof ArrayEval){ >+ if(((ArrayEval)ops[i]).getComponentError()!= 0) { >+ ((ArrayEval)result).setComponentError(((ArrayEval)ops[i]).getComponentError()); >+ return result; >+ } >+ } >+ } >+ } >+ return result; >+ } >+} >Index: src/java/org/apache/poi/ss/usermodel/Cell.java >=================================================================== >--- src/java/org/apache/poi/ss/usermodel/Cell.java (revision 892169) >+++ src/java/org/apache/poi/ss/usermodel/Cell.java (working copy) >@@ -21,6 +21,7 @@ > import java.util.Date; > > import org.apache.poi.ss.formula.FormulaParseException; >+import org.apache.poi.ss.util.CellRangeAddress; > > /** > * High level representation of a cell in a row of a spreadsheet. >@@ -372,4 +373,15 @@ > * @param link hypelrink associated with this cell > */ > void setHyperlink(Hyperlink link); >+ >+ /** >+ * Only valid for array formula cells >+ * @return range of the array formula group that the cell belongs to. >+ */ >+ CellRangeAddress getArrayFormulaRange(); >+ >+ /** >+ * @return <code>true</code> if this cell is part of group of cells having a common array formula. >+ */ >+ boolean isPartOfArrayFormulaGroup(); > } >Index: src/java/org/apache/poi/ss/usermodel/Sheet.java >=================================================================== >--- src/java/org/apache/poi/ss/usermodel/Sheet.java (revision 892169) >+++ src/java/org/apache/poi/ss/usermodel/Sheet.java (working copy) >@@ -781,4 +781,19 @@ > */ > boolean isSelected(); > >+ >+ /** >+ * Sets array formula to specified region for result. >+ * >+ * @param formula Formula >+ * @param range Region of array formula for result. >+ */ >+ void setArrayFormula(String formula, CellRangeAddress range); >+ >+ /** >+ * Remove a Array Formula from this sheet. All cells contained in the Array Formula range are removed as well >+ * >+ * @param cell any cell within Array Formula range >+ */ >+ void removeArrayFormula(Cell cell); > } >Index: src/java/org/apache/poi/ss/util/CellRangeAddress.java >=================================================================== >--- src/java/org/apache/poi/ss/util/CellRangeAddress.java (revision 892169) >+++ src/java/org/apache/poi/ss/util/CellRangeAddress.java (working copy) >@@ -24,7 +24,7 @@ > > /** > * See OOO documentation: excelfileformat.pdf sec 2.5.14 - 'Cell Range Address'<p/> >- * >+ * > * Note - {@link SelectionRecord} uses the BIFF5 version of this structure > * @author Dragos Buleandra (dragos.buleandra@trade2b.ro) > */ >@@ -51,7 +51,7 @@ > out.writeShort(getFirstColumn()); > out.writeShort(getLastColumn()); > } >- >+ > public CellRangeAddress(RecordInputStream in) { > super(readUShortAndCheck(in), in.readUShort(), in.readUShort(), in.readUShort()); > } >@@ -72,20 +72,31 @@ > return numberOfItems * ENCODED_SIZE; > } > >- public String formatAsString() { >- StringBuffer sb = new StringBuffer(); >- CellReference cellRefFrom = new CellReference(getFirstRow(), getFirstColumn()); >- CellReference cellRefTo = new CellReference(getLastRow(), getLastColumn()); >- sb.append(cellRefFrom.formatAsString()); >- sb.append(':'); >- sb.append(cellRefTo.formatAsString()); >- return sb.toString(); >- } >+ public String formatAsString() { >+ StringBuffer sb = new StringBuffer(); >+ CellReference cellRefFrom = new CellReference(getFirstRow(), getFirstColumn()); >+ CellReference cellRefTo = new CellReference(getLastRow(), getLastColumn()); >+ sb.append(cellRefFrom.formatAsString()); >+ sb.append(':'); >+ sb.append(cellRefTo.formatAsString()); >+ return sb.toString(); >+ } > >- public static CellRangeAddress valueOf(String ref) { >- int sep = ref.indexOf(":"); >- CellReference cellFrom = new CellReference(ref.substring(0, sep)); >- CellReference cellTo = new CellReference(ref.substring(sep + 1)); >- return new CellRangeAddress(cellFrom.getRow(), cellTo.getRow(), cellFrom.getCol(), cellTo.getCol()); >- } >+ /** >+ * @param ref usually a standard area ref (e.g. "B1:D8"). May be a single cell >+ * ref (e.g. "B5") in which case the result is a 1 x 1 cell range. >+ */ >+ public static CellRangeAddress valueOf(String ref) { >+ int sep = ref.indexOf(":"); >+ CellReference a; >+ CellReference b; >+ if (sep == -1) { >+ a = new CellReference(ref); >+ b = a; >+ } else { >+ a = new CellReference(ref.substring(0, sep)); >+ b = new CellReference(ref.substring(sep + 1)); >+ } >+ return new CellRangeAddress(a.getRow(), b.getRow(), a.getCol(), b.getCol()); >+ } > } >Index: src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFCell.java >=================================================================== >--- src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFCell.java (revision 892169) >+++ src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFCell.java (working copy) >@@ -37,6 +37,7 @@ > import org.apache.poi.ss.usermodel.FormulaError; > import org.apache.poi.ss.usermodel.Hyperlink; > import org.apache.poi.ss.usermodel.RichTextString; >+import org.apache.poi.ss.util.CellRangeAddress; > import org.apache.poi.ss.util.CellReference; > import org.apache.poi.xssf.model.SharedStringsTable; > import org.apache.poi.xssf.model.StylesTable; >@@ -344,7 +345,11 @@ > if(cellType != CELL_TYPE_FORMULA) throw typeMismatch(CELL_TYPE_FORMULA, cellType, false); > > CTCellFormula f = _cell.getF(); >- if(f.getT() == STCellFormulaType.SHARED){ >+ if (isPartOfArrayFormulaGroup() && f == null) { >+ XSSFCell cell = getSheet().getFirstCellInArrayFormula(this); >+ return cell.getCellFormula(); >+ } >+ if (f.getT() == STCellFormulaType.SHARED) { > return convertSharedFormula((int)f.getSi()); > } > return f.getStringValue(); >@@ -371,6 +376,17 @@ > } > > public void setCellFormula(String formula) { >+ setFormula(formula, FormulaType.CELL); >+ } >+ >+ /* package */ void setCellArrayFormula(String formula, CellRangeAddress range) { >+ setFormula(formula, FormulaType.ARRAY); >+ CTCellFormula cellFormula = _cell.getF(); >+ cellFormula.setT(STCellFormulaType.ARRAY); >+ cellFormula.setRef(range.formatAsString()); >+ } >+ >+ private void setFormula(String formula, int formulaType) { > XSSFWorkbook wb = _row.getSheet().getWorkbook(); > if (formula == null) { > wb.onDeleteFormula(this); >@@ -380,7 +396,7 @@ > > XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb); > //validate through the FormulaParser >- FormulaParser.parse(formula, fpb, FormulaType.CELL, wb.getSheetIndex(getSheet())); >+ FormulaParser.parse(formula, fpb, formulaType, wb.getSheetIndex(getSheet())); > > CTCellFormula f = CTCellFormula.Factory.newInstance(); > f.setStringValue(formula); >@@ -461,7 +477,7 @@ > */ > public int getCellType() { > >- if (_cell.getF() != null) { >+ if (_cell.getF() != null || getSheet().isCellInArrayFormulaContext(this)) { > return CELL_TYPE_FORMULA; > } > >@@ -475,7 +491,7 @@ > * on the cached value of the formula > */ > public int getCachedFormulaResultType() { >- if (_cell.getF() == null) { >+ if (getCellType() != CELL_TYPE_FORMULA) { > throw new IllegalStateException("Only formula cells have cached results"); > } > >@@ -941,4 +957,16 @@ > } > throw new IllegalStateException("Unexpected formula result type (" + cellType + ")"); > } >+ public CellRangeAddress getArrayFormulaRange() { >+ XSSFCell cell = getSheet().getFirstCellInArrayFormula(this); >+ if (cell == null) { >+ throw new IllegalStateException("not an array formula cell."); >+ } >+ String formulaRef = cell._cell.getF().getRef(); >+ return CellRangeAddress.valueOf(formulaRef); >+ } >+ >+ public boolean isPartOfArrayFormulaGroup() { >+ return getSheet().isCellInArrayFormulaContext(this); >+ } > } >Index: src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFEvaluationCell.java >=================================================================== >--- src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFEvaluationCell.java (revision 892169) >+++ src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFEvaluationCell.java (working copy) >@@ -1,75 +1,79 @@ >-/* ==================================================================== >- 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.xssf.usermodel; >- >-import org.apache.poi.ss.formula.EvaluationCell; >-import org.apache.poi.ss.formula.EvaluationSheet; >- >-/** >- * XSSF wrapper for a cell under evaluation >- * >- * @author Josh Micich >- */ >-final class XSSFEvaluationCell implements EvaluationCell { >- >- private final EvaluationSheet _evalSheet; >- private final XSSFCell _cell; >- >- public XSSFEvaluationCell(XSSFCell cell, XSSFEvaluationSheet evaluationSheet) { >- _cell = cell; >- _evalSheet = evaluationSheet; >- } >- >- public XSSFEvaluationCell(XSSFCell cell) { >- this(cell, new XSSFEvaluationSheet(cell.getSheet())); >- } >- >- public Object getIdentityKey() { >- // save memory by just using the cell itself as the identity key >- // Note - this assumes HSSFCell has not overridden hashCode and equals >- return _cell; >- } >- >- public XSSFCell getXSSFCell() { >- return _cell; >- } >- public boolean getBooleanCellValue() { >- return _cell.getBooleanCellValue(); >- } >- public int getCellType() { >- return _cell.getCellType(); >- } >- public int getColumnIndex() { >- return _cell.getColumnIndex(); >- } >- public int getErrorCellValue() { >- return _cell.getErrorCellValue(); >- } >- public double getNumericCellValue() { >- return _cell.getNumericCellValue(); >- } >- public int getRowIndex() { >- return _cell.getRowIndex(); >- } >- public EvaluationSheet getSheet() { >- return _evalSheet; >- } >- public String getStringCellValue() { >- return _cell.getRichStringCellValue().getString(); >- } >-} >+/* ==================================================================== >+ 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.xssf.usermodel; >+ >+import org.apache.poi.ss.formula.EvaluationCell; >+import org.apache.poi.ss.formula.EvaluationSheet; >+ >+/** >+ * XSSF wrapper for a cell under evaluation >+ * >+ * @author Josh Micich >+ */ >+final class XSSFEvaluationCell implements EvaluationCell { >+ >+ private final EvaluationSheet _evalSheet; >+ private final XSSFCell _cell; >+ >+ public XSSFEvaluationCell(XSSFCell cell, XSSFEvaluationSheet evaluationSheet) { >+ _cell = cell; >+ _evalSheet = evaluationSheet; >+ } >+ >+ public XSSFEvaluationCell(XSSFCell cell) { >+ this(cell, new XSSFEvaluationSheet(cell.getSheet())); >+ } >+ >+ public Object getIdentityKey() { >+ // save memory by just using the cell itself as the identity key >+ // Note - this assumes HSSFCell has not overridden hashCode and equals >+ return _cell; >+ } >+ >+ public XSSFCell getXSSFCell() { >+ return _cell; >+ } >+ public boolean getBooleanCellValue() { >+ return _cell.getBooleanCellValue(); >+ } >+ public int getCellType() { >+ return _cell.getCellType(); >+ } >+ public int getColumnIndex() { >+ return _cell.getColumnIndex(); >+ } >+ public int getErrorCellValue() { >+ return _cell.getErrorCellValue(); >+ } >+ public double getNumericCellValue() { >+ return _cell.getNumericCellValue(); >+ } >+ public int getRowIndex() { >+ return _cell.getRowIndex(); >+ } >+ public EvaluationSheet getSheet() { >+ return _evalSheet; >+ } >+ public String getStringCellValue() { >+ return _cell.getRichStringCellValue().getString(); >+ } >+ >+ public boolean isArrayFormula() { >+ return _cell.isPartOfArrayFormulaGroup(); >+ } >+} >Index: src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFFormulaEvaluator.java >=================================================================== >--- src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFFormulaEvaluator.java (revision 892169) >+++ src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFFormulaEvaluator.java (working copy) >@@ -1,270 +1,370 @@ >-/* ==================================================================== >- 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.xssf.usermodel; >- >-import java.util.Iterator; >- >-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.NumberEval; >-import org.apache.poi.hssf.record.formula.eval.StringEval; >-import org.apache.poi.hssf.record.formula.eval.ValueEval; >-import org.apache.poi.hssf.record.formula.udf.UDFFinder; >-import org.apache.poi.ss.formula.IStabilityClassifier; >-import org.apache.poi.ss.formula.WorkbookEvaluator; >-import org.apache.poi.ss.usermodel.Cell; >-import org.apache.poi.ss.usermodel.CellValue; >-import org.apache.poi.ss.usermodel.FormulaEvaluator; >-import org.apache.poi.ss.usermodel.Row; >-import org.apache.poi.ss.usermodel.Sheet; >- >-/** >- * Evaluates formula cells.<p/> >- * >- * For performance reasons, this class keeps a cache of all previously calculated intermediate >- * cell values. Be sure to call {@link #clearAllCachedResultValues()} if any workbook cells are changed between >- * calls to evaluate~ methods on this class. >- * >- * @author Amol S. Deshmukh < amolweb at ya hoo dot com > >- * @author Josh Micich >- */ >-public class XSSFFormulaEvaluator implements FormulaEvaluator { >- >- private WorkbookEvaluator _bookEvaluator; >- >- public XSSFFormulaEvaluator(XSSFWorkbook workbook) { >- this(workbook, null, null); >- } >- /** >- * @param stabilityClassifier used to optimise caching performance. Pass <code>null</code> >- * for the (conservative) assumption that any cell may have its definition changed after >- * evaluation begins. >- * @deprecated (Sep 2009) (reduce overloading) use {@link #create(XSSFWorkbook, org.apache.poi.ss.formula.IStabilityClassifier, org.apache.poi.hssf.record.formula.udf.UDFFinder)} >- */ >- @Deprecated >- public XSSFFormulaEvaluator(XSSFWorkbook workbook, IStabilityClassifier stabilityClassifier) { >- _bookEvaluator = new WorkbookEvaluator(XSSFEvaluationWorkbook.create(workbook), stabilityClassifier, null); >- } >- private XSSFFormulaEvaluator(XSSFWorkbook workbook, IStabilityClassifier stabilityClassifier, UDFFinder udfFinder) { >- _bookEvaluator = new WorkbookEvaluator(XSSFEvaluationWorkbook.create(workbook), stabilityClassifier, udfFinder); >- } >- >- /** >- * @param stabilityClassifier used to optimise caching performance. Pass <code>null</code> >- * for the (conservative) assumption that any cell may have its definition changed after >- * evaluation begins. >- * @param udfFinder pass <code>null</code> for default (AnalysisToolPak only) >- */ >- public static XSSFFormulaEvaluator create(XSSFWorkbook workbook, IStabilityClassifier stabilityClassifier, UDFFinder udfFinder) { >- return new XSSFFormulaEvaluator(workbook, stabilityClassifier, udfFinder); >- } >- >- >- /** >- * Should be called whenever there are major changes (e.g. moving sheets) to input cells >- * in the evaluated workbook. >- * Failure to call this method after changing cell values will cause incorrect behaviour >- * of the evaluate~ methods of this class >- */ >- public void clearAllCachedResultValues() { >- _bookEvaluator.clearAllCachedResultValues(); >- } >- public void notifySetFormula(Cell cell) { >- _bookEvaluator.notifyUpdateCell(new XSSFEvaluationCell((XSSFCell)cell)); >- } >- public void notifyDeleteCell(Cell cell) { >- _bookEvaluator.notifyDeleteCell(new XSSFEvaluationCell((XSSFCell)cell)); >- } >- >- /** >- * If cell contains a formula, the formula is evaluated and returned, >- * else the CellValue simply copies the appropriate cell value from >- * the cell and also its cell type. This method should be preferred over >- * evaluateInCell() when the call should not modify the contents of the >- * original cell. >- * @param cell >- */ >- public CellValue evaluate(Cell cell) { >- if (cell == null) { >- return null; >- } >- >- switch (cell.getCellType()) { >- case XSSFCell.CELL_TYPE_BOOLEAN: >- return CellValue.valueOf(cell.getBooleanCellValue()); >- case XSSFCell.CELL_TYPE_ERROR: >- return CellValue.getError(cell.getErrorCellValue()); >- case XSSFCell.CELL_TYPE_FORMULA: >- return evaluateFormulaCellValue(cell); >- case XSSFCell.CELL_TYPE_NUMERIC: >- return new CellValue(cell.getNumericCellValue()); >- case XSSFCell.CELL_TYPE_STRING: >- return new CellValue(cell.getRichStringCellValue().getString()); >- } >- throw new IllegalStateException("Bad cell type (" + cell.getCellType() + ")"); >- } >- >- >- /** >- * If cell contains formula, it evaluates the formula, >- * and saves the result of the formula. The cell >- * remains as a formula cell. >- * Else if cell does not contain formula, this method leaves >- * the cell unchanged. >- * Note that the type of the formula result is returned, >- * so you know what kind of value is also stored with >- * the formula. >- * <pre> >- * int evaluatedCellType = evaluator.evaluateFormulaCell(cell); >- * </pre> >- * Be aware that your cell will hold both the formula, >- * and the result. If you want the cell replaced with >- * the result of the formula, use {@link #evaluate(org.apache.poi.ss.usermodel.Cell)} } >- * @param cell The cell to evaluate >- * @return The type of the formula result (the cell's type remains as HSSFCell.CELL_TYPE_FORMULA however) >- */ >- public int evaluateFormulaCell(Cell cell) { >- if (cell == null || cell.getCellType() != XSSFCell.CELL_TYPE_FORMULA) { >- return -1; >- } >- CellValue cv = evaluateFormulaCellValue(cell); >- // cell remains a formula cell, but the cached value is changed >- setCellValue(cell, cv); >- return cv.getCellType(); >- } >- >- /** >- * If cell contains formula, it evaluates the formula, and >- * puts the formula result back into the cell, in place >- * of the old formula. >- * Else if cell does not contain formula, this method leaves >- * the cell unchanged. >- * Note that the same instance of HSSFCell is returned to >- * allow chained calls like: >- * <pre> >- * int evaluatedCellType = evaluator.evaluateInCell(cell).getCellType(); >- * </pre> >- * Be aware that your cell value will be changed to hold the >- * result of the formula. If you simply want the formula >- * value computed for you, use {@link #evaluateFormulaCell(org.apache.poi.ss.usermodel.Cell)} } >- * @param cell >- */ >- public XSSFCell evaluateInCell(Cell cell) { >- if (cell == null) { >- return null; >- } >- XSSFCell result = (XSSFCell) cell; >- if (cell.getCellType() == XSSFCell.CELL_TYPE_FORMULA) { >- CellValue cv = evaluateFormulaCellValue(cell); >- setCellType(cell, cv); // cell will no longer be a formula cell >- setCellValue(cell, cv); >- } >- return result; >- } >- private static void setCellType(Cell cell, CellValue cv) { >- int cellType = cv.getCellType(); >- switch (cellType) { >- case XSSFCell.CELL_TYPE_BOOLEAN: >- case XSSFCell.CELL_TYPE_ERROR: >- case XSSFCell.CELL_TYPE_NUMERIC: >- case XSSFCell.CELL_TYPE_STRING: >- cell.setCellType(cellType); >- return; >- case XSSFCell.CELL_TYPE_BLANK: >- // never happens - blanks eventually get translated to zero >- case XSSFCell.CELL_TYPE_FORMULA: >- // this will never happen, we have already evaluated the formula >- } >- throw new IllegalStateException("Unexpected cell value type (" + cellType + ")"); >- } >- >- private static void setCellValue(Cell cell, CellValue cv) { >- int cellType = cv.getCellType(); >- switch (cellType) { >- case XSSFCell.CELL_TYPE_BOOLEAN: >- cell.setCellValue(cv.getBooleanValue()); >- break; >- case XSSFCell.CELL_TYPE_ERROR: >- cell.setCellErrorValue(cv.getErrorValue()); >- break; >- case XSSFCell.CELL_TYPE_NUMERIC: >- cell.setCellValue(cv.getNumberValue()); >- break; >- case XSSFCell.CELL_TYPE_STRING: >- cell.setCellValue(new XSSFRichTextString(cv.getStringValue())); >- break; >- case XSSFCell.CELL_TYPE_BLANK: >- // never happens - blanks eventually get translated to zero >- case XSSFCell.CELL_TYPE_FORMULA: >- // this will never happen, we have already evaluated the formula >- default: >- throw new IllegalStateException("Unexpected cell value type (" + cellType + ")"); >- } >- } >- >- /** >- * Loops over all cells in all sheets of the supplied >- * workbook. >- * For cells that contain formulas, their formulas are >- * evaluated, and the results are saved. These cells >- * remain as formula cells. >- * For cells that do not contain formulas, no changes >- * are made. >- * This is a helpful wrapper around looping over all >- * cells, and calling evaluateFormulaCell on each one. >- */ >- public static void evaluateAllFormulaCells(XSSFWorkbook wb) { >- XSSFFormulaEvaluator evaluator = new XSSFFormulaEvaluator(wb); >- for(int i=0; i<wb.getNumberOfSheets(); i++) { >- Sheet sheet = wb.getSheetAt(i); >- >- for (Iterator<Row> rit = sheet.rowIterator(); rit.hasNext();) { >- Row r = rit.next(); >- >- for (Iterator cit = r.cellIterator(); cit.hasNext();) { >- XSSFCell c = (XSSFCell) cit.next(); >- if (c.getCellType() == XSSFCell.CELL_TYPE_FORMULA) >- evaluator.evaluateFormulaCell(c); >- } >- } >- } >- } >- >- /** >- * Returns a CellValue wrapper around the supplied ValueEval instance. >- */ >- private CellValue evaluateFormulaCellValue(Cell cell) { >- ValueEval eval = _bookEvaluator.evaluate(new XSSFEvaluationCell((XSSFCell) cell)); >- if (eval instanceof NumberEval) { >- NumberEval ne = (NumberEval) eval; >- return new CellValue(ne.getNumberValue()); >- } >- if (eval instanceof BoolEval) { >- BoolEval be = (BoolEval) eval; >- return CellValue.valueOf(be.getBooleanValue()); >- } >- if (eval instanceof StringEval) { >- StringEval ne = (StringEval) eval; >- return new CellValue(ne.getStringValue()); >- } >- if (eval instanceof ErrorEval) { >- return CellValue.getError(((ErrorEval)eval).getErrorCode()); >- } >- throw new RuntimeException("Unexpected eval class (" + eval.getClass().getName() + ")"); >- } >-} >+/* ==================================================================== >+ 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.xssf.usermodel; >+ >+import java.util.Iterator; >+ >+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.NumberEval; >+import org.apache.poi.hssf.record.formula.eval.StringEval; >+import org.apache.poi.hssf.record.formula.eval.ValueEval; >+import org.apache.poi.hssf.record.formula.udf.UDFFinder; >+import org.apache.poi.ss.formula.ArrayEval; >+import org.apache.poi.ss.formula.IStabilityClassifier; >+import org.apache.poi.ss.formula.WorkbookEvaluator; >+import org.apache.poi.ss.usermodel.ArrayFormulaEvaluatorHelper; >+import org.apache.poi.ss.usermodel.Cell; >+import org.apache.poi.ss.usermodel.CellValue; >+import org.apache.poi.ss.usermodel.FormulaEvaluator; >+import org.apache.poi.ss.usermodel.Row; >+import org.apache.poi.ss.usermodel.Sheet; >+import org.apache.poi.ss.util.CellRangeAddress; >+ >+/** >+ * Evaluates formula cells.<p/> >+ * >+ * For performance reasons, this class keeps a cache of all previously calculated intermediate >+ * cell values. Be sure to call {@link #clearAllCachedResultValues()} if any workbook cells are changed between >+ * calls to evaluate~ methods on this class. >+ * >+ * @author Amol S. Deshmukh < amolweb at ya hoo dot com > >+ * @author Josh Micich >+ * @author Zahars Sulkins(Zahars.Sulkins at exigenservices.com) - Array Formula support >+ * @author Vladimirs Abramovs(Vladimirs.Abramovs at exigenservices.com) - Array Formula support >+ */ >+public class XSSFFormulaEvaluator implements FormulaEvaluator { >+ >+ private WorkbookEvaluator _bookEvaluator; >+ >+ public XSSFFormulaEvaluator(XSSFWorkbook workbook) { >+ this(workbook, null, null); >+ } >+ /** >+ * @param stabilityClassifier used to optimise caching performance. Pass <code>null</code> >+ * for the (conservative) assumption that any cell may have its definition changed after >+ * evaluation begins. >+ * @deprecated (Sep 2009) (reduce overloading) use {@link #create(XSSFWorkbook, org.apache.poi.ss.formula.IStabilityClassifier, org.apache.poi.hssf.record.formula.udf.UDFFinder)} >+ */ >+ @Deprecated >+ public XSSFFormulaEvaluator(XSSFWorkbook workbook, IStabilityClassifier stabilityClassifier) { >+ _bookEvaluator = new WorkbookEvaluator(XSSFEvaluationWorkbook.create(workbook), stabilityClassifier, null); >+ } >+ private XSSFFormulaEvaluator(XSSFWorkbook workbook, IStabilityClassifier stabilityClassifier, UDFFinder udfFinder) { >+ _bookEvaluator = new WorkbookEvaluator(XSSFEvaluationWorkbook.create(workbook), stabilityClassifier, udfFinder); >+ } >+ >+ /** >+ * @param stabilityClassifier used to optimise caching performance. Pass <code>null</code> >+ * for the (conservative) assumption that any cell may have its definition changed after >+ * evaluation begins. >+ * @param udfFinder pass <code>null</code> for default (AnalysisToolPak only) >+ */ >+ public static XSSFFormulaEvaluator create(XSSFWorkbook workbook, IStabilityClassifier stabilityClassifier, UDFFinder udfFinder) { >+ return new XSSFFormulaEvaluator(workbook, stabilityClassifier, udfFinder); >+ } >+ >+ >+ /** >+ * Should be called whenever there are major changes (e.g. moving sheets) to input cells >+ * in the evaluated workbook. >+ * Failure to call this method after changing cell values will cause incorrect behaviour >+ * of the evaluate~ methods of this class >+ */ >+ public void clearAllCachedResultValues() { >+ _bookEvaluator.clearAllCachedResultValues(); >+ } >+ public void notifySetFormula(Cell cell) { >+ _bookEvaluator.notifyUpdateCell(new XSSFEvaluationCell((XSSFCell)cell)); >+ } >+ public void notifyDeleteCell(Cell cell) { >+ _bookEvaluator.notifyDeleteCell(new XSSFEvaluationCell((XSSFCell)cell)); >+ } >+ >+ /** >+ * If cell contains a formula, the formula is evaluated and returned, >+ * else the CellValue simply copies the appropriate cell value from >+ * the cell and also its cell type. This method should be preferred over >+ * evaluateInCell() when the call should not modify the contents of the >+ * original cell. >+ * @param cell >+ */ >+ public CellValue evaluate(Cell cell) { >+ if (cell == null) { >+ return null; >+ } >+ >+ switch (cell.getCellType()) { >+ case XSSFCell.CELL_TYPE_BOOLEAN: >+ return CellValue.valueOf(cell.getBooleanCellValue()); >+ case XSSFCell.CELL_TYPE_ERROR: >+ return CellValue.getError(cell.getErrorCellValue()); >+ case XSSFCell.CELL_TYPE_FORMULA: >+ return evaluateFormulaCellValue(cell); >+ case XSSFCell.CELL_TYPE_NUMERIC: >+ return new CellValue(cell.getNumericCellValue()); >+ case XSSFCell.CELL_TYPE_STRING: >+ return new CellValue(cell.getRichStringCellValue().getString()); >+ } >+ throw new IllegalStateException("Bad cell type (" + cell.getCellType() + ")"); >+ } >+ >+ >+ /** >+ * If cell contains formula, it evaluates the formula, >+ * and saves the result of the formula. The cell >+ * remains as a formula cell. >+ * Else if cell does not contain formula, this method leaves >+ * the cell unchanged. >+ * Note that the type of the formula result is returned, >+ * so you know what kind of value is also stored with >+ * the formula. >+ * <pre> >+ * int evaluatedCellType = evaluator.evaluateFormulaCell(cell); >+ * </pre> >+ * Be aware that your cell will hold both the formula, >+ * and the result. If you want the cell replaced with >+ * the result of the formula, use {@link #evaluate(org.apache.poi.ss.usermodel.Cell)} } >+ * @param cell The cell to evaluate >+ * @return The type of the formula result (the cell's type remains as HSSFCell.CELL_TYPE_FORMULA however) >+ */ >+ public int evaluateFormulaCell(Cell cell) { >+ if (cell == null || cell.getCellType() != XSSFCell.CELL_TYPE_FORMULA) { >+ return -1; >+ } >+ // cell remains a formula cell, but the cached value is changed >+ CellValue cv; >+ if (cell.isPartOfArrayFormulaGroup()) { // Array Formula Context >+ CellValue[][] cvs = evaluateFormulaCellArrayValues((XSSFCell) cell); >+ CellValue[][] values = setCellValues(cell, cvs); >+ int rowIndex = cell.getRowIndex() - cell.getArrayFormulaRange().getFirstRow(); >+ int colIndex = cell.getColumnIndex() - cell.getArrayFormulaRange().getFirstColumn(); >+ cv = values[rowIndex][colIndex]; >+ } else { // Single Formula >+ cv = evaluateFormulaCellValue(cell); >+ setCellValue(cell, cv); >+ } >+ return cv.getCellType(); >+ } >+ >+ /** >+ * If cell contains formula, it evaluates the formula, and >+ * puts the formula result back into the cell, in place >+ * of the old formula. >+ * Else if cell does not contain formula, this method leaves >+ * the cell unchanged. >+ * Note that the same instance of HSSFCell is returned to >+ * allow chained calls like: >+ * <pre> >+ * int evaluatedCellType = evaluator.evaluateInCell(cell).getCellType(); >+ * </pre> >+ * Be aware that your cell value will be changed to hold the >+ * result of the formula. If you simply want the formula >+ * value computed for you, use {@link #evaluateFormulaCell(org.apache.poi.ss.usermodel.Cell)} } >+ * @param cell >+ */ >+ public XSSFCell evaluateInCell(Cell cell) { >+ if (cell == null) { >+ return null; >+ } >+ XSSFCell result = (XSSFCell) cell; >+ if (cell.getCellType() == XSSFCell.CELL_TYPE_FORMULA) { >+ if (cell.isPartOfArrayFormulaGroup()) { // Array Formula Context >+ CellValue[][] cvs = evaluateFormulaCellArrayValues((XSSFCell) cell); >+ setCellsTypes(cell, cvs); // cells will no longer be a formula cell >+ setCellValues(cell, cvs); >+ >+ } else { // Single Formula >+ CellValue cv = evaluateFormulaCellValue(cell); >+ setCellType(cell, cv); // cell will no longer be a formula cell >+ setCellValue(cell, cv); >+ >+ } >+ } >+ return result; >+ } >+ private static void setCellType(Cell cell, CellValue cv) { >+ int cellType = cv.getCellType(); >+ switch (cellType) { >+ case XSSFCell.CELL_TYPE_BOOLEAN: >+ case XSSFCell.CELL_TYPE_ERROR: >+ case XSSFCell.CELL_TYPE_NUMERIC: >+ case XSSFCell.CELL_TYPE_STRING: >+ cell.setCellType(cellType); >+ return; >+ case XSSFCell.CELL_TYPE_BLANK: >+ // never happens - blanks eventually get translated to zero >+ case XSSFCell.CELL_TYPE_FORMULA: >+ // this will never happen, we have already evaluated the formula >+ } >+ throw new IllegalStateException("Unexpected cell value type (" + cellType + ")"); >+ } >+ >+ private static void setCellValue(Cell cell, CellValue cv) { >+ int cellType = cv.getCellType(); >+ switch (cellType) { >+ case XSSFCell.CELL_TYPE_BOOLEAN: >+ cell.setCellValue(cv.getBooleanValue()); >+ break; >+ case XSSFCell.CELL_TYPE_ERROR: >+ cell.setCellErrorValue(cv.getErrorValue()); >+ break; >+ case XSSFCell.CELL_TYPE_NUMERIC: >+ cell.setCellValue(cv.getNumberValue()); >+ break; >+ case XSSFCell.CELL_TYPE_STRING: >+ cell.setCellValue(new XSSFRichTextString(cv.getStringValue())); >+ break; >+ case XSSFCell.CELL_TYPE_BLANK: >+ // never happens - blanks eventually get translated to zero >+ case XSSFCell.CELL_TYPE_FORMULA: >+ // this will never happen, we have already evaluated the formula >+ default: >+ throw new IllegalStateException("Unexpected cell value type (" + cellType + ")"); >+ } >+ } >+ >+ private void setCellsTypes(Cell cell, CellValue[][] cvs) { >+ CellRangeAddress range = cell.getArrayFormulaRange(); >+ int rowStart = range.getFirstRow(); >+ int colStart = range.getFirstColumn(); >+ Sheet sheet = cell.getSheet(); >+ for (int i = rowStart; i <= range.getLastRow(); i++) { >+ for (int j = colStart; j <= range.getLastColumn(); j++) { >+ Row row = sheet.getRow(i); >+ Cell c = row.getCell(j); >+ if ((i - rowStart) < cvs.length && (j - colStart) < cvs[i - rowStart].length) { >+ setCellType(c, cvs[i - rowStart][j - colStart]); >+ } >+ } >+ } >+ } >+ >+ /** >+ * Set value in Range >+ * >+ * @param cell >+ * @param cvs >+ * @return >+ */ >+ private CellValue[][] setCellValues(Cell cell, CellValue[][] cvs) { >+ CellRangeAddress range = cell.getArrayFormulaRange(); >+ int rowStart = range.getFirstRow(); >+ int colStart = range.getFirstColumn(); >+ Sheet sheet = cell.getSheet(); >+ CellValue[][] answer = ArrayFormulaEvaluatorHelper.transformToRange(cvs, range); >+ for (int i = rowStart; i <= range.getLastRow(); i++) { >+ for (int j = colStart; j <= range.getLastColumn(); j++) { >+ Row row = sheet.getRow(i); >+ if (row == null) { >+ row = sheet.createRow(i); >+ } >+ Cell c = row.getCell(j); >+ if (c == null) { >+ c = row.createCell(j); >+ } >+ CellValue cellValue = answer[i - rowStart][j - colStart]; >+ setCellValue(c, cellValue); >+ } >+ } >+ return answer; >+ } >+ >+ /** >+ * Loops over all cells in all sheets of the supplied >+ * workbook. >+ * For cells that contain formulas, their formulas are >+ * evaluated, and the results are saved. These cells >+ * remain as formula cells. >+ * For cells that do not contain formulas, no changes >+ * are made. >+ * This is a helpful wrapper around looping over all >+ * cells, and calling evaluateFormulaCell on each one. >+ */ >+ public static void evaluateAllFormulaCells(XSSFWorkbook wb) { >+ XSSFFormulaEvaluator evaluator = new XSSFFormulaEvaluator(wb); >+ for(int i=0; i<wb.getNumberOfSheets(); i++) { >+ Sheet sheet = wb.getSheetAt(i); >+ >+ for (Iterator<Row> rit = sheet.rowIterator(); rit.hasNext();) { >+ Row r = rit.next(); >+ >+ for (Iterator cit = r.cellIterator(); cit.hasNext();) { >+ XSSFCell c = (XSSFCell) cit.next(); >+ if (c.getCellType() == XSSFCell.CELL_TYPE_FORMULA) { >+ evaluator.evaluateFormulaCell(c); >+ } >+ } >+ } >+ } >+ } >+ >+ /** >+ * Returns a CellValue wrapper around the supplied ValueEval instance. >+ */ >+ private CellValue evaluateFormulaCellValue(Cell cell) { >+ ValueEval eval = _bookEvaluator.evaluate(new XSSFEvaluationCell((XSSFCell) cell)); >+ if (eval instanceof ArrayEval) {// support of arrays >+ if (cell.isPartOfArrayFormulaGroup()) { >+ eval = ArrayFormulaEvaluatorHelper.dereferenceValue((ArrayEval) eval, cell); >+ } else { >+ eval = ((ArrayEval) eval).getValue(0, 0); >+ } >+ } >+ if (eval instanceof NumberEval) { >+ NumberEval ne = (NumberEval) eval; >+ return new CellValue(ne.getNumberValue()); >+ } >+ if (eval instanceof BoolEval) { >+ BoolEval be = (BoolEval) eval; >+ return CellValue.valueOf(be.getBooleanValue()); >+ } >+ if (eval instanceof StringEval) { >+ StringEval ne = (StringEval) eval; >+ return new CellValue(ne.getStringValue()); >+ } >+ if (eval instanceof ErrorEval) { >+ return CellValue.getError(((ErrorEval)eval).getErrorCode()); >+ } >+ throw new RuntimeException("Unexpected eval class (" + eval.getClass().getName() + ")"); >+ } >+ >+ /** >+ * Returns a Array CellValue wrapper around the supplied ArrayEval instance. >+ */ >+ private CellValue[][] evaluateFormulaCellArrayValues(XSSFCell cell) { >+ ValueEval eval = _bookEvaluator.evaluate(new XSSFEvaluationCell(cell)); >+ if (eval instanceof ArrayEval) {// support of arrays >+ ArrayEval ae = (ArrayEval) eval; >+ int rowCount = ae.getHeight(); >+ int colCount = ae.getWidth(); >+ CellValue[][] answer = new CellValue[rowCount][colCount]; >+ for (int i = 0; i < rowCount; i++) { >+ for (int j = 0; j < colCount; j++) { >+ ValueEval val = ae.getValue(i, j); >+ answer[i][j] = ArrayFormulaEvaluatorHelper.evalToCellValue(val); >+ } >+ } >+ return answer; >+ } >+ // non-array (usually from aggregate function) >+ CellValue[][] answer = new CellValue[1][1]; >+ answer[0][0] = ArrayFormulaEvaluatorHelper.evalToCellValue(eval); >+ return answer; >+ } >+} >Index: src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFSheet.java >=================================================================== >--- src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFSheet.java (revision 892169) >+++ src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFSheet.java (working copy) >@@ -79,6 +79,7 @@ > private ColumnHelper columnHelper; > private CommentsTable sheetComments; > private Map<Integer, XSSFCell> sharedFormulas; >+ private List<CellRangeAddress> arrayFormulas; > > /** > * Creates new XSSFSheet - called by XSSFWorkbook to create a sheet from scratch. >@@ -153,6 +154,7 @@ > private void initRows(CTWorksheet worksheet) { > rows = new TreeMap<Integer, XSSFRow>(); > sharedFormulas = new HashMap<Integer, XSSFCell>(); >+ arrayFormulas = new ArrayList<CellRangeAddress>(); > for (CTRow row : worksheet.getSheetData().getRowArray()) { > XSSFRow r = new XSSFRow(row, this); > rows.put(r.getRowNum(), r); >@@ -2316,9 +2318,12 @@ > //collect cells holding shared formulas > CTCell ct = cell.getCTCell(); > CTCellFormula f = ct.getF(); >- if(f != null && f.getT() == STCellFormulaType.SHARED && f.isSetRef() && f.getStringValue() != null){ >+ if (f != null && f.getT() == STCellFormulaType.SHARED && f.isSetRef() && f.getStringValue() != null) { > sharedFormulas.put((int)f.getSi(), cell); > } >+ if (f != null && f.getT() == STCellFormulaType.ARRAY && f.getRef() != null) { >+ arrayFormulas.add(CellRangeAddress.valueOf(f.getRef())); >+ } > } > > @Override >@@ -2676,4 +2681,54 @@ > private boolean sheetProtectionEnabled() { > return worksheet.getSheetProtection().getSheet(); > } >+ >+ /* package */ boolean isCellInArrayFormulaContext(XSSFCell cell) { >+ for (CellRangeAddress range : arrayFormulas) { >+ if (range.isInRange(cell.getRowIndex(), cell.getColumnIndex())) { >+ return true; >+ } >+ } >+ return false; >+ } >+ >+ /* package */ XSSFCell getFirstCellInArrayFormula(XSSFCell cell) { >+ for (CellRangeAddress range : arrayFormulas) { >+ if (range.isInRange(cell.getRowIndex(), cell.getColumnIndex())) { >+ return getRow(range.getFirstRow()).getCell(range.getFirstColumn()); >+ } >+ } >+ return null; >+ } >+ >+ public void setArrayFormula(String formula, CellRangeAddress range) { >+ XSSFRow row = getRow(range.getFirstRow()); >+ if (row == null) { >+ row = createRow(range.getFirstRow()); >+ } >+ XSSFCell mainArrayFormulaCell = row.getCell(range.getFirstColumn()); >+ if (mainArrayFormulaCell == null) { >+ mainArrayFormulaCell = row.createCell(range.getFirstColumn()); >+ } >+ mainArrayFormulaCell.setCellArrayFormula(formula, range); >+ arrayFormulas.add(range); >+ } >+ >+ public void removeArrayFormula(Cell cell) { >+ for (CellRangeAddress range : arrayFormulas) { >+ if (range.isInRange(cell.getRowIndex(), cell.getColumnIndex())) { >+ arrayFormulas.remove(range); >+ for (int rowIndex = range.getFirstRow(); rowIndex <= range.getLastRow(); rowIndex++) { >+ XSSFRow row = getRow(rowIndex); >+ for (int columnIndex = range.getFirstColumn(); columnIndex <= range.getLastColumn(); columnIndex++) { >+ XSSFCell arrayFormulaCell = row.getCell(columnIndex); >+ if (arrayFormulaCell != null) { >+ arrayFormulaCell.setCellType(Cell.CELL_TYPE_BLANK); >+ } >+ } >+ } >+ return; >+ } >+ } >+ throw new RuntimeException("Cell does not belong to Array Formula"); >+ } > } >Index: src/ooxml/testcases/org/apache/poi/xssf/usermodel/AllXSSFUsermodelTests.java >=================================================================== >--- src/ooxml/testcases/org/apache/poi/xssf/usermodel/AllXSSFUsermodelTests.java (revision 892169) >+++ src/ooxml/testcases/org/apache/poi/xssf/usermodel/AllXSSFUsermodelTests.java (working copy) >@@ -1,68 +1,72 @@ >-/* ==================================================================== >- 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.xssf.usermodel; >- >-import junit.framework.Test; >-import junit.framework.TestSuite; >- >-import org.apache.poi.xssf.usermodel.extensions.TestXSSFBorder; >-import org.apache.poi.xssf.usermodel.extensions.TestXSSFCellFill; >-import org.apache.poi.xssf.usermodel.extensions.TestXSSFSheetComments; >-import org.apache.poi.xssf.usermodel.helpers.TestColumnHelper; >-import org.apache.poi.xssf.usermodel.helpers.TestHeaderFooterHelper; >- >-/** >- * Collects all tests for <tt>org.apache.poi.xssf.usermodel</tt> and sub-packages. >- * >- * @author Josh Micich >- */ >-public final class AllXSSFUsermodelTests { >- >- public static Test suite() { >- TestSuite result = new TestSuite(AllXSSFUsermodelTests.class.getName()); >- result.addTestSuite(TestFormulaEvaluatorOnXSSF.class); >- result.addTestSuite(TestSheetHiding.class); >- result.addTestSuite(TestXSSFBugs.class); >- result.addTestSuite(TestXSSFDataFormat.class); >- result.addTestSuite(TestXSSFCellStyle.class); >- result.addTestSuite(TestXSSFComment.class); >- result.addTestSuite(TestXSSFDialogSheet.class); >- result.addTestSuite(TestXSSFDrawing.class); >- result.addTestSuite(TestXSSFFont.class); >- result.addTestSuite(TestXSSFFormulaEvaluation.class); >- result.addTestSuite(TestXSSFHeaderFooter.class); >- result.addTestSuite(TestXSSFHyperlink.class); >- result.addTestSuite(TestXSSFName.class); >- result.addTestSuite(TestXSSFPicture.class); >- result.addTestSuite(TestXSSFPictureData.class); >- result.addTestSuite(TestXSSFPrintSetup.class); >- result.addTestSuite(TestXSSFRichTextString.class); >- result.addTestSuite(TestXSSFRow.class); >- result.addTestSuite(TestXSSFSheet.class); >- result.addTestSuite(TestXSSFWorkbook.class); >- >- result.addTestSuite(TestXSSFBorder.class); >- result.addTestSuite(TestXSSFCellFill.class); >- result.addTestSuite(TestXSSFSheetComments.class); >- >- result.addTestSuite(TestColumnHelper.class); >- result.addTestSuite(TestHeaderFooterHelper.class); >- >- return result; >- } >-} >+/* ==================================================================== >+ 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.xssf.usermodel; >+ >+import junit.framework.Test; >+import junit.framework.TestSuite; >+ >+import org.apache.poi.xssf.usermodel.extensions.TestXSSFBorder; >+import org.apache.poi.xssf.usermodel.extensions.TestXSSFCellFill; >+import org.apache.poi.xssf.usermodel.extensions.TestXSSFSheetComments; >+import org.apache.poi.xssf.usermodel.helpers.TestColumnHelper; >+import org.apache.poi.xssf.usermodel.helpers.TestHeaderFooterHelper; >+ >+/** >+ * Collects all tests for <tt>org.apache.poi.xssf.usermodel</tt> and sub-packages. >+ * >+ * @author Josh Micich >+ */ >+public final class AllXSSFUsermodelTests { >+ >+ public static Test suite() { >+ TestSuite result = new TestSuite(AllXSSFUsermodelTests.class.getName()); >+ result.addTestSuite(TestFormulaEvaluatorOnXSSF.class); >+ result.addTestSuite(TestSheetHiding.class); >+ result.addTestSuite(TestXSSFAddRemoveArrayFormula.class); >+ result.addTestSuite(TestXSSFArrayEvaluation.class); >+ result.addTestSuite(TestXSSFArrayFormulaEvaluation.class); >+ result.addTestSuite(TestXSSFArrayFormulaFunction.class); >+ result.addTestSuite(TestXSSFBugs.class); >+ result.addTestSuite(TestXSSFDataFormat.class); >+ result.addTestSuite(TestXSSFCellStyle.class); >+ result.addTestSuite(TestXSSFComment.class); >+ result.addTestSuite(TestXSSFDialogSheet.class); >+ result.addTestSuite(TestXSSFDrawing.class); >+ result.addTestSuite(TestXSSFFont.class); >+ result.addTestSuite(TestXSSFFormulaEvaluation.class); >+ result.addTestSuite(TestXSSFHeaderFooter.class); >+ result.addTestSuite(TestXSSFHyperlink.class); >+ result.addTestSuite(TestXSSFName.class); >+ result.addTestSuite(TestXSSFPicture.class); >+ result.addTestSuite(TestXSSFPictureData.class); >+ result.addTestSuite(TestXSSFPrintSetup.class); >+ result.addTestSuite(TestXSSFRichTextString.class); >+ result.addTestSuite(TestXSSFRow.class); >+ result.addTestSuite(TestXSSFSheet.class); >+ result.addTestSuite(TestXSSFWorkbook.class); >+ >+ result.addTestSuite(TestXSSFBorder.class); >+ result.addTestSuite(TestXSSFCellFill.class); >+ result.addTestSuite(TestXSSFSheetComments.class); >+ >+ result.addTestSuite(TestColumnHelper.class); >+ result.addTestSuite(TestHeaderFooterHelper.class); >+ >+ return result; >+ } >+} >Index: src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFAddRemoveArrayFormula.java >=================================================================== >--- src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFAddRemoveArrayFormula.java (revision 0) >+++ src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFAddRemoveArrayFormula.java (revision 0) >@@ -0,0 +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. >+==================================================================== */ >+ >+package org.apache.poi.xssf.usermodel; >+ >+import org.apache.poi.ss.usermodel.BaseTestAddRemoveArrayFormula; >+import org.apache.poi.xssf.XSSFITestDataProvider; >+ >+ >+public final class TestXSSFAddRemoveArrayFormula extends BaseTestAddRemoveArrayFormula { >+ >+ public TestXSSFAddRemoveArrayFormula() { >+ super(XSSFITestDataProvider.getInstance(), "ArrayFormula.xlsx"); >+ } >+} >Index: src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFArrayEvaluation.java >=================================================================== >--- src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFArrayEvaluation.java (revision 0) >+++ src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFArrayEvaluation.java (revision 0) >@@ -0,0 +1,33 @@ >+/* ==================================================================== >+ 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.xssf.usermodel; >+ >+import org.apache.poi.ss.formula.eval.BaseTestArrayEvaluation; >+import org.apache.poi.ss.usermodel.ArrayFormulaTestHelper; >+ >+public final class TestXSSFArrayEvaluation extends BaseTestArrayEvaluation { >+ >+ private static ArrayFormulaTestHelper th; >+ >+ protected ArrayFormulaTestHelper th() { >+ if (th == null) { >+ th = new ArrayFormulaTestHelper("ConstantArray.xlsx"); >+ } >+ return th; >+ } >+} >Index: src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFArrayFormulaEvaluation.java >=================================================================== >--- src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFArrayFormulaEvaluation.java (revision 0) >+++ src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFArrayFormulaEvaluation.java (revision 0) >@@ -0,0 +1,33 @@ >+/* ==================================================================== >+ 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.xssf.usermodel; >+ >+import org.apache.poi.ss.formula.eval.BaseTestArrayFormulaEvaluation; >+import org.apache.poi.ss.usermodel.ArrayFormulaTestHelper; >+ >+public final class TestXSSFArrayFormulaEvaluation extends BaseTestArrayFormulaEvaluation { >+ >+ private static ArrayFormulaTestHelper th; >+ >+ protected ArrayFormulaTestHelper th() { >+ if (th == null) { >+ th = new ArrayFormulaTestHelper("ArrayFormula.xlsx"); >+ } >+ return th; >+ } >+} >Index: src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFArrayFormulaFunction.java >=================================================================== >--- src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFArrayFormulaFunction.java (revision 0) >+++ src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFArrayFormulaFunction.java (revision 0) >@@ -0,0 +1,33 @@ >+/* ==================================================================== >+ 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.xssf.usermodel; >+ >+import org.apache.poi.ss.formula.eval.BaseTestArrayFormulaFunction; >+import org.apache.poi.ss.usermodel.ArrayFormulaTestHelper; >+ >+public final class TestXSSFArrayFormulaFunction extends BaseTestArrayFormulaFunction { >+ >+ private static ArrayFormulaTestHelper th; >+ >+ protected ArrayFormulaTestHelper th() { >+ if (th == null) { >+ th = new ArrayFormulaTestHelper("ArrayFormulaFunctions.xlsx"); >+ } >+ return th; >+ } >+} >Index: src/testcases/org/apache/poi/hssf/record/formula/eval/AllFormulaEvalTests.java >=================================================================== >--- src/testcases/org/apache/poi/hssf/record/formula/eval/AllFormulaEvalTests.java (revision 892169) >+++ src/testcases/org/apache/poi/hssf/record/formula/eval/AllFormulaEvalTests.java (working copy) >@@ -30,12 +30,16 @@ > public static Test suite() { > TestSuite result = new TestSuite(AllFormulaEvalTests.class.getName()); > result.addTestSuite(TestAreaEval.class); >+ result.addTestSuite(TestArrayEval.class); > result.addTestSuite(TestCircularReferences.class); > result.addTestSuite(TestDivideEval.class); > result.addTestSuite(TestEqualEval.class); > result.addTestSuite(TestExternalFunction.class); > result.addTestSuite(TestFormulaBugs.class); > result.addTestSuite(TestFormulasFromSpreadsheet.class); >+ result.addTestSuite(TestHSSFArrayEvaluation.class); >+ result.addTestSuite(TestHSSFArrayFormulaEvaluation.class); >+ result.addTestSuite(TestHSSFArrayFormulaFunction.class); > result.addTestSuite(TestMinusZeroResult.class); > result.addTestSuite(TestMissingArgEval.class); > result.addTestSuite(TestPercentEval.class); >Index: src/testcases/org/apache/poi/hssf/record/formula/eval/TestArrayEval.java >=================================================================== >--- src/testcases/org/apache/poi/hssf/record/formula/eval/TestArrayEval.java (revision 0) >+++ src/testcases/org/apache/poi/hssf/record/formula/eval/TestArrayEval.java (revision 0) >@@ -0,0 +1,47 @@ >+/* ==================================================================== >+ 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.eval; >+ >+import junit.framework.AssertionFailedError; >+import junit.framework.TestCase; >+ >+import org.apache.poi.ss.formula.ArrayEval; >+ >+/** >+ * Tests for {@link ArrayEval} >+ * >+ * @author Josh Micich >+ */ >+public final class TestArrayEval extends TestCase { >+ >+ public void testToString() { >+ >+ ValueEval x = NumberEval.ZERO; >+ >+ ValueEval[][] values = { >+ { x, x }, { x, x }, >+ }; >+ ArrayEval ae = new ArrayEval(values); >+ >+ try { >+ ae.toString(); >+ } catch (ArrayIndexOutOfBoundsException e) { >+ throw new AssertionFailedError("Identified bug in toString"); >+ } >+ } >+} >Index: src/testcases/org/apache/poi/hssf/record/formula/eval/TestArrayEvaluationExtra.java >=================================================================== >--- src/testcases/org/apache/poi/hssf/record/formula/eval/TestArrayEvaluationExtra.java (revision 0) >+++ src/testcases/org/apache/poi/hssf/record/formula/eval/TestArrayEvaluationExtra.java (revision 0) >@@ -0,0 +1,149 @@ >+/* ==================================================================== >+ 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.eval; >+ >+import junit.framework.TestCase; >+ >+import org.apache.poi.hssf.usermodel.HSSFWorkbook; >+import org.apache.poi.ss.usermodel.Cell; >+import org.apache.poi.ss.usermodel.CellValue; >+import org.apache.poi.ss.usermodel.ErrorConstants; >+import org.apache.poi.ss.usermodel.FormulaEvaluator; >+import org.apache.poi.ss.usermodel.Row; >+import org.apache.poi.ss.usermodel.Sheet; >+import org.apache.poi.ss.usermodel.Workbook; >+import org.apache.poi.ss.util.CellRangeAddress; >+ >+/** >+ * @author Josh Micich >+ */ >+public class TestArrayEvaluationExtra extends TestCase { >+ private static boolean FIXED = false; >+ >+ // simple case already works >+ public void testArraysInSimpleNonArrayFormula() { >+ Workbook wb = new HSSFWorkbook(); >+ Sheet sheet = wb.createSheet("Sheet1"); >+ >+ Row row1 = sheet.createRow(0); >+ Cell cellA1 = row1.createCell(0); >+ Cell cellB1 = row1.createCell(1); >+ Cell cellC1 = row1.createCell(2); >+ >+ FormulaEvaluator fe = wb.getCreationHelper().createFormulaEvaluator(); >+ >+ String formula = "{3,4}+{0.2,0.1,0.4}"; >+ >+ cellA1.setCellFormula(formula); >+ assertEquals(formula, cellA1.getCellFormula()); >+ confirmEvaluate(cellA1, fe, 3.2); >+ >+ sheet.setArrayFormula(formula, CellRangeAddress.valueOf("A1:C1")); >+ confirmEvaluate(cellA1, fe, 3.2); >+ confirmEvaluate(cellB1, fe, 4.1); >+ >+ CellValue cv; >+ fe.clearAllCachedResultValues(); >+ cv = fe.evaluate(cellC1); >+ assertEquals(Cell.CELL_TYPE_ERROR, cv.getCellType()); >+ assertEquals(cv.getErrorValue(), ErrorConstants.ERROR_NA); >+ } >+ >+ >+ public void testArraysInNonArrayFormula() { >+ Workbook wb = new HSSFWorkbook(); >+ Sheet sheet = wb.createSheet("Sheet1"); >+ Row row149 = sheet.createRow(148); >+ Row row150 = sheet.createRow(149); >+ row149.createCell(0).setCellValue(1.0); >+ row149.createCell(1).setCellValue(2.0); >+ row150.createCell(0).setCellValue(3.0); >+ row150.createCell(1).setCellValue(4.0); >+ >+ Row row1 = sheet.createRow(0); >+ Cell cellD1 = row1.createCell(3); >+ Cell cellE1 = row1.createCell(4); >+ Cell cellD2 = sheet.createRow(1).createCell(3); >+ CellRangeAddress colRange = CellRangeAddress.valueOf("D1:D2"); >+ CellRangeAddress rowRange = CellRangeAddress.valueOf("D1:E2"); >+ >+ FormulaEvaluator fe = wb.getCreationHelper().createFormulaEvaluator(); >+ >+ // Note: literal arrays are firstly row arrays, then column arrays >+ String rowFormula = "SUM(OFFSET(A149:B150,{1,0},{1,0},{1,1},{1,1}))"; >+ String colFormula = "SUM(OFFSET(A149:B150,{1;0},{1;0},{1;1},{1;1}))"; >+ >+ cellD1.setCellFormula(rowFormula); >+ assertEquals(rowFormula, cellD1.getCellFormula()); >+ if (FIXED) { >+ confirmEvaluate(cellD1, fe, 4.0); >+ } >+ >+ sheet.setArrayFormula(rowFormula, colRange); >+ if (FIXED) { >+ confirmEvaluate(cellD1, fe, 4.0); >+ } >+ if (FIXED) { >+ confirmEvaluate(cellD2, fe, 4.0); >+ } >+ sheet.removeArrayFormula(cellD1); >+ >+ sheet.setArrayFormula(rowFormula, rowRange); >+ if (FIXED) { >+ confirmEvaluate(cellD1, fe, 4.0); >+ } >+ if (FIXED) { >+ confirmEvaluate(cellE1, fe, 1.0); >+ } >+ sheet.removeArrayFormula(cellD1); >+ >+ >+ sheet.setArrayFormula(colFormula, colRange); >+ if (FIXED) { >+ confirmEvaluate(cellD1, fe, 4.0); >+ } >+ if (FIXED) { >+ confirmEvaluate(cellD2, fe, 1.0); >+ } >+ sheet.removeArrayFormula(cellD1); >+ } >+ >+ public void testSumStringElement() { >+ Workbook wb = new HSSFWorkbook(); >+ Sheet sheet = wb.createSheet("Sheet1"); >+ >+ Row row1 = sheet.createRow(0); >+ Cell cellA1 = row1.createCell(0); >+ >+ FormulaEvaluator fe = wb.getCreationHelper().createFormulaEvaluator(); >+ >+ String formula = "SUM({3,\"4\"})"; >+ >+ cellA1.setCellFormula(formula); >+ assertEquals(formula, cellA1.getCellFormula()); >+ confirmEvaluate(cellA1, fe, 3.0); >+ } >+ >+ private void confirmEvaluate(Cell cell, FormulaEvaluator fe, double expectedResult) { >+ CellValue cv; >+ fe.clearAllCachedResultValues(); >+ cv = fe.evaluate(cell); >+ assertEquals(Cell.CELL_TYPE_NUMERIC, cv.getCellType()); >+ assertEquals(expectedResult, cv.getNumberValue(), 0.0); >+ } >+} >Index: src/testcases/org/apache/poi/hssf/record/formula/eval/TestHSSFArrayEvaluation.java >=================================================================== >--- src/testcases/org/apache/poi/hssf/record/formula/eval/TestHSSFArrayEvaluation.java (revision 0) >+++ src/testcases/org/apache/poi/hssf/record/formula/eval/TestHSSFArrayEvaluation.java (revision 0) >@@ -0,0 +1,33 @@ >+/* ==================================================================== >+ 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.eval; >+ >+import org.apache.poi.ss.formula.eval.BaseTestArrayEvaluation; >+import org.apache.poi.ss.usermodel.ArrayFormulaTestHelper; >+ >+public final class TestHSSFArrayEvaluation extends BaseTestArrayEvaluation{ >+ >+ private static ArrayFormulaTestHelper th; >+ >+ protected ArrayFormulaTestHelper th() { >+ if (th == null) { >+ th = new ArrayFormulaTestHelper("ConstantArray.xls"); >+ } >+ return th; >+ } >+} >Index: src/testcases/org/apache/poi/hssf/record/formula/eval/TestHSSFArrayFormulaEvaluation.java >=================================================================== >--- src/testcases/org/apache/poi/hssf/record/formula/eval/TestHSSFArrayFormulaEvaluation.java (revision 0) >+++ src/testcases/org/apache/poi/hssf/record/formula/eval/TestHSSFArrayFormulaEvaluation.java (revision 0) >@@ -0,0 +1,33 @@ >+/* ==================================================================== >+ 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.eval; >+ >+import org.apache.poi.ss.formula.eval.BaseTestArrayFormulaEvaluation; >+import org.apache.poi.ss.usermodel.ArrayFormulaTestHelper; >+ >+public final class TestHSSFArrayFormulaEvaluation extends BaseTestArrayFormulaEvaluation{ >+ >+ private static ArrayFormulaTestHelper th; >+ >+ protected ArrayFormulaTestHelper th() { >+ if (th == null) { >+ th = new ArrayFormulaTestHelper("ArrayFormula.xls"); >+ } >+ return th; >+ } >+} >Index: src/testcases/org/apache/poi/hssf/record/formula/eval/TestHSSFArrayFormulaFunction.java >=================================================================== >--- src/testcases/org/apache/poi/hssf/record/formula/eval/TestHSSFArrayFormulaFunction.java (revision 0) >+++ src/testcases/org/apache/poi/hssf/record/formula/eval/TestHSSFArrayFormulaFunction.java (revision 0) >@@ -0,0 +1,33 @@ >+/* ==================================================================== >+ 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.eval; >+ >+import org.apache.poi.ss.formula.eval.BaseTestArrayFormulaFunction; >+import org.apache.poi.ss.usermodel.ArrayFormulaTestHelper; >+ >+public final class TestHSSFArrayFormulaFunction extends BaseTestArrayFormulaFunction { >+ >+ private static ArrayFormulaTestHelper th; >+ >+ public ArrayFormulaTestHelper th() { >+ if (th == null) { >+ th = new ArrayFormulaTestHelper("ArrayFormulaFunctions.xls"); >+ } >+ return th; >+ } >+} >Index: src/testcases/org/apache/poi/hssf/usermodel/AllUserModelTests.java >=================================================================== >--- src/testcases/org/apache/poi/hssf/usermodel/AllUserModelTests.java (revision 892169) >+++ src/testcases/org/apache/poi/hssf/usermodel/AllUserModelTests.java (working copy) >@@ -22,14 +22,14 @@ > > /** > * Collects all tests for the <tt>org.apache.poi.hssf.usermodel</tt> package. >- * >+ * > * @author Josh Micich > */ > public class AllUserModelTests { >- >+ > public static Test suite() { > TestSuite result = new TestSuite(AllUserModelTests.class.getName()); >- >+ > result.addTestSuite(TestBugs.class); > result.addTestSuite(TestCellStyle.class); > result.addTestSuite(TestCloneSheet.class); >@@ -40,6 +40,7 @@ > result.addTestSuite(TestFormulas.class); > result.addTestSuite(TestFormulaEvaluatorBugs.class); > result.addTestSuite(TestFormulaEvaluatorDocs.class); >+ result.addTestSuite(TestHSSFAddRemoveArrayFormula.class); > result.addTestSuite(TestHSSFCell.class); > result.addTestSuite(TestHSSFClientAnchor.class); > result.addTestSuite(TestHSSFComment.class); >@@ -71,8 +72,8 @@ > } > result.addTestSuite(TestUnicodeWorkbook.class); > result.addTestSuite(TestUppercaseWorkbook.class); >- result.addTestSuite(TestWorkbook.class); >- >+ result.addTestSuite(TestWorkbook.class); >+ > return result; > } > } >Index: src/testcases/org/apache/poi/hssf/usermodel/TestHSSFAddRemoveArrayFormula.java >=================================================================== >--- src/testcases/org/apache/poi/hssf/usermodel/TestHSSFAddRemoveArrayFormula.java (revision 0) >+++ src/testcases/org/apache/poi/hssf/usermodel/TestHSSFAddRemoveArrayFormula.java (revision 0) >@@ -0,0 +1,28 @@ >+/* ==================================================================== >+ 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.usermodel; >+ >+import org.apache.poi.hssf.HSSFITestDataProvider; >+import org.apache.poi.ss.usermodel.BaseTestAddRemoveArrayFormula; >+ >+public final class TestHSSFAddRemoveArrayFormula extends BaseTestAddRemoveArrayFormula { >+ >+ public TestHSSFAddRemoveArrayFormula() { >+ super(HSSFITestDataProvider.getInstance(), "ArrayFormula.xls"); >+ } >+} >Index: src/testcases/org/apache/poi/ss/formula/eval/BaseTestArrayEvaluation.java >=================================================================== >--- src/testcases/org/apache/poi/ss/formula/eval/BaseTestArrayEvaluation.java (revision 0) >+++ src/testcases/org/apache/poi/ss/formula/eval/BaseTestArrayEvaluation.java (revision 0) >@@ -0,0 +1,311 @@ >+/* ==================================================================== >+ 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.eval; >+ >+import junit.framework.TestCase; >+ >+import org.apache.poi.ss.usermodel.ArrayFormulaTestHelper; >+import org.apache.poi.ss.usermodel.FormulaError; >+ >+public abstract class BaseTestArrayEvaluation extends TestCase { >+ >+ protected abstract ArrayFormulaTestHelper th(); >+ >+ public final void testSingleArg() { >+ confirmNumeric(5); >+ } >+ >+ public final void testSingleArgOnArray() { >+ confirmNumeric(6); >+ } >+ >+ public final void testTwoArgFirstArray() { >+ confirmNumeric(9); >+ } >+ >+ public final void testTwoArgTwoArrays() { >+ confirmNumeric(10); >+ } >+ >+ public final void testTwoArgFirstArrayResult() { >+ confirmNumeric(11); >+ } >+ >+ public final void testAggregateOnArray() { >+ confirmNumeric(13); >+ } >+ >+ public final void testAggregateOnTwoArrays() { >+ confirmNumeric(14); >+ } >+ >+ public final void testAggregateWithSpecialLast() { >+ confirmNumeric(15); >+ } >+ >+ public final void testAggregateWithSpecialLastAsArray() { >+ confirmNumeric(16); >+ } >+ >+ public final void testAggregateOnArrayFromSingle() { >+ confirmNumeric(19); >+ } >+ >+ public final void testAggregateOnTwoDimArrayFromSingle() { >+ confirmNumeric(20); >+ } >+ >+ public final void testAggregateOnArrayFromTwoArg() { >+ confirmNumeric(21); >+ } >+ >+ public final void testAggregateOnNonProperArray() { >+ confirmError(22, FormulaError.NA); >+ } >+ >+ public final void testAggregateOnTwoArg() { >+ confirmNumeric(23); >+ } >+ public final void testAggregateOnTwoArgWithLastSpecial() { >+ confirmNumeric(24); >+ } >+ >+ public final void testAggregateOnNonProperArrayViaSingle() { >+ confirmError(25, FormulaError.NA); >+ } >+ >+ public final void testSimpleNumericOneArg() { >+ confirmNumeric(27); >+ } >+ >+ public final void testSimpleNumericTwoArg() { >+ confirmNumeric(28); >+ } >+ >+ public final void testSimpleAgregate() { >+ >+ confirmNumeric(29); >+ } >+ >+ public final void testSimpleAgregateWithLast() { >+ confirmNumeric(30); >+ } >+ >+ public final void testSimpleTextOnNumeric() { >+ confirmNumeric(43); >+ } >+ >+ public final void testSingleArgTextReturnNum() { >+ confirmNumeric(44); >+ } >+ >+ public final void testSingleArgOnArrayFromNum() { >+ confirmNumeric(45); >+ } >+ public void NumericAggregateOnArrayFromTextSingleArg() { >+ >+ confirmNumeric(46); >+ } >+ public final void testNumericAggregateSigleArgtTextSingleArgTextArray() { >+ >+ confirmNumeric(47); >+ } >+ >+ public final void testSimpleMultiArgText() { >+ confirmString(48); >+ } >+ >+ public final void testSimpleMultiArgTextOneArray() { >+ confirmString(49); >+ } >+ >+ public final void testSimpleMultiArgTextTwoArraysSizeMatch() { >+ confirmString(50); >+ } >+ >+ public final void testSimpleMultiArgTextTwoArraysSizeNotMatch() { >+ confirmString(51); >+ } >+ >+ public final void testNumericAggregateOnTextArraySizeMatch() { >+ confirmNumeric(52); >+ } >+ >+ public final void testAggregateOnTextArraySizenotMatch() { >+ confirmError(53, FormulaError.NA); >+ } >+ >+ public final void testSimpleXY() { >+ confirmNumeric(60); >+ } >+ >+ public final void testXYonArray() { >+ confirmNumeric(61); >+ } >+ >+ public final void testXYonArrayFromFunction() { >+ confirmNumeric(62); >+ } >+ >+ public final void testXYonWrongArray() { >+ confirmError(63, FormulaError.NA); >+ } >+ >+ public final void testAndTrue() { >+ confirmBoolean(73); >+ } >+ >+ public final void testAndFalse() { >+ confirmBoolean(74); >+ } >+ >+ public final void testOrTrue() { >+ confirmBoolean(75); >+ } >+ >+ public final void testOrFalse() { >+ confirmBoolean(76); >+ } >+ >+ public final void testChoose() { >+ confirmNumeric(82); >+ } >+ >+ public final void testChooseAggregate() { >+ confirmNumeric(83); >+ } >+ >+ public final void testVlookupArrayAsTable() { >+ confirmNumeric(88); >+ } >+ >+ public final void testVlookupAllArrays() { >+ confirmNumeric(89); >+ } >+ >+ public final void testHlookupArrayAsTable() { >+ confirmNumeric(95); >+ } >+ >+ public final void testHlookupAllArrays() { >+ confirmNumeric(96); >+ } >+ >+ public final void testLookupArray() { >+ confirmNumeric(101); >+ } >+ >+ public final void testLookupAllArrays() { >+ confirmNumeric(102); >+ } >+ >+ public final void testLookupAggregateArrays() { >+ confirmNumeric(103); >+ } >+ >+ public final void testCountArray() { >+ confirmNumeric(108); >+ } >+ >+ public final void testCountaArray() { >+ confirmNumeric(112); >+ } >+ >+ public final void testCountifArray() { >+ confirmNumeric(117); >+ } >+ >+ public final void testCountifArrayAggregate() { >+ confirmNumeric(118); >+ } >+ >+ public final void testSumifArray() { >+ confirmNumeric(124); >+ } >+ >+ public final void testSumifArrayAggregate() { >+ confirmNumeric(125); >+ } >+ >+ public final void testSumproductArray() { >+ confirmNumeric(130); >+ } >+ >+ public final void testColumnsArray() { >+ confirmNumeric(136); >+ } >+ >+ public final void testMatchArray() { >+ confirmNumeric(140); >+ } >+ >+ public final void testMatchArrayAggregate() { >+ confirmNumeric(141); >+ } >+ >+ public final void testModeArray() { >+ confirmNumeric(145); >+ } >+ >+ public final void testOffsetArray() { >+ confirmNumeric(149); >+ } >+ >+ public final void testRowsArray() { >+ confirmNumeric(154); >+ } >+ >+ public final void testIndexArray() { >+ confirmNumeric(160); >+ } >+ >+ public final void testIndexArrayEntireColumn() { >+ confirmNumeric(161); >+ } >+ >+ public final void testIndexArrayEntireColumnAggregate() { >+ confirmNumeric(162); >+ } >+ private void confirmError(int rowNum, FormulaError err) { >+ String formulaCell = "C" + rowNum; >+ ArrayFormulaTestHelper th = th(); >+ FormulaError fe = th.calculateNumericFormulaWithError(formulaCell); >+ assertEquals(formulaCell, fe.getCode(), err.getCode()); >+ } >+ private void confirmNumeric(int rowNum) { >+ String formulaCell = "C" + rowNum; >+ String expResCell = "D" + rowNum; >+ ArrayFormulaTestHelper th = th(); >+ double actResult = th.calculateNumericFormula(formulaCell); >+ assertEquals(expResCell + "-" + formulaCell, th.getNumericValue(expResCell), actResult, 0.0); >+ } >+ private void confirmString(int rowNum) { >+ String formulaCell = "C" + rowNum; >+ String expResCell = "D" + rowNum; >+ ArrayFormulaTestHelper th = th(); >+ String actResult = th.calculateStringFormula(formulaCell); >+ assertEquals(expResCell + "-" + formulaCell, th.getStringValue(expResCell), actResult); >+ } >+ private void confirmBoolean(int rowNum) { >+ String formulaCell = "C" + rowNum; >+ String expResCell = "D" + rowNum; >+ ArrayFormulaTestHelper th = th(); >+ boolean actResult = th.calculateBooleanFormula(formulaCell); >+ assertEquals(expResCell + "-" + formulaCell, th.getBooleanValue(expResCell), actResult); >+ } >+} >Index: src/testcases/org/apache/poi/ss/formula/eval/BaseTestArrayFormulaEvaluation.java >=================================================================== >--- src/testcases/org/apache/poi/ss/formula/eval/BaseTestArrayFormulaEvaluation.java (revision 0) >+++ src/testcases/org/apache/poi/ss/formula/eval/BaseTestArrayFormulaEvaluation.java (revision 0) >@@ -0,0 +1,277 @@ >+/* ==================================================================== >+ 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.eval; >+ >+import org.apache.poi.ss.usermodel.ArrayFormulaTestHelper; >+ >+import junit.framework.TestCase; >+ >+public abstract class BaseTestArrayFormulaEvaluation extends TestCase { >+ >+ protected abstract ArrayFormulaTestHelper th(); >+ private ArrayFormulaTestHelper _th; >+ >+ protected void setUp() { >+ _th = th(); >+ } >+ >+ public final void testNumericSquareArrayFormula() { >+ // Clean cell's values before calculation >+ _th.setNumericValue("C4", 0); >+ _th.setNumericValue("D4", 0); >+ _th.setNumericValue("C5", 0); >+ _th.setNumericValue("D5", 0); >+ >+ assertEquals("C4-F4",_th.getNumericValue("F4"), _th.calculateNumericFormula("C4"), 0); >+ assertEquals("D4-G4",_th.getNumericValue("G4"), _th.calculateNumericFormula("D4"), 0); >+ assertEquals("C5-F5",_th.getNumericValue("F5"), _th.calculateNumericFormula("C5"), 0); >+ assertEquals("D5-G5",_th.getNumericValue("G5"), _th.calculateNumericFormula("D5"), 0); >+ } >+ >+ public final void testNumericArrayFormulaWORange() { >+ >+ // Clean cell's values before calculation >+ _th.setNumericValue("C7", 0); >+ >+ assertEquals("C7-F7",_th.getNumericValue("F7"), _th.calculateNumericFormula("C7"), 0); >+ } >+ >+ public final void testNumericArrayFormulaFullRow() { >+ >+ // Clean cell's values before calculation >+ _th.setNumericValue("A9", 0); >+ _th.setNumericValue("B9", 0); >+ _th.setNumericValue("C9", 0); >+ assertEquals("A9-F9",_th.getNumericValue("F9"), _th.calculateNumericFormula("A9"), 0); >+ assertEquals("B9-G9",_th.getNumericValue("G9"), _th.calculateNumericFormula("B9"), 0); >+ assertEquals("C9-H9",_th.getNumericValue("H9"), _th.calculateNumericFormula("C9"), 0); >+ } >+ >+ public final void testNumericArrayFormulaSmallRow() { >+ >+ // Clean cell's values before calculation >+ _th.setNumericValue("A11", 0); >+ _th.setNumericValue("B11", 0); >+ assertEquals("A11-F11",_th.getNumericValue("F11"), _th.calculateNumericFormula("A11"), 0); >+ assertEquals("B11-G11",_th.getNumericValue("G11"), _th.calculateNumericFormula("B11"), 0); >+ } >+ >+ public final void testNumericArrayFormulaBigRow() { >+ >+ // Clean cell's values before calculation >+ _th.setNumericValue("A13", 0); >+ _th.setNumericValue("B13", 0); >+ _th.setNumericValue("C13", 0); >+ >+ assertEquals("A13-F13",_th.getNumericValue("F13"), _th.calculateNumericFormula("A13"), 0); >+ assertEquals("B13-G13",_th.getNumericValue("G13"), _th.calculateNumericFormula("B13"), 0); >+ assertEquals("C13-H13",_th.getNumericValue("H13"), _th.calculateNumericFormula("C13"), 0); >+ assertEquals("D3-I13",_th.getErrorValue("I13"), _th.calculateNumericFormulaWithError("D13").getString()); >+ } >+ >+ public final void testNumericArrayFormulaFewerRows() { >+ >+ // Clean cell's values before calculation >+ _th.setNumericValue("A16", 0); >+ _th.setNumericValue("B16", 0); >+ _th.setNumericValue("A17", 0); >+ _th.setNumericValue("B17", 0); >+ >+ assertEquals("A16-F16",_th.getNumericValue("F16"), _th.calculateNumericFormula("A16"), 0); >+ assertEquals("B16-G16",_th.getNumericValue("G16"), _th.calculateNumericFormula("B16"), 0); >+ assertEquals("A17-F17",_th.getNumericValue("F17"), _th.calculateNumericFormula("A17"), 0); >+ assertEquals("B17-G17",_th.getNumericValue("G17"), _th.calculateNumericFormula("B17"), 0); >+ } >+ >+ public final void testNumericArrayFormulaDataExceed() { >+ // Clean cell's values before calculation >+ _th.setNumericValue("A19", 0); >+ _th.setNumericValue("B19", 0); >+ _th.setNumericValue("A20", 0); >+ _th.setNumericValue("B20", 0); >+ >+ assertEquals("A19-F19",_th.getNumericValue("F19"), _th.calculateNumericFormula("A19"), 0); >+ assertEquals("B19-G19",_th.getNumericValue("G19"), _th.calculateNumericFormula("B19"), 0); >+ assertEquals("A20-F20",_th.getNumericValue("F20"), _th.calculateNumericFormula("A20"), 0); >+ assertEquals("B20-G20",_th.getNumericValue("G20"), _th.calculateNumericFormula("B20"), 0); >+ } >+ >+ public final void testNumericArrayFormulaCol4Row() { >+ >+ // Clean cell's values before calculation >+ _th.setNumericValue("A22", 0); >+ _th.setNumericValue("A23", 0); >+ _th.setNumericValue("A24", 0); >+ _th.setNumericValue("A25", 0); >+ >+ assertEquals("A22-F22",_th.getNumericValue("F22"), _th.calculateNumericFormula("A22"), 0); >+ assertEquals("A23-F23",_th.getNumericValue("F23"), _th.calculateNumericFormula("A23"), 0); >+ assertEquals("A24-F24",_th.getNumericValue("F24"), _th.calculateNumericFormula("A24"), 0); >+ assertEquals("A25-F25",_th.getNumericValue("F25"), _th.calculateNumericFormula("A25"), 0); >+ } >+ >+ public final void testNumericArrayFormulaRow4Col() { >+ >+ // Clean cell's values before calculation >+ _th.setNumericValue("A27", 0); >+ _th.setNumericValue("B27", 0); >+ _th.setNumericValue("C27", 0); >+ _th.setNumericValue("D27", 0); >+ >+ assertEquals("A27-F27",_th.getNumericValue("F27"), _th.calculateNumericFormula("A27"), 0); >+ assertEquals("B27-G27",_th.getNumericValue("G27"), _th.calculateNumericFormula("B27"), 0); >+ assertEquals("C27-H27",_th.getNumericValue("H27"), _th.calculateNumericFormula("C27"), 0); >+ assertEquals("D27-I27",_th.getNumericValue("I27"), _th.calculateNumericFormula("D27"), 0); >+ } >+ >+ public final void testNumericArrayFormulaDataRow4Col() { >+ >+ // Clean cell's values before calculation >+ _th.setNumericValue("A27", 0); >+ _th.setNumericValue("B27", 0); >+ _th.setNumericValue("C27", 0); >+ _th.setNumericValue("D27", 0); >+ >+ assertEquals("A27-F27",_th.getNumericValue("F27"), _th.calculateNumericFormula("A27"), 0); >+ assertEquals("B27-G27",_th.getNumericValue("G27"), _th.calculateNumericFormula("B27"), 0); >+ assertEquals("C27-H27",_th.getNumericValue("H27"), _th.calculateNumericFormula("C27"), 0); >+ assertEquals("D27-I27",_th.getNumericValue("I27"), _th.calculateNumericFormula("D27"), 0); >+ } >+ >+ public final void testNumericArrayFormulaDataShortage() { >+ >+ // Clean cell's values before calculation >+ _th.setNumericValue("B30", 0); >+ _th.setNumericValue("C30", 0); >+ _th.setNumericValue("D30", 0); >+ _th.setNumericValue("B31", 0); >+ _th.setNumericValue("C31", 0); >+ _th.setNumericValue("D31", 0); >+ _th.setNumericValue("B32", 0); >+ _th.setNumericValue("C32", 0); >+ _th.setNumericValue("D32", 0); >+ >+ assertEquals("C30-G30",_th.getNumericValue("G30"), _th.calculateNumericFormula("C30"), 0); >+ assertEquals("B30-F30",_th.getNumericValue("F30"), _th.calculateNumericFormula("B30"), 0); >+ if (false) { // TODO - fix this >+ assertEquals("C30-G30", _th.getNumericValue("G30"), _th.getNumericValue("C30"), 0); >+ } >+ assertEquals("B31-G30",_th.getNumericValue("F31"), _th.calculateNumericFormula("B31"), 0); >+ assertEquals("C31-G31",_th.getNumericValue("G31"), _th.calculateNumericFormula("C31"), 0); >+ assertEquals("D30-H30",_th.getErrorValue("H30"), _th.calculateNumericFormulaWithError("D30").getString()); >+ assertEquals("D31-H31",_th.getErrorValue("H31"), _th.calculateNumericFormulaWithError("D31").getString()); >+ >+ assertEquals("B32-F32",_th.getErrorValue("F32"), _th.calculateNumericFormulaWithError("B32").getString()); >+ assertEquals("C32-G32",_th.getErrorValue("G32"), _th.calculateNumericFormulaWithError("C32").getString()); >+ assertEquals("D32-H32",_th.getErrorValue("H32"), _th.calculateNumericFormulaWithError("D32").getString()); >+ } >+ >+ public final void testNumericArrayFormulaRefArguments() { >+ >+ // Clean cell's values before calculation >+ _th.setNumericValue("A37", 0); >+ _th.setNumericValue("B37", 0); >+ _th.setNumericValue("C37", 0); >+ >+ assertEquals("A37-F37",_th.getNumericValue("F37"), _th.calculateNumericFormula("A37"), 0); >+ assertEquals("B37-G37",_th.getNumericValue("G37"), _th.calculateNumericFormula("B37"), 0); >+ assertEquals("C37-H37",_th.getNumericValue("H37"), _th.calculateNumericFormula("C37"), 0); >+ } >+ >+ public final void testNumericArrayFormulasRefArguments() { >+ >+ // Clean cell's values before calculation >+ _th.setNumericValue("A40", 0); >+ _th.setNumericValue("B40", 0); >+ _th.setNumericValue("C40", 0); >+ >+ assertEquals("A40-F40",_th.getNumericValue("F40"), _th.calculateNumericFormula("A40"), 0); >+ assertEquals("B40-G40",_th.getNumericValue("G40"), _th.calculateNumericFormula("B40"), 0); >+ assertEquals("C40-H40",_th.getNumericValue("H40"), _th.calculateNumericFormula("C40"), 0); >+ } >+ >+ public final void testNumericOperation4Range() { >+ >+ // Clean cell's values before calculation >+ _th.setNumericValue("C43", 0); >+ _th.setNumericValue("C44", 0); >+ _th.setNumericValue("C45", 0); >+ _th.setNumericValue("C46", 0); >+ >+ assertEquals("C43-F43",_th.getNumericValue("F43"), _th.calculateNumericFormula("C43"), 0); >+ assertEquals("C44-F44",_th.getNumericValue("F44"), _th.calculateNumericFormula("C44"), 0); >+ assertEquals("C45-F45",_th.getNumericValue("F45"), _th.calculateNumericFormula("C45"), 0); >+ assertEquals("C46-F46",_th.getNumericValue("F46"), _th.calculateNumericFormula("C46"), 0); >+ } >+ >+ public final void testNumericOperation4DiffRanges() { >+ >+ // Clean cell's values before calculation >+ _th.setNumericValue("C48", 0); >+ _th.setNumericValue("C49", 0); >+ _th.setNumericValue("C50", 0); >+ _th.setNumericValue("C51", 0); >+ >+ assertEquals("C48-F48",_th.getNumericValue("F48"), _th.calculateNumericFormula("C48"), 0); >+ assertEquals("C49-F49",_th.getNumericValue("F49"), _th.calculateNumericFormula("C49"), 0); >+ assertEquals("C50-F50",_th.getNumericValue("F50"), _th.calculateNumericFormula("C50"), 0); >+ assertEquals("C51-F51",_th.getNumericValue("F51"), _th.calculateNumericFormula("C51"), 0); >+ if (false) { // TODO - fix these >+ assertEquals("C50-F50", _th.getErrorValue("F50"), _th.getErrorValue("C50")); >+ assertEquals("C51-F51",_th.getErrorValue("F51"), _th.getErrorValue("C51")); >+ } >+ } >+ >+ public final void testNumericArrayChangeRefArguments() { >+ >+ // Clean cell's values before calculation >+ _th.setNumericValue("A40", 0); >+ _th.setNumericValue("B40", 0); >+ _th.setNumericValue("C40", 0); >+ >+ assertEquals("A40-F40", _th.getNumericValue("F40"), _th.calculateNumericFormula("A40"), 0); >+ assertEquals("B40-G40", _th.getNumericValue("G40"), _th.calculateNumericFormula("B40"), 0); >+ assertEquals("C40-H40", _th.getNumericValue("H40"), _th.calculateNumericFormula("C40"), 0); >+ >+ _th.setNumericValue("A41", 0.4); >+ _th.setNumericValue("B41", 0.5); >+ _th.setNumericValue("C41", 0.6); >+ >+ assertEquals("B40-G40", Math.cos(Math.sin(0.5)), _th.calculateNumericFormula("B40"), 0); >+ assertEquals("A40-F40", Math.cos(Math.sin(0.4)), _th.calculateNumericFormula("A40"), 0); >+ assertEquals("C40-H40", Math.cos(Math.sin(0.6)), _th.calculateNumericFormula("C40"), 0); >+ } >+ >+ public final void testNumericArrayDifTypeArguments() { >+ >+ // Clean cell's values before calculation >+ _th.setNumericValue("A54", 0); >+ _th.setNumericValue("B54", 0); >+ _th.setNumericValue("C54", 0); >+ _th.setNumericValue("A55", 0); >+ _th.setNumericValue("B55", 0); >+ _th.setNumericValue("C55", 0); >+ >+ assertEquals("A54-F54",_th.getNumericValue("F54"), _th.calculateNumericFormula("A54"), 0); >+ assertEquals("B54-G54",_th.getNumericValue("G54"), _th.calculateNumericFormula("B54"), 0); >+ assertEquals("C54-H54",_th.getNumericValue("H54"), _th.calculateNumericFormula("C54"), 0); >+ assertEquals("A55-F55",_th.getNumericValue("F55"), _th.calculateNumericFormula("A55"), 0); >+ assertEquals("B55-G55",_th.getNumericValue("G55"), _th.calculateNumericFormula("B55"), 0); >+ assertEquals("C55-H55",_th.getNumericValue("H55"), _th.calculateNumericFormula("C55"), 0); >+ } >+} >Index: src/testcases/org/apache/poi/ss/formula/eval/BaseTestArrayFormulaFunction.java >=================================================================== >--- src/testcases/org/apache/poi/ss/formula/eval/BaseTestArrayFormulaFunction.java (revision 0) >+++ src/testcases/org/apache/poi/ss/formula/eval/BaseTestArrayFormulaFunction.java (revision 0) >@@ -0,0 +1,149 @@ >+/* ==================================================================== >+ 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.eval; >+ >+import org.apache.poi.ss.usermodel.ArrayFormulaTestHelper; >+ >+import junit.framework.TestCase; >+ >+public abstract class BaseTestArrayFormulaFunction extends TestCase { >+ >+ private ArrayFormulaTestHelper _th; >+ protected abstract ArrayFormulaTestHelper th(); >+ >+ protected void setUp() { >+ _th = th(); >+ } >+ >+ /** >+ * check if non-calculated array cells does not contain value >+ */ >+ public final void testNonCalculated1() { >+ _th.confirmNotNumeric("C4"); >+ } >+ >+ public final void testHorizontalArray1() { >+ assertEquals("C4-I4",_th.getNumericValue("I4"), _th.calculateNumericFormula("C4"), 0); >+ } >+ >+ public final void testNonCalculated2() { >+ _th.confirmNotNumeric("D4"); >+ } >+ >+ public final void testHorizontalArray2() { >+ assertEquals("D4-J4",_th.getNumericValue("J4"), _th.calculateNumericFormula("D4"), 0); >+ assertEquals("E4-K4",_th.getNumericValue("K4"), _th.calculateNumericFormula("E4"), 0); >+ } >+ >+ public final void testHorizontalArray3() { >+ assertTrue("C4:E4 - I4:K4",_th.calculateAndCompareNumericArray('C', 4, 'E', 4, 'I', 4, 0)); >+ } >+ >+ public final void testTwoDimArray() { >+ assertTrue("C6:E7 - I6:K7",_th.calculateAndCompareNumericArray('C', 6, 'E', 7, 'I', 6, 0)); >+ } >+ >+ public final void testVertArray() { >+ assertTrue("C9:C11 - I9:I11",_th.calculateAndCompareNumericArray('C', 9, 'C', 11, 'I', 9, 0)); >+ } >+ >+ public final void testPowerArray() { >+ assertTrue("C16:D16 - I16:K16",_th.calculateAndCompareNumericArray('C', 16, 'D', 16, 'I', 16, 0)); >+ } >+ >+ public final void testPowerArrayChanged() { >+ _th.setNumericValue("A15", 2); >+ assertEquals("changed array", 4, _th.calculateNumericFormula("C16"),0); >+ } >+ >+ public final void testIndexArray() { >+ assertTrue("C23:C25 - I23:I25",_th.calculateAndCompareNumericArray('C', 23, 'C', 25, 'I', 23, 0)); >+ } >+ >+ public final void testMatrixMultiply() { >+ assertTrue("C33:D34 - I33:K34",_th.calculateAndCompareNumericArray('C', 33, 'D', 34, 'I', 33, 0)); >+ } >+ >+ public final void testSumOnMultiply() { >+ assertEquals("C41",_th.getNumericValue("I41"), _th.calculateNumericFormula("C41"), 0); >+ } >+ >+ public final void testSumOnRangeArray() { >+ assertEquals("C48",_th.getNumericValue("I48"), _th.calculateNumericFormula("C48"), 0); >+ } >+ >+ public final void testSumOnTwoArraya() { >+ assertEquals("C50",_th.getNumericValue("I50"), _th.calculateNumericFormula("C50"), 0); >+ } >+ >+ public final void testArrayMultiply() { >+ assertTrue("C55:F57 - I55:L57",_th.calculateAndCompareNumericArray('C', 55, 'F', 57, 'I', 55, 0)); >+ } >+ >+ >+ public final void testSumOnLen() { >+ assertEquals("C63",_th.getNumericValue("I63"), _th.calculateNumericFormula("C63"), 0); >+ } >+ >+ public final void testSmallRangeArray() { >+ assertTrue("C69:D69",_th.calculateAndCompareNumericArray('C', 69, 'D', 69, 'I', 69, 0)); >+ } >+ >+ public final void testAverageOnSmall() { >+ assertEquals("C72",_th.getNumericValue("I72"), _th.calculateNumericFormula("C72"), 0); >+ } >+ >+ public final void testSumOnIf() { >+ assertEquals("C75",_th.getNumericValue("I75"), _th.calculateNumericFormula("C75"), 0); >+ } >+ >+ public final void testSumOnComplexIf() { >+ assertEquals("C78",_th.getNumericValue("I78"), _th.calculateNumericFormula("C78"), 0); >+ } >+ >+ public final void testSumOnComplexIf2() { >+ assertEquals("C84",_th.getNumericValue("I84"), _th.calculateNumericFormula("C84"), 0); >+ } >+ >+ public final void testColumn() { >+ assertTrue("C87:F87",_th.calculateAndCompareNumericArray('C', 87, 'F', 87, 'I', 87, 0)); >+ } >+ >+ public final void testRow() { >+ assertTrue("C90:C94",_th.calculateAndCompareNumericArray('C', 90, 'C', 94, 'I', 90, 0)); >+ } >+ >+ public final void testColumnNonArray() { >+ assertEquals("C88",_th.getNumericValue("I88"), _th.calculateNumericFormula("C88"), 0); >+ } >+ >+ public final void testColumnArray() { >+ assertEquals("C89",_th.getNumericValue("I89"), _th.calculateNumericFormula("C89"), 0); >+ } >+ >+ public final void testReferenceToArray() { >+ assertEquals("C99", _th.getNumericValue("i99"), _th.calculateNumericFormula("C99"),0); >+ } >+ >+ public final void testReferenceArrayToArray() { >+ assertEquals("B102", _th.getNumericValue("i102"), _th.calculateNumericFormula("b102"),0.001); >+ assertEquals("B103", _th.getNumericValue("i103"), _th.calculateNumericFormula("b103"),0.001); >+ assertEquals("C102", _th.getNumericValue("j102"), _th.calculateNumericFormula("c102"),0.001); >+ assertEquals("C103", _th.getNumericValue("j103"), _th.calculateNumericFormula("C103"),0.001); >+ } >+} >Index: src/testcases/org/apache/poi/ss/usermodel/ArrayFormulaTestHelper.java >=================================================================== >--- src/testcases/org/apache/poi/ss/usermodel/ArrayFormulaTestHelper.java (revision 0) >+++ src/testcases/org/apache/poi/ss/usermodel/ArrayFormulaTestHelper.java (revision 0) >@@ -0,0 +1,258 @@ >+/* ==================================================================== >+ 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.usermodel; >+ >+import junit.framework.AssertionFailedError; >+ >+import org.apache.commons.logging.Log; >+import org.apache.commons.logging.LogFactory; >+import org.apache.poi.hssf.HSSFTestDataSamples; >+import org.apache.poi.ss.usermodel.Cell; >+import org.apache.poi.ss.usermodel.ErrorConstants; >+import org.apache.poi.ss.usermodel.FormulaError; >+import org.apache.poi.ss.usermodel.FormulaEvaluator; >+import org.apache.poi.ss.usermodel.Row; >+import org.apache.poi.ss.usermodel.Sheet; >+import org.apache.poi.ss.usermodel.Workbook; >+import org.apache.poi.ss.util.CellRangeAddress; >+import org.apache.poi.ss.util.CellReference; >+import org.apache.poi.xssf.XSSFTestDataSamples; >+ >+public final class ArrayFormulaTestHelper { >+ private static final Log log = LogFactory.getLog(ArrayFormulaTestHelper.class); >+ >+ private final Workbook _wb; >+ >+ public ArrayFormulaTestHelper(String sampleFileName) { >+ >+ boolean isXssf = sampleFileName.endsWith(".xlsx"); >+ if (isXssf) { >+ _wb = XSSFTestDataSamples.openSampleWorkbook(sampleFileName); >+ } else { >+ _wb = HSSFTestDataSamples.openSampleWorkbook(sampleFileName); >+ } >+ } >+ >+ public Cell getCell(String cellRef) { >+ >+ log.debug("Access to Cell:" + cellRef); >+ Sheet sheet = _wb.getSheetAt(0); >+ CellReference cellReference = new CellReference(cellRef); >+ Row row = sheet.getRow(cellReference.getRow()); >+ if (row==null) { >+ throw new IllegalArgumentException("Illegal access to cell:" + cellRef); >+ } >+ Cell cell = row.getCell(cellReference.getCol()); >+ if (cell==null) { >+ throw new IllegalArgumentException("Illegal access to cell:" + cellRef); >+ } >+ return cell; >+ } >+ >+ >+ public FormulaError calculateNumericFormulaWithError(String cellRef) { >+ Cell cell = getCell(cellRef); >+ if (cell.getCellType() != Cell.CELL_TYPE_FORMULA) { >+ log.error("Not formula in cell: " + cell.toString()); >+ throw new IllegalArgumentException("Not formula" + cell.toString()); >+ } >+ >+ log.debug("Formula: " + cell.getCellFormula()); >+ FormulaEvaluator evaluator = _wb.getCreationHelper().createFormulaEvaluator(); >+ int type = evaluator.evaluateFormulaCell(cell); >+ if (type != Cell.CELL_TYPE_ERROR) { >+ log.error("not error result"); >+ throw new IllegalArgumentException("Not error type: " + type); >+ } >+ >+ byte result = cell.getErrorCellValue(); >+ log.debug("Error value:" + result); >+ return FormulaError.forInt(result); >+ } >+ >+ public String calculateStringFormula(String cellRef) { >+ >+ Cell cell = getCell(cellRef); >+ if (cell.getCellType() != Cell.CELL_TYPE_FORMULA) { >+ log.error("Not formula in cell: " + cell.toString()); >+ throw new IllegalArgumentException("Not formula" + cell.toString()); >+ } >+ >+ log.debug("Formula: " + cell.getCellFormula()); >+ FormulaEvaluator evaluator = _wb.getCreationHelper().createFormulaEvaluator(); >+ int type = evaluator.evaluateFormulaCell(cell); >+ if (type != Cell.CELL_TYPE_STRING) { >+ log.error("not string result: " + type); >+ throw new IllegalArgumentException("Not string type" + type); >+ } >+ >+ String result = cell.getStringCellValue(); >+ log.debug("Calculated:" + result); >+ return result; >+ } >+ >+ public double calculateNumericFormula(String cellRef) { >+ >+ Cell cell = getCell(cellRef); >+ if (cell.getCellType() != Cell.CELL_TYPE_FORMULA) { >+ log.error("Not formula in cell: " + cell.toString()); >+ throw new IllegalArgumentException("Not formula" + cell.toString()); >+ } >+ >+ log.debug("Formula: " + cell.getCellFormula()); >+ FormulaEvaluator evaluator = _wb.getCreationHelper().createFormulaEvaluator(); >+ int type = evaluator.evaluateFormulaCell(cell); >+ if (type != Cell.CELL_TYPE_NUMERIC) { >+ log.error("not numeric result: " + type); >+ throw new IllegalArgumentException("Not numeric type" + type); >+ } >+ >+ double result = cell.getNumericCellValue(); >+ log.debug("Calculated:" + result); >+ return result; >+ } >+ >+ public boolean calculateBooleanFormula(String cellRef) { >+ >+ Cell cell = getCell(cellRef); >+ if (cell.getCellType() != Cell.CELL_TYPE_FORMULA) { >+ log.error("Not formula in cell: " + cell.toString()); >+ throw new IllegalArgumentException("Not formula" + cell.toString()); >+ } >+ >+ log.debug("Formula: " + cell.getCellFormula()); >+ FormulaEvaluator evaluator = _wb.getCreationHelper().createFormulaEvaluator(); >+ int type = evaluator.evaluateFormulaCell(cell); >+ if (type != Cell.CELL_TYPE_BOOLEAN) { >+ log.error("not boolean result: " + type); >+ throw new IllegalArgumentException("Not boolean type" + type); >+ } >+ >+ boolean result = cell.getBooleanCellValue(); >+ log.debug("Calculated:" + result); >+ return result; >+ } >+ >+ public void confirmNotNumeric(String cellRef) { >+ Cell cell = getCell(cellRef); >+ if (cell.getCellType() == Cell.CELL_TYPE_NUMERIC) { >+ throw new AssertionFailedError("Cell type was unexpectedly numeric:" + cell.toString()); >+ } >+ } >+ >+ public void setNumericValue(String cellRef, double value) { >+ getCell(cellRef).setCellValue(value); >+ } >+ >+ public double getNumericValue(String cellRef) { >+ >+ Cell cell = getCell(cellRef); >+ if (cell.getCellType() != Cell.CELL_TYPE_NUMERIC) { >+ log.error("Not numeric in cell:" + cell.toString()); >+ throw new IllegalArgumentException("Not numeric:" + cell.toString()); >+ } >+ double result = cell.getNumericCellValue(); >+ log.debug("value: " + result ); >+ return result; >+ } >+ >+ public String getStringValue(String cellRef) { >+ >+ Cell cell = getCell(cellRef); >+ if (cell.getCellType() != Cell.CELL_TYPE_STRING) { >+ log.error("Not string in cell:" + cell.toString()); >+ throw new IllegalArgumentException("Not string:" + cell.toString()); >+ } >+ String result = cell.getStringCellValue(); >+ log.debug("value: " + result ); >+ return result; >+ } >+ >+ public boolean getBooleanValue(String cellRef) { >+ >+ Cell cell = getCell(cellRef); >+ if (cell.getCellType() != Cell.CELL_TYPE_BOOLEAN) { >+ log.error("Not boolean in cell:" + cell.toString()); >+ throw new IllegalArgumentException("Not boolean:" + cell.toString()); >+ } >+ boolean result = cell.getBooleanCellValue(); >+ log.debug("value: " + result ); >+ return result; >+ } >+ >+ public boolean calculateAndCompareNumericArray(char calculateFromColumn, int calculateFromRow, char calculateToColumn, >+ int calculateToRow, char compareFromColumn, int compareFromRow, double precision) { >+ >+ if (calculateFromColumn> calculateToColumn) { >+ throw new IllegalArgumentException("columns: " + calculateFromColumn + " " + calculateToColumn); >+ } >+ >+ if (calculateFromRow > calculateToRow) { >+ throw new IllegalArgumentException("rows: " + calculateFromRow + " " + calculateToRow); >+ } >+ >+ for(int c=0; calculateFromColumn + c <= calculateToColumn; c++ ) { >+ for(int r=0; calculateFromRow +r <= calculateToRow; r++) { >+ char ch[] = new char[1]; >+ ch[0] = (char)(calculateFromColumn+c); >+ String calculateRef = (new String(ch)) + (calculateFromRow+r); >+ double calcResult = calculateNumericFormula(calculateRef); >+ >+ ch[0]=(char)(compareFromColumn+c); >+ String compareRef = (new String(ch)) + (compareFromRow + r); >+ double compareResult = getNumericValue(compareRef); >+ >+ if (Math.abs(compareResult - calcResult) > precision) { >+ // comparison failed >+ log.debug("Array comparison failed. " + "Calculated cell:" + calculateRef + " Compared cell: " + compareRef + >+ " calculated result: " + calcResult + " compared result: " + compareResult); >+ return false; >+ } >+ } >+ } >+ return true; >+ } >+ >+ public void setArrayFormula(String cellRef, String formula, String range) { >+ Sheet sheet = getCell(cellRef).getSheet(); >+ sheet.setArrayFormula(formula, CellRangeAddress.valueOf(range)); >+ } >+ >+ public void removeArrayFormula(String cellRef) { >+ Cell cell = getCell(cellRef); >+ Sheet sheet = cell.getSheet(); >+ sheet.removeArrayFormula(cell); >+ } >+ >+ public String getErrorValue(String cellRef) { >+ >+ Cell cell = getCell(cellRef); >+ if (cell.getCellType() != Cell.CELL_TYPE_ERROR) { >+ log.error("Not numeric in cell:" + cell.toString()); >+ throw new IllegalArgumentException("Not error:" + cell.toString()); >+ } >+ byte result = cell.getErrorCellValue(); >+ log.debug("value: " + result ); >+ return ErrorConstants.getText(result); >+ } >+ >+ public int getCellType(String cellRef) { >+ Cell cell = getCell(cellRef); >+ return cell.getCellType(); >+ } >+} >Index: src/testcases/org/apache/poi/ss/usermodel/BaseTestAddRemoveArrayFormula.java >=================================================================== >--- src/testcases/org/apache/poi/ss/usermodel/BaseTestAddRemoveArrayFormula.java (revision 0) >+++ src/testcases/org/apache/poi/ss/usermodel/BaseTestAddRemoveArrayFormula.java (revision 0) >@@ -0,0 +1,121 @@ >+/* ==================================================================== >+ 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.usermodel; >+ >+import junit.framework.TestCase; >+ >+import org.apache.poi.ss.ITestDataProvider; >+import org.apache.poi.ss.usermodel.Cell; >+import org.apache.poi.ss.usermodel.FormulaEvaluator; >+import org.apache.poi.ss.usermodel.Row; >+import org.apache.poi.ss.usermodel.Sheet; >+import org.apache.poi.ss.usermodel.Workbook; >+import org.apache.poi.ss.util.CellRangeAddress; >+ >+/** >+ * Array Formula Set/Remove test class >+ * >+ * Workbook contains formulas and tests >+ */ >+public abstract class BaseTestAddRemoveArrayFormula extends TestCase { >+ >+ protected final ITestDataProvider _testDataProvider; >+ private final String _sampleSpreadsheetName; >+ >+ private ArrayFormulaTestHelper _th; >+ >+ protected BaseTestAddRemoveArrayFormula(ITestDataProvider testDataProvider, String sampleSpreadsheetName) { >+ _testDataProvider = testDataProvider; >+ _sampleSpreadsheetName = sampleSpreadsheetName; >+ } >+ >+ protected void setUp() { >+ _th = new ArrayFormulaTestHelper(_sampleSpreadsheetName); >+ } >+ >+ >+ public final void testNewNumericArrayFormula() { >+ >+ _th.setArrayFormula("A35", "SIN({0.1,0.2,0.3})", "A35:B35"); >+ >+ assertEquals("A35-F35", _th.getNumericValue("F35"), _th.calculateNumericFormula("A35"), 0); >+ assertEquals("B35-G35", _th.getNumericValue("G35"), _th.calculateNumericFormula("B35"), 0); >+ } >+ >+ public final void testNewNumericArrayFormulaOut() { >+ >+ // create empty workbook >+ Workbook workbook = _testDataProvider.createWorkbook(); >+ Sheet sheet = workbook.createSheet("Sheet1"); >+ >+ Row rowd = sheet.createRow((short) (0)); >+ Cell cd = rowd.createCell((short) 0); >+ CellRangeAddress range = new CellRangeAddress(0, 1, 0, 1); >+ sheet.setArrayFormula("SQRT({1,4;9,16})", range); >+ >+ // Calculate formula >+ FormulaEvaluator eval = workbook.getCreationHelper().createFormulaEvaluator(); >+ eval.evaluateFormulaCell(cd); >+ >+ // Set tested values (copy from 5 rows above) >+ for (int rowIn = range.getFirstRow(); rowIn <= range.getLastRow(); rowIn++) { >+ for (int colIn = range.getFirstColumn(); colIn <= range.getLastColumn(); colIn++) { >+ Cell cell = sheet.getRow(rowIn).getCell(colIn); >+ int lowerRowIx = rowIn + 5; >+ double value = cell.getNumericCellValue(); >+ Row row = sheet.getRow(lowerRowIx); >+ if (row == null) { >+ row = sheet.createRow(lowerRowIx); >+ } >+ row.createCell(colIn).setCellValue(value); >+ } >+ } >+ >+ // serialize/deserialize file >+ workbook = _testDataProvider.writeOutAndReadBack(workbook); >+ sheet = workbook.getSheetAt(0); >+ // Set 0 values before calculation >+ for (int rowIn = range.getFirstRow(); rowIn <= range.getLastRow(); rowIn++) { >+ for (int colIn = range.getFirstColumn(); colIn <= range.getLastColumn(); colIn++) { >+ sheet.getRow(rowIn).getCell(colIn).setCellValue(0.0); >+ } >+ } >+ // Calculate formula (we use cell from firstRow and firstColumn) >+ eval = workbook.getCreationHelper().createFormulaEvaluator(); >+ eval.evaluateFormulaCell(sheet.getRow(range.getFirstRow()).getCell(range.getFirstColumn())); >+ // Check calculated values >+ for (int rowIn = range.getFirstRow(); rowIn <= range.getLastRow(); rowIn++) { >+ for (int colIn = range.getFirstColumn(); colIn <= range.getLastColumn(); colIn++) { >+ Cell cell = sheet.getRow(rowIn).getCell(colIn); >+ double value = cell.getNumericCellValue(); >+ cell = sheet.getRow(rowIn + 5).getCell(colIn); >+ assertEquals("ArrayFormula:" + rowIn + "," + colIn, cell.getNumericCellValue(), >+ value, 0); >+ } >+ } >+ } >+ >+ public final void testRemoveArrayFormula() { >+ >+ _th.removeArrayFormula("A40"); >+ >+ assertEquals("A40-F40", Cell.CELL_TYPE_BLANK, _th.getCellType("A40"), 0); >+ assertEquals("B40-G40", Cell.CELL_TYPE_BLANK, _th.getCellType("B40"), 0); >+ assertEquals("C40-H40", Cell.CELL_TYPE_BLANK, _th.getCellType("C40"), 0); >+ } >+}
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Raw
Actions:
View
Attachments on
bug 48292
:
24623
|
24645
|
24658
|
24659
|
24682
|
24683
|
24731
|
24732
|
24746
|
28053
|
28054