diff --git a/build.xml b/build.xml index c58f06ae2..121cbbd2d 100644 --- a/build.xml +++ b/build.xml @@ -245,6 +245,7 @@ under the License. + diff --git a/src/documentation/content/xdocs/changes.xml b/src/documentation/content/xdocs/changes.xml index b84dec334..fc755ea79 100644 --- a/src/documentation/content/xdocs/changes.xml +++ b/src/documentation/content/xdocs/changes.xml @@ -44,6 +44,12 @@ Created a common interface for handling Excel files, irrespective of if they are .xls or .xlsx + 45025 - improved FormulaParser parse error messages + 45046 - allowed EXTERNALBOOK(0x01AE) to be optional in the LinkTable + 45066 - fixed sheet encoding size mismatch problems + 45003 - Support embeded HDGF visio documents + 45001 - Partial fix for HWPF Range.insertBefore() and Range.delete() with unicode characters + 44977 - Support for AM/PM in excel date formats Support for specifying a policy to HSSF on missing / blank cells when fetching 44937 - Partial support for extracting Escher images from HWPF files 44824 - Avoid an infinite loop when reading some HWPF pictures diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml index 0b7f0140d..ba2c99ec6 100644 --- a/src/documentation/content/xdocs/status.xml +++ b/src/documentation/content/xdocs/status.xml @@ -41,6 +41,12 @@ Created a common interface for handling Excel files, irrespective of if they are .xls or .xlsx + 45025 - improved FormulaParser parse error messages + 45046 - allowed EXTERNALBOOK(0x01AE) to be optional in the LinkTable + 45066 - fixed sheet encoding size mismatch problems + 45003 - Support embeded HDGF visio documents + 45001 - Partial fix for HWPF Range.insertBefore() and Range.delete() with unicode characters + 44977 - Support for AM/PM in excel date formats Support for specifying a policy to HSSF on missing / blank cells when fetching 44937 - Partial support for extracting Escher images from HWPF files 44824 - Avoid an infinite loop when reading some HWPF pictures diff --git a/src/examples/src/org/apache/poi/hslf/usermodel/examples/SoundFinder.java b/src/examples/src/org/apache/poi/hslf/usermodel/examples/SoundFinder.java new file mode 100644 index 000000000..800a4952c --- /dev/null +++ b/src/examples/src/org/apache/poi/hslf/usermodel/examples/SoundFinder.java @@ -0,0 +1,80 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ +package org.apache.poi.hslf.usermodel.examples; +import org.apache.poi.ddf.*; +import org.apache.poi.hslf.model.*; +import org.apache.poi.hslf.record.InteractiveInfo; +import org.apache.poi.hslf.record.InteractiveInfoAtom; +import org.apache.poi.hslf.record.Record; +import org.apache.poi.hslf.usermodel.*; +import java.io.FileInputStream; +import java.util.Iterator; +import java.util.List; + +/** + * For each slide iterate over shapes and found associated sound data. + * + * @author Yegor Kozlov + */ +public class SoundFinder { + public static void main(String[] args) throws Exception { + SlideShow ppt = new SlideShow(new FileInputStream(args[0])); + SoundData[] sounds = ppt.getSoundData(); + + Slide[] slide = ppt.getSlides(); + for (int i = 0; i < slide.length; i++) { + Shape[] shape = slide[i].getShapes(); + for (int j = 0; j < shape.length; j++) { + int soundRef = getSoundReference(shape[j]); + if(soundRef != -1) { + System.out.println("Slide["+i+"], shape["+j+"], soundRef: "+soundRef); + System.out.println(" " + sounds[soundRef].getSoundName()); + System.out.println(" " + sounds[soundRef].getSoundType()); + } + } + } + } + + /** + * Check if a given shape is associated with a sound. + * @return 0-based reference to a sound in the sound collection + * or -1 if the shape is not associated with a sound + */ + protected static int getSoundReference(Shape shape){ + int soundRef = -1; + //dive into the shape container and search for InteractiveInfoAtom + EscherContainerRecord spContainer = shape.getSpContainer(); + List spchild = spContainer.getChildRecords(); + for (Iterator it = spchild.iterator(); it.hasNext();) { + EscherRecord obj = (EscherRecord) it.next(); + if (obj.getRecordId() == EscherClientDataRecord.RECORD_ID) { + byte[] data = obj.serialize(); + Record[] records = Record.findChildRecords(data, 8, +data.length - 8); + for (int j = 0; j < records.length; j++) { + if (records[j] instanceof InteractiveInfo) { + InteractiveInfoAtom info = ((InteractiveInfo)records[j]).getInteractiveInfoAtom(); + if (info.getAction() == InteractiveInfoAtom.ACTION_MEDIA) { + soundRef = info.getSoundRef(); + } + } + } + } + } + return soundRef; + } +} diff --git a/src/java/org/apache/poi/POIDocument.java b/src/java/org/apache/poi/POIDocument.java index 01e50231c..4d7e50c0f 100644 --- a/src/java/org/apache/poi/POIDocument.java +++ b/src/java/org/apache/poi/POIDocument.java @@ -20,6 +20,7 @@ package org.apache.poi; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.OutputStream; import java.util.Iterator; import java.util.List; @@ -191,6 +192,11 @@ public abstract class POIDocument { System.err.println("Couldn't write property set with name " + name + " as not supported by HPSF yet"); } } + + /** + * Writes the document out to the specified output stream + */ + public abstract void write(OutputStream out) throws IOException; /** * Copies nodes from one POIFS to the other minus the excepts diff --git a/src/java/org/apache/poi/hssf/model/FormulaParser.java b/src/java/org/apache/poi/hssf/model/FormulaParser.java index 39a79bb76..085a140ad 100644 --- a/src/java/org/apache/poi/hssf/model/FormulaParser.java +++ b/src/java/org/apache/poi/hssf/model/FormulaParser.java @@ -55,7 +55,7 @@ public final class FormulaParser { */ 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. + // general client code. public FormulaParseException(String msg) { super(msg); } @@ -127,42 +127,34 @@ public final class FormulaParser { // Just return if so and reset 'look' to something to keep // SkipWhitespace from spinning look = (char)0; - } + } pointer++; //System.out.println("Got char: "+ look); } /** Report What Was Expected */ private RuntimeException expected(String s) { - String msg = "Parse error near char " + (pointer-1) + "'" + look + "'" + String msg = "Parse error near char " + (pointer-1) + " '" + look + "'" + " in specified formula '" + formulaString + "'. Expected " + s; return new FormulaParseException(msg); } - - /** 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 White Space */ private boolean IsWhite( char c) { return (c ==' ' || c== TAB); @@ -178,7 +170,7 @@ public final class FormulaParser { /** * Consumes the next input character if it is equal to the one specified otherwise throws an * unchecked exception. This method does not consume whitespace (before or after the - * matched character). + * matched character). */ private void Match(char x) { if (look != x) { @@ -218,7 +210,6 @@ public final class FormulaParser { return Token.toString(); } - /** Get a Number */ private String GetNum() { StringBuffer value = new StringBuffer(); @@ -281,18 +272,18 @@ public final class FormulaParser { // 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.getNumberOfNames(); i++) { // named range name matching is case insensitive - if(book.getNameAt(i).getNameName().equalsIgnoreCase(name)) { + if(book.getNameAt(i).getNameName().equalsIgnoreCase(name)) { return new NamePtg(name, book); } } - throw new FormulaParseException("Found reference to named range \"" + throw new FormulaParseException("Found reference to named range \"" + name + "\", but that named range wasn't defined!"); } @@ -307,19 +298,19 @@ public final class FormulaParser { /** * 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). + * 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 - + // 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++; + numArgs++; tokens.add(nameToken); } //average 2 args per function @@ -477,26 +468,25 @@ public final class FormulaParser { private static boolean isArgumentDelimiter(char ch) { return ch == ',' || ch == ')'; } - + /** get arguments to a function */ private int Arguments(List argumentPointers) { SkipWhite(); if(look == ')') { return 0; } - + boolean missedPrevArg = true; - int numArgs = 0; - while(true) { + while (true) { SkipWhite(); - if(isArgumentDelimiter(look)) { - if(missedPrevArg) { + if (isArgumentDelimiter(look)) { + if (missedPrevArg) { tokens.add(new MissingArgPtg()); addArgumentPointer(argumentPointers); numArgs++; } - if(look == ')') { + if (look == ')') { break; } Match(','); @@ -507,6 +497,10 @@ public final class FormulaParser { addArgumentPointer(argumentPointers); numArgs++; missedPrevArg = false; + SkipWhite(); + if (!isArgumentDelimiter(look)) { + throw expected("',' or ')'"); + } } return numArgs; } @@ -524,7 +518,7 @@ public final class FormulaParser { tokens.add(new PowerPtg()); } } - + private void percentFactor() { tokens.add(parseSimpleFactor()); while(true) { @@ -536,8 +530,8 @@ public final class FormulaParser { tokens.add(new PercentPtg()); } } - - + + /** * factors (without ^ or % ) */ @@ -561,9 +555,6 @@ public final class FormulaParser { 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(); @@ -707,10 +698,9 @@ public final class FormulaParser { } - private StringPtg parseStringLiteral() - { + private StringPtg parseStringLiteral() { Match('"'); - + StringBuffer token = new StringBuffer(); while (true) { if (look == '"') { @@ -745,7 +735,7 @@ public final class FormulaParser { return; // finished with Term } } - + private void comparisonExpression() { concatExpression(); while (true) { @@ -787,7 +777,7 @@ public final class FormulaParser { } return new LessThanPtg(); } - + private void concatExpression() { additiveExpression(); @@ -801,7 +791,7 @@ public final class FormulaParser { tokens.add(new ConcatPtg()); } } - + /** Parse and Translate an Expression */ private void additiveExpression() { @@ -838,8 +828,9 @@ end; **/ - /** API call to execute the parsing of the formula - * + /** + * API call to execute the parsing of the formula + * @deprecated use Ptg[] FormulaParser.parse(String, HSSFWorkbook) directly */ public void parse() { pointer=0; @@ -847,8 +838,8 @@ end; comparisonExpression(); if(pointer <= formulaLength) { - String msg = "Unused input [" + formulaString.substring(pointer-1) - + "] after attempting to parse the formula [" + formulaString + "]"; + String msg = "Unused input [" + formulaString.substring(pointer-1) + + "] after attempting to parse the formula [" + formulaString + "]"; throw new FormulaParseException(msg); } } @@ -863,11 +854,12 @@ end; * a result of the parsing */ public Ptg[] getRPNPtg() { - return getRPNPtg(FORMULA_TYPE_CELL); + return getRPNPtg(FORMULA_TYPE_CELL); } public Ptg[] getRPNPtg(int formulaType) { Node node = createTree(); + // RVA is for 'operand class': 'reference', 'value', 'array' setRootLevelRVA(node, formulaType); setParameterRVA(node,formulaType); return (Ptg[]) tokens.toArray(new Ptg[0]); @@ -948,7 +940,7 @@ end; } } /** - * Convience method which takes in a list then passes it to the + * Convenience method which takes in a list then passes it to the * other toFormulaString signature. * @param book workbook for 3D and named references * @param lptgs list of Ptg, can be null or empty @@ -963,7 +955,7 @@ end; return retval; } /** - * Convience method which takes in a list then passes it to the + * Convenience method which takes in a list then passes it to the * other toFormulaString signature. Works on the current * workbook for 3D and named references * @param lptgs list of Ptg, can be null or empty @@ -1011,7 +1003,7 @@ end; continue; // but if it ever did, care must be taken: // tAttrSpace comes *before* the operand it applies to, which may be consistent - // with how the formula text appears but is against the RPN ordering assumed here + // with how the formula text appears but is against the RPN ordering assumed here } if (attrPtg.isSemiVolatile()) { // similar to tAttrSpace - RPN is violated @@ -1038,7 +1030,7 @@ end; stack.push(o.toFormulaString(operands)); } if(stack.isEmpty()) { - // inspection of the code above reveals that every stack.pop() is followed by a + // 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"); } diff --git a/src/java/org/apache/poi/hssf/model/LinkTable.java b/src/java/org/apache/poi/hssf/model/LinkTable.java index 88c94a61e..a19971b7d 100755 --- a/src/java/org/apache/poi/hssf/model/LinkTable.java +++ b/src/java/org/apache/poi/hssf/model/LinkTable.java @@ -41,7 +41,7 @@ import org.apache.poi.hssf.record.SupBookRecord; * * In BIFF8 the Link Table consists of * * @@ -63,6 +63,7 @@ import org.apache.poi.hssf.record.SupBookRecord; * @author Josh Micich */ final class LinkTable { + // TODO make this class into a record aggregate private static final class CRNBlock { @@ -79,8 +80,8 @@ final class LinkTable { _crns = crns; } public CRNRecord[] getCrns() { - return (CRNRecord[]) _crns.clone(); - } + return (CRNRecord[]) _crns.clone(); + } } private static final class ExternalBookBlock { @@ -136,16 +137,19 @@ final class LinkTable { while(rs.peekNextClass() == SupBookRecord.class) { temp.add(new ExternalBookBlock(rs)); } - if(temp.size() < 1) { - throw new RuntimeException("Need at least one EXTERNALBOOK blocks"); - } + _externalBookBlocks = new ExternalBookBlock[temp.size()]; temp.toArray(_externalBookBlocks); temp.clear(); - - // If link table is present, there is always 1 of ExternSheetRecord - Record next = rs.getNext(); - _externSheetRecord = (ExternSheetRecord)next; + + if (_externalBookBlocks.length > 0) { + // If any ExternalBookBlock present, there is always 1 of ExternSheetRecord + Record next = rs.getNext(); + _externSheetRecord = (ExternSheetRecord) next; + } else { + _externSheetRecord = null; + } + _definedNames = new ArrayList(); // collect zero or more DEFINEDNAMEs id=0x18 while(rs.peekNextClass() == NameRecord.class) { @@ -222,7 +226,7 @@ final class LinkTable { public void addName(NameRecord name) { _definedNames.add(name); - // TODO - this is messy + // TODO - this is messy // 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); @@ -242,8 +246,8 @@ final class LinkTable { public int getSheetIndexFromExternSheetIndex(int externSheetNumber) { if (externSheetNumber >= _externSheetRecord.getNumOfREFStructures()) { - return -1; - } + return -1; + } return _externSheetRecord.getREFRecordAt(externSheetNumber).getIndexToFirstSupBook(); } @@ -265,7 +269,7 @@ final class LinkTable { ExternSheetSubRecord esr = _externSheetRecord.getREFRecordAt(i); if (esr.getIndexToFirstSupBook() == sheetNumber - && esr.getIndexToLastSupBook() == sheetNumber){ + && esr.getIndexToLastSupBook() == sheetNumber){ return i; } } diff --git a/src/java/org/apache/poi/hssf/model/Sheet.java b/src/java/org/apache/poi/hssf/model/Sheet.java index 45090e85c..871ecc100 100644 --- a/src/java/org/apache/poi/hssf/model/Sheet.java +++ b/src/java/org/apache/poi/hssf/model/Sheet.java @@ -96,8 +96,8 @@ public final class Sheet implements Model { protected List condFormatting = new ArrayList(); /** Add an UncalcedRecord if not true indicating formulas have not been calculated */ - protected boolean uncalced = false; - + protected boolean _isUncalced = false; + public static final byte PANE_LOWER_RIGHT = (byte)0; public static final byte PANE_UPPER_RIGHT = (byte)1; public static final byte PANE_LOWER_LEFT = (byte)2; @@ -162,7 +162,7 @@ public final class Sheet implements Model { } } else if (rec.getSid() == UncalcedRecord.sid) { - retval.uncalced = true; + retval._isUncalced = true; } else if (rec.getSid() == DimensionsRecord.sid) { @@ -329,16 +329,8 @@ public final class Sheet implements Model { } } retval.records = records; -// if (retval.rows == null) -// { -// retval.rows = new RowRecordsAggregate(); -// } retval.checkCells(); retval.checkRows(); -// if (retval.cells == null) -// { -// retval.cells = new ValueRecordsAggregate(); -// } if (log.check( POILogger.DEBUG )) log.log(POILogger.DEBUG, "sheet createSheet (existing file) exited"); return retval; @@ -816,17 +808,17 @@ public final class Sheet implements Model { // Once the rows have been found in the list of records, start // writing out the blocked row information. This includes the DBCell references if (record instanceof RowRecordsAggregate) { - pos += ((RowRecordsAggregate)record).serialize(pos, data, cells); // rec.length; + pos += ((RowRecordsAggregate)record).serialize(pos, data, cells); } else if (record instanceof ValueRecordsAggregate) { //Do nothing here. The records were serialized during the RowRecordAggregate block serialization } else { - pos += record.serialize(pos, data ); // rec.length; + pos += record.serialize(pos, data ); } // If the BOF record was just serialized then add the IndexRecord if (record.getSid() == BOFRecord.sid) { // Add an optional UncalcedRecord - if (uncalced) { + if (_isUncalced) { UncalcedRecord rec = new UncalcedRecord(); pos += rec.serialize(pos, data); } @@ -837,31 +829,10 @@ public final class Sheet implements Model { pos += serializeIndexRecord(k, pos, data); } } - - //// uncomment to test record sizes //// -// System.out.println( record.getClass().getName() ); -// byte[] data2 = new byte[record.getRecordSize()]; -// record.serialize(0, data2 ); // rec.length; -// if (LittleEndian.getUShort(data2, 2) != record.getRecordSize() - 4 -// && record instanceof RowRecordsAggregate == false -// && record instanceof ValueRecordsAggregate == false -// && record instanceof EscherAggregate == false) -// { -// throw new RuntimeException("Blah!!! Size off by " + ( LittleEndian.getUShort(data2, 2) - record.getRecordSize() - 4) + " records."); -// } - -//asd: int len = record.serialize(pos + offset, data ); - - ///// DEBUG BEGIN ///// -//asd: if (len != record.getRecordSize()) -//asd: throw new IllegalStateException("Record size does not match serialized bytes. Serialized size = " + len + " but getRecordSize() returns " + record.getRecordSize() + ". Record object is " + record.getClass()); - ///// DEBUG END ///// - -//asd: pos += len; // rec.length; - } - if (log.check( POILogger.DEBUG )) + if (log.check( POILogger.DEBUG )) { log.log(POILogger.DEBUG, "Sheet.serialize returning "); + } return pos-offset; } @@ -875,10 +846,17 @@ public final class Sheet implements Model { for (int j = BOFRecordIndex+1; j < records.size(); j++) { Record tmpRec = (( Record ) records.get(j)); - if (tmpRec instanceof RowRecordsAggregate) - break; + if (tmpRec instanceof UncalcedRecord) { + continue; + } + if (tmpRec instanceof RowRecordsAggregate) { + break; + } sheetRecSize+= tmpRec.getRecordSize(); } + if (_isUncalced) { + sheetRecSize += UncalcedRecord.getStaticRecordSize(); + } //Add the references to the DBCells in the IndexRecord (one for each block) int blockCount = rows.getRowBlockCount(); //Calculate the size of this IndexRecord @@ -2017,31 +1995,33 @@ public final class Sheet implements Model { { int retval = 0; - for ( int k = 0; k < records.size(); k++ ) - { - retval += ( (Record) records.get( k ) ).getRecordSize(); - } - //Add space for the IndexRecord - if (rows != null) { - final int blocks = rows.getRowBlockCount(); - retval += IndexRecord.getRecordSizeForBlockCount(blocks); - - //Add space for the DBCell records - //Once DBCell per block. - //8 bytes per DBCell (non variable section) - //2 bytes per row reference - retval += (8 * blocks); - for (Iterator itr = rows.getIterator(); itr.hasNext();) { - RowRecord row = (RowRecord)itr.next(); - if (cells != null && cells.rowHasCells(row.getRowNumber())) - retval += 2; + for ( int k = 0; k < records.size(); k++) { + Record record = (Record) records.get(k); + if (record instanceof UncalcedRecord) { + // skip the UncalcedRecord if present, it's only encoded if the isUncalced flag is set + continue; } + retval += record.getRecordSize(); + } + if (rows != null) { + // Add space for the IndexRecord and DBCell records + final int nBlocks = rows.getRowBlockCount(); + int nRows = 0; + if (cells != null) { + for (Iterator itr = rows.getIterator(); itr.hasNext();) { + RowRecord row = (RowRecord)itr.next(); + if (cells.rowHasCells(row.getRowNumber())) { + nRows++; + } + } + } + retval += IndexRecord.getRecordSizeForBlockCount(nBlocks); + retval += DBCellRecord.calculateSizeOfRecords(nBlocks, nRows); } // Add space for UncalcedRecord - if (uncalced) { + if (_isUncalced) { retval += UncalcedRecord.getStaticRecordSize(); } - return retval; } @@ -2518,13 +2498,13 @@ public final class Sheet implements Model { * @return whether an uncalced record must be inserted or not at generation */ public boolean getUncalced() { - return uncalced; + return _isUncalced; } /** * @param uncalced whether an uncalced record must be inserted or not at generation */ public void setUncalced(boolean uncalced) { - this.uncalced = uncalced; + this._isUncalced = uncalced; } /** diff --git a/src/java/org/apache/poi/hssf/model/Workbook.java b/src/java/org/apache/poi/hssf/model/Workbook.java index 08f226318..d87fc2c63 100644 --- a/src/java/org/apache/poi/hssf/model/Workbook.java +++ b/src/java/org/apache/poi/hssf/model/Workbook.java @@ -191,12 +191,11 @@ public class Workbook implements Model case ExternSheetRecord.sid : throw new RuntimeException("Extern sheet is part of LinkTable"); case NameRecord.sid : - throw new RuntimeException("DEFINEDNAME is part of LinkTable"); case SupBookRecord.sid : + // LinkTable can start with either of these 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; k+=retval.linkTable.getRecordCount() - 1; continue; case FormatRecord.sid : diff --git a/src/java/org/apache/poi/hssf/record/DBCellRecord.java b/src/java/org/apache/poi/hssf/record/DBCellRecord.java index caeb333a5..1da6b82c7 100644 --- a/src/java/org/apache/poi/hssf/record/DBCellRecord.java +++ b/src/java/org/apache/poi/hssf/record/DBCellRecord.java @@ -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,7 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. ==================================================================== */ - package org.apache.poi.hssf.record; @@ -29,10 +27,7 @@ import org.apache.poi.util.LittleEndian; * @author Jason Height * @version 2.0-pre */ - -public class DBCellRecord - extends Record -{ +public final class DBCellRecord extends Record { public final static int BLOCK_SIZE = 32; public final static short sid = 0xd7; private int field_1_row_offset; @@ -46,7 +41,6 @@ public class DBCellRecord * Constructs a DBCellRecord and sets its fields appropriately * @param in the RecordInputstream to read the record from */ - public DBCellRecord(RecordInputStream in) { super(in); @@ -78,7 +72,6 @@ public class DBCellRecord * * @param offset offset to the start of the first cell in the next DBCell block */ - public void setRowOffset(int offset) { field_1_row_offset = offset; @@ -108,7 +101,6 @@ public class DBCellRecord * * @return rowoffset to the start of the first cell in the next DBCell block */ - public int getRowOffset() { return field_1_row_offset; @@ -120,7 +112,6 @@ public class DBCellRecord * @param index of the cell offset to retrieve * @return celloffset from the celloffset array */ - public short getCellOffsetAt(int index) { return field_2_cell_offsets[ index ]; @@ -131,7 +122,6 @@ public class DBCellRecord * * @return number of cell offsets */ - public int getNumCellOffsets() { return field_2_cell_offsets.length; @@ -175,9 +165,15 @@ public class DBCellRecord return 8 + (getNumCellOffsets() * 2); } - /** Returns the size of a DBCellRecord when it needs to reference a certain number of rows*/ - public static int getRecordSizeForRows(int rows) { - return 8 + (rows * 2); + /** + * @returns the size of the group of DBCellRecords needed to encode + * the specified number of blocks and rows + */ + public static int calculateSizeOfRecords(int nBlocks, int nRows) { + // One DBCell per block. + // 8 bytes per DBCell (non variable section) + // 2 bytes per row reference + return nBlocks * 8 + nRows * 2; } public short getSid() diff --git a/src/java/org/apache/poi/hssf/record/formula/Area3DPtg.java b/src/java/org/apache/poi/hssf/record/formula/Area3DPtg.java index 458bd1e59..668a25151 100644 --- a/src/java/org/apache/poi/hssf/record/formula/Area3DPtg.java +++ b/src/java/org/apache/poi/hssf/record/formula/Area3DPtg.java @@ -27,7 +27,7 @@ import org.apache.poi.util.LittleEndian; /** - * Title: Area 3D Ptg - 3D referecnce (Sheet + Area)

+ * Title: Area 3D Ptg - 3D reference (Sheet + Area)

* Description: Defined a area in Extern Sheet.

* REFERENCE:

* @author Libin Roman (Vista Portal LDT. Developer) @@ -35,7 +35,6 @@ import org.apache.poi.util.LittleEndian; * @author Jason Height (jheight at chariot dot net dot au) * @version 1.0-pre */ - public class Area3DPtg extends Ptg implements AreaI { public final static byte sid = 0x3b; @@ -84,23 +83,15 @@ public class Area3DPtg extends Ptg implements AreaI setExternSheetIndex(externalSheetIndex); } - public String toString() - { - StringBuffer buffer = new StringBuffer(); - - buffer.append( "AreaPtg\n" ); - buffer.append( "Index to Extern Sheet = " + getExternSheetIndex() ).append( "\n" ); - buffer.append( "firstRow = " + getFirstRow() ).append( "\n" ); - buffer.append( "lastRow = " + getLastRow() ).append( "\n" ); - buffer.append( "firstCol = " + getFirstColumn() ).append( "\n" ); - buffer.append( "lastCol = " + getLastColumn() ).append( "\n" ); - buffer.append( "firstColRel= " - + isFirstRowRelative() ).append( "\n" ); - buffer.append( "lastColRowRel = " - + isLastRowRelative() ).append( "\n" ); - buffer.append( "firstColRel = " + isFirstColRelative() ).append( "\n" ); - buffer.append( "lastColRel = " + isLastColRelative() ).append( "\n" ); - return buffer.toString(); + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append(getClass().getName()); + sb.append(" ["); + sb.append("sheetIx=").append(getExternSheetIndex()); + sb.append(" ! "); + sb.append(AreaReference.formatAsString(this)); + sb.append("]"); + return sb.toString(); } public void writeBytes( byte[] array, int offset ) @@ -284,7 +275,7 @@ public class Area3DPtg extends Ptg implements AreaI } // Now the normal area bit - retval.append( AreaPtg.toFormulaString(this, book) ); + retval.append(AreaReference.formatAsString(this)); // All done return retval.toString(); @@ -326,6 +317,7 @@ public class Area3DPtg extends Ptg implements AreaI public int hashCode() { + // TODO - hashCode seems to be unused int result; result = (int) field_1_index_extern_sheet; result = 29 * result + (int) field_2_first_row; diff --git a/src/java/org/apache/poi/hssf/record/formula/AreaPtg.java b/src/java/org/apache/poi/hssf/record/formula/AreaPtg.java index c4e7534c7..38cae39f4 100644 --- a/src/java/org/apache/poi/hssf/record/formula/AreaPtg.java +++ b/src/java/org/apache/poi/hssf/record/formula/AreaPtg.java @@ -114,23 +114,13 @@ public class AreaPtg extends Ptg implements AreaI { return "AreaPtg"; } - public String toString() - { - StringBuffer buffer = new StringBuffer(); - - buffer.append(getAreaPtgName()); - buffer.append("\n"); - buffer.append("firstRow = " + getFirstRow()).append("\n"); - buffer.append("lastRow = " + getLastRow()).append("\n"); - buffer.append("firstCol = " + getFirstColumn()).append("\n"); - buffer.append("lastCol = " + getLastColumn()).append("\n"); - buffer.append("firstColRowRel= " - + isFirstRowRelative()).append("\n"); - buffer.append("lastColRowRel = " - + isLastRowRelative()).append("\n"); - buffer.append("firstColRel = " + isFirstColRelative()).append("\n"); - buffer.append("lastColRel = " + isLastColRelative()).append("\n"); - return buffer.toString(); + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append(getClass().getName()); + sb.append(" ["); + sb.append(AreaReference.formatAsString(this)); + sb.append("]"); + return sb.toString(); } public void writeBytes(byte [] array, int offset) { @@ -307,19 +297,8 @@ public class AreaPtg extends Ptg implements AreaI { field_4_last_column = column; } - public String toFormulaString(Workbook book) - { - return toFormulaString(this, book); - } - protected static String toFormulaString(AreaI area, Workbook book) { - CellReference topLeft = new CellReference(area.getFirstRow(),area.getFirstColumn(),!area.isFirstRowRelative(),!area.isFirstColRelative()); - CellReference botRight = new CellReference(area.getLastRow(),area.getLastColumn(),!area.isLastRowRelative(),!area.isLastColRelative()); - - if(AreaReference.isWholeColumnReference(topLeft, botRight)) { - return (new AreaReference(topLeft, botRight)).formatAsString(); - } else { - return topLeft.formatAsString() + ":" + botRight.formatAsString(); - } + public String toFormulaString(Workbook book) { + return AreaReference.formatAsString(this); } public byte getDefaultOperandClass() { diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java b/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java index 08ddb9656..32fce2f09 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java @@ -15,12 +15,6 @@ limitations under the License. ==================================================================== */ - -/* - * HSSFWorkbook.java - * - * Created on September 30, 2001, 3:37 PM - */ package org.apache.poi.hssf.usermodel; import java.io.ByteArrayInputStream; @@ -81,7 +75,6 @@ import org.apache.poi.util.POILogger; * @author Shawn Laubach (slaubach at apache dot org) * @version 2.0-pre */ - public class HSSFWorkbook extends POIDocument implements org.apache.poi.ss.usermodel.Workbook { private static final int DEBUG = POILogger.DEBUG; @@ -105,7 +98,7 @@ public class HSSFWorkbook extends POIDocument implements org.apache.poi.ss.userm * this holds the HSSFSheet objects attached to this workbook */ - protected ArrayList sheets; + protected List _sheets; /** * this holds the HSSFName objects attached to this workbook @@ -159,7 +152,7 @@ public class HSSFWorkbook extends POIDocument implements org.apache.poi.ss.userm { super(null, null); workbook = book; - sheets = new ArrayList( INITIAL_CAPACITY ); + _sheets = new ArrayList( INITIAL_CAPACITY ); names = new ArrayList( INITIAL_CAPACITY ); } @@ -250,7 +243,7 @@ public class HSSFWorkbook extends POIDocument implements org.apache.poi.ss.userm this.directory = null; } - sheets = new ArrayList(INITIAL_CAPACITY); + _sheets = new ArrayList(INITIAL_CAPACITY); names = new ArrayList(INITIAL_CAPACITY); // Grab the data from the workbook stream, however @@ -280,7 +273,7 @@ public class HSSFWorkbook extends POIDocument implements org.apache.poi.ss.userm HSSFSheet hsheet = new HSSFSheet(this, sheet); - sheets.add(hsheet); + _sheets.add(hsheet); // workbook.setSheetName(sheets.size() -1, "Sheet"+sheets.size()); } @@ -378,12 +371,12 @@ public class HSSFWorkbook extends POIDocument implements org.apache.poi.ss.userm */ public void setSheetOrder(String sheetname, int pos ) { - sheets.add(pos,sheets.remove(getSheetIndex(sheetname))); + _sheets.add(pos,_sheets.remove(getSheetIndex(sheetname))); workbook.setSheetOrder(sheetname, pos); } private void validateSheetIndex(int index) { - int lastSheetIx = sheets.size() - 1; + int lastSheetIx = _sheets.size() - 1; if (index < 0 || index > lastSheetIx) { throw new IllegalArgumentException("Sheet index (" + index +") is out of range (0.." + lastSheetIx + ")"); @@ -397,7 +390,7 @@ public class HSSFWorkbook extends POIDocument implements org.apache.poi.ss.userm public void setSelectedTab(int index) { validateSheetIndex(index); - int nSheets = sheets.size(); + int nSheets = _sheets.size(); for (int i=0; i (sheets.size() - 1)) - { - throw new RuntimeException("Sheet out of bounds"); } - - workbook.setSheetName( sheet, name); + validateSheetIndex(sheetIx); + workbook.setSheetName(sheetIx, name); } @@ -533,15 +522,12 @@ public class HSSFWorkbook extends POIDocument implements org.apache.poi.ss.userm * or contains /\?*[] * @param sheet number (0 based) */ - public void setSheetName( int sheet, String name, short encoding ) + public void setSheetName(int sheetIx, String name, short encoding) { - if (workbook.doesContainsSheetName( name, sheet )) + if (workbook.doesContainsSheetName( name, sheetIx )) { throw new IllegalArgumentException( "The workbook already contains a sheet with this name" ); - - if (sheet > (sheets.size() - 1)) - { - throw new RuntimeException("Sheet out of bounds"); } + validateSheetIndex(sheetIx); switch ( encoding ) { case ENCODING_COMPRESSED_UNICODE: @@ -553,51 +539,39 @@ public class HSSFWorkbook extends POIDocument implements org.apache.poi.ss.userm throw new RuntimeException( "Unsupported encoding" ); } - workbook.setSheetName( sheet, name, encoding ); + workbook.setSheetName( sheetIx, name, encoding ); } /** * get the sheet name - * @param sheet Number + * @param sheetIx Number * @return Sheet name */ - - public String getSheetName(int sheet) + public String getSheetName(int sheetIx) { - if (sheet > (sheets.size() - 1)) - { - throw new RuntimeException("Sheet out of bounds"); - } - return workbook.getSheetName(sheet); + validateSheetIndex(sheetIx); + return workbook.getSheetName(sheetIx); } /** * check whether a sheet is hidden - * @param sheet Number + * @param sheetIx Number * @return True if sheet is hidden */ - - public boolean isSheetHidden(int sheet) { - if (sheet > (sheets.size() - 1)) - { - throw new RuntimeException("Sheet out of bounds"); - } - return workbook.isSheetHidden(sheet); + public boolean isSheetHidden(int sheetIx) { + validateSheetIndex(sheetIx); + return workbook.isSheetHidden(sheetIx); } /** * Hide or unhide a sheet * - * @param sheetnum The sheet number + * @param sheetIx The sheet index * @param hidden True to mark the sheet as hidden, false otherwise */ - - public void setSheetHidden(int sheet, boolean hidden) { - if (sheet > (sheets.size() - 1)) - { - throw new RuntimeException("Sheet out of bounds"); - } - workbook.setSheetHidden(sheet,hidden); + public void setSheetHidden(int sheetIx, boolean hidden) { + validateSheetIndex(sheetIx); + workbook.setSheetHidden(sheetIx, hidden); } /* @@ -619,12 +593,12 @@ public class HSSFWorkbook extends POIDocument implements org.apache.poi.ss.userm /** Returns the index of the given sheet * @param sheet the sheet to look up - * @return index of the sheet (0 based) + * @return index of the sheet (0 based). -1 if not found */ public int getSheetIndex(org.apache.poi.ss.usermodel.Sheet sheet) { - for(int i=0; i= 'A' && parts[0].charAt(0) <= 'Z' && - parts[1].charAt(0) >= 'A' && parts[1].charAt(0) <= 'Z') { - // Represented internally as x$1 to x$65536 - // which is the maximum range of rows - parts[0] = parts[0] + "$1"; - parts[1] = parts[1] + "$65536"; + parts[1].length() == 1 && + parts[0].charAt(0) >= 'A' && parts[0].charAt(0) <= 'Z' && + parts[1].charAt(0) >= 'A' && parts[1].charAt(0) <= 'Z') { + // Represented internally as x$1 to x$65536 + // which is the maximum range of rows + parts[0] = parts[0] + "$1"; + parts[1] = parts[1] + "$65536"; } _firstCell = new CellReference(parts[0]); @@ -74,9 +75,9 @@ public final class AreaReference { * Creates an area ref from a pair of Cell References. */ public AreaReference(CellReference topLeft, CellReference botRight) { - _firstCell = topLeft; - _lastCell = botRight; - _isSingleCell = false; + _firstCell = topLeft; + _lastCell = botRight; + _isSingleCell = false; } /** @@ -98,17 +99,17 @@ public final class AreaReference { * such as C:C or D:G ? */ public static boolean isWholeColumnReference(CellReference topLeft, CellReference botRight) { - // These are represented as something like - // C$1:C$65535 or D$1:F$0 - // i.e. absolute from 1st row to 0th one - if(topLeft.getRow() == 0 && topLeft.isRowAbsolute() && - botRight.getRow() == 65535 && botRight.isRowAbsolute()) { - return true; - } - return false; + // These are represented as something like + // C$1:C$65535 or D$1:F$0 + // i.e. absolute from 1st row to 0th one + if(topLeft.getRow() == 0 && topLeft.isRowAbsolute() && + botRight.getRow() == 65535 && botRight.isRowAbsolute()) { + return true; + } + return false; } public boolean isWholeColumnReference() { - return isWholeColumnReference(_firstCell, _lastCell); + return isWholeColumnReference(_firstCell, _lastCell); } /** @@ -155,26 +156,26 @@ public final class AreaReference { * Returns a reference to every cell covered by this area */ public CellReference[] getAllReferencedCells() { - // Special case for single cell reference - if(_isSingleCell) { - return new CellReference[] { _firstCell, }; - } + // Special case for single cell reference + if(_isSingleCell) { + return new CellReference[] { _firstCell, }; + } - // Interpolate between the two + // Interpolate between the two int minRow = Math.min(_firstCell.getRow(), _lastCell.getRow()); - int maxRow = Math.max(_firstCell.getRow(), _lastCell.getRow()); - int minCol = Math.min(_firstCell.getCol(), _lastCell.getCol()); - int maxCol = Math.max(_firstCell.getCol(), _lastCell.getCol()); + int maxRow = Math.max(_firstCell.getRow(), _lastCell.getRow()); + int minCol = Math.min(_firstCell.getCol(), _lastCell.getCol()); + int maxCol = Math.max(_firstCell.getCol(), _lastCell.getCol()); String sheetName = _firstCell.getSheetName(); - - ArrayList refs = new ArrayList(); - for(int row=minRow; row<=maxRow; row++) { - for(int col=minCol; col<=maxCol; col++) { - CellReference ref = new CellReference(sheetName, row, col, _firstCell.isRowAbsolute(), _firstCell.isColAbsolute()); - refs.add(ref); - } - } - return (CellReference[])refs.toArray(new CellReference[refs.size()]); + + ArrayList refs = new ArrayList(); + for(int row=minRow; row<=maxRow; row++) { + for(int col=minCol; col<=maxCol; col++) { + CellReference ref = new CellReference(sheetName, row, col, _firstCell.isRowAbsolute(), _firstCell.isColAbsolute()); + refs.add(ref); + } + } + return (CellReference[])refs.toArray(new CellReference[refs.size()]); } /** @@ -189,14 +190,14 @@ public final class AreaReference { * @return the text representation of this area reference as it would appear in a formula. */ public String formatAsString() { - // Special handling for whole-column references - if(isWholeColumnReference()) { - return - CellReference.convertNumToColString(_firstCell.getCol()) - + ":" + - CellReference.convertNumToColString(_lastCell.getCol()); - } - + // Special handling for whole-column references + if(isWholeColumnReference()) { + return + CellReference.convertNumToColString(_firstCell.getCol()) + + ":" + + CellReference.convertNumToColString(_lastCell.getCol()); + } + StringBuffer sb = new StringBuffer(32); sb.append(_firstCell.formatAsString()); if(!_isSingleCell) { @@ -210,6 +211,18 @@ public final class AreaReference { } return sb.toString(); } + /** + * Formats a 2-D area as it would appear in a formula. See formatAsString() (no-arg) + */ + public static String formatAsString(AreaI area) { + CellReference topLeft = new CellReference(area.getFirstRow(),area.getFirstColumn(),!area.isFirstRowRelative(),!area.isFirstColRelative()); + CellReference botRight = new CellReference(area.getLastRow(),area.getLastColumn(),!area.isLastRowRelative(),!area.isLastColRelative()); + + if(isWholeColumnReference(topLeft, botRight)) { + return (new AreaReference(topLeft, botRight)).formatAsString(); + } + return topLeft.formatAsString() + ":" + botRight.formatAsString(); + } public String toString() { StringBuffer sb = new StringBuffer(64); sb.append(getClass().getName()).append(" ["); diff --git a/src/java/org/apache/poi/hssf/util/HSSFColor.java b/src/java/org/apache/poi/hssf/util/HSSFColor.java index 9251ccc74..2c51b3d20 100644 --- a/src/java/org/apache/poi/hssf/util/HSSFColor.java +++ b/src/java/org/apache/poi/hssf/util/HSSFColor.java @@ -15,16 +15,16 @@ limitations under the License. ==================================================================== */ - package org.apache.poi.hssf.util; +import java.lang.reflect.Field; import java.util.Hashtable; import org.apache.poi.ss.usermodel.Color; /** * Intends to provide support for the very evil index to triplet issue and - * will likely replace the color contants interface for HSSF 2.0. + * will likely replace the color constants interface for HSSF 2.0. * This class contains static inner class members for representing colors. * Each color has an index (for the standard palette in Excel (tm) ), * native (RGB) triplet and string triplet. The string triplet is as the @@ -35,14 +35,10 @@ import org.apache.poi.ss.usermodel.Color; * @author Andrew C. Oliver (acoliver at apache dot org) * @author Brian Sanders (bsanders at risklabs dot com) - full default color palette */ - -public class HSSFColor implements Color -{ - private final static int PALETTE_SIZE = 56; - private final static int DISTINCT_COLOR_COUNT = 46; +public class HSSFColor implements Color { + // TODO make subclass instances immutable /** Creates a new instance of HSSFColor */ - public HSSFColor() { } @@ -54,87 +50,86 @@ public class HSSFColor implements Color * it takes to create it once per request but you will not hold onto it * if you have none of those requests. * - * @return a hashtable containing all colors mapped to their excel-style - * pallette index + * @return a hashtable containing all colors keyed by Integer excel-style palette indexes */ public final static Hashtable getIndexHash() { - Hashtable hash = new Hashtable(PALETTE_SIZE); + return createColorsByIndexMap(); + } - hash.put(new Integer(HSSFColor.BLACK.index), new HSSFColor.BLACK()); - hash.put(new Integer(HSSFColor.BROWN.index), new HSSFColor.BROWN()); - hash.put(new Integer(HSSFColor.OLIVE_GREEN.index), - new HSSFColor.OLIVE_GREEN()); - hash.put(new Integer(HSSFColor.DARK_GREEN.index), new HSSFColor.DARK_GREEN()); - hash.put(new Integer(HSSFColor.DARK_TEAL.index), new HSSFColor.DARK_TEAL()); - hash.put(new Integer(HSSFColor.DARK_BLUE.index), new HSSFColor.DARK_BLUE()); - hash.put(new Integer(HSSFColor.DARK_BLUE.index2), new HSSFColor.DARK_BLUE()); - hash.put(new Integer(HSSFColor.INDIGO.index), new HSSFColor.INDIGO()); - hash.put(new Integer(HSSFColor.GREY_80_PERCENT.index), - new HSSFColor.GREY_80_PERCENT()); - hash.put(new Integer(HSSFColor.ORANGE.index), new HSSFColor.ORANGE()); - hash.put(new Integer(HSSFColor.DARK_YELLOW.index), - new HSSFColor.DARK_YELLOW()); - hash.put(new Integer(HSSFColor.GREEN.index), new HSSFColor.GREEN()); - hash.put(new Integer(HSSFColor.TEAL.index), new HSSFColor.TEAL()); - hash.put(new Integer(HSSFColor.TEAL.index2), new HSSFColor.TEAL()); - hash.put(new Integer(HSSFColor.BLUE.index), new HSSFColor.BLUE()); - hash.put(new Integer(HSSFColor.BLUE.index2), new HSSFColor.BLUE()); - hash.put(new Integer(HSSFColor.BLUE_GREY.index), new HSSFColor.BLUE_GREY()); - hash.put(new Integer(HSSFColor.GREY_50_PERCENT.index), - new HSSFColor.GREY_50_PERCENT()); - hash.put(new Integer(HSSFColor.RED.index), new HSSFColor.RED()); - hash.put(new Integer(HSSFColor.LIGHT_ORANGE.index), - new HSSFColor.LIGHT_ORANGE()); - hash.put(new Integer(HSSFColor.LIME.index), new HSSFColor.LIME()); - hash.put(new Integer(HSSFColor.SEA_GREEN.index), new HSSFColor.SEA_GREEN()); - hash.put(new Integer(HSSFColor.AQUA.index), new HSSFColor.AQUA()); - hash.put(new Integer(HSSFColor.LIGHT_BLUE.index), new HSSFColor.LIGHT_BLUE()); - hash.put(new Integer(HSSFColor.VIOLET.index), new HSSFColor.VIOLET()); - hash.put(new Integer(HSSFColor.VIOLET.index2), new HSSFColor.VIOLET()); - hash.put(new Integer(HSSFColor.GREY_40_PERCENT.index), - new HSSFColor.GREY_40_PERCENT()); - hash.put(new Integer(HSSFColor.PINK.index), new HSSFColor.PINK()); - hash.put(new Integer(HSSFColor.PINK.index2), new HSSFColor.PINK()); - hash.put(new Integer(HSSFColor.GOLD.index), new HSSFColor.GOLD()); - hash.put(new Integer(HSSFColor.YELLOW.index), new HSSFColor.YELLOW()); - hash.put(new Integer(HSSFColor.YELLOW.index2), new HSSFColor.YELLOW()); - hash.put(new Integer(HSSFColor.BRIGHT_GREEN.index), - new HSSFColor.BRIGHT_GREEN()); - hash.put(new Integer(HSSFColor.BRIGHT_GREEN.index2), - new HSSFColor.BRIGHT_GREEN()); - hash.put(new Integer(HSSFColor.TURQUOISE.index), new HSSFColor.TURQUOISE()); - hash.put(new Integer(HSSFColor.TURQUOISE.index2), new HSSFColor.TURQUOISE()); - hash.put(new Integer(HSSFColor.DARK_RED.index), new HSSFColor.DARK_RED()); - hash.put(new Integer(HSSFColor.DARK_RED.index2), new HSSFColor.DARK_RED()); - hash.put(new Integer(HSSFColor.SKY_BLUE.index), new HSSFColor.SKY_BLUE()); - hash.put(new Integer(HSSFColor.PLUM.index), new HSSFColor.PLUM()); - hash.put(new Integer(HSSFColor.PLUM.index2), new HSSFColor.PLUM()); - hash.put(new Integer(HSSFColor.GREY_25_PERCENT.index), - new HSSFColor.GREY_25_PERCENT()); - hash.put(new Integer(HSSFColor.ROSE.index), new HSSFColor.ROSE()); - hash.put(new Integer(HSSFColor.LIGHT_YELLOW.index), - new HSSFColor.LIGHT_YELLOW()); - hash.put(new Integer(HSSFColor.LIGHT_GREEN.index), - new HSSFColor.LIGHT_GREEN()); - hash.put(new Integer(HSSFColor.LIGHT_TURQUOISE.index), - new HSSFColor.LIGHT_TURQUOISE()); - hash.put(new Integer(HSSFColor.LIGHT_TURQUOISE.index2), - new HSSFColor.LIGHT_TURQUOISE()); - hash.put(new Integer(HSSFColor.PALE_BLUE.index), new HSSFColor.PALE_BLUE()); - hash.put(new Integer(HSSFColor.LAVENDER.index), new HSSFColor.LAVENDER()); - hash.put(new Integer(HSSFColor.WHITE.index), new HSSFColor.WHITE()); - hash.put(new Integer(HSSFColor.CORNFLOWER_BLUE.index), - new HSSFColor.CORNFLOWER_BLUE()); - hash.put(new Integer(HSSFColor.LEMON_CHIFFON.index), - new HSSFColor.LEMON_CHIFFON()); - hash.put(new Integer(HSSFColor.MAROON.index), new HSSFColor.MAROON()); - hash.put(new Integer(HSSFColor.ORCHID.index), new HSSFColor.ORCHID()); - hash.put(new Integer(HSSFColor.CORAL.index), new HSSFColor.CORAL()); - hash.put(new Integer(HSSFColor.ROYAL_BLUE.index), new HSSFColor.ROYAL_BLUE()); - hash.put(new Integer(HSSFColor.LIGHT_CORNFLOWER_BLUE.index), - new HSSFColor.LIGHT_CORNFLOWER_BLUE()); - return hash; + private static Hashtable createColorsByIndexMap() { + HSSFColor[] colors = getAllColors(); + Hashtable result = new Hashtable(colors.length * 3 / 2); + + for (int i = 0; i < colors.length; i++) { + HSSFColor color = colors[i]; + + Integer index1 = new Integer(color.getIndex()); + if (result.containsKey(index1)) { + HSSFColor prevColor = (HSSFColor)result.get(index1); + throw new RuntimeException("Dup color index (" + index1 + + ") for colors (" + prevColor.getClass().getName() + + "),(" + color.getClass().getName() + ")"); + } + result.put(index1, color); + } + + for (int i = 0; i < colors.length; i++) { + HSSFColor color = colors[i]; + Integer index2 = getIndex2(color); + if (index2 == null) { + // most colors don't have a second index + continue; + } + if (result.containsKey(index2)) { + if (false) { // Many of the second indexes clash + HSSFColor prevColor = (HSSFColor)result.get(index2); + throw new RuntimeException("Dup color index (" + index2 + + ") for colors (" + prevColor.getClass().getName() + + "),(" + color.getClass().getName() + ")"); + } + } + result.put(index2, color); + } + return result; + } + + private static Integer getIndex2(HSSFColor color) { + + Field f; + try { + f = color.getClass().getDeclaredField("index2"); + } catch (NoSuchFieldException e) { + // can happen because not all colors have a second index + return null; + } + + Short s; + try { + s = (Short) f.get(color); + } catch (IllegalArgumentException e) { + throw new RuntimeException(e); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + return new Integer(s.intValue()); + } + + private static HSSFColor[] getAllColors() { + + return new HSSFColor[] { + new BLACK(), new BROWN(), new OLIVE_GREEN(), new DARK_GREEN(), + new DARK_TEAL(), new DARK_BLUE(), new INDIGO(), new GREY_80_PERCENT(), + new ORANGE(), new DARK_YELLOW(), new GREEN(), new TEAL(), new BLUE(), + new BLUE_GREY(), new GREY_50_PERCENT(), new RED(), new LIGHT_ORANGE(), new LIME(), + new SEA_GREEN(), new AQUA(), new LIGHT_BLUE(), new VIOLET(), new GREY_40_PERCENT(), + new PINK(), new GOLD(), new YELLOW(), new BRIGHT_GREEN(), new TURQUOISE(), + new DARK_RED(), new SKY_BLUE(), new PLUM(), new GREY_25_PERCENT(), new ROSE(), + new LIGHT_YELLOW(), new LIGHT_GREEN(), new LIGHT_TURQUOISE(), new PALE_BLUE(), + new LAVENDER(), new WHITE(), new CORNFLOWER_BLUE(), new LEMON_CHIFFON(), + new MAROON(), new ORCHID(), new CORAL(), new ROYAL_BLUE(), + new LIGHT_CORNFLOWER_BLUE(), new TAN(), + }; } /** @@ -144,73 +139,28 @@ public class HSSFColor implements Color * it takes to create it once per request but you will not hold onto it * if you have none of those requests. * - * @return a hashtable containing all colors mapped to their gnumeric-like - * triplet string + * @return a hashtable containing all colors keyed by String gnumeric-like triplets */ - public final static Hashtable getTripletHash() { - Hashtable hash = new Hashtable(DISTINCT_COLOR_COUNT); + return createColorsByHexStringMap(); + } - hash.put(HSSFColor.BLACK.hexString, new HSSFColor.BLACK()); - hash.put(HSSFColor.BROWN.hexString, new HSSFColor.BROWN()); - hash.put(HSSFColor.OLIVE_GREEN.hexString, - new HSSFColor.OLIVE_GREEN()); - hash.put(HSSFColor.DARK_GREEN.hexString, new HSSFColor.DARK_GREEN()); - hash.put(HSSFColor.DARK_TEAL.hexString, new HSSFColor.DARK_TEAL()); - hash.put(HSSFColor.DARK_BLUE.hexString, new HSSFColor.DARK_BLUE()); - hash.put(HSSFColor.INDIGO.hexString, new HSSFColor.INDIGO()); - hash.put(HSSFColor.GREY_80_PERCENT.hexString, - new HSSFColor.GREY_80_PERCENT()); - hash.put(HSSFColor.ORANGE.hexString, new HSSFColor.ORANGE()); - hash.put(HSSFColor.DARK_YELLOW.hexString, - new HSSFColor.DARK_YELLOW()); - hash.put(HSSFColor.GREEN.hexString, new HSSFColor.GREEN()); - hash.put(HSSFColor.TEAL.hexString, new HSSFColor.TEAL()); - hash.put(HSSFColor.BLUE.hexString, new HSSFColor.BLUE()); - hash.put(HSSFColor.BLUE_GREY.hexString, new HSSFColor.BLUE_GREY()); - hash.put(HSSFColor.GREY_50_PERCENT.hexString, - new HSSFColor.GREY_50_PERCENT()); - hash.put(HSSFColor.RED.hexString, new HSSFColor.RED()); - hash.put(HSSFColor.LIGHT_ORANGE.hexString, - new HSSFColor.LIGHT_ORANGE()); - hash.put(HSSFColor.LIME.hexString, new HSSFColor.LIME()); - hash.put(HSSFColor.SEA_GREEN.hexString, new HSSFColor.SEA_GREEN()); - hash.put(HSSFColor.AQUA.hexString, new HSSFColor.AQUA()); - hash.put(HSSFColor.LIGHT_BLUE.hexString, new HSSFColor.LIGHT_BLUE()); - hash.put(HSSFColor.VIOLET.hexString, new HSSFColor.VIOLET()); - hash.put(HSSFColor.GREY_40_PERCENT.hexString, - new HSSFColor.GREY_40_PERCENT()); - hash.put(HSSFColor.PINK.hexString, new HSSFColor.PINK()); - hash.put(HSSFColor.GOLD.hexString, new HSSFColor.GOLD()); - hash.put(HSSFColor.YELLOW.hexString, new HSSFColor.YELLOW()); - hash.put(HSSFColor.BRIGHT_GREEN.hexString, - new HSSFColor.BRIGHT_GREEN()); - hash.put(HSSFColor.TURQUOISE.hexString, new HSSFColor.TURQUOISE()); - hash.put(HSSFColor.DARK_RED.hexString, new HSSFColor.DARK_RED()); - hash.put(HSSFColor.SKY_BLUE.hexString, new HSSFColor.SKY_BLUE()); - hash.put(HSSFColor.PLUM.hexString, new HSSFColor.PLUM()); - hash.put(HSSFColor.GREY_25_PERCENT.hexString, - new HSSFColor.GREY_25_PERCENT()); - hash.put(HSSFColor.ROSE.hexString, new HSSFColor.ROSE()); - hash.put(HSSFColor.LIGHT_YELLOW.hexString, - new HSSFColor.LIGHT_YELLOW()); - hash.put(HSSFColor.LIGHT_GREEN.hexString, - new HSSFColor.LIGHT_GREEN()); - hash.put(HSSFColor.LIGHT_TURQUOISE.hexString, - new HSSFColor.LIGHT_TURQUOISE()); - hash.put(HSSFColor.PALE_BLUE.hexString, new HSSFColor.PALE_BLUE()); - hash.put(HSSFColor.LAVENDER.hexString, new HSSFColor.LAVENDER()); - hash.put(HSSFColor.WHITE.hexString, new HSSFColor.WHITE()); - hash.put(HSSFColor.CORNFLOWER_BLUE.hexString, new HSSFColor.CORNFLOWER_BLUE()); - hash.put(HSSFColor.LEMON_CHIFFON.hexString, new HSSFColor.LEMON_CHIFFON()); - hash.put(HSSFColor.MAROON.hexString, new HSSFColor.MAROON()); - hash.put(HSSFColor.ORCHID.hexString, new HSSFColor.ORCHID()); - hash.put(HSSFColor.CORAL.hexString, new HSSFColor.CORAL()); - hash.put(HSSFColor.ROYAL_BLUE.hexString, new HSSFColor.ROYAL_BLUE()); - hash.put(HSSFColor.LIGHT_CORNFLOWER_BLUE.hexString, - new HSSFColor.LIGHT_CORNFLOWER_BLUE()); - return hash; + private static Hashtable createColorsByHexStringMap() { + HSSFColor[] colors = getAllColors(); + Hashtable result = new Hashtable(colors.length * 3 / 2); + + for (int i = 0; i < colors.length; i++) { + HSSFColor color = colors[i]; + + String hexString = color.getHexString(); + if (result.containsKey(hexString)) { + throw new RuntimeException("Dup color hexString (" + hexString + + ") for color (" + color.getClass().getName() + ")"); + } + result.put(hexString, color); + } + return result; } /** @@ -1492,7 +1442,7 @@ public class HSSFColor implements Color return hexString; } } - + /** * Class CORNFLOWER_BLUE */ @@ -1521,8 +1471,8 @@ public class HSSFColor implements Color return hexString; } } - - + + /** * Class LEMON_CHIFFON */ @@ -1551,7 +1501,7 @@ public class HSSFColor implements Color return hexString; } } - + /** * Class MAROON */ @@ -1580,7 +1530,7 @@ public class HSSFColor implements Color return hexString; } } - + /** * Class ORCHID */ @@ -1609,7 +1559,7 @@ public class HSSFColor implements Color return hexString; } } - + /** * Class CORAL */ @@ -1638,7 +1588,7 @@ public class HSSFColor implements Color return hexString; } } - + /** * Class ROYAL_BLUE */ @@ -1667,7 +1617,7 @@ public class HSSFColor implements Color return hexString; } } - + /** * Class LIGHT_CORNFLOWER_BLUE */ @@ -1696,19 +1646,19 @@ public class HSSFColor implements Color return hexString; } } - + /** * Special Default/Normal/Automatic color. *

Note: This class is NOT in the default HashTables returned by HSSFColor. * The index is a special case which is interpreted in the various setXXXColor calls. - * + * * @author Jason * */ public final static class AUTOMATIC extends HSSFColor { - private static HSSFColor instance = new AUTOMATIC(); - + private static HSSFColor instance = new AUTOMATIC(); + public final static short index = 0x40; public short getIndex() @@ -1725,7 +1675,7 @@ public class HSSFColor implements Color { return BLACK.hexString; } - + public static HSSFColor getInstance() { return instance; } diff --git a/src/java/org/apache/poi/ss/usermodel/DateUtil.java b/src/java/org/apache/poi/ss/usermodel/DateUtil.java index 9335de35d..0a9bdcfe8 100644 --- a/src/java/org/apache/poi/ss/usermodel/DateUtil.java +++ b/src/java/org/apache/poi/ss/usermodel/DateUtil.java @@ -226,7 +226,9 @@ public class DateUtil // Otherwise, check it's only made up, in any case, of: // y m d h s - / , . : - if(fs.matches("^[yYmMdDhHsS\\-/,. :]+$")) { + // optionally followed by AM/PM + // optionally followed by AM/PM + if(fs.matches("^[yYmMdDhHsS\\-/,. :]+[ampAMP]*$")) { return true; } diff --git a/src/resources/main/org/apache/poi/hssf/record/formula/function/functionMetadata-asGenerated.txt b/src/resources/main/org/apache/poi/hssf/record/formula/function/functionMetadata-asGenerated.txt index 475131e1c..8a85f4284 100644 --- a/src/resources/main/org/apache/poi/hssf/record/formula/function/functionMetadata-asGenerated.txt +++ b/src/resources/main/org/apache/poi/hssf/record/formula/function/functionMetadata-asGenerated.txt @@ -14,7 +14,7 @@ # limitations under the License. # Created by (org.apache.poi.hssf.record.formula.function.ExcelFileFormatDocFunctionExtractor) -# from source file 'excelfileformat.odt' (size=355750, crc=0x2FAEA65A) +# from source file 'excelfileformat.odt' (size=356107, md5=0x8f789cb6e75594caf068f8e193004ef4) # #Columns: (index, name, minParams, maxParams, returnClass, paramClasses, isVolatile, hasFootnote ) @@ -37,7 +37,7 @@ 15 SIN 1 1 V V 16 COS 1 1 V V 17 TAN 1 1 V V -18 ARCTAN 1 1 V V +18 ATAN 1 1 V V 19 PI 0 0 V - 20 SQRT 1 1 V V 21 EXP 1 1 V V @@ -141,8 +141,8 @@ 169 COUNTA 0 30 V R 183 PRODUCT 0 30 V R 184 FACT 1 1 V V -191 DPRODUCT 3 3 V R R R -192 ISNONTEXT 1 1 V V +189 DPRODUCT 3 3 V R R R +190 ISNONTEXT 1 1 V V 193 STDEVP 1 30 V R 194 VARP 1 30 V R 195 DSTDEVP 3 3 V R R R @@ -184,6 +184,8 @@ 244 INFO 1 1 V V # New Built-In Sheet Functions in BIFF4 14 FIXED 2 3 V V V V x +204 USDOLLAR 1 2 V V V x +215 DBCS 1 1 V V x 216 RANK 2 3 V V R V 247 DB 4 5 V V V V V V 252 FREQUENCY 2 2 A R R diff --git a/src/resources/main/org/apache/poi/hssf/record/formula/function/functionMetadata.txt b/src/resources/main/org/apache/poi/hssf/record/formula/function/functionMetadata.txt index 6902027de..8a85f4284 100644 --- a/src/resources/main/org/apache/poi/hssf/record/formula/function/functionMetadata.txt +++ b/src/resources/main/org/apache/poi/hssf/record/formula/function/functionMetadata.txt @@ -14,11 +14,9 @@ # limitations under the License. # Created by (org.apache.poi.hssf.record.formula.function.ExcelFileFormatDocFunctionExtractor) -# from source file 'excelfileformat.odt' (size=355750, crc=0x2FAEA65A) +# from source file 'excelfileformat.odt' (size=356107, md5=0x8f789cb6e75594caf068f8e193004ef4) # #Columns: (index, name, minParams, maxParams, returnClass, paramClasses, isVolatile, hasFootnote ) -# -# + some manual edits ! # Built-In Sheet Functions in BIFF2 0 COUNT 0 30 V R @@ -186,7 +184,7 @@ 244 INFO 1 1 V V # New Built-In Sheet Functions in BIFF4 14 FIXED 2 3 V V V V x -204 USDOLLAR 1 1 V V x +204 USDOLLAR 1 2 V V V x 215 DBCS 1 1 V V x 216 RANK 2 3 V V R V 247 DB 4 5 V V V V V V diff --git a/src/scratchpad/src/org/apache/poi/hdgf/HDGFDiagram.java b/src/scratchpad/src/org/apache/poi/hdgf/HDGFDiagram.java index af6616307..764b8e3f5 100644 --- a/src/scratchpad/src/org/apache/poi/hdgf/HDGFDiagram.java +++ b/src/scratchpad/src/org/apache/poi/hdgf/HDGFDiagram.java @@ -18,6 +18,7 @@ package org.apache.poi.hdgf; import java.io.FileInputStream; import java.io.IOException; +import java.io.OutputStream; import org.apache.poi.POIDocument; import org.apache.poi.hdgf.chunks.ChunkFactory; @@ -27,6 +28,7 @@ import org.apache.poi.hdgf.streams.PointerContainingStream; import org.apache.poi.hdgf.streams.Stream; import org.apache.poi.hdgf.streams.StringsStream; import org.apache.poi.hdgf.streams.TrailerStream; +import org.apache.poi.poifs.filesystem.DirectoryNode; import org.apache.poi.poifs.filesystem.DocumentEntry; import org.apache.poi.poifs.filesystem.POIFSFileSystem; import org.apache.poi.util.LittleEndian; @@ -53,14 +55,17 @@ public class HDGFDiagram extends POIDocument { private PointerFactory ptrFactory; public HDGFDiagram(POIFSFileSystem fs) throws IOException { - super(fs); + this(fs.getRoot(), fs); + } + public HDGFDiagram(DirectoryNode dir, POIFSFileSystem fs) throws IOException { + super(dir, fs); DocumentEntry docProps = - (DocumentEntry)filesystem.getRoot().getEntry("VisioDocument"); + (DocumentEntry)dir.getEntry("VisioDocument"); // Grab the document stream _docstream = new byte[docProps.getSize()]; - filesystem.createDocumentInputStream("VisioDocument").read(_docstream); + dir.createDocumentInputStream("VisioDocument").read(_docstream); // Read in the common POI streams readProperties(); @@ -149,6 +154,10 @@ public class HDGFDiagram extends POIDocument { } } + public void write(OutputStream out) { + throw new IllegalStateException("Writing is not yet implemented, see http://poi.apache.org/hdgf/"); + } + /** * For testing only */ diff --git a/src/scratchpad/src/org/apache/poi/hwpf/model/FSPATable.java b/src/scratchpad/src/org/apache/poi/hwpf/model/FSPATable.java index 58c69ff4b..812ab1a4a 100644 --- a/src/scratchpad/src/org/apache/poi/hwpf/model/FSPATable.java +++ b/src/scratchpad/src/org/apache/poi/hwpf/model/FSPATable.java @@ -21,17 +21,18 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; +import java.util.Map; /** * This class holds all the FSPA (File Shape Address) structures. * * @author Squeeself */ -public class FSPATable +public final class FSPATable { - protected ArrayList shapes = new ArrayList(); - protected HashMap cps = new HashMap(); - protected List _text; + private final List _shapes = new ArrayList(); + private final Map _shapeIndexesByPropertyStart = new HashMap(); + private final List _text; public FSPATable(byte[] tableStream, int fcPlcspa, int lcbPlcspa, List tpt) { @@ -46,32 +47,35 @@ public class FSPATable GenericPropertyNode property = plex.getProperty(i); FSPA fspa = new FSPA(property.getBytes(), 0); - shapes.add(fspa); - cps.put(Integer.valueOf(property.getStart()), Integer.valueOf(i)); + _shapes.add(fspa); + _shapeIndexesByPropertyStart.put(new Integer(property.getStart()), new Integer(i)); } } public FSPA getFspaFromCp(int cp) { - Integer idx = (Integer)cps.get(Integer.valueOf(cp)); - if (idx == null) + Integer idx = (Integer)_shapeIndexesByPropertyStart.get(new Integer(cp)); + if (idx == null) { return null; - return (FSPA)shapes.get(idx.intValue()); + } + return (FSPA)_shapes.get(idx.intValue()); } - public List getShapes() + public FSPA[] getShapes() { - return shapes; + FSPA[] result = new FSPA[_shapes.size()]; + _shapes.toArray(result); + return result; } public String toString() { StringBuffer buf = new StringBuffer(); - buf.append("[FPSA PLC size=").append(shapes.size()).append("]\n"); - for (Iterator it = cps.keySet().iterator(); it.hasNext(); ) + buf.append("[FPSA PLC size=").append(_shapes.size()).append("]\n"); + for (Iterator it = _shapeIndexesByPropertyStart.keySet().iterator(); it.hasNext(); ) { Integer i = (Integer) it.next(); - FSPA fspa = (FSPA) shapes.get(((Integer)cps.get(i)).intValue()); + FSPA fspa = (FSPA) _shapes.get(((Integer)_shapeIndexesByPropertyStart.get(i)).intValue()); buf.append(" [FC: ").append(i.toString()).append("] "); buf.append(fspa.toString()); buf.append("\n"); diff --git a/src/scratchpad/src/org/apache/poi/hwpf/model/FileInformationBlock.java b/src/scratchpad/src/org/apache/poi/hwpf/model/FileInformationBlock.java index 48e6d78b3..887e13d82 100644 --- a/src/scratchpad/src/org/apache/poi/hwpf/model/FileInformationBlock.java +++ b/src/scratchpad/src/org/apache/poi/hwpf/model/FileInformationBlock.java @@ -294,6 +294,16 @@ public class FileInformationBlock extends FIBAbstractType _longHandler.setLong(FIBLongHandler.CBMAC, cbMac); } + public int getCcpText() + { + return _longHandler.getLong(FIBLongHandler.CCPTEXT); + } + + public void setCcpText(int ccpText) + { + _longHandler.setLong(FIBLongHandler.CCPTEXT, ccpText); + } + public void clearOffsetsSizes() { _fieldHandler.clearFields(); diff --git a/src/scratchpad/src/org/apache/poi/hwpf/model/TextPiece.java b/src/scratchpad/src/org/apache/poi/hwpf/model/TextPiece.java index 67c634d9f..bc33954df 100644 --- a/src/scratchpad/src/org/apache/poi/hwpf/model/TextPiece.java +++ b/src/scratchpad/src/org/apache/poi/hwpf/model/TextPiece.java @@ -90,12 +90,20 @@ public class TextPiece extends PropertyNode implements Comparable public void adjustForDelete(int start, int length) { + + if (usesUnicode()) { + + start /= 2; + length /= 2; + } + int myStart = getStart(); int myEnd = getEnd(); int end = start + length; /* do we have to delete from this text piece? */ if (start <= myEnd && end >= myStart) { + /* find where the deleted area overlaps with this text piece */ int overlapStart = Math.max(myStart, start); int overlapEnd = Math.min(myEnd, end); diff --git a/src/scratchpad/src/org/apache/poi/hwpf/usermodel/Range.java b/src/scratchpad/src/org/apache/poi/hwpf/usermodel/Range.java index f2d9a615f..85592a92a 100644 --- a/src/scratchpad/src/org/apache/poi/hwpf/usermodel/Range.java +++ b/src/scratchpad/src/org/apache/poi/hwpf/usermodel/Range.java @@ -226,6 +226,25 @@ public class Range } } + /** + * Does any TextPiece in this Range use unicode? + * + * @return true if it does and false if it doesn't + */ + public boolean usesUnicode() { + + initText(); + + for (int i = _textStart; i < _textEnd; i++) + { + TextPiece piece = (TextPiece)_text.get(i); + if (piece.usesUnicode()) + return true; + } + + return false; + } + /** * Gets the text that this Range contains. * @@ -306,13 +325,19 @@ public class Range // Since this is the first item in our list, it is safe to assume that // _start >= tp.getStart() int insertIndex = _start - tp.getStart(); + if (tp.usesUnicode()) + insertIndex /= 2; sb.insert(insertIndex, text); + int adjustedLength = _doc.getTextTable().adjustForInsert(_textStart, text.length()); _doc.getCharacterTable().adjustForInsert(_charStart, adjustedLength); _doc.getParagraphTable().adjustForInsert(_parStart, adjustedLength); _doc.getSectionTable().adjustForInsert(_sectionStart, adjustedLength); adjustForInsert(text.length()); + // update the FIB.CCPText field + adjustFIB(text.length()); + return getCharacterRun(0); } @@ -489,6 +514,7 @@ public class Range public void delete() { + initAll(); int numSections = _sections.size(); @@ -519,6 +545,12 @@ public class Range TextPiece piece = (TextPiece)_text.get(x); piece.adjustForDelete(_start, _end - _start); } + + // update the FIB.CCPText field + if (usesUnicode()) + adjustFIB(-((_end - _start) / 2)); + else + adjustFIB(-(_end - _start)); } /** @@ -827,6 +859,19 @@ public class Range _sectionRangeFound = false; } + /** + * Adjust the value of FIB.CCPText after an insert or a delete... + * + * @param adjustment The (signed) value that should be added to FIB.CCPText + */ + protected void adjustFIB(int adjustment) { + + // update the FIB.CCPText field (this should happen once per adjustment, so we don't want it in + // adjustForInsert() or it would get updated multiple times if the range has a parent) + // without this, OpenOffice.org (v. 2.2.x) does not see all the text in the document + _doc.getFileInformationBlock().setCcpText(_doc.getFileInformationBlock().getCcpText() + adjustment); + } + /** * adjust this range after an insert happens. * @param length the length to adjust for @@ -834,6 +879,7 @@ public class Range private void adjustForInsert(int length) { _end += length; + reset(); Range parent = (Range)_parent.get(); if (parent != null) @@ -842,4 +888,14 @@ public class Range } } + + public int getStartOffset() { + + return _start; + } + + public int getEndOffset() { + + return _end; + } } diff --git a/src/testcases/org/apache/poi/hssf/data/ex45046-21984.xls b/src/testcases/org/apache/poi/hssf/data/ex45046-21984.xls new file mode 100644 index 000000000..96ef3ee7c Binary files /dev/null and b/src/testcases/org/apache/poi/hssf/data/ex45046-21984.xls differ diff --git a/src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java b/src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java index 1895705a5..f2821140f 100644 --- a/src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java +++ b/src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java @@ -55,120 +55,99 @@ import org.apache.poi.hssf.usermodel.HSSFWorkbook; /** * Test the low level formula parser functionality. High level tests are to - * be done via usermodel/HSSFCell.setFormulaValue() . - * Some tests are also done in scratchpad, if they need - * HSSFFormulaEvaluator, which is there + * be done via usermodel/HSSFCell.setFormulaValue(). */ public final class TestFormulaParser extends TestCase { - /** - * @return parsed token array already confirmed not null - */ - private static Ptg[] parseFormula(String s) { - // TODO - replace multiple copies of this code with calls to this method - FormulaParser fp = new FormulaParser(s, null); - fp.parse(); - Ptg[] result = fp.getRPNPtg(); - assertNotNull("Ptg array should not be null", result); - return result; - } - - public void testSimpleFormula() { - FormulaParser fp = new FormulaParser("2+2",null); - fp.parse(); - Ptg[] ptgs = fp.getRPNPtg(); - assertTrue("three tokens expected, got "+ptgs.length,ptgs.length == 3); - } - public void testFormulaWithSpace1() { - FormulaParser fp = new FormulaParser(" 2 + 2 ",null); - fp.parse(); - Ptg[] ptgs = fp.getRPNPtg(); - assertTrue("three tokens expected, got "+ptgs.length,ptgs.length == 3); - assertTrue("",(ptgs[0] instanceof IntPtg)); - assertTrue("",(ptgs[1] instanceof IntPtg)); - assertTrue("",(ptgs[2] instanceof AddPtg)); - } - - public void testFormulaWithSpace2() { - Ptg[] ptgs; - FormulaParser fp; - fp = new FormulaParser("2+ sum( 3 , 4) ",null); - fp.parse(); - ptgs = fp.getRPNPtg(); - assertTrue("five tokens expected, got "+ptgs.length,ptgs.length == 5); - } - - public void testFormulaWithSpaceNRef() { - Ptg[] ptgs; - FormulaParser fp; - fp = new FormulaParser("sum( A2:A3 )",null); - fp.parse(); - ptgs = fp.getRPNPtg(); - assertTrue("two tokens expected, got "+ptgs.length,ptgs.length == 2); - } - - public void testFormulaWithString() { - Ptg[] ptgs; - FormulaParser fp; - fp = new FormulaParser("\"hello\" & \"world\" ",null); - fp.parse(); - ptgs = fp.getRPNPtg(); - assertTrue("three token expected, got " + ptgs.length, ptgs.length == 3); - } - - public void testTRUE() throws Exception { - FormulaParser fp = new FormulaParser("TRUE", null); - fp.parse(); - Ptg[] asts = fp.getRPNPtg(); - assertEquals(1, asts.length); - BoolPtg flag = (BoolPtg) asts[0]; - assertEquals(true, flag.getValue()); - } - - public void testYN() throws Exception { - final String yn = "IF(TRUE,\"Y\",\"N\")"; - FormulaParser fp = new FormulaParser(yn, null); - fp.parse(); - Ptg[] asts = fp.getRPNPtg(); - assertEquals(7, asts.length); - - BoolPtg flag = (BoolPtg) asts[0]; - AttrPtg funif = (AttrPtg) asts[1]; - StringPtg y = (StringPtg) asts[2]; - AttrPtg goto1 = (AttrPtg) asts[3]; - StringPtg n = (StringPtg) asts[4]; - - - assertEquals(true, flag.getValue()); - assertEquals("Y", y.getValue()); - assertEquals("N", n.getValue()); - assertEquals("IF", funif.toFormulaString((HSSFWorkbook) null)); - assertTrue("Goto ptg exists", goto1.isGoto()); - } - - public void testSimpleIf() throws Exception { - final String simpleif = "IF(1=1,0,1)"; - FormulaParser fp = new FormulaParser(simpleif, null); + /** + * @return parsed token array already confirmed not null + */ + private static Ptg[] parseFormula(String s) { + FormulaParser fp = new FormulaParser(s, null); fp.parse(); - Ptg[] asts = fp.getRPNPtg(); - assertEquals(9, asts.length); + Ptg[] result = fp.getRPNPtg(); + assertNotNull("Ptg array should not be null", result); + return result; + } + + public void testSimpleFormula() { + Ptg[] ptgs = parseFormula("2+2"); + assertEquals(3, ptgs.length); + } + + public void testFormulaWithSpace1() { + Ptg[] ptgs = parseFormula(" 2 + 2 "); + assertEquals(3, ptgs.length); + assertTrue("",(ptgs[0] instanceof IntPtg)); + assertTrue("",(ptgs[1] instanceof IntPtg)); + assertTrue("",(ptgs[2] instanceof AddPtg)); + } + + public void testFormulaWithSpace2() { + Ptg[] ptgs = parseFormula("2+ sum( 3 , 4) "); + assertEquals(5, ptgs.length); + } + + public void testFormulaWithSpaceNRef() { + Ptg[] ptgs = parseFormula("sum( A2:A3 )"); + assertEquals(2, ptgs.length); + } + + public void testFormulaWithString() { + Ptg[] ptgs = parseFormula("\"hello\" & \"world\" "); + assertEquals(3, ptgs.length); + } + + public void testTRUE() { + Ptg[] ptgs = parseFormula("TRUE"); + assertEquals(1, ptgs.length); + BoolPtg flag = (BoolPtg) ptgs[0]; + assertEquals(true, flag.getValue()); + } + + public void testYN() { + Ptg[] ptgs = parseFormula("IF(TRUE,\"Y\",\"N\")"); + assertEquals(7, ptgs.length); + + BoolPtg flag = (BoolPtg) ptgs[0]; + AttrPtg funif = (AttrPtg) ptgs[1]; + StringPtg y = (StringPtg) ptgs[2]; + AttrPtg goto1 = (AttrPtg) ptgs[3]; + StringPtg n = (StringPtg) ptgs[4]; + + + assertEquals(true, flag.getValue()); + assertEquals("Y", y.getValue()); + assertEquals("N", n.getValue()); + assertEquals("IF", funif.toFormulaString((HSSFWorkbook) null)); + assertTrue("Goto ptg exists", goto1.isGoto()); + } + + public void testSimpleIf() { + String formula = "IF(1=1,0,1)"; + + Class[] expectedClasses = { + IntPtg.class, + IntPtg.class, + EqualPtg.class, + AttrPtg.class, + IntPtg.class, + AttrPtg.class, + IntPtg.class, + AttrPtg.class, + FuncVarPtg.class, + }; + confirmTokenClasses(formula, expectedClasses); - IntPtg op1 = (IntPtg) asts[0]; - IntPtg op2 = (IntPtg) asts[1]; - EqualPtg eq = (EqualPtg) asts[2]; - AttrPtg ifPtg = (AttrPtg) asts[3]; - IntPtg res1 = (IntPtg) asts[4]; - - AttrPtg ptgGoto= (AttrPtg) asts[5]; - assertEquals("Goto 1 Length", (short)10, ptgGoto.getData()); - - IntPtg res2 = (IntPtg) asts[6]; - AttrPtg ptgGoto2 = (AttrPtg) asts[7]; - assertEquals("Goto 2 Length", (short)3, ptgGoto2.getData()); - - assertEquals("If FALSE offset", (short)7, ifPtg.getData()); - - FuncVarPtg funcPtg = (FuncVarPtg)asts[8]; + Ptg[] ptgs = parseFormula(formula); + + AttrPtg ifPtg = (AttrPtg) ptgs[3]; + AttrPtg ptgGoto= (AttrPtg) ptgs[5]; + assertEquals("Goto 1 Length", 10, ptgGoto.getData()); + + AttrPtg ptgGoto2 = (AttrPtg) ptgs[7]; + assertEquals("Goto 2 Length", 3, ptgGoto2.getData()); + assertEquals("If FALSE offset", 7, ifPtg.getData()); } /** @@ -176,753 +155,714 @@ public final class TestFormulaParser extends TestCase { * */ public void testNestedFunctionIf() { - String function = "IF(A1=B1,AVERAGE(A1:B1),AVERAGE(A2:B2))"; + Ptg[] ptgs = parseFormula("IF(A1=B1,AVERAGE(A1:B1),AVERAGE(A2:B2))"); + assertEquals(11, ptgs.length); - FormulaParser fp = new FormulaParser(function, null); - fp.parse(); - Ptg[] asts = fp.getRPNPtg(); - assertEquals("11 Ptgs expected", 11, asts.length); - - assertTrue("IF Attr set correctly", (asts[3] instanceof AttrPtg)); - AttrPtg ifFunc = (AttrPtg)asts[3]; + assertTrue("IF Attr set correctly", (ptgs[3] instanceof AttrPtg)); + AttrPtg ifFunc = (AttrPtg)ptgs[3]; assertTrue("It is not an if", ifFunc.isOptimizedIf()); - - assertTrue("Average Function set correctly", (asts[5] instanceof FuncVarPtg)); + + assertTrue("Average Function set correctly", (ptgs[5] instanceof FuncVarPtg)); } - + public void testIfSingleCondition(){ - String function = "IF(1=1,10)"; + Ptg[] ptgs = parseFormula("IF(1=1,10)"); + assertEquals(7, ptgs.length); - FormulaParser fp = new FormulaParser(function, null); - fp.parse(); - Ptg[] asts = fp.getRPNPtg(); - assertEquals("7 Ptgs expected", 7, asts.length); - - assertTrue("IF Attr set correctly", (asts[3] instanceof AttrPtg)); - AttrPtg ifFunc = (AttrPtg)asts[3]; + assertTrue("IF Attr set correctly", (ptgs[3] instanceof AttrPtg)); + AttrPtg ifFunc = (AttrPtg)ptgs[3]; assertTrue("It is not an if", ifFunc.isOptimizedIf()); - - assertTrue("Single Value is not an IntPtg", (asts[4] instanceof IntPtg)); - IntPtg intPtg = (IntPtg)asts[4]; + + assertTrue("Single Value is not an IntPtg", (ptgs[4] instanceof IntPtg)); + IntPtg intPtg = (IntPtg)ptgs[4]; assertEquals("Result", (short)10, intPtg.getValue()); - - assertTrue("Ptg is not a Variable Function", (asts[6] instanceof FuncVarPtg)); - FuncVarPtg funcPtg = (FuncVarPtg)asts[6]; + + assertTrue("Ptg is not a Variable Function", (ptgs[6] instanceof FuncVarPtg)); + FuncVarPtg funcPtg = (FuncVarPtg)ptgs[6]; assertEquals("Arguments", 2, funcPtg.getNumberOfOperands()); } public void testSumIf() { - String function ="SUMIF(A1:A5,\">4000\",B1:B5)"; - FormulaParser fp = new FormulaParser(function, null); - fp.parse(); - Ptg[] asts = fp.getRPNPtg(); - assertEquals("4 Ptgs expected", 4, asts.length); + Ptg[] ptgs = parseFormula("SUMIF(A1:A5,\">4000\",B1:B5)"); + assertEquals(4, ptgs.length); } - + /** * Bug Reported by xt-jens.riis@nokia.com (Jens Riis) * Refers to Bug #17582 * */ - public void testNonAlphaFormula(){ + public void testNonAlphaFormula() { String currencyCell = "F3"; - String function="\"TOTAL[\"&"+currencyCell+"&\"]\""; + Ptg[] ptgs = parseFormula("\"TOTAL[\"&"+currencyCell+"&\"]\""); + assertEquals(5, ptgs.length); + assertTrue ("Ptg[0] is a string", (ptgs[0] instanceof StringPtg)); + StringPtg firstString = (StringPtg)ptgs[0]; - Ptg[] asts = parseFormula(function); - assertEquals("5 ptgs expected", 5, asts.length); - assertTrue ("Ptg[0] is a string", (asts[0] instanceof StringPtg)); - StringPtg firstString = (StringPtg)asts[0]; - assertEquals("TOTAL[", firstString.getValue()); //the PTG order isn't 100% correct but it still works - dmui } public void testSimpleLogical() { - Ptg[] ptgs = parseFormula("IF(A1=1,\"*\",IF(4<>1,\"first\",\"second\"))"); - assertEquals("Ptg array length", 17, ptgs.length); - + assertEquals(17, ptgs.length); + assertEquals("6th Ptg is not a goto (Attr) ptg",AttrPtg.class,ptgs[5].getClass()); assertEquals("9th Ptg is not a not equal ptg",NotEqualPtg.class,ptgs[8].getClass()); assertEquals("15th Ptg is not the inner IF variable function ptg",FuncVarPtg.class,ptgs[14].getClass()); } - - public void testMacroFunction() { - HSSFWorkbook w = new HSSFWorkbook(); - FormulaParser fp = new FormulaParser("FOO()", w); - fp.parse(); - Ptg[] ptg = fp.getRPNPtg(); - // the name gets encoded as the first arg - NamePtg tname = (NamePtg) ptg[0]; - assertEquals("FOO", tname.toFormulaString(w)); - - AbstractFunctionPtg tfunc = (AbstractFunctionPtg) ptg[1]; - assertTrue(tfunc.isExternalFunction()); - } - - public void testEmbeddedSlash() { - FormulaParser fp = new FormulaParser("HYPERLINK(\"http://www.jakarta.org\",\"Jakarta\")",null); - fp.parse(); - Ptg[] ptg = fp.getRPNPtg(); - assertTrue("first ptg is string",ptg[0] instanceof StringPtg); - assertTrue("second ptg is string",ptg[1] instanceof StringPtg); - } - - public void testConcatenate() { - FormulaParser fp = new FormulaParser("CONCATENATE(\"first\",\"second\")", null); + public void testMacroFunction() { + HSSFWorkbook w = new HSSFWorkbook(); + FormulaParser fp = new FormulaParser("FOO()", w); fp.parse(); Ptg[] ptg = fp.getRPNPtg(); - assertTrue("first ptg is string", ptg[0] instanceof StringPtg); - assertTrue("second ptg is string", ptg[1] instanceof StringPtg); + + // the name gets encoded as the first arg + NamePtg tname = (NamePtg) ptg[0]; + assertEquals("FOO", tname.toFormulaString(w)); + + AbstractFunctionPtg tfunc = (AbstractFunctionPtg) ptg[1]; + assertTrue(tfunc.isExternalFunction()); } - public void testWorksheetReferences() - { - HSSFWorkbook wb = new HSSFWorkbook(); - - wb.createSheet("NoQuotesNeeded"); - wb.createSheet("Quotes Needed Here &#$@"); - - HSSFSheet sheet = wb.createSheet("Test"); - HSSFRow row = sheet.createRow(0); - HSSFCell cell; - - cell = row.createCell((short)0); - cell.setCellFormula("NoQuotesNeeded!A1"); - - cell = row.createCell((short)1); - cell.setCellFormula("'Quotes Needed Here &#$@'!A1"); - } + public void testEmbeddedSlash() { + Ptg[] ptgs = parseFormula("HYPERLINK(\"http://www.jakarta.org\",\"Jakarta\")"); + assertTrue("first ptg is string", ptgs[0] instanceof StringPtg); + assertTrue("second ptg is string", ptgs[1] instanceof StringPtg); + } - public void testUnaryMinus() - { - FormulaParser fp = new FormulaParser("-A1", null); - fp.parse(); - Ptg[] ptg = fp.getRPNPtg(); - assertTrue("got 2 ptgs", ptg.length == 2); - assertTrue("first ptg is reference",ptg[0] instanceof ReferencePtg); - assertTrue("second ptg is Minus",ptg[1] instanceof UnaryMinusPtg); - } + public void testConcatenate() { + Ptg[] ptgs = parseFormula("CONCATENATE(\"first\",\"second\")"); + assertTrue("first ptg is string", ptgs[0] instanceof StringPtg); + assertTrue("second ptg is string", ptgs[1] instanceof StringPtg); + } - public void testUnaryPlus() - { - FormulaParser fp = new FormulaParser("+A1", null); - fp.parse(); - Ptg[] ptg = fp.getRPNPtg(); - assertTrue("got 2 ptgs", ptg.length == 2); - assertTrue("first ptg is reference",ptg[0] instanceof ReferencePtg); - assertTrue("second ptg is Plus",ptg[1] instanceof UnaryPlusPtg); - } + public void testWorksheetReferences() { + HSSFWorkbook wb = new HSSFWorkbook(); - public void testLeadingSpaceInString() - { + wb.createSheet("NoQuotesNeeded"); + wb.createSheet("Quotes Needed Here &#$@"); + + HSSFSheet sheet = wb.createSheet("Test"); + HSSFRow row = sheet.createRow(0); + HSSFCell cell; + + cell = row.createCell((short)0); + cell.setCellFormula("NoQuotesNeeded!A1"); + + cell = row.createCell((short)1); + cell.setCellFormula("'Quotes Needed Here &#$@'!A1"); + } + + public void testUnaryMinus() { + Ptg[] ptgs = parseFormula("-A1"); + assertEquals(2, ptgs.length); + assertTrue("first ptg is reference",ptgs[0] instanceof ReferencePtg); + assertTrue("second ptg is Minus",ptgs[1] instanceof UnaryMinusPtg); + } + + public void testUnaryPlus() { + Ptg[] ptgs = parseFormula("+A1"); + assertEquals(2, ptgs.length); + assertTrue("first ptg is reference",ptgs[0] instanceof ReferencePtg); + assertTrue("second ptg is Plus",ptgs[1] instanceof UnaryPlusPtg); + } + + public void testLeadingSpaceInString() { String value = " hi "; - FormulaParser fp = new FormulaParser("\"" + value + "\"", null); - fp.parse(); - Ptg[] ptg = fp.getRPNPtg(); + Ptg[] ptgs = parseFormula("\"" + value + "\""); - assertTrue("got 1 ptg", ptg.length == 1); - assertTrue("ptg0 is a StringPtg", ptg[0] instanceof StringPtg); - assertTrue("ptg0 contains exact value", ((StringPtg)ptg[0]).getValue().equals(value)); + assertEquals(1, ptgs.length); + assertTrue("ptg0 is a StringPtg", ptgs[0] instanceof StringPtg); + assertTrue("ptg0 contains exact value", ((StringPtg)ptgs[0]).getValue().equals(value)); } - - public void testLookupAndMatchFunctionArgs() - { - FormulaParser fp = new FormulaParser("lookup(A1, A3:A52, B3:B52)", null); - fp.parse(); - Ptg[] ptg = fp.getRPNPtg(); - assertTrue("got 4 ptg", ptg.length == 4); - assertTrue("ptg0 has Value class", ptg[0].getPtgClass() == Ptg.CLASS_VALUE); - - fp = new FormulaParser("match(A1, A3:A52)", null); - fp.parse(); - ptg = fp.getRPNPtg(); + public void testLookupAndMatchFunctionArgs() { + Ptg[] ptgs = parseFormula("lookup(A1, A3:A52, B3:B52)"); - assertTrue("got 3 ptg", ptg.length == 3); - assertTrue("ptg0 has Value class", ptg[0].getPtgClass() == Ptg.CLASS_VALUE); + assertEquals(4, ptgs.length); + assertTrue("ptg0 has Value class", ptgs[0].getPtgClass() == Ptg.CLASS_VALUE); + + ptgs = parseFormula("match(A1, A3:A52)"); + + assertEquals(3, ptgs.length); + assertTrue("ptg0 has Value class", ptgs[0].getPtgClass() == Ptg.CLASS_VALUE); } - + /** bug 33160*/ public void testLargeInt() { - FormulaParser fp = new FormulaParser("40", null); - fp.parse(); - Ptg[] ptg=fp.getRPNPtg(); - assertTrue("ptg is Int, is "+ptg[0].getClass(),ptg[0] instanceof IntPtg); - - fp = new FormulaParser("40000", null); - fp.parse(); - ptg=fp.getRPNPtg(); - assertTrue("ptg should be IntPtg, is "+ptg[0].getClass(), ptg[0] instanceof IntPtg); + Ptg[] ptgs = parseFormula("40"); + assertTrue("ptg is Int, is "+ptgs[0].getClass(),ptgs[0] instanceof IntPtg); + + ptgs = parseFormula("40000"); + assertTrue("ptg should be IntPtg, is "+ptgs[0].getClass(), ptgs[0] instanceof IntPtg); } /** bug 33160, testcase by Amol Deshmukh*/ public void testSimpleLongFormula() { - FormulaParser fp = new FormulaParser("40000/2", null); - fp.parse(); - Ptg[] ptgs = fp.getRPNPtg(); - assertTrue("three tokens expected, got " + ptgs.length, ptgs.length == 3); + Ptg[] ptgs = parseFormula("40000/2"); + assertEquals(3, ptgs.length); assertTrue("IntPtg", (ptgs[0] instanceof IntPtg)); assertTrue("IntPtg", (ptgs[1] instanceof IntPtg)); assertTrue("DividePtg", (ptgs[2] instanceof DividePtg)); } - + /** bug 35027, underscore in sheet name */ public void testUnderscore() { HSSFWorkbook wb = new HSSFWorkbook(); - - wb.createSheet("Cash_Flow"); - - HSSFSheet sheet = wb.createSheet("Test"); - HSSFRow row = sheet.createRow(0); - HSSFCell cell; - - cell = row.createCell((short)0); - cell.setCellFormula("Cash_Flow!A1"); - + + wb.createSheet("Cash_Flow"); + + HSSFSheet sheet = wb.createSheet("Test"); + HSSFRow row = sheet.createRow(0); + HSSFCell cell; + + cell = row.createCell((short)0); + cell.setCellFormula("Cash_Flow!A1"); } - // bug 38396 : Formula with exponential numbers not parsed correctly. - public void testExponentialParsing() { - FormulaParser fp = new FormulaParser("1.3E21/2", null); - fp.parse(); - Ptg[] ptgs = fp.getRPNPtg(); - assertTrue("three tokens expected, got " + ptgs.length, ptgs.length == 3); - assertTrue("NumberPtg", (ptgs[0] instanceof NumberPtg)); - assertTrue("IntPtg", (ptgs[1] instanceof IntPtg)); - assertTrue("DividePtg", (ptgs[2] instanceof DividePtg)); - - fp = new FormulaParser("1322E21/2", null); - fp.parse(); - ptgs = fp.getRPNPtg(); - assertTrue("three tokens expected, got " + ptgs.length, ptgs.length == 3); - assertTrue("NumberPtg", (ptgs[0] instanceof NumberPtg)); - assertTrue("IntPtg", (ptgs[1] instanceof IntPtg)); - assertTrue("DividePtg", (ptgs[2] instanceof DividePtg)); - - fp = new FormulaParser("1.3E1/2", null); - fp.parse(); - ptgs = fp.getRPNPtg(); - assertTrue("three tokens expected, got " + ptgs.length, ptgs.length == 3); - assertTrue("NumberPtg", (ptgs[0] instanceof NumberPtg)); - assertTrue("IntPtg", (ptgs[1] instanceof IntPtg)); - assertTrue("DividePtg", (ptgs[2] instanceof DividePtg)); - - } - public void testExponentialInSheet() throws Exception { - HSSFWorkbook wb = new HSSFWorkbook(); - - wb.createSheet("Cash_Flow"); - - HSSFSheet sheet = wb.createSheet("Test"); - HSSFRow row = sheet.createRow(0); - HSSFCell cell = row.createCell((short)0); - String formula = null; - - cell.setCellFormula("1.3E21/3"); - formula = cell.getCellFormula(); - assertEquals("Exponential formula string", "1.3E21/3", formula); - - cell.setCellFormula("-1.3E21/3"); - formula = cell.getCellFormula(); - assertEquals("Exponential formula string", "-1.3E21/3", formula); - - cell.setCellFormula("1322E21/3"); - formula = cell.getCellFormula(); - assertEquals("Exponential formula string", "1.322E24/3", formula); - - cell.setCellFormula("-1322E21/3"); - formula = cell.getCellFormula(); - assertEquals("Exponential formula string", "-1.322E24/3", formula); - - cell.setCellFormula("1.3E1/3"); - formula = cell.getCellFormula(); - assertEquals("Exponential formula string", "13.0/3", formula); - - cell.setCellFormula("-1.3E1/3"); - formula = cell.getCellFormula(); - assertEquals("Exponential formula string", "-13.0/3", formula); - - cell.setCellFormula("1.3E-4/3"); - formula = cell.getCellFormula(); - assertEquals("Exponential formula string", "1.3E-4/3", formula); - - cell.setCellFormula("-1.3E-4/3"); - formula = cell.getCellFormula(); - assertEquals("Exponential formula string", "-1.3E-4/3", formula); - - cell.setCellFormula("13E-15/3"); - formula = cell.getCellFormula(); - assertEquals("Exponential formula string", "1.3E-14/3", formula); - - cell.setCellFormula("-13E-15/3"); - formula = cell.getCellFormula(); - assertEquals("Exponential formula string", "-1.3E-14/3", formula); - - cell.setCellFormula("1.3E3/3"); - formula = cell.getCellFormula(); - assertEquals("Exponential formula string", "1300.0/3", formula); - - cell.setCellFormula("-1.3E3/3"); - formula = cell.getCellFormula(); - assertEquals("Exponential formula string", "-1300.0/3", formula); - - cell.setCellFormula("1300000000000000/3"); - formula = cell.getCellFormula(); - assertEquals("Exponential formula string", "1.3E15/3", formula); - - cell.setCellFormula("-1300000000000000/3"); - formula = cell.getCellFormula(); - assertEquals("Exponential formula string", "-1.3E15/3", formula); - - cell.setCellFormula("-10E-1/3.1E2*4E3/3E4"); - formula = cell.getCellFormula(); - assertEquals("Exponential formula string", "-1.0/310.0*4000.0/30000.0", formula); - } - - public static void main(String [] args) { - System.out.println("Testing org.apache.poi.hssf.record.formula.FormulaParser"); - junit.textui.TestRunner.run(TestFormulaParser.class); - } - - public void testNumbers() { - HSSFWorkbook wb = new HSSFWorkbook(); - - wb.createSheet("Cash_Flow"); - - HSSFSheet sheet = wb.createSheet("Test"); - HSSFRow row = sheet.createRow(0); - HSSFCell cell = row.createCell((short)0); - String formula = null; - - // starts from decimal point - - cell.setCellFormula(".1"); - formula = cell.getCellFormula(); - assertEquals("0.1", formula); - - cell.setCellFormula("+.1"); - formula = cell.getCellFormula(); - assertEquals("+0.1", formula); - - cell.setCellFormula("-.1"); - formula = cell.getCellFormula(); - assertEquals("-0.1", formula); - - // has exponent - - cell.setCellFormula("10E1"); - formula = cell.getCellFormula(); - assertEquals("100.0", formula); - - cell.setCellFormula("10E+1"); - formula = cell.getCellFormula(); - assertEquals("100.0", formula); - - cell.setCellFormula("10E-1"); - formula = cell.getCellFormula(); - assertEquals("1.0", formula); - } - - public void testRanges() { - HSSFWorkbook wb = new HSSFWorkbook(); - - wb.createSheet("Cash_Flow"); - - HSSFSheet sheet = wb.createSheet("Test"); - HSSFRow row = sheet.createRow(0); - HSSFCell cell = row.createCell((short)0); - String formula = null; - - cell.setCellFormula("A1.A2"); - formula = cell.getCellFormula(); - assertEquals("A1:A2", formula); - - cell.setCellFormula("A1..A2"); - formula = cell.getCellFormula(); - assertEquals("A1:A2", formula); - - cell.setCellFormula("A1...A2"); - formula = cell.getCellFormula(); - assertEquals("A1:A2", formula); - } - - /** - * Test for bug observable at svn revision 618865 (5-Feb-2008)
- * a formula consisting of a single no-arg function got rendered without the function braces - */ - public void testToFormulaStringZeroArgFunction() { - HSSFWorkbook book = new HSSFWorkbook(); - - Ptg[] ptgs = { - new FuncPtg(10), - }; - assertEquals("NA()", FormulaParser.toFormulaString(book, ptgs)); - } - - public void testPercent() { - Ptg[] ptgs; - ptgs = parseFormula("5%"); - assertEquals(2, ptgs.length); - assertEquals(ptgs[0].getClass(), IntPtg.class); - assertEquals(ptgs[1].getClass(), PercentPtg.class); - - // spaces OK - ptgs = parseFormula(" 250 % "); - assertEquals(2, ptgs.length); - assertEquals(ptgs[0].getClass(), IntPtg.class); - assertEquals(ptgs[1].getClass(), PercentPtg.class); - - - // double percent OK - ptgs = parseFormula("12345.678%%"); - assertEquals(3, ptgs.length); - assertEquals(ptgs[0].getClass(), NumberPtg.class); - assertEquals(ptgs[1].getClass(), PercentPtg.class); - assertEquals(ptgs[2].getClass(), PercentPtg.class); - - // percent of a bracketed expression - ptgs = parseFormula("(A1+35)%*B1%"); - assertEquals(8, ptgs.length); - assertEquals(ptgs[4].getClass(), PercentPtg.class); - assertEquals(ptgs[6].getClass(), PercentPtg.class); - - // percent of a text quantity - ptgs = parseFormula("\"8.75\"%"); - assertEquals(2, ptgs.length); - assertEquals(ptgs[0].getClass(), StringPtg.class); - assertEquals(ptgs[1].getClass(), PercentPtg.class); - - // percent to the power of - ptgs = parseFormula("50%^3"); - assertEquals(4, ptgs.length); - assertEquals(ptgs[0].getClass(), IntPtg.class); - assertEquals(ptgs[1].getClass(), PercentPtg.class); - assertEquals(ptgs[2].getClass(), IntPtg.class); - assertEquals(ptgs[3].getClass(), PowerPtg.class); - - // - // things that parse OK but would *evaluate* to an error - - ptgs = parseFormula("\"abc\"%"); - assertEquals(2, ptgs.length); - assertEquals(ptgs[0].getClass(), StringPtg.class); - assertEquals(ptgs[1].getClass(), PercentPtg.class); - - ptgs = parseFormula("#N/A%"); - assertEquals(2, ptgs.length); - assertEquals(ptgs[0].getClass(), ErrPtg.class); - assertEquals(ptgs[1].getClass(), PercentPtg.class); - } - - /** - * Tests combinations of various operators in the absence of brackets - */ - public void testPrecedenceAndAssociativity() { - - Class[] expClss; - - // TRUE=TRUE=2=2 evaluates to FALSE - expClss = new Class[] { BoolPtg.class, BoolPtg.class, EqualPtg.class, - IntPtg.class, EqualPtg.class, IntPtg.class, EqualPtg.class, }; - confirmTokenClasses("TRUE=TRUE=2=2", expClss); - - - // 2^3^2 evaluates to 64 not 512 - expClss = new Class[] { IntPtg.class, IntPtg.class, PowerPtg.class, - IntPtg.class, PowerPtg.class, }; - confirmTokenClasses("2^3^2", expClss); - - // "abc" & 2 + 3 & "def" evaluates to "abc5def" - expClss = new Class[] { StringPtg.class, IntPtg.class, IntPtg.class, - AddPtg.class, ConcatPtg.class, StringPtg.class, ConcatPtg.class, }; - confirmTokenClasses("\"abc\"&2+3&\"def\"", expClss); - - - // (1 / 2) - (3 * 4) - expClss = new Class[] { IntPtg.class, IntPtg.class, DividePtg.class, - IntPtg.class, IntPtg.class, MultiplyPtg.class, SubtractPtg.class, }; - confirmTokenClasses("1/2-3*4", expClss); - - // 2 * (2^2) - expClss = new Class[] { IntPtg.class, IntPtg.class, IntPtg.class, PowerPtg.class, MultiplyPtg.class, }; - // NOT: (2 *2) ^ 2 -> int int multiply int power - confirmTokenClasses("2*2^2", expClss); - - // 2^200% -> 2 not 1.6E58 - expClss = new Class[] { IntPtg.class, IntPtg.class, PercentPtg.class, PowerPtg.class, }; - confirmTokenClasses("2^200%", expClss); - } - - private static void confirmTokenClasses(String formula, Class[] expectedClasses) { - Ptg[] ptgs = parseFormula(formula); - assertEquals(expectedClasses.length, ptgs.length); - for (int i = 0; i < expectedClasses.length; i++) { - if(expectedClasses[i] != ptgs[i].getClass()) { - fail("difference at token[" + i + "]: expected (" - + expectedClasses[i].getName() + ") but got (" - + ptgs[i].getClass().getName() + ")"); - } - } - } - - public void testPower() { - confirmTokenClasses("2^5", new Class[] { IntPtg.class, IntPtg.class, PowerPtg.class, }); - } - - private static Ptg parseSingleToken(String formula, Class ptgClass) { - Ptg[] ptgs = parseFormula(formula); - assertEquals(1, ptgs.length); - Ptg result = ptgs[0]; - assertEquals(ptgClass, result.getClass()); - return result; - } - - public void testParseNumber() { - IntPtg ip; - - // bug 33160 - ip = (IntPtg) parseSingleToken("40", IntPtg.class); - assertEquals(40, ip.getValue()); - ip = (IntPtg) parseSingleToken("40000", IntPtg.class); - assertEquals(40000, ip.getValue()); - - // check the upper edge of the IntPtg range: - ip = (IntPtg) parseSingleToken("65535", IntPtg.class); - assertEquals(65535, ip.getValue()); - NumberPtg np = (NumberPtg) parseSingleToken("65536", NumberPtg.class); - assertEquals(65536, np.getValue(), 0); - - np = (NumberPtg) parseSingleToken("65534.6", NumberPtg.class); - assertEquals(65534.6, np.getValue(), 0); - } - - public void testMissingArgs() { - - Class[] expClss; - - expClss = new Class[] { ReferencePtg.class, MissingArgPtg.class, ReferencePtg.class, - FuncVarPtg.class, }; - confirmTokenClasses("if(A1, ,C1)", expClss); - - expClss = new Class[] { MissingArgPtg.class, AreaPtg.class, MissingArgPtg.class, - FuncVarPtg.class, }; - confirmTokenClasses("counta( , A1:B2, )", expClss); - } - - public void testParseErrorLiterals() { - - confirmParseErrorLiteral(ErrPtg.NULL_INTERSECTION, "#NULL!"); - confirmParseErrorLiteral(ErrPtg.DIV_ZERO, "#DIV/0!"); - confirmParseErrorLiteral(ErrPtg.VALUE_INVALID, "#VALUE!"); - confirmParseErrorLiteral(ErrPtg.REF_INVALID, "#REF!"); - confirmParseErrorLiteral(ErrPtg.NAME_INVALID, "#NAME?"); - confirmParseErrorLiteral(ErrPtg.NUM_ERROR, "#NUM!"); - confirmParseErrorLiteral(ErrPtg.N_A, "#N/A"); - } - - private static void confirmParseErrorLiteral(ErrPtg expectedToken, String formula) { - assertEquals(expectedToken, parseSingleToken(formula, ErrPtg.class)); - } - - /** - * To aid readability the parameters have been encoded with single quotes instead of double - * quotes. This method converts single quotes to double quotes before performing the parse - * and result check. - */ - private static void confirmStringParse(String singleQuotedValue) { - // formula: internal quotes become double double, surround with double quotes - String formula = '"' + singleQuotedValue.replaceAll("'", "\"\"") + '"'; - String expectedValue = singleQuotedValue.replace('\'', '"'); - - StringPtg sp = (StringPtg) parseSingleToken(formula, StringPtg.class); - assertEquals(expectedValue, sp.getValue()); - } - public void testParseStringLiterals_bug28754() { - - StringPtg sp; - try { - sp = (StringPtg) parseSingleToken("\"test\"\"ing\"", StringPtg.class); - } catch (RuntimeException e) { - if(e.getMessage().startsWith("Cannot Parse")) { - throw new AssertionFailedError("Identified bug 28754a"); - } - throw e; - } - assertEquals("test\"ing", sp.getValue()); - - HSSFWorkbook wb = new HSSFWorkbook(); - HSSFSheet sheet = wb.createSheet(); - wb.setSheetName(0, "Sheet1"); - - HSSFRow row = sheet.createRow(0); - HSSFCell cell = row.createCell((short)0); - cell.setCellFormula("right(\"test\"\"ing\", 3)"); - String actualCellFormula = cell.getCellFormula(); - if("RIGHT(\"test\"ing\",3)".equals(actualCellFormula)) { - throw new AssertionFailedError("Identified bug 28754b"); - } - assertEquals("RIGHT(\"test\"\"ing\",3)", actualCellFormula); - } - - public void testParseStringLiterals() { - confirmStringParse("goto considered harmful"); - - confirmStringParse("goto 'considered' harmful"); - - confirmStringParse(""); - confirmStringParse("'"); - confirmStringParse("''"); - confirmStringParse("' '"); - confirmStringParse(" ' "); - } - - public void testParseSumIfSum() { - String formulaString; - Ptg[] ptgs; - ptgs = parseFormula("sum(5, 2, if(3>2, sum(A1:A2), 6))"); - formulaString = FormulaParser.toFormulaString(null, ptgs); - assertEquals("SUM(5,2,IF(3>2,SUM(A1:A2),6))", formulaString); - - ptgs = parseFormula("if(1<2,sum(5, 2, if(3>2, sum(A1:A2), 6)),4)"); - formulaString = FormulaParser.toFormulaString(null, ptgs); - assertEquals("IF(1<2,SUM(5,2,IF(3>2,SUM(A1:A2),6)),4)", formulaString); - } - public void testParserErrors() { - parseExpectedException("1 2"); - parseExpectedException(" 12 . 345 "); - parseExpectedException("1 .23 "); - - parseExpectedException("sum(#NAME)"); - parseExpectedException("1 + #N / A * 2"); - parseExpectedException("#value?"); - parseExpectedException("#DIV/ 0+2"); - - - parseExpectedException("IF(TRUE)"); - parseExpectedException("countif(A1:B5, C1, D1)"); - } - - private static void parseExpectedException(String formula) { - try { - parseFormula(formula); - throw new AssertionFailedError("expected parse exception"); - } catch (FormulaParseException e) { - // expected during successful test - assertNotNull(e.getMessage()); - } catch (RuntimeException e) { - e.printStackTrace(); - fail("Wrong exception:" + e.getMessage()); - } - } - - public void testSetFormulaWithRowBeyond32768_Bug44539() { - - HSSFWorkbook wb = new HSSFWorkbook(); - HSSFSheet sheet = wb.createSheet(); - wb.setSheetName(0, "Sheet1"); - - HSSFRow row = sheet.createRow(0); - HSSFCell cell = row.createCell((short)0); - cell.setCellFormula("SUM(A32769:A32770)"); - if("SUM(A-32767:A-32766)".equals(cell.getCellFormula())) { - fail("Identified bug 44539"); - } - assertEquals("SUM(A32769:A32770)", cell.getCellFormula()); - } - - public void testSpaceAtStartOfFormula() { - // Simulating cell formula of "= 4" (note space) - // The same Ptg array can be observed if an excel file is saved with that exact formula - - AttrPtg spacePtg = AttrPtg.createSpace(AttrPtg.SpaceType.SPACE_BEFORE, 1); - Ptg[] ptgs = { spacePtg, new IntPtg(4), }; - String formulaString; - try { - formulaString = FormulaParser.toFormulaString(null, ptgs); - } catch (IllegalStateException e) { - if(e.getMessage().equalsIgnoreCase("too much stuff left on the stack")) { - throw new AssertionFailedError("Identified bug 44609"); - } - // else some unexpected error - throw e; - } - // FormulaParser strips spaces anyway - assertEquals("4", formulaString); - - ptgs = new Ptg[] { new IntPtg(3), spacePtg, new IntPtg(4), spacePtg, new AddPtg()}; - formulaString = FormulaParser.toFormulaString(null, ptgs); - assertEquals("3+4", formulaString); - } - - /** - * Checks some internal error detecting logic ('stack underflow error' in toFormulaString) - */ - public void testTooFewOperandArgs() { - // Simulating badly encoded cell formula of "=/1" - // Not sure if Excel could ever produce this - Ptg[] ptgs = { - // Excel would probably have put tMissArg here - new IntPtg(1), - new DividePtg(), - }; - try { - FormulaParser.toFormulaString(null, ptgs); - fail("Expected exception was not thrown"); - } catch (IllegalStateException e) { - // expected during successful test - assertTrue(e.getMessage().startsWith("Too few arguments suppled to operation token")); - } - } - /** - * Make sure that POI uses the right Func Ptg when encoding formulas. Functions with variable - * number of args should get FuncVarPtg, functions with fixed args should get FuncPtg.

- * - * Prior to the fix for bug 44675 POI would encode FuncVarPtg for all functions. In many cases - * Excel tolerates the wrong Ptg and evaluates the formula OK (e.g. SIN), but in some cases - * (e.g. COUNTIF) Excel fails to evaluate the formula, giving '#VALUE!' instead. - */ - public void testFuncPtgSelection() { - HSSFWorkbook book = new HSSFWorkbook(); - Ptg[] ptgs; - ptgs = FormulaParser.parse("countif(A1:A2, 1)", book); - assertEquals(3, ptgs.length); - if(FuncVarPtg.class == ptgs[2].getClass()) { - throw new AssertionFailedError("Identified bug 44675"); - } - assertEquals(FuncPtg.class, ptgs[2].getClass()); - ptgs = FormulaParser.parse("sin(1)", book); - assertEquals(2, ptgs.length); - assertEquals(FuncPtg.class, ptgs[1].getClass()); - } - - public void testWrongNumberOfFunctionArgs() { - confirmArgCountMsg("sin()", "Too few arguments to function 'SIN'. Expected 1 but got 0."); - confirmArgCountMsg("countif(1, 2, 3, 4)", "Too many arguments to function 'COUNTIF'. Expected 2 but got 4."); - confirmArgCountMsg("index(1, 2, 3, 4, 5, 6)", "Too many arguments to function 'INDEX'. At most 4 were expected but got 6."); - confirmArgCountMsg("vlookup(1, 2)", "Too few arguments to function 'VLOOKUP'. At least 3 were expected but got 2."); - } - - private static void confirmArgCountMsg(String formula, String expectedMessage) { - HSSFWorkbook book = new HSSFWorkbook(); - try { - FormulaParser.parse(formula, book); - throw new AssertionFailedError("Didn't get parse exception as expected"); - } catch (FormulaParseException e) { - assertEquals(expectedMessage, e.getMessage()); - } - } + // bug 38396 : Formula with exponential numbers not parsed correctly. + public void testExponentialParsing() { + Ptg[] ptgs; + ptgs = parseFormula("1.3E21/2"); + assertEquals(3, ptgs.length); + assertTrue("NumberPtg", (ptgs[0] instanceof NumberPtg)); + assertTrue("IntPtg", (ptgs[1] instanceof IntPtg)); + assertTrue("DividePtg", (ptgs[2] instanceof DividePtg)); + + ptgs = parseFormula("1322E21/2"); + assertEquals(3, ptgs.length); + assertTrue("NumberPtg", (ptgs[0] instanceof NumberPtg)); + assertTrue("IntPtg", (ptgs[1] instanceof IntPtg)); + assertTrue("DividePtg", (ptgs[2] instanceof DividePtg)); + + ptgs = parseFormula("1.3E1/2"); + assertEquals(3, ptgs.length); + assertTrue("NumberPtg", (ptgs[0] instanceof NumberPtg)); + assertTrue("IntPtg", (ptgs[1] instanceof IntPtg)); + assertTrue("DividePtg", (ptgs[2] instanceof DividePtg)); + } + + public void testExponentialInSheet() { + HSSFWorkbook wb = new HSSFWorkbook(); + + wb.createSheet("Cash_Flow"); + + HSSFSheet sheet = wb.createSheet("Test"); + HSSFRow row = sheet.createRow(0); + HSSFCell cell = row.createCell((short)0); + String formula = null; + + cell.setCellFormula("1.3E21/3"); + formula = cell.getCellFormula(); + assertEquals("Exponential formula string", "1.3E21/3", formula); + + cell.setCellFormula("-1.3E21/3"); + formula = cell.getCellFormula(); + assertEquals("Exponential formula string", "-1.3E21/3", formula); + + cell.setCellFormula("1322E21/3"); + formula = cell.getCellFormula(); + assertEquals("Exponential formula string", "1.322E24/3", formula); + + cell.setCellFormula("-1322E21/3"); + formula = cell.getCellFormula(); + assertEquals("Exponential formula string", "-1.322E24/3", formula); + + cell.setCellFormula("1.3E1/3"); + formula = cell.getCellFormula(); + assertEquals("Exponential formula string", "13.0/3", formula); + + cell.setCellFormula("-1.3E1/3"); + formula = cell.getCellFormula(); + assertEquals("Exponential formula string", "-13.0/3", formula); + + cell.setCellFormula("1.3E-4/3"); + formula = cell.getCellFormula(); + assertEquals("Exponential formula string", "1.3E-4/3", formula); + + cell.setCellFormula("-1.3E-4/3"); + formula = cell.getCellFormula(); + assertEquals("Exponential formula string", "-1.3E-4/3", formula); + + cell.setCellFormula("13E-15/3"); + formula = cell.getCellFormula(); + assertEquals("Exponential formula string", "1.3E-14/3", formula); + + cell.setCellFormula("-13E-15/3"); + formula = cell.getCellFormula(); + assertEquals("Exponential formula string", "-1.3E-14/3", formula); + + cell.setCellFormula("1.3E3/3"); + formula = cell.getCellFormula(); + assertEquals("Exponential formula string", "1300.0/3", formula); + + cell.setCellFormula("-1.3E3/3"); + formula = cell.getCellFormula(); + assertEquals("Exponential formula string", "-1300.0/3", formula); + + cell.setCellFormula("1300000000000000/3"); + formula = cell.getCellFormula(); + assertEquals("Exponential formula string", "1.3E15/3", formula); + + cell.setCellFormula("-1300000000000000/3"); + formula = cell.getCellFormula(); + assertEquals("Exponential formula string", "-1.3E15/3", formula); + + cell.setCellFormula("-10E-1/3.1E2*4E3/3E4"); + formula = cell.getCellFormula(); + assertEquals("Exponential formula string", "-1.0/310.0*4000.0/30000.0", formula); + } + + public void testNumbers() { + HSSFWorkbook wb = new HSSFWorkbook(); + + wb.createSheet("Cash_Flow"); + + HSSFSheet sheet = wb.createSheet("Test"); + HSSFRow row = sheet.createRow(0); + HSSFCell cell = row.createCell((short)0); + String formula = null; + + // starts from decimal point + + cell.setCellFormula(".1"); + formula = cell.getCellFormula(); + assertEquals("0.1", formula); + + cell.setCellFormula("+.1"); + formula = cell.getCellFormula(); + assertEquals("+0.1", formula); + + cell.setCellFormula("-.1"); + formula = cell.getCellFormula(); + assertEquals("-0.1", formula); + + // has exponent + + cell.setCellFormula("10E1"); + formula = cell.getCellFormula(); + assertEquals("100.0", formula); + + cell.setCellFormula("10E+1"); + formula = cell.getCellFormula(); + assertEquals("100.0", formula); + + cell.setCellFormula("10E-1"); + formula = cell.getCellFormula(); + assertEquals("1.0", formula); + } + + public void testRanges() { + HSSFWorkbook wb = new HSSFWorkbook(); + + wb.createSheet("Cash_Flow"); + + HSSFSheet sheet = wb.createSheet("Test"); + HSSFRow row = sheet.createRow(0); + HSSFCell cell = row.createCell((short)0); + String formula = null; + + cell.setCellFormula("A1.A2"); + formula = cell.getCellFormula(); + assertEquals("A1:A2", formula); + + cell.setCellFormula("A1..A2"); + formula = cell.getCellFormula(); + assertEquals("A1:A2", formula); + + cell.setCellFormula("A1...A2"); + formula = cell.getCellFormula(); + assertEquals("A1:A2", formula); + } + + /** + * Test for bug observable at svn revision 618865 (5-Feb-2008)
+ * a formula consisting of a single no-arg function got rendered without the function braces + */ + public void testToFormulaStringZeroArgFunction() { + HSSFWorkbook book = new HSSFWorkbook(); + + Ptg[] ptgs = { + new FuncPtg(10), + }; + assertEquals("NA()", FormulaParser.toFormulaString(book, ptgs)); + } + + public void testPercent() { + Ptg[] ptgs; + ptgs = parseFormula("5%"); + assertEquals(2, ptgs.length); + assertEquals(ptgs[0].getClass(), IntPtg.class); + assertEquals(ptgs[1].getClass(), PercentPtg.class); + + // spaces OK + ptgs = parseFormula(" 250 % "); + assertEquals(2, ptgs.length); + assertEquals(ptgs[0].getClass(), IntPtg.class); + assertEquals(ptgs[1].getClass(), PercentPtg.class); + + + // double percent OK + ptgs = parseFormula("12345.678%%"); + assertEquals(3, ptgs.length); + assertEquals(ptgs[0].getClass(), NumberPtg.class); + assertEquals(ptgs[1].getClass(), PercentPtg.class); + assertEquals(ptgs[2].getClass(), PercentPtg.class); + + // percent of a bracketed expression + ptgs = parseFormula("(A1+35)%*B1%"); + assertEquals(8, ptgs.length); + assertEquals(ptgs[4].getClass(), PercentPtg.class); + assertEquals(ptgs[6].getClass(), PercentPtg.class); + + // percent of a text quantity + ptgs = parseFormula("\"8.75\"%"); + assertEquals(2, ptgs.length); + assertEquals(ptgs[0].getClass(), StringPtg.class); + assertEquals(ptgs[1].getClass(), PercentPtg.class); + + // percent to the power of + ptgs = parseFormula("50%^3"); + assertEquals(4, ptgs.length); + assertEquals(ptgs[0].getClass(), IntPtg.class); + assertEquals(ptgs[1].getClass(), PercentPtg.class); + assertEquals(ptgs[2].getClass(), IntPtg.class); + assertEquals(ptgs[3].getClass(), PowerPtg.class); + + // + // things that parse OK but would *evaluate* to an error + + ptgs = parseFormula("\"abc\"%"); + assertEquals(2, ptgs.length); + assertEquals(ptgs[0].getClass(), StringPtg.class); + assertEquals(ptgs[1].getClass(), PercentPtg.class); + + ptgs = parseFormula("#N/A%"); + assertEquals(2, ptgs.length); + assertEquals(ptgs[0].getClass(), ErrPtg.class); + assertEquals(ptgs[1].getClass(), PercentPtg.class); + } + + /** + * Tests combinations of various operators in the absence of brackets + */ + public void testPrecedenceAndAssociativity() { + + Class[] expClss; + + // TRUE=TRUE=2=2 evaluates to FALSE + expClss = new Class[] { BoolPtg.class, BoolPtg.class, EqualPtg.class, + IntPtg.class, EqualPtg.class, IntPtg.class, EqualPtg.class, }; + confirmTokenClasses("TRUE=TRUE=2=2", expClss); + + + // 2^3^2 evaluates to 64 not 512 + expClss = new Class[] { IntPtg.class, IntPtg.class, PowerPtg.class, + IntPtg.class, PowerPtg.class, }; + confirmTokenClasses("2^3^2", expClss); + + // "abc" & 2 + 3 & "def" evaluates to "abc5def" + expClss = new Class[] { StringPtg.class, IntPtg.class, IntPtg.class, + AddPtg.class, ConcatPtg.class, StringPtg.class, ConcatPtg.class, }; + confirmTokenClasses("\"abc\"&2+3&\"def\"", expClss); + + + // (1 / 2) - (3 * 4) + expClss = new Class[] { IntPtg.class, IntPtg.class, DividePtg.class, + IntPtg.class, IntPtg.class, MultiplyPtg.class, SubtractPtg.class, }; + confirmTokenClasses("1/2-3*4", expClss); + + // 2 * (2^2) + expClss = new Class[] { IntPtg.class, IntPtg.class, IntPtg.class, PowerPtg.class, MultiplyPtg.class, }; + // NOT: (2 *2) ^ 2 -> int int multiply int power + confirmTokenClasses("2*2^2", expClss); + + // 2^200% -> 2 not 1.6E58 + expClss = new Class[] { IntPtg.class, IntPtg.class, PercentPtg.class, PowerPtg.class, }; + confirmTokenClasses("2^200%", expClss); + } + + private static void confirmTokenClasses(String formula, Class[] expectedClasses) { + Ptg[] ptgs = parseFormula(formula); + assertEquals(expectedClasses.length, ptgs.length); + for (int i = 0; i < expectedClasses.length; i++) { + if(expectedClasses[i] != ptgs[i].getClass()) { + fail("difference at token[" + i + "]: expected (" + + expectedClasses[i].getName() + ") but got (" + + ptgs[i].getClass().getName() + ")"); + } + } + } + + public void testPower() { + confirmTokenClasses("2^5", new Class[] { IntPtg.class, IntPtg.class, PowerPtg.class, }); + } + + private static Ptg parseSingleToken(String formula, Class ptgClass) { + Ptg[] ptgs = parseFormula(formula); + assertEquals(1, ptgs.length); + Ptg result = ptgs[0]; + assertEquals(ptgClass, result.getClass()); + return result; + } + + public void testParseNumber() { + IntPtg ip; + + // bug 33160 + ip = (IntPtg) parseSingleToken("40", IntPtg.class); + assertEquals(40, ip.getValue()); + ip = (IntPtg) parseSingleToken("40000", IntPtg.class); + assertEquals(40000, ip.getValue()); + + // check the upper edge of the IntPtg range: + ip = (IntPtg) parseSingleToken("65535", IntPtg.class); + assertEquals(65535, ip.getValue()); + NumberPtg np = (NumberPtg) parseSingleToken("65536", NumberPtg.class); + assertEquals(65536, np.getValue(), 0); + + np = (NumberPtg) parseSingleToken("65534.6", NumberPtg.class); + assertEquals(65534.6, np.getValue(), 0); + } + + public void testMissingArgs() { + + Class[] expClss; + + expClss = new Class[] { ReferencePtg.class, MissingArgPtg.class, ReferencePtg.class, + FuncVarPtg.class, }; + confirmTokenClasses("if(A1, ,C1)", expClss); + + expClss = new Class[] { MissingArgPtg.class, AreaPtg.class, MissingArgPtg.class, + FuncVarPtg.class, }; + confirmTokenClasses("counta( , A1:B2, )", expClss); + } + + public void testParseErrorLiterals() { + + confirmParseErrorLiteral(ErrPtg.NULL_INTERSECTION, "#NULL!"); + confirmParseErrorLiteral(ErrPtg.DIV_ZERO, "#DIV/0!"); + confirmParseErrorLiteral(ErrPtg.VALUE_INVALID, "#VALUE!"); + confirmParseErrorLiteral(ErrPtg.REF_INVALID, "#REF!"); + confirmParseErrorLiteral(ErrPtg.NAME_INVALID, "#NAME?"); + confirmParseErrorLiteral(ErrPtg.NUM_ERROR, "#NUM!"); + confirmParseErrorLiteral(ErrPtg.N_A, "#N/A"); + } + + private static void confirmParseErrorLiteral(ErrPtg expectedToken, String formula) { + assertEquals(expectedToken, parseSingleToken(formula, ErrPtg.class)); + } + + /** + * To aid readability the parameters have been encoded with single quotes instead of double + * quotes. This method converts single quotes to double quotes before performing the parse + * and result check. + */ + private static void confirmStringParse(String singleQuotedValue) { + // formula: internal quotes become double double, surround with double quotes + String formula = '"' + singleQuotedValue.replaceAll("'", "\"\"") + '"'; + String expectedValue = singleQuotedValue.replace('\'', '"'); + + StringPtg sp = (StringPtg) parseSingleToken(formula, StringPtg.class); + assertEquals(expectedValue, sp.getValue()); + } + public void testParseStringLiterals_bug28754() { + + StringPtg sp; + try { + sp = (StringPtg) parseSingleToken("\"test\"\"ing\"", StringPtg.class); + } catch (RuntimeException e) { + if(e.getMessage().startsWith("Cannot Parse")) { + throw new AssertionFailedError("Identified bug 28754a"); + } + throw e; + } + assertEquals("test\"ing", sp.getValue()); + + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sheet = wb.createSheet(); + wb.setSheetName(0, "Sheet1"); + + HSSFRow row = sheet.createRow(0); + HSSFCell cell = row.createCell((short)0); + cell.setCellFormula("right(\"test\"\"ing\", 3)"); + String actualCellFormula = cell.getCellFormula(); + if("RIGHT(\"test\"ing\",3)".equals(actualCellFormula)) { + throw new AssertionFailedError("Identified bug 28754b"); + } + assertEquals("RIGHT(\"test\"\"ing\",3)", actualCellFormula); + } + + public void testParseStringLiterals() { + confirmStringParse("goto considered harmful"); + + confirmStringParse("goto 'considered' harmful"); + + confirmStringParse(""); + confirmStringParse("'"); + confirmStringParse("''"); + confirmStringParse("' '"); + confirmStringParse(" ' "); + } + + public void testParseSumIfSum() { + String formulaString; + Ptg[] ptgs; + ptgs = parseFormula("sum(5, 2, if(3>2, sum(A1:A2), 6))"); + formulaString = FormulaParser.toFormulaString(null, ptgs); + assertEquals("SUM(5,2,IF(3>2,SUM(A1:A2),6))", formulaString); + + ptgs = parseFormula("if(1<2,sum(5, 2, if(3>2, sum(A1:A2), 6)),4)"); + formulaString = FormulaParser.toFormulaString(null, ptgs); + assertEquals("IF(1<2,SUM(5,2,IF(3>2,SUM(A1:A2),6)),4)", formulaString); + } + public void testParserErrors() { + parseExpectedException("1 2"); + parseExpectedException(" 12 . 345 "); + parseExpectedException("1 .23 "); + + parseExpectedException("sum(#NAME)"); + parseExpectedException("1 + #N / A * 2"); + parseExpectedException("#value?"); + parseExpectedException("#DIV/ 0+2"); + + + parseExpectedException("IF(TRUE)"); + parseExpectedException("countif(A1:B5, C1, D1)"); + } + + private static void parseExpectedException(String formula) { + try { + parseFormula(formula); + throw new AssertionFailedError("expected parse exception"); + } catch (FormulaParseException e) { + // expected during successful test + assertNotNull(e.getMessage()); + } catch (RuntimeException e) { + e.printStackTrace(); + fail("Wrong exception:" + e.getMessage()); + } + } + + public void testSetFormulaWithRowBeyond32768_Bug44539() { + + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sheet = wb.createSheet(); + wb.setSheetName(0, "Sheet1"); + + HSSFRow row = sheet.createRow(0); + HSSFCell cell = row.createCell((short)0); + cell.setCellFormula("SUM(A32769:A32770)"); + if("SUM(A-32767:A-32766)".equals(cell.getCellFormula())) { + fail("Identified bug 44539"); + } + assertEquals("SUM(A32769:A32770)", cell.getCellFormula()); + } + + public void testSpaceAtStartOfFormula() { + // Simulating cell formula of "= 4" (note space) + // The same Ptg array can be observed if an excel file is saved with that exact formula + + AttrPtg spacePtg = AttrPtg.createSpace(AttrPtg.SpaceType.SPACE_BEFORE, 1); + Ptg[] ptgs = { spacePtg, new IntPtg(4), }; + String formulaString; + try { + formulaString = FormulaParser.toFormulaString(null, ptgs); + } catch (IllegalStateException e) { + if(e.getMessage().equalsIgnoreCase("too much stuff left on the stack")) { + throw new AssertionFailedError("Identified bug 44609"); + } + // else some unexpected error + throw e; + } + // FormulaParser strips spaces anyway + assertEquals("4", formulaString); + + ptgs = new Ptg[] { new IntPtg(3), spacePtg, new IntPtg(4), spacePtg, new AddPtg()}; + formulaString = FormulaParser.toFormulaString(null, ptgs); + assertEquals("3+4", formulaString); + } + + /** + * Checks some internal error detecting logic ('stack underflow error' in toFormulaString) + */ + public void testTooFewOperandArgs() { + // Simulating badly encoded cell formula of "=/1" + // Not sure if Excel could ever produce this + Ptg[] ptgs = { + // Excel would probably have put tMissArg here + new IntPtg(1), + new DividePtg(), + }; + try { + FormulaParser.toFormulaString(null, ptgs); + fail("Expected exception was not thrown"); + } catch (IllegalStateException e) { + // expected during successful test + assertTrue(e.getMessage().startsWith("Too few arguments suppled to operation token")); + } + } + /** + * Make sure that POI uses the right Func Ptg when encoding formulas. Functions with variable + * number of args should get FuncVarPtg, functions with fixed args should get FuncPtg.

+ * + * Prior to the fix for bug 44675 POI would encode FuncVarPtg for all functions. In many cases + * Excel tolerates the wrong Ptg and evaluates the formula OK (e.g. SIN), but in some cases + * (e.g. COUNTIF) Excel fails to evaluate the formula, giving '#VALUE!' instead. + */ + public void testFuncPtgSelection() { + + Ptg[] ptgs; + ptgs = parseFormula("countif(A1:A2, 1)"); + assertEquals(3, ptgs.length); + if(FuncVarPtg.class == ptgs[2].getClass()) { + throw new AssertionFailedError("Identified bug 44675"); + } + assertEquals(FuncPtg.class, ptgs[2].getClass()); + ptgs = parseFormula("sin(1)"); + assertEquals(2, ptgs.length); + assertEquals(FuncPtg.class, ptgs[1].getClass()); + } + + public void testWrongNumberOfFunctionArgs() { + confirmArgCountMsg("sin()", "Too few arguments to function 'SIN'. Expected 1 but got 0."); + confirmArgCountMsg("countif(1, 2, 3, 4)", "Too many arguments to function 'COUNTIF'. Expected 2 but got 4."); + confirmArgCountMsg("index(1, 2, 3, 4, 5, 6)", "Too many arguments to function 'INDEX'. At most 4 were expected but got 6."); + confirmArgCountMsg("vlookup(1, 2)", "Too few arguments to function 'VLOOKUP'. At least 3 were expected but got 2."); + } + + private static void confirmArgCountMsg(String formula, String expectedMessage) { + HSSFWorkbook book = new HSSFWorkbook(); + try { + FormulaParser.parse(formula, book); + throw new AssertionFailedError("Didn't get parse exception as expected"); + } catch (FormulaParseException e) { + assertEquals(expectedMessage, e.getMessage()); + } + } + + public void testParseErrorExpecteMsg() { + + try { + parseFormula("round(3.14;2)"); + throw new AssertionFailedError("Didn't get parse exception as expected"); + } catch (FormulaParseException e) { + assertEquals("Parse error near char 10 ';' in specified formula 'round(3.14;2)'. Expected ',' or ')'", e.getMessage()); + } + } } diff --git a/src/testcases/org/apache/poi/hssf/model/TestSheet.java b/src/testcases/org/apache/poi/hssf/model/TestSheet.java index 9281eb80d..3bdcae57c 100644 --- a/src/testcases/org/apache/poi/hssf/model/TestSheet.java +++ b/src/testcases/org/apache/poi/hssf/model/TestSheet.java @@ -17,6 +17,7 @@ package org.apache.poi.hssf.model; +import junit.framework.AssertionFailedError; import junit.framework.TestCase; import org.apache.poi.hssf.record.*; import org.apache.poi.hssf.record.aggregates.ColumnInfoRecordsAggregate; @@ -351,5 +352,25 @@ public final class TestSheet extends TestCase { xfindex = sheet.getXFIndexForColAt((short) 10); assertEquals(DEFAULT_IDX, xfindex); } + + /** + * Prior to bug 45066, POI would get the estimated sheet size wrong + * when an UncalcedRecord was present.

+ */ + public void testUncalcSize_bug45066() { + + List records = new ArrayList(); + records.add(new BOFRecord()); + records.add(new UncalcedRecord()); + records.add(new EOFRecord()); + Sheet sheet = Sheet.createSheet( records, 0, 0 ); + + int estimatedSize = sheet.getSize(); + int serializedSize = sheet.serialize(0, new byte[estimatedSize]); + if (serializedSize != estimatedSize) { + throw new AssertionFailedError("Identified bug 45066 b"); + } + assertEquals(50, serializedSize); + } } diff --git a/src/testcases/org/apache/poi/hssf/record/formula/function/ExcelFileFormatDocFunctionExtractor.java b/src/testcases/org/apache/poi/hssf/record/formula/function/ExcelFileFormatDocFunctionExtractor.java index 47137df4f..7702fce3d 100644 --- a/src/testcases/org/apache/poi/hssf/record/formula/function/ExcelFileFormatDocFunctionExtractor.java +++ b/src/testcases/org/apache/poi/hssf/record/formula/function/ExcelFileFormatDocFunctionExtractor.java @@ -26,9 +26,12 @@ import java.io.InputStream; import java.io.OutputStream; import java.io.PrintStream; import java.io.UnsupportedEncodingException; +import java.math.BigInteger; import java.net.MalformedURLException; import java.net.URL; import java.net.URLConnection; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -37,7 +40,6 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.Stack; -import java.util.zip.CRC32; import java.util.zip.ZipException; import java.util.zip.ZipFile; @@ -149,7 +151,6 @@ public final class ExcelFileFormatDocFunctionExtractor { private static final class FunctionDataCollector { - private final Map _allFunctionsByIndex; private final Map _allFunctionsByName; private final Set _groupFunctionIndexes; @@ -184,25 +185,29 @@ public final class ExcelFileFormatDocFunctionExtractor { _allFunctionsByName.put(funcName, fd); } + /** + * Some extra validation here. + * Any function which changes definition will have a footnote in the source document + */ private void checkRedefinedFunction(boolean hasNote, String funcName, Integer funcIxKey) { FunctionData fdPrev; + // check by index fdPrev = (FunctionData) _allFunctionsByIndex.get(funcIxKey); if(fdPrev != null) { - if(fdPrev.hasFootnote() && hasNote) { - // func def can change if both have a foot-note - _allFunctionsByName.remove(fdPrev.getName()); - } else { - throw new RuntimeException("changing function definition without foot-note"); + if(!fdPrev.hasFootnote() || !hasNote) { + throw new RuntimeException("changing function [" + + funcIxKey + "] definition without foot-note"); } + _allFunctionsByName.remove(fdPrev.getName()); } + // check by name fdPrev = (FunctionData) _allFunctionsByName.get(funcName); if(fdPrev != null) { - if(fdPrev.hasFootnote() && hasNote) { - // func def can change if both have a foot-note - _allFunctionsByIndex.remove(new Integer(fdPrev.getIndex())); - } else { - throw new RuntimeException("changing function definition without foot-note"); + if(!fdPrev.hasFootnote() || !hasNote) { + throw new RuntimeException("changing function '" + + funcName + "' definition without foot-note"); } + _allFunctionsByIndex.remove(new Integer(fdPrev.getIndex())); } } @@ -237,9 +242,13 @@ public final class ExcelFileFormatDocFunctionExtractor { private static final String[] TABLE_CELL_RELPATH_NAMES = { "table:table-row", "table:table-cell", "text:p", }; - private static final String[] NOTE_REF_RELPATH_NAMES = { + // after May 2008 there was one more style applied to the footnotes + private static final String[] NOTE_REF_RELPATH_NAMES_OLD = { "table:table-row", "table:table-cell", "text:p", "text:span", "text:note-ref", }; + private static final String[] NOTE_REF_RELPATH_NAMES = { + "table:table-row", "table:table-cell", "text:p", "text:span", "text:span", "text:note-ref", + }; private final Stack _elemNameStack; @@ -368,6 +377,8 @@ public final class ExcelFileFormatDocFunctionExtractor { } else if(matchesRelPath(TABLE_CELL_RELPATH_NAMES)) { _textNodeBuffer.setLength(0); _cellHasNote = false; + } else if(matchesRelPath(NOTE_REF_RELPATH_NAMES_OLD)) { + _cellHasNote = true; } else if(matchesRelPath(NOTE_REF_RELPATH_NAMES)) { _cellHasNote = true; } @@ -456,6 +467,9 @@ public final class ExcelFileFormatDocFunctionExtractor { } private static void processFile(File effDocFile, File outFile) { + if(!effDocFile.exists()) { + throw new RuntimeException("file '" + effDocFile.getAbsolutePath() + "' does not exist"); + } OutputStream os; try { os = new FileOutputStream(outFile); @@ -475,7 +489,7 @@ public final class ExcelFileFormatDocFunctionExtractor { ps.println("# Created by (" + genClass.getName() + ")"); // identify the source file ps.print("# from source file '" + SOURCE_DOC_FILE_NAME + "'"); - ps.println(" (size=" + effDocFile.length() + ", crc=" + getFileCRC(effDocFile) + ")"); + ps.println(" (size=" + effDocFile.length() + ", md5=" + getFileMD5(effDocFile) + ")"); ps.println("#"); ps.println("#Columns: (index, name, minParams, maxParams, returnClass, paramClasses, isVolatile, hasFootnote )"); ps.println(""); @@ -490,6 +504,14 @@ public final class ExcelFileFormatDocFunctionExtractor { throw new RuntimeException(e); } ps.close(); + + String canonicalOutputFileName; + try { + canonicalOutputFileName = outFile.getCanonicalPath(); + } catch (IOException e) { + throw new RuntimeException(e); + } + System.out.println("Successfully output to '" + canonicalOutputFileName + "'"); } private static void outputLicenseHeader(PrintStream ps) { @@ -519,8 +541,14 @@ public final class ExcelFileFormatDocFunctionExtractor { /** * Helps identify the source file */ - private static String getFileCRC(File f) { - CRC32 crc = new CRC32(); + private static String getFileMD5(File f) { + MessageDigest m; + try { + m = MessageDigest.getInstance("MD5"); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + byte[]buf = new byte[2048]; try { InputStream is = new FileInputStream(f); @@ -529,21 +557,17 @@ public final class ExcelFileFormatDocFunctionExtractor { if(bytesRead<1) { break; } - crc.update(buf, 0, bytesRead); + m.update(buf, 0, bytesRead); } is.close(); } catch (IOException e) { throw new RuntimeException(e); } - return "0x" + Long.toHexString(crc.getValue()).toUpperCase(); + + return "0x" + new BigInteger(1, m.digest()).toString(16); } - private static File getSourceFile() { - if (false) { - File dir = new File("c:/temp"); - File effDocFile = new File(dir, SOURCE_DOC_FILE_NAME); - return effDocFile; - } + private static File downloadSourceFile() { URL url; try { url = new URL("http://sc.openoffice.org/" + SOURCE_DOC_FILE_NAME); @@ -557,7 +581,7 @@ public final class ExcelFileFormatDocFunctionExtractor { URLConnection conn = url.openConnection(); InputStream is = conn.getInputStream(); System.out.println("downloading " + url.toExternalForm()); - result = File.createTempFile("excelfileformat", "odt"); + result = File.createTempFile("excelfileformat", ".odt"); OutputStream os = new FileOutputStream(result); while(true) { int bytesRead = is.read(buf); @@ -577,12 +601,17 @@ public final class ExcelFileFormatDocFunctionExtractor { public static void main(String[] args) { - File effDocFile = getSourceFile(); - if(!effDocFile.exists()) { - throw new RuntimeException("file '" + effDocFile.getAbsolutePath() + "' does not exist"); - } - File outFile = new File("functionMetadata-asGenerated.txt"); - processFile(effDocFile, outFile); + + if (false) { // set true to use local file + File dir = new File("c:/temp"); + File effDocFile = new File(dir, SOURCE_DOC_FILE_NAME); + processFile(effDocFile, outFile); + return; + } + + File tempEFFDocFile = downloadSourceFile(); + processFile(tempEFFDocFile, outFile); + tempEFFDocFile.delete(); } } diff --git a/src/testcases/org/apache/poi/hssf/record/formula/function/TestParseMissingBuiltInFuncs.java b/src/testcases/org/apache/poi/hssf/record/formula/function/TestParseMissingBuiltInFuncs.java index 1e26fa706..7030c5a50 100644 --- a/src/testcases/org/apache/poi/hssf/record/formula/function/TestParseMissingBuiltInFuncs.java +++ b/src/testcases/org/apache/poi/hssf/record/formula/function/TestParseMissingBuiltInFuncs.java @@ -21,7 +21,6 @@ import junit.framework.AssertionFailedError; import junit.framework.TestCase; import org.apache.poi.hssf.model.FormulaParser; -import org.apache.poi.hssf.model.Workbook; import org.apache.poi.hssf.record.formula.AbstractFunctionPtg; import org.apache.poi.hssf.record.formula.FuncPtg; import org.apache.poi.hssf.record.formula.FuncVarPtg; @@ -29,7 +28,7 @@ import org.apache.poi.hssf.record.formula.Ptg; import org.apache.poi.hssf.usermodel.HSSFWorkbook; /** * Tests parsing of some built-in functions that were not properly - * registered in POI as bug #44675, #44733 (March/April 2008). + * registered in POI as of bug #44675, #44733 (March/April 2008). * * @author Josh Micich */ @@ -76,7 +75,7 @@ public final class TestParseMissingBuiltInFuncs extends TestCase { } public void testUsdollar() { - confirmFunc("USDOLLAR(1)", 2, false, 204); + confirmFunc("USDOLLAR(1)", 2, true, 204); } public void testDBCS() { diff --git a/src/testcases/org/apache/poi/hssf/record/formula/function/TestReadMissingBuiltInFuncs.java b/src/testcases/org/apache/poi/hssf/record/formula/function/TestReadMissingBuiltInFuncs.java index 6766f2fc0..0a62d64cd 100644 --- a/src/testcases/org/apache/poi/hssf/record/formula/function/TestReadMissingBuiltInFuncs.java +++ b/src/testcases/org/apache/poi/hssf/record/formula/function/TestReadMissingBuiltInFuncs.java @@ -17,22 +17,18 @@ package org.apache.poi.hssf.record.formula.function; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; import java.lang.reflect.InvocationTargetException; +import junit.framework.AssertionFailedError; +import junit.framework.TestCase; + import org.apache.poi.hssf.HSSFTestDataSamples; import org.apache.poi.hssf.record.RecordFormatException; import org.apache.poi.hssf.usermodel.HSSFSheet; import org.apache.poi.hssf.usermodel.HSSFWorkbook; - -import junit.framework.AssertionFailedError; -import junit.framework.TestCase; /** * Tests reading from a sample spreadsheet some built-in functions that were not properly - * registered in POI as bug #44675, #44733 (March/April 2008). + * registered in POI as of bug #44675, #44733 (March/April 2008). * * @author Josh Micich */ diff --git a/src/testcases/org/apache/poi/hssf/usermodel/AllUserModelTests.java b/src/testcases/org/apache/poi/hssf/usermodel/AllUserModelTests.java index 0e18e21e5..363e58c14 100755 --- a/src/testcases/org/apache/poi/hssf/usermodel/AllUserModelTests.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/AllUserModelTests.java @@ -28,7 +28,7 @@ import junit.framework.TestSuite; public class AllUserModelTests { public static Test suite() { - TestSuite result = new TestSuite("Tests for org.apache.poi.hssf.usermodel"); + TestSuite result = new TestSuite(AllUserModelTests.class.getName()); result.addTestSuite(TestBugs.class); result.addTestSuite(TestCellStyle.class); @@ -58,6 +58,7 @@ public class AllUserModelTests { result.addTestSuite(TestHSSFSheetSetOrder.class); result.addTestSuite(TestHSSFTextbox.class); result.addTestSuite(TestHSSFWorkbook.class); + result.addTestSuite(TestLinkTable.class); result.addTestSuite(TestNamedRange.class); result.addTestSuite(TestOLE2Embeding.class); result.addTestSuite(TestPOIFSProperties.class); diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFCell.java b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFCell.java index a2e8bd3ba..7f4375847 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFCell.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFCell.java @@ -58,6 +58,30 @@ public final class TestHSSFCell extends TestCase { } } + public void testSetValues() throws Exception { + HSSFWorkbook book = new HSSFWorkbook(); + HSSFSheet sheet = book.createSheet("test"); + HSSFRow row = sheet.createRow(0); + + HSSFCell cell = row.createCell((short)0); + + cell.setCellValue(1.2); + assertEquals(1.2, cell.getNumericCellValue(), 0.0001); + assertEquals(HSSFCell.CELL_TYPE_NUMERIC, cell.getCellType()); + + cell.setCellValue(false); + assertEquals(false, cell.getBooleanCellValue()); + assertEquals(HSSFCell.CELL_TYPE_BOOLEAN, cell.getCellType()); + + cell.setCellValue(new HSSFRichTextString("Foo")); + assertEquals("Foo", cell.getRichStringCellValue().getString()); + assertEquals(HSSFCell.CELL_TYPE_STRING, cell.getCellType()); + + cell.setCellValue(new HSSFRichTextString("345")); + assertEquals("345", cell.getRichStringCellValue().getString()); + assertEquals(HSSFCell.CELL_TYPE_STRING, cell.getCellType()); + } + /** * test that Boolean and Error types (BoolErrRecord) are supported properly. */ diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFDateUtil.java b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFDateUtil.java index cd0901a29..76c098da2 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFDateUtil.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFDateUtil.java @@ -266,6 +266,8 @@ public class TestHSSFDateUtil extends TestCase { formats = new String[] { "yyyy-mm-dd hh:mm:ss", "yyyy/mm/dd HH:MM:SS", "mm/dd HH:MM", "yy/mmm/dd SS", + "mm/dd HH:MM AM", "mm/dd HH:MM am", + "mm/dd HH:MM PM", "mm/dd HH:MM pm" }; for(int i=0; i= minWithRow1Only); assertTrue(sheet2.getColumnWidth((short)0) <= maxWithRow1Only); @@ -747,10 +682,7 @@ public final class TestHSSFSheet extends TestCase { // remove the 2nd row merged region and check that the 2nd row value is used to the autoSizeColumn width sheet2.removeMergedRegion(1); sheet2.autoSizeColumn((short)0); - out = new ByteArrayOutputStream(); - wb2.write(out); - out.close(); - HSSFWorkbook wb3 = new HSSFWorkbook(new ByteArrayInputStream(out.toByteArray())); + HSSFWorkbook wb3 = HSSFTestDataSamples.writeOutAndReadBack(wb2); HSSFSheet sheet3 = wb3.getSheet(sheetName); assertTrue(sheet3.getColumnWidth((short)0) >= minWithRow1And2); assertTrue(sheet3.getColumnWidth((short)0) <= maxWithRow1And2); @@ -827,7 +759,7 @@ public final class TestHSSFSheet extends TestCase { assertTrue(wb3.getSheetAt(3).getForceFormulaRecalculation()); } - public void testColumnWidth() throws Exception { + public void testColumnWidth() { //check we can correctly read column widths from a reference workbook HSSFWorkbook wb = openSample("colwidth.xls"); @@ -867,11 +799,8 @@ public final class TestHSSFSheet extends TestCase { } //serialize and read again - ByteArrayOutputStream out = new ByteArrayOutputStream(); - wb.write(out); - out.close(); + wb = HSSFTestDataSamples.writeOutAndReadBack(wb); - wb = new HSSFWorkbook(new ByteArrayInputStream(out.toByteArray())); sh = wb.getSheetAt(0); assertEquals(10, sh.getDefaultColumnWidth()); //columns A-C have default width diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFWorkbook.java b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFWorkbook.java index cb5b3d355..b9873fa4b 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFWorkbook.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFWorkbook.java @@ -20,12 +20,16 @@ package org.apache.poi.hssf.usermodel; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; +import java.util.List; import junit.framework.AssertionFailedError; import junit.framework.TestCase; import org.apache.poi.hssf.HSSFTestDataSamples; +import org.apache.poi.hssf.model.Sheet; import org.apache.poi.hssf.record.NameRecord; +import org.apache.poi.hssf.record.Record; +import org.apache.poi.hssf.record.RecordInputStream; import org.apache.poi.util.TempFile; /** * @@ -376,4 +380,49 @@ public final class TestHSSFWorkbook extends TestCase { assertEquals("active", expectedActive, sheet.isActive()); assertEquals("selected", expectedSelected, sheet.isSelected()); } -} + + /** + * If Sheet.getSize() returns a different result to Sheet.serialize(), this will cause the BOF + * records to be written with invalid offset indexes. Excel does not like this, and such + * errors are particularly hard to track down. This test ensures that HSSFWorkbook throws + * a specific exception as soon as the situation is detected. See bugzilla 45066 + */ + public void testSheetSerializeSizeMismatch_bug45066() { + HSSFWorkbook wb = new HSSFWorkbook(); + Sheet sheet = wb.createSheet("Sheet1").getSheet(); + List sheetRecords = sheet.getRecords(); + // one way (of many) to cause the discrepancy is with a badly behaved record: + sheetRecords.add(new BadlyBehavedRecord()); + // There is also much logic inside Sheet that (if buggy) might also cause the discrepancy + try { + wb.getBytes(); + throw new AssertionFailedError("Identified bug 45066 a"); + } catch (IllegalStateException e) { + // Expected badly behaved sheet record to cause exception + assertTrue(e.getMessage().startsWith("Actual serialized sheet size")); + } + } + /** + * result returned by getRecordSize() differs from result returned by serialize() + */ + private static final class BadlyBehavedRecord extends Record { + public BadlyBehavedRecord() { + // + } + protected void fillFields(RecordInputStream in) { + throw new RuntimeException("Should not be called"); + } + public short getSid() { + return 0x777; + } + public int serialize(int offset, byte[] data) { + return 4; + } + protected void validateSid(short id) { + throw new RuntimeException("Should not be called"); + } + public int getRecordSize() { + return 8; + } + } + } diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestLinkTable.java b/src/testcases/org/apache/poi/hssf/usermodel/TestLinkTable.java new file mode 100644 index 000000000..7d1082e86 --- /dev/null +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestLinkTable.java @@ -0,0 +1,44 @@ +package org.apache.poi.hssf.usermodel; + +import junit.framework.AssertionFailedError; +import junit.framework.TestCase; + +import org.apache.poi.hssf.HSSFTestDataSamples; +/** + * Tests for LinkTable + * + * @author Josh Micich + */ +public final class TestLinkTable extends TestCase { + + /** + * The example file attached to bugzilla 45046 is a clear example of Name records being present + * without an External Book (SupBook) record. Excel has no trouble reading this file.
+ * TODO get OOO documentation updated to reflect this (that EXTERNALBOOK is optional). + * + * It's not clear what exact steps need to be taken in Excel to create such a workbook + */ + public void testLinkTableWithoutExternalBookRecord_bug45046() { + HSSFWorkbook wb; + + try { + wb = HSSFTestDataSamples.openSampleWorkbook("ex45046-21984.xls"); + } catch (RuntimeException e) { + if ("DEFINEDNAME is part of LinkTable".equals(e.getMessage())) { + throw new AssertionFailedError("Identified bug 45046 b"); + } + throw e; + } + // some other sanity checks + assertEquals(3, wb.getNumberOfSheets()); + String formula = wb.getSheetAt(0).getRow(4).getCell(13).getCellFormula(); + + if ("ipcSummenproduktIntern($P5,N$6,$A$9,N$5)".equals(formula)) { + // The reported symptom of this bugzilla is an earlier bug (already fixed) + throw new AssertionFailedError("Identified bug 41726"); + // This is observable in version 3.0 + } + + assertEquals("ipcSummenproduktIntern($C5,N$2,$A$9,N$1)", formula); + } +} diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestSheetHiding.java b/src/testcases/org/apache/poi/hssf/usermodel/TestSheetHiding.java index fc2a24b78..62a26e90b 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/TestSheetHiding.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestSheetHiding.java @@ -45,8 +45,8 @@ public final class TestSheetHiding extends TestCase { */ public void testTextSheets() throws Exception { // Both should have two sheets - assertEquals(2, wbH.sheets.size()); - assertEquals(2, wbU.sheets.size()); + assertEquals(2, wbH.getNumberOfSheets()); + assertEquals(2, wbU.getNumberOfSheets()); // All sheets should have one row assertEquals(0, wbH.getSheetAt(0).getLastRowNum());