]> source.dussan.org Git - poi.git/commitdiff
Merged revisions 638786-638802,638805-638811,638813-638814,638816-639230,639233-63924...
authorNick Burch <nick@apache.org>
Sat, 12 Jul 2008 18:17:16 +0000 (18:17 +0000)
committerNick Burch <nick@apache.org>
Sat, 12 Jul 2008 18:17:16 +0000 (18:17 +0000)
https://svn.apache.org:443/repos/asf/poi/trunk

........
  r675853 | josh | 2008-07-11 08:59:44 +0100 (Fri, 11 Jul 2008) | 1 line

  Patch 45289 - finished support for special comparison operators in COUNTIF
........
  r676201 | nick | 2008-07-12 17:56:55 +0100 (Sat, 12 Jul 2008) | 1 line

  Support for cloning one font record onto another, plus tests
........
  r676203 | nick | 2008-07-12 18:21:54 +0100 (Sat, 12 Jul 2008) | 1 line

  Support for cloning one extended format record onto another, plus tests
........
  r676205 | nick | 2008-07-12 18:38:10 +0100 (Sat, 12 Jul 2008) | 1 line

  Allow the cloning of one HSSFCellStyle onto another, including cloning styles from one HSSFWorkbook onto another
........

git-svn-id: https://svn.apache.org/repos/asf/poi/branches/ooxml@676209 13f79535-47bb-0310-9956-ffa450edef68

25 files changed:
src/documentation/content/xdocs/changes.xml
src/documentation/content/xdocs/spreadsheet/converting.xml
src/documentation/content/xdocs/status.xml
src/java/org/apache/poi/hssf/model/Workbook.java
src/java/org/apache/poi/hssf/record/ExtendedFormatRecord.java
src/java/org/apache/poi/hssf/record/FontRecord.java
src/java/org/apache/poi/hssf/record/formula/functions/Countif.java
src/java/org/apache/poi/hssf/record/formula/functions/Offset.java
src/java/org/apache/poi/hssf/usermodel/HSSFCell.java
src/java/org/apache/poi/hssf/usermodel/HSSFCellStyle.java
src/ooxml/interfaces-jdk15/org/apache/poi/ss/usermodel/CellStyle.java
src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFCell.java
src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFCellStyle.java
src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFCell.java
src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFCellStyle.java
src/testcases/org/apache/poi/hssf/data/FormulaEvalTestData.xls
src/testcases/org/apache/poi/hssf/data/countifExamples.xls [new file with mode: 0644]
src/testcases/org/apache/poi/hssf/model/AllModelTests.java
src/testcases/org/apache/poi/hssf/model/TestWorkbook.java [new file with mode: 0644]
src/testcases/org/apache/poi/hssf/record/AllRecordTests.java
src/testcases/org/apache/poi/hssf/record/TestExtendedFormatRecord.java [new file with mode: 0644]
src/testcases/org/apache/poi/hssf/record/TestFontRecord.java [new file with mode: 0644]
src/testcases/org/apache/poi/hssf/record/formula/functions/TestCountFuncs.java
src/testcases/org/apache/poi/hssf/usermodel/TestCellStyle.java
src/testcases/org/apache/poi/hssf/usermodel/TestHSSFCell.java

index fbd94b832b6a70afc36e41cac9b4f0d4ff2ad461..971e0df451d9aa9497fcce757f81c4b9ac7c7a2c 100644 (file)
@@ -50,6 +50,8 @@
            <action dev="POI-DEVELOPERS" type="add">Created a common interface for handling Excel files, irrespective of if they are .xls or .xlsx</action>
         </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>
index 98e2909c42ff380d58e71479ff4e1029151e803c..b54356193473c41cb8ebb32083016063a3202464 100644 (file)
         </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
index 581578f54ba7531cca1f8a76fd01d013cc9faf8b..88099bf3e09d051cf93a66cf3f5af982185acecf 100644 (file)
@@ -47,6 +47,8 @@
            <action dev="POI-DEVELOPERS" type="add">Created a common interface for handling Excel files, irrespective of if they are .xls or .xlsx</action>
         </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>
index 329e217a81158488e5390ce77facdb5bd6d7b6ac..d56051445b2c35bb6309e6af9e8b47404a292730 100644 (file)
@@ -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
index c668d5f8762ee6ac99a0e4159a887e8cd09a15c8..ae983876711ef44ebb5d8efebcc5ca04769b6539 100644 (file)
@@ -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;
index 42e058f4724147f2faaaf15bc6dfdb6aa2e7c4f6..d6a5ce859fa721e6a1a258ffd915474bad1c34a1 100644 (file)
@@ -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;
index 2e445a8bf6bb45ef0d27a620db603886fc179696..902a991b378c47aedf712209e10f21876b5c9084 100644 (file)
 * 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 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;
                }
        }
 
@@ -132,8 +338,7 @@ public final class Countif implements Function {
                                // 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
@@ -144,31 +349,47 @@ public final class Countif implements Function {
                        // 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);
                }
@@ -181,50 +402,29 @@ public final class Countif implements Function {
         */
        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':
index 8a10e6225388100cdae1c401e4c7c366b5dd9c81..08bdf3d64c12b0c7495657aec2ab9cdab3f2b8fc 100644 (file)
@@ -25,7 +25,9 @@ import org.apache.poi.hssf.record.formula.eval.AreaEval;
 import org.apache.poi.hssf.record.formula.eval.BoolEval;
 import org.apache.poi.hssf.record.formula.eval.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);
-       }
 }
index a5279993e90f21d9530badfc99dab18091741462..c81929afec353b79eaac911dd600301894f29c2f 100644 (file)
@@ -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());
     }
 
     /**
index 11d14782be40288280a53f8b91f38ecf96380189..5bf795e453ae9bd9524b0782ea12d71af0a0e96e 100644 (file)
@@ -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;
index 8a3a59822d958f7418bee93d938767b0ab6ce0c4..9149c103b05db03a96c46983ae06dbffa5565609 100644 (file)
@@ -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);
+}
index 8e93def77f69553ca33151e1e456982055603ad9..db79c2d2f01d8df25777a595ef8b2ef7987517ce 100644 (file)
@@ -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)
             );
         }
     }
index 42ff608b654fdf1fdf5e525b92ae87f58abd822a..443fc1efaf03f0388cfa4abb2bb89ea471996404 100644 (file)
@@ -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();
index bf32d3d0bcd3bfd86a92a6151adc94448daf8144..1a1cf9a3a291133833c957e4d16c3d19a7cc8328 100644 (file)
@@ -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) {}
+    }
 }
index 7780b5c3f2d8dc8cc07cd69108aa29ac432f8b71..77afcb9693975e2283a4c453e3dd657997b59639 100644 (file)
@@ -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
+       }
 }
index 7be92c5fa43389d199e7ad4e75b75e1c7a2043f7..eba6607ade6da210856ebd818f93e1676e9ce275 100644 (file)
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 (file)
index 0000000..b15bd16
Binary files /dev/null and b/src/testcases/org/apache/poi/hssf/data/countifExamples.xls differ
index 045e371a22afc6d167e37f96edc12a45b3197170..f157d3a0f2a0dc92d9e27448457ea787093ff77e 100755 (executable)
@@ -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 (file)
index 0000000..99663d2
--- /dev/null
@@ -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
index 988be1dac5ef7303ff03314764819ba6c4910a68..573f6157823afa41c4eb245508f6ee382be16ac2 100755 (executable)
@@ -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 (file)
index 0000000..5bbf05b
--- /dev/null
@@ -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 (file)
index 0000000..6485f58
--- /dev/null
@@ -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]);
+    }
+}
index 1ec657dfe2490a410758d80b56ac67060eb921b7..763eb9820cec55b49a9f94e979bf013783f5bc67 100755 (executable)
 
 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<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");
+               }
+       }
 }
index 0daa80326ecf7a33bb39e61a8f33e171d9aceede..39b949f7de2949dec5fac86fa46aed85c87070f5 100644 (file)
@@ -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)
     {
index 7f4375847780648e714b0a41c86921bcf8edb68d..c3d73cf4a7eba2b105984e836da9d158fa938cde 100644 (file)
@@ -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);