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.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  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.Iterator;
  21. import java.util.List;
  22. import org.apache.poi.ooxml.POIXMLDocumentPart;
  23. import org.apache.poi.ss.formula.FormulaParser;
  24. import org.apache.poi.ss.formula.FormulaRenderer;
  25. import org.apache.poi.ss.formula.FormulaType;
  26. import org.apache.poi.ss.formula.ptg.Ptg;
  27. import org.apache.poi.ss.formula.ptg.Pxg;
  28. import org.apache.poi.ss.formula.ptg.Pxg3D;
  29. import org.apache.poi.ss.usermodel.Cell;
  30. import org.apache.poi.ss.usermodel.CellType;
  31. import org.apache.poi.ss.usermodel.Row;
  32. import org.apache.poi.ss.usermodel.Sheet;
  33. import org.apache.poi.xssf.usermodel.XSSFCell;
  34. import org.apache.poi.xssf.usermodel.XSSFChart;
  35. import org.apache.poi.xssf.usermodel.XSSFDrawing;
  36. import org.apache.poi.xssf.usermodel.XSSFEvaluationWorkbook;
  37. import org.apache.poi.xssf.usermodel.XSSFName;
  38. import org.apache.poi.xssf.usermodel.XSSFWorkbook;
  39. import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTCellFormula;
  40. import org.w3c.dom.Node;
  41. import org.w3c.dom.NodeList;
  42. /**
  43. * Utility to update formulas and named ranges when a sheet name was changed
  44. *
  45. * @author Yegor Kozlov
  46. */
  47. public final class XSSFFormulaUtils {
  48. private final XSSFWorkbook _wb;
  49. private final XSSFEvaluationWorkbook _fpwb;
  50. public XSSFFormulaUtils(XSSFWorkbook wb) {
  51. _wb = wb;
  52. _fpwb = XSSFEvaluationWorkbook.create(_wb);
  53. }
  54. /**
  55. * Update sheet name in all charts, formulas and named ranges.
  56. * Called from {@link XSSFWorkbook#setSheetName(int, String)}
  57. * <p>
  58. * <p>
  59. * The idea is to parse every formula and render it back to string
  60. * with the updated sheet name. This is done by parsing into Ptgs,
  61. * looking for ones with sheet references in them, and changing those
  62. * </p>
  63. *
  64. * @param sheetIndex the 0-based index of the sheet being changed
  65. * @param oldName the old sheet name
  66. * @param newName the new sheet name
  67. */
  68. public void updateSheetName(final int sheetIndex, final String oldName, final String newName) {
  69. // update named ranges
  70. for (XSSFName nm : _wb.getAllNames()) {
  71. if (nm.getSheetIndex() == -1 || nm.getSheetIndex() == sheetIndex) {
  72. updateName(nm, oldName, newName);
  73. }
  74. }
  75. // update formulas
  76. for (Sheet sh : _wb) {
  77. for (Row row : sh) {
  78. for (Cell cell : row) {
  79. if (cell.getCellType() == CellType.FORMULA) {
  80. updateFormula((XSSFCell) cell, oldName, newName);
  81. }
  82. }
  83. }
  84. }
  85. // update charts
  86. List<POIXMLDocumentPart> rels = _wb.getSheetAt(sheetIndex).getRelations();
  87. for (POIXMLDocumentPart r : rels) {
  88. if (r instanceof XSSFDrawing) {
  89. XSSFDrawing dg = (XSSFDrawing) r;
  90. Iterator<XSSFChart> it = dg.getCharts().iterator();
  91. while (it.hasNext()) {
  92. XSSFChart chart = it.next();
  93. Node dom = chart.getCTChartSpace().getDomNode();
  94. updateDomSheetReference(dom, oldName, newName);
  95. }
  96. }
  97. }
  98. }
  99. /**
  100. * Parse cell formula and re-assemble it back using the new sheet name
  101. *
  102. * @param cell the cell to update
  103. */
  104. private void updateFormula(XSSFCell cell, String oldName, String newName) {
  105. CTCellFormula f = cell.getCTCell().getF();
  106. if (f != null) {
  107. String formula = f.getStringValue();
  108. if (formula != null && formula.length() > 0) {
  109. int sheetIndex = _wb.getSheetIndex(cell.getSheet());
  110. Ptg[] ptgs = FormulaParser.parse(formula, _fpwb, FormulaType.CELL, sheetIndex, cell.getRowIndex());
  111. for (Ptg ptg : ptgs) {
  112. updatePtg(ptg, oldName, newName);
  113. }
  114. String updatedFormula = FormulaRenderer.toFormulaString(_fpwb, ptgs);
  115. if (!formula.equals(updatedFormula)) {
  116. f.setStringValue(updatedFormula);
  117. }
  118. }
  119. }
  120. }
  121. /**
  122. * Parse formula in the named range and re-assemble it back using the new sheet name.
  123. *
  124. * @param name the name to update
  125. */
  126. private void updateName(XSSFName name, String oldName, String newName) {
  127. String formula = name.getRefersToFormula();
  128. if (formula != null) {
  129. int sheetIndex = name.getSheetIndex();
  130. int rowIndex = -1; //don't care
  131. Ptg[] ptgs = FormulaParser.parse(formula, _fpwb, FormulaType.NAMEDRANGE, sheetIndex, rowIndex);
  132. for (Ptg ptg : ptgs) {
  133. updatePtg(ptg, oldName, newName);
  134. }
  135. String updatedFormula = FormulaRenderer.toFormulaString(_fpwb, ptgs);
  136. if (!formula.equals(updatedFormula)) {
  137. name.setRefersToFormula(updatedFormula);
  138. }
  139. }
  140. }
  141. private void updatePtg(Ptg ptg, String oldName, String newName) {
  142. if (ptg instanceof Pxg) {
  143. Pxg pxg = (Pxg)ptg;
  144. if (pxg.getExternalWorkbookNumber() < 1) {
  145. if (pxg.getSheetName() != null &&
  146. pxg.getSheetName().equals(oldName)) {
  147. pxg.setSheetName(newName);
  148. }
  149. if (pxg instanceof Pxg3D) {
  150. Pxg3D pxg3D = (Pxg3D)pxg;
  151. if (pxg3D.getLastSheetName() != null &&
  152. pxg3D.getLastSheetName().equals(oldName)) {
  153. pxg3D.setLastSheetName(newName);
  154. }
  155. }
  156. }
  157. }
  158. }
  159. /**
  160. * Parse the DOM tree recursively searching for text containing reference to the old sheet name and replacing it.
  161. *
  162. * @param dom the XML node in which to perform the replacement.
  163. *
  164. * Code extracted from: <a href="https://bz.apache.org/bugzilla/show_bug.cgi?id=54470">Bug 54470</a>
  165. */
  166. private void updateDomSheetReference(Node dom, final String oldName, final String newName) {
  167. String value = dom.getNodeValue();
  168. if (value != null) {
  169. // make sure the value contains the old sheet and not a similar sheet
  170. // (ex: Valid: 'Sheet1'! or Sheet1! ; NotValid: 'Sheet1Test'! or Sheet1Test!)
  171. if (value.contains(oldName+"!") || value.contains(oldName+"'!")) {
  172. XSSFName temporary = _wb.createName();
  173. temporary.setRefersToFormula(value);
  174. updateName(temporary, oldName, newName);
  175. dom.setNodeValue(temporary.getRefersToFormula());
  176. _wb.removeName(temporary);
  177. }
  178. }
  179. NodeList nl = dom.getChildNodes();
  180. for (int i = 0; i < nl.getLength(); i++) {
  181. updateDomSheetReference(nl.item(i), oldName, newName);
  182. }
  183. }
  184. }