From 5199f75fe3104d2d1cf960ec2cee7b2abc04c62b Mon Sep 17 00:00:00 2001 From: =?utf8?q?Alain=20B=C3=A9arez?= Date: Mon, 1 Oct 2018 19:27:32 +0000 Subject: [PATCH] extract valuable code from 54470 submitted patch git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1842548 13f79535-47bb-0310-9956-ffa450edef68 --- .../chart/XDDFCategoryDataSource.java | 19 ++++ .../xddf/usermodel/chart/XDDFDataSource.java | 2 + .../chart/XDDFDataSourcesFactory.java | 106 ++++++++++++------ .../usermodel/helpers/XSSFFormulaUtils.java | 62 +++++++++- .../poi/xssf/usermodel/TestXSSFWorkbook.java | 42 +++++-- test-data/spreadsheet/60509.xlsx | Bin 0 -> 12057 bytes 6 files changed, 182 insertions(+), 49 deletions(-) create mode 100755 test-data/spreadsheet/60509.xlsx diff --git a/src/ooxml/java/org/apache/poi/xddf/usermodel/chart/XDDFCategoryDataSource.java b/src/ooxml/java/org/apache/poi/xddf/usermodel/chart/XDDFCategoryDataSource.java index a65db84097..a21852b621 100644 --- a/src/ooxml/java/org/apache/poi/xddf/usermodel/chart/XDDFCategoryDataSource.java +++ b/src/ooxml/java/org/apache/poi/xddf/usermodel/chart/XDDFCategoryDataSource.java @@ -23,4 +23,23 @@ import org.apache.poi.util.Beta; @Beta public interface XDDFCategoryDataSource extends XDDFDataSource { + @Override + default int getColIndex() { + return 0; + } + + @Override + default boolean isNumeric() { + return false; + } + + @Override + default boolean isReference() { + return true; + } + + @Override + default String getDataRangeReference() { + return getFormula(); + } } diff --git a/src/ooxml/java/org/apache/poi/xddf/usermodel/chart/XDDFDataSource.java b/src/ooxml/java/org/apache/poi/xddf/usermodel/chart/XDDFDataSource.java index 1ab617571d..d64f62bfb5 100644 --- a/src/ooxml/java/org/apache/poi/xddf/usermodel/chart/XDDFDataSource.java +++ b/src/ooxml/java/org/apache/poi/xddf/usermodel/chart/XDDFDataSource.java @@ -34,4 +34,6 @@ public interface XDDFDataSource { int getColIndex(); String getDataRangeReference(); + + String getFormula(); } diff --git a/src/ooxml/java/org/apache/poi/xddf/usermodel/chart/XDDFDataSourcesFactory.java b/src/ooxml/java/org/apache/poi/xddf/usermodel/chart/XDDFDataSourcesFactory.java index be8a34ad23..a09c1f920f 100644 --- a/src/ooxml/java/org/apache/poi/xddf/usermodel/chart/XDDFDataSourcesFactory.java +++ b/src/ooxml/java/org/apache/poi/xddf/usermodel/chart/XDDFDataSourcesFactory.java @@ -41,39 +41,50 @@ public class XDDFDataSourcesFactory { } public static XDDFCategoryDataSource fromDataSource(final CTAxDataSource categoryDS) { - return new XDDFCategoryDataSource() { - private CTStrData category = (CTStrData) categoryDS.getStrRef().getStrCache().copy(); - - @Override - public boolean isNumeric() { - return false; - } - - @Override - public boolean isReference() { - return true; - } - - @Override - public int getPointCount() { - return (int) category.getPtCount().getVal(); - } - - @Override - public String getPointAt(int index) { - return category.getPtArray(index).getV(); - } - - @Override - public String getDataRangeReference() { - return categoryDS.getStrRef().getF(); - } - - @Override - public int getColIndex() { - return 0; - } - }; + if (categoryDS.getStrRef() == null) { + return new XDDFCategoryDataSource() { + private CTNumData category = (CTNumData) categoryDS.getNumRef().getNumCache().copy(); + + @Override + public boolean isNumeric() { + return true; + } + + @Override + public String getFormula() { + return categoryDS.getNumRef().getF(); + } + + @Override + public int getPointCount() { + return (int) category.getPtCount().getVal(); + } + + @Override + public String getPointAt(int index) { + return category.getPtArray(index).getV(); + } + }; + } else { + return new XDDFCategoryDataSource() { + private CTStrData category = (CTStrData) categoryDS.getStrRef().getStrCache().copy(); + + @Override + public String getFormula() { + return categoryDS.getStrRef().getF(); + } + + @Override + public int getPointCount() { + return (int) category.getPtCount().getVal(); + } + + @Override + public String getPointAt(int index) { + return category.getPtArray(index).getV(); + } + }; + } } public static XDDFNumericalDataSource fromDataSource(final CTNumDataSource valuesDS) { @@ -81,6 +92,11 @@ public class XDDFDataSourcesFactory { private CTNumData values = (CTNumData) valuesDS.getNumRef().getNumCache().copy(); private String formatCode = values.isSetFormatCode() ? values.getFormatCode() : null; + @Override + public String getFormula() { + return valuesDS.getNumRef().getF(); + } + @Override public String getFormatCode() { return formatCode; @@ -124,7 +140,7 @@ public class XDDFDataSourcesFactory { } public static XDDFNumericalDataSource fromArray(T[] elements, String dataRange) { - return new NumericalArrayDataSource(elements, dataRange); + return new NumericalArrayDataSource<>(elements, dataRange); } public static XDDFCategoryDataSource fromArray(String[] elements, String dataRange) { @@ -132,7 +148,7 @@ public class XDDFDataSourcesFactory { } public static XDDFNumericalDataSource fromArray(T[] elements, String dataRange, int col) { - return new NumericalArrayDataSource(elements, dataRange, col); + return new NumericalArrayDataSource<>(elements, dataRange, col); } public static XDDFCategoryDataSource fromArray(String[] elements, String dataRange, int col) { @@ -212,6 +228,11 @@ public class XDDFDataSourcesFactory { super(elements, dataRange, col); } + @Override + public String getFormula() { + return getDataRangeReference(); + } + @Override public String getFormatCode() { return formatCode; @@ -232,6 +253,11 @@ public class XDDFDataSourcesFactory { public StringArrayDataSource(String[] elements, String dataRange, int col) { super(elements, dataRange, col); } + + @Override + public String getFormula() { + return getDataRangeReference(); + } } private abstract static class AbstractCellRangeDataSource implements XDDFDataSource { @@ -290,6 +316,11 @@ public class XDDFDataSourcesFactory { super(sheet, cellRangeAddress); } + @Override + public String getFormula() { + return getDataRangeReference(); + } + private String formatCode; @Override @@ -324,6 +355,11 @@ public class XDDFDataSourcesFactory { super(sheet, cellRangeAddress); } + @Override + public String getFormula() { + return getDataRangeReference(); + } + @Override public String getPointAt(int index) { CellValue cellValue = getCellValueAt(index); diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFFormulaUtils.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFFormulaUtils.java index df3cfb3830..926e2a2d87 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFFormulaUtils.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFFormulaUtils.java @@ -19,6 +19,10 @@ package org.apache.poi.xssf.usermodel.helpers; +import java.util.Iterator; +import java.util.List; + +import org.apache.poi.ooxml.POIXMLDocumentPart; import org.apache.poi.ss.formula.FormulaParser; import org.apache.poi.ss.formula.FormulaRenderer; import org.apache.poi.ss.formula.FormulaType; @@ -30,10 +34,14 @@ import org.apache.poi.ss.usermodel.CellType; 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.XSSFChart; +import org.apache.poi.xssf.usermodel.XSSFDrawing; 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; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; /** * Utility to update formulas and named ranges when a sheet name was changed @@ -50,7 +58,7 @@ public final class XSSFFormulaUtils { } /** - * Update sheet name in all formulas and named ranges. + * Update sheet name in all charts, formulas and named ranges. * Called from {@link XSSFWorkbook#setSheetName(int, String)} *

*

@@ -81,6 +89,20 @@ public final class XSSFFormulaUtils { } } } + + // update charts + List rels = _wb.getSheetAt(sheetIndex).getRelations(); + for (POIXMLDocumentPart r : rels) { + if (r instanceof XSSFDrawing) { + XSSFDrawing dg = (XSSFDrawing) r; + Iterator it = dg.getCharts().iterator(); + while (it.hasNext()) { + XSSFChart chart = it.next(); + Node dom = chart.getCTChartSpace().getDomNode(); + updateDomSheetReference(dom, oldName, newName); + } + } + } } /** @@ -99,7 +121,9 @@ public final class XSSFFormulaUtils { updatePtg(ptg, oldName, newName); } String updatedFormula = FormulaRenderer.toFormulaString(_fpwb, ptgs); - if (!formula.equals(updatedFormula)) f.setStringValue(updatedFormula); + if (!formula.equals(updatedFormula)) { + f.setStringValue(updatedFormula); + } } } } @@ -119,10 +143,12 @@ public final class XSSFFormulaUtils { updatePtg(ptg, oldName, newName); } String updatedFormula = FormulaRenderer.toFormulaString(_fpwb, ptgs); - if (!formula.equals(updatedFormula)) name.setRefersToFormula(updatedFormula); + if (!formula.equals(updatedFormula)) { + name.setRefersToFormula(updatedFormula); + } } } - + private void updatePtg(Ptg ptg, String oldName, String newName) { if (ptg instanceof Pxg) { Pxg pxg = (Pxg)ptg; @@ -141,4 +167,32 @@ public final class XSSFFormulaUtils { } } } + + + /** + * Parse the DOM tree recursively searching for text containing reference to the old sheet name and replacing it. + * + * @param dom the XML node in which to perform the replacement. + * + * Code extracted from: Bug 54470 + */ + private void updateDomSheetReference(Node dom, final String oldName, final String newName) { + String value = dom.getNodeValue(); + if (value != null) { + // make sure the value contains the old sheet and not a similar sheet + // (ex: Valid: 'Sheet1'! or Sheet1! ; NotValid: 'Sheet1Test'! or Sheet1Test!) + if (value.contains(oldName+"!") || value.contains(oldName+"'!")) { + XSSFName temporary = _wb.createName(); + temporary.setRefersToFormula(value); + updateName(temporary, oldName, newName); + dom.setNodeValue(temporary.getRefersToFormula()); + _wb.removeName(temporary); + } + } + NodeList nl = dom.getChildNodes(); + for (int i = 0; i < nl.getLength(); i++) { + updateDomSheetReference(nl.item(i), oldName, newName); + } + } + } diff --git a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFWorkbook.java b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFWorkbook.java index 0c6a0adeff..20dad4b84a 100644 --- a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFWorkbook.java +++ b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFWorkbook.java @@ -39,8 +39,8 @@ import java.util.List; import java.util.zip.CRC32; import org.apache.poi.POIDataSamples; -import org.apache.poi.ooxml.POIXMLProperties; import org.apache.poi.hssf.HSSFTestDataSamples; +import org.apache.poi.ooxml.POIXMLProperties; import org.apache.poi.openxml4j.exceptions.InvalidFormatException; import org.apache.poi.openxml4j.opc.ContentTypes; import org.apache.poi.openxml4j.opc.OPCPackage; @@ -67,6 +67,8 @@ import org.apache.poi.ss.util.CellReference; import org.apache.poi.util.IOUtils; import org.apache.poi.util.LocaleUtil; import org.apache.poi.util.TempFile; +import org.apache.poi.xddf.usermodel.chart.XDDFBarChartData; +import org.apache.poi.xddf.usermodel.chart.XDDFChartData; import org.apache.poi.xssf.XSSFITestDataProvider; import org.apache.poi.xssf.XSSFTestDataSamples; import org.apache.poi.xssf.model.StylesTable; @@ -553,7 +555,9 @@ public final class TestXSSFWorkbook extends BaseTestXWorkbook { Sheet sheet = wb.getSheetAt(0); sheet.shiftRows(2, sheet.getLastRowNum(), 1, true, false); Row newRow = sheet.getRow(2); - if (newRow == null) newRow = sheet.createRow(2); + if (newRow == null) { + newRow = sheet.createRow(2); + } newRow.createCell(0).setCellValue(" Another Header"); wb.cloneSheet(0); @@ -667,8 +671,8 @@ public final class TestXSSFWorkbook extends BaseTestXWorkbook { XSSFWorkbook wb3 = XSSFTestDataSamples.writeOutAndReadBack(wb2); assertNotNull(wb3); sheet = wb3.getSheetAt(0); - row = sheet.getRow(2); - + row = sheet.getRow(2); + assertEquals("test1", row.getCell(3).getStringCellValue()); assertEquals("test2", row.getCell(4).getStringCellValue()); wb3.close(); @@ -700,6 +704,24 @@ public final class TestXSSFWorkbook extends BaseTestXWorkbook { } } + @Test + public void bug60509() throws Exception { + XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("60509.xlsx"); + assertSheetOrder(wb, "Sheet1", "Sheet2", "Sheet3"); + int sheetIndex = wb.getSheetIndex("Sheet1"); + wb.setSheetName(sheetIndex, "Sheet1-Renamed"); + Workbook read = XSSFTestDataSamples.writeOutAndReadBack(wb); + assertNotNull(read); + assertSheetOrder(read, "Sheet1-Renamed", "Sheet2", "Sheet3"); + XSSFSheet sheet = (XSSFSheet) read.getSheet("Sheet1-Renamed"); + XDDFChartData.Series series = sheet.getDrawingPatriarch().getCharts().get(0).getChartSeries().get(0).getSeries().get(0); + assertTrue("should be a bar chart data series", series instanceof XDDFBarChartData.Series); + String formula = ((XDDFBarChartData.Series) series).getCategoryData().getFormula(); + assertTrue("should contain new sheet name", formula.startsWith("'Sheet1-Renamed'!")); + read.close(); + wb.close(); + } + private static final int INDEX_NOT_FOUND = -1; private static boolean isEmpty(CharSequence cs) { @@ -1009,22 +1031,22 @@ public final class TestXSSFWorkbook extends BaseTestXWorkbook { final String filename = "SampleSS.xlsx"; final File file = POIDataSamples.getSpreadSheetInstance().getFile(filename); Workbook wb; - + // Some tests commented out because close() modifies the file // See bug 58779 - + // String //wb = new XSSFWorkbook(file.getPath()); //assertCloseDoesNotModifyFile(filename, wb); - + // File //wb = new XSSFWorkbook(file); //assertCloseDoesNotModifyFile(filename, wb); - + // InputStream wb = new XSSFWorkbook(new FileInputStream(file)); assertCloseDoesNotModifyFile(filename, wb); - + // OPCPackage //wb = new XSSFWorkbook(OPCPackage.open(file)); //assertCloseDoesNotModifyFile(filename, wb); @@ -1070,7 +1092,7 @@ public final class TestXSSFWorkbook extends BaseTestXWorkbook { XSSFTable table2 = wb.getSheet("Foglio2").createTable(); table2.setName("Table2"); assertSame("Did not find Table2", table2, wb.getTable("Table2")); - + // If table name is modified after getTable is called, the table can only be found by its new name // This test makes sure that if any caching is done that getTable never uses a stale cache table1.setName("Table1"); diff --git a/test-data/spreadsheet/60509.xlsx b/test-data/spreadsheet/60509.xlsx new file mode 100755 index 0000000000000000000000000000000000000000..835d57c095ca3a6b8a7c38ebd3de5e46efb0c017 GIT binary patch literal 12057 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