123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441 |
- /* ====================================================================
- Licensed to the Apache Software Foundation (ASF) under one or more
- contributor license agreements. See the NOTICE file distributed with
- this work for additional information regarding copyright ownership.
- The ASF licenses this file to You under the Apache License, Version 2.0
- (the "License"); you may not use this file except in compliance with
- the License. You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- ==================================================================== */
- package org.apache.poi.xssf.usermodel;
-
- import org.apache.poi.ss.SpreadsheetVersion;
- import org.apache.poi.ss.formula.ptg.Ptg;
-
- import org.apache.poi.ss.formula.FormulaParser;
- import org.apache.poi.ss.formula.FormulaType;
- import org.apache.poi.ss.usermodel.Name;
- import org.apache.poi.ss.util.AreaReference;
- import org.apache.poi.ss.util.CellReference;
- import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTDefinedName;
-
- /**
- * Represents a defined named range in a SpreadsheetML workbook.
- * <p>
- * Defined names are descriptive text that is used to represents a cell, range of cells, formula, or constant value.
- * Use easy-to-understand names, such as Products, to refer to hard to understand ranges, such as {@code Sales!C20:C30}.
- * </p>
- * Example:
- * <pre>{@code
- * XSSFWorkbook wb = new XSSFWorkbook();
- * XSSFSheet sh = wb.createSheet("Sheet1");
- *
- * //applies to the entire workbook
- * XSSFName name1 = wb.createName();
- * name1.setNameName("FMLA");
- * name1.setRefersToFormula("Sheet1!$B$3");
- *
- * //applies to Sheet1
- * XSSFName name2 = wb.createName();
- * name2.setNameName("SheetLevelName");
- * name2.setComment("This name is scoped to Sheet1");
- * name2.setLocalSheetId(0);
- * name2.setRefersToFormula("Sheet1!$B$3");
- *
- * }</pre>
- */
- public final class XSSFName implements Name {
-
- /**
- * A built-in defined name that specifies the workbook's print area
- */
- public static final String BUILTIN_PRINT_AREA = "_xlnm.Print_Area";
-
- /**
- * A built-in defined name that specifies the row(s) or column(s) to repeat
- * at the top of each printed page.
- */
- public static final String BUILTIN_PRINT_TITLE = "_xlnm.Print_Titles";
-
- /**
- * A built-in defined name that refers to a range containing the criteria values
- * to be used in applying an advanced filter to a range of data
- */
- public static final String BUILTIN_CRITERIA = "_xlnm.Criteria:";
-
-
- /**
- * this defined name refers to the range containing the filtered
- * output values resulting from applying an advanced filter criteria to a source
- * range
- */
- public static final String BUILTIN_EXTRACT = "_xlnm.Extract:";
-
- /**
- * Can be one of the following
- * <ul>
- * <li> this defined name refers to a range to which an advanced filter has been
- * applied. This represents the source data range, unfiltered.
- * <li> This defined name refers to a range to which an AutoFilter has been
- * applied
- * </ul>
- */
- public static final String BUILTIN_FILTER_DB = "_xlnm._FilterDatabase";
-
- /**
- * A built-in defined name that refers to a consolidation area
- */
- public static final String BUILTIN_CONSOLIDATE_AREA = "_xlnm.Consolidate_Area";
-
- /**
- * A built-in defined name that specified that the range specified is from a database data source
- */
- public static final String BUILTIN_DATABASE = "_xlnm.Database";
-
- /**
- * A built-in defined name that refers to a sheet title.
- */
- public static final String BUILTIN_SHEET_TITLE = "_xlnm.Sheet_Title";
-
- private final XSSFWorkbook _workbook;
- private final CTDefinedName _ctName;
-
- /**
- * Creates an XSSFName object - called internally by XSSFWorkbook.
- *
- * @param name - the xml bean that holds data represenring this defined name.
- * @param workbook - the workbook object associated with the name
- * @see org.apache.poi.xssf.usermodel.XSSFWorkbook#createName()
- */
- protected XSSFName(CTDefinedName name, XSSFWorkbook workbook) {
- _workbook = workbook;
- _ctName = name;
- }
-
- /**
- * Returns the underlying named range object
- */
- protected CTDefinedName getCTName() {
- return _ctName;
- }
-
- /**
- * Returns the name that will appear in the user interface for the defined name.
- *
- * @return text name of this defined name
- */
- @Override
- public String getNameName() {
- return _ctName.getName();
- }
-
- /**
- * Sets the name that will appear in the user interface for the defined name.
- * Names must begin with a letter or underscore, not contain spaces and be unique across the workbook.
- *
- * <p>
- * A name must always be unique within its scope. POI prevents you from defining a name that is not unique
- * within its scope. However you can use the same name in different scopes. Example:
- * <pre>{@code
- * //by default names are workbook-global
- * XSSFName name;
- * name = workbook.createName();
- * name.setNameName("sales_08");
- *
- * name = workbook.createName();
- * name.setNameName("sales_08"); //will throw an exception: "The workbook already contains this name (case-insensitive)"
- *
- * //create sheet-level name
- * name = workbook.createName();
- * name.setSheetIndex(0); //the scope of the name is the first sheet
- * name.setNameName("sales_08"); //ok
- *
- * name = workbook.createName();
- * name.setSheetIndex(0);
- * name.setNameName("sales_08"); //will throw an exception: "The sheet already contains this name (case-insensitive)"
- *
- * }</pre>
- *
- * @param name name of this defined name
- * @throws IllegalArgumentException if the name is invalid or the workbook already contains this name (case-insensitive)
- */
- @Override
- public void setNameName(String name) {
- validateName(name);
-
- String oldName = getNameName();
- int sheetIndex = getSheetIndex();
- //Check to ensure no other names have the same case-insensitive name at the same scope
- for (XSSFName foundName : _workbook.getNames(name)) {
- if (foundName.getSheetIndex() == sheetIndex && foundName != this) {
- String msg = "The "+(sheetIndex == -1 ? "workbook" : "sheet")+" already contains this name: " + name;
- throw new IllegalArgumentException(msg);
- }
- }
- _ctName.setName(name);
- //Need to update the name -> named ranges map
- _workbook.updateName(this, oldName);
- }
-
- @Override
- public String getRefersToFormula() {
- String result = _ctName.getStringValue();
- if (result == null || result.length() < 1) {
- return null;
- }
- return result;
- }
-
- @Override
- public void setRefersToFormula(String formulaText) {
- XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(_workbook);
- //validate through the FormulaParser
- FormulaParser.parse(formulaText, fpb, FormulaType.NAMEDRANGE, getSheetIndex(), -1);
-
- _ctName.setStringValue(formulaText);
- }
-
- @Override
- public boolean isDeleted(){
- String formulaText = getRefersToFormula();
- if (formulaText == null) {
- return false;
- }
- XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(_workbook);
- Ptg[] ptgs = FormulaParser.parse(formulaText, fpb, FormulaType.NAMEDRANGE, getSheetIndex(), -1);
- return Ptg.doesFormulaReferToDeletedCell(ptgs);
- }
-
- /**
- * Tell Excel that this name applies to the worksheet with the specified index instead of the entire workbook.
- *
- * @param index the sheet index this name applies to, -1 unsets this property making the name workbook-global
- */
- @Override
- public void setSheetIndex(int index) {
- int lastSheetIx = _workbook.getNumberOfSheets() - 1;
- if (index < -1 || index > lastSheetIx) {
- throw new IllegalArgumentException("Sheet index (" + index +") is out of range" +
- (lastSheetIx == -1 ? "" : (" (0.." + lastSheetIx + ")")));
- }
-
- if(index == -1) {
- if(_ctName.isSetLocalSheetId()) _ctName.unsetLocalSheetId();
- } else {
- _ctName.setLocalSheetId(index);
- }
- }
-
- /**
- * Returns the sheet index this name applies to.
- *
- * @return the sheet index this name applies to, -1 if this name applies to the entire workbook
- */
- @Override
- public int getSheetIndex() {
- return _ctName.isSetLocalSheetId() ? (int) _ctName.getLocalSheetId() : -1;
- }
-
- /**
- * Indicates that the defined name refers to a user-defined function.
- * This attribute is used when there is an add-in or other code project associated with the file.
- *
- * @param value {@code true} indicates the name refers to a function.
- */
- @Override
- public void setFunction(boolean value) {
- _ctName.setFunction(value);
- }
-
- /**
- * Indicates that the defined name refers to a user-defined function.
- * This attribute is used when there is an add-in or other code project associated with the file.
- *
- * @return {@code true} indicates the name refers to a function.
- */
- public boolean getFunction() {
- return _ctName.getFunction();
- }
-
- /**
- * Specifies the function group index if the defined name refers to a function. The function
- * group defines the general category for the function. This attribute is used when there is
- * an add-in or other code project associated with the file.
- *
- * @param functionGroupId the function group index that defines the general category for the function
- */
- public void setFunctionGroupId(int functionGroupId) {
- _ctName.setFunctionGroupId(functionGroupId);
- }
-
- /**
- * Returns the function group index if the defined name refers to a function. The function
- * group defines the general category for the function. This attribute is used when there is
- * an add-in or other code project associated with the file.
- *
- * @return the function group index that defines the general category for the function
- */
- public int getFunctionGroupId() {
- return (int) _ctName.getFunctionGroupId();
- }
-
- /**
- * Get the sheets name which this named range is referenced to
- *
- * @return sheet name, which this named range referred to.
- * Empty string if the referenced sheet name was not found.
- */
- @Override
- public String getSheetName() {
- if (_ctName.isSetLocalSheetId()) {
- // Given as explicit sheet id
- int sheetId = (int)_ctName.getLocalSheetId();
- return _workbook.getSheetName(sheetId);
- }
- String ref = getRefersToFormula();
- AreaReference areaRef = new AreaReference(ref, SpreadsheetVersion.EXCEL2007);
- return areaRef.getFirstCell().getSheetName();
- }
-
- /**
- * Is the name refers to a user-defined function ?
- *
- * @return {@code true} if this name refers to a user-defined function
- */
- @Override
- public boolean isFunctionName() {
- return getFunction();
- }
-
- /**
- * Checks if this name is hidden, eg one of the built-in Excel
- * internal names
- *
- * @return true if this name is a hidden one
- */
- @Override
- public boolean isHidden() {
- return _ctName.getHidden();
- }
-
- /**
- * Returns the comment the user provided when the name was created.
- *
- * @return the user comment for this named range
- */
- @Override
- public String getComment() {
- return _ctName.getComment();
- }
-
- /**
- * Specifies the comment the user provided when the name was created.
- *
- * @param comment the user comment for this named range
- */
- @Override
- public void setComment(String comment) {
- _ctName.setComment(comment);
- }
-
- @Override
- public int hashCode() {
- return _ctName.toString().hashCode();
- }
-
- /**
- * Compares this name to the specified object.
- * The result is {@code true} if the argument is XSSFName and the
- * underlying CTDefinedName bean equals to the CTDefinedName representing this name
- *
- * @param o the object to compare this {@code XSSFName} against.
- * @return {@code true} if the {@code XSSFName }are equal;
- * {@code false} otherwise.
- */
- @Override
- public boolean equals(Object o) {
- if(o == this) return true;
-
- if (!(o instanceof XSSFName)) return false;
-
- XSSFName cf = (XSSFName) o;
- return _ctName.toString().equals(cf.getCTName().toString());
- }
-
- /**
- * https://support.office.com/en-us/article/Define-and-use-names-in-formulas-4D0F13AC-53B7-422E-AFD2-ABD7FF379C64#bmsyntax_rules_for_names
- *
- * Valid characters:
- * First character: { letter | underscore | backslash }
- * Remaining characters: { letter | number | period | underscore }
- *
- * Cell shorthand: cannot be { "C" | "c" | "R" | "r" }
- *
- * Cell references disallowed: cannot be a cell reference $A$1 or R1C1
- *
- * Spaces are not valid (follows from valid characters above)
- *
- * Name length: (XSSF-specific?) 255 characters maximum
- *
- * Case sensitivity: all names are case-insensitive
- *
- * Uniqueness: must be unique (for names with the same scope)
- */
- private static void validateName(String name) {
-
- if (name.length() == 0) {
- throw new IllegalArgumentException("Name cannot be blank");
- }
- if (name.length() > 255) {
- throw new IllegalArgumentException("Invalid name: '"+name+"': cannot exceed 255 characters in length");
- }
- if (name.equalsIgnoreCase("R") || name.equalsIgnoreCase("C")) {
- throw new IllegalArgumentException("Invalid name: '"+name+"': cannot be special shorthand R or C");
- }
-
- // is first character valid?
- char c = name.charAt(0);
- String allowedSymbols = "_\\";
- boolean characterIsValid = (Character.isLetter(c) || allowedSymbols.indexOf(c) != -1);
- if (!characterIsValid) {
- throw new IllegalArgumentException("Invalid name: '"+name+"': first character must be underscore or a letter");
- }
-
- // are all other characters valid?
- allowedSymbols = "_.\\"; //backslashes needed for unicode escape
- for (final char ch : name.toCharArray()) {
- characterIsValid = (Character.isLetterOrDigit(ch) || allowedSymbols.indexOf(ch) != -1);
- if (!characterIsValid) {
- throw new IllegalArgumentException("Invalid name: '"+name+"': name must be letter, digit, period, or underscore");
- }
- }
-
- // Is the name a valid $A$1 cell reference
- // Because $, :, and ! are disallowed characters, A1-style references become just a letter-number combination
- if (name.matches("[A-Za-z]+\\d+")) {
- String col = name.replaceAll("\\d", "");
- String row = name.replaceAll("[A-Za-z]", "");
-
- try {
- if (CellReference.cellReferenceIsWithinRange(col, row, SpreadsheetVersion.EXCEL2007)) {
- throw new IllegalArgumentException("Invalid name: '"+name+"': cannot be $A$1-style cell reference");
- }
- } catch (final NumberFormatException e) {
- // row was not parseable as an Integer, such as a BigInt
- // therefore name passes the not-a-cell-reference criteria
- }
- }
-
- // Is the name a valid R1C1 cell reference?
- if (name.matches("[Rr]\\d+[Cc]\\d+")) {
- throw new IllegalArgumentException("Invalid name: '"+name+"': cannot be R1C1-style cell reference");
- }
- }
- }
|