Change related formulas and named ranges when XSSFWorkbook.setSheetName is called, see Bugzilla 47100
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1077878 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
f050cf6eeb
commit
817a44dcd4
@ -33,7 +33,10 @@
|
|||||||
</developers>
|
</developers>
|
||||||
|
|
||||||
<changes>
|
<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">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">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>
|
<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.PackageRelationshipTypes;
|
||||||
import org.apache.poi.openxml4j.opc.PackagingURIHelper;
|
import org.apache.poi.openxml4j.opc.PackagingURIHelper;
|
||||||
import org.apache.poi.openxml4j.opc.TargetMode;
|
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.formula.udf.UDFFinder;
|
||||||
import org.apache.poi.ss.usermodel.Row;
|
import org.apache.poi.ss.usermodel.Row;
|
||||||
import org.apache.poi.ss.usermodel.Sheet;
|
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.ss.util.WorkbookUtil;
|
||||||
import org.apache.poi.util.*;
|
import org.apache.poi.util.*;
|
||||||
import org.apache.poi.xssf.model.*;
|
import org.apache.poi.xssf.model.*;
|
||||||
|
import org.apache.poi.xssf.usermodel.helpers.XSSFFormulaUtils;
|
||||||
import org.apache.xmlbeans.XmlException;
|
import org.apache.xmlbeans.XmlException;
|
||||||
import org.apache.xmlbeans.XmlObject;
|
import org.apache.xmlbeans.XmlObject;
|
||||||
import org.apache.xmlbeans.XmlOptions;
|
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
|
* Will throw IllegalArgumentException if the name is greater than 31 chars
|
||||||
* or contains /\?*[]
|
* or contains /\?*[]
|
||||||
*
|
*
|
||||||
* @param sheet number (0 based)
|
* @param sheetIndex number (0 based)
|
||||||
*/
|
*/
|
||||||
public void setSheetName(int sheet, String name) {
|
public void setSheetName(int sheetIndex, String name) {
|
||||||
validateSheetIndex(sheet);
|
validateSheetIndex(sheetIndex);
|
||||||
WorkbookUtil.validateSheetName(name);
|
WorkbookUtil.validateSheetName(name);
|
||||||
if (containsSheet(name, sheet ))
|
if (containsSheet(name, sheetIndex ))
|
||||||
throw new IllegalArgumentException( "The workbook already contains a sheet of this name" );
|
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.ITestDataProvider;
|
||||||
import org.apache.poi.ss.util.CellRangeAddress;
|
import org.apache.poi.ss.util.CellRangeAddress;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Yegor Kozlov
|
* @author Yegor Kozlov
|
||||||
*/
|
*/
|
||||||
@ -417,4 +420,130 @@ public abstract class BaseTestWorkbook extends TestCase {
|
|||||||
c3 = r.getCell(3);
|
c3 = r.getCell(3);
|
||||||
assertEquals(c3.getCellFormula(), formulaString);
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user