From a8122dcc10b973329ee40823c4238afb5ae69537 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.eu.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 + src/java/org/apache/poi/hssf/dev/BiffViewer.java | 2 + .../poi/hssf/eventmodel/EventRecordFactory.java | 3 +- .../apache/poi/hssf/record/HyperlinkRecord.java | 4 + .../org/apache/poi/hssf/record/RecordFactory.java | 1 + .../org/apache/poi/hssf/record/TableRecord.java | 248 +++++++++++++++++++++ .../org/apache/poi/hssf/record/formula/Ptg.java | 1 + .../org/apache/poi/hssf/record/formula/TblPtg.java | 87 ++++++++ src/testcases/org/apache/poi/hssf/data/44958.xls | Bin 0 -> 31232 bytes .../org/apache/poi/hssf/record/AllRecordTests.java | 1 + .../apache/poi/hssf/record/TestTableRecord.java | 107 +++++++++ .../org/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 c043624..be530cb 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 a12e6dd..2ec9801 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 9396e67..85b57f0 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 04660c5..8c4abb7 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 798d4e1..e28cf5b 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 5a62798..aaee949 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 0000000..7a48e5e --- /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 6964df2..7a882e4 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 0000000..f055b5d --- /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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