diff --git a/src/documentation/content/xdocs/changes.xml b/src/documentation/content/xdocs/changes.xml index 380ecb923..b84dec334 100644 --- a/src/documentation/content/xdocs/changes.xml +++ b/src/documentation/content/xdocs/changes.xml @@ -43,7 +43,14 @@ Created a common interface for handling PowerPoint files, irrespective of if they are .ppt or .pptx Created a common interface for handling Excel files, irrespective of if they are .xls or .xlsx - + + Support for specifying a policy to HSSF on missing / blank cells when fetching + 44937 - Partial support for extracting Escher images from HWPF files + 44824 - Avoid an infinite loop when reading some HWPF pictures + 44898 - Correctly handle short last blocks in POIFS + + + 44306 - fixed reading/writing of AttrPtg(type=choose) and method toFormulaString() for CHOOSE formulas 24207 - added HSSFName.isDeleted() to check if the name points to cell that no longer exists 40414 - fixed selected/active sheet after removing sheet from workbook 44523 - fixed workbook sheet selection and focus diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml index ebf869ce9..0b7f0140d 100644 --- a/src/documentation/content/xdocs/status.xml +++ b/src/documentation/content/xdocs/status.xml @@ -40,7 +40,14 @@ Created a common interface for handling PowerPoint files, irrespective of if they are .ppt or .pptx Created a common interface for handling Excel files, irrespective of if they are .xls or .xlsx - + + Support for specifying a policy to HSSF on missing / blank cells when fetching + 44937 - Partial support for extracting Escher images from HWPF files + 44824 - Avoid an infinite loop when reading some HWPF pictures + 44898 - Correctly handle short last blocks in POIFS + + + 44306 - fixed reading/writing of AttrPtg(type=choose) and method toFormulaString() for CHOOSE formulas 24207 - added HSSFName.isDeleted() to check if the name points to cell that no longer exists 40414 - fixed selected/active sheet after removing sheet from workbook 44523 - fixed workbook sheet selection and focus diff --git a/src/java/org/apache/poi/ddf/EscherClientAnchorRecord.java b/src/java/org/apache/poi/ddf/EscherClientAnchorRecord.java index f3d90c715..ec3354c59 100644 --- a/src/java/org/apache/poi/ddf/EscherClientAnchorRecord.java +++ b/src/java/org/apache/poi/ddf/EscherClientAnchorRecord.java @@ -65,20 +65,27 @@ public class EscherClientAnchorRecord int size = 0; // Always find 4 two byte entries. Sometimes find 9 - field_1_flag = LittleEndian.getShort( data, pos + size ); size += 2; - field_2_col1 = LittleEndian.getShort( data, pos + size ); size += 2; - field_3_dx1 = LittleEndian.getShort( data, pos + size ); size += 2; - field_4_row1 = LittleEndian.getShort( data, pos + size ); size += 2; - if(bytesRemaining >= 18) { - field_5_dy1 = LittleEndian.getShort( data, pos + size ); size += 2; - field_6_col2 = LittleEndian.getShort( data, pos + size ); size += 2; - field_7_dx2 = LittleEndian.getShort( data, pos + size ); size += 2; - field_8_row2 = LittleEndian.getShort( data, pos + size ); size += 2; - field_9_dy2 = LittleEndian.getShort( data, pos + size ); size += 2; - shortRecord = false; - } else { - shortRecord = true; - } + if (bytesRemaining == 4) // Word format only 4 bytes + { + // Not sure exactly what the format is quite yet, likely a reference to a PLC + } + else + { + field_1_flag = LittleEndian.getShort( data, pos + size ); size += 2; + field_2_col1 = LittleEndian.getShort( data, pos + size ); size += 2; + field_3_dx1 = LittleEndian.getShort( data, pos + size ); size += 2; + field_4_row1 = LittleEndian.getShort( data, pos + size ); size += 2; + if(bytesRemaining >= 18) { + field_5_dy1 = LittleEndian.getShort( data, pos + size ); size += 2; + field_6_col2 = LittleEndian.getShort( data, pos + size ); size += 2; + field_7_dx2 = LittleEndian.getShort( data, pos + size ); size += 2; + field_8_row2 = LittleEndian.getShort( data, pos + size ); size += 2; + field_9_dy2 = LittleEndian.getShort( data, pos + size ); size += 2; + shortRecord = false; + } else { + shortRecord = true; + } + } bytesRemaining -= size; remainingData = new byte[bytesRemaining]; System.arraycopy( data, pos + size, remainingData, 0, bytesRemaining ); diff --git a/src/java/org/apache/poi/hssf/model/FormulaParser.java b/src/java/org/apache/poi/hssf/model/FormulaParser.java index 88a39ffe6..39a79bb76 100644 --- a/src/java/org/apache/poi/hssf/model/FormulaParser.java +++ b/src/java/org/apache/poi/hssf/model/FormulaParser.java @@ -134,7 +134,10 @@ public final class FormulaParser { /** Report What Was Expected */ private RuntimeException expected(String s) { - return new FormulaParseException(s + " Expected"); + String msg = "Parse error near char " + (pointer-1) + "'" + look + "'" + + " in specified formula '" + formulaString + "'. Expected " + + s; + return new FormulaParseException(msg); } @@ -1000,7 +1003,7 @@ end; if (ptg instanceof AttrPtg) { AttrPtg attrPtg = ((AttrPtg) ptg); - if (attrPtg.isOptimizedIf()) { + if (attrPtg.isOptimizedIf() || attrPtg.isOptimizedChoose() || attrPtg.isGoto()) { continue; } if (attrPtg.isSpace()) { @@ -1014,6 +1017,9 @@ end; // similar to tAttrSpace - RPN is violated continue; } + if (!attrPtg.isSum()) { + throw new RuntimeException("Unexpected tAttr: " + attrPtg.toString()); + } } final OperationPtg o = (OperationPtg) ptg; 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 c2ff29116..263c1728b 100644 --- a/src/java/org/apache/poi/hssf/record/formula/AttrPtg.java +++ b/src/java/org/apache/poi/hssf/record/formula/AttrPtg.java @@ -39,6 +39,11 @@ public final class AttrPtg extends OperationPtg { private byte field_1_options; private short field_2_data; + /** only used for tAttrChoose: table of offsets to starts of args */ + private final int[] _jumpTable; + /** only used for tAttrChoose: offset to the tFuncVar for CHOOSE() */ + private final int _chooseFuncOffset; + // 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); @@ -63,7 +68,7 @@ public final class AttrPtg extends OperationPtg { /** 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; + public static final int SPACE_BEFORE_CLOSE_PAREN = 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) */ @@ -71,16 +76,33 @@ public final class AttrPtg extends OperationPtg { } public AttrPtg() { + _jumpTable = null; + _chooseFuncOffset = -1; } public AttrPtg(RecordInputStream in) { field_1_options = in.readByte(); field_2_data = in.readShort(); + if (isOptimizedChoose()) { + int nCases = field_2_data; + int[] jumpTable = new int[nCases]; + for (int i = 0; i < jumpTable.length; i++) { + jumpTable[i] = in.readUShort(); + } + _jumpTable = jumpTable; + _chooseFuncOffset = in.readUShort(); + } else { + _jumpTable = null; + _chooseFuncOffset = -1; + } + } - private AttrPtg(int options, int data) { + private AttrPtg(int options, int data, int[] jt, int chooseFuncOffset) { field_1_options = (byte) options; field_2_data = (short) data; + _jumpTable = jt; + _chooseFuncOffset = chooseFuncOffset; } /** @@ -89,7 +111,7 @@ public final class AttrPtg extends OperationPtg { */ public static AttrPtg createSpace(int type, int count) { int data = type & 0x00FF | (count << 8) & 0x00FFFF; - return new AttrPtg(space.set(0), data); + return new AttrPtg(space.set(0), data, null, -1); } public void setOptions(byte options) @@ -136,14 +158,14 @@ public final class AttrPtg extends OperationPtg { field_1_options=optiIf.setByteBoolean(field_1_options,bif); } - /** - * Flags this ptg as a goto/jump - * @param isGoto - */ - public void setGoto(boolean isGoto) { - field_1_options=optGoto.setByteBoolean(field_1_options, isGoto); - } - + /** + * Flags this ptg as a goto/jump + * @param isGoto + */ + public void setGoto(boolean isGoto) { + field_1_options=optGoto.setByteBoolean(field_1_options, isGoto); + } + // lets hope no one uses this anymore public boolean isBaxcel() { @@ -181,7 +203,7 @@ public final class AttrPtg extends OperationPtg { if(isOptimizedIf()) { sb.append("if dist=").append(getData()); } else if(isOptimizedChoose()) { - sb.append("choose dist=").append(getData()); + sb.append("choose nCases=").append(getData()); } else if(isGoto()) { sb.append("skip dist=").append(getData()); } else if(isSum()) { @@ -195,13 +217,28 @@ public final class AttrPtg extends OperationPtg { public void writeBytes(byte [] array, int offset) { - array[offset]=sid; - array[offset+1]=field_1_options; - LittleEndian.putShort(array,offset+2,field_2_data); + LittleEndian.putByte(array, offset+0, sid); + LittleEndian.putByte(array, offset+1, field_1_options); + LittleEndian.putShort(array,offset+2, field_2_data); + int[] jt = _jumpTable; + if (jt != null) { + int joff = offset+4; + LittleEndian.putUShort(array, joff, _chooseFuncOffset); + joff+=2; + for (int i = 0; i < jt.length; i++) { + LittleEndian.putUShort(array, joff, jt[i]); + joff+=2; + } + LittleEndian.putUShort(array, joff, _chooseFuncOffset); + } + } public int getSize() { + if (_jumpTable != null) { + return SIZE + (_jumpTable.length + 1) * LittleEndian.SHORT_SIZE; + } return SIZE; } @@ -255,12 +292,17 @@ public final class AttrPtg extends OperationPtg { - public byte getDefaultOperandClass() {return Ptg.CLASS_VALUE;} + public byte getDefaultOperandClass() { + return Ptg.CLASS_VALUE; + } public Object clone() { - AttrPtg ptg = new AttrPtg(); - ptg.field_1_options = field_1_options; - ptg.field_2_data = field_2_data; - return ptg; + int[] jt; + if (_jumpTable == null) { + jt = null; + } else { + jt = (int[]) _jumpTable.clone(); + } + return new AttrPtg(field_1_options, field_2_data, jt, _chooseFuncOffset); } } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFRow.java b/src/java/org/apache/poi/hssf/usermodel/HSSFRow.java index d62a7c23a..056c872e7 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFRow.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFRow.java @@ -319,6 +319,36 @@ public final class HSSFRow implements Comparable, Row { if(cellnum<0||cellnum>=cells.length) return null; return cells[cellnum]; } + + /** + * Get the hssfcell representing a given column (logical cell) + * 0-based. If you ask for a cell that is not defined, then + * your supplied policy says what to do + * + * @param cellnum 0 based column number + * @param policy Policy on blank / missing cells + * @return representing that column or null if undefined + policy allows. + */ + public HSSFCell getCell(int cellnum, MissingCellPolicy policy) { + HSSFCell cell = getCell(cellnum); + if(policy == RETURN_NULL_AND_BLANK) { + return cell; + } + if(policy == RETURN_BLANK_AS_NULL) { + if(cell == null) return cell; + if(cell.getCellType() == HSSFCell.CELL_TYPE_BLANK) { + return null; + } + return cell; + } + if(policy == CREATE_NULL_AS_BLANK) { + if(cell == null) { + return createCell((short)cellnum, HSSFCell.CELL_TYPE_BLANK); + } + return cell; + } + throw new IllegalArgumentException("Illegal policy " + policy + " (" + policy.id + ")"); + } /** * get the number of the first cell contained in this row. @@ -485,6 +515,7 @@ public final class HSSFRow implements Comparable, Row { return cellnum; } + /** * @return cell iterator of the physically defined cells. * Note that the 4th element might well not be cell 4, as the iterator @@ -503,6 +534,9 @@ public final class HSSFRow implements Comparable, Row { return cellIterator(); } + /** + * An iterator over the (physical) cells in the row. + */ private class CellIterator implements Iterator { int thisId=-1; diff --git a/src/java/org/apache/poi/poifs/storage/RawDataBlock.java b/src/java/org/apache/poi/poifs/storage/RawDataBlock.java index b4630a78b..5ca1781d0 100644 --- a/src/java/org/apache/poi/poifs/storage/RawDataBlock.java +++ b/src/java/org/apache/poi/poifs/storage/RawDataBlock.java @@ -37,6 +37,7 @@ public class RawDataBlock { private byte[] _data; private boolean _eof; + private boolean _hasData; private static POILogger log = POILogFactory.getLogger(RawDataBlock.class); /** @@ -66,6 +67,7 @@ public class RawDataBlock throws IOException { _data = new byte[ blockSize ]; int count = IOUtils.readFully(stream, _data); + _hasData = (count > 0); if (count == -1) { _eof = true; @@ -94,16 +96,21 @@ public class RawDataBlock /** * When we read the data, did we hit end of file? * - * @return true if no data was read because we were at the end of - * the file, else false - * - * @exception IOException + * @return true if the EoF was hit during this block, or + * false if not. If you have a dodgy short last block, then + * it's possible to both have data, and also hit EoF... */ - public boolean eof() - throws IOException - { + public boolean eof() { return _eof; } + /** + * Did we actually find any data to read? It's possible, + * in the event of a short last block, to both have hit + * the EoF, but also to have data + */ + public boolean hasData() { + return _hasData; + } /* ********** START implementation of ListManagedBlock ********** */ @@ -117,7 +124,7 @@ public class RawDataBlock public byte [] getData() throws IOException { - if (eof()) + if (! hasData()) { throw new IOException("Cannot return empty data"); } diff --git a/src/java/org/apache/poi/poifs/storage/RawDataBlockList.java b/src/java/org/apache/poi/poifs/storage/RawDataBlockList.java index 76ab21956..66eb237a8 100644 --- a/src/java/org/apache/poi/poifs/storage/RawDataBlockList.java +++ b/src/java/org/apache/poi/poifs/storage/RawDataBlockList.java @@ -51,12 +51,16 @@ public class RawDataBlockList while (true) { RawDataBlock block = new RawDataBlock(stream, bigBlockSize); + + // If there was data, add the block to the list + if(block.hasData()) { + blocks.add(block); + } - if (block.eof()) - { + // If the stream is now at the End Of File, we're done + if (block.eof()) { break; } - blocks.add(block); } setBlocks(( RawDataBlock [] ) blocks.toArray(new RawDataBlock[ 0 ])); } diff --git a/src/ooxml/interfaces-jdk14/org/apache/poi/ss/usermodel/Row.java b/src/ooxml/interfaces-jdk14/org/apache/poi/ss/usermodel/Row.java index 80a191d7c..8a0810dd3 100644 --- a/src/ooxml/interfaces-jdk14/org/apache/poi/ss/usermodel/Row.java +++ b/src/ooxml/interfaces-jdk14/org/apache/poi/ss/usermodel/Row.java @@ -33,4 +33,22 @@ public interface Row { HSSFCell getCell(int cellnum); Iterator cellIterator(); + + /** + * Used to specify the different possible policies + * if for the case of null and blank cells + */ + public static class MissingCellPolicy { + private static int NEXT_ID = 1; + public final int id; + private MissingCellPolicy() { + this.id = NEXT_ID++; + } + } + /** Missing cells are returned as null, Blank cells are returned as normal */ + public static final MissingCellPolicy RETURN_NULL_AND_BLANK = new MissingCellPolicy(); + /** Missing cells are returned as null, as are blank cells */ + public static final MissingCellPolicy RETURN_BLANK_AS_NULL = new MissingCellPolicy(); + /** A new, blank cell is created for missing cells. Blank cells are returned as normal */ + public static final MissingCellPolicy CREATE_NULL_AS_BLANK = new MissingCellPolicy(); } diff --git a/src/ooxml/interfaces-jdk15/org/apache/poi/ss/usermodel/Row.java b/src/ooxml/interfaces-jdk15/org/apache/poi/ss/usermodel/Row.java index 28257bae4..0c46c5863 100644 --- a/src/ooxml/interfaces-jdk15/org/apache/poi/ss/usermodel/Row.java +++ b/src/ooxml/interfaces-jdk15/org/apache/poi/ss/usermodel/Row.java @@ -88,9 +88,20 @@ public interface Row extends Iterable { * ask for a cell that is not defined....you get a null. * * @param cellnum 0 based column number - * @return HSSFCell representing that column or null if undefined. + * @return Cell representing that column or null if undefined. */ Cell getCell(int cellnum); + + /** + * Get the hssfcell representing a given column (logical cell) + * 0-based. If you ask for a cell that is not defined, then + * your supplied policy says what to do + * + * @param cellnum 0 based column number + * @param policy Policy on blank / missing cells + * @return representing that column or null if undefined + policy allows. + */ + public Cell getCell(int cellnum, MissingCellPolicy policy); /** * get the number of the first cell contained in this row. @@ -170,4 +181,22 @@ public interface Row extends Iterable { boolean equals(Object obj); + + /** + * Used to specify the different possible policies + * if for the case of null and blank cells + */ + public static class MissingCellPolicy { + private static int NEXT_ID = 1; + public final int id; + private MissingCellPolicy() { + this.id = NEXT_ID++; + } + } + /** Missing cells are returned as null, Blank cells are returned as normal */ + public static final MissingCellPolicy RETURN_NULL_AND_BLANK = new MissingCellPolicy(); + /** Missing cells are returned as null, as are blank cells */ + public static final MissingCellPolicy RETURN_BLANK_AS_NULL = new MissingCellPolicy(); + /** A new, blank cell is created for missing cells. Blank cells are returned as normal */ + public static final MissingCellPolicy CREATE_NULL_AS_BLANK = new MissingCellPolicy(); } diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFRow.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFRow.java index 1a1e831a2..3047368e2 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFRow.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFRow.java @@ -21,6 +21,7 @@ import java.util.Iterator; import java.util.LinkedList; import java.util.List; +import org.apache.poi.hssf.usermodel.HSSFCell; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.Row; import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTCell; @@ -138,6 +139,27 @@ public class XSSFRow implements Row { } return null; } + + public Cell getCell(int cellnum, MissingCellPolicy policy) { + Cell cell = getCell(cellnum); + if(policy == RETURN_NULL_AND_BLANK) { + return cell; + } + if(policy == RETURN_BLANK_AS_NULL) { + if(cell == null) return cell; + if(cell.getCellType() == HSSFCell.CELL_TYPE_BLANK) { + return null; + } + return cell; + } + if(policy == CREATE_NULL_AS_BLANK) { + if(cell == null) { + return createCell((short)cellnum, HSSFCell.CELL_TYPE_BLANK); + } + return cell; + } + throw new IllegalArgumentException("Illegal policy " + policy + " (" + policy.id + ")"); + } public short getFirstCellNum() { for (Iterator it = cellIterator() ; it.hasNext() ; ) { diff --git a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFRow.java b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFRow.java index 54ded70ca..a682bab1c 100644 --- a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFRow.java +++ b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFRow.java @@ -22,6 +22,7 @@ import java.util.Iterator; import junit.framework.TestCase; import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.Row; import org.apache.poi.xssf.usermodel.TestXSSFCell.DummySharedStringSource; /** @@ -166,6 +167,64 @@ public final class TestXSSFRow extends TestCase { row.setZeroHeight(true); assertTrue(row.getZeroHeight()); } + + /** + * Tests for the missing/blank cell policy stuff + */ + public void testGetCellPolicy() throws Exception { + XSSFRow row = new XSSFRow(createParentObjects()); + + // 0 -> string + // 1 -> num + // 2 missing + // 3 missing + // 4 -> blank + // 5 -> num + row.createCell((short)0).setCellValue(new XSSFRichTextString("test")); + row.createCell((short)1).setCellValue(3.2); + row.createCell((short)4, Cell.CELL_TYPE_BLANK); + row.createCell((short)5).setCellValue(4); + + // First up, no policy + assertEquals(Cell.CELL_TYPE_STRING, row.getCell(0).getCellType()); + assertEquals(Cell.CELL_TYPE_NUMERIC, row.getCell(1).getCellType()); + assertEquals(null, row.getCell(2)); + assertEquals(null, row.getCell(3)); + assertEquals(Cell.CELL_TYPE_BLANK, row.getCell(4).getCellType()); + assertEquals(Cell.CELL_TYPE_NUMERIC, row.getCell(5).getCellType()); + + // RETURN_NULL_AND_BLANK - same as default + assertEquals(Cell.CELL_TYPE_STRING, row.getCell(0, Row.RETURN_NULL_AND_BLANK).getCellType()); + assertEquals(Cell.CELL_TYPE_NUMERIC, row.getCell(1, Row.RETURN_NULL_AND_BLANK).getCellType()); + assertEquals(null, row.getCell(2, Row.RETURN_NULL_AND_BLANK)); + assertEquals(null, row.getCell(3, Row.RETURN_NULL_AND_BLANK)); + assertEquals(Cell.CELL_TYPE_BLANK, row.getCell(4, Row.RETURN_NULL_AND_BLANK).getCellType()); + assertEquals(Cell.CELL_TYPE_NUMERIC, row.getCell(5, Row.RETURN_NULL_AND_BLANK).getCellType()); + + // RETURN_BLANK_AS_NULL - nearly the same + assertEquals(Cell.CELL_TYPE_STRING, row.getCell(0, XSSFRow.RETURN_BLANK_AS_NULL).getCellType()); + assertEquals(Cell.CELL_TYPE_NUMERIC, row.getCell(1, XSSFRow.RETURN_BLANK_AS_NULL).getCellType()); + assertEquals(null, row.getCell(2, XSSFRow.RETURN_BLANK_AS_NULL)); + assertEquals(null, row.getCell(3, XSSFRow.RETURN_BLANK_AS_NULL)); + assertEquals(null, row.getCell(4, XSSFRow.RETURN_BLANK_AS_NULL)); + assertEquals(Cell.CELL_TYPE_NUMERIC, row.getCell(5, XSSFRow.RETURN_BLANK_AS_NULL).getCellType()); + + // CREATE_NULL_AS_BLANK - creates as needed + assertEquals(Cell.CELL_TYPE_STRING, row.getCell(0, XSSFRow.CREATE_NULL_AS_BLANK).getCellType()); + assertEquals(Cell.CELL_TYPE_NUMERIC, row.getCell(1, XSSFRow.CREATE_NULL_AS_BLANK).getCellType()); + assertEquals(Cell.CELL_TYPE_BLANK, row.getCell(2, XSSFRow.CREATE_NULL_AS_BLANK).getCellType()); + assertEquals(Cell.CELL_TYPE_BLANK, row.getCell(3, XSSFRow.CREATE_NULL_AS_BLANK).getCellType()); + assertEquals(Cell.CELL_TYPE_BLANK, row.getCell(4, XSSFRow.CREATE_NULL_AS_BLANK).getCellType()); + assertEquals(Cell.CELL_TYPE_NUMERIC, row.getCell(5, XSSFRow.CREATE_NULL_AS_BLANK).getCellType()); + + // Check created ones get the right column + assertEquals((short)0, row.getCell(0, XSSFRow.CREATE_NULL_AS_BLANK).getCellNum()); + assertEquals((short)1, row.getCell(1, XSSFRow.CREATE_NULL_AS_BLANK).getCellNum()); + assertEquals((short)2, row.getCell(2, XSSFRow.CREATE_NULL_AS_BLANK).getCellNum()); + assertEquals((short)3, row.getCell(3, XSSFRow.CREATE_NULL_AS_BLANK).getCellNum()); + assertEquals((short)4, row.getCell(4, XSSFRow.CREATE_NULL_AS_BLANK).getCellNum()); + assertEquals((short)5, row.getCell(5, XSSFRow.CREATE_NULL_AS_BLANK).getCellNum()); + } /** * Method that returns a row with some sample cells diff --git a/src/scratchpad/src/org/apache/poi/hwpf/HWPFDocument.java b/src/scratchpad/src/org/apache/poi/hwpf/HWPFDocument.java index a54e50de4..a43357f02 100644 --- a/src/scratchpad/src/org/apache/poi/hwpf/HWPFDocument.java +++ b/src/scratchpad/src/org/apache/poi/hwpf/HWPFDocument.java @@ -53,10 +53,10 @@ public class HWPFDocument extends POIDocument protected FileInformationBlock _fib; /** main document stream buffer*/ - private byte[] _mainStream; + protected byte[] _mainStream; /** table stream buffer*/ - private byte[] _tableStream; + protected byte[] _tableStream; /** data stream buffer*/ protected byte[] _dataStream; @@ -93,6 +93,12 @@ public class HWPFDocument extends POIDocument /** Holds pictures table */ protected PicturesTable _pictures; + + /** Holds FSBA (shape) information */ + protected FSPATable _fspa; + + /** Escher Drawing Group information */ + protected EscherRecordHolder _dgg; protected HWPFDocument() { @@ -204,9 +210,6 @@ public class HWPFDocument extends POIDocument { _dataStream = new byte[0]; } - - // read in the pictures stream - _pictures = new PicturesTable(this, _dataStream); // get the start of text in the main stream int fcMin = _fib.getFcMin(); @@ -226,6 +229,20 @@ public class HWPFDocument extends POIDocument _cbt.adjustForDelete(0, 0, cpMin); _pbt.adjustForDelete(0, 0, cpMin); } + + // Read FSPA and Escher information + _fspa = new FSPATable(_tableStream, _fib.getFcPlcspaMom(), _fib.getLcbPlcspaMom(), getTextTable().getTextPieces()); + + if (_fib.getFcDggInfo() != 0) + { + _dgg = new EscherRecordHolder(_tableStream, _fib.getFcDggInfo(), _fib.getLcbDggInfo()); + } else + { + _dgg = new EscherRecordHolder(); + } + + // read in the pictures stream + _pictures = new PicturesTable(this, _dataStream, _mainStream, _fspa, _dgg); _st = new SectionTable(_mainStream, _tableStream, _fib.getFcPlcfsed(), _fib.getLcbPlcfsed(), fcMin, getTextTable().getTextPieces()); _ss = new StyleSheet(_tableStream, _fib.getFcStshf()); diff --git a/src/scratchpad/src/org/apache/poi/hwpf/model/EscherRecordHolder.java b/src/scratchpad/src/org/apache/poi/hwpf/model/EscherRecordHolder.java new file mode 100644 index 000000000..4242dd4df --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hwpf/model/EscherRecordHolder.java @@ -0,0 +1,116 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package org.apache.poi.hwpf.model; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import org.apache.poi.ddf.DefaultEscherRecordFactory; +import org.apache.poi.ddf.EscherContainerRecord; +import org.apache.poi.ddf.EscherRecord; +import org.apache.poi.ddf.EscherRecordFactory; + +/** + * Based on AbstractEscherRecordHolder fomr HSSF. + * + * @author Squeeself + */ +public class EscherRecordHolder +{ + protected ArrayList escherRecords = new ArrayList(); + + public EscherRecordHolder() + { + + } + + public EscherRecordHolder(byte[] data, int offset, int size) + { + fillEscherRecords(data, offset, size); + } + + private void fillEscherRecords(byte[] data, int offset, int size) + { + EscherRecordFactory recordFactory = new DefaultEscherRecordFactory(); + int pos = offset; + while ( pos < offset + size) + { + EscherRecord r = recordFactory.createRecord(data, pos); + escherRecords.add(r); + int bytesRead = r.fillFields(data, pos, recordFactory); + pos += bytesRead + 1; // There is an empty byte between each top-level record in a Word doc + } + } + + public List getEscherRecords() + { + return escherRecords; + } + + public String toString() + { + StringBuffer buffer = new StringBuffer(); + + final String nl = System.getProperty("line.separator"); + if (escherRecords.size() == 0) + buffer.append("No Escher Records Decoded" + nl); + for ( Iterator iterator = escherRecords.iterator(); iterator.hasNext(); ) + { + EscherRecord r = (EscherRecord) iterator.next(); + buffer.append(r.toString()); + } + + return buffer.toString(); + } + + /** + * If we have a EscherContainerRecord as one of our + * children (and most top level escher holders do), + * then return that. + */ + public EscherContainerRecord getEscherContainer() { + for(Iterator it = escherRecords.iterator(); it.hasNext();) { + Object er = it.next(); + if(er instanceof EscherContainerRecord) { + return (EscherContainerRecord)er; + } + } + return null; + } + + /** + * Descends into all our children, returning the + * first EscherRecord with the given id, or null + * if none found + */ + public EscherRecord findFirstWithId(short id) { + return findFirstWithId(id, getEscherRecords()); + } + private EscherRecord findFirstWithId(short id, List records) { + // Check at our level + for(Iterator it = records.iterator(); it.hasNext();) { + EscherRecord r = (EscherRecord)it.next(); + if(r.getRecordId() == id) { + return r; + } + } + + // Then check our children in turn + for(Iterator it = records.iterator(); it.hasNext();) { + EscherRecord r = (EscherRecord)it.next(); + if(r.isContainerRecord()) { + EscherRecord found = + findFirstWithId(id, r.getChildRecords()); + if(found != null) { + return found; + } + } + } + + // Not found in this lot + return null; + } +} diff --git a/src/scratchpad/src/org/apache/poi/hwpf/model/FSPA.java b/src/scratchpad/src/org/apache/poi/hwpf/model/FSPA.java new file mode 100644 index 000000000..bb3d5def1 --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hwpf/model/FSPA.java @@ -0,0 +1,182 @@ +/* ==================================================================== + 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.hwpf.model; + +import org.apache.poi.util.BitField; +import org.apache.poi.util.BitFieldFactory; +import org.apache.poi.util.LittleEndian; + +/** + * File Shape Address structure + * + * @author Squeeself + */ +public class FSPA +{ + public static final int FSPA_SIZE = 26; + private int spid; // Shape identifier. Used to get data position + private int xaLeft; // Enclosing rectangle + private int yaTop; // Enclosing rectangle + private int xaRight; // Enclosing rectangle + private int yaBottom; // Enclosing rectangle + private short options; + private static BitField fHdr = BitFieldFactory.getInstance(0x0001); // 1 in undo when in header + private static BitField bx = BitFieldFactory.getInstance(0x0006); // x pos relative to anchor CP: 0 - page margin, 1 - top of page, 2 - text, 3 - reserved + private static BitField by = BitFieldFactory.getInstance(0x0018); // y pos relative to anchor CP: ditto + private static BitField wr = BitFieldFactory.getInstance(0x01E0); // Text wrapping mode: 0 - like 2 w/o absolute, 1 - no text next to shape, 2 - wrap around absolute object, 3 - wrap as if no object, 4 - wrap tightly around object, 5 - wrap tightly, allow holes, 6-15 - reserved + private static BitField wrk = BitFieldFactory.getInstance(0x1E00); // Text wrapping mode type (for modes 2&4): 0 - wrap both sides, 1 - wrap only left, 2 - wrap only right, 3 - wrap largest side + private static BitField fRcaSimple = BitFieldFactory.getInstance(0x2000); // Overwrites bx if set, forcing rectangle to be page relative + private static BitField fBelowText = BitFieldFactory.getInstance(0x4000); // if true, shape is below text, otherwise above + private static BitField fAnchorLock = BitFieldFactory.getInstance(0x8000); // if true, anchor is locked + private int cTxbx; // Count of textboxes in shape (undo doc only) + + public FSPA() + { + } + + public FSPA(byte[] bytes, int offset) + { + spid = LittleEndian.getInt(bytes, offset); + offset += LittleEndian.INT_SIZE; + xaLeft = LittleEndian.getInt(bytes, offset); + offset += LittleEndian.INT_SIZE; + yaTop = LittleEndian.getInt(bytes, offset); + offset += LittleEndian.INT_SIZE; + xaRight = LittleEndian.getInt(bytes, offset); + offset += LittleEndian.INT_SIZE; + yaBottom = LittleEndian.getInt(bytes, offset); + offset += LittleEndian.INT_SIZE; + options = LittleEndian.getShort(bytes, offset); + offset += LittleEndian.SHORT_SIZE; + cTxbx = LittleEndian.getInt(bytes, offset); + } + + public int getSpid() + { + return spid; + } + + public int getXaLeft() + { + return xaLeft; + } + + public int getYaTop() + { + return yaTop; + } + + public int getXaRight() + { + return xaRight; + } + + public int getYaBottom() + { + return yaBottom; + } + + public boolean isFHdr() + { + return fHdr.isSet(options); + } + + public short getBx() + { + return bx.getShortValue(options); + } + + public short getBy() + { + return by.getShortValue(options); + } + + public short getWr() + { + return wr.getShortValue(options); + } + + public short getWrk() + { + return wrk.getShortValue(options); + } + + public boolean isFRcaSimple() + { + return fRcaSimple.isSet(options); + } + + public boolean isFBelowText() + { + return fBelowText.isSet(options); + } + + public boolean isFAnchorLock() + { + return fAnchorLock.isSet(options); + } + + public int getCTxbx() + { + return cTxbx; + } + + public byte[] toByteArray() + { + int offset = 0; + byte[] buf = new byte[FSPA_SIZE]; + + LittleEndian.putInt(buf, offset, spid); + offset += LittleEndian.INT_SIZE; + LittleEndian.putInt(buf, offset, xaLeft); + offset += LittleEndian.INT_SIZE; + LittleEndian.putInt(buf, offset, yaTop); + offset += LittleEndian.INT_SIZE; + LittleEndian.putInt(buf, offset, xaRight); + offset += LittleEndian.INT_SIZE; + LittleEndian.putInt(buf, offset, yaBottom); + offset += LittleEndian.INT_SIZE; + LittleEndian.putShort(buf, offset, options); + offset += LittleEndian.SHORT_SIZE; + LittleEndian.putInt(buf, offset, cTxbx); + offset += LittleEndian.INT_SIZE; + + return buf; + } + + public String toString() + { + StringBuffer buf = new StringBuffer(); + buf.append("spid: ").append(spid); + buf.append(", xaLeft: ").append(xaLeft); + buf.append(", yaTop: ").append(yaTop); + buf.append(", xaRight: ").append(xaRight); + buf.append(", yaBottom: ").append(yaBottom); + buf.append(", options: ").append(options); + buf.append(" (fHdr: ").append(isFHdr()); + buf.append(", bx: ").append(getBx()); + buf.append(", by: ").append(getBy()); + buf.append(", wr: ").append(getWr()); + buf.append(", wrk: ").append(getWrk()); + buf.append(", fRcaSimple: ").append(isFRcaSimple()); + buf.append(", fBelowText: ").append(isFBelowText()); + buf.append(", fAnchorLock: ").append(isFAnchorLock()); + buf.append("), cTxbx: ").append(cTxbx); + return buf.toString(); + } +} diff --git a/src/scratchpad/src/org/apache/poi/hwpf/model/FSPATable.java b/src/scratchpad/src/org/apache/poi/hwpf/model/FSPATable.java new file mode 100644 index 000000000..58c69ff4b --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hwpf/model/FSPATable.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.hwpf.model; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; + +/** + * This class holds all the FSPA (File Shape Address) structures. + * + * @author Squeeself + */ +public class FSPATable +{ + protected ArrayList shapes = new ArrayList(); + protected HashMap cps = new HashMap(); + protected List _text; + + public FSPATable(byte[] tableStream, int fcPlcspa, int lcbPlcspa, List tpt) + { + _text = tpt; + // Will be 0 if no drawing objects in document + if (fcPlcspa == 0) + return; + + PlexOfCps plex = new PlexOfCps(tableStream, fcPlcspa, lcbPlcspa, FSPA.FSPA_SIZE); + for (int i=0; i < plex.length(); i++) + { + GenericPropertyNode property = plex.getProperty(i); + FSPA fspa = new FSPA(property.getBytes(), 0); + + shapes.add(fspa); + cps.put(Integer.valueOf(property.getStart()), Integer.valueOf(i)); + } + } + + public FSPA getFspaFromCp(int cp) + { + Integer idx = (Integer)cps.get(Integer.valueOf(cp)); + if (idx == null) + return null; + return (FSPA)shapes.get(idx.intValue()); + } + + public List getShapes() + { + return shapes; + } + + public String toString() + { + StringBuffer buf = new StringBuffer(); + buf.append("[FPSA PLC size=").append(shapes.size()).append("]\n"); + for (Iterator it = cps.keySet().iterator(); it.hasNext(); ) + { + Integer i = (Integer) it.next(); + FSPA fspa = (FSPA) shapes.get(((Integer)cps.get(i)).intValue()); + buf.append(" [FC: ").append(i.toString()).append("] "); + buf.append(fspa.toString()); + buf.append("\n"); + } + buf.append("[/FSPA PLC]"); + return buf.toString(); + } +} diff --git a/src/scratchpad/src/org/apache/poi/hwpf/model/FileInformationBlock.java b/src/scratchpad/src/org/apache/poi/hwpf/model/FileInformationBlock.java index ee8f9724e..48e6d78b3 100644 --- a/src/scratchpad/src/org/apache/poi/hwpf/model/FileInformationBlock.java +++ b/src/scratchpad/src/org/apache/poi/hwpf/model/FileInformationBlock.java @@ -308,6 +308,26 @@ public class FileInformationBlock extends FIBAbstractType { return _fieldHandler.getFieldSize(FIBFieldHandler.PLCFFLDMOM); } + + public int getFcPlcspaMom() + { + return _fieldHandler.getFieldOffset(FIBFieldHandler.PLCSPAMOM); + } + + public int getLcbPlcspaMom() + { + return _fieldHandler.getFieldSize(FIBFieldHandler.PLCSPAMOM); + } + + public int getFcDggInfo() + { + return _fieldHandler.getFieldOffset(FIBFieldHandler.DGGINFO); + } + + public int getLcbDggInfo() + { + return _fieldHandler.getFieldSize(FIBFieldHandler.DGGINFO); + } public void writeTo (byte[] mainStream, HWPFOutputStream tableStream) throws IOException diff --git a/src/scratchpad/src/org/apache/poi/hwpf/model/PicturesTable.java b/src/scratchpad/src/org/apache/poi/hwpf/model/PicturesTable.java index d9598b106..9681741d9 100644 --- a/src/scratchpad/src/org/apache/poi/hwpf/model/PicturesTable.java +++ b/src/scratchpad/src/org/apache/poi/hwpf/model/PicturesTable.java @@ -26,7 +26,12 @@ import org.apache.poi.hwpf.usermodel.Range; import java.util.List; import java.util.ArrayList; - +import java.util.Iterator; +import org.apache.poi.ddf.DefaultEscherRecordFactory; +import org.apache.poi.ddf.EscherBSERecord; +import org.apache.poi.ddf.EscherBlipRecord; +import org.apache.poi.ddf.EscherRecord; +import org.apache.poi.ddf.EscherRecordFactory; /** * Holds information about all pictures embedded in Word Document either via "Insert -> Picture -> From File" or via @@ -57,6 +62,9 @@ public class PicturesTable private HWPFDocument _document; private byte[] _dataStream; + private byte[] _mainStream; + private FSPATable _fspa; + private EscherRecordHolder _dgg; /** @link dependency * @stereotype instantiate*/ @@ -67,10 +75,13 @@ public class PicturesTable * @param document * @param _dataStream */ - public PicturesTable(HWPFDocument _document, byte[] _dataStream) + public PicturesTable(HWPFDocument _document, byte[] _dataStream, byte[] _mainStream, FSPATable fspa, EscherRecordHolder dgg) { - this._document = _document; + this._document = _document; this._dataStream = _dataStream; + this._mainStream = _mainStream; + this._fspa = fspa; + this._dgg = dgg; } /** @@ -83,6 +94,13 @@ public class PicturesTable } return false; } + + public boolean hasEscherPicture(CharacterRun run) { + if (run.isSpecialCharacter() && !run.isObj() && !run.isOle2() && !run.isData() && run.text().startsWith("\u0008")) { + return true; + } + return false; + } /** * determines whether specified CharacterRun contains reference to a picture @@ -122,6 +140,46 @@ public class PicturesTable } return null; } + + /** + * Performs a recursive search for pictures in the given list of escher records. + * + * @param escherRecords the escher records. + * @param pictures the list to populate with the pictures. + */ + private void searchForPictures(List escherRecords, List pictures) + { + Iterator recordIter = escherRecords.iterator(); + while (recordIter.hasNext()) + { + Object obj = recordIter.next(); + if (obj instanceof EscherRecord) + { + EscherRecord escherRecord = (EscherRecord) obj; + + if (escherRecord instanceof EscherBSERecord) + { + EscherBSERecord bse = (EscherBSERecord) escherRecord; + EscherBlipRecord blip = bse.getBlipRecord(); + if (blip != null) + { + pictures.add(new Picture(blip.getPicturedata())); + } + else if (bse.getOffset() > 0) + { + // Blip stored in delay stream, which in a word doc, is the main stream + EscherRecordFactory recordFactory = new DefaultEscherRecordFactory(); + blip = (EscherBlipRecord) recordFactory.createRecord(_mainStream, bse.getOffset()); + blip.fillFields(_mainStream, bse.getOffset(), recordFactory); + pictures.add(new Picture(blip.getPicturedata())); + } + } + + // Recursive call. + searchForPictures(escherRecord.getChildRecords(), pictures); + } + } + } /** * Not all documents have all the images concatenated in the data stream @@ -136,12 +194,13 @@ public class PicturesTable for (int i = 0; i < range.numCharacterRuns(); i++) { CharacterRun run = range.getCharacterRun(i); String text = run.text(); - int j = text.charAt(0); Picture picture = extractPicture(run, false); if (picture != null) { pictures.add(picture); } } + + searchForPictures(_dgg.getEscherRecords(), pictures); return pictures; } diff --git a/src/scratchpad/src/org/apache/poi/hwpf/usermodel/Picture.java b/src/scratchpad/src/org/apache/poi/hwpf/usermodel/Picture.java index e9ee228c1..8a3737865 100644 --- a/src/scratchpad/src/org/apache/poi/hwpf/usermodel/Picture.java +++ b/src/scratchpad/src/org/apache/poi/hwpf/usermodel/Picture.java @@ -98,6 +98,15 @@ public class Picture fillImageContent(); } } + + public Picture(byte[] _dataStream) + { + this._dataStream = _dataStream; + this.dataBlockStartOfsset = 0; + this.dataBlockSize = _dataStream.length; + this.pictureBytesStartOffset = 0; + this.size = _dataStream.length; + } private void fillWidthHeight() { @@ -363,6 +372,7 @@ public class Picture do { firstByte = _dataStream[pointer]; secondByte = _dataStream[pointer+1]; + pointer += 2; } while (!(firstByte==(byte)0xFF) && pointer 0) { + throw new AssertionFailedError("identified bug 44306"); + } } } @@ -888,13 +887,13 @@ public final class TestBugs extends TestCase { writeOutAndReadBack(wb); assertTrue("no errors writing sample xls", true); } - + /** * Had a problem apparently, not sure what as it * works just fine... */ public void test44891() throws Exception { - HSSFWorkbook wb = openSample("44891.xls"); + HSSFWorkbook wb = openSample("44891.xls"); assertTrue("no errors reading sample xls", true); writeOutAndReadBack(wb); assertTrue("no errors writing sample xls", true); @@ -906,7 +905,7 @@ public final class TestBugs extends TestCase { * Works fine with poi-3.1-beta1. */ public void test44235() throws Exception { - HSSFWorkbook wb = openSample("44235.xls"); + HSSFWorkbook wb = openSample("44235.xls"); assertTrue("no errors reading sample xls", true); writeOutAndReadBack(wb); assertTrue("no errors writing sample xls", true); @@ -930,7 +929,7 @@ public final class TestBugs extends TestCase { } public void test36947() throws Exception { - HSSFWorkbook wb = openSample("36947.xls"); + HSSFWorkbook wb = openSample("36947.xls"); assertTrue("no errors reading sample xls", true); writeOutAndReadBack(wb); assertTrue("no errors writing sample xls", true); @@ -947,7 +946,7 @@ public final class TestBugs extends TestCase { } public void test39634() throws Exception { - HSSFWorkbook wb = openSample("39634.xls"); + HSSFWorkbook wb = openSample("39634.xls"); assertTrue("no errors reading sample xls", true); writeOutAndReadBack(wb); assertTrue("no errors writing sample xls", true); diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFRow.java b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFRow.java index 7611abb51..d307a0e25 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFRow.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFRow.java @@ -204,4 +204,64 @@ public final class TestHSSFRow extends TestCase { row.createCell((short) 255); assertEquals(256, row.getLastCellNum()); } + + /** + * Tests for the missing/blank cell policy stuff + */ + public void testGetCellPolicy() throws Exception { + HSSFWorkbook book = new HSSFWorkbook(); + HSSFSheet sheet = book.createSheet("test"); + HSSFRow row = sheet.createRow(0); + + // 0 -> string + // 1 -> num + // 2 missing + // 3 missing + // 4 -> blank + // 5 -> num + row.createCell((short)0).setCellValue(new HSSFRichTextString("test")); + row.createCell((short)1).setCellValue(3.2); + row.createCell((short)4, HSSFCell.CELL_TYPE_BLANK); + row.createCell((short)5).setCellValue(4); + + // First up, no policy + assertEquals(HSSFCell.CELL_TYPE_STRING, row.getCell(0).getCellType()); + assertEquals(HSSFCell.CELL_TYPE_NUMERIC, row.getCell(1).getCellType()); + assertEquals(null, row.getCell(2)); + assertEquals(null, row.getCell(3)); + assertEquals(HSSFCell.CELL_TYPE_BLANK, row.getCell(4).getCellType()); + assertEquals(HSSFCell.CELL_TYPE_NUMERIC, row.getCell(5).getCellType()); + + // RETURN_NULL_AND_BLANK - same as default + assertEquals(HSSFCell.CELL_TYPE_STRING, row.getCell(0, HSSFRow.RETURN_NULL_AND_BLANK).getCellType()); + assertEquals(HSSFCell.CELL_TYPE_NUMERIC, row.getCell(1, HSSFRow.RETURN_NULL_AND_BLANK).getCellType()); + assertEquals(null, row.getCell(2, HSSFRow.RETURN_NULL_AND_BLANK)); + assertEquals(null, row.getCell(3, HSSFRow.RETURN_NULL_AND_BLANK)); + assertEquals(HSSFCell.CELL_TYPE_BLANK, row.getCell(4, HSSFRow.RETURN_NULL_AND_BLANK).getCellType()); + assertEquals(HSSFCell.CELL_TYPE_NUMERIC, row.getCell(5, HSSFRow.RETURN_NULL_AND_BLANK).getCellType()); + + // RETURN_BLANK_AS_NULL - nearly the same + assertEquals(HSSFCell.CELL_TYPE_STRING, row.getCell(0, HSSFRow.RETURN_BLANK_AS_NULL).getCellType()); + assertEquals(HSSFCell.CELL_TYPE_NUMERIC, row.getCell(1, HSSFRow.RETURN_BLANK_AS_NULL).getCellType()); + assertEquals(null, row.getCell(2, HSSFRow.RETURN_BLANK_AS_NULL)); + assertEquals(null, row.getCell(3, HSSFRow.RETURN_BLANK_AS_NULL)); + assertEquals(null, row.getCell(4, HSSFRow.RETURN_BLANK_AS_NULL)); + assertEquals(HSSFCell.CELL_TYPE_NUMERIC, row.getCell(5, HSSFRow.RETURN_BLANK_AS_NULL).getCellType()); + + // CREATE_NULL_AS_BLANK - creates as needed + assertEquals(HSSFCell.CELL_TYPE_STRING, row.getCell(0, HSSFRow.CREATE_NULL_AS_BLANK).getCellType()); + assertEquals(HSSFCell.CELL_TYPE_NUMERIC, row.getCell(1, HSSFRow.CREATE_NULL_AS_BLANK).getCellType()); + assertEquals(HSSFCell.CELL_TYPE_BLANK, row.getCell(2, HSSFRow.CREATE_NULL_AS_BLANK).getCellType()); + assertEquals(HSSFCell.CELL_TYPE_BLANK, row.getCell(3, HSSFRow.CREATE_NULL_AS_BLANK).getCellType()); + assertEquals(HSSFCell.CELL_TYPE_BLANK, row.getCell(4, HSSFRow.CREATE_NULL_AS_BLANK).getCellType()); + assertEquals(HSSFCell.CELL_TYPE_NUMERIC, row.getCell(5, HSSFRow.CREATE_NULL_AS_BLANK).getCellType()); + + // Check created ones get the right column + assertEquals((short)0, row.getCell(0, HSSFRow.CREATE_NULL_AS_BLANK).getCellNum()); + assertEquals((short)1, row.getCell(1, HSSFRow.CREATE_NULL_AS_BLANK).getCellNum()); + assertEquals((short)2, row.getCell(2, HSSFRow.CREATE_NULL_AS_BLANK).getCellNum()); + assertEquals((short)3, row.getCell(3, HSSFRow.CREATE_NULL_AS_BLANK).getCellNum()); + assertEquals((short)4, row.getCell(4, HSSFRow.CREATE_NULL_AS_BLANK).getCellNum()); + assertEquals((short)5, row.getCell(5, HSSFRow.CREATE_NULL_AS_BLANK).getCellNum()); + } } diff --git a/src/testcases/org/apache/poi/poifs/filesystem/TestPOIFSFileSystem.java b/src/testcases/org/apache/poi/poifs/filesystem/TestPOIFSFileSystem.java index 1cde86918..4a948ba8c 100755 --- a/src/testcases/org/apache/poi/poifs/filesystem/TestPOIFSFileSystem.java +++ b/src/testcases/org/apache/poi/poifs/filesystem/TestPOIFSFileSystem.java @@ -130,7 +130,7 @@ public final class TestPOIFSFileSystem extends TestCase { * The other is to fix the handling of the last block in * POIFS, since it seems to be slight wrong */ - public void DISABLEDtestShortLastBlock() throws Exception { + public void testShortLastBlock() throws Exception { String[] files = new String[] { "ShortLastBlock.qwp", "ShortLastBlock.wps" };