From 726fe10074f18b33c096f43a5f5beedacfddb02d Mon Sep 17 00:00:00 2001 From: Javen O'Neal Date: Thu, 12 Jan 2017 10:39:26 +0000 Subject: [PATCH] bug 60260: parse unicode sheet names git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1778418 13f79535-47bb-0310-9956-ffa450edef68 --- .../apache/poi/ss/formula/FormulaParser.java | 81 ++++++++++++------ .../poi/ss/formula/TestFormulaParser.java | 32 ++++++- .../usermodel/TestXSSFSheetShiftRows.java | 11 +++ .../poi/ss/usermodel/BaseTestNamedRange.java | 15 ++++ test-data/spreadsheet/unicodeSheetName.xlsx | Bin 0 -> 8924 bytes 5 files changed, 108 insertions(+), 31 deletions(-) create mode 100644 test-data/spreadsheet/unicodeSheetName.xlsx diff --git a/src/java/org/apache/poi/ss/formula/FormulaParser.java b/src/java/org/apache/poi/ss/formula/FormulaParser.java index 745836ef7..27499501f 100644 --- a/src/java/org/apache/poi/ss/formula/FormulaParser.java +++ b/src/java/org/apache/poi/ss/formula/FormulaParser.java @@ -96,7 +96,7 @@ public final class FormulaParser { private final static POILogger log = POILogFactory.getLogger(FormulaParser.class); private final String _formulaString; private final int _formulaLength; - /** points at the next character to be read (after the {@link #look} char) */ + /** points at the next character to be read (after the {@link #look} codepoint) */ private int _pointer; private ParseNode _rootNode; @@ -106,10 +106,10 @@ public final class FormulaParser { private final static char LF = '\n'; // Normally just XSSF /** - * Lookahead Character. + * Lookahead unicode codepoint * gets value '\0' when the input string is exhausted */ - private char look; + private int look; /** * Tracks whether the run of whitespace preceding "look" could be an @@ -226,20 +226,20 @@ public final class FormulaParser { throw new RuntimeException("too far"); } if (_pointer < _formulaLength) { - look=_formulaString.charAt(_pointer); + look=_formulaString.codePointAt(_pointer); } else { // Just return if so and reset 'look' to something to keep // SkipWhitespace from spinning look = (char)0; _inIntersection = false; } - _pointer++; - //System.out.println("Got char: "+ look); + _pointer += Character.charCount(look); + //System.out.println(new StringBuilder("Got char: ").appendCodePoint(look)).toString(); } private void resetPointer(int ptr) { _pointer = ptr; if (_pointer <= _formulaLength) { - look=_formulaString.charAt(_pointer-1); + look=_formulaString.codePointAt(_pointer - Character.charCount(look)); } else { // Just return if so and reset 'look' to something to keep // SkipWhitespace from spinning @@ -255,25 +255,32 @@ public final class FormulaParser { msg = "The specified formula '" + _formulaString + "' starts with an equals sign which is not allowed."; } else { - msg = "Parse error near char " + (_pointer-1) + " '" + look + "'" - + " in specified formula '" + _formulaString + "'. Expected " - + s; + msg = new StringBuilder("Parse error near char ") + .append(_pointer-1) //this is the codepoint index, not char index, which may be larger if there are multi-byte chars + .append(" '") + .appendCodePoint(look) + .append("'") + .append(" in specified formula '") + .append(_formulaString) + .append("'. Expected ") + .append(s) + .toString(); } return new FormulaParseException(msg); } /** Recognize an Alpha Character */ - private static boolean IsAlpha(char c) { + private static boolean IsAlpha(int c) { return Character.isLetter(c) || c == '$' || c=='_'; } /** Recognize a Decimal Digit */ - private static boolean IsDigit(char c) { + private static boolean IsDigit(int c) { return Character.isDigit(c); } /** Recognize White Space */ - private static boolean IsWhite( char c) { + private static boolean IsWhite(int c) { return c ==' ' || c== TAB || c == CR || c == LF; } @@ -289,9 +296,13 @@ public final class FormulaParser { * unchecked exception. This method does not consume whitespace (before or after the * matched character). */ - private void Match(char x) { + private void Match(int x) { if (look != x) { - throw expected("'" + x + "'"); + throw expected(new StringBuilder() + .append("'") + .appendCodePoint(x) + .append("'") + .toString()); } GetChar(); } @@ -301,7 +312,7 @@ public final class FormulaParser { StringBuilder value = new StringBuilder(); while (IsDigit(this.look)){ - value.append(this.look); + value.appendCodePoint(this.look); GetChar(); } return value.length() == 0 ? null : value.toString(); @@ -826,7 +837,7 @@ public final class FormulaParser { } StringBuilder name = new StringBuilder(); while (look!=']') { - name.append(look); + name.appendCodePoint(look); GetChar(); } Match(']'); @@ -914,7 +925,7 @@ public final class FormulaParser { throw expected("number, string, defined name, or data table"); } while (isValidDefinedNameChar(look)) { - sb.append(look); + sb.appendCodePoint(look); GetChar(); } SkipWhite(); @@ -923,13 +934,18 @@ public final class FormulaParser { } /** - * + * @param ch unicode codepoint * @return true if the specified character may be used in a defined name */ - private static boolean isValidDefinedNameChar(char ch) { + private static boolean isValidDefinedNameChar(int ch) { if (Character.isLetterOrDigit(ch)) { return true; } + // the sheet naming rules are vague on whether unicode characters are allowed + // assume they're allowed. + if (ch > 128) { + return true; + } switch (ch) { case '.': case '_': @@ -937,6 +953,7 @@ public final class FormulaParser { case '\\': // of all things return true; } + // includes special non-name control characters like ! $ : , ( ) [ ] and space return false; } @@ -1120,7 +1137,7 @@ public final class FormulaParser { StringBuilder sb = new StringBuilder(); GetChar(); while (look != ']') { - sb.append(look); + sb.appendCodePoint(look); GetChar(); } GetChar(); @@ -1148,7 +1165,7 @@ public final class FormulaParser { StringBuilder sb = new StringBuilder(); boolean done = look == '\''; while(!done) { - sb.append(look); + sb.appendCodePoint(look); GetChar(); if(look == '\'') { @@ -1176,7 +1193,7 @@ public final class FormulaParser { StringBuilder sb = new StringBuilder(); // can concatenate idens with dots while (isUnquotedSheetNameChar(look)) { - sb.append(look); + sb.appendCodePoint(look); GetChar(); } NameIdentifier iden = new NameIdentifier(sb.toString(), false); @@ -1214,11 +1231,17 @@ public final class FormulaParser { /** * very similar to {@link SheetNameFormatter#isSpecialChar(char)} + * @param ch unicode codepoint */ - private static boolean isUnquotedSheetNameChar(char ch) { + private static boolean isUnquotedSheetNameChar(int ch) { if(Character.isLetterOrDigit(ch)) { return true; } + // the sheet naming rules are vague on whether unicode characters are allowed + // assume they're allowed. + if (ch > 128) { + return true; + } switch(ch) { case '.': // dot is OK case '_': // underscore is OK @@ -1413,7 +1436,11 @@ public final class FormulaParser { } } - private static boolean isArgumentDelimiter(char ch) { + /** + * @param ch unicode codepoint + * + */ + private static boolean isArgumentDelimiter(int ch) { return ch == ',' || ch == ')'; } @@ -1754,7 +1781,7 @@ public final class FormulaParser { } StringBuilder sb = new StringBuilder(); while (Character.isLetterOrDigit(look) || look == '.') { - sb.append(look); + sb.appendCodePoint(look); GetChar(); } if (sb.length() < 1) { @@ -1819,7 +1846,7 @@ public final class FormulaParser { break; } } - token.append(look); + token.appendCodePoint(look); GetChar(); } return token.toString(); diff --git a/src/ooxml/testcases/org/apache/poi/ss/formula/TestFormulaParser.java b/src/ooxml/testcases/org/apache/poi/ss/formula/TestFormulaParser.java index 421913fe7..a9d739796 100644 --- a/src/ooxml/testcases/org/apache/poi/ss/formula/TestFormulaParser.java +++ b/src/ooxml/testcases/org/apache/poi/ss/formula/TestFormulaParser.java @@ -23,6 +23,8 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import java.io.IOException; + import org.apache.poi.hssf.usermodel.HSSFEvaluationWorkbook; import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.ss.formula.ptg.AbstractFunctionPtg; @@ -30,6 +32,7 @@ import org.apache.poi.ss.formula.ptg.NameXPxg; import org.apache.poi.ss.formula.ptg.Ptg; import org.apache.poi.ss.formula.ptg.Ref3DPxg; import org.apache.poi.ss.formula.ptg.StringPtg; +import org.apache.poi.util.IOUtils; import org.apache.poi.xssf.XSSFTestDataSamples; import org.apache.poi.xssf.usermodel.XSSFEvaluationWorkbook; import org.apache.poi.xssf.usermodel.XSSFWorkbook; @@ -55,16 +58,31 @@ public class TestFormulaParser { } } + private static void checkHSSFFormula(String formula) { + HSSFWorkbook wb = new HSSFWorkbook(); + FormulaParsingWorkbook workbook = HSSFEvaluationWorkbook.create(wb); + FormulaParser.parse(formula, workbook, FormulaType.CELL, 0); + IOUtils.closeQuietly(wb); + } + private static void checkXSSFFormula(String formula) { + XSSFWorkbook wb = new XSSFWorkbook(); + FormulaParsingWorkbook workbook = XSSFEvaluationWorkbook.create(wb); + FormulaParser.parse(formula, workbook, FormulaType.CELL, 0); + IOUtils.closeQuietly(wb); + } + private static void checkFormula(String formula) { + checkHSSFFormula(formula); + checkXSSFFormula(formula); + } + @Test public void testHSSFPassCase() { - FormulaParsingWorkbook workbook = HSSFEvaluationWorkbook.create(new HSSFWorkbook()); - FormulaParser.parse("Sheet1!1:65536", workbook, FormulaType.CELL, 0); + checkHSSFFormula("Sheet1!1:65536"); } @Test public void testXSSFWorksForOver65536() { - FormulaParsingWorkbook workbook = XSSFEvaluationWorkbook.create(new XSSFWorkbook()); - FormulaParser.parse("Sheet1!1:65537", workbook, FormulaType.CELL, 0); + checkXSSFFormula("Sheet1!1:65537"); } @Test @@ -203,4 +221,10 @@ public class TestFormulaParser { assertEquals("Column", 0, pxg.getColumn()); wb.close(); } + + // bug 60260 + @Test + public void testUnicodeSheetName() { + checkFormula("'Sheet\u30FB1'!A1:A6"); + } } diff --git a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFSheetShiftRows.java b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFSheetShiftRows.java index fa4c77fbf..84a2dfdbc 100644 --- a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFSheetShiftRows.java +++ b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFSheetShiftRows.java @@ -35,6 +35,7 @@ import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.ss.util.CellAddress; import org.apache.poi.ss.util.CellUtil; +import org.apache.poi.util.IOUtils; import org.apache.poi.xssf.XSSFITestDataProvider; import org.apache.poi.xssf.XSSFTestDataSamples; import org.apache.xmlbeans.impl.values.XmlValueDisconnectedException; @@ -449,4 +450,14 @@ public final class TestXSSFSheetShiftRows extends BaseTestSheetShiftRows { wb.close(); } + + // bug 60260: shift rows or rename a sheet containing a named range + // that refers to formula with a unicode (non-ASCII) sheet name formula + @Test + public void shiftRowsWithUnicodeNamedRange() throws IOException { + XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("unicodeSheetName.xlsx"); + XSSFSheet sheet = wb.getSheetAt(0); + sheet.shiftRows(1, 2, 3); + IOUtils.closeQuietly(wb); + } } diff --git a/src/testcases/org/apache/poi/ss/usermodel/BaseTestNamedRange.java b/src/testcases/org/apache/poi/ss/usermodel/BaseTestNamedRange.java index 1bb73f9f5..74b5a5fac 100644 --- a/src/testcases/org/apache/poi/ss/usermodel/BaseTestNamedRange.java +++ b/src/testcases/org/apache/poi/ss/usermodel/BaseTestNamedRange.java @@ -31,6 +31,7 @@ import java.util.List; import org.apache.poi.ss.ITestDataProvider; import org.apache.poi.ss.util.AreaReference; import org.apache.poi.ss.util.CellReference; +import org.apache.poi.util.IOUtils; import org.junit.Test; /** @@ -737,4 +738,18 @@ public abstract class BaseTestNamedRange { } } + + // bug 60260: renaming a sheet with a named range referring to a unicode (non-ASCII) sheet name + @Test + public void renameSheetWithNamedRangeReferringToUnicodeSheetName() { + Workbook wb = _testDataProvider.createWorkbook(); + wb.createSheet("Sheet\u30FB1"); + + Name name = wb.createName(); + name.setNameName("test_named_range"); + name.setRefersToFormula("'Sheet\u30FB201'!A1:A6"); + + wb.setSheetName(0, "Sheet 1"); + IOUtils.closeQuietly(wb); + } } diff --git a/test-data/spreadsheet/unicodeSheetName.xlsx b/test-data/spreadsheet/unicodeSheetName.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..8c0fa8c4d15fa1e7b62bbec3c0c402a90c7ac387 GIT binary patch literal 8924 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 literal 0 HcmV?d00001