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:
parent
51c3adb24f
commit
b9fab26825
@ -82,10 +82,21 @@ public class CellRangeAddress extends CellRangeAddressBase {
|
||||
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) {
|
||||
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());
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
@ -37,6 +37,7 @@ import org.apache.poi.ss.usermodel.DateUtil;
|
||||
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 @@ public final class XSSFCell implements Cell {
|
||||
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();
|
||||
@ -370,7 +375,29 @@ public final class XSSFCell implements Cell {
|
||||
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) {
|
||||
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);
|
||||
@ -461,7 +488,7 @@ public final class XSSFCell implements Cell {
|
||||
*/
|
||||
public int getCellType() {
|
||||
|
||||
if (_cell.getF() != null) {
|
||||
if (_cell.getF() != null || getSheet().isCellInArrayFormulaContext(this)) {
|
||||
return CELL_TYPE_FORMULA;
|
||||
}
|
||||
|
||||
@ -941,4 +968,31 @@ public final class XSSFCell implements Cell {
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -79,6 +79,7 @@ public class XSSFSheet extends POIXMLDocumentPart implements Sheet {
|
||||
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 @@ public class XSSFSheet extends POIXMLDocumentPart implements Sheet {
|
||||
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 @@ public class XSSFSheet extends POIXMLDocumentPart implements Sheet {
|
||||
//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,105 @@ public class XSSFSheet extends POIXMLDocumentPart implements Sheet {
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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");
|
||||
}
|
||||
}
|
||||
|
@ -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.STPane;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
|
||||
public class TestXSSFSheet extends BaseTestSheet {
|
||||
|
||||
@ -912,4 +914,153 @@ public class TestXSSFSheet extends BaseTestSheet {
|
||||
//existing cells are invalidated
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
@ -192,4 +192,18 @@ public final class TestCellRange extends TestCase
|
||||
assertEquals(1, cr3.length);
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user