From 82f6d9f9918318641fc3eb79769c215e101d61de Mon Sep 17 00:00:00 2001 From: Nick Burch Date: Thu, 10 Jul 2008 20:37:09 +0000 Subject: [PATCH] Merged revisions 638786-638802,638805-638811,638813-638814,638816-639230,639233-639241,639243-639253,639255-639486,639488-639601,639603-639835,639837-639917,639919-640056,640058-640710,640712-641156,641158-641184,641186-641795,641797-641798,641800-641933,641935-641963,641965-641966,641968-641995,641997-642230,642232-642562,642564-642565,642568-642570,642572-642573,642576-642736,642739-642877,642879,642881-642890,642892-642903,642905-642945,642947-643624,643626-643653,643655-643669,643671,643673-643830,643832-643833,643835-644342,644344-644472,644474-644508,644510-645347,645349-645351,645353-645559,645561-645565,645568-645951,645953-646193,646195-646311,646313-646404,646406-646665,646667-646853,646855-646869,646871-647151,647153-647185,647187-647277,647279-647566,647568-647573,647575,647578-647711,647714-647737,647739-647823,647825-648155,648157-648202,648204-648273,648275,648277-648302,648304-648333,648335-648588,648590-648622,648625-648673,648675-649141,649144,649146-649556,649558-649795,649799,649801-649910,649912-649913,649915-650128,650131-650132,650134-650137,650140-650914,650916-651991,651993-652284,652286-652287,652289,652291,652293-652297,652299-652328,652330-652425,652427-652445,652447-652560,652562-652933,652935,652937-652993,652995-653116,653118-653124,653126-653483,653487-653519,653522-653550,653552-653607,653609-653667,653669-653674,653676-653814,653817-653830,653832-653891,653893-653944,653946-654055,654057-654355,654357-654365,654367-654648,654651-655215,655217-655277,655279-655281,655283-655911,655913-656212,656214,656216-656251,656253-656698,656700-656756,656758-656892,656894-657135,657137-657165,657168-657179,657181-657354,657356-657357,657359-657701,657703-657874,657876-658032,658034-658284,658286,658288-658301,658303-658307,658309-658321,658323-658335,658337-658348,658351,658353-658832,658834-658983,658985,658987-659066,659068-659402,659404-659428,659430-659451,659453-659454,659456-659461,659463-659477,659479-659524,659526-659571,659574,659576-660255,660257-660262,660264-660279,660281-660343,660345-660473,660475-660827,660829-660833,660835-660888,660890-663321,663323-663435,663437-663764,663766-663854,663856-664219,664221-664489,664494-664514,664516-668013,668015-668142,668144-668152,668154,668156-668256,668258,668260-669139,669141-669455,669457-669657,669659-669808,669810-670189,670191-671321,671323-672229,672231-672549,672551-672552,672554-672561,672563-672566,672568,672571-673049,673051-673852,673854-673862,673864-673986,673988-673996,673998-674347,674349-675673 via svnmerge from https://svn.apache.org:443/repos/asf/poi/trunk ........ r674891 | nick | 2008-07-08 18:31:36 +0100 (Tue, 08 Jul 2008) | 1 line Test to check that formula stuff is mostly working with zip codes ........ r674911 | josh | 2008-07-08 19:56:21 +0100 (Tue, 08 Jul 2008) | 1 line Fix bug #45338 - JDK 1.4/1.5 issue from r673997 ........ r674937 | josh | 2008-07-08 21:19:40 +0100 (Tue, 08 Jul 2008) | 1 line Hooked up new junit in suite (from bug 45338) ........ r674953 | josh | 2008-07-08 22:00:13 +0100 (Tue, 08 Jul 2008) | 1 line Fix for bug 45354 - Proper distinguishing of cell references and named ranges within formulas ........ r675079 | josh | 2008-07-09 02:45:33 +0100 (Wed, 09 Jul 2008) | 1 line Fix for bug 45348 - required tweaks to RVA formula logic ........ r675086 | josh | 2008-07-09 03:15:52 +0100 (Wed, 09 Jul 2008) | 2 lines Minor junit fix after r674953 (bug 45354). Hooked up loose tests in suite. ........ r675218 | josh | 2008-07-09 15:58:06 +0100 (Wed, 09 Jul 2008) | 1 line Fix for bug 45367 - fixed boundary case when row zero is the last row removed from the sheet ........ r675661 | nick | 2008-07-10 18:52:33 +0100 (Thu, 10 Jul 2008) | 1 line Apply some changes inspired by bug # 45373, which improve the performance of HSSFSheet.shiftRows ........ r675671 | nick | 2008-07-10 19:41:25 +0100 (Thu, 10 Jul 2008) | 1 line Fix bug #45365 - Handle more excel number formatting rules in FormatTrackingHSSFListener / XLS2CSVmra ........ git-svn-id: https://svn.apache.org/repos/asf/poi/branches/ooxml@675718 13f79535-47bb-0310-9956-ffa450edef68 --- src/documentation/content/xdocs/changes.xml | 5 + src/documentation/content/xdocs/status.xml | 5 + .../eventusermodel/examples/XLS2CSVmra.java | 48 +--- .../FormatTrackingHSSFListener.java | 62 ++++ .../apache/poi/hssf/model/FormulaParser.java | 25 +- .../hssf/model/OperandClassTransformer.java | 57 ++-- .../record/formula/SheetNameFormatter.java | 120 +++----- .../apache/poi/hssf/usermodel/HSSFRow.java | 13 + .../apache/poi/hssf/usermodel/HSSFSheet.java | 114 +++++--- .../poi/hssf/usermodel/HSSFWorkbook.java | 72 +++-- .../apache/poi/hssf/util/CellReference.java | 18 +- .../org/apache/poi/ss/util/CellReference.java | 264 ++++++++++++++---- .../org/apache/poi/hssf/HSSFTests.java | 2 + .../org/apache/poi/hssf/data/45365.xls | Bin 0 -> 51712 bytes .../org/apache/poi/hssf/data/testRVA.xls | Bin 32256 -> 32256 bytes .../AllEventUserModelTests.java | 38 +++ .../TestEventWorkbookBuilder.java | 4 +- .../TestFormatTrackingHSSFListener.java | 43 ++- .../poi/hssf/model/TestFormulaParser.java | 34 +++ .../model/TestOperandClassTransformer.java | 30 +- .../formula/TestSheetNameFormatter.java | 10 +- .../apache/poi/hssf/usermodel/TestBugs.java | 80 ++++++ .../poi/hssf/usermodel/TestHSSFSheet.java | 14 + .../poi/hssf/util/AllHSSFUtilTests.java | 15 +- .../poi/hssf/util/TestCellReference.java | 23 +- .../apache/poi/hssf/util/TestHSSFColor.java | 6 +- 26 files changed, 780 insertions(+), 322 deletions(-) create mode 100644 src/testcases/org/apache/poi/hssf/data/45365.xls create mode 100644 src/testcases/org/apache/poi/hssf/eventusermodel/AllEventUserModelTests.java diff --git a/src/documentation/content/xdocs/changes.xml b/src/documentation/content/xdocs/changes.xml index 46b55c3ce..f0c5d67ac 100644 --- a/src/documentation/content/xdocs/changes.xml +++ b/src/documentation/content/xdocs/changes.xml @@ -50,6 +50,11 @@ Created a common interface for handling Excel files, irrespective of if they are .xls or .xlsx + 45365 - Handle more excel number formatting rules in FormatTrackingHSSFListener / XLS2CSVmra + 45373 - Improve the performance of HSSFSheet.shiftRows + 45367 - Fixed bug when last row removed from sheet is row zero + 45348 - Tweaks to RVA formula logic + 45354 - Fixed recognition of named ranges within formulas 45338 - Fix HSSFWorkbook to give you the same HSSFFont every time, and then fix it to find newly added fonts 45336 - Fix HSSFColor.getTripletHash() 45334 - Fixed formula parser to handle dots in identifiers diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml index 31427e939..693fc42e2 100644 --- a/src/documentation/content/xdocs/status.xml +++ b/src/documentation/content/xdocs/status.xml @@ -47,6 +47,11 @@ Created a common interface for handling Excel files, irrespective of if they are .xls or .xlsx + 45365 - Handle more excel number formatting rules in FormatTrackingHSSFListener / XLS2CSVmra + 45373 - Improve the performance of HSSFSheet.shiftRows + 45367 - Fixed bug when last row removed from sheet is row zero + 45348 - Tweaks to RVA formula logic + 45354 - Fixed recognition of named ranges within formulas 45338 - Fix HSSFWorkbook to give you the same HSSFFont every time, and then fix it to find newly added fonts 45336 - Fix HSSFColor.getTripletHash() 45334 - Fixed formula parser to handle dots in identifiers diff --git a/src/examples/src/org/apache/poi/hssf/eventusermodel/examples/XLS2CSVmra.java b/src/examples/src/org/apache/poi/hssf/eventusermodel/examples/XLS2CSVmra.java index 1c9b22035..632eacf5c 100644 --- a/src/examples/src/org/apache/poi/hssf/eventusermodel/examples/XLS2CSVmra.java +++ b/src/examples/src/org/apache/poi/hssf/eventusermodel/examples/XLS2CSVmra.java @@ -20,10 +20,6 @@ import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.PrintStream; -import java.text.DateFormat; -import java.text.DecimalFormat; -import java.text.SimpleDateFormat; -import java.util.Date; import org.apache.poi.hssf.eventusermodel.FormatTrackingHSSFListener; import org.apache.poi.hssf.eventusermodel.HSSFEventFactory; @@ -37,7 +33,6 @@ import org.apache.poi.hssf.model.FormulaParser; import org.apache.poi.hssf.record.BOFRecord; import org.apache.poi.hssf.record.BlankRecord; import org.apache.poi.hssf.record.BoolErrRecord; -import org.apache.poi.hssf.record.CellValueRecordInterface; import org.apache.poi.hssf.record.FormulaRecord; import org.apache.poi.hssf.record.LabelRecord; import org.apache.poi.hssf.record.LabelSSTRecord; @@ -47,7 +42,6 @@ import org.apache.poi.hssf.record.RKRecord; import org.apache.poi.hssf.record.Record; import org.apache.poi.hssf.record.SSTRecord; import org.apache.poi.hssf.record.StringRecord; -import org.apache.poi.hssf.usermodel.HSSFDateUtil; import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.poifs.filesystem.POIFSFileSystem; @@ -180,7 +174,7 @@ public class XLS2CSVmra implements HSSFListener { nextRow = frec.getRow(); nextColumn = frec.getColumn(); } else { - thisStr = formatNumberDateCell(frec, frec.getValue()); + thisStr = formatListener.formatNumberDateCell(frec); } } else { thisStr = '"' + @@ -231,7 +225,7 @@ public class XLS2CSVmra implements HSSFListener { thisColumn = numrec.getColumn(); // Format - thisStr = formatNumberDateCell(numrec, numrec.getValue()); + thisStr = formatListener.formatNumberDateCell(numrec); break; case RKRecord.sid: RKRecord rkrec = (RKRecord) record; @@ -290,44 +284,6 @@ public class XLS2CSVmra implements HSSFListener { } } - /** - * Formats a number or date cell, be that a real number, or the - * answer to a formula - */ - private String formatNumberDateCell(CellValueRecordInterface cell, double value) { - // Get the built in format, if there is one - int formatIndex = formatListener.getFormatIndex(cell); - String formatString = formatListener.getFormatString(cell); - - if(formatString == null) { - return Double.toString(value); - } else { - // Is it a date? - if(HSSFDateUtil.isADateFormat(formatIndex,formatString) && - HSSFDateUtil.isValidExcelDate(value)) { - // Java wants M not m for month - formatString = formatString.replace('m','M'); - // Change \- into -, if it's there - formatString = formatString.replaceAll("\\\\-","-"); - - // Format as a date - Date d = HSSFDateUtil.getJavaDate(value, false); - DateFormat df = new SimpleDateFormat(formatString); - return df.format(d); - } else { - if(formatString == "General") { - // Some sort of wierd default - return Double.toString(value); - } - - // Format as a number - DecimalFormat df = new DecimalFormat(formatString); - return df.format(value); - } - } - } - - public static void main(String[] args) throws Exception { if(args.length < 1) { System.err.println("Use:"); diff --git a/src/java/org/apache/poi/hssf/eventusermodel/FormatTrackingHSSFListener.java b/src/java/org/apache/poi/hssf/eventusermodel/FormatTrackingHSSFListener.java index b88143713..5a84f4564 100644 --- a/src/java/org/apache/poi/hssf/eventusermodel/FormatTrackingHSSFListener.java +++ b/src/java/org/apache/poi/hssf/eventusermodel/FormatTrackingHSSFListener.java @@ -16,7 +16,11 @@ ==================================================================== */ package org.apache.poi.hssf.eventusermodel; +import java.text.DateFormat; +import java.text.DecimalFormat; +import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Date; import java.util.Hashtable; import java.util.List; import java.util.Map; @@ -24,8 +28,11 @@ import java.util.Map; import org.apache.poi.hssf.record.CellValueRecordInterface; import org.apache.poi.hssf.record.ExtendedFormatRecord; import org.apache.poi.hssf.record.FormatRecord; +import org.apache.poi.hssf.record.FormulaRecord; +import org.apache.poi.hssf.record.NumberRecord; import org.apache.poi.hssf.record.Record; import org.apache.poi.hssf.usermodel.HSSFDataFormat; +import org.apache.poi.hssf.usermodel.HSSFDateUtil; /** * A proxy HSSFListener that keeps track of the document @@ -69,6 +76,61 @@ public class FormatTrackingHSSFListener implements HSSFListener { } } + /** + * Formats the given numeric of date Cell's contents + * as a String, in as close as we can to the way + * that Excel would do so. + * Uses the various format records to manage this. + * + * TODO - move this to a central class in such a + * way that hssf.usermodel can make use of it too + */ + public String formatNumberDateCell(CellValueRecordInterface cell) { + double value; + if(cell instanceof NumberRecord) { + value = ((NumberRecord)cell).getValue(); + } else if(cell instanceof FormulaRecord) { + value = ((FormulaRecord)cell).getValue(); + } else { + throw new IllegalArgumentException("Unsupported CellValue Record passed in " + cell); + } + + // Get the built in format, if there is one + int formatIndex = getFormatIndex(cell); + String formatString = getFormatString(cell); + + if(formatString == null) { + return Double.toString(value); + } else { + // Is it a date? + if(HSSFDateUtil.isADateFormat(formatIndex,formatString) && + HSSFDateUtil.isValidExcelDate(value)) { + // Java wants M not m for month + formatString = formatString.replace('m','M'); + // Change \- into -, if it's there + formatString = formatString.replaceAll("\\\\-","-"); + + // Format as a date + Date d = HSSFDateUtil.getJavaDate(value, false); + DateFormat df = new SimpleDateFormat(formatString); + return df.format(d); + } else { + if(formatString == "General") { + // Some sort of wierd default + return Double.toString(value); + } + if(formatString == "0.00E+00") { + // This seems to mean output as a normal double + return Double.toString(value); + } + + // Format as a number + DecimalFormat df = new DecimalFormat(formatString); + return df.format(value); + } + } + } + /** * Returns the format string, eg $##.##, for the * given number format index. diff --git a/src/java/org/apache/poi/hssf/model/FormulaParser.java b/src/java/org/apache/poi/hssf/model/FormulaParser.java index 2cc2a8053..afe7de239 100644 --- a/src/java/org/apache/poi/hssf/model/FormulaParser.java +++ b/src/java/org/apache/poi/hssf/model/FormulaParser.java @@ -29,6 +29,7 @@ import org.apache.poi.hssf.record.formula.function.FunctionMetadataRegistry; import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.hssf.util.AreaReference; import org.apache.poi.hssf.util.CellReference; +import org.apache.poi.hssf.util.CellReference.NameType; /** * This class parses a formula string into a List of tokens in RPN order. @@ -293,9 +294,14 @@ public final class FormulaParser { // This can be either a cell ref or a named range // Try to spot which it is - if (isValidCellReference(name)) { + int nameType = CellReference.classifyCellReference(name); + if (nameType == NameType.CELL) { return new RefPtg(name); } + if (nameType != NameType.NAMED_RANGE) { + new FormulaParseException("Name '" + name + + "' does not look like a cell reference or named range"); + } for(int i = 0; i < book.getNumberOfNames(); i++) { // named range name matching is case insensitive @@ -303,11 +309,12 @@ public final class FormulaParser { return new NamePtg(name, book); } } - throw new FormulaParseException("Found reference to named range \"" - + name + "\", but that named range wasn't defined!"); + throw new FormulaParseException("Specified named range '" + + name + "' does not exist in the current workbook."); } /** + * @param name an 'identifier' like string (i.e. contains alphanums, and dots) * @return null if name cannot be split at a dot */ private AreaReference parseArea(String name) { @@ -323,6 +330,8 @@ public final class FormulaParser { return null; } } + // This expression is only valid as an area ref, if the LHS and RHS of the dot(s) are both + // cell refs. Otherwise, this expression must be a named range name String partA = name.substring(0, dotPos); if (!isValidCellReference(partA)) { return null; @@ -336,12 +345,14 @@ public final class FormulaParser { return new AreaReference(topLeft, bottomRight); } + /** + * @return true if the specified name is a valid cell reference + */ private static boolean isValidCellReference(String str) { - // TODO - exact rules for recognising cell references may be too complicated for regex - return CELL_REFERENCE_PATTERN.matcher(str).matches(); + return CellReference.classifyCellReference(str) == NameType.CELL; } - - + + /** * Note - Excel function names are 'case aware but not case sensitive'. This method may end * up creating a defined name record in the workbook if the specified name is not an internal diff --git a/src/java/org/apache/poi/hssf/model/OperandClassTransformer.java b/src/java/org/apache/poi/hssf/model/OperandClassTransformer.java index 5358324a3..07d2bd2fd 100644 --- a/src/java/org/apache/poi/hssf/model/OperandClassTransformer.java +++ b/src/java/org/apache/poi/hssf/model/OperandClassTransformer.java @@ -71,11 +71,16 @@ final class OperandClassTransformer { + _formulaType + ") not supported yet"); } - transformNode(rootNode, rootNodeOperandClass, false); + transformNode(rootNode, rootNodeOperandClass, false, false); } + /** + * @param callerForceArrayFlag true if one of the current node's parents is a + * function Ptg which has been changed from default 'V' to 'A' type (due to requirements on + * the function return value). + */ private void transformNode(ParseNode node, byte desiredOperandClass, - boolean callerForceArrayFlag) { + boolean callerForceArrayFlag, boolean isDirectChildOfValueOperator) { Ptg token = node.getToken(); ParseNode[] children = node.getChildren(); if (token instanceof ValueOperatorPtg || token instanceof ControlPtg) { @@ -84,7 +89,7 @@ final class OperandClassTransformer { // but any child nodes are processed according to desiredOperandClass and callerForceArrayFlag for (int i = 0; i < children.length; i++) { ParseNode child = children[i]; - transformNode(child, desiredOperandClass, callerForceArrayFlag); + transformNode(child, desiredOperandClass, callerForceArrayFlag, true); } return; } @@ -101,22 +106,34 @@ final class OperandClassTransformer { // nothing to do return; } - if (callerForceArrayFlag) { - switch (desiredOperandClass) { - case Ptg.CLASS_VALUE: - case Ptg.CLASS_ARRAY: - token.setClass(Ptg.CLASS_ARRAY); - break; - case Ptg.CLASS_REF: - token.setClass(Ptg.CLASS_REF); - break; - default: - throw new IllegalStateException("Unexpected operand class (" - + desiredOperandClass + ")"); - } - } else { - token.setClass(desiredOperandClass); - } + if (isDirectChildOfValueOperator) { + // As per OOO documentation Sec 3.2.4 "Token Class Transformation", "Step 1" + // All direct operands of value operators that are initially 'R' type will + // be converted to 'V' type. + if (token.getPtgClass() == Ptg.CLASS_REF) { + token.setClass(Ptg.CLASS_VALUE); + } + } + token.setClass(transformClass(token.getPtgClass(), desiredOperandClass, callerForceArrayFlag)); + } + + private byte transformClass(byte currentOperandClass, byte desiredOperandClass, + boolean callerForceArrayFlag) { + switch (desiredOperandClass) { + case Ptg.CLASS_VALUE: + if (!callerForceArrayFlag) { + return Ptg.CLASS_VALUE; + } + // else fall through + case Ptg.CLASS_ARRAY: + return Ptg.CLASS_ARRAY; + case Ptg.CLASS_REF: + if (!callerForceArrayFlag) { + return currentOperandClass; + } + return Ptg.CLASS_REF; + } + throw new IllegalStateException("Unexpected operand class (" + desiredOperandClass + ")"); } private void transformFunctionNode(AbstractFunctionPtg afp, ParseNode[] children, @@ -200,7 +217,7 @@ final class OperandClassTransformer { for (int i = 0; i < children.length; i++) { ParseNode child = children[i]; byte paramOperandClass = afp.getParameterClass(i); - transformNode(child, paramOperandClass, localForceArrayFlag); + transformNode(child, paramOperandClass, localForceArrayFlag, false); } } } diff --git a/src/java/org/apache/poi/hssf/record/formula/SheetNameFormatter.java b/src/java/org/apache/poi/hssf/record/formula/SheetNameFormatter.java index 8e47cbe7a..ace857da1 100755 --- a/src/java/org/apache/poi/hssf/record/formula/SheetNameFormatter.java +++ b/src/java/org/apache/poi/hssf/record/formula/SheetNameFormatter.java @@ -14,13 +14,14 @@ See the License for the specific language governing permissions and limitations under the License. ==================================================================== */ - package org.apache.poi.hssf.record.formula; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.apache.poi.hssf.util.CellReference; + /** * Formats sheet names for use in formula expressions. * @@ -28,14 +29,12 @@ import java.util.regex.Pattern; */ public final class SheetNameFormatter { - private static final String BIFF8_LAST_COLUMN = "IV"; - private static final int BIFF8_LAST_COLUMN_TEXT_LEN = BIFF8_LAST_COLUMN.length(); - private static final String BIFF8_LAST_ROW = String.valueOf(0x10000); - private static final int BIFF8_LAST_ROW_TEXT_LEN = BIFF8_LAST_ROW.length(); - private static final char DELIMITER = '\''; - private static final Pattern CELL_REF_PATTERN = Pattern.compile("([A-Za-z])+[0-9]+"); + /** + * Matches a single cell ref with no absolute ('$') markers + */ + private static final Pattern CELL_REF_PATTERN = Pattern.compile("([A-Za-z]+)([0-9]+)"); private SheetNameFormatter() { // no instances of this class @@ -105,27 +104,27 @@ public final class SheetNameFormatter { return false; } - /** - * @return true if the presence of the specified character in a sheet name would - * require the sheet name to be delimited in formulas. This includes every non-alphanumeric - * character besides underscore '_'. - */ - /* package */ static boolean isSpecialChar(char ch) { - // note - Character.isJavaIdentifierPart() would allow dollars '$' - if(Character.isLetterOrDigit(ch)) { - return false; - } - switch(ch) { - case '_': // underscore is ok - return false; - case '\n': - case '\r': - case '\t': - throw new RuntimeException("Illegal character (0x" - + Integer.toHexString(ch) + ") found in sheet name"); - } - return true; - } + /** + * @return true if the presence of the specified character in a sheet name would + * require the sheet name to be delimited in formulas. This includes every non-alphanumeric + * character besides underscore '_'. + */ + /* package */ static boolean isSpecialChar(char ch) { + // note - Character.isJavaIdentifierPart() would allow dollars '$' + if(Character.isLetterOrDigit(ch)) { + return false; + } + switch(ch) { + case '_': // underscore is ok + return false; + case '\n': + case '\r': + case '\t': + throw new RuntimeException("Illegal character (0x" + + Integer.toHexString(ch) + ") found in sheet name"); + } + return true; + } /** @@ -149,64 +148,11 @@ public final class SheetNameFormatter { *

* For better or worse this implementation attempts to replicate Excel's formula renderer. * Excel uses range checking on the apparent 'row' and 'column' components. Note however that - * the maximum sheet size varies across versions: - *

- *

- * - * - * - * - *
Version  File Format  Last Column  Last Row
97-2003BIFF8"IV" (2^8)65536 (2^14)
2007BIFF12"XFD" (2^14)1048576 (2^20)
- * POI currently targets BIFF8 (Excel 97-2003), so the following behaviour can be observed for - * this method: - *
- * - * - * - * - * - * - * - * - * - * - * - *
Input           Result 
"A1", 1true
"a111", 1true
"A65536", 1true
"A65537", 1false
"iv1", 2true
"IW1", 2false
"AAA1", 3false
"a111", 1true
"Sheet1", 6false
+ * the maximum sheet size varies across versions. + * @see org.apache.poi.hssf.util.CellReference */ - /* package */ static boolean cellReferenceIsWithinRange(String rawSheetName, int numberOfLetters) { - - if(numberOfLetters > BIFF8_LAST_COLUMN_TEXT_LEN) { - // "Sheet1" case etc - return false; // that was easy - } - int nDigits = rawSheetName.length() - numberOfLetters; - if(nDigits > BIFF8_LAST_ROW_TEXT_LEN) { - return false; - } - if(numberOfLetters == BIFF8_LAST_COLUMN_TEXT_LEN) { - String colStr = rawSheetName.substring(0, BIFF8_LAST_COLUMN_TEXT_LEN).toUpperCase(); - if(colStr.compareTo(BIFF8_LAST_COLUMN) > 0) { - return false; - } - } else { - // apparent column name has less chars than max - // no need to check range - } - - if(nDigits == BIFF8_LAST_ROW_TEXT_LEN) { - String colStr = rawSheetName.substring(numberOfLetters); - // ASCII comparison is valid if digit count is same - if(colStr.compareTo(BIFF8_LAST_ROW) > 0) { - return false; - } - } else { - // apparent row has less chars than max - // no need to check range - } - - return true; + /* package */ static boolean cellReferenceIsWithinRange(String lettersPrefix, String numbersSuffix) { + return CellReference.cellReferenceIsWithinRange(lettersPrefix, numbersSuffix); } /** @@ -239,7 +185,7 @@ public final class SheetNameFormatter { // rawSheetName == "Sheet1" gets this far. String lettersPrefix = matcher.group(1); - return cellReferenceIsWithinRange(rawSheetName, lettersPrefix.length()); + String numbersSuffix = matcher.group(2); + return cellReferenceIsWithinRange(lettersPrefix, numbersSuffix); } - } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFRow.java b/src/java/org/apache/poi/hssf/usermodel/HSSFRow.java index 3e95aaee4..0ddac1f6d 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFRow.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFRow.java @@ -187,6 +187,19 @@ public final class HSSFRow implements Comparable, Row { row.setFirstCol(findFirstCell(row.getFirstCol())); } } + + /** + * Removes all the cells from the row, and their + * records too. + */ + protected void removeAllCells() { + for(int i=0; i 0) - { + while (r == null && rownum > 0) { r = getRow(--rownum); } - if (r == null) - return -1; + if (r == null) { + return -1; + } return rownum; } @@ -1224,6 +1225,28 @@ public class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet * @param resetOriginalRowHeight whether to set the original row's height to the default */ public void shiftRows( int startRow, int endRow, int n, boolean copyRowHeight, boolean resetOriginalRowHeight) + { + shiftRows(startRow, endRow, n, copyRowHeight, resetOriginalRowHeight, true); + } + + /** + * Shifts rows between startRow and endRow n number of rows. + * If you use a negative number, it will shift rows up. + * Code ensures that rows don't wrap around + * + *

+ * Additionally shifts merged regions that are completely defined in these + * rows (ie. merged 2 cells on a row to be shifted). + *

+ * TODO Might want to add bounds checking here + * @param startRow the row to start shifting + * @param endRow the row to end shifting + * @param n the number of rows to shift + * @param copyRowHeight whether to copy the row height during the shift + * @param resetOriginalRowHeight whether to set the original row's height to the default + * @param moveComments whether to move comments at the same time as the cells they are attached to + */ + public void shiftRows( int startRow, int endRow, int n, boolean copyRowHeight, boolean resetOriginalRowHeight, boolean moveComments) { int s, e, inc; if ( n < 0 ) @@ -1250,44 +1273,55 @@ public class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet row2Replace = createRow( rowNum + n ); HSSFCell cell; + + // Remove all the old cells from the row we'll + // be writing too, before we start overwriting + // any cells. This avoids issues with cells + // changing type, and records not being correctly + // overwritten + row2Replace.removeAllCells(); + // If this row doesn't exist, nothing needs to + // be done for the now empty destination row + if (row == null) continue; // Nothing to do for this row + // Fetch the first and last columns of the + // row now, so we still have them to hand + // once we start removing cells + short firstCol = row.getFirstCellNum(); + short lastCol = row.getLastCellNum(); - - // Removes the cells before over writting them. - for ( short col = row2Replace.getFirstCellNum(); col <= row2Replace.getLastCellNum(); col++ ) - { - cell = row2Replace.getCell( col ); - if ( cell != null ) - row2Replace.removeCell( cell ); + // Fix up row heights if required + if (copyRowHeight) { + row2Replace.setHeight(row.getHeight()); + } + if (resetOriginalRowHeight) { + row.setHeight((short)0xff); } - if (row == null) continue; // Nothing to do for this row - else { - if (copyRowHeight) { - row2Replace.setHeight(row.getHeight()); - } - if (resetOriginalRowHeight) { - row.setHeight((short)0xff); - } - } - for ( short col = row.getFirstCellNum(); col <= row.getLastCellNum(); col++ ) - { - cell = row.getCell( col ); - if ( cell != null ) - { - row.removeCell( cell ); - CellValueRecordInterface cellRecord = cell.getCellValueRecord(); - cellRecord.setRow( rowNum + n ); - row2Replace.createCellFromRecord( cellRecord ); - sheet.addValueRecord( rowNum + n, cellRecord ); - } - - // move comments if exist (can exist even if cell is null) - HSSFComment comment = getCellComment(rowNum, col); - if (comment != null) { - comment.setRow(rowNum + n); - } + // Copy each cell from the source row to + // the destination row + for(Iterator cells = row.cellIterator(); cells.hasNext(); ) { + cell = (HSSFCell)cells.next(); + row.removeCell( cell ); + CellValueRecordInterface cellRecord = cell.getCellValueRecord(); + cellRecord.setRow( rowNum + n ); + row2Replace.createCellFromRecord( cellRecord ); + sheet.addValueRecord( rowNum + n, cellRecord ); + } + // Now zap all the cells in the source row + row.removeAllCells(); + + // Move comments from the source row to the + // destination row. Note that comments can + // exist for cells which are null + if(moveComments) { + for( short col = firstCol; col <= lastCol; col++ ) { + HSSFComment comment = getCellComment(rowNum, col); + if (comment != null) { + comment.setRow(rowNum + n); + } + } } } if ( endRow == lastrow || endRow + n > lastrow ) lastrow = Math.min( endRow + n, 65535 ); diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java b/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java index b80ccb790..09d66e1f6 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java @@ -376,28 +376,28 @@ public class HSSFWorkbook extends POIDocument implements org.apache.poi.ss.userm log.log(POILogger.DEBUG, "convertLabelRecords exit"); } - /** - * Retrieves the current policy on what to do when - * getting missing or blank cells from a row. - * The default is to return blank and null cells. - * {@link MissingCellPolicy} - */ - public MissingCellPolicy getMissingCellPolicy() { - return missingCellPolicy; - } + /** + * Retrieves the current policy on what to do when + * getting missing or blank cells from a row. + * The default is to return blank and null cells. + * {@link MissingCellPolicy} + */ + public MissingCellPolicy getMissingCellPolicy() { + return missingCellPolicy; + } - /** - * Sets the policy on what to do when - * getting missing or blank cells from a row. - * This will then apply to all calls to - * {@link Row.getCell()}. See - * {@link MissingCellPolicy} - */ - public void setMissingCellPolicy(MissingCellPolicy missingCellPolicy) { - this.missingCellPolicy = missingCellPolicy; - } + /** + * Sets the policy on what to do when + * getting missing or blank cells from a row. + * This will then apply to all calls to + * {@link HSSFRow.getCell()}. See + * {@link MissingCellPolicy} + */ + public void setMissingCellPolicy(MissingCellPolicy missingCellPolicy) { + this.missingCellPolicy = missingCellPolicy; + } - /** + /** * sets the order of appearance for a given sheet. * * @param sheetname the name of the sheet to reorder @@ -1041,11 +1041,11 @@ public class HSSFWorkbook extends POIDocument implements org.apache.poi.ss.userm String name, boolean italic, boolean strikeout, short typeOffset, byte underline) { - for (short i=0; i<=getNumberOfFonts(); i++) { - // Remember - there is no 4! - if(i == 4) continue; - - HSSFFont hssfFont = getFontAt(i); + for (short i=0; i<=getNumberOfFonts(); i++) { + // Remember - there is no 4! + if(i == 4) continue; + + HSSFFont hssfFont = getFontAt(i); if (hssfFont.getBoldweight() == boldWeight && hssfFont.getColor() == color && hssfFont.getFontHeight() == fontHeight @@ -1077,19 +1077,17 @@ public class HSSFWorkbook extends POIDocument implements org.apache.poi.ss.userm * @param idx index number * @return HSSFFont at the index */ + public HSSFFont getFontAt(short idx) { + if(fonts == null) fonts = new Hashtable(); + + // So we don't confuse users, give them back + // the same object every time, but create + // them lazily + Short sIdx = new Short(idx); + if(fonts.containsKey(sIdx)) { + return (HSSFFont)fonts.get(sIdx); + } - public HSSFFont getFontAt(short idx) - { - if(fonts == null) fonts = new Hashtable(); - - // So we don't confuse users, give them back - // the same object every time, but create - // them lazily - Short sIdx = Short.valueOf(idx); - if(fonts.containsKey(sIdx)) { - return (HSSFFont)fonts.get(sIdx); - } - FontRecord font = workbook.getFontRecordAt(idx); HSSFFont retval = new HSSFFont(idx, font); fonts.put(sIdx, retval); diff --git a/src/java/org/apache/poi/hssf/util/CellReference.java b/src/java/org/apache/poi/hssf/util/CellReference.java index c63e7bc99..0ddb0764e 100644 --- a/src/java/org/apache/poi/hssf/util/CellReference.java +++ b/src/java/org/apache/poi/hssf/util/CellReference.java @@ -25,6 +25,15 @@ package org.apache.poi.hssf.util; * @author Dennis Doubleday (patch to seperateRowColumns()) */ public final class CellReference extends org.apache.poi.ss.util.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 BAD_CELL_OR_NAMED_RANGE = -1; + } + /** * Create an cell ref from a string representation. Sheet names containing special characters should be * delimited and escaped as per normal syntax rules for formulas. @@ -33,8 +42,15 @@ public final class CellReference extends org.apache.poi.ss.util.CellReference { super(cellRef); } + public CellReference(int pRow, int pCol) { + super(pRow, pCol, true, true); + } + public CellReference(int pRow, short pCol) { + super(pRow, (int)pCol, true, true); + } + public CellReference(int pRow, int pCol, boolean pAbsRow, boolean pAbsCol) { - this(null, pRow, pCol, pAbsRow, pAbsCol); + super(null, pRow, pCol, pAbsRow, pAbsCol); } public CellReference(String pSheetName, int pRow, int pCol, boolean pAbsRow, boolean pAbsCol) { super(pSheetName, pRow, pCol, pAbsRow, pAbsCol); diff --git a/src/java/org/apache/poi/ss/util/CellReference.java b/src/java/org/apache/poi/ss/util/CellReference.java index 4f90311f3..b46cd88cc 100644 --- a/src/java/org/apache/poi/ss/util/CellReference.java +++ b/src/java/org/apache/poi/ss/util/CellReference.java @@ -17,17 +17,26 @@ package org.apache.poi.ss.util; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + import org.apache.poi.hssf.record.formula.SheetNameFormatter; /** - * Common convertion functions between Excel style A1, C27 style - * cell references, and POI usermodel style row=0, column=0 - * style references. - * Applys for both HSSF and XSSF. + * * @author Avik Sengupta * @author Dennis Doubleday (patch to seperateRowColumns()) */ 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 BAD_CELL_OR_NAMED_RANGE = -1; + } + /** The character ($) that signifies a row or column value is absolute instead of relative */ private static final char ABSOLUTE_REFERENCE_MARKER = '$'; /** The character (!) that separates sheet names from cell references */ @@ -35,6 +44,20 @@ public class CellReference { /** The character (') used to quote sheet names when they contain special characters */ private static final char SPECIAL_NAME_DELIMITER = '\''; + /** + * Matches a run of letters followed by a run of digits. The run of letters is group 1 and the + * run of digits is group 2. Each group may optionally be prefixed with a single '$'. + */ + private static final Pattern CELL_REF_PATTERN = Pattern.compile("\\$?([A-Za-z]+)\\$?([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). + */ + private static final Pattern NAMED_RANGE_NAME_PATTERN = Pattern.compile("[_A-Za-z][_.A-Za-z0-9]*"); + private static final String BIFF8_LAST_COLUMN = "IV"; + private static final int BIFF8_LAST_COLUMN_TEXT_LEN = BIFF8_LAST_COLUMN.length(); + private static final String BIFF8_LAST_ROW = String.valueOf(0x10000); + private static final int BIFF8_LAST_ROW_TEXT_LEN = BIFF8_LAST_ROW.length(); private final int _rowIndex; private final int _colIndex; @@ -70,13 +93,13 @@ public class CellReference { _rowIndex = Integer.parseInt(rowRef)-1; // -1 to convert 1-based to zero-based } - /** - * Creates a cell reference for the given row and cell. - * Assumes these references are relative - */ - public CellReference(int row, int col) { - this(row, col, false, false); + public CellReference(int pRow, int pCol) { + this(pRow, pCol, false, false); } + public CellReference(int pRow, short pCol) { + this(pRow, (int)pCol, false, false); + } + public CellReference(int pRow, int pCol, boolean pAbsRow, boolean pAbsCol) { this(null, pRow, pCol, pAbsRow, pAbsCol); } @@ -97,7 +120,7 @@ public class CellReference { } public int getRow(){return _rowIndex;} - public int getCol(){return _colIndex;} + public short getCol(){return (short) _colIndex;} public boolean isRowAbsolute(){return _isRowAbs;} public boolean isColAbsolute(){return _isColAbs;} /** @@ -111,27 +134,148 @@ public class CellReference { /** * takes in a column reference portion of a CellRef and converts it from * ALPHA-26 number format to 0-based base 10. - * ALPHA-26 goes A to Z, then AA to AZ, BA to BZ, ..., ZA to ZZ, - * AAA to AAZ, ABA to ABZ, ..., AZA to AZZ, BAA to BAZ etc */ private int convertColStringToNum(String ref) { - int lastIx = ref.length()-1; - int retval=0; - int pos = 0; - - for (int k = lastIx; k > -1; k--) { - char thechar = ref.charAt(k); - // Character.getNumericValue() returns the values - // 10-35 for the letter A-Z - int shift = (int)Math.pow(26, pos); - retval += (Character.getNumericValue(thechar)-9) * shift; - pos++; - } - return retval-1; + int lastIx = ref.length()-1; + int retval=0; + int pos = 0; + + for (int k = lastIx; k > -1; k--) { + char thechar = ref.charAt(k); + // Character.getNumericValue() returns the values + // 10-35 for the letter A-Z + int shift = (int)Math.pow(26, pos); + retval += (Character.getNumericValue(thechar)-9) * shift; + pos++; + } + return retval-1; } - /** + * Classifies an identifier as either a simple (2D) cell reference or a named range name + * @return one of the values from NameType + */ + public static int classifyCellReference(String str) { + int len = str.length(); + if (len < 1) { + throw new IllegalArgumentException("Empty string not allowed"); + } + char firstChar = str.charAt(0); + switch (firstChar) { + case ABSOLUTE_REFERENCE_MARKER: + case '.': + case '_': + break; + default: + if (!Character.isLetter(firstChar)) { + throw new IllegalArgumentException("Invalid first char (" + firstChar + + ") of cell reference or named range. Letter expected"); + } + } + if (!Character.isDigit(str.charAt(len-1))) { + // no digits at end of str + return validateNamedRangeName(str); + } + Matcher cellRefPatternMatcher = CELL_REF_PATTERN.matcher(str); + if (!cellRefPatternMatcher.matches()) { + return validateNamedRangeName(str); + } + String lettersGroup = cellRefPatternMatcher.group(1); + String digitsGroup = cellRefPatternMatcher.group(2); + if (cellReferenceIsWithinRange(lettersGroup, digitsGroup)) { + // valid cell reference + return NameType.CELL; + } + // If str looks like a cell reference, but is out of (row/col) range, it is a valid + // named range name + // This behaviour is a little weird. For example, "IW123" is a valid named range name + // because the column "IW" is beyond the maximum "IV". Note - this behaviour is version + // dependent. In Excel 2007, "IW123" is not a valid named range name. + if (str.indexOf(ABSOLUTE_REFERENCE_MARKER) >= 0) { + // Of course, named range names cannot have '$' + return NameType.BAD_CELL_OR_NAMED_RANGE; + } + return NameType.NAMED_RANGE; + } + + private static int validateNamedRangeName(String str) { + if (!NAMED_RANGE_NAME_PATTERN.matcher(str).matches()) { + return NameType.BAD_CELL_OR_NAMED_RANGE; + } + return NameType.NAMED_RANGE; + + } + + + /** + * Used to decide whether a name of the form "[A-Z]*[0-9]*" that appears in a formula can be + * interpreted as a cell reference. Names of that form can be also used for sheets and/or + * named ranges, and in those circumstances, the question of whether the potential cell + * reference is valid (in range) becomes important. + *

+ * Note - that the maximum sheet size varies across Excel versions: + *

+ *

+ * + * + * + * + *
Version  File Format  Last Column  Last Row
97-2003BIFF8"IV" (2^8)65536 (2^14)
2007BIFF12"XFD" (2^14)1048576 (2^20)
+ * POI currently targets BIFF8 (Excel 97-2003), so the following behaviour can be observed for + * this method: + *
+ * + * + * + * + * + * + * + * + * + * + * + *
Input           Result 
"A", "1"true
"a", "111"true
"A", "65536"true
"A", "65537"false
"iv", "1"true
"IW", "1"false
"AAA", "1"false
"a", "111"true
"Sheet", "1"false
+ * + * @param colStr a string of only letter characters + * @param rowStr a string of only digit characters + * @return true if the row and col parameters are within range of a BIFF8 spreadsheet. + */ + public static boolean cellReferenceIsWithinRange(String colStr, String rowStr) { + int numberOfLetters = colStr.length(); + if(numberOfLetters > BIFF8_LAST_COLUMN_TEXT_LEN) { + // "Sheet1" case etc + return false; // that was easy + } + int nDigits = rowStr.length(); + if(nDigits > BIFF8_LAST_ROW_TEXT_LEN) { + return false; + } + if(numberOfLetters == BIFF8_LAST_COLUMN_TEXT_LEN) { + if(colStr.toUpperCase().compareTo(BIFF8_LAST_COLUMN) > 0) { + return false; + } + } else { + // apparent column name has less chars than max + // no need to check range + } + + if(nDigits == BIFF8_LAST_ROW_TEXT_LEN) { + // ASCII comparison is valid if digit count is same + if(rowStr.compareTo(BIFF8_LAST_ROW) > 0) { + return false; + } + } else { + // apparent row has less chars than max + // no need to check range + } + + return true; + } + + /** * 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 * name still in ALPHA-26 number format. The third element is the row. @@ -212,24 +356,24 @@ public class CellReference { * eg column #3 -> D */ protected static String convertNumToColString(int col) { - // Excel counts column A as the 1st column, we - // treat it as the 0th one - int excelColNum = col + 1; - - String colRef = ""; - int colRemain = excelColNum; - - while(colRemain > 0) { - int thisPart = colRemain % 26; - if(thisPart == 0) { thisPart = 26; } - colRemain = (colRemain - thisPart) / 26; - - // The letter A is at 65 - char colChar = (char)(thisPart+64); - colRef = colChar + colRef; - } - - return colRef; + // Excel counts column A as the 1st column, we + // treat it as the 0th one + int excelColNum = col + 1; + + String colRef = ""; + int colRemain = excelColNum; + + while(colRemain > 0) { + int thisPart = colRemain % 26; + if(thisPart == 0) { thisPart = 26; } + colRemain = (colRemain - thisPart) / 26; + + // The letter A is at 65 + char colChar = (char)(thisPart+64); + colRef = colChar + colRef; + } + + return colRef; } /** @@ -260,21 +404,21 @@ public class CellReference { return sb.toString(); } - /** - * Returns the three parts of the cell reference, the - * Sheet name (or null if none supplied), the 1 based - * row number, and the A based column letter. - * This will not include any markers for absolute - * references, so use {@link #formatAsString()} - * to properly turn references into strings. - */ - public String[] getCellRefParts() { - return new String[] { - _sheetName, - Integer.toString(_rowIndex+1), - convertNumToColString(_colIndex) - }; - } + /** + * Returns the three parts of the cell reference, the + * Sheet name (or null if none supplied), the 1 based + * row number, and the A based column letter. + * This will not include any markers for absolute + * references, so use {@link #formatAsString()} + * to properly turn references into strings. + */ + public String[] getCellRefParts() { + return new String[] { + _sheetName, + Integer.toString(_rowIndex+1), + convertNumToColString(_colIndex) + }; + } /** * Appends cell reference with '$' markers for absolute values as required. diff --git a/src/testcases/org/apache/poi/hssf/HSSFTests.java b/src/testcases/org/apache/poi/hssf/HSSFTests.java index 0a4fa66af..ff15491d9 100644 --- a/src/testcases/org/apache/poi/hssf/HSSFTests.java +++ b/src/testcases/org/apache/poi/hssf/HSSFTests.java @@ -22,6 +22,7 @@ import junit.framework.TestSuite; import org.apache.poi.hssf.eventmodel.TestEventRecordFactory; import org.apache.poi.hssf.eventmodel.TestModelFactory; +import org.apache.poi.hssf.eventusermodel.AllEventUserModelTests; import org.apache.poi.hssf.model.AllModelTests; import org.apache.poi.hssf.record.AllRecordTests; import org.apache.poi.hssf.usermodel.AllUserModelTests; @@ -48,6 +49,7 @@ public final class HSSFTests { TestSuite suite = new TestSuite("Tests for org.apache.poi.hssf"); // $JUnit-BEGIN$ + suite.addTest(AllEventUserModelTests.suite()); suite.addTest(AllModelTests.suite()); suite.addTest(AllUserModelTests.suite()); suite.addTest(AllRecordTests.suite()); diff --git a/src/testcases/org/apache/poi/hssf/data/45365.xls b/src/testcases/org/apache/poi/hssf/data/45365.xls new file mode 100644 index 0000000000000000000000000000000000000000..fbf1d97c589d0c82c232b0578b4ced79d75c6b71 GIT binary patch literal 51712 zcmeHw2YggT*Zyo0lBEZT5D;+*Eg&WI^lTcTgd#-*f+U1cLqZ8nMHdS;M5HN2jR+!$ z2uN391x3`T*s!4n6hTBqv7oP#|9Q^2H<|3-4e;vweSH7lyZOz@+~>}ndFGUvxpVI( zhu*GL{P^YuN0rAXUiqu@C6!g6hTABQ#GhcLMoqtu*I3rgXXT2g97=^9F{DYc>0mQp)P5tQ0f z>OiR@rB0MOQ|dygE2T(EQIw)7#ZZc+6h|qZQUWC>r9?_el#(f>P)eoLjZzw=?v#2^ z>Pe{=rF2TYDfOY$mr_4U*HY?FX#k~xlm=0{j?!RC*HaooX(**(lrks{r*s1)#0*jv z<+HWp|7q9hukz`qfNYVf^62*zHH+9>{HHE?jLF%LE_2u+m2$IWRjQNsf4g|@rw48B zS3Wtnf_`VxITxIbK7*q+}(s+%z?jezLgl zJxB|qnG=u#HIDLm6;v8d?c3-);s+cmC|d5+A>*hLd0jqv0665Z1ul(GB(-n${bJjP zz7C0*RAoCwabP92?&>##UZc@GkWL;|eri*|OC*&#Mkzy0!Z`2vpZp{-hb$X7xT*0t z%H^otwwf4)296y!b#VTa2_y6KC#J0TDxb$4*(jMe+U}Y0q4-Ha#-uewNxPy(=*`}pj%b#te#!_fT znp&C?WJ{(NC(=+O_G?cQQEQ75xnx}_gAxH3EOCT->Dfy8I*yS2YMJ^sczBl(os--7 zED?LbI^r)-!bTGxdz8;sY&4BlTmloV;Y&W4+GyEup*pHmP)E6_BBq{pJwi&VSHbm- z6l`L@Acx8qr~^wNUp;LU5K2=YEqlVA2y4>1Nt1|}j5cPrQNUHShzixt4eOsXYPepY z5v{Q5mlbFmpqH5UTDVwe8VzV6Gy;vcaxjw-ZG=nNp>Z~>O&FaU z(Z)HVO{0L;w6>yE)`&J`#WJ)!@N(tjX#GWlr``||@MuervgQ&J${OrMi#S@gl{RQz zZM1J*%FXK6HB_syoD6F!dLGd-CL`QD$Y|Lpps`wmrKJhcdC^n3LSgDjtUb*_XiTq% zj!{($wN{0Bmh!00DB4v$g@xw`v|?(oWsGr{5zdClC({k% z=og9ZORq>!>#>T}8&9H>dsVrdm!)YCNDmNjZ#U)rvDv8n&kE32ywxD^m5 zk0H+tR?pymfC0KH23V-th+vr5ZdAKjvoaiGqbE+xa3uC_*ROY@fa>a5R4|cF;%uXU zK(&eDQ;lsDP*rWl?M4Ju1D~&=w%`tg4;uy8)pJ-^(NUv-YHBO)Nu(ehS6ACmr*TX` z4YeIND<(ilO|=8JE~N-T!D=U%mqCP8R=aTLLs+v$0iD!t_EFO`ZQ&*`lwq8<7=bx2)W$IdnEe z?ZMqwSl>K4Ct~zy!iTH96rr|ZbQ`t7(T03;I4#^9bYs<;QWF{@+ER+3oA#D;Gc=5D zT5{AVE)z~i6`E28QXn^+6kN62TYuK+A*Q)hU z)rYF8fTdiX7AOWjdi36__g<|6b}(LvW`ti#Xrlaz3h>7q2cDMl=REOsKJfKE@TV+s zn&k!e7zeN^^)qh0J#cJHm5&$ke;Pgof1meoD13xJjmA1oGlk&Tbw7b-qf*QE3IO}CsUW`3iHG615OW8S2&nO{cgkJ&!dZAI#z)0Ag4#uBMAAMhe&nx({LE7g>D&+v#sddBe& zwHS+Si~*%Cy=Kjt(n?Gp_~+E^8JKvEA%O|MB3qUBf!X$%9-f>)#VXNUB(^LmDJs3d zB)etHmJ((3!qg07MlVc`M`Iy>)5 zFyCIVlA`pAE5V|vtP(n~>Q3nx@1r|SE~XMV4^LS>D#0?UjEPU3R9p$xsu!%JD81rJ zu+S*0gbu8AQgJ0sFQyWCn(Et6utY6m;!`KIM}oD055;2Lcfm@E(kre6H$7#Q(1Ep1 zDz2p2#Z-c!vZC(bHl>V-Pn}d;3GQ7kSV>WO#g)(sRI7vztaVaxCCx9U5_#J0+r(J0 zUa&hkqp>jcT?y997p$Zxz2Zt7U!(kmra|CC#w%Kr^*Go9W=) zOjrfY$a*m0&_;6`tdXCX6kr0)5*J;RuwcO zH%{D4P|oP%#iy;C3Gr;Evv)IXD`-Y;t+*LG+2;GR*3Deu*-RJjW;#^R3~s&*7pR;w z`mb+3wr-{_n(0)rr(I3Wbh-#nD@xy4^p$lp^*o!g@U$isG^2Z3QTo@rc3L+R>e-Bi zr?se{8Qs&0(m(v_1?y(&dp2X?Y1dTHjP7Yg=|%2Otea`z*^Gs!wX2{R-P4NFw|{oh zx|u6Io3Ze;jukYcdseTSD@H*>XTGZvoKx`JkOPwUWe z%j2(FH{K`pJj zO6Sc)k9qT|k|3XRCB42k=SAyEo0L^b9_3kSV~Bk1B6@+m$8$^h#$IJ>4{?#!A@xpp8JR zj*!Q0p)ryK)1yt=E>6zM&7PKdZ;GS$SCn`BP>&2IkBxSZGfxUzK0yl3;pgb|$?W36IEQufV&kv&a$yiOx*& z5^QOiJ^>dnoGw7McU2~K(H>P+E~!m#%;vKZ^VJNhWE54_ovNHf`6+~tqlP9xk)+AW zbYDPQKWX{-6a+EltFkpH37~hF@osPVFufq)<#7qX+MSeACS*cY{Do(U3Txd@v}1lYY(YHZ+g%kR*$?%)5w9R zm3I-p5YiuBcb-HM1XnE^G)m}Eh3O=Cp7A$_vS51ip1PerE^k84C}S+^g@MMI^>Bx7qxlT07-Ui5Sd`7j1a zxozUPfu8S}{ar~9fl9+vxfH29tUOnnq9*%LWKE%gO{zjTR;TY3(4Bjq{3#Q&avkL4 zDw2v&`nqL))WVBX5^s&Q=TS_KP+wcOZFJ0T~`z^o~v&sz~7~@(bWW9{Vu<^|=a|K};UNPFZfR1=cXfJ<)-A z1Q)GIvjt7)1N#n4OdsIrpFd-fUo9H#=^6jvtSNaIb!DDOD9_!mDot5~snIDEs^-9J zHNRku!={*Tzh2GP_K+4(hilQ#K&oUUD_{WiVixCmlR7Y-Zwq_A%x4f8U8E(s{zpfd?gewS(4Y$Y(l=mAM(8s zhX0LV|BJbv@JA?xs+VkbN=l9S+NS=bRUIxtqJd&+!L-q%ODG`i>K7V@@up3-y)Grn z7@xYv?rB#dn_5BG3Jn8`d4$c=Ff>G?l<~cw;Bwy! zs_NGir}^_J$#}Z|OYg1JNAv}$ujeVX@d>3)?Z9^g@im%iBuCMks>s)3Z3UCE|6KRu zyS+#DOBlg;E5@&3c^e}x%%R4o8O;qe4yZYF<|TUXRlZ$#JD0wLW%JU~Clgv|`S8;G znS_>F{&CtgK#erc&`*GZR^(HhQ1*KzP2DTYXc#5Reu0&I1`!Lpn$7NGQ`xXRX)&hy6)FGwi`9m_W;?X)D6JdN`B(D$H#fqi{E z*~iDg;Fq2laq+^?3(s;C7+x59;8~8AT3#3w=2;F8JUlUYmnVjJ_QVj9o*2#hUKn)c zSq?Awd1BC@Cx&?S#A;h$bu2LWqvy3(SYQ}NJj>xBwkL*%xSkjuVtQhDi06snA(khG z=l-4;o_l*@c<$?oUG0JW#bbheD^}6l=}YOnjlUx+*qH?OTqmTRWiZPSmZ2;gvUIQv zW7(8tbC%&OTd{1-vMtL9mhD+~VA+vnCzhRAc467o8A{Ana!h~+I2Afe=-iZh|~L zDwt%*leI|}&8C0vWTayJ23%FRGcZy3AwIiPQq-#R^ijz4cS)+n zdmy_#4Y}o6$huo0Tf9%w{Vg-T^(5sTgT5!ZspVOcXU6{w;CGS> zUj7&GPLL7b{Xw$O{wGQ2=I_8zmi0elYZhv4UDX$sq#2K9WvrR$WF^37e59WunO{{ z)sP3DWMm!W>uVW*3UWUCN$ZWsH`@f6!TD92k?;N-WP`1c&oXj;t-%lDIiZU6lsOG0 zGABd2b0HmLA&W;rs&SA`#y4@9NnHNWOyI#cLT28?WmukJ>1OGe4P3E|ko?U+7O+&e zARl}yWFaG&Gl<_UbQ2b2@N4Mwr+E;Y5^3s-ydE_^`d~wXAby0*8=dp71v_pw5Pw2$ z`ZeRp<}Xb!kN`s355N0^tFvYrNF_qL4xD$k>*M_lB#@9jdlPT^<;O_|5=4l5WAna4 zde1NrJ0WKuT>Nvy)_V=4G9i=R?G(N|=qUrKLdfWdZcVGS*=rzG3CXmj*N-ebWFXZD zx#_o8Z~EZ%j|`+bAs@Vc$B0uWJ~NOSgj9RuvHYq_KQxevHIUkbth;M$LD1$C22zKRwFR5bzn%55frJp!_xRSeUmf|? zK&~LYjoeY=c;1~7=5t{ zAe$-~=VP46cV?+aj6`8u?QA^f6CqtmEK?Zi#xku;x+LF|k#v^5SPCL}mM%tIT!Smo z@P4dFz$uKc(fPG!L}V+@d+~EQf$RLFcBS!C-T7KCey$#Tt68X8<|1QFy-%@^9*@K_Nrr1<;DIO_t zBQw5?L2_2sh|%0DZLCqrjIeY`UdA1XXo+RcyEK0_VnUE4t|7)4UvQTJeiE%6`P`-Q zi{q*OqBc{%wX?vTMu)&&2k|NVRR_3%I3t%IM87k|UPG-9D1ZDjdd_Kh~i7wjCM-mYBx zKdQgK#m|gi>|%glB=h?l`lA`WRDYtyf6u-uiR&@>VMhh^M*WlgM00;jf2Mu0y90hQ zQ(Xr3^@tDbG=ZP2TrNNRV%H1&V%ffzf&M)F4?A$+Cv&{)n_V7KcBil#2!66gx(xK^ z;eXhf1iu8fugr3?mXQ^qNLl^MyeEkCL}ncs+hqimaY{x5T}u3kmrLH4l&iYX`23+t zQBTqUo$L3MD%5`V*z}_d=drWzpgwx;$~LyZ!Yr^R7FbgYteFMY+yZN1frVROEiJHC z78su4dG-wt!91}x7Fb>epiGVS%-`!0-x&XS;Yg$P>fUZchxaKYC(aEU>N?Sfm9O zWr0OoVEDqZXS?_ot0xv`fyG;32_6{saU6sp&UnYeSnc|XGr3okvl5Y)8fSWJNj&L@ zB6Mmu_(V0Gu>etIznej!+ zm?{?6?Jm~$_5m4jW%Sj@V8Nw)N5j4tub{sq)>atHiuETNm6E=+D7w_8u1m>#>st_W ze?jzzu@m|ee$oTcf#^oprLH;Yx9)A?-Qq*K6yK70SftE(dY+@xhylUPbtJ}d=uh@E zL?5CXUs}}t&s#&fC+K1Mw?sp_7wRLQjIMg` zz5K}-^Vzp#{E|_^7%OLe0#2NO*ybt}!9+~))c%d)<6CHc&OxJ|BR|z8C z;vI9|xcQV0YY=c3exg5#VqJftInkwzPvRwdY_gt=7?jaelZI|sPev^2d2Pqh7)cTg z{b4->{dIFfdh5?yi(y(x@s)}-C%Tay>at?o)F0Mn(4X*=h%-lUFczJ8>rZqn<@MMU z3y2i$ij*-=d{Lx^h}I3=3vS-$W1R>dvX6VIUoqGIiOd#aQ-$3LuDp+Dg# zn$Twv-3#?G&pUo~UlbkaQgo(!BqO@N8j&j{V-5eq`WgD`?t~P5gmK=X$%a~9&gz=G z7%%*H;}>gr=ui0RBcJ%MXj^9>z9>E_n$}mEy65S6@6piD00nPDKKVvsX zX1R*_c{7u-TDP*E_lgGP!@dSf)4sUJf&PS_Xhk%nYfiVOSW5b!uikt`v9}gQbKbc0 zLGVaJf4JX*{(3qgMJo~qdSvPL6}?JNyw_LyjOWcjd|zb6_BGd6xc7tpbbg{6(W0=? zy<0{^U+X__e!@~`BYM&iZ{ET$lwQ>q{q=J8P&W4f)*{|n#Tyh|=-tFL^=W9#(PokF z@N@hwDu9sme|>%a)Z>l#S$`K*iI9@m>Y+`)t8E~Gg!KI;^RLIU8yH9sAuqob68-Ga zrUqgsB;&fQj_z#{22z=jyx*og*06d91F1qtz`Br_2Kzf12;P^-SpRO_51;C4Ak_#N zwpC4h`pqZtTLMrF2&V8^;vVqhgr0x0bExLD4GLR5LUR?j&(99p&8ORlc zI14+D8!$P}KJidw$lS=oAIBSILJ2wd~Z76tT*=BFAP2&`A@ zi+hH&>sH@DV7-Zr?t1CZW)1^^_5S?VysAO9&6;7o5#RQ2^79I@URV0|Q<`dga_pd9 zhdUZq!Fqvr9Nzzst&4%cdYh()oOtThNCScOYMlEsA}Kb;Kw!Pcdqw_S_aU)NSNg6* zn%ewXob$K(CIai#?ep%R?Q=w7Rgce=G2ruz1F?YQ5MNjR{!eJ?LOTo zQ5MNj_G!QNZ+-EzL|G)gC!D4VmRI`Zs*w%2&yn;FRGOkVoIUioL|G)gWs|1b)ql17 zuTP1cBH2z~1y7#ZilPjD6-jSwrl~oxg+?8)QM)eBg!nxo5f(}BD5t4KgHMDm_)H=! zlHS`+Q^}KGUGb+8VUcX3hE=QF-r*6^KqS4-ou=kDIectDHL+SG+bA+5ept{6(OV?F z?M?3=$@4 zES)tG#SvEfcK7R#SCR;eqERtTeRWsQCZoS^Zj?cURUdd@@7oVcghjFCjy-cW|Dii2 z!lKx6^#{$J+;yx(SQJ~Xa{YtPy;09$)C0@S>mD-na5IUpD7M@$cP==4{vokk6kG1W zAH$cN*(nhg#gO1~dcQp}M@77Q2G~GT}G#bU$D|zsnL*Go3IEZ5FEw1xjtJSAW1lA+_ z2HmvAL}0y6dzbqM3>KS5vGsNh3icmtVL2 zF8&q8)_Z;R+%D=?S0RZZF~+0o>PL{&7~=CWHnSYfEHB)1TZ=Q|nv4 z7TxfuL{>E0=KWCzKJ9s*L>Bg;5m~)i>oFWzoBOI)4i1&bis8t*fBCZuANWNgD~89! zU7c<@x}-oND~7EXb!Pmoj*BF+@Rd~>nRrIN#F3z7BCv(G$s z-d`dshOOrs6>n(f1HhNEKJ&3GmysA0F zyb6)EKRe;RH3QA7V7+JdHd7@{#lK?MdI<%Iv)_0@<}N%jKpgcc7g>HBQ?YEPN#Qqd zuTi6j=dM_`(_JseEsZabxht0KH1MyUC-$F}D2rt~&1=7D{+EL#%3?Xn{67Bnv#Z~h zD2rt~9op8eu-+cAQ!LwQ+`1R8I@(a8ESBvQdHdcGMMjk2`@=NvZ_9nA9j%)X^|5TH z*a~ST2_B^iI$lA07Hj zbQjBZTGRW3xGha2%3?XnuDR3Mc5<{tSu97{*?@h0Hpff6#Il`Ud7%A_Hs45;#d4Gt zM!IhKB42zWmZL1;{JX2-jrl8)p`fr;IOpiALktdi_)84tim-#6lcf?~mojx4d)KL=a`6Gry0^c*{f(WyRT>jt%T9 zHjiWL-FR?u&BQFT45I9}P4%jLbgzjZ${HVU@j?7J^D0Ey`G*fbxj9b!D~_!o7cTLzMMpjmER}9K&;mw){xEHlD4w?3R0Ge*3<7e>_`n!u!dcc0Vl^j%VxD z{rq^?o+l*s;@NspBiqE&zo0Q{#=5a_lfr$q21@M3bL>q&8gycS5qtQ4dYa1VJiKqg zi(xEx4_i2B(%y98+y^}oy*57K}oyD{DZcg2C!+UR-y};V> z&Kv)*|9MSyFZK> zE?#19&6peSNtD=&=hzFle_6_l-6Zzn*?I?_7`U&FL!u;}WAC=enk^!Fry2U;)yIfxv0egOZ{wqjKJS_*mPugiH}iG@hC>Nn$U7t;hG584`O59DC0{GW&R9n1lHy zu=OTw>yt6RtHfRcTW{*w`%gZ)My!{>*1K=@XD74wN$e%C^%}>15;@W)v6sNsYnnRh z`d8kQ*h^sRt#8#S_>IrSG6@`eFV7fx_`W$}nFN{PYMtBv_F0L~1YY^KST$l;LZZZ8 z0$cB^4te7Xjd>`6t;g$%nu$g)5PPGFhW9$SOQI-&tw*DmYiT#Jc>-IH@7gDtWe|H0 zOsqd?={ysG^@h#7p;ecm=2fuXsjxe@oM~_N0(WPQ_fxCnyw=f)cQI1vA#tu>q4p-m zh4N3?_SjZaT1g4{JvO|H;Uq8ce72rwfhAdB$rf0O1(s@ob+f=QmUwc(x5+%Q9u`KG74d9X@`tLP9BBwUx?{&T|KjP>^efGqJrE6slN#r@?*3Sp7-q%#- zkVLi~@8Lcpb4VgvZ~DGolTKYN^FtzAZ&zfiuGfwc%OtY(_MIDdWJOIAf%P8z?&gHf z#$8gPtlye<-fGK~kv>sY09QVGLra^?A&G3gYFl!C-|aTbVEwjc{PUIfWQola*?M%J zKI?qCSq5{+?k7r)553t$Fo#_EQLC4Zg_~EwddIiVe&CN=#lI5SdSUY(JMiK=vVOyu zPS$gY^^(|nyplEU?eTv~)6{ztp4(jeTk-xRw%)9G|5IS=3?O>x8Jf#te4EzoAOfqKmR%)v6sxThaoV**!xRn>+#;lcM^NaY`tC^ zNB+1XUScnqtrt4XKBeC(u}m^sZ}OGZi`z6c5m;~XYm?U&WXSq0nXTt;x}@dTxnjL! zw%*W3gJz8Rmx*Bgmejm{RMl5Z1hF@$!p45{Nmk( z=2eKjuTRW<^7O;zRfxS7wUL;!QoKj-YJEx_sqn5Urk*j7EWR7wVKx^AZn+~d-x+yY3jr)_dGk@*mF-| z>%IM9>xb^YL1Hh3t@rI;-D2_%N$jPt^|DXj@lf$rv0e&W@6+@`irL&TsUgXd;EJXB$8M5ZK?u%$9v6sTutGE2yPVe@Y*h}Hqd;Qq?Chr;h7AYKix!=uRU*%1Sy%e_I zPp2CCZ+SrGJ*;Rk?}_zN*?L>!ZrndRLoAcZ)?2cz_m4XxCH7L;dI1LyZNB|piM>>| z-l?x^iy-7bb_<2o<#9k^}@56rM z+pT&~te48xd#U8QE!jIH_VCxa($wdUq1Sv8Ec0F}TW`)nzp9zXB=%C-dSCoG_nyGc z5__o}dj}IMcbvFLER)LCD?0O7UZLGYU_IX9GGZ^4t;aJ$wnRuOTW{ygnQzbe(L@k? z)h8uxZ~dH!Aoe!4T=4M_+aU|1Ao~qt zd@W@0Z;;MwP`=+l$Vt;7-HRd5Y=kV_3AyP2Wbs+ZL+2nf)z2hdzaZ~C#b;lKT(AW) zVm9Qa)U!_EhkG(`>`$oqZQoXxn3p#({#hySZ`D?~KO|;O;pcGL=&kB|3B{8y^KWa| znXl4hFz4m15b*UAd(GE|~aAdqan~UnMp!;i&VFysz)g zc$d`6d!P00r(QQWi}aPLg~+`>=Th}CuJjqAMimVDv9*z4tuR#>BF62u2_9}jB| zU{q}9t3CaCy{~eWFB!r$U#bi?^oKnK;^!28;xXb|GOkFy(zfIqa(m${#pmQIksTTD zq{+^l*ZoZ1v(d*Sd7H|~CFR{3^BrL9Z@~X_{fTaLZRu7J?dsa{){XQ{m(nYd4(8#F z2re?j&>!|%;D5qTY7~CL!&}N(@3!^Y^?Vq!(MNpAixgXlHFW7P_+j4%{x^_o5v>RV zUsBhcp7++GH=_4SKP$M7nEr=7DEQw%<|qCnW3==}dN2Br9?MzrwoZJNcvvLo*6n8Dvd4Sv`s#PJ~3{Kf0fmliALsM5BsOY&n5h%C23pNgsv?)>)p2ZDj|Yx zB2rcfx|9eLL`HSVo9j@S->{cU{M^D%agG0;6z}|}q`5x8zB2spI$_3nLH@U;=ns3` z@IT=vHWF)k&uOApeclsoiFfKgDm8d}g&y7Fd16z2?359-li`2ZZ-@U0KZypzzM3w} zab>(5;)De%_6l#i3wvR#N0=D{&|fJ1ivs577Je&8gS+LONkt^@tntnj(_!E*~Pl1Du@iGLL@Hr7IIw&b$(ByY5E5FdX^f z$zb4m9n$?Wc(@NCpLrZ?oNJ+7XCQRx-p+arfPL{a_`p?o8jNSU=1(C%8D4!6&xhOE zq%58WXHq{spSEUMU?VNCYzu6Z1(suhjkdtXSYTr8#edK>UTETdWegi%GU+zxqe7vz{7kl)MMXN++Gu|TMk`BmM662rLMKE8f8%g{%F z?|A~U=LX2+&5-k1?&3C<@Q7AzC-9ZrMs1!W;vro%FuJ*3b3+C}s^8I;D~0g^F9WH< z`SAV7ci3g{LtN6ai}}rC2J%;0H!MRr(OEic@qI>Omjl0k7368w+`Fu;W32T78tyrV8`0VW6sCfx%G=azFEG}7#&*t;B zu`IK>%(&eKKa5v&r5b|k9o*NI%;{m4DGvc3h1H6Bcm?F?#~^n;2|1GcJ&I*N?!!FJ z&)R@Q$4%RjlL z!)*6lwnZrGFOzFI!u7t%_4ef6f5rK}lE+Nz+6}K#``FvMbACSiUp;8i70ezP%XnYb ztBdtF?0JJ9#&Npd1%A%8AL5!pZ<18Ax%2|={ZGv1US_jmAIeO7m!$id!z7)fah3Z1 zBjB&{ocSux-A#EFQtu1GGnW;UEQ~!$(rNz^at-I*$h$gYo-AyJd9rvi@~+G;41QyT zpBwz#lTeE*=L3>z?kmXe+>iW&FCzZ|_q*oX$anY{GVoK#mSE$W2R6>z!NwVaw%s2x zr@N6ad;mNgDH6(KTu)q3Rj-;yc)r zWsMt>o9{Mhme1ck?^;W-^ex@{to3;5TxtW0fQgr zeL`KVzkxhX4rA~BnzeYEnWe14mEW(1{Fuj`P}tfv2rShezN$9!8wh>4?q_Dntcjt_ z;{*2T5a!{}JPN@~ZDg+JfVlg<2jmR%oAH{#4{H!Y-OSIGh!V!%cWKV~rYsw>bg+zI z83!rl9@!b#(hcA%Ed^j!Q2(bnw9vug`eJUJ@2g>owwc}UwP?|F7=Y$dNFsE zuzrUBWpM9(ajKY~aTV8~xB7STrvG6r5C8MU&zrHXIX(Y(*mz%S^22=v{7?8vIlWb# zpN@OiD2Q&szmqrZi+ddSpUzKjTjykOWjlJet;dRY-dEgs<8rOt82@m;1^*k)txEp# zkTSmD-VgqFx$?t(DE#ko<%fG>_@D4=#a5O%H;nT#qKcPD-obb{ODW%$^ARj1FS0!& z9eqeSE3$)DM)Kxgp27V&{O<-XA(5fSgq+o-epZhdJ*xhG-W*@BR{;Mr_%Tt>|aX`4{^Z@IU7F39m_aZ-Oo(`ED|O9r6V%V|nGgf$@8}OdyxPp4YGiEWeWc zR$R53$OFB89DQ89Af|B%qPGo$+ zehwY05kQT;5izi5u~Hk)8M|i5nf!ar3Pi>@5*9 zr|?sJrAXZyD$eUyT})o)9=)VDV$N^a=OTVCgCFvOT$HSsx18nr`Tsx3e}TFe~NuSb0c(la6M!&%K(m##T*}Xxy+-RarRNZssTqvzpX$vu$0JXz+V3@ zOBc(g94qHJzn$e_H1`p#`?c0$Xf>EwR9s zT3`=ZU=Leh%Pg?v7T5|4Y^4SEhzExII~QT(@hTI3@8a>;U4Xor0_n`-yf$(dP6i@n z+_R9E{|KdSMc(1!@|rAWd=ev@Sh`pivNX!i;d)pq#@#m?<2QU9^=5Lt-B`*lLk#Dm zSVpppW|_b;nI-Qj7=QJyJ0r5Q&=YytQ}Etdpzm(eS^B0uNquL**k9L3< z_{nH5vxdI^5ymwNBD0S^YsgH|nagxxDLVyvUiv9Ll%9)>Tlr^Nxk;1qwmlWRm%qMy2x!#jbTweMh_7N$FIsaiCB7QENpD@t* z$^0Z-L%Ck*gRqod>0HGEy42YVSK%#;b*_?cYuFcKBJnE}e!^Yqm9~Y4zJ3sGNiAYW zv6R@w3HHXmm!41JUWvboOyQEDfil~_$YFP7_+o=Beipd=!>55`;IrvKd~{ECI2*imsUx-?b-8ZIS$N$~{ztnSCY z^14Tv`$w3!NPjAe+tzFJ<|izrjI<#&iWD8|nihSFba4IN()+9+=3AqfLy4b5_?hRx zSns7bCuynT_z+JOZ;oJoy3`{^a6!VkWSHT9n9qrgvtoXtOJOY9()kHXsaNNu=XGW} z8y&aehqVRq%M^aXK-lO~U#EKO^6%I;%+MArCVe(-DEB8==g>J<#rpGRAa#k<$3HzU z`t#PS=uFP)<6kUum9wUOvF0Lvg~E^Xg8Z*b(I3`-#Ls=X^21t`_!VD{{P1mctc!`C z8p-dPVMR?wk99dQ%e-9q;U0nbxh_|JxL+ZDg_kQo z+&d9J_vOkD_i@Cp_;TfkdrIP`vYn8UzdWSGFYa%Nnd5ThhkI?}=e%6`;l7{vWnQlQ zum?f>T$d|9?1vD)!po80hsvpbu0#LP!mm(O{8NLz!E9Y3-mkXW9*7+jCnbESx9UE7 zFeN@TM3TCS-a7Yxln-s{QTm~Gb!-k*IV_l7F2|=9z4(`v-GK5PbEA3$yPdl;%=qrtg1u_zuUGp zi-nSIqBFcmwauP7eT==H8e`BvaX)1%p^twGy<=!z{(ti~Q~*sux#y`%PzzKTLXd3ws!0ju$&CCP>iF;H|DFcUmry9!JYt1x3co16^Rb_P?>jd5=?CrV znikJ}96?wiCHMx;J1GTF>P`v1l0gZnYWv1YIC@ zCQ(9vW>SJKT$JcRj=Gx?bhLmH^zjiTT}ROCfJsxv<462W;Ur zoT=u7w(x>mG$v>SC5*-+WHhcu^}rsm75XqZf69cZV{>u}rZN?D7pFkKPu9eoQI7su zd1G?QP8LuJ@W-b+C0uZh61d?2{jt+;w9$}$W9)I#Z;U^g^c#LQ);PvBA(TcWj_RJD z-9KmgxSScuv$At@df;!q$e1)Dbz;`I+yT=@j?c+1;95a#MB=o9vH4T@95)f_0j7+b zIw?167P`PqH^ljVQ>cmjX;Uql1FiZfBP_<{jTxAqpF4HH*!&rJee<$&O6><;!B;%d z-SqW7se>IUi9OPX5Dp9tjmn!gcFMSj_RgtnLiR7Ps)krn|lB=jqz12s(i>Ux{Fy&$jUSyd| zZT}*xz35{9m)p0|?CnnzJo@0Ub7U&F^t>BJin8&y@95FvvU6k@c65k{=;$aN4>)X+ j#*H40{}@33^9v(&+W{CU+D59Mqqa? zL@yC_QBW5V-2?{;N@3kae?Z<9(On36o#yHHzT4EIIPmiRp5OEQKA!W=t+Tmx_G;K- zO?-Oo8T!h{Eb~g`QRAn@EKqGv2r)Kn@jdJ#*P}K6Io(i51EY<@{l+D0h38ySez6m|bEnhXoGVfaUwOZ|~>U8n?nzdbYTW z-_*16a{fXW#oy#IZcW>y8*6$pqWGKnNdN5}kJK=Y95&4UDb?oyx=)-r-WTlF+q&Z+ z`&Jm%Lnh*UdN5*5Hv!jG{%r7Cu};#t1Hj55;LZ`FVd%WdbmP{=UZugNl)xj+@CnZV z=7hki|ABdp50~v0%j(Ox%9lsO_7%FACQu6S&!d$*kX&Mne@nJ8W>jCDVl2QPjJ3qg zR?LXMv8pH;B{L0FK+V`Ia#%s8Ad6WEQ?uZ`)UH`8YUN-~IaT(k0?Z-=Xh?`sT{Q78 zXhdV^Mvyo{Bqq;`Ry!byY!sn~CdgDXsCiMf6orV)6f3+kS|(bf1`I2$e0QqFK7c0r zgdILUma1cZKA)=N?^7pPgm2Jo|9Hu)$VRD(0^c77#{R-Y7oY4RMy{o7r`%6Oeu|v( z6dc-*B6mYi-&##!0%9>!!-X=?_+5%et{k9BPI|W_5(Vm{L~L$FuXMtR_IgKh$@iTc z2=TY$32#tJY?5;yVsR&)-B`HP;crgR%i@(23*M0AkONd_8sf#}o!m#92+vH6c*Bw- za4QZYvW+9$uII-l4|*e#QzAJtsNdpb1`Z;}xM;YTcqhhiF)@PFnQ5V1oMhk3_r33XZ}#mj?yg># z0@VY+%Aj%e)(w@l8SicnDMrWmp2_@t;|-`I6}DSn$JKlus064dRYOaubrzuh$qOuC zEKFt?tEb;n=i>Vvm>0AAsVF%mmj$Yz=Im8P&XhA1I1A_00!hi~YTFjN@xTT|>DTmm z)v7oVpo+ULRwp7|O1mkYcC(;F%jwJXgQJK*PiLG9f1x43-=h~^9tEo7dpR0-w4fIC z66ultYr6qk0tf*V3A7&@~ zA*m6(1%qhf`+;VMcMj9QT$jH|YMfFtk8nwo8%Y2WdNFq|5|)|@o)hzEks3db7Tz}^ z5f8=HN}ZV#9m#!*~PTM6<&Ca?Tm-f K&e-nDtoa{If4A-c diff --git a/src/testcases/org/apache/poi/hssf/eventusermodel/AllEventUserModelTests.java b/src/testcases/org/apache/poi/hssf/eventusermodel/AllEventUserModelTests.java new file mode 100644 index 000000000..41e3b8632 --- /dev/null +++ b/src/testcases/org/apache/poi/hssf/eventusermodel/AllEventUserModelTests.java @@ -0,0 +1,38 @@ +/* ==================================================================== + 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.hssf.eventusermodel; + +import junit.framework.Test; +import junit.framework.TestSuite; + +/** + * Collects all tests for org.apache.poi.hssf.eventusermodel. + * + * @author Josh Micich + */ +public class AllEventUserModelTests { + + public static Test suite() { + TestSuite result = new TestSuite(AllEventUserModelTests.class.getName()); + result.addTestSuite(TestEventWorkbookBuilder.class); + result.addTestSuite(TestFormatTrackingHSSFListener.class); + result.addTestSuite(TestHSSFEventFactory.class); + result.addTestSuite(TestMissingRecordAwareHSSFListener.class); + return result; + } +} diff --git a/src/testcases/org/apache/poi/hssf/eventusermodel/TestEventWorkbookBuilder.java b/src/testcases/org/apache/poi/hssf/eventusermodel/TestEventWorkbookBuilder.java index adf084331..215732deb 100644 --- a/src/testcases/org/apache/poi/hssf/eventusermodel/TestEventWorkbookBuilder.java +++ b/src/testcases/org/apache/poi/hssf/eventusermodel/TestEventWorkbookBuilder.java @@ -134,7 +134,7 @@ public final class TestEventWorkbookBuilder extends TestCase { fr = (FormulaRecord)mockListen._frecs.get(5); assertEquals(6, fr.getRow()); assertEquals(0, fr.getColumn()); - assertEquals("SUM('Sh3'!A1:A4)", FormulaParser.toFormulaString(stubHSSF, fr.getParsedExpression())); + assertEquals("SUM(Sh3!A1:A4)", FormulaParser.toFormulaString(stubHSSF, fr.getParsedExpression())); // Now, load via Usermodel and re-check @@ -142,7 +142,7 @@ public final class TestEventWorkbookBuilder extends TestCase { POIFSFileSystem fs = new POIFSFileSystem(is); HSSFWorkbook wb = new HSSFWorkbook(fs); assertEquals("Sheet1!A1", wb.getSheetAt(0).getRow(1).getCell(0).getCellFormula()); - assertEquals("SUM('Sh3'!A1:A4)", wb.getSheetAt(0).getRow(6).getCell(0).getCellFormula()); + assertEquals("SUM(Sh3!A1:A4)", wb.getSheetAt(0).getRow(6).getCell(0).getCellFormula()); } private static final class MockHSSFListener implements HSSFListener { diff --git a/src/testcases/org/apache/poi/hssf/eventusermodel/TestFormatTrackingHSSFListener.java b/src/testcases/org/apache/poi/hssf/eventusermodel/TestFormatTrackingHSSFListener.java index e52a3bc96..60f5d2ca7 100644 --- a/src/testcases/org/apache/poi/hssf/eventusermodel/TestFormatTrackingHSSFListener.java +++ b/src/testcases/org/apache/poi/hssf/eventusermodel/TestFormatTrackingHSSFListener.java @@ -24,6 +24,9 @@ import java.util.List; import junit.framework.TestCase; import org.apache.poi.hssf.HSSFTestDataSamples; +import org.apache.poi.hssf.record.CellValueRecordInterface; +import org.apache.poi.hssf.record.FormulaRecord; +import org.apache.poi.hssf.record.NumberRecord; import org.apache.poi.hssf.record.Record; import org.apache.poi.poifs.filesystem.POIFSFileSystem; /** @@ -31,16 +34,17 @@ import org.apache.poi.poifs.filesystem.POIFSFileSystem; */ public final class TestFormatTrackingHSSFListener extends TestCase { private FormatTrackingHSSFListener listener; - - public void setUp() { + private MockHSSFListener mockListen; + + private void processFile(String filename) throws Exception { HSSFRequest req = new HSSFRequest(); - MockHSSFListener mockListen = new MockHSSFListener(); + mockListen = new MockHSSFListener(); listener = new FormatTrackingHSSFListener(mockListen); req.addListenerForAllRecords(listener); HSSFEventFactory factory = new HSSFEventFactory(); try { - InputStream is = HSSFTestDataSamples.openSampleFileStream("MissingBits.xls"); + InputStream is = HSSFTestDataSamples.openSampleFileStream(filename); POIFSFileSystem fs = new POIFSFileSystem(is); factory.processWorkbookEvents(req, fs); } catch (IOException e) { @@ -49,11 +53,42 @@ public final class TestFormatTrackingHSSFListener extends TestCase { } public void testFormats() throws Exception { + processFile("MissingBits.xls"); + assertEquals("_(*#,##0_);_(*(#,##0);_(* \"-\"_);_(@_)", listener.getFormatString(41)); assertEquals("_($*#,##0_);_($*(#,##0);_($* \"-\"_);_(@_)", listener.getFormatString(42)); assertEquals("_(*#,##0.00_);_(*(#,##0.00);_(*\"-\"??_);_(@_)", listener.getFormatString(43)); } + /** + * Ensure that all number and formula records can be + * turned into strings without problems + */ + public void testTurnToString() throws Exception { + processFile("45365.xls"); + + for(int i=0; i 0); + } + } + + // TODO - test some specific format strings + } + private static final class MockHSSFListener implements HSSFListener { public MockHSSFListener() {} private final List _records = new ArrayList(); diff --git a/src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java b/src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java index 115a7e081..a21850887 100644 --- a/src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java +++ b/src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java @@ -46,6 +46,7 @@ import org.apache.poi.hssf.record.formula.SubtractPtg; import org.apache.poi.hssf.record.formula.UnaryMinusPtg; import org.apache.poi.hssf.record.formula.UnaryPlusPtg; import org.apache.poi.hssf.usermodel.HSSFCell; +import org.apache.poi.hssf.usermodel.HSSFName; import org.apache.poi.hssf.usermodel.HSSFRow; import org.apache.poi.hssf.usermodel.HSSFSheet; import org.apache.poi.hssf.usermodel.HSSFWorkbook; @@ -791,4 +792,37 @@ public final class TestFormulaParser extends TestCase { assertEquals("ERROR.TYPE", funcPtg.getName()); } + public void testNamedRangeThatLooksLikeCell() { + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sheet = wb.createSheet("Sheet1"); + HSSFName name = wb.createName(); + name.setReference("Sheet1!B1"); + name.setNameName("pfy1"); + + Ptg[] ptgs; + try { + ptgs = FormulaParser.parse("count(pfy1)", wb); + } catch (IllegalArgumentException e) { + if (e.getMessage().equals("Specified colIx (1012) is out of range")) { + throw new AssertionFailedError("Identified bug 45354"); + } + throw e; + } + assertEquals(2, ptgs.length); + assertEquals(NamePtg.class, ptgs[0].getClass()); + + HSSFCell cell = sheet.createRow(0).createCell((short)0); + cell.setCellFormula("count(pfy1)"); + assertEquals("COUNT(pfy1)", cell.getCellFormula()); + try { + cell.setCellFormula("count(pf1)"); + throw new AssertionFailedError("Expected formula parse execption"); + } catch (FormulaParseException e) { + if (!e.getMessage().equals("Specified named range 'pf1' does not exist in the current workbook.")) { + throw e; + } + } + cell.setCellFormula("count(fp1)"); // plain cell ref, col is in range + + } } diff --git a/src/testcases/org/apache/poi/hssf/model/TestOperandClassTransformer.java b/src/testcases/org/apache/poi/hssf/model/TestOperandClassTransformer.java index 90a5d8b13..47cfb574c 100644 --- a/src/testcases/org/apache/poi/hssf/model/TestOperandClassTransformer.java +++ b/src/testcases/org/apache/poi/hssf/model/TestOperandClassTransformer.java @@ -57,6 +57,31 @@ public final class TestOperandClassTransformer extends TestCase { confirmFuncClass(ptgs, 2, "INDEX", Ptg.CLASS_VALUE); } + /** + * Even though count expects args of type R, because A1 is a direct operand of a + * value operator it must get type V + */ + public void testDirectOperandOfValueOperator() { + String formula = "COUNT(A1*1)"; + Ptg[] ptgs = FormulaParser.parse(formula, null); + if (ptgs[0].getPtgClass() == Ptg.CLASS_REF) { + throw new AssertionFailedError("Identified bug 45348"); + } + + confirmTokenClass(ptgs, 0, Ptg.CLASS_VALUE); + confirmTokenClass(ptgs, 3, Ptg.CLASS_VALUE); + } + + /** + * A cell ref passed to a function expecting type V should be converted to type V + */ + public void testRtoV() { + + String formula = "lookup(A1, A3:A52, B3:B52)"; + Ptg[] ptgs = FormulaParser.parse(formula, null); + confirmTokenClass(ptgs, 0, Ptg.CLASS_VALUE); + } + public void testComplexIRR_bug45041() { String formula = "(1+IRR(SUMIF(A:A,ROW(INDIRECT(MIN(A:A)&\":\"&MAX(A:A))),B:B),0))^365-1"; Ptg[] ptgs = FormulaParser.parse(formula, null); @@ -89,8 +114,11 @@ public final class TestOperandClassTransformer extends TestCase { private void confirmTokenClass(Ptg[] ptgs, int i, byte operandClass) { Ptg ptg = ptgs[i]; + if (ptg.isBaseToken()) { + throw new AssertionFailedError("ptg[" + i + "] is a base token"); + } if (operandClass != ptg.getPtgClass()) { - throw new AssertionFailedError("Wrong operand class for function ptg (" + throw new AssertionFailedError("Wrong operand class for ptg (" + ptg.toString() + "). Expected " + getOperandClassName(operandClass) + " but got " + getOperandClassName(ptg.getPtgClass())); } diff --git a/src/testcases/org/apache/poi/hssf/record/formula/TestSheetNameFormatter.java b/src/testcases/org/apache/poi/hssf/record/formula/TestSheetNameFormatter.java index 768c42958..369c09583 100644 --- a/src/testcases/org/apache/poi/hssf/record/formula/TestSheetNameFormatter.java +++ b/src/testcases/org/apache/poi/hssf/record/formula/TestSheetNameFormatter.java @@ -14,7 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. ==================================================================== */ - package org.apache.poi.hssf.record.formula; @@ -72,11 +71,14 @@ public final class TestSheetNameFormatter extends TestCase { confirmCellNameMatch("aa1", true); confirmCellNameMatch("A1A", false); confirmCellNameMatch("A1A1", false); + confirmCellNameMatch("Sh3", false); confirmCellNameMatch("SALES20080101", false); // out of range } private static void confirmCellRange(String text, int numberOfPrefixLetters, boolean expected) { - assertEquals(expected, SheetNameFormatter.cellReferenceIsWithinRange(text, numberOfPrefixLetters)); + String prefix = text.substring(0, numberOfPrefixLetters); + String suffix = text.substring(numberOfPrefixLetters); + assertEquals(expected, SheetNameFormatter.cellReferenceIsWithinRange(prefix, suffix)); } /** @@ -93,7 +95,7 @@ public final class TestSheetNameFormatter extends TestCase { confirmCellRange("AAA1", 3, false); confirmCellRange("a111", 1, true); confirmCellRange("Sheet1", 6, false); - confirmCellRange("iV65536", 2, true); // max cell in Excel 97-2003 - confirmCellRange("IW65537", 2, false); + confirmCellRange("iV65536", 2, true); // max cell in Excel 97-2003 + confirmCellRange("IW65537", 2, false); } } diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java b/src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java index cf4857368..523d42ec9 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java @@ -31,8 +31,12 @@ import org.apache.poi.ss.util.Region; import org.apache.poi.hssf.HSSFTestDataSamples; import org.apache.poi.hssf.model.Workbook; +import org.apache.poi.hssf.record.CellValueRecordInterface; import org.apache.poi.hssf.record.EmbeddedObjectRefSubRecord; +import org.apache.poi.hssf.record.FormulaRecord; import org.apache.poi.hssf.record.NameRecord; +import org.apache.poi.hssf.record.Record; +import org.apache.poi.hssf.record.aggregates.FormulaRecordAggregate; import org.apache.poi.hssf.record.formula.DeletedArea3DPtg; import org.apache.poi.util.TempFile; @@ -1136,4 +1140,80 @@ public final class TestBugs extends TestCase { ) ); } + + /** + * From the mailing list - ensure we can handle a formula + * containing a zip code, eg ="70164" + * @throws Exception + */ + public void testZipCodeFormulas() throws Exception { + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet s = wb.createSheet(); + s.createRow(0); + HSSFCell c1 = s.getRow(0).createCell((short)0); + HSSFCell c2 = s.getRow(0).createCell((short)1); + + // As number and string + c1.setCellFormula("70164"); + c2.setCellFormula("\"70164\""); + + // Check the formulas + assertEquals("70164.0", c1.getCellFormula()); + assertEquals("\"70164\"", c2.getCellFormula()); + + // And check the values - blank + assertEquals(0.0, c1.getNumericCellValue(), 0.00001); + assertEquals("", c1.getRichStringCellValue().getString()); + assertEquals(0.0, c2.getNumericCellValue(), 0.00001); + assertEquals("", c2.getRichStringCellValue().getString()); + + // Now evaluate + HSSFFormulaEvaluator eval = new HSSFFormulaEvaluator(s, wb); + eval.setCurrentRow(s.getRow(0)); + eval.evaluateFormulaCell(c1); + eval.evaluateFormulaCell(c2); + + // Check + assertEquals(70164.0, c1.getNumericCellValue(), 0.00001); + assertEquals("", c1.getRichStringCellValue().getString()); + assertEquals(0.0, c2.getNumericCellValue(), 0.00001); + + // TODO - why isn't this working? +// assertEquals("70164", c2.getRichStringCellValue().getString()); + + + // Write and read + HSSFWorkbook nwb = writeOutAndReadBack(wb); + HSSFSheet ns = nwb.getSheetAt(0); + HSSFCell nc1 = ns.getRow(0).getCell((short)0); + HSSFCell nc2 = ns.getRow(0).getCell((short)1); + + // Re-check + assertEquals(70164.0, nc1.getNumericCellValue(), 0.00001); + assertEquals("", nc1.getRichStringCellValue().getString()); + assertEquals(0.0, nc2.getNumericCellValue(), 0.00001); + assertEquals("70164", nc2.getRichStringCellValue().getString()); + + // Now check record level stuff too + ns.getSheet().setLoc(0); + int fn = 0; + CellValueRecordInterface cvr; + while((cvr = ns.getSheet().getNextValueRecord()) != null) { + if(cvr instanceof FormulaRecordAggregate) { + FormulaRecordAggregate fr = (FormulaRecordAggregate)cvr; + + if(fn == 0) { + assertEquals(70164.0, fr.getFormulaRecord().getValue(), 0.0001); + assertNull(fr.getStringRecord()); + } else { + assertEquals(0.0, fr.getFormulaRecord().getValue(), 0.0001); + assertNotNull(fr.getStringRecord()); + assertEquals("70164", fr.getStringRecord().getString()); + } + + fn++; + } + } + assertEquals(2, fn); + } } diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFSheet.java b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFSheet.java index 3b154b256..90971b3c0 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFSheet.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFSheet.java @@ -169,6 +169,20 @@ public final class TestHSSFSheet extends TestCase { sheet.removeRow(row); } + public void testRemoveZeroRow() { + HSSFWorkbook workbook = new HSSFWorkbook(); + HSSFSheet sheet = workbook.createSheet("Sheet1"); + HSSFRow row = sheet.createRow(0); + try { + sheet.removeRow(row); + } catch (IllegalArgumentException e) { + if (e.getMessage().equals("Invalid row number (-1) outside allowable range (0..65535)")) { + throw new AssertionFailedError("Identified bug 45367"); + } + throw e; + } + } + public void testCloneSheet() { HSSFWorkbook workbook = new HSSFWorkbook(); HSSFSheet sheet = workbook.createSheet("Test Clone"); diff --git a/src/testcases/org/apache/poi/hssf/util/AllHSSFUtilTests.java b/src/testcases/org/apache/poi/hssf/util/AllHSSFUtilTests.java index 1f7aa5f25..ec88e5c43 100755 --- a/src/testcases/org/apache/poi/hssf/util/AllHSSFUtilTests.java +++ b/src/testcases/org/apache/poi/hssf/util/AllHSSFUtilTests.java @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. ==================================================================== */ - + package org.apache.poi.hssf.util; import junit.framework.Test; @@ -28,12 +28,13 @@ import junit.framework.TestSuite; public class AllHSSFUtilTests { public static Test suite() { - TestSuite result = new TestSuite("Tests for org.apache.poi.hssf.util"); - result.addTestSuite(TestAreaReference.class); - result.addTestSuite(TestCellReference.class); - result.addTestSuite(TestRangeAddress.class); - result.addTestSuite(TestRKUtil.class); - result.addTestSuite(TestSheetReferences.class); + TestSuite result = new TestSuite(AllHSSFUtilTests.class.getName()); + result.addTestSuite(TestAreaReference.class); + result.addTestSuite(TestCellReference.class); + result.addTestSuite(TestHSSFColor.class); + result.addTestSuite(TestRangeAddress.class); + result.addTestSuite(TestRKUtil.class); + result.addTestSuite(TestSheetReferences.class); return result; } } diff --git a/src/testcases/org/apache/poi/hssf/util/TestCellReference.java b/src/testcases/org/apache/poi/hssf/util/TestCellReference.java index 648fb9a8e..8ec8f9946 100644 --- a/src/testcases/org/apache/poi/hssf/util/TestCellReference.java +++ b/src/testcases/org/apache/poi/hssf/util/TestCellReference.java @@ -20,6 +20,8 @@ package org.apache.poi.hssf.util; import junit.framework.TestCase; +import org.apache.poi.hssf.util.CellReference.NameType; + public final class TestCellReference extends TestCase { @@ -75,7 +77,6 @@ public final class TestCellReference extends TestCase { confirmCell(cf, "Amazing!", 0, 0, false, false, "'Amazing!'!A1"); } - /* package */ static void confirmCell(CellReference cf, String expSheetName, int expRow, int expCol, boolean expIsRowAbs, boolean expIsColAbs, String expText) { @@ -87,8 +88,22 @@ public final class TestCellReference extends TestCase { assertEquals("text is wrong", expText, cf.formatAsString()); } - public static void main(String [] args) { - System.out.println("Testing org.apache.poi.hssf.util.TestCellReference"); - junit.textui.TestRunner.run(TestCellReference.class); + public void testClassifyCellReference() { + confirmNameType("a1", NameType.CELL); + confirmNameType("pfy1", NameType.NAMED_RANGE); + confirmNameType("pf1", NameType.NAMED_RANGE); // (col) out of cell range + confirmNameType("fp1", NameType.CELL); + confirmNameType("pf$1", NameType.BAD_CELL_OR_NAMED_RANGE); + confirmNameType("_A1", NameType.NAMED_RANGE); + confirmNameType("A_1", NameType.NAMED_RANGE); + confirmNameType("A1_", NameType.NAMED_RANGE); + confirmNameType(".A1", NameType.BAD_CELL_OR_NAMED_RANGE); + confirmNameType("A.1", NameType.NAMED_RANGE); + confirmNameType("A1.", NameType.NAMED_RANGE); + } + + private void confirmNameType(String ref, int expectedResult) { + int actualResult = CellReference.classifyCellReference(ref); + assertEquals(expectedResult, actualResult); } } diff --git a/src/testcases/org/apache/poi/hssf/util/TestHSSFColor.java b/src/testcases/org/apache/poi/hssf/util/TestHSSFColor.java index 42571eb32..d93f5d7b0 100644 --- a/src/testcases/org/apache/poi/hssf/util/TestHSSFColor.java +++ b/src/testcases/org/apache/poi/hssf/util/TestHSSFColor.java @@ -14,13 +14,15 @@ See the License for the specific language governing permissions and limitations under the License. ==================================================================== */ - + package org.apache.poi.hssf.util; import java.util.Hashtable; import junit.framework.TestCase; - +/** + * @author Nick Burch + */ public final class TestHSSFColor extends TestCase { public void testBasics() { assertNotNull(HSSFColor.YELLOW.class);