From: Nick Burch Date: Sat, 29 Mar 2008 16:41:25 +0000 (+0000) Subject: Merge changes from trunk to the ooxml branch - revisions 634630 to X-Git-Tag: REL_3_5_BETA2~166 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=1684f36a8f87471be7cab1a045e7bda9f0bee427;p=poi.git Merge changes from trunk to the ooxml branch - revisions 634630 to 638000 git-svn-id: https://svn.apache.org/repos/asf/poi/branches/ooxml@642554 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/src/documentation/content/xdocs/changes.xml b/src/documentation/content/xdocs/changes.xml index fdead9b9b5..4bfd32b0e6 100644 --- a/src/documentation/content/xdocs/changes.xml +++ b/src/documentation/content/xdocs/changes.xml @@ -36,6 +36,12 @@ + 44609 - Handle leading spaces in formulas, such as '= 4' + 44608 - Support for PercentPtg in the formula evaluator + 44606 - Support calculated string values for evaluated formulas + Add accessors to horizontal and vertical alignment in HSSFTextbox + 44593 - Improved handling of short DVRecords + 28627 / 44580 - Fix Range.delete() in HWPF 44539 - Support for area references in formulas of rows >= 32768 44536 - Improved support for detecting read-only recommended files 43901 - Correctly update the internal last cell number when adding and removing cells (previously sometimes off-by-one) diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml index c59cae1a3d..a6467d5164 100644 --- a/src/documentation/content/xdocs/status.xml +++ b/src/documentation/content/xdocs/status.xml @@ -33,6 +33,12 @@ + 44609 - Handle leading spaces in formulas, such as '= 4' + 44608 - Support for PercentPtg in the formula evaluator + 44606 - Support calculated string values for evaluated formulas + Add accessors to horizontal and vertical alignment in HSSFTextbox + 44593 - Improved handling of short DVRecords + 28627 / 44580 - Fix Range.delete() in HWPF 44539 - Support for area references in formulas of rows >= 32768 44536 - Improved support for detecting read-only recommended files 43901 - Correctly update the internal last cell number when adding and removing cells (previously sometimes off-by-one) diff --git a/src/java/org/apache/poi/hssf/model/FormulaParser.java b/src/java/org/apache/poi/hssf/model/FormulaParser.java index 7b89b90d81..07bb51483c 100644 --- a/src/java/org/apache/poi/hssf/model/FormulaParser.java +++ b/src/java/org/apache/poi/hssf/model/FormulaParser.java @@ -943,23 +943,7 @@ end; } Stack stack = new Stack(); - // Excel allows to have AttrPtg at position 0 (such as Blanks) which - // do not have any operands. Skip them. - int i; - if(ptgs[0] instanceof AttrPtg) { - AttrPtg attrPtg0 = (AttrPtg) ptgs[0]; - if(attrPtg0.isSemiVolatile()) { - // no visible formula for semi-volatile - } else { - // TODO -this requirement is unclear and is not addressed by any junits - stack.push(ptgs[0].toFormulaString(book)); - } - i=1; - } else { - i=0; - } - - for ( ; i < ptgs.length; i++) { + for (int i=0 ; i < ptgs.length; i++) { Ptg ptg = ptgs[i]; // TODO - what about MemNoMemPtg? if(ptg instanceof MemAreaPtg || ptg instanceof MemFuncPtg || ptg instanceof MemErrPtg) { @@ -973,21 +957,30 @@ end; continue; } - if (ptg instanceof AttrPtg && ((AttrPtg) ptg).isOptimizedIf()) { - continue; + if (ptg instanceof AttrPtg) { + AttrPtg attrPtg = ((AttrPtg) ptg); + if (attrPtg.isOptimizedIf()) { + continue; + } + if (attrPtg.isSpace()) { + // POI currently doesn't render spaces in formulas + 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 + } } final OperationPtg o = (OperationPtg) ptg; int nOperands = o.getNumberOfOperands(); final String[] operands = new String[nOperands]; - for (int j = nOperands-1; j >= 0; j--) { + for (int j = nOperands-1; j >= 0; j--) { // reverse iteration because args were pushed in-order if(stack.isEmpty()) { - //TODO: write junit to prove this works String msg = "Too few arguments suppled to operation token (" + o.getClass().getName() + "). Expected (" + nOperands - + " but got " + (nOperands - j + 1); - throw new FormulaParseException(msg); + + ") operands but got (" + (nOperands - j + 1) + ")"; + throw new IllegalStateException(msg); } operands[j] = (String) stack.pop(); } diff --git a/src/java/org/apache/poi/hssf/model/TextboxShape.java b/src/java/org/apache/poi/hssf/model/TextboxShape.java index 4b10278091..e55fcacbc3 100644 --- a/src/java/org/apache/poi/hssf/model/TextboxShape.java +++ b/src/java/org/apache/poi/hssf/model/TextboxShape.java @@ -133,8 +133,8 @@ public class TextboxShape HSSFTextbox shape = hssfShape; TextObjectRecord obj = new TextObjectRecord(); - obj.setHorizontalTextAlignment( TextObjectRecord.HORIZONTAL_TEXT_ALIGNMENT_LEFT_ALIGNED ); - obj.setVerticalTextAlignment( TextObjectRecord.VERTICAL_TEXT_ALIGNMENT_TOP ); + obj.setHorizontalTextAlignment( hssfShape.getHorizontalAlignment() ); + obj.setVerticalTextAlignment( hssfShape.getVerticalAlignment()); obj.setTextLocked( true ); obj.setTextOrientation( TextObjectRecord.TEXT_ORIENTATION_NONE ); int frLength = ( shape.getString().numFormattingRuns() + 1 ) * 8; diff --git a/src/java/org/apache/poi/hssf/record/formula/AttrPtg.java b/src/java/org/apache/poi/hssf/record/formula/AttrPtg.java index 2ba4723804..d355fbfa1f 100644 --- a/src/java/org/apache/poi/hssf/record/formula/AttrPtg.java +++ b/src/java/org/apache/poi/hssf/record/formula/AttrPtg.java @@ -33,20 +33,42 @@ import org.apache.poi.util.BitFieldFactory; * @author Jason Height (jheight at chariot dot net dot au) */ -public class AttrPtg - extends OperationPtg -{ +public final class AttrPtg extends OperationPtg { public final static byte sid = 0x19; private final static int SIZE = 4; private byte field_1_options; private short field_2_data; - private BitField semiVolatile = BitFieldFactory.getInstance(0x01); - private BitField optiIf = BitFieldFactory.getInstance(0x02); - private BitField optiChoose = BitFieldFactory.getInstance(0x04); - private BitField optGoto = BitFieldFactory.getInstance(0x08); - private BitField sum = BitFieldFactory.getInstance(0x10); - private BitField baxcel = BitFieldFactory.getInstance(0x20); - private BitField space = BitFieldFactory.getInstance(0x40); + + // flags 'volatile' and 'space', can be combined. + // OOO spec says other combinations are theoretically possible but not likely to occur. + private static final BitField semiVolatile = BitFieldFactory.getInstance(0x01); + private static final BitField optiIf = BitFieldFactory.getInstance(0x02); + private static final BitField optiChoose = BitFieldFactory.getInstance(0x04); + private static final BitField optGoto = BitFieldFactory.getInstance(0x08); // skip + private static final BitField sum = BitFieldFactory.getInstance(0x10); + private static final BitField baxcel = BitFieldFactory.getInstance(0x20); // 'assignment-style formula in a macro sheet' + private static final BitField space = BitFieldFactory.getInstance(0x40); + + public static final class SpaceType { + private SpaceType() { + // no instances of this class + } + + /** 00H = Spaces before the next token (not allowed before tParen token) */ + public static final int SPACE_BEFORE = 0x00; + /** 01H = Carriage returns before the next token (not allowed before tParen token) */ + public static final int CR_BEFORE = 0x01; + /** 02H = Spaces before opening parenthesis (only allowed before tParen token) */ + public static final int SPACE_BEFORE_OPEN_PAREN = 0x02; + /** 03H = Carriage returns before opening parenthesis (only allowed before tParen token) */ + public static final int CR_BEFORE_OPEN_PAREN = 0x03; + /** 04H = Spaces before closing parenthesis (only allowed before tParen, tFunc, and tFuncVar tokens) */ + public static final int SPACE_BEFORE_CLOSE_PAERN = 0x04; + /** 05H = Carriage returns before closing parenthesis (only allowed before tParen, tFunc, and tFuncVar tokens) */ + public static final int CR_BEFORE_CLOSE_PAREN = 0x05; + /** 06H = Spaces following the equality sign (only in macro sheets) */ + public static final int SPACE_AFTER_EQUALITY = 0x06; + } public AttrPtg() { } @@ -56,6 +78,19 @@ public class AttrPtg field_1_options = in.readByte(); field_2_data = in.readShort(); } + private AttrPtg(int options, int data) { + field_1_options = (byte) options; + field_2_data = (short) data; + } + + /** + * @param type a constant from SpaceType + * @param count the number of space characters + */ + public static AttrPtg createSpace(int type, int count) { + int data = type & 0x00FF | (count << 8) & 0x00FFFF; + return new AttrPtg(space.set(0), data); + } public void setOptions(byte options) { @@ -131,21 +166,31 @@ public class AttrPtg return field_2_data; } - public String toString() - { - StringBuffer buffer = new StringBuffer(); + public String toString() { + StringBuffer sb = new StringBuffer(64); + sb.append(getClass().getName()).append(" ["); - buffer.append("AttrPtg\n"); - buffer.append("options=").append(field_1_options).append("\n"); - buffer.append("data =").append(field_2_data).append("\n"); - buffer.append("semi =").append(isSemiVolatile()).append("\n"); - buffer.append("optimif=").append(isOptimizedIf()).append("\n"); - buffer.append("optchos=").append(isOptimizedChoose()).append("\n"); - buffer.append("isGoto =").append(isGoto()).append("\n"); - buffer.append("isSum =").append(isSum()).append("\n"); - buffer.append("isBaxce=").append(isBaxcel()).append("\n"); - buffer.append("isSpace=").append(isSpace()).append("\n"); - return buffer.toString(); + if(isSemiVolatile()) { + sb.append("volatile "); + } + if(isSpace()) { + sb.append("space count=").append((field_2_data >> 8) & 0x00FF); + sb.append(" type=").append(field_2_data & 0x00FF).append(" "); + } + // the rest seem to be mutually exclusive + if(isOptimizedIf()) { + sb.append("if dist=").append(getData()); + } else if(isOptimizedChoose()) { + sb.append("choose dist=").append(getData()); + } else if(isGoto()) { + sb.append("skip dist=").append(getData()); + } else if(isSum()) { + sb.append("sum "); + } else if(isBaxcel()) { + sb.append("assign "); + } + sb.append("]"); + return sb.toString(); } public void writeBytes(byte [] array, int offset) diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java b/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java index ae8a8253e6..ededf0d648 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java @@ -603,29 +603,30 @@ public class HSSFCell implements Cell if (hvalue == null) { setCellType(CELL_TYPE_BLANK, false, row, col, styleIndex); + return; } - else - { - if ((cellType != CELL_TYPE_STRING ) && ( cellType != CELL_TYPE_FORMULA)) - { - setCellType(CELL_TYPE_STRING, false, row, col, styleIndex); - } - int index = 0; - - UnicodeString str = hvalue.getUnicodeString(); -// jmh if (encoding == ENCODING_COMPRESSED_UNICODE) -// jmh { -// jmh str.setCompressedUnicode(); -// jmh } else if (encoding == ENCODING_UTF_16) -// jmh { -// jmh str.setUncompressedUnicode(); -// jmh } - index = book.addSSTString(str); - (( LabelSSTRecord ) record).setSSTIndex(index); - stringValue = hvalue; - stringValue.setWorkbookReferences(book, (( LabelSSTRecord ) record)); - stringValue.setUnicodeString(book.getSSTString(index)); + if (cellType == CELL_TYPE_FORMULA) { + // Set the 'pre-evaluated result' for the formula + // note - formulas do not preserve text formatting. + FormulaRecordAggregate fr = (FormulaRecordAggregate) record; + // must make new sr because fr.getStringRecord() may be null + StringRecord sr = new StringRecord(); + sr.setString(hvalue.getString()); // looses format + fr.setStringRecord(sr); + return; + } + + if (cellType != CELL_TYPE_STRING) { + setCellType(CELL_TYPE_STRING, false, row, col, styleIndex); } + int index = 0; + + UnicodeString str = hvalue.getUnicodeString(); + index = book.addSSTString(str); + (( LabelSSTRecord ) record).setSSTIndex(index); + stringValue = hvalue; + stringValue.setWorkbookReferences(book, (( LabelSSTRecord ) record)); + stringValue.setUnicodeString(book.getSSTString(index)); } public void setCellFormula(String formula) { diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFTextbox.java b/src/java/org/apache/poi/hssf/usermodel/HSSFTextbox.java index ac86b2d8dd..48816131a4 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFTextbox.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFTextbox.java @@ -19,6 +19,9 @@ package org.apache.poi.hssf.usermodel; import org.apache.poi.ss.usermodel.RichTextString; +import org.apache.poi.util.BitField; +import org.apache.poi.util.BitFieldFactory; + /** * A textbox is a shape that may hold a rich text string. * @@ -29,7 +32,27 @@ public class HSSFTextbox { public final static short OBJECT_TYPE_TEXT = 6; + /** + * How to align text horizontally + */ + public final static short HORIZONTAL_ALIGNMENT_LEFT = 1; + public final static short HORIZONTAL_ALIGNMENT_CENTERED = 2; + public final static short HORIZONTAL_ALIGNMENT_RIGHT = 3; + public final static short HORIZONTAL_ALIGNMENT_JUSTIFIED = 4; + public final static short HORIZONTAL_ALIGNMENT_DISTRIBUTED = 7; + + /** + * How to align text vertically + */ + public final static short VERTICAL_ALIGNMENT_TOP = 1; + public final static short VERTICAL_ALIGNMENT_CENTER = 2; + public final static short VERTICAL_ALIGNMENT_BOTTOM = 3; + public final static short VERTICAL_ALIGNMENT_JUSTIFY = 4; + public final static short VERTICAL_ALIGNMENT_DISTRIBUTED= 7; + + int marginLeft, marginRight, marginTop, marginBottom; + short halign, valign; HSSFRichTextString string = new HSSFRichTextString(""); @@ -42,6 +65,9 @@ public class HSSFTextbox { super( parent, anchor ); setShapeType(OBJECT_TYPE_TEXT); + + halign = HORIZONTAL_ALIGNMENT_LEFT; + valign = VERTICAL_ALIGNMENT_TOP; } /** @@ -123,4 +149,36 @@ public class HSSFTextbox { this.marginBottom = marginBottom; } + + /** + * Gets the horizontal alignment. + */ + public short getHorizontalAlignment() + { + return halign; + } + + /** + * Sets the horizontal alignment. + */ + public void setHorizontalAlignment( short align ) + { + this.halign = align; + } + + /** + * Gets the vertical alignment. + */ + public short getVerticalAlignment() + { + return valign; + } + + /** + * Sets the vertical alignment. + */ + public void setVerticalAlignment( short align ) + { + this.valign = align; + } } diff --git a/src/java/org/apache/poi/hssf/util/HSSFCellRangeAddress.java b/src/java/org/apache/poi/hssf/util/HSSFCellRangeAddress.java index 438f5e5968..73804966fb 100644 --- a/src/java/org/apache/poi/hssf/util/HSSFCellRangeAddress.java +++ b/src/java/org/apache/poi/hssf/util/HSSFCellRangeAddress.java @@ -18,6 +18,9 @@ package org.apache.poi.hssf.util; import org.apache.poi.hssf.record.RecordInputStream; import org.apache.poi.util.LittleEndian; +import org.apache.poi.util.POILogFactory; +import org.apache.poi.util.POILogger; + import java.util.ArrayList; /** @@ -38,6 +41,8 @@ import java.util.ArrayList; public class HSSFCellRangeAddress { + private static POILogger logger = POILogFactory.getLogger(HSSFCellRangeAddress.class); + /** * Number of following ADDR structures */ @@ -74,8 +79,19 @@ public class HSSFCellRangeAddress { short first_row = in.readShort(); short first_col = in.readShort(); - short last_row = in.readShort(); - short last_col = in.readShort(); + + short last_row = first_row; + short last_col = first_col; + if(in.remaining() >= 4) { + last_row = in.readShort(); + last_col = in.readShort(); + } else { + // Ran out of data + // For now, issue a warning, finish, and + // hope for the best.... + logger.log(POILogger.WARN, "Ran out of data reading cell references for DVRecord"); + k = this.field_addr_number; + } AddrStructure region = new AddrStructure(first_row, first_col, last_row, last_col); this.field_regions_list.add(region); diff --git a/src/java/org/apache/poi/poifs/common/POIFSConstants.java b/src/java/org/apache/poi/poifs/common/POIFSConstants.java index 399f52be4b..ff2050274d 100644 --- a/src/java/org/apache/poi/poifs/common/POIFSConstants.java +++ b/src/java/org/apache/poi/poifs/common/POIFSConstants.java @@ -27,7 +27,11 @@ package org.apache.poi.poifs.common; public interface POIFSConstants { + /** Most files use 512 bytes as their big block size */ public static final int BIG_BLOCK_SIZE = 0x0200; + /** Some use 4096 bytes */ + public static final int LARGER_BIG_BLOCK_SIZE = 0x1000; + public static final int END_OF_CHAIN = -2; public static final int PROPERTY_SIZE = 0x0080; public static final int UNUSED_BLOCK = -1; diff --git a/src/java/org/apache/poi/poifs/eventfilesystem/POIFSReader.java b/src/java/org/apache/poi/poifs/eventfilesystem/POIFSReader.java index fe94b4aaf9..73911e6b0e 100644 --- a/src/java/org/apache/poi/poifs/eventfilesystem/POIFSReader.java +++ b/src/java/org/apache/poi/poifs/eventfilesystem/POIFSReader.java @@ -78,7 +78,7 @@ public class POIFSReader HeaderBlockReader header_block_reader = new HeaderBlockReader(stream); // read the rest of the stream into blocks - RawDataBlockList data_blocks = new RawDataBlockList(stream); + RawDataBlockList data_blocks = new RawDataBlockList(stream, header_block_reader.getBigBlockSize()); // set up the block allocation table (necessary for the // data_blocks to be manageable diff --git a/src/java/org/apache/poi/poifs/filesystem/POIFSFileSystem.java b/src/java/org/apache/poi/poifs/filesystem/POIFSFileSystem.java index ef9acfe60b..61774dc676 100644 --- a/src/java/org/apache/poi/poifs/filesystem/POIFSFileSystem.java +++ b/src/java/org/apache/poi/poifs/filesystem/POIFSFileSystem.java @@ -33,6 +33,7 @@ import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.apache.poi.poifs.common.POIFSConstants; import org.apache.poi.poifs.dev.POIFSViewable; import org.apache.poi.poifs.property.DirectoryProperty; import org.apache.poi.poifs.property.Property; @@ -63,7 +64,6 @@ public class POIFSFileSystem { private static final Log _logger = LogFactory.getLog(POIFSFileSystem.class); - private static final class CloseIgnoringInputStream extends InputStream { private final InputStream _is; @@ -91,11 +91,16 @@ public class POIFSFileSystem private PropertyTable _property_table; private List _documents; private DirectoryNode _root; + + /** + * What big block size the file uses. Most files + * use 512 bytes, but a few use 4096 + */ + private int bigBlockSize = POIFSConstants.BIG_BLOCK_SIZE; /** * Constructor, intended for writing */ - public POIFSFileSystem() { _property_table = new PropertyTable(); @@ -138,13 +143,15 @@ public class POIFSFileSystem this(); boolean success = false; - // read the header block from the stream HeaderBlockReader header_block_reader; - // read the rest of the stream into blocks RawDataBlockList data_blocks; try { + // read the header block from the stream header_block_reader = new HeaderBlockReader(stream); - data_blocks = new RawDataBlockList(stream); + bigBlockSize = header_block_reader.getBigBlockSize(); + + // read the rest of the stream into blocks + data_blocks = new RawDataBlockList(stream, bigBlockSize); success = true; } finally { closeInputStream(stream, success); @@ -307,7 +314,7 @@ public class POIFSFileSystem // create a list of BATManaged objects: the documents plus the // property table and the small block table - List bm_objects = new ArrayList(); + List bm_objects = new ArrayList(); bm_objects.addAll(_documents); bm_objects.add(_property_table); @@ -602,6 +609,13 @@ public class POIFSFileSystem return "POIFS FileSystem"; } + /** + * @return The Big Block size, normally 512 bytes, sometimes 4096 bytes + */ + public int getBigBlockSize() { + return bigBlockSize; + } + /* ********** END begin implementation of POIFSViewable ********** */ } // end public class POIFSFileSystem diff --git a/src/java/org/apache/poi/poifs/storage/HeaderBlockReader.java b/src/java/org/apache/poi/poifs/storage/HeaderBlockReader.java index 0d5bb817b4..b001b81058 100644 --- a/src/java/org/apache/poi/poifs/storage/HeaderBlockReader.java +++ b/src/java/org/apache/poi/poifs/storage/HeaderBlockReader.java @@ -21,8 +21,6 @@ package org.apache.poi.poifs.storage; import java.io.*; -import java.util.*; - import org.apache.poi.poifs.common.POIFSConstants; import org.apache.poi.poifs.filesystem.OfficeXmlFileException; import org.apache.poi.util.IOUtils; @@ -30,7 +28,6 @@ import org.apache.poi.util.IntegerField; import org.apache.poi.util.LittleEndian; import org.apache.poi.util.LittleEndianConsts; import org.apache.poi.util.LongField; -import org.apache.poi.util.ShortField; /** * The block containing the archive header @@ -41,6 +38,11 @@ import org.apache.poi.util.ShortField; public class HeaderBlockReader implements HeaderBlockConstants { + /** + * What big block size the file uses. Most files + * use 512 bytes, but a few use 4096 + */ + private int bigBlockSize = POIFSConstants.BIG_BLOCK_SIZE; // number of big block allocation table blocks (int) private IntegerField _bat_count; @@ -69,20 +71,27 @@ public class HeaderBlockReader public HeaderBlockReader(final InputStream stream) throws IOException { - _data = new byte[ POIFSConstants.BIG_BLOCK_SIZE ]; - int byte_count = IOUtils.readFully(stream, _data); - - if (byte_count != POIFSConstants.BIG_BLOCK_SIZE) - { - if (byte_count == -1) - //Cant have -1 bytes read in the error message! - byte_count = 0; - String type = " byte" + ((byte_count == 1) ? ("") - : ("s")); - - throw new IOException("Unable to read entire header; " - + byte_count + type + " read; expected " - + POIFSConstants.BIG_BLOCK_SIZE + " bytes"); + // At this point, we don't know how big our + // block sizes are + // So, read the first 32 bytes to check, then + // read the rest of the block + byte[] blockStart = new byte[32]; + int bsCount = IOUtils.readFully(stream, blockStart); + if(bsCount != 32) { + alertShortRead(bsCount); + } + + // Figure out our block size + if(blockStart[30] == 12) { + bigBlockSize = POIFSConstants.LARGER_BIG_BLOCK_SIZE; + } + _data = new byte[ bigBlockSize ]; + System.arraycopy(blockStart, 0, _data, 0, blockStart.length); + + // Now we can read the rest of our header + int byte_count = IOUtils.readFully(stream, _data, blockStart.length, _data.length - blockStart.length); + if (byte_count+bsCount != bigBlockSize) { + alertShortRead(byte_count); } // verify signature @@ -110,13 +119,24 @@ public class HeaderBlockReader _xbat_start = new IntegerField(_xbat_start_offset, _data); _xbat_count = new IntegerField(_xbat_count_offset, _data); } + + private void alertShortRead(int read) throws IOException { + if (read == -1) + //Cant have -1 bytes read in the error message! + read = 0; + String type = " byte" + ((read == 1) ? ("") + : ("s")); + + throw new IOException("Unable to read entire header; " + + read + type + " read; expected " + + bigBlockSize + " bytes"); + } /** * get start of Property Table * * @return the index of the first block of the Property Table */ - public int getPropertyStart() { return _property_start.get(); @@ -174,5 +194,12 @@ public class HeaderBlockReader { return _xbat_start.get(); } + + /** + * @return The Big Block size, normally 512 bytes, sometimes 4096 bytes + */ + public int getBigBlockSize() { + return bigBlockSize; + } } // end public class HeaderBlockReader diff --git a/src/java/org/apache/poi/poifs/storage/RawDataBlockList.java b/src/java/org/apache/poi/poifs/storage/RawDataBlockList.java index eed318fb55..76ab219562 100644 --- a/src/java/org/apache/poi/poifs/storage/RawDataBlockList.java +++ b/src/java/org/apache/poi/poifs/storage/RawDataBlockList.java @@ -37,19 +37,20 @@ public class RawDataBlockList * Constructor RawDataBlockList * * @param stream the InputStream from which the data will be read + * @param bigBlockSize The big block size, either 512 bytes or 4096 bytes * * @exception IOException on I/O errors, and if an incomplete * block is read */ - public RawDataBlockList(final InputStream stream) + public RawDataBlockList(final InputStream stream, int bigBlockSize) throws IOException { List blocks = new ArrayList(); while (true) { - RawDataBlock block = new RawDataBlock(stream); + RawDataBlock block = new RawDataBlock(stream, bigBlockSize); if (block.eof()) { diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/PercentEval.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/PercentEval.java new file mode 100755 index 0000000000..c698a4e502 --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/PercentEval.java @@ -0,0 +1,71 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.hssf.record.formula.eval; + +import org.apache.poi.hssf.record.formula.PercentPtg; +import org.apache.poi.hssf.record.formula.Ptg; + +/** + * Implementation of Excel formula token '%'.

+ * @author Josh Micich + */ +public final class PercentEval extends NumericOperationEval { + + private PercentPtg _delegate; + + private static final ValueEvalToNumericXlator NUM_XLATOR = new ValueEvalToNumericXlator( + (short) (ValueEvalToNumericXlator.BOOL_IS_PARSED + | ValueEvalToNumericXlator.REF_BOOL_IS_PARSED + | ValueEvalToNumericXlator.STRING_IS_PARSED | ValueEvalToNumericXlator.REF_STRING_IS_PARSED)); + + public PercentEval(Ptg ptg) { + _delegate = (PercentPtg) ptg; + } + + protected ValueEvalToNumericXlator getXlator() { + return NUM_XLATOR; + } + + public Eval evaluate(Eval[] args, int srcRow, short srcCol) { + if (args.length != 1) { + return ErrorEval.VALUE_INVALID; + } + + ValueEval ve = singleOperandEvaluate(args[0], srcRow, srcCol); + if (ve instanceof NumericValueEval) { + double d0 = ((NumericValueEval) ve).getNumberValue(); + return new NumberEval(d0 / 100); + } + + if (ve instanceof BlankEval) { + return NumberEval.ZERO; + } + if (ve instanceof ErrorEval) { + return ve; + } + return ErrorEval.VALUE_INVALID; + } + + public int getNumberOfOperands() { + return _delegate.getNumberOfOperands(); + } + + public int getType() { + return _delegate.getType(); + } +} diff --git a/src/scratchpad/src/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java b/src/scratchpad/src/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java index 3fce306557..58ab5b47ae 100644 --- a/src/scratchpad/src/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java +++ b/src/scratchpad/src/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java @@ -24,71 +24,40 @@ import java.util.Stack; import org.apache.poi.hssf.model.FormulaParser; import org.apache.poi.hssf.model.Workbook; -import org.apache.poi.hssf.record.formula.AddPtg; import org.apache.poi.hssf.record.formula.Area3DPtg; import org.apache.poi.hssf.record.formula.AreaPtg; import org.apache.poi.hssf.record.formula.AttrPtg; import org.apache.poi.hssf.record.formula.BoolPtg; -import org.apache.poi.hssf.record.formula.ConcatPtg; import org.apache.poi.hssf.record.formula.ControlPtg; -import org.apache.poi.hssf.record.formula.DividePtg; -import org.apache.poi.hssf.record.formula.EqualPtg; -import org.apache.poi.hssf.record.formula.FuncPtg; -import org.apache.poi.hssf.record.formula.FuncVarPtg; -import org.apache.poi.hssf.record.formula.GreaterEqualPtg; -import org.apache.poi.hssf.record.formula.GreaterThanPtg; import org.apache.poi.hssf.record.formula.IntPtg; -import org.apache.poi.hssf.record.formula.LessEqualPtg; -import org.apache.poi.hssf.record.formula.LessThanPtg; import org.apache.poi.hssf.record.formula.MemErrPtg; import org.apache.poi.hssf.record.formula.MissingArgPtg; -import org.apache.poi.hssf.record.formula.MultiplyPtg; import org.apache.poi.hssf.record.formula.NamePtg; import org.apache.poi.hssf.record.formula.NameXPtg; -import org.apache.poi.hssf.record.formula.NotEqualPtg; import org.apache.poi.hssf.record.formula.NumberPtg; import org.apache.poi.hssf.record.formula.OperationPtg; import org.apache.poi.hssf.record.formula.ParenthesisPtg; -import org.apache.poi.hssf.record.formula.PowerPtg; import org.apache.poi.hssf.record.formula.Ptg; import org.apache.poi.hssf.record.formula.Ref3DPtg; import org.apache.poi.hssf.record.formula.ReferencePtg; import org.apache.poi.hssf.record.formula.StringPtg; -import org.apache.poi.hssf.record.formula.SubtractPtg; -import org.apache.poi.hssf.record.formula.UnaryMinusPtg; -import org.apache.poi.hssf.record.formula.UnaryPlusPtg; import org.apache.poi.hssf.record.formula.UnionPtg; import org.apache.poi.hssf.record.formula.UnknownPtg; -import org.apache.poi.hssf.record.formula.eval.AddEval; import org.apache.poi.hssf.record.formula.eval.Area2DEval; import org.apache.poi.hssf.record.formula.eval.Area3DEval; import org.apache.poi.hssf.record.formula.eval.AreaEval; import org.apache.poi.hssf.record.formula.eval.BlankEval; import org.apache.poi.hssf.record.formula.eval.BoolEval; -import org.apache.poi.hssf.record.formula.eval.ConcatEval; -import org.apache.poi.hssf.record.formula.eval.DivideEval; -import org.apache.poi.hssf.record.formula.eval.EqualEval; import org.apache.poi.hssf.record.formula.eval.ErrorEval; import org.apache.poi.hssf.record.formula.eval.Eval; -import org.apache.poi.hssf.record.formula.eval.FuncVarEval; import org.apache.poi.hssf.record.formula.eval.FunctionEval; -import org.apache.poi.hssf.record.formula.eval.GreaterEqualEval; -import org.apache.poi.hssf.record.formula.eval.GreaterThanEval; -import org.apache.poi.hssf.record.formula.eval.LessEqualEval; -import org.apache.poi.hssf.record.formula.eval.LessThanEval; -import org.apache.poi.hssf.record.formula.eval.MultiplyEval; import org.apache.poi.hssf.record.formula.eval.NameEval; -import org.apache.poi.hssf.record.formula.eval.NotEqualEval; import org.apache.poi.hssf.record.formula.eval.NumberEval; import org.apache.poi.hssf.record.formula.eval.OperationEval; -import org.apache.poi.hssf.record.formula.eval.PowerEval; import org.apache.poi.hssf.record.formula.eval.Ref2DEval; import org.apache.poi.hssf.record.formula.eval.Ref3DEval; import org.apache.poi.hssf.record.formula.eval.RefEval; import org.apache.poi.hssf.record.formula.eval.StringEval; -import org.apache.poi.hssf.record.formula.eval.SubtractEval; -import org.apache.poi.hssf.record.formula.eval.UnaryMinusEval; -import org.apache.poi.hssf.record.formula.eval.UnaryPlusEval; import org.apache.poi.hssf.record.formula.eval.ValueEval; /** @@ -98,8 +67,6 @@ import org.apache.poi.hssf.record.formula.eval.ValueEval; public class HSSFFormulaEvaluator { // params to lookup the right constructor using reflection - private static final Class[] OPERATION_CONSTRUCTOR_CLASS_ARRAY = new Class[] { Ptg.class }; - private static final Class[] VALUE_CONTRUCTOR_CLASS_ARRAY = new Class[] { Ptg.class }; private static final Class[] AREA3D_CONSTRUCTOR_CLASS_ARRAY = new Class[] { Ptg.class, ValueEval[].class }; @@ -111,8 +78,6 @@ public class HSSFFormulaEvaluator { // Maps for mapping *Eval to *Ptg private static final Map VALUE_EVALS_MAP = new HashMap(); - private static final Map OPERATION_EVALS_MAP = new HashMap(); - /* * Following is the mapping between the Ptg tokens returned * by the FormulaParser and the *Eval classes that are used @@ -124,26 +89,6 @@ public class HSSFFormulaEvaluator { VALUE_EVALS_MAP.put(NumberPtg.class, NumberEval.class); VALUE_EVALS_MAP.put(StringPtg.class, StringEval.class); - OPERATION_EVALS_MAP.put(AddPtg.class, AddEval.class); - OPERATION_EVALS_MAP.put(ConcatPtg.class, ConcatEval.class); - OPERATION_EVALS_MAP.put(DividePtg.class, DivideEval.class); - OPERATION_EVALS_MAP.put(EqualPtg.class, EqualEval.class); - //OPERATION_EVALS_MAP.put(ExpPtg.class, ExpEval.class); // TODO: check - // this - OPERATION_EVALS_MAP.put(FuncPtg.class, FuncVarEval.class); // TODO: - // check this - OPERATION_EVALS_MAP.put(FuncVarPtg.class, FuncVarEval.class); - OPERATION_EVALS_MAP.put(GreaterEqualPtg.class, GreaterEqualEval.class); - OPERATION_EVALS_MAP.put(GreaterThanPtg.class, GreaterThanEval.class); - OPERATION_EVALS_MAP.put(LessEqualPtg.class, LessEqualEval.class); - OPERATION_EVALS_MAP.put(LessThanPtg.class, LessThanEval.class); - OPERATION_EVALS_MAP.put(MultiplyPtg.class, MultiplyEval.class); - OPERATION_EVALS_MAP.put(NotEqualPtg.class, NotEqualEval.class); - OPERATION_EVALS_MAP.put(PowerPtg.class, PowerEval.class); - OPERATION_EVALS_MAP.put(SubtractPtg.class, SubtractEval.class); - OPERATION_EVALS_MAP.put(UnaryMinusPtg.class, UnaryMinusEval.class); - OPERATION_EVALS_MAP.put(UnaryPlusPtg.class, UnaryPlusEval.class); - } @@ -402,7 +347,7 @@ public class HSSFFormulaEvaluator { if (optg instanceof AttrPtg) { continue; } if (optg instanceof UnionPtg) { continue; } - OperationEval operation = (OperationEval) getOperationEvalForPtg(optg); + OperationEval operation = OperationEvaluatorFactory.create(optg); int numops = operation.getNumberOfOperands(); Eval[] ops = new Eval[numops]; @@ -557,25 +502,6 @@ public class HSSFFormulaEvaluator { return values; } - /** - * returns the OperationEval concrete impl instance corresponding - * to the suplied operationPtg - * @param ptg - */ - protected static Eval getOperationEvalForPtg(OperationPtg ptg) { - Eval retval = null; - - Class clazz = (Class) OPERATION_EVALS_MAP.get(ptg.getClass()); - try { - Constructor constructor = clazz.getConstructor(OPERATION_CONSTRUCTOR_CLASS_ARRAY); - retval = (OperationEval) constructor.newInstance(new Ptg[] { ptg }); - } - catch (Exception e) { - throw new RuntimeException("Fatal Error: ", e); - } - return retval; - } - /** * returns an appropriate Eval impl instance for the Ptg. The Ptg must be * one of: Area3DPtg, AreaPtg, ReferencePtg, Ref3DPtg, IntPtg, NumberPtg, diff --git a/src/scratchpad/src/org/apache/poi/hssf/usermodel/OperationEvaluatorFactory.java b/src/scratchpad/src/org/apache/poi/hssf/usermodel/OperationEvaluatorFactory.java new file mode 100755 index 0000000000..1292009699 --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hssf/usermodel/OperationEvaluatorFactory.java @@ -0,0 +1,165 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.hssf.usermodel; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Modifier; +import java.util.HashMap; +import java.util.Map; + +import org.apache.poi.hssf.record.formula.AddPtg; +import org.apache.poi.hssf.record.formula.ConcatPtg; +import org.apache.poi.hssf.record.formula.DividePtg; +import org.apache.poi.hssf.record.formula.EqualPtg; +import org.apache.poi.hssf.record.formula.ExpPtg; +import org.apache.poi.hssf.record.formula.FuncPtg; +import org.apache.poi.hssf.record.formula.FuncVarPtg; +import org.apache.poi.hssf.record.formula.GreaterEqualPtg; +import org.apache.poi.hssf.record.formula.GreaterThanPtg; +import org.apache.poi.hssf.record.formula.LessEqualPtg; +import org.apache.poi.hssf.record.formula.LessThanPtg; +import org.apache.poi.hssf.record.formula.MultiplyPtg; +import org.apache.poi.hssf.record.formula.NotEqualPtg; +import org.apache.poi.hssf.record.formula.OperationPtg; +import org.apache.poi.hssf.record.formula.PercentPtg; +import org.apache.poi.hssf.record.formula.PowerPtg; +import org.apache.poi.hssf.record.formula.Ptg; +import org.apache.poi.hssf.record.formula.SubtractPtg; +import org.apache.poi.hssf.record.formula.UnaryMinusPtg; +import org.apache.poi.hssf.record.formula.UnaryPlusPtg; +import org.apache.poi.hssf.record.formula.eval.AddEval; +import org.apache.poi.hssf.record.formula.eval.ConcatEval; +import org.apache.poi.hssf.record.formula.eval.DivideEval; +import org.apache.poi.hssf.record.formula.eval.EqualEval; +import org.apache.poi.hssf.record.formula.eval.FuncVarEval; +import org.apache.poi.hssf.record.formula.eval.GreaterEqualEval; +import org.apache.poi.hssf.record.formula.eval.GreaterThanEval; +import org.apache.poi.hssf.record.formula.eval.LessEqualEval; +import org.apache.poi.hssf.record.formula.eval.LessThanEval; +import org.apache.poi.hssf.record.formula.eval.MultiplyEval; +import org.apache.poi.hssf.record.formula.eval.NotEqualEval; +import org.apache.poi.hssf.record.formula.eval.OperationEval; +import org.apache.poi.hssf.record.formula.eval.PercentEval; +import org.apache.poi.hssf.record.formula.eval.PowerEval; +import org.apache.poi.hssf.record.formula.eval.SubtractEval; +import org.apache.poi.hssf.record.formula.eval.UnaryMinusEval; +import org.apache.poi.hssf.record.formula.eval.UnaryPlusEval; + +/** + * This class creates OperationEval instances to help evaluate OperationPtg + * formula tokens. + * + * @author Josh Micich + */ +final class OperationEvaluatorFactory { + private static final Class[] OPERATION_CONSTRUCTOR_CLASS_ARRAY = new Class[] { Ptg.class }; + + private static final Map _constructorsByPtgClass = initialiseConstructorsMap(); + + private OperationEvaluatorFactory() { + // no instances of this class + } + + private static Map initialiseConstructorsMap() { + Map m = new HashMap(32); + add(m, AddPtg.class, AddEval.class); + add(m, ConcatPtg.class, ConcatEval.class); + add(m, DividePtg.class, DivideEval.class); + add(m, EqualPtg.class, EqualEval.class); + add(m, FuncPtg.class, FuncVarEval.class); + add(m, FuncVarPtg.class, FuncVarEval.class); + add(m, GreaterEqualPtg.class, GreaterEqualEval.class); + add(m, GreaterThanPtg.class, GreaterThanEval.class); + add(m, LessEqualPtg.class, LessEqualEval.class); + add(m, LessThanPtg.class, LessThanEval.class); + add(m, MultiplyPtg.class, MultiplyEval.class); + add(m, NotEqualPtg.class, NotEqualEval.class); + add(m, PercentPtg.class, PercentEval.class); + add(m, PowerPtg.class, PowerEval.class); + add(m, SubtractPtg.class, SubtractEval.class); + add(m, UnaryMinusPtg.class, UnaryMinusEval.class); + add(m, UnaryPlusPtg.class, UnaryPlusEval.class); + return m; + } + + private static void add(Map m, Class ptgClass, Class evalClass) { + + // perform some validation now, to keep later exception handlers simple + if(!Ptg.class.isAssignableFrom(ptgClass)) { + throw new IllegalArgumentException("Expected Ptg subclass"); + } + if(!OperationEval.class.isAssignableFrom(evalClass)) { + throw new IllegalArgumentException("Expected OperationEval subclass"); + } + if (!Modifier.isPublic(evalClass.getModifiers())) { + throw new RuntimeException("Eval class must be public"); + } + if (Modifier.isAbstract(evalClass.getModifiers())) { + throw new RuntimeException("Eval class must not be abstract"); + } + + Constructor constructor; + try { + constructor = evalClass.getDeclaredConstructor(OPERATION_CONSTRUCTOR_CLASS_ARRAY); + } catch (NoSuchMethodException e) { + throw new RuntimeException("Missing constructor"); + } + if (!Modifier.isPublic(constructor.getModifiers())) { + throw new RuntimeException("Eval constructor must be public"); + } + m.put(ptgClass, constructor); + } + + /** + * returns the OperationEval concrete impl instance corresponding + * to the supplied operationPtg + */ + public static OperationEval create(OperationPtg ptg) { + if(ptg == null) { + throw new IllegalArgumentException("ptg must not be null"); + } + + Class ptgClass = ptg.getClass(); + + Constructor constructor = (Constructor) _constructorsByPtgClass.get(ptgClass); + if(constructor == null) { + if(ptgClass == ExpPtg.class) { + // ExpPtg is used for array formulas and shared formulas. + // it is currently unsupported, and may not even get implemented here + throw new RuntimeException("ExpPtg currently not supported"); + } + throw new RuntimeException("Unexpected operation ptg class (" + ptgClass.getName() + ")"); + } + + Object result; + Object[] initargs = { ptg }; + try { + result = constructor.newInstance(initargs); + } catch (IllegalArgumentException e) { + throw new RuntimeException(e); + } catch (InstantiationException e) { + throw new RuntimeException(e); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } catch (InvocationTargetException e) { + throw new RuntimeException(e); + } + return (OperationEval) result; + } +} 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 593214b180..67c634d9f6 100644 --- a/src/scratchpad/src/org/apache/poi/hwpf/model/TextPiece.java +++ b/src/scratchpad/src/org/apache/poi/hwpf/model/TextPiece.java @@ -90,7 +90,19 @@ public class TextPiece extends PropertyNode implements Comparable public void adjustForDelete(int start, int length) { - + 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); + ((StringBuffer)_buf).delete(overlapStart, overlapEnd); + + super.adjustForDelete(start, length); + } } public int characterLength() 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 60e00f3256..f2d9a615f8 100644 --- a/src/scratchpad/src/org/apache/poi/hwpf/usermodel/Range.java +++ b/src/scratchpad/src/org/apache/poi/hwpf/usermodel/Range.java @@ -494,6 +494,7 @@ public class Range int numSections = _sections.size(); int numRuns = _characters.size(); int numParagraphs = _paragraphs.size(); + int numTextPieces = _text.size(); for (int x = _charStart; x < numRuns; x++) { @@ -512,6 +513,12 @@ public class Range SEPX sepx = (SEPX)_sections.get(x); sepx.adjustForDelete(_start, _end - _start); } + + for (int x = _textStart; x < numTextPieces; x++) + { + TextPiece piece = (TextPiece)_text.get(x); + piece.adjustForDelete(_start, _end - _start); + } } /** diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/AllFormulaEvalTests.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/AllFormulaEvalTests.java index 3260c371c6..5098c789a7 100755 --- a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/AllFormulaEvalTests.java +++ b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/AllFormulaEvalTests.java @@ -31,7 +31,9 @@ public class AllFormulaEvalTests { TestSuite result = new TestSuite("Tests for org.apache.poi.hssf.record.formula.eval"); result.addTestSuite(TestCircularReferences.class); result.addTestSuite(TestExternalFunction.class); + result.addTestSuite(TestFormulaBugs.class); result.addTestSuite(TestFormulasFromSpreadsheet.class); + result.addTestSuite(TestPercentEval.class); result.addTestSuite(TestUnaryPlusEval.class); return result; } diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/TestFormulaBugs.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/TestFormulaBugs.java new file mode 100755 index 0000000000..617f5d0d4e --- /dev/null +++ b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/TestFormulaBugs.java @@ -0,0 +1,217 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.hssf.record.formula.eval; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; + +import junit.framework.AssertionFailedError; +import junit.framework.TestCase; + +import org.apache.poi.hssf.usermodel.HSSFCell; +import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator; +import org.apache.poi.hssf.usermodel.HSSFRow; +import org.apache.poi.hssf.usermodel.HSSFSheet; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator.CellValue; + +/** + * Miscellaneous tests for bugzilla entries.

The test name contains the + * bugzilla bug id. + * + * + * @author Josh Micich + */ +public final class TestFormulaBugs extends TestCase { + + private static final String TEST_DATA_DIR_SYS_PROPERTY_NAME = "HSSF.testdata.path"; + + /** + * Opens a sample file from the standard HSSF test data directory + * + * @return an open InputStream for the specified sample file + */ + private static InputStream openSampleFileStream(String sampleFileName) { + // TODO - move this method somewhere common + String dataDirName = System + .getProperty(TEST_DATA_DIR_SYS_PROPERTY_NAME); + if (dataDirName == null) { + throw new RuntimeException("Must set system property '" + + TEST_DATA_DIR_SYS_PROPERTY_NAME + + "' before running tests"); + } + File dataDir = new File(dataDirName); + if (!dataDir.exists()) { + throw new RuntimeException("Data dir '" + dataDirName + + "' specified by system property '" + + TEST_DATA_DIR_SYS_PROPERTY_NAME + "' does not exist"); + } + File f = new File(dataDir, sampleFileName); + if (!f.exists()) { + throw new RuntimeException("Sample file '" + sampleFileName + + "' not found in data dir '" + dataDirName + "'"); + } + InputStream is; + try { + is = new FileInputStream(f); + } catch (FileNotFoundException e) { + throw new RuntimeException(e); + } + return is; + } + + /** + * Bug 27349 - VLOOKUP with reference to another sheet.

This test was + * added long after the relevant functionality was fixed. + */ + public void test27349() { + // 27349-vlookupAcrossSheets.xls is bugzilla/attachment.cgi?id=10622 + InputStream is = openSampleFileStream("27349-vlookupAcrossSheets.xls"); + HSSFWorkbook wb; + try { + // original bug may have thrown exception here, or output warning to + // stderr + wb = new HSSFWorkbook(is); + } catch (IOException e) { + throw new RuntimeException(e); + } + + HSSFSheet sheet = wb.getSheetAt(0); + HSSFRow row = sheet.getRow(1); + HSSFCell cell = row.getCell(0); + + // this definitely would have failed due to 27349 + assertEquals("VLOOKUP(1,'DATA TABLE'!$A$8:'DATA TABLE'!$B$10,2)", cell + .getCellFormula()); + + // We might as well evaluate the formula + HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(sheet, wb); + fe.setCurrentRow(row); + CellValue cv = fe.evaluate(cell); + + assertEquals(HSSFCell.CELL_TYPE_NUMERIC, cv.getCellType()); + assertEquals(3.0, cv.getNumberValue(), 0.0); + } + + /** + * Bug 27405 - isnumber() formula always evaluates to false in if statement

+ * + * seems to be a duplicate of 24925 + */ + public void test27405() { + + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sheet = wb.createSheet("input"); + // input row 0 + HSSFRow row = sheet.createRow((short) 0); + HSSFCell cell = row.createCell((short) 0); + cell = row.createCell((short) 1); + cell.setCellValue(1); // B1 + // input row 1 + row = sheet.createRow((short) 1); + cell = row.createCell((short) 1); + cell.setCellValue(999); // B2 + + int rno = 4; + row = sheet.createRow(rno); + cell = row.createCell((short) 1); // B5 + cell.setCellFormula("isnumber(b1)"); + cell = row.createCell((short) 3); // D5 + cell.setCellFormula("IF(ISNUMBER(b1),b1,b2)"); + + if (false) { // set true to check excel file manually + // bug report mentions 'Editing the formula in excel "fixes" the problem.' + try { + FileOutputStream fileOut = new FileOutputStream("27405output.xls"); + wb.write(fileOut); + fileOut.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + // use POI's evaluator as an extra sanity check + HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(sheet, wb); + fe.setCurrentRow(row); + CellValue cv; + cv = fe.evaluate(cell); + assertEquals(HSSFCell.CELL_TYPE_NUMERIC, cv.getCellType()); + assertEquals(1.0, cv.getNumberValue(), 0.0); + + cv = fe.evaluate(row.getCell(1)); + assertEquals(HSSFCell.CELL_TYPE_BOOLEAN, cv.getCellType()); + assertEquals(true, cv.getBooleanValue()); + } + + /** + * Bug 42448 - Can't parse SUMPRODUCT(A!C7:A!C67, B8:B68) / B69

+ */ + public void test42448() { + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sheet1 = wb.createSheet("Sheet1"); + + HSSFRow row = sheet1.createRow(0); + HSSFCell cell = row.createCell((short) 0); + + // it's important to create the referenced sheet first + HSSFSheet sheet2 = wb.createSheet("A"); // note name 'A' + // TODO - POI crashes if the formula is added before this sheet + // RuntimeException("Zero length string is an invalid sheet name") + // Excel doesn't crash but the formula doesn't work until it is + // re-entered + + String inputFormula = "SUMPRODUCT(A!C7:A!C67, B8:B68) / B69"; // as per bug report + try { + cell.setCellFormula(inputFormula); + } catch (StringIndexOutOfBoundsException e) { + throw new AssertionFailedError("Identified bug 42448"); + } + + assertEquals("SUMPRODUCT(A!C7:C67,B8:B68)/B69", cell.getCellFormula()); + + // might as well evaluate the sucker... + + addCell(sheet2, 5, 2, 3.0); // A!C6 + addCell(sheet2, 6, 2, 4.0); // A!C7 + addCell(sheet2, 66, 2, 5.0); // A!C67 + addCell(sheet2, 67, 2, 6.0); // A!C68 + + addCell(sheet1, 6, 1, 7.0); // B7 + addCell(sheet1, 7, 1, 8.0); // B8 + addCell(sheet1, 67, 1, 9.0); // B68 + addCell(sheet1, 68, 1, 10.0); // B69 + + double expectedResult = (4.0 * 8.0 + 5.0 * 9.0) / 10.0; + + HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(sheet1, wb); + fe.setCurrentRow(row); + CellValue cv = fe.evaluate(cell); + + assertEquals(HSSFCell.CELL_TYPE_NUMERIC, cv.getCellType()); + assertEquals(expectedResult, cv.getNumberValue(), 0.0); + } + + private static void addCell(HSSFSheet sheet, int rowIx, int colIx, + double value) { + sheet.createRow(rowIx).createCell((short) colIx).setCellValue(value); + } +} diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/TestFormulasFromSpreadsheet.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/TestFormulasFromSpreadsheet.java index f57221c9b0..2d5408c76a 100644 --- a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/TestFormulasFromSpreadsheet.java +++ b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/TestFormulasFromSpreadsheet.java @@ -15,7 +15,6 @@ * limitations under the License. */ - package org.apache.poi.hssf.record.formula.eval; import java.io.FileInputStream; @@ -59,36 +58,36 @@ public final class TestFormulasFromSpreadsheet extends TestCase { * Name of the test spreadsheet (found in the standard test data folder) */ public final static String FILENAME = "FormulaEvalTestData.xls"; - /** - * Row (zero-based) in the test spreadsheet where the operator examples start. - */ + /** + * Row (zero-based) in the test spreadsheet where the operator examples start. + */ public static final int START_OPERATORS_ROW_INDEX = 22; // Row '23' - /** - * Row (zero-based) in the test spreadsheet where the function examples start. - */ - public static final int START_FUNCTIONS_ROW_INDEX = 83; // Row '84' + /** + * Row (zero-based) in the test spreadsheet where the function examples start. + */ + public static final int START_FUNCTIONS_ROW_INDEX = 87; // Row '88' /** * Index of the column that contains the function names */ - public static final short COLUMN_INDEX_FUNCTION_NAME = 1; // Column 'B' + public static final short COLUMN_INDEX_FUNCTION_NAME = 1; // Column 'B' - /** - * Used to indicate when there are no more functions left - */ + /** + * Used to indicate when there are no more functions left + */ public static final String FUNCTION_NAMES_END_SENTINEL = ""; /** * Index of the column where the test values start (for each function) */ - public static final short COLUMN_INDEX_FIRST_TEST_VALUE = 3; // Column 'D' - - /** - * Each function takes 4 rows in the test spreadsheet - */ + public static final short COLUMN_INDEX_FIRST_TEST_VALUE = 3; // Column 'D' + + /** + * Each function takes 4 rows in the test spreadsheet + */ public static final int NUMBER_OF_ROWS_PER_FUNCTION = 4; } - private HSSFWorkbook workbook; + private HSSFWorkbook workbook; private HSSFSheet sheet; // Note - multiple failures are aggregated before ending. // If one or more functions fail, a single AssertionFailedError is thrown at the end @@ -97,138 +96,138 @@ public final class TestFormulasFromSpreadsheet extends TestCase { private int _evaluationFailureCount; private int _evaluationSuccessCount; - private static final HSSFCell getExpectedValueCell(HSSFRow row, short columnIndex) { - if (row == null) { - return null; - } - return row.getCell(columnIndex); - } + private static final HSSFCell getExpectedValueCell(HSSFRow row, short columnIndex) { + if (row == null) { + return null; + } + return row.getCell(columnIndex); + } - private static void confirmExpectedResult(String msg, HSSFCell expected, HSSFFormulaEvaluator.CellValue actual) { - if (expected == null) { + private static void confirmExpectedResult(String msg, HSSFCell expected, HSSFFormulaEvaluator.CellValue actual) { + if (expected == null) { throw new AssertionFailedError(msg + " - Bad setup data expected value is null"); } if(actual == null) { throw new AssertionFailedError(msg + " - actual value was null"); } - + if (expected.getCellType() == HSSFCell.CELL_TYPE_STRING) { - String value = expected.getRichStringCellValue().getString(); - if (value.startsWith("#")) { - // TODO - this code never called - expected.setCellType(HSSFCell.CELL_TYPE_ERROR); - // expected.setCellErrorValue(...?); - } + String value = expected.getRichStringCellValue().getString(); + if (value.startsWith("#")) { + // TODO - this code never called + expected.setCellType(HSSFCell.CELL_TYPE_ERROR); + // expected.setCellErrorValue(...?); + } } switch (expected.getCellType()) { case HSSFCell.CELL_TYPE_BLANK: - assertEquals(msg, HSSFCell.CELL_TYPE_BLANK, actual.getCellType()); - break; + assertEquals(msg, HSSFCell.CELL_TYPE_BLANK, actual.getCellType()); + break; case HSSFCell.CELL_TYPE_BOOLEAN: - assertEquals(msg, HSSFCell.CELL_TYPE_BOOLEAN, actual.getCellType()); - assertEquals(msg, expected.getBooleanCellValue(), actual.getBooleanValue()); - break; + assertEquals(msg, HSSFCell.CELL_TYPE_BOOLEAN, actual.getCellType()); + assertEquals(msg, expected.getBooleanCellValue(), actual.getBooleanValue()); + break; case HSSFCell.CELL_TYPE_ERROR: - assertEquals(msg, HSSFCell.CELL_TYPE_ERROR, actual.getCellType()); - if(false) { // TODO: fix ~45 functions which are currently returning incorrect error values - assertEquals(msg, expected.getErrorCellValue(), actual.getErrorValue()); - } - break; + assertEquals(msg, HSSFCell.CELL_TYPE_ERROR, actual.getCellType()); + if(false) { // TODO: fix ~45 functions which are currently returning incorrect error values + assertEquals(msg, expected.getErrorCellValue(), actual.getErrorValue()); + } + break; case HSSFCell.CELL_TYPE_FORMULA: // will never be used, since we will call method after formula evaluation - throw new AssertionFailedError("Cannot expect formula as result of formula evaluation: " + msg); + throw new AssertionFailedError("Cannot expect formula as result of formula evaluation: " + msg); case HSSFCell.CELL_TYPE_NUMERIC: - assertEquals(msg, HSSFCell.CELL_TYPE_NUMERIC, actual.getCellType()); - TestMathX.assertEquals(msg, expected.getNumericCellValue(), actual.getNumberValue(), TestMathX.POS_ZERO, TestMathX.DIFF_TOLERANCE_FACTOR); -// double delta = Math.abs(expected.getNumericCellValue()-actual.getNumberValue()); -// double pctExpected = Math.abs(0.00001*expected.getNumericCellValue()); -// assertTrue(msg, delta <= pctExpected); - break; + assertEquals(msg, HSSFCell.CELL_TYPE_NUMERIC, actual.getCellType()); + TestMathX.assertEquals(msg, expected.getNumericCellValue(), actual.getNumberValue(), TestMathX.POS_ZERO, TestMathX.DIFF_TOLERANCE_FACTOR); +// double delta = Math.abs(expected.getNumericCellValue()-actual.getNumberValue()); +// double pctExpected = Math.abs(0.00001*expected.getNumericCellValue()); +// assertTrue(msg, delta <= pctExpected); + break; case HSSFCell.CELL_TYPE_STRING: - assertEquals(msg, HSSFCell.CELL_TYPE_STRING, actual.getCellType()); - assertEquals(msg, expected.getRichStringCellValue().getString(), actual.getRichTextStringValue().getString()); - break; + assertEquals(msg, HSSFCell.CELL_TYPE_STRING, actual.getCellType()); + assertEquals(msg, expected.getRichStringCellValue().getString(), actual.getRichTextStringValue().getString()); + break; } - } + } protected void setUp() throws Exception { - if (workbook == null) { - String filePath = System.getProperty("HSSF.testdata.path")+ "/" + SS.FILENAME; - FileInputStream fin = new FileInputStream( filePath ); - workbook = new HSSFWorkbook( fin ); - sheet = workbook.getSheetAt( 0 ); - } - _functionFailureCount = 0; - _functionSuccessCount = 0; - _evaluationFailureCount = 0; - _evaluationSuccessCount = 0; - } - - public void testFunctionsFromTestSpreadsheet() { - - processFunctionGroup(SS.START_OPERATORS_ROW_INDEX, null); - processFunctionGroup(SS.START_FUNCTIONS_ROW_INDEX, null); - // example for debugging individual functions/operators: -// processFunctionGroup(SS.START_OPERATORS_ROW_INDEX, "ConcatEval"); -// processFunctionGroup(SS.START_FUNCTIONS_ROW_INDEX, "AVERAGE"); - - // confirm results - String successMsg = "There were " - + _evaluationSuccessCount + " successful evaluation(s) and " + if (workbook == null) { + String filePath = System.getProperty("HSSF.testdata.path")+ "/" + SS.FILENAME; + FileInputStream fin = new FileInputStream( filePath ); + workbook = new HSSFWorkbook( fin ); + sheet = workbook.getSheetAt( 0 ); + } + _functionFailureCount = 0; + _functionSuccessCount = 0; + _evaluationFailureCount = 0; + _evaluationSuccessCount = 0; + } + + public void testFunctionsFromTestSpreadsheet() { + + processFunctionGroup(SS.START_OPERATORS_ROW_INDEX, null); + processFunctionGroup(SS.START_FUNCTIONS_ROW_INDEX, null); + // example for debugging individual functions/operators: +// processFunctionGroup(SS.START_OPERATORS_ROW_INDEX, "ConcatEval"); +// processFunctionGroup(SS.START_FUNCTIONS_ROW_INDEX, "AVERAGE"); + + // confirm results + String successMsg = "There were " + + _evaluationSuccessCount + " successful evaluation(s) and " + _functionSuccessCount + " function(s) without error"; if(_functionFailureCount > 0) { String msg = _functionFailureCount + " function(s) failed in " + _evaluationFailureCount + " evaluation(s). " + successMsg; - throw new AssertionFailedError(msg); - } + throw new AssertionFailedError(msg); + } if(false) { // normally no output for successful tests System.out.println(getClass().getName() + ": " + successMsg); } } - /** - * @param startRowIndex row index in the spreadsheet where the first function/operator is found - * @param testFocusFunctionName name of a single function/operator to test alone. - * Typically pass null to test all functions - */ + /** + * @param startRowIndex row index in the spreadsheet where the first function/operator is found + * @param testFocusFunctionName name of a single function/operator to test alone. + * Typically pass null to test all functions + */ private void processFunctionGroup(int startRowIndex, String testFocusFunctionName) { HSSFFormulaEvaluator evaluator = new HSSFFormulaEvaluator(sheet, workbook); - int rowIndex = startRowIndex; - while (true) { - HSSFRow r = sheet.getRow(rowIndex); - String targetFunctionName = getTargetFunctionName(r); - if(targetFunctionName == null) { - throw new AssertionFailedError("Test spreadsheet cell empty on row (" - + (rowIndex+1) + "). Expected function name or '" - + SS.FUNCTION_NAMES_END_SENTINEL + "'"); - } - if(targetFunctionName.equals(SS.FUNCTION_NAMES_END_SENTINEL)) { - // found end of functions list - break; - } - if(testFocusFunctionName == null || targetFunctionName.equalsIgnoreCase(testFocusFunctionName)) { - - // expected results are on the row below - HSSFRow expectedValuesRow = sheet.getRow(rowIndex + 1); - if(expectedValuesRow == null) { - int missingRowNum = rowIndex + 2; //+1 for 1-based, +1 for next row - throw new AssertionFailedError("Missing expected values row for function '" - + targetFunctionName + " (row " + missingRowNum + ")"); - } - switch(processFunctionRow(evaluator, targetFunctionName, r, expectedValuesRow)) { - case Result.ALL_EVALUATIONS_SUCCEEDED: _functionSuccessCount++; break; - case Result.SOME_EVALUATIONS_FAILED: _functionFailureCount++; break; - default: - throw new RuntimeException("unexpected result"); - case Result.NO_EVALUATIONS_FOUND: // do nothing - } - } - rowIndex += SS.NUMBER_OF_ROWS_PER_FUNCTION; - } + int rowIndex = startRowIndex; + while (true) { + HSSFRow r = sheet.getRow(rowIndex); + String targetFunctionName = getTargetFunctionName(r); + if(targetFunctionName == null) { + throw new AssertionFailedError("Test spreadsheet cell empty on row (" + + (rowIndex+1) + "). Expected function name or '" + + SS.FUNCTION_NAMES_END_SENTINEL + "'"); + } + if(targetFunctionName.equals(SS.FUNCTION_NAMES_END_SENTINEL)) { + // found end of functions list + break; + } + if(testFocusFunctionName == null || targetFunctionName.equalsIgnoreCase(testFocusFunctionName)) { + + // expected results are on the row below + HSSFRow expectedValuesRow = sheet.getRow(rowIndex + 1); + if(expectedValuesRow == null) { + int missingRowNum = rowIndex + 2; //+1 for 1-based, +1 for next row + throw new AssertionFailedError("Missing expected values row for function '" + + targetFunctionName + " (row " + missingRowNum + ")"); + } + switch(processFunctionRow(evaluator, targetFunctionName, r, expectedValuesRow)) { + case Result.ALL_EVALUATIONS_SUCCEEDED: _functionSuccessCount++; break; + case Result.SOME_EVALUATIONS_FAILED: _functionFailureCount++; break; + default: + throw new RuntimeException("unexpected result"); + case Result.NO_EVALUATIONS_FOUND: // do nothing + } + } + rowIndex += SS.NUMBER_OF_ROWS_PER_FUNCTION; + } } /** @@ -236,16 +235,16 @@ public final class TestFormulasFromSpreadsheet extends TestCase { * @return a constant from the local Result class denoting whether there were any evaluation * cases, and whether they all succeeded. */ - private int processFunctionRow(HSSFFormulaEvaluator evaluator, String targetFunctionName, - HSSFRow formulasRow, HSSFRow expectedValuesRow) { - - int result = Result.NO_EVALUATIONS_FOUND; // so far - short endcolnum = formulasRow.getLastCellNum(); - evaluator.setCurrentRow(formulasRow); + private int processFunctionRow(HSSFFormulaEvaluator evaluator, String targetFunctionName, + HSSFRow formulasRow, HSSFRow expectedValuesRow) { + + int result = Result.NO_EVALUATIONS_FOUND; // so far + short endcolnum = formulasRow.getLastCellNum(); + evaluator.setCurrentRow(formulasRow); - // iterate across the row for all the evaluation cases - for (short colnum=SS.COLUMN_INDEX_FIRST_TEST_VALUE; colnum < endcolnum; colnum++) { - HSSFCell c = formulasRow.getCell(colnum); + // iterate across the row for all the evaluation cases + for (short colnum=SS.COLUMN_INDEX_FIRST_TEST_VALUE; colnum < endcolnum; colnum++) { + HSSFCell c = formulasRow.getCell(colnum); if (c == null || c.getCellType() != HSSFCell.CELL_TYPE_FORMULA) { continue; } @@ -265,13 +264,13 @@ public final class TestFormulasFromSpreadsheet extends TestCase { printShortStackTrace(System.err, e); result = Result.SOME_EVALUATIONS_FAILED; } - } + } return result; } - /** - * Useful to keep output concise when expecting many failures to be reported by this test case - */ + /** + * Useful to keep output concise when expecting many failures to be reported by this test case + */ private static void printShortStackTrace(PrintStream ps, AssertionFailedError e) { StackTraceElement[] stes = e.getStackTrace(); @@ -304,8 +303,8 @@ public final class TestFormulasFromSpreadsheet extends TestCase { } /** - * @return null if cell is missing, empty or blank - */ + * @return null if cell is missing, empty or blank + */ private static String getTargetFunctionName(HSSFRow r) { if(r == null) { System.err.println("Warning - given null row, can't figure out function name"); diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/TestPercentEval.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/TestPercentEval.java new file mode 100755 index 0000000000..be8cef13fa --- /dev/null +++ b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/TestPercentEval.java @@ -0,0 +1,82 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.hssf.record.formula.eval; + +import junit.framework.AssertionFailedError; +import junit.framework.TestCase; + +import org.apache.poi.hssf.record.formula.PercentPtg; +import org.apache.poi.hssf.record.formula.functions.NumericFunctionInvoker; +import org.apache.poi.hssf.usermodel.HSSFCell; +import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator; +import org.apache.poi.hssf.usermodel.HSSFRow; +import org.apache.poi.hssf.usermodel.HSSFSheet; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator.CellValue; + +/** + * Test for percent operator evaluator. + * + * @author Josh Micich + */ +public final class TestPercentEval extends TestCase { + + private static void confirm(ValueEval arg, double expectedResult) { + Eval[] args = { + arg, + }; + + PercentEval opEval = new PercentEval(new PercentPtg()); + double result = NumericFunctionInvoker.invoke(opEval, args, -1, (short)-1); + + assertEquals(expectedResult, result, 0); + } + + public void testBasic() { + confirm(new NumberEval(5), 0.05); + confirm(new NumberEval(3000), 30.0); + confirm(new NumberEval(-150), -1.5); + confirm(new StringEval("0.2"), 0.002); + confirm(BoolEval.TRUE, 0.01); + } + + public void testInSpreadSheet() { + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sheet = wb.createSheet("Sheet1"); + HSSFRow row = sheet.createRow(0); + HSSFCell cell = row.createCell((short)0); + cell.setCellFormula("B1%"); + row.createCell((short)1).setCellValue(50.0); + + HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(sheet, wb); + fe.setCurrentRow(row); + CellValue cv; + try { + cv = fe.evaluate(cell); + } catch (RuntimeException e) { + if(e.getCause() instanceof NullPointerException) { + throw new AssertionFailedError("Identified bug 44608"); + } + // else some other unexpected error + throw e; + } + assertEquals(HSSFCell.CELL_TYPE_NUMERIC, cv.getCellType()); + assertEquals(0.5, cv.getNumberValue(), 0.0); + } + +} diff --git a/src/scratchpad/testcases/org/apache/poi/hwpf/data/Bug28627.doc b/src/scratchpad/testcases/org/apache/poi/hwpf/data/Bug28627.doc new file mode 100644 index 0000000000..91b031d1d7 Binary files /dev/null and b/src/scratchpad/testcases/org/apache/poi/hwpf/data/Bug28627.doc differ diff --git a/src/scratchpad/testcases/org/apache/poi/hwpf/usermodel/TestProblems.java b/src/scratchpad/testcases/org/apache/poi/hwpf/usermodel/TestProblems.java index e82c4d1304..23681486f3 100644 --- a/src/scratchpad/testcases/org/apache/poi/hwpf/usermodel/TestProblems.java +++ b/src/scratchpad/testcases/org/apache/poi/hwpf/usermodel/TestProblems.java @@ -16,19 +16,14 @@ */ package org.apache.poi.hwpf.usermodel; -import java.io.ByteArrayOutputStream; +import java.io.File; import java.io.FileInputStream; -import java.util.Iterator; -import java.util.List; +import java.io.FileOutputStream; + +import junit.framework.TestCase; import org.apache.poi.hwpf.HWPFDocument; import org.apache.poi.hwpf.model.StyleSheet; -import org.apache.poi.hwpf.model.TextPiece; -import org.apache.poi.hwpf.usermodel.Paragraph; -import org.apache.poi.hwpf.usermodel.Range; -import org.apache.poi.util.LittleEndian; - -import junit.framework.TestCase; /** * Test various problem documents @@ -36,16 +31,18 @@ import junit.framework.TestCase; * @author Nick Burch (nick at torchbox dot com) */ public class TestProblems extends TestCase { + private String dirname = System.getProperty("HWPF.testdata.path"); protected void setUp() throws Exception { - } - + } + /** * ListEntry passed no ListTable */ public void testListEntryNoListTable() throws Exception { - HWPFDocument doc = new HWPFDocument(new FileInputStream(dirname + "/ListEntryNoListTable.doc")); + HWPFDocument doc = new HWPFDocument(new FileInputStream( + new File(dirname, "ListEntryNoListTable.doc"))); Range r = doc.getRange(); StyleSheet styleSheet = doc.getStyleSheet(); @@ -62,7 +59,8 @@ public class TestProblems extends TestCase { * AIOOB for TableSprmUncompressor.unCompressTAPOperation */ public void testSprmAIOOB() throws Exception { - HWPFDocument doc = new HWPFDocument(new FileInputStream(dirname + "/AIOOB-Tap.doc")); + HWPFDocument doc = new HWPFDocument(new FileInputStream( + new File(dirname, "AIOOB-Tap.doc"))); Range r = doc.getRange(); StyleSheet styleSheet = doc.getStyleSheet(); @@ -79,7 +77,8 @@ public class TestProblems extends TestCase { * Test for TableCell not skipping the last paragraph */ public void testTableCellLastParagraph() throws Exception { - HWPFDocument doc = new HWPFDocument(new FileInputStream(dirname + "/Bug44292.doc")); + HWPFDocument doc = new HWPFDocument(new FileInputStream( + new File(dirname, "Bug44292.doc"))); Range r = doc.getRange(); //get the table @@ -104,4 +103,39 @@ public class TestProblems extends TestCase { // Last cell should have one paragraph assertEquals(1, cell.numParagraphs()); } + + public void testRangeDelete() throws Exception { + HWPFDocument doc = new HWPFDocument(new FileInputStream( + new File(dirname, "Bug28627.doc"))); + + Range range = doc.getRange(); + int numParagraphs = range.numParagraphs(); + + int totalLength = 0, deletedLength = 0; + + for (int i = 0; i < numParagraphs; i++) { + Paragraph para = range.getParagraph(i); + String text = para.text(); + + totalLength += text.length(); + if (text.indexOf("{delete me}") > -1) { + para.delete(); + deletedLength = text.length(); + } + } + + // check the text length after deletion + int newLength = 0; + range = doc.getRange(); + numParagraphs = range.numParagraphs(); + + for (int i = 0; i < numParagraphs; i++) { + Paragraph para = range.getParagraph(i); + String text = para.text(); + + newLength += text.length(); + } + + assertEquals(newLength, totalLength - deletedLength); + } } diff --git a/src/testcases/org/apache/poi/hssf/data/27349-vlookupAcrossSheets.xls b/src/testcases/org/apache/poi/hssf/data/27349-vlookupAcrossSheets.xls new file mode 100755 index 0000000000..c21d434a70 Binary files /dev/null and b/src/testcases/org/apache/poi/hssf/data/27349-vlookupAcrossSheets.xls differ diff --git a/src/testcases/org/apache/poi/hssf/data/Bug44593.xls b/src/testcases/org/apache/poi/hssf/data/Bug44593.xls new file mode 100644 index 0000000000..84d1311441 Binary files /dev/null and b/src/testcases/org/apache/poi/hssf/data/Bug44593.xls differ diff --git a/src/testcases/org/apache/poi/hssf/data/FormulaEvalTestData.xls b/src/testcases/org/apache/poi/hssf/data/FormulaEvalTestData.xls index 6260d878bc..aaaf958a9d 100644 Binary files a/src/testcases/org/apache/poi/hssf/data/FormulaEvalTestData.xls and b/src/testcases/org/apache/poi/hssf/data/FormulaEvalTestData.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 f922e75d82..b79236ff08 100644 --- a/src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java +++ b/src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java @@ -844,4 +844,48 @@ public final class TestFormulaParser extends TestCase { } 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")); + } + } } diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java b/src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java index f9bb362c7b..f06f591c42 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java @@ -508,6 +508,30 @@ extends TestCase { } assertTrue("No Exceptions till here!", true); } + + public void test28031() { + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sheet = wb.createSheet(); + wb.setSheetName(0, "Sheet1"); + + HSSFRow row = sheet.createRow(0); + HSSFCell cell = row.createCell((short)0); + String formulaText = + "IF(ROUND(A2*B2*C2,2)>ROUND(B2*D2,2),ROUND(A2*B2*C2,2),ROUND(B2*D2,2))"; + cell.setCellFormula(formulaText); + + assertEquals(formulaText, cell.getCellFormula()); + if(false) { + // this file can be inspected manually + try { + OutputStream os = new FileOutputStream("/tmp/output28031.xls"); + wb.write(os); + os.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } public void test33082() throws java.io.IOException { String filename = System.getProperty("HSSF.testdata.path"); @@ -1127,6 +1151,23 @@ extends TestCase { in.close(); assertFalse(wb.isWriteProtected()); } + + /** + * Some files were having problems with the DVRecord, + * probably due to dropdowns + */ + public void test44593() throws Exception { + FileInputStream in = new FileInputStream(new File(cwd, "Bug44593.xls")); + + // Used to blow up with an IllegalArgumentException + // when creating a DVRecord + // Now won't, but no idea if this means we have + // rubbish in the DVRecord or not... + HSSFWorkbook wb = new HSSFWorkbook(in); + in.close(); + + assertEquals(2, wb.getNumberOfSheets()); + } } diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFCell.java b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFCell.java index 6c604d1b4d..38d3b89295 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFCell.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFCell.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.usermodel; @@ -24,12 +22,11 @@ import java.io.FileInputStream; import java.io.FileOutputStream; import java.util.Date; import java.util.GregorianCalendar; -import java.util.List; +import junit.framework.AssertionFailedError; import junit.framework.TestCase; import org.apache.poi.hssf.model.Sheet; -import org.apache.poi.hssf.record.HyperlinkRecord; import org.apache.poi.hssf.util.HSSFColor; import org.apache.poi.poifs.filesystem.POIFSFileSystem; import org.apache.poi.util.TempFile; @@ -41,12 +38,7 @@ import org.apache.poi.util.TempFile; * @author Dan Sherman (dsherman at isisph.com) * @author Alex Jacoby (ajacoby at gmail.com) */ - -public class TestHSSFCell -extends TestCase { - public TestHSSFCell(String s) { - super(s); - } +public final class TestHSSFCell extends TestCase { /** * test that Boolean and Error types (BoolErrRecord) are supported properly. @@ -388,6 +380,17 @@ extends TestCase { assertEquals("Formula", "A1+B1", c.toString()); } + public void testSetStringInFormulaCell_bug44606() { + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFCell cell = wb.createSheet("Sheet1").createRow(0).createCell((short)0); + cell.setCellFormula("B1&C1"); + try { + cell.setCellValue(new HSSFRichTextString("hello")); + } catch (ClassCastException e) { + throw new AssertionFailedError("Identified bug 44606"); + } + } + public static void main(String [] args) { System.out .println("Testing org.apache.poi.hssf.usermodel.TestHSSFCell"); diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFTextbox.java b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFTextbox.java new file mode 100755 index 0000000000..f7ce61faf2 --- /dev/null +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFTextbox.java @@ -0,0 +1,52 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one or more +* contributor license agreements. See the NOTICE file distributed with +* this work for additional information regarding copyright ownership. +* The ASF licenses this file to You under the Apache License, Version 2.0 +* (the "License"); you may not use this file except in compliance with +* the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +package org.apache.poi.hssf.usermodel; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; + +import junit.framework.TestCase; + +/** + * Test HSSFTextbox. + * + * @author Yegor Kozlov (yegor at apache.org) + */ +public final class TestHSSFTextbox extends TestCase{ + + /** + * Test that accessors to horizontal and vertical alignment work properly + */ + public void testAlignment() { + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sh1 = wb.createSheet(); + HSSFPatriarch patriarch = sh1.createDrawingPatriarch(); + + HSSFTextbox textbox = patriarch.createTextbox(new HSSFClientAnchor(0, 0, 0, 0, (short) 1, 1, (short) 6, 4)); + HSSFRichTextString str = new HSSFRichTextString("Hello, World"); + textbox.setString(str); + textbox.setHorizontalAlignment(HSSFTextbox.HORIZONTAL_ALIGNMENT_CENTERED); + textbox.setVerticalAlignment(HSSFTextbox.VERTICAL_ALIGNMENT_CENTER); + + assertEquals(HSSFTextbox.HORIZONTAL_ALIGNMENT_CENTERED, textbox.getHorizontalAlignment()); + assertEquals(HSSFTextbox.VERTICAL_ALIGNMENT_CENTER, textbox.getVerticalAlignment()); + } + + } diff --git a/src/testcases/org/apache/poi/poifs/property/TestPropertyTable.java b/src/testcases/org/apache/poi/poifs/property/TestPropertyTable.java index 895c40f704..008504fb00 100644 --- a/src/testcases/org/apache/poi/poifs/property/TestPropertyTable.java +++ b/src/testcases/org/apache/poi/poifs/property/TestPropertyTable.java @@ -25,6 +25,7 @@ import java.util.*; import junit.framework.*; +import org.apache.poi.poifs.common.POIFSConstants; import org.apache.poi.poifs.storage.BlockAllocationTableReader; import org.apache.poi.poifs.storage.RawDataBlockList; @@ -2598,7 +2599,7 @@ public class TestPropertyTable ( byte ) 0xFF, ( byte ) 0xFF, ( byte ) 0xFF, ( byte ) 0xFF }; RawDataBlockList data_blocks = - new RawDataBlockList(new ByteArrayInputStream(raw_data_array)); + new RawDataBlockList(new ByteArrayInputStream(raw_data_array), POIFSConstants.BIG_BLOCK_SIZE); int[] bat_array = { 15 diff --git a/src/testcases/org/apache/poi/poifs/storage/LocalRawDataBlockList.java b/src/testcases/org/apache/poi/poifs/storage/LocalRawDataBlockList.java index ed3e8d9259..21049ebf18 100644 --- a/src/testcases/org/apache/poi/poifs/storage/LocalRawDataBlockList.java +++ b/src/testcases/org/apache/poi/poifs/storage/LocalRawDataBlockList.java @@ -19,6 +19,8 @@ package org.apache.poi.poifs.storage; +import org.apache.poi.poifs.common.POIFSConstants; +import org.apache.poi.poifs.filesystem.POIFSFileSystem; import org.apache.poi.util.LittleEndian; import org.apache.poi.util.LittleEndianConsts; @@ -47,7 +49,7 @@ public class LocalRawDataBlockList public LocalRawDataBlockList() throws IOException { - super(new ByteArrayInputStream(new byte[ 0 ])); + super(new ByteArrayInputStream(new byte[ 0 ]), POIFSConstants.BIG_BLOCK_SIZE); _list = new ArrayList(); _array = null; } diff --git a/src/testcases/org/apache/poi/poifs/storage/TestRawDataBlockList.java b/src/testcases/org/apache/poi/poifs/storage/TestRawDataBlockList.java index ac6fc08c05..d151029762 100644 --- a/src/testcases/org/apache/poi/poifs/storage/TestRawDataBlockList.java +++ b/src/testcases/org/apache/poi/poifs/storage/TestRawDataBlockList.java @@ -21,6 +21,7 @@ package org.apache.poi.poifs.storage; import java.io.*; +import org.apache.poi.poifs.common.POIFSConstants; import org.apache.poi.util.DummyPOILogger; import org.apache.poi.util.POILogFactory; @@ -69,7 +70,7 @@ public class TestRawDataBlockList { data[ j ] = ( byte ) j; } - new RawDataBlockList(new ByteArrayInputStream(data)); + new RawDataBlockList(new ByteArrayInputStream(data), POIFSConstants.BIG_BLOCK_SIZE); } /** @@ -81,7 +82,7 @@ public class TestRawDataBlockList public void testEmptyConstructor() throws IOException { - new RawDataBlockList(new ByteArrayInputStream(new byte[ 0 ])); + new RawDataBlockList(new ByteArrayInputStream(new byte[ 0 ]), POIFSConstants.BIG_BLOCK_SIZE); } /** @@ -108,7 +109,7 @@ public class TestRawDataBlockList // Check we logged the error logger.reset(); - new RawDataBlockList(new ByteArrayInputStream(data)); + new RawDataBlockList(new ByteArrayInputStream(data), POIFSConstants.BIG_BLOCK_SIZE); assertEquals(1, logger.logged.size()); } } diff --git a/src/testcases/org/apache/poi/poifs/storage/TestSmallBlockTableReader.java b/src/testcases/org/apache/poi/poifs/storage/TestSmallBlockTableReader.java index bb2e3c4c0e..4d4254a91e 100644 --- a/src/testcases/org/apache/poi/poifs/storage/TestSmallBlockTableReader.java +++ b/src/testcases/org/apache/poi/poifs/storage/TestSmallBlockTableReader.java @@ -25,6 +25,7 @@ import java.util.*; import junit.framework.*; +import org.apache.poi.poifs.common.POIFSConstants; import org.apache.poi.poifs.property.PropertyTable; import org.apache.poi.poifs.property.RootProperty; @@ -2112,7 +2113,7 @@ public class TestSmallBlockTableReader ( byte ) 0xFF, ( byte ) 0xFF, ( byte ) 0xFF, ( byte ) 0xFF }; RawDataBlockList data_blocks = - new RawDataBlockList(new ByteArrayInputStream(raw_data_array)); + new RawDataBlockList(new ByteArrayInputStream(raw_data_array), POIFSConstants.BIG_BLOCK_SIZE); int[] bat_array = { 15