From f60ca168298d7f7a89041ef36e6c44949a0ee677 Mon Sep 17 00:00:00 2001 From: Greg Woolsey Date: Tue, 11 Apr 2017 20:27:23 +0000 Subject: [PATCH] Issue #60971, handle formula chart titles implemented per issue, breaking out static text vs. formula based title getters and setters, with unit test updates and additions. git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1791025 13f79535-47bb-0310-9956-ffa450edef68 --- .../apache/poi/xssf/usermodel/XSSFChart.java | 93 +++++++++++++++++- .../poi/xssf/usermodel/TestXSSFChart.java | 6 +- .../xssf/usermodel/TestXSSFChartSheet.java | 2 +- .../usermodel/charts/TestXSSFChartTitle.java | 41 ++++++-- .../chartTitle_withTitleFormula.xlsx | Bin 0 -> 13515 bytes 5 files changed, 127 insertions(+), 15 deletions(-) create mode 100644 test-data/spreadsheet/chartTitle_withTitleFormula.xlsx diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFChart.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFChart.java index 1bee1ad995..43e865989a 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFChart.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFChart.java @@ -34,6 +34,7 @@ import org.apache.poi.ss.usermodel.charts.ChartAxis; import org.apache.poi.ss.usermodel.charts.ChartAxisFactory; import org.apache.poi.ss.usermodel.charts.ChartData; import org.apache.poi.util.Internal; +import org.apache.poi.util.Removal; import org.apache.poi.xssf.usermodel.charts.XSSFCategoryAxis; import org.apache.poi.xssf.usermodel.charts.XSSFChartAxis; import org.apache.poi.xssf.usermodel.charts.XSSFChartDataFactory; @@ -49,6 +50,7 @@ import org.openxmlformats.schemas.drawingml.x2006.chart.CTChartSpace; import org.openxmlformats.schemas.drawingml.x2006.chart.CTPageMargins; import org.openxmlformats.schemas.drawingml.x2006.chart.CTPlotArea; import org.openxmlformats.schemas.drawingml.x2006.chart.CTPrintSettings; +import org.openxmlformats.schemas.drawingml.x2006.chart.CTStrRef; import org.openxmlformats.schemas.drawingml.x2006.chart.CTTitle; import org.openxmlformats.schemas.drawingml.x2006.chart.CTTx; import org.openxmlformats.schemas.drawingml.x2006.chart.CTValAx; @@ -250,9 +252,27 @@ public final class XSSFChart extends POIXMLDocumentPart implements Chart, ChartA } /** - * Returns the title, or null if none is set + * Returns the title static text, or null if none is set. + * Note that a title formula may be set instead. + * @return static title text, if set + * @deprecated POI 3.16, use {@link #getTitleText()} instead. */ + @Deprecated + @Removal(version="4.0") public XSSFRichTextString getTitle() { + return getTitleText(); + } + + /** + * Returns the title static text, or null if none is set. + * Note that a title formula may be set instead. + * Empty text result is for backward compatibility, and could mean the title text is empty or there is a formula instead. + * Check for a formula first, falling back on text for cleaner logic. + * @return static title text if set, + * null if there is no title, + * empty string if the title text is empty or the title uses a formula instead + */ + public XSSFRichTextString getTitleText() { if(! chart.isSetTitle()) { return null; } @@ -278,9 +298,21 @@ public final class XSSFChart extends POIXMLDocumentPart implements Chart, ChartA } /** - * Sets the title text. + * Sets the title text as a static string. + * @param newTitle to use + * @deprecated POI 3.16, use {@link #setTitleText(String)} instead. */ + @Deprecated + @Removal(version="4.0") public void setTitle(String newTitle) { + + } + + /** + * Sets the title text as a static string. + * @param newTitle to use + */ + public void setTitleText(String newTitle) { CTTitle ctTitle; if (chart.isSetTitle()) { ctTitle = chart.getTitle(); @@ -325,6 +357,63 @@ public final class XSSFChart extends POIXMLDocumentPart implements Chart, ChartA run.setT(newTitle); } } + + /** + * Get the chart title formula expression if there is one + * @return formula expression or null + */ + public String getTitleFormula() { + if(! chart.isSetTitle()) { + return null; + } + + CTTitle title = chart.getTitle(); + + if (! title.isSetTx()) { + return null; + } + + CTTx tx = title.getTx(); + + if (! tx.isSetStrRef()) { + return null; + } + + return tx.getStrRef().getF(); + } + + /** + * Set the formula expression to use for the chart title + * @param formula + */ + public void setTitleFormula(String formula) { + CTTitle ctTitle; + if (chart.isSetTitle()) { + ctTitle = chart.getTitle(); + } else { + ctTitle = chart.addNewTitle(); + } + + CTTx tx; + if (ctTitle.isSetTx()) { + tx = ctTitle.getTx(); + } else { + tx = ctTitle.addNewTx(); + } + + if (tx.isSetRich()) { + tx.unsetRich(); + } + + CTStrRef strRef; + if (tx.isSetStrRef()) { + strRef = tx.getStrRef(); + } else { + strRef = tx.addNewStrRef(); + } + + strRef.setF(formula); + } public XSSFChartLegend getOrCreateLegend() { return new XSSFChartLegend(this); diff --git a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFChart.java b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFChart.java index 1c8732402e..f033e8ed4e 100644 --- a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFChart.java +++ b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFChart.java @@ -49,13 +49,13 @@ public final class TestXSSFChart extends TestCase { // Check the titles XSSFChart chart = s2.createDrawingPatriarch().getCharts().get(0); - assertEquals(null, chart.getTitle()); + assertEquals(null, chart.getTitleText()); chart = s2.createDrawingPatriarch().getCharts().get(1); - assertEquals("Pie Chart Title Thingy", chart.getTitle().getString()); + assertEquals("Pie Chart Title Thingy", chart.getTitleText().getString()); chart = s3.createDrawingPatriarch().getCharts().get(0); - assertEquals("Sheet 3 Chart with Title", chart.getTitle().getString()); + assertEquals("Sheet 3 Chart with Title", chart.getTitleText().getString()); assertNotNull(XSSFTestDataSamples.writeOutAndReadBack(wb)); } diff --git a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFChartSheet.java b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFChartSheet.java index 2eb4fc3e07..8cb76ac0fc 100644 --- a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFChartSheet.java +++ b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFChartSheet.java @@ -78,6 +78,6 @@ public final class TestXSSFChartSheet { assertEquals(1, cs.createDrawingPatriarch().getCharts().size()); XSSFChart chart = cs.createDrawingPatriarch().getCharts().get(0); - assertNull(chart.getTitle()); + assertNull(chart.getTitleText()); } } diff --git a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/charts/TestXSSFChartTitle.java b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/charts/TestXSSFChartTitle.java index 08d1f179df..7282aa0f41 100644 --- a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/charts/TestXSSFChartTitle.java +++ b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/charts/TestXSSFChartTitle.java @@ -120,12 +120,20 @@ public class TestXSSFChartTitle { Workbook wb = createWorkbookWithChart(); XSSFChart chart = getChartFromWorkbook(wb, "linechart"); assertNotNull(chart); - assertNull(chart.getTitle()); + assertNull(chart.getTitleText()); final String myTitle = "My chart title"; - chart.setTitle(myTitle); - XSSFRichTextString queryTitle = chart.getTitle(); + chart.setTitleText(myTitle); + XSSFRichTextString queryTitle = chart.getTitleText(); assertNotNull(queryTitle); assertEquals(myTitle, queryTitle.toString()); + + final String myTitleFormula = "1 & \" and \" & 2"; + chart.setTitleFormula(myTitleFormula); + // setting formula should unset text, but since there is a formula, returns an empty string + assertEquals("", chart.getTitleText().toString()); + String titleFormula = chart.getTitleFormula(); + assertNotNull(titleFormula); + assertEquals(myTitleFormula, titleFormula); wb.close(); } @@ -134,12 +142,12 @@ public class TestXSSFChartTitle { Workbook wb = XSSFTestDataSamples.openSampleWorkbook("chartTitle_withTitle.xlsx"); XSSFChart chart = getChartFromWorkbook(wb, "Sheet1"); assertNotNull(chart); - XSSFRichTextString originalTitle = chart.getTitle(); + XSSFRichTextString originalTitle = chart.getTitleText(); assertNotNull(originalTitle); final String myTitle = "My chart title"; assertFalse(myTitle.equals(originalTitle.toString())); - chart.setTitle(myTitle); - XSSFRichTextString queryTitle = chart.getTitle(); + chart.setTitleText(myTitle); + XSSFRichTextString queryTitle = chart.getTitleText(); assertNotNull(queryTitle); assertEquals(myTitle, queryTitle.toString()); wb.close(); @@ -150,12 +158,27 @@ public class TestXSSFChartTitle { Workbook wb = XSSFTestDataSamples.openSampleWorkbook("chartTitle_noTitle.xlsx"); XSSFChart chart = getChartFromWorkbook(wb, "Sheet1"); assertNotNull(chart); - assertNull(chart.getTitle()); + assertNull(chart.getTitleText()); final String myTitle = "My chart title"; - chart.setTitle(myTitle); - XSSFRichTextString queryTitle = chart.getTitle(); + chart.setTitleText(myTitle); + XSSFRichTextString queryTitle = chart.getTitleText(); assertNotNull(queryTitle); assertEquals(myTitle, queryTitle.toString()); wb.close(); } + + @Test + public void testExistingChartWithFormulaTitle() throws IOException { + Workbook wb = XSSFTestDataSamples.openSampleWorkbook("chartTitle_withTitleFormula.xlsx"); + XSSFChart chart = getChartFromWorkbook(wb, "Sheet1"); + assertNotNull(chart); + XSSFRichTextString originalTitle = chart.getTitleText(); + assertNotNull(originalTitle); + assertEquals("", originalTitle.toString()); + String formula = chart.getTitleFormula(); + assertNotNull(formula); + assertEquals("Sheet1!$E$1", formula); + wb.close(); + } + } diff --git a/test-data/spreadsheet/chartTitle_withTitleFormula.xlsx b/test-data/spreadsheet/chartTitle_withTitleFormula.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..0f609ca3b6868b2def4108fdd474ba17ba88355f GIT binary patch literal 13515 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