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
This commit is contained in:
Nick Burch 2008-07-10 20:37:09 +00:00
parent 22b3573e3d
commit 82f6d9f991
26 changed files with 780 additions and 322 deletions

View File

@ -50,6 +50,11 @@
<action dev="POI-DEVELOPERS" type="add">Created a common interface for handling Excel files, irrespective of if they are .xls or .xlsx</action>
</release>
<release version="3.1.1-alpha1" date="2008-??-??">
<action dev="POI-DEVELOPERS" type="fix">45365 - Handle more excel number formatting rules in FormatTrackingHSSFListener / XLS2CSVmra</action>
<action dev="POI-DEVELOPERS" type="fix">45373 - Improve the performance of HSSFSheet.shiftRows</action>
<action dev="POI-DEVELOPERS" type="fix">45367 - Fixed bug when last row removed from sheet is row zero</action>
<action dev="POI-DEVELOPERS" type="fix">45348 - Tweaks to RVA formula logic</action>
<action dev="POI-DEVELOPERS" type="fix">45354 - Fixed recognition of named ranges within formulas</action>
<action dev="POI-DEVELOPERS" type="fix">45338 - Fix HSSFWorkbook to give you the same HSSFFont every time, and then fix it to find newly added fonts</action>
<action dev="POI-DEVELOPERS" type="fix">45336 - Fix HSSFColor.getTripletHash()</action>
<action dev="POI-DEVELOPERS" type="fix">45334 - Fixed formula parser to handle dots in identifiers</action>

View File

@ -47,6 +47,11 @@
<action dev="POI-DEVELOPERS" type="add">Created a common interface for handling Excel files, irrespective of if they are .xls or .xlsx</action>
</release>
<release version="3.1.1-alpha1" date="2008-??-??">
<action dev="POI-DEVELOPERS" type="fix">45365 - Handle more excel number formatting rules in FormatTrackingHSSFListener / XLS2CSVmra</action>
<action dev="POI-DEVELOPERS" type="fix">45373 - Improve the performance of HSSFSheet.shiftRows</action>
<action dev="POI-DEVELOPERS" type="fix">45367 - Fixed bug when last row removed from sheet is row zero</action>
<action dev="POI-DEVELOPERS" type="fix">45348 - Tweaks to RVA formula logic</action>
<action dev="POI-DEVELOPERS" type="fix">45354 - Fixed recognition of named ranges within formulas</action>
<action dev="POI-DEVELOPERS" type="fix">45338 - Fix HSSFWorkbook to give you the same HSSFFont every time, and then fix it to find newly added fonts</action>
<action dev="POI-DEVELOPERS" type="fix">45336 - Fix HSSFColor.getTripletHash()</action>
<action dev="POI-DEVELOPERS" type="fix">45334 - Fixed formula parser to handle dots in identifiers</action>

View File

@ -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:");

View File

@ -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.

View File

@ -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 <code>null</code> 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,9 +345,11 @@ public final class FormulaParser {
return new AreaReference(topLeft, bottomRight);
}
/**
* @return <code>true</code> 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;
}

View File

@ -71,11 +71,16 @@ final class OperandClassTransformer {
+ _formulaType + ") not supported yet");
}
transformNode(rootNode, rootNodeOperandClass, false);
transformNode(rootNode, rootNodeOperandClass, false, false);
}
/**
* @param callerForceArrayFlag <code>true</code> 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);
}
}
}

View File

@ -15,12 +15,13 @@
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 <code>true</code> 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 <code>true</code> 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 {
* <p/>
* 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:
* <p/>
* <blockquote><table border="0" cellpadding="1" cellspacing="0"
* summary="Notable cases.">
* <tr><th>Version&nbsp;&nbsp;</th><th>File Format&nbsp;&nbsp;</th>
* <th>Last Column&nbsp;&nbsp;</th><th>Last Row</th></tr>
* <tr><td>97-2003</td><td>BIFF8</td><td>"IV" (2^8)</td><td>65536 (2^14)</td></tr>
* <tr><td>2007</td><td>BIFF12</td><td>"XFD" (2^14)</td><td>1048576 (2^20)</td></tr>
* </table></blockquote>
* POI currently targets BIFF8 (Excel 97-2003), so the following behaviour can be observed for
* this method:
* <blockquote><table border="0" cellpadding="1" cellspacing="0"
* summary="Notable cases.">
* <tr><th>Input&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</th>
* <th>Result&nbsp;</th></tr>
* <tr><td>"A1", 1</td><td>true</td></tr>
* <tr><td>"a111", 1</td><td>true</td></tr>
* <tr><td>"A65536", 1</td><td>true</td></tr>
* <tr><td>"A65537", 1</td><td>false</td></tr>
* <tr><td>"iv1", 2</td><td>true</td></tr>
* <tr><td>"IW1", 2</td><td>false</td></tr>
* <tr><td>"AAA1", 3</td><td>false</td></tr>
* <tr><td>"a111", 1</td><td>true</td></tr>
* <tr><td>"Sheet1", 6</td><td>false</td></tr>
* </table></blockquote>
* 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);
}
}

View File

@ -188,6 +188,19 @@ public final class HSSFRow implements Comparable, Row {
}
}
/**
* Removes all the cells from the row, and their
* records too.
*/
protected void removeAllCells() {
for(int i=0; i<cells.length; i++) {
if(cells[i] != null) {
removeCell(cells[i], true);
}
}
cells=new HSSFCell[INITIAL_CAPACITY];
}
/**
* create a high level HSSFCell object from an existing low level record. Should
* only be called from HSSFSheet or HSSFRow itself.

View File

@ -280,18 +280,19 @@ public class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet
/**
* used internally to refresh the "last row" when the last row is removed.
*/
private int findLastRow(int lastrow)
{
private int findLastRow(int lastrow) {
if (lastrow < 1) {
return -1;
}
int rownum = lastrow - 1;
HSSFRow r = getRow(rownum);
while (r == null && rownum > 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
*
* <p>
* Additionally shifts merged regions that are completely defined in these
* rows (ie. merged 2 cells on a row to be shifted).
* <p>
* 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 )
@ -1251,43 +1274,54 @@ public class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet
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 );
}
// 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 if exist (can exist even if cell is null)
HSSFComment comment = getCellComment(rowNum, col);
if (comment != null) {
comment.setRow(rowNum + n);
}
// 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 );

View File

@ -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;
for (short i=0; i<=getNumberOfFonts(); i++) {
// Remember - there is no 4!
if(i == 4) continue;
HSSFFont hssfFont = getFontAt(i);
HSSFFont hssfFont = getFontAt(i);
if (hssfFont.getBoldweight() == boldWeight
&& hssfFont.getColor() == color
&& hssfFont.getFontHeight() == fontHeight
@ -1077,18 +1077,16 @@ 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();
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);
}
// 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);
}
FontRecord font = workbook.getFontRecordAt(idx);
HSSFFont retval = new HSSFFont(idx, font);

View File

@ -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);

View File

@ -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;
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;
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 <tt>NameType</tt>
*/
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.
* <p/>
* Note - that the maximum sheet size varies across Excel versions:
* <p/>
* <blockquote><table border="0" cellpadding="1" cellspacing="0"
* summary="Notable cases.">
* <tr><th>Version&nbsp;&nbsp;</th><th>File Format&nbsp;&nbsp;</th>
* <th>Last Column&nbsp;&nbsp;</th><th>Last Row</th></tr>
* <tr><td>97-2003</td><td>BIFF8</td><td>"IV" (2^8)</td><td>65536 (2^14)</td></tr>
* <tr><td>2007</td><td>BIFF12</td><td>"XFD" (2^14)</td><td>1048576 (2^20)</td></tr>
* </table></blockquote>
* POI currently targets BIFF8 (Excel 97-2003), so the following behaviour can be observed for
* this method:
* <blockquote><table border="0" cellpadding="1" cellspacing="0"
* summary="Notable cases.">
* <tr><th>Input&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</th>
* <th>Result&nbsp;</th></tr>
* <tr><td>"A", "1"</td><td>true</td></tr>
* <tr><td>"a", "111"</td><td>true</td></tr>
* <tr><td>"A", "65536"</td><td>true</td></tr>
* <tr><td>"A", "65537"</td><td>false</td></tr>
* <tr><td>"iv", "1"</td><td>true</td></tr>
* <tr><td>"IW", "1"</td><td>false</td></tr>
* <tr><td>"AAA", "1"</td><td>false</td></tr>
* <tr><td>"a", "111"</td><td>true</td></tr>
* <tr><td>"Sheet", "1"</td><td>false</td></tr>
* </table></blockquote>
*
* @param colStr a string of only letter characters
* @param rowStr a string of only digit characters
* @return <code>true</code> 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;
// Excel counts column A as the 1st column, we
// treat it as the 0th one
int excelColNum = col + 1;
String colRef = "";
int colRemain = excelColNum;
String colRef = "";
int colRemain = excelColNum;
while(colRemain > 0) {
int thisPart = colRemain % 26;
if(thisPart == 0) { thisPart = 26; }
colRemain = (colRemain - thisPart) / 26;
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;
}
// The letter A is at 65
char colChar = (char)(thisPart+64);
colRef = colChar + colRef;
}
return 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.

View File

@ -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());

Binary file not shown.

View File

@ -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 <tt>org.apache.poi.hssf.eventusermodel</tt>.
*
* @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;
}
}

View File

@ -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 {

View File

@ -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;
private MockHSSFListener mockListen;
public void setUp() {
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<mockListen._records.size(); i++) {
Record r = (Record)mockListen._records.get(i);
CellValueRecordInterface cvr = null;
if(r instanceof NumberRecord) {
cvr = (CellValueRecordInterface)r;
}
if(r instanceof FormulaRecord) {
cvr = (CellValueRecordInterface)r;
}
if(cvr != null) {
// Should always give us a string
String s = listener.formatNumberDateCell(cvr);
assertNotNull(s);
assertTrue(s.length() > 0);
}
}
// TODO - test some specific format strings
}
private static final class MockHSSFListener implements HSSFListener {
public MockHSSFListener() {}
private final List _records = new ArrayList();

View File

@ -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
}
}

View File

@ -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()));
}

View File

@ -15,7 +15,6 @@
limitations under the License.
==================================================================== */
package org.apache.poi.hssf.record.formula;
import junit.framework.TestCase;
@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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");

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -20,7 +20,9 @@ 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);