638000 git-svn-id: https://svn.apache.org/repos/asf/poi/branches/ooxml@642554 13f79535-47bb-0310-9956-ffa450edef68tags/REL_3_5_BETA2
@@ -36,6 +36,12 @@ | |||
<!-- Don't forget to update status.xml too! --> | |||
<release version="3.1-beta1" date="2008-??-??"> | |||
<action dev="POI-DEVELOPERS" type="fix">44609 - Handle leading spaces in formulas, such as '= 4'</action> | |||
<action dev="POI-DEVELOPERS" type="add">44608 - Support for PercentPtg in the formula evaluator</action> | |||
<action dev="POI-DEVELOPERS" type="fix">44606 - Support calculated string values for evaluated formulas</action> | |||
<action dev="POI-DEVELOPERS" type="add">Add accessors to horizontal and vertical alignment in HSSFTextbox</action> | |||
<action dev="POI-DEVELOPERS" type="add">44593 - Improved handling of short DVRecords</action> | |||
<action dev="POI-DEVELOPERS" type="add">28627 / 44580 - Fix Range.delete() in HWPF</action> | |||
<action dev="POI-DEVELOPERS" type="add">44539 - Support for area references in formulas of rows >= 32768</action> | |||
<action dev="POI-DEVELOPERS" type="add">44536 - Improved support for detecting read-only recommended files</action> | |||
<action dev="POI-DEVELOPERS" type="fix">43901 - Correctly update the internal last cell number when adding and removing cells (previously sometimes off-by-one)</action> |
@@ -33,6 +33,12 @@ | |||
<!-- Don't forget to update changes.xml too! --> | |||
<changes> | |||
<release version="3.1-beta1" date="2008-??-??"> | |||
<action dev="POI-DEVELOPERS" type="fix">44609 - Handle leading spaces in formulas, such as '= 4'</action> | |||
<action dev="POI-DEVELOPERS" type="add">44608 - Support for PercentPtg in the formula evaluator</action> | |||
<action dev="POI-DEVELOPERS" type="fix">44606 - Support calculated string values for evaluated formulas</action> | |||
<action dev="POI-DEVELOPERS" type="add">Add accessors to horizontal and vertical alignment in HSSFTextbox</action> | |||
<action dev="POI-DEVELOPERS" type="add">44593 - Improved handling of short DVRecords</action> | |||
<action dev="POI-DEVELOPERS" type="add">28627 / 44580 - Fix Range.delete() in HWPF</action> | |||
<action dev="POI-DEVELOPERS" type="add">44539 - Support for area references in formulas of rows >= 32768</action> | |||
<action dev="POI-DEVELOPERS" type="add">44536 - Improved support for detecting read-only recommended files</action> | |||
<action dev="POI-DEVELOPERS" type="fix">43901 - Correctly update the internal last cell number when adding and removing cells (previously sometimes off-by-one)</action> |
@@ -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(); | |||
} |
@@ -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; |
@@ -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 <tt>SpaceType</tt> | |||
* @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) |
@@ -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) { |
@@ -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; | |||
} | |||
} |
@@ -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); |
@@ -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; |
@@ -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 |
@@ -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 | |||
@@ -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 | |||
@@ -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()) | |||
{ |
@@ -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 '%'. <p/> | |||
* @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(); | |||
} | |||
} |
@@ -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, |
@@ -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 <tt>OperationEval</tt> instances to help evaluate <tt>OperationPtg</tt> | |||
* 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; | |||
} | |||
} |
@@ -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() |
@@ -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); | |||
} | |||
} | |||
/** |
@@ -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; | |||
} |
@@ -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.<p/> 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 <tt>InputStream</tt> 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.<p/> This test was | |||
* added <em>long</em> 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<p/> | |||
* | |||
* 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 <p/> | |||
*/ | |||
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); | |||
} | |||
} |
@@ -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 = "<END-OF-FUNCTIONS>"; | |||
/** | |||
* 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 <code>null</code> 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 <code>null</code> 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 <code>null</code> if cell is missing, empty or blank | |||
*/ | |||
* @return <code>null</code> 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"); |
@@ -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); | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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")); | |||
} | |||
} | |||
} |
@@ -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()); | |||
} | |||
} | |||
@@ -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"); |
@@ -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 <code>HSSFTextbox</code>. | |||
* | |||
* @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()); | |||
} | |||
} |
@@ -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 |
@@ -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; | |||
} |
@@ -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()); | |||
} | |||
} |
@@ -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 |