git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1077878 13f79535-47bb-0310-9956-ffa450edef68tags/REL_3_8_BETA2
@@ -33,7 +33,10 @@ | |||
</developers> | |||
<changes> | |||
<release version="3.8-beta1" date="2010-??-??"> | |||
<release version="3.8-beta2" date="2011-??-??"> | |||
<action dev="poi-developers" type="fix">47100 - Change related formulas and named ranges when XSSFWorkbook.setSheetName is called</action> | |||
</release> | |||
<release version="3.8-beta1" date="2011-03-07"> | |||
<action dev="poi-developers" type="add">50610 - Ant tasks for running POI against a workbook</action> | |||
<action dev="poi-developers" type="add">32903 - Correct XBAT chaining explanation in /poifs/fileformat.html</action> | |||
<action dev="poi-developers" type="add">50829 - Support for getting the tables associated with a XSSFSheet</action> |
@@ -46,7 +46,6 @@ import org.apache.poi.openxml4j.opc.PackageRelationship; | |||
import org.apache.poi.openxml4j.opc.PackageRelationshipTypes; | |||
import org.apache.poi.openxml4j.opc.PackagingURIHelper; | |||
import org.apache.poi.openxml4j.opc.TargetMode; | |||
import org.apache.poi.ss.formula.udf.AggregatingUDFFinder; | |||
import org.apache.poi.ss.formula.udf.UDFFinder; | |||
import org.apache.poi.ss.usermodel.Row; | |||
import org.apache.poi.ss.usermodel.Sheet; | |||
@@ -56,6 +55,7 @@ import org.apache.poi.ss.util.CellReference; | |||
import org.apache.poi.ss.util.WorkbookUtil; | |||
import org.apache.poi.util.*; | |||
import org.apache.poi.xssf.model.*; | |||
import org.apache.poi.xssf.usermodel.helpers.XSSFFormulaUtils; | |||
import org.apache.xmlbeans.XmlException; | |||
import org.apache.xmlbeans.XmlObject; | |||
import org.apache.xmlbeans.XmlOptions; | |||
@@ -1109,14 +1109,18 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook, Iterable<X | |||
* Will throw IllegalArgumentException if the name is greater than 31 chars | |||
* or contains /\?*[] | |||
* | |||
* @param sheet number (0 based) | |||
* @param sheetIndex number (0 based) | |||
*/ | |||
public void setSheetName(int sheet, String name) { | |||
validateSheetIndex(sheet); | |||
public void setSheetName(int sheetIndex, String name) { | |||
validateSheetIndex(sheetIndex); | |||
WorkbookUtil.validateSheetName(name); | |||
if (containsSheet(name, sheet )) | |||
if (containsSheet(name, sheetIndex )) | |||
throw new IllegalArgumentException( "The workbook already contains a sheet of this name" ); | |||
workbook.getSheets().getSheetArray(sheet).setName(name); | |||
XSSFFormulaUtils utils = new XSSFFormulaUtils(this); | |||
utils.updateSheetName(sheetIndex, name); | |||
workbook.getSheets().getSheetArray(sheetIndex).setName(name); | |||
} | |||
/** |
@@ -0,0 +1,147 @@ | |||
/* | |||
* ==================================================================== | |||
* 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.helpers; | |||
import org.apache.poi.ss.formula.EvaluationWorkbook.ExternalSheet; | |||
import org.apache.poi.ss.formula.FormulaParser; | |||
import org.apache.poi.ss.formula.FormulaRenderer; | |||
import org.apache.poi.ss.formula.FormulaRenderingWorkbook; | |||
import org.apache.poi.ss.formula.FormulaType; | |||
import org.apache.poi.ss.formula.ptg.NamePtg; | |||
import org.apache.poi.ss.formula.ptg.NameXPtg; | |||
import org.apache.poi.ss.formula.ptg.Ptg; | |||
import org.apache.poi.ss.usermodel.Cell; | |||
import org.apache.poi.ss.usermodel.Row; | |||
import org.apache.poi.ss.usermodel.Sheet; | |||
import org.apache.poi.xssf.usermodel.XSSFCell; | |||
import org.apache.poi.xssf.usermodel.XSSFEvaluationWorkbook; | |||
import org.apache.poi.xssf.usermodel.XSSFName; | |||
import org.apache.poi.xssf.usermodel.XSSFWorkbook; | |||
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTCellFormula; | |||
/** | |||
* Utility to update formulas and named ranges when a sheet name was changed | |||
* | |||
* @author Yegor Kozlov | |||
*/ | |||
public final class XSSFFormulaUtils { | |||
private final XSSFWorkbook _wb; | |||
private final XSSFEvaluationWorkbook _fpwb; | |||
public XSSFFormulaUtils(XSSFWorkbook wb) { | |||
_wb = wb; | |||
_fpwb = XSSFEvaluationWorkbook.create(_wb); | |||
} | |||
/** | |||
* Update sheet name in all formulas and named ranges. | |||
* Called from {@link XSSFWorkbook#setSheetName(int, String)} | |||
* <p/> | |||
* <p> | |||
* The idea is to parse every formula and render it back to string | |||
* with the updated sheet name. The FormulaParsingWorkbook passed to the formula parser | |||
* is constructed from the old workbook (sheet name is not yet updated) and | |||
* the FormulaRenderingWorkbook passed to FormulaRenderer#toFormulaString is a custom implementation that | |||
* returns the new sheet name. | |||
* </p> | |||
* | |||
* @param sheetIndex the 0-based index of the sheet being changed | |||
* @param name the new sheet name | |||
*/ | |||
public void updateSheetName(final int sheetIndex, final String name) { | |||
/** | |||
* An instance of FormulaRenderingWorkbook that returns | |||
*/ | |||
FormulaRenderingWorkbook frwb = new FormulaRenderingWorkbook() { | |||
public ExternalSheet getExternalSheet(int externSheetIndex) { | |||
return _fpwb.getExternalSheet(externSheetIndex); | |||
} | |||
public String getSheetNameByExternSheet(int externSheetIndex) { | |||
if (externSheetIndex == sheetIndex) return name; | |||
else return _fpwb.getSheetNameByExternSheet(externSheetIndex); | |||
} | |||
public String resolveNameXText(NameXPtg nameXPtg) { | |||
return _fpwb.resolveNameXText(nameXPtg); | |||
} | |||
public String getNameText(NamePtg namePtg) { | |||
return _fpwb.getNameText(namePtg); | |||
} | |||
}; | |||
// update named ranges | |||
for (int i = 0; i < _wb.getNumberOfNames(); i++) { | |||
XSSFName nm = _wb.getNameAt(i); | |||
if (nm.getSheetIndex() == -1 || nm.getSheetIndex() == sheetIndex) { | |||
updateName(nm, frwb); | |||
} | |||
} | |||
// update formulas | |||
for (Sheet sh : _wb) { | |||
for (Row row : sh) { | |||
for (Cell cell : row) { | |||
if (cell.getCellType() == Cell.CELL_TYPE_FORMULA) { | |||
updateFormula((XSSFCell) cell, frwb); | |||
} | |||
} | |||
} | |||
} | |||
} | |||
/** | |||
* Parse cell formula and re-assemble it back using the specified FormulaRenderingWorkbook. | |||
* | |||
* @param cell the cell to update | |||
* @param frwb the formula rendering workbbok that returns new sheet name | |||
*/ | |||
private void updateFormula(XSSFCell cell, FormulaRenderingWorkbook frwb) { | |||
CTCellFormula f = cell.getCTCell().getF(); | |||
if (f != null) { | |||
String formula = f.getStringValue(); | |||
if (formula != null) { | |||
int sheetIndex = _wb.getSheetIndex(cell.getSheet()); | |||
Ptg[] ptgs = FormulaParser.parse(formula, _fpwb, FormulaType.CELL, sheetIndex); | |||
String updatedFormula = FormulaRenderer.toFormulaString(frwb, ptgs); | |||
if (!formula.equals(updatedFormula)) f.setStringValue(updatedFormula); | |||
} | |||
} | |||
} | |||
/** | |||
* Parse formula in the named range and re-assemble it back using the specified FormulaRenderingWorkbook. | |||
* | |||
* @param name the name to update | |||
* @param frwb the formula rendering workbbok that returns new sheet name | |||
*/ | |||
private void updateName(XSSFName name, FormulaRenderingWorkbook frwb) { | |||
String formula = name.getRefersToFormula(); | |||
if (formula != null) { | |||
int sheetIndex = name.getSheetIndex(); | |||
Ptg[] ptgs = FormulaParser.parse(formula, _fpwb, FormulaType.NAMEDRANGE, sheetIndex); | |||
String updatedFormula = FormulaRenderer.toFormulaString(frwb, ptgs); | |||
if (!formula.equals(updatedFormula)) name.setRefersToFormula(updatedFormula); | |||
} | |||
} | |||
} |
@@ -23,6 +23,9 @@ import junit.framework.TestCase; | |||
import org.apache.poi.ss.ITestDataProvider; | |||
import org.apache.poi.ss.util.CellRangeAddress; | |||
import java.io.ByteArrayInputStream; | |||
import java.io.ByteArrayOutputStream; | |||
/** | |||
* @author Yegor Kozlov | |||
*/ | |||
@@ -417,4 +420,130 @@ public abstract class BaseTestWorkbook extends TestCase { | |||
c3 = r.getCell(3); | |||
assertEquals(c3.getCellFormula(), formulaString); | |||
} | |||
private Workbook newSetSheetNameTestingWorkbook() throws Exception { | |||
Workbook wb = _testDataProvider.createWorkbook(); | |||
Sheet sh1 = wb.createSheet("Worksheet"); | |||
Sheet sh2 = wb.createSheet("Testing 47100"); | |||
Sheet sh3 = wb.createSheet("To be renamed"); | |||
Name name1 = wb.createName(); | |||
name1.setNameName("sale_1"); | |||
name1.setRefersToFormula("Worksheet!$A$1"); | |||
Name name2 = wb.createName(); | |||
name2.setNameName("sale_2"); | |||
name2.setRefersToFormula("'Testing 47100'!$A$1"); | |||
Name name3 = wb.createName(); | |||
name3.setNameName("sale_3"); | |||
name3.setRefersToFormula("'Testing 47100'!$B$1"); | |||
Name name4 = wb.createName(); | |||
name4.setNameName("sale_4"); | |||
name4.setRefersToFormula("'To be renamed'!$A$3"); | |||
sh1.createRow(0).createCell(0).setCellFormula("SUM('Testing 47100'!A1:C1)"); | |||
sh1.createRow(1).createCell(0).setCellFormula("SUM('Testing 47100'!A1:C1,'To be renamed'!A1:A5)"); | |||
sh1.createRow(2).createCell(0).setCellFormula("sale_2+sale_3+'Testing 47100'!C1"); | |||
sh2.createRow(0).createCell(0).setCellValue(1); | |||
sh2.getRow(0).createCell(1).setCellValue(2); | |||
sh2.getRow(0).createCell(2).setCellValue(3); | |||
sh3.createRow(0).createCell(0).setCellValue(1); | |||
sh3.createRow(1).createCell(0).setCellValue(2); | |||
sh3.createRow(2).createCell(0).setCellValue(3); | |||
sh3.createRow(3).createCell(0).setCellValue(4); | |||
sh3.createRow(4).createCell(0).setCellValue(5); | |||
sh3.createRow(5).createCell(0).setCellFormula("sale_3"); | |||
sh3.createRow(6).createCell(0).setCellFormula("'Testing 47100'!C1"); | |||
return wb; | |||
} | |||
/** | |||
* Ensure that Workbook#setSheetName updates all dependent formulas and named ranges | |||
* | |||
* @see <a href="https://issues.apache.org/bugzilla/show_bug.cgi?id=47100">Bugzilla 47100</a> | |||
*/ | |||
public final void testSetSheetName() throws Exception { | |||
Workbook wb = newSetSheetNameTestingWorkbook(); | |||
Sheet sh1 = wb.getSheetAt(0); | |||
Name sale_2 = wb.getNameAt(1); | |||
Name sale_3 = wb.getNameAt(2); | |||
Name sale_4 = wb.getNameAt(3); | |||
assertEquals("sale_2", sale_2.getNameName()); | |||
assertEquals("'Testing 47100'!$A$1", sale_2.getRefersToFormula()); | |||
assertEquals("sale_3", sale_3.getNameName()); | |||
assertEquals("'Testing 47100'!$B$1", sale_3.getRefersToFormula()); | |||
assertEquals("sale_4", sale_4.getNameName()); | |||
assertEquals("'To be renamed'!$A$3", sale_4.getRefersToFormula()); | |||
FormulaEvaluator evaluator = wb.getCreationHelper().createFormulaEvaluator(); | |||
Cell cell0 = sh1.getRow(0).getCell(0); | |||
Cell cell1 = sh1.getRow(1).getCell(0); | |||
Cell cell2 = sh1.getRow(2).getCell(0); | |||
assertEquals("SUM('Testing 47100'!A1:C1)", cell0.getCellFormula()); | |||
assertEquals("SUM('Testing 47100'!A1:C1,'To be renamed'!A1:A5)", cell1.getCellFormula()); | |||
assertEquals("sale_2+sale_3+'Testing 47100'!C1", cell2.getCellFormula()); | |||
assertEquals(6.0, evaluator.evaluate(cell0).getNumberValue()); | |||
assertEquals(21.0, evaluator.evaluate(cell1).getNumberValue()); | |||
assertEquals(6.0, evaluator.evaluate(cell2).getNumberValue()); | |||
wb.setSheetName(1, "47100 - First"); | |||
wb.setSheetName(2, "47100 - Second"); | |||
assertEquals("sale_2", sale_2.getNameName()); | |||
assertEquals("'47100 - First'!$A$1", sale_2.getRefersToFormula()); | |||
assertEquals("sale_3", sale_3.getNameName()); | |||
assertEquals("'47100 - First'!$B$1", sale_3.getRefersToFormula()); | |||
assertEquals("sale_4", sale_4.getNameName()); | |||
assertEquals("'47100 - Second'!$A$3", sale_4.getRefersToFormula()); | |||
assertEquals("SUM('47100 - First'!A1:C1)", cell0.getCellFormula()); | |||
assertEquals("SUM('47100 - First'!A1:C1,'47100 - Second'!A1:A5)", cell1.getCellFormula()); | |||
assertEquals("sale_2+sale_3+'47100 - First'!C1", cell2.getCellFormula()); | |||
evaluator.clearAllCachedResultValues(); | |||
assertEquals(6.0, evaluator.evaluate(cell0).getNumberValue()); | |||
assertEquals(21.0, evaluator.evaluate(cell1).getNumberValue()); | |||
assertEquals(6.0, evaluator.evaluate(cell2).getNumberValue()); | |||
wb = _testDataProvider.writeOutAndReadBack(wb); | |||
sh1 = wb.getSheetAt(0); | |||
sale_2 = wb.getNameAt(1); | |||
sale_3 = wb.getNameAt(2); | |||
sale_4 = wb.getNameAt(3); | |||
cell0 = sh1.getRow(0).getCell(0); | |||
cell1 = sh1.getRow(1).getCell(0); | |||
cell2 = sh1.getRow(2).getCell(0); | |||
assertEquals("sale_2", sale_2.getNameName()); | |||
assertEquals("'47100 - First'!$A$1", sale_2.getRefersToFormula()); | |||
assertEquals("sale_3", sale_3.getNameName()); | |||
assertEquals("'47100 - First'!$B$1", sale_3.getRefersToFormula()); | |||
assertEquals("sale_4", sale_4.getNameName()); | |||
assertEquals("'47100 - Second'!$A$3", sale_4.getRefersToFormula()); | |||
assertEquals("SUM('47100 - First'!A1:C1)", cell0.getCellFormula()); | |||
assertEquals("SUM('47100 - First'!A1:C1,'47100 - Second'!A1:A5)", cell1.getCellFormula()); | |||
assertEquals("sale_2+sale_3+'47100 - First'!C1", cell2.getCellFormula()); | |||
evaluator = wb.getCreationHelper().createFormulaEvaluator(); | |||
assertEquals(6.0, evaluator.evaluate(cell0).getNumberValue()); | |||
assertEquals(21.0, evaluator.evaluate(cell1).getNumberValue()); | |||
assertEquals(6.0, evaluator.evaluate(cell2).getNumberValue()); | |||
} | |||
} |