<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="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">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>
</authors>
</header>
<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>
<p>If you have existing HSSF usermodel code that works just
<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="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">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>
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
{
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() {
final int prime = 31;
public int getRecordSize()
{
+ // Note - no matter the original, we always
+ // re-serialise the font name as unicode
return (getFontNameLength() * 2) + 20;
}
{
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() {
final int prime = 31;
* limitations under the License.
*/
-
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.BlankEval;
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.Eval;
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.StringEval;
import org.apache.poi.hssf.record.formula.eval.ValueEval;
* @author Josh Micich
*/
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.
*/
- private interface I_MatchPredicate {
+ /* package */ interface I_MatchPredicate {
boolean matches(Eval x);
}
-
+
private static final class NumberMatcher implements I_MatchPredicate {
private final double _value;
+ private final CmpOp _operator;
- public NumberMatcher(double value) {
+ public NumberMatcher(double value, CmpOp operator) {
_value = value;
+ _operator = operator;
}
public boolean matches(Eval x) {
+ double testValue;
if(x instanceof StringEval) {
// if the target(x) is a string, but parses as a number
// it may still count as a match
StringEval se = (StringEval)x;
- Double val = parseDouble(se.getStringValue());
+ Double val = OperandResolver.parseDouble(se.getStringValue());
if(val == null) {
// x is text that is not a number
return false;
}
- return val.doubleValue() == _value;
- }
- if(!(x instanceof NumberEval)) {
+ testValue = val.doubleValue();
+ } else if((x instanceof NumberEval)) {
+ NumberEval ne = (NumberEval) x;
+ testValue = ne.getNumberValue();
+ } else {
return false;
}
- NumberEval ne = (NumberEval) x;
- return ne.getNumberValue() == _value;
+ return _operator.evaluate(Double.compare(testValue, _value));
}
}
private static final class BooleanMatcher implements I_MatchPredicate {
- private final boolean _value;
+ private final int _value;
+ private final CmpOp _operator;
- public BooleanMatcher(boolean value) {
- _value = value;
+ public BooleanMatcher(boolean value, CmpOp operator) {
+ _value = boolToInt(value);
+ _operator = operator;
+ }
+
+ private static int boolToInt(boolean value) {
+ return value ? 1 : 0;
}
public boolean matches(Eval x) {
+ int testValue;
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;
Boolean val = parseBoolean(se.getStringValue());
if(val == null) {
// x is text that is not a boolean
return false;
}
- if (true) { // change to false to observe more intuitive behaviour
- // Note - Unlike with numbers, it seems that COUNTA never matches
- // boolean values when the target(x) is a string
- return false;
- }
- return val.booleanValue() == _value;
- }
- if(!(x instanceof BoolEval)) {
+ testValue = boolToInt(val.booleanValue());
+ } else if((x instanceof BoolEval)) {
+ BoolEval be = (BoolEval) x;
+ testValue = boolToInt(be.getBooleanValue());
+ } else {
return false;
}
- BoolEval be = (BoolEval) x;
- return be.getBooleanValue() == _value;
+ return _operator.evaluate(testValue - _value);
}
}
private static final class StringMatcher implements I_MatchPredicate {
private final String _value;
+ private final CmpOp _operator;
+ private final Pattern _pattern;
- public StringMatcher(String value) {
+ public StringMatcher(String value, CmpOp operator) {
_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) {
+ 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;
+ }
if(!(x instanceof StringEval)) {
+ // 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;
}
- StringEval se = (StringEval) x;
- return se.getStringValue() == _value;
+ 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;
}
}
// perhaps this should be an exception
return ErrorEval.VALUE_INVALID;
}
-
- AreaEval range = (AreaEval) args[0];
+
Eval criteriaArg = args[1];
if(criteriaArg instanceof RefEval) {
// criteria is not a literal value, but a cell reference
// other non literal tokens such as function calls, have been fully evaluated
// 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);
- return countMatchingCellsInArea(range, mp);
+ return countMatchingCellsInArea(args[0], mp);
}
/**
* @return the number of evaluated cells in the range that match the specified criteria
*/
- private Eval countMatchingCellsInArea(AreaEval range, I_MatchPredicate criteriaPredicate) {
- ValueEval[] values = range.getValues();
+ private Eval countMatchingCellsInArea(Eval rangeArg, I_MatchPredicate criteriaPredicate) {
int result = 0;
- for (int i = 0; i < values.length; i++) {
- if(criteriaPredicate.matches(values[i])) {
+ if (rangeArg instanceof RefEval) {
+ RefEval refEval = (RefEval) rangeArg;
+ if(criteriaPredicate.matches(refEval.getInnerValueEval())) {
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);
}
-
- private static I_MatchPredicate createCriteriaPredicate(Eval evaluatedCriteriaArg) {
+
+ /* package */ static I_MatchPredicate createCriteriaPredicate(Eval evaluatedCriteriaArg) {
+
if(evaluatedCriteriaArg instanceof NumberEval) {
- return new NumberMatcher(((NumberEval)evaluatedCriteriaArg).getNumberValue());
+ return new NumberMatcher(((NumberEval)evaluatedCriteriaArg).getNumberValue(), CmpOp.OP_NONE);
}
if(evaluatedCriteriaArg instanceof BoolEval) {
- return new BooleanMatcher(((BoolEval)evaluatedCriteriaArg).getBooleanValue());
+ return new BooleanMatcher(((BoolEval)evaluatedCriteriaArg).getBooleanValue(), CmpOp.OP_NONE);
}
-
+
if(evaluatedCriteriaArg instanceof StringEval) {
return createGeneralMatchPredicate((StringEval)evaluatedCriteriaArg);
}
*/
private static I_MatchPredicate createGeneralMatchPredicate(StringEval stringEval) {
String value = stringEval.getStringValue();
- char firstChar = value.charAt(0);
+ CmpOp operator = CmpOp.getOperator(value);
+ value = value.substring(operator.getLength());
+
Boolean booleanVal = parseBoolean(value);
if(booleanVal != null) {
- return new BooleanMatcher(booleanVal.booleanValue());
+ return new BooleanMatcher(booleanVal.booleanValue(), operator);
}
-
- Double doubleVal = parseDouble(value);
+
+ Double doubleVal = OperandResolver.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");
+ return new NumberMatcher(doubleVal.doubleValue(), operator);
}
-
- //else - just a plain string with no interpretation.
- return new StringMatcher(value);
- }
- /**
- * Under certain circumstances COUNTA will equate a plain number with a string representation of that number
- */
- /* 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)
-
- double val;
- try {
- val = Double.parseDouble(strRep);
- } catch (NumberFormatException e) {
- return null;
- }
- return new Double(val);
+ //else - just a plain string with no interpretation.
+ return new StringMatcher(value, operator);
}
/**
* Boolean literals ('TRUE', 'FALSE') treated similarly but NOT same as numbers.
*/
/* package */ static Boolean parseBoolean(String strRep) {
+ if (strRep.length() < 1) {
+ return null;
+ }
switch(strRep.charAt(0)) {
case 't':
case 'T':
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.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.OperandResolver;
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.StringEval;
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.
* Two instances of this class together specify an area range.
return sb.toString();
}
}
-
-
+
/**
* Encapsulates either an area or cell reference which may be 2d or 3d.
*/
public int getWidth() {
return _width;
}
-
public int getHeight() {
return _height;
}
-
public int getFirstRowIndex() {
return _firstRowIndex;
}
-
public int getFirstColumnIndex() {
return _firstColumnIndex;
}
-
public boolean isIs3d() {
return _externalSheetIndex > 0;
}
}
return (short) _externalSheetIndex;
}
-
}
public ValueEval evaluate(Eval[] args, int srcCellRow, short srcCellCol, Workbook workbook, Sheet sheet) {
return ErrorEval.VALUE_INVALID;
}
-
try {
BaseRef baseRef = evaluateBaseRef(args[0]);
int rowOffset = evaluateIntArg(args[1], srcCellRow, srcCellCol);
LinearOffsetRange rowOffsetRange = new LinearOffsetRange(rowOffset, height);
LinearOffsetRange colOffsetRange = new LinearOffsetRange(columnOffset, width);
return createOffset(baseRef, rowOffsetRange, colOffsetRange, workbook, sheet);
- } catch (EvalEx e) {
- return e.getError();
+ } catch (EvaluationException e) {
+ return e.getErrorEval();
}
}
-
private static AreaEval createOffset(BaseRef baseRef,
LinearOffsetRange rowOffsetRange, LinearOffsetRange colOffsetRange,
- Workbook workbook, Sheet sheet) throws EvalEx {
+ Workbook workbook, Sheet sheet) throws EvaluationException {
LinearOffsetRange rows = rowOffsetRange.normaliseAndTranslate(baseRef.getFirstRowIndex());
LinearOffsetRange cols = colOffsetRange.normaliseAndTranslate(baseRef.getFirstColumnIndex());
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)) {
- throw new EvalEx(ErrorEval.REF_INVALID);
+ throw new EvaluationException(ErrorEval.REF_INVALID);
}
if(baseRef.isIs3d()) {
Area3DPtg a3dp = new Area3DPtg(rows.getFirstIndex(), rows.getLastIndex(),
return HSSFFormulaEvaluator.evaluateAreaPtg(sheet, workbook, ap);
}
-
- private static BaseRef evaluateBaseRef(Eval eval) throws EvalEx {
+ private static BaseRef evaluateBaseRef(Eval eval) throws EvaluationException {
if(eval instanceof RefEval) {
return new BaseRef((RefEval)eval);
return new BaseRef((AreaEval)eval);
}
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
*/
- 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);
return convertDoubleToInt(d);
return (int)Math.floor(d);
}
-
- private static double evaluateDoubleArg(Eval eval, int srcCellRow, short srcCellCol) throws EvalEx {
- ValueEval ve = evaluateSingleValue(eval, srcCellRow, srcCellCol);
+ private static double evaluateDoubleArg(Eval eval, int srcCellRow, short srcCellCol) throws EvaluationException {
+ ValueEval ve = OperandResolver.getSingleValue(eval, srcCellRow, srcCellCol);
if (ve instanceof NumericValueEval) {
return ((NumericValueEval) ve).getNumberValue();
}
if (ve instanceof StringEval) {
StringEval se = (StringEval) ve;
- Double d = parseDouble(se.getStringValue());
+ Double d = OperandResolver.parseDouble(se.getStringValue());
if(d == null) {
- throw new EvalEx(ErrorEval.VALUE_INVALID);
+ throw new EvaluationException(ErrorEval.VALUE_INVALID);
}
return d.doubleValue();
}
}
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);
- }
}
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());
}
/**
import org.apache.poi.hssf.model.Workbook;
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.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.Font;
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
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() {
final int prime = 31;
int result = 1;
*/
short getFillForegroundColor();
-}
\ No newline at end of file
+ /**
+ * 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);
+}
if(style == null) {
this.cell.setS(0);
} else {
+ XSSFCellStyle xStyle = (XSSFCellStyle)style;
+ xStyle.verifyBelongsToStylesSource(
+ row.getSheet().getWorkbook().getStylesSource()
+ );
+
this.cell.setS(
- row.getSheet().getWorkbook().getStylesSource().putStyle(style)
+ row.getSheet().getWorkbook().getStylesSource().putStyle(xStyle)
);
}
}
cellXf = CTXf.Factory.newInstance();
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() {
return (short)getAlignmentEnum().intValue();
XSSFRow row = new XSSFRow(sheet);
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) {}
+ }
}
cellStyle.setWrapText(false);
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
+ }
}
result.addTestSuite(TestRVA.class);
result.addTestSuite(TestSheet.class);
result.addTestSuite(TestSheetAdditional.class);
+ result.addTestSuite(TestWorkbook.class);
return result;
}
}
--- /dev/null
+/* ====================================================================
+ 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();
+ }
+ }
+}
\ No newline at end of file
result.addTestSuite(TestEmbeddedObjectRefSubRecord.class);
result.addTestSuite(TestEndSubRecord.class);
result.addTestSuite(TestEscherAggregate.class);
+ result.addTestSuite(TestExtendedFormatRecord.class);
result.addTestSuite(TestExternalNameRecord.class);
+ result.addTestSuite(TestFontRecord.class);
result.addTestSuite(TestFontBasisRecord.class);
result.addTestSuite(TestFontIndexRecord.class);
result.addTestSuite(TestFormulaRecord.class);
--- /dev/null
+/* ====================================================================
+ 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]);
+ }
+}
--- /dev/null
+
+/* ====================================================================
+ 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]);
+ }
+}
package org.apache.poi.hssf.record.formula.functions;
+import junit.framework.AssertionFailedError;
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.RefPtg;
import org.apache.poi.hssf.record.formula.eval.Area2DEval;
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.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()
double result = NumericFunctionInvoker.invoke(new Countif(), args);
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");
+ }
+ }
}
// 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)
{
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) {
junit.textui.TestRunner.run(TestHSSFCell.class);