added tests for XSSF usermodel for array formulas, this change is a step towards adoption of the patch submitted in Bugzilla 48292

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@893625 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Yegor Kozlov 2009-12-23 20:58:01 +00:00
parent 51c3adb24f
commit b9fab26825
5 changed files with 342 additions and 6 deletions

View File

@ -82,10 +82,21 @@ public class CellRangeAddress extends CellRangeAddressBase {
return sb.toString(); return sb.toString();
} }
/**
* @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) { public static CellRangeAddress valueOf(String ref) {
int sep = ref.indexOf(":"); int sep = ref.indexOf(":");
CellReference cellFrom = new CellReference(ref.substring(0, sep)); CellReference a;
CellReference cellTo = new CellReference(ref.substring(sep + 1)); CellReference b;
return new CellRangeAddress(cellFrom.getRow(), cellTo.getRow(), cellFrom.getCol(), cellTo.getCol()); 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());
} }
} }

View File

@ -37,6 +37,7 @@ import org.apache.poi.ss.usermodel.DateUtil;
import org.apache.poi.ss.usermodel.FormulaError; import org.apache.poi.ss.usermodel.FormulaError;
import org.apache.poi.ss.usermodel.Hyperlink; import org.apache.poi.ss.usermodel.Hyperlink;
import org.apache.poi.ss.usermodel.RichTextString; 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.ss.util.CellReference;
import org.apache.poi.xssf.model.SharedStringsTable; import org.apache.poi.xssf.model.SharedStringsTable;
import org.apache.poi.xssf.model.StylesTable; import org.apache.poi.xssf.model.StylesTable;
@ -344,7 +345,11 @@ public final class XSSFCell implements Cell {
if(cellType != CELL_TYPE_FORMULA) throw typeMismatch(CELL_TYPE_FORMULA, cellType, false); if(cellType != CELL_TYPE_FORMULA) throw typeMismatch(CELL_TYPE_FORMULA, cellType, false);
CTCellFormula f = _cell.getF(); 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 convertSharedFormula((int)f.getSi());
} }
return f.getStringValue(); return f.getStringValue();
@ -370,7 +375,29 @@ public final class XSSFCell implements Cell {
return FormulaRenderer.toFormulaString(fpb, fmla); return FormulaRenderer.toFormulaString(fpb, fmla);
} }
/**
* Sets formula for this cell.
* <p>
* Note, this method only sets the formula string and does not calculate the formula value.
* To set the precalculated value use {@link #setCellValue(double)} or {@link #setCellValue(String)}
* </p>
*
* @param formula the formula to set, e.g. <code>"SUM(C4:E4)"</code>.
* If the argument is <code>null</code> then the current formula is removed.
* @throws org.apache.poi.ss.formula.FormulaParseException if the formula has incorrect syntax or is otherwise invalid
*/
public void setCellFormula(String formula) { 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(); XSSFWorkbook wb = _row.getSheet().getWorkbook();
if (formula == null) { if (formula == null) {
wb.onDeleteFormula(this); wb.onDeleteFormula(this);
@ -461,7 +488,7 @@ public final class XSSFCell implements Cell {
*/ */
public int getCellType() { public int getCellType() {
if (_cell.getF() != null) { if (_cell.getF() != null || getSheet().isCellInArrayFormulaContext(this)) {
return CELL_TYPE_FORMULA; return CELL_TYPE_FORMULA;
} }
@ -941,4 +968,31 @@ public final class XSSFCell implements Cell {
} }
throw new IllegalStateException("Unexpected formula result type (" + cellType + ")"); throw new IllegalStateException("Unexpected formula result type (" + cellType + ")");
} }
/**
* If this cell is part of an array formula, returns a CellRangeAddress object
* that represents the entire array.
*
* @return the range of the array formula group that this cell belongs to.
* @throws IllegalStateException if this cell is not part of an array formula
* @see #isPartOfArrayFormulaGroup()
*/
public CellRangeAddress getArrayFormulaRange() {
XSSFCell cell = getSheet().getFirstCellInArrayFormula(this);
if (cell == null) {
throw new IllegalStateException("Cell " + _cell.getR() + " is not part of an array formula");
}
String formulaRef = cell._cell.getF().getRef();
return CellRangeAddress.valueOf(formulaRef);
}
/**
* Test if this cell is included in an array formula
*
* @return true if this cell is part of an array formula
* @see #getArrayFormulaRange()
*/
public boolean isPartOfArrayFormulaGroup() {
return getSheet().isCellInArrayFormulaContext(this);
}
} }

View File

@ -79,6 +79,7 @@ public class XSSFSheet extends POIXMLDocumentPart implements Sheet {
private ColumnHelper columnHelper; private ColumnHelper columnHelper;
private CommentsTable sheetComments; private CommentsTable sheetComments;
private Map<Integer, XSSFCell> sharedFormulas; private Map<Integer, XSSFCell> sharedFormulas;
private List<CellRangeAddress> arrayFormulas;
/** /**
* Creates new XSSFSheet - called by XSSFWorkbook to create a sheet from scratch. * Creates new XSSFSheet - called by XSSFWorkbook to create a sheet from scratch.
@ -153,6 +154,7 @@ public class XSSFSheet extends POIXMLDocumentPart implements Sheet {
private void initRows(CTWorksheet worksheet) { private void initRows(CTWorksheet worksheet) {
rows = new TreeMap<Integer, XSSFRow>(); rows = new TreeMap<Integer, XSSFRow>();
sharedFormulas = new HashMap<Integer, XSSFCell>(); sharedFormulas = new HashMap<Integer, XSSFCell>();
arrayFormulas = new ArrayList<CellRangeAddress>();
for (CTRow row : worksheet.getSheetData().getRowArray()) { for (CTRow row : worksheet.getSheetData().getRowArray()) {
XSSFRow r = new XSSFRow(row, this); XSSFRow r = new XSSFRow(row, this);
rows.put(r.getRowNum(), r); rows.put(r.getRowNum(), r);
@ -2316,9 +2318,12 @@ public class XSSFSheet extends POIXMLDocumentPart implements Sheet {
//collect cells holding shared formulas //collect cells holding shared formulas
CTCell ct = cell.getCTCell(); CTCell ct = cell.getCTCell();
CTCellFormula f = ct.getF(); 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); sharedFormulas.put((int)f.getSi(), cell);
} }
if (f != null && f.getT() == STCellFormulaType.ARRAY && f.getRef() != null) {
arrayFormulas.add(CellRangeAddress.valueOf(f.getRef()));
}
} }
@Override @Override
@ -2676,4 +2681,105 @@ public class XSSFSheet extends POIXMLDocumentPart implements Sheet {
private boolean sheetProtectionEnabled() { private boolean sheetProtectionEnabled() {
return worksheet.getSheetProtection().getSheet(); 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;
}
/**
* Sets array formula to the specified range of cells.
* <p>
* Note, that this method silently creates cells in the
* specified range if they don't exist.
* </p>
* Example:
* <blockquote><pre>
* Workbook workbook = new XSSFWorkbook();
* Sheet sheet = workbook.createSheet();
* CellRangeAddress range = CellRangeAddress.valueOf("C1:C3");
* Cell[] cells = sheet.setArrayFormula("A1:A3*B1:B3", range);
* </pre></blockquote>
* Three cells in the C1:C3 range are created and returned.
*
* @param formula the formula to set
* @param range Region of array formula for result.
* @return the array of cells that represent the entire formula array
* @throws org.apache.poi.ss.formula.FormulaParseException if
* the formula has incorrect syntax or is otherwise invalid
*/
public XSSFCell[] 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);
XSSFCell[] cells = new XSSFCell[range.getNumberOfCells()];
int k = 0;
for (int rowIndex = range.getFirstRow(); rowIndex <= range.getLastRow(); rowIndex++) {
row = getRow(rowIndex);
if (row == null) {
row = createRow(rowIndex);
}
for (int columnIndex = range.getFirstColumn(); columnIndex <= range.getLastColumn(); columnIndex++) {
XSSFCell arrayFormulaCell = row.getCell(columnIndex);
if (arrayFormulaCell == null) {
arrayFormulaCell = row.createCell(columnIndex);
}
cells[k++] = arrayFormulaCell;
}
}
return cells;
}
/**
* Remove an Array Formula from this sheet.
* <p>
* All cells contained in the Array Formula range are removed as well
* </p>
*
* @param cell any cell within Array Formula range
* @return the array of affected cells.
* @throws IllegalArgumentException if the specified cell is not part of an array formula
*/
public XSSFCell[] removeArrayFormula(Cell cell) {
ArrayList<XSSFCell> lst = new ArrayList<XSSFCell>();
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);
lst.add(arrayFormulaCell);
}
}
}
return lst.toArray(new XSSFCell[lst.size()]);
}
}
String ref = ((XSSFCell)cell).getCTCell().getR();
throw new IllegalArgumentException("Cell " + ref + " is not part of an array formula");
}
} }

View File

@ -32,6 +32,8 @@ import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTWorksheet;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTXf; import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTXf;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.STPane; import org.openxmlformats.schemas.spreadsheetml.x2006.main.STPane;
import java.util.Arrays;
public class TestXSSFSheet extends BaseTestSheet { public class TestXSSFSheet extends BaseTestSheet {
@ -912,4 +914,153 @@ public class TestXSSFSheet extends BaseTestSheet {
//existing cells are invalidated //existing cells are invalidated
assertEquals(0, wsh.getSheetData().getRowArray(0).sizeOfCArray()); assertEquals(0, wsh.getSheetData().getRowArray(0).sizeOfCArray());
} }
public void testSetArrayFormula_File() throws Exception {
XSSFWorkbook workbook = new XSSFWorkbook("D:\\java\\apache\\apache-poi\\bugzilla\\array-formulas\\template.xlsx");
XSSFSheet sheet1 = workbook.getSheetAt(0);
sheet1.setArrayFormula("SUM(C1:C2*D1:D2)", CellRangeAddress.valueOf("B1"));
sheet1.setArrayFormula("MAX(C1:C2-D1:D2)", CellRangeAddress.valueOf("B2"));
XSSFSheet sheet2 = workbook.getSheetAt(1);
sheet2.setArrayFormula("A1:A3*B1:B3", CellRangeAddress.valueOf("C1:C3"));
java.io.FileOutputStream out = new java.io.FileOutputStream("D:\\java\\apache\\apache-poi\\bugzilla\\array-formulas\\poi-template.xlsx");
workbook.write(out);
out.close();
}
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

@ -192,4 +192,18 @@ public final class TestCellRange extends TestCase
assertEquals(1, cr3.length); assertEquals(1, cr3.length);
assertEquals("A1:B2", cr3[0].formatAsString()); assertEquals("A1:B2", cr3[0].formatAsString());
} }
public void testValueOf() {
CellRangeAddress cr1 = CellRangeAddress.valueOf("A1:B1");
assertEquals(0, cr1.getFirstColumn());
assertEquals(0, cr1.getFirstRow());
assertEquals(1, cr1.getLastColumn());
assertEquals(0, cr1.getLastRow());
CellRangeAddress cr2 = CellRangeAddress.valueOf("B1");
assertEquals(1, cr2.getFirstColumn());
assertEquals(0, cr2.getFirstRow());
assertEquals(1, cr2.getLastColumn());
assertEquals(0, cr2.getLastRow());
}
} }