<!-- Don't forget to update status.xml too! -->
<release version="3.2-alpha1" date="2008-??-??">
+ <action dev="POI-DEVELOPERS" type="fix">45866 - allowed for change of unicode compression across Continue records</action>
<action dev="POI-DEVELOPERS" type="fix">45964 - support for link formulas in Text Objects</action>
<action dev="POI-DEVELOPERS" type="fix">43354 - support for evalating formulas with missing args</action>
<action dev="POI-DEVELOPERS" type="fix">45912 - fixed ArrayIndexOutOfBoundsException in EmbeddedObjectRefSubRecord</action>
<!-- Don't forget to update changes.xml too! -->
<changes>
<release version="3.2-alpha1" date="2008-??-??">
+ <action dev="POI-DEVELOPERS" type="fix">45866 - allowed for change of unicode compression across Continue records</action>
<action dev="POI-DEVELOPERS" type="fix">45964 - support for link formulas in Text Objects</action>
<action dev="POI-DEVELOPERS" type="fix">43354 - support for evalating formulas with missing args</action>
<action dev="POI-DEVELOPERS" type="fix">45912 - fixed ArrayIndexOutOfBoundsException in EmbeddedObjectRefSubRecord</action>
return result;
}
- /**
- * given a byte array of 16-bit unicode characters, compress to 8-bit and
- * return a string
- *
- * { 0x16, 0x00 } -0x16
- *
- * @param length the length of the final string
- * @return the converted string
- * @exception IllegalArgumentException if len is too large (i.e.,
- * there is not enough data in string to create a String of that
- * length)
- */
- public String readUnicodeLEString(int length) {
- if ((length < 0) || (((remaining() / 2) < length) && !isContinueNext())) {
- throw new IllegalArgumentException("Illegal length - asked for " + length + " but only " + (remaining()/2) + " left!");
- }
-
- StringBuffer buf = new StringBuffer(length);
- for (int i=0;i<length;i++) {
- if ((remaining() == 0) && (isContinueNext())){
- nextRecord();
- int compressByte = readByte();
- if(compressByte != 1) throw new IllegalArgumentException("compressByte in continue records must be 1 while reading unicode LE string");
- }
- char ch = (char)readShort();
- buf.append(ch);
- }
- return buf.toString();
- }
+ public String readString() {
+ int requestedLength = readUShort();
+ byte compressFlag = readByte();
+ return readStringCommon(requestedLength, compressFlag == 0);
+ }
+ /**
+ * given a byte array of 16-bit unicode characters, compress to 8-bit and
+ * return a string
+ *
+ * { 0x16, 0x00 } -0x16
+ *
+ * @param requestedLength the length of the final string
+ * @return the converted string
+ * @exception IllegalArgumentException if len is too large (i.e.,
+ * there is not enough data in string to create a String of that
+ * length)
+ */
+ public String readUnicodeLEString(int requestedLength) {
+ return readStringCommon(requestedLength, false);
+ }
- public String readCompressedUnicode(int length) {
- if ((length < 0) || ((remaining() < length) && !isContinueNext())) {
- throw new IllegalArgumentException("Illegal length " + length);
- }
+ public String readCompressedUnicode(int requestedLength) {
+ return readStringCommon(requestedLength, true);
+ }
- StringBuffer buf = new StringBuffer(length);
- for (int i=0;i<length;i++) {
- if ((remaining() == 0) && (isContinueNext())) {
- nextRecord();
- int compressByte = readByte();
- if(compressByte != 0) throw new IllegalArgumentException("compressByte in continue records must be 0 while reading compressed unicode");
- }
- byte b = readByte();
- char ch = (char)(0x00FF & b); // avoid sex
- buf.append(ch);
- }
- return buf.toString();
- }
+ private String readStringCommon(int requestedLength, boolean pIsCompressedEncoding) {
+ // Sanity check to detect garbage string lengths
+ if (requestedLength < 0 || requestedLength > 0x100000) { // 16 million chars?
+ throw new IllegalArgumentException("Bad requested string length (" + requestedLength + ")");
+ }
+ char[] buf = new char[requestedLength];
+ boolean isCompressedEncoding = pIsCompressedEncoding;
+ int curLen = 0;
+ while(true) {
+ int availableChars =isCompressedEncoding ? remaining() : remaining() / LittleEndian.SHORT_SIZE;
+ if (requestedLength - curLen <= availableChars) {
+ // enough space in current record, so just read it out
+ while(curLen < requestedLength) {
+ char ch;
+ if (isCompressedEncoding) {
+ ch = (char)readUByte();
+ } else {
+ ch = (char)readShort();
+ }
+ buf[curLen] = ch;
+ curLen++;
+ }
+ return new String(buf);
+ }
+ // else string has been spilled into next continue record
+ // so read what's left of the current record
+ while(availableChars > 0) {
+ char ch;
+ if (isCompressedEncoding) {
+ ch = (char)readUByte();
+ } else {
+ ch = (char)readShort();
+ }
+ buf[curLen] = ch;
+ curLen++;
+ availableChars--;
+ }
+ if (!isContinueNext()) {
+ throw new RecordFormatException("Expected to find a ContinueRecord in order to read remaining "
+ + (requestedLength-curLen) + " of " + requestedLength + " chars");
+ }
+ if(remaining() != 0) {
+ throw new RecordFormatException("Odd number of bytes(" + remaining() + ") left behind");
+ }
+ nextRecord();
+ // note - the compressed flag may change on the fly
+ byte compressFlag = readByte();
+ isCompressedEncoding = (compressFlag == 0);
+ }
+ }
/** Returns an excel style unicode string from the bytes reminaing in the record.
* <i>Note:</i> Unicode strings differ from <b>normal</b> strings due to the addition of
result.addTestSuite(TestPaneRecord.class);
result.addTestSuite(TestPlotAreaRecord.class);
result.addTestSuite(TestPlotGrowthRecord.class);
+ result.addTestSuite(TestRecordInputStream.class);
result.addTestSuite(TestRecordFactory.class);
result.addTestSuite(TestSCLRecord.class);
result.addTestSuite(TestSSTDeserializer.class);
--- /dev/null
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.record;
+
+import org.apache.poi.util.HexRead;
+
+import junit.framework.AssertionFailedError;
+import junit.framework.TestCase;
+
+/**
+ * Tests for {@link RecordInputStream}
+ *
+ * @author Josh Micich
+ */
+public final class TestRecordInputStream extends TestCase {
+
+ /**
+ * Data inspired by attachment 22626 of bug 45866<br/>
+ * A unicode string of 18 chars, with a continue record where the compression flag changes
+ */
+ private static final String HED_DUMP1 = ""
+ + "1A 59 00 8A 9E 8A " // 3 uncompressed unicode chars
+ + "3C 00 " // Continue sid
+ + "10 00 " // rec size 16 (1+15)
+ + "00" // next chunk is compressed
+ + "20 2D 20 4D 75 6C 74 69 6C 69 6E 67 75 61 6C " // 15 chars
+ ;
+ /**
+ * same string re-arranged
+ */
+ private static final String HED_DUMP2 = ""
+ // 15 chars at end of current record
+ + "4D 75 6C 74 69 6C 69 6E 67 75 61 6C 20 2D 20"
+ + "3C 00 " // Continue sid
+ + "07 00 " // rec size 7 (1+6)
+ + "01" // this bit uncompressed
+ + "1A 59 00 8A 9E 8A " // 3 uncompressed unicode chars
+ ;
+ public void testChangeOfCompressionFlag_bug25866() {
+ byte[] changingFlagSimpleData = HexRead.readFromString(""
+ + "AA AA " // fake SID
+ + "06 00 " // first rec len 6
+ + HED_DUMP1
+ );
+ RecordInputStream in = TestcaseRecordInputStream.create(changingFlagSimpleData);
+ String actual;
+ try {
+ actual = in.readUnicodeLEString(18);
+ } catch (IllegalArgumentException e) {
+ if ("compressByte in continue records must be 1 while reading unicode LE string".equals(e.getMessage())) {
+ throw new AssertionFailedError("Identified bug 45866");
+ }
+
+ throw e;
+ }
+ assertEquals("\u591A\u8A00\u8A9E - Multilingual", actual);
+ }
+
+ public void testChangeFromUnCompressedToCompressed() {
+ byte[] changingFlagSimpleData = HexRead.readFromString(""
+ + "AA AA " // fake SID
+ + "0F 00 " // first rec len 15
+ + HED_DUMP2
+ );
+ RecordInputStream in = TestcaseRecordInputStream.create(changingFlagSimpleData);
+ String actual = in.readCompressedUnicode(18);
+ assertEquals("Multilingual - \u591A\u8A00\u8A9E", actual);
+ }
+
+ public void testReadString() {
+ byte[] changingFlagFullData = HexRead.readFromString(""
+ + "AA AA " // fake SID
+ + "12 00 " // first rec len 18 (15 + next 3 bytes)
+ + "12 00 " // total chars 18
+ + "00 " // this bit compressed
+ + HED_DUMP2
+ );
+ RecordInputStream in = TestcaseRecordInputStream.create(changingFlagFullData);
+ String actual = in.readString();
+ assertEquals("Multilingual - \u591A\u8A00\u8A9E", actual);
+ }
+}
-
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
package org.apache.poi.hssf.record;
+import org.apache.poi.util.HexRead;
+
import junit.framework.TestCase;
/**
*
* @author Jason Height (jheight at apache.org)
*/
-public class TestUnicodeString
- extends TestCase
-{
+public final class TestUnicodeString extends TestCase {
- public TestUnicodeString( String s )
- {
- super( s );
- }
- public void testSmallStringSize()
- throws Exception
- {
+ public void testSmallStringSize() {
//Test a basic string
UnicodeString s = makeUnicodeString("Test");
UnicodeString.UnicodeRecordStats stats = new UnicodeString.UnicodeRecordStats();
assertEquals(30, stats.recordSize);
}
- public void testPerfectStringSize()
- throws Exception
- {
+ public void testPerfectStringSize() {
//Test a basic string
UnicodeString s = makeUnicodeString(SSTRecord.MAX_RECORD_SIZE-2-1);
UnicodeString.UnicodeRecordStats stats = new UnicodeString.UnicodeRecordStats();
assertEquals(SSTRecord.MAX_RECORD_SIZE-1, stats.recordSize);
}
- public void testPerfectRichStringSize()
- throws Exception
- {
+ public void testPerfectRichStringSize() {
//Test a rich text string
UnicodeString s = makeUnicodeString(SSTRecord.MAX_RECORD_SIZE-2-1-8-2);
s.addFormatRun(new UnicodeString.FormatRun((short)1,(short)0));
assertEquals(SSTRecord.MAX_RECORD_SIZE-1, stats.recordSize);
}
- public void testContinuedStringSize() throws Exception {
+ public void testContinuedStringSize() {
//Test a basic string
UnicodeString s = makeUnicodeString(SSTRecord.MAX_RECORD_SIZE-2-1+20);
UnicodeString.UnicodeRecordStats stats = new UnicodeString.UnicodeRecordStats();
}
/** Tests that a string size calculation that fits neatly in two records, the second being a continue*/
- public void testPerfectContinuedStringSize() throws Exception {
+ public void testPerfectContinuedStringSize() {
//Test a basic string
int strSize = SSTRecord.MAX_RECORD_SIZE*2;
//String overhead
- private UnicodeString makeUnicodeString( String s )
+ private static UnicodeString makeUnicodeString( String s )
{
UnicodeString st = new UnicodeString(s);
st.setOptionFlags((byte)0);
return st;
}
- private UnicodeString makeUnicodeString( int numChars) {
+ private static UnicodeString makeUnicodeString( int numChars) {
StringBuffer b = new StringBuffer(numChars);
for (int i=0;i<numChars;i++) {
b.append(i%10);
}
return makeUnicodeString(b.toString());
}
-
}