From 91c8480933a42e15df91d3c2fecf7fee3deaa6c7 Mon Sep 17 00:00:00 2001 From: Josh Micich Date: Fri, 10 Oct 2008 00:40:58 +0000 Subject: [PATCH] Fix for bug 45964 - support for link formulas in Text Objects git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@703302 13f79535-47bb-0310-9956-ffa450edef68 --- src/documentation/content/xdocs/changes.xml | 1 + src/documentation/content/xdocs/status.xml | 1 + .../apache/poi/hssf/model/TextboxShape.java | 14 +- .../poi/hssf/record/ContinueRecord.java | 83 +-- .../apache/poi/hssf/record/RecordFactory.java | 87 +-- .../poi/hssf/record/TextObjectBaseRecord.java | 427 ----------- .../poi/hssf/record/TextObjectRecord.java | 664 +++++++++++------- .../poi/hssf/usermodel/HSSFComment.java | 5 +- .../hssf/usermodel/HSSFRichTextString.java | 2 +- .../hssf/record/TestTextObjectBaseRecord.java | 92 +-- .../poi/hssf/record/TestTextObjectRecord.java | 129 ++-- 11 files changed, 643 insertions(+), 862 deletions(-) delete mode 100644 src/java/org/apache/poi/hssf/record/TextObjectBaseRecord.java diff --git a/src/documentation/content/xdocs/changes.xml b/src/documentation/content/xdocs/changes.xml index 19943dc4af..2ba7f0c978 100644 --- a/src/documentation/content/xdocs/changes.xml +++ b/src/documentation/content/xdocs/changes.xml @@ -37,6 +37,7 @@ + 45964 - support for link formulas in Text Objects 43354 - support for evalating formulas with missing args 45912 - fixed ArrayIndexOutOfBoundsException in EmbeddedObjectRefSubRecord 45889 - fixed ArrayIndexOutOfBoundsException when constructing HSLF Table with a single row diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml index 94d1e2caee..8f661cdb4e 100644 --- a/src/documentation/content/xdocs/status.xml +++ b/src/documentation/content/xdocs/status.xml @@ -34,6 +34,7 @@ + 45964 - support for link formulas in Text Objects 43354 - support for evalating formulas with missing args 45912 - fixed ArrayIndexOutOfBoundsException in EmbeddedObjectRefSubRecord 45889 - fixed ArrayIndexOutOfBoundsException when constructing HSLF Table with a single row diff --git a/src/java/org/apache/poi/hssf/model/TextboxShape.java b/src/java/org/apache/poi/hssf/model/TextboxShape.java index b1d3370fbf..e74197711b 100644 --- a/src/java/org/apache/poi/hssf/model/TextboxShape.java +++ b/src/java/org/apache/poi/hssf/model/TextboxShape.java @@ -138,15 +138,11 @@ public class TextboxShape HSSFTextbox shape = hssfShape; TextObjectRecord obj = new TextObjectRecord(); - obj.setHorizontalTextAlignment( hssfShape.getHorizontalAlignment() ); - obj.setVerticalTextAlignment( hssfShape.getVerticalAlignment()); - obj.setTextLocked( true ); - obj.setTextOrientation( TextObjectRecord.TEXT_ORIENTATION_NONE ); - int frLength = ( shape.getString().numFormattingRuns() + 1 ) * 8; - obj.setFormattingRunLength( (short) frLength ); - obj.setTextLength( (short) shape.getString().length() ); - obj.setStr( shape.getString() ); - obj.setReserved7( 0 ); + obj.setHorizontalTextAlignment(hssfShape.getHorizontalAlignment()); + obj.setVerticalTextAlignment(hssfShape.getVerticalAlignment()); + obj.setTextLocked(true); + obj.setTextOrientation(TextObjectRecord.TEXT_ORIENTATION_NONE); + obj.setStr(shape.getString()); return obj; } diff --git a/src/java/org/apache/poi/hssf/record/ContinueRecord.java b/src/java/org/apache/poi/hssf/record/ContinueRecord.java index 435c454712..e20c4633c3 100644 --- a/src/java/org/apache/poi/hssf/record/ContinueRecord.java +++ b/src/java/org/apache/poi/hssf/record/ContinueRecord.java @@ -22,76 +22,44 @@ package org.apache.poi.hssf.record; import org.apache.poi.util.LittleEndian; /** - * Title: Continue Record - Helper class used primarily for SST Records

+ * Title: Continue Record(0x003C) - Helper class used primarily for SST Records

* Description: handles overflow for prior record in the input * stream; content is tailored to that prior record

* @author Marc Johnson (mjohnson at apache dot org) * @author Andrew C. Oliver (acoliver at apache dot org) * @author Csaba Nagy (ncsaba at yahoo dot com) - * @version 2.0-pre */ - -public class ContinueRecord - extends Record -{ +public final class ContinueRecord extends Record { public final static short sid = 0x003C; - private byte[] field_1_data; - - /** - * default constructor - */ + private byte[] _data; - public ContinueRecord() - { + public ContinueRecord(byte[] data) { + _data = data; } /** * USE ONLY within "processContinue" */ - public byte [] serialize() { - byte[] retval = new byte[ field_1_data.length + 4 ]; + byte[] retval = new byte[ _data.length + 4 ]; serialize(0, retval); return retval; } - public int serialize(int offset, byte [] data) - { - - LittleEndian.putShort(data, offset, sid); - LittleEndian.putShort(data, offset + 2, ( short ) field_1_data.length); - System.arraycopy(field_1_data, 0, data, offset + 4, field_1_data.length); - return field_1_data.length + 4; - // throw new RecordFormatException( - // "You're not supposed to serialize Continue records like this directly"); - } - - /* - * @param data raw data - */ - - public void setData(byte [] data) - { - field_1_data = data; + public int serialize(int offset, byte[] data) { + return write(data, offset, null, _data); } /** * get the data for continuation * @return byte array containing all of the continued data */ - public byte [] getData() { - return field_1_data; + return _data; } - /** - * Debugging toString - * - * @return string representation - */ - public String toString() { StringBuffer buffer = new StringBuffer(); @@ -113,19 +81,36 @@ public class ContinueRecord * * @param in the RecordInputstream to read the record from */ - public ContinueRecord(RecordInputStream in) { - field_1_data = in.readRemainder(); + _data = in.readRemainder(); } - /** - * Clone this record. - */ public Object clone() { - ContinueRecord clone = new ContinueRecord(); - clone.setData(field_1_data); - return clone; + return new ContinueRecord(_data); } + /** + * Writes the full encoding of a Continue record without making an instance + */ + public static int write(byte[] destBuf, int destOffset, Byte initialDataByte, byte[] srcData) { + return write(destBuf, destOffset, initialDataByte, srcData, 0, srcData.length); + } + /** + * @param initialDataByte (optional - often used for unicode flag). + * If supplied, this will be written before srcData + * @return the total number of bytes written + */ + public static int write(byte[] destBuf, int destOffset, Byte initialDataByte, byte[] srcData, int srcOffset, int len) { + int totalLen = len + (initialDataByte == null ? 0 : 1); + LittleEndian.putUShort(destBuf, destOffset, sid); + LittleEndian.putUShort(destBuf, destOffset + 2, totalLen); + int pos = destOffset + 4; + if (initialDataByte != null) { + LittleEndian.putByte(destBuf, pos, initialDataByte.byteValue()); + pos += 1; + } + System.arraycopy(srcData, srcOffset, destBuf, pos, len); + return 4 + totalLen; + } } diff --git a/src/java/org/apache/poi/hssf/record/RecordFactory.java b/src/java/org/apache/poi/hssf/record/RecordFactory.java index 864065de77..7539e597da 100644 --- a/src/java/org/apache/poi/hssf/record/RecordFactory.java +++ b/src/java/org/apache/poi/hssf/record/RecordFactory.java @@ -321,51 +321,54 @@ public final class RecordFactory { Record lastRecord = null; while (recStream.hasNextRecord()) { recStream.nextRecord(); - if (recStream.getSid() != 0) { - Record[] recs = createRecord(recStream); // handle MulRK records + if (recStream.getSid() == 0) { + // After EOF, Excel seems to pad block with zeros + continue; + } + Record[] recs = createRecord(recStream); // handle MulRK records - if (recs.length > 1) { - for (int k = 0; k < recs.length; k++) { - records.add(recs[ k ]); // these will be number records - } - } else { - Record record = recs[ 0 ]; + if (recs.length > 1) { + for (int k = 0; k < recs.length; k++) { + records.add(recs[ k ]); // these will be number records + } + continue; + } + Record record = recs[ 0 ]; - if (record != null) { - if (record.getSid() == DrawingGroupRecord.sid - && lastRecord instanceof DrawingGroupRecord) { - DrawingGroupRecord lastDGRecord = (DrawingGroupRecord) lastRecord; - lastDGRecord.join((AbstractEscherHolderRecord) record); - } else if (record.getSid() == ContinueRecord.sid && - ((lastRecord instanceof ObjRecord) || (lastRecord instanceof TextObjectRecord))) { - // Drawing records have a very strange continue behaviour. - //There can actually be OBJ records mixed between the continues. - lastDrawingRecord.processContinueRecord( ((ContinueRecord)record).getData() ); - //we must remember the position of the continue record. - //in the serialization procedure the original structure of records must be preserved - records.add(record); - } else if (record.getSid() == ContinueRecord.sid && - (lastRecord instanceof DrawingGroupRecord)) { - ((DrawingGroupRecord)lastRecord).processContinueRecord(((ContinueRecord)record).getData()); - } else if (record.getSid() == ContinueRecord.sid && - (lastRecord instanceof StringRecord)) { - ((StringRecord)lastRecord).processContinueRecord(((ContinueRecord)record).getData()); - } else if (record.getSid() == ContinueRecord.sid) { - if (lastRecord instanceof UnknownRecord) { - //Gracefully handle records that we dont know about, - //that happen to be continued - records.add(record); - } else - throw new RecordFormatException("Unhandled Continue Record"); - } else { - lastRecord = record; - if (record instanceof DrawingRecord) { - lastDrawingRecord = (DrawingRecord) record; - } - records.add(record); - } - } + if (record == null) { + continue; + } + if (record.getSid() == DrawingGroupRecord.sid + && lastRecord instanceof DrawingGroupRecord) { + DrawingGroupRecord lastDGRecord = (DrawingGroupRecord) lastRecord; + lastDGRecord.join((AbstractEscherHolderRecord) record); + } else if (record.getSid() == ContinueRecord.sid) { + ContinueRecord contRec = (ContinueRecord)record; + + if (lastRecord instanceof ObjRecord || lastRecord instanceof TextObjectRecord) { + // Drawing records have a very strange continue behaviour. + //There can actually be OBJ records mixed between the continues. + lastDrawingRecord.processContinueRecord(contRec.getData() ); + //we must remember the position of the continue record. + //in the serialization procedure the original structure of records must be preserved + records.add(record); + } else if (lastRecord instanceof DrawingGroupRecord) { + ((DrawingGroupRecord)lastRecord).processContinueRecord(contRec.getData()); + } else if (lastRecord instanceof StringRecord) { + ((StringRecord)lastRecord).processContinueRecord(contRec.getData()); + } else if (lastRecord instanceof UnknownRecord) { + //Gracefully handle records that we don't know about, + //that happen to be continued + records.add(record); + } else { + throw new RecordFormatException("Unhandled Continue Record"); + } + } else { + lastRecord = record; + if (record instanceof DrawingRecord) { + lastDrawingRecord = (DrawingRecord) record; } + records.add(record); } } return records; diff --git a/src/java/org/apache/poi/hssf/record/TextObjectBaseRecord.java b/src/java/org/apache/poi/hssf/record/TextObjectBaseRecord.java deleted file mode 100644 index 59e0cd2e66..0000000000 --- a/src/java/org/apache/poi/hssf/record/TextObjectBaseRecord.java +++ /dev/null @@ -1,427 +0,0 @@ - -/* ==================================================================== - 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; - -import org.apache.poi.util.BitField; -import org.apache.poi.util.BitFieldFactory; -import org.apache.poi.util.HexDump; -import org.apache.poi.util.LittleEndian; - -/** - * The TXO record is used to define the properties of a text box. It is followed - * by two continue records unless there is no actual text. The first continue record contains - * the text data and the next continue record contains the formatting runs.

- * - * @author Glen Stampoultzis (glens at apache.org) - */ -public class TextObjectBaseRecord extends Record { - // TODO - don't instantiate superclass - public final static short sid = 0x01B6; - - private static final BitField reserved1 = BitFieldFactory.getInstance(0x0001); - private static final BitField HorizontalTextAlignment = BitFieldFactory.getInstance(0x000E); - private static final BitField VerticalTextAlignment = BitFieldFactory.getInstance(0x0070); - private static final BitField reserved2 = BitFieldFactory.getInstance(0x0180); - private static final BitField textLocked = BitFieldFactory.getInstance(0x0200); - private static final BitField reserved3 = BitFieldFactory.getInstance(0xFC00); - - private short field_1_options; - public final static short HORIZONTAL_TEXT_ALIGNMENT_LEFT_ALIGNED = 1; - public final static short HORIZONTAL_TEXT_ALIGNMENT_CENTERED = 2; - public final static short HORIZONTAL_TEXT_ALIGNMENT_RIGHT_ALIGNED = 3; - public final static short HORIZONTAL_TEXT_ALIGNMENT_JUSTIFIED = 4; - public final static short VERTICAL_TEXT_ALIGNMENT_TOP = 1; - public final static short VERTICAL_TEXT_ALIGNMENT_CENTER = 2; - public final static short VERTICAL_TEXT_ALIGNMENT_BOTTOM = 3; - public final static short VERTICAL_TEXT_ALIGNMENT_JUSTIFY = 4; - private short field_2_textOrientation; - public final static short TEXT_ORIENTATION_NONE = 0; - public final static short TEXT_ORIENTATION_TOP_TO_BOTTOM = 1; - public final static short TEXT_ORIENTATION_ROT_RIGHT = 2; - public final static short TEXT_ORIENTATION_ROT_LEFT = 3; - private short field_3_reserved4; - private short field_4_reserved5; - private short field_5_reserved6; - private short field_6_textLength; - private short field_7_formattingRunLength; - private int field_8_reserved7; - - - public TextObjectBaseRecord() - { - - } - - public TextObjectBaseRecord(RecordInputStream in) - { - field_1_options = in.readShort(); - field_2_textOrientation = in.readShort(); - field_3_reserved4 = in.readShort(); - field_4_reserved5 = in.readShort(); - field_5_reserved6 = in.readShort(); - field_6_textLength = in.readShort(); - field_7_formattingRunLength = in.readShort(); - field_8_reserved7 = in.readInt(); - - } - - public String toString() - { - StringBuffer buffer = new StringBuffer(); - - buffer.append("[TXO]\n"); - buffer.append(" .options = ") - .append("0x").append(HexDump.toHex( getOptions ())) - .append(" (").append( getOptions() ).append(" )"); - buffer.append(System.getProperty("line.separator")); - buffer.append(" .reserved1 = ").append(isReserved1()).append('\n'); - buffer.append(" .HorizontalTextAlignment = ").append(getHorizontalTextAlignment()).append('\n'); - buffer.append(" .VerticalTextAlignment = ").append(getVerticalTextAlignment()).append('\n'); - buffer.append(" .reserved2 = ").append(getReserved2()).append('\n'); - buffer.append(" .textLocked = ").append(isTextLocked()).append('\n'); - buffer.append(" .reserved3 = ").append(getReserved3()).append('\n'); - buffer.append(" .textOrientation = ") - .append("0x").append(HexDump.toHex( getTextOrientation ())) - .append(" (").append( getTextOrientation() ).append(" )"); - buffer.append(System.getProperty("line.separator")); - buffer.append(" .reserved4 = ") - .append("0x").append(HexDump.toHex( getReserved4 ())) - .append(" (").append( getReserved4() ).append(" )"); - buffer.append(System.getProperty("line.separator")); - buffer.append(" .reserved5 = ") - .append("0x").append(HexDump.toHex( getReserved5 ())) - .append(" (").append( getReserved5() ).append(" )"); - buffer.append(System.getProperty("line.separator")); - buffer.append(" .reserved6 = ") - .append("0x").append(HexDump.toHex( getReserved6 ())) - .append(" (").append( getReserved6() ).append(" )"); - buffer.append(System.getProperty("line.separator")); - buffer.append(" .textLength = ") - .append("0x").append(HexDump.toHex( getTextLength ())) - .append(" (").append( getTextLength() ).append(" )"); - buffer.append(System.getProperty("line.separator")); - buffer.append(" .formattingRunLength = ") - .append("0x").append(HexDump.toHex( getFormattingRunLength ())) - .append(" (").append( getFormattingRunLength() ).append(" )"); - buffer.append(System.getProperty("line.separator")); - buffer.append(" .reserved7 = ") - .append("0x").append(HexDump.toHex( getReserved7 ())) - .append(" (").append( getReserved7() ).append(" )"); - buffer.append(System.getProperty("line.separator")); - - buffer.append("[/TXO]\n"); - return buffer.toString(); - } - - public int serialize(int offset, byte[] data) - { - int pos = 0; - - LittleEndian.putShort(data, 0 + offset, sid); - LittleEndian.putShort(data, 2 + offset, (short)(getRecordSize() - 4)); - - LittleEndian.putShort(data, 4 + offset + pos, field_1_options); - LittleEndian.putShort(data, 6 + offset + pos, field_2_textOrientation); - LittleEndian.putShort(data, 8 + offset + pos, field_3_reserved4); - LittleEndian.putShort(data, 10 + offset + pos, field_4_reserved5); - LittleEndian.putShort(data, 12 + offset + pos, field_5_reserved6); - LittleEndian.putShort(data, 14 + offset + pos, field_6_textLength); - LittleEndian.putShort(data, 16 + offset + pos, field_7_formattingRunLength); - LittleEndian.putInt(data, 18 + offset + pos, field_8_reserved7); - - return getRecordSize(); - } - - public int getRecordSize() - { - return 4 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 4; - } - - public short getSid() - { - return sid; - } - - public Object clone() { - TextObjectBaseRecord rec = new TextObjectBaseRecord(); - - rec.field_1_options = field_1_options; - rec.field_2_textOrientation = field_2_textOrientation; - rec.field_3_reserved4 = field_3_reserved4; - rec.field_4_reserved5 = field_4_reserved5; - rec.field_5_reserved6 = field_5_reserved6; - rec.field_6_textLength = field_6_textLength; - rec.field_7_formattingRunLength = field_7_formattingRunLength; - rec.field_8_reserved7 = field_8_reserved7; - return rec; - } - - - - - /** - * Get the options field for the TextObjectBase record. - */ - public short getOptions() - { - return field_1_options; - } - - /** - * Set the options field for the TextObjectBase record. - */ - public void setOptions(short field_1_options) - { - this.field_1_options = field_1_options; - } - - /** - * Get the text orientation field for the TextObjectBase record. - * - * @return One of - * TEXT_ORIENTATION_NONE - * TEXT_ORIENTATION_TOP_TO_BOTTOM - * TEXT_ORIENTATION_ROT_RIGHT - * TEXT_ORIENTATION_ROT_LEFT - */ - public short getTextOrientation() - { - return field_2_textOrientation; - } - - /** - * Set the text orientation field for the TextObjectBase record. - * - * @param field_2_textOrientation - * One of - * TEXT_ORIENTATION_NONE - * TEXT_ORIENTATION_TOP_TO_BOTTOM - * TEXT_ORIENTATION_ROT_RIGHT - * TEXT_ORIENTATION_ROT_LEFT - */ - public void setTextOrientation(short field_2_textOrientation) - { - this.field_2_textOrientation = field_2_textOrientation; - } - - /** - * Get the reserved4 field for the TextObjectBase record. - */ - public short getReserved4() - { - return field_3_reserved4; - } - - /** - * Set the reserved4 field for the TextObjectBase record. - */ - public void setReserved4(short field_3_reserved4) - { - this.field_3_reserved4 = field_3_reserved4; - } - - /** - * Get the reserved5 field for the TextObjectBase record. - */ - public short getReserved5() - { - return field_4_reserved5; - } - - /** - * Set the reserved5 field for the TextObjectBase record. - */ - public void setReserved5(short field_4_reserved5) - { - this.field_4_reserved5 = field_4_reserved5; - } - - /** - * Get the reserved6 field for the TextObjectBase record. - */ - public short getReserved6() - { - return field_5_reserved6; - } - - /** - * Set the reserved6 field for the TextObjectBase record. - */ - public void setReserved6(short field_5_reserved6) - { - this.field_5_reserved6 = field_5_reserved6; - } - - /** - * Get the text length field for the TextObjectBase record. - */ - public short getTextLength() - { - return field_6_textLength; - } - - /** - * Set the text length field for the TextObjectBase record. - */ - public void setTextLength(short field_6_textLength) - { - this.field_6_textLength = field_6_textLength; - } - - /** - * Get the formatting run length field for the TextObjectBase record. - */ - public short getFormattingRunLength() - { - return field_7_formattingRunLength; - } - - /** - * Set the formatting run length field for the TextObjectBase record. - */ - public void setFormattingRunLength(short field_7_formattingRunLength) - { - this.field_7_formattingRunLength = field_7_formattingRunLength; - } - - /** - * Get the reserved7 field for the TextObjectBase record. - */ - public int getReserved7() - { - return field_8_reserved7; - } - - /** - * Set the reserved7 field for the TextObjectBase record. - */ - public void setReserved7(int field_8_reserved7) - { - this.field_8_reserved7 = field_8_reserved7; - } - - /** - * Sets the reserved1 field value. - * reserved field - */ - public void setReserved1(boolean value) - { - field_1_options = reserved1.setShortBoolean(field_1_options, value); - } - - /** - * reserved field - * @return the reserved1 field value. - */ - public boolean isReserved1() - { - return reserved1.isSet(field_1_options); - } - - /** - * Sets the Horizontal text alignment field value. - * - */ - public void setHorizontalTextAlignment(short value) - { - field_1_options = HorizontalTextAlignment.setShortValue(field_1_options, value); - } - - /** - * - * @return the Horizontal text alignment field value. - */ - public short getHorizontalTextAlignment() - { - return HorizontalTextAlignment.getShortValue(field_1_options); - } - - /** - * Sets the Vertical text alignment field value. - * - */ - public void setVerticalTextAlignment(short value) - { - field_1_options = VerticalTextAlignment.setShortValue(field_1_options, value); - } - - /** - * - * @return the Vertical text alignment field value. - */ - public short getVerticalTextAlignment() - { - return VerticalTextAlignment.getShortValue(field_1_options); - } - - /** - * Sets the reserved2 field value. - * - */ - public void setReserved2(short value) - { - field_1_options = reserved2.setShortValue(field_1_options, value); - } - - /** - * - * @return the reserved2 field value. - */ - public short getReserved2() - { - return reserved2.getShortValue(field_1_options); - } - - /** - * Sets the text locked field value. - * Text has been locked - */ - public void setTextLocked(boolean value) - { - field_1_options = textLocked.setShortBoolean(field_1_options, value); - } - - /** - * Text has been locked - * @return the text locked field value. - */ - public boolean isTextLocked() - { - return textLocked.isSet(field_1_options); - } - - /** - * Sets the reserved3 field value. - * - */ - public void setReserved3(short value) - { - field_1_options = reserved3.setShortValue(field_1_options, value); - } - - /** - * - * @return the reserved3 field value. - */ - public short getReserved3() - { - return reserved3.getShortValue(field_1_options); - } -} diff --git a/src/java/org/apache/poi/hssf/record/TextObjectRecord.java b/src/java/org/apache/poi/hssf/record/TextObjectRecord.java index 600aaf1542..6529dc93f8 100644 --- a/src/java/org/apache/poi/hssf/record/TextObjectRecord.java +++ b/src/java/org/apache/poi/hssf/record/TextObjectRecord.java @@ -19,253 +19,427 @@ package org.apache.poi.hssf.record; import java.io.UnsupportedEncodingException; +import org.apache.poi.hssf.record.formula.Ptg; import org.apache.poi.hssf.usermodel.HSSFRichTextString; +import org.apache.poi.util.BitField; +import org.apache.poi.util.BitFieldFactory; import org.apache.poi.util.HexDump; import org.apache.poi.util.LittleEndian; -public class TextObjectRecord - extends TextObjectBaseRecord -{ - HSSFRichTextString str; - - public TextObjectRecord() - { - } - - public TextObjectRecord( RecordInputStream in ) - { - super( in ); - - if (getTextLength() > 0) { - if (in.isContinueNext() && in.remaining() == 0) { - //1st Continue - in.nextRecord(); - processRawString(in); - } else - throw new RecordFormatException("Expected Continue record to hold string data for TextObjectRecord"); - } - if (getFormattingRunLength() > 0) { - if (in.isContinueNext() && in.remaining() == 0) { - in.nextRecord(); - processFontRuns(in); - } else throw new RecordFormatException("Expected Continue Record to hold font runs for TextObjectRecord"); - } - if (str == null) - str = new HSSFRichTextString(""); - } - - - public int getRecordSize() - { - int continue1Size = 0; - int continue2Size = 0; - if (str.length() != 0) - { - int length = str.length() * 2; - while(length > 0){ - int chunkSize = Math.min(RecordInputStream.MAX_RECORD_DATA_SIZE-2, length); - length -= chunkSize; - - continue1Size += chunkSize; - continue1Size += 1 + 4; - } - - continue2Size = (str.numFormattingRuns() + 1) * 8 + 4; - } - return super.getRecordSize() + continue1Size + continue2Size; - } - - - - public int serialize( int offset, byte[] data ) - { - // Temporarily blank out str so that record size is calculated without the continue records. - HSSFRichTextString temp = str; - str = new HSSFRichTextString(""); - int bytesWritten1 = super.serialize( offset, data ); - str = temp; - - int pos = offset + bytesWritten1; - if ( str.getString().equals( "" ) == false ) - { - ContinueRecord c2 = createContinue2(); - int bytesWritten2 = 0; - - try - { - byte[] c1Data = str.getString().getBytes( "UTF-16LE" ); - int length = c1Data.length; - - int charsWritten = 0; - int spos = pos; - while(length > 0){ - int chunkSize = Math.min(RecordInputStream.MAX_RECORD_DATA_SIZE-2 , length); - length -= chunkSize; - - //continue header - LittleEndian.putShort(data, spos, ContinueRecord.sid); - spos += LittleEndian.SHORT_SIZE; - LittleEndian.putShort(data, spos, (short)(chunkSize+1)); - spos += LittleEndian.SHORT_SIZE; - - //The first byte specifies if the text is compressed unicode or unicode. - //(regardless what was read, we always serialize double-byte unicode characters (UTF-16LE). - data[spos] = 1; - spos += LittleEndian.BYTE_SIZE; - - //copy characters data - System.arraycopy(c1Data, charsWritten, data, spos, chunkSize); - spos += chunkSize; - charsWritten += chunkSize; - } - - bytesWritten2 = (spos-pos); - } - catch ( UnsupportedEncodingException e ) - { - throw new RuntimeException( e.getMessage(), e ); - } - - pos += bytesWritten2; - int bytesWritten3 = c2.serialize( pos, data ); - pos += bytesWritten3; - - int size = bytesWritten1 + bytesWritten2 + bytesWritten3; - if ( size != getRecordSize() ) - throw new RecordFormatException(size + " bytes written but getRecordSize() reports " + getRecordSize()); - return size; - } - if ( bytesWritten1 != getRecordSize() ) - throw new RecordFormatException(bytesWritten1 + " bytes written but getRecordSize() reports " + getRecordSize()); - return bytesWritten1; - } - - private ContinueRecord createContinue2() - { - ContinueRecord c2 = new ContinueRecord(); - byte[] c2Data = new byte[str.numFormattingRuns() * 8 + 8]; - int pos = 0; - for ( int i = 0; i < str.numFormattingRuns(); i++ ) - { - LittleEndian.putShort( c2Data, pos, (short) str.getIndexOfFormattingRun( i ) ); - pos += 2; - LittleEndian.putShort( c2Data, pos, str.getFontOfFormattingRun( i ) == str.NO_FONT ? 0 : str.getFontOfFormattingRun( i ) ); - pos += 2; - pos += 4; // skip reserved - } - LittleEndian.putShort( c2Data, pos, (short) str.length() ); - pos += 2; - LittleEndian.putShort( c2Data, pos, (short) 0 ); - pos += 2; - pos += 4; // skip reserved - - c2.setData( c2Data ); - - return c2; - } - - private void processFontRuns( RecordInputStream in ) - { - while (in.remaining() > 0) - { - short index = in.readShort(); - short iFont = in.readShort(); - in.readInt(); // skip reserved. - - str.applyFont( index, str.length(), iFont ); - } - } - - private void processRawString( RecordInputStream in ) - { - String s; - byte compressByte = in.readByte(); - boolean isCompressed = compressByte == 0; - if ( isCompressed ) - { - s = in.readCompressedUnicode(getTextLength()); - } - else - { - s = in.readUnicodeLEString(getTextLength()); - } - str = new HSSFRichTextString( s ); - } - - public HSSFRichTextString getStr() - { - return str; - } - - public void setStr( HSSFRichTextString str ) - { - this.str = str; - } - - public String toString() - { - StringBuffer buffer = new StringBuffer(); - - buffer.append( "[TXO]\n" ); - buffer.append( " .options = " ) - .append( "0x" ).append( HexDump.toHex( getOptions() ) ) - .append( " (" ).append( getOptions() ).append( " )" ); - buffer.append( System.getProperty( "line.separator" ) ); - buffer.append( " .reserved1 = " ).append( isReserved1() ).append( '\n' ); - buffer.append( " .HorizontalTextAlignment = " ).append( getHorizontalTextAlignment() ).append( '\n' ); - buffer.append( " .VerticalTextAlignment = " ).append( getVerticalTextAlignment() ).append( '\n' ); - buffer.append( " .reserved2 = " ).append( getReserved2() ).append( '\n' ); - buffer.append( " .textLocked = " ).append( isTextLocked() ).append( '\n' ); - buffer.append( " .reserved3 = " ).append( getReserved3() ).append( '\n' ); - buffer.append( " .textOrientation = " ) - .append( "0x" ).append( HexDump.toHex( getTextOrientation() ) ) - .append( " (" ).append( getTextOrientation() ).append( " )" ); - buffer.append( System.getProperty( "line.separator" ) ); - buffer.append( " .reserved4 = " ) - .append( "0x" ).append( HexDump.toHex( getReserved4() ) ) - .append( " (" ).append( getReserved4() ).append( " )" ); - buffer.append( System.getProperty( "line.separator" ) ); - buffer.append( " .reserved5 = " ) - .append( "0x" ).append( HexDump.toHex( getReserved5() ) ) - .append( " (" ).append( getReserved5() ).append( " )" ); - buffer.append( System.getProperty( "line.separator" ) ); - buffer.append( " .reserved6 = " ) - .append( "0x" ).append( HexDump.toHex( getReserved6() ) ) - .append( " (" ).append( getReserved6() ).append( " )" ); - buffer.append( System.getProperty( "line.separator" ) ); - buffer.append( " .textLength = " ) - .append( "0x" ).append( HexDump.toHex( getTextLength() ) ) - .append( " (" ).append( getTextLength() ).append( " )" ); - buffer.append( System.getProperty( "line.separator" ) ); - buffer.append( " .reserved7 = " ) - .append( "0x" ).append( HexDump.toHex( getReserved7() ) ) - .append( " (" ).append( getReserved7() ).append( " )" ); - buffer.append( System.getProperty( "line.separator" ) ); - - buffer.append( " .string = " ).append(str).append('\n'); - - for (int i = 0; i < str.numFormattingRuns(); i++) { - buffer.append( " .textrun = " ).append(str.getFontOfFormattingRun(i)).append('\n'); - - } - buffer.append( "[/TXO]\n" ); - return buffer.toString(); - } - - public Object clone() { - - TextObjectRecord rec = new TextObjectRecord(); - rec.str = str; - - rec.setOptions(getOptions()); - rec.setTextOrientation(getTextOrientation()); - rec.setReserved4(getReserved4()); - rec.setReserved5(getReserved5()); - rec.setReserved6(getReserved6()); - rec.setTextLength(getTextLength()); - rec.setFormattingRunLength(getFormattingRunLength()); - rec.setReserved7(getReserved7()); - return rec; - } - +/** + * The TXO record (0x01B6) is used to define the properties of a text box. It is + * followed by two or more continue records unless there is no actual text. The + * first continue records contain the text data and the last continue record + * contains the formatting runs.

+ * + * @author Glen Stampoultzis (glens at apache.org) + */ +public final class TextObjectRecord extends Record { + public final static short sid = 0x01B6; + + private static final int FORMAT_RUN_ENCODED_SIZE = 8; // 2 shorts and 4 bytes reserved + + private static final BitField HorizontalTextAlignment = BitFieldFactory.getInstance(0x000E); + private static final BitField VerticalTextAlignment = BitFieldFactory.getInstance(0x0070); + private static final BitField textLocked = BitFieldFactory.getInstance(0x0200); + + public final static short HORIZONTAL_TEXT_ALIGNMENT_LEFT_ALIGNED = 1; + public final static short HORIZONTAL_TEXT_ALIGNMENT_CENTERED = 2; + public final static short HORIZONTAL_TEXT_ALIGNMENT_RIGHT_ALIGNED = 3; + public final static short HORIZONTAL_TEXT_ALIGNMENT_JUSTIFIED = 4; + public final static short VERTICAL_TEXT_ALIGNMENT_TOP = 1; + public final static short VERTICAL_TEXT_ALIGNMENT_CENTER = 2; + public final static short VERTICAL_TEXT_ALIGNMENT_BOTTOM = 3; + public final static short VERTICAL_TEXT_ALIGNMENT_JUSTIFY = 4; + + public final static short TEXT_ORIENTATION_NONE = 0; + public final static short TEXT_ORIENTATION_TOP_TO_BOTTOM = 1; + public final static short TEXT_ORIENTATION_ROT_RIGHT = 2; + public final static short TEXT_ORIENTATION_ROT_LEFT = 3; + + private int field_1_options; + private int field_2_textOrientation; + private int field_3_reserved4; + private int field_4_reserved5; + private int field_5_reserved6; + private int field_8_reserved7; + + private HSSFRichTextString _text; + + /* + * Note - the next three fields are very similar to those on + * EmbededObjectRefSubRecord(ftPictFmla 0x0009) + * + * some observed values for the 4 bytes preceding the formula: C0 5E 86 03 + * C0 11 AC 02 80 F1 8A 03 D4 F0 8A 03 + */ + private int _unknownPreFormulaInt; + /** expect tRef, tRef3D, tArea, tArea3D or tName */ + private Ptg _linkRefPtg; + /** + * Not clear if needed . Excel seems to be OK if this byte is not present. + * Value is often the same as the earlier firstColumn byte. */ + private Byte _unknownPostFormulaByte; + + public TextObjectRecord() { + } + + public TextObjectRecord(RecordInputStream in) { + field_1_options = in.readUShort(); + field_2_textOrientation = in.readUShort(); + field_3_reserved4 = in.readUShort(); + field_4_reserved5 = in.readUShort(); + field_5_reserved6 = in.readUShort(); + int field_6_textLength = in.readUShort(); + int field_7_formattingDataLength = in.readUShort(); + field_8_reserved7 = in.readInt(); + + if (in.remaining() > 0) { + // Text Objects can have simple reference formulas + // (This bit not mentioned in the MS document) + if (in.remaining() < 11) { + throw new RecordFormatException("Not enough remaining data for a link formula"); + } + int formulaSize = in.readUShort(); + _unknownPreFormulaInt = in.readInt(); + Ptg[] ptgs = Ptg.readTokens(formulaSize, in); + if (ptgs.length != 1) { + throw new RecordFormatException("Read " + ptgs.length + + " tokens but expected exactly 1"); + } + _linkRefPtg = ptgs[0]; + if (in.remaining() > 0) { + _unknownPostFormulaByte = new Byte(in.readByte()); + } else { + _unknownPostFormulaByte = null; + } + } else { + _linkRefPtg = null; + } + if (in.remaining() > 0) { + throw new RecordFormatException("Unused " + in.remaining() + " bytes at end of record"); + } + + String text; + if (field_6_textLength > 0) { + text = readRawString(in, field_6_textLength); + } else { + text = ""; + } + _text = new HSSFRichTextString(text); + + if (field_7_formattingDataLength > 0) { + if (in.isContinueNext() && in.remaining() == 0) { + in.nextRecord(); + processFontRuns(in, _text, field_7_formattingDataLength); + } else { + throw new RecordFormatException( + "Expected Continue Record to hold font runs for TextObjectRecord"); + } + } + } + + private static String readRawString(RecordInputStream in, int textLength) { + byte compressByte = in.readByte(); + boolean isCompressed = (compressByte & 0x01) == 0; + if (isCompressed) { + return in.readCompressedUnicode(textLength); + } + return in.readUnicodeLEString(textLength); + } + + private static void processFontRuns(RecordInputStream in, HSSFRichTextString str, + int formattingRunDataLength) { + if (formattingRunDataLength % FORMAT_RUN_ENCODED_SIZE != 0) { + throw new RecordFormatException("Bad format run data length " + formattingRunDataLength + + ")"); + } + if (in.remaining() != formattingRunDataLength) { + throw new RecordFormatException("Expected " + formattingRunDataLength + + " bytes but got " + in.remaining()); + } + int nRuns = formattingRunDataLength / FORMAT_RUN_ENCODED_SIZE; + for (int i = 0; i < nRuns; i++) { + short index = in.readShort(); + short iFont = in.readShort(); + in.readInt(); // skip reserved. + str.applyFont(index, str.length(), iFont); + } + } + + public short getSid() { + return sid; + } + + /** + * Only for the current record. does not include any subsequent Continue + * records + */ + private int getDataSize() { + int result = 2 + 2 + 2 + 2 + 2 + 2 + 2 + 4; + if (_linkRefPtg != null) { + result += 2 // formula size + + 4 // unknownInt + +_linkRefPtg.getSize(); + if (_unknownPostFormulaByte != null) { + result += 1; + } + } + return result; + } + + private int serializeTXORecord(int offset, byte[] data) { + int dataSize = getDataSize(); + + LittleEndian.putUShort(data, 0 + offset, TextObjectRecord.sid); + LittleEndian.putUShort(data, 2 + offset, dataSize); + + + LittleEndian.putUShort(data, 4 + offset, field_1_options); + LittleEndian.putUShort(data, 6 + offset, field_2_textOrientation); + LittleEndian.putUShort(data, 8 + offset, field_3_reserved4); + LittleEndian.putUShort(data, 10 + offset, field_4_reserved5); + LittleEndian.putUShort(data, 12 + offset, field_5_reserved6); + LittleEndian.putUShort(data, 14 + offset, _text.length()); + LittleEndian.putUShort(data, 16 + offset, getFormattingDataLength()); + LittleEndian.putInt(data, 18 + offset, field_8_reserved7); + + if (_linkRefPtg != null) { + int pos = offset+22; + int formulaSize = _linkRefPtg.getSize(); + LittleEndian.putUShort(data, pos, formulaSize); + pos += LittleEndian.SHORT_SIZE; + LittleEndian.putInt(data, pos, _unknownPreFormulaInt); + pos += LittleEndian.INT_SIZE; + _linkRefPtg.writeBytes(data, pos); + pos += formulaSize; + if (_unknownPostFormulaByte != null) { + LittleEndian.putByte(data, pos, _unknownPostFormulaByte.byteValue()); + pos += LittleEndian.BYTE_SIZE; + } + } + + return 4 + dataSize; + } + + private int serializeTrailingRecords(int offset, byte[] data) { + byte[] textBytes; + try { + textBytes = _text.getString().getBytes("UTF-16LE"); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e.getMessage(), e); + } + int remainingLength = textBytes.length; + + int countTextBytesWritten = 0; + int pos = offset; + // (regardless what was read, we always serialize double-byte + // unicode characters (UTF-16LE). + Byte unicodeFlag = new Byte((byte)1); + while (remainingLength > 0) { + int chunkSize = Math.min(RecordInputStream.MAX_RECORD_DATA_SIZE - 2, remainingLength); + remainingLength -= chunkSize; + pos += ContinueRecord.write(data, pos, unicodeFlag, textBytes, countTextBytesWritten, chunkSize); + countTextBytesWritten += chunkSize; + } + + byte[] formatData = createFormatData(_text); + pos += ContinueRecord.write(data, pos, null, formatData); + return pos - offset; + } + + private int getTrailingRecordsSize() { + if (_text.length() < 1) { + return 0; + } + int encodedTextSize = 0; + int textBytesLength = _text.length() * LittleEndian.SHORT_SIZE; + while (textBytesLength > 0) { + int chunkSize = Math.min(RecordInputStream.MAX_RECORD_DATA_SIZE - 2, textBytesLength); + textBytesLength -= chunkSize; + + encodedTextSize += 4; // +4 for ContinueRecord sid+size + encodedTextSize += 1+chunkSize; // +1 for compressed unicode flag, + } + + int encodedFormatSize = (_text.numFormattingRuns() + 1) * FORMAT_RUN_ENCODED_SIZE + + 4; // +4 for ContinueRecord sid+size + return encodedTextSize + encodedFormatSize; + } + + + public int serialize(int offset, byte[] data) { + + int expectedTotalSize = getRecordSize(); + int totalSize = serializeTXORecord(offset, data); + + if (_text.getString().length() > 0) { + totalSize += serializeTrailingRecords(offset+totalSize, data); + } + + if (totalSize != expectedTotalSize) + throw new RecordFormatException(totalSize + + " bytes written but getRecordSize() reports " + expectedTotalSize); + return totalSize; + } + + /** + * Note - this total size includes all potential {@link ContinueRecord}s written + */ + public int getRecordSize() { + int baseSize = 4 + getDataSize(); + return baseSize + getTrailingRecordsSize(); + } + + + private int getFormattingDataLength() { + if (_text.length() < 1) { + // important - no formatting data if text is empty + return 0; + } + return (_text.numFormattingRuns() + 1) * FORMAT_RUN_ENCODED_SIZE; + } + + private static byte[] createFormatData(HSSFRichTextString str) { + int nRuns = str.numFormattingRuns(); + byte[] result = new byte[(nRuns + 1) * FORMAT_RUN_ENCODED_SIZE]; + int pos = 0; + for (int i = 0; i < nRuns; i++) { + LittleEndian.putUShort(result, pos, str.getIndexOfFormattingRun(i)); + pos += 2; + int fontIndex = str.getFontOfFormattingRun(i); + LittleEndian.putUShort(result, pos, fontIndex == str.NO_FONT ? 0 : fontIndex); + pos += 2; + pos += 4; // skip reserved + } + LittleEndian.putUShort(result, pos, str.length()); + pos += 2; + LittleEndian.putUShort(result, pos, 0); + pos += 2; + pos += 4; // skip reserved + + return result; + } + + /** + * Sets the Horizontal text alignment field value. + */ + public void setHorizontalTextAlignment(int value) { + field_1_options = HorizontalTextAlignment.setValue(field_1_options, value); + } + + /** + * @return the Horizontal text alignment field value. + */ + public int getHorizontalTextAlignment() { + return HorizontalTextAlignment.getValue(field_1_options); + } + + /** + * Sets the Vertical text alignment field value. + */ + public void setVerticalTextAlignment(int value) { + field_1_options = VerticalTextAlignment.setValue(field_1_options, value); + } + + /** + * @return the Vertical text alignment field value. + */ + public int getVerticalTextAlignment() { + return VerticalTextAlignment.getValue(field_1_options); + } + + /** + * Sets the text locked field value. + */ + public void setTextLocked(boolean value) { + field_1_options = textLocked.setBoolean(field_1_options, value); + } + + /** + * @return the text locked field value. + */ + public boolean isTextLocked() { + return textLocked.isSet(field_1_options); + } + + /** + * Get the text orientation field for the TextObjectBase record. + * + * @return One of TEXT_ORIENTATION_NONE TEXT_ORIENTATION_TOP_TO_BOTTOM + * TEXT_ORIENTATION_ROT_RIGHT TEXT_ORIENTATION_ROT_LEFT + */ + public int getTextOrientation() { + return field_2_textOrientation; + } + + /** + * Set the text orientation field for the TextObjectBase record. + * + * @param textOrientation + * One of TEXT_ORIENTATION_NONE TEXT_ORIENTATION_TOP_TO_BOTTOM + * TEXT_ORIENTATION_ROT_RIGHT TEXT_ORIENTATION_ROT_LEFT + */ + public void setTextOrientation(int textOrientation) { + this.field_2_textOrientation = textOrientation; + } + + public HSSFRichTextString getStr() { + return _text; + } + + public void setStr(HSSFRichTextString str) { + _text = str; + } + + public Ptg getLinkRefPtg() { + return _linkRefPtg; + } + + public String toString() { + StringBuffer sb = new StringBuffer(); + + sb.append("[TXO]\n"); + sb.append(" .options = ").append(HexDump.shortToHex(field_1_options)).append("\n"); + sb.append(" .isHorizontal = ").append(getHorizontalTextAlignment()).append('\n'); + sb.append(" .isVertical = ").append(getVerticalTextAlignment()).append('\n'); + sb.append(" .textLocked = ").append(isTextLocked()).append('\n'); + sb.append(" .textOrientation= ").append(HexDump.shortToHex(getTextOrientation())).append("\n"); + sb.append(" .reserved4 = ").append(HexDump.shortToHex(field_3_reserved4)).append("\n"); + sb.append(" .reserved5 = ").append(HexDump.shortToHex(field_4_reserved5)).append("\n"); + sb.append(" .reserved6 = ").append(HexDump.shortToHex(field_5_reserved6)).append("\n"); + sb.append(" .textLength = ").append(HexDump.shortToHex(_text.length())).append("\n"); + sb.append(" .reserved7 = ").append(HexDump.intToHex(field_8_reserved7)).append("\n"); + + sb.append(" .string = ").append(_text).append('\n'); + + for (int i = 0; i < _text.numFormattingRuns(); i++) { + sb.append(" .textrun = ").append(_text.getFontOfFormattingRun(i)).append('\n'); + + } + sb.append("[/TXO]\n"); + return sb.toString(); + } + + public Object clone() { + + TextObjectRecord rec = new TextObjectRecord(); + rec._text = _text; + + rec.field_1_options = field_1_options; + rec.field_2_textOrientation = field_2_textOrientation; + rec.field_3_reserved4 = field_3_reserved4; + rec.field_4_reserved5 = field_4_reserved5; + rec.field_5_reserved6 = field_5_reserved6; + rec.field_8_reserved7 = field_8_reserved7; + + rec._text = _text; // clone needed? + + if (_linkRefPtg != null) { + rec._unknownPreFormulaInt = _unknownPreFormulaInt; + rec._linkRefPtg = _linkRefPtg.copy(); + rec._unknownPostFormulaByte = rec._unknownPostFormulaByte; + } + return rec; + } } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFComment.java b/src/java/org/apache/poi/hssf/usermodel/HSSFComment.java index f47286879d..7018388a45 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFComment.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFComment.java @@ -152,10 +152,7 @@ public class HSSFComment extends HSSFTextbox { if (string.numFormattingRuns() == 0) string.applyFont((short)0); if (txo != null) { - int frLength = ( string.numFormattingRuns() + 1 ) * 8; - txo.setFormattingRunLength( (short) frLength ); - txo.setTextLength( (short) string.length() ); - txo.setStr( string ); + txo.setStr(string); } super.setString(string); } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFRichTextString.java b/src/java/org/apache/poi/hssf/usermodel/HSSFRichTextString.java index 1b7342be61..6ddacf6976 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFRichTextString.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFRichTextString.java @@ -194,7 +194,7 @@ public class HSSFRichTextString /** - * @return the number of characters in the font. + * @return the number of characters in the text. */ public int length() { diff --git a/src/testcases/org/apache/poi/hssf/record/TestTextObjectBaseRecord.java b/src/testcases/org/apache/poi/hssf/record/TestTextObjectBaseRecord.java index b9d80dbad5..b8f9e86468 100644 --- a/src/testcases/org/apache/poi/hssf/record/TestTextObjectBaseRecord.java +++ b/src/testcases/org/apache/poi/hssf/record/TestTextObjectBaseRecord.java @@ -18,6 +18,11 @@ package org.apache.poi.hssf.record; +import java.io.ByteArrayInputStream; + +import org.apache.poi.hssf.usermodel.HSSFRichTextString; +import org.apache.poi.util.HexRead; + import junit.framework.TestCase; /** @@ -25,63 +30,62 @@ import junit.framework.TestCase; * class works correctly. Test data taken directly from a real * Excel file. * - * @author Glen Stampoultzis (glens at apache.org) */ -public class TestTextObjectBaseRecord extends TestCase { - byte[] data = new byte[] { - 0x44, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x00, 0x00, - }; +public final class TestTextObjectBaseRecord extends TestCase { + /** data for one TXO rec and two continue recs */ + private static final byte[] data = HexRead.readFromString( + "B6 01 " + // TextObjectRecord.sid + "12 00 " + // size 18 + "44 02 02 00 00 00 00 00" + + "00 00 " + + "02 00 " + // strLen 2 + "10 00 " + // 16 bytes for 2 format runs + "00 00" + + "00 00 " + + "3C 00 " + // ContinueRecord.sid + "05 00 " + // size 5 + "01 " + // unicode uncompressed + "41 00 42 00 " + // 'AB' + "3C 00 " + // ContinueRecord.sid + "10 00 " + // size 16 + "00 00 18 00 00 00 00 00 " + + "02 00 00 00 00 00 00 00 " + ); + public void testLoad() { - TextObjectBaseRecord record = new TextObjectBaseRecord(new TestcaseRecordInputStream((short)0x1B6, (short)data.length, data)); - - -// assertEquals( (short), record.getOptions()); - assertEquals( false, record.isReserved1() ); - assertEquals( TextObjectBaseRecord.HORIZONTAL_TEXT_ALIGNMENT_CENTERED, record.getHorizontalTextAlignment() ); - assertEquals( TextObjectBaseRecord.VERTICAL_TEXT_ALIGNMENT_JUSTIFY, record.getVerticalTextAlignment() ); - assertEquals( 0, record.getReserved2() ); - assertEquals( true, record.isTextLocked() ); - assertEquals( 0, record.getReserved3() ); - assertEquals( TextObjectBaseRecord.TEXT_ORIENTATION_ROT_RIGHT, record.getTextOrientation()); - assertEquals( 0, record.getReserved4()); - assertEquals( 0, record.getReserved5()); - assertEquals( 0, record.getReserved6()); - assertEquals( 2, record.getTextLength()); - assertEquals( 2, record.getFormattingRunLength()); - assertEquals( 0, record.getReserved7()); - - - assertEquals( 22, record.getRecordSize() ); + RecordInputStream in = new RecordInputStream(new ByteArrayInputStream(data)); + in.nextRecord(); + TextObjectRecord record = new TextObjectRecord(in); + + + assertEquals(TextObjectRecord.HORIZONTAL_TEXT_ALIGNMENT_CENTERED, record.getHorizontalTextAlignment()); + assertEquals(TextObjectRecord.VERTICAL_TEXT_ALIGNMENT_JUSTIFY, record.getVerticalTextAlignment()); + assertEquals(true, record.isTextLocked()); + assertEquals(TextObjectRecord.TEXT_ORIENTATION_ROT_RIGHT, record.getTextOrientation()); + + assertEquals(51, record.getRecordSize() ); } public void testStore() { - TextObjectBaseRecord record = new TextObjectBaseRecord(); + TextObjectRecord record = new TextObjectRecord(); + HSSFRichTextString str = new HSSFRichTextString("AB"); + str.applyFont(0, 2, (short)0x0018); + str.applyFont(2, 2, (short)0x0320); -// record.setOptions( (short) 0x0000); - record.setReserved1( false ); - record.setHorizontalTextAlignment( TextObjectBaseRecord.HORIZONTAL_TEXT_ALIGNMENT_CENTERED ); - record.setVerticalTextAlignment( TextObjectBaseRecord.VERTICAL_TEXT_ALIGNMENT_JUSTIFY ); - record.setReserved2( (short)0 ); - record.setTextLocked( true ); - record.setReserved3( (short)0 ); - record.setTextOrientation( TextObjectBaseRecord.TEXT_ORIENTATION_ROT_RIGHT ); - record.setReserved4( (short)0 ); - record.setReserved5( (short)0 ); - record.setReserved6( (short)0 ); - record.setTextLength( (short)2 ); - record.setFormattingRunLength( (short)2 ); - record.setReserved7( 0 ); + record.setHorizontalTextAlignment(TextObjectRecord.HORIZONTAL_TEXT_ALIGNMENT_CENTERED); + record.setVerticalTextAlignment(TextObjectRecord.VERTICAL_TEXT_ALIGNMENT_JUSTIFY); + record.setTextLocked(true); + record.setTextOrientation(TextObjectRecord.TEXT_ORIENTATION_ROT_RIGHT); + record.setStr(str); byte [] recordBytes = record.serialize(); - assertEquals(recordBytes.length - 4, data.length); + assertEquals(recordBytes.length, data.length); for (int i = 0; i < data.length; i++) - assertEquals("At offset " + i, data[i], recordBytes[i+4]); + assertEquals("At offset " + i, data[i], recordBytes[i]); } } diff --git a/src/testcases/org/apache/poi/hssf/record/TestTextObjectRecord.java b/src/testcases/org/apache/poi/hssf/record/TestTextObjectRecord.java index 91458e9ddb..b7d45e28b0 100644 --- a/src/testcases/org/apache/poi/hssf/record/TestTextObjectRecord.java +++ b/src/testcases/org/apache/poi/hssf/record/TestTextObjectRecord.java @@ -22,7 +22,11 @@ import java.util.Arrays; import junit.framework.TestCase; +import org.apache.poi.hssf.record.formula.Ptg; +import org.apache.poi.hssf.record.formula.RefPtg; import org.apache.poi.hssf.usermodel.HSSFRichTextString; +import org.apache.poi.util.HexRead; +import org.apache.poi.util.LittleEndian; /** * Tests that serialization and deserialization of the TextObjectRecord . @@ -32,17 +36,23 @@ import org.apache.poi.hssf.usermodel.HSSFRichTextString; */ public final class TestTextObjectRecord extends TestCase { - byte[] data = {(byte)0xB6, 0x01, 0x12, 0x00, 0x12, 0x02, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x3C, 0x00, 0x1B, 0x00, 0x01, 0x48, 0x00, 0x65, 0x00, 0x6C, - 0x00, 0x6C, 0x00, 0x6F, 0x00, 0x2C, 0x00, 0x20, 0x00, 0x57, 0x00, - 0x6F, 0x00, 0x72, 0x00, 0x6C, 0x00, 0x64, 0x00, 0x21, 0x00, 0x3C, - 0x00, 0x08, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + private static final byte[] simpleData = HexRead.readFromString( + "B6 01 12 00 " + + "12 02 00 00 00 00 00 00" + + "00 00 0D 00 08 00 00 00" + + "00 00 " + + "3C 00 1B 00 " + + "01 48 00 65 00 6C 00 6C 00 6F 00 " + + "2C 00 20 00 57 00 6F 00 72 00 6C " + + "00 64 00 21 00 " + + "3C 00 08 " + + "00 0D 00 00 00 00 00 00 00" + ); public void testRead() { - RecordInputStream is = new RecordInputStream(new ByteArrayInputStream(data)); + RecordInputStream is = new RecordInputStream(new ByteArrayInputStream(simpleData)); is.nextRecord(); TextObjectRecord record = new TextObjectRecord(is); @@ -50,36 +60,51 @@ public final class TestTextObjectRecord extends TestCase { assertEquals(TextObjectRecord.HORIZONTAL_TEXT_ALIGNMENT_LEFT_ALIGNED, record.getHorizontalTextAlignment()); assertEquals(TextObjectRecord.VERTICAL_TEXT_ALIGNMENT_TOP, record.getVerticalTextAlignment()); assertEquals(TextObjectRecord.TEXT_ORIENTATION_NONE, record.getTextOrientation()); - assertEquals(0, record.getReserved7()); assertEquals("Hello, World!", record.getStr().getString()); - } - public void testWrite() - { + public void testWrite() { HSSFRichTextString str = new HSSFRichTextString("Hello, World!"); TextObjectRecord record = new TextObjectRecord(); - int frLength = ( str.numFormattingRuns() + 1 ) * 8; - record.setFormattingRunLength( (short) frLength ); - record.setTextLength( (short) str.length() ); - record.setStr( str ); + record.setStr(str); record.setHorizontalTextAlignment( TextObjectRecord.HORIZONTAL_TEXT_ALIGNMENT_LEFT_ALIGNED ); record.setVerticalTextAlignment( TextObjectRecord.VERTICAL_TEXT_ALIGNMENT_TOP ); record.setTextLocked( true ); record.setTextOrientation( TextObjectRecord.TEXT_ORIENTATION_NONE ); - record.setReserved7( 0 ); byte [] ser = record.serialize(); - //assertEquals(ser.length , data.length); + assertEquals(ser.length , simpleData.length); - //assertTrue(Arrays.equals(data, ser)); + assertTrue(Arrays.equals(simpleData, ser)); //read again - RecordInputStream is = new RecordInputStream(new ByteArrayInputStream(data)); + RecordInputStream is = new RecordInputStream(new ByteArrayInputStream(simpleData)); is.nextRecord(); record = new TextObjectRecord(is); + } + + /** + * Zero {@link ContinueRecord}s follow a {@link TextObjectRecord} if the text is empty + */ + public void testWriteEmpty() { + HSSFRichTextString str = new HSSFRichTextString(""); + + TextObjectRecord record = new TextObjectRecord(); + record.setStr(str); + byte [] ser = record.serialize(); + + int formatDataLen = LittleEndian.getUShort(ser, 16); + assertEquals("formatDataLength", 0, formatDataLen); + + assertEquals(22, ser.length); // just the TXO record + + //read again + RecordInputStream is = new RecordInputStream(new ByteArrayInputStream(ser)); + is.nextRecord(); + record = new TextObjectRecord(is); + assertEquals(0, record.getStr().length()); } /** @@ -95,10 +120,7 @@ public final class TestTextObjectRecord extends TestCase { HSSFRichTextString str = new HSSFRichTextString(buff.toString()); TextObjectRecord obj = new TextObjectRecord(); - int frLength = ( str.numFormattingRuns() + 1 ) * 8; - obj.setFormattingRunLength( (short) frLength ); - obj.setTextLength( (short) str.length() ); - obj.setStr( str ); + obj.setStr(str); byte [] data = obj.serialize(); RecordInputStream is = new RecordInputStream(new ByteArrayInputStream(data)); @@ -120,30 +142,12 @@ public final class TestTextObjectRecord extends TestCase { HSSFRichTextString str = new HSSFRichTextString(text); TextObjectRecord obj = new TextObjectRecord(); - int frLength = ( str.numFormattingRuns() + 1 ) * 8; - obj.setFormattingRunLength( (short) frLength ); - obj.setTextLength( (short) str.length() ); - obj.setReserved1(true); - obj.setReserved2((short)2); - obj.setReserved3((short)3); - obj.setReserved4((short)4); - obj.setReserved5((short)5); - obj.setReserved6((short)6); - obj.setReserved7((short)7); obj.setStr( str ); TextObjectRecord cloned = (TextObjectRecord)obj.clone(); - assertEquals(obj.getReserved2(), cloned.getReserved2()); - assertEquals(obj.getReserved3(), cloned.getReserved3()); - assertEquals(obj.getReserved4(), cloned.getReserved4()); - assertEquals(obj.getReserved5(), cloned.getReserved5()); - assertEquals(obj.getReserved6(), cloned.getReserved6()); - assertEquals(obj.getReserved7(), cloned.getReserved7()); assertEquals(obj.getRecordSize(), cloned.getRecordSize()); - assertEquals(obj.getOptions(), cloned.getOptions()); assertEquals(obj.getHorizontalTextAlignment(), cloned.getHorizontalTextAlignment()); - assertEquals(obj.getFormattingRunLength(), cloned.getFormattingRunLength()); assertEquals(obj.getStr().getString(), cloned.getStr().getString()); //finally check that the serialized data is the same @@ -151,4 +155,47 @@ public final class TestTextObjectRecord extends TestCase { byte[] cln = cloned.serialize(); assertTrue(Arrays.equals(src, cln)); } + + /** similar to {@link #simpleData} but with link formula at end of TXO rec*/ + private static final byte[] linkData = HexRead.readFromString( + "B6 01 " + // TextObjectRecord.sid + "1E 00 " + // size 18 + "44 02 02 00 00 00 00 00" + + "00 00 " + + "02 00 " + // strLen 2 + "10 00 " + // 16 bytes for 2 format runs + "00 00 00 00 " + + + "05 00 " + // formula size + "D4 F0 8A 03 " + // unknownInt + "24 01 00 13 C0 " + //tRef(T2) + "13 " + // ?? + + "3C 00 " + // ContinueRecord.sid + "05 00 " + // size 5 + "01 " + // unicode uncompressed + "41 00 42 00 " + // 'AB' + "3C 00 " + // ContinueRecord.sid + "10 00 " + // size 16 + "00 00 18 00 00 00 00 00 " + + "02 00 00 00 00 00 00 00 " + ); + + + public void testLinkFormula() { + RecordInputStream is = new RecordInputStream(new ByteArrayInputStream(linkData)); + is.nextRecord(); + TextObjectRecord rec = new TextObjectRecord(is); + + Ptg ptg = rec.getLinkRefPtg(); + assertNotNull(ptg); + assertEquals(RefPtg.class, ptg.getClass()); + RefPtg rptg = (RefPtg) ptg; + assertEquals("T2", rptg.toFormulaString()); + + byte [] data2 = rec.serialize(); + assertEquals(linkData.length, data2.length); + assertTrue(Arrays.equals(linkData, data2)); + } + } -- 2.39.5