]> source.dussan.org Git - poi.git/commitdiff
records.UnicodeString isn't actually a Record, just a common part that exists in...
authorNick Burch <nick@apache.org>
Mon, 18 Jan 2010 12:18:00 +0000 (12:18 +0000)
committerNick Burch <nick@apache.org>
Mon, 18 Jan 2010 12:18:00 +0000 (12:18 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@900362 13f79535-47bb-0310-9956-ffa450edef68

18 files changed:
src/java/org/apache/poi/hssf/model/InternalWorkbook.java
src/java/org/apache/poi/hssf/record/DVRecord.java
src/java/org/apache/poi/hssf/record/SSTDeserializer.java
src/java/org/apache/poi/hssf/record/SSTRecord.java
src/java/org/apache/poi/hssf/record/SSTSerializer.java
src/java/org/apache/poi/hssf/record/UnicodeString.java [deleted file]
src/java/org/apache/poi/hssf/record/common/UnicodeString.java [new file with mode: 0644]
src/java/org/apache/poi/hssf/usermodel/HSSFCell.java
src/java/org/apache/poi/hssf/usermodel/HSSFOptimiser.java
src/java/org/apache/poi/hssf/usermodel/HSSFRichTextString.java
src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java
src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java
src/testcases/org/apache/poi/hssf/record/AllRecordTests.java
src/testcases/org/apache/poi/hssf/record/TestSSTRecord.java
src/testcases/org/apache/poi/hssf/record/TestSSTRecordSizeCalculator.java
src/testcases/org/apache/poi/hssf/record/TestUnicodeString.java [deleted file]
src/testcases/org/apache/poi/hssf/record/common/TestUnicodeString.java [new file with mode: 0644]
src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java

index 3c2a210acbe97848d233cb261806849ce50ab5c8..868d2a0a624b07d6977d5b5118c6ad3b86084264 100644 (file)
@@ -71,12 +71,12 @@ import org.apache.poi.hssf.record.SSTRecord;
 import org.apache.poi.hssf.record.StyleRecord;
 import org.apache.poi.hssf.record.SupBookRecord;
 import org.apache.poi.hssf.record.TabIdRecord;
-import org.apache.poi.hssf.record.UnicodeString;
 import org.apache.poi.hssf.record.UseSelFSRecord;
 import org.apache.poi.hssf.record.WindowOneRecord;
 import org.apache.poi.hssf.record.WindowProtectRecord;
 import org.apache.poi.hssf.record.WriteAccessRecord;
 import org.apache.poi.hssf.record.WriteProtectRecord;
+import org.apache.poi.hssf.record.common.UnicodeString;
 import org.apache.poi.hssf.record.formula.NameXPtg;
 import org.apache.poi.hssf.record.formula.FormulaShifter;
 import org.apache.poi.hssf.record.formula.Ptg;
index a6d9f11b619691409c04c3035802442d128c3ffb..67e873691f952211b4498228de3dae17344ca172 100644 (file)
@@ -17,6 +17,7 @@
 
 package org.apache.poi.hssf.record;
 
+import org.apache.poi.hssf.record.common.UnicodeString;
 import org.apache.poi.hssf.record.formula.Ptg;
 import org.apache.poi.hssf.usermodel.HSSFDataValidation;
 import org.apache.poi.ss.formula.Formula;
index 304dc3bfeb26f393eb63dabc7b35e38af374ff3c..c9f6569b4af42dbfb41dcfa4d8a95d9be8b94ec2 100644 (file)
@@ -19,6 +19,7 @@
 
 package org.apache.poi.hssf.record;
 
+import org.apache.poi.hssf.record.common.UnicodeString;
 import org.apache.poi.util.IntMapper;
 
 /**
index 481fa84d3cc80f21211692f025ead389048fe50e..e95f155eb5cb37a6780c0e37a41799b1ffe74bbf 100644 (file)
@@ -19,6 +19,7 @@ package org.apache.poi.hssf.record;
 
 import java.util.Iterator;
 
+import org.apache.poi.hssf.record.common.UnicodeString;
 import org.apache.poi.hssf.record.cont.ContinuableRecord;
 import org.apache.poi.hssf.record.cont.ContinuableRecordOutput;
 import org.apache.poi.util.IntMapper;
index 78844deb3071d04bb100871392789c6af64b7377..f1022642e477cf8511db324c58fbe2786375f633 100644 (file)
@@ -17,6 +17,7 @@
 
 package org.apache.poi.hssf.record;
 
+import org.apache.poi.hssf.record.common.UnicodeString;
 import org.apache.poi.hssf.record.cont.ContinuableRecordOutput;
 import org.apache.poi.util.IntMapper;
 
diff --git a/src/java/org/apache/poi/hssf/record/UnicodeString.java b/src/java/org/apache/poi/hssf/record/UnicodeString.java
deleted file mode 100644 (file)
index 9198e82..0000000
+++ /dev/null
@@ -1,587 +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 java.util.ArrayList;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-
-import org.apache.poi.hssf.record.cont.ContinuableRecordOutput;
-import org.apache.poi.util.BitField;
-import org.apache.poi.util.BitFieldFactory;
-import org.apache.poi.util.HexDump;
-import org.apache.poi.util.LittleEndianInput;
-import org.apache.poi.util.LittleEndianOutput;
-
-/**
- * Title: Unicode String<p/>
- * Description:  Unicode String - just standard fields that are in several records.
- *               It is considered more desirable then repeating it in all of them.<p/>
- * REFERENCE:  PG 264 Microsoft Excel 97 Developer's Kit (ISBN: 1-57231-498-2)<p/>
- * @author  Andrew C. Oliver
- * @author Marc Johnson (mjohnson at apache dot org)
- * @author Glen Stampoultzis (glens at apache.org)
- */
-public final class UnicodeString implements Comparable<UnicodeString> {
-    private short             field_1_charCount;
-    private byte              field_2_optionflags;
-    private String            field_3_string;
-    private List<FormatRun> field_4_format_runs;
-    private byte[] field_5_ext_rst;
-    private static final BitField   highByte  = BitFieldFactory.getInstance(0x1);
-    private static final BitField   extBit    = BitFieldFactory.getInstance(0x4);
-    private static final BitField   richText  = BitFieldFactory.getInstance(0x8);
-
-    public static class FormatRun implements Comparable<FormatRun> {
-        final short _character;
-        short _fontIndex;
-
-        public FormatRun(short character, short fontIndex) {
-            this._character = character;
-            this._fontIndex = fontIndex;
-        }
-
-        public FormatRun(LittleEndianInput in) {
-            this(in.readShort(), in.readShort());
-        }
-
-        public short getCharacterPos() {
-            return _character;
-        }
-
-        public short getFontIndex() {
-            return _fontIndex;
-        }
-
-        public boolean equals(Object o) {
-            if (!(o instanceof FormatRun)) {
-                return false;
-            }
-            FormatRun other = ( FormatRun ) o;
-
-            return _character == other._character && _fontIndex == other._fontIndex;
-        }
-
-        public int compareTo(FormatRun r) {
-            if (_character == r._character && _fontIndex == r._fontIndex) {
-                return 0;
-            }
-            if (_character == r._character) {
-                return _fontIndex - r._fontIndex;
-            }
-            return _character - r._character;
-        }
-
-        public String toString() {
-            return "character="+_character+",fontIndex="+_fontIndex;
-        }
-
-        public void serialize(LittleEndianOutput out) {
-            out.writeShort(_character);
-            out.writeShort(_fontIndex);
-        }
-    }
-
-    private UnicodeString() {
-     //Used for clone method.
-    }
-
-    public UnicodeString(String str)
-    {
-      setString(str);
-    }
-
-
-
-    public int hashCode()
-    {
-        int stringHash = 0;
-        if (field_3_string != null)
-            stringHash = field_3_string.hashCode();
-        return field_1_charCount + stringHash;
-    }
-
-    /**
-     * Our handling of equals is inconsistent with compareTo.  The trouble is because we don't truely understand
-     * rich text fields yet it's difficult to make a sound comparison.
-     *
-     * @param o     The object to compare.
-     * @return      true if the object is actually equal.
-     */
-    public boolean equals(Object o)
-    {
-        if (!(o instanceof UnicodeString)) {
-            return false;
-        }
-        UnicodeString other = (UnicodeString) o;
-
-        //OK lets do this in stages to return a quickly, first check the actual string
-        boolean eq = ((field_1_charCount == other.field_1_charCount)
-                && (field_2_optionflags == other.field_2_optionflags)
-                && field_3_string.equals(other.field_3_string));
-        if (!eq) return false;
-
-        //OK string appears to be equal but now lets compare formatting runs
-        if ((field_4_format_runs == null) && (other.field_4_format_runs == null))
-          //Strings are equal, and there are not formatting runs.
-          return true;
-        if (((field_4_format_runs == null) && (other.field_4_format_runs != null)) ||
-             (field_4_format_runs != null) && (other.field_4_format_runs == null))
-           //Strings are equal, but one or the other has formatting runs
-           return false;
-
-        //Strings are equal, so now compare formatting runs.
-        int size = field_4_format_runs.size();
-        if (size != other.field_4_format_runs.size())
-          return false;
-
-        for (int i=0;i<size;i++) {
-          FormatRun run1 = field_4_format_runs.get(i);
-          FormatRun run2 = other.field_4_format_runs.get(i);
-
-          if (!run1.equals(run2))
-            return false;
-        }
-
-        //Well the format runs are equal as well!, better check the ExtRst data
-        //Which by the way we dont know how to decode!
-        if ((field_5_ext_rst == null) && (other.field_5_ext_rst == null))
-          return true;
-        if (((field_5_ext_rst == null) && (other.field_5_ext_rst != null)) ||
-            ((field_5_ext_rst != null) && (other.field_5_ext_rst == null)))
-          return false;
-        size = field_5_ext_rst.length;
-        if (size != field_5_ext_rst.length)
-          return false;
-
-        //Check individual bytes!
-        for (int i=0;i<size;i++) {
-          if (field_5_ext_rst[i] != other.field_5_ext_rst[i])
-            return false;
-        }
-        //Phew!! After all of that we have finally worked out that the strings
-        //are identical.
-        return true;
-    }
-
-    /**
-     * construct a unicode string record and fill its fields, ID is ignored
-     * @param in the RecordInputstream to read the record from
-     */
-    public UnicodeString(RecordInputStream in) {
-        field_1_charCount   = in.readShort();
-        field_2_optionflags = in.readByte();
-
-        int runCount = 0;
-        int extensionLength = 0;
-        //Read the number of rich runs if rich text.
-        if ( isRichText() )
-        {
-            runCount = in.readShort();
-        }
-        //Read the size of extended data if present.
-        if ( isExtendedText() )
-        {
-            extensionLength = in.readInt();
-        }
-
-        boolean isCompressed = ((field_2_optionflags & 1) == 0);
-        if (isCompressed) {
-            field_3_string = in.readCompressedUnicode(getCharCount());
-        } else {
-            field_3_string = in.readUnicodeLEString(getCharCount());
-        }
-
-
-        if (isRichText() && (runCount > 0)) {
-          field_4_format_runs = new ArrayList<FormatRun>(runCount);
-          for (int i=0;i<runCount;i++) {
-            field_4_format_runs.add(new FormatRun(in));
-          }
-        }
-
-        if (isExtendedText() && (extensionLength > 0)) {
-          field_5_ext_rst = new byte[extensionLength];
-          for (int i=0;i<extensionLength;i++) {
-            field_5_ext_rst[i] = in.readByte();
-            }
-        }
-    }
-
-
-
-    /**
-     * get the number of characters in the string,
-     *  as an un-wrapped int
-     *
-     * @return number of characters
-     */
-    public int getCharCount() {
-       if(field_1_charCount < 0) {
-               return field_1_charCount + 65536;
-       }
-        return field_1_charCount;
-    }
-
-    /**
-     * get the number of characters in the string,
-     * wrapped as needed to fit within a short
-     *
-     * @return number of characters
-     */
-    public short getCharCountShort() {
-        return field_1_charCount;
-    }
-
-    /**
-     * set the number of characters in the string
-     * @param cc - number of characters
-     */
-
-    public void setCharCount(short cc)
-    {
-        field_1_charCount = cc;
-    }
-
-    /**
-     * get the option flags which among other things return if this is a 16-bit or
-     * 8 bit string
-     *
-     * @return optionflags bitmask
-     *
-     */
-
-    public byte getOptionFlags()
-    {
-        return field_2_optionflags;
-    }
-
-    /**
-     * set the option flags which among other things return if this is a 16-bit or
-     * 8 bit string
-     *
-     * @param of  optionflags bitmask
-     *
-     */
-
-    public void setOptionFlags(byte of)
-    {
-        field_2_optionflags = of;
-    }
-
-    /**
-     * @return the actual string this contains as a java String object
-     */
-    public String getString()
-    {
-        return field_3_string;
-    }
-
-    /**
-     * set the actual string this contains
-     * @param string  the text
-     */
-
-    public void setString(String string)
-    {
-        field_3_string = string;
-        setCharCount((short)field_3_string.length());
-        // scan for characters greater than 255 ... if any are
-        // present, we have to use 16-bit encoding. Otherwise, we
-        // can use 8-bit encoding
-        boolean useUTF16 = false;
-        int strlen = string.length();
-
-        for ( int j = 0; j < strlen; j++ )
-        {
-            if ( string.charAt( j ) > 255 )
-        {
-                useUTF16 = true;
-                break;
-            }
-        }
-        if (useUTF16)
-          //Set the uncompressed bit
-          field_2_optionflags = highByte.setByte(field_2_optionflags);
-        else field_2_optionflags = highByte.clearByte(field_2_optionflags);
-    }
-
-    public int getFormatRunCount() {
-      if (field_4_format_runs == null)
-        return 0;
-      return field_4_format_runs.size();
-    }
-
-    public FormatRun getFormatRun(int index) {
-      if (field_4_format_runs == null) {
-               return null;
-       }
-      if (index < 0 || index >= field_4_format_runs.size()) {
-               return null;
-       }
-      return field_4_format_runs.get(index);
-    }
-
-    private int findFormatRunAt(int characterPos) {
-      int size = field_4_format_runs.size();
-      for (int i=0;i<size;i++) {
-        FormatRun r = field_4_format_runs.get(i);
-        if (r._character == characterPos)
-          return i;
-        else if (r._character > characterPos)
-          return -1;
-      }
-      return -1;
-    }
-
-    /** Adds a font run to the formatted string.
-     *
-     *  If a font run exists at the current charcter location, then it is
-     *  replaced with the font run to be added.
-     */
-    public void addFormatRun(FormatRun r) {
-      if (field_4_format_runs == null) {
-               field_4_format_runs = new ArrayList<FormatRun>();
-       }
-
-      int index = findFormatRunAt(r._character);
-      if (index != -1)
-         field_4_format_runs.remove(index);
-
-      field_4_format_runs.add(r);
-      //Need to sort the font runs to ensure that the font runs appear in
-      //character order
-      Collections.sort(field_4_format_runs);
-
-      //Make sure that we now say that we are a rich string
-      field_2_optionflags = richText.setByte(field_2_optionflags);
-    }
-
-    public Iterator<FormatRun> formatIterator() {
-      if (field_4_format_runs != null) {
-        return field_4_format_runs.iterator();
-      }
-      return null;
-    }
-
-    public void removeFormatRun(FormatRun r) {
-      field_4_format_runs.remove(r);
-      if (field_4_format_runs.size() == 0) {
-        field_4_format_runs = null;
-        field_2_optionflags = richText.clearByte(field_2_optionflags);
-      }
-    }
-
-    public void clearFormatting() {
-      field_4_format_runs = null;
-      field_2_optionflags = richText.clearByte(field_2_optionflags);
-    }
-
-
-    void setExtendedRst(byte[] ext_rst) {
-      if (ext_rst != null)
-        field_2_optionflags = extBit.setByte(field_2_optionflags);
-      else field_2_optionflags = extBit.clearByte(field_2_optionflags);
-      this.field_5_ext_rst = ext_rst;
-    }
-
-
-    /**
-     * Swaps all use in the string of one font index
-     *  for use of a different font index.
-     * Normally only called when fonts have been
-     *  removed / re-ordered
-     */
-    public void swapFontUse(short oldFontIndex, short newFontIndex) {
-        for (FormatRun run : field_4_format_runs) {
-            if(run._fontIndex == oldFontIndex) {
-                run._fontIndex = newFontIndex;
-            }
-        }
-    }
-
-    /**
-     * unlike the real records we return the same as "getString()" rather than debug info
-     * @see #getDebugInfo()
-     * @return String value of the record
-     */
-
-    public String toString()
-    {
-        return getString();
-    }
-
-    /**
-     * return a character representation of the fields of this record
-     *
-     *
-     * @return String of output for biffviewer etc.
-     *
-     */
-    public String getDebugInfo()
-    {
-        StringBuffer buffer = new StringBuffer();
-
-        buffer.append("[UNICODESTRING]\n");
-        buffer.append("    .charcount       = ")
-            .append(Integer.toHexString(getCharCount())).append("\n");
-        buffer.append("    .optionflags     = ")
-            .append(Integer.toHexString(getOptionFlags())).append("\n");
-        buffer.append("    .string          = ").append(getString()).append("\n");
-        if (field_4_format_runs != null) {
-          for (int i = 0; i < field_4_format_runs.size();i++) {
-            FormatRun r = field_4_format_runs.get(i);
-            buffer.append("      .format_run"+i+"          = ").append(r.toString()).append("\n");
-          }
-        }
-        if (field_5_ext_rst != null) {
-          buffer.append("    .field_5_ext_rst          = ").append("\n").append(HexDump.toHex(field_5_ext_rst)).append("\n");
-        }
-        buffer.append("[/UNICODESTRING]\n");
-        return buffer.toString();
-    }
-
-    public void serialize(ContinuableRecordOutput out) {
-        int numberOfRichTextRuns = 0;
-        int extendedDataSize = 0;
-        if (isRichText() && field_4_format_runs != null) {
-            numberOfRichTextRuns = field_4_format_runs.size();
-        }
-        if (isExtendedText() && field_5_ext_rst != null) {
-            extendedDataSize = field_5_ext_rst.length;
-        }
-
-        out.writeString(field_3_string, numberOfRichTextRuns, extendedDataSize);
-
-        if (numberOfRichTextRuns > 0) {
-
-          //This will ensure that a run does not split a continue
-          for (int i=0;i<numberOfRichTextRuns;i++) {
-              if (out.getAvailableSpace() < 4) {
-                  out.writeContinue();
-              }
-                FormatRun r = field_4_format_runs.get(i);
-                r.serialize(out);
-          }
-        }
-
-        if (extendedDataSize > 0) {
-            // OK ExtRst is actually not documented, so i am going to hope
-            // that we can actually continue on byte boundaries
-
-            int extPos = 0;
-            while (true) {
-                int nBytesToWrite = Math.min(extendedDataSize - extPos, out.getAvailableSpace());
-                out.write(field_5_ext_rst, extPos, nBytesToWrite);
-                extPos += nBytesToWrite;
-                if (extPos >= extendedDataSize) {
-                    break;
-                }
-                out.writeContinue();
-            }
-        }
-    }
-
-    public int compareTo(UnicodeString str) {
-
-        int result = getString().compareTo(str.getString());
-
-        //As per the equals method lets do this in stages
-        if (result != 0)
-          return result;
-
-        //OK string appears to be equal but now lets compare formatting runs
-        if ((field_4_format_runs == null) && (str.field_4_format_runs == null))
-          //Strings are equal, and there are no formatting runs.
-          return 0;
-
-        if ((field_4_format_runs == null) && (str.field_4_format_runs != null))
-          //Strings are equal, but one or the other has formatting runs
-          return 1;
-        if ((field_4_format_runs != null) && (str.field_4_format_runs == null))
-          //Strings are equal, but one or the other has formatting runs
-          return -1;
-
-        //Strings are equal, so now compare formatting runs.
-        int size = field_4_format_runs.size();
-        if (size != str.field_4_format_runs.size())
-          return size - str.field_4_format_runs.size();
-
-        for (int i=0;i<size;i++) {
-          FormatRun run1 = field_4_format_runs.get(i);
-          FormatRun run2 = str.field_4_format_runs.get(i);
-
-          result = run1.compareTo(run2);
-          if (result != 0)
-            return result;
-        }
-
-        //Well the format runs are equal as well!, better check the ExtRst data
-        //Which by the way we don't know how to decode!
-        if ((field_5_ext_rst == null) && (str.field_5_ext_rst == null))
-          return 0;
-        if ((field_5_ext_rst == null) && (str.field_5_ext_rst != null))
-         return 1;
-        if ((field_5_ext_rst != null) && (str.field_5_ext_rst == null))
-          return -1;
-
-        size = field_5_ext_rst.length;
-        if (size != field_5_ext_rst.length)
-          return size - field_5_ext_rst.length;
-
-        //Check individual bytes!
-        for (int i=0;i<size;i++) {
-          if (field_5_ext_rst[i] != str.field_5_ext_rst[i])
-            return field_5_ext_rst[i] - str.field_5_ext_rst[i];
-        }
-        //Phew!! After all of that we have finally worked out that the strings
-        //are identical.
-        return 0;
-    }
-
-    private boolean isRichText()
-    {
-      return richText.isSet(getOptionFlags());
-    }
-
-    private boolean isExtendedText()
-        {
-        return extBit.isSet(getOptionFlags());
-    }
-
-    public Object clone() {
-        UnicodeString str = new UnicodeString();
-        str.field_1_charCount = field_1_charCount;
-        str.field_2_optionflags = field_2_optionflags;
-        str.field_3_string = field_3_string;
-        if (field_4_format_runs != null) {
-          str.field_4_format_runs = new ArrayList<FormatRun>();
-          for (FormatRun r : field_4_format_runs) {
-            str.field_4_format_runs.add(new FormatRun(r._character, r._fontIndex));
-            }
-        }
-        if (field_5_ext_rst != null) {
-          str.field_5_ext_rst = new byte[field_5_ext_rst.length];
-          System.arraycopy(field_5_ext_rst, 0, str.field_5_ext_rst, 0,
-                           field_5_ext_rst.length);
-        }
-
-        return str;
-    }
-}
diff --git a/src/java/org/apache/poi/hssf/record/common/UnicodeString.java b/src/java/org/apache/poi/hssf/record/common/UnicodeString.java
new file mode 100644 (file)
index 0000000..842a734
--- /dev/null
@@ -0,0 +1,588 @@
+/* ====================================================================
+   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.common;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.poi.hssf.record.RecordInputStream;
+import org.apache.poi.hssf.record.cont.ContinuableRecordOutput;
+import org.apache.poi.util.BitField;
+import org.apache.poi.util.BitFieldFactory;
+import org.apache.poi.util.HexDump;
+import org.apache.poi.util.LittleEndianInput;
+import org.apache.poi.util.LittleEndianOutput;
+
+/**
+ * Title: Unicode String<p/>
+ * Description:  Unicode String - just standard fields that are in several records.
+ *               It is considered more desirable then repeating it in all of them.<p/>
+ * REFERENCE:  PG 264 Microsoft Excel 97 Developer's Kit (ISBN: 1-57231-498-2)<p/>
+ * @author  Andrew C. Oliver
+ * @author Marc Johnson (mjohnson at apache dot org)
+ * @author Glen Stampoultzis (glens at apache.org)
+ */
+public final class UnicodeString implements Comparable<UnicodeString> {
+    private short             field_1_charCount;
+    private byte              field_2_optionflags;
+    private String            field_3_string;
+    private List<FormatRun> field_4_format_runs;
+    private byte[] field_5_ext_rst;
+    private static final BitField   highByte  = BitFieldFactory.getInstance(0x1);
+    private static final BitField   extBit    = BitFieldFactory.getInstance(0x4);
+    private static final BitField   richText  = BitFieldFactory.getInstance(0x8);
+
+    public static class FormatRun implements Comparable<FormatRun> {
+        final short _character;
+        short _fontIndex;
+
+        public FormatRun(short character, short fontIndex) {
+            this._character = character;
+            this._fontIndex = fontIndex;
+        }
+
+        public FormatRun(LittleEndianInput in) {
+            this(in.readShort(), in.readShort());
+        }
+
+        public short getCharacterPos() {
+            return _character;
+        }
+
+        public short getFontIndex() {
+            return _fontIndex;
+        }
+
+        public boolean equals(Object o) {
+            if (!(o instanceof FormatRun)) {
+                return false;
+            }
+            FormatRun other = ( FormatRun ) o;
+
+            return _character == other._character && _fontIndex == other._fontIndex;
+        }
+
+        public int compareTo(FormatRun r) {
+            if (_character == r._character && _fontIndex == r._fontIndex) {
+                return 0;
+            }
+            if (_character == r._character) {
+                return _fontIndex - r._fontIndex;
+            }
+            return _character - r._character;
+        }
+
+        public String toString() {
+            return "character="+_character+",fontIndex="+_fontIndex;
+        }
+
+        public void serialize(LittleEndianOutput out) {
+            out.writeShort(_character);
+            out.writeShort(_fontIndex);
+        }
+    }
+
+    private UnicodeString() {
+     //Used for clone method.
+    }
+
+    public UnicodeString(String str)
+    {
+      setString(str);
+    }
+
+
+
+    public int hashCode()
+    {
+        int stringHash = 0;
+        if (field_3_string != null)
+            stringHash = field_3_string.hashCode();
+        return field_1_charCount + stringHash;
+    }
+
+    /**
+     * Our handling of equals is inconsistent with compareTo.  The trouble is because we don't truely understand
+     * rich text fields yet it's difficult to make a sound comparison.
+     *
+     * @param o     The object to compare.
+     * @return      true if the object is actually equal.
+     */
+    public boolean equals(Object o)
+    {
+        if (!(o instanceof UnicodeString)) {
+            return false;
+        }
+        UnicodeString other = (UnicodeString) o;
+
+        //OK lets do this in stages to return a quickly, first check the actual string
+        boolean eq = ((field_1_charCount == other.field_1_charCount)
+                && (field_2_optionflags == other.field_2_optionflags)
+                && field_3_string.equals(other.field_3_string));
+        if (!eq) return false;
+
+        //OK string appears to be equal but now lets compare formatting runs
+        if ((field_4_format_runs == null) && (other.field_4_format_runs == null))
+          //Strings are equal, and there are not formatting runs.
+          return true;
+        if (((field_4_format_runs == null) && (other.field_4_format_runs != null)) ||
+             (field_4_format_runs != null) && (other.field_4_format_runs == null))
+           //Strings are equal, but one or the other has formatting runs
+           return false;
+
+        //Strings are equal, so now compare formatting runs.
+        int size = field_4_format_runs.size();
+        if (size != other.field_4_format_runs.size())
+          return false;
+
+        for (int i=0;i<size;i++) {
+          FormatRun run1 = field_4_format_runs.get(i);
+          FormatRun run2 = other.field_4_format_runs.get(i);
+
+          if (!run1.equals(run2))
+            return false;
+        }
+
+        //Well the format runs are equal as well!, better check the ExtRst data
+        //Which by the way we dont know how to decode!
+        if ((field_5_ext_rst == null) && (other.field_5_ext_rst == null))
+          return true;
+        if (((field_5_ext_rst == null) && (other.field_5_ext_rst != null)) ||
+            ((field_5_ext_rst != null) && (other.field_5_ext_rst == null)))
+          return false;
+        size = field_5_ext_rst.length;
+        if (size != field_5_ext_rst.length)
+          return false;
+
+        //Check individual bytes!
+        for (int i=0;i<size;i++) {
+          if (field_5_ext_rst[i] != other.field_5_ext_rst[i])
+            return false;
+        }
+        //Phew!! After all of that we have finally worked out that the strings
+        //are identical.
+        return true;
+    }
+
+    /**
+     * construct a unicode string record and fill its fields, ID is ignored
+     * @param in the RecordInputstream to read the record from
+     */
+    public UnicodeString(RecordInputStream in) {
+        field_1_charCount   = in.readShort();
+        field_2_optionflags = in.readByte();
+
+        int runCount = 0;
+        int extensionLength = 0;
+        //Read the number of rich runs if rich text.
+        if ( isRichText() )
+        {
+            runCount = in.readShort();
+        }
+        //Read the size of extended data if present.
+        if ( isExtendedText() )
+        {
+            extensionLength = in.readInt();
+        }
+
+        boolean isCompressed = ((field_2_optionflags & 1) == 0);
+        if (isCompressed) {
+            field_3_string = in.readCompressedUnicode(getCharCount());
+        } else {
+            field_3_string = in.readUnicodeLEString(getCharCount());
+        }
+
+
+        if (isRichText() && (runCount > 0)) {
+          field_4_format_runs = new ArrayList<FormatRun>(runCount);
+          for (int i=0;i<runCount;i++) {
+            field_4_format_runs.add(new FormatRun(in));
+          }
+        }
+
+        if (isExtendedText() && (extensionLength > 0)) {
+          field_5_ext_rst = new byte[extensionLength];
+          for (int i=0;i<extensionLength;i++) {
+            field_5_ext_rst[i] = in.readByte();
+            }
+        }
+    }
+
+
+
+    /**
+     * get the number of characters in the string,
+     *  as an un-wrapped int
+     *
+     * @return number of characters
+     */
+    public int getCharCount() {
+       if(field_1_charCount < 0) {
+               return field_1_charCount + 65536;
+       }
+        return field_1_charCount;
+    }
+
+    /**
+     * get the number of characters in the string,
+     * wrapped as needed to fit within a short
+     *
+     * @return number of characters
+     */
+    public short getCharCountShort() {
+        return field_1_charCount;
+    }
+
+    /**
+     * set the number of characters in the string
+     * @param cc - number of characters
+     */
+
+    public void setCharCount(short cc)
+    {
+        field_1_charCount = cc;
+    }
+
+    /**
+     * get the option flags which among other things return if this is a 16-bit or
+     * 8 bit string
+     *
+     * @return optionflags bitmask
+     *
+     */
+
+    public byte getOptionFlags()
+    {
+        return field_2_optionflags;
+    }
+
+    /**
+     * set the option flags which among other things return if this is a 16-bit or
+     * 8 bit string
+     *
+     * @param of  optionflags bitmask
+     *
+     */
+
+    public void setOptionFlags(byte of)
+    {
+        field_2_optionflags = of;
+    }
+
+    /**
+     * @return the actual string this contains as a java String object
+     */
+    public String getString()
+    {
+        return field_3_string;
+    }
+
+    /**
+     * set the actual string this contains
+     * @param string  the text
+     */
+
+    public void setString(String string)
+    {
+        field_3_string = string;
+        setCharCount((short)field_3_string.length());
+        // scan for characters greater than 255 ... if any are
+        // present, we have to use 16-bit encoding. Otherwise, we
+        // can use 8-bit encoding
+        boolean useUTF16 = false;
+        int strlen = string.length();
+
+        for ( int j = 0; j < strlen; j++ )
+        {
+            if ( string.charAt( j ) > 255 )
+        {
+                useUTF16 = true;
+                break;
+            }
+        }
+        if (useUTF16)
+          //Set the uncompressed bit
+          field_2_optionflags = highByte.setByte(field_2_optionflags);
+        else field_2_optionflags = highByte.clearByte(field_2_optionflags);
+    }
+
+    public int getFormatRunCount() {
+      if (field_4_format_runs == null)
+        return 0;
+      return field_4_format_runs.size();
+    }
+
+    public FormatRun getFormatRun(int index) {
+      if (field_4_format_runs == null) {
+               return null;
+       }
+      if (index < 0 || index >= field_4_format_runs.size()) {
+               return null;
+       }
+      return field_4_format_runs.get(index);
+    }
+
+    private int findFormatRunAt(int characterPos) {
+      int size = field_4_format_runs.size();
+      for (int i=0;i<size;i++) {
+        FormatRun r = field_4_format_runs.get(i);
+        if (r._character == characterPos)
+          return i;
+        else if (r._character > characterPos)
+          return -1;
+      }
+      return -1;
+    }
+
+    /** Adds a font run to the formatted string.
+     *
+     *  If a font run exists at the current charcter location, then it is
+     *  replaced with the font run to be added.
+     */
+    public void addFormatRun(FormatRun r) {
+      if (field_4_format_runs == null) {
+               field_4_format_runs = new ArrayList<FormatRun>();
+       }
+
+      int index = findFormatRunAt(r._character);
+      if (index != -1)
+         field_4_format_runs.remove(index);
+
+      field_4_format_runs.add(r);
+      //Need to sort the font runs to ensure that the font runs appear in
+      //character order
+      Collections.sort(field_4_format_runs);
+
+      //Make sure that we now say that we are a rich string
+      field_2_optionflags = richText.setByte(field_2_optionflags);
+    }
+
+    public Iterator<FormatRun> formatIterator() {
+      if (field_4_format_runs != null) {
+        return field_4_format_runs.iterator();
+      }
+      return null;
+    }
+
+    public void removeFormatRun(FormatRun r) {
+      field_4_format_runs.remove(r);
+      if (field_4_format_runs.size() == 0) {
+        field_4_format_runs = null;
+        field_2_optionflags = richText.clearByte(field_2_optionflags);
+      }
+    }
+
+    public void clearFormatting() {
+      field_4_format_runs = null;
+      field_2_optionflags = richText.clearByte(field_2_optionflags);
+    }
+
+
+    void setExtendedRst(byte[] ext_rst) {
+      if (ext_rst != null)
+        field_2_optionflags = extBit.setByte(field_2_optionflags);
+      else field_2_optionflags = extBit.clearByte(field_2_optionflags);
+      this.field_5_ext_rst = ext_rst;
+    }
+
+
+    /**
+     * Swaps all use in the string of one font index
+     *  for use of a different font index.
+     * Normally only called when fonts have been
+     *  removed / re-ordered
+     */
+    public void swapFontUse(short oldFontIndex, short newFontIndex) {
+        for (FormatRun run : field_4_format_runs) {
+            if(run._fontIndex == oldFontIndex) {
+                run._fontIndex = newFontIndex;
+            }
+        }
+    }
+
+    /**
+     * unlike the real records we return the same as "getString()" rather than debug info
+     * @see #getDebugInfo()
+     * @return String value of the record
+     */
+
+    public String toString()
+    {
+        return getString();
+    }
+
+    /**
+     * return a character representation of the fields of this record
+     *
+     *
+     * @return String of output for biffviewer etc.
+     *
+     */
+    public String getDebugInfo()
+    {
+        StringBuffer buffer = new StringBuffer();
+
+        buffer.append("[UNICODESTRING]\n");
+        buffer.append("    .charcount       = ")
+            .append(Integer.toHexString(getCharCount())).append("\n");
+        buffer.append("    .optionflags     = ")
+            .append(Integer.toHexString(getOptionFlags())).append("\n");
+        buffer.append("    .string          = ").append(getString()).append("\n");
+        if (field_4_format_runs != null) {
+          for (int i = 0; i < field_4_format_runs.size();i++) {
+            FormatRun r = field_4_format_runs.get(i);
+            buffer.append("      .format_run"+i+"          = ").append(r.toString()).append("\n");
+          }
+        }
+        if (field_5_ext_rst != null) {
+          buffer.append("    .field_5_ext_rst          = ").append("\n").append(HexDump.toHex(field_5_ext_rst)).append("\n");
+        }
+        buffer.append("[/UNICODESTRING]\n");
+        return buffer.toString();
+    }
+
+    public void serialize(ContinuableRecordOutput out) {
+        int numberOfRichTextRuns = 0;
+        int extendedDataSize = 0;
+        if (isRichText() && field_4_format_runs != null) {
+            numberOfRichTextRuns = field_4_format_runs.size();
+        }
+        if (isExtendedText() && field_5_ext_rst != null) {
+            extendedDataSize = field_5_ext_rst.length;
+        }
+
+        out.writeString(field_3_string, numberOfRichTextRuns, extendedDataSize);
+
+        if (numberOfRichTextRuns > 0) {
+
+          //This will ensure that a run does not split a continue
+          for (int i=0;i<numberOfRichTextRuns;i++) {
+              if (out.getAvailableSpace() < 4) {
+                  out.writeContinue();
+              }
+                FormatRun r = field_4_format_runs.get(i);
+                r.serialize(out);
+          }
+        }
+
+        if (extendedDataSize > 0) {
+            // OK ExtRst is actually not documented, so i am going to hope
+            // that we can actually continue on byte boundaries
+
+            int extPos = 0;
+            while (true) {
+                int nBytesToWrite = Math.min(extendedDataSize - extPos, out.getAvailableSpace());
+                out.write(field_5_ext_rst, extPos, nBytesToWrite);
+                extPos += nBytesToWrite;
+                if (extPos >= extendedDataSize) {
+                    break;
+                }
+                out.writeContinue();
+            }
+        }
+    }
+
+    public int compareTo(UnicodeString str) {
+
+        int result = getString().compareTo(str.getString());
+
+        //As per the equals method lets do this in stages
+        if (result != 0)
+          return result;
+
+        //OK string appears to be equal but now lets compare formatting runs
+        if ((field_4_format_runs == null) && (str.field_4_format_runs == null))
+          //Strings are equal, and there are no formatting runs.
+          return 0;
+
+        if ((field_4_format_runs == null) && (str.field_4_format_runs != null))
+          //Strings are equal, but one or the other has formatting runs
+          return 1;
+        if ((field_4_format_runs != null) && (str.field_4_format_runs == null))
+          //Strings are equal, but one or the other has formatting runs
+          return -1;
+
+        //Strings are equal, so now compare formatting runs.
+        int size = field_4_format_runs.size();
+        if (size != str.field_4_format_runs.size())
+          return size - str.field_4_format_runs.size();
+
+        for (int i=0;i<size;i++) {
+          FormatRun run1 = field_4_format_runs.get(i);
+          FormatRun run2 = str.field_4_format_runs.get(i);
+
+          result = run1.compareTo(run2);
+          if (result != 0)
+            return result;
+        }
+
+        //Well the format runs are equal as well!, better check the ExtRst data
+        //Which by the way we don't know how to decode!
+        if ((field_5_ext_rst == null) && (str.field_5_ext_rst == null))
+          return 0;
+        if ((field_5_ext_rst == null) && (str.field_5_ext_rst != null))
+         return 1;
+        if ((field_5_ext_rst != null) && (str.field_5_ext_rst == null))
+          return -1;
+
+        size = field_5_ext_rst.length;
+        if (size != field_5_ext_rst.length)
+          return size - field_5_ext_rst.length;
+
+        //Check individual bytes!
+        for (int i=0;i<size;i++) {
+          if (field_5_ext_rst[i] != str.field_5_ext_rst[i])
+            return field_5_ext_rst[i] - str.field_5_ext_rst[i];
+        }
+        //Phew!! After all of that we have finally worked out that the strings
+        //are identical.
+        return 0;
+    }
+
+    private boolean isRichText()
+    {
+      return richText.isSet(getOptionFlags());
+    }
+
+    private boolean isExtendedText()
+        {
+        return extBit.isSet(getOptionFlags());
+    }
+
+    public Object clone() {
+        UnicodeString str = new UnicodeString();
+        str.field_1_charCount = field_1_charCount;
+        str.field_2_optionflags = field_2_optionflags;
+        str.field_3_string = field_3_string;
+        if (field_4_format_runs != null) {
+          str.field_4_format_runs = new ArrayList<FormatRun>();
+          for (FormatRun r : field_4_format_runs) {
+            str.field_4_format_runs.add(new FormatRun(r._character, r._fontIndex));
+            }
+        }
+        if (field_5_ext_rst != null) {
+          str.field_5_ext_rst = new byte[field_5_ext_rst.length];
+          System.arraycopy(field_5_ext_rst, 0, str.field_5_ext_rst, 0,
+                           field_5_ext_rst.length);
+        }
+
+        return str;
+    }
+}
index 071f1b43138a227a681b152501585a043cc8893c..ac7ab6f911a6fe33bac1862d3608cea87bc7b195 100644 (file)
@@ -41,8 +41,8 @@ import org.apache.poi.hssf.record.Record;
 import org.apache.poi.hssf.record.RecordBase;
 import org.apache.poi.hssf.record.SubRecord;
 import org.apache.poi.hssf.record.TextObjectRecord;
-import org.apache.poi.hssf.record.UnicodeString;
 import org.apache.poi.hssf.record.aggregates.FormulaRecordAggregate;
+import org.apache.poi.hssf.record.common.UnicodeString;
 import org.apache.poi.hssf.record.formula.ExpPtg;
 import org.apache.poi.hssf.record.formula.Ptg;
 import org.apache.poi.hssf.record.formula.eval.ErrorEval;
index 0de860214ab46d55101e16b8eb492cf6b5252068..66327d9417b74e43285b4f51d0f2e2eb477c4dfa 100644 (file)
@@ -21,7 +21,7 @@ import java.util.Iterator;
 
 import org.apache.poi.hssf.record.ExtendedFormatRecord;
 import org.apache.poi.hssf.record.FontRecord;
-import org.apache.poi.hssf.record.UnicodeString;
+import org.apache.poi.hssf.record.common.UnicodeString;
 
 /**
  * Excel can get cranky if you give it files containing too
index e69a203b9f8fcfb6d34b3e2771a2d6ceafa295f0..f8e3871bcf15aafb66719efe85a0681403e0899f 100644 (file)
@@ -21,7 +21,7 @@ import java.util.Iterator;
 
 import org.apache.poi.hssf.model.InternalWorkbook;
 import org.apache.poi.hssf.record.LabelSSTRecord;
-import org.apache.poi.hssf.record.UnicodeString;
+import org.apache.poi.hssf.record.common.UnicodeString;
 import org.apache.poi.ss.usermodel.Font;
 import org.apache.poi.ss.usermodel.RichTextString;
 /**
index 5c8c30eed72f7e9fed5c9030c8fb8345e84085c5..97e534be2a38d604ab0c2dc7341ae6cb700f4814 100644 (file)
@@ -52,9 +52,9 @@ import org.apache.poi.hssf.record.ObjRecord;
 import org.apache.poi.hssf.record.Record;
 import org.apache.poi.hssf.record.RecordFactory;
 import org.apache.poi.hssf.record.SSTRecord;
-import org.apache.poi.hssf.record.UnicodeString;
 import org.apache.poi.hssf.record.UnknownRecord;
 import org.apache.poi.hssf.record.aggregates.RecordAggregate.RecordVisitor;
+import org.apache.poi.hssf.record.common.UnicodeString;
 import org.apache.poi.hssf.record.formula.Area3DPtg;
 import org.apache.poi.hssf.record.formula.MemFuncPtg;
 import org.apache.poi.hssf.record.formula.NameXPtg;
index 2d2784c2daeaccae2aaff2f4666893daa7f6d773..72e055fe70a1192d9d42c2d8bcedede4e5c3bd69 100644 (file)
@@ -24,7 +24,7 @@ import junit.framework.TestCase;
 
 import org.apache.poi.hssf.HSSFTestDataSamples;
 import org.apache.poi.hssf.record.NameRecord;
-import org.apache.poi.hssf.record.UnicodeString;
+import org.apache.poi.hssf.record.common.UnicodeString;
 import org.apache.poi.hssf.record.constant.ErrorConstant;
 import org.apache.poi.hssf.record.formula.AbstractFunctionPtg;
 import org.apache.poi.hssf.record.formula.AddPtg;
index 6d913bb5dfee3f790893b3b284ddd0ff007f101d..d49475f5cbdad6e6decd053acdd90ea16f029900 100644 (file)
@@ -23,6 +23,7 @@ import junit.framework.TestSuite;
 import org.apache.poi.hssf.record.aggregates.AllRecordAggregateTests;
 import org.apache.poi.hssf.record.cf.TestCellRange;
 import org.apache.poi.hssf.record.chart.AllChartRecordTests;
+import org.apache.poi.hssf.record.common.TestUnicodeString;
 import org.apache.poi.hssf.record.constant.TestConstantValueParser;
 import org.apache.poi.hssf.record.crypto.AllHSSFEncryptionTests;
 import org.apache.poi.hssf.record.formula.AllFormulaTests;
index 4a9b7c0b8cfd4728420a4a09b538febeb1507598..65d56cf318ecdefe15730535bfc8090c934c5ced 100644 (file)
@@ -30,6 +30,7 @@ import junit.framework.AssertionFailedError;
 import junit.framework.TestCase;
 
 import org.apache.poi.hssf.HSSFTestDataSamples;
+import org.apache.poi.hssf.record.common.UnicodeString;
 import org.apache.poi.hssf.usermodel.HSSFSheet;
 import org.apache.poi.hssf.usermodel.HSSFWorkbook;
 import org.apache.poi.util.HexRead;
index 2a0830ac79ddfd582ffdc83ea0fb03aab721955d..b171a77a134678a02f7ae3ab70537351bd59a6ad 100644 (file)
@@ -19,6 +19,7 @@ package org.apache.poi.hssf.record;
 
 import junit.framework.TestCase;
 
+import org.apache.poi.hssf.record.common.UnicodeString;
 import org.apache.poi.hssf.record.cont.ContinuableRecordOutput;
 import org.apache.poi.util.IntMapper;
 
diff --git a/src/testcases/org/apache/poi/hssf/record/TestUnicodeString.java b/src/testcases/org/apache/poi/hssf/record/TestUnicodeString.java
deleted file mode 100644 (file)
index 1a80f9e..0000000
+++ /dev/null
@@ -1,168 +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 junit.framework.TestCase;
-
-import org.apache.poi.hssf.record.cont.ContinuableRecordOutput;
-
-/**
- * Tests that {@link UnicodeString} record size calculates correctly.  The record size
- * is used when serializing {@link SSTRecord}s.
- *
- * @author Jason Height (jheight at apache.org)
- */
-public final class TestUnicodeString extends TestCase {
-    private static final int MAX_DATA_SIZE = RecordInputStream.MAX_RECORD_DATA_SIZE;
-
-    /** a 4 character string requiring 16 bit encoding */
-    private static final String STR_16_BIT = "A\u591A\u8A00\u8A9E";
-
-    private static void confirmSize(int expectedSize, UnicodeString s) {
-        confirmSize(expectedSize, s, 0);
-    }
-    /**
-     * Note - a value of zero for <tt>amountUsedInCurrentRecord</tt> would only ever occur just
-     * after a {@link ContinueRecord} had been started.  In the initial {@link SSTRecord} this 
-     * value starts at 8 (for the first {@link UnicodeString} written).  In general, it can be
-     * any value between 0 and {@link #MAX_DATA_SIZE}
-     */
-    private static void confirmSize(int expectedSize, UnicodeString s, int amountUsedInCurrentRecord) {
-        ContinuableRecordOutput out = ContinuableRecordOutput.createForCountingOnly();
-        out.writeContinue();
-        for(int i=amountUsedInCurrentRecord; i>0; i--) {
-            out.writeByte(0);
-        }
-        int size0 = out.getTotalSize();
-        s.serialize(out);
-        int size1 = out.getTotalSize();
-        int actualSize = size1-size0;
-        assertEquals(expectedSize, actualSize);
-    }
-
-    public void testSmallStringSize() {
-        //Test a basic string
-        UnicodeString s = makeUnicodeString("Test");
-        confirmSize(7, s);
-
-        //Test a small string that is uncompressed
-        s = makeUnicodeString(STR_16_BIT);
-        s.setOptionFlags((byte)0x01);
-        confirmSize(11, s);
-
-        //Test a compressed small string that has rich text formatting
-        s.setString("Test");
-        s.setOptionFlags((byte)0x8);
-        UnicodeString.FormatRun r = new UnicodeString.FormatRun((short)0,(short)1);
-        s.addFormatRun(r);
-        UnicodeString.FormatRun r2 = new UnicodeString.FormatRun((short)2,(short)2);
-        s.addFormatRun(r2);
-        confirmSize(17, s);
-
-        //Test a uncompressed small string that has rich text formatting
-        s.setString(STR_16_BIT);
-        s.setOptionFlags((byte)0x9);
-        confirmSize(21, s);
-
-        //Test a compressed small string that has rich text and extended text
-        s.setString("Test");
-        s.setOptionFlags((byte)0xC);
-        s.setExtendedRst(new byte[]{(byte)0x1,(byte)0x2,(byte)0x3,(byte)0x4,(byte)0x5});
-        confirmSize(26, s);
-
-        //Test a uncompressed small string that has rich text and extended text
-        s.setString(STR_16_BIT);
-        s.setOptionFlags((byte)0xD);
-        confirmSize(30, s);
-    }
-
-    public void testPerfectStringSize() {
-      //Test a basic string
-      UnicodeString s = makeUnicodeString(MAX_DATA_SIZE-2-1);
-      confirmSize(MAX_DATA_SIZE, s);
-
-      //Test an uncompressed string
-      //Note that we can only ever get to a maximim size of 8227 since an uncompressed
-      //string is writing double bytes.
-      s = makeUnicodeString((MAX_DATA_SIZE-2-1)/2, true);
-      s.setOptionFlags((byte)0x1);
-      confirmSize(MAX_DATA_SIZE-1, s);
-    }
-
-    public void testPerfectRichStringSize() {
-      //Test a rich text string
-      UnicodeString s = makeUnicodeString(MAX_DATA_SIZE-2-1-8-2);
-      s.addFormatRun(new UnicodeString.FormatRun((short)1,(short)0));
-      s.addFormatRun(new UnicodeString.FormatRun((short)2,(short)1));
-      s.setOptionFlags((byte)0x8);
-      confirmSize(MAX_DATA_SIZE, s);
-
-      //Test an uncompressed rich text string
-      //Note that we can only ever get to a maximum size of 8227 since an uncompressed
-      //string is writing double bytes.
-      s = makeUnicodeString((MAX_DATA_SIZE-2-1-8-2)/2, true);
-      s.addFormatRun(new UnicodeString.FormatRun((short)1,(short)0));
-      s.addFormatRun(new UnicodeString.FormatRun((short)2,(short)1));
-      s.setOptionFlags((byte)0x9);
-      confirmSize(MAX_DATA_SIZE-1, s);
-    }
-
-    public void testContinuedStringSize() {
-      //Test a basic string
-      UnicodeString s = makeUnicodeString(MAX_DATA_SIZE-2-1+20);
-      confirmSize(MAX_DATA_SIZE+4+1+20, s);
-    }
-
-    /** Tests that a string size calculation that fits neatly in two records, the second being a continue*/
-    public void testPerfectContinuedStringSize() {
-      //Test a basic string
-      int strSize = MAX_DATA_SIZE*2;
-      //String overhead
-      strSize -= 3;
-      //Continue Record overhead
-      strSize -= 4;
-      //Continue Record additional byte overhead
-      strSize -= 1;
-      UnicodeString s = makeUnicodeString(strSize);
-      confirmSize(MAX_DATA_SIZE*2, s);
-    }
-
-
-    private static UnicodeString makeUnicodeString(String s) {
-      UnicodeString st = new UnicodeString(s);
-      st.setOptionFlags((byte)0);
-      return st;
-    }
-
-    private static UnicodeString makeUnicodeString(int numChars) {
-        return makeUnicodeString(numChars, false);
-    }
-    /**
-     * @param is16Bit if <code>true</code> the created string will have characters > 0x00FF
-     * @return a string of the specified number of characters
-     */
-    private static UnicodeString makeUnicodeString(int numChars, boolean is16Bit) {
-      StringBuffer b = new StringBuffer(numChars);
-      int charBase = is16Bit ? 0x8A00 : 'A';
-      for (int i=0;i<numChars;i++) {
-        char ch = (char) ((i%16)+charBase);
-        b.append(ch);
-      }
-      return makeUnicodeString(b.toString());
-    }
-}
diff --git a/src/testcases/org/apache/poi/hssf/record/common/TestUnicodeString.java b/src/testcases/org/apache/poi/hssf/record/common/TestUnicodeString.java
new file mode 100644 (file)
index 0000000..6ecab71
--- /dev/null
@@ -0,0 +1,171 @@
+/* ====================================================================
+   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.common;
+
+import junit.framework.TestCase;
+
+import org.apache.poi.hssf.record.ContinueRecord;
+import org.apache.poi.hssf.record.RecordInputStream;
+import org.apache.poi.hssf.record.SSTRecord;
+import org.apache.poi.hssf.record.cont.ContinuableRecordOutput;
+
+/**
+ * Tests that {@link UnicodeString} record size calculates correctly.  The record size
+ * is used when serializing {@link SSTRecord}s.
+ *
+ * @author Jason Height (jheight at apache.org)
+ */
+public final class TestUnicodeString extends TestCase {
+    private static final int MAX_DATA_SIZE = RecordInputStream.MAX_RECORD_DATA_SIZE;
+
+    /** a 4 character string requiring 16 bit encoding */
+    private static final String STR_16_BIT = "A\u591A\u8A00\u8A9E";
+
+    private static void confirmSize(int expectedSize, UnicodeString s) {
+        confirmSize(expectedSize, s, 0);
+    }
+    /**
+     * Note - a value of zero for <tt>amountUsedInCurrentRecord</tt> would only ever occur just
+     * after a {@link ContinueRecord} had been started.  In the initial {@link SSTRecord} this 
+     * value starts at 8 (for the first {@link UnicodeString} written).  In general, it can be
+     * any value between 0 and {@link #MAX_DATA_SIZE}
+     */
+    private static void confirmSize(int expectedSize, UnicodeString s, int amountUsedInCurrentRecord) {
+        ContinuableRecordOutput out = ContinuableRecordOutput.createForCountingOnly();
+        out.writeContinue();
+        for(int i=amountUsedInCurrentRecord; i>0; i--) {
+            out.writeByte(0);
+        }
+        int size0 = out.getTotalSize();
+        s.serialize(out);
+        int size1 = out.getTotalSize();
+        int actualSize = size1-size0;
+        assertEquals(expectedSize, actualSize);
+    }
+
+    public void testSmallStringSize() {
+        //Test a basic string
+        UnicodeString s = makeUnicodeString("Test");
+        confirmSize(7, s);
+
+        //Test a small string that is uncompressed
+        s = makeUnicodeString(STR_16_BIT);
+        s.setOptionFlags((byte)0x01);
+        confirmSize(11, s);
+
+        //Test a compressed small string that has rich text formatting
+        s.setString("Test");
+        s.setOptionFlags((byte)0x8);
+        UnicodeString.FormatRun r = new UnicodeString.FormatRun((short)0,(short)1);
+        s.addFormatRun(r);
+        UnicodeString.FormatRun r2 = new UnicodeString.FormatRun((short)2,(short)2);
+        s.addFormatRun(r2);
+        confirmSize(17, s);
+
+        //Test a uncompressed small string that has rich text formatting
+        s.setString(STR_16_BIT);
+        s.setOptionFlags((byte)0x9);
+        confirmSize(21, s);
+
+        //Test a compressed small string that has rich text and extended text
+        s.setString("Test");
+        s.setOptionFlags((byte)0xC);
+        s.setExtendedRst(new byte[]{(byte)0x1,(byte)0x2,(byte)0x3,(byte)0x4,(byte)0x5});
+        confirmSize(26, s);
+
+        //Test a uncompressed small string that has rich text and extended text
+        s.setString(STR_16_BIT);
+        s.setOptionFlags((byte)0xD);
+        confirmSize(30, s);
+    }
+
+    public void testPerfectStringSize() {
+      //Test a basic string
+      UnicodeString s = makeUnicodeString(MAX_DATA_SIZE-2-1);
+      confirmSize(MAX_DATA_SIZE, s);
+
+      //Test an uncompressed string
+      //Note that we can only ever get to a maximim size of 8227 since an uncompressed
+      //string is writing double bytes.
+      s = makeUnicodeString((MAX_DATA_SIZE-2-1)/2, true);
+      s.setOptionFlags((byte)0x1);
+      confirmSize(MAX_DATA_SIZE-1, s);
+    }
+
+    public void testPerfectRichStringSize() {
+      //Test a rich text string
+      UnicodeString s = makeUnicodeString(MAX_DATA_SIZE-2-1-8-2);
+      s.addFormatRun(new UnicodeString.FormatRun((short)1,(short)0));
+      s.addFormatRun(new UnicodeString.FormatRun((short)2,(short)1));
+      s.setOptionFlags((byte)0x8);
+      confirmSize(MAX_DATA_SIZE, s);
+
+      //Test an uncompressed rich text string
+      //Note that we can only ever get to a maximum size of 8227 since an uncompressed
+      //string is writing double bytes.
+      s = makeUnicodeString((MAX_DATA_SIZE-2-1-8-2)/2, true);
+      s.addFormatRun(new UnicodeString.FormatRun((short)1,(short)0));
+      s.addFormatRun(new UnicodeString.FormatRun((short)2,(short)1));
+      s.setOptionFlags((byte)0x9);
+      confirmSize(MAX_DATA_SIZE-1, s);
+    }
+
+    public void testContinuedStringSize() {
+      //Test a basic string
+      UnicodeString s = makeUnicodeString(MAX_DATA_SIZE-2-1+20);
+      confirmSize(MAX_DATA_SIZE+4+1+20, s);
+    }
+
+    /** Tests that a string size calculation that fits neatly in two records, the second being a continue*/
+    public void testPerfectContinuedStringSize() {
+      //Test a basic string
+      int strSize = MAX_DATA_SIZE*2;
+      //String overhead
+      strSize -= 3;
+      //Continue Record overhead
+      strSize -= 4;
+      //Continue Record additional byte overhead
+      strSize -= 1;
+      UnicodeString s = makeUnicodeString(strSize);
+      confirmSize(MAX_DATA_SIZE*2, s);
+    }
+
+
+    private static UnicodeString makeUnicodeString(String s) {
+      UnicodeString st = new UnicodeString(s);
+      st.setOptionFlags((byte)0);
+      return st;
+    }
+
+    private static UnicodeString makeUnicodeString(int numChars) {
+        return makeUnicodeString(numChars, false);
+    }
+    /**
+     * @param is16Bit if <code>true</code> the created string will have characters > 0x00FF
+     * @return a string of the specified number of characters
+     */
+    private static UnicodeString makeUnicodeString(int numChars, boolean is16Bit) {
+      StringBuffer b = new StringBuffer(numChars);
+      int charBase = is16Bit ? 0x8A00 : 'A';
+      for (int i=0;i<numChars;i++) {
+        char ch = (char) ((i%16)+charBase);
+        b.append(ch);
+      }
+      return makeUnicodeString(b.toString());
+    }
+}
index 740cd8e59976b94881b041b4f27fbac31dc8ef47..d96ad74662a3d4532ca20f90341b37b715a3b4c2 100644 (file)
@@ -1536,4 +1536,14 @@ public final class TestBugs extends BaseTestBugzillaIssues {
     public void test47251() {
         openSample("47251.xls");
     }
+    
+    /**
+     * Round trip a file with an unusual ExtRst record
+     */
+    public void test47847() {
+       HSSFWorkbook wb = openSample("47251.xls");
+       assertEquals(1, wb.getNumberOfSheets());
+       wb = writeOutAndReadBack(wb);
+       assertEquals(1, wb.getNumberOfSheets());
+    }
 }