From f6b247a19c3abfe183ebdf63e27a88911be4110e Mon Sep 17 00:00:00 2001 From: "(no author)" <(no author)@unknown> Date: Sat, 1 Jun 2002 02:25:53 +0000 Subject: [PATCH] This commit was manufactured by cvs2svn to create tag 'REL_1_5_BRANCH_MERGE3'. git-svn-id: https://svn.apache.org/repos/asf/jakarta/poi/tags/REL_1_5_BRANCH_MERGE3@352659 13f79535-47bb-0310-9956-ffa450edef68 --- src/documentation/images/logoRaPiGmbH1.png | Bin 0 -> 5976 bytes src/documentation/images/logoRaPiGmbH2.png | Bin 0 -> 6001 bytes src/documentation/images/logoRaPiGmbH3.png | Bin 0 -> 3594 bytes src/documentation/images/logoRaPiGmbH4.png | Bin 0 -> 2658 bytes src/documentation/images/logoRaPiGmbH5.png | Bin 0 -> 7423 bytes src/documentation/images/logoRaPiGmbH6.png | Bin 0 -> 6640 bytes src/documentation/images/logoRaPiGmbH7.png | Bin 0 -> 9630 bytes src/documentation/xdocs/book.xml | 1 + src/documentation/xdocs/branching.xml | 97 ++ src/documentation/xdocs/historyandfuture.xml | 2 +- src/documentation/xdocs/news/logocontest.xml | 177 ++-- .../org/apache/poi/hssf/dev/BiffViewer.java | 8 +- .../org/apache/poi/hssf/model/Workbook.java | 17 +- .../poi/hssf/record/ContinueRecord.java | 7 +- .../poi/hssf/record/RecordProcessor.java | 202 ++++ .../org/apache/poi/hssf/record/RowRecord.java | 4 +- .../poi/hssf/record/SSTDeserializer.java | 357 +++++++ .../org/apache/poi/hssf/record/SSTRecord.java | 901 +++--------------- .../apache/poi/hssf/record/SSTSerializer.java | 356 +++++++ .../apache/poi/hssf/record/UnicodeString.java | 61 +- .../apache/poi/hssf/usermodel/HSSFRow.java | 28 +- .../apache/poi/hssf/usermodel/HSSFSheet.java | 159 ++-- .../org/apache/poi/util/LittleEndian.java | 13 + .../org/apache/poi/hssf/data/duprich1.xls | Bin 0 -> 21504 bytes .../org/apache/poi/hssf/data/duprich2.xls | Bin 0 -> 13824 bytes .../apache/poi/hssf/record/TestSSTRecord.java | 617 ++++++------ .../poi/hssf/usermodel/TestHSSFRow.java | 39 +- .../poi/hssf/usermodel/TestHSSFSheet.java | 10 +- .../org/apache/poi/util/TestLittleEndian.java | 6 + 29 files changed, 1741 insertions(+), 1321 deletions(-) create mode 100644 src/documentation/images/logoRaPiGmbH1.png create mode 100644 src/documentation/images/logoRaPiGmbH2.png create mode 100644 src/documentation/images/logoRaPiGmbH3.png create mode 100644 src/documentation/images/logoRaPiGmbH4.png create mode 100644 src/documentation/images/logoRaPiGmbH5.png create mode 100644 src/documentation/images/logoRaPiGmbH6.png create mode 100644 src/documentation/images/logoRaPiGmbH7.png create mode 100644 src/documentation/xdocs/branching.xml create mode 100644 src/java/org/apache/poi/hssf/record/RecordProcessor.java create mode 100644 src/java/org/apache/poi/hssf/record/SSTDeserializer.java create mode 100644 src/java/org/apache/poi/hssf/record/SSTSerializer.java create mode 100644 src/testcases/org/apache/poi/hssf/data/duprich1.xls create mode 100644 src/testcases/org/apache/poi/hssf/data/duprich2.xls diff --git a/src/documentation/images/logoRaPiGmbH1.png b/src/documentation/images/logoRaPiGmbH1.png new file mode 100644 index 0000000000000000000000000000000000000000..3bef8bb6e9583059dfe31df3f3b3a69fb3ba6354 GIT binary patch literal 5976 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 CknTVL literal 0 HcmV?d00001 diff --git a/src/documentation/images/logoRaPiGmbH2.png b/src/documentation/images/logoRaPiGmbH2.png new file mode 100644 index 0000000000000000000000000000000000000000..d842217557b9b3a3e18ff41459d9a76c41cf4d14 GIT binary patch literal 6001 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 QI{*Lx07*qoM6N<$f?TE37XSbN literal 0 HcmV?d00001 diff --git a/src/documentation/images/logoRaPiGmbH4.png b/src/documentation/images/logoRaPiGmbH4.png new file mode 100644 index 0000000000000000000000000000000000000000..3b8d44d6e49d3dc917088c4fe5d6c1c199f51915 GIT binary patch literal 2658 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 E1Lh8c1poj5 literal 0 HcmV?d00001 diff --git a/src/documentation/xdocs/book.xml b/src/documentation/xdocs/book.xml index e5ab10098e..1d4b945e38 100644 --- a/src/documentation/xdocs/book.xml +++ b/src/documentation/xdocs/book.xml @@ -39,6 +39,7 @@ + diff --git a/src/documentation/xdocs/branching.xml b/src/documentation/xdocs/branching.xml new file mode 100644 index 0000000000..4246d83443 --- /dev/null +++ b/src/documentation/xdocs/branching.xml @@ -0,0 +1,97 @@ + + + + + +
+ Branching + + + +
+ + +
+

+ Branches are tagged in the following way: +

+
    +
  • REL_1_5_BRANCH
  • +
  • REL_2_0_BRANCH
  • +
+

+ Merge points should be tagged as follows: +

+
    +
  • REL_1_5_BRANCH_MERGE1
  • +
  • REL_1_5_BRANCH_MERGE2
  • +
  • etc...
  • +
+

+ Releases should be tagged as: +

+
    +
  • REL_1_5
  • +
  • REL_1_5_1
  • +
  • REL_1_5_2
  • +
  • etc...
  • +
+ +
+
+

+ Don't forget which branch you are currently on. This is critically + important. Committing stuff to the wrong branch causes all sorts of + headaches. Best to name your checkout after the branch you are on. +

+
+
+

+ All branching is currently managed by Glen Stampoultzis. If you wish + to create your own branch please let him know. Merging is also + handled by Glen. Just pop him a mail if you feel it's necessary to + create a branch or perform a merge. +

+

+ The reason to go through a single point for branching is that it can be + an easy thing to get wrong. Having a single person managing branches + means there is less chance of getting getting our wires crossed with this + difficult area of CVS. +

+
+
+

+ The following branches are currently active: +

+ + + + + + + + + + + + + +
+ Branch + + Description +
+ HEAD + + This is the trunk and is always active. Currently it is being used to continue development + of the 2.0 release. +
+ REL_1_5_BRANCH + + All bug fixes not specifically relevant to the 2.0 work should be placed in this branch. + From here they will merged back to the trunk and the merge point marked. +
+
+ + +
\ No newline at end of file diff --git a/src/documentation/xdocs/historyandfuture.xml b/src/documentation/xdocs/historyandfuture.xml index 3855480f69..741f6aad5c 100755 --- a/src/documentation/xdocs/historyandfuture.xml +++ b/src/documentation/xdocs/historyandfuture.xml @@ -3,7 +3,7 @@
- + Project History diff --git a/src/documentation/xdocs/news/logocontest.xml b/src/documentation/xdocs/news/logocontest.xml index 9c45d06c66..9d9179c865 100644 --- a/src/documentation/xdocs/news/logocontest.xml +++ b/src/documentation/xdocs/news/logocontest.xml @@ -2,114 +2,139 @@ -
- - - - -
- - -
-

- Here are the current logo submissions. Thanks to the artists! -

-
-

- +

+ + + + + +
+ + +
+

+ Here are the current logo submissions. Thanks to the artists! +

+
+

+

-
-
+
+

-     - +     +

-
-
+
+

- +

-
-
+
+

-     - +     +

-
-
+
+

-     -     - +     +     +

-
-
+
+

-     -     - +     +     +

-     - +     +

-
-
+
+

- +

-
-
+
+

-     - +     +

-
-
+
+

-     - +     +

-
-
+
+

-     - +     +

-     - +     +

-     - +     +

-     - +     +

-     - +     +

-     - +     +

-
-
+
+
+

+     + +

+
+

-     - + Contact Person: Fancy at: fancy at my-feiqi.com

+

+     + +

+

+ +

+

+ +

+

+ +

+

+ +

+

+ +

+
+
- -
- -
- - Copyright (c) @year@ The Apache Software Foundation All rights reserved. - $Revision$ $Date$ - -
+ +
+ + Copyright (c) @year@ The Apache Software Foundation All rights reserved. + $Revision$ $Date$ + +
diff --git a/src/java/org/apache/poi/hssf/dev/BiffViewer.java b/src/java/org/apache/poi/hssf/dev/BiffViewer.java index 12267d23d8..7d366f1455 100644 --- a/src/java/org/apache/poi/hssf/dev/BiffViewer.java +++ b/src/java/org/apache/poi/hssf/dev/BiffViewer.java @@ -631,12 +631,12 @@ public class BiffViewer retval = new LinkedDataRecord(rectype, size, data); break; - case FormulaRecord.sid: - retval = new FormulaRecord(rectype, size, data); - break; +// case FormulaRecord.sid: +// retval = new FormulaRecord(rectype, size, data); +// break; case SheetPropertiesRecord.sid: - retval = new FormulaRecord(rectype, size, data); + retval = new SheetPropertiesRecord(rectype, size, data); break; diff --git a/src/java/org/apache/poi/hssf/model/Workbook.java b/src/java/org/apache/poi/hssf/model/Workbook.java index d45153f9c9..55fe659322 100644 --- a/src/java/org/apache/poi/hssf/model/Workbook.java +++ b/src/java/org/apache/poi/hssf/model/Workbook.java @@ -643,26 +643,11 @@ public class Workbook { log.log(DEBUG, "Serializing Workbook with offsets"); - // ArrayList bytes = new ArrayList(records.size()); -// int arraysize = getSize(); // 0; int pos = 0; -// for (int k = 0; k < records.size(); k++) -// { -// bytes.add((( Record ) records.get(k)).serialize()); -// -// } -// for (int k = 0; k < bytes.size(); k++) -// { -// arraysize += (( byte [] ) bytes.get(k)).length; -// } for (int k = 0; k < records.size(); k++) { - - // byte[] rec = (( byte [] ) bytes.get(k)); - // System.arraycopy(rec, 0, data, offset + pos, rec.length); - pos += (( Record ) records.get(k)).serialize(pos + offset, - data); // rec.length; + pos += (( Record ) records.get(k)).serialize(pos + offset, data); // rec.length; } log.log(DEBUG, "Exiting serialize workbook"); return pos; diff --git a/src/java/org/apache/poi/hssf/record/ContinueRecord.java b/src/java/org/apache/poi/hssf/record/ContinueRecord.java index 2b67a62d40..5017ade922 100644 --- a/src/java/org/apache/poi/hssf/record/ContinueRecord.java +++ b/src/java/org/apache/poi/hssf/record/ContinueRecord.java @@ -161,9 +161,7 @@ public class ContinueRecord // how many continue records do we need // System.out.println("In ProcessContinue"); - int records = - (data.length - / 8214); // we've a 1 offset but we're also off by one due to rounding...so it balances out + int records = (data.length / 8214); // we've a 1 offset but we're also off by one due to rounding...so it balances out int offset = 8214; // System.out.println("we have "+records+" continue records to process"); @@ -174,8 +172,7 @@ public class ContinueRecord for (int cr = 0; cr < records; cr++) { ContinueRecord contrec = new ContinueRecord(); - int arraysize = Math.min((8214 - 4), - (data.length - offset)); + int arraysize = Math.min((8214 - 4), (data.length - offset)); byte[] crdata = new byte[ arraysize ]; System.arraycopy(data, offset, crdata, 0, arraysize); diff --git a/src/java/org/apache/poi/hssf/record/RecordProcessor.java b/src/java/org/apache/poi/hssf/record/RecordProcessor.java new file mode 100644 index 0000000000..13bfc3a188 --- /dev/null +++ b/src/java/org/apache/poi/hssf/record/RecordProcessor.java @@ -0,0 +1,202 @@ +/* ==================================================================== + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2002 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Apache" and "Apache Software Foundation" and + * "Apache POI" must not be used to endorse or promote products + * derived from this software without prior written permission. For + * written permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache", + * "Apache POI", nor may "Apache" appear in their name, without + * prior written permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + */ + +package org.apache.poi.hssf.record; + +import org.apache.poi.util.LittleEndianConsts; +import org.apache.poi.util.LittleEndian; + +/** + * Process a single record. That is, an SST record or a continue record. + * Refactored from code originally in SSTRecord. + * + * @author Glen Stampoultzis (glens at apache.org) + */ +class RecordProcessor +{ + private byte[] data; + private int recordOffset; + private int available; + private SSTRecordHeader sstRecordHeader; + + public RecordProcessor( byte[] data, int available, int numStrings, int numUniqueStrings ) + { + this.data = data; + this.available = available; + this.sstRecordHeader = new SSTRecordHeader(numStrings, numUniqueStrings); + } + + public int getAvailable() + { + return available; + } + + public void writeRecordHeader( int offset, int totalWritten, int recordLength, boolean first_record ) + { + if ( first_record ) + { + available -= 8; + recordOffset = sstRecordHeader.writeSSTHeader( data, recordOffset + offset + totalWritten, recordLength ); + } + else + { + recordOffset = writeContinueHeader( data, recordOffset + offset + totalWritten, recordLength ); + } + } + + public byte[] writeStringRemainder( boolean lastStringCompleted, byte[] stringreminant, int offset, int totalWritten ) + { + if ( !lastStringCompleted ) + { + // write reminant -- it'll all fit neatly + System.arraycopy( stringreminant, 0, data, recordOffset + offset + totalWritten, stringreminant.length ); + adjustPointers( stringreminant.length ); + } + else + { + // write as much of the remnant as possible + System.arraycopy( stringreminant, 0, data, recordOffset + offset + totalWritten, available ); + byte[] leftover = new byte[( stringreminant.length - available ) + LittleEndianConsts.BYTE_SIZE]; + + System.arraycopy( stringreminant, available, leftover, LittleEndianConsts.BYTE_SIZE, stringreminant.length - available ); + leftover[0] = stringreminant[0]; + stringreminant = leftover; + adjustPointers( available ); // Consume all available remaining space + } + return stringreminant; + } + + public void writeWholeString( UnicodeString unistr, int offset, int totalWritten ) + { + unistr.serialize( recordOffset + offset + totalWritten, data ); + int rsize = unistr.getRecordSize(); + adjustPointers( rsize ); + } + + public byte[] writePartString( UnicodeString unistr, int offset, int totalWritten ) + { + byte[] stringReminant; + byte[] ucs = unistr.serialize(); + + System.arraycopy( ucs, 0, data, recordOffset + offset + totalWritten, available ); + stringReminant = new byte[( ucs.length - available ) + LittleEndianConsts.BYTE_SIZE]; + System.arraycopy( ucs, available, stringReminant, LittleEndianConsts.BYTE_SIZE, ucs.length - available ); + stringReminant[0] = ucs[LittleEndianConsts.SHORT_SIZE]; + available = 0; + return stringReminant; + } + + + private int writeContinueHeader( final byte[] data, final int pos, + final int recsize ) + { + int offset = pos; + + LittleEndian.putShort( data, offset, ContinueRecord.sid ); + offset += LittleEndianConsts.SHORT_SIZE; + LittleEndian.putShort( data, offset, (short) ( recsize ) ); + offset += LittleEndianConsts.SHORT_SIZE; + return offset - pos; + } + + + private void adjustPointers( int amount ) + { + recordOffset += amount; + available -= amount; + } +} + +class SSTRecordHeader +{ + int numStrings; + int numUniqueStrings; + + /** + * + */ + public SSTRecordHeader( int numStrings, int numUniqueStrings ) + { + this.numStrings = numStrings; + this.numUniqueStrings = numUniqueStrings; + } + + /** + * Writes out the SST record. This consists of the sid, the record size, the number of + * strings and the number of unique strings. + * + * @param data The data buffer to write the header to. + * @param bufferIndex The index into the data buffer where the header should be written. + * @param recSize The number of records written. + * + * @return The bufer of bytes modified. + */ + public int writeSSTHeader( byte[] data, int bufferIndex, int recSize ) + { + int offset = bufferIndex; + + LittleEndian.putShort( data, offset, SSTRecord.sid ); + offset += LittleEndianConsts.SHORT_SIZE; + LittleEndian.putShort( data, offset, (short) ( recSize ) ); + offset += LittleEndianConsts.SHORT_SIZE; +// LittleEndian.putInt( data, offset, getNumStrings() ); + LittleEndian.putInt( data, offset, numStrings ); + offset += LittleEndianConsts.INT_SIZE; +// LittleEndian.putInt( data, offset, getNumUniqueStrings() ); + LittleEndian.putInt( data, offset, numUniqueStrings ); + offset += LittleEndianConsts.INT_SIZE; + return offset - bufferIndex; + } + +} \ No newline at end of file diff --git a/src/java/org/apache/poi/hssf/record/RowRecord.java b/src/java/org/apache/poi/hssf/record/RowRecord.java index 65b627ea55..fc29fcbd26 100644 --- a/src/java/org/apache/poi/hssf/record/RowRecord.java +++ b/src/java/org/apache/poi/hssf/record/RowRecord.java @@ -452,8 +452,8 @@ public class RowRecord LittleEndian.putShort(data, 0 + offset, sid); LittleEndian.putShort(data, 2 + offset, ( short ) 16); LittleEndian.putShort(data, 4 + offset, getRowNumber()); - LittleEndian.putShort(data, 6 + offset, getFirstCol()); - LittleEndian.putShort(data, 8 + offset, getLastCol()); + LittleEndian.putShort(data, 6 + offset, getFirstCol() == -1 ? (short)0 : getFirstCol()); + LittleEndian.putShort(data, 8 + offset, getLastCol() == -1 ? (short)0 : getLastCol()); LittleEndian.putShort(data, 10 + offset, getHeight()); LittleEndian.putShort(data, 12 + offset, getOptimize()); LittleEndian.putShort(data, 14 + offset, field_6_reserved); diff --git a/src/java/org/apache/poi/hssf/record/SSTDeserializer.java b/src/java/org/apache/poi/hssf/record/SSTDeserializer.java new file mode 100644 index 0000000000..58b62c316a --- /dev/null +++ b/src/java/org/apache/poi/hssf/record/SSTDeserializer.java @@ -0,0 +1,357 @@ +package org.apache.poi.hssf.record; + +import org.apache.poi.util.LittleEndian; +import org.apache.poi.util.LittleEndianConsts; +import org.apache.poi.util.BinaryTree; +import org.apache.poi.util.HexDump; + +import java.io.IOException; + +class SSTDeserializer +{ + + private BinaryTree strings; + /** this is the number of characters we expect in the first sub-record in a subsequent continuation record */ + private int continuationExpectedChars; + /** this is the string we were working on before hitting the end of the current record. This string is NOT finished. */ + private String unfinishedString; + /** this is the total length of the current string being handled */ + private int totalLengthBytes; + /** this is the offset into a string field of the actual string data */ + private int stringDataOffset; + /** this is true if the string uses wide characters */ + private boolean wideChar; + + + public SSTDeserializer(BinaryTree strings) + { + this.strings = strings; + setExpectedChars( 0 ); + unfinishedString = ""; + totalLengthBytes = 0; + stringDataOffset = 0; + wideChar = false; + } + + /** + * This is the starting point where strings are constructed. Note that + * strings may span across multiple continuations. Read the SST record + * carefully before beginning to hack. + */ + public void manufactureStrings( final byte[] data, final int index, + short size ) + { + int offset = index; + + while ( offset < size ) + { + int remaining = size - offset; + + if ( ( remaining > 0 ) && ( remaining < LittleEndianConsts.SHORT_SIZE ) ) + { + throw new RecordFormatException( "Cannot get length of the last string in SSTRecord" ); + } + if ( remaining == LittleEndianConsts.SHORT_SIZE ) + { + setExpectedChars( LittleEndian.getUShort( data, offset ) ); + unfinishedString = ""; + break; + } + short charCount = LittleEndian.getShort( data, offset ); + + setupStringParameters( data, offset, charCount ); + if ( remaining < totalLengthBytes ) + { + setExpectedChars( calculateCharCount( totalLengthBytes - remaining ) ); + charCount -= getExpectedChars(); + totalLengthBytes = remaining; + } + else + { + setExpectedChars( 0 ); + } + processString( data, offset, charCount ); + offset += totalLengthBytes; + if ( getExpectedChars() != 0 ) + { + break; + } + } + } + + + /** + * Detemines the option types for the string (ie, compressed or uncompressed unicode, rich text string or + * plain string etc) and calculates the length and offset for the string. + * + * @param data + * @param index + * @param char_count + */ + private void setupStringParameters( final byte[] data, final int index, + final int char_count ) + { + byte optionFlag = data[index + LittleEndianConsts.SHORT_SIZE]; + + wideChar = ( optionFlag & 1 ) == 1; + boolean farEast = ( optionFlag & 4 ) == 4; + boolean richText = ( optionFlag & 8 ) == 8; + + totalLengthBytes = SSTRecord.STRING_MINIMAL_OVERHEAD + calculateByteCount( char_count ); + stringDataOffset = SSTRecord.STRING_MINIMAL_OVERHEAD; + if ( richText ) + { + short run_count = LittleEndian.getShort( data, index + stringDataOffset ); + + stringDataOffset += LittleEndianConsts.SHORT_SIZE; + totalLengthBytes += LittleEndianConsts.SHORT_SIZE + ( LittleEndianConsts.INT_SIZE * run_count ); + } + if ( farEast ) + { + int extension_length = LittleEndian.getInt( data, index + stringDataOffset ); + + stringDataOffset += LittleEndianConsts.INT_SIZE; + totalLengthBytes += LittleEndianConsts.INT_SIZE + extension_length; + } + } + + + private void processString( final byte[] data, final int index, + final short char_count ) + { + byte[] stringDataBuffer = new byte[totalLengthBytes]; + int length = SSTRecord.STRING_MINIMAL_OVERHEAD + calculateByteCount( char_count ); + byte[] bstring = new byte[length]; + + System.arraycopy( data, index, stringDataBuffer, 0, stringDataBuffer.length ); + int offset = 0; + + LittleEndian.putShort( bstring, offset, char_count ); + offset += LittleEndianConsts.SHORT_SIZE; + bstring[offset] = stringDataBuffer[offset]; + +// System.out.println( "offset = " + stringDataOffset ); +// System.out.println( "length = " + (bstring.length - STRING_MINIMAL_OVERHEAD) ); +// System.out.println( "src.length = " + str_data.length ); +// try +// { +// System.out.println( "----------------------- DUMP -------------------------" ); +// HexDump.dump( stringDataBuffer, (long)stringDataOffset, System.out, 1); +// } +// catch ( IOException e ) +// { +// } +// catch ( ArrayIndexOutOfBoundsException e ) +// { +// } +// catch ( IllegalArgumentException e ) +// { +// } + System.arraycopy( stringDataBuffer, stringDataOffset, bstring, + SSTRecord.STRING_MINIMAL_OVERHEAD, + bstring.length - SSTRecord.STRING_MINIMAL_OVERHEAD ); + UnicodeString string = new UnicodeString( UnicodeString.sid, + (short) bstring.length, + bstring ); + + if ( getExpectedChars() != 0 ) + { + unfinishedString = string.getString(); + } + else + { + Integer integer = new Integer( strings.size() ); + addToStringTable( strings, integer, string ); + } + } + + /** + * Okay, we are doing some major cheating here. Because we can't handle rich text strings properly + * we end up getting duplicate strings. To get around this I'm doing do things: 1. Converting rich + * text to normal text and 2. If there's a duplicate I'm adding a space onto the end. Sneaky perhaps + * but it gets the job done until we can handle this a little better. + */ + static public void addToStringTable( BinaryTree strings, Integer integer, UnicodeString string ) + { + if (string.isRichText()) + string.setOptionFlags( (byte)(string.getOptionFlags() & (~8) ) ); + + boolean added = false; + while (added == false) + { + try + { + strings.put( integer, string ); + added = true; + } + catch( Exception ignore ) + { + string.setString( string.getString() + " " ); + } + } + } + + + + private int calculateCharCount( final int byte_count ) + { + return byte_count / ( wideChar ? LittleEndianConsts.SHORT_SIZE + : LittleEndianConsts.BYTE_SIZE ); + } + + /** + * Process a Continue record. A Continue record for an SST record + * contains the same kind of data that the SST record contains, + * with the following exceptions: + *

+ *

    + *
  1. The string counts at the beginning of the SST record are + * not in the Continue record + *
  2. The first string in the Continue record might NOT begin + * with a size. If the last string in the previous record is + * continued in this record, the size is determined by that + * last string in the previous record; the first string will + * begin with a flag byte, followed by the remaining bytes (or + * words) of the last string from the previous + * record. Otherwise, the first string in the record will + * begin with a string length + *
+ * + * @param record the Continue record's byte data + */ + + public void processContinueRecord( final byte[] record ) + { + if ( getExpectedChars() == 0 ) + { + unfinishedString = ""; + totalLengthBytes = 0; + stringDataOffset = 0; + wideChar = false; + manufactureStrings( record, 0, (short) record.length ); + } + else + { + int data_length = record.length - LittleEndianConsts.BYTE_SIZE; + + if ( calculateByteCount( getExpectedChars() ) > data_length ) + { + + // create artificial data to create a UnicodeString + byte[] input = + new byte[record.length + LittleEndianConsts.SHORT_SIZE]; + short size = (short) ( ( ( record[0] & 1 ) == 1 ) + ? ( data_length / LittleEndianConsts.SHORT_SIZE ) + : ( data_length / LittleEndianConsts.BYTE_SIZE ) ); + + LittleEndian.putShort( input, (byte) 0, size ); + System.arraycopy( record, 0, input, LittleEndianConsts.SHORT_SIZE, record.length ); + UnicodeString ucs = new UnicodeString( UnicodeString.sid, (short) input.length, input ); + + unfinishedString = unfinishedString + ucs.getString(); + setExpectedChars( getExpectedChars() - size ); + } + else + { + setupStringParameters( record, -LittleEndianConsts.SHORT_SIZE, + getExpectedChars() ); + byte[] str_data = new byte[totalLengthBytes]; + int length = SSTRecord.STRING_MINIMAL_OVERHEAD + + ( calculateByteCount( getExpectedChars() ) ); + byte[] bstring = new byte[length]; + + // Copy data from the record into the string + // buffer. Copy skips the length of a short in the + // string buffer, to leave room for the string length. + System.arraycopy( record, 0, str_data, + LittleEndianConsts.SHORT_SIZE, + str_data.length + - LittleEndianConsts.SHORT_SIZE ); + + // write the string length + LittleEndian.putShort( bstring, 0, + (short) getExpectedChars() ); + + // write the options flag + bstring[LittleEndianConsts.SHORT_SIZE] = + str_data[LittleEndianConsts.SHORT_SIZE]; + + // copy the bytes/words making up the string; skipping + // past all the overhead of the str_data array + System.arraycopy( str_data, stringDataOffset, bstring, + SSTRecord.STRING_MINIMAL_OVERHEAD, + bstring.length - SSTRecord.STRING_MINIMAL_OVERHEAD ); + + // use special constructor to create the final string + UnicodeString string = + new UnicodeString( UnicodeString.sid, + (short) bstring.length, bstring, + unfinishedString ); + Integer integer = new Integer( strings.size() ); + +// field_3_strings.put( integer, string ); + addToStringTable( strings, integer, string ); + manufactureStrings( record, totalLengthBytes - LittleEndianConsts.SHORT_SIZE, (short) record.length ); + } + } + } + + /** + * @return the number of characters we expect in the first + * sub-record in a subsequent continuation record + */ + + int getExpectedChars() + { + return continuationExpectedChars; + } + + private void setExpectedChars( final int count ) + { + continuationExpectedChars = count; + } + + private int calculateByteCount( final int character_count ) + { + return character_count * ( wideChar ? LittleEndianConsts.SHORT_SIZE : LittleEndianConsts.BYTE_SIZE ); + } + + + /** + * @return the unfinished string + */ + + String getUnfinishedString() + { + return unfinishedString; + } + + /** + * @return the total length of the current string + */ + + int getTotalLength() + { + return totalLengthBytes; + } + + /** + * @return offset into current string data + */ + + int getStringDataOffset() + { + return stringDataOffset; + } + + /** + * @return true if current string uses wide characters + */ + + boolean isWideChar() + { + return wideChar; + } + + +} diff --git a/src/java/org/apache/poi/hssf/record/SSTRecord.java b/src/java/org/apache/poi/hssf/record/SSTRecord.java index d8428148ab..6011c8f5d7 100644 --- a/src/java/org/apache/poi/hssf/record/SSTRecord.java +++ b/src/java/org/apache/poi/hssf/record/SSTRecord.java @@ -1,4 +1,3 @@ - /* ==================================================================== * The Apache Software License, Version 1.1 * @@ -59,7 +58,8 @@ import org.apache.poi.util.BinaryTree; import org.apache.poi.util.LittleEndian; import org.apache.poi.util.LittleEndianConsts; -import java.util.*; +import java.util.Iterator; +import java.util.List; /** * Title: Static String Table Record @@ -71,65 +71,45 @@ import java.util.*; *

* @author Andrew C. Oliver (acoliver at apache dot org) * @author Marc Johnson (mjohnson at apache dot org) + * @author Glen Stampoultzis (glens at apache.org) * @version 2.0-pre * @see org.apache.poi.hssf.record.LabelSSTRecord * @see org.apache.poi.hssf.record.ContinueRecord */ public class SSTRecord - extends Record + extends Record { - // how big can an SST record be? As big as any record can be: 8228 - // bytes - private static final int _max = 8228; - - // standard record overhead: two shorts (record id plus data space - // size) - private static final int _std_record_overhead = - 2 * LittleEndianConsts.SHORT_SIZE; - - // SST overhead: the standard record overhead, plus the number of - // strings and the number of unique strings -- two ints - private static final int _sst_record_overhead = - (_std_record_overhead + (2 * LittleEndianConsts.INT_SIZE)); + /** how big can an SST record be? As big as any record can be: 8228 bytes */ + static final int MAX_RECORD_SIZE = 8228; - // how much data can we stuff into an SST record? That would be - // _max minus the standard SST record overhead - private static final int _max_data_space = - _max - _sst_record_overhead; + /** standard record overhead: two shorts (record id plus data space size)*/ + static final int STD_RECORD_OVERHEAD = + 2 * LittleEndianConsts.SHORT_SIZE; - // overhead for each string includes the string's character count - // (a short) and the flag describing its characteristics (a byte) - private static final int _string_minimal_overhead = - LittleEndianConsts.SHORT_SIZE + LittleEndianConsts.BYTE_SIZE; - public static final short sid = 0xfc; + /** SST overhead: the standard record overhead, plus the number of strings and the number of unique strings -- two ints */ + static final int SST_RECORD_OVERHEAD = + ( STD_RECORD_OVERHEAD + ( 2 * LittleEndianConsts.INT_SIZE ) ); - // union of strings in the SST and EXTSST - private int field_1_num_strings; + /** how much data can we stuff into an SST record? That would be _max minus the standard SST record overhead */ + static final int MAX_DATA_SPACE = MAX_RECORD_SIZE - SST_RECORD_OVERHEAD; - // according to docs ONLY SST - private int field_2_num_unique_strings; - private BinaryTree field_3_strings; + /** overhead for each string includes the string's character count (a short) and the flag describing its characteristics (a byte) */ + static final int STRING_MINIMAL_OVERHEAD = LittleEndianConsts.SHORT_SIZE + LittleEndianConsts.BYTE_SIZE; - // this is the number of characters we expect in the first - // sub-record in a subsequent continuation record - private int __expected_chars; + public static final short sid = 0xfc; - // this is the string we were working on before hitting the end of - // the current record. This string is NOT finished. - private String _unfinished_string; + /** union of strings in the SST and EXTSST */ + private int field_1_num_strings; - // this is the total length of the current string being handled - private int _total_length_bytes; + /** according to docs ONLY SST */ + private int field_2_num_unique_strings; + private BinaryTree field_3_strings; - // this is the offset into a string field of the actual string - // data - private int _string_data_offset; - - // this is true if the string uses wide characters - private boolean _wide_char; - private List _record_lengths = null; + /** Record lengths for initial SST record and all continue records */ + private List _record_lengths = null; + private SSTDeserializer deserializer; /** * default constructor @@ -137,14 +117,10 @@ public class SSTRecord public SSTRecord() { - field_1_num_strings = 0; + field_1_num_strings = 0; field_2_num_unique_strings = 0; - field_3_strings = new BinaryTree(); - setExpectedChars(0); - _unfinished_string = ""; - _total_length_bytes = 0; - _string_data_offset = 0; - _wide_char = false; + field_3_strings = new BinaryTree(); + deserializer = new SSTDeserializer(field_3_strings); } /** @@ -156,9 +132,9 @@ public class SSTRecord * @param data of the record (should not contain sid/len) */ - public SSTRecord(final short id, final short size, final byte [] data) + public SSTRecord( final short id, final short size, final byte[] data ) { - super(id, size, data); + super( id, size, data ); } /** @@ -171,10 +147,10 @@ public class SSTRecord * @param offset of the record */ - public SSTRecord(final short id, final short size, final byte [] data, - int offset) + public SSTRecord( final short id, final short size, final byte[] data, + int offset ) { - super(id, size, data, offset); + super( id, size, data, offset ); } /** @@ -192,13 +168,13 @@ public class SSTRecord * @return the index of that string in the table */ - public int addString(final String string) + public int addString( final String string ) { int rval; - if (string == null) + if ( string == null ) { - rval = addString("", false); + rval = addString( "", false ); } else { @@ -207,17 +183,17 @@ public class SSTRecord // present, we have to use 16-bit encoding. Otherwise, we // can use 8-bit encoding boolean useUTF16 = false; - int strlen = string.length(); + int strlen = string.length(); - for (int j = 0; j < strlen; j++) + for ( int j = 0; j < strlen; j++ ) { - if (string.charAt(j) > 255) + if ( string.charAt( j ) > 255 ) { useUTF16 = true; break; } } - rval = addString(string, useUTF16); + rval = addString( string, useUTF16 ); } return rval; } @@ -238,21 +214,21 @@ public class SSTRecord * @return the index of that string in the table */ - public int addString(final String string, final boolean useUTF16) + public int addString( final String string, final boolean useUTF16 ) { field_1_num_strings++; - String str = (string == null) ? "" - : string; - int rval = -1; - UnicodeString ucs = new UnicodeString(); - - ucs.setString(str); - ucs.setCharCount(( short ) str.length()); - ucs.setOptionFlags(( byte ) (useUTF16 ? 1 - : 0)); - Integer integer = ( Integer ) field_3_strings.getKeyForValue(ucs); - - if (integer != null) + String str = ( string == null ) ? "" + : string; + int rval = -1; + UnicodeString ucs = new UnicodeString(); + + ucs.setString( str ); + ucs.setCharCount( (short) str.length() ); + ucs.setOptionFlags( (byte) ( useUTF16 ? 1 + : 0 ) ); + Integer integer = (Integer) field_3_strings.getKeyForValue( ucs ); + + if ( integer != null ) { rval = integer.intValue(); } @@ -263,8 +239,9 @@ public class SSTRecord // strings we've already collected rval = field_3_strings.size(); field_2_num_unique_strings++; - integer = new Integer(rval); - field_3_strings.put(integer, ucs); + integer = new Integer( rval ); + SSTDeserializer.addToStringTable( field_3_strings, integer, ucs ); +// field_3_strings.put( integer, ucs ); } return rval; } @@ -298,7 +275,7 @@ public class SSTRecord * */ - public void setNumStrings(final int count) + public void setNumStrings( final int count ) { field_1_num_strings = count; } @@ -314,7 +291,7 @@ public class SSTRecord * @param count number of strings */ - public void getNumUniqueStrings(final int count) + public void getNumUniqueStrings( final int count ) { field_2_num_unique_strings = count; } @@ -327,16 +304,15 @@ public class SSTRecord * @return the desired string */ - public String getString(final int id) + public String getString( final int id ) { - return (( UnicodeString ) field_3_strings.get(new Integer(id))) - .getString(); + return ( (UnicodeString) field_3_strings.get( new Integer( id ) ) ).getString(); } - public boolean getString16bit(final int id) + public boolean isString16bit( final int id ) { - return ((( UnicodeString ) field_3_strings.get(new Integer(id))) - .getOptionFlags() == 1); + UnicodeString unicodeString = ( (UnicodeString) field_3_strings.get( new Integer( id ) ) ); + return ( ( unicodeString.getOptionFlags() & 0x01 ) == 1 ); } /** @@ -349,326 +325,24 @@ public class SSTRecord { StringBuffer buffer = new StringBuffer(); - buffer.append("[SST]\n"); - buffer.append(" .numstrings = ") - .append(Integer.toHexString(getNumStrings())).append("\n"); - buffer.append(" .uniquestrings = ") - .append(Integer.toHexString(getNumUniqueStrings())).append("\n"); - for (int k = 0; k < field_3_strings.size(); k++) + buffer.append( "[SST]\n" ); + buffer.append( " .numstrings = " ) + .append( Integer.toHexString( getNumStrings() ) ).append( "\n" ); + buffer.append( " .uniquestrings = " ) + .append( Integer.toHexString( getNumUniqueStrings() ) ).append( "\n" ); + for ( int k = 0; k < field_3_strings.size(); k++ ) { - buffer.append(" .string_" + k + " = ") - .append((( UnicodeString ) field_3_strings - .get(new Integer(k))).toString()).append("\n"); + buffer.append( " .string_" + k + " = " ) + .append( ( (UnicodeString) field_3_strings + .get( new Integer( k ) ) ).toString() ).append( "\n" ); } - buffer.append("[/SST]\n"); + buffer.append( "[/SST]\n" ); return buffer.toString(); } - /** - * Create a byte array consisting of an SST record and any - * required Continue records, ready to be written out. - *

- * If an SST record and any subsequent Continue records are read - * in to create this instance, this method should produce a byte - * array that is identical to the byte array produced by - * concatenating the input records' data. - * - * @return the byte array - */ - - public int serialize(int offset, byte [] data) - { - int rval = getRecordSize(); - int record_length_index = 0; - - // get the linear size of that array - int unicodesize = calculateUnicodeSize(); - - if (unicodesize > _max_data_space) - { - byte[] stringreminant = null; - int unipos = 0; - boolean lastneedcontinue = false; - int stringbyteswritten = 0; - boolean first_record = true; - int totalWritten = 0; - int size = 0; - - while (totalWritten != rval) - { - int pos = 0; - - // write the appropriate header - int available; - - if (first_record) - { - size = - (( Integer ) _record_lengths - .get(record_length_index++)).intValue(); - available = size - 8; - pos = writeSSTHeader(data, - pos + offset - + totalWritten, size); - size += _std_record_overhead; - first_record = false; - } - else - { - pos = 0; - int to_be_written = (unicodesize - stringbyteswritten) - + (lastneedcontinue ? 1 - : 0); // not used? - - size = - (( Integer ) _record_lengths - .get(record_length_index++)).intValue(); - available = size; - pos = writeContinueHeader(data, - pos + offset - + totalWritten, size); - size = size + _std_record_overhead; - } - - // now, write the rest of the data into the current - // record space - if (lastneedcontinue) - { - - // the last string in the previous record was not - // written out completely - if (stringreminant.length <= available) - { - - // write reminant -- it'll all fit neatly - System.arraycopy(stringreminant, 0, data, - pos + offset + totalWritten, - stringreminant.length); - stringbyteswritten += stringreminant.length - 1; - pos += stringreminant.length; - lastneedcontinue = false; - available -= stringreminant.length; - } - else - { - - // write as much of the remnant as possible - System.arraycopy(stringreminant, 0, data, - pos + offset + totalWritten, - available); - stringbyteswritten += available - 1; - pos += available; - byte[] leftover = - new byte[ (stringreminant.length - available) + LittleEndianConsts.BYTE_SIZE ]; - - System.arraycopy(stringreminant, available, leftover, - LittleEndianConsts.BYTE_SIZE, - stringreminant.length - available); - leftover[ 0 ] = stringreminant[ 0 ]; - stringreminant = leftover; - available = 0; - lastneedcontinue = true; - } - } - - // last string's remnant, if any, is cleaned up as - // best as can be done ... now let's try and write - // some more strings - for (; unipos < field_3_strings.size(); unipos++) - { - Integer intunipos = new Integer(unipos); - UnicodeString unistr = - (( UnicodeString ) field_3_strings.get(intunipos)); - - if (unistr.getRecordSize() <= available) - { - unistr.serialize(pos + offset + totalWritten, data); - int rsize = unistr.getRecordSize(); - - stringbyteswritten += rsize; - pos += rsize; - available -= rsize; - } - else - { - - // can't write the entire string out - if (available >= _string_minimal_overhead) - { - - // we can write some of it - byte[] ucs = unistr.serialize(); - - System.arraycopy(ucs, 0, data, - pos + offset + totalWritten, - available); - stringbyteswritten += available; - stringreminant = - new byte[ (ucs.length - available) + LittleEndianConsts.BYTE_SIZE ]; - System.arraycopy(ucs, available, stringreminant, - LittleEndianConsts.BYTE_SIZE, - ucs.length - available); - stringreminant[ 0 ] = - ucs[ LittleEndianConsts.SHORT_SIZE ]; - available = 0; - lastneedcontinue = true; - unipos++; - } - break; - } - } - totalWritten += size; - } - } - else - { - - // short data: write one simple SST record - int datasize = _sst_record_overhead + unicodesize; // not used? - - writeSSTHeader( - data, 0 + offset, - _sst_record_overhead - + (( Integer ) _record_lengths.get( - record_length_index++)).intValue() - _std_record_overhead); - int pos = _sst_record_overhead; - - for (int k = 0; k < field_3_strings.size(); k++) - { - UnicodeString unistr = - (( UnicodeString ) field_3_strings.get(new Integer(k))); - - System.arraycopy(unistr.serialize(), 0, data, pos + offset, - unistr.getRecordSize()); - pos += unistr.getRecordSize(); - } - } - return rval; - } - - // not used: remove? - private int calculateStringsize() - { - int retval = 0; - - for (int k = 0; k < field_3_strings.size(); k++) - { - retval += - (( UnicodeString ) field_3_strings.get(new Integer(k))) - .getRecordSize(); - } - return retval; - } - - /** - * Process a Continue record. A Continue record for an SST record - * contains the same kind of data that the SST record contains, - * with the following exceptions: - *

- *

    - *
  1. The string counts at the beginning of the SST record are - * not in the Continue record - *
  2. The first string in the Continue record might NOT begin - * with a size. If the last string in the previous record is - * continued in this record, the size is determined by that - * last string in the previous record; the first string will - * begin with a flag byte, followed by the remaining bytes (or - * words) of the last string from the previous - * record. Otherwise, the first string in the record will - * begin with a string length - *
- * - * @param record the Continue record's byte data - */ - - public void processContinueRecord(final byte [] record) - { - if (getExpectedChars() == 0) - { - _unfinished_string = ""; - _total_length_bytes = 0; - _string_data_offset = 0; - _wide_char = false; - manufactureStrings(record, 0, ( short ) record.length); - } - else - { - int data_length = record.length - LittleEndianConsts.BYTE_SIZE; - - if (calculateByteCount(getExpectedChars()) > data_length) - { - - // create artificial data to create a UnicodeString - byte[] input = - new byte[ record.length + LittleEndianConsts.SHORT_SIZE ]; - short size = ( short ) (((record[ 0 ] & 1) == 1) - ? (data_length - / LittleEndianConsts.SHORT_SIZE) - : (data_length - / LittleEndianConsts.BYTE_SIZE)); - - LittleEndian.putShort(input, ( byte ) 0, size); - System.arraycopy(record, 0, input, - LittleEndianConsts.SHORT_SIZE, - record.length); - UnicodeString ucs = new UnicodeString(UnicodeString.sid, - ( short ) input.length, - input); - - _unfinished_string = _unfinished_string + ucs.getString(); - setExpectedChars(getExpectedChars() - size); - } - else - { - setupStringParameters(record, -LittleEndianConsts.SHORT_SIZE, - getExpectedChars()); - byte[] str_data = new byte[ _total_length_bytes ]; - int length = _string_minimal_overhead - + (calculateByteCount(getExpectedChars())); - byte[] bstring = new byte[ length ]; - - // Copy data from the record into the string - // buffer. Copy skips the length of a short in the - // string buffer, to leave room for the string length. - System.arraycopy(record, 0, str_data, - LittleEndianConsts.SHORT_SIZE, - str_data.length - - LittleEndianConsts.SHORT_SIZE); - - // write the string length - LittleEndian.putShort(bstring, 0, - ( short ) getExpectedChars()); - - // write the options flag - bstring[ LittleEndianConsts.SHORT_SIZE ] = - str_data[ LittleEndianConsts.SHORT_SIZE ]; - - // copy the bytes/words making up the string; skipping - // past all the overhead of the str_data array - System.arraycopy(str_data, _string_data_offset, bstring, - _string_minimal_overhead, - bstring.length - _string_minimal_overhead); - - // use special constructor to create the final string - UnicodeString string = - new UnicodeString(UnicodeString.sid, - ( short ) bstring.length, bstring, - _unfinished_string); - Integer integer = new Integer(field_3_strings.size()); - - field_3_strings.put(integer, string); - manufactureStrings(record, - _total_length_bytes - - LittleEndianConsts - .SHORT_SIZE, ( short ) record.length); - } - } - } - /** * @return sid */ - public short getSid() { return sid; @@ -677,30 +351,23 @@ public class SSTRecord /** * @return hashcode */ - public int hashCode() { return field_2_num_unique_strings; } - /** - * - * @param o - * @return true if equal - */ - - public boolean equals(Object o) + public boolean equals( Object o ) { - if ((o == null) || (o.getClass() != this.getClass())) + if ( ( o == null ) || ( o.getClass() != this.getClass() ) ) { return false; } - SSTRecord other = ( SSTRecord ) o; + SSTRecord other = (SSTRecord) o; - return ((field_1_num_strings == other - .field_1_num_strings) && (field_2_num_unique_strings == other - .field_2_num_unique_strings) && field_3_strings - .equals(other.field_3_strings)); + return ( ( field_1_num_strings == other + .field_1_num_strings ) && ( field_2_num_unique_strings == other + .field_2_num_unique_strings ) && field_3_strings + .equals( other.field_3_strings ) ); } /** @@ -711,12 +378,12 @@ public class SSTRecord * @exception RecordFormatException if validation fails */ - protected void validateSid(final short id) - throws RecordFormatException + protected void validateSid( final short id ) + throws RecordFormatException { - if (id != sid) + if ( id != sid ) { - throw new RecordFormatException("NOT An SST RECORD"); + throw new RecordFormatException( "NOT An SST RECORD" ); } } @@ -800,33 +467,20 @@ public class SSTRecord * @param size size of the raw data */ - protected void fillFields(final byte [] data, final short size, - int offset) + protected void fillFields( final byte[] data, final short size, + int offset ) { // this method is ALWAYS called after construction -- using // the nontrivial constructor, of course -- so this is where // we initialize our fields - field_1_num_strings = LittleEndian.getInt(data, 0 + offset); - field_2_num_unique_strings = LittleEndian.getInt(data, 4 + offset); - field_3_strings = new BinaryTree(); - setExpectedChars(0); - _unfinished_string = ""; - _total_length_bytes = 0; - _string_data_offset = 0; - _wide_char = false; - manufactureStrings(data, 8 + offset, size); + field_1_num_strings = LittleEndian.getInt( data, 0 + offset ); + field_2_num_unique_strings = LittleEndian.getInt( data, 4 + offset ); + field_3_strings = new BinaryTree(); + deserializer = new SSTDeserializer(field_3_strings); + deserializer.manufactureStrings( data, 8 + offset, size ); } - /** - * @return the number of characters we expect in the first - * sub-record in a subsequent continuation record - */ - - int getExpectedChars() - { - return __expected_chars; - } /** * @return an iterator of the strings we hold. All instances are @@ -848,372 +502,43 @@ public class SSTRecord } /** - * @return the unfinished string + * called by the class that is responsible for writing this sucker. + * Subclasses should implement this so that their data is passed back in a + * byte array. + * + * @return byte array containing instance data */ - String getUnfinishedString() + public int serialize( int offset, byte[] data ) { - return _unfinished_string; + SSTSerializer serializer = new SSTSerializer( + _record_lengths, field_3_strings, getNumStrings(), getNumUniqueStrings() ); + return serializer.serialize( offset, data ); } - /** - * @return the total length of the current string - */ - int getTotalLength() + // we can probably simplify this later...this calculates the size + // w/o serializing but still is a bit slow + public int getRecordSize() { - return _total_length_bytes; - } + SSTSerializer serializer = new SSTSerializer( + _record_lengths, field_3_strings, getNumStrings(), getNumUniqueStrings() ); - /** - * @return offset into current string data - */ + return serializer.getRecordSize(); + } - int getStringDataOffset() + SSTDeserializer getDeserializer() { - return _string_data_offset; + return deserializer; } /** - * @return true if current string uses wide characters + * Strange to handle continue records this way. Is it a smell? */ - - boolean isWideChar() - { - return _wide_char; - } - - private int writeSSTHeader(final byte [] data, final int pos, - final int recsize) - { - int offset = pos; - - LittleEndian.putShort(data, offset, sid); - offset += LittleEndianConsts.SHORT_SIZE; - LittleEndian.putShort(data, offset, ( short ) (recsize)); - offset += LittleEndianConsts.SHORT_SIZE; - LittleEndian.putInt(data, offset, getNumStrings()); - offset += LittleEndianConsts.INT_SIZE; - LittleEndian.putInt(data, offset, getNumUniqueStrings()); - offset += LittleEndianConsts.INT_SIZE; - return offset - pos; - } - - private int writeContinueHeader(final byte [] data, final int pos, - final int recsize) - { - int offset = pos; - - LittleEndian.putShort(data, offset, ContinueRecord.sid); - offset += LittleEndianConsts.SHORT_SIZE; - LittleEndian.putShort(data, offset, ( short ) (recsize)); - offset += LittleEndianConsts.SHORT_SIZE; - return offset - pos; - } - - private int calculateUCArrayLength(final byte [][] ucarray) - { - int retval = 0; - - for (int k = 0; k < ucarray.length; k++) - { - retval += ucarray[ k ].length; - } - return retval; - } - - private void manufactureStrings(final byte [] data, final int index, - short size) - { - int offset = index; - - while (offset < size) - { - int remaining = size - offset; - - if ((remaining > 0) - && (remaining < LittleEndianConsts.SHORT_SIZE)) - { - throw new RecordFormatException( - "Cannot get length of the last string in SSTRecord"); - } - if (remaining == LittleEndianConsts.SHORT_SIZE) - { - setExpectedChars(LittleEndian.getShort(data, offset)); - _unfinished_string = ""; - break; - } - short char_count = LittleEndian.getShort(data, offset); - - setupStringParameters(data, offset, char_count); - if (remaining < _total_length_bytes) - { - setExpectedChars(calculateCharCount(_total_length_bytes - - remaining)); - char_count -= getExpectedChars(); - _total_length_bytes = remaining; - } - else - { - setExpectedChars(0); - } - processString(data, offset, char_count); - offset += _total_length_bytes; - if (getExpectedChars() != 0) - { - break; - } - } - } - - private void setupStringParameters(final byte [] data, final int index, - final int char_count) - { - byte flag = data[ index + LittleEndianConsts.SHORT_SIZE ]; - - _wide_char = (flag & 1) == 1; - boolean extended = (flag & 4) == 4; - boolean formatted_run = (flag & 8) == 8; - - _total_length_bytes = _string_minimal_overhead - + calculateByteCount(char_count); - _string_data_offset = _string_minimal_overhead; - if (formatted_run) - { - short run_count = LittleEndian.getShort(data, - index - + _string_data_offset); - - _string_data_offset += LittleEndianConsts.SHORT_SIZE; - _total_length_bytes += LittleEndianConsts.SHORT_SIZE - + (LittleEndianConsts.INT_SIZE - * run_count); - } - if (extended) - { - int extension_length = LittleEndian.getInt(data, - index - + _string_data_offset); - - _string_data_offset += LittleEndianConsts.INT_SIZE; - _total_length_bytes += LittleEndianConsts.INT_SIZE - + extension_length; - } - } - - private void processString(final byte [] data, final int index, - final short char_count) - { - byte[] str_data = new byte[ _total_length_bytes ]; - int length = _string_minimal_overhead - + calculateByteCount(char_count); - byte[] bstring = new byte[ length ]; - - System.arraycopy(data, index, str_data, 0, str_data.length); - int offset = 0; - - LittleEndian.putShort(bstring, offset, char_count); - offset += LittleEndianConsts.SHORT_SIZE; - bstring[ offset ] = str_data[ offset ]; - System.arraycopy(str_data, _string_data_offset, bstring, - _string_minimal_overhead, - bstring.length - _string_minimal_overhead); - UnicodeString string = new UnicodeString(UnicodeString.sid, - ( short ) bstring.length, - bstring); - - if (getExpectedChars() != 0) - { - _unfinished_string = string.getString(); - } - else - { - Integer integer = new Integer(field_3_strings.size()); - - field_3_strings.put(integer, string); - } - } - - private void setExpectedChars(final int count) - { - __expected_chars = count; - } - - private int calculateByteCount(final int character_count) - { - return character_count * (_wide_char ? LittleEndianConsts.SHORT_SIZE - : LittleEndianConsts.BYTE_SIZE); - } - - private int calculateCharCount(final int byte_count) + public void processContinueRecord( byte[] record ) { - return byte_count / (_wide_char ? LittleEndianConsts.SHORT_SIZE - : LittleEndianConsts.BYTE_SIZE); + deserializer.processContinueRecord( record ); } +} - // we can probably simplify this later...this calculates the size - // w/o serializing but still is a bit slow - public int getRecordSize() - { - _record_lengths = new ArrayList(); - int retval = 0; - int unicodesize = calculateUnicodeSize(); - - if (unicodesize > _max_data_space) - { - UnicodeString unistr = null; - int stringreminant = 0; - int unipos = 0; - boolean lastneedcontinue = false; - int stringbyteswritten = 0; - boolean finished = false; - boolean first_record = true; - int totalWritten = 0; - - while (!finished) - { - int record = 0; - int pos = 0; - - if (first_record) - { - - // writing SST record - record = _max; - pos = 12; - first_record = false; - _record_lengths.add(new Integer(record - - _std_record_overhead)); - } - else - { - - // writing continue record - pos = 0; - int to_be_written = (unicodesize - stringbyteswritten) - + (lastneedcontinue ? 1 - : 0); - int size = Math.min(_max - _std_record_overhead, - to_be_written); - - if (size == to_be_written) - { - finished = true; - } - record = size + _std_record_overhead; - _record_lengths.add(new Integer(size)); - pos = 4; - } - if (lastneedcontinue) - { - int available = _max - pos; - - if (stringreminant <= available) - { - - // write reminant - stringbyteswritten += stringreminant - 1; - pos += stringreminant; - lastneedcontinue = false; - } - else - { - - // write as much of the remnant as possible - int toBeWritten = unistr.maxBrokenLength(available); - - if (available != toBeWritten) - { - int shortrecord = record - - (available - toBeWritten); - - _record_lengths.set( - _record_lengths.size() - 1, - new Integer( - shortrecord - _std_record_overhead)); - record = shortrecord; - } - stringbyteswritten += toBeWritten - 1; - pos += toBeWritten; - stringreminant -= toBeWritten - 1; - lastneedcontinue = true; - } - } - for (; unipos < field_3_strings.size(); unipos++) - { - int available = _max - pos; - Integer intunipos = new Integer(unipos); - - unistr = - (( UnicodeString ) field_3_strings.get(intunipos)); - if (unistr.getRecordSize() <= available) - { - stringbyteswritten += unistr.getRecordSize(); - pos += unistr.getRecordSize(); - } - else - { - if (available >= _string_minimal_overhead) - { - int toBeWritten = - unistr.maxBrokenLength(available); - - stringbyteswritten += toBeWritten; - stringreminant = - (unistr.getRecordSize() - toBeWritten) - + LittleEndianConsts.BYTE_SIZE; - if (available != toBeWritten) - { - int shortrecord = record - - (available - toBeWritten); - - _record_lengths.set( - _record_lengths.size() - 1, - new Integer( - shortrecord - _std_record_overhead)); - record = shortrecord; - } - lastneedcontinue = true; - unipos++; - } - else - { - int shortrecord = record - available; - - _record_lengths.set( - _record_lengths.size() - 1, - new Integer( - shortrecord - _std_record_overhead)); - record = shortrecord; - } - break; - } - } - totalWritten += record; - } - retval = totalWritten; - } - else - { - - // short data: write one simple SST record - retval = _sst_record_overhead + unicodesize; - _record_lengths.add(new Integer(unicodesize)); - } - return retval; - } - - private int calculateUnicodeSize() - { - int retval = 0; - - for (int k = 0; k < field_3_strings.size(); k++) - { - UnicodeString string = - ( UnicodeString ) field_3_strings.get(new Integer(k)); - retval += string.getRecordSize(); - } - return retval; - } -} diff --git a/src/java/org/apache/poi/hssf/record/SSTSerializer.java b/src/java/org/apache/poi/hssf/record/SSTSerializer.java new file mode 100644 index 0000000000..5802279145 --- /dev/null +++ b/src/java/org/apache/poi/hssf/record/SSTSerializer.java @@ -0,0 +1,356 @@ +/* ==================================================================== + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2002 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Apache" and "Apache Software Foundation" and + * "Apache POI" must not be used to endorse or promote products + * derived from this software without prior written permission. For + * written permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache", + * "Apache POI", nor may "Apache" appear in their name, without + * prior written permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + */ + +package org.apache.poi.hssf.record; + +import org.apache.poi.util.BinaryTree; +import org.apache.poi.util.LittleEndianConsts; + +import java.util.List; +import java.util.ArrayList; + +/** + * This class handles serialization of SST records. It utilizes the record processor + * class write individual records. This has been refactored from the SSTRecord class. + * + * @author Glen Stampoultzis (glens at apache.org) + */ +class SSTSerializer +{ + + private List recordLengths; + private BinaryTree strings; + private int numStrings; + private int numUniqueStrings; + private SSTRecordHeader sstRecordHeader; + + public SSTSerializer( List recordLengths, BinaryTree strings, int numStrings, int numUniqueStrings ) + { + this.recordLengths = recordLengths; + this.strings = strings; + this.numStrings = numStrings; + this.numUniqueStrings = numUniqueStrings; + this.sstRecordHeader = new SSTRecordHeader(numStrings, numUniqueStrings); + } + + /** + * Create a byte array consisting of an SST record and any + * required Continue records, ready to be written out. + *

+ * If an SST record and any subsequent Continue records are read + * in to create this instance, this method should produce a byte + * array that is identical to the byte array produced by + * concatenating the input records' data. + * + * @return the byte array + */ + public int serialize( int offset, byte[] data ) + { + int record_size = getRecordSize(); + int record_length_index = 0; + + if ( calculateUnicodeSize() > SSTRecord.MAX_DATA_SPACE ) + serializeLargeRecord( record_size, record_length_index, data, offset ); + else + serializeSingleSSTRecord( data, offset, record_length_index ); + return record_size; + } + + private int calculateUnicodeSize() + { + int retval = 0; + + for ( int k = 0; k < strings.size(); k++ ) + { + retval += getUnicodeString(k).getRecordSize(); + } + return retval; + } + + // we can probably simplify this later...this calculates the size + // w/o serializing but still is a bit slow + public int getRecordSize() + { + recordLengths = new ArrayList(); + int retval = 0; + int unicodesize = calculateUnicodeSize(); + + if ( unicodesize > SSTRecord.MAX_DATA_SPACE ) + { + retval = calcRecordSizesForLongStrings( unicodesize ); + } + else + { + // short data: write one simple SST record + retval = SSTRecord.SST_RECORD_OVERHEAD + unicodesize; + recordLengths.add( new Integer( unicodesize ) ); + } + return retval; + } + + private int calcRecordSizesForLongStrings( int unicodesize ) + { + int retval; + UnicodeString unistr = null; + int stringreminant = 0; + int unipos = 0; + boolean lastneedcontinue = false; + int stringbyteswritten = 0; + boolean finished = false; + boolean first_record = true; + int totalWritten = 0; + + while ( !finished ) + { + int record = 0; + int pos = 0; + + if ( first_record ) + { + + // writing SST record + record = SSTRecord.MAX_RECORD_SIZE; + pos = 12; + first_record = false; + recordLengths.add( new Integer( record - SSTRecord.STD_RECORD_OVERHEAD ) ); + } + else + { + + // writing continue record + pos = 0; + int to_be_written = ( unicodesize - stringbyteswritten ) + ( lastneedcontinue ? 1 : 0 ); + int size = Math.min( SSTRecord.MAX_RECORD_SIZE - SSTRecord.STD_RECORD_OVERHEAD, to_be_written ); + + if ( size == to_be_written ) + { + finished = true; + } + record = size + SSTRecord.STD_RECORD_OVERHEAD; + recordLengths.add( new Integer( size ) ); + pos = 4; + } + if ( lastneedcontinue ) + { + int available = SSTRecord.MAX_RECORD_SIZE - pos; + + if ( stringreminant <= available ) + { + + // write reminant + stringbyteswritten += stringreminant - 1; + pos += stringreminant; + lastneedcontinue = false; + } + else + { + + // write as much of the remnant as possible + int toBeWritten = unistr.maxBrokenLength( available ); + + if ( available != toBeWritten ) + { + int shortrecord = record - ( available - toBeWritten ); + recordLengths.set( recordLengths.size() - 1, + new Integer( shortrecord - SSTRecord.STD_RECORD_OVERHEAD ) ); + record = shortrecord; + } + stringbyteswritten += toBeWritten - 1; + pos += toBeWritten; + stringreminant -= toBeWritten - 1; + lastneedcontinue = true; + } + } + for ( ; unipos < strings.size(); unipos++ ) + { + int available = SSTRecord.MAX_RECORD_SIZE - pos; + Integer intunipos = new Integer( unipos ); + + unistr = ( (UnicodeString) strings.get( intunipos ) ); + if ( unistr.getRecordSize() <= available ) + { + stringbyteswritten += unistr.getRecordSize(); + pos += unistr.getRecordSize(); + } + else + { + if ( available >= SSTRecord.STRING_MINIMAL_OVERHEAD ) + { + int toBeWritten = + unistr.maxBrokenLength( available ); + + stringbyteswritten += toBeWritten; + stringreminant = + ( unistr.getRecordSize() - toBeWritten ) + + LittleEndianConsts.BYTE_SIZE; + if ( available != toBeWritten ) + { + int shortrecord = record + - ( available - toBeWritten ); + + recordLengths.set( + recordLengths.size() - 1, + new Integer( + shortrecord - SSTRecord.STD_RECORD_OVERHEAD ) ); + record = shortrecord; + } + lastneedcontinue = true; + unipos++; + } + else + { + int shortrecord = record - available; + + recordLengths.set( recordLengths.size() - 1, + new Integer( shortrecord - SSTRecord.STD_RECORD_OVERHEAD ) ); + record = shortrecord; + } + break; + } + } + totalWritten += record; + } + retval = totalWritten; + + return retval; + } + + + private void serializeSingleSSTRecord( byte[] data, int offset, int record_length_index ) + { + // short data: write one simple SST record + + int len = ( (Integer) recordLengths.get( record_length_index++ ) ).intValue(); + int recordSize = SSTRecord.SST_RECORD_OVERHEAD + len - SSTRecord.STD_RECORD_OVERHEAD; + sstRecordHeader.writeSSTHeader( data, 0 + offset, recordSize ); + int pos = SSTRecord.SST_RECORD_OVERHEAD; + + for ( int k = 0; k < strings.size(); k++ ) + { +// UnicodeString unistr = ( (UnicodeString) strings.get( new Integer( k ) ) ); + System.arraycopy( getUnicodeString(k).serialize(), 0, data, pos + offset, getUnicodeString(k).getRecordSize() ); + pos += getUnicodeString(k).getRecordSize(); + } + } + + /** + * Large records are serialized to an SST and to one or more CONTINUE records. Joy. They have the special + * characteristic that they can change the option field when a single string is split across to a + * CONTINUE record. + */ + private void serializeLargeRecord( int record_size, int record_length_index, byte[] buffer, int offset ) + { + + byte[] stringReminant = null; + int stringIndex = 0; + boolean lastneedcontinue = false; + boolean first_record = true; + int totalWritten = 0; + + while ( totalWritten != record_size ) + { + int recordLength = ( (Integer) recordLengths.get( record_length_index++ ) ).intValue(); + RecordProcessor recordProcessor = new RecordProcessor( buffer, + recordLength, numStrings, numUniqueStrings ); + + // write the appropriate header + recordProcessor.writeRecordHeader( offset, totalWritten, recordLength, first_record ); + first_record = false; + + // now, write the rest of the data into the current + // record space + if ( lastneedcontinue ) + { + lastneedcontinue = stringReminant.length > recordProcessor.getAvailable(); + // the last string in the previous record was not written out completely + stringReminant = recordProcessor.writeStringRemainder( lastneedcontinue, + stringReminant, offset, totalWritten ); + } + + // last string's remnant, if any, is cleaned up as best as can be done ... now let's try and write + // some more strings + for ( ; stringIndex < strings.size(); stringIndex++ ) + { + UnicodeString unistr = getUnicodeString( stringIndex ); + + if ( unistr.getRecordSize() <= recordProcessor.getAvailable() ) + { + recordProcessor.writeWholeString( unistr, offset, totalWritten ); + } + else + { + + // can't write the entire string out + if ( recordProcessor.getAvailable() >= SSTRecord.STRING_MINIMAL_OVERHEAD ) + { + + // we can write some of it + stringReminant = recordProcessor.writePartString( unistr, offset, totalWritten ); + lastneedcontinue = true; + stringIndex++; + } + break; + } + } + totalWritten += recordLength + SSTRecord.STD_RECORD_OVERHEAD; + } + } + + private UnicodeString getUnicodeString( int index ) + { + Integer intunipos = new Integer( index ); + return ( (UnicodeString) strings.get( intunipos ) ); + } + +} diff --git a/src/java/org/apache/poi/hssf/record/UnicodeString.java b/src/java/org/apache/poi/hssf/record/UnicodeString.java index 097be19b1c..2d68815255 100644 --- a/src/java/org/apache/poi/hssf/record/UnicodeString.java +++ b/src/java/org/apache/poi/hssf/record/UnicodeString.java @@ -66,6 +66,7 @@ import org.apache.poi.util.StringUtil; * REFERENCE: PG 264 Microsoft Excel 97 Developer's Kit (ISBN: 1-57231-498-2)

* @author Andrew C. Oliver * @author Marc Johnson (mjohnson at apache dot org) + * @author Glen Stampoultzis (glens at apache.org) * @version 2.0-pre */ @@ -77,12 +78,28 @@ public class UnicodeString private short field_1_charCount; // = 0; private byte field_2_optionflags; // = 0; private String field_3_string; // = null; + private final int RICH_TEXT_BIT = 8; + + public UnicodeString() + { + } + public int hashCode() { - return field_1_charCount; + 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 == null) || (o.getClass() != this.getClass())) @@ -96,10 +113,6 @@ public class UnicodeString && field_3_string.equals(other.field_3_string)); } - public UnicodeString() - { - } - /** * construct a unicode string record and fill its fields, ID is ignored * @param id - ignored @@ -278,19 +291,10 @@ public class UnicodeString public int serialize(int offset, byte [] data) { - int charsize = 1; - - if (getOptionFlags() == 1) - { - charsize = 2; - } - - // byte[] retval = new byte[ 3 + (getString().length() * charsize) ]; LittleEndian.putShort(data, 0 + offset, getCharCount()); data[ 2 + offset ] = getOptionFlags(); -// System.out.println("Unicode: We've got "+retval[2]+" for our option flag"); - if (getOptionFlags() == 0) + if (!isUncompressedUnicode()) { StringUtil.putCompressedUnicode(getString(), data, 0x3 + offset); } @@ -302,14 +306,14 @@ public class UnicodeString return getRecordSize(); } - public int getRecordSize() + private boolean isUncompressedUnicode() { - int charsize = 1; + return (getOptionFlags() & 0x01) == 1; + } - if (getOptionFlags() == 1) - { - charsize = 2; - } + public int getRecordSize() + { + int charsize = isUncompressedUnicode() ? 2 : 1; return 3 + (getString().length() * charsize); } @@ -338,11 +342,16 @@ public class UnicodeString return this.getString().compareTo(str.getString()); } + public boolean isRichText() + { + return (getOptionFlags() & RICH_TEXT_BIT) != 0; + } + int maxBrokenLength(final int proposedBrokenLength) { int rval = proposedBrokenLength; - if ((field_2_optionflags & 1) == 1) + if (isUncompressedUnicode()) { int proposedStringLength = proposedBrokenLength - 3; @@ -355,12 +364,4 @@ public class UnicodeString return rval; } -// public boolean equals(Object obj) { -// if (!(obj instanceof UnicodeString)) return false; -// -// UnicodeString str = (UnicodeString)obj; -// -// -// return this.getString().equals(str.getString()); -// } } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFRow.java b/src/java/org/apache/poi/hssf/usermodel/HSSFRow.java index 39da13e42c..e9209800e3 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFRow.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFRow.java @@ -126,8 +126,8 @@ public class HSSFRow this.sheet = sheet; row = new RowRecord(); row.setHeight((short) 0xff); - row.setLastCol((short)-1); - row.setFirstCol((short)-1); + row.setLastCol((short) -1); + row.setFirstCol((short) -1); // row.setRowNumber(rowNum); setRowNum(rowNum); @@ -213,11 +213,11 @@ public class HSSFRow if (cell.getCellNum() == row.getLastCol()) { - row.setLastCol( findLastCell(row.getLastCol()) ); + row.setLastCol(findLastCell(row.getLastCol())); } if (cell.getCellNum() == row.getFirstCol()) { - row.setFirstCol( findFirstCell(row.getFirstCol()) ); + row.setFirstCol(findFirstCell(row.getFirstCol())); } } @@ -270,11 +270,11 @@ public class HSSFRow { if (row.getFirstCol() == -1) { - row.setFirstCol( cell.getCellNum() ); + row.setFirstCol(cell.getCellNum()); } if (row.getLastCol() == -1) { - row.setLastCol( cell.getCellNum() ); + row.setLastCol(cell.getCellNum()); } cells.put(new Integer(cell.getCellNum()), cell); @@ -292,8 +292,8 @@ public class HSSFRow * get the hssfcell representing a given column (logical cell) 0-based. If you * ask for a cell that is not defined....you get a null. * - * @param cellnum - 0 based column number - * @returns HSSFCell representing that column or null if undefined. + * @param cellnum 0 based column number + * @return HSSFCell representing that column or null if undefined. */ public HSSFCell getCell(short cellnum) @@ -318,7 +318,10 @@ public class HSSFRow public short getFirstCellNum() { - return row.getFirstCol(); + if (getPhysicalNumberOfCells() == 0) + return -1; + else + return row.getFirstCol(); } /** @@ -328,7 +331,10 @@ public class HSSFRow public short getLastCellNum() { - return row.getLastCol(); + if (getPhysicalNumberOfCells() == 0) + return -1; + else + return row.getLastCol(); } @@ -441,7 +447,7 @@ public class HSSFRow } /** - * @returns cell iterator of the physically defined cells. Note element 4 may + * @return cell iterator of the physically defined cells. Note element 4 may * actually be row cell depending on how many are defined! */ diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java b/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java index ddb5dc8a65..c6cb967622 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java @@ -1,4 +1,3 @@ - /* ==================================================================== * The Apache Software License, Version 1.1 * @@ -60,11 +59,14 @@ */ package org.apache.poi.hssf.usermodel; -import org.apache.poi.util.POILogFactory; import org.apache.poi.hssf.model.Sheet; import org.apache.poi.hssf.model.Workbook; -import org.apache.poi.hssf.record.*; +import org.apache.poi.hssf.record.CellValueRecordInterface; +import org.apache.poi.hssf.record.RowRecord; +import org.apache.poi.hssf.record.VCenterRecord; +import org.apache.poi.hssf.record.WSBoolRecord; import org.apache.poi.hssf.util.Region; +import org.apache.poi.util.POILogFactory; import org.apache.poi.util.POILogger; import java.util.Iterator; @@ -74,12 +76,12 @@ import java.util.TreeMap; * High level representation of a worksheet. * @author Andrew C. Oliver (acoliver at apache dot org) * @author Glen Stampoultzis (glens at apache.org) - * @version 1.0-pre + * @author Libin Roman (romal at vistaportal.com) */ public class HSSFSheet { - private static final int DEBUG = POILogger.DEBUG; + private static final int DEBUG = POILogger.DEBUG; /** * Used for compile-time optimization. This is the initial size for the collection of @@ -87,17 +89,17 @@ public class HSSFSheet * by setting this to a higher number and recompiling a custom edition of HSSFSheet. */ - public final static int INITIAL_CAPACITY = 20; + public final static int INITIAL_CAPACITY = 20; /** * reference to the low level Sheet object */ - private Sheet sheet; - private TreeMap rows; - private Workbook book; - private int firstrow; - private int lastrow; + private Sheet sheet; + private TreeMap rows; + private Workbook book; + private int firstrow; + private int lastrow; private static POILogger log = POILogFactory.getLogger(HSSFSheet.class); /** @@ -110,8 +112,8 @@ public class HSSFSheet protected HSSFSheet(Workbook book) { - sheet = Sheet.createSheet(); - rows = new TreeMap(); // new ArrayList(INITIAL_CAPACITY); + sheet = Sheet.createSheet(); + rows = new TreeMap(); // new ArrayList(INITIAL_CAPACITY); this.book = book; } @@ -127,16 +129,11 @@ public class HSSFSheet protected HSSFSheet(Workbook book, Sheet sheet) { this.sheet = sheet; - rows = new TreeMap(); - this.book = book; + rows = new TreeMap(); + this.book = book; setPropertiesFromSheet(sheet); } - /** private default constructor prevents bogus initializationless construction */ - - private HSSFSheet() - { - } /** * used internally to set the properties given a Sheet object @@ -144,8 +141,8 @@ public class HSSFSheet private void setPropertiesFromSheet(Sheet sheet) { - int sloc = sheet.getLoc(); - RowRecord row = sheet.getNextRow(); + int sloc = sheet.getLoc(); + RowRecord row = sheet.getNextRow(); while (row != null) { @@ -154,8 +151,8 @@ public class HSSFSheet row = sheet.getNextRow(); } sheet.setLoc(sloc); - CellValueRecordInterface cval = sheet.getNextValueRecord(); - long timestart = System.currentTimeMillis(); + CellValueRecordInterface cval = sheet.getNextValueRecord(); + long timestart = System.currentTimeMillis(); log.log(DEBUG, "Time at start of cell creating in HSSF sheet = ", new Long(timestart)); @@ -163,8 +160,8 @@ public class HSSFSheet while (cval != null) { - long cellstart = System.currentTimeMillis(); - HSSFRow hrow = lastrow; + long cellstart = System.currentTimeMillis(); + HSSFRow hrow = lastrow; if ((lastrow == null) || (lastrow.getRowNum() != cval.getRow())) { @@ -236,10 +233,10 @@ public class HSSFSheet while (iter.hasNext()) { - HSSFCell cell = ( HSSFCell ) iter.next(); + HSSFCell cell = (HSSFCell) iter.next(); sheet.removeValueRecord(row.getRowNum(), - cell.getCellValueRecord()); + cell.getCellValueRecord()); } sheet.removeRow(row.getRowRecord()); } @@ -251,10 +248,10 @@ public class HSSFSheet private int findLastRow(int lastrow) { - int rownum = lastrow - 1; - HSSFRow r = getRow(rownum); + int rownum = lastrow - 1; + HSSFRow r = getRow(rownum); - while (r == null) + while (r == null && rownum >= 0) { r = getRow(--rownum); } @@ -267,13 +264,17 @@ public class HSSFSheet private int findFirstRow(int firstrow) { - int rownum = firstrow + 1; - HSSFRow r = getRow(rownum); + int rownum = firstrow + 1; + HSSFRow r = getRow(rownum); - while (r == null) + while (r == null && rownum <= getLastRowNum()) { r = getRow(++rownum); } + + if (rownum > getLastRowNum()) + return -1; + return rownum; } @@ -311,8 +312,8 @@ public class HSSFSheet { HSSFRow row = new HSSFRow(); - row.setRowNum(( short ) rownum); - return ( HSSFRow ) rows.get(row); + row.setRowNum((short) rownum); + return (HSSFRow) rows.get(row); } /** @@ -344,26 +345,6 @@ public class HSSFSheet return lastrow; } - /** - * Seems to be unused (gjs) - * - * used internally to add cells from a high level row to the low level model - * @param row the row object to represent in low level RowRecord. - */ - private void addCellsFromRow(HSSFRow row) - { - Iterator iter = row.cellIterator(); - - // for (int k = 0; k < row.getPhysicalNumberOfCells(); k++) - while (iter.hasNext()) - { - HSSFCell cell = - ( HSSFCell ) iter.next(); // row.getPhysicalCellAt(k); - - sheet.addValueRecord(row.getRowNum(), cell.getCellValueRecord()); - } - } - /** * set the width (in units of 1/256th of a character width) * @param column - the column to set (0-based) @@ -400,7 +381,7 @@ public class HSSFSheet /** * get the default row height for the sheet (if the rows do not define their own height) in * twips (1/20 of a point) - * @retun default row height + * @return default row height */ public short getDefaultRowHeight() @@ -449,7 +430,7 @@ public class HSSFSheet public void setDefaultRowHeightInPoints(float height) { - sheet.setDefaultRowHeight(( short ) (height * 20)); + sheet.setDefaultRowHeight((short) (height * 20)); } /** @@ -480,10 +461,10 @@ public class HSSFSheet public int addMergedRegion(Region region) { - return sheet.addMergedRegion(( short ) region.getRowFrom(), - region.getColumnFrom(), - ( short ) region.getRowTo(), - region.getColumnTo()); + return sheet.addMergedRegion((short) region.getRowFrom(), + region.getColumnFrom(), + (short) region.getRowTo(), + region.getColumnTo()); } /** @@ -494,7 +475,7 @@ public class HSSFSheet public void setVerticallyCenter(boolean value) { VCenterRecord record = - ( VCenterRecord ) sheet.findFirstRecordBySid(VCenterRecord.sid); + (VCenterRecord) sheet.findFirstRecordBySid(VCenterRecord.sid); record.setVCenter(value); } @@ -506,7 +487,7 @@ public class HSSFSheet public boolean getVerticallyCenter(boolean value) { VCenterRecord record = - ( VCenterRecord ) sheet.findFirstRecordBySid(VCenterRecord.sid); + (VCenterRecord) sheet.findFirstRecordBySid(VCenterRecord.sid); return record.getVCenter(); } @@ -543,7 +524,7 @@ public class HSSFSheet } /** - * @returns an iterator of the PHYSICAL rows. Meaning the 3rd element may not + * @return an iterator of the PHYSICAL rows. Meaning the 3rd element may not * be the third row if say for instance the second row is undefined. */ @@ -571,7 +552,7 @@ public class HSSFSheet public void setAlternativeExpression(boolean b) { WSBoolRecord record = - ( WSBoolRecord ) sheet.findFirstRecordBySid(WSBoolRecord.sid); + (WSBoolRecord) sheet.findFirstRecordBySid(WSBoolRecord.sid); record.setAlternateExpression(b); } @@ -584,7 +565,7 @@ public class HSSFSheet public void setAlternativeFormula(boolean b) { WSBoolRecord record = - ( WSBoolRecord ) sheet.findFirstRecordBySid(WSBoolRecord.sid); + (WSBoolRecord) sheet.findFirstRecordBySid(WSBoolRecord.sid); record.setAlternateFormula(b); } @@ -597,7 +578,7 @@ public class HSSFSheet public void setAutobreaks(boolean b) { WSBoolRecord record = - ( WSBoolRecord ) sheet.findFirstRecordBySid(WSBoolRecord.sid); + (WSBoolRecord) sheet.findFirstRecordBySid(WSBoolRecord.sid); record.setAutobreaks(b); } @@ -610,7 +591,7 @@ public class HSSFSheet public void setDialog(boolean b) { WSBoolRecord record = - ( WSBoolRecord ) sheet.findFirstRecordBySid(WSBoolRecord.sid); + (WSBoolRecord) sheet.findFirstRecordBySid(WSBoolRecord.sid); record.setDialog(b); } @@ -624,7 +605,7 @@ public class HSSFSheet public void setDisplayGuts(boolean b) { WSBoolRecord record = - ( WSBoolRecord ) sheet.findFirstRecordBySid(WSBoolRecord.sid); + (WSBoolRecord) sheet.findFirstRecordBySid(WSBoolRecord.sid); record.setDisplayGuts(b); } @@ -637,7 +618,7 @@ public class HSSFSheet public void setFitToPage(boolean b) { WSBoolRecord record = - ( WSBoolRecord ) sheet.findFirstRecordBySid(WSBoolRecord.sid); + (WSBoolRecord) sheet.findFirstRecordBySid(WSBoolRecord.sid); record.setFitToPage(b); } @@ -650,7 +631,7 @@ public class HSSFSheet public void setRowSumsBelow(boolean b) { WSBoolRecord record = - ( WSBoolRecord ) sheet.findFirstRecordBySid(WSBoolRecord.sid); + (WSBoolRecord) sheet.findFirstRecordBySid(WSBoolRecord.sid); record.setRowSumsBelow(b); } @@ -663,7 +644,7 @@ public class HSSFSheet public void setRowSumsRight(boolean b) { WSBoolRecord record = - ( WSBoolRecord ) sheet.findFirstRecordBySid(WSBoolRecord.sid); + (WSBoolRecord) sheet.findFirstRecordBySid(WSBoolRecord.sid); record.setRowSumsRight(b); } @@ -675,8 +656,8 @@ public class HSSFSheet public boolean getAlternateExpression() { - return (( WSBoolRecord ) sheet.findFirstRecordBySid(WSBoolRecord.sid)) - .getAlternateExpression(); + return ((WSBoolRecord) sheet.findFirstRecordBySid(WSBoolRecord.sid)) + .getAlternateExpression(); } /** @@ -686,8 +667,8 @@ public class HSSFSheet public boolean getAlternateFormula() { - return (( WSBoolRecord ) sheet.findFirstRecordBySid(WSBoolRecord.sid)) - .getAlternateFormula(); + return ((WSBoolRecord) sheet.findFirstRecordBySid(WSBoolRecord.sid)) + .getAlternateFormula(); } /** @@ -697,8 +678,8 @@ public class HSSFSheet public boolean getAutobreaks() { - return (( WSBoolRecord ) sheet.findFirstRecordBySid(WSBoolRecord.sid)) - .getAutobreaks(); + return ((WSBoolRecord) sheet.findFirstRecordBySid(WSBoolRecord.sid)) + .getAutobreaks(); } /** @@ -708,8 +689,8 @@ public class HSSFSheet public boolean getDialog() { - return (( WSBoolRecord ) sheet.findFirstRecordBySid(WSBoolRecord.sid)) - .getDialog(); + return ((WSBoolRecord) sheet.findFirstRecordBySid(WSBoolRecord.sid)) + .getDialog(); } /** @@ -720,8 +701,8 @@ public class HSSFSheet public boolean getDisplayGuts() { - return (( WSBoolRecord ) sheet.findFirstRecordBySid(WSBoolRecord.sid)) - .getDisplayGuts(); + return ((WSBoolRecord) sheet.findFirstRecordBySid(WSBoolRecord.sid)) + .getDisplayGuts(); } /** @@ -731,8 +712,8 @@ public class HSSFSheet public boolean getFitToPage() { - return (( WSBoolRecord ) sheet.findFirstRecordBySid(WSBoolRecord.sid)) - .getFitToPage(); + return ((WSBoolRecord) sheet.findFirstRecordBySid(WSBoolRecord.sid)) + .getFitToPage(); } /** @@ -742,8 +723,8 @@ public class HSSFSheet public boolean getRowSumsBelow() { - return (( WSBoolRecord ) sheet.findFirstRecordBySid(WSBoolRecord.sid)) - .getRowSumsBelow(); + return ((WSBoolRecord) sheet.findFirstRecordBySid(WSBoolRecord.sid)) + .getRowSumsBelow(); } /** @@ -753,7 +734,7 @@ public class HSSFSheet public boolean getRowSumsRight() { - return (( WSBoolRecord ) sheet.findFirstRecordBySid(WSBoolRecord.sid)) - .getRowSumsRight(); + return ((WSBoolRecord) sheet.findFirstRecordBySid(WSBoolRecord.sid)) + .getRowSumsRight(); } } diff --git a/src/java/org/apache/poi/util/LittleEndian.java b/src/java/org/apache/poi/util/LittleEndian.java index 6c7a4f4d23..2346a0a27f 100644 --- a/src/java/org/apache/poi/util/LittleEndian.java +++ b/src/java/org/apache/poi/util/LittleEndian.java @@ -592,4 +592,17 @@ public class LittleEndian return copy; } + /** + * Retrieves and unsigned short. This is converted UP to a int + * so it can fit. + * + * @param data The data to read + * @param offset The offset to read the short from + * @return An integer representation of the short. + */ + public static int getUShort( byte[] data, int offset ) + { + return (int)getNumber(data, offset, SHORT_SIZE); + } + } diff --git a/src/testcases/org/apache/poi/hssf/data/duprich1.xls b/src/testcases/org/apache/poi/hssf/data/duprich1.xls new file mode 100644 index 0000000000000000000000000000000000000000..3fddbedd27b9f4e5fdcb36af6acf4367b03aa081 GIT binary patch literal 21504 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 B5^(?k literal 0 HcmV?d00001 diff --git a/src/testcases/org/apache/poi/hssf/data/duprich2.xls b/src/testcases/org/apache/poi/hssf/data/duprich2.xls new file mode 100644 index 0000000000000000000000000000000000000000..57af63b3d4db728347a498037ee0f02a0211a2e4 GIT binary patch literal 13824 zcmeHOdx%t382{bbncZ0*`|^?6I!v?1KHN1QrP8wQlo|$EqJ}~f)4Ds`ExTq zedjy(cka3OeCOW#^;eCf2i}`=7G+@ts&J`Li)tU=VSctLH-Pnpf)q_Ui+Qqa_zxja z8|FY(hj6L+bj_D>W;w`b5x~2_6O4hMND1sj6R<9m8cby}se$OqUD;fEdnyyXap7L= zL!TpkIL(K5oIWf}Zb_1qYWwnvuEQBx( zh|P<&bhflC>`rusV4+{1rAV*^VMIzL%34ve zyU>A!KvIl%M71Z$F1IgUu~-}Him@))6;F0Wf}Lnbt0;P63143LCGtJ!1c%jch#e@i z<#If8y}h2nJ24-3_zW8DqJ5*PX8n52IEY(3Ad>Cfi8g~o2Mt{t%Q8xG&HAgXBXXM|7 zsHSdoJSu#dtjhn(R9&4flk7_JmH5)?mEY^2RxaOYLe#|UKLv@U=nY&Yp5i~Cz0Ieb3@7Vd2+y=2dvgsUZLQV z?Eh4KnoVImfO^e0%7X+AZ1ck5KfYPJWnFJl`5nriukmxr$y3IJ+leRGvu9azA}F?I zke*lR-5RgOQZ{@apNS(z={iy;h~SUd>{PWn#MZ&DlhWY<&7B;Jfd`O6jz@}5;hPs$ z4fg4&6&C?F&+r7dbbt>vhu5b&;sLI;WVIz>wAZ8#9_CfX>oHKLEY}B-kq8I4T4iu# z-e{&?c~U6n$(3KulQ~w-lWV=47b@dj)VT=GDv_3wSghArN@7c9oLeVW>bX*NOQJD@ z>od0`X*~(}mhiJv)n@5VHOBXs65O@0#F#V826Z5Gr6FWpey$qXCa^V*;19~x>n`cfG4Ti^elz8#K{Wbq{Ragt2!H6&R*43W4`fZZfnzw9S* zy3sp43D<$<58v zT|LsD+NS-%%jagdee$UjXFWb!pZTTwb4TvPHdq8K0u}*_fJML}U=gqgSOhEr76FTZ zMd1I8z}5W!z?su$_Aj`#Vc)Ct|2xmWE&l&^q*{{r|LaJym${k5x1!w11mrc!Hj?<~ zIglD`pK%k-5($(Hdq8K0u}*_fJML}U=gqgSOhEr76FUEghoJo zZgGvp2j**e{Z6U)J){2DA-=wRVkB;}`0V1p%U4> 8 ), + (byte) 8, (byte) 0, (byte) 0, (byte) 0, (byte) 0, + (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0 + }; + + assertEquals( expected.length, output.length ); + for ( int k = 0; k < expected.length; k++ ) { - ( byte ) record.getSid(), ( byte ) (record.getSid() >> 8), - ( byte ) 8, ( byte ) 0, ( byte ) 0, ( byte ) 0, ( byte ) 0, - ( byte ) 0, ( byte ) 0, ( byte ) 0, ( byte ) 0, ( byte ) 0 - }; - - assertEquals(expected.length, output.length); - for (int k = 0; k < expected.length; k++) - { - assertEquals(String.valueOf(k), expected[ k ], output[ k ]); + assertEquals( String.valueOf( k ), expected[k], output[k] ); } } @@ -473,87 +476,87 @@ public class TestSSTRecord * @param ignored_args */ - public static void main(String [] ignored_args) + public static void main( String[] ignored_args ) { - System.out.println("Testing hssf.record.SSTRecord functionality"); - junit.textui.TestRunner.run(TestSSTRecord.class); + System.out.println( "Testing hssf.record.SSTRecord functionality" ); + junit.textui.TestRunner.run( TestSSTRecord.class ); } - private byte [] readTestData(String filename) - throws IOException + private byte[] readTestData( String filename ) + throws IOException { - File file = new File(_test_file_path - + File.separator - + filename); - FileInputStream stream = new FileInputStream(file); - int characterCount = 0; - byte b = ( byte ) 0; - List bytes = new ArrayList(); - boolean done = false; - - while (!done) + File file = new File( _test_file_path + + File.separator + + filename ); + FileInputStream stream = new FileInputStream( file ); + int characterCount = 0; + byte b = (byte) 0; + List bytes = new ArrayList(); + boolean done = false; + + while ( !done ) { int count = stream.read(); - switch (count) + switch ( count ) { - case '0' : - case '1' : - case '2' : - case '3' : - case '4' : - case '5' : - case '6' : - case '7' : - case '8' : - case '9' : + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': b <<= 4; - b += ( byte ) (count - '0'); + b += (byte) ( count - '0' ); characterCount++; - if (characterCount == 2) + if ( characterCount == 2 ) { - bytes.add(new Byte(b)); + bytes.add( new Byte( b ) ); characterCount = 0; - b = ( byte ) 0; + b = (byte) 0; } break; - case 'A' : - case 'B' : - case 'C' : - case 'D' : - case 'E' : - case 'F' : + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': b <<= 4; - b += ( byte ) (count + 10 - 'A'); + b += (byte) ( count + 10 - 'A' ); characterCount++; - if (characterCount == 2) + if ( characterCount == 2 ) { - bytes.add(new Byte(b)); + bytes.add( new Byte( b ) ); characterCount = 0; - b = ( byte ) 0; + b = (byte) 0; } break; - case 'a' : - case 'b' : - case 'c' : - case 'd' : - case 'e' : - case 'f' : + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': b <<= 4; - b += ( byte ) (count + 10 - 'a'); + b += (byte) ( count + 10 - 'a' ); characterCount++; - if (characterCount == 2) + if ( characterCount == 2 ) { - bytes.add(new Byte(b)); + bytes.add( new Byte( b ) ); characterCount = 0; - b = ( byte ) 0; + b = (byte) 0; } break; - case -1 : + case -1: done = true; break; @@ -562,13 +565,55 @@ public class TestSSTRecord } } stream.close(); - Byte[] polished = ( Byte [] ) bytes.toArray(new Byte[ 0 ]); - byte[] rval = new byte[ polished.length ]; + Byte[] polished = (Byte[]) bytes.toArray( new Byte[0] ); + byte[] rval = new byte[polished.length]; - for (int j = 0; j < polished.length; j++) + for ( int j = 0; j < polished.length; j++ ) { - rval[ j ] = polished[ j ].byteValue(); + rval[j] = polished[j].byteValue(); } return rval; } + + /** + * Tests that workbooks with rich text that duplicates a non rich text cell can be read and written. + */ + public void testReadWriteDuplicatedRichText1() + throws Exception + { + File file = new File( _test_file_path + File.separator + "duprich1.xls" ); + InputStream stream = new FileInputStream(file); + HSSFWorkbook wb = new HSSFWorkbook(stream); + stream.close(); + HSSFSheet sheet = wb.getSheetAt(1); + assertEquals("01/05 (Wed) ", sheet.getRow(0).getCell((short)8).getStringCellValue()); + assertEquals("01/05 (Wed)", sheet.getRow(1).getCell((short)8).getStringCellValue()); + + file = File.createTempFile("testout", "xls"); + FileOutputStream outStream = new FileOutputStream(file); + wb.write(outStream); + outStream.close(); + file.delete(); + + // test the second file. + file = new File( _test_file_path + File.separator + "duprich2.xls" ); + stream = new FileInputStream(file); + wb = new HSSFWorkbook(stream); + stream.close(); + sheet = wb.getSheetAt(0); + int row = 0; + assertEquals("Testing ", sheet.getRow(row++).getCell((short)0).getStringCellValue()); + assertEquals("rich", sheet.getRow(row++).getCell((short)0).getStringCellValue()); + assertEquals("text", sheet.getRow(row++).getCell((short)0).getStringCellValue()); + assertEquals("strings", sheet.getRow(row++).getCell((short)0).getStringCellValue()); + assertEquals("Testing ", sheet.getRow(row++).getCell((short)0).getStringCellValue()); + assertEquals("Testing", sheet.getRow(row++).getCell((short)0).getStringCellValue()); + +// file = new File("/tryme.xls"); + file = File.createTempFile("testout", ".xls"); + outStream = new FileOutputStream(file); + wb.write(outStream); + outStream.close(); + file.delete(); + } } diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFRow.java b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFRow.java index f8c584839a..3c7fb51636 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFRow.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFRow.java @@ -55,7 +55,10 @@ package org.apache.poi.hssf.usermodel; import junit.framework.TestCase; -import org.apache.poi.hssf.record.RowRecord; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; /** * Test HSSFRow is okay. @@ -87,33 +90,47 @@ public class TestHSSFRow assertEquals(1, row.getFirstCellNum()); assertEquals(2, row.getLastCellNum()); - RowRecord rowRecord = new RowRecord(); - rowRecord.setFirstCol((short) 2); - rowRecord.setLastCol((short) 5); - row = new HSSFRow(workbook.getWorkbook(), sheet.getSheet(), rowRecord); - assertEquals(2, row.getFirstCellNum()); - assertEquals(5, row.getLastCellNum()); } public void testRemoveCell() + throws Exception { HSSFWorkbook workbook = new HSSFWorkbook(); HSSFSheet sheet = workbook.createSheet(); HSSFRow row = sheet.createRow((short) 0); assertEquals(-1, row.getLastCellNum()); assertEquals(-1, row.getFirstCellNum()); - row.createCell((short)1); + row.createCell((short) 1); assertEquals(1, row.getLastCellNum()); assertEquals(1, row.getFirstCellNum()); - row.createCell((short)3); + row.createCell((short) 3); assertEquals(3, row.getLastCellNum()); assertEquals(1, row.getFirstCellNum()); - row.removeCell(row.getCell((short)3)); + row.removeCell(row.getCell((short) 3)); assertEquals(1, row.getLastCellNum()); assertEquals(1, row.getFirstCellNum()); - row.removeCell(row.getCell((short)1)); + row.removeCell(row.getCell((short) 1)); assertEquals(-1, row.getLastCellNum()); assertEquals(-1, row.getFirstCellNum()); + // check the row record actually writes it out as 0's + byte[] data = new byte[100]; + row.getRowRecord().serialize(0, data); + assertEquals(0, data[6]); + assertEquals(0, data[8]); + + File file = File.createTempFile("XXX", "XLS"); + FileOutputStream stream = new FileOutputStream(file); + workbook.write(stream); + stream.close(); + FileInputStream inputStream = new FileInputStream(file); + workbook = new HSSFWorkbook(inputStream); + sheet = workbook.getSheetAt(0); + stream.close(); + file.delete(); + assertEquals(-1, sheet.getRow((short) 0).getLastCellNum()); + assertEquals(-1, sheet.getRow((short) 0).getFirstCellNum()); + + } } diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFSheet.java b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFSheet.java index 4868aee042..ddaedd922e 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFSheet.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFSheet.java @@ -58,7 +58,6 @@ import junit.framework.TestCase; import org.apache.poi.hssf.model.Sheet; import org.apache.poi.hssf.record.VCenterRecord; import org.apache.poi.hssf.record.WSBoolRecord; -import org.apache.poi.hssf.dev.BiffViewer; import java.io.File; import java.io.FileInputStream; @@ -190,7 +189,14 @@ public class TestHSSFSheet tempFile.delete(); assertNotNull(row); assertEquals(2, row.getPhysicalNumberOfCells()); + } - + public void testRemoveRow() + { + HSSFWorkbook workbook = new HSSFWorkbook(); + HSSFSheet sheet = workbook.createSheet("Test boolean"); + HSSFRow row = sheet.createRow((short) 2); + sheet.removeRow(row); } + } diff --git a/src/testcases/org/apache/poi/util/TestLittleEndian.java b/src/testcases/org/apache/poi/util/TestLittleEndian.java index 1f2780d897..f9f2df2382 100644 --- a/src/testcases/org/apache/poi/util/TestLittleEndian.java +++ b/src/testcases/org/apache/poi/util/TestLittleEndian.java @@ -437,6 +437,12 @@ public class TestLittleEndian return result; } + public void testUnsignedShort() + throws Exception + { + assertEquals(0xffff, LittleEndian.getUShort(new byte[] { (byte)0xff, (byte)0xff }, 0)); + } + /** * main method to run the unit tests * -- 2.39.5