]> source.dussan.org Git - poi.git/commitdiff
added tests for XSSF usermodel for array formulas, this change is a step towards...
authorYegor Kozlov <yegor@apache.org>
Wed, 23 Dec 2009 20:58:01 +0000 (20:58 +0000)
committerYegor Kozlov <yegor@apache.org>
Wed, 23 Dec 2009 20:58:01 +0000 (20:58 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@893625 13f79535-47bb-0310-9956-ffa450edef68

src/java/org/apache/poi/ss/util/CellRangeAddress.java
src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFCell.java
src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFSheet.java
src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFSheet.java
src/testcases/org/apache/poi/hssf/record/cf/TestCellRange.java

index a84f13573c86b687b11391c70b71cde10c2baa2a..8c907116aea714bd15e2f10353dd060d6f6bb394 100644 (file)
@@ -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());
     }
 }
index 55aa71cfcf1a3753cea30be6291e49086d86a26a..e5a193ee6c9f7f6182fe5577eabc545025e50a1d 100644 (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.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);
+    }
 }
index a9b0c145bfb652a4abb6c0210245e67b2f85f118..0574a000b224dff6b7dafdec28c9792463d04726 100644 (file)
@@ -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");
+    }
 }
index b66d85e2a82f24e51d42d592925c2f980b41c639..5d7dff3ee2ccfec7dc9a6e1817ec27957f989a44 100644 (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.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));
+    }
 }
index ced198a5a22dc2e58fec529e39ee23e33ffd5504..b13c6fd249f6878d7ba6a098a5920401dc19eed6 100644 (file)
@@ -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());
+    }
 }