Author: nick
Date: Wed Feb 20 10:14:30 2008
New Revision: 629552

URL: http://svn.apache.org/viewvc?rev=629552&view=rev
Log:
Patch from Josh from bug #44403 - Further support for unusual, but valid, 
arguments to the Mid function

Added:
    
poi/trunk/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/EvaluationException.java
   (with props)
    
poi/trunk/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/OperandResolver.java
   (with props)
    
poi/trunk/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestMid.java
   (with props)
Modified:
    poi/trunk/src/documentation/content/xdocs/changes.xml
    poi/trunk/src/documentation/content/xdocs/hslf/index.xml
    poi/trunk/src/documentation/content/xdocs/status.xml
    
poi/trunk/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Mid.java
    
poi/trunk/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/AllIndividualFunctionEvaluationTests.java

Modified: poi/trunk/src/documentation/content/xdocs/changes.xml
URL: 
http://svn.apache.org/viewvc/poi/trunk/src/documentation/content/xdocs/changes.xml?rev=629552&r1=629551&r2=629552&view=diff
==============================================================================
--- poi/trunk/src/documentation/content/xdocs/changes.xml (original)
+++ poi/trunk/src/documentation/content/xdocs/changes.xml Wed Feb 20 10:14:30 
2008
@@ -36,6 +36,7 @@
 
                <!-- Don't forget to update status.xml too! -->
         <release version="3.1-beta1" date="2008-??-??">
+           <action dev="POI-DEVELOPERS" type="fix">44403 - Further support for 
unusual, but valid, arguments to the Mid function</action>
            <action dev="POI-DEVELOPERS" type="fix">44410 - Support for 
whole-column ranges, such as C:C, in formula strings and the formula 
evaluator</action>
            <action dev="POI-DEVELOPERS" type="fix">44421 - Update Match 
function to properly support Area references</action>
            <action dev="POI-DEVELOPERS" type="fix">44417 - Improved handling 
of references for the need to quote the sheet name for some formulas, but not 
when fetching a sheet by name</action>

Modified: poi/trunk/src/documentation/content/xdocs/hslf/index.xml
URL: 
http://svn.apache.org/viewvc/poi/trunk/src/documentation/content/xdocs/hslf/index.xml?rev=629552&r1=629551&r2=629552&view=diff
==============================================================================
--- poi/trunk/src/documentation/content/xdocs/hslf/index.xml (original)
+++ poi/trunk/src/documentation/content/xdocs/hslf/index.xml Wed Feb 20 
10:14:30 2008
@@ -45,8 +45,8 @@
                 <link 
href="http://svn.apache.org/viewcvs.cgi/poi/trunk/src/scratchpad/";>scratchpad 
area</link> 
                 of the POI SVN repository. 
                                Ensure that you have the scratchpad jar or the 
scratchpad 
-                build area in your
-                               classpath before experimenting with this code.
+                build area in your classpath before experimenting with 
+                this code - the main POI jar is not enough.
                        </note>
                        <p>The <link href="./quick-guide.html">quick 
guide</link> documentation provides 
             information on using this API. Comments and fixes gratefully 
accepted on the POI

Modified: poi/trunk/src/documentation/content/xdocs/status.xml
URL: 
http://svn.apache.org/viewvc/poi/trunk/src/documentation/content/xdocs/status.xml?rev=629552&r1=629551&r2=629552&view=diff
==============================================================================
--- poi/trunk/src/documentation/content/xdocs/status.xml (original)
+++ poi/trunk/src/documentation/content/xdocs/status.xml Wed Feb 20 10:14:30 
2008
@@ -33,6 +33,7 @@
        <!-- Don't forget to update changes.xml too! -->
     <changes>
         <release version="3.1-beta1" date="2008-??-??">
+           <action dev="POI-DEVELOPERS" type="fix">44403 - Further support for 
unusual, but valid, arguments to the Mid function</action>
            <action dev="POI-DEVELOPERS" type="fix">44410 - Support for 
whole-column ranges, such as C:C, in formula strings and the formula 
evaluator</action>
            <action dev="POI-DEVELOPERS" type="fix">44421 - Update Match 
function to properly support Area references</action>
            <action dev="POI-DEVELOPERS" type="fix">44417 - Improved handling 
of references for the need to quote the sheet name for some formulas, but not 
when fetching a sheet by name</action>

Added: 
poi/trunk/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/EvaluationException.java
URL: 
http://svn.apache.org/viewvc/poi/trunk/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/EvaluationException.java?rev=629552&view=auto
==============================================================================
--- 
poi/trunk/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/EvaluationException.java
 (added)
+++ 
poi/trunk/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/EvaluationException.java
 Wed Feb 20 10:14:30 2008
@@ -0,0 +1,134 @@
+/* ====================================================================
+   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.formula.eval;
+
+/**
+ * This class is used to simplify error handling logic <i>within</i> operator 
and function
+ * implementations.   Note - <tt>OperationEval.evaluate()</tt> and 
<tt>Function.evaluate()</tt>
+ * method signatures do not throw this exception so it cannot propagate 
outside.<p/>
+ * 
+ * Here is an example coded without <tt>EvaluationException</tt>, to show how 
it can help:
+ * <pre>
+ * public Eval evaluate(Eval[] args, int srcRow, short srcCol) {
+ *     // ...
+ *     Eval arg0 = args[0];
+ *     if(arg0 instanceof ErrorEval) {
+ *             return arg0;
+ *     }
+ *     if(!(arg0 instanceof AreaEval)) {
+ *             return ErrorEval.VALUE_INVALID;
+ *     }
+ *     double temp = 0;
+ *     AreaEval area = (AreaEval)arg0;
+ *     ValueEval[] values = area.getValues();
+ *     for (int i = 0; i < values.length; i++) {
+ *             ValueEval ve = values[i];
+ *             if(ve instanceof ErrorEval) {
+ *                     return ve;
+ *             }
+ *             if(!(ve instanceof NumericValueEval)) {
+ *                     return ErrorEval.VALUE_INVALID;
+ *             }
+ *             temp += ((NumericValueEval)ve).getNumberValue();
+ *     }
+ *     // ...
+ * }    
+ * </pre>
+ * In this example, if any error is encountered while processing the 
arguments, an error is 
+ * returned immediately. This code is difficult to refactor due to all the 
points where errors
+ * are returned.<br/>
+ * Using <tt>EvaluationException</tt> allows the error returning code to be 
consolidated to one
+ * place.<p/>
+ * <pre>
+ * public Eval evaluate(Eval[] args, int srcRow, short srcCol) {
+ *     try {
+ *             // ...
+ *             AreaEval area = getAreaArg(args[0]);
+ *             double temp = sumValues(area.getValues());
+ *             // ...
+ *     } catch (EvaluationException e) {
+ *             return e.getErrorEval();
+ *     }
+ *}
+ *
+ *private static AreaEval getAreaArg(Eval arg0) throws EvaluationException {
+ *     if (arg0 instanceof ErrorEval) {
+ *             throw new EvaluationException((ErrorEval) arg0);
+ *     }
+ *     if (arg0 instanceof AreaEval) {
+ *             return (AreaEval) arg0;
+ *     }
+ *     throw EvaluationException.invalidValue();
+ *}
+ *
+ *private double sumValues(ValueEval[] values) throws EvaluationException {
+ *     double temp = 0;
+ *     for (int i = 0; i < values.length; i++) {
+ *             ValueEval ve = values[i];
+ *             if (ve instanceof ErrorEval) {
+ *                     throw new EvaluationException((ErrorEval) ve);
+ *             }
+ *             if (!(ve instanceof NumericValueEval)) {
+ *                     throw EvaluationException.invalidValue();
+ *             }
+ *             temp += ((NumericValueEval) ve).getNumberValue();
+ *     }
+ *     return temp;
+ *}
+ * </pre>   
+ * It is not mandatory to use EvaluationException, doing so might give the 
following advantages:<br/>
+ *  - Methods can more easily be extracted, allowing for re-use.<br/>
+ *  - Type management (typecasting etc) is simpler because error conditions 
have been separated from
+ * intermediate calculation values.<br/>
+ *  - Fewer local variables are required. Local variables can have stronger 
types.<br/>
+ *  - It is easier to mimic common Excel error handling behaviour (exit upon 
encountering first 
+ *  error), because exceptions conveniently propagate up the call stack 
regardless of execution 
+ *  points or the number of levels of nested calls.<p/>
+ *  
+ * <b>Note</b> - Only standard evaluation errors are represented by 
<tt>EvaluationException</tt> (
+ * i.e. conditions expected to be encountered when evaluating arbitrary Excel 
formulas). Conditions
+ * that could never occur in an Excel spreadsheet should result in runtime 
exceptions. Care should
+ * be taken to not translate any POI internal error into an Excel evaluation 
error code.   
+ * 
+ * @author Josh Micich
+ */
+public final class EvaluationException extends Exception {
+       private final ErrorEval _errorEval;
+
+       public EvaluationException(ErrorEval errorEval) {
+               _errorEval = errorEval;
+       }
+       // some convenience factory methods
+
+    /** <b>#VALUE!</b> - Wrong type of operand */
+       public static EvaluationException invalidValue() {
+               return new EvaluationException(ErrorEval.VALUE_INVALID);
+       }
+    /** <b>#REF!</b> - Illegal or deleted cell reference */
+       public static EvaluationException invalidRef() {
+               return new EvaluationException(ErrorEval.REF_INVALID);
+       }
+    /** <b>#NUM!</b> - Value range overflow */
+       public static EvaluationException numberError() {
+               return new EvaluationException(ErrorEval.NUM_ERROR);
+       }
+       
+       public ErrorEval getErrorEval() {
+               return _errorEval;
+       }
+}

Propchange: 
poi/trunk/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/EvaluationException.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: 
poi/trunk/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/EvaluationException.java
------------------------------------------------------------------------------
    svn:executable = *

Added: 
poi/trunk/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/OperandResolver.java
URL: 
http://svn.apache.org/viewvc/poi/trunk/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/OperandResolver.java?rev=629552&view=auto
==============================================================================
--- 
poi/trunk/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/OperandResolver.java
 (added)
+++ 
poi/trunk/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/OperandResolver.java
 Wed Feb 20 10:14:30 2008
@@ -0,0 +1,249 @@
+/* ====================================================================
+   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.formula.eval;
+
+
+/**
+ * Provides functionality for evaluating arguments to functions and operators.
+ * 
+ * @author Josh Micich
+ */
+public final class OperandResolver {
+
+       private OperandResolver() {
+               // no instances of this class
+       }
+       
+       /**
+        * Retrieves a single value from a variety of different argument types 
according to standard
+        * Excel rules.  Does not perform any type conversion.
+        * @param arg the evaluated argument as passed to the function or 
operator.
+        * @param srcCellRow used when arg is a single column AreaRef
+        * @param srcCellCol used when arg is a single row AreaRef
+        * @return a <tt>NumberEval</tt>, <tt>StringEval</tt>, 
<tt>BoolEval</tt> or <tt>BlankEval</tt>.
+        * Never <code>null</code> or <tt>ErrorEval</tt>.
+        * @throws EvaluationException(#VALUE!) if srcCellRow or srcCellCol do 
not properly index into
+        *  an AreaEval.  If the actual value retrieved is an ErrorEval, a 
corresponding 
+        *  EvaluationException is thrown.
+        */
+       public static ValueEval getSingleValue(Eval arg, int srcCellRow, short 
srcCellCol)
+                       throws EvaluationException {
+               if (arg instanceof RefEval) {
+                       RefEval re = (RefEval) arg;
+                       return re.getInnerValueEval();
+               }
+               Eval result;
+               if (arg instanceof AreaEval) {
+                       result = chooseSingleElementFromArea((AreaEval) arg, 
srcCellRow, srcCellCol);
+               } else {
+                       result = arg;
+               }
+               if (result instanceof ErrorEval) {
+                       throw new EvaluationException((ErrorEval) result);
+               }
+               if (result instanceof ValueEval) {
+                       return (ValueEval) result;
+               }
+               throw new RuntimeException("Unexpected eval type (" + 
result.getClass().getName() + ")");
+       }
+
+       /**
+        * Implements (some perhaps not well known) Excel functionality to 
select a single cell from an
+        * area depending on the coordinates of the calling cell.  Here is an 
example demonstrating
+        * both selection from a single row area and a single column area in 
the same formula.
+        * 
+        *    <table border="1" cellpadding="1" cellspacing="1" summary="sample 
spreadsheet">
+        *      
<tr><th>&nbsp;</th><th>&nbsp;A&nbsp;</th><th>&nbsp;B&nbsp;</th><th>&nbsp;C&nbsp;</th><th>&nbsp;D&nbsp;</th></tr>
+        *      
<tr><th>1</th><td>15</td><td>20</td><td>25</td><td>&nbsp;</td></tr>
+        *      
<tr><th>2</th><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>200</td></tr>
+        *      
<tr><th>3</th><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>300</td></tr>
+        *      
<tr><th>3</th><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>400</td></tr>
+        *    </table>
+        * 
+        * If the formula "=1000+A1:B1+D2:D3" is put into the 9 cells from A2 
to C4, the spreadsheet
+        * will look like this:
+        * 
+        *    <table border="1" cellpadding="1" cellspacing="1" summary="sample 
spreadsheet">
+        *      
<tr><th>&nbsp;</th><th>&nbsp;A&nbsp;</th><th>&nbsp;B&nbsp;</th><th>&nbsp;C&nbsp;</th><th>&nbsp;D&nbsp;</th></tr>
+        *      
<tr><th>1</th><td>15</td><td>20</td><td>25</td><td>&nbsp;</td></tr>
+        *      
<tr><th>2</th><td>1215</td><td>1220</td><td>#VALUE!</td><td>200</td></tr>
+        *      
<tr><th>3</th><td>1315</td><td>1320</td><td>#VALUE!</td><td>300</td></tr>
+        *      
<tr><th>4</th><td>#VALUE!</td><td>#VALUE!</td><td>#VALUE!</td><td>400</td></tr>
+        *    </table>
+        * 
+        * Note that the row area (A1:B1) does not include column C and the 
column area (D2:D3) does 
+        * not include row 4, so the values in C1(=25) and D4(=400) are not 
accessible to the formula
+        * as written, but in the 4 cells A2:B3, the row and column selection 
works ok.<p/>
+        * 
+        * The same concept is extended to references across sheets, such that 
even multi-row, 
+        * multi-column areas can be useful.<p/>
+        * 
+        * Of course with carefully (or carelessly) chosen parameters, cyclic 
references can occur and
+        * hence this method <b>can</b> throw a 'circular reference' 
EvaluationException.  Note that 
+        * this method does not attempt to detect cycles.  Every cell in the 
specified Area <tt>ae</tt>
+        * has already been evaluated prior to this method call.  Any cell (or 
cell<b>s</b>) part of 
+        * <tt>ae</tt> that would incur a cyclic reference error if selected by 
this method, will 
+        * already have the value <t>ErrorEval.CIRCULAR_REF_ERROR</tt> upon 
entry to this method.  It
+        * is assumed logic exists elsewhere to produce this behaviour.
+        * 
+        * @return whatever the selected cell's evaluated value is.  Never 
<code>null</code>. Never
+        *  <tt>ErrorEval</tt>.
+        * @throws EvaluationException if there is a problem with indexing into 
the area, or if the
+        *  evaluated cell has an error.
+        */
+       public static ValueEval chooseSingleElementFromArea(AreaEval ae, 
+                       int srcCellRow, short srcCellCol) throws 
EvaluationException {
+               ValueEval result = chooseSingleElementFromAreaInternal(ae, 
srcCellRow, srcCellCol);
+               if(result == null) {
+                       // This seems to be required because AreaEval.values() 
array may contain nulls.
+                       // perhaps that should not be allowed.
+                       result = BlankEval.INSTANCE;
+               }
+               if (result instanceof ErrorEval) {
+                       throw new EvaluationException((ErrorEval) result);
+                       
+               }
+               return result;
+       }
+       
+       /**
+        * @return possibly  <tt>ErrorEval</tt>, and <code>null</code> 
+        */
+       private static ValueEval chooseSingleElementFromAreaInternal(AreaEval 
ae, 
+                       int srcCellRow, short srcCellCol) throws 
EvaluationException {
+       
+               if(false) {
+                       // this is too simplistic
+                       if(ae.containsRow(srcCellRow) && 
ae.containsColumn(srcCellCol)) {
+                               throw new 
EvaluationException(ErrorEval.CIRCULAR_REF_ERROR);
+                       }
+               /*
+               Circular references are not dealt with directly here, but it is 
worth noting some issues.
+               
+               ANY one of the return statements in this method could return a 
cell that is identical
+               to the one immediately being evaluated.  The evaluating cell is 
identified by srcCellRow,
+               srcCellRow AND sheet.  The sheet is not available in any nearby 
calling method, so that's
+               one reason why circular references are not easy to detect here. 
(The sheet of the returned
+               cell can be obtained from ae if it is an Area3DEval.)
+               
+               Another reason there's little value in attempting to detect 
circular references here is
+               that only direct circular references could be detected.  If the 
cycle involved two or more
+               cells this method could not detect it.  
+               
+               Logic to detect evaluation cycles of all kinds has been coded 
in EvaluationCycleDetector
+               (and HSSFFormulaEvaluator). 
+                */
+               }
+               
+               if (ae.isColumn()) {
+                       if(ae.isRow()) {
+                               return ae.getValues()[0];
+                       }
+                       if(!ae.containsRow(srcCellRow)) {
+                               throw EvaluationException.invalidValue();
+                       }
+                       return ae.getValueAt(srcCellRow, ae.getFirstColumn());
+               }
+               if(!ae.isRow()) {
+                       // multi-column, multi-row area
+                       if(ae.containsRow(srcCellRow) && 
ae.containsColumn(srcCellCol)) {
+                               return ae.getValueAt(ae.getFirstRow(), 
ae.getFirstColumn());
+                       }
+                       throw EvaluationException.invalidValue();
+               }
+               if(!ae.containsColumn(srcCellCol)) {
+                       throw EvaluationException.invalidValue();
+               }
+               return ae.getValueAt(ae.getFirstRow(), srcCellCol);
+       }
+
+       /**
+        * Applies some conversion rules if the supplied value is not already 
an integer.<br/>
+        * Value is first coerced to a <tt>double</tt> ( See 
<tt>coerceValueToDouble()</tt> ).<p/>
+        * 
+        * Excel typically converts doubles to integers by truncating toward 
negative infinity.<br/>
+        * The equivalent java code is:<br/>
+        * &nbsp;&nbsp;<code>return (int)Math.floor(d);</code><br/>
+        * <b>not</b>:<br/>
+        * &nbsp;&nbsp;<code>return (int)d; // wrong - rounds toward 
zero</code> 
+        * 
+        */
+       public static int coerceValueToInt(ValueEval ev) throws 
EvaluationException {
+               double d = coerceValueToDouble(ev);
+               // Note - the standard java type conversion from double to int 
truncates toward zero.
+               // but Math.floor() truncates toward negative infinity
+               return (int)Math.floor(d);
+       }
+
+       /**
+        * Applies some conversion rules if the supplied value is not already a 
number.
+        * Note - <tt>BlankEval</tt> is not supported and must be handled by 
the caller. 
+        * @param ev must be a <tt>NumberEval</tt>, <tt>StringEval</tt> or 
<tt>BoolEval</tt>
+        * @return actual, parsed or interpreted double value (respectively).
+        * @throws EvaluationException(#VALUE!) only if a StringEval is 
supplied and cannot be parsed
+        * as a double (See <tt>parseDouble()</tt> for allowable formats).
+        * @throws RuntimeException if the supplied parameter is not 
<tt>NumberEval</tt>,
+        *  <tt>StringEval</tt> or <tt>BoolEval</tt>
+        */
+       public static double coerceValueToDouble(ValueEval ev) throws 
EvaluationException {
+
+           if (ev instanceof NumericValueEval) {
+               // this also handles booleans
+               return ((NumericValueEval)ev).getNumberValue();
+           }
+           if (ev instanceof StringEval) {
+               Double dd = parseDouble(((StringEval) ev).getStringValue());
+               if (dd == null) {
+                       throw EvaluationException.invalidValue();
+               }
+               return dd.doubleValue();
+               }
+               throw new RuntimeException("Unexpected arg eval type (" + 
ev.getClass().getName() + ")");
+       }
+       
+       /**
+        * Converts a string to a double using standard rules that Excel would 
use.<br/>
+        * Tolerates currency prefixes, commas, leading and trailing spaces.<p/>
+        *   
+        *  Some examples:<br/> 
+        *  " 123 " -&gt; 123.0<br/>
+        *  ".123" -&gt; 0.123<br/>
+        *  " $ 1,000.00 " -&gt; 1000.0<br/>
+        *  "$1.25E4" -&gt; 12500.0<br/>
+        *  
+        * @param text
+        * @return <code>null</code> if the specified text cannot be parsed as 
a number
+        */
+       public static Double parseDouble(String text) {
+               if(!Character.isDigit(text.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(text);
+               } catch (NumberFormatException e) {
+                       return null;
+               }
+               return new Double(val);
+       }
+       
+       
+}

Propchange: 
poi/trunk/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/OperandResolver.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: 
poi/trunk/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/OperandResolver.java
------------------------------------------------------------------------------
    svn:executable = *

Modified: 
poi/trunk/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Mid.java
URL: 
http://svn.apache.org/viewvc/poi/trunk/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Mid.java?rev=629552&r1=629551&r2=629552&view=diff
==============================================================================
--- 
poi/trunk/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Mid.java
 (original)
+++ 
poi/trunk/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Mid.java
 Wed Feb 20 10:14:30 2008
@@ -1,99 +1,100 @@
 /*
-* 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.
-*/
-/*
- * Created on May 15, 2005
+ * 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.formula.functions;
 
 import org.apache.poi.hssf.record.formula.eval.BlankEval;
 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.NumericValueEval;
+import org.apache.poi.hssf.record.formula.eval.EvaluationException;
+import org.apache.poi.hssf.record.formula.eval.OperandResolver;
 import org.apache.poi.hssf.record.formula.eval.StringEval;
 import org.apache.poi.hssf.record.formula.eval.StringValueEval;
 import org.apache.poi.hssf.record.formula.eval.ValueEval;
 
 /**
- * An implementation of the MID function:
- * Returns a specific number of characters from a text string, 
- * starting at the position you specify, based on the number 
- * of characters you specify.
+ * An implementation of the MID function<br/> MID returns a specific number of
+ * characters from a text string, starting at the specified position.<p/>
+ * 
+ * <b>Syntax<b>:<br/> <b>MID</b>(<b>text</b>, <b>start_num</b>,
+ * <b>num_chars</b>)<br/>
+ * 
  * @author Manda Wilson &lt; wilson at c bio dot msk cc dot org &gt;
  */
 public class Mid extends TextFunction {
        /**
-        * Returns a specific number of characters from a text string, 
-        * starting at the position you specify, based on the number 
-        * of characters you specify.
+        * Returns a specific number of characters from a text string, starting 
at
+        * the position you specify, based on the number of characters you 
specify.
         * 
         * @see org.apache.poi.hssf.record.formula.eval.Eval
         */
-    public Eval evaluate(Eval[] operands, int srcCellRow, short srcCellCol) {  
        
-       Eval retval = null;
-        String str = null;
-        int startNum = 0;
-        int numChars = 0;
-        
-        switch (operands.length) {
-               default:
-                   retval = ErrorEval.VALUE_INVALID;
-               case 3:
-                       // first operand is text string containing characters 
to extract
-                   // second operand is position of first character to extract
-                   // third operand is the number of characters to return
-                   ValueEval firstveval = singleOperandEvaluate(operands[0], 
srcCellRow, srcCellCol);
-                   ValueEval secondveval = singleOperandEvaluate(operands[1], 
srcCellRow, srcCellCol);
-                   ValueEval thirdveval = singleOperandEvaluate(operands[2], 
srcCellRow, srcCellCol);
-                   if (firstveval instanceof StringValueEval
-                       && secondveval instanceof NumericValueEval
-                       && thirdveval instanceof NumericValueEval) {
-                       
-                       StringValueEval strEval = (StringValueEval) firstveval;
-                       str = strEval.getStringValue();
-                       
-                       NumericValueEval startNumEval = (NumericValueEval) 
secondveval;
-                       // NOTE: it is safe to cast to int here
-                       // because in Excel =MID("test", 1, 1.7) returns t 
-                       // so 1.7 must be truncated to 1
-                       // and =MID("test", 1.9, 2) returns te 
-                       // so 1.9 must be truncated to 1
-                       startNum = (int) startNumEval.getNumberValue();
-                       
-                       NumericValueEval numCharsEval = (NumericValueEval) 
thirdveval;
-                       numChars = (int) numCharsEval.getNumberValue();
-                       
-                   } else {
-                       retval = ErrorEval.VALUE_INVALID;
-                   }
-           }
-               
-        if (retval == null) {
-                       if (startNum < 1 || numChars < 0) {
-                               retval = ErrorEval.VALUE_INVALID;
-                       } else if (startNum > str.length() || numChars == 0) {
-                               retval = BlankEval.INSTANCE;
-                       } else if (startNum + numChars > str.length()) {
-                               retval = new StringEval(str.substring(startNum 
- 1));
-                       } else {
-                               retval = new StringEval(str.substring(startNum 
- 1, (numChars + startNum - 1)));
-                       } 
-        } 
-               return retval;
-    }
+       public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) {
+               if (args.length != 3) {
+                       return ErrorEval.VALUE_INVALID;
+               }
+
+               String text;
+               int startIx; // zero based
+               int numChars;
+
+               try {
+                       text = evaluateTextArg(args[0], srcCellRow, srcCellCol);
+                       int startCharNum = evaluateNumberArg(args[1], 
srcCellRow, srcCellCol);
+                       numChars = evaluateNumberArg(args[2], srcCellRow, 
srcCellCol);
+                       startIx = startCharNum - 1; // convert to zero-based
+               } catch (EvaluationException e) {
+                       return e.getErrorEval();
+               }
+
+               int len = text.length();
+               if (startIx < 0) {
+                       return ErrorEval.VALUE_INVALID;
+               }
+               if (numChars < 0) {
+                       return ErrorEval.VALUE_INVALID;
+               }
+               if (numChars < 0 || startIx > len) {
+                       return new StringEval("");
+               }
+               int endIx = startIx + numChars;
+               if (endIx > len) {
+                       endIx = len;
+               }
+               String result = text.substring(startIx, endIx);
+               return new StringEval(result);
+
+       }
+
+       public static int evaluateNumberArg(Eval arg, int srcCellRow, short 
srcCellCol) throws EvaluationException {
+               ValueEval ev = OperandResolver.getSingleValue(arg, srcCellRow, 
srcCellCol);
+               if (ev instanceof BlankEval) {
+                       // Note - for start_num arg, blank causes 
error(#VALUE!),
+                       // but for num_chars causes empty string to be returned.
+                       return 0;
+               }
+
+               return OperandResolver.coerceValueToInt(ev);
+       }
 
-}
+       private static String evaluateTextArg(Eval arg, int srcCellRow, short 
srcCellCol) throws EvaluationException {
+               ValueEval ev = OperandResolver.getSingleValue(arg, srcCellRow, 
srcCellCol);
+               if (ev instanceof StringValueEval) {
+                       return ((StringValueEval) ev).getStringValue();
+               }
+               throw EvaluationException.invalidValue();
+       }
+}
\ No newline at end of file

Modified: 
poi/trunk/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/AllIndividualFunctionEvaluationTests.java
URL: 
http://svn.apache.org/viewvc/poi/trunk/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/AllIndividualFunctionEvaluationTests.java?rev=629552&r1=629551&r2=629552&view=diff
==============================================================================
--- 
poi/trunk/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/AllIndividualFunctionEvaluationTests.java
 (original)
+++ 
poi/trunk/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/AllIndividualFunctionEvaluationTests.java
 Wed Feb 20 10:14:30 2008
@@ -35,6 +35,7 @@
                result.addTestSuite(TestDate.class);
                result.addTestSuite(TestFinanceLib.class);
                result.addTestSuite(TestIndex.class);
+               result.addTestSuite(TestMid.class);
                result.addTestSuite(TestMathX.class);
                result.addTestSuite(TestMatch.class);
                result.addTestSuite(TestRowCol.class);

Added: 
poi/trunk/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestMid.java
URL: 
http://svn.apache.org/viewvc/poi/trunk/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestMid.java?rev=629552&view=auto
==============================================================================
--- 
poi/trunk/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestMid.java
 (added)
+++ 
poi/trunk/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestMid.java
 Wed Feb 20 10:14:30 2008
@@ -0,0 +1,114 @@
+/* ====================================================================
+   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.formula.functions;
+
+import org.apache.poi.hssf.record.formula.AreaPtg;
+import org.apache.poi.hssf.record.formula.ReferencePtg;
+import org.apache.poi.hssf.record.formula.eval.Area2DEval;
+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.Ref2DEval;
+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;
+
+import junit.framework.TestCase;
+/**
+ * Tests for Excel function MID()
+ * 
+ * @author Josh Micich
+ */
+public final class TestMid extends TestCase {
+
+       
+       private static Eval invokeMid(Eval text, Eval startPos, Eval numChars) {
+               Eval[] args = new Eval[] { text, startPos, numChars, };
+               return new Mid().evaluate(args, -1, (short)-1);
+       }
+
+       private void confirmMid(Eval text, Eval startPos, Eval numChars, String 
expected) {
+               Eval result = invokeMid(text, startPos, numChars);
+               assertEquals(StringEval.class, result.getClass());
+               assertEquals(expected, ((StringEval)result).getStringValue());
+       }
+
+       private void confirmMid(Eval text, Eval startPos, Eval numChars, 
ErrorEval expectedError) {
+               Eval result = invokeMid(text, startPos, numChars);
+               assertEquals(ErrorEval.class, result.getClass());
+               assertEquals(expectedError.getErrorCode(), 
((ErrorEval)result).getErrorCode());
+       }
+       
+       public void testBasic() {
+               
+               confirmMid(new StringEval("galactic"), new NumberEval(3), new 
NumberEval(4), "lact");
+       }
+       
+       /**
+        * Valid cases where args are not precisely (string, int, int) but can 
be resolved OK.
+        */
+       public void testUnusualArgs() {
+               // startPos with fractional digits
+               confirmMid(new StringEval("galactic"), new NumberEval(3.1), new 
NumberEval(4), "lact");
+               
+               // string startPos
+               confirmMid(new StringEval("galactic"), new StringEval("3"), new 
NumberEval(4), "lact");
+               
+               // text (first) arg type is number, other args are strings with 
fractional digits 
+               confirmMid(new NumberEval(123456), new StringEval("3.1"), new 
StringEval("2.9"), "34");
+               
+               // startPos is 1x1 area ref, numChars is cell ref
+               AreaEval aeStart = new Area2DEval(new AreaPtg("A1:A1"), new 
ValueEval[] { new NumberEval(2), } );
+               RefEval reNumChars = new Ref2DEval(new ReferencePtg("B1"), new 
NumberEval(3),false);
+               confirmMid(new StringEval("galactic"), aeStart, reNumChars, 
"ala");
+
+               confirmMid(new StringEval("galactic"), new NumberEval(3.1), 
BlankEval.INSTANCE, "");
+
+               confirmMid(new StringEval("galactic"), new NumberEval(3), 
BoolEval.FALSE, "");
+               confirmMid(new StringEval("galactic"), new NumberEval(3), 
BoolEval.TRUE, "l");
+       
+       }
+
+       /**
+        * Extreme values for startPos and numChars
+        */
+       public void testExtremes() {
+               confirmMid(new StringEval("galactic"), new NumberEval(4), new 
NumberEval(400), "actic");
+               
+               confirmMid(new StringEval("galactic"), new NumberEval(30), new 
NumberEval(4), "");
+               confirmMid(new StringEval("galactic"), new NumberEval(3), new 
NumberEval(0), "");
+       }
+
+       /**
+        * All sorts of ways to make MID return defined errors.
+        */
+       public void testErrors() {
+               confirmMid(ErrorEval.NAME_INVALID, new NumberEval(3), new 
NumberEval(4), ErrorEval.NAME_INVALID);
+               confirmMid(new StringEval("galactic"), ErrorEval.NAME_INVALID, 
new NumberEval(4), ErrorEval.NAME_INVALID);
+               confirmMid(new StringEval("galactic"), new NumberEval(3), 
ErrorEval.NAME_INVALID, ErrorEval.NAME_INVALID);
+               confirmMid(new StringEval("galactic"), ErrorEval.DIV_ZERO, 
ErrorEval.NAME_INVALID, ErrorEval.DIV_ZERO);
+               
+               confirmMid(new StringEval("galactic"), BlankEval.INSTANCE, new 
NumberEval(3.1), ErrorEval.VALUE_INVALID);
+               
+               confirmMid(new StringEval("galactic"), new NumberEval(0), new 
NumberEval(4), ErrorEval.VALUE_INVALID);
+               confirmMid(new StringEval("galactic"), new NumberEval(1), new 
NumberEval(-1), ErrorEval.VALUE_INVALID);
+       }
+}

Propchange: 
poi/trunk/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestMid.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: 
poi/trunk/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestMid.java
------------------------------------------------------------------------------
    svn:executable = *



---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]

Reply via email to