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());
}
}
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;
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();
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);
*/
public int getCellType() {
- if (_cell.getF() != null) {
+ if (_cell.getF() != null || getSheet().isCellInArrayFormulaContext(this)) {
return CELL_TYPE_FORMULA;
}
}
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);
+ }
}
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.
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);
//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
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");
+ }
}
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 {
//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));
+ }
}
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());
+ }
}