You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

XSSFFormulaUtils.java 7.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. /*
  2. * ====================================================================
  3. * Licensed to the Apache Software Foundation (ASF) under one or more
  4. * contributor license agreements. See the NOTICE file distributed with
  5. * this work for additional information regarding copyright ownership.
  6. * The ASF licenses this file to You under the Apache License, Version 2.0
  7. * (the "License"); you may not use this file except in compliance with
  8. * the License. You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing, software
  13. * distributed under the License is distributed on an "AS IS" BASIS,
  14. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. * See the License for the specific language governing permissions and
  16. * limitations under the License.
  17. * ====================================================================
  18. */
  19. package org.apache.poi.xssf.usermodel.helpers;
  20. import java.util.List;
  21. import org.apache.poi.ooxml.POIXMLDocumentPart;
  22. import org.apache.poi.ss.formula.FormulaParser;
  23. import org.apache.poi.ss.formula.FormulaRenderer;
  24. import org.apache.poi.ss.formula.FormulaType;
  25. import org.apache.poi.ss.formula.ptg.Ptg;
  26. import org.apache.poi.ss.formula.ptg.Pxg;
  27. import org.apache.poi.ss.formula.ptg.Pxg3D;
  28. import org.apache.poi.ss.usermodel.Cell;
  29. import org.apache.poi.ss.usermodel.CellType;
  30. import org.apache.poi.ss.usermodel.Row;
  31. import org.apache.poi.ss.usermodel.Sheet;
  32. import org.apache.poi.xssf.usermodel.XSSFCell;
  33. import org.apache.poi.xssf.usermodel.XSSFChart;
  34. import org.apache.poi.xssf.usermodel.XSSFDrawing;
  35. import org.apache.poi.xssf.usermodel.XSSFEvaluationWorkbook;
  36. import org.apache.poi.xssf.usermodel.XSSFName;
  37. import org.apache.poi.xssf.usermodel.XSSFWorkbook;
  38. import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTCellFormula;
  39. import org.w3c.dom.Node;
  40. import org.w3c.dom.NodeList;
  41. /**
  42. * Utility to update formulas and named ranges when a sheet name was changed
  43. */
  44. public final class XSSFFormulaUtils {
  45. private final XSSFWorkbook _wb;
  46. private final XSSFEvaluationWorkbook _fpwb;
  47. public XSSFFormulaUtils(XSSFWorkbook wb) {
  48. _wb = wb;
  49. _fpwb = XSSFEvaluationWorkbook.create(_wb);
  50. }
  51. /**
  52. * Update sheet name in all charts, formulas and named ranges.
  53. * Called from {@link XSSFWorkbook#setSheetName(int, String)}
  54. * <p>
  55. * The idea is to parse every formula and render it back to string
  56. * with the updated sheet name. This is done by parsing into Ptgs,
  57. * looking for ones with sheet references in them, and changing those
  58. *
  59. * @param sheetIndex the 0-based index of the sheet being changed
  60. * @param oldName the old sheet name
  61. * @param newName the new sheet name
  62. */
  63. public void updateSheetName(final int sheetIndex, final String oldName, final String newName) {
  64. // update named ranges
  65. for (XSSFName nm : _wb.getAllNames()) {
  66. if (nm.getSheetIndex() == -1 || nm.getSheetIndex() == sheetIndex) {
  67. updateName(nm, oldName, newName);
  68. }
  69. }
  70. // update formulas
  71. for (Sheet sh : _wb) {
  72. for (Row row : sh) {
  73. for (Cell cell : row) {
  74. if (cell.getCellType() == CellType.FORMULA) {
  75. updateFormula((XSSFCell) cell, oldName, newName);
  76. }
  77. }
  78. }
  79. }
  80. // update charts
  81. List<POIXMLDocumentPart> rels = _wb.getSheetAt(sheetIndex).getRelations();
  82. for (POIXMLDocumentPart r : rels) {
  83. if (r instanceof XSSFDrawing) {
  84. XSSFDrawing dg = (XSSFDrawing) r;
  85. for (XSSFChart chart : dg.getCharts()) {
  86. Node dom = chart.getCTChartSpace().getDomNode();
  87. updateDomSheetReference(dom, oldName, newName);
  88. }
  89. }
  90. }
  91. }
  92. /**
  93. * Parse cell formula and re-assemble it back using the new sheet name
  94. *
  95. * @param cell the cell to update
  96. */
  97. private void updateFormula(XSSFCell cell, String oldName, String newName) {
  98. CTCellFormula f = cell.getCTCell().getF();
  99. if (f != null) {
  100. String formula = f.getStringValue();
  101. if (formula != null && formula.length() > 0) {
  102. int sheetIndex = _wb.getSheetIndex(cell.getSheet());
  103. Ptg[] ptgs = FormulaParser.parse(formula, _fpwb, FormulaType.CELL, sheetIndex, cell.getRowIndex());
  104. for (Ptg ptg : ptgs) {
  105. updatePtg(ptg, oldName, newName);
  106. }
  107. String updatedFormula = FormulaRenderer.toFormulaString(_fpwb, ptgs);
  108. if (!formula.equals(updatedFormula)) {
  109. f.setStringValue(updatedFormula);
  110. }
  111. }
  112. }
  113. }
  114. /**
  115. * Parse formula in the named range and re-assemble it back using the new sheet name.
  116. *
  117. * @param name the name to update
  118. */
  119. private void updateName(XSSFName name, String oldName, String newName) {
  120. String formula = name.getRefersToFormula();
  121. if (formula != null) {
  122. int sheetIndex = name.getSheetIndex();
  123. int rowIndex = -1; //don't care
  124. Ptg[] ptgs = FormulaParser.parse(formula, _fpwb, FormulaType.NAMEDRANGE, sheetIndex, rowIndex);
  125. for (Ptg ptg : ptgs) {
  126. updatePtg(ptg, oldName, newName);
  127. }
  128. String updatedFormula = FormulaRenderer.toFormulaString(_fpwb, ptgs);
  129. if (!formula.equals(updatedFormula)) {
  130. name.setRefersToFormula(updatedFormula);
  131. }
  132. }
  133. }
  134. private void updatePtg(Ptg ptg, String oldName, String newName) {
  135. if (ptg instanceof Pxg) {
  136. Pxg pxg = (Pxg)ptg;
  137. if (pxg.getExternalWorkbookNumber() < 1) {
  138. if (pxg.getSheetName() != null &&
  139. pxg.getSheetName().equals(oldName)) {
  140. pxg.setSheetName(newName);
  141. }
  142. if (pxg instanceof Pxg3D) {
  143. Pxg3D pxg3D = (Pxg3D)pxg;
  144. if (pxg3D.getLastSheetName() != null &&
  145. pxg3D.getLastSheetName().equals(oldName)) {
  146. pxg3D.setLastSheetName(newName);
  147. }
  148. }
  149. }
  150. }
  151. }
  152. /**
  153. * Parse the DOM tree recursively searching for text containing reference to the old sheet name and replacing it.
  154. *
  155. * @param dom the XML node in which to perform the replacement.
  156. *
  157. * Code extracted from: <a href="https://bz.apache.org/bugzilla/show_bug.cgi?id=54470">Bug 54470</a>
  158. */
  159. private void updateDomSheetReference(Node dom, final String oldName, final String newName) {
  160. String value = dom.getNodeValue();
  161. if (value != null) {
  162. // make sure the value contains the old sheet and not a similar sheet
  163. // (ex: Valid: 'Sheet1'! or Sheet1! ; NotValid: 'Sheet1Test'! or Sheet1Test!)
  164. if (value.contains(oldName+"!") || value.contains(oldName+"'!")) {
  165. XSSFName temporary = _wb.createName();
  166. temporary.setRefersToFormula(value);
  167. updateName(temporary, oldName, newName);
  168. dom.setNodeValue(temporary.getRefersToFormula());
  169. _wb.removeName(temporary);
  170. }
  171. }
  172. NodeList nl = dom.getChildNodes();
  173. for (int i = 0; i < nl.getLength(); i++) {
  174. updateDomSheetReference(nl.item(i), oldName, newName);
  175. }
  176. }
  177. }