import org.apache.poi.hssf.model.InternalWorkbook;
import org.apache.poi.hssf.record.NameCommentRecord;
import org.apache.poi.hssf.record.NameRecord;
+import org.apache.poi.ss.SpreadsheetVersion;
import org.apache.poi.ss.formula.FormulaType;
import org.apache.poi.ss.formula.ptg.Ptg;
import org.apache.poi.ss.usermodel.Name;
+import org.apache.poi.ss.util.CellReference;
/**
* High Level Representation of a 'defined name' which could be a 'built-in' name,
}
}
+ /**
+ * 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)
+ *
+ * @param name
+ */
private static void validateName(String name) {
- /* equivalent to:
- Pattern.compile(
- "[\\p{IsAlphabetic}_]" +
- "[\\p{IsAlphabetic}0-9_\\\\]*",
- Pattern.CASE_INSENSITIVE).matcher(name).matches();
- \p{IsAlphabetic} doesn't work on Java 6, and other regex-based character classes don't work on unicode
- thus we are stuck with Character.isLetter (for now).
- */
-
+
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 = "_";
+ 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
+ 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+"'");
+ 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]", "");
+ if (CellReference.cellReferenceIsWithinRange(col, row, SpreadsheetVersion.EXCEL97)) {
+ throw new IllegalArgumentException("Invalid name: '"+name+"': cannot be $A$1-style cell reference");
+ }
+ }
+
+ // 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");
+ }
}
public void setRefersToFormula(String formulaText) {
==================================================================== */
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;
/**
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)
+ *
+ * @param name
+ */
private static void validateName(String name) {
- /* equivalent to:
- Pattern.compile(
- "[\\p{IsAlphabetic}_]" +
- "[\\p{IsAlphabetic}0-9_\\\\]*",
- Pattern.CASE_INSENSITIVE).matcher(name).matches();
- \p{IsAlphabetic} doesn't work on Java 6, and other regex-based character classes don't work on unicode
- thus we are stuck with Character.isLetter (for now).
- */
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 = "_";
+ 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
+ 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+"'");
+ 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]", "");
+ if (CellReference.cellReferenceIsWithinRange(col, row, SpreadsheetVersion.EXCEL97)) {
+ throw new IllegalArgumentException("Invalid name: '"+name+"': cannot be $A$1-style cell reference");
+ }
+ }
+
+ // 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");
+ }
}
}
wb.close();
}
+ // bug 56781: name validation only checks for first character's validity and presence of spaces
+ // bug 60246: validate name does not allow DOT in named ranges
@Test
- public void test56781() throws IOException {
+ public void testValid() throws IOException {
Workbook wb = _testDataProvider.createWorkbook();
Name name = wb.createName();
for (String valid : Arrays.asList(
"Hello",
"number1",
- "_underscore"
- //"p.e.r.o.i.d.s",
- //"\\Backslash",
- //"Backslash\\"
+ "_underscore",
+ "underscore_",
+ "p.e.r.o.i.d.s",
+ "\\Backslash",
+ "Backslash\\"
)) {
name.setNameName(valid);
}
+ wb.close();
+ }
+
+ @Test
+ public void testInvalid() throws IOException {
+ Workbook wb = _testDataProvider.createWorkbook();
+
+ Name name = wb.createName();
try {
name.setNameName("");
fail("expected exception: (blank)");
"Colon:",
"A-Minus",
"A+Plus",
- "Dollar$")) {
+ "Dollar$",
+ ".periodAtBeginning",
+ "R", //special shorthand
+ "C", //special shorthand
+ "A1", // A1-style cell reference
+ "R1C1", // R1C1-style cell reference
+ "NameThatIsLongerThan255Characters.NameThatIsLongerThan255Characters.NameThatIsLongerThan255Characters..."+
+ "NameThatIsLongerThan255Characters.NameThatIsLongerThan255Characters.NameThatIsLongerThan255Characters..."+
+ "NameThatIsLongerThan255Characters.NameThatIsLongerThan255Characters.NameThatIsLongerThan255Characters..."+
+ "NameThatIsLongerThan255Characters.NameThatIsLongerThan255Characters.NameThatIsLongerThan255Characters..."+
+ "NameThatIsLongerThan255Characters.NameThatIsLongerThan255Characters.NameThatIsLongerThan255Characters"
+ )) {
try {
name.setNameName(invalid);
fail("expected exception: " + invalid);