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-674890,674892-674910,674912-674936,674938-674952,674954-675078,675080-675085,675087-675217,675219-675660,675662-675670,675672-675716,675718-675726,675728-675733,675735-675775,675777-675782,675784,675786-675791,675794-676205 via svnmerge from

https://svn.apache.org:443/repos/asf/poi/trunk

........
  r675853 | josh | 2008-07-11 08:59:44 +0100 (Fri, 11 Jul 2008) | 1 line
  
  Patch 45289 - finished support for special comparison operators in COUNTIF
........
  r676201 | nick | 2008-07-12 17:56:55 +0100 (Sat, 12 Jul 2008) | 1 line
  
  Support for cloning one font record onto another, plus tests
........
  r676203 | nick | 2008-07-12 18:21:54 +0100 (Sat, 12 Jul 2008) | 1 line
  
  Support for cloning one extended format record onto another, plus tests
........
  r676205 | nick | 2008-07-12 18:38:10 +0100 (Sat, 12 Jul 2008) | 1 line
  
  Allow the cloning of one HSSFCellStyle onto another, including cloning styles from one HSSFWorkbook onto another
........


git-svn-id: https://svn.apache.org/repos/asf/poi/branches/ooxml@676209 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Nick Burch 2008-07-12 18:17:16 +00:00
parent 1cbe4a5db2
commit cec1612121
25 changed files with 1157 additions and 158 deletions

View File

@ -50,6 +50,8 @@
<action dev="POI-DEVELOPERS" type="add">Created a common interface for handling Excel files, irrespective of if they are .xls or .xlsx</action> <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>
<release version="3.1.1-alpha1" date="2008-??-??"> <release version="3.1.1-alpha1" date="2008-??-??">
<action dev="POI-DEVELOPERS" type="add">Allow the cloning of one HSSFCellStyle onto another, including cloning styles from one HSSFWorkbook onto another</action>
<action dev="POI-DEVELOPERS" type="fix">45289 - finished support for special comparison operators in COUNTIF</action>
<action dev="POI-DEVELOPERS" type="fix">45126 - Avoid generating multiple NamedRanges with the same name, which Excel dislikes</action> <action dev="POI-DEVELOPERS" type="fix">45126 - Avoid generating multiple NamedRanges with the same name, which Excel dislikes</action>
<action dev="POI-DEVELOPERS" type="fix">Fix cell.getRichStringCellValue() for formula cells with string results</action> <action dev="POI-DEVELOPERS" type="fix">Fix cell.getRichStringCellValue() for formula cells with string results</action>
<action dev="POI-DEVELOPERS" type="fix">45365 - Handle more excel number formatting rules in FormatTrackingHSSFListener / XLS2CSVmra</action> <action dev="POI-DEVELOPERS" type="fix">45365 - Handle more excel number formatting rules in FormatTrackingHSSFListener / XLS2CSVmra</action>

View File

@ -27,7 +27,21 @@
</authors> </authors>
</header> </header>
<body> <body>
<section><title>Converting existing HSSF Usermodel code to SS Usermodel (for XSSF and HSSF)</title> <section><title>Things that have to be changed when upgrading to POI 3.5</title>
<p>Wherever possible, we have tried to ensure that you can use your
existing POI code with POI 3.5 without requiring any changes. However,
Java doesn't always make that easy, and unfortunately there are a
few changes that may be required for some users.</p>
<section><title>org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator.CellValue</title>
<p>Annoyingly, java will not let you access a static inner class via
a child of the parent one. So, all references to
<em>org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator.CellValue</em>
will need to be changed to
<em>org.apache.poi.ss.usermodel.FormulaEvaluator.CellValue</em>
</p>
</section>
</section>
<section><title>Converting existing HSSF Usermodel code to SS Usermodel (for XSSF and HSSF)</title>
<section><title>Why change?</title> <section><title>Why change?</title>
<p>If you have existing HSSF usermodel code that works just <p>If you have existing HSSF usermodel code that works just

View File

@ -47,6 +47,8 @@
<action dev="POI-DEVELOPERS" type="add">Created a common interface for handling Excel files, irrespective of if they are .xls or .xlsx</action> <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>
<release version="3.1.1-alpha1" date="2008-??-??"> <release version="3.1.1-alpha1" date="2008-??-??">
<action dev="POI-DEVELOPERS" type="add">Allow the cloning of one HSSFCellStyle onto another, including cloning styles from one HSSFWorkbook onto another</action>
<action dev="POI-DEVELOPERS" type="fix">45289 - finished support for special comparison operators in COUNTIF</action>
<action dev="POI-DEVELOPERS" type="fix">45126 - Avoid generating multiple NamedRanges with the same name, which Excel dislikes</action> <action dev="POI-DEVELOPERS" type="fix">45126 - Avoid generating multiple NamedRanges with the same name, which Excel dislikes</action>
<action dev="POI-DEVELOPERS" type="fix">Fix cell.getRichStringCellValue() for formula cells with string results</action> <action dev="POI-DEVELOPERS" type="fix">Fix cell.getRichStringCellValue() for formula cells with string results</action>
<action dev="POI-DEVELOPERS" type="fix">45365 - Handle more excel number formatting rules in FormatTrackingHSSFListener / XLS2CSVmra</action> <action dev="POI-DEVELOPERS" type="fix">45365 - Handle more excel number formatting rules in FormatTrackingHSSFListener / XLS2CSVmra</action>

View File

@ -408,6 +408,24 @@ public class Workbook implements Model
return retval; return retval;
} }
/**
* Retrieves the index of the given font
*/
public int getFontIndex(FontRecord font) {
for(int i=0; i<=numfonts; i++) {
FontRecord thisFont =
( FontRecord ) records.get((records.getFontpos() - (numfonts - 1)) + i);
if(thisFont == font) {
// There is no 4!
if(i > 3) {
return (i+1);
}
return i;
}
}
throw new IllegalArgumentException("Could not find that font!");
}
/** /**
* creates a new font record and adds it to the "font table". This causes the * creates a new font record and adds it to the "font table". This causes the

View File

@ -1814,6 +1814,27 @@ public class ExtendedFormatRecord
{ {
return sid; return sid;
} }
/**
* Clones all the style information from another
* ExtendedFormatRecord, onto this one. This
* will then hold all the same style options.
*
* If The source ExtendedFormatRecord comes from
* a different Workbook, you will need to sort
* out the font and format indicies yourself!
*/
public void cloneStyleFrom(ExtendedFormatRecord source) {
field_1_font_index = source.field_1_font_index;
field_2_format_index = source.field_2_format_index;
field_3_cell_options = source.field_3_cell_options;
field_4_alignment_options = source.field_4_alignment_options;
field_5_indention_options = source.field_5_indention_options;
field_6_border_options = source.field_6_border_options;
field_7_palette_options = source.field_7_palette_options;
field_8_adtl_palette_options = source.field_8_adtl_palette_options;
field_9_fill_palette_options = source.field_9_fill_palette_options;
}
public int hashCode() { public int hashCode() {
final int prime = 31; final int prime = 31;

View File

@ -531,6 +531,8 @@ public class FontRecord
public int getRecordSize() public int getRecordSize()
{ {
// Note - no matter the original, we always
// re-serialise the font name as unicode
return (getFontNameLength() * 2) + 20; return (getFontNameLength() * 2) + 20;
} }
@ -538,6 +540,25 @@ public class FontRecord
{ {
return sid; return sid;
} }
/**
* Clones all the font style information from another
* FontRecord, onto this one. This
* will then hold all the same font style options.
*/
public void cloneStyleFrom(FontRecord source) {
field_1_font_height = source.field_1_font_height;
field_2_attributes = source.field_2_attributes;
field_3_color_palette_index = source.field_3_color_palette_index;
field_4_bold_weight = source.field_4_bold_weight;
field_5_super_sub_script = source.field_5_super_sub_script;
field_6_underline = source.field_6_underline;
field_7_family = source.field_7_family;
field_8_charset = source.field_8_charset;
field_9_zero = source.field_9_zero;
field_10_font_name_len = source.field_10_font_name_len;
field_11_font_name = source.field_11_font_name;
}
public int hashCode() { public int hashCode() {
final int prime = 31; final int prime = 31;

View File

@ -15,14 +15,17 @@
* limitations under the License. * limitations under the License.
*/ */
package org.apache.poi.hssf.record.formula.functions; package org.apache.poi.hssf.record.formula.functions;
import java.util.regex.Pattern;
import org.apache.poi.hssf.record.formula.eval.AreaEval; import org.apache.poi.hssf.record.formula.eval.AreaEval;
import org.apache.poi.hssf.record.formula.eval.BlankEval;
import org.apache.poi.hssf.record.formula.eval.BoolEval; import org.apache.poi.hssf.record.formula.eval.BoolEval;
import org.apache.poi.hssf.record.formula.eval.ErrorEval; import org.apache.poi.hssf.record.formula.eval.ErrorEval;
import org.apache.poi.hssf.record.formula.eval.Eval; import org.apache.poi.hssf.record.formula.eval.Eval;
import org.apache.poi.hssf.record.formula.eval.NumberEval; import org.apache.poi.hssf.record.formula.eval.NumberEval;
import org.apache.poi.hssf.record.formula.eval.OperandResolver;
import org.apache.poi.hssf.record.formula.eval.RefEval; import org.apache.poi.hssf.record.formula.eval.RefEval;
import org.apache.poi.hssf.record.formula.eval.StringEval; import org.apache.poi.hssf.record.formula.eval.StringEval;
import org.apache.poi.hssf.record.formula.eval.ValueEval; import org.apache.poi.hssf.record.formula.eval.ValueEval;
@ -40,85 +43,288 @@ import org.apache.poi.hssf.record.formula.eval.ValueEval;
* @author Josh Micich * @author Josh Micich
*/ */
public final class Countif implements Function { public final class Countif implements Function {
private static final class CmpOp {
public static final int NONE = 0;
public static final int EQ = 1;
public static final int NE = 2;
public static final int LE = 3;
public static final int LT = 4;
public static final int GT = 5;
public static final int GE = 6;
public static final CmpOp OP_NONE = op("", NONE);
public static final CmpOp OP_EQ = op("=", EQ);
public static final CmpOp OP_NE = op("<>", NE);
public static final CmpOp OP_LE = op("<=", LE);
public static final CmpOp OP_LT = op("<", LT);
public static final CmpOp OP_GT = op(">", GT);
public static final CmpOp OP_GE = op(">=", GE);
private final String _representation;
private final int _code;
private static CmpOp op(String rep, int code) {
return new CmpOp(rep, code);
}
private CmpOp(String representation, int code) {
_representation = representation;
_code = code;
}
/**
* @return number of characters used to represent this operator
*/
public int getLength() {
return _representation.length();
}
public int getCode() {
return _code;
}
public static CmpOp getOperator(String value) {
int len = value.length();
if (len < 1) {
return OP_NONE;
}
char firstChar = value.charAt(0);
switch(firstChar) {
case '=':
return OP_EQ;
case '>':
if (len > 1) {
switch(value.charAt(1)) {
case '=':
return OP_GE;
}
}
return OP_GT;
case '<':
if (len > 1) {
switch(value.charAt(1)) {
case '=':
return OP_LE;
case '>':
return OP_NE;
}
}
return OP_LT;
}
return OP_NONE;
}
public boolean evaluate(boolean cmpResult) {
switch (_code) {
case NONE:
case EQ:
return cmpResult;
case NE:
return !cmpResult;
}
throw new RuntimeException("Cannot call boolean evaluate on non-equality operator '"
+ _representation + "'");
}
public boolean evaluate(int cmpResult) {
switch (_code) {
case NONE:
case EQ:
return cmpResult == 0;
case NE: return cmpResult == 0;
case LT: return cmpResult < 0;
case LE: return cmpResult <= 0;
case GT: return cmpResult > 0;
case GE: return cmpResult <= 0;
}
throw new RuntimeException("Cannot call boolean evaluate on non-equality operator '"
+ _representation + "'");
}
public String toString() {
StringBuffer sb = new StringBuffer(64);
sb.append(getClass().getName());
sb.append(" [").append(_representation).append("]");
return sb.toString();
}
}
/** /**
* Common interface for the matching criteria. * Common interface for the matching criteria.
*/ */
private interface I_MatchPredicate { /* package */ interface I_MatchPredicate {
boolean matches(Eval x); boolean matches(Eval x);
} }
private static final class NumberMatcher implements I_MatchPredicate { private static final class NumberMatcher implements I_MatchPredicate {
private final double _value; private final double _value;
private final CmpOp _operator;
public NumberMatcher(double value) { public NumberMatcher(double value, CmpOp operator) {
_value = value; _value = value;
_operator = operator;
} }
public boolean matches(Eval x) { public boolean matches(Eval x) {
double testValue;
if(x instanceof StringEval) { if(x instanceof StringEval) {
// if the target(x) is a string, but parses as a number // if the target(x) is a string, but parses as a number
// it may still count as a match // it may still count as a match
StringEval se = (StringEval)x; StringEval se = (StringEval)x;
Double val = parseDouble(se.getStringValue()); Double val = OperandResolver.parseDouble(se.getStringValue());
if(val == null) { if(val == null) {
// x is text that is not a number // x is text that is not a number
return false; return false;
} }
return val.doubleValue() == _value; testValue = val.doubleValue();
} } else if((x instanceof NumberEval)) {
if(!(x instanceof NumberEval)) { NumberEval ne = (NumberEval) x;
testValue = ne.getNumberValue();
} else {
return false; return false;
} }
NumberEval ne = (NumberEval) x; return _operator.evaluate(Double.compare(testValue, _value));
return ne.getNumberValue() == _value;
} }
} }
private static final class BooleanMatcher implements I_MatchPredicate { private static final class BooleanMatcher implements I_MatchPredicate {
private final boolean _value; private final int _value;
private final CmpOp _operator;
public BooleanMatcher(boolean value) { public BooleanMatcher(boolean value, CmpOp operator) {
_value = value; _value = boolToInt(value);
_operator = operator;
}
private static int boolToInt(boolean value) {
return value ? 1 : 0;
} }
public boolean matches(Eval x) { public boolean matches(Eval x) {
int testValue;
if(x instanceof StringEval) { if(x instanceof StringEval) {
if (true) { // change to false to observe more intuitive behaviour
// Note - Unlike with numbers, it seems that COUNTIF never matches
// boolean values when the target(x) is a string
return false;
}
StringEval se = (StringEval)x; StringEval se = (StringEval)x;
Boolean val = parseBoolean(se.getStringValue()); Boolean val = parseBoolean(se.getStringValue());
if(val == null) { if(val == null) {
// x is text that is not a boolean // x is text that is not a boolean
return false; return false;
} }
if (true) { // change to false to observe more intuitive behaviour testValue = boolToInt(val.booleanValue());
// Note - Unlike with numbers, it seems that COUNTA never matches } else if((x instanceof BoolEval)) {
// boolean values when the target(x) is a string BoolEval be = (BoolEval) x;
return false; testValue = boolToInt(be.getBooleanValue());
} } else {
return val.booleanValue() == _value;
}
if(!(x instanceof BoolEval)) {
return false; return false;
} }
BoolEval be = (BoolEval) x; return _operator.evaluate(testValue - _value);
return be.getBooleanValue() == _value;
} }
} }
private static final class StringMatcher implements I_MatchPredicate { private static final class StringMatcher implements I_MatchPredicate {
private final String _value; private final String _value;
private final CmpOp _operator;
private final Pattern _pattern;
public StringMatcher(String value) { public StringMatcher(String value, CmpOp operator) {
_value = value; _value = value;
_operator = operator;
switch(operator.getCode()) {
case CmpOp.NONE:
case CmpOp.EQ:
case CmpOp.NE:
_pattern = getWildCardPattern(value);
break;
default:
_pattern = null;
}
} }
public boolean matches(Eval x) { public boolean matches(Eval x) {
if(!(x instanceof StringEval)) { if (x instanceof BlankEval) {
switch(_operator.getCode()) {
case CmpOp.NONE:
case CmpOp.EQ:
return _value.length() == 0;
}
// no other criteria matches a blank cell
return false; return false;
} }
StringEval se = (StringEval) x; if(!(x instanceof StringEval)) {
return se.getStringValue() == _value; // must always be string
// even if match str is wild, but contains only digits
// e.g. '4*7', NumberEval(4567) does not match
return false;
}
String testedValue = ((StringEval) x).getStringValue();
if (testedValue.length() < 1 && _value.length() < 1) {
// odd case: criteria '=' behaves differently to criteria ''
switch(_operator.getCode()) {
case CmpOp.NONE: return true;
case CmpOp.EQ: return false;
case CmpOp.NE: return true;
}
return false;
}
if (_pattern != null) {
return _operator.evaluate(_pattern.matcher(testedValue).matches());
}
return _operator.evaluate(testedValue.compareTo(_value));
}
/**
* Translates Excel countif wildcard strings into java regex strings
* @return <code>null</code> if the specified value contains no special wildcard characters.
*/
private static Pattern getWildCardPattern(String value) {
int len = value.length();
StringBuffer sb = new StringBuffer(len);
boolean hasWildCard = false;
for(int i=0; i<len; i++) {
char ch = value.charAt(i);
switch(ch) {
case '?':
hasWildCard = true;
// match exactly one character
sb.append('.');
continue;
case '*':
hasWildCard = true;
// match one or more occurrences of any character
sb.append(".*");
continue;
case '~':
if (i+1<len) {
ch = value.charAt(i+1);
switch (ch) {
case '?':
case '*':
hasWildCard = true;
sb.append('[').append(ch).append(']');
i++; // Note - incrementing loop variable here
continue;
}
}
// else not '~?' or '~*'
sb.append('~'); // just plain '~'
continue;
case '.':
case '$':
case '^':
case '[':
case ']':
case '(':
case ')':
// escape literal characters that would have special meaning in regex
sb.append("\\").append(ch);
continue;
}
sb.append(ch);
}
if (hasWildCard) {
return Pattern.compile(sb.toString());
}
return null;
} }
} }
@ -132,8 +338,7 @@ public final class Countif implements Function {
// perhaps this should be an exception // perhaps this should be an exception
return ErrorEval.VALUE_INVALID; return ErrorEval.VALUE_INVALID;
} }
AreaEval range = (AreaEval) args[0];
Eval criteriaArg = args[1]; Eval criteriaArg = args[1];
if(criteriaArg instanceof RefEval) { if(criteriaArg instanceof RefEval) {
// criteria is not a literal value, but a cell reference // criteria is not a literal value, but a cell reference
@ -144,31 +349,47 @@ public final class Countif implements Function {
// other non literal tokens such as function calls, have been fully evaluated // other non literal tokens such as function calls, have been fully evaluated
// for example COUNTIF(B2:D4, COLUMN(E1)) // for example COUNTIF(B2:D4, COLUMN(E1))
} }
if(criteriaArg instanceof BlankEval) {
// If the criteria arg is a reference to a blank cell, countif always returns zero.
return NumberEval.ZERO;
}
I_MatchPredicate mp = createCriteriaPredicate(criteriaArg); I_MatchPredicate mp = createCriteriaPredicate(criteriaArg);
return countMatchingCellsInArea(range, mp); return countMatchingCellsInArea(args[0], mp);
} }
/** /**
* @return the number of evaluated cells in the range that match the specified criteria * @return the number of evaluated cells in the range that match the specified criteria
*/ */
private Eval countMatchingCellsInArea(AreaEval range, I_MatchPredicate criteriaPredicate) { private Eval countMatchingCellsInArea(Eval rangeArg, I_MatchPredicate criteriaPredicate) {
ValueEval[] values = range.getValues();
int result = 0; int result = 0;
for (int i = 0; i < values.length; i++) { if (rangeArg instanceof RefEval) {
if(criteriaPredicate.matches(values[i])) { RefEval refEval = (RefEval) rangeArg;
if(criteriaPredicate.matches(refEval.getInnerValueEval())) {
result++; result++;
} }
} else if (rangeArg instanceof AreaEval) {
AreaEval range = (AreaEval) rangeArg;
ValueEval[] values = range.getValues();
for (int i = 0; i < values.length; i++) {
if(criteriaPredicate.matches(values[i])) {
result++;
}
}
} else {
throw new IllegalArgumentException("Bad range arg type (" + rangeArg.getClass().getName() + ")");
} }
return new NumberEval(result); return new NumberEval(result);
} }
private static I_MatchPredicate createCriteriaPredicate(Eval evaluatedCriteriaArg) { /* package */ static I_MatchPredicate createCriteriaPredicate(Eval evaluatedCriteriaArg) {
if(evaluatedCriteriaArg instanceof NumberEval) { if(evaluatedCriteriaArg instanceof NumberEval) {
return new NumberMatcher(((NumberEval)evaluatedCriteriaArg).getNumberValue()); return new NumberMatcher(((NumberEval)evaluatedCriteriaArg).getNumberValue(), CmpOp.OP_NONE);
} }
if(evaluatedCriteriaArg instanceof BoolEval) { if(evaluatedCriteriaArg instanceof BoolEval) {
return new BooleanMatcher(((BoolEval)evaluatedCriteriaArg).getBooleanValue()); return new BooleanMatcher(((BoolEval)evaluatedCriteriaArg).getBooleanValue(), CmpOp.OP_NONE);
} }
if(evaluatedCriteriaArg instanceof StringEval) { if(evaluatedCriteriaArg instanceof StringEval) {
return createGeneralMatchPredicate((StringEval)evaluatedCriteriaArg); return createGeneralMatchPredicate((StringEval)evaluatedCriteriaArg);
} }
@ -181,50 +402,29 @@ public final class Countif implements Function {
*/ */
private static I_MatchPredicate createGeneralMatchPredicate(StringEval stringEval) { private static I_MatchPredicate createGeneralMatchPredicate(StringEval stringEval) {
String value = stringEval.getStringValue(); String value = stringEval.getStringValue();
char firstChar = value.charAt(0); CmpOp operator = CmpOp.getOperator(value);
value = value.substring(operator.getLength());
Boolean booleanVal = parseBoolean(value); Boolean booleanVal = parseBoolean(value);
if(booleanVal != null) { if(booleanVal != null) {
return new BooleanMatcher(booleanVal.booleanValue()); return new BooleanMatcher(booleanVal.booleanValue(), operator);
} }
Double doubleVal = parseDouble(value);
if(doubleVal != null) {
return new NumberMatcher(doubleVal.doubleValue());
}
switch(firstChar) {
case '>':
case '<':
case '=':
throw new RuntimeException("Incomplete code - criteria expressions such as '"
+ value + "' not supported yet");
}
//else - just a plain string with no interpretation.
return new StringMatcher(value);
}
/** Double doubleVal = OperandResolver.parseDouble(value);
* Under certain circumstances COUNTA will equate a plain number with a string representation of that number if(doubleVal != null) {
*/ return new NumberMatcher(doubleVal.doubleValue(), operator);
/* package */ static Double parseDouble(String strRep) {
if(!Character.isDigit(strRep.charAt(0))) {
// avoid using NumberFormatException to tell when string is not a number
return null;
} }
// TODO - support notation like '1E3' (==1000)
//else - just a plain string with no interpretation.
double val; return new StringMatcher(value, operator);
try {
val = Double.parseDouble(strRep);
} catch (NumberFormatException e) {
return null;
}
return new Double(val);
} }
/** /**
* Boolean literals ('TRUE', 'FALSE') treated similarly but NOT same as numbers. * Boolean literals ('TRUE', 'FALSE') treated similarly but NOT same as numbers.
*/ */
/* package */ static Boolean parseBoolean(String strRep) { /* package */ static Boolean parseBoolean(String strRep) {
if (strRep.length() < 1) {
return null;
}
switch(strRep.charAt(0)) { switch(strRep.charAt(0)) {
case 't': case 't':
case 'T': case 'T':

View File

@ -25,7 +25,9 @@ import org.apache.poi.hssf.record.formula.eval.AreaEval;
import org.apache.poi.hssf.record.formula.eval.BoolEval; import org.apache.poi.hssf.record.formula.eval.BoolEval;
import org.apache.poi.hssf.record.formula.eval.ErrorEval; import org.apache.poi.hssf.record.formula.eval.ErrorEval;
import org.apache.poi.hssf.record.formula.eval.Eval; import org.apache.poi.hssf.record.formula.eval.Eval;
import org.apache.poi.hssf.record.formula.eval.EvaluationException;
import org.apache.poi.hssf.record.formula.eval.NumericValueEval; import org.apache.poi.hssf.record.formula.eval.NumericValueEval;
import org.apache.poi.hssf.record.formula.eval.OperandResolver;
import org.apache.poi.hssf.record.formula.eval.Ref3DEval; import org.apache.poi.hssf.record.formula.eval.Ref3DEval;
import org.apache.poi.hssf.record.formula.eval.RefEval; import org.apache.poi.hssf.record.formula.eval.RefEval;
import org.apache.poi.hssf.record.formula.eval.StringEval; import org.apache.poi.hssf.record.formula.eval.StringEval;
@ -55,21 +57,6 @@ public final class Offset implements FreeRefFunction {
private static final int LAST_VALID_COLUMN_INDEX = 0xFF; private static final int LAST_VALID_COLUMN_INDEX = 0xFF;
/**
* Exceptions are used within this class to help simplify flow control when error conditions
* are encountered
*/
private static final class EvalEx extends Exception {
private final ErrorEval _error;
public EvalEx(ErrorEval error) {
_error = error;
}
public ErrorEval getError() {
return _error;
}
}
/** /**
* A one dimensional base + offset. Represents either a row range or a column range. * A one dimensional base + offset. Represents either a row range or a column range.
* Two instances of this class together specify an area range. * Two instances of this class together specify an area range.
@ -133,8 +120,7 @@ public final class Offset implements FreeRefFunction {
return sb.toString(); return sb.toString();
} }
} }
/** /**
* Encapsulates either an area or cell reference which may be 2d or 3d. * Encapsulates either an area or cell reference which may be 2d or 3d.
*/ */
@ -175,19 +161,15 @@ public final class Offset implements FreeRefFunction {
public int getWidth() { public int getWidth() {
return _width; return _width;
} }
public int getHeight() { public int getHeight() {
return _height; return _height;
} }
public int getFirstRowIndex() { public int getFirstRowIndex() {
return _firstRowIndex; return _firstRowIndex;
} }
public int getFirstColumnIndex() { public int getFirstColumnIndex() {
return _firstColumnIndex; return _firstColumnIndex;
} }
public boolean isIs3d() { public boolean isIs3d() {
return _externalSheetIndex > 0; return _externalSheetIndex > 0;
} }
@ -198,7 +180,6 @@ public final class Offset implements FreeRefFunction {
} }
return (short) _externalSheetIndex; return (short) _externalSheetIndex;
} }
} }
public ValueEval evaluate(Eval[] args, int srcCellRow, short srcCellCol, Workbook workbook, Sheet sheet) { public ValueEval evaluate(Eval[] args, int srcCellRow, short srcCellCol, Workbook workbook, Sheet sheet) {
@ -207,7 +188,6 @@ public final class Offset implements FreeRefFunction {
return ErrorEval.VALUE_INVALID; return ErrorEval.VALUE_INVALID;
} }
try { try {
BaseRef baseRef = evaluateBaseRef(args[0]); BaseRef baseRef = evaluateBaseRef(args[0]);
int rowOffset = evaluateIntArg(args[1], srcCellRow, srcCellCol); int rowOffset = evaluateIntArg(args[1], srcCellRow, srcCellCol);
@ -227,24 +207,23 @@ public final class Offset implements FreeRefFunction {
LinearOffsetRange rowOffsetRange = new LinearOffsetRange(rowOffset, height); LinearOffsetRange rowOffsetRange = new LinearOffsetRange(rowOffset, height);
LinearOffsetRange colOffsetRange = new LinearOffsetRange(columnOffset, width); LinearOffsetRange colOffsetRange = new LinearOffsetRange(columnOffset, width);
return createOffset(baseRef, rowOffsetRange, colOffsetRange, workbook, sheet); return createOffset(baseRef, rowOffsetRange, colOffsetRange, workbook, sheet);
} catch (EvalEx e) { } catch (EvaluationException e) {
return e.getError(); return e.getErrorEval();
} }
} }
private static AreaEval createOffset(BaseRef baseRef, private static AreaEval createOffset(BaseRef baseRef,
LinearOffsetRange rowOffsetRange, LinearOffsetRange colOffsetRange, LinearOffsetRange rowOffsetRange, LinearOffsetRange colOffsetRange,
Workbook workbook, Sheet sheet) throws EvalEx { Workbook workbook, Sheet sheet) throws EvaluationException {
LinearOffsetRange rows = rowOffsetRange.normaliseAndTranslate(baseRef.getFirstRowIndex()); LinearOffsetRange rows = rowOffsetRange.normaliseAndTranslate(baseRef.getFirstRowIndex());
LinearOffsetRange cols = colOffsetRange.normaliseAndTranslate(baseRef.getFirstColumnIndex()); LinearOffsetRange cols = colOffsetRange.normaliseAndTranslate(baseRef.getFirstColumnIndex());
if(rows.isOutOfBounds(0, LAST_VALID_ROW_INDEX)) { if(rows.isOutOfBounds(0, LAST_VALID_ROW_INDEX)) {
throw new EvalEx(ErrorEval.REF_INVALID); throw new EvaluationException(ErrorEval.REF_INVALID);
} }
if(cols.isOutOfBounds(0, LAST_VALID_COLUMN_INDEX)) { if(cols.isOutOfBounds(0, LAST_VALID_COLUMN_INDEX)) {
throw new EvalEx(ErrorEval.REF_INVALID); throw new EvaluationException(ErrorEval.REF_INVALID);
} }
if(baseRef.isIs3d()) { if(baseRef.isIs3d()) {
Area3DPtg a3dp = new Area3DPtg(rows.getFirstIndex(), rows.getLastIndex(), Area3DPtg a3dp = new Area3DPtg(rows.getFirstIndex(), rows.getLastIndex(),
@ -260,8 +239,7 @@ public final class Offset implements FreeRefFunction {
return HSSFFormulaEvaluator.evaluateAreaPtg(sheet, workbook, ap); return HSSFFormulaEvaluator.evaluateAreaPtg(sheet, workbook, ap);
} }
private static BaseRef evaluateBaseRef(Eval eval) throws EvaluationException {
private static BaseRef evaluateBaseRef(Eval eval) throws EvalEx {
if(eval instanceof RefEval) { if(eval instanceof RefEval) {
return new BaseRef((RefEval)eval); return new BaseRef((RefEval)eval);
@ -270,16 +248,15 @@ public final class Offset implements FreeRefFunction {
return new BaseRef((AreaEval)eval); return new BaseRef((AreaEval)eval);
} }
if (eval instanceof ErrorEval) { if (eval instanceof ErrorEval) {
throw new EvalEx((ErrorEval) eval); throw new EvaluationException((ErrorEval) eval);
} }
throw new EvalEx(ErrorEval.VALUE_INVALID); throw new EvaluationException(ErrorEval.VALUE_INVALID);
} }
/** /**
* OFFSET's numeric arguments (2..5) have similar processing rules * OFFSET's numeric arguments (2..5) have similar processing rules
*/ */
private static int evaluateIntArg(Eval eval, int srcCellRow, short srcCellCol) throws EvalEx { private static int evaluateIntArg(Eval eval, int srcCellRow, short srcCellCol) throws EvaluationException {
double d = evaluateDoubleArg(eval, srcCellRow, srcCellCol); double d = evaluateDoubleArg(eval, srcCellRow, srcCellCol);
return convertDoubleToInt(d); return convertDoubleToInt(d);
@ -295,18 +272,17 @@ public final class Offset implements FreeRefFunction {
return (int)Math.floor(d); return (int)Math.floor(d);
} }
private static double evaluateDoubleArg(Eval eval, int srcCellRow, short srcCellCol) throws EvaluationException {
private static double evaluateDoubleArg(Eval eval, int srcCellRow, short srcCellCol) throws EvalEx { ValueEval ve = OperandResolver.getSingleValue(eval, srcCellRow, srcCellCol);
ValueEval ve = evaluateSingleValue(eval, srcCellRow, srcCellCol);
if (ve instanceof NumericValueEval) { if (ve instanceof NumericValueEval) {
return ((NumericValueEval) ve).getNumberValue(); return ((NumericValueEval) ve).getNumberValue();
} }
if (ve instanceof StringEval) { if (ve instanceof StringEval) {
StringEval se = (StringEval) ve; StringEval se = (StringEval) ve;
Double d = parseDouble(se.getStringValue()); Double d = OperandResolver.parseDouble(se.getStringValue());
if(d == null) { if(d == null) {
throw new EvalEx(ErrorEval.VALUE_INVALID); throw new EvaluationException(ErrorEval.VALUE_INVALID);
} }
return d.doubleValue(); return d.doubleValue();
} }
@ -319,44 +295,4 @@ public final class Offset implements FreeRefFunction {
} }
throw new RuntimeException("Unexpected eval type (" + ve.getClass().getName() + ")"); throw new RuntimeException("Unexpected eval type (" + ve.getClass().getName() + ")");
} }
private static Double parseDouble(String s) {
// TODO - find a home for this method
// TODO - support various number formats: sign char, dollars, commas
// OFFSET and COUNTIF seem to handle these
return Countif.parseDouble(s);
}
private static ValueEval evaluateSingleValue(Eval eval, int srcCellRow, short srcCellCol) throws EvalEx {
if(eval instanceof RefEval) {
return ((RefEval)eval).getInnerValueEval();
}
if(eval instanceof AreaEval) {
return chooseSingleElementFromArea((AreaEval)eval, srcCellRow, srcCellCol);
}
if (eval instanceof ValueEval) {
return (ValueEval) eval;
}
throw new RuntimeException("Unexpected eval type (" + eval.getClass().getName() + ")");
}
// TODO - this code seems to get repeated a bit
private static ValueEval chooseSingleElementFromArea(AreaEval ae, int srcCellRow, short srcCellCol) throws EvalEx {
if (ae.isColumn()) {
if (ae.isRow()) {
return ae.getValues()[0];
}
if (!ae.containsRow(srcCellRow)) {
throw new EvalEx(ErrorEval.VALUE_INVALID);
}
return ae.getValueAt(srcCellRow, ae.getFirstColumn());
}
if (!ae.isRow()) {
throw new EvalEx(ErrorEval.VALUE_INVALID);
}
if (!ae.containsColumn(srcCellCol)) {
throw new EvalEx(ErrorEval.VALUE_INVALID);
}
return ae.getValueAt(ae.getFirstRow(), srcCellCol);
}
} }

View File

@ -924,7 +924,13 @@ public class HSSFCell implements Cell
public void setCellStyle(CellStyle style) public void setCellStyle(CellStyle style)
{ {
record.setXFIndex(((HSSFCellStyle) style).getIndex()); HSSFCellStyle hStyle = (HSSFCellStyle)style;
// Verify it really does belong to our workbook
hStyle.verifyBelongsToWorkbook(book);
// Change our cell record to use this style
record.setXFIndex(hStyle.getIndex());
} }
/** /**

View File

@ -20,6 +20,7 @@ package org.apache.poi.hssf.usermodel;
import org.apache.poi.hssf.model.Workbook; import org.apache.poi.hssf.model.Workbook;
import org.apache.poi.hssf.record.ExtendedFormatRecord; import org.apache.poi.hssf.record.ExtendedFormatRecord;
import org.apache.poi.hssf.record.FontRecord;
import org.apache.poi.hssf.util.HSSFColor; import org.apache.poi.hssf.util.HSSFColor;
import org.apache.poi.ss.usermodel.CellStyle; import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.Font; import org.apache.poi.ss.usermodel.Font;
@ -295,6 +296,16 @@ public class HSSFCellStyle implements CellStyle
return format.getFormat(getDataFormat()); return format.getFormat(getDataFormat());
} }
/**
* Get the contents of the format string, by looking up
* the DataFormat against the supplied low level workbook
* @see org.apache.poi.hssf.usermodel.HSSFDataFormat
*/
public String getDataFormatString(org.apache.poi.hssf.model.Workbook workbook) {
HSSFDataFormat format = new HSSFDataFormat( workbook );
return format.getFormat(getDataFormat());
}
/** /**
* set the font for this style * set the font for this style
@ -930,6 +941,69 @@ public class HSSFCellStyle implements CellStyle
return format.getFillForeground(); return format.getFillForeground();
} }
/**
* Verifies that this style belongs to the supplied Workbook.
* Will throw an exception if it belongs to a different one.
* This is normally called when trying to assign a style to a
* cell, to ensure the cell and the style are from the same
* workbook (if they're not, it won't work)
* @throws IllegalArgumentException if there's a workbook mis-match
*/
public void verifyBelongsToWorkbook(HSSFWorkbook wb) {
if(wb.getWorkbook() != workbook) {
throw new IllegalArgumentException("This Style does not belong to the supplied Workbook. Are you trying to assign a style from one workbook to the cell of a differnt workbook?");
}
}
/**
* Clones all the style information from another
* HSSFCellStyle, onto this one. This
* HSSFCellStyle will then have all the same
* properties as the source, but the two may
* be edited independently.
* Any stylings on this HSSFCellStyle will be lost!
*
* The source HSSFCellStyle could be from another
* HSSFWorkbook if you like. This allows you to
* copy styles from one HSSFWorkbook to another.
*/
public void cloneStyleFrom(CellStyle source) {
if(source instanceof HSSFCellStyle) {
this.cloneStyleFrom((HSSFCellStyle)source);
}
throw new IllegalArgumentException("Can only clone from one HSSFCellStyle to another, not between HSSFCellStyle and XSSFCellStyle");
}
public void cloneStyleFrom(HSSFCellStyle source) {
// First we need to clone the extended format
// record
format.cloneStyleFrom(source.format);
// Handle matching things if we cross workbooks
if(workbook != source.workbook) {
// Then we need to clone the format string,
// and update the format record for this
short fmt = workbook.createFormat(
source.getDataFormatString()
);
setDataFormat(fmt);
// Finally we need to clone the font,
// and update the format record for this
FontRecord fr = workbook.createNewFont();
fr.cloneStyleFrom(
source.workbook.getFontRecordAt(
source.getFontIndex()
)
);
HSSFFont font = new HSSFFont(
(short)workbook.getFontIndex(fr), fr
);
setFont(font);
}
}
public int hashCode() { public int hashCode() {
final int prime = 31; final int prime = 31;
int result = 1; int result = 1;

View File

@ -705,4 +705,21 @@ public interface CellStyle {
*/ */
short getFillForegroundColor(); short getFillForegroundColor();
} /**
* Clones all the style information from another
* CellStyle, onto this one. This
* CellStyle will then have all the same
* properties as the source, but the two may
* be edited independently.
* Any stylings on this CellStyle will be lost!
*
* The source CellStyle could be from another
* Workbook if you like. This allows you to
* copy styles from one Workbook to another.
*
* However, both of the CellStyles will need
* to be of the same type (HSSFCellStyle or
* XSSFCellStyle)
*/
public void cloneStyleFrom(CellStyle source);
}

View File

@ -333,8 +333,13 @@ public final class XSSFCell implements Cell {
if(style == null) { if(style == null) {
this.cell.setS(0); this.cell.setS(0);
} else { } else {
XSSFCellStyle xStyle = (XSSFCellStyle)style;
xStyle.verifyBelongsToStylesSource(
row.getSheet().getWorkbook().getStylesSource()
);
this.cell.setS( this.cell.setS(
row.getSheet().getWorkbook().getStylesSource().putStyle(style) row.getSheet().getWorkbook().getStylesSource().putStyle(xStyle)
); );
} }
} }

View File

@ -84,6 +84,43 @@ public class XSSFCellStyle implements CellStyle {
cellXf = CTXf.Factory.newInstance(); cellXf = CTXf.Factory.newInstance();
cellStyleXf = null; cellStyleXf = null;
} }
/**
* Verifies that this style belongs to the supplied Workbook
* Styles Source.
* Will throw an exception if it belongs to a different one.
* This is normally called when trying to assign a style to a
* cell, to ensure the cell and the style are from the same
* workbook (if they're not, it won't work)
* @throws IllegalArgumentException if there's a workbook mis-match
*/
public void verifyBelongsToStylesSource(StylesSource src) {
if(this.stylesSource != src) {
throw new IllegalArgumentException("This Style does not belong to the supplied Workbook Stlyes Source. Are you trying to assign a style from one workbook to the cell of a differnt workbook?");
}
}
/**
* Clones all the style information from another
* XSSFCellStyle, onto this one. This
* XSSFCellStyle will then have all the same
* properties as the source, but the two may
* be edited independently.
* Any stylings on this XSSFCellStyle will be lost!
*
* The source XSSFCellStyle could be from another
* XSSFWorkbook if you like. This allows you to
* copy styles from one XSSFWorkbook to another.
*/
public void cloneStyleFrom(CellStyle source) {
if(source instanceof XSSFCellStyle) {
this.cloneStyleFrom((XSSFCellStyle)source);
}
throw new IllegalArgumentException("Can only clone from one XSSFCellStyle to another, not between HSSFCellStyle and XSSFCellStyle");
}
public void cloneStyleFrom(XSSFCellStyle source) {
throw new IllegalStateException("TODO");
}
public short getAlignment() { public short getAlignment() {
return (short)getAlignmentEnum().intValue(); return (short)getAlignmentEnum().intValue();

View File

@ -342,4 +342,41 @@ public class TestXSSFCell extends TestCase {
XSSFRow row = new XSSFRow(sheet); XSSFRow row = new XSSFRow(sheet);
return row; return row;
} }
/**
* Test to ensure we can only assign cell styles that belong
* to our workbook, and not those from other workbooks.
*/
public void testCellStyleWorkbookMatch() throws Exception {
XSSFWorkbook wbA = new XSSFWorkbook();
XSSFWorkbook wbB = new XSSFWorkbook();
XSSFCellStyle styA = (XSSFCellStyle)wbA.createCellStyle();
XSSFCellStyle styB = (XSSFCellStyle)wbB.createCellStyle();
styA.verifyBelongsToStylesSource(wbA.getStylesSource());
styB.verifyBelongsToStylesSource(wbB.getStylesSource());
try {
styA.verifyBelongsToStylesSource(wbB.getStylesSource());
fail();
} catch(IllegalArgumentException e) {}
try {
styB.verifyBelongsToStylesSource(wbA.getStylesSource());
fail();
} catch(IllegalArgumentException e) {}
Cell cellA = wbA.createSheet().createRow(0).createCell((short)0);
Cell cellB = wbB.createSheet().createRow(0).createCell((short)0);
cellA.setCellStyle(styA);
cellB.setCellStyle(styB);
try {
cellA.setCellStyle(styB);
fail();
} catch(IllegalArgumentException e) {}
try {
cellB.setCellStyle(styA);
fail();
} catch(IllegalArgumentException e) {}
}
} }

View File

@ -273,4 +273,17 @@ public class TestXSSFCellStyle extends TestCase {
cellStyle.setWrapText(false); cellStyle.setWrapText(false);
assertFalse(cellXf.getAlignment().getWrapText()); assertFalse(cellXf.getAlignment().getWrapText());
} }
/**
* Cloning one XSSFCellStyle onto Another, same XSSFWorkbook
*/
public void testCloneStyleSameWB() throws Exception {
// TODO
}
/**
* Cloning one XSSFCellStyle onto Another, different XSSFWorkbooks
*/
public void testCloneStyleDiffWB() throws Exception {
// TODO
}
} }

View File

@ -38,6 +38,7 @@ public final class AllModelTests {
result.addTestSuite(TestRVA.class); result.addTestSuite(TestRVA.class);
result.addTestSuite(TestSheet.class); result.addTestSuite(TestSheet.class);
result.addTestSuite(TestSheetAdditional.class); result.addTestSuite(TestSheetAdditional.class);
result.addTestSuite(TestWorkbook.class);
return result; return result;
} }
} }

View File

@ -0,0 +1,61 @@
/* ====================================================================
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.model;
import org.apache.poi.hssf.record.FontRecord;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import junit.framework.TestCase;
/**
* Unit test for the Workbook class.
*
* @author Glen Stampoultzis (glens at apache.org)
*/
public final class TestWorkbook extends TestCase {
public void testFontStuff() throws Exception {
Workbook wb = (new HW()).getWorkbook();
assertEquals(4, wb.getNumberOfFontRecords());
FontRecord f1 = wb.getFontRecordAt(0);
FontRecord f4 = wb.getFontRecordAt(3);
assertEquals(0, wb.getFontIndex(f1));
assertEquals(3, wb.getFontIndex(f4));
assertEquals(f1, wb.getFontRecordAt(0));
assertEquals(f4, wb.getFontRecordAt(3));
// There is no 4! new ones go in at 5
FontRecord n = wb.createNewFont();
assertEquals(5, wb.getNumberOfFontRecords());
assertEquals(5, wb.getFontIndex(n));
assertEquals(n, wb.getFontRecordAt(5));
}
private class HW extends HSSFWorkbook {
private HW() {
super();
}
protected Workbook getWorkbook() {
return super.getWorkbook();
}
}
}

View File

@ -61,7 +61,9 @@ public final class AllRecordTests {
result.addTestSuite(TestEmbeddedObjectRefSubRecord.class); result.addTestSuite(TestEmbeddedObjectRefSubRecord.class);
result.addTestSuite(TestEndSubRecord.class); result.addTestSuite(TestEndSubRecord.class);
result.addTestSuite(TestEscherAggregate.class); result.addTestSuite(TestEscherAggregate.class);
result.addTestSuite(TestExtendedFormatRecord.class);
result.addTestSuite(TestExternalNameRecord.class); result.addTestSuite(TestExternalNameRecord.class);
result.addTestSuite(TestFontRecord.class);
result.addTestSuite(TestFontBasisRecord.class); result.addTestSuite(TestFontBasisRecord.class);
result.addTestSuite(TestFontIndexRecord.class); result.addTestSuite(TestFontIndexRecord.class);
result.addTestSuite(TestFormulaRecord.class); result.addTestSuite(TestFormulaRecord.class);

View File

@ -0,0 +1,138 @@
/* ====================================================================
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.record;
import junit.framework.TestCase;
/**
*/
public final class TestExtendedFormatRecord extends TestCase {
byte[] header = new byte[] {
0xE0-256, 00, 0x14, 00 // sid=e0, 20 bytes long
};
byte[] data = new byte[] {
00, 00, // Font 0
00, 00, // Format 0
0xF5-256, 0xFF-256, // Cell opts ...
0x20, 00, // Alignment 20
00, 00, // Ident 0
00, 00, // Border 0
00, 00, // Palette 0
00, 00, 00, 00, // ADTL Palette 0
0xC0-256, 0x20 // Fill Palette 20c0
};
public TestExtendedFormatRecord(String name)
{
super(name);
}
public void testLoad()
throws Exception
{
ExtendedFormatRecord record = new ExtendedFormatRecord(new TestcaseRecordInputStream((short)0xe0, (short)data.length, data));
assertEquals(0, record.getFontIndex());
assertEquals(0, record.getFormatIndex());
assertEquals(0xF5-256, record.getCellOptions());
assertEquals(0x20, record.getAlignmentOptions());
assertEquals(0, record.getIndentionOptions());
assertEquals(0, record.getBorderOptions());
assertEquals(0, record.getPaletteOptions());
assertEquals(0, record.getAdtlPaletteOptions());
assertEquals(0x20c0, record.getFillPaletteOptions());
assertEquals( 20 + 4, record.getRecordSize() );
record.validateSid((short)0xe0);
}
public void testStore()
{
// .fontindex = 0
// .formatindex = 0
// .celloptions = fffffff5
// .islocked = true
// .ishidden = false
// .recordtype= 1
// .parentidx = fff
// .alignmentoptions= 20
// .alignment = 0
// .wraptext = false
// .valignment= 2
// .justlast = 0
// .rotation = 0
// .indentionoptions= 0
// .indent = 0
// .shrinktoft= false
// .mergecells= false
// .readngordr= 0
// .formatflag= false
// .fontflag = false
// .prntalgnmt= false
// .borderflag= false
// .paternflag= false
// .celloption= false
// .borderoptns = 0
// .lftln = 0
// .rgtln = 0
// .topln = 0
// .btmln = 0
// .paleteoptns = 0
// .leftborder= 0
// .rghtborder= 0
// .diag = 0
// .paleteoptn2 = 0
// .topborder = 0
// .botmborder= 0
// .adtldiag = 0
// .diaglnstyl= 0
// .fillpattrn= 0
// .fillpaloptn = 20c0
// .foreground= 40
// .background= 41
ExtendedFormatRecord record = new ExtendedFormatRecord();
record.setFontIndex((short)0);
record.setFormatIndex((short)0);
record.setLocked(true);
record.setHidden(false);
record.setXFType((short)1);
record.setParentIndex((short)0xfff);
record.setVerticalAlignment((short)2);
record.setFillForeground((short)0x40);
record.setFillBackground((short)0x41);
byte [] recordBytes = record.serialize();
assertEquals(recordBytes.length - 4, data.length);
for (int i = 0; i < data.length; i++)
assertEquals("At offset " + i, data[i], recordBytes[i+4]);
}
public void testCloneOnto() throws Exception {
ExtendedFormatRecord base = new ExtendedFormatRecord(new TestcaseRecordInputStream((short)0xe0, (short)data.length, data));
ExtendedFormatRecord other = new ExtendedFormatRecord();
other.cloneStyleFrom(base);
byte [] recordBytes = other.serialize();
assertEquals(recordBytes.length - 4, data.length);
for (int i = 0; i < data.length; i++)
assertEquals("At offset " + i, data[i], recordBytes[i+4]);
}
}

View File

@ -0,0 +1,124 @@
/* ====================================================================
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.record;
import junit.framework.TestCase;
/**
* Tests the serialization and deserialization of the FontRecord
* class works correctly. Test data taken directly from a real
* Excel file.
*/
public class TestFontRecord
extends TestCase
{
byte[] header = new byte[] {
0x31, 00, 0x1a, 00, // sid=31, 26 bytes long
};
byte[] data = new byte[] {
0xC8-256, 00, // font height = xc8
00, 00, // attrs = 0
0xFF-256, 0x7F, // colour palette = x7fff
0x90-256, 0x01, // bold weight = x190
00, 00, // supersubscript
00, 00, // underline, family
00, 00, // charset, padding
05, 01, // name length, unicode flag
0x41, 0x00, 0x72, 0x00, 0x69, // Arial, as unicode
0x00, 0x61, 0x00, 0x6C, 0x00
};
public TestFontRecord(String name)
{
super(name);
}
public void testLoad()
throws Exception
{
FontRecord record = new FontRecord(new TestcaseRecordInputStream((short)0x31, (short)data.length, data));
assertEquals( 0xc8, record.getFontHeight());
assertEquals( 0x00, record.getAttributes());
assertFalse( record.isItalic());
assertFalse( record.isStruckout());
assertFalse( record.isMacoutlined());
assertFalse( record.isMacshadowed());
assertEquals( 0x7fff, record.getColorPaletteIndex());
assertEquals( 0x190, record.getBoldWeight());
assertEquals( 0x00, record.getSuperSubScript());
assertEquals( 0x00, record.getUnderline());
assertEquals( 0x00, record.getFamily());
assertEquals( 0x00, record.getCharset());
assertEquals( 0x05, record.getFontNameLength());
assertEquals( "Arial", record.getFontName());
assertEquals( 26 + 4, record.getRecordSize() );
record.validateSid((short)0x31);
}
public void testStore()
{
// .fontheight = c8
// .attributes = 0
// .italic = false
// .strikout = false
// .macoutlined= false
// .macshadowed= false
// .colorpalette = 7fff
// .boldweight = 190
// .supersubscript = 0
// .underline = 0
// .family = 0
// .charset = 0
// .namelength = 5
// .fontname = Arial
FontRecord record = new FontRecord();
record.setFontHeight((short)0xc8);
record.setAttributes((short)0);
record.setColorPaletteIndex((short)0x7fff);
record.setBoldWeight((short)0x190);
record.setSuperSubScript((short)0);
record.setUnderline((byte)0);
record.setFamily((byte)0);
record.setCharset((byte)0);
record.setFontNameLength((byte)5);
record.setFontName("Arial");
byte [] recordBytes = record.serialize();
assertEquals(recordBytes.length - 4, data.length);
for (int i = 0; i < data.length; i++)
assertEquals("At offset " + i, data[i], recordBytes[i+4]);
}
public void testCloneOnto() throws Exception {
FontRecord base = new FontRecord(new TestcaseRecordInputStream((short)0x31, (short)data.length, data));
FontRecord other = new FontRecord();
other.cloneStyleFrom(base);
byte [] recordBytes = other.serialize();
assertEquals(recordBytes.length - 4, data.length);
for (int i = 0; i < data.length; i++)
assertEquals("At offset " + i, data[i], recordBytes[i+4]);
}
}

View File

@ -18,8 +18,10 @@
package org.apache.poi.hssf.record.formula.functions; package org.apache.poi.hssf.record.formula.functions;
import junit.framework.AssertionFailedError;
import junit.framework.TestCase; import junit.framework.TestCase;
import org.apache.poi.hssf.HSSFTestDataSamples;
import org.apache.poi.hssf.record.formula.AreaPtg; import org.apache.poi.hssf.record.formula.AreaPtg;
import org.apache.poi.hssf.record.formula.RefPtg; import org.apache.poi.hssf.record.formula.RefPtg;
import org.apache.poi.hssf.record.formula.eval.Area2DEval; import org.apache.poi.hssf.record.formula.eval.Area2DEval;
@ -31,6 +33,13 @@ import org.apache.poi.hssf.record.formula.eval.NumberEval;
import org.apache.poi.hssf.record.formula.eval.Ref2DEval; import org.apache.poi.hssf.record.formula.eval.Ref2DEval;
import org.apache.poi.hssf.record.formula.eval.StringEval; import org.apache.poi.hssf.record.formula.eval.StringEval;
import org.apache.poi.hssf.record.formula.eval.ValueEval; import org.apache.poi.hssf.record.formula.eval.ValueEval;
import org.apache.poi.hssf.record.formula.functions.Countif.I_MatchPredicate;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.FormulaEvaluator.CellValue;
/** /**
* Test cases for COUNT(), COUNTA() COUNTIF(), COUNTBLANK() * Test cases for COUNT(), COUNTA() COUNTIF(), COUNTBLANK()
@ -146,4 +155,154 @@ public final class TestCountFuncs extends TestCase {
double result = NumericFunctionInvoker.invoke(new Countif(), args); double result = NumericFunctionInvoker.invoke(new Countif(), args);
assertEquals(expected, result, 0); assertEquals(expected, result, 0);
} }
public void testCountIfEmptyStringCriteria() {
I_MatchPredicate mp;
// pred '=' matches blank cell but not empty string
mp = Countif.createCriteriaPredicate(new StringEval("="));
confirmPredicate(false, mp, "");
confirmPredicate(true, mp, null);
// pred '' matches both blank cell but not empty string
mp = Countif.createCriteriaPredicate(new StringEval(""));
confirmPredicate(true, mp, "");
confirmPredicate(true, mp, null);
// pred '<>' matches empty string but not blank cell
mp = Countif.createCriteriaPredicate(new StringEval("<>"));
confirmPredicate(false, mp, null);
confirmPredicate(true, mp, "");
}
public void testCountifComparisons() {
I_MatchPredicate mp;
mp = Countif.createCriteriaPredicate(new StringEval(">5"));
confirmPredicate(false, mp, 4);
confirmPredicate(false, mp, 5);
confirmPredicate(true, mp, 6);
mp = Countif.createCriteriaPredicate(new StringEval("<=5"));
confirmPredicate(true, mp, 4);
confirmPredicate(true, mp, 5);
confirmPredicate(false, mp, 6);
confirmPredicate(true, mp, "4.9");
confirmPredicate(false, mp, "4.9t");
confirmPredicate(false, mp, "5.1");
confirmPredicate(false, mp, null);
mp = Countif.createCriteriaPredicate(new StringEval("=abc"));
confirmPredicate(true, mp, "abc");
mp = Countif.createCriteriaPredicate(new StringEval("=42"));
confirmPredicate(false, mp, 41);
confirmPredicate(true, mp, 42);
confirmPredicate(true, mp, "42");
mp = Countif.createCriteriaPredicate(new StringEval(">abc"));
confirmPredicate(false, mp, 4);
confirmPredicate(false, mp, "abc");
confirmPredicate(true, mp, "abd");
mp = Countif.createCriteriaPredicate(new StringEval(">4t3"));
confirmPredicate(false, mp, 4);
confirmPredicate(false, mp, 500);
confirmPredicate(true, mp, "500");
confirmPredicate(true, mp, "4t4");
}
public void testWildCards() {
I_MatchPredicate mp;
mp = Countif.createCriteriaPredicate(new StringEval("a*b"));
confirmPredicate(false, mp, "abc");
confirmPredicate(true, mp, "ab");
confirmPredicate(true, mp, "axxb");
confirmPredicate(false, mp, "xab");
mp = Countif.createCriteriaPredicate(new StringEval("a?b"));
confirmPredicate(false, mp, "abc");
confirmPredicate(false, mp, "ab");
confirmPredicate(false, mp, "axxb");
confirmPredicate(false, mp, "xab");
confirmPredicate(true, mp, "axb");
mp = Countif.createCriteriaPredicate(new StringEval("a~?"));
confirmPredicate(false, mp, "a~a");
confirmPredicate(false, mp, "a~?");
confirmPredicate(true, mp, "a?");
mp = Countif.createCriteriaPredicate(new StringEval("~*a"));
confirmPredicate(false, mp, "~aa");
confirmPredicate(false, mp, "~*a");
confirmPredicate(true, mp, "*a");
mp = Countif.createCriteriaPredicate(new StringEval("12?12"));
confirmPredicate(false, mp, 12812);
confirmPredicate(true, mp, "12812");
confirmPredicate(false, mp, "128812");
}
public void testNotQuiteWildCards() {
I_MatchPredicate mp;
// make sure special reg-ex chars are treated like normal chars
mp = Countif.createCriteriaPredicate(new StringEval("a.b"));
confirmPredicate(false, mp, "aab");
confirmPredicate(true, mp, "a.b");
mp = Countif.createCriteriaPredicate(new StringEval("a~b"));
confirmPredicate(false, mp, "ab");
confirmPredicate(false, mp, "axb");
confirmPredicate(false, mp, "a~~b");
confirmPredicate(true, mp, "a~b");
mp = Countif.createCriteriaPredicate(new StringEval(">a*b"));
confirmPredicate(false, mp, "a(b");
confirmPredicate(true, mp, "aab");
confirmPredicate(false, mp, "a*a");
confirmPredicate(true, mp, "a*c");
}
private static void confirmPredicate(boolean expectedResult, I_MatchPredicate matchPredicate, int value) {
assertEquals(expectedResult, matchPredicate.matches(new NumberEval(value)));
}
private static void confirmPredicate(boolean expectedResult, I_MatchPredicate matchPredicate, String value) {
Eval ev = value == null ? (Eval)BlankEval.INSTANCE : new StringEval(value);
assertEquals(expectedResult, matchPredicate.matches(ev));
}
public void testCountifFromSpreadsheet() {
final String FILE_NAME = "countifExamples.xls";
final int START_ROW_IX = 1;
final int COL_IX_ACTUAL = 2;
final int COL_IX_EXPECTED = 3;
int failureCount = 0;
HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook(FILE_NAME);
HSSFSheet sheet = wb.getSheetAt(0);
HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(sheet, wb);
int maxRow = sheet.getLastRowNum();
for (int rowIx=START_ROW_IX; rowIx<maxRow; rowIx++) {
HSSFRow row = sheet.getRow(rowIx);
if(row == null) {
continue;
}
HSSFCell cell = row.getCell(COL_IX_ACTUAL);
fe.setCurrentRow(row);
CellValue cv = fe.evaluate(cell);
double actualValue = cv.getNumberValue();
double expectedValue = row.getCell(COL_IX_EXPECTED).getNumericCellValue();
if (actualValue != expectedValue) {
System.err.println("Problem with test case on row " + (rowIx+1) + " "
+ "Expected = (" + expectedValue + ") Actual=(" + actualValue + ") ");
failureCount++;
}
}
if (failureCount > 0) {
throw new AssertionFailedError(failureCount + " countif evaluations failed. See stderr for more details");
}
}
} }

View File

@ -229,6 +229,80 @@ public class TestCellStyle
// assert((s.getLastRowNum() == 99)); // assert((s.getLastRowNum() == 99));
} }
/**
* Cloning one HSSFCellStyle onto Another, same
* HSSFWorkbook
*/
public void testCloneStyleSameWB() throws Exception {
HSSFWorkbook wb = new HSSFWorkbook();
HSSFFont fnt = wb.createFont();
fnt.setFontName("TestingFont");
assertEquals(5, wb.getNumberOfFonts());
HSSFCellStyle orig = wb.createCellStyle();
orig.setAlignment(HSSFCellStyle.ALIGN_RIGHT);
orig.setFont(fnt);
orig.setDataFormat((short)18);
assertTrue(HSSFCellStyle.ALIGN_RIGHT == orig.getAlignment());
assertTrue(fnt == orig.getFont(wb));
assertTrue(18 == orig.getDataFormat());
HSSFCellStyle clone = wb.createCellStyle();
assertFalse(HSSFCellStyle.ALIGN_RIGHT == clone.getAlignment());
assertFalse(fnt == clone.getFont(wb));
assertFalse(18 == clone.getDataFormat());
clone.cloneStyleFrom(orig);
assertTrue(HSSFCellStyle.ALIGN_RIGHT == clone.getAlignment());
assertTrue(fnt == clone.getFont(wb));
assertTrue(18 == clone.getDataFormat());
assertEquals(5, wb.getNumberOfFonts());
}
/**
* Cloning one HSSFCellStyle onto Another, across
* two different HSSFWorkbooks
*/
public void testCloneStyleDiffWB() throws Exception {
HSSFWorkbook wbOrig = new HSSFWorkbook();
HSSFFont fnt = wbOrig.createFont();
fnt.setFontName("TestingFont");
assertEquals(5, wbOrig.getNumberOfFonts());
HSSFDataFormat fmt = wbOrig.createDataFormat();
fmt.getFormat("MadeUpOne");
fmt.getFormat("MadeUpTwo");
HSSFCellStyle orig = wbOrig.createCellStyle();
orig.setAlignment(HSSFCellStyle.ALIGN_RIGHT);
orig.setFont(fnt);
orig.setDataFormat(fmt.getFormat("Test##"));
assertTrue(HSSFCellStyle.ALIGN_RIGHT == orig.getAlignment());
assertTrue(fnt == orig.getFont(wbOrig));
assertTrue(fmt.getFormat("Test##") == orig.getDataFormat());
// Now a style on another workbook
HSSFWorkbook wbClone = new HSSFWorkbook();
assertEquals(4, wbClone.getNumberOfFonts());
HSSFDataFormat fmtClone = wbClone.createDataFormat();
HSSFCellStyle clone = wbClone.createCellStyle();
assertEquals(4, wbClone.getNumberOfFonts());
assertFalse(HSSFCellStyle.ALIGN_RIGHT == clone.getAlignment());
assertFalse("TestingFont" == clone.getFont(wbClone).getFontName());
clone.cloneStyleFrom(orig);
assertTrue(HSSFCellStyle.ALIGN_RIGHT == clone.getAlignment());
assertTrue("TestingFont" == clone.getFont(wbClone).getFontName());
assertTrue(fmtClone.getFormat("Test##") == clone.getDataFormat());
assertFalse(fmtClone.getFormat("Test##") == fmt.getFormat("Test##"));
assertEquals(5, wbClone.getNumberOfFonts());
}
public static void main(String [] ignored_args) public static void main(String [] ignored_args)
{ {

View File

@ -381,6 +381,43 @@ public final class TestHSSFCell extends TestCase {
throw new AssertionFailedError("Identified bug 44606"); throw new AssertionFailedError("Identified bug 44606");
} }
} }
/**
* Test to ensure we can only assign cell styles that belong
* to our workbook, and not those from other workbooks.
*/
public void testCellStyleWorkbookMatch() throws Exception {
HSSFWorkbook wbA = new HSSFWorkbook();
HSSFWorkbook wbB = new HSSFWorkbook();
HSSFCellStyle styA = wbA.createCellStyle();
HSSFCellStyle styB = wbB.createCellStyle();
styA.verifyBelongsToWorkbook(wbA);
styB.verifyBelongsToWorkbook(wbB);
try {
styA.verifyBelongsToWorkbook(wbB);
fail();
} catch(IllegalArgumentException e) {}
try {
styB.verifyBelongsToWorkbook(wbA);
fail();
} catch(IllegalArgumentException e) {}
HSSFCell cellA = wbA.createSheet().createRow(0).createCell((short)0);
HSSFCell cellB = wbB.createSheet().createRow(0).createCell((short)0);
cellA.setCellStyle(styA);
cellB.setCellStyle(styB);
try {
cellA.setCellStyle(styB);
fail();
} catch(IllegalArgumentException e) {}
try {
cellB.setCellStyle(styA);
fail();
} catch(IllegalArgumentException e) {}
}
public static void main(String [] args) { public static void main(String [] args) {
junit.textui.TestRunner.run(TestHSSFCell.class); junit.textui.TestRunner.run(TestHSSFCell.class);