diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml index 99942aaae..6ba397d18 100644 --- a/src/documentation/content/xdocs/status.xml +++ b/src/documentation/content/xdocs/status.xml @@ -35,6 +35,7 @@ + 46776 - Added clone() method to MulBlankRecord to fix crash in Sheet.cloneSheet() 47244 - Fixed HSSFSheet to handle missing header / footer records 47312 - Fixed formula parser to properly reject cell references with a '0' row component 47199 - Fixed PageSettingsBlock/Sheet to tolerate margin records after other non-PSB records diff --git a/src/java/org/apache/poi/hssf/record/MulBlankRecord.java b/src/java/org/apache/poi/hssf/record/MulBlankRecord.java index 20a061a02..87d4eafea 100644 --- a/src/java/org/apache/poi/hssf/record/MulBlankRecord.java +++ b/src/java/org/apache/poi/hssf/record/MulBlankRecord.java @@ -22,128 +22,114 @@ import org.apache.poi.util.LittleEndianOutput; /** * Title: Multiple Blank cell record(0x00BE)

* Description: Represents a set of columns in a row with no value but with styling. - * In this release we have read-only support for this record type. - * The RecordFactory converts this to a set of BlankRecord objects.

+ *

* REFERENCE: PG 329 Microsoft Excel 97 Developer's Kit (ISBN: 1-57231-498-2)

* @author Andrew C. Oliver (acoliver at apache dot org) * @author Glen Stampoultzis (glens at apache.org) * @see BlankRecord */ public final class MulBlankRecord extends StandardRecord { - public final static short sid = 0x00BE; - - private int field_1_row; - private short field_2_first_col; - private short[] field_3_xfs; - private short field_4_last_col; + public final static short sid = 0x00BE; - public MulBlankRecord(int row, int firstCol, short[] xfs) { - field_1_row = row; - field_2_first_col = (short)firstCol; - field_3_xfs = xfs; - field_4_last_col = (short) (firstCol + xfs.length - 1); - } + private final int _row; + private final int _firstCol; + private final short[] _xfs; + private final int _lastCol; - /** - * get the row number of the cells this represents - * - * @return row number - */ - public int getRow() - { - return field_1_row; - } + public MulBlankRecord(int row, int firstCol, short[] xfs) { + _row = row; + _firstCol = firstCol; + _xfs = xfs; + _lastCol = firstCol + xfs.length - 1; + } - /** - * starting column (first cell this holds in the row) - * @return first column number - */ - public short getFirstColumn() - { - return field_2_first_col; - } + /** + * @return the row number of the cells this represents + */ + public int getRow() { + return _row; + } - /** - * ending column (last cell this holds in the row) - * @return first column number - */ - public short getLastColumn() - { - return field_4_last_col; - } + /** + * @return starting column (first cell this holds in the row). Zero based + */ + public int getFirstColumn() { + return _firstCol; + } - /** - * get the number of columns this contains (last-first +1) - * @return number of columns (last - first +1) - */ - public int getNumColumns() - { - return field_4_last_col - field_2_first_col + 1; - } + /** + * get the number of columns this contains (last-first +1) + * @return number of columns (last - first +1) + */ + public int getNumColumns() { + return _lastCol - _firstCol + 1; + } - /** - * returns the xf index for column (coffset = column - field_2_first_col) - * @param coffset the column (coffset = column - field_2_first_col) - * @return the XF index for the column - */ - public short getXFAt(int coffset) - { - return field_3_xfs[ coffset ]; - } + /** + * returns the xf index for column (coffset = column - field_2_first_col) + * @param coffset the column (coffset = column - field_2_first_col) + * @return the XF index for the column + */ + public short getXFAt(int coffset) { + return _xfs[coffset]; + } - /** - * @param in the RecordInputstream to read the record from - */ - public MulBlankRecord(RecordInputStream in) { - field_1_row = in.readUShort(); - field_2_first_col = in.readShort(); - field_3_xfs = parseXFs(in); - field_4_last_col = in.readShort(); - } + /** + * @param in the RecordInputstream to read the record from + */ + public MulBlankRecord(RecordInputStream in) { + _row = in.readUShort(); + _firstCol = in.readShort(); + _xfs = parseXFs(in); + _lastCol = in.readShort(); + } - private static short [] parseXFs(RecordInputStream in) - { - short[] retval = new short[ (in.remaining() - 2) / 2 ]; + private static short [] parseXFs(RecordInputStream in) { + short[] retval = new short[(in.remaining() - 2) / 2]; - for (int idx = 0; idx < retval.length;idx++) - { - retval[idx] = in.readShort(); - } - return retval; - } + for (int idx = 0; idx < retval.length;idx++) { + retval[idx] = in.readShort(); + } + return retval; + } - public String toString() { - StringBuffer buffer = new StringBuffer(); + public String toString() { + StringBuffer buffer = new StringBuffer(); - buffer.append("[MULBLANK]\n"); - buffer.append("row = ").append(Integer.toHexString(getRow())).append("\n"); - buffer.append("firstcol = ").append(Integer.toHexString(getFirstColumn())).append("\n"); - buffer.append(" lastcol = ").append(Integer.toHexString(getLastColumn())).append("\n"); - for (int k = 0; k < getNumColumns(); k++) { - buffer.append("xf").append(k).append(" = ").append( - Integer.toHexString(getXFAt(k))).append("\n"); - } - buffer.append("[/MULBLANK]\n"); - return buffer.toString(); - } + buffer.append("[MULBLANK]\n"); + buffer.append("row = ").append(Integer.toHexString(getRow())).append("\n"); + buffer.append("firstcol = ").append(Integer.toHexString(getFirstColumn())).append("\n"); + buffer.append(" lastcol = ").append(Integer.toHexString(_lastCol)).append("\n"); + for (int k = 0; k < getNumColumns(); k++) { + buffer.append("xf").append(k).append(" = ").append( + Integer.toHexString(getXFAt(k))).append("\n"); + } + buffer.append("[/MULBLANK]\n"); + return buffer.toString(); + } - public short getSid() - { - return sid; - } + public short getSid() { + return sid; + } - public void serialize(LittleEndianOutput out) { - out.writeShort(field_1_row); - out.writeShort(field_2_first_col); - int nItems = field_3_xfs.length; - for (int i = 0; i < nItems; i++) { - out.writeShort(field_3_xfs[i]); - } - out.writeShort(field_4_last_col); - } + public void serialize(LittleEndianOutput out) { + out.writeShort(_row); + out.writeShort(_firstCol); + int nItems = _xfs.length; + for (int i = 0; i < nItems; i++) { + out.writeShort(_xfs[i]); + } + out.writeShort(_lastCol); + } - protected int getDataSize() { - // 3 short fields + array of shorts - return 6 + field_3_xfs.length * 2; - } + protected int getDataSize() { + // 3 short fields + array of shorts + return 6 + _xfs.length * 2; + } + + @Override + public Object clone() { + // immutable - so OK to return this + return this; + } } diff --git a/src/testcases/org/apache/poi/hssf/model/TestSheet.java b/src/testcases/org/apache/poi/hssf/model/TestSheet.java index 68ff2f659..a16c278e5 100644 --- a/src/testcases/org/apache/poi/hssf/model/TestSheet.java +++ b/src/testcases/org/apache/poi/hssf/model/TestSheet.java @@ -18,6 +18,7 @@ package org.apache.poi.hssf.model; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import junit.framework.AssertionFailedError; @@ -34,6 +35,7 @@ import org.apache.poi.hssf.record.FormulaRecord; import org.apache.poi.hssf.record.GutsRecord; import org.apache.poi.hssf.record.IndexRecord; import org.apache.poi.hssf.record.MergeCellsRecord; +import org.apache.poi.hssf.record.MulBlankRecord; import org.apache.poi.hssf.record.NumberRecord; import org.apache.poi.hssf.record.Record; import org.apache.poi.hssf.record.RecordBase; @@ -629,11 +631,11 @@ public final class TestSheet extends TestCase { assertEquals(colIx, dims.getFirstCol()); assertEquals(colIx, dims.getLastCol()); } - + /** - * Prior to the fix for bug 46547, shifting formulas would have the side-effect + * Prior to the fix for bug 46547, shifting formulas would have the side-effect * of creating a {@link ConditionalFormattingTable}. There was no impairment to - * functionality since empty record aggregates are equivalent to missing record + * functionality since empty record aggregates are equivalent to missing record * aggregates. However, since this unnecessary creation helped expose bug 46547b, * and since there is a slight performance hit the fix was made to avoid it. */ @@ -643,14 +645,14 @@ public final class TestSheet extends TestCase { List sheetRecs = sheet.getRecords(); assertEquals(22, sheetRecs.size()); - + FormulaShifter shifter = FormulaShifter.createForRowShift(0, 0, 0, 1); sheet.updateFormulasAfterCellShift(shifter, 0); if (sheetRecs.size() == 23 && sheetRecs.get(21) instanceof ConditionalFormattingTable) { throw new AssertionFailedError("Identified bug 46547a"); } assertEquals(22, sheetRecs.size()); - + } /** * Bug 46547 happened when attempting to add conditional formatting to a sheet @@ -671,4 +673,33 @@ public final class TestSheet extends TestCase { } assertNotNull(cft); } + + public void testCloneMulBlank_bug46776() { + Record[] recs = { + Sheet.createBOF(), + new DimensionsRecord(), + new RowRecord(1), + new MulBlankRecord(1, 3, new short[] { 0x0F, 0x0F, 0x0F, } ), + new RowRecord(2), + createWindow2Record(), + EOFRecord.instance, + }; + + Sheet sheet = createSheet(Arrays.asList(recs)); + + Sheet sheet2; + try { + sheet2 = sheet.cloneSheet(); + } catch (RuntimeException e) { + if (e.getMessage().equals("The class org.apache.poi.hssf.record.MulBlankRecord needs to define a clone method")) { + throw new AssertionFailedError("Identified bug 46776"); + } + throw e; + } + + RecordCollector rc = new RecordCollector(); + sheet2.visitContainedRecords(rc, 0); + Record[] clonedRecs = rc.getRecords(); + assertEquals(recs.length+2, clonedRecs.length); // +2 for INDEX and DBCELL + } }