From 619eff5dffd996b9d3ce9a3358dc5349c0e93d4a Mon Sep 17 00:00:00 2001 From: Nick Burch Date: Sun, 13 Jul 2008 12:37:29 +0000 Subject: [PATCH] Inspired by bug #44958 - Record level support for Data Tables. (No formula parser support though) git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@676310 13f79535-47bb-0310-9956-ffa450edef68 --- src/documentation/content/xdocs/changes.xml | 1 + src/documentation/content/xdocs/status.xml | 1 + .../org/apache/poi/hssf/dev/BiffViewer.java | 2 + .../hssf/eventmodel/EventRecordFactory.java | 3 +- .../poi/hssf/record/HyperlinkRecord.java | 4 + .../apache/poi/hssf/record/RecordFactory.java | 1 + .../apache/poi/hssf/record/TableRecord.java | 248 ++++++++++++++++++ .../apache/poi/hssf/record/formula/Ptg.java | 1 + .../poi/hssf/record/formula/TblPtg.java | 87 ++++++ .../org/apache/poi/hssf/data/44958.xls | Bin 0 -> 31232 bytes .../poi/hssf/record/AllRecordTests.java | 1 + .../poi/hssf/record/TestTableRecord.java | 107 ++++++++ .../apache/poi/hssf/usermodel/TestBugs.java | 32 +++ 13 files changed, 487 insertions(+), 1 deletion(-) create mode 100644 src/java/org/apache/poi/hssf/record/TableRecord.java create mode 100644 src/java/org/apache/poi/hssf/record/formula/TblPtg.java create mode 100644 src/testcases/org/apache/poi/hssf/data/44958.xls create mode 100644 src/testcases/org/apache/poi/hssf/record/TestTableRecord.java diff --git a/src/documentation/content/xdocs/changes.xml b/src/documentation/content/xdocs/changes.xml index c043624b6f..be530cb5c2 100644 --- a/src/documentation/content/xdocs/changes.xml +++ b/src/documentation/content/xdocs/changes.xml @@ -37,6 +37,7 @@ + 44958 - Record level support for Data Tables. (No formula parser support though) 35583 - Include a version class, org.apache.poi.Version, to allow easy introspection of the POI version Allow the cloning of one HSSFCellStyle onto another, including cloning styles from one HSSFWorkbook onto another 45289 - finished support for special comparison operators in COUNTIF diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml index a12e6dd7f6..2ec98011f6 100644 --- a/src/documentation/content/xdocs/status.xml +++ b/src/documentation/content/xdocs/status.xml @@ -34,6 +34,7 @@ + 44958 - Record level support for Data Tables. (No formula parser support though) 35583 - Include a version class, org.apache.poi.Version, to allow easy introspection of the POI version Allow the cloning of one HSSFCellStyle onto another, including cloning styles from one HSSFWorkbook onto another 45289 - finished support for special comparison operators in COUNTIF diff --git a/src/java/org/apache/poi/hssf/dev/BiffViewer.java b/src/java/org/apache/poi/hssf/dev/BiffViewer.java index 9396e679de..85b57f089f 100644 --- a/src/java/org/apache/poi/hssf/dev/BiffViewer.java +++ b/src/java/org/apache/poi/hssf/dev/BiffViewer.java @@ -365,6 +365,8 @@ public final class BiffViewer { return new FileSharingRecord( in ); case HyperlinkRecord.sid: return new HyperlinkRecord( in ); + case TableRecord.sid: + return new TableRecord( in ); } return new UnknownRecord( in ); } diff --git a/src/java/org/apache/poi/hssf/eventmodel/EventRecordFactory.java b/src/java/org/apache/poi/hssf/eventmodel/EventRecordFactory.java index 04660c5680..8c4abb76ad 100644 --- a/src/java/org/apache/poi/hssf/eventmodel/EventRecordFactory.java +++ b/src/java/org/apache/poi/hssf/eventmodel/EventRecordFactory.java @@ -96,6 +96,7 @@ import org.apache.poi.hssf.record.SharedFormulaRecord; import org.apache.poi.hssf.record.StringRecord; import org.apache.poi.hssf.record.StyleRecord; import org.apache.poi.hssf.record.TabIdRecord; +import org.apache.poi.hssf.record.TableRecord; import org.apache.poi.hssf.record.TopMarginRecord; import org.apache.poi.hssf.record.UnknownRecord; import org.apache.poi.hssf.record.UseSelFSRecord; @@ -160,7 +161,7 @@ public class EventRecordFactory TopMarginRecord.class, BottomMarginRecord.class, PaletteRecord.class, StringRecord.class, SharedFormulaRecord.class, WriteProtectRecord.class, FilePassRecord.class, PaneRecord.class, - NoteRecord.class + NoteRecord.class, TableRecord.class }; } diff --git a/src/java/org/apache/poi/hssf/record/HyperlinkRecord.java b/src/java/org/apache/poi/hssf/record/HyperlinkRecord.java index 798d4e1ff5..e28cf5bf86 100644 --- a/src/java/org/apache/poi/hssf/record/HyperlinkRecord.java +++ b/src/java/org/apache/poi/hssf/record/HyperlinkRecord.java @@ -248,6 +248,8 @@ public class HyperlinkRecord extends Record { */ public String getLabel() { + if(label == null) return null; + int idx = label.indexOf('\u0000'); return idx == -1 ? label : label.substring(0, idx); } @@ -269,6 +271,8 @@ public class HyperlinkRecord extends Record { */ public String getAddress() { + if(address == null) return null; + int idx = address.indexOf('\u0000'); return idx == -1 ? address : address.substring(0, idx); } diff --git a/src/java/org/apache/poi/hssf/record/RecordFactory.java b/src/java/org/apache/poi/hssf/record/RecordFactory.java index 5a627988f2..aaee94991d 100644 --- a/src/java/org/apache/poi/hssf/record/RecordFactory.java +++ b/src/java/org/apache/poi/hssf/record/RecordFactory.java @@ -86,6 +86,7 @@ public class RecordFactory CRNRecord.class, CFHeaderRecord.class, CFRuleRecord.class, + TableRecord.class }; } private static Map recordsMap = recordsToMap(records); diff --git a/src/java/org/apache/poi/hssf/record/TableRecord.java b/src/java/org/apache/poi/hssf/record/TableRecord.java new file mode 100644 index 0000000000..7a48e5e0e7 --- /dev/null +++ b/src/java/org/apache/poi/hssf/record/TableRecord.java @@ -0,0 +1,248 @@ +/* ==================================================================== + 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. +==================================================================== */ + + +/** + * TableRecord - The record specifies a data table. + * This record is preceded by a single Formula record that + * defines the first cell in the data table, which should + * only contain a single Ptg, {@link TblPtg}. + * + * See p536 of the June 08 binary docs + */ +package org.apache.poi.hssf.record; + +import org.apache.poi.hssf.record.formula.TblPtg; +import org.apache.poi.util.BitField; +import org.apache.poi.util.BitFieldFactory; +import org.apache.poi.util.LittleEndian; + +public class TableRecord extends Record { + public static final short sid = 566; + private short field_1_ref_rowFirst; + private short field_2_ref_rowLast; + private short field_3_ref_colFirst; + private short field_4_ref_colLast; + + private byte field_5_flags; + private byte field_6_res; + private short field_7_rowInputRow; + private short field_8_colInputRow; + private short field_9_rowInputCol; + private short field_10_colInputCol; + + private BitField alwaysCalc = BitFieldFactory.getInstance(0x0001); + private BitField reserved1 = BitFieldFactory.getInstance(0x0002); + private BitField rowOrColInpCell = BitFieldFactory.getInstance(0x0004); + private BitField oneOrTwoVar = BitFieldFactory.getInstance(0x0008); + private BitField rowDeleted = BitFieldFactory.getInstance(0x0010); + private BitField colDeleted = BitFieldFactory.getInstance(0x0020); + private BitField reserved2 = BitFieldFactory.getInstance(0x0040); + private BitField reserved3 = BitFieldFactory.getInstance(0x0080); + + protected void fillFields(RecordInputStream in) { + field_1_ref_rowFirst = in.readShort(); + field_2_ref_rowLast = in.readShort(); + field_3_ref_colFirst = in.readUByte(); + field_4_ref_colLast = in.readUByte(); + field_5_flags = in.readByte(); + field_6_res = in.readByte(); + field_7_rowInputRow = in.readShort(); + field_8_colInputRow = in.readShort(); + field_9_rowInputCol = in.readShort(); + field_10_colInputCol = in.readShort(); + } + + public TableRecord(RecordInputStream in) { + super(in); + } + public TableRecord() { + super(); + } + + + public short getRowFirst() { + return field_1_ref_rowFirst; + } + public void setRowFirst(short field_1_ref_rowFirst) { + this.field_1_ref_rowFirst = field_1_ref_rowFirst; + } + + public short getRowLast() { + return field_2_ref_rowLast; + } + public void setRowLast(short field_2_ref_rowLast) { + this.field_2_ref_rowLast = field_2_ref_rowLast; + } + + public short getColFirst() { + return field_3_ref_colFirst; + } + public void setColFirst(short field_3_ref_colFirst) { + this.field_3_ref_colFirst = field_3_ref_colFirst; + } + + public short getColLast() { + return field_4_ref_colLast; + } + public void setColLast(short field_4_ref_colLast) { + this.field_4_ref_colLast = field_4_ref_colLast; + } + + public byte getFlags() { + return field_5_flags; + } + public void setFlags(byte field_5_flags) { + this.field_5_flags = field_5_flags; + } + + public byte getReserved() { + return field_6_res; + } + public void setReserved(byte field_6_res) { + this.field_6_res = field_6_res; + } + + public short getRowInputRow() { + return field_7_rowInputRow; + } + public void setRowInputRow(short field_7_rowInputRow) { + this.field_7_rowInputRow = field_7_rowInputRow; + } + + public short getColInputRow() { + return field_8_colInputRow; + } + public void setColInputRow(short field_8_colInputRow) { + this.field_8_colInputRow = field_8_colInputRow; + } + + public short getRowInputCol() { + return field_9_rowInputCol; + } + public void setRowInputCol(short field_9_rowInputCol) { + this.field_9_rowInputCol = field_9_rowInputCol; + } + + public short getColInputCol() { + return field_10_colInputCol; + } + public void setColInputCol(short field_10_colInputCol) { + this.field_10_colInputCol = field_10_colInputCol; + } + + + public boolean isAlwaysCalc() { + return alwaysCalc.isSet(field_5_flags); + } + public void setAlwaysCalc(boolean flag) { + field_5_flags = alwaysCalc.setByteBoolean(field_5_flags, flag); + } + + public boolean isRowOrColInpCell() { + return rowOrColInpCell.isSet(field_5_flags); + } + public void setRowOrColInpCell(boolean flag) { + field_5_flags = rowOrColInpCell.setByteBoolean(field_5_flags, flag); + } + + public boolean isOneNotTwoVar() { + return oneOrTwoVar.isSet(field_5_flags); + } + public void setOneNotTwoVar(boolean flag) { + field_5_flags = oneOrTwoVar.setByteBoolean(field_5_flags, flag); + } + + public boolean isColDeleted() { + return colDeleted.isSet(field_5_flags); + } + public void setColDeleted(boolean flag) { + field_5_flags = colDeleted.setByteBoolean(field_5_flags, flag); + } + + public boolean isRowDeleted() { + return rowDeleted.isSet(field_5_flags); + } + public void setRowDeleted(boolean flag) { + field_5_flags = rowDeleted.setByteBoolean(field_5_flags, flag); + } + + + public short getSid() { + return sid; + } + + public int serialize(int offset, byte[] data) { + LittleEndian.putShort(data, 0 + offset, sid); + LittleEndian.putShort(data, 2 + offset, ( short ) (16)); + + LittleEndian.putShort(data, 4 + offset, field_1_ref_rowFirst); + LittleEndian.putShort(data, 6 + offset, field_2_ref_rowLast); + LittleEndian.putByte(data, 8 + offset, field_3_ref_colFirst); + LittleEndian.putByte(data, 9 + offset, field_4_ref_colLast); + LittleEndian.putByte(data, 10 + offset, field_5_flags); + LittleEndian.putByte(data, 11 + offset, field_6_res); + LittleEndian.putShort(data, 12 + offset, field_7_rowInputRow); + LittleEndian.putShort(data, 14 + offset, field_8_colInputRow); + LittleEndian.putShort(data, 16 + offset, field_9_rowInputCol); + LittleEndian.putShort(data, 18 + offset, field_10_colInputCol); + + return getRecordSize(); + } + public int getRecordSize() { + return 4+16; + } + + protected void validateSid(short id) { + if (id != sid) + { + throw new RecordFormatException("NOT A TABLE RECORD"); + } + } + + public String toString() + { + StringBuffer buffer = new StringBuffer(); + buffer.append("[TABLE]\n"); + buffer.append(" .row from = ") + .append(Integer.toHexString(field_1_ref_rowFirst)).append("\n"); + buffer.append(" .row to = ") + .append(Integer.toHexString(field_2_ref_rowLast)).append("\n"); + buffer.append(" .column from = ") + .append(Integer.toHexString(field_3_ref_colFirst)).append("\n"); + buffer.append(" .column to = ") + .append(Integer.toHexString(field_4_ref_colLast)).append("\n"); + + buffer.append(" .flags = ") + .append(Integer.toHexString(field_5_flags)).append("\n"); + buffer.append(" .always calc =") + .append(isAlwaysCalc()).append("\n"); + + buffer.append(" .reserved = ") + .append(Integer.toHexString(field_6_res)).append("\n"); + buffer.append(" .row input row = ") + .append(Integer.toHexString(field_7_rowInputRow)).append("\n"); + buffer.append(" .col input row = ") + .append(Integer.toHexString(field_8_colInputRow)).append("\n"); + buffer.append(" .row input col = ") + .append(Integer.toHexString(field_9_rowInputCol)).append("\n"); + buffer.append(" .col input col = ") + .append(Integer.toHexString(field_10_colInputCol)).append("\n"); + buffer.append("[/TABLE]\n"); + return buffer.toString(); + } +} diff --git a/src/java/org/apache/poi/hssf/record/formula/Ptg.java b/src/java/org/apache/poi/hssf/record/formula/Ptg.java index 6964df2ef8..7a882e4ae0 100644 --- a/src/java/org/apache/poi/hssf/record/formula/Ptg.java +++ b/src/java/org/apache/poi/hssf/record/formula/Ptg.java @@ -188,6 +188,7 @@ public abstract class Ptg implements Cloneable { switch(id) { case 0x00: return new UnknownPtg(); // TODO - not a real Ptg case ExpPtg.sid: return new ExpPtg(in); // 0x01 + case TblPtg.sid: return new TblPtg(in); // 0x02 case AddPtg.sid: return AddPtg.instance; // 0x03 case SubtractPtg.sid: return SubtractPtg.instance; // 0x04 case MultiplyPtg.sid: return MultiplyPtg.instance; // 0x05 diff --git a/src/java/org/apache/poi/hssf/record/formula/TblPtg.java b/src/java/org/apache/poi/hssf/record/formula/TblPtg.java new file mode 100644 index 0000000000..f055b5d29b --- /dev/null +++ b/src/java/org/apache/poi/hssf/record/formula/TblPtg.java @@ -0,0 +1,87 @@ +/* ==================================================================== + 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; + +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.hssf.record.RecordFormatException; +import org.apache.poi.hssf.record.RecordInputStream; + +import org.apache.poi.util.LittleEndian; + +/** + * This ptg indicates a data table. + * It only occurs in a FORMULA record, never in an + * ARRAY or NAME record. When ptgTbl occurs in a + * formula, it is the only token in the formula. + * (TODO - check this when processing) + * This indicates that the cell containing the + * formula is an interior cell in a data table; + * the table description is found in a TABLE + * record. Rows and columns which contain input + * values to be substituted in the table do + * not contain ptgTbl. + * See page 811 of the june 08 binary docs. + */ +public final class TblPtg extends ControlPtg { + private final static int SIZE = 4; + public final static short sid = 0x2; + /** The row number of the upper left corner */ + private final short field_1_first_row; + /** The column number of the upper left corner */ + private final short field_2_first_col; + + public TblPtg(RecordInputStream in) + { + field_1_first_row = in.readShort(); + field_2_first_col = in.readUByte(); + } + + public void writeBytes(byte [] array, int offset) + { + array[offset+0]= (byte) (sid); + LittleEndian.putShort(array,offset+1,field_1_first_row); + LittleEndian.putByte(array,offset+3,field_2_first_col); + } + + public int getSize() + { + return SIZE; + } + + public short getRow() { + return field_1_first_row; + } + + public short getColumn() { + return field_2_first_col; + } + + public String toFormulaString(HSSFWorkbook book) + { + // table(....)[][] + throw new RecordFormatException("Table and Arrays are not yet supported"); + } + + public String toString() + { + StringBuffer buffer = new StringBuffer("[Data Table - Parent cell is an interior cell in a data table]\n"); + buffer.append("top left row = ").append(getRow()).append("\n"); + buffer.append("top left col = ").append(getColumn()).append("\n"); + return buffer.toString(); + } +} diff --git a/src/testcases/org/apache/poi/hssf/data/44958.xls b/src/testcases/org/apache/poi/hssf/data/44958.xls new file mode 100644 index 0000000000000000000000000000000000000000..c16701118de6766cc705eee952224ad0fb6fc43f GIT binary patch literal 31232 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