diff --git a/src/documentation/content/xdocs/changes.xml b/src/documentation/content/xdocs/changes.xml index fbd94b832..971e0df45 100644 --- a/src/documentation/content/xdocs/changes.xml +++ b/src/documentation/content/xdocs/changes.xml @@ -50,6 +50,8 @@ Created a common interface for handling Excel files, irrespective of if they are .xls or .xlsx + Allow the cloning of one HSSFCellStyle onto another, including cloning styles from one HSSFWorkbook onto another + 45289 - finished support for special comparison operators in COUNTIF 45126 - Avoid generating multiple NamedRanges with the same name, which Excel dislikes Fix cell.getRichStringCellValue() for formula cells with string results 45365 - Handle more excel number formatting rules in FormatTrackingHSSFListener / XLS2CSVmra diff --git a/src/documentation/content/xdocs/spreadsheet/converting.xml b/src/documentation/content/xdocs/spreadsheet/converting.xml index 98e2909c4..b54356193 100644 --- a/src/documentation/content/xdocs/spreadsheet/converting.xml +++ b/src/documentation/content/xdocs/spreadsheet/converting.xml @@ -27,7 +27,21 @@ -
Converting existing HSSF Usermodel code to SS Usermodel (for XSSF and HSSF) +
Things that have to be changed when upgrading to POI 3.5 +

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.

+
org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator.CellValue +

Annoyingly, java will not let you access a static inner class via + a child of the parent one. So, all references to + org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator.CellValue + will need to be changed to + org.apache.poi.ss.usermodel.FormulaEvaluator.CellValue +

+
+
+
Converting existing HSSF Usermodel code to SS Usermodel (for XSSF and HSSF)
Why change?

If you have existing HSSF usermodel code that works just diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml index 581578f54..88099bf3e 100644 --- a/src/documentation/content/xdocs/status.xml +++ b/src/documentation/content/xdocs/status.xml @@ -47,6 +47,8 @@ Created a common interface for handling Excel files, irrespective of if they are .xls or .xlsx + Allow the cloning of one HSSFCellStyle onto another, including cloning styles from one HSSFWorkbook onto another + 45289 - finished support for special comparison operators in COUNTIF 45126 - Avoid generating multiple NamedRanges with the same name, which Excel dislikes Fix cell.getRichStringCellValue() for formula cells with string results 45365 - Handle more excel number formatting rules in FormatTrackingHSSFListener / XLS2CSVmra diff --git a/src/java/org/apache/poi/hssf/model/Workbook.java b/src/java/org/apache/poi/hssf/model/Workbook.java index 329e217a8..d56051445 100644 --- a/src/java/org/apache/poi/hssf/model/Workbook.java +++ b/src/java/org/apache/poi/hssf/model/Workbook.java @@ -408,6 +408,24 @@ public class Workbook implements Model 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 diff --git a/src/java/org/apache/poi/hssf/record/ExtendedFormatRecord.java b/src/java/org/apache/poi/hssf/record/ExtendedFormatRecord.java index c668d5f87..ae9838767 100644 --- a/src/java/org/apache/poi/hssf/record/ExtendedFormatRecord.java +++ b/src/java/org/apache/poi/hssf/record/ExtendedFormatRecord.java @@ -1814,6 +1814,27 @@ public class ExtendedFormatRecord { 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; diff --git a/src/java/org/apache/poi/hssf/record/FontRecord.java b/src/java/org/apache/poi/hssf/record/FontRecord.java index 42e058f47..d6a5ce859 100644 --- a/src/java/org/apache/poi/hssf/record/FontRecord.java +++ b/src/java/org/apache/poi/hssf/record/FontRecord.java @@ -531,6 +531,8 @@ public class FontRecord public int getRecordSize() { + // Note - no matter the original, we always + // re-serialise the font name as unicode return (getFontNameLength() * 2) + 20; } @@ -538,6 +540,25 @@ public class FontRecord { 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; diff --git a/src/java/org/apache/poi/hssf/record/formula/functions/Countif.java b/src/java/org/apache/poi/hssf/record/formula/functions/Countif.java index 2e445a8bf..902a991b3 100644 --- a/src/java/org/apache/poi/hssf/record/formula/functions/Countif.java +++ b/src/java/org/apache/poi/hssf/record/formula/functions/Countif.java @@ -15,14 +15,17 @@ * 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; @@ -40,85 +43,288 @@ 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 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; } - StringEval se = (StringEval) x; - return se.getStringValue() == _value; + 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; + } + 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 null 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': - 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); - } - /** - * 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; + Double doubleVal = OperandResolver.parseDouble(value); + if(doubleVal != null) { + return new NumberMatcher(doubleVal.doubleValue(), operator); } - // 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': diff --git a/src/java/org/apache/poi/hssf/record/formula/functions/Offset.java b/src/java/org/apache/poi/hssf/record/formula/functions/Offset.java index 8a10e6225..08bdf3d64 100644 --- a/src/java/org/apache/poi/hssf/record/formula/functions/Offset.java +++ b/src/java/org/apache/poi/hssf/record/formula/functions/Offset.java @@ -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.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; @@ -55,21 +57,6 @@ public final class Offset implements FreeRefFunction { 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. @@ -133,8 +120,7 @@ public final class Offset implements FreeRefFunction { return sb.toString(); } } - - + /** * 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() { return _width; } - public int getHeight() { return _height; } - public int getFirstRowIndex() { return _firstRowIndex; } - public int getFirstColumnIndex() { return _firstColumnIndex; } - public boolean isIs3d() { return _externalSheetIndex > 0; } @@ -198,7 +180,6 @@ public final class Offset implements FreeRefFunction { } return (short) _externalSheetIndex; } - } 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; } - try { BaseRef baseRef = evaluateBaseRef(args[0]); int rowOffset = evaluateIntArg(args[1], srcCellRow, srcCellCol); @@ -227,24 +207,23 @@ public final class Offset implements FreeRefFunction { 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(), @@ -260,8 +239,7 @@ public final class Offset implements FreeRefFunction { 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); @@ -270,16 +248,15 @@ public final class Offset implements FreeRefFunction { 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); @@ -295,18 +272,17 @@ public final class Offset implements FreeRefFunction { 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(); } @@ -319,44 +295,4 @@ public final class Offset implements FreeRefFunction { } 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); - } } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java b/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java index a5279993e..c81929afe 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java @@ -924,7 +924,13 @@ public class HSSFCell implements Cell 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()); } /** diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFCellStyle.java b/src/java/org/apache/poi/hssf/usermodel/HSSFCellStyle.java index 11d14782b..5bf795e45 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFCellStyle.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFCellStyle.java @@ -20,6 +20,7 @@ package org.apache.poi.hssf.usermodel; 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; @@ -295,6 +296,16 @@ public class HSSFCellStyle implements CellStyle 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 @@ -930,6 +941,69 @@ public class HSSFCellStyle implements CellStyle 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; diff --git a/src/ooxml/interfaces-jdk15/org/apache/poi/ss/usermodel/CellStyle.java b/src/ooxml/interfaces-jdk15/org/apache/poi/ss/usermodel/CellStyle.java index 8a3a59822..9149c103b 100644 --- a/src/ooxml/interfaces-jdk15/org/apache/poi/ss/usermodel/CellStyle.java +++ b/src/ooxml/interfaces-jdk15/org/apache/poi/ss/usermodel/CellStyle.java @@ -705,4 +705,21 @@ public interface CellStyle { */ 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); +} diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFCell.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFCell.java index 8e93def77..db79c2d2f 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFCell.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFCell.java @@ -333,8 +333,13 @@ public final class XSSFCell implements Cell { 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) ); } } diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFCellStyle.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFCellStyle.java index 42ff608b6..443fc1efa 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFCellStyle.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFCellStyle.java @@ -84,6 +84,43 @@ public class XSSFCellStyle implements CellStyle { 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(); diff --git a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFCell.java b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFCell.java index bf32d3d0b..1a1cf9a3a 100644 --- a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFCell.java +++ b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFCell.java @@ -342,4 +342,41 @@ public class TestXSSFCell extends TestCase { 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) {} + } } diff --git a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFCellStyle.java b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFCellStyle.java index 7780b5c3f..77afcb969 100644 --- a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFCellStyle.java +++ b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFCellStyle.java @@ -273,4 +273,17 @@ public class TestXSSFCellStyle extends TestCase { 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 + } } diff --git a/src/testcases/org/apache/poi/hssf/data/FormulaEvalTestData.xls b/src/testcases/org/apache/poi/hssf/data/FormulaEvalTestData.xls index 7be92c5fa..eba6607ad 100644 Binary files a/src/testcases/org/apache/poi/hssf/data/FormulaEvalTestData.xls and b/src/testcases/org/apache/poi/hssf/data/FormulaEvalTestData.xls differ diff --git a/src/testcases/org/apache/poi/hssf/data/countifExamples.xls b/src/testcases/org/apache/poi/hssf/data/countifExamples.xls new file mode 100644 index 000000000..b15bd162a Binary files /dev/null and b/src/testcases/org/apache/poi/hssf/data/countifExamples.xls differ diff --git a/src/testcases/org/apache/poi/hssf/model/AllModelTests.java b/src/testcases/org/apache/poi/hssf/model/AllModelTests.java index 045e371a2..f157d3a0f 100755 --- a/src/testcases/org/apache/poi/hssf/model/AllModelTests.java +++ b/src/testcases/org/apache/poi/hssf/model/AllModelTests.java @@ -38,6 +38,7 @@ public final class AllModelTests { result.addTestSuite(TestRVA.class); result.addTestSuite(TestSheet.class); result.addTestSuite(TestSheetAdditional.class); + result.addTestSuite(TestWorkbook.class); return result; } } diff --git a/src/testcases/org/apache/poi/hssf/model/TestWorkbook.java b/src/testcases/org/apache/poi/hssf/model/TestWorkbook.java new file mode 100644 index 000000000..99663d203 --- /dev/null +++ b/src/testcases/org/apache/poi/hssf/model/TestWorkbook.java @@ -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(); + } + } +} \ No newline at end of file diff --git a/src/testcases/org/apache/poi/hssf/record/AllRecordTests.java b/src/testcases/org/apache/poi/hssf/record/AllRecordTests.java index 988be1dac..573f61578 100755 --- a/src/testcases/org/apache/poi/hssf/record/AllRecordTests.java +++ b/src/testcases/org/apache/poi/hssf/record/AllRecordTests.java @@ -61,7 +61,9 @@ public final class AllRecordTests { 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); diff --git a/src/testcases/org/apache/poi/hssf/record/TestExtendedFormatRecord.java b/src/testcases/org/apache/poi/hssf/record/TestExtendedFormatRecord.java new file mode 100644 index 000000000..5bbf05be7 --- /dev/null +++ b/src/testcases/org/apache/poi/hssf/record/TestExtendedFormatRecord.java @@ -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]); + } +} diff --git a/src/testcases/org/apache/poi/hssf/record/TestFontRecord.java b/src/testcases/org/apache/poi/hssf/record/TestFontRecord.java new file mode 100644 index 000000000..6485f586f --- /dev/null +++ b/src/testcases/org/apache/poi/hssf/record/TestFontRecord.java @@ -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]); + } +} diff --git a/src/testcases/org/apache/poi/hssf/record/formula/functions/TestCountFuncs.java b/src/testcases/org/apache/poi/hssf/record/formula/functions/TestCountFuncs.java index 1ec657dfe..763eb9820 100755 --- a/src/testcases/org/apache/poi/hssf/record/formula/functions/TestCountFuncs.java +++ b/src/testcases/org/apache/poi/hssf/record/formula/functions/TestCountFuncs.java @@ -18,8 +18,10 @@ 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; @@ -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.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() @@ -146,4 +155,154 @@ public final class TestCountFuncs extends TestCase { 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 0) { + throw new AssertionFailedError(failureCount + " countif evaluations failed. See stderr for more details"); + } + } } diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestCellStyle.java b/src/testcases/org/apache/poi/hssf/usermodel/TestCellStyle.java index 0daa80326..39b949f7d 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/TestCellStyle.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestCellStyle.java @@ -229,6 +229,80 @@ public class TestCellStyle // 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) { diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFCell.java b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFCell.java index 7f4375847..c3d73cf4a 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFCell.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFCell.java @@ -381,6 +381,43 @@ public final class TestHSSFCell extends TestCase { 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);