You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@poi.apache.org by jo...@apache.org on 2008/10/10 02:40:59 UTC

svn commit: r703302 - in /poi/trunk/src: documentation/content/xdocs/ java/org/apache/poi/hssf/model/ java/org/apache/poi/hssf/record/ java/org/apache/poi/hssf/usermodel/ testcases/org/apache/poi/hssf/record/

Author: josh
Date: Thu Oct  9 17:40:58 2008
New Revision: 703302

URL: http://svn.apache.org/viewvc?rev=703302&view=rev
Log:
Fix for bug 45964 - support for link formulas in Text Objects

Removed:
    poi/trunk/src/java/org/apache/poi/hssf/record/TextObjectBaseRecord.java
Modified:
    poi/trunk/src/documentation/content/xdocs/changes.xml
    poi/trunk/src/documentation/content/xdocs/status.xml
    poi/trunk/src/java/org/apache/poi/hssf/model/TextboxShape.java
    poi/trunk/src/java/org/apache/poi/hssf/record/ContinueRecord.java
    poi/trunk/src/java/org/apache/poi/hssf/record/RecordFactory.java
    poi/trunk/src/java/org/apache/poi/hssf/record/TextObjectRecord.java
    poi/trunk/src/java/org/apache/poi/hssf/usermodel/HSSFComment.java
    poi/trunk/src/java/org/apache/poi/hssf/usermodel/HSSFRichTextString.java
    poi/trunk/src/testcases/org/apache/poi/hssf/record/TestTextObjectBaseRecord.java
    poi/trunk/src/testcases/org/apache/poi/hssf/record/TestTextObjectRecord.java

Modified: poi/trunk/src/documentation/content/xdocs/changes.xml
URL: http://svn.apache.org/viewvc/poi/trunk/src/documentation/content/xdocs/changes.xml?rev=703302&r1=703301&r2=703302&view=diff
==============================================================================
--- poi/trunk/src/documentation/content/xdocs/changes.xml (original)
+++ poi/trunk/src/documentation/content/xdocs/changes.xml Thu Oct  9 17:40:58 2008
@@ -37,6 +37,7 @@
 
 		<!-- Don't forget to update status.xml too! -->
         <release version="3.2-alpha1" date="2008-??-??">
+           <action dev="POI-DEVELOPERS" type="fix">45964 - support for link formulas in Text Objects</action>
            <action dev="POI-DEVELOPERS" type="fix">43354 - support for evalating formulas with missing args</action>
            <action dev="POI-DEVELOPERS" type="fix">45912 - fixed ArrayIndexOutOfBoundsException in EmbeddedObjectRefSubRecord</action>
            <action dev="POI-DEVELOPERS" type="fix">45889 - fixed ArrayIndexOutOfBoundsException when constructing HSLF Table with a single row </action>

Modified: poi/trunk/src/documentation/content/xdocs/status.xml
URL: http://svn.apache.org/viewvc/poi/trunk/src/documentation/content/xdocs/status.xml?rev=703302&r1=703301&r2=703302&view=diff
==============================================================================
--- poi/trunk/src/documentation/content/xdocs/status.xml (original)
+++ poi/trunk/src/documentation/content/xdocs/status.xml Thu Oct  9 17:40:58 2008
@@ -34,6 +34,7 @@
 	<!-- Don't forget to update changes.xml too! -->
     <changes>
         <release version="3.2-alpha1" date="2008-??-??">
+           <action dev="POI-DEVELOPERS" type="fix">45964 - support for link formulas in Text Objects</action>
            <action dev="POI-DEVELOPERS" type="fix">43354 - support for evalating formulas with missing args</action>
            <action dev="POI-DEVELOPERS" type="fix">45912 - fixed ArrayIndexOutOfBoundsException in EmbeddedObjectRefSubRecord</action>
            <action dev="POI-DEVELOPERS" type="fix">45889 - fixed ArrayIndexOutOfBoundsException when constructing HSLF Table with a single row </action>

Modified: poi/trunk/src/java/org/apache/poi/hssf/model/TextboxShape.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/hssf/model/TextboxShape.java?rev=703302&r1=703301&r2=703302&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/hssf/model/TextboxShape.java (original)
+++ poi/trunk/src/java/org/apache/poi/hssf/model/TextboxShape.java Thu Oct  9 17:40:58 2008
@@ -138,15 +138,11 @@
         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;
     }

Modified: poi/trunk/src/java/org/apache/poi/hssf/record/ContinueRecord.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/hssf/record/ContinueRecord.java?rev=703302&r1=703301&r2=703302&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/hssf/record/ContinueRecord.java (original)
+++ poi/trunk/src/java/org/apache/poi/hssf/record/ContinueRecord.java Thu Oct  9 17:40:58 2008
@@ -22,76 +22,44 @@
 import org.apache.poi.util.LittleEndian;
 
 /**
- * Title:        Continue Record - Helper class used primarily for SST Records <P>
+ * Title:        Continue Record(0x003C) - Helper class used primarily for SST Records <P>
  * Description:  handles overflow for prior record in the input
  *               stream; content is tailored to that prior record<P>
  * @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 @@
      *
      * @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;
+    }
 }

Modified: poi/trunk/src/java/org/apache/poi/hssf/record/RecordFactory.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/hssf/record/RecordFactory.java?rev=703302&r1=703301&r2=703302&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/hssf/record/RecordFactory.java (original)
+++ poi/trunk/src/java/org/apache/poi/hssf/record/RecordFactory.java Thu Oct  9 17:40:58 2008
@@ -321,51 +321,54 @@
 		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;

Modified: poi/trunk/src/java/org/apache/poi/hssf/record/TextObjectRecord.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/hssf/record/TextObjectRecord.java?rev=703302&r1=703301&r2=703302&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/hssf/record/TextObjectRecord.java (original)
+++ poi/trunk/src/java/org/apache/poi/hssf/record/TextObjectRecord.java Thu Oct  9 17:40:58 2008
@@ -19,253 +19,427 @@
 
 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.<p/>
+ * 
+ * @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;
+	}
 }

Modified: poi/trunk/src/java/org/apache/poi/hssf/usermodel/HSSFComment.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/hssf/usermodel/HSSFComment.java?rev=703302&r1=703301&r2=703302&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/hssf/usermodel/HSSFComment.java (original)
+++ poi/trunk/src/java/org/apache/poi/hssf/usermodel/HSSFComment.java Thu Oct  9 17:40:58 2008
@@ -152,10 +152,7 @@
         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);
     }

Modified: poi/trunk/src/java/org/apache/poi/hssf/usermodel/HSSFRichTextString.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/hssf/usermodel/HSSFRichTextString.java?rev=703302&r1=703301&r2=703302&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/hssf/usermodel/HSSFRichTextString.java (original)
+++ poi/trunk/src/java/org/apache/poi/hssf/usermodel/HSSFRichTextString.java Thu Oct  9 17:40:58 2008
@@ -194,7 +194,7 @@
     
 
     /**
-     * @return  the number of characters in the font.
+     * @return  the number of characters in the text.
      */
     public int length()
     {

Modified: poi/trunk/src/testcases/org/apache/poi/hssf/record/TestTextObjectBaseRecord.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/testcases/org/apache/poi/hssf/record/TestTextObjectBaseRecord.java?rev=703302&r1=703301&r2=703302&view=diff
==============================================================================
--- poi/trunk/src/testcases/org/apache/poi/hssf/record/TestTextObjectBaseRecord.java (original)
+++ poi/trunk/src/testcases/org/apache/poi/hssf/record/TestTextObjectBaseRecord.java Thu Oct  9 17:40:58 2008
@@ -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 @@
  * 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));
+        RecordInputStream in = new RecordInputStream(new ByteArrayInputStream(data));
+        in.nextRecord();
+        TextObjectRecord record = new TextObjectRecord(in);
 
 
-//        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(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( 22, record.getRecordSize() );
+        assertEquals(51, record.getRecordSize() );
     }
 
     public void testStore()
     {
-        TextObjectBaseRecord record = new TextObjectBaseRecord();
-
+        TextObjectRecord record = new TextObjectRecord();
 
 
-//        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 );
+        HSSFRichTextString str = new HSSFRichTextString("AB");
+        str.applyFont(0, 2, (short)0x0018);
+        str.applyFont(2, 2, (short)0x0320);
+
+        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]);
     }
 }

Modified: poi/trunk/src/testcases/org/apache/poi/hssf/record/TestTextObjectRecord.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/testcases/org/apache/poi/hssf/record/TestTextObjectRecord.java?rev=703302&r1=703301&r2=703302&view=diff
==============================================================================
--- poi/trunk/src/testcases/org/apache/poi/hssf/record/TestTextObjectRecord.java (original)
+++ poi/trunk/src/testcases/org/apache/poi/hssf/record/TestTextObjectRecord.java Thu Oct  9 17:40:58 2008
@@ -22,7 +22,11 @@
 
 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 @@
  */
 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 @@
         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 @@
             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 @@
         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 @@
         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));
+	}
+    
 }



---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@poi.apache.org
For additional commands, e-mail: commits-help@poi.apache.org