View | Details | Raw Unified | Return to bug 44450
Collapse All | Expand All

(-)src/java/org/apache/poi/hssf/record/aggregates/ValueRecordsAggregate.java (-1 / +7 lines)
Lines 21-26 Link Here
21
21
22
import org.apache.poi.hssf.record.*;
22
import org.apache.poi.hssf.record.*;
23
23
24
import java.io.EOFException;
24
import java.util.Iterator;
25
import java.util.Iterator;
25
import java.util.List;
26
import java.util.List;
26
27
Lines 34-40 Link Here
34
 * @author Jason Height (jheight at chariot dot net dot au)
35
 * @author Jason Height (jheight at chariot dot net dot au)
35
 */
36
 */
36
37
37
public class ValueRecordsAggregate
38
public final class ValueRecordsAggregate
38
    extends Record
39
    extends Record
39
{
40
{
40
    public final static short sid       = -1000;
41
    public final static short sid       = -1000;
Lines 135-140 Link Here
135
            if (rec instanceof SharedFormulaRecord) {
136
            if (rec instanceof SharedFormulaRecord) {
136
            	sharedFormulas.add(rec);
137
            	sharedFormulas.add(rec);
137
            }
138
            }
139
// jm hack. better version of this patch in 44449
140
if (rec instanceof EOFRecord) {
141
    break; 
142
}
143
// end hack
138
        }
144
        }
139
145
140
        // Now do the main processing sweep
146
        // Now do the main processing sweep
(-)src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/ErrorEval.java (-6 / +10 lines)
Lines 28-34 Link Here
28
    /**
28
    /**
29
     * Contains raw Excel error codes (as defined in OOO's excelfileformat.pdf (2.5.6)
29
     * Contains raw Excel error codes (as defined in OOO's excelfileformat.pdf (2.5.6)
30
     */
30
     */
31
    private static final class ErrorCode {
31
    public static final class ErrorCode {
32
        /** <b>#NULL!</b>  - Intersection of two cell ranges is empty */
32
        /** <b>#NULL!</b>  - Intersection of two cell ranges is empty */
33
        public static final int NULL = 0x00;
33
        public static final int NULL = 0x00;
34
        /** <b>#DIV/0!</b> - Division by zero */
34
        /** <b>#DIV/0!</b> - Division by zero */
Lines 44-49 Link Here
44
        /** <b>#N/A</b> - Argument or function not available */
44
        /** <b>#N/A</b> - Argument or function not available */
45
        public static final int N_A = 0x2A;   
45
        public static final int N_A = 0x2A;   
46
        
46
        
47
        /** <b>~CYCLIC~REFERENCE~</b> - Cyclic reference detected (not a real Excel error code) */
48
        public static final int CYCLIC_REF = 0xFFFFFFC4;   
49
        
47
        public static final String getText(int errorCode) {
50
        public static final String getText(int errorCode) {
48
            switch(errorCode) {
51
            switch(errorCode) {
49
                case NULL:  return "#NULL!";
52
                case NULL:  return "#NULL!";
Lines 53-60 Link Here
53
                case NAME:  return "#NAME?";
56
                case NAME:  return "#NAME?";
54
                case NUM:   return "#NUM!";
57
                case NUM:   return "#NUM!";
55
                case N_A:   return "#N/A";
58
                case N_A:   return "#N/A";
59
                
60
                case CYCLIC_REF:   return "~CYCLIC~REFERENCE~";
56
            }
61
            }
57
            return "???";
62
            return "err(" + errorCode + ")???" ;
58
        }
63
        }
59
    }
64
    }
60
65
Lines 83-99 Link Here
83
            case ErrorCode.NULL: return NULL_INTERSECTION;
88
            case ErrorCode.NULL: return NULL_INTERSECTION;
84
            case ErrorCode.DIV_0: return DIV_ZERO;
89
            case ErrorCode.DIV_0: return DIV_ZERO;
85
            case ErrorCode.VALUE: return VALUE_INVALID;
90
            case ErrorCode.VALUE: return VALUE_INVALID;
86
//            case ErrorCode.REF: return REF_INVALID;
91
            case ErrorCode.REF: return REF_INVALID;
87
            case ErrorCode.REF: return UNKNOWN_ERROR;
88
            case ErrorCode.NAME: return NAME_INVALID;
92
            case ErrorCode.NAME: return NAME_INVALID;
89
            case ErrorCode.NUM: return NUM_ERROR;
93
            case ErrorCode.NUM: return NUM_ERROR;
90
            case ErrorCode.N_A: return NA;
94
            case ErrorCode.N_A: return NA;
95
            case ErrorCode.CYCLIC_REF: return CIRCULAR_REF_ERROR;
91
            
96
            
92
            // these cases probably shouldn't be coming through here 
97
            // these cases probably shouldn't be coming through here 
93
            // but (as of Jan-2008) a lot of code depends on it. 
98
            // but (as of Jan-2008) a lot of code depends on it. 
94
//            case -20: return UNKNOWN_ERROR;
99
//            case -20: return UNKNOWN_ERROR;
95
//            case -30: return FUNCTION_NOT_IMPLEMENTED;
100
//            case -30: return FUNCTION_NOT_IMPLEMENTED;
96
//            case -60: return CIRCULAR_REF_ERROR;
97
        }
101
        }
98
        throw new RuntimeException("Unexpected error code (" + errorCode + ")");
102
        throw new RuntimeException("Unexpected error code (" + errorCode + ")");
99
    }
103
    }
Lines 102-108 Link Here
102
    public static final ErrorEval UNKNOWN_ERROR = new ErrorEval(-20);
106
    public static final ErrorEval UNKNOWN_ERROR = new ErrorEval(-20);
103
    public static final ErrorEval FUNCTION_NOT_IMPLEMENTED = new ErrorEval(-30);
107
    public static final ErrorEval FUNCTION_NOT_IMPLEMENTED = new ErrorEval(-30);
104
    // Note - Excel does not seem to represent this condition with an error code
108
    // Note - Excel does not seem to represent this condition with an error code
105
    public static final ErrorEval CIRCULAR_REF_ERROR = new ErrorEval(-60); 
109
    public static final ErrorEval CIRCULAR_REF_ERROR = new ErrorEval(ErrorCode.CYCLIC_REF); 
106
110
107
111
108
    private int errorCode;
112
    private int errorCode;
(-)src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/StringValueEval.java (-5 / +5 lines)
Lines 14-23 Link Here
14
* See the License for the specific language governing permissions and
14
* See the License for the specific language governing permissions and
15
* limitations under the License.
15
* limitations under the License.
16
*/
16
*/
17
/*
17
18
 * Created on May 8, 2005
19
 *
20
 */
21
package org.apache.poi.hssf.record.formula.eval;
18
package org.apache.poi.hssf.record.formula.eval;
22
19
23
/**
20
/**
Lines 26-30 Link Here
26
 */
23
 */
27
public interface StringValueEval extends ValueEval {
24
public interface StringValueEval extends ValueEval {
28
25
29
    public String getStringValue();
26
	/**
27
	 * @return never <code>null</code>, possibly empty string.
28
	 */
29
    String getStringValue();
30
}
30
}
(-)src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/StringEval.java (-7 / +14 lines)
Lines 14-23 Link Here
14
* See the License for the specific language governing permissions and
14
* See the License for the specific language governing permissions and
15
* limitations under the License.
15
* limitations under the License.
16
*/
16
*/
17
/*
17
18
 * Created on May 8, 2005
19
 *
20
 */
21
package org.apache.poi.hssf.record.formula.eval;
18
package org.apache.poi.hssf.record.formula.eval;
22
19
23
import org.apache.poi.hssf.record.formula.Ptg;
20
import org.apache.poi.hssf.record.formula.Ptg;
Lines 27-47 Link Here
27
 * @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt;
24
 * @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt;
28
 *  
25
 *  
29
 */
26
 */
30
public class StringEval implements StringValueEval {
27
public final class StringEval implements StringValueEval {
31
28
32
    public static final StringEval EMPTY_INSTANCE = new StringEval("");
29
    public static final StringEval EMPTY_INSTANCE = new StringEval("");
33
    
30
    
34
    private String value;
31
    private final String value;
35
32
36
    public StringEval(Ptg ptg) {
33
    public StringEval(Ptg ptg) {
37
        this.value = ((StringPtg) ptg).getValue();
34
        this(((StringPtg) ptg).getValue());
38
    }
35
    }
39
36
40
    public StringEval(String value) {
37
    public StringEval(String value) {
38
    	if(value == null) {
39
    		throw new IllegalArgumentException("value must not be null");
40
    	}
41
        this.value = value;
41
        this.value = value;
42
    }
42
    }
43
43
44
    public String getStringValue() {
44
    public String getStringValue() {
45
        return value;
45
        return value;
46
    }
46
    }
47
    public String toString() {
48
        StringBuffer sb = new StringBuffer(64);
49
        sb.append(getClass().getName()).append(" [");
50
        sb.append(value);
51
        sb.append("]");
52
        return sb.toString();
53
    }
47
}
54
}
(-)src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Lookup.java (-21 / +92 lines)
Lines 1-25 Link Here
1
/*
1
/* ====================================================================
2
* Licensed to the Apache Software Foundation (ASF) under one or more
2
   Licensed to the Apache Software Foundation (ASF) under one or more
3
* contributor license agreements.  See the NOTICE file distributed with
3
   contributor license agreements.  See the NOTICE file distributed with
4
* this work for additional information regarding copyright ownership.
4
   this work for additional information regarding copyright ownership.
5
* The ASF licenses this file to You under the Apache License, Version 2.0
5
   The ASF licenses this file to You under the Apache License, Version 2.0
6
* (the "License"); you may not use this file except in compliance with
6
   (the "License"); you may not use this file except in compliance with
7
* the License.  You may obtain a copy of the License at
7
   the License.  You may obtain a copy of the License at
8
*
8
9
*     http://www.apache.org/licenses/LICENSE-2.0
9
       http://www.apache.org/licenses/LICENSE-2.0
10
*
10
11
* Unless required by applicable law or agreed to in writing, software
11
   Unless required by applicable law or agreed to in writing, software
12
* distributed under the License is distributed on an "AS IS" BASIS,
12
   distributed under the License is distributed on an "AS IS" BASIS,
13
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
* See the License for the specific language governing permissions and
14
   See the License for the specific language governing permissions and
15
* limitations under the License.
15
   limitations under the License.
16
*/
16
==================================================================== */
17
/*
17
18
 * Created on May 15, 2005
19
 *
20
 */
21
package org.apache.poi.hssf.record.formula.functions;
18
package org.apache.poi.hssf.record.formula.functions;
22
19
23
public class Lookup extends NotImplementedFunction {
20
import org.apache.poi.hssf.record.formula.eval.AreaEval;
21
import org.apache.poi.hssf.record.formula.eval.ErrorEval;
22
import org.apache.poi.hssf.record.formula.eval.Eval;
23
import org.apache.poi.hssf.record.formula.eval.EvaluationException;
24
import org.apache.poi.hssf.record.formula.eval.OperandResolver;
25
import org.apache.poi.hssf.record.formula.eval.ValueEval;
26
import org.apache.poi.hssf.record.formula.functions.LookupUtils.ValueVector;
24
27
28
/**
29
 * Implementation of Excel function LOOKUP.<p/>
30
 * 
31
 * LOOKUP finds an index  row in a lookup table by the first column value and returns the value from another column.
32
 * 
33
 * <b>Syntax</b>:<br/>
34
 * <b>VLOOKUP</b>(<b>lookup_value</b>, <b>lookup_vector</b>, result_vector)<p/>
35
 * 
36
 * <b>lookup_value</b>  The value to be found in the lookup vector.<br/>
37
 * <b>lookup_vector</> An area reference for the lookup data. <br/>
38
 * <b>result_vector</b> Single row or single column area reference from which the result value is chosen.<br/>
39
 * 
40
 * @author Josh Micich
41
 */
42
public final class Lookup implements Function {
43
	private static final class SimpleValueVector implements ValueVector {
44
		private final ValueEval[] _values;
45
46
		public SimpleValueVector(ValueEval[] values) {
47
			_values = values;
48
		}
49
		public ValueEval getItem(int index) {
50
			return _values[index];
51
		}
52
		public int getSize() {
53
			return _values.length;
54
		}
55
	}
56
57
	public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) {
58
		switch(args.length) {
59
			case 3:
60
				break;
61
			case 2:
62
				// complex rules to choose lookupVector and resultVector from the single area ref
63
				throw new RuntimeException("Two arg version of LOOKUP not supported yet");
64
			default:
65
				return ErrorEval.VALUE_INVALID;
66
		}
67
		
68
		
69
		try {
70
			ValueEval lookupValue = OperandResolver.getSingleValue(args[0], srcCellRow, srcCellCol);
71
			AreaEval aeLookupVector = LookupUtils.resolveTableArrayArg(args[1]);
72
			AreaEval aeResultVector = LookupUtils.resolveTableArrayArg(args[2]);
73
			
74
			ValueVector lookupVector = createVector(aeLookupVector);
75
			ValueVector resultVector = createVector(aeResultVector);
76
			if(lookupVector.getSize() > resultVector.getSize()) {
77
				// Excel seems to handle this by accessing past the end of the result vector.
78
				throw new RuntimeException("Lookup vector and result vector of differing sizes not supported yet");
79
			}
80
			int index = LookupUtils.lookupIndexOfValue(lookupValue, lookupVector, true);
81
			
82
			return resultVector.getItem(index);
83
		} catch (EvaluationException e) {
84
			return e.getErrorEval();
85
		}
86
	}
87
88
	private static ValueVector createVector(AreaEval ae) {
89
		
90
		if(!ae.isRow() && !ae.isColumn()) {
91
			// extra complexity required to emulate the way LOOKUP can handles these abnormal cases.
92
			throw new RuntimeException("non-vector lookup or result areas not supported yet");
93
		}
94
		return new SimpleValueVector(ae.getValues());
95
	}
25
}
96
}
(-)src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Vlookup.java (-21 / +119 lines)
Lines 1-25 Link Here
1
/*
1
/* ====================================================================
2
* Licensed to the Apache Software Foundation (ASF) under one or more
2
   Licensed to the Apache Software Foundation (ASF) under one or more
3
* contributor license agreements.  See the NOTICE file distributed with
3
   contributor license agreements.  See the NOTICE file distributed with
4
* this work for additional information regarding copyright ownership.
4
   this work for additional information regarding copyright ownership.
5
* The ASF licenses this file to You under the Apache License, Version 2.0
5
   The ASF licenses this file to You under the Apache License, Version 2.0
6
* (the "License"); you may not use this file except in compliance with
6
   (the "License"); you may not use this file except in compliance with
7
* the License.  You may obtain a copy of the License at
7
   the License.  You may obtain a copy of the License at
8
*
8
9
*     http://www.apache.org/licenses/LICENSE-2.0
9
       http://www.apache.org/licenses/LICENSE-2.0
10
*
10
11
* Unless required by applicable law or agreed to in writing, software
11
   Unless required by applicable law or agreed to in writing, software
12
* distributed under the License is distributed on an "AS IS" BASIS,
12
   distributed under the License is distributed on an "AS IS" BASIS,
13
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
* See the License for the specific language governing permissions and
14
   See the License for the specific language governing permissions and
15
* limitations under the License.
15
   limitations under the License.
16
*/
16
==================================================================== */
17
/*
17
18
 * Created on May 15, 2005
19
 *
20
 */
21
package org.apache.poi.hssf.record.formula.functions;
18
package org.apache.poi.hssf.record.formula.functions;
22
19
23
public class Vlookup extends NotImplementedFunction {
20
import org.apache.poi.hssf.record.formula.eval.AreaEval;
21
import org.apache.poi.hssf.record.formula.eval.ErrorEval;
22
import org.apache.poi.hssf.record.formula.eval.Eval;
23
import org.apache.poi.hssf.record.formula.eval.EvaluationException;
24
import org.apache.poi.hssf.record.formula.eval.OperandResolver;
25
import org.apache.poi.hssf.record.formula.eval.ValueEval;
26
import org.apache.poi.hssf.record.formula.functions.LookupUtils.ValueVector;
27
/**
28
 * Implementation of the VLOOKUP() function.<p/>
29
 * 
30
 * VLOOKUP finds a row in a lookup table by the first column value and returns the value from another column.
31
 * 
32
 * <b>Syntax</b>:<br/>
33
 * <b>VLOOKUP</b>(<b>lookup_value</b>, <b>table_array</b>, <b>col_index_num</b>, range_lookup)<p/>
34
 * 
35
 * <b>lookup_value</b>  The value to be found in the first column of the table array.<br/>
36
 * <b>table_array</> An area reference for the lookup data. <br/>
37
 * <b>col_index_num</b> a 1 based index specifying which column value of the lookup data will be returned.<br/>
38
 * <b>range_lookup</b> If TRUE (default), VLOOKUP finds the largest value less than or equal to 
39
 * the lookup_value.  If FALSE, only exact matches will be considered<br/>   
40
 * 
41
 * @author Josh Micich
42
 */
43
public final class Vlookup implements Function {
44
	
45
	private static final class ColumnVector implements ValueVector {
24
46
47
		private final AreaEval _tableArray;
48
		private final int _size;
49
		private final int _columnAbsoluteIndex;
50
		private final int _firstRowAbsoluteIndex;
51
52
		public ColumnVector(AreaEval tableArray, int columnIndex) {
53
			_columnAbsoluteIndex = tableArray.getFirstColumn() + columnIndex;
54
			if(!tableArray.containsColumn((short)_columnAbsoluteIndex)) {
55
				int lastColIx =  tableArray.getLastColumn() -  tableArray.getFirstColumn();
56
				throw new IllegalArgumentException("Specified column index (" + columnIndex 
57
						+ ") is outside the allowed range (0.." + lastColIx + ")");
58
			}
59
			_tableArray = tableArray;
60
			_size = tableArray.getLastRow() - tableArray.getFirstRow() + 1;
61
			if(_size < 1) {
62
				throw new RuntimeException("bad table array size zero");
63
			}
64
			_firstRowAbsoluteIndex = tableArray.getFirstRow();
65
		}
66
67
		public ValueEval getItem(int index) {
68
			if(index>_size) {
69
				throw new ArrayIndexOutOfBoundsException("Specified index (" + index 
70
						+ ") is outside the allowed range (0.." + (_size-1) + ")");
71
			}
72
			return _tableArray.getValueAt(_firstRowAbsoluteIndex + index, (short)_columnAbsoluteIndex);
73
		}
74
		public int getSize() {
75
			return _size;
76
		}
77
	}
78
79
	public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) {
80
		Eval arg3 = null;
81
		switch(args.length) {
82
			case 4:
83
				arg3 = args[3]; // important: assumed array element is never null
84
			case 3:
85
				break;
86
			default:
87
				// wrong number of arguments
88
				return ErrorEval.VALUE_INVALID;
89
		}
90
		try {
91
			// Evaluation order:
92
			// arg0 lookup_value, arg1 table_array, arg3 range_lookup, find lookup value, arg2 col_index, fetch result
93
			ValueEval lookupValue = OperandResolver.getSingleValue(args[0], srcCellRow, srcCellCol);
94
			AreaEval tableArray = LookupUtils.resolveTableArrayArg(args[1]);
95
			boolean isRangeLookup = LookupUtils.resolveRangeLookupArg(arg3, srcCellRow, srcCellCol);
96
			int rowIndex = LookupUtils.lookupIndexOfValue(lookupValue, new ColumnVector(tableArray, 0), isRangeLookup);
97
			ValueEval veColIndex = OperandResolver.getSingleValue(args[2], srcCellRow, srcCellCol);
98
			int colIndex = LookupUtils.resolveRowOrColIndexArg(veColIndex);
99
			ValueVector resultCol = createResultColumnVector(tableArray, colIndex);
100
			return resultCol.getItem(rowIndex);
101
		} catch (EvaluationException e) {
102
			return e.getErrorEval();
103
		}
104
	}
105
106
107
	/**
108
	 * Returns one column from an <tt>AreaEval</tt>
109
	 * 
110
	 * @throws EvaluationException (#VALUE!) if colIndex is negative, (#REF!) if colIndex is too high
111
	 */
112
	private ValueVector createResultColumnVector(AreaEval tableArray, int colIndex) throws EvaluationException {
113
		if(colIndex < 0) {
114
			throw EvaluationException.invalidValue();
115
		}
116
		int nCols = tableArray.getLastColumn() - tableArray.getFirstColumn() + 1;
117
		
118
		if(colIndex >= nCols) {
119
			throw EvaluationException.invalidRef();
120
		}
121
		return new ColumnVector(tableArray, colIndex);
122
	}
25
}
123
}
(-)src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Hlookup.java (-21 / +119 lines)
Lines 1-25 Link Here
1
/*
1
/* ====================================================================
2
* Licensed to the Apache Software Foundation (ASF) under one or more
2
   Licensed to the Apache Software Foundation (ASF) under one or more
3
* contributor license agreements.  See the NOTICE file distributed with
3
   contributor license agreements.  See the NOTICE file distributed with
4
* this work for additional information regarding copyright ownership.
4
   this work for additional information regarding copyright ownership.
5
* The ASF licenses this file to You under the Apache License, Version 2.0
5
   The ASF licenses this file to You under the Apache License, Version 2.0
6
* (the "License"); you may not use this file except in compliance with
6
   (the "License"); you may not use this file except in compliance with
7
* the License.  You may obtain a copy of the License at
7
   the License.  You may obtain a copy of the License at
8
*
8
9
*     http://www.apache.org/licenses/LICENSE-2.0
9
       http://www.apache.org/licenses/LICENSE-2.0
10
*
10
11
* Unless required by applicable law or agreed to in writing, software
11
   Unless required by applicable law or agreed to in writing, software
12
* distributed under the License is distributed on an "AS IS" BASIS,
12
   distributed under the License is distributed on an "AS IS" BASIS,
13
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
* See the License for the specific language governing permissions and
14
   See the License for the specific language governing permissions and
15
* limitations under the License.
15
   limitations under the License.
16
*/
16
==================================================================== */
17
/*
17
18
 * Created on May 15, 2005
19
 *
20
 */
21
package org.apache.poi.hssf.record.formula.functions;
18
package org.apache.poi.hssf.record.formula.functions;
22
19
23
public class Hlookup extends NotImplementedFunction {
20
import org.apache.poi.hssf.record.formula.eval.AreaEval;
21
import org.apache.poi.hssf.record.formula.eval.ErrorEval;
22
import org.apache.poi.hssf.record.formula.eval.Eval;
23
import org.apache.poi.hssf.record.formula.eval.EvaluationException;
24
import org.apache.poi.hssf.record.formula.eval.OperandResolver;
25
import org.apache.poi.hssf.record.formula.eval.ValueEval;
26
import org.apache.poi.hssf.record.formula.functions.LookupUtils.ValueVector;
27
/**
28
 * Implementation of the VLOOKUP() function.<p/>
29
 * 
30
 * HLOOKUP finds a column in a lookup table by the first row value and returns the value from another row.
31
 * 
32
 * <b>Syntax</b>:<br/>
33
 * <b>HLOOKUP</b>(<b>lookup_value</b>, <b>table_array</b>, <b>row_index_num</b>, range_lookup)<p/>
34
 * 
35
 * <b>lookup_value</b>  The value to be found in the first column of the table array.<br/>
36
 * <b>table_array</> An area reference for the lookup data. <br/>
37
 * <b>row_index_num</b> a 1 based index specifying which row value of the lookup data will be returned.<br/>
38
 * <b>range_lookup</b> If TRUE (default), HLOOKUP finds the largest value less than or equal to 
39
 * the lookup_value.  If FALSE, only exact matches will be considered<br/>   
40
 * 
41
 * @author Josh Micich
42
 */
43
public final class Hlookup implements Function {
44
	
45
	private static final class RowVector implements ValueVector {
24
46
47
		private final AreaEval _tableArray;
48
		private final int _size;
49
		private final int _rowAbsoluteIndex;
50
		private final int _firstColumnAbsoluteIndex;
51
52
		public RowVector(AreaEval tableArray, int rowIndex) {
53
			_rowAbsoluteIndex = tableArray.getFirstRow() + rowIndex;
54
			if(!tableArray.containsRow(_rowAbsoluteIndex)) {
55
				int lastRowIx =  tableArray.getLastRow() -  tableArray.getFirstRow();
56
				throw new IllegalArgumentException("Specified row index (" + rowIndex 
57
						+ ") is outside the allowed range (0.." + lastRowIx + ")");
58
			}
59
			_tableArray = tableArray;
60
			_size = tableArray.getLastColumn() - tableArray.getFirstColumn() + 1;
61
			if(_size < 1) {
62
				throw new RuntimeException("bad table array size zero");
63
			}
64
			_firstColumnAbsoluteIndex = tableArray.getFirstColumn();
65
		}
66
67
		public ValueEval getItem(int index) {
68
			if(index>_size) {
69
				throw new ArrayIndexOutOfBoundsException("Specified index (" + index 
70
						+ ") is outside the allowed range (0.." + (_size-1) + ")");
71
			}
72
			return _tableArray.getValueAt(_rowAbsoluteIndex, (short) (_firstColumnAbsoluteIndex + index));
73
		}
74
		public int getSize() {
75
			return _size;
76
		}
77
	}
78
79
	public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) {
80
		Eval arg3 = null;
81
		switch(args.length) {
82
			case 4:
83
				arg3 = args[3]; // important: assumed array element is never null
84
			case 3:
85
				break;
86
			default:
87
				// wrong number of arguments
88
				return ErrorEval.VALUE_INVALID;
89
		}
90
		try {
91
			// Evaluation order:
92
			// arg0 lookup_value, arg1 table_array, arg3 range_lookup, find lookup value, arg2 row_index, fetch result
93
			ValueEval lookupValue = OperandResolver.getSingleValue(args[0], srcCellRow, srcCellCol);
94
			AreaEval tableArray = LookupUtils.resolveTableArrayArg(args[1]);
95
			boolean isRangeLookup = LookupUtils.resolveRangeLookupArg(arg3, srcCellRow, srcCellCol);
96
			int colIndex = LookupUtils.lookupIndexOfValue(lookupValue, new RowVector(tableArray, 0), isRangeLookup);
97
			ValueEval veColIndex = OperandResolver.getSingleValue(args[2], srcCellRow, srcCellCol);
98
			int rowIndex = LookupUtils.resolveRowOrColIndexArg(veColIndex);
99
			ValueVector resultCol = createResultColumnVector(tableArray, rowIndex);
100
			return resultCol.getItem(colIndex);
101
		} catch (EvaluationException e) {
102
			return e.getErrorEval();
103
		}
104
	}
105
106
107
	/**
108
	 * Returns one column from an <tt>AreaEval</tt>
109
	 * 
110
	 * @throws EvaluationException (#VALUE!) if colIndex is negative, (#REF!) if colIndex is too high
111
	 */
112
	private ValueVector createResultColumnVector(AreaEval tableArray, int colIndex) throws EvaluationException {
113
		if(colIndex < 0) {
114
			throw EvaluationException.invalidValue();
115
		}
116
		int nCols = tableArray.getLastColumn() - tableArray.getFirstRow() + 1;
117
		
118
		if(colIndex >= nCols) {
119
			throw EvaluationException.invalidRef();
120
		}
121
		return new RowVector(tableArray, colIndex);
122
	}
25
}
123
}
(-)src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Match.java (-153 / +67 lines)
Lines 18-31 Link Here
18
package org.apache.poi.hssf.record.formula.functions;
18
package org.apache.poi.hssf.record.formula.functions;
19
19
20
import org.apache.poi.hssf.record.formula.eval.AreaEval;
20
import org.apache.poi.hssf.record.formula.eval.AreaEval;
21
import org.apache.poi.hssf.record.formula.eval.BoolEval;
22
import org.apache.poi.hssf.record.formula.eval.ErrorEval;
21
import org.apache.poi.hssf.record.formula.eval.ErrorEval;
23
import org.apache.poi.hssf.record.formula.eval.Eval;
22
import org.apache.poi.hssf.record.formula.eval.Eval;
23
import org.apache.poi.hssf.record.formula.eval.EvaluationException;
24
import org.apache.poi.hssf.record.formula.eval.NumberEval;
24
import org.apache.poi.hssf.record.formula.eval.NumberEval;
25
import org.apache.poi.hssf.record.formula.eval.NumericValueEval;
25
import org.apache.poi.hssf.record.formula.eval.NumericValueEval;
26
import org.apache.poi.hssf.record.formula.eval.OperandResolver;
26
import org.apache.poi.hssf.record.formula.eval.RefEval;
27
import org.apache.poi.hssf.record.formula.eval.RefEval;
27
import org.apache.poi.hssf.record.formula.eval.StringEval;
28
import org.apache.poi.hssf.record.formula.eval.StringEval;
28
import org.apache.poi.hssf.record.formula.eval.ValueEval;
29
import org.apache.poi.hssf.record.formula.eval.ValueEval;
30
import org.apache.poi.hssf.record.formula.functions.LookupUtils.CompareResult;
31
import org.apache.poi.hssf.record.formula.functions.LookupUtils.LookupValueComparer;
29
32
30
/**
33
/**
31
 * Implementation for the MATCH() Excel function.<p/>
34
 * Implementation for the MATCH() Excel function.<p/>
Lines 62-79 Link Here
62
 */
65
 */
63
public final class Match implements Function {
66
public final class Match implements Function {
64
	
67
	
65
	private static final class EvalEx extends Exception {
66
		private final ErrorEval _error;
67
68
68
		public EvalEx(ErrorEval error) {
69
			_error = error;
70
		}
71
		public ErrorEval getError() {
72
			return _error;
73
		}
74
	}
75
	
76
77
	public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) {
69
	public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) {
78
		
70
		
79
		double match_type = 1; // default
71
		double match_type = 1; // default
Lines 82-88 Link Here
82
			case 3:
74
			case 3:
83
				try {
75
				try {
84
					match_type = evaluateMatchTypeArg(args[2], srcCellRow, srcCellCol);
76
					match_type = evaluateMatchTypeArg(args[2], srcCellRow, srcCellCol);
85
				} catch (EvalEx e) {
77
				} catch (EvaluationException e) {
86
					// Excel/MATCH() seems to have slightly abnormal handling of errors with
78
					// Excel/MATCH() seems to have slightly abnormal handling of errors with
87
					// the last parameter.  Errors do not propagate up.  Every error gets
79
					// the last parameter.  Errors do not propagate up.  Every error gets
88
					// translated into #REF!
80
					// translated into #REF!
Lines 100-208 Link Here
100
		
92
		
101
		
93
		
102
		try {
94
		try {
103
			ValueEval lookupValue = evaluateLookupValue(args[0], srcCellRow, srcCellCol);
95
			ValueEval lookupValue = OperandResolver.getSingleValue(args[0], srcCellRow, srcCellCol);
104
			ValueEval[] lookupRange = evaluateLookupRange(args[1]);
96
			ValueEval[] lookupRange = evaluateLookupRange(args[1]);
105
			int index = findIndexOfValue(lookupValue, lookupRange, matchExact, findLargestLessThanOrEqual);
97
			int index = findIndexOfValue(lookupValue, lookupRange, matchExact, findLargestLessThanOrEqual);
106
			return new NumberEval(index + 1); // +1 to convert to 1-based
98
			return new NumberEval(index + 1); // +1 to convert to 1-based
107
		} catch (EvalEx e) {
99
		} catch (EvaluationException e) {
108
			return e.getError();
100
			return e.getErrorEval();
109
		}
101
		}
110
	}
102
	}
111
103
112
	private static ValueEval chooseSingleElementFromArea(AreaEval ae, 
104
	private static ValueEval[] evaluateLookupRange(Eval eval) throws EvaluationException {
113
			int srcCellRow, short srcCellCol) throws EvalEx {
114
		if (ae.isColumn()) {
115
			if(ae.isRow()) {
116
				return ae.getValues()[0];
117
			}
118
			if(!ae.containsRow(srcCellRow)) {
119
				throw new EvalEx(ErrorEval.VALUE_INVALID);
120
			}
121
			return ae.getValueAt(srcCellRow, ae.getFirstColumn());
122
		}
123
		if(!ae.isRow()) {
124
			throw new EvalEx(ErrorEval.VALUE_INVALID);
125
		}
126
		if(!ae.containsColumn(srcCellCol)) {
127
			throw new EvalEx(ErrorEval.VALUE_INVALID);
128
		}
129
		return ae.getValueAt(ae.getFirstRow(), srcCellCol);
130
		
131
	}
132
133
	private static ValueEval evaluateLookupValue(Eval eval, int srcCellRow, short srcCellCol)
134
			throws EvalEx {
135
		if (eval instanceof RefEval) {
105
		if (eval instanceof RefEval) {
136
			RefEval re = (RefEval) eval;
106
			RefEval re = (RefEval) eval;
137
			return re.getInnerValueEval();
138
		}
139
		if (eval instanceof AreaEval) {
140
			return chooseSingleElementFromArea((AreaEval) eval, srcCellRow, srcCellCol);
141
		}
142
		if (eval instanceof ValueEval) {
143
			return (ValueEval) eval;
144
		}
145
		throw new RuntimeException("Unexpected eval type (" + eval.getClass().getName() + ")");
146
	}
147
148
149
	private static ValueEval[] evaluateLookupRange(Eval eval) throws EvalEx {
150
		if (eval instanceof RefEval) {
151
			RefEval re = (RefEval) eval;
152
			return new ValueEval[] { re.getInnerValueEval(), };
107
			return new ValueEval[] { re.getInnerValueEval(), };
153
		}
108
		}
154
		if (eval instanceof AreaEval) {
109
		if (eval instanceof AreaEval) {
155
			AreaEval ae = (AreaEval) eval;
110
			AreaEval ae = (AreaEval) eval;
156
			if(!ae.isColumn() && !ae.isRow()) {
111
			if(!ae.isColumn() && !ae.isRow()) {
157
				throw new EvalEx(ErrorEval.NA);
112
				throw new EvaluationException(ErrorEval.NA);
158
			}
113
			}
159
			return ae.getValues();
114
			return ae.getValues();
160
		}
115
		}
161
		
116
		
162
		// Error handling for lookup_range arg is also unusual
117
		// Error handling for lookup_range arg is also unusual
163
		if(eval instanceof NumericValueEval) {
118
		if(eval instanceof NumericValueEval) {
164
			throw new EvalEx(ErrorEval.NA);
119
			throw new EvaluationException(ErrorEval.NA);
165
		}
120
		}
166
		if (eval instanceof StringEval) {
121
		if (eval instanceof StringEval) {
167
			StringEval se = (StringEval) eval;
122
			StringEval se = (StringEval) eval;
168
			Double d = parseDouble(se.getStringValue());
123
			Double d = OperandResolver.parseDouble(se.getStringValue());
169
			if(d == null) {
124
			if(d == null) {
170
				// plain string
125
				// plain string
171
				throw new EvalEx(ErrorEval.VALUE_INVALID);
126
				throw new EvaluationException(ErrorEval.VALUE_INVALID);
172
			}
127
			}
173
			// else looks like a number
128
			// else looks like a number
174
			throw new EvalEx(ErrorEval.NA);
129
			throw new EvaluationException(ErrorEval.NA);
175
		}
130
		}
176
		throw new RuntimeException("Unexpected eval type (" + eval.getClass().getName() + ")");
131
		throw new RuntimeException("Unexpected eval type (" + eval.getClass().getName() + ")");
177
	}
132
	}
178
133
179
134
180
	private static Double parseDouble(String stringValue) {
181
		// TODO find better home for parseDouble
182
		return Countif.parseDouble(stringValue);
183
	}
184
135
185
186
187
	private static double evaluateMatchTypeArg(Eval arg, int srcCellRow, short srcCellCol) 
136
	private static double evaluateMatchTypeArg(Eval arg, int srcCellRow, short srcCellCol) 
188
			throws EvalEx {
137
			throws EvaluationException {
189
		Eval match_type = arg;
138
		Eval match_type = OperandResolver.getSingleValue(arg, srcCellRow, srcCellCol);
190
		if(arg instanceof AreaEval) {
139
191
			AreaEval ae = (AreaEval) arg;
192
			// an area ref can work as a scalar value if it is 1x1
193
			if(ae.isColumn() &&  ae.isRow()) {
194
				match_type = ae.getValues()[0];
195
			} else {
196
				match_type = chooseSingleElementFromArea(ae, srcCellRow, srcCellCol);
197
			}
198
		}
199
		
200
		if(match_type instanceof RefEval) {
201
			RefEval re = (RefEval) match_type;
202
			match_type = re.getInnerValueEval();
203
		}
204
		if(match_type instanceof ErrorEval) {
140
		if(match_type instanceof ErrorEval) {
205
			throw new EvalEx((ErrorEval)match_type);
141
			throw new EvaluationException((ErrorEval)match_type);
206
		}
142
		}
207
		if(match_type instanceof NumericValueEval) {
143
		if(match_type instanceof NumericValueEval) {
208
			NumericValueEval ne = (NumericValueEval) match_type;
144
			NumericValueEval ne = (NumericValueEval) match_type;
Lines 210-221 Link Here
210
		}
146
		}
211
		if (match_type instanceof StringEval) {
147
		if (match_type instanceof StringEval) {
212
			StringEval se = (StringEval) match_type;
148
			StringEval se = (StringEval) match_type;
213
			Double d = parseDouble(se.getStringValue());
149
			Double d = OperandResolver.parseDouble(se.getStringValue());
214
			if(d == null) {
150
			if(d == null) {
215
				// plain string
151
				// plain string
216
				throw new EvalEx(ErrorEval.VALUE_INVALID);
152
				throw new EvaluationException(ErrorEval.VALUE_INVALID);
217
			}
153
			}
218
			// if the string parses as a number, it is ok
154
			// if the string parses as a number, it is OK
219
			return d.doubleValue();
155
			return d.doubleValue();
220
		}
156
		}
221
		throw new RuntimeException("Unexpected match_type type (" + match_type.getClass().getName() + ")");
157
		throw new RuntimeException("Unexpected match_type type (" + match_type.getClass().getName() + ")");
Lines 225-312 Link Here
225
	 * @return zero based index
161
	 * @return zero based index
226
	 */
162
	 */
227
	private static int findIndexOfValue(ValueEval lookupValue, ValueEval[] lookupRange,
163
	private static int findIndexOfValue(ValueEval lookupValue, ValueEval[] lookupRange,
228
			boolean matchExact, boolean findLargestLessThanOrEqual) throws EvalEx {
164
			boolean matchExact, boolean findLargestLessThanOrEqual) throws EvaluationException {
229
		// TODO - wildcard matching when matchExact and lookupValue is text containing * or ?
165
166
		LookupValueComparer lookupComparer = createLookupComparer(lookupValue, matchExact);
167
		
230
		if(matchExact) {
168
		if(matchExact) {
231
			for (int i = 0; i < lookupRange.length; i++) {
169
			for (int i = 0; i < lookupRange.length; i++) {
232
				ValueEval lri = lookupRange[i];
170
				if(lookupComparer.compareTo(lookupRange[i]).isEqual()) {
233
				if(lri.getClass() != lookupValue.getClass()) {
234
					continue;
235
				}
236
				if(compareValues(lookupValue, lri) == 0) {
237
					return i;
171
					return i;
238
				}
172
				}
239
			}
173
			}
240
		} else {
174
			throw new EvaluationException(ErrorEval.NA);
175
		}
176
		
177
		if(findLargestLessThanOrEqual) {
241
			// Note - backward iteration
178
			// Note - backward iteration
242
			if(findLargestLessThanOrEqual) {
179
			for (int i = lookupRange.length - 1; i>=0;  i--) {
243
				for (int i = lookupRange.length - 1; i>=0;  i--) {
180
				CompareResult cmp = lookupComparer.compareTo(lookupRange[i]);
244
					ValueEval lri = lookupRange[i];
181
				if(cmp.isTypeMismatch()) {
245
					if(lri.getClass() != lookupValue.getClass()) {
182
					continue;
246
						continue;
247
					}
248
					int cmp = compareValues(lookupValue, lri);
249
					if(cmp == 0) {
250
						return i;
251
					}
252
					if(cmp > 0) {
253
						return i;
254
					}
255
				}
183
				}
256
			} else {
184
				if(!cmp.isLessThan()) {
257
				// find smallest greater than or equal to
185
					return i;
258
				for (int i = 0; i<lookupRange.length; i++) {
259
					ValueEval lri = lookupRange[i];
260
					if(lri.getClass() != lookupValue.getClass()) {
261
						continue;
262
					}
263
					int cmp = compareValues(lookupValue, lri);
264
					if(cmp == 0) {
265
						return i;
266
					}
267
					if(cmp > 0) {
268
						if(i<1) {
269
							throw new EvalEx(ErrorEval.NA);
270
						}
271
						return i-1;
272
					}
273
				}
186
				}
274
				
275
			}
187
			}
188
			throw new EvaluationException(ErrorEval.NA);
276
		}
189
		}
190
		
191
		// else - find smallest greater than or equal to
192
		// TODO - is binary search used for (match_type==+1) ?
193
		for (int i = 0; i<lookupRange.length; i++) {
194
			CompareResult cmp = lookupComparer.compareTo(lookupRange[i]);
195
			if(cmp.isEqual()) {
196
				return i;
197
			}
198
			if(cmp.isGreaterThan()) {
199
				if(i<1) {
200
					throw new EvaluationException(ErrorEval.NA);
201
				}
202
				return i-1;
203
			}
204
		}
277
205
278
		throw new EvalEx(ErrorEval.NA);
206
		throw new EvaluationException(ErrorEval.NA);
279
	}
207
	}
280
208
281
209
	private static LookupValueComparer createLookupComparer(ValueEval lookupValue, boolean matchExact) throws EvaluationException {
282
	/**
210
		if (matchExact && lookupValue instanceof StringEval) {
283
	 * This method can only compare a pair of <tt>NumericValueEval</tt>s, <tt>StringEval</tt>s
211
			String stringValue = ((StringEval) lookupValue).getStringValue();
284
	 * or <tt>BoolEval</tt>s
212
			if(isLookupValueWild(stringValue)) {
285
	 * @return negative for a&lt;b, positive for a&gt;b and 0 for a = b
213
				throw new RuntimeException("Wildcard lookup values '" + stringValue + "' not supported yet");
286
	 */
287
	private static int compareValues(ValueEval a, ValueEval b) {
288
		if (a instanceof StringEval) {
289
			StringEval sa = (StringEval) a;
290
			StringEval sb = (StringEval) b;
291
			return sa.getStringValue().compareToIgnoreCase(sb.getStringValue());
292
		}
293
		if (a instanceof NumericValueEval) {
294
			NumericValueEval na = (NumericValueEval) a;
295
			NumericValueEval nb = (NumericValueEval) b;
296
			return Double.compare(na.getNumberValue(), nb.getNumberValue());
297
		}
298
		if (a instanceof BoolEval) {
299
			boolean ba = ((BoolEval) a).getBooleanValue();
300
			boolean bb = ((BoolEval) b).getBooleanValue();
301
			if(ba == bb) {
302
				return 0;
303
			}
214
			}
304
			// TRUE > FALSE
215
			
305
			if(ba) {
306
				return +1;
307
			}
308
			return -1;
309
		}
216
		}
310
		throw new RuntimeException("bad eval type (" + a.getClass().getName() + ")");
217
		return LookupUtils.createLookupComparer(lookupValue);
311
	}
218
	}
219
220
	private static boolean isLookupValueWild(String stringValue) {
221
		if(stringValue.indexOf('?') >=0 || stringValue.indexOf('*') >=0) {
222
			return true;
223
		}
224
		return false;
225
	}
312
}
226
}

Return to bug 44450