From 772ff6d1f406e0724336bb98b9b41805fed32568 Mon Sep 17 00:00:00 2001 From: Josh Micich Date: Wed, 27 Aug 2008 18:51:03 +0000 Subject: [PATCH] Added names of known but uninterpreted BIFF records git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@689559 13f79535-47bb-0310-9956-ffa450edef68 --- .../apache/poi/hssf/model/RecordOrderer.java | 29 +- .../apache/poi/hssf/record/UnknownRecord.java | 377 ++++++++++++------ .../record/aggregates/PageSettingsBlock.java | 5 +- 3 files changed, 272 insertions(+), 139 deletions(-) diff --git a/src/java/org/apache/poi/hssf/model/RecordOrderer.java b/src/java/org/apache/poi/hssf/model/RecordOrderer.java index 887497a911..e30e0a2a6e 100644 --- a/src/java/org/apache/poi/hssf/model/RecordOrderer.java +++ b/src/java/org/apache/poi/hssf/model/RecordOrderer.java @@ -43,6 +43,7 @@ import org.apache.poi.hssf.record.SCLRecord; import org.apache.poi.hssf.record.SaveRecalcRecord; import org.apache.poi.hssf.record.SelectionRecord; import org.apache.poi.hssf.record.UncalcedRecord; +import org.apache.poi.hssf.record.UnknownRecord; import org.apache.poi.hssf.record.WindowTwoRecord; import org.apache.poi.hssf.record.aggregates.ConditionalFormattingTable; import org.apache.poi.hssf.record.aggregates.DataValidityTable; @@ -57,8 +58,6 @@ import org.apache.poi.hssf.record.aggregates.PageSettingsBlock; * @author Josh Micich */ final class RecordOrderer { - // TODO - add UninterpretedRecord as base class for many of these - // unimplemented sids // TODO - simplify logic using a generalised record ordering @@ -126,7 +125,7 @@ final class RecordOrderer { case PrintGridlinesRecord.sid: case GridsetRecord.sid: case DefaultRowHeightRecord.sid: - case 0x0081: // SHEETPR + case UnknownRecord.SHEETPR_0081: return true; // next is the 'Worksheet Protection Block' } @@ -149,10 +148,10 @@ final class RecordOrderer { case SCLRecord.sid: case PaneRecord.sid: case SelectionRecord.sid: - case 0x0099:// STANDARDWIDTH + case UnknownRecord.STANDARDWIDTH_0099: // MergedCellsTable usually here - case 0x015f:// LABELRANGES - case 0x00ef:// PHONETICPR + case UnknownRecord.LABELRANGES_015F: + case UnknownRecord.PHONETICPR_00EF: return i + 1; } } @@ -168,7 +167,7 @@ final class RecordOrderer { case SCLRecord.sid: case PaneRecord.sid: case SelectionRecord.sid: - case 0x0099:// STANDARDWIDTH + case UnknownRecord.STANDARDWIDTH_0099: return i + 1; } } @@ -229,16 +228,16 @@ final class RecordOrderer { short sid = ((Record)rb).getSid(); switch(sid) { case WindowTwoRecord.sid: - case 0x00A0: // SCL + case UnknownRecord.SCL_00A0: case PaneRecord.sid: case SelectionRecord.sid: - case 0x0099: // STANDARDWIDTH + case UnknownRecord.STANDARDWIDTH_0099: // MergedCellsTable - case 0x015F: // LABELRANGES - case 0x00EF: // PHONETICPR + case UnknownRecord.LABELRANGES_015F: + case UnknownRecord.PHONETICPR_00EF: // ConditionalFormattingTable case HyperlinkRecord.sid: - case 0x0800: // QUICKTIP + case UnknownRecord.QUICKTIP_0800: return true; } return false; @@ -246,9 +245,9 @@ final class RecordOrderer { private static boolean isDVTSubsequentRecord(short sid) { switch(sid) { - case 0x0862: // SHEETLAYOUT - case 0x0867: // SHEETPROTECTION - case 0x0868: // RANGEPROTECTION + case UnknownRecord.SHEETEXT_0862: + case UnknownRecord.SHEETPROTECTION_0867: + case UnknownRecord.RANGEPROTECTION_0868: case EOFRecord.sid: return true; } diff --git a/src/java/org/apache/poi/hssf/record/UnknownRecord.java b/src/java/org/apache/poi/hssf/record/UnknownRecord.java index d3e76a020f..851dbfbf55 100644 --- a/src/java/org/apache/poi/hssf/record/UnknownRecord.java +++ b/src/java/org/apache/poi/hssf/record/UnknownRecord.java @@ -1,4 +1,3 @@ - /* ==================================================================== Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with @@ -15,10 +14,10 @@ See the License for the specific language governing permissions and limitations under the License. ==================================================================== */ - package org.apache.poi.hssf.record; +import org.apache.poi.util.HexDump; import org.apache.poi.util.LittleEndian; /** @@ -31,125 +30,259 @@ import org.apache.poi.util.LittleEndian; * @author Jason Height (jheight at chariot dot net dot au) * @author Glen Stampoultzis (glens at apache.org) */ +public final class UnknownRecord extends Record { + + /* + * Some Record IDs used by POI as 'milestones' in the record stream + */ + public static final int PLS_004D = 0x004D; + public static final int SHEETPR_0081 = 0x0081; + public static final int STANDARDWIDTH_0099 = 0x0099; + public static final int SCL_00A0 = 0x00A0; + public static final int BITMAP_00E9 = 0x00E9; + public static final int PHONETICPR_00EF = 0x00EF; + public static final int LABELRANGES_015F = 0x015F; + public static final int QUICKTIP_0800 = 0x0800; + public static final int SHEETEXT_0862 = 0x0862; // OOO calls this SHEETLAYOUT + public static final int SHEETPROTECTION_0867 = 0x0867; + public static final int RANGEPROTECTION_0868 = 0x0868; + + private int _sid; + private byte[] _rawData; + + /** + * @param id id of the record -not validated, just stored for serialization + * @param data the data + */ + public UnknownRecord(int id, byte[] data) { + _sid = id & 0xFFFF; + _rawData = data; + } + + + /** + * construct an unknown record. No fields are interpreted and the record will + * be serialized in its original form more or less + * @param in the RecordInputstream to read the record from + */ + public UnknownRecord(RecordInputStream in) { + _sid = in.getSid(); + _rawData = in.readRemainder(); + if (false && getBiffName(_sid) == null) { + // unknown sids in the range 0x0004-0x0013 are probably 'sub-records' of ObjectRecord + // those sids are in a different number space. + // TODO - put unknown OBJ sub-records in a different class + System.out.println("Unknown record 0x" + Integer.toHexString(_sid).toUpperCase()); + } + } + + /** + * spit the record out AS IS. no interpretation or identification + */ + public final int serialize(int offset, byte[] data) { + LittleEndian.putUShort(data, 0 + offset, _sid); + int dataSize = _rawData.length; + LittleEndian.putUShort(data, 2 + offset, dataSize); + System.arraycopy(_rawData, 0, data, 4 + offset, dataSize); + return 4 + dataSize; + } + + public final int getRecordSize() { + return 4 + _rawData.length; + } + + /** + * NO OP! + */ + protected void validateSid(short id) { + // if we had a valid sid we wouldn't be using the "Unknown Record" record now would we? + } + + /** + * print a sort of string representation ([UNKNOWN RECORD] id = x [/UNKNOWN RECORD]) + */ + public final String toString() { + String biffName = getBiffName(_sid); + if (biffName == null) { + biffName = "UNKNOWNRECORD"; + } + StringBuffer sb = new StringBuffer(); + + sb.append("[").append(biffName).append("] (0x"); + sb.append(Integer.toHexString(_sid).toUpperCase() + ")\n"); + if (_rawData.length > 0) { + sb.append(" rawData=").append(HexDump.toHex(_rawData)).append("\n"); + } + sb.append("[/").append(biffName).append("]\n"); + return sb.toString(); + } + + public final short getSid() { + return (short) _sid; + } + + /** + * These BIFF record types are known but still uninterpreted by POI + * + * @return the documented name of this BIFF record type + */ + private static String getBiffName(int sid) { + // Note to POI developers: + // Make sure you delete the corresponding entry from + // this method any time a new Record subclass is created. + switch (sid) { + case PLS_004D: return "PLS"; + case 0x0050: return "DCON"; + case 0x007F: return "IMDATA"; + case SHEETPR_0081: return "SHEETPR"; + case 0x0090: return "SORT"; + case 0x0094: return "LHRECORD"; + case STANDARDWIDTH_0099: return "STANDARDWIDTH"; + case 0x009D: return "AUTOFILTERINFO"; + case SCL_00A0: return "SCL"; + case 0x00AE: return "SCENMAN"; + case 0x00D3: return "OBPROJ"; + case 0x00DC: return "PARAMQRY"; + case 0x00DE: return "OLESIZE"; + case BITMAP_00E9: return "BITMAP"; + case PHONETICPR_00EF: return "PHONETICPR"; + + case LABELRANGES_015F: return "LABELRANGES"; + case 0x01BA: return "CODENAME"; + case 0x01A9: return "USERBVIEW"; + case 0x01AA: return "USERSVIEWBEGIN"; + case 0x01AB: return "USERSVIEWEND"; + case 0x01AD: return "QSI"; + + case 0x01C0: return "EXCEL9FILE"; + + case 0x0802: return "QSISXTAG"; + case 0x0803: return "DBQUERYEXT"; + case 0x0805: return "TXTQUERY"; + + case QUICKTIP_0800: return "QUICKTIP"; + case 0x0850: return "CHARTFRTINFO"; + case 0x0852: return "STARTBLOCK"; + case 0x0853: return "ENDBLOCK"; + case 0x0856: return "CATLAB"; + case SHEETEXT_0862: return "SHEETEXT"; + case 0x0863: return "BOOKEXT"; + case SHEETPROTECTION_0867: return "SHEETPROTECTION"; + case RANGEPROTECTION_0868: return "RANGEPROTECTION"; + case 0x086B: return "DATALABEXTCONTENTS"; + case 0x086C: return "CELLWATCH"; + case 0x0874: return "DROPDOWNOBJIDS"; + case 0x0876: return "DCONN"; + case 0x087B: return "CFEX"; + case 0x087C: return "XFCRC"; + case 0x087D: return "XFEXT"; + case 0x088B: return "PLV"; + case 0x088C: return "COMPAT12"; + case 0x088D: return "DXF"; + case 0x088E: return "TABLESTYLES"; + case 0x0892: return "STYLEEXT"; + case 0x0896: return "THEME"; + case 0x0897: return "GUIDTYPELIB"; + case 0x089A: return "MTRSETTINGS"; + case 0x089B: return "COMPRESSPICTURES"; + case 0x089C: return "HEADERFOOTER"; + case 0x08A3: return "FORCEFULLCALCULATION"; + case 0x08A4: return "SHAPEPROPSSTREAM"; + case 0x08A5: return "TEXTPROPSSTREAM"; + case 0x08A6: return "RICHTEXTSTREAM"; + + case 0x08C8: return "PLV{Mac Excel}"; + + case 0x1051: return "SHAPEPROPSSTREAM"; + + } + if (isObservedButUnknown(sid)) { + return "UNKNOWN-" + Integer.toHexString(sid).toUpperCase(); + } + + return null; + } + + /** + * + * @return true if the unknown record id has been observed in POI unit tests + */ + private static boolean isObservedButUnknown(int sid) { + switch (sid) { + case 0x0033: + // contains 2 bytes of data: 0x0001 or 0x0003 + case 0x0034: + // Seems to be written by MSAccess + // contains text "[Microsoft JET Created Table]0021010" + // appears after last cell value record and before WINDOW2 + case 0x01BD: + case 0x01C2: + // Written by Excel 2007 + // rawData is multiple of 12 bytes long + // appears after last cell value record and before WINDOW2 or drawing records + case 0x089D: + case 0x089E: + case 0x08A7: + + case 0x1001: + case 0x1006: + case 0x1007: + case 0x1009: + case 0x100A: + case 0x100B: + case 0x100C: + case 0x1014: + case 0x1017: + case 0x1018: + case 0x1019: + case 0x101A: + case 0x101B: + case 0x101D: + case 0x101E: + case 0x101F: + case 0x1020: + case 0x1021: + case 0x1022: + case 0x1024: + case 0x1025: + case 0x1026: + case 0x1027: + case 0x1032: + case 0x1033: + case 0x1034: + case 0x1035: + case 0x103A: + case 0x1041: + case 0x1043: + case 0x1044: + case 0x1045: + case 0x1046: + case 0x104A: + case 0x104B: + case 0x104E: + case 0x104F: + case 0x1051: + case 0x105C: + case 0x105D: + case 0x105F: + case 0x1060: + case 0x1062: + case 0x1063: + case 0x1064: + case 0x1065: + case 0x1066: + return true; + } + return false; + } + + protected final void fillFields(RecordInputStream in) { + throw new RecordFormatException( + "Unknown record cannot be constructed via offset -- we need a copy of the data"); + } -public class UnknownRecord - extends Record -{ - private short sid = 0; - private byte[] thedata = null; - - public UnknownRecord() - { - } - - /** - * @param id id of the record -not validated, just stored for serialization - * @param data the data - */ - public UnknownRecord(short id, byte[] data) - { - this.sid = id; - this.thedata = data; - } - - - /** - * construct an unknown record. No fields are interperated and the record will - * be serialized in its original form more or less - * @param in the RecordInputstream to read the record from - */ - - public UnknownRecord(RecordInputStream in) - { - sid = in.getSid(); - thedata = in.readRemainder(); - - //System.out.println("UnknownRecord: 0x"+Integer.toHexString(sid)); - } - - /** - * spit the record out AS IS. no interpretation or identification - */ - public int serialize(int offset, byte [] data) - { - if (thedata == null) - { - thedata = new byte[ 0 ]; - } - LittleEndian.putShort(data, 0 + offset, sid); - LittleEndian.putShort(data, 2 + offset, ( short ) (thedata.length)); - if (thedata.length > 0) - { - System.arraycopy(thedata, 0, data, 4 + offset, thedata.length); - } - return getRecordSize(); - } - - public int getRecordSize() - { - int retval = 4; - - if (thedata != null) - { - retval += thedata.length; - } - return retval; - } - - protected void fillFields(byte [] data, short sid) - { - this.sid = sid; - thedata = data; - } - - /** - * NO OP! - */ - - protected void validateSid(short id) - { - - // if we had a valid sid we wouldn't be using the "Unknown Record" record now would we? - } - - /** - * print a sort of string representation ([UNKNOWN RECORD] id = x [/UNKNOWN RECORD]) - */ - - public String toString() - { - StringBuffer buffer = new StringBuffer(); - - buffer.append("[UNKNOWN RECORD:" + Integer.toHexString(sid) + "]\n"); - buffer.append(" .id = ").append(Integer.toHexString(sid)) - .append("\n"); - buffer.append("[/UNKNOWN RECORD]\n"); - return buffer.toString(); - } - - public short getSid() - { - return sid; - } - - /** - * called by the constructor, should set class level fields. Should throw - * runtime exception for bad/icomplete data. - * - * @param in the RecordInputstream to read the record from - */ - - protected void fillFields(RecordInputStream in) - { - throw new RecordFormatException( - "Unknown record cannot be constructed via offset -- we need a copy of the data"); - } - - /** Unlike the other Record.clone methods this is a shallow clone*/ - public Object clone() { - UnknownRecord rec = new UnknownRecord(); - rec.sid = sid; - rec.thedata = thedata; - return rec; - } + public final Object clone() { + // immutable - ok to return this + return this; + } } diff --git a/src/java/org/apache/poi/hssf/record/aggregates/PageSettingsBlock.java b/src/java/org/apache/poi/hssf/record/aggregates/PageSettingsBlock.java index cc8be41791..819940e12e 100644 --- a/src/java/org/apache/poi/hssf/record/aggregates/PageSettingsBlock.java +++ b/src/java/org/apache/poi/hssf/record/aggregates/PageSettingsBlock.java @@ -35,6 +35,7 @@ import org.apache.poi.hssf.record.PrintSetupRecord; import org.apache.poi.hssf.record.Record; import org.apache.poi.hssf.record.RightMarginRecord; import org.apache.poi.hssf.record.TopMarginRecord; +import org.apache.poi.hssf.record.UnknownRecord; import org.apache.poi.hssf.record.VCenterRecord; import org.apache.poi.hssf.record.VerticalPageBreakRecord; @@ -99,9 +100,9 @@ public final class PageSettingsBlock extends RecordAggregate { case RightMarginRecord.sid: case TopMarginRecord.sid: case BottomMarginRecord.sid: - case 0x004D: // PLS + case UnknownRecord.PLS_004D: case PrintSetupRecord.sid: - case 0x00E9: // BITMAP + case UnknownRecord.BITMAP_00E9: return true; } return false; -- 2.39.5