001: /* ====================================================================
002: Licensed to the Apache Software Foundation (ASF) under one or more
003: contributor license agreements. See the NOTICE file distributed with
004: this work for additional information regarding copyright ownership.
005: The ASF licenses this file to You under the Apache License, Version 2.0
006: (the "License"); you may not use this file except in compliance with
007: the License. You may obtain a copy of the License at
008:
009: http://www.apache.org/licenses/LICENSE-2.0
010:
011: Unless required by applicable law or agreed to in writing, software
012: distributed under the License is distributed on an "AS IS" BASIS,
013: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: See the License for the specific language governing permissions and
015: limitations under the License.
016: ==================================================================== */
017:
018: package org.apache.poi.hssf.record;
019:
020: import org.apache.poi.util.*;
021:
022: /**
023: * A sub-record within the OBJ record which stores a reference to an object
024: * stored in a separate entry within the OLE2 compound file.
025: *
026: * @author Daniel Noll
027: */
028: public class EmbeddedObjectRefSubRecord extends SubRecord {
029: public static final short sid = 0x9;
030:
031: public short field_1_stream_id_offset; // Offset to stream ID from the point after this value.
032: public short[] field_2_unknown; // Unknown stuff at the front. TODO: Confirm that it's a short[]
033: // TODO: Consider making a utility class for these. I've discovered the same field ordering
034: // in FormatRecord and StringRecord, it may be elsewhere too.
035: public short field_3_unicode_len; // Length of Unicode string.
036: public boolean field_4_unicode_flag; // Flags whether the string is Unicode.
037: public String field_5_ole_classname; // Classname of the embedded OLE document (e.g. Word.Document.8)
038: public int field_6_stream_id; // ID of the OLE stream containing the actual data.
039:
040: private int field_5_ole_classname_padding; // developer laziness...
041: public byte[] remainingBytes;
042:
043: public EmbeddedObjectRefSubRecord() {
044: field_2_unknown = new short[0];
045: remainingBytes = new byte[0];
046: field_1_stream_id_offset = 6;
047: field_5_ole_classname = "";
048: }
049:
050: /**
051: * Constructs an EmbeddedObjectRef record and sets its fields appropriately.
052: *
053: * @param in the record input stream.
054: */
055: public EmbeddedObjectRefSubRecord(RecordInputStream in) {
056: super (in);
057: }
058:
059: /**
060: * Checks the sid matches the expected side for this record
061: *
062: * @param id the expected sid.
063: */
064: protected void validateSid(short id) {
065: if (id != sid) {
066: throw new RecordFormatException(
067: "Not a EmbeddedObjectRef record");
068: }
069: }
070:
071: public short getSid() {
072: return sid;
073: }
074:
075: protected void fillFields(RecordInputStream in) {
076: field_1_stream_id_offset = in.readShort();
077: field_2_unknown = in.readShortArray();
078: field_3_unicode_len = in.readShort();
079: field_4_unicode_flag = (in.readByte() & 0x01) != 0;
080:
081: if (field_4_unicode_flag) {
082: field_5_ole_classname = in
083: .readUnicodeLEString(field_3_unicode_len);
084: } else {
085: field_5_ole_classname = in
086: .readCompressedUnicode(field_3_unicode_len);
087: }
088:
089: // Padded with NUL bytes. The -2 is because field_1_stream_id_offset
090: // is relative to after the offset field, whereas in.getRecordOffset()
091: // is relative to the start of this record (minus the header.)
092: field_5_ole_classname_padding = 0;
093: while (in.getRecordOffset() - 2 < field_1_stream_id_offset) {
094: field_5_ole_classname_padding++;
095: in.readByte(); // discard
096: }
097:
098: field_6_stream_id = in.readInt();
099: remainingBytes = in.readRemainder();
100: }
101:
102: public int serialize(int offset, byte[] data) {
103: int pos = offset;
104:
105: LittleEndian.putShort(data, pos, sid);
106: pos += 2;
107: LittleEndian.putShort(data, pos, (short) (getRecordSize() - 4));
108: pos += 2;
109:
110: LittleEndian.putShort(data, pos, field_1_stream_id_offset);
111: pos += 2;
112: LittleEndian.putShortArray(data, pos, field_2_unknown);
113: pos += field_2_unknown.length * 2 + 2;
114: LittleEndian.putShort(data, pos, field_3_unicode_len);
115: pos += 2;
116: data[pos] = field_4_unicode_flag ? (byte) 0x01 : (byte) 0x00;
117: pos++;
118:
119: if (field_4_unicode_flag) {
120: StringUtil.putUnicodeLE(field_5_ole_classname, data, pos);
121: pos += field_5_ole_classname.length() * 2;
122: } else {
123: StringUtil.putCompressedUnicode(field_5_ole_classname,
124: data, pos);
125: pos += field_5_ole_classname.length();
126: }
127:
128: // Padded with the same number of NUL bytes as were originally skipped.
129: // XXX: This is only accurate until we make the classname mutable.
130: pos += field_5_ole_classname_padding;
131:
132: LittleEndian.putInt(data, pos, field_6_stream_id);
133: pos += 4;
134:
135: System.arraycopy(remainingBytes, 0, data, pos,
136: remainingBytes.length);
137:
138: return getRecordSize();
139: }
140:
141: /**
142: * Size of record (exluding 4 byte header)
143: */
144: public int getRecordSize() {
145: // The stream id offset is relative to after the stream ID.
146: // Add 2 bytes for the stream id offset and 4 bytes for the stream id itself and 4 byts for the record header.
147: return remainingBytes.length + field_1_stream_id_offset + 2 + 4
148: + 4;
149: }
150:
151: /**
152: * Gets the stream ID containing the actual data. The data itself
153: * can be found under a top-level directory entry in the OLE2 filesystem
154: * under the name "MBD<var>xxxxxxxx</var>" where <var>xxxxxxxx</var> is
155: * this ID converted into hex (in big endian order, funnily enough.)
156: *
157: * @return the data stream ID.
158: */
159: public int getStreamId() {
160: return field_6_stream_id;
161: }
162:
163: public String toString() {
164: StringBuffer buffer = new StringBuffer();
165: buffer.append("[ftPictFmla]\n");
166: buffer.append(" .streamIdOffset = ").append("0x")
167: .append(HexDump.toHex(field_1_stream_id_offset))
168: .append(" (").append(field_1_stream_id_offset).append(
169: " )").append(
170: System.getProperty("line.separator"));
171: buffer.append(" .unknown = ").append("0x")
172: .append(HexDump.toHex(field_2_unknown)).append(" (")
173: .append(field_2_unknown.length).append(" )").append(
174: System.getProperty("line.separator"));
175: buffer.append(" .unicodeLen = ").append("0x")
176: .append(HexDump.toHex(field_3_unicode_len))
177: .append(" (").append(field_3_unicode_len).append(" )")
178: .append(System.getProperty("line.separator"));
179: buffer.append(" .unicodeFlag = ").append("0x")
180: .append(field_4_unicode_flag ? 0x01 : 0x00)
181: .append(" (").append(field_4_unicode_flag).append(" )")
182: .append(System.getProperty("line.separator"));
183: buffer.append(" .oleClassname = ").append(
184: field_5_ole_classname).append(
185: System.getProperty("line.separator"));
186: buffer.append(" .streamId = ").append("0x")
187: .append(HexDump.toHex(field_6_stream_id)).append(" (")
188: .append(field_6_stream_id).append(" )").append(
189: System.getProperty("line.separator"));
190: buffer.append("[/ftPictFmla]");
191: return buffer.toString();
192: }
193:
194: }
|