From e346cb4473c8215fb21510b2e89acb61b1489672 Mon Sep 17 00:00:00 2001 From: Avik Sengupta Date: Tue, 11 Jun 2002 20:18:28 +0000 Subject: [PATCH] More formula enhancements, particularly for fixed args tFunc 0x21/41/61. Also try not to bomb on unknown Ptg git-svn-id: https://svn.apache.org/repos/asf/jakarta/poi/trunk@352668 13f79535-47bb-0310-9956-ffa450edef68 --- .../apache/poi/hssf/dev/FormulaViewer.java | 96 +++- .../apache/poi/hssf/record/FormulaRecord.java | 24 +- .../record/formula/AbstractFunctionPtg.java | 538 ++++++++++++++++++ .../poi/hssf/record/formula/AreaPtg.java | 2 +- .../poi/hssf/record/formula/AttrPtg.java | 4 +- .../hssf/record/formula/FormulaParser.java | 34 +- .../poi/hssf/record/formula/FuncPtg.java | 31 + .../poi/hssf/record/formula/FuncVarPtg.java | 42 ++ .../hssf/record/formula/MissingArgPtg.java | 101 ++++ .../apache/poi/hssf/record/formula/Ptg.java | 41 +- .../apache/poi/hssf/util/ReferenceUtil.java | 137 ----- 11 files changed, 873 insertions(+), 177 deletions(-) create mode 100644 src/java/org/apache/poi/hssf/record/formula/AbstractFunctionPtg.java create mode 100644 src/java/org/apache/poi/hssf/record/formula/FuncPtg.java create mode 100644 src/java/org/apache/poi/hssf/record/formula/FuncVarPtg.java create mode 100644 src/java/org/apache/poi/hssf/record/formula/MissingArgPtg.java delete mode 100644 src/java/org/apache/poi/hssf/util/ReferenceUtil.java diff --git a/src/java/org/apache/poi/hssf/dev/FormulaViewer.java b/src/java/org/apache/poi/hssf/dev/FormulaViewer.java index e01ede0fa..30e268380 100644 --- a/src/java/org/apache/poi/hssf/dev/FormulaViewer.java +++ b/src/java/org/apache/poi/hssf/dev/FormulaViewer.java @@ -88,6 +88,7 @@ import org.apache.poi.hssf.usermodel.*; public class FormulaViewer { private String file; + private boolean list=false; /** Creates new FormulaViewer */ @@ -118,10 +119,67 @@ public class FormulaViewer if (record.getSid() == FormulaRecord.sid) { - parseFormulaRecord(( FormulaRecord ) record); + if (list) { + listFormula((FormulaRecord) record); + }else { + parseFormulaRecord(( FormulaRecord ) record); + } } } } + + private void listFormula(FormulaRecord record) { + String sep="~"; + List tokens= record.getParsedExpression(); + int numptgs = record.getNumberOfExpressionTokens(); + Ptg token = null; + String name,numArg; + if (tokens != null) { + token = (Ptg) tokens.get(numptgs-1); + if (token instanceof FuncPtg) { + numArg = String.valueOf(numptgs-1); + } else { numArg = String.valueOf(-1);} + + StringBuffer buf = new StringBuffer(); + + buf.append(name=((OperationPtg) token).toFormulaString()); + buf.append(sep); + switch (token.getPtgClass()) { + case Ptg.CLASS_REF : + buf.append("REF"); + break; + case Ptg.CLASS_VALUE : + buf.append("VALUE"); + break; + case Ptg.CLASS_ARRAY : + buf.append("ARRAY"); + break; + } + + buf.append(sep); + if (numptgs>1) { + token = (Ptg) tokens.get(numptgs-2); + switch (token.getPtgClass()) { + case Ptg.CLASS_REF : + buf.append("REF"); + break; + case Ptg.CLASS_VALUE : + buf.append("VALUE"); + break; + case Ptg.CLASS_ARRAY : + buf.append("ARRAY"); + break; + } + }else { + buf.append("VALUE"); + } + buf.append(sep); + buf.append(numArg); + System.out.println(buf.toString()); + } else { + System.out.println("#NAME"); + } + } /** * Method parseFormulaRecord @@ -142,23 +200,36 @@ public class FormulaViewer + record.getNumberOfExpressionTokens()); System.out.println(", options = " + record.getOptions()); System.out.println("RPN List = "+formulaString(record)); - System.out.println("Formula text = "+ composeForumla(record)); + System.out.println("Formula text = "+ composeFormula(record)); } private String formulaString(FormulaRecord record) { StringBuffer formula = new StringBuffer("="); int numptgs = record.getNumberOfExpressionTokens(); List tokens = record.getParsedExpression(); + Ptg token; StringBuffer buf = new StringBuffer(); for (int i=0;i2 ) || args[ 0 ].equals("--help")) { System.out.println( "FormulaViewer .8 proof that the devil lies in the details (or just in BIFF8 files in general)"); System.out.println("usage: Give me a big fat file name"); + } else if (args[0].equals("--listFunctions")) { // undocumented attribute to research functions!~ + try { + FormulaViewer viewer = new FormulaViewer(); + viewer.setFile(args[1]); + viewer.setList(true); + viewer.run(); + } + catch (Exception e) { + System.out.println("Whoops!"); + e.printStackTrace(); + } } else { diff --git a/src/java/org/apache/poi/hssf/record/FormulaRecord.java b/src/java/org/apache/poi/hssf/record/FormulaRecord.java index e4f9bed88..b5ee198f1 100644 --- a/src/java/org/apache/poi/hssf/record/FormulaRecord.java +++ b/src/java/org/apache/poi/hssf/record/FormulaRecord.java @@ -67,7 +67,7 @@ import org.apache.poi.util.LittleEndian; import org.apache.poi.hssf.record.formula.*; /** - * Formula Record - This is not really supported in this release. Its here for future use.

+ * Formula Record. * REFERENCE: PG 317/444 Microsoft Excel 97 Developer's Kit (ISBN: 1-57231-498-2)

* @author Andrew C. Oliver (acoliver at apache dot org) * @version 2.0-pre @@ -134,7 +134,7 @@ public class FormulaRecord protected void fillFields(byte [] data, short size, int offset) { - if (EXPERIMENTAL_FORMULA_SUPPORT_ENABLED) { + try { //field_1_row = LittleEndian.getShort(data, 0 + offset); field_1_row = LittleEndian.getUShort(data, 0 + offset); field_2_column = LittleEndian.getShort(data, 2 + offset); @@ -146,11 +146,15 @@ public class FormulaRecord field_8_parsed_expr = getParsedExpressionTokens(data, size, offset); - } else { + } catch (java.lang.UnsupportedOperationException uoe) { + field_8_parsed_expr = null; all_data = new byte[size+4]; LittleEndian.putShort(all_data,0,sid); LittleEndian.putShort(all_data,2,size); System.arraycopy(data,offset,all_data,4,size); + System.err.println("[WARNING] Unknown Ptg " + + uoe.getMessage() + + " at cell ("+field_1_row+","+field_2_column+")"); } } @@ -164,7 +168,6 @@ public class FormulaRecord while (pos < size) { Ptg ptg = Ptg.createPtg(data, pos); - pos += ptg.getSize(); stack.push(ptg); } @@ -307,13 +310,20 @@ public class FormulaRecord public int getNumberOfExpressionTokens() { - return field_8_parsed_expr.size(); + if (this.field_8_parsed_expr == null) { + return 0; + } else { + return field_8_parsed_expr.size(); + } } /** * get the stack as a list * * @return list of tokens (casts stack to a list and returns it!) + * this method can return null is we are unable to create Ptgs from + * existing excel file + * callers should check for null! */ public List getParsedExpression() @@ -351,7 +361,7 @@ public class FormulaRecord public int serialize(int offset, byte [] data) { - if (EXPERIMENTAL_FORMULA_SUPPORT_ENABLED) { + if (this.field_8_parsed_expr != null) { int ptgSize = getTotalPtgSize(); LittleEndian.putShort(data, 0 + offset, sid); @@ -378,7 +388,7 @@ public class FormulaRecord { int retval =0; - if (EXPERIMENTAL_FORMULA_SUPPORT_ENABLED) { + if (this.field_8_parsed_expr != null) { retval = getTotalPtgSize() + 26; } else { retval =all_data.length; diff --git a/src/java/org/apache/poi/hssf/record/formula/AbstractFunctionPtg.java b/src/java/org/apache/poi/hssf/record/formula/AbstractFunctionPtg.java new file mode 100644 index 000000000..50c76fdaf --- /dev/null +++ b/src/java/org/apache/poi/hssf/record/formula/AbstractFunctionPtg.java @@ -0,0 +1,538 @@ +package org.apache.poi.hssf.record.formula; + +import org.apache.poi.util.BinaryTree; +/** + * This class provides the base functionality for Excel sheet functions + * There are two kinds of function Ptgs - tFunc and tFuncVar + * Therefore, this class will have ONLY two subclasses + * @author Avik Sengupta + * @author Andrew C. Oliver (acoliver at apache dot org) + */ +public abstract class AbstractFunctionPtg extends OperationPtg { + + private final static int SIZE = 4; + + private static BinaryTree map = produceHash(); + protected static Object[][] functionData = produceFunctionData(); + protected byte returnClass; + protected byte[] paramClass; + + protected byte field_1_num_args; + protected short field_2_fnc_index; + + + public String toString() { + StringBuffer buffer = new StringBuffer(); + buffer + .append("").append("\n") + .append(" field_1_num_args=").append(field_1_num_args).append("\n") + .append(" name =").append(lookupName(field_2_fnc_index)).append("\n") + .append(" field_2_fnc_index=").append(field_2_fnc_index).append("\n") + .append(""); + return buffer.toString(); + } + + public int getType() { + return -1; + } + + + + public short getFunctionIndex() { + return field_2_fnc_index; + } + + public String getName() { + return lookupName(field_2_fnc_index); + } + + public String toFormulaString() { + return getName(); + } + + public String toFormulaString(String[] operands) { + StringBuffer buf = new StringBuffer(); + buf.append(getName()+"("); + if (operands.length >0) { + for (int i=0;i 0) { + // Excel allows to have AttrPtg at position 0 (such as Blanks) which + // do not have any operands. Skip them. + if (ptgs[i] instanceof OperationPtg && i>0) { o = (OperationPtg) ptgs[i]; numOperands = o.getNumberOfOperands(); operands = new String[numOperands]; @@ -561,7 +568,6 @@ end; } String result = o.toFormulaString(operands); stack.push(result); - } } else { stack.push(ptgs[i].toFormulaString()); } diff --git a/src/java/org/apache/poi/hssf/record/formula/FuncPtg.java b/src/java/org/apache/poi/hssf/record/formula/FuncPtg.java new file mode 100644 index 000000000..ad573d836 --- /dev/null +++ b/src/java/org/apache/poi/hssf/record/formula/FuncPtg.java @@ -0,0 +1,31 @@ +package org.apache.poi.hssf.record.formula; +import org.apache.poi.util.LittleEndian; + +public class FuncPtg extends AbstractFunctionPtg{ + + public final static short sid = 0x21; + private int numParams=0; + /**Creates new function pointer from a byte array + * usually called while reading an excel file. + */ + public FuncPtg(byte[] data, int offset) { + offset++; + //field_1_num_args = data[ offset + 0 ]; + field_2_fnc_index = LittleEndian.getShort(data,offset + 0 ); + try { + numParams = ( (Integer)functionData[field_2_fnc_index][2]).intValue(); + } catch (NullPointerException npe) { + numParams=0; + } + } + + public void writeBytes(byte[] array, int offset) { + array[offset+0]=(byte) (sid + ptgClass); + //array[offset+1]=field_1_num_args; + LittleEndian.putShort(array,offset+1,field_2_fnc_index); + } + + public int getNumberOfOperands() { + return numParams; + } +} \ No newline at end of file diff --git a/src/java/org/apache/poi/hssf/record/formula/FuncVarPtg.java b/src/java/org/apache/poi/hssf/record/formula/FuncVarPtg.java new file mode 100644 index 000000000..fff93a5db --- /dev/null +++ b/src/java/org/apache/poi/hssf/record/formula/FuncVarPtg.java @@ -0,0 +1,42 @@ +package org.apache.poi.hssf.record.formula; +import org.apache.poi.util.LittleEndian; +public class FuncVarPtg extends AbstractFunctionPtg{ + + public final static short sid = 0x22; + + /**Creates new function pointer from a byte array + * usually called while reading an excel file. + */ + public FuncVarPtg(byte[] data, int offset) { + offset++; + field_1_num_args = data[ offset + 0 ]; + field_2_fnc_index = LittleEndian.getShort(data,offset + 1 ); + } + + /** + * Create a function ptg from a string tokenised by the parser + */ + protected FuncVarPtg(String pName, byte pNumOperands) { + field_1_num_args = pNumOperands; + field_2_fnc_index = lookupIndex(pName); + try{ + returnClass = ( (Byte) functionData[field_2_fnc_index][0]).byteValue(); + paramClass = (byte[]) functionData[field_2_fnc_index][1]; + } catch (NullPointerException npe ) { + returnClass = Ptg.CLASS_VALUE; + paramClass = new byte[] {Ptg.CLASS_VALUE}; + } + } + + public void writeBytes(byte[] array, int offset) { + array[offset+0]=(byte) (sid + ptgClass); + array[offset+1]=field_1_num_args; + LittleEndian.putShort(array,offset+2,field_2_fnc_index); + } + + public int getNumberOfOperands() { + return field_1_num_args; + } + + +} diff --git a/src/java/org/apache/poi/hssf/record/formula/MissingArgPtg.java b/src/java/org/apache/poi/hssf/record/formula/MissingArgPtg.java new file mode 100644 index 000000000..1abb59ed7 --- /dev/null +++ b/src/java/org/apache/poi/hssf/record/formula/MissingArgPtg.java @@ -0,0 +1,101 @@ +/* ==================================================================== + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2002 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Apache" and "Apache Software Foundation" and + * "Apache POI" must not be used to endorse or promote products + * derived from this software without prior written permission. For + * written permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache", + * "Apache POI", nor may "Apache" appear in their name, without + * prior written permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + */ + +package org.apache.poi.hssf.record.formula; + + +/** + * Missing Function Arguments + * + * Avik Sengupta + */ +public class MissingArgPtg + extends Ptg +{ + + private final static int SIZE = 1; + public final static byte sid = 0x16; + + protected MissingArgPtg() + { + } + + public MissingArgPtg(byte [] data, int offset) + { + // doesn't need anything + } + + + + public void writeBytes(byte [] array, int offset) + { + array[ offset + 0 ] = sid; + } + + public int getSize() + { + return SIZE; + } + + + public String toFormulaString() + { + return " "; + } + + public byte getDefaultOperandClass() {return Ptg.CLASS_VALUE;} + +} + + diff --git a/src/java/org/apache/poi/hssf/record/formula/Ptg.java b/src/java/org/apache/poi/hssf/record/formula/Ptg.java index 24c219c6c..24a25a5fc 100644 --- a/src/java/org/apache/poi/hssf/record/formula/Ptg.java +++ b/src/java/org/apache/poi/hssf/record/formula/Ptg.java @@ -133,14 +133,15 @@ public abstract class Ptg final byte valueRef = ReferencePtg.sid + 0x20; final byte arrayRef = ReferencePtg.sid + 0x40; - final byte valueFunc = FunctionPtg.sid + 0x20; - final byte arrayFunc = FunctionPtg.sid + 0x40; + final byte valueFunc = FuncPtg.sid + 0x20; + final byte arrayFunc = FuncPtg.sid + 0x40; + final byte valueFuncVar = FuncVarPtg.sid +0x20; + final byte arrayFuncVar = FuncVarPtg.sid+0x40; final byte valueArea = AreaPtg.sid + 0x20; final byte arrayArea = AreaPtg.sid + 0x40; switch (id) { - case AddPtg.sid : retval = new AddPtg(data, offset); break; @@ -200,15 +201,26 @@ public abstract class Ptg retval = new ParenthesisPtg(data, offset); break; - case FunctionPtg.sid : - retval = new FunctionPtg(data, offset); + case FuncPtg.sid : + retval = new FuncPtg(data, offset); break; case valueFunc : - retval = new FunctionPtg(data, offset); + retval = new FuncPtg(data, offset); break; case arrayFunc : - retval = new FunctionPtg(data, offset); + retval = new FuncPtg(data, offset); + break; + + case FuncVarPtg.sid : + retval = new FuncVarPtg(data, offset); + break; + + case valueFuncVar : + retval = new FuncVarPtg(data, offset); + break; + case arrayFuncVar : + retval = new FuncVarPtg(data, offset); break; case NumberPtg.sid : @@ -234,13 +246,16 @@ public abstract class Ptg case Ref3DPtg.sid: retval = new Ref3DPtg(data, offset); break; + + case MissingArgPtg.sid: + retval = new MissingArgPtg(data,offset); + break; default : - // retval = new UnknownPtg(); - throw new RuntimeException("Unknown PTG = " - + Integer.toHexString(( int ) id) - + " (" + ( int ) id + ")"); + //retval = new UnknownPtg(); + throw new java.lang.UnsupportedOperationException( + Integer.toHexString(( int ) id) + " (" + ( int ) id + ")"); } if (id > 0x60) { @@ -295,6 +310,10 @@ public abstract class Ptg ptgClass = thePtgClass; } + /** returns the class (REF/VALUE/ARRAY) for this Ptg */ + public byte getPtgClass() { + return ptgClass; + } public abstract byte getDefaultOperandClass(); diff --git a/src/java/org/apache/poi/hssf/util/ReferenceUtil.java b/src/java/org/apache/poi/hssf/util/ReferenceUtil.java deleted file mode 100644 index da3a7f0f6..000000000 --- a/src/java/org/apache/poi/hssf/util/ReferenceUtil.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * ReferenceUtil.java - * - * Created on April 28, 2002, 1:09 PM - */ - -package org.apache.poi.hssf.util; - -/** - * Handles conversion between A1= 0,0 (cell ref to numeric conversion) - * @author Andrew C. Oliver (acoliver at apache dot org) - */ -public class ReferenceUtil { - - /** You don't neeed to construct this */ - private ReferenceUtil() { - } - - /** - * takes in a cell range and returns an array of integers x1,y1,x2,y2 - */ - public static int[] getXYXYFromAreaRef(String reference) { - int retval[] = null; - String[] refs = seperateAreaRefs(reference); - int[] xy1 = getXYFromReference(refs[0]); - int[] xy2 = getXYFromReference(refs[1]); - retval = new int[] {xy1[0],xy1[1],xy2[0],xy2[1]}; - return retval; - } - - /** - * takes in a cell reference string A1 for instance and returns an integer - * array with the first element being the row number and the second being - * the column number, all in 0-based base 10 format. - * @param reference a cell reference such as A1 or AA1 or IV1 - * @return xyarray int array containing row and column number - */ - public static int[] getXYFromReference(String reference) { - int[] retval = new int[2]; - String[] parts = seperateRowColumns(reference); - retval[1] = convertColStringToNum(parts[0]); - retval[0] = Integer.parseInt(parts[1])-1; - return retval; - } - - /** - * takes in a row and column and returns a string cellref - * @param row the 0 based row such as 0 - * @param col the 0 based col number such as 1 - * @return cellreference string such as B1 - */ - public static String getReferenceFromXY(int row, int col) { - String retval = convertNumToColString(col) + ""+(row+1); - return retval; - } - - /** - * takes in a 0-based base-10 column and returns a ALPHA-26 representation - */ - private static String convertNumToColString(int col) { - String retval = null; - int mod = col % 26; - int div = col / 26; - char small=(char)(mod + 65); - char big = (char)(div + 64); - - if (div == 0) { - retval = ""+small; - } else { - retval = ""+big+""+small; - } - - return retval; - } - - /** - * takes in a column reference portion of a CellRef and converts it from - * ALPHA-26 number format to 0-based base 10. - */ - private static int convertColStringToNum(String ref) { - int len = ref.length(); - int retval=0; - int pos = 0; - for (int k = ref.length()-1; k > -1; k--) { - char thechar = ref.charAt(k); - if ( pos == 0) { - retval += (Character.getNumericValue(thechar)-9); - } else { - retval += (Character.getNumericValue(thechar)-9) * (pos * 26); - } - pos++; - } - return retval-1; - } - - - /** - * Seperates the row from the columns and returns an array. Element in - * position one is the substring containing the columns still in ALPHA-26 - * number format. - */ - private static String[] seperateRowColumns(String reference) { - int loc = 0; // location of first number - String retval[] = new String[2]; - int length = reference.length(); - - char[] chars = reference.toCharArray(); - - for (loc = 0; loc < chars.length; loc++) { - if (Character.isDigit(chars[loc])) { - break; - } - } - - retval[0] = reference.substring(0,loc); - retval[1] = reference.substring(loc); - return retval; - } - - - /** - * seperates Area refs in two parts and returns them as seperate elements in a - * String array - */ - private static String[] seperateAreaRefs(String reference) { - String retval[] = new String[2]; - int length = reference.length(); - - int loc = reference.indexOf(':',0); - - retval[0] = reference.substring(0,loc); - retval[1] = reference.substring(loc+1); - return retval; - } - - -}