You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@poi.apache.org by ug...@apache.org on 2008/03/08 12:49:17 UTC

svn commit: r634936 [2/8] - in /poi/branches/ooxml: ./ legal/ src/contrib/src/org/apache/poi/hssf/contrib/view/ src/documentation/content/xdocs/ src/documentation/content/xdocs/getinvolved/ src/documentation/content/xdocs/hslf/ src/java/org/apache/poi/...

Modified: poi/branches/ooxml/src/java/org/apache/poi/hssf/model/FormulaParser.java
URL: http://svn.apache.org/viewvc/poi/branches/ooxml/src/java/org/apache/poi/hssf/model/FormulaParser.java?rev=634936&r1=634935&r2=634936&view=diff
==============================================================================
--- poi/branches/ooxml/src/java/org/apache/poi/hssf/model/FormulaParser.java (original)
+++ poi/branches/ooxml/src/java/org/apache/poi/hssf/model/FormulaParser.java Sat Mar  8 03:49:00 2008
@@ -1,4 +1,3 @@
-
 /* ====================================================================
    Licensed to the Apache Software Foundation (ASF) under one or more
    contributor license agreements.  See the NOTICE file distributed with
@@ -15,15 +14,13 @@
    See the License for the specific language governing permissions and
    limitations under the License.
 ==================================================================== */
-        
-
 
 package org.apache.poi.hssf.model;
 
 import java.util.ArrayList;
 import java.util.Iterator;
-import java.util.LinkedList;
 import java.util.List;
+import java.util.Stack;
 import java.util.regex.Pattern;
 
 //import PTG's .. since we need everything, import *
@@ -33,7 +30,7 @@
 
 /**
  * This class parses a formula string into a List of tokens in RPN order.
- * Inspired by 
+ * Inspired by
  *           Lets Build a Compiler, by Jack Crenshaw
  * BNF for the formula expression is :
  * <expression> ::= <term> [<addop> <term>]*
@@ -48,138 +45,124 @@
  *  @author Peter M. Murray (pete at quantrix dot com)
  *  @author Pavel Krupets (pkrupets at palmtreebusiness dot com)
  */
-public class FormulaParser {
+public final class FormulaParser {
     
+    /**
+     * Specific exception thrown when a supplied formula does not parse properly.<br/>
+     * Primarily used by test cases when testing for specific parsing exceptions.</p>
+     *    
+     */
+    static final class FormulaParseException extends RuntimeException {
+        // This class was given package scope until it would become clear that it is useful to
+        // general client code. 
+        public FormulaParseException(String msg) {
+            super(msg);
+        }
+    }
+
     public static int FORMULA_TYPE_CELL = 0;
     public static int FORMULA_TYPE_SHARED = 1;
     public static int FORMULA_TYPE_ARRAY =2;
     public static int FORMULA_TYPE_CONDFOMRAT = 3;
     public static int FORMULA_TYPE_NAMEDRANGE = 4;
-    
-    private String formulaString;
-    private int pointer=0;
-    private int formulaLength;
-    
-    private List tokens = new java.util.Stack();
-    
-    /**
-     * Using an unsynchronized linkedlist to implement a stack since we're not multi-threaded.
-     */
-    private List functionTokens = new LinkedList();
+
+    private final String formulaString;
+    private final int formulaLength;
+    private int pointer;
+
+    private final List tokens = new Stack();
 
     /**
      * Used for spotting if we have a cell reference,
      *  or a named range
      */
     private final static Pattern CELL_REFERENCE_PATTERN = Pattern.compile("(?:('?)[^:\\\\/\\?\\*\\[\\]]+\\1!)?\\$?[A-Za-z]+\\$?[\\d]+");
-        
+
     private static char TAB = '\t';
-    private static char CR = '\n';
-    
-   private char look;              // Lookahead Character
-   
-   private Workbook book;
-    
-    
-    /** 
+
+    /**
+     * Lookahead Character.
+     * gets value '\0' when the input string is exhausted
+     */
+    private char look;
+
+    private Workbook book;
+
+
+    /**
      * Create the formula parser, with the string that is to be
      *  parsed against the supplied workbook.
      * A later call the parse() method to return ptg list in
      *  rpn order, then call the getRPNPtg() to retrive the
      *  parse results.
      * This class is recommended only for single threaded use.
-     * 
+     *
      * If you only have a usermodel.HSSFWorkbook, and not a
      *  model.Workbook, then use the convenience method on
-     *  usermodel.HSSFFormulaEvaluator 
+     *  usermodel.HSSFFormulaEvaluator
      */
     public FormulaParser(String formula, Workbook book){
         formulaString = formula;
         pointer=0;
         this.book = book;
-    	formulaLength = formulaString.length();
+        formulaLength = formulaString.length();
+    }
+
+    public static Ptg[] parse(String formula, Workbook book) {
+        FormulaParser fp = new FormulaParser(formula, book);
+        fp.parse();
+        return fp.getRPNPtg();
     }
-    
 
     /** Read New Character From Input Stream */
     private void GetChar() {
         // Check to see if we've walked off the end of the string.
-	// Just return if so and reset Look to smoething to keep 
-	// SkipWhitespace from spinning
-        if (pointer == formulaLength) {
+        if (pointer > formulaLength) {
+            throw new RuntimeException("too far");
+        }
+        if (pointer < formulaLength) {
+            look=formulaString.charAt(pointer);
+        } else {
+            // Just return if so and reset 'look' to something to keep
+            // SkipWhitespace from spinning
             look = (char)0;
-	    return;
-	}
-        look=formulaString.charAt(pointer++);
+        }    
+        pointer++;
         //System.out.println("Got char: "+ look);
     }
-    
-
-    /** Report an Error */
-    private void Error(String s) {
-        System.out.println("Error: "+s);
-    }
-    
-    
- 
-    /** Report Error and Halt */
-    private void Abort(String s) {
-        Error(s);
-        //System.exit(1);  //throw exception??
-        throw new RuntimeException("Cannot Parse, sorry : " + s + " @ " + pointer + " [Formula String was: '" + formulaString + "']");
-    }
-    
-    
 
     /** Report What Was Expected */
-    private void Expected(String s) {
-        Abort(s + " Expected");
+    private RuntimeException expected(String s) {
+        return new FormulaParseException(s + " Expected");
     }
-    
-    
- 
+
+
+
     /** Recognize an Alpha Character */
     private boolean IsAlpha(char c) {
         return Character.isLetter(c) || c == '$' || c=='_';
     }
-    
-    
- 
+
+
+
     /** Recognize a Decimal Digit */
     private boolean IsDigit(char c) {
         //System.out.println("Checking digit for"+c);
         return Character.isDigit(c);
     }
-    
-    
+
+
 
     /** Recognize an Alphanumeric */
     private boolean  IsAlNum(char c) {
         return  (IsAlpha(c) || IsDigit(c));
     }
-    
-    
 
-    /** Recognize an Addop */
-    private boolean IsAddop( char c) {
-        return (c =='+' || c =='-');
-    }
-    
 
     /** Recognize White Space */
     private boolean IsWhite( char c) {
         return  (c ==' ' || c== TAB);
     }
-    
-    /**
-     * Determines special characters;primarily in use for definition of string literals
-     * @param c
-     * @return boolean
-     */
-    private boolean IsSpecialChar(char c) {
-    	return (c == '>' || c== '<' || c== '=' || c=='&' || c=='[' || c==']');
-    }
-    
 
     /** Skip Over Leading White Space */
     private void SkipWhite() {
@@ -187,108 +170,84 @@
             GetChar();
         }
     }
-    
-    
 
-    /** Match a Specific Input Character */
+    /**
+     *  Consumes the next input character if it is equal to the one specified otherwise throws an
+     *  unchecked exception. This method does <b>not</b> consume whitespace (before or after the
+     *  matched character). 
+     */
     private void Match(char x) {
         if (look != x) {
-            Expected("" + x + "");
-        }else {
-            GetChar();
-            SkipWhite();
+            throw expected("'" + x + "'");
         }
+        GetChar();
     }
-    
+
     /** Get an Identifier */
     private String GetName() {
         StringBuffer Token = new StringBuffer();
         if (!IsAlpha(look) && look != '\'') {
-            Expected("Name");
+            throw expected("Name");
         }
         if(look == '\'')
         {
-        	Match('\'');
-        	boolean done = look == '\'';
-        	while(!done)
-        	{
-        		Token.append(Character.toUpperCase(look));
-        		GetChar();
-        		if(look == '\'')
-        		{
-        			Match('\'');
-        			done = look != '\'';
-        		}
-        	}
+            Match('\'');
+            boolean done = look == '\'';
+            while(!done)
+            {
+                Token.append(look);
+                GetChar();
+                if(look == '\'')
+                {
+                    Match('\'');
+                    done = look != '\'';
+                }
+            }
         }
         else
         {
-	        while (IsAlNum(look)) {
-	            Token.append(Character.toUpperCase(look));
-	            GetChar();
-	        }
-		}
-        SkipWhite();
-        return Token.toString();
-    }
-    
-    /**Get an Identifier AS IS, without stripping white spaces or 
-       converting to uppercase; used for literals */
-    private String GetNameAsIs() {
-        StringBuffer Token = new StringBuffer();
-		
-		while (IsAlNum(look) || IsWhite(look) || IsSpecialChar(look)) {
-            Token = Token.append(look);
-            GetChar();
+            while (IsAlNum(look)) {
+                Token.append(look);
+                GetChar();
+            }
         }
         return Token.toString();
     }
-    
-    
+
+
     /** Get a Number */
     private String GetNum() {
         StringBuffer value = new StringBuffer();
-        
+
         while (IsDigit(this.look)){
             value.append(this.look);
             GetChar();
         }
-        
-        SkipWhite();
-        
         return value.length() == 0 ? null : value.toString();
     }
-        
-    
-    /** Output a String with Tab */
-    private void  Emit(String s){
-        System.out.print(TAB+s);
-    }
 
-    /** Output a String with Tab and CRLF */
-    private void EmitLn(String s) {
-        Emit(s);
-        System.out.println();;
-    }
-    
     /** Parse and Translate a String Identifier */
-    private void Ident() {
+    private Ptg parseIdent() {
         String name;
         name = GetName();
         if (look == '('){
             //This is a function
-            function(name);
-        } else if (look == ':' || look == '.') { // this is a AreaReference
+            return function(name);
+        }
+
+        if (look == ':' || look == '.') { // this is a AreaReference
             GetChar();
-            
+
             while (look == '.') { // formulas can have . or .. or ... instead of :
                 GetChar();
             }
-            
+
             String first = name;
             String second = GetName();
-            tokens.add(new AreaPtg(first+":"+second));
-        } else if (look == '!') {
+            return new AreaPtg(first+":"+second);
+        }
+
+        if (look == '!') {
             Match('!');
             String sheetName = name;
             String first = GetName();
@@ -297,79 +256,78 @@
                 Match(':');
                 String second=GetName();
                 if (look == '!') {
-                	//The sheet name was included in both of the areas. Only really
-                	//need it once
-                	Match('!');
-                	String third=GetName();
-                	
-                	if (!sheetName.equals(second))
-                		throw new RuntimeException("Unhandled double sheet reference.");
-                	
-                	tokens.add(new Area3DPtg(first+":"+third,externIdx));
-                } else {                                  
-                  tokens.add(new Area3DPtg(first+":"+second,externIdx));
+                    //The sheet name was included in both of the areas. Only really
+                    //need it once
+                    Match('!');
+                    String third=GetName();
+
+                    if (!sheetName.equals(second))
+                        throw new RuntimeException("Unhandled double sheet reference.");
+
+                    return new Area3DPtg(first+":"+third,externIdx);
                 }
-            } else {
-                tokens.add(new Ref3DPtg(first,externIdx));
+                return new Area3DPtg(first+":"+second,externIdx);
             }
-        } else {
-            // This can be either a cell ref or a named range
-        	// Try to spot which it is
-        	boolean cellRef = CELL_REFERENCE_PATTERN.matcher(name).matches();
-            boolean boolLit = (name.equals("TRUE") || name.equals("FALSE"));
-            
-            if (boolLit) {
-                tokens.add(new BoolPtg(name));
-            } else if (cellRef) {
-                tokens.add(new ReferencePtg(name));
-            } else {
-            	boolean nameRecordExists = false;
-                for(int i = 0; i < book.getNumNames(); i++) {
-                	// Our formula will by now contain an upper-cased
-                	//  version of any named range names
-                    if(book.getNameRecord(i).getNameText().toUpperCase().equals(name)) {
-                        nameRecordExists = true;
-                    }
-                }
-                if(!nameRecordExists)
-                    Abort("Found reference to named range \"" + name + "\", but that named range wasn't defined!");
-                tokens.add(new NamePtg(name, book));
+            return new Ref3DPtg(first,externIdx);
+        }
+        if (name.equalsIgnoreCase("TRUE") || name.equalsIgnoreCase("FALSE")) {
+            return new BoolPtg(name.toUpperCase());
+        }
+
+        // This can be either a cell ref or a named range
+        // Try to spot which it is
+        boolean cellRef = CELL_REFERENCE_PATTERN.matcher(name).matches();
+ 
+        if (cellRef) {
+            return new ReferencePtg(name);
+        }
+
+        for(int i = 0; i < book.getNumNames(); i++) {
+            // named range name matching is case insensitive
+            if(book.getNameRecord(i).getNameText().equalsIgnoreCase(name)) {
+                return new NamePtg(name, book);
             }
         }
+        throw new FormulaParseException("Found reference to named range \"" 
+                    + name + "\", but that named range wasn't defined!");
     }
-    
+
     /**
      * Adds a pointer to the last token to the latest function argument list.
      * @param obj
      */
-    private void addArgumentPointer() {
-		if (this.functionTokens.size() > 0) {
-			//no bounds check because this method should not be called unless a token array is setup by function()
-			List arguments = (List)this.functionTokens.get(0);
-			arguments.add(tokens.get(tokens.size()-1));
-		}
+    private void addArgumentPointer(List argumentPointers) {
+        argumentPointers.add(tokens.get(tokens.size()-1));
     }
-    
-    private void function(String name) {
-    	//average 2 args per function
-    	this.functionTokens.add(0, new ArrayList(2));
-    	
+
+    /**
+     * Note - Excel function names are 'case aware but not case sensitive'.  This method may end
+     * up creating a defined name record in the workbook if the specified name is not an internal
+     * Excel function, and has not been encountered before. 
+     * 
+     * @param name case preserved function name (as it was entered/appeared in the formula). 
+     */
+    private Ptg function(String name) {
+        int numArgs =0 ;
+        // Note regarding parameter - 
+        if(!AbstractFunctionPtg.isInternalFunctionName(name)) {
+            // external functions get a Name token which points to a defined name record
+            NamePtg nameToken = new NamePtg(name, this.book);
+            
+            // in the token tree, the name is more or less the first argument
+            numArgs++;  
+            tokens.add(nameToken);
+        }
+        //average 2 args per function
+        List argumentPointers = new ArrayList(2);
+
         Match('(');
-        int numArgs = Arguments();
+        numArgs += Arguments(argumentPointers);
         Match(')');
-                
-        AbstractFunctionPtg functionPtg = getFunction(name,(byte)numArgs);
-        
-		tokens.add(functionPtg);
- 
-        if (functionPtg.getName().equals("externalflag")) {
-            tokens.add(new NamePtg(name, this.book));
-        }
 
- 		//remove what we just put in
-		this.functionTokens.remove(0);
+        return getFunction(name, numArgs, argumentPointers);
     }
-    
+
     /**
      * Adds the size of all the ptgs after the provided index (inclusive).
      * <p>
@@ -378,17 +336,17 @@
      * @return int
      */
     private int getPtgSize(int index) {
-    	int count = 0;
-    	
-    	Iterator ptgIterator = tokens.listIterator(index);
-    	while (ptgIterator.hasNext()) {
-    		Ptg ptg = (Ptg)ptgIterator.next();
-    		count+=ptg.getSize();
-    	}
-    	
-    	return count;
+        int count = 0;
+
+        Iterator ptgIterator = tokens.listIterator(index);
+        while (ptgIterator.hasNext()) {
+            Ptg ptg = (Ptg)ptgIterator.next();
+            count+=ptg.getSize();
+        }
+
+        return count;
     }
-    
+
     private int getPtgSize(int start, int end) {
         int count = 0;
         int index = start;
@@ -398,390 +356,430 @@
             count+=ptg.getSize();
             index++;
         }
-        
+
         return count;
     }
     /**
      * Generates the variable function ptg for the formula.
      * <p>
-     * For IF Formulas, additional PTGs are added to the tokens 
+     * For IF Formulas, additional PTGs are added to the tokens
      * @param name
      * @param numArgs
      * @return Ptg a null is returned if we're in an IF formula, it needs extreme manipulation and is handled in this function
      */
-    private AbstractFunctionPtg getFunction(String name, byte numArgs) {
-        AbstractFunctionPtg retval = null;
-        
-        if (name.equals("IF")) {
-            retval = new FuncVarPtg(AbstractFunctionPtg.ATTR_NAME, numArgs);
-            
-            //simulated pop, no bounds checking because this list better be populated by function()
-            List argumentPointers = (List)this.functionTokens.get(0);
-            
-            
-            AttrPtg ifPtg = new AttrPtg();
-            ifPtg.setData((short)7); //mirroring excel output
-            ifPtg.setOptimizedIf(true);
-            
-            if (argumentPointers.size() != 2  && argumentPointers.size() != 3) {
-                throw new IllegalArgumentException("["+argumentPointers.size()+"] Arguments Found - An IF formula requires 2 or 3 arguments. IF(CONDITION, TRUE_VALUE, FALSE_VALUE [OPTIONAL]");
-            }
-            
-            //Biffview of an IF formula record indicates the attr ptg goes after the condition ptgs and are
-            //tracked in the argument pointers
-            //The beginning first argument pointer is the last ptg of the condition
-            int ifIndex = tokens.indexOf(argumentPointers.get(0))+1;
-            tokens.add(ifIndex, ifPtg);
-            
-            //we now need a goto ptgAttr to skip to the end of the formula after a true condition
-            //the true condition is should be inserted after the last ptg in the first argument
-            
-            int gotoIndex = tokens.indexOf(argumentPointers.get(1))+1;
-            
-            AttrPtg goto1Ptg = new AttrPtg();
-            goto1Ptg.setGoto(true);
-            
-            
-            tokens.add(gotoIndex, goto1Ptg);
-            
-            
-            if (numArgs > 2) { //only add false jump if there is a false condition
-                
-                //second goto to skip past the function ptg
-                AttrPtg goto2Ptg = new AttrPtg();
-                goto2Ptg.setGoto(true);
-                goto2Ptg.setData((short)(retval.getSize()-1));
-                //Page 472 of the Microsoft Excel Developer's kit states that:
-                //The b(or w) field specifies the number byes (or words to skip, minus 1
-                
-                tokens.add(goto2Ptg); //this goes after all the arguments are defined
-            }
-            
-            //data portion of the if ptg points to the false subexpression (Page 472 of MS Excel Developer's kit)
-            //count the number of bytes after the ifPtg to the False Subexpression
-            //doesn't specify -1 in the documentation
-            ifPtg.setData((short)(getPtgSize(ifIndex+1, gotoIndex)));
-            
-            //count all the additional (goto) ptgs but dont count itself
-            int ptgCount = this.getPtgSize(gotoIndex)-goto1Ptg.getSize()+retval.getSize();
-            if (ptgCount > (int)Short.MAX_VALUE) {
-                throw new RuntimeException("Ptg Size exceeds short when being specified for a goto ptg in an if");
-            }
-            
-            goto1Ptg.setData((short)(ptgCount-1));
-            
-        } else {
-            
-            retval = new FuncVarPtg(name,numArgs);
+    private AbstractFunctionPtg getFunction(String name, int numArgs, List argumentPointers) {
+
+        AbstractFunctionPtg retval = new FuncVarPtg(name, (byte)numArgs);
+        if (!name.equals(AbstractFunctionPtg.FUNCTION_NAME_IF)) {
+            // early return for everything else besides IF()
+            return retval;
         }
-        
+
+
+        AttrPtg ifPtg = new AttrPtg();
+        ifPtg.setData((short)7); //mirroring excel output
+        ifPtg.setOptimizedIf(true);
+
+        if (argumentPointers.size() != 2  && argumentPointers.size() != 3) {
+            throw new IllegalArgumentException("["+argumentPointers.size()+"] Arguments Found - An IF formula requires 2 or 3 arguments. IF(CONDITION, TRUE_VALUE, FALSE_VALUE [OPTIONAL]");
+        }
+
+        //Biffview of an IF formula record indicates the attr ptg goes after the condition ptgs and are
+        //tracked in the argument pointers
+        //The beginning first argument pointer is the last ptg of the condition
+        int ifIndex = tokens.indexOf(argumentPointers.get(0))+1;
+        tokens.add(ifIndex, ifPtg);
+
+        //we now need a goto ptgAttr to skip to the end of the formula after a true condition
+        //the true condition is should be inserted after the last ptg in the first argument
+
+        int gotoIndex = tokens.indexOf(argumentPointers.get(1))+1;
+
+        AttrPtg goto1Ptg = new AttrPtg();
+        goto1Ptg.setGoto(true);
+
+
+        tokens.add(gotoIndex, goto1Ptg);
+
+
+        if (numArgs > 2) { //only add false jump if there is a false condition
+
+            //second goto to skip past the function ptg
+            AttrPtg goto2Ptg = new AttrPtg();
+            goto2Ptg.setGoto(true);
+            goto2Ptg.setData((short)(retval.getSize()-1));
+            //Page 472 of the Microsoft Excel Developer's kit states that:
+            //The b(or w) field specifies the number byes (or words to skip, minus 1
+
+            tokens.add(goto2Ptg); //this goes after all the arguments are defined
+        }
+
+        //data portion of the if ptg points to the false subexpression (Page 472 of MS Excel Developer's kit)
+        //count the number of bytes after the ifPtg to the False Subexpression
+        //doesn't specify -1 in the documentation
+        ifPtg.setData((short)(getPtgSize(ifIndex+1, gotoIndex)));
+
+        //count all the additional (goto) ptgs but dont count itself
+        int ptgCount = this.getPtgSize(gotoIndex)-goto1Ptg.getSize()+retval.getSize();
+        if (ptgCount > Short.MAX_VALUE) {
+            throw new RuntimeException("Ptg Size exceeds short when being specified for a goto ptg in an if");
+        }
+
+        goto1Ptg.setData((short)(ptgCount-1));
+
         return retval;
     }
+
+    private static boolean isArgumentDelimiter(char ch) {
+        return ch ==  ',' || ch == ')';
+    }
     
     /** get arguments to a function */
-    private int Arguments() {
+    private int Arguments(List argumentPointers) {
+        SkipWhite();
+        if(look == ')') {
+            return 0;
+        }
+        
+        boolean missedPrevArg = true;
+        
         int numArgs = 0;
-        if (look != ')')  {
-            numArgs++; 
-            Expression();
-			   addArgumentPointer();
-        }
-        while (look == ','  || look == ';') { //TODO handle EmptyArgs
-            if(look == ',') {
-              Match(',');
-            }
-            else {
-              Match(';');
+        while(true) {
+            SkipWhite();
+            if(isArgumentDelimiter(look)) {
+                if(missedPrevArg) {
+                    tokens.add(new MissingArgPtg());
+                    addArgumentPointer(argumentPointers);
+                    numArgs++;
+                }
+                if(look == ')') {
+                    break;
+                }
+                Match(',');
+                missedPrevArg = true;
+                continue;
             }
-            Expression();
-			   addArgumentPointer();
+            comparisonExpression();
+            addArgumentPointer(argumentPointers);
             numArgs++;
+            missedPrevArg = false;
         }
         return numArgs;
     }
 
    /** Parse and Translate a Math Factor  */
-    private void Factor() {
-    	if (look == '-')
-    	{
-    		Match('-');
-    		Factor();
-    		tokens.add(new UnaryMinusPtg());
-    	}
-        else if (look == '+') {
-            Match('+');
-            Factor();
-            tokens.add(new UnaryPlusPtg());
-        }
-        else if (look == '(' ) {
-            Match('(');
-            Expression();
-            Match(')');
-            tokens.add(new ParenthesisPtg());
-        } else if (IsAlpha(look) || look == '\''){
-            Ident();
-        } else if(look == '"') {
-           StringLiteral();
-        } else if (look == ')' || look == ',') {
-        	tokens.add(new MissingArgPtg());
-        } else {
-            String number2 = null;
-            String exponent = null;
-            String number1 = GetNum();
-            
-            if (look == '.') {
-                GetChar();
-                number2 = GetNum();
+    private void powerFactor() {
+        percentFactor();
+        while(true) {
+            SkipWhite();
+            if(look != '^') {
+                return;
             }
-            
-            if (look == 'E') {
+            Match('^');
+            percentFactor();
+            tokens.add(new PowerPtg());
+        }
+    }
+    
+    private void percentFactor() {
+        tokens.add(parseSimpleFactor());
+        while(true) {
+            SkipWhite();
+            if(look != '%') {
+                return;
+            }
+            Match('%');
+            tokens.add(new PercentPtg());
+        }
+    }
+    
+    
+    /**
+     * factors (without ^ or % )
+     */
+    private Ptg parseSimpleFactor() {
+        SkipWhite();
+        switch(look) {
+            case '#':
+                return parseErrorLiteral();
+            case '-':
+                Match('-');
+                powerFactor();
+                return new UnaryMinusPtg();
+            case '+':
+                Match('+');
+                powerFactor();
+                return new UnaryPlusPtg();
+            case '(':
+                Match('(');
+                comparisonExpression();
+                Match(')');
+                return new ParenthesisPtg();
+            case '"':
+                return parseStringLiteral();
+            case ',':
+            case ')':
+                return new MissingArgPtg(); // TODO - not quite the right place to recognise a missing arg
+        }
+        if (IsAlpha(look) || look == '\''){
+            return parseIdent();
+        }
+        // else - assume number
+        return parseNumber();
+    }
+
+
+    private Ptg parseNumber() {
+        String number2 = null;
+        String exponent = null;
+        String number1 = GetNum();
+
+        if (look == '.') {
+            GetChar();
+            number2 = GetNum();
+        }
+
+        if (look == 'E') {
+            GetChar();
+
+            String sign = "";
+            if (look == '+') {
                 GetChar();
-                
-                String sign = "";
-                if (look == '+') {
-                    GetChar();
-                } else if (look == '-') {
-                    GetChar();
-                    sign = "-";
-                }
-                
-                String number = GetNum();
-                if (number == null) {
-                    Expected("Integer");
-                }
-                exponent = sign + number;
+            } else if (look == '-') {
+                GetChar();
+                sign = "-";
             }
-            
-            if (number1 == null && number2 == null) {
-                Expected("Integer");
+
+            String number = GetNum();
+            if (number == null) {
+                throw expected("Integer");
             }
-            
-            tokens.add(getNumberPtgFromString(number1, number2, exponent));
+            exponent = sign + number;
         }
+
+        if (number1 == null && number2 == null) {
+            throw expected("Integer");
+        }
+
+        return getNumberPtgFromString(number1, number2, exponent);
     }
-    
-	/** 
-	 * Get a PTG for an integer from its string representation. 
-	 * return Int or Number Ptg based on size of input
-	 */
-	private Ptg getNumberPtgFromString(String number1, String number2, String exponent) {
+
+
+    private ErrPtg parseErrorLiteral() {
+        Match('#');
+        String part1 = GetName().toUpperCase();
+
+        switch(part1.charAt(0)) {
+            case 'V':
+                if(part1.equals("VALUE")) {
+                    Match('!');
+                    return ErrPtg.VALUE_INVALID;
+                }
+                throw expected("#VALUE!");
+            case 'R':
+                if(part1.equals("REF")) {
+                    Match('!');
+                    return ErrPtg.REF_INVALID;
+                }
+                throw expected("#REF!");
+            case 'D':
+                if(part1.equals("DIV")) {
+                    Match('/');
+                    Match('0');
+                    Match('!');
+                    return ErrPtg.DIV_ZERO;
+                }
+                throw expected("#DIV/0!");
+            case 'N':
+                if(part1.equals("NAME")) {
+                    Match('?');  // only one that ends in '?'
+                    return ErrPtg.NAME_INVALID;
+                }
+                if(part1.equals("NUM")) {
+                    Match('!');
+                    return ErrPtg.NUM_ERROR;
+                }
+                if(part1.equals("NULL")) {
+                    Match('!');
+                    return ErrPtg.NULL_INTERSECTION;
+                }
+                if(part1.equals("N")) {
+                    Match('/');
+                    if(look != 'A' && look != 'a') {
+                        throw expected("#N/A");
+                    }
+                    Match(look);
+                    // Note - no '!' or '?' suffix
+                    return ErrPtg.N_A;
+                }
+                throw expected("#NAME?, #NUM!, #NULL! or #N/A");
+
+        }
+        throw expected("#VALUE!, #REF!, #DIV/0!, #NAME?, #NUM!, #NULL! or #N/A");
+    }
+
+
+    /**
+     * Get a PTG for an integer from its string representation.
+     * return Int or Number Ptg based on size of input
+     */
+    private static Ptg getNumberPtgFromString(String number1, String number2, String exponent) {
         StringBuffer number = new StringBuffer();
-        
-	    if (number2 == null) {
-	        number.append(number1);
-    	    
-    	    if (exponent != null) {
-    	        number.append('E');
-    	        number.append(exponent);
-    	    }
-    	    
+
+        if (number2 == null) {
+            number.append(number1);
+
+            if (exponent != null) {
+                number.append('E');
+                number.append(exponent);
+            }
+
             String numberStr = number.toString();
-            
+            int intVal;
             try {
-                return new IntPtg(numberStr);
+                intVal = Integer.parseInt(numberStr);
             } catch (NumberFormatException e) {
                 return new NumberPtg(numberStr);
             }
-	    } else {
-            if (number1 != null) {
-                number.append(number1);
-            }
-            
-            number.append('.');
-            number.append(number2);
-            
-            if (exponent != null) {
-                number.append('E');
-                number.append(exponent);
+            if (IntPtg.isInRange(intVal)) {
+                return new IntPtg(intVal);
             }
-            
-            return new NumberPtg(number.toString());
-	    }
-	}
-	
-	
-	private void StringLiteral() 
-	{
-		// Can't use match here 'cuz it consumes whitespace
-		// which we need to preserve inside the string.
-		// - pete
-		// Match('"');
-		if (look != '"')
-			Expected("\"");
-		else
-		{
-			GetChar();
-			StringBuffer Token = new StringBuffer();
-			for (;;)
-			{
-				if (look == '"')
-				{
-					GetChar();
-					SkipWhite(); //potential white space here since it doesnt matter up to the operator
-					if (look == '"')
-						Token.append("\"");
-					else
-						break;
-				}
-				else if (look == 0)
-				{
-					break;
-				}
-				else
-				{
-					Token.append(look);
-					GetChar();
-				}
-			}
-			tokens.add(new StringPtg(Token.toString()));
-		}
-	}
-    
-    /** Recognize and Translate a Multiply */
-    private void Multiply(){
-        Match('*');
-        Factor();
-        tokens.add(new MultiplyPtg());
-  
-    }
-    
-    
-    /** Recognize and Translate a Divide */
-    private void Divide() {
-        Match('/');
-        Factor();
-        tokens.add(new DividePtg());
+            return new NumberPtg(numberStr);
+        }
+
+        if (number1 != null) {
+            number.append(number1);
+        }
+
+        number.append('.');
+        number.append(number2);
+
+        if (exponent != null) {
+            number.append('E');
+            number.append(exponent);
+        }
 
+        return new NumberPtg(number.toString());
     }
-    
-    
-    /** Parse and Translate a Math Term */
-    private void  Term(){
-        Factor();
-		 while (look == '*' || look == '/' || look == '^' || look == '&') {
+
+
+    private StringPtg parseStringLiteral()
+    {
+        Match('"');
         
-            ///TODO do we need to do anything here??
-            if (look == '*') Multiply();
-            else if (look == '/') Divide();
-            else if (look == '^') Power();
-            else if (look == '&') Concat();
+        StringBuffer token = new StringBuffer();
+        while (true) {
+            if (look == '"') {
+                GetChar();
+                if (look != '"') {
+                    break;
+                }
+             }
+            token.append(look);
+            GetChar();
         }
+        return new StringPtg(token.toString());
     }
-    
-    /** Recognize and Translate an Add */
-    private void Add() {
-        Match('+');
-        Term();
-        tokens.add(new AddPtg());
+
+    /** Parse and Translate a Math Term */
+    private void  Term() {
+        powerFactor();
+        while(true) {
+            SkipWhite();
+            switch(look) {
+                case '*':
+                    Match('*');
+                    powerFactor();
+                    tokens.add(new MultiplyPtg());
+                    continue;
+                case '/':
+                    Match('/');
+                    powerFactor();
+                    tokens.add(new DividePtg());
+                    continue;
+            }
+            return; // finished with Term
+        }
     }
     
-    /** Recognize and Translate a Concatination */
-    private void Concat() {
-        Match('&');
-        Term();
-        tokens.add(new ConcatPtg());
+    private void comparisonExpression() {
+        concatExpression();
+        while (true) {
+            SkipWhite();
+            switch(look) {
+                case '=':
+                case '>':
+                case '<':
+                    Ptg comparisonToken = getComparisonToken();
+                    concatExpression();
+                    tokens.add(comparisonToken);
+                    continue;
+            }
+            return; // finished with predicate expression
+        }
     }
-    
-    /** Recognize and Translate a test for Equality  */
-    private void Equal() {
-        Match('=');
-        Expression();
-        tokens.add(new EqualPtg());
+
+    private Ptg getComparisonToken() {
+        if(look == '=') {
+            Match(look);
+            return new EqualPtg();
+        }
+        boolean isGreater = look == '>';
+        Match(look);
+        if(isGreater) {
+            if(look == '=') {
+                Match('=');
+                return new GreaterEqualPtg();
+            }
+            return new GreaterThanPtg();
+        }
+        switch(look) {
+            case '=':
+                Match('=');
+                return new LessEqualPtg();
+            case '>':
+                Match('>');
+                return new NotEqualPtg();
+        }
+        return new LessThanPtg();
     }
     
-    /** Recognize and Translate a Subtract */
-    private void Subtract() {
-        Match('-');
-        Term();
-        tokens.add(new SubtractPtg());
-    }    
 
-    private void Power() {
-        Match('^');
-        Term();
-        tokens.add(new PowerPtg());
+    private void concatExpression() {
+        additiveExpression();
+        while (true) {
+            SkipWhite();
+            if(look != '&') {
+                break; // finished with concat expression
+            }
+            Match('&');
+            additiveExpression();
+            tokens.add(new ConcatPtg());
+        }
     }
     
-    
+
     /** Parse and Translate an Expression */
-    private void Expression() {
+    private void additiveExpression() {
         Term();
-        while (IsAddop(look)) {
-            if (look == '+' )  Add();
-            else if (look == '-') Subtract();
+        while (true) {
+            SkipWhite();
+            switch(look) {
+                case '+':
+                    Match('+');
+                    Term();
+                    tokens.add(new AddPtg());
+                    continue;
+                case '-':
+                    Match('-');
+                    Term();
+                    tokens.add(new SubtractPtg());
+                    continue;
+            }
+            return; // finished with additive expression
         }
-        
-		/*
-		 * This isn't quite right since it would allow multiple comparison operators.
-		 */
-		
-		  if(look == '=' || look == '>' || look == '<') {
-		  		if (look == '=') Equal();
-		      else if (look == '>') GreaterThan();
-		      else if (look == '<') LessThan();
-		      return;
-		  }        
-        
-        
     }
-    
-    /** Recognize and Translate a Greater Than  */
-    private void GreaterThan() {
-		Match('>');
-		if(look == '=')
-		    GreaterEqual();
-		else {
-		    Expression();
-		    tokens.add(new GreaterThanPtg());
-		}
-    }
-    
-    /** Recognize and Translate a Less Than  */
-    private void LessThan() {
-		Match('<');
-		if(look == '=')
-		    LessEqual();
-		else if(look == '>')
-		    NotEqual();
-		else {
-		    Expression();
-		    tokens.add(new LessThanPtg());
-		}
-
-	}  
-   
-   /**
-    * Recognize and translate Greater than or Equal
-    *
-    */ 
-	private void GreaterEqual() {
-	    Match('=');
-	    Expression();
-	    tokens.add(new GreaterEqualPtg());
-	}    
-
-	/**
-	 * Recognize and translate Less than or Equal
-	 *
-	 */ 
-
-	private void LessEqual() {
-	    Match('=');
-	    Expression();
-	    tokens.add(new LessEqualPtg());
-	}
-	
-	/**
-	 * Recognize and not Equal
-	 *
-	 */ 
-
-	private void NotEqual() {
-	    Match('>');
-	    Expression();
-	    tokens.add(new NotEqualPtg());
-	}    
-    
+
     //{--------------------------------------------------------------}
     //{ Parse and Translate an Assignment Statement }
     /**
@@ -794,48 +792,46 @@
 
 end;
      **/
-    
- 
-    /** Initialize */
-    
-    private void  init() {
-        GetChar();
-        SkipWhite();
-    }
-    
+
+
     /** API call to execute the parsing of the formula
      *
      */
     public void parse() {
-        synchronized (tokens) {
-            init();
-            Expression();
+        pointer=0;
+        GetChar();
+        comparisonExpression();
+
+        if(pointer <= formulaLength) {
+            String msg = "Unused input [" + formulaString.substring(pointer-1) 
+                + "] after attempting to parse the formula [" + formulaString + "]"; 
+            throw new FormulaParseException(msg);
         }
     }
-    
-    
+
+
     /*********************************
      * PARSER IMPLEMENTATION ENDS HERE
      * EXCEL SPECIFIC METHODS BELOW
      *******************************/
-    
-    /** API call to retrive the array of Ptgs created as 
+
+    /** API call to retrive the array of Ptgs created as
      * a result of the parsing
      */
     public Ptg[] getRPNPtg() {
      return getRPNPtg(FORMULA_TYPE_CELL);
     }
-    
+
     public Ptg[] getRPNPtg(int formulaType) {
         Node node = createTree();
         setRootLevelRVA(node, formulaType);
         setParameterRVA(node,formulaType);
         return (Ptg[]) tokens.toArray(new Ptg[0]);
     }
-    
+
     private void setRootLevelRVA(Node n, int formulaType) {
         //Pg 16, excelfileformat.pdf @ openoffice.org
-        Ptg p = (Ptg) n.getValue();
+        Ptg p = n.getValue();
             if (formulaType == FormulaParser.FORMULA_TYPE_NAMEDRANGE) {
                 if (p.getDefaultOperandClass() == Ptg.CLASS_REF) {
                     setClass(n,Ptg.CLASS_REF);
@@ -845,9 +841,9 @@
             } else {
                 setClass(n,Ptg.CLASS_VALUE);
             }
-        
+
     }
-    
+
     private void setParameterRVA(Node n, int formulaType) {
         Ptg p = n.getValue();
         int numOperands = n.getNumChildren();
@@ -863,11 +859,11 @@
             for (int i =0;i<numOperands;i++) {
                 setParameterRVA(n.getChild(i),formulaType);
             }
-        } 
+        }
     }
     private void setParameterRVA(Node n, int expectedClass,int formulaType) {
-        Ptg p = (Ptg) n.getValue();
-        if (expectedClass == Ptg.CLASS_REF) { //pg 15, table 1 
+        Ptg p = n.getValue();
+        if (expectedClass == Ptg.CLASS_REF) { //pg 15, table 1
             if (p.getDefaultOperandClass() == Ptg.CLASS_REF ) {
                 setClass(n, Ptg.CLASS_REF);
             }
@@ -887,7 +883,7 @@
             } else {
                 setClass(n,Ptg.CLASS_VALUE);
             }
-        } else { //Array class, pg 16. 
+        } else { //Array class, pg 16.
             if (p.getDefaultOperandClass() == Ptg.CLASS_VALUE &&
                  (formulaType==FORMULA_TYPE_CELL || formulaType == FORMULA_TYPE_SHARED)) {
                  setClass(n,Ptg.CLASS_VALUE);
@@ -896,9 +892,9 @@
             }
         }
     }
-    
+
      private void setClass(Node n, byte theClass) {
-        Ptg p = (Ptg) n.getValue();
+        Ptg p = n.getValue();
         if (p instanceof AbstractFunctionPtg || !(p instanceof OperationPtg)) {
             p.setClass(theClass);
         } else {
@@ -909,7 +905,7 @@
      }
     /**
      * Convience method which takes in a list then passes it to the
-     *  other toFormulaString signature. 
+     *  other toFormulaString signature.
      * @param book   workbook for 3D and named references
      * @param lptgs  list of Ptg, can be null or empty
      * @return a human readable String
@@ -930,9 +926,9 @@
      * @return a human readable String
      */
     public String toFormulaString(List lptgs) {
-    	return toFormulaString(book, lptgs);
+        return toFormulaString(book, lptgs);
     }
-    
+
     /**
      * Static method to convert an array of Ptgs in RPN order
      * to a human readable string format in infix mode.
@@ -941,61 +937,74 @@
      * @return a human readable String
      */
     public static String toFormulaString(Workbook book, Ptg[] ptgs) {
-        if (ptgs == null || ptgs.length == 0) return "#NAME";
-        java.util.Stack stack = new java.util.Stack();
-        AttrPtg ifptg = null;
+        if (ptgs == null || ptgs.length == 0) {
+            // TODO - what is the justification for returning "#NAME" (which is not "#NAME?", btw)
+            return "#NAME";
+        }
+        Stack stack = new Stack();
 
            // Excel allows to have AttrPtg at position 0 (such as Blanks) which
            // do not have any operands. Skip them.
-        stack.push(ptgs[0].toFormulaString(book));
-                  
-        for (int i = 1; i < ptgs.length; i++) {
-            if (! (ptgs[i] instanceof OperationPtg)) {
-                stack.push(ptgs[i].toFormulaString(book));
+        int i;
+        if(ptgs[0] instanceof AttrPtg) {
+            AttrPtg attrPtg0 = (AttrPtg) ptgs[0];
+            if(attrPtg0.isSemiVolatile()) {
+                // no visible formula for semi-volatile
+            } else {
+                // TODO -this requirement is unclear and is not addressed by any junits
+                stack.push(ptgs[0].toFormulaString(book));
+            }
+            i=1;
+        } else {
+            i=0;
+        }
+
+        for ( ; i < ptgs.length; i++) {
+            Ptg ptg = ptgs[i];
+            // TODO - what about MemNoMemPtg?
+            if(ptg instanceof MemAreaPtg || ptg instanceof MemFuncPtg || ptg instanceof MemErrPtg) {
+                // marks the start of a list of area expressions which will be naturally combined
+                // by their trailing operators (e.g. UnionPtg)
+                // TODO - put comment and throw exception in toFormulaString() of these classes
                 continue;
             }
-                      
-            if (ptgs[i] instanceof AttrPtg && ((AttrPtg) ptgs[i]).isOptimizedIf()) {
-                ifptg = (AttrPtg) ptgs[i];
+            if (! (ptg instanceof OperationPtg)) {
+                stack.push(ptg.toFormulaString(book));
                 continue;
             }
-                      
-            final OperationPtg o = (OperationPtg) ptgs[i];
-            final String[] operands = new String[o.getNumberOfOperands()];
-
-            for (int j = operands.length; j > 0; j--) {
-                //TODO: catch stack underflow and throw parse exception.
-                operands[j - 1] = (String) stack.pop();
-                      }  
-
-            stack.push(o.toFormulaString(operands));
-            if (!(o instanceof AbstractFunctionPtg)) continue;
 
-            final AbstractFunctionPtg f = (AbstractFunctionPtg) o;
-            final String fname = f.getName();
-            if (fname == null) continue;
-
-            if ((ifptg != null) && (fname.equals("specialflag"))) {
-                             // this special case will be way different.
-                stack.push(ifptg.toFormulaString(new String[]{(String) stack.pop()}));
+            if (ptg instanceof AttrPtg && ((AttrPtg) ptg).isOptimizedIf()) {
                 continue;
-                      }
-            if (fname.equals("externalflag")) {
-                final String top = (String) stack.pop();
-                final int paren = top.indexOf('(');
-                final int comma = top.indexOf(',');
-                if (comma == -1) {
-                    final int rparen = top.indexOf(')');
-                    stack.push(top.substring(paren + 1, rparen) + "()");
-                  }
-                else {
-                    stack.push(top.substring(paren + 1, comma) + '(' +
-                               top.substring(comma + 1));
             }
+
+            final OperationPtg o = (OperationPtg) ptg;
+            int nOperands = o.getNumberOfOperands();
+            final String[] operands = new String[nOperands];
+
+            for (int j = nOperands-1; j >= 0; j--) {
+                if(stack.isEmpty()) {
+                    //TODO: write junit to prove this works
+                   String msg = "Too few arguments suppled to operation token ("
+                        + o.getClass().getName() + "). Expected (" + nOperands
+                        + " but got " + (nOperands - j + 1);
+                    throw new FormulaParseException(msg);
+                }
+                operands[j] = (String) stack.pop();
+            }
+            stack.push(o.toFormulaString(operands));
         }
-    }
-        // TODO: catch stack underflow and throw parse exception.
-        return (String) stack.pop();
+        if(stack.isEmpty()) {
+            // inspection of the code above reveals that every stack.pop() is followed by a 
+            // stack.push(). So this is either an internal error or impossible.
+            throw new IllegalStateException("Stack underflow");
+        }
+        String result = (String) stack.pop();
+        if(!stack.isEmpty()) {
+            // Might be caused by some tokens like AttrPtg and Mem*Ptg, which really shouldn't
+            // put anything on the stack
+            throw new IllegalStateException("too much stuff left on the stack");
+        }
+        return result;
     }
     /**
      * Static method to convert an array of Ptgs in RPN order
@@ -1005,7 +1014,7 @@
      * @return a human readable String
      */
     public String toFormulaString(Ptg[] ptgs) {
-    	return toFormulaString(book, ptgs);
+        return toFormulaString(book, ptgs);
     }
 
 
@@ -1013,19 +1022,19 @@
      *used to run the class(RVA) change algo
      */
     private Node createTree() {
-        java.util.Stack stack = new java.util.Stack();
+        Stack stack = new Stack();
         int numPtgs = tokens.size();
         OperationPtg o;
         int numOperands;
         Node[] operands;
         for (int i=0;i<numPtgs;i++) {
             if (tokens.get(i) instanceof OperationPtg) {
-                
+
                 o = (OperationPtg) tokens.get(i);
                 numOperands = o.getNumberOfOperands();
                 operands = new Node[numOperands];
                 for (int j=0;j<numOperands;j++) {
-                    operands[numOperands-j-1] = (Node) stack.pop(); 
+                    operands[numOperands-j-1] = (Node) stack.pop();
                 }
                 Node result = new Node(o);
                 result.setChildren(operands);
@@ -1036,7 +1045,7 @@
         }
         return (Node) stack.pop();
     }
-   
+
     /** toString on the parser instance returns the RPN ordered list of tokens
      *   Useful for testing
      */
@@ -1045,21 +1054,21 @@
            for (int i=0;i<tokens.size();i++) {
             buf.append( ( (Ptg)tokens.get(i)).toFormulaString(book));
             buf.append(' ');
-        } 
+        }
         return buf.toString();
     }
-    
-}   
+
     /** Private helper class, used to create a tree representation of the formula*/
-    class Node {
+    private static final class Node {
         private Ptg value=null;
         private Node[] children=new Node[0];
         private int numChild=0;
         public Node(Ptg val) {
-            value = val; 
+            value = val;
         }
         public void setChildren(Node[] child) {children = child;numChild=child.length;}
         public int getNumChildren() {return numChild;}
         public Node getChild(int number) {return children[number];}
         public Ptg getValue() {return value;}
     }
+}

Modified: poi/branches/ooxml/src/java/org/apache/poi/hssf/model/Workbook.java
URL: http://svn.apache.org/viewvc/poi/branches/ooxml/src/java/org/apache/poi/hssf/model/Workbook.java?rev=634936&r1=634935&r2=634936&view=diff
==============================================================================
--- poi/branches/ooxml/src/java/org/apache/poi/hssf/model/Workbook.java (original)
+++ poi/branches/ooxml/src/java/org/apache/poi/hssf/model/Workbook.java Sat Mar  8 03:49:00 2008
@@ -79,10 +79,8 @@
      */
     protected SSTRecord        sst         = null;
 
-    /**
-     * Holds the Extern Sheet with references to bound sheets
-     */
-    protected ExternSheetRecord externSheet= null;
+
+    private LinkTable linkTable; // optionally occurs if there are  references in the document. (4.10.3)
 
     /**
      * holds the "boundsheet" records (aka bundlesheet) so that they can have their
@@ -92,8 +90,6 @@
 
     protected ArrayList        formats = new ArrayList();
 
-    protected ArrayList        names = new ArrayList();
-    
     protected ArrayList        hyperlinks = new ArrayList();
 
     protected int              numxfs      = 0;   // hold the number of extended format records
@@ -134,6 +130,7 @@
                     new Integer(recs.size()));
         Workbook  retval  = new Workbook();
         ArrayList records = new ArrayList(recs.size() / 3);
+        retval.records.setRecords(records);
 
         int k;
         for (k = 0; k < recs.size(); k++) {
@@ -192,21 +189,16 @@
                     retval.records.setBackuppos( k );
                     break;
                 case ExternSheetRecord.sid :
-                    if (log.check( POILogger.DEBUG ))
-                        log.log(DEBUG, "found extern sheet record at " + k);
-                    retval.externSheet = ( ExternSheetRecord ) rec;
-                    break;
+                    throw new RuntimeException("Extern sheet is part of LinkTable");
                 case NameRecord.sid :
-                    if (log.check( POILogger.DEBUG ))
-                        log.log(DEBUG, "found name record at " + k);
-                    retval.names.add(rec);
-                    //                    retval.records.namepos = k;
-                    break;
+                    throw new RuntimeException("DEFINEDNAME is part of LinkTable");
                 case SupBookRecord.sid :
                     if (log.check( POILogger.DEBUG ))
                         log.log(DEBUG, "found SupBook record at " + k);
+                    retval.linkTable = new LinkTable(recs, k, retval.records);
                     //                    retval.records.supbookpos = k;
-                    break;
+                    k+=retval.linkTable.getRecordCount() - 1;
+                    continue;
                 case FormatRecord.sid :
                     if (log.check( POILogger.DEBUG ))
                         log.log(DEBUG, "found format record at " + k);
@@ -262,8 +254,6 @@
             		break;
             }
         }
-
-        retval.records.setRecords(records);
         
         if (retval.windowOne == null) {
             retval.windowOne = (WindowOneRecord) retval.createWindowOne();
@@ -283,6 +273,7 @@
             log.log( DEBUG, "creating new workbook from scratch" );
         Workbook retval = new Workbook();
         ArrayList records = new ArrayList( 30 );
+        retval.records.setRecords(records);
         ArrayList formats = new ArrayList( 8 );
 
         records.add( retval.createBOF() );
@@ -339,8 +330,9 @@
             records.add( retval.createStyle( k ) );
         }
         records.add( retval.createUseSelFS() );
-        for ( int k = 0; k < 1; k++ )
-        {   // now just do 1
+
+        int nBoundSheets = 1; // now just do 1
+        for ( int k = 0; k < nBoundSheets; k++ ) {   
             BoundSheetRecord bsr =
                     (BoundSheetRecord) retval.createBoundSheet( k );
 
@@ -351,12 +343,14 @@
 //        retval.records.supbookpos = retval.records.bspos + 1;
 //        retval.records.namepos = retval.records.supbookpos + 2;
         records.add( retval.createCountry() );
+        for ( int k = 0; k < nBoundSheets; k++ ) {   
+            retval.getOrCreateLinkTable().checkExternSheet(k);
+        }
         retval.sst = (SSTRecord) retval.createSST();
         records.add( retval.sst );
         records.add( retval.createExtendedSST() );
 
         records.add( retval.createEOF() );
-        retval.records.setRecords(records);
         if (log.check( POILogger.DEBUG ))
             log.log( DEBUG, "exit create new workbook from scratch" );
         return retval;
@@ -369,36 +363,20 @@
 	 * @param sheetIndex Index to match
 	 * @return null if no builtin NameRecord matches
 	 */
-	public NameRecord getSpecificBuiltinRecord(byte name, int sheetIndex)
-	{
-	    Iterator iterator = names.iterator();
-	    while (iterator.hasNext()) {
-	        NameRecord record = ( NameRecord ) iterator.next();
-	
-	        //print areas are one based
-	        if (record.getBuiltInName() == name && record.getIndexToSheet() == sheetIndex) {
-	            return record;
-	        }
-	    }
-	    
-	    return null;
-	    
-	}
+    public NameRecord getSpecificBuiltinRecord(byte name, int sheetIndex)
+    {
+        return getOrCreateLinkTable().getSpecificBuiltinRecord(name, sheetIndex);
+    }
 
 	/**
 	 * Removes the specified Builtin NameRecord that matches the name and index
 	 * @param name byte representation of the builtin to match
 	 * @param sheetIndex zero-based sheet reference
 	 */
-	public void removeBuiltinRecord(byte name, int sheetIndex) {
-		//the name array is smaller so searching through it should be faster than
-		//using the findFirstXXXX methods
-		NameRecord record = getSpecificBuiltinRecord(name, sheetIndex);
-		if (record != null) {
-			names.remove(record);
-		}
-		
-	}
+    public void removeBuiltinRecord(byte name, int sheetIndex) {
+        linkTable.removeBuiltinRecord(name, sheetIndex);
+        // TODO - do we need "this.records.remove(...);" similar to that in this.removeName(int namenum) {}?
+    }
 
     public int getNumRecords() {
         return records.size();
@@ -614,6 +592,7 @@
             records.add(records.getBspos()+1, bsr);
             records.setBspos( records.getBspos() + 1 );
             boundsheets.add(bsr);
+            getOrCreateLinkTable().checkExternSheet(sheetnum);
             fixTabIdRecord();
         }
     }
@@ -1824,14 +1803,26 @@
     protected Record createEOF() {
         return new EOFRecord();
     }
+    
+    /**
+     * lazy initialization
+     * Note - creating the link table causes creation of 1 EXTERNALBOOK and 1 EXTERNALSHEET record
+     */
+    private LinkTable getOrCreateLinkTable() {
+        if(linkTable == null) {
+            linkTable = new LinkTable((short) getNumSheets(), records);
+        }
+        return linkTable;
+    }
 
     public SheetReferences getSheetReferences() {
         SheetReferences refs = new SheetReferences();
         
-        if (externSheet != null) {
-            for (int k = 0; k < externSheet.getNumOfREFStructures(); k++) {
+        if (linkTable != null) {
+            int numRefStructures = linkTable.getNumberOfREFStructures();
+            for (short k = 0; k < numRefStructures; k++) {
                 
-                String sheetName = findSheetNameFromExternSheet((short)k);
+                String sheetName = findSheetNameFromExternSheet(k);
                 refs.addSheetReference(sheetName, k);
                 
             }
@@ -1846,7 +1837,8 @@
     public String findSheetNameFromExternSheet(short num){
         String result="";
 
-        short indexToSheet = externSheet.getREFRecordAt(num).getIndexToFirstSupBook();
+        short indexToSheet = linkTable.getIndexToSheet(num);
+        
         if (indexToSheet>-1) { //error check, bail out gracefully!
             result = getSheetName(indexToSheet);
         }
@@ -1861,10 +1853,7 @@
      */
     public int getSheetIndexFromExternSheetIndex(int externSheetNumber)
     {
-        if (externSheetNumber >= externSheet.getNumOfREFStructures())
-            return -1;
-        else
-            return externSheet.getREFRecordAt(externSheetNumber).getIndexToFirstSupBook();
+        return linkTable.getSheetIndexFromExternSheetIndex(externSheetNumber);
     }
 
     /** returns the extern sheet number for specific sheet number ,
@@ -1873,58 +1862,17 @@
      * @return index to extern sheet
      */
     public short checkExternSheet(int sheetNumber){
-
-        int i = 0;
-        boolean flag = false;
-        short result = 0;
-
-        if (externSheet == null) {
-            externSheet = createExternSheet();
-        }
-
-        //Trying to find reference to this sheet
-        while (i < externSheet.getNumOfREFStructures() && !flag){
-            ExternSheetSubRecord record = externSheet.getREFRecordAt(i);
-
-            if (record.getIndexToFirstSupBook() ==  sheetNumber &&
-            record.getIndexToLastSupBook() == sheetNumber){
-                flag = true;
-                result = (short) i;
-            }
-
-            ++i;
-        }
-
-        //We Havent found reference to this sheet
-        if (!flag) {
-            result = addSheetIndexToExternSheet((short) sheetNumber);
-        }
-
-        return result;
-    }
-
-    private short addSheetIndexToExternSheet(short sheetNumber){
-        short result;
-
-        ExternSheetSubRecord record = new ExternSheetSubRecord();
-        record.setIndexToFirstSupBook(sheetNumber);
-        record.setIndexToLastSupBook(sheetNumber);
-        externSheet.addREFRecord(record);
-        externSheet.setNumOfREFStructures((short)(externSheet.getNumOfREFStructures() + 1));
-        result = (short)(externSheet.getNumOfREFStructures() - 1);
-
-        return result;
+        return getOrCreateLinkTable().checkExternSheet(sheetNumber);
     }
 
-
-
     /** gets the total number of names
      * @return number of names
      */
     public int getNumNames(){
-        int result = names.size();
-
-        return result;
+        if(linkTable == null) {
+            return 0;
+        }
+        return linkTable.getNumNames();
     }
 
     /** gets the name record
@@ -1932,28 +1880,14 @@
      * @return name record
      */
     public NameRecord getNameRecord(int index){
-        NameRecord result = (NameRecord) names.get(index);
-
-        return result;
-
+        return linkTable.getNameRecord(index);
     }
 
     /** creates new name
      * @return new name record
      */
     public NameRecord createName(){
-
-        NameRecord name = new NameRecord();
-
-        // Not the most efficient way but the other way was causing too many bugs
-        int idx = findFirstRecordLocBySid(ExternSheetRecord.sid);
-        if (idx == -1) idx = findFirstRecordLocBySid(SupBookRecord.sid);
-        if (idx == -1) idx = findFirstRecordLocBySid(CountryRecord.sid);
-
-        records.add(idx+names.size()+1, name);
-        names.add(name);
-
-        return name;
+        return addName(new NameRecord());
     }
 
 
@@ -1962,67 +1896,41 @@
      */
     public NameRecord addName(NameRecord name)
     {
-        // Not the most efficient way but the other way was causing too many bugs
-        int idx = findFirstRecordLocBySid(ExternSheetRecord.sid);
-        if (idx == -1) idx = findFirstRecordLocBySid(SupBookRecord.sid);
-        if (idx == -1) idx = findFirstRecordLocBySid(CountryRecord.sid);
-        records.add(idx+names.size()+1, name);
-        names.add(name);
+        
+        getOrCreateLinkTable().addName(name);
 
         return name;
     }
 
-	/**Generates a NameRecord to represent a built-in region
-	 * @return a new NameRecord unless the index is invalid
-	 */
-	public NameRecord createBuiltInName(byte builtInName, int index)
-	{
-		if (index == -1 || index+1 > (int)Short.MAX_VALUE) 
-			throw new IllegalArgumentException("Index is not valid ["+index+"]");
-	    
-		NameRecord name = new NameRecord(builtInName, (short)(index));
-	            
-		addName(name);
-	    
-		return name;
-	}
+    /**Generates a NameRecord to represent a built-in region
+     * @return a new NameRecord unless the index is invalid
+     */
+    public NameRecord createBuiltInName(byte builtInName, int index)
+    {
+        if (index == -1 || index+1 > Short.MAX_VALUE) 
+            throw new IllegalArgumentException("Index is not valid ["+index+"]");
+        
+        NameRecord name = new NameRecord(builtInName, (short)(index));
+                
+        addName(name);
+        
+        return name;
+    }
 
 
     /** removes the name
      * @param namenum name index
      */
     public void removeName(int namenum){
-        if (names.size() > namenum) {
+        
+        if (linkTable.getNumNames() > namenum) {
             int idx = findFirstRecordLocBySid(NameRecord.sid);
             records.remove(idx + namenum);
-            names.remove(namenum);
+            linkTable.removeName(namenum);
         }
 
     }
 
-    /** creates a new extern sheet record
-     * @return the new extern sheet record
-     */
-    protected ExternSheetRecord createExternSheet(){
-        ExternSheetRecord externSheet = new ExternSheetRecord();
-
-        int idx = findFirstRecordLocBySid(CountryRecord.sid);
-
-        records.add(idx+1, externSheet);
-//        records.add(records.supbookpos + 1 , rec);
-
-        //We also adds the supBook for internal reference
-        SupBookRecord supbook = new SupBookRecord();
-
-        supbook.setNumberOfSheets((short)getNumSheets());
-        //supbook.setFlag();
-
-        records.add(idx+1, supbook);
-//        records.add(records.supbookpos + 1 , supbook);
-
-        return externSheet;
-    }
-
     /**
      * Returns a format index that matches the passed in format.  It does not tie into HSSFDataFormat.
      * @param format the format string
@@ -2392,6 +2300,17 @@
         }
         return this.fileShare;
     }
+    
+    /**
+     * is the workbook protected with a password (not encrypted)?
+     */
+    public boolean isWriteProtected() {
+        if (this.fileShare == null) {
+        	return false;
+        }
+        FileSharingRecord frec = getFileSharing();
+        return (frec.getReadOnly() == 1);
+    }
 
     /**
      * protect a workbook with a password (not encypted, just sets writeprotect
@@ -2419,5 +2338,14 @@
         writeProtect = null;
     }
 
+    /**
+     * @param refIndex Index to REF entry in EXTERNSHEET record in the Link Table
+     * @param definedNameIndex zero-based to DEFINEDNAME or EXTERNALNAME record
+     * @return the string representation of the defined or external name
+     */
+    public String resolveNameXText(int refIndex, int definedNameIndex) {
+        return linkTable.resolveNameXText(refIndex, definedNameIndex);
+    }
 }
+
 

Modified: poi/branches/ooxml/src/java/org/apache/poi/hssf/record/DVALRecord.java
URL: http://svn.apache.org/viewvc/poi/branches/ooxml/src/java/org/apache/poi/hssf/record/DVALRecord.java?rev=634936&r1=634935&r2=634936&view=diff
==============================================================================
--- poi/branches/ooxml/src/java/org/apache/poi/hssf/record/DVALRecord.java (original)
+++ poi/branches/ooxml/src/java/org/apache/poi/hssf/record/DVALRecord.java Sat Mar  8 03:49:00 2008
@@ -1,4 +1,3 @@
-
 /* ====================================================================
    Copyright 2002-2004   Apache Software Foundation
 
@@ -20,13 +19,11 @@
 import org.apache.poi.util.LittleEndian;
 
 /**
- * Title:        DVAL Record<P>
+ * Title:        DATAVALIDATIONS Record<P>
  * Description:  used in data validation ;
- *               This record is the list header of all data validation records in the current sheet.
+ *               This record is the list header of all data validation records (0x01BE) in the current sheet.
  * @author Dragos Buleandra (dragos.buleandra@trade2b.ro)
- * @version 2.0-pre
  */
-
 public class DVALRecord extends Record
 {
 	public final static short sid = 0x01B2;
@@ -41,13 +38,14 @@
 	/** Object ID of the drop down arrow object for list boxes ;
 	 * in our case this will be always FFFF , until
 	 * MSODrawingGroup and MSODrawing records are implemented */
-	private int  field_cbo_id      = 0xFFFFFFFF;
+	private int  field_cbo_id;
 
 	/** Number of following DV Records */
-	private int  field_5_dv_no     = 0x00000000;
+	private int  field_5_dv_no;
 
-    public DVALRecord()
-    {
+    public DVALRecord() {
+        field_cbo_id = 0xFFFFFFFF;
+        field_5_dv_no = 0x00000000;
     }
 
     /**

Modified: poi/branches/ooxml/src/java/org/apache/poi/hssf/record/FileSharingRecord.java
URL: http://svn.apache.org/viewvc/poi/branches/ooxml/src/java/org/apache/poi/hssf/record/FileSharingRecord.java?rev=634936&r1=634935&r2=634936&view=diff
==============================================================================
--- poi/branches/ooxml/src/java/org/apache/poi/hssf/record/FileSharingRecord.java (original)
+++ poi/branches/ooxml/src/java/org/apache/poi/hssf/record/FileSharingRecord.java Sat Mar  8 03:49:00 2008
@@ -19,6 +19,8 @@
 package org.apache.poi.hssf.record;
 
 import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.POILogFactory;
+import org.apache.poi.util.POILogger;
 import org.apache.poi.util.StringUtil;
 
 /**
@@ -29,6 +31,8 @@
  */
 
 public class FileSharingRecord extends Record {
+    private static POILogger logger = POILogFactory.getLogger(FileSharingRecord.class);
+    	
     public final static short sid = 0x5b;
     private short             field_1_readonly;
     private short             field_2_password;
@@ -58,8 +62,23 @@
         field_1_readonly = in.readShort();
         field_2_password = in.readShort();
         field_3_username_length = in.readByte();
+        
+        // Is this really correct? The latest docs
+        //  seem to hint there's nothing between the
+        //  username length and the username string
         field_4_unknown = in.readShort();
-        field_5_username = in.readCompressedUnicode(field_3_username_length);
+        
+        // Ensure we don't try to read more data than
+        //  there actually is
+        if(field_3_username_length > in.remaining()) {
+        	logger.log(POILogger.WARN, "FileSharingRecord defined a username of length " + field_3_username_length + ", but only " + in.remaining() + " bytes were left, truncating");
+        	field_3_username_length = (byte)in.remaining();
+        }
+        if(field_3_username_length > 0) {
+        	field_5_username = in.readCompressedUnicode(field_3_username_length);
+        } else {
+        	field_5_username = "";
+        }
     }
 
     //this is the world's lamest "security".  thanks to Wouter van Vugt for making me

Modified: poi/branches/ooxml/src/java/org/apache/poi/hssf/record/NameRecord.java
URL: http://svn.apache.org/viewvc/poi/branches/ooxml/src/java/org/apache/poi/hssf/record/NameRecord.java?rev=634936&r1=634935&r2=634936&view=diff
==============================================================================
--- poi/branches/ooxml/src/java/org/apache/poi/hssf/record/NameRecord.java (original)
+++ poi/branches/ooxml/src/java/org/apache/poi/hssf/record/NameRecord.java Sat Mar  8 03:49:00 2008
@@ -726,7 +726,7 @@
         	for(int i=0; i<refs.length; i++) {
 	            ptg = new Area3DPtg();
 	            ((Area3DPtg) ptg).setExternSheetIndex(externSheetIndex);
-	            ((Area3DPtg) ptg).setArea(refs[i].toString());
+	            ((Area3DPtg) ptg).setArea(refs[i].formatAsString());
 	            field_13_name_definition.push(ptg);
 	            this.setDefinitionTextLength( (short)(getDefinitionLength() + ptg.getSize()) );
         	}

Modified: poi/branches/ooxml/src/java/org/apache/poi/hssf/record/RecordFactory.java
URL: http://svn.apache.org/viewvc/poi/branches/ooxml/src/java/org/apache/poi/hssf/record/RecordFactory.java?rev=634936&r1=634935&r2=634936&view=diff
==============================================================================
--- poi/branches/ooxml/src/java/org/apache/poi/hssf/record/RecordFactory.java (original)
+++ poi/branches/ooxml/src/java/org/apache/poi/hssf/record/RecordFactory.java Sat Mar  8 03:49:00 2008
@@ -77,7 +77,11 @@
                 NoteRecord.class, ObjectProtectRecord.class, ScenarioProtectRecord.class, 
                 FileSharingRecord.class, ChartTitleFormatRecord.class,
                 DVRecord.class, DVALRecord.class, UncalcedRecord.class,
-                HyperlinkRecord.class
+                HyperlinkRecord.class,
+                ExternalNameRecord.class, // TODO - same changes in non-@deprecated version of this class
+                SupBookRecord.class,
+                CRNCountRecord.class,
+                CRNRecord.class,
             };
     }
     private static Map           recordsMap  = recordsToMap(records);

Modified: poi/branches/ooxml/src/java/org/apache/poi/hssf/record/RecordInputStream.java
URL: http://svn.apache.org/viewvc/poi/branches/ooxml/src/java/org/apache/poi/hssf/record/RecordInputStream.java?rev=634936&r1=634935&r2=634936&view=diff
==============================================================================
--- poi/branches/ooxml/src/java/org/apache/poi/hssf/record/RecordInputStream.java (original)
+++ poi/branches/ooxml/src/java/org/apache/poi/hssf/record/RecordInputStream.java Sat Mar  8 03:49:00 2008
@@ -267,7 +267,7 @@
     
   public String readCompressedUnicode(int length) {
     if ((length < 0) || ((remaining() < length) && !isContinueNext())) {
-            throw new IllegalArgumentException("Illegal length");
+            throw new IllegalArgumentException("Illegal length " + length);
     }
 
     StringBuffer buf = new StringBuffer(length);

Modified: poi/branches/ooxml/src/java/org/apache/poi/hssf/record/SharedFormulaRecord.java
URL: http://svn.apache.org/viewvc/poi/branches/ooxml/src/java/org/apache/poi/hssf/record/SharedFormulaRecord.java?rev=634936&r1=634935&r2=634936&view=diff
==============================================================================
--- poi/branches/ooxml/src/java/org/apache/poi/hssf/record/SharedFormulaRecord.java (original)
+++ poi/branches/ooxml/src/java/org/apache/poi/hssf/record/SharedFormulaRecord.java Sat Mar  8 03:49:00 2008
@@ -1,4 +1,3 @@
-
 /* ====================================================================
    Licensed to the Apache Software Foundation (ASF) under one or more
    contributor license agreements.  See the NOTICE file distributed with
@@ -15,8 +14,7 @@
    See the License for the specific language governing permissions and
    limitations under the License.
 ==================================================================== */
-        
-
+ 
 package org.apache.poi.hssf.record;
 
 import java.util.Stack;
@@ -34,10 +32,7 @@
  * record types.
  * @author Danny Mui at apache dot org
  */
-
-public class SharedFormulaRecord
-    extends Record
-{
+public final class SharedFormulaRecord extends Record {
 	 public final static short   sid = 0x4BC;
     
     private int               field_1_first_row;
@@ -58,15 +53,15 @@
 
     public SharedFormulaRecord(RecordInputStream in)
     {
-    	  super(in);
+          super(in);
     }
     
     protected void validateSid(short id)
     {
-		if (id != this.sid)
-		{
-			throw new RecordFormatException("Not a valid SharedFormula");
-		}        
+        if (id != this.sid)
+        {
+            throw new RecordFormatException("Not a valid SharedFormula");
+        }        
     }    
     
     public int getFirstRow() {
@@ -96,14 +91,14 @@
 
     public int serialize(int offset, byte [] data)
     {
-    	//Because this record is converted to individual Formula records, this method is not required.
-    	throw new UnsupportedOperationException("Cannot serialize a SharedFormulaRecord");
+        //Because this record is converted to individual Formula records, this method is not required.
+        throw new UnsupportedOperationException("Cannot serialize a SharedFormulaRecord");
     }
 
     public int getRecordSize()
     {
-    	//Because this record is converted to individual Formula records, this method is not required.
-    	throw new UnsupportedOperationException("Cannot get the size for a SharedFormulaRecord");
+        //Because this record is converted to individual Formula records, this method is not required.
+        throw new UnsupportedOperationException("Cannot get the size for a SharedFormulaRecord");
 
     }
 
@@ -257,39 +252,40 @@
       }
     }
     
-    private short fixupRelativeColumn(int currentcolumn, short column, boolean relative) {
-    	if(relative) {
-    		if((column&128)!=0) column=(short)(column-256);
-    		column+=currentcolumn;
-    	}
-    	return column;
-	}
-
-	private short fixupRelativeRow(int currentrow, short row, boolean relative) {
-		if(relative) {
-			row+=currentrow;
-		}
-		return row;
-	}
-
-	/**
-	 * Mirroring formula records so it is registered in the ValueRecordsAggregate
-	 */
-	public boolean isInValueSection()
-	{
-		 return true;
-	}
-
-
-	 /**
-	  * Register it in the ValueRecordsAggregate so it can go into the FormulaRecordAggregate
-	  */
-	 public boolean isValue() {
-	 	return true;
-	 }
+    private int fixupRelativeColumn(int currentcolumn, int column, boolean relative) {
+        if(relative) {
+            // mask out upper bits to produce 'wrapping' at column 256 ("IV")
+            return (column + currentcolumn) & 0x00FF;
+        }
+        return column;
+    }
+
+    private int fixupRelativeRow(int currentrow, int row, boolean relative) {
+        if(relative) {
+            // mask out upper bits to produce 'wrapping' at row 65536
+            return (row+currentrow) & 0x00FFFF;
+        }
+        return row;
+    }
+
+    /**
+     * Mirroring formula records so it is registered in the ValueRecordsAggregate
+     */
+    public boolean isInValueSection()
+    {
+         return true;
+    }
+
+
+     /**
+      * Register it in the ValueRecordsAggregate so it can go into the FormulaRecordAggregate
+      */
+     public boolean isValue() {
+         return true;
+     }
 
     public Object clone() {
-    	//Because this record is converted to individual Formula records, this method is not required.
-    	throw new UnsupportedOperationException("Cannot clone a SharedFormulaRecord");
+        //Because this record is converted to individual Formula records, this method is not required.
+        throw new UnsupportedOperationException("Cannot clone a SharedFormulaRecord");
     }
 }

Modified: poi/branches/ooxml/src/java/org/apache/poi/hssf/record/StyleRecord.java
URL: http://svn.apache.org/viewvc/poi/branches/ooxml/src/java/org/apache/poi/hssf/record/StyleRecord.java?rev=634936&r1=634935&r2=634936&view=diff
==============================================================================
--- poi/branches/ooxml/src/java/org/apache/poi/hssf/record/StyleRecord.java (original)
+++ poi/branches/ooxml/src/java/org/apache/poi/hssf/record/StyleRecord.java Sat Mar  8 03:49:00 2008
@@ -88,13 +88,18 @@
         else if (getType() == STYLE_USER_DEFINED)
         {
             field_2_name_length = in.readShort();
-            field_3_string_options = in.readByte();
             
-            byte[] string = in.readRemainder();
-            if (fHighByte.isSet(field_3_string_options)) {
-                field_4_name= StringUtil.getFromUnicodeBE(string, 0, field_2_name_length);
-            }else {
-                field_4_name=StringUtil.getFromCompressedUnicode(string, 0, field_2_name_length);
+            // Some files from Crystal Reports lack
+            //  the remaining fields, which is naughty
+            if(in.remaining() > 0) {
+	            field_3_string_options = in.readByte();
+	            
+	            byte[] string = in.readRemainder();
+	            if (fHighByte.isSet(field_3_string_options)) {
+	                field_4_name= StringUtil.getFromUnicodeBE(string, 0, field_2_name_length);
+	            } else {
+	                field_4_name=StringUtil.getFromCompressedUnicode(string, 0, field_2_name_length);
+	            }
             }
         }
 



---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@poi.apache.org
For additional commands, e-mail: commits-help@poi.apache.org