added HSSF usermodel tests for array formulas, added support for array formulas in ss interfaces

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@893870 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Yegor Kozlov 2009-12-25 15:15:55 +00:00
parent b3093fc671
commit aba7267376
18 changed files with 772 additions and 159 deletions

View File

@ -17,7 +17,10 @@
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;
@ -28,6 +31,7 @@ import org.apache.poi.util.LittleEndianOutput;
* 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 {
@ -35,7 +39,7 @@ public final class ArrayRecord extends SharedValueRecordBase {
private static final int OPT_ALWAYS_RECALCULATE = 0x0001;
private static final int OPT_CALCULATE_ON_OPEN = 0x0002;
private int _options;
private int _options;
private int _field3notUsed;
private Formula _formula;
@ -48,6 +52,13 @@ public final class ArrayRecord extends SharedValueRecordBase {
_formula = Formula.read(formulaTokenLen, in, totalFormulaLen);
}
public ArrayRecord(Formula formula, CellRangeAddress8Bit range ) {
super(range);
_options = 0; //YK: Excel 2007 leaves this field unset
_field3notUsed = 0;
_formula = formula;
}
public boolean isAlwaysRecalculate() {
return (_options & OPT_ALWAYS_RECALCULATE) != 0;
}
@ -55,7 +66,11 @@ public final class ArrayRecord extends SharedValueRecordBase {
return (_options & OPT_CALCULATE_ON_OPEN) != 0;
}
protected int getExtraDataSize() {
public void setOptions(int val){
_options = val;
}
protected int getExtraDataSize() {
return 2 + 4
+ _formula.getEncodedSize();
}
@ -84,4 +99,42 @@ public final class ArrayRecord extends SharedValueRecordBase {
sb.append("]");
return sb.toString();
}
/**
* @return the equivalent {@link Ptg} array that the formula would have,
* were it not shared.
*/
public Ptg[] getFormulaTokens() {
return _formula.getTokens();
/*
YK: I don't understand all t
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);
*/
}
}

View File

@ -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.SharedFormulaRecord;
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 final class FormulaRecordAggregate extends RecordAggregate implements Cel
}
public Ptg[] getFormulaTokens() {
if (_sharedFormulaRecord == null) {
return _formulaRecord.getParsedExpression();
if (_sharedFormulaRecord != null) {
return _sharedFormulaRecord.getFormulaTokens(_formulaRecord);
}
CellReference expRef = _formulaRecord.getFormula().getExpReference();
if (expRef != null) {
ArrayRecord arec = _sharedValueManager.getArrayRecord(expRef.getRow(), expRef.getCol());
return arec.getFormulaTokens();
}
return _sharedFormulaRecord.getFormulaTokens(_formulaRecord);
return _formulaRecord.getParsedExpression();
}
/**
@ -216,4 +226,41 @@ public final class FormulaRecordAggregate extends RecordAggregate implements Cel
_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());
if (arec == null) {
throw new IllegalStateException("ArrayRecord was not found for the locator " + expRef.formatAsString());
}
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());
}
}

View File

@ -17,9 +17,11 @@
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;
@ -41,6 +43,7 @@ import org.apache.poi.ss.util.CellReference;
* </ul>
*
* @author Josh Micich
* @author Vladimirs Abramovs(Vladimirs.Abramovs at exigenservices.com) - handling of ArrayRecords
*/
public final class SharedValueManager {
@ -112,12 +115,12 @@ public final class SharedValueManager {
/**
* @return a new empty {@link SharedValueManager}.
*/
public static final SharedValueManager createEmpty() {
public static SharedValueManager createEmpty() {
// Note - must create distinct instances because they are assumed to be mutable.
return new SharedValueManager(
new SharedFormulaRecord[0], new CellReference[0], new ArrayRecord[0], new TableRecord[0]);
}
private final ArrayRecord[] _arrayRecords;
private final List<ArrayRecord> _arrayRecords;
private final TableRecord[] _tableRecords;
private final Map<SharedFormulaRecord, SharedFormulaGroup> _groupsBySharedFormulaRecord;
/** cached for optimization purposes */
@ -129,7 +132,8 @@ public final class SharedValueManager {
if (nShF != firstCells.length) {
throw new IllegalArgumentException("array sizes don't match: " + nShF + "!=" + firstCells.length + ".");
}
_arrayRecords = arrayRecords;
_arrayRecords = new ArrayList<ArrayRecord>();
_arrayRecords.addAll(Arrays.asList(arrayRecords));
_tableRecords = tableRecords;
Map<SharedFormulaRecord, SharedFormulaGroup> m = new HashMap<SharedFormulaRecord, SharedFormulaGroup>(nShF * 3 / 2);
for (int i = 0; i < nShF; i++) {
@ -139,14 +143,6 @@ public final class SharedValueManager {
_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) {
@ -260,8 +256,7 @@ public final class SharedValueManager {
return tr;
}
}
for (int i = 0; i < _arrayRecords.length; i++) {
ArrayRecord ar = _arrayRecords[i];
for (ArrayRecord ar : _arrayRecords) {
if (ar.isFirstCell(row, column)) {
return ar;
}
@ -281,4 +276,40 @@ public final class SharedValueManager {
}
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;
}
}

View File

@ -43,6 +43,7 @@ import org.apache.poi.hssf.record.SubRecord;
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,7 +51,9 @@ import org.apache.poi.ss.usermodel.CellStyle;
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.util.CellReference;
import org.apache.poi.ss.formula.FormulaType;
import org.apache.poi.ss.SpreadsheetVersion;
import org.apache.poi.util.POILogger;
@ -1160,4 +1163,31 @@ public class HSSFCell implements Cell {
}
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) {
String ref = new CellReference(this).formatAsString();
throw new IllegalStateException("Cell "+ref+" is not part of an array formula");
}
return ((FormulaRecordAggregate)_record).getArrayFormulaRange();
}
public boolean isPartOfArrayFormulaGroup() {
if (_cellType != CELL_TYPE_FORMULA) {
return false;
}
return ((FormulaRecordAggregate)_record).isPartOfArrayFormula();
}
}

View File

@ -31,6 +31,7 @@ import java.util.List;
import java.util.TreeMap;
import org.apache.poi.ddf.EscherRecord;
import org.apache.poi.hssf.model.HSSFFormulaParser;
import org.apache.poi.hssf.model.InternalSheet;
import org.apache.poi.hssf.model.InternalWorkbook;
import org.apache.poi.hssf.record.CellValueRecordInterface;
@ -44,14 +45,18 @@ import org.apache.poi.hssf.record.SCLRecord;
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;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.ss.util.CellReference;
import org.apache.poi.ss.SpreadsheetVersion;
import org.apache.poi.util.POILogFactory;
import org.apache.poi.util.POILogger;
@ -1870,4 +1875,57 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet {
return wb.getSheetName(idx);
}
public HSSFCell[] setArrayFormula(String formula, CellRangeAddress range) {
HSSFCell[] cells = new HSSFCell[range.getNumberOfCells()];
int k = 0;
// make sure the formula parses OK first
int sheetIndex = _workbook.getSheetIndex(this);
Ptg[] ptgs = HSSFFormulaParser.parse(formula, _workbook, FormulaType.ARRAY, sheetIndex);
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);
cells[k++] = cell;
}
}
HSSFCell mainArrayFormulaCell = getRow(firstRow).getCell(firstColumn);
FormulaRecordAggregate agg = (FormulaRecordAggregate)mainArrayFormulaCell.getCellValueRecord();
agg.setArrayFormula(range, ptgs);
return cells;
}
public HSSFCell[] removeArrayFormula(Cell cell) {
ArrayList<HSSFCell> lst = new ArrayList<HSSFCell>();
CellValueRecordInterface rec = ((HSSFCell) cell).getCellValueRecord();
if (!(rec instanceof FormulaRecordAggregate)) {
String ref = new CellReference(cell).formatAsString();
throw new IllegalArgumentException("Cell " + ref + " is not part of an array formula");
}
FormulaRecordAggregate fra = (FormulaRecordAggregate) rec;
CellRangeAddress range = fra.removeArrayFormula(cell.getRowIndex(), cell.getColumnIndex());
if (range == null) {
String ref = new CellReference(cell).formatAsString();
throw new IllegalArgumentException("Cell " + ref + " is not part of 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++) {
HSSFCell rCell = getRow(rowIn).getCell(colIn);
rCell.setCellType(Cell.CELL_TYPE_BLANK);
lst.add(rCell);
}
}
return lst.toArray(new HSSFCell[lst.size()]);
}
}

View File

@ -72,6 +72,9 @@ final class OperandClassTransformer {
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;

View File

@ -21,6 +21,7 @@ import java.util.Calendar;
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,18 @@ public interface Cell {
* @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();
}

View File

@ -781,4 +781,20 @@ public interface Sheet extends Iterable<Row> {
*/
boolean isSelected();
/**
* Sets array formula to specified region for result.
*
* @param formula Formula
* @param range Region of array formula for result.
*/
Cell[] 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
*/
Cell[] removeArrayFormula(Cell cell);
}

View File

@ -77,8 +77,11 @@ public class CellRangeAddress extends CellRangeAddressBase {
CellReference cellRefFrom = new CellReference(getFirstRow(), getFirstColumn());
CellReference cellRefTo = new CellReference(getLastRow(), getLastColumn());
sb.append(cellRefFrom.formatAsString());
sb.append(':');
sb.append(cellRefTo.formatAsString());
//for a single-cell reference return A1 instead of A1:A1
if(!cellRefFrom.equals(cellRefTo)){
sb.append(':');
sb.append(cellRefTo.formatAsString());
}
return sb.toString();
}

View File

@ -22,6 +22,7 @@ import java.util.regex.Pattern;
import org.apache.poi.hssf.record.formula.SheetNameFormatter;
import org.apache.poi.ss.SpreadsheetVersion;
import org.apache.poi.ss.usermodel.Cell;
/**
*
@ -113,6 +114,10 @@ public class CellReference {
public CellReference(int pRow, short pCol) {
this(pRow, pCol & 0xFFFF, false, false);
}
public CellReference(Cell cell) {
this(cell.getRowIndex(), cell.getColumnIndex(), false, false);
}
public CellReference(int pRow, int pCol, boolean pAbsRow, boolean pAbsCol) {
this(null, pRow, pCol, pAbsRow, pAbsCol);
@ -483,4 +488,22 @@ public class CellReference {
}
sb.append(_rowIndex+1);
}
/**
* Checks whether this cell reference is equal to another object.
* <p>
* Two cells references are assumed to be equal if their string representations
* ({@link #formatAsString()} are equal.
* </p>
*/
@Override
public boolean equals(Object o){
if(o == null || !(o instanceof CellReference)) {
return false;
}
String me = formatAsString();
String anotherRef = ((CellReference)o).formatAsString();
return me.equals(anotherRef);
}
}

View File

@ -407,9 +407,9 @@ public final class XSSFCell implements Cell {
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();
CTCellFormula f = CTCellFormula.Factory.newInstance();
f.setStringValue(formula);
_cell.setF(f);
if(_cell.isSetV()) _cell.unsetV();

View File

@ -0,0 +1,98 @@
/* ====================================================================
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.BaseTestArrayFormulas;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.XSSFITestDataProvider;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTCellFormula;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.STCellFormulaType;
/**
* Test array formulas in XSSF
*
* @author Yegor Kozlov
*/
public final class TestXSSFArrayFormulas extends BaseTestArrayFormulas {
@Override
protected XSSFITestDataProvider getTestDataProvider(){
return XSSFITestDataProvider.getInstance();
}
public void testXSSFSetArrayFormula_singleCell() {
XSSFWorkbook workbook = getTestDataProvider().createWorkbook();
XSSFSheet sheet = workbook.createSheet();
// row 3 does not yet exist
assertNull(sheet.getRow(2));
CellRangeAddress range = new CellRangeAddress(2, 2, 2, 2);
XSSFCell[] cells = sheet.setArrayFormula("SUM(C11:C12*D11:D12)", range);
assertEquals(1, cells.length);
// sheet.setArrayFormula creates rows and cells for the designated range
assertNotNull(sheet.getRow(2));
XSSFCell cell = sheet.getRow(2).getCell(2);
assertNotNull(cell);
assertTrue(cell.isPartOfArrayFormulaGroup());
assertSame(cells[0], sheet.getFirstCellInArrayFormula(cells[0]));
//retrieve the range and check it is the same
assertEquals(range.formatAsString(), cell.getArrayFormulaRange().formatAsString());
//check the CTCellFormula bean
CTCellFormula f = cell.getCTCell().getF();
assertEquals("SUM(C11:C12*D11:D12)", f.getStringValue());
assertEquals("C3", f.getRef());
assertEquals(STCellFormulaType.ARRAY, f.getT());
}
public void testXSSFSetArrayFormula_multiCell() {
XSSFCell[] cells;
XSSFWorkbook workbook = getTestDataProvider().createWorkbook();
XSSFSheet sheet = workbook.createSheet();
CellRangeAddress range = new CellRangeAddress(3, 5, 2, 2);
assertEquals("C4:C6", range.formatAsString());
cells = sheet.setArrayFormula("SUM(A1:A3*B1:B3)", range);
assertEquals(3, cells.length);
// sheet.setArrayFormula creates rows and cells for the designated range
assertEquals("C4", cells[0].getCTCell().getR());
assertEquals("C5", cells[1].getCTCell().getR());
assertEquals("C6", cells[2].getCTCell().getR());
assertSame(cells[0], sheet.getFirstCellInArrayFormula(cells[0]));
/*
* From the spec:
* For a multi-cell formula, the c elements for all cells except the top-left
* cell in that range shall not have an f element;
*/
//the first cell has an f element
CTCellFormula f = cells[0].getCTCell().getF();
assertEquals("SUM(A1:A3*B1:B3)", f.getStringValue());
assertEquals("C4:C6", f.getRef());
assertEquals(STCellFormulaType.ARRAY, f.getT());
//the other two cells don't have an f element
assertNull(cells[1].getCTCell().getF());
assertNull(cells[2].getCTCell().getF());
}
}

View File

@ -914,139 +914,4 @@ public class TestXSSFSheet extends BaseTestSheet {
//existing cells are invalidated
assertEquals(0, wsh.getSheetData().getRowArray(0).sizeOfCArray());
}
public void testSetArrayFormula() throws Exception {
XSSFCell[] cells;
XSSFWorkbook workbook = new XSSFWorkbook();
XSSFSheet sheet = workbook.createSheet();
XSSFCell cell = sheet.createRow(0).createCell(0);
assertFalse(cell.isPartOfArrayFormulaGroup());
assertFalse(sheet.isCellInArrayFormulaContext(cell));
try {
CellRangeAddress range = cell.getArrayFormulaRange();
fail("expected exception");
} catch (IllegalStateException e){
assertEquals("Cell A1 is not part of an array formula", e.getMessage());
}
// 1. single-cell formula
// row 3 does not yet exist
assertNull(sheet.getRow(2));
CellRangeAddress range = new CellRangeAddress(2, 2, 2, 2);
cells = sheet.setArrayFormula("SUM(C11:C12*D11:D12)", range);
assertEquals(1, cells.length);
// sheet.setArrayFormula creates rows and cells for the designated range
assertNotNull(sheet.getRow(2));
cell = sheet.getRow(2).getCell(2);
assertNotNull(cell);
assertTrue(cell.isPartOfArrayFormulaGroup());
assertSame(cells[0], sheet.getFirstCellInArrayFormula(cells[0]));
//retrieve the range and check it is the same
assertEquals(range.formatAsString(), cell.getArrayFormulaRange().formatAsString());
// 2. multi-cell formula
//rows 3-5 don't exist yet
assertNull(sheet.getRow(3));
assertNull(sheet.getRow(4));
assertNull(sheet.getRow(5));
range = new CellRangeAddress(3, 5, 2, 2);
assertEquals("C4:C6", range.formatAsString());
cells = sheet.setArrayFormula("SUM(A1:A3*B1:B3)", range);
assertEquals(3, cells.length);
// sheet.setArrayFormula creates rows and cells for the designated range
assertEquals("C4", cells[0].getCTCell().getR());
assertEquals("C5", cells[1].getCTCell().getR());
assertEquals("C6", cells[2].getCTCell().getR());
assertSame(cells[0], sheet.getFirstCellInArrayFormula(cells[0]));
/*
* For a multi-cell formula, the c elements for all cells except the top-left
* cell in that range shall not have an f element;
*/
assertEquals("SUM(A1:A3*B1:B3)", cells[0].getCTCell().getF().getStringValue());
assertNull(cells[1].getCTCell().getF());
assertNull(cells[2].getCTCell().getF());
for(XSSFCell acell : cells){
assertTrue(acell.isPartOfArrayFormulaGroup());
assertEquals(Cell.CELL_TYPE_FORMULA, acell.getCellType());
assertEquals("SUM(A1:A3*B1:B3)", acell.getCellFormula());
//retrieve the range and check it is the same
assertEquals(range.formatAsString(), acell.getArrayFormulaRange().formatAsString());
}
}
public void testRemoveArrayFormula() throws Exception {
XSSFCell[] cells;
XSSFWorkbook workbook = new XSSFWorkbook();
XSSFSheet sheet = workbook.createSheet();
CellRangeAddress range = new CellRangeAddress(3, 5, 2, 2);
assertEquals("C4:C6", range.formatAsString());
cells = sheet.setArrayFormula("SUM(A1:A3*B1:B3)", range);
assertEquals(3, cells.length);
// remove the formula cells in C4:C6
XSSFCell[] dcells = sheet.removeArrayFormula(cells[0]);
// removeArrayFormula should return the same cells as setArrayFormula
assertTrue(Arrays.equals(cells, dcells));
for(XSSFCell acell : cells){
assertFalse(acell.isPartOfArrayFormulaGroup());
assertEquals(Cell.CELL_TYPE_BLANK, acell.getCellType());
}
//invocation on a not-array-formula cell throws IllegalStateException
try {
sheet.removeArrayFormula(cells[0]);
fail("expected exception");
} catch (IllegalArgumentException e){
assertEquals("Cell C4 is not part of an array formula", e.getMessage());
}
}
public void testReadArrayFormula() throws Exception {
XSSFCell[] cells;
XSSFWorkbook workbook = new XSSFWorkbook();
XSSFSheet sheet1 = workbook.createSheet();
cells = sheet1.setArrayFormula("SUM(A1:A3*B1:B3)", CellRangeAddress.valueOf("C4:C6"));
assertEquals(3, cells.length);
cells = sheet1.setArrayFormula("MAX(A1:A3*B1:B3)", CellRangeAddress.valueOf("A4:A6"));
assertEquals(3, cells.length);
XSSFSheet sheet2 = workbook.createSheet();
cells = sheet2.setArrayFormula("MIN(A1:A3*B1:B3)", CellRangeAddress.valueOf("D2:D4"));
assertEquals(3, cells.length);
workbook = getTestDataProvider().writeOutAndReadBack(workbook);
sheet1 = workbook.getSheetAt(0);
for(int rownum=3; rownum <= 5; rownum++) {
XSSFCell cell1 = sheet1.getRow(rownum).getCell(2);
assertTrue( sheet1.isCellInArrayFormulaContext(cell1));
assertTrue( cell1.isPartOfArrayFormulaGroup());
XSSFCell cell2 = sheet1.getRow(rownum).getCell(0);
assertTrue( sheet1.isCellInArrayFormulaContext(cell2));
assertTrue( cell2.isPartOfArrayFormulaGroup());
}
sheet2 = workbook.getSheetAt(1);
for(int rownum=1; rownum <= 3; rownum++) {
XSSFCell cell1 = sheet2.getRow(rownum).getCell(3);
assertTrue( sheet2.isCellInArrayFormulaContext(cell1));
assertTrue( cell1.isPartOfArrayFormulaGroup());
}
XSSFCell acnhorCell = sheet2.getRow(1).getCell(3);
XSSFCell fmlaCell = sheet2.getRow(2).getCell(3);
assertSame(acnhorCell, sheet2.getFirstCellInArrayFormula(fmlaCell));
assertSame(acnhorCell, sheet2.getFirstCellInArrayFormula(acnhorCell));
}
}

View File

@ -0,0 +1,68 @@
/* ====================================================================
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 junit.framework.AssertionFailedError;
import junit.framework.ComparisonFailure;
import junit.framework.TestCase;
import org.apache.poi.hssf.HSSFTestDataSamples;
import org.apache.poi.hssf.util.CellRangeAddress8Bit;
import org.apache.poi.hssf.record.formula.Ptg;
import org.apache.poi.hssf.record.formula.RefPtg;
import org.apache.poi.hssf.usermodel.*;
import org.apache.poi.ss.usermodel.CellValue;
import org.apache.poi.ss.formula.FormulaParser;
import org.apache.poi.ss.formula.FormulaRenderer;
import org.apache.poi.ss.formula.FormulaType;
import org.apache.poi.ss.formula.Formula;
import org.apache.poi.util.LittleEndianInput;
import org.apache.poi.util.HexRead;
import org.apache.poi.util.HexDump;
import java.util.Arrays;
public final class TestArrayRecord extends TestCase {
public void testRead() {
String hex =
"21 02 25 00 01 00 01 00 01 01 00 00 00 00 00 00 " +
"17 00 65 00 00 01 00 02 C0 02 C0 65 00 00 01 00 " +
"03 C0 03 C0 04 62 01 07 00";
byte[] data = HexRead.readFromString(hex);
RecordInputStream in = TestcaseRecordInputStream.create(data);
ArrayRecord r1 = new ArrayRecord(in);
CellRangeAddress8Bit range = r1.getRange();
assertEquals(1, range.getFirstColumn());
assertEquals(1, range.getLastColumn());
assertEquals(1, range.getFirstRow());
assertEquals(1, range.getLastRow());
Ptg[] ptg = r1.getFormulaTokens();
assertEquals("MAX(C1:C2-D1:D2)", FormulaRenderer.toFormulaString(null, ptg));
//construct a new ArrayRecord with the same contents as r1
Ptg[] fmlaPtg = FormulaParser.parse("MAX(C1:C2-D1:D2)", null, FormulaType.ARRAY, 0);
ArrayRecord r2 = new ArrayRecord(Formula.create(fmlaPtg), new CellRangeAddress8Bit(1, 1, 1, 1));
byte[] ser = r2.serialize();
//serialize and check that the data is the same as in r1
assertEquals(HexDump.toHex(data), HexDump.toHex(ser));
}
}

View File

@ -24,7 +24,13 @@ import org.apache.poi.hssf.record.FormulaRecord;
import org.apache.poi.hssf.record.Record;
import org.apache.poi.hssf.record.RecordFormatException;
import org.apache.poi.hssf.record.StringRecord;
import org.apache.poi.hssf.record.formula.Ptg;
import org.apache.poi.hssf.record.formula.ExpPtg;
import org.apache.poi.hssf.usermodel.RecordInspector.RecordCollector;
import org.apache.poi.hssf.model.HSSFFormulaParser;
import org.apache.poi.ss.formula.FormulaType;
import org.apache.poi.ss.formula.FormulaRenderer;
import org.apache.poi.ss.util.CellRangeAddress;
/**
*
@ -39,7 +45,8 @@ public final class TestFormulaRecordAggregate extends TestCase {
s.setString("abc");
FormulaRecordAggregate fagg = new FormulaRecordAggregate(f, s, SharedValueManager.createEmpty());
assertEquals("abc", fagg.getStringValue());
}
assertFalse(fagg.isPartOfArrayFormula());
}
/**
* Sometimes a {@link StringRecord} appears after a {@link FormulaRecord} even though the
@ -71,4 +78,27 @@ public final class TestFormulaRecordAggregate extends TestCase {
assertEquals(1, vraRecs.length);
assertEquals(fr, vraRecs[0]);
}
public void testArrayFormulas() {
int rownum = 4;
int colnum = 4;
FormulaRecord fr = new FormulaRecord();
fr.setRow(rownum);
fr.setColumn((short)colnum);
FormulaRecordAggregate agg = new FormulaRecordAggregate(fr, null, SharedValueManager.createEmpty());
Ptg[] ptgsForCell = {new ExpPtg(rownum, colnum)};
agg.setParsedExpression(ptgsForCell);
String formula = "SUM(A1:A3*B1:B3)";
Ptg[] ptgs = HSSFFormulaParser.parse(formula, null, FormulaType.ARRAY, 0);
agg.setArrayFormula(new CellRangeAddress(rownum, rownum, colnum, colnum), ptgs);
assertTrue(agg.isPartOfArrayFormula());
assertEquals("E5", agg.getArrayFormulaRange().formatAsString());
Ptg[] ptg = agg.getFormulaTokens();
String fmlaSer = FormulaRenderer.toFormulaString(null, ptg);
assertEquals(formula, fmlaSer);
}
}

View File

@ -0,0 +1,60 @@
/* ====================================================================
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.usermodel.BaseTestArrayFormulas;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.hssf.HSSFITestDataProvider;
import org.apache.poi.hssf.record.aggregates.FormulaRecordAggregate;
/**
* Test array formulas in HSSF
*
* @author Yegor Kozlov
*/
public final class TestHSSFArrayFormulas extends BaseTestArrayFormulas {
@Override
protected HSSFITestDataProvider getTestDataProvider(){
return HSSFITestDataProvider.getInstance();
}
public void testHSSFSetArrayFormula_singleCell() {
HSSFWorkbook workbook = getTestDataProvider().createWorkbook();
HSSFSheet sheet = workbook.createSheet();
CellRangeAddress range = new CellRangeAddress(2, 2, 2, 2);
HSSFCell[] cells = sheet.setArrayFormula("SUM(C11:C12*D11:D12)", range);
assertEquals(1, cells.length);
// sheet.setArrayFormula creates rows and cells for the designated range
assertNotNull(sheet.getRow(2));
HSSFCell cell = sheet.getRow(2).getCell(2);
assertNotNull(cell);
assertTrue(cell.isPartOfArrayFormulaGroup());
//retrieve the range and check it is the same
assertEquals(range.formatAsString(), cell.getArrayFormulaRange().formatAsString());
FormulaRecordAggregate agg = (FormulaRecordAggregate)cell.getCellValueRecord();
assertEquals(range.formatAsString(), agg.getArrayFormulaRange().formatAsString());
assertTrue(agg.isPartOfArrayFormula());
}
}

View File

@ -20,6 +20,7 @@ package org.apache.poi.hssf.usermodel;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.Arrays;
import junit.framework.AssertionFailedError;
@ -42,6 +43,9 @@ import org.apache.poi.hssf.record.WindowTwoRecord;
import org.apache.poi.hssf.record.aggregates.WorksheetProtectionBlock;
import org.apache.poi.hssf.usermodel.RecordInspector.RecordCollector;
import org.apache.poi.ss.usermodel.BaseTestSheet;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.ss.util.CellRangeAddressList;
import org.apache.poi.util.TempFile;
@ -820,5 +824,4 @@ public final class TestHSSFSheet extends BaseTestSheet {
s.setRightToLeft(true);
assertEquals(true, s.isRightToLeft());
}
}

View File

@ -0,0 +1,210 @@
/* ====================================================================
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.SpreadsheetVersion;
import org.apache.poi.ss.formula.FormulaParseException;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.ss.util.CellReference;
import java.util.Iterator;
import java.util.Arrays;
/**
* Common superclass for testing usermodel API for array formulas
*
* @author Yegor Kozlov
*/
public abstract class BaseTestArrayFormulas extends TestCase {
/**
* @return an object that provides test data in HSSF / XSSF specific way
*/
protected abstract ITestDataProvider getTestDataProvider();
/**
* Set single-cell array formula
*/
public void testSetArrayFormula_singleCell() {
Workbook workbook = getTestDataProvider().createWorkbook();
Sheet sheet = workbook.createSheet();
// row 3 does not yet exist
assertNull(sheet.getRow(2));
CellRangeAddress range = new CellRangeAddress(2, 2, 2, 2);
Cell[] cells = sheet.setArrayFormula("SUM(C11:C12*D11:D12)", range);
assertEquals(1, cells.length);
// sheet.setArrayFormula creates rows and cells for the designated range
assertNotNull(sheet.getRow(2));
Cell cell = sheet.getRow(2).getCell(2);
assertNotNull(cell);
assertTrue(cell.isPartOfArrayFormulaGroup());
//retrieve the range and check it is the same
assertEquals(range.formatAsString(), cell.getArrayFormulaRange().formatAsString());
//check the formula
assertEquals("SUM(C11:C12*D11:D12)", cell.getCellFormula());
}
/**
* Set multi-cell array formula
*/
public void testSetArrayFormula_multiCell() {
Workbook workbook = getTestDataProvider().createWorkbook();
Sheet sheet = workbook.createSheet();
// multi-cell formula
// rows 3-5 don't exist yet
assertNull(sheet.getRow(3));
assertNull(sheet.getRow(4));
assertNull(sheet.getRow(5));
CellRangeAddress range = new CellRangeAddress(3, 5, 2, 2);
assertEquals("C4:C6", range.formatAsString());
Cell[] cells = sheet.setArrayFormula("SUM(A1:A3*B1:B3)", range);
assertEquals(3, cells.length);
// sheet.setArrayFormula creates rows and cells for the designated range
assertSame(cells[0], sheet.getRow(3).getCell(2));
assertSame(cells[1], sheet.getRow(4).getCell(2));
assertSame(cells[2], sheet.getRow(5).getCell(2));
for(Cell acell : cells){
assertTrue(acell.isPartOfArrayFormulaGroup());
assertEquals(Cell.CELL_TYPE_FORMULA, acell.getCellType());
assertEquals("SUM(A1:A3*B1:B3)", acell.getCellFormula());
//retrieve the range and check it is the same
assertEquals(range.formatAsString(), acell.getArrayFormulaRange().formatAsString());
}
}
/**
* Passing an incorrect formula to sheet.setArrayFormula
* should throw FormulaParseException
*/
public void testSetArrayFormula_incorrectFormula() {
Workbook workbook = getTestDataProvider().createWorkbook();
Sheet sheet = workbook.createSheet();
try {
sheet.setArrayFormula("incorrect-formula(C11_C12*D11_D12)",
new CellRangeAddress(10, 10, 10, 10));
fail("expected exception");
} catch (FormulaParseException e){
//expected exception
}
}
/**
* Calls of cell.getArrayFormulaRange and sheet.removeArrayFormula
* on a not-array-formula cell throw IllegalStateException
*/
public void testArrayFormulas_illegalCalls() {
Workbook workbook = getTestDataProvider().createWorkbook();
Sheet sheet = workbook.createSheet();
Cell cell = sheet.createRow(0).createCell(0);
assertFalse(cell.isPartOfArrayFormulaGroup());
try {
CellRangeAddress range = cell.getArrayFormulaRange();
fail("expected exception");
} catch (IllegalStateException e){
assertEquals("Cell A1 is not part of an array formula", e.getMessage());
}
try {
sheet.removeArrayFormula(cell);
fail("expected exception");
} catch (IllegalArgumentException e){
assertEquals("Cell A1 is not part of an array formula", e.getMessage());
}
}
/**
* create and remove array formulas
*/
public void testRemoveArrayFormula() {
Workbook workbook = getTestDataProvider().createWorkbook();
Sheet sheet = workbook.createSheet();
CellRangeAddress range = new CellRangeAddress(3, 5, 2, 2);
assertEquals("C4:C6", range.formatAsString());
Cell[] cells = sheet.setArrayFormula("SUM(A1:A3*B1:B3)", range);
assertEquals(3, cells.length);
// remove the formula cells in C4:C6
Cell[] dcells = sheet.removeArrayFormula(cells[0]);
// removeArrayFormula should return the same cells as setArrayFormula
assertTrue(Arrays.equals(cells, dcells));
for(Cell acell : cells){
assertFalse(acell.isPartOfArrayFormulaGroup());
assertEquals(Cell.CELL_TYPE_BLANK, acell.getCellType());
}
// cells C4:C6 are not included in array formula,
// invocation of sheet.removeArrayFormula on any of them throws IllegalArgumentException
for(Cell acell : cells){
try {
sheet.removeArrayFormula(acell);
fail("expected exception");
} catch (IllegalArgumentException e){
String ref = new CellReference(acell).formatAsString();
assertEquals("Cell "+ref+" is not part of an array formula", e.getMessage());
}
}
}
/**
* Test that when reading a workbook from input stream, array formulas are recognized
*/
public void testReadArrayFormula() {
Cell[] cells;
Workbook workbook = getTestDataProvider().createWorkbook();
Sheet sheet1 = workbook.createSheet();
cells = sheet1.setArrayFormula("SUM(A1:A3*B1:B3)", CellRangeAddress.valueOf("C4:C6"));
assertEquals(3, cells.length);
cells = sheet1.setArrayFormula("MAX(A1:A3*B1:B3)", CellRangeAddress.valueOf("A4:A6"));
assertEquals(3, cells.length);
Sheet sheet2 = workbook.createSheet();
cells = sheet2.setArrayFormula("MIN(A1:A3*B1:B3)", CellRangeAddress.valueOf("D2:D4"));
assertEquals(3, cells.length);
workbook = getTestDataProvider().writeOutAndReadBack(workbook);
sheet1 = workbook.getSheetAt(0);
for(int rownum=3; rownum <= 5; rownum++) {
Cell cell1 = sheet1.getRow(rownum).getCell(2);
assertTrue( cell1.isPartOfArrayFormulaGroup());
Cell cell2 = sheet1.getRow(rownum).getCell(0);
assertTrue( cell2.isPartOfArrayFormulaGroup());
}
sheet2 = workbook.getSheetAt(1);
for(int rownum=1; rownum <= 3; rownum++) {
Cell cell1 = sheet2.getRow(rownum).getCell(3);
assertTrue( cell1.isPartOfArrayFormulaGroup());
}
}
}