minor improvements to sheet name validation and identification of cell references vs defined names
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@806395 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
b9020a69e8
commit
4076768ed3
@ -54,10 +54,9 @@ public final class BoundSheetRecord extends StandardRecord {
|
||||
/**
|
||||
* UTF8: sid + len + bof + flags + len(str) + unicode + str 2 + 2 + 4 + 2 +
|
||||
* 1 + 1 + len(str)
|
||||
*
|
||||
*
|
||||
* UNICODE: sid + len + bof + flags + len(str) + unicode + str 2 + 2 + 4 + 2 +
|
||||
* 1 + 1 + 2 * len(str)
|
||||
*
|
||||
*/
|
||||
public BoundSheetRecord(RecordInputStream in) {
|
||||
field_1_position_of_BOF = in.readInt();
|
||||
@ -75,9 +74,8 @@ public final class BoundSheetRecord extends StandardRecord {
|
||||
/**
|
||||
* set the offset in bytes of the Beginning of File Marker within the HSSF
|
||||
* Stream part of the POIFS file
|
||||
*
|
||||
* @param pos
|
||||
* offset in bytes
|
||||
*
|
||||
* @param pos offset in bytes
|
||||
*/
|
||||
public void setPositionOfBof(int pos) {
|
||||
field_1_position_of_BOF = pos;
|
||||
@ -86,10 +84,10 @@ public final class BoundSheetRecord extends StandardRecord {
|
||||
/**
|
||||
* Set the sheetname for this sheet. (this appears in the tabs at the bottom)
|
||||
* @param sheetName the name of the sheet
|
||||
* @throws IllegalArgumentException if sheet name will cause excel to crash.
|
||||
* @throws IllegalArgumentException if sheet name will cause excel to crash.
|
||||
*/
|
||||
public void setSheetname(String sheetName) {
|
||||
|
||||
|
||||
validateSheetName(sheetName);
|
||||
field_5_sheetname = sheetName;
|
||||
field_4_isMultibyteUnicode = StringUtil.hasMultibyte(sheetName) ? 1 : 0;
|
||||
@ -117,10 +115,14 @@ public final class BoundSheetRecord extends StandardRecord {
|
||||
// all other chars OK
|
||||
continue;
|
||||
}
|
||||
throw new IllegalArgumentException("Invalid char (" + ch
|
||||
throw new IllegalArgumentException("Invalid char (" + ch
|
||||
+ ") found at index (" + i + ") in sheet name '" + sheetName + "'");
|
||||
}
|
||||
}
|
||||
if (sheetName.charAt(0) == '\'' || sheetName.charAt(len-1) == '\'') {
|
||||
throw new IllegalArgumentException("Invalid sheet name '" + sheetName
|
||||
+ "'. Sheet names must not begin or end with (').");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get the offset in bytes of the Beginning of File Marker within the HSSF Stream part of the POIFS file
|
||||
@ -154,7 +156,7 @@ public final class BoundSheetRecord extends StandardRecord {
|
||||
buffer.append("[/BOUNDSHEET]\n");
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
|
||||
protected int getDataSize() {
|
||||
return 8 + field_5_sheetname.length() * (isMultibyte() ? 2 : 1);
|
||||
}
|
||||
@ -179,33 +181,33 @@ public final class BoundSheetRecord extends StandardRecord {
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the sheet hidden? Different from very hidden
|
||||
* Is the sheet hidden? Different from very hidden
|
||||
*/
|
||||
public boolean isHidden() {
|
||||
return hiddenFlag.isSet(field_2_option_flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the sheet hidden? Different from very hidden
|
||||
* Is the sheet hidden? Different from very hidden
|
||||
*/
|
||||
public void setHidden(boolean hidden) {
|
||||
field_2_option_flags = hiddenFlag.setBoolean(field_2_option_flags, hidden);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the sheet very hidden? Different from (normal) hidden
|
||||
* Is the sheet very hidden? Different from (normal) hidden
|
||||
*/
|
||||
public boolean isVeryHidden() {
|
||||
return veryHiddenFlag.isSet(field_2_option_flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the sheet very hidden? Different from (normal) hidden
|
||||
* Is the sheet very hidden? Different from (normal) hidden
|
||||
*/
|
||||
public void setVeryHidden(boolean veryHidden) {
|
||||
field_2_option_flags = veryHiddenFlag.setBoolean(field_2_option_flags, veryHidden);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts a List of {@link BoundSheetRecord}s to an array and sorts by the position of their
|
||||
* BOFs.
|
||||
|
@ -32,11 +32,12 @@ public class CellReference {
|
||||
/**
|
||||
* Used to classify identifiers found in formulas as cell references or not.
|
||||
*/
|
||||
public static final class NameType {
|
||||
public static final int CELL = 1;
|
||||
public static final int NAMED_RANGE = 2;
|
||||
public static final int COLUMN = 3;
|
||||
public static final int BAD_CELL_OR_NAMED_RANGE = -1;
|
||||
public enum NameType {
|
||||
CELL,
|
||||
NAMED_RANGE,
|
||||
COLUMN,
|
||||
ROW,
|
||||
BAD_CELL_OR_NAMED_RANGE;
|
||||
}
|
||||
|
||||
/** The character ($) that signifies a row or column value is absolute instead of relative */
|
||||
@ -57,6 +58,11 @@ public class CellReference {
|
||||
* The text may optionally be prefixed with a single '$'.
|
||||
*/
|
||||
private static final Pattern COLUMN_REF_PATTERN = Pattern.compile("\\$?([A-Za-z]+)");
|
||||
/**
|
||||
* Matches a run of one or more digits. The run of digits is group 1.
|
||||
* The text may optionally be prefixed with a single '$'.
|
||||
*/
|
||||
private static final Pattern ROW_REF_PATTERN = Pattern.compile("\\$?([0-9]+)");
|
||||
/**
|
||||
* Named range names must start with a letter or underscore. Subsequent characters may include
|
||||
* digits or dot. (They can even end in dot).
|
||||
@ -176,7 +182,7 @@ public class CellReference {
|
||||
* Classifies an identifier as either a simple (2D) cell reference or a named range name
|
||||
* @return one of the values from <tt>NameType</tt>
|
||||
*/
|
||||
public static int classifyCellReference(String str, SpreadsheetVersion ssVersion) {
|
||||
public static NameType classifyCellReference(String str, SpreadsheetVersion ssVersion) {
|
||||
int len = str.length();
|
||||
if (len < 1) {
|
||||
throw new IllegalArgumentException("Empty string not allowed");
|
||||
@ -188,7 +194,7 @@ public class CellReference {
|
||||
case '_':
|
||||
break;
|
||||
default:
|
||||
if (!Character.isLetter(firstChar)) {
|
||||
if (!Character.isLetter(firstChar) && !Character.isDigit(firstChar)) {
|
||||
throw new IllegalArgumentException("Invalid first char (" + firstChar
|
||||
+ ") of cell reference or named range. Letter expected");
|
||||
}
|
||||
@ -219,7 +225,7 @@ public class CellReference {
|
||||
return NameType.NAMED_RANGE;
|
||||
}
|
||||
|
||||
private static int validateNamedRangeName(String str, SpreadsheetVersion ssVersion) {
|
||||
private static NameType validateNamedRangeName(String str, SpreadsheetVersion ssVersion) {
|
||||
Matcher colMatcher = COLUMN_REF_PATTERN.matcher(str);
|
||||
if (colMatcher.matches()) {
|
||||
String colStr = colMatcher.group(1);
|
||||
@ -227,6 +233,13 @@ public class CellReference {
|
||||
return NameType.COLUMN;
|
||||
}
|
||||
}
|
||||
Matcher rowMatcher = ROW_REF_PATTERN.matcher(str);
|
||||
if (rowMatcher.matches()) {
|
||||
String rowStr = rowMatcher.group(1);
|
||||
if (isRowWithnRange(rowStr, ssVersion)) {
|
||||
return NameType.ROW;
|
||||
}
|
||||
}
|
||||
if (!NAMED_RANGE_NAME_PATTERN.matcher(str).matches()) {
|
||||
return NameType.BAD_CELL_OR_NAMED_RANGE;
|
||||
}
|
||||
@ -274,18 +287,7 @@ public class CellReference {
|
||||
if (!isColumnWithnRange(colStr, ssVersion)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int rowNum = Integer.parseInt(rowStr);
|
||||
|
||||
if (rowNum < 0) {
|
||||
throw new IllegalStateException("Invalid rowStr '" + rowStr + "'.");
|
||||
}
|
||||
if (rowNum == 0) {
|
||||
// execution gets here because caller does first pass of discriminating
|
||||
// potential cell references using a simplistic regex pattern.
|
||||
return false;
|
||||
}
|
||||
return rowNum <= ssVersion.getMaxRows();
|
||||
return isRowWithnRange(rowStr, ssVersion);
|
||||
}
|
||||
|
||||
public static boolean isColumnWithnRange(String colStr, SpreadsheetVersion ssVersion) {
|
||||
@ -308,6 +310,20 @@ public class CellReference {
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean isRowWithnRange(String rowStr, SpreadsheetVersion ssVersion) {
|
||||
int rowNum = Integer.parseInt(rowStr);
|
||||
|
||||
if (rowNum < 0) {
|
||||
throw new IllegalStateException("Invalid rowStr '" + rowStr + "'.");
|
||||
}
|
||||
if (rowNum == 0) {
|
||||
// execution gets here because caller does first pass of discriminating
|
||||
// potential cell references using a simplistic regex pattern.
|
||||
return false;
|
||||
}
|
||||
return rowNum <= ssVersion.getMaxRows();
|
||||
}
|
||||
|
||||
/**
|
||||
* Separates the row from the columns and returns an array of three Strings. The first element
|
||||
* is the sheet name. Only the first element may be null. The second element in is the column
|
||||
|
@ -18,6 +18,10 @@
|
||||
package org.apache.poi.hssf.record;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.poi.util.HexRead;
|
||||
|
||||
import junit.framework.AssertionFailedError;
|
||||
import junit.framework.TestCase;
|
||||
@ -32,67 +36,95 @@ import junit.framework.TestCase;
|
||||
public final class TestBoundSheetRecord extends TestCase {
|
||||
|
||||
|
||||
public void testRecordLength() {
|
||||
BoundSheetRecord record = new BoundSheetRecord("Sheet1");
|
||||
assertEquals(18, record.getRecordSize());
|
||||
}
|
||||
public void testRecordLength() {
|
||||
BoundSheetRecord record = new BoundSheetRecord("Sheet1");
|
||||
assertEquals(18, record.getRecordSize());
|
||||
}
|
||||
|
||||
public void testWideRecordLength() {
|
||||
BoundSheetRecord record = new BoundSheetRecord("Sheet\u20ac");
|
||||
assertEquals(24, record.getRecordSize());
|
||||
}
|
||||
public void testWideRecordLength() {
|
||||
BoundSheetRecord record = new BoundSheetRecord("Sheet\u20ac");
|
||||
assertEquals(24, record.getRecordSize());
|
||||
}
|
||||
|
||||
public void testName() {
|
||||
BoundSheetRecord record = new BoundSheetRecord("1234567890223456789032345678904");
|
||||
public void testName() {
|
||||
BoundSheetRecord record = new BoundSheetRecord("1234567890223456789032345678904");
|
||||
|
||||
try {
|
||||
record.setSheetname("s//*s");
|
||||
throw new AssertionFailedError("Should have thrown IllegalArgumentException, but didnt");
|
||||
} catch (IllegalArgumentException e) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
public void testDeserializeUnicode() {
|
||||
|
||||
byte[] data = {
|
||||
// (byte)0x85, 0x00, // sid
|
||||
// 0x1a, 0x00, // length
|
||||
0x3C, 0x09, 0x00, 0x00, // bof
|
||||
0x00, 0x00, // flags
|
||||
0x09, // len( str )
|
||||
0x01, // unicode
|
||||
// <str>
|
||||
0x21, 0x04, 0x42, 0x04, 0x40, 0x04,
|
||||
0x30, 0x04, 0x3D, 0x04, 0x38, 0x04,
|
||||
0x47, 0x04, 0x3A, 0x04, 0x30, 0x04
|
||||
// </str>
|
||||
};
|
||||
|
||||
RecordInputStream in = TestcaseRecordInputStream.create(BoundSheetRecord.sid, data);
|
||||
try {
|
||||
record.setSheetname("s//*s");
|
||||
throw new AssertionFailedError("Should have thrown IllegalArgumentException, but didnt");
|
||||
} catch (IllegalArgumentException e) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
public void testDeserializeUnicode() {
|
||||
|
||||
byte[] data = HexRead.readFromString(""
|
||||
+ "85 00 1A 00" // sid, length
|
||||
+ "3C 09 00 00" // bof
|
||||
+ "00 00"// flags
|
||||
+ "09 01" // str-len. unicode flag
|
||||
// string data
|
||||
+ "21 04 42 04 40 04"
|
||||
+ "30 04 3D 04 38 04"
|
||||
+ "47 04 3A 04 30 04"
|
||||
);
|
||||
|
||||
RecordInputStream in = TestcaseRecordInputStream.create(data);
|
||||
BoundSheetRecord bsr = new BoundSheetRecord(in);
|
||||
// sheet name is unicode Russian for 'minor page'
|
||||
assertEquals("\u0421\u0442\u0440\u0430\u043D\u0438\u0447\u043A\u0430", bsr.getSheetname());
|
||||
|
||||
|
||||
byte[] data2 = bsr.serialize();
|
||||
assertTrue(Arrays.equals(data, data2));
|
||||
}
|
||||
|
||||
public void testOrdering() {
|
||||
BoundSheetRecord bs1 = new BoundSheetRecord("SheetB");
|
||||
BoundSheetRecord bs2 = new BoundSheetRecord("SheetC");
|
||||
BoundSheetRecord bs3 = new BoundSheetRecord("SheetA");
|
||||
bs1.setPositionOfBof(11);
|
||||
bs2.setPositionOfBof(33);
|
||||
bs3.setPositionOfBof(22);
|
||||
public void testOrdering() {
|
||||
BoundSheetRecord bs1 = new BoundSheetRecord("SheetB");
|
||||
BoundSheetRecord bs2 = new BoundSheetRecord("SheetC");
|
||||
BoundSheetRecord bs3 = new BoundSheetRecord("SheetA");
|
||||
bs1.setPositionOfBof(11);
|
||||
bs2.setPositionOfBof(33);
|
||||
bs3.setPositionOfBof(22);
|
||||
|
||||
ArrayList l = new ArrayList();
|
||||
l.add(bs1);
|
||||
l.add(bs2);
|
||||
l.add(bs3);
|
||||
List<BoundSheetRecord> l = new ArrayList<BoundSheetRecord>();
|
||||
l.add(bs1);
|
||||
l.add(bs2);
|
||||
l.add(bs3);
|
||||
|
||||
BoundSheetRecord[] r = BoundSheetRecord.orderByBofPosition(l);
|
||||
assertEquals(3, r.length);
|
||||
assertEquals(bs1, r[0]);
|
||||
assertEquals(bs3, r[1]);
|
||||
assertEquals(bs2, r[2]);
|
||||
}
|
||||
BoundSheetRecord[] r = BoundSheetRecord.orderByBofPosition(l);
|
||||
assertEquals(3, r.length);
|
||||
assertEquals(bs1, r[0]);
|
||||
assertEquals(bs3, r[1]);
|
||||
assertEquals(bs2, r[2]);
|
||||
}
|
||||
|
||||
public void testValidNames() {
|
||||
confirmValid("Sheet1", true);
|
||||
confirmValid("O'Brien's sales", true);
|
||||
confirmValid(" data # ", true);
|
||||
confirmValid("data $1.00", true);
|
||||
|
||||
confirmValid("data?", false);
|
||||
confirmValid("abc/def", false);
|
||||
confirmValid("data[0]", false);
|
||||
confirmValid("data*", false);
|
||||
confirmValid("abc\\def", false);
|
||||
confirmValid("'data", false);
|
||||
confirmValid("data'", false);
|
||||
}
|
||||
|
||||
private static void confirmValid(String sheetName, boolean expectedResult) {
|
||||
|
||||
try {
|
||||
new BoundSheetRecord(sheetName);
|
||||
if (!expectedResult) {
|
||||
throw new AssertionFailedError("Expected sheet name '" + sheetName + "' to be invalid");
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
if (expectedResult) {
|
||||
throw new AssertionFailedError("Expected sheet name '" + sheetName + "' to be valid");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,7 @@
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
==================================================================== */
|
||||
|
||||
|
||||
package org.apache.poi.hssf.util;
|
||||
|
||||
|
||||
@ -26,12 +26,12 @@ import org.apache.poi.ss.SpreadsheetVersion;
|
||||
|
||||
|
||||
public final class TestCellReference extends TestCase {
|
||||
|
||||
|
||||
public void testAbsRef1(){
|
||||
CellReference cf = new CellReference("$B$5");
|
||||
confirmCell(cf, null, 4, 1, true, true, "$B$5");
|
||||
}
|
||||
|
||||
|
||||
public void testAbsRef2(){
|
||||
CellReference cf = new CellReference(4,1,true,true);
|
||||
confirmCell(cf, null, 4, 1, true, true, "$B$5");
|
||||
@ -41,17 +41,17 @@ public final class TestCellReference extends TestCase {
|
||||
CellReference cf = new CellReference("B$5");
|
||||
confirmCell(cf, null, 4, 1, true, false, "B$5");
|
||||
}
|
||||
|
||||
|
||||
public void testAbsRef4(){
|
||||
CellReference cf = new CellReference(4,1,true,false);
|
||||
confirmCell(cf, null, 4, 1, true, false, "B$5");
|
||||
}
|
||||
|
||||
|
||||
public void testAbsRef5(){
|
||||
CellReference cf = new CellReference("$B5");
|
||||
confirmCell(cf, null, 4, 1, false, true, "$B5");
|
||||
}
|
||||
|
||||
|
||||
public void testAbsRef6(){
|
||||
CellReference cf = new CellReference(4,1,false,true);
|
||||
confirmCell(cf, null, 4, 1, false, true, "$B5");
|
||||
@ -61,27 +61,27 @@ public final class TestCellReference extends TestCase {
|
||||
CellReference cf = new CellReference("B5");
|
||||
confirmCell(cf, null, 4, 1, false, false, "B5");
|
||||
}
|
||||
|
||||
|
||||
public void testAbsRef8(){
|
||||
CellReference cf = new CellReference(4,1,false,false);
|
||||
confirmCell(cf, null, 4, 1, false, false, "B5");
|
||||
}
|
||||
|
||||
|
||||
public void testSpecialSheetNames() {
|
||||
CellReference cf;
|
||||
cf = new CellReference("'profit + loss'!A1");
|
||||
confirmCell(cf, "profit + loss", 0, 0, false, false, "'profit + loss'!A1");
|
||||
|
||||
|
||||
cf = new CellReference("'O''Brien''s Sales'!A1");
|
||||
confirmCell(cf, "O'Brien's Sales", 0, 0, false, false, "'O''Brien''s Sales'!A1");
|
||||
|
||||
|
||||
cf = new CellReference("'Amazing!'!A1");
|
||||
confirmCell(cf, "Amazing!", 0, 0, false, false, "'Amazing!'!A1");
|
||||
}
|
||||
|
||||
/* package */ static void confirmCell(CellReference cf, String expSheetName, int expRow,
|
||||
/* package */ static void confirmCell(CellReference cf, String expSheetName, int expRow,
|
||||
int expCol, boolean expIsRowAbs, boolean expIsColAbs, String expText) {
|
||||
|
||||
|
||||
assertEquals(expSheetName, cf.getSheetName());
|
||||
assertEquals("row index is wrong", expRow, cf.getRow());
|
||||
assertEquals("col index is wrong", expCol, cf.getCol());
|
||||
@ -103,9 +103,19 @@ public final class TestCellReference extends TestCase {
|
||||
confirmNameType("A.1", NameType.NAMED_RANGE);
|
||||
confirmNameType("A1.", NameType.NAMED_RANGE);
|
||||
}
|
||||
|
||||
private void confirmNameType(String ref, int expectedResult) {
|
||||
int actualResult = CellReference.classifyCellReference(ref, SpreadsheetVersion.EXCEL97);
|
||||
|
||||
public void testClassificationOfRowReferences(){
|
||||
confirmNameType("10", NameType.ROW);
|
||||
confirmNameType("$10", NameType.ROW);
|
||||
confirmNameType("65536", NameType.ROW);
|
||||
|
||||
confirmNameType("65537", NameType.BAD_CELL_OR_NAMED_RANGE);
|
||||
confirmNameType("$100000", NameType.BAD_CELL_OR_NAMED_RANGE);
|
||||
confirmNameType("$1$1", NameType.BAD_CELL_OR_NAMED_RANGE);
|
||||
}
|
||||
|
||||
private void confirmNameType(String ref, NameType expectedResult) {
|
||||
NameType actualResult = CellReference.classifyCellReference(ref, SpreadsheetVersion.EXCEL97);
|
||||
assertEquals(expectedResult, actualResult);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user