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.

XSSFName.java 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441
  1. /* ====================================================================
  2. Licensed to the Apache Software Foundation (ASF) under one or more
  3. contributor license agreements. See the NOTICE file distributed with
  4. this work for additional information regarding copyright ownership.
  5. The ASF licenses this file to You under the Apache License, Version 2.0
  6. (the "License"); you may not use this file except in compliance with
  7. the License. You may obtain a copy of the License at
  8. http://www.apache.org/licenses/LICENSE-2.0
  9. Unless required by applicable law or agreed to in writing, software
  10. distributed under the License is distributed on an "AS IS" BASIS,
  11. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. See the License for the specific language governing permissions and
  13. limitations under the License.
  14. ==================================================================== */
  15. package org.apache.poi.xssf.usermodel;
  16. import org.apache.poi.ss.SpreadsheetVersion;
  17. import org.apache.poi.ss.formula.ptg.Ptg;
  18. import org.apache.poi.ss.formula.FormulaParser;
  19. import org.apache.poi.ss.formula.FormulaType;
  20. import org.apache.poi.ss.usermodel.Name;
  21. import org.apache.poi.ss.util.AreaReference;
  22. import org.apache.poi.ss.util.CellReference;
  23. import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTDefinedName;
  24. /**
  25. * Represents a defined named range in a SpreadsheetML workbook.
  26. * <p>
  27. * Defined names are descriptive text that is used to represents a cell, range of cells, formula, or constant value.
  28. * Use easy-to-understand names, such as Products, to refer to hard to understand ranges, such as {@code Sales!C20:C30}.
  29. * </p>
  30. * Example:
  31. * <pre>{@code
  32. * XSSFWorkbook wb = new XSSFWorkbook();
  33. * XSSFSheet sh = wb.createSheet("Sheet1");
  34. *
  35. * //applies to the entire workbook
  36. * XSSFName name1 = wb.createName();
  37. * name1.setNameName("FMLA");
  38. * name1.setRefersToFormula("Sheet1!$B$3");
  39. *
  40. * //applies to Sheet1
  41. * XSSFName name2 = wb.createName();
  42. * name2.setNameName("SheetLevelName");
  43. * name2.setComment("This name is scoped to Sheet1");
  44. * name2.setLocalSheetId(0);
  45. * name2.setRefersToFormula("Sheet1!$B$3");
  46. *
  47. * }</pre>
  48. */
  49. public final class XSSFName implements Name {
  50. /**
  51. * A built-in defined name that specifies the workbook's print area
  52. */
  53. public static final String BUILTIN_PRINT_AREA = "_xlnm.Print_Area";
  54. /**
  55. * A built-in defined name that specifies the row(s) or column(s) to repeat
  56. * at the top of each printed page.
  57. */
  58. public static final String BUILTIN_PRINT_TITLE = "_xlnm.Print_Titles";
  59. /**
  60. * A built-in defined name that refers to a range containing the criteria values
  61. * to be used in applying an advanced filter to a range of data
  62. */
  63. public static final String BUILTIN_CRITERIA = "_xlnm.Criteria:";
  64. /**
  65. * this defined name refers to the range containing the filtered
  66. * output values resulting from applying an advanced filter criteria to a source
  67. * range
  68. */
  69. public static final String BUILTIN_EXTRACT = "_xlnm.Extract:";
  70. /**
  71. * Can be one of the following
  72. * <ul>
  73. * <li> this defined name refers to a range to which an advanced filter has been
  74. * applied. This represents the source data range, unfiltered.
  75. * <li> This defined name refers to a range to which an AutoFilter has been
  76. * applied
  77. * </ul>
  78. */
  79. public static final String BUILTIN_FILTER_DB = "_xlnm._FilterDatabase";
  80. /**
  81. * A built-in defined name that refers to a consolidation area
  82. */
  83. public static final String BUILTIN_CONSOLIDATE_AREA = "_xlnm.Consolidate_Area";
  84. /**
  85. * A built-in defined name that specified that the range specified is from a database data source
  86. */
  87. public static final String BUILTIN_DATABASE = "_xlnm.Database";
  88. /**
  89. * A built-in defined name that refers to a sheet title.
  90. */
  91. public static final String BUILTIN_SHEET_TITLE = "_xlnm.Sheet_Title";
  92. private final XSSFWorkbook _workbook;
  93. private final CTDefinedName _ctName;
  94. /**
  95. * Creates an XSSFName object - called internally by XSSFWorkbook.
  96. *
  97. * @param name - the xml bean that holds data represenring this defined name.
  98. * @param workbook - the workbook object associated with the name
  99. * @see org.apache.poi.xssf.usermodel.XSSFWorkbook#createName()
  100. */
  101. protected XSSFName(CTDefinedName name, XSSFWorkbook workbook) {
  102. _workbook = workbook;
  103. _ctName = name;
  104. }
  105. /**
  106. * Returns the underlying named range object
  107. */
  108. protected CTDefinedName getCTName() {
  109. return _ctName;
  110. }
  111. /**
  112. * Returns the name that will appear in the user interface for the defined name.
  113. *
  114. * @return text name of this defined name
  115. */
  116. @Override
  117. public String getNameName() {
  118. return _ctName.getName();
  119. }
  120. /**
  121. * Sets the name that will appear in the user interface for the defined name.
  122. * Names must begin with a letter or underscore, not contain spaces and be unique across the workbook.
  123. *
  124. * <p>
  125. * A name must always be unique within its scope. POI prevents you from defining a name that is not unique
  126. * within its scope. However you can use the same name in different scopes. Example:
  127. * <pre>{@code
  128. * //by default names are workbook-global
  129. * XSSFName name;
  130. * name = workbook.createName();
  131. * name.setNameName("sales_08");
  132. *
  133. * name = workbook.createName();
  134. * name.setNameName("sales_08"); //will throw an exception: "The workbook already contains this name (case-insensitive)"
  135. *
  136. * //create sheet-level name
  137. * name = workbook.createName();
  138. * name.setSheetIndex(0); //the scope of the name is the first sheet
  139. * name.setNameName("sales_08"); //ok
  140. *
  141. * name = workbook.createName();
  142. * name.setSheetIndex(0);
  143. * name.setNameName("sales_08"); //will throw an exception: "The sheet already contains this name (case-insensitive)"
  144. *
  145. * }</pre>
  146. *
  147. * @param name name of this defined name
  148. * @throws IllegalArgumentException if the name is invalid or the workbook already contains this name (case-insensitive)
  149. */
  150. @Override
  151. public void setNameName(String name) {
  152. validateName(name);
  153. String oldName = getNameName();
  154. int sheetIndex = getSheetIndex();
  155. //Check to ensure no other names have the same case-insensitive name at the same scope
  156. for (XSSFName foundName : _workbook.getNames(name)) {
  157. if (foundName.getSheetIndex() == sheetIndex && foundName != this) {
  158. String msg = "The "+(sheetIndex == -1 ? "workbook" : "sheet")+" already contains this name: " + name;
  159. throw new IllegalArgumentException(msg);
  160. }
  161. }
  162. _ctName.setName(name);
  163. //Need to update the name -> named ranges map
  164. _workbook.updateName(this, oldName);
  165. }
  166. @Override
  167. public String getRefersToFormula() {
  168. String result = _ctName.getStringValue();
  169. if (result == null || result.length() < 1) {
  170. return null;
  171. }
  172. return result;
  173. }
  174. @Override
  175. public void setRefersToFormula(String formulaText) {
  176. XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(_workbook);
  177. //validate through the FormulaParser
  178. FormulaParser.parse(formulaText, fpb, FormulaType.NAMEDRANGE, getSheetIndex(), -1);
  179. _ctName.setStringValue(formulaText);
  180. }
  181. @Override
  182. public boolean isDeleted(){
  183. String formulaText = getRefersToFormula();
  184. if (formulaText == null) {
  185. return false;
  186. }
  187. XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(_workbook);
  188. Ptg[] ptgs = FormulaParser.parse(formulaText, fpb, FormulaType.NAMEDRANGE, getSheetIndex(), -1);
  189. return Ptg.doesFormulaReferToDeletedCell(ptgs);
  190. }
  191. /**
  192. * Tell Excel that this name applies to the worksheet with the specified index instead of the entire workbook.
  193. *
  194. * @param index the sheet index this name applies to, -1 unsets this property making the name workbook-global
  195. */
  196. @Override
  197. public void setSheetIndex(int index) {
  198. int lastSheetIx = _workbook.getNumberOfSheets() - 1;
  199. if (index < -1 || index > lastSheetIx) {
  200. throw new IllegalArgumentException("Sheet index (" + index +") is out of range" +
  201. (lastSheetIx == -1 ? "" : (" (0.." + lastSheetIx + ")")));
  202. }
  203. if(index == -1) {
  204. if(_ctName.isSetLocalSheetId()) _ctName.unsetLocalSheetId();
  205. } else {
  206. _ctName.setLocalSheetId(index);
  207. }
  208. }
  209. /**
  210. * Returns the sheet index this name applies to.
  211. *
  212. * @return the sheet index this name applies to, -1 if this name applies to the entire workbook
  213. */
  214. @Override
  215. public int getSheetIndex() {
  216. return _ctName.isSetLocalSheetId() ? (int) _ctName.getLocalSheetId() : -1;
  217. }
  218. /**
  219. * Indicates that the defined name refers to a user-defined function.
  220. * This attribute is used when there is an add-in or other code project associated with the file.
  221. *
  222. * @param value {@code true} indicates the name refers to a function.
  223. */
  224. @Override
  225. public void setFunction(boolean value) {
  226. _ctName.setFunction(value);
  227. }
  228. /**
  229. * Indicates that the defined name refers to a user-defined function.
  230. * This attribute is used when there is an add-in or other code project associated with the file.
  231. *
  232. * @return {@code true} indicates the name refers to a function.
  233. */
  234. public boolean getFunction() {
  235. return _ctName.getFunction();
  236. }
  237. /**
  238. * Specifies the function group index if the defined name refers to a function. The function
  239. * group defines the general category for the function. This attribute is used when there is
  240. * an add-in or other code project associated with the file.
  241. *
  242. * @param functionGroupId the function group index that defines the general category for the function
  243. */
  244. public void setFunctionGroupId(int functionGroupId) {
  245. _ctName.setFunctionGroupId(functionGroupId);
  246. }
  247. /**
  248. * Returns the function group index if the defined name refers to a function. The function
  249. * group defines the general category for the function. This attribute is used when there is
  250. * an add-in or other code project associated with the file.
  251. *
  252. * @return the function group index that defines the general category for the function
  253. */
  254. public int getFunctionGroupId() {
  255. return (int) _ctName.getFunctionGroupId();
  256. }
  257. /**
  258. * Get the sheets name which this named range is referenced to
  259. *
  260. * @return sheet name, which this named range referred to.
  261. * Empty string if the referenced sheet name was not found.
  262. */
  263. @Override
  264. public String getSheetName() {
  265. if (_ctName.isSetLocalSheetId()) {
  266. // Given as explicit sheet id
  267. int sheetId = (int)_ctName.getLocalSheetId();
  268. return _workbook.getSheetName(sheetId);
  269. }
  270. String ref = getRefersToFormula();
  271. AreaReference areaRef = new AreaReference(ref, SpreadsheetVersion.EXCEL2007);
  272. return areaRef.getFirstCell().getSheetName();
  273. }
  274. /**
  275. * Is the name refers to a user-defined function ?
  276. *
  277. * @return {@code true} if this name refers to a user-defined function
  278. */
  279. @Override
  280. public boolean isFunctionName() {
  281. return getFunction();
  282. }
  283. /**
  284. * Checks if this name is hidden, eg one of the built-in Excel
  285. * internal names
  286. *
  287. * @return true if this name is a hidden one
  288. */
  289. @Override
  290. public boolean isHidden() {
  291. return _ctName.getHidden();
  292. }
  293. /**
  294. * Returns the comment the user provided when the name was created.
  295. *
  296. * @return the user comment for this named range
  297. */
  298. @Override
  299. public String getComment() {
  300. return _ctName.getComment();
  301. }
  302. /**
  303. * Specifies the comment the user provided when the name was created.
  304. *
  305. * @param comment the user comment for this named range
  306. */
  307. @Override
  308. public void setComment(String comment) {
  309. _ctName.setComment(comment);
  310. }
  311. @Override
  312. public int hashCode() {
  313. return _ctName.toString().hashCode();
  314. }
  315. /**
  316. * Compares this name to the specified object.
  317. * The result is {@code true} if the argument is XSSFName and the
  318. * underlying CTDefinedName bean equals to the CTDefinedName representing this name
  319. *
  320. * @param o the object to compare this {@code XSSFName} against.
  321. * @return {@code true} if the {@code XSSFName }are equal;
  322. * {@code false} otherwise.
  323. */
  324. @Override
  325. public boolean equals(Object o) {
  326. if(o == this) return true;
  327. if (!(o instanceof XSSFName)) return false;
  328. XSSFName cf = (XSSFName) o;
  329. return _ctName.toString().equals(cf.getCTName().toString());
  330. }
  331. /**
  332. * https://support.office.com/en-us/article/Define-and-use-names-in-formulas-4D0F13AC-53B7-422E-AFD2-ABD7FF379C64#bmsyntax_rules_for_names
  333. *
  334. * Valid characters:
  335. * First character: { letter | underscore | backslash }
  336. * Remaining characters: { letter | number | period | underscore }
  337. *
  338. * Cell shorthand: cannot be { "C" | "c" | "R" | "r" }
  339. *
  340. * Cell references disallowed: cannot be a cell reference $A$1 or R1C1
  341. *
  342. * Spaces are not valid (follows from valid characters above)
  343. *
  344. * Name length: (XSSF-specific?) 255 characters maximum
  345. *
  346. * Case sensitivity: all names are case-insensitive
  347. *
  348. * Uniqueness: must be unique (for names with the same scope)
  349. */
  350. private static void validateName(String name) {
  351. if (name.length() == 0) {
  352. throw new IllegalArgumentException("Name cannot be blank");
  353. }
  354. if (name.length() > 255) {
  355. throw new IllegalArgumentException("Invalid name: '"+name+"': cannot exceed 255 characters in length");
  356. }
  357. if (name.equalsIgnoreCase("R") || name.equalsIgnoreCase("C")) {
  358. throw new IllegalArgumentException("Invalid name: '"+name+"': cannot be special shorthand R or C");
  359. }
  360. // is first character valid?
  361. char c = name.charAt(0);
  362. String allowedSymbols = "_\\";
  363. boolean characterIsValid = (Character.isLetter(c) || allowedSymbols.indexOf(c) != -1);
  364. if (!characterIsValid) {
  365. throw new IllegalArgumentException("Invalid name: '"+name+"': first character must be underscore or a letter");
  366. }
  367. // are all other characters valid?
  368. allowedSymbols = "_.\\"; //backslashes needed for unicode escape
  369. for (final char ch : name.toCharArray()) {
  370. characterIsValid = (Character.isLetterOrDigit(ch) || allowedSymbols.indexOf(ch) != -1);
  371. if (!characterIsValid) {
  372. throw new IllegalArgumentException("Invalid name: '"+name+"': name must be letter, digit, period, or underscore");
  373. }
  374. }
  375. // Is the name a valid $A$1 cell reference
  376. // Because $, :, and ! are disallowed characters, A1-style references become just a letter-number combination
  377. if (name.matches("[A-Za-z]+\\d+")) {
  378. String col = name.replaceAll("\\d", "");
  379. String row = name.replaceAll("[A-Za-z]", "");
  380. try {
  381. if (CellReference.cellReferenceIsWithinRange(col, row, SpreadsheetVersion.EXCEL2007)) {
  382. throw new IllegalArgumentException("Invalid name: '"+name+"': cannot be $A$1-style cell reference");
  383. }
  384. } catch (final NumberFormatException e) {
  385. // row was not parseable as an Integer, such as a BigInt
  386. // therefore name passes the not-a-cell-reference criteria
  387. }
  388. }
  389. // Is the name a valid R1C1 cell reference?
  390. if (name.matches("[Rr]\\d+[Cc]\\d+")) {
  391. throw new IllegalArgumentException("Invalid name: '"+name+"': cannot be R1C1-style cell reference");
  392. }
  393. }
  394. }