fixed: Bug 43088: Excel file can't be loaded if comments exceed a size of 4111 characters
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@569821 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
3d40a25d36
commit
22350f3b21
@ -233,8 +233,11 @@ public class RecordInputStream extends InputStream
|
|||||||
|
|
||||||
StringBuffer buf = new StringBuffer(length);
|
StringBuffer buf = new StringBuffer(length);
|
||||||
for (int i=0;i<length;i++) {
|
for (int i=0;i<length;i++) {
|
||||||
if ((remaining() == 0) && (isContinueNext()))
|
if ((remaining() == 0) && (isContinueNext())){
|
||||||
nextRecord();
|
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();
|
char ch = (char)readShort();
|
||||||
buf.append(ch);
|
buf.append(ch);
|
||||||
}
|
}
|
||||||
@ -242,14 +245,17 @@ public class RecordInputStream extends InputStream
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String readCompressedUnicode(int length) {
|
public String readCompressedUnicode(int length) {
|
||||||
if ((length < 0) || (remaining() < length)) {
|
if ((length < 0) || ((remaining() < length) && !isContinueNext())) {
|
||||||
throw new IllegalArgumentException("Illegal length");
|
throw new IllegalArgumentException("Illegal length");
|
||||||
}
|
}
|
||||||
|
|
||||||
StringBuffer buf = new StringBuffer(length);
|
StringBuffer buf = new StringBuffer(length);
|
||||||
for (int i=0;i<length;i++) {
|
for (int i=0;i<length;i++) {
|
||||||
if ((remaining() == 0) && (isContinueNext()))
|
if ((remaining() == 0) && (isContinueNext())) {
|
||||||
nextRecord();
|
nextRecord();
|
||||||
|
int compressByte = readByte();
|
||||||
|
if(compressByte != 0) throw new IllegalArgumentException("compressByte in continue records must be 0 while reading compressed unicode");
|
||||||
|
}
|
||||||
byte b = readByte();
|
byte b = readByte();
|
||||||
//Typecast direct to char from byte with high bit set causes all ones
|
//Typecast direct to char from byte with high bit set causes all ones
|
||||||
//in the high byte of the char (which is of course incorrect)
|
//in the high byte of the char (which is of course incorrect)
|
||||||
|
@ -21,6 +21,7 @@ import org.apache.poi.hssf.usermodel.HSSFRichTextString;
|
|||||||
import org.apache.poi.util.LittleEndian;
|
import org.apache.poi.util.LittleEndian;
|
||||||
import org.apache.poi.util.HexDump;
|
import org.apache.poi.util.HexDump;
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
|
||||||
public class TextObjectRecord
|
public class TextObjectRecord
|
||||||
extends TextObjectBaseRecord
|
extends TextObjectBaseRecord
|
||||||
@ -40,21 +41,21 @@ public class TextObjectRecord
|
|||||||
|
|
||||||
protected void fillFields(RecordInputStream in)
|
protected void fillFields(RecordInputStream in)
|
||||||
{
|
{
|
||||||
super.fillFields(in);
|
super.fillFields(in);
|
||||||
if (getTextLength() > 0) {
|
if (getTextLength() > 0) {
|
||||||
if (in.isContinueNext() && in.remaining() == 0) {
|
|
||||||
//1st Continue
|
|
||||||
in.nextRecord();
|
|
||||||
processRawString(in);
|
|
||||||
} else
|
|
||||||
throw new RecordFormatException("Expected Continue record to hold string data for TextObjectRecord");
|
|
||||||
}
|
|
||||||
if (getFormattingRunLength() > 0) {
|
|
||||||
if (in.isContinueNext() && in.remaining() == 0) {
|
if (in.isContinueNext() && in.remaining() == 0) {
|
||||||
in.nextRecord();
|
//1st Continue
|
||||||
processFontRuns(in);
|
in.nextRecord();
|
||||||
} else throw new RecordFormatException("Expected Continue Record to hold font runs for TextObjectRecord");
|
processRawString(in);
|
||||||
}
|
} else
|
||||||
|
throw new RecordFormatException("Expected Continue record to hold string data for TextObjectRecord");
|
||||||
|
}
|
||||||
|
if (getFormattingRunLength() > 0) {
|
||||||
|
if (in.isContinueNext() && in.remaining() == 0) {
|
||||||
|
in.nextRecord();
|
||||||
|
processFontRuns(in);
|
||||||
|
} else throw new RecordFormatException("Expected Continue Record to hold font runs for TextObjectRecord");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -64,7 +65,15 @@ public class TextObjectRecord
|
|||||||
int continue2Size = 0;
|
int continue2Size = 0;
|
||||||
if (str.length() != 0)
|
if (str.length() != 0)
|
||||||
{
|
{
|
||||||
continue1Size = str.length() * 2 + 1 + 4;
|
int length = str.length() * 2;
|
||||||
|
while(length > 0){
|
||||||
|
int chunkSize = Math.min(RecordInputStream.MAX_RECORD_DATA_SIZE-2, length);
|
||||||
|
length -= chunkSize;
|
||||||
|
|
||||||
|
continue1Size += chunkSize;
|
||||||
|
continue1Size += 1 + 4;
|
||||||
|
}
|
||||||
|
|
||||||
continue2Size = (str.numFormattingRuns() + 1) * 8 + 4;
|
continue2Size = (str.numFormattingRuns() + 1) * 8 + 4;
|
||||||
}
|
}
|
||||||
return super.getRecordSize() + continue1Size + continue2Size;
|
return super.getRecordSize() + continue1Size + continue2Size;
|
||||||
@ -83,9 +92,44 @@ public class TextObjectRecord
|
|||||||
int pos = offset + bytesWritten1;
|
int pos = offset + bytesWritten1;
|
||||||
if ( str.getString().equals( "" ) == false )
|
if ( str.getString().equals( "" ) == false )
|
||||||
{
|
{
|
||||||
ContinueRecord c1 = createContinue1();
|
|
||||||
ContinueRecord c2 = createContinue2();
|
ContinueRecord c2 = createContinue2();
|
||||||
int bytesWritten2 = c1.serialize( pos, data );
|
int bytesWritten2 = 0;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
byte[] c1Data = str.getString().getBytes( "UTF-16LE" );
|
||||||
|
int length = c1Data.length;
|
||||||
|
|
||||||
|
int charsWritten = 0;
|
||||||
|
int spos = pos;
|
||||||
|
while(length > 0){
|
||||||
|
int chunkSize = Math.min(RecordInputStream.MAX_RECORD_DATA_SIZE-2 , length);
|
||||||
|
length -= chunkSize;
|
||||||
|
|
||||||
|
//continue header
|
||||||
|
LittleEndian.putShort(data, spos, ContinueRecord.sid);
|
||||||
|
spos += LittleEndian.SHORT_SIZE;
|
||||||
|
LittleEndian.putShort(data, spos, (short)(chunkSize+1));
|
||||||
|
spos += LittleEndian.SHORT_SIZE;
|
||||||
|
|
||||||
|
//The first byte specifies if the text is compressed unicode or unicode.
|
||||||
|
//(regardless what was read, we always serialize double-byte unicode characters (UTF-16LE).
|
||||||
|
data[spos] = 1;
|
||||||
|
spos += LittleEndian.BYTE_SIZE;
|
||||||
|
|
||||||
|
//copy characters data
|
||||||
|
System.arraycopy(c1Data, charsWritten, data, spos, chunkSize);
|
||||||
|
spos += chunkSize;
|
||||||
|
charsWritten += chunkSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
bytesWritten2 = (spos-pos);
|
||||||
|
}
|
||||||
|
catch ( UnsupportedEncodingException e )
|
||||||
|
{
|
||||||
|
throw new RuntimeException( e.getMessage(), e );
|
||||||
|
}
|
||||||
|
|
||||||
pos += bytesWritten2;
|
pos += bytesWritten2;
|
||||||
int bytesWritten3 = c2.serialize( pos, data );
|
int bytesWritten3 = c2.serialize( pos, data );
|
||||||
pos += bytesWritten3;
|
pos += bytesWritten3;
|
||||||
@ -100,23 +144,6 @@ public class TextObjectRecord
|
|||||||
return bytesWritten1;
|
return bytesWritten1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ContinueRecord createContinue1()
|
|
||||||
{
|
|
||||||
ContinueRecord c1 = new ContinueRecord();
|
|
||||||
byte[] c1Data = new byte[str.length() * 2 + 1];
|
|
||||||
try
|
|
||||||
{
|
|
||||||
c1Data[0] = 1;
|
|
||||||
System.arraycopy( str.getString().getBytes( "UTF-16LE" ), 0, c1Data, 1, str.length() * 2 );
|
|
||||||
}
|
|
||||||
catch ( UnsupportedEncodingException e )
|
|
||||||
{
|
|
||||||
throw new RuntimeException( e.getMessage() );
|
|
||||||
}
|
|
||||||
c1.setData( c1Data );
|
|
||||||
return c1;
|
|
||||||
}
|
|
||||||
|
|
||||||
private ContinueRecord createContinue2()
|
private ContinueRecord createContinue2()
|
||||||
{
|
{
|
||||||
ContinueRecord c2 = new ContinueRecord();
|
ContinueRecord c2 = new ContinueRecord();
|
||||||
|
@ -0,0 +1,120 @@
|
|||||||
|
|
||||||
|
/* ====================================================================
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
contributor license agreements. See the NOTICE file distributed with
|
||||||
|
this work for additional information regarding copyright ownership.
|
||||||
|
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
(the "License"); you may not use this file except in compliance with
|
||||||
|
the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
==================================================================== */
|
||||||
|
|
||||||
|
package org.apache.poi.hssf.record;
|
||||||
|
|
||||||
|
import junit.framework.*;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
|
||||||
|
import org.apache.poi.hssf.usermodel.HSSFRichTextString;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests that serialization and deserialization of the TextObjectRecord .
|
||||||
|
* Test data taken directly from a real Excel file.
|
||||||
|
*
|
||||||
|
* @author Yegor Kozlov
|
||||||
|
*/
|
||||||
|
public class TestTextObjectRecord extends TestCase {
|
||||||
|
|
||||||
|
byte[] data = {(byte)0xB6, 0x01, 0x12, 0x00, 0x12, 0x02, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x3C, 0x00, 0x1B, 0x00, 0x01, 0x48, 0x00, 0x65, 0x00, 0x6C,
|
||||||
|
0x00, 0x6C, 0x00, 0x6F, 0x00, 0x2C, 0x00, 0x20, 0x00, 0x57, 0x00,
|
||||||
|
0x6F, 0x00, 0x72, 0x00, 0x6C, 0x00, 0x64, 0x00, 0x21, 0x00, 0x3C,
|
||||||
|
0x00, 0x08, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
|
||||||
|
|
||||||
|
|
||||||
|
public void testRead()
|
||||||
|
throws Exception
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
RecordInputStream is = new RecordInputStream(new ByteArrayInputStream(data));
|
||||||
|
is.nextRecord();
|
||||||
|
TextObjectRecord record = new TextObjectRecord(is);
|
||||||
|
|
||||||
|
assertEquals(TextObjectRecord.sid, record.getSid());
|
||||||
|
record.validateSid(TextObjectRecord.sid);
|
||||||
|
assertEquals(TextObjectRecord.HORIZONTAL_TEXT_ALIGNMENT_LEFT_ALIGNED, record.getHorizontalTextAlignment());
|
||||||
|
assertEquals(TextObjectRecord.VERTICAL_TEXT_ALIGNMENT_TOP, record.getVerticalTextAlignment());
|
||||||
|
assertEquals(TextObjectRecord.TEXT_ORIENTATION_NONE, record.getTextOrientation());
|
||||||
|
assertEquals(0, record.getReserved7());
|
||||||
|
assertEquals("Hello, World!", record.getStr().getString());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testWrite()
|
||||||
|
{
|
||||||
|
HSSFRichTextString str = new HSSFRichTextString("Hello, World!");
|
||||||
|
|
||||||
|
TextObjectRecord record = new TextObjectRecord();
|
||||||
|
int frLength = ( str.numFormattingRuns() + 1 ) * 8;
|
||||||
|
record.setFormattingRunLength( (short) frLength );
|
||||||
|
record.setTextLength( (short) str.length() );
|
||||||
|
record.setStr( str );
|
||||||
|
record.setHorizontalTextAlignment( TextObjectRecord.HORIZONTAL_TEXT_ALIGNMENT_LEFT_ALIGNED );
|
||||||
|
record.setVerticalTextAlignment( TextObjectRecord.VERTICAL_TEXT_ALIGNMENT_TOP );
|
||||||
|
record.setTextLocked( true );
|
||||||
|
record.setTextOrientation( TextObjectRecord.TEXT_ORIENTATION_NONE );
|
||||||
|
record.setReserved7( 0 );
|
||||||
|
|
||||||
|
byte [] ser = record.serialize();
|
||||||
|
assertEquals(ser.length , data.length);
|
||||||
|
|
||||||
|
assertTrue(Arrays.equals(data, ser));
|
||||||
|
|
||||||
|
//read again
|
||||||
|
RecordInputStream is = new RecordInputStream(new ByteArrayInputStream(data));
|
||||||
|
is.nextRecord();
|
||||||
|
record = new TextObjectRecord(is);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test that TextObjectRecord serializes logs records properly.
|
||||||
|
*/
|
||||||
|
public void testLongRecords() {
|
||||||
|
int[] length = {1024, 2048, 4096, 8192, 16384}; //test against strings of different length
|
||||||
|
for (int i = 0; i < length.length; i++) {
|
||||||
|
StringBuffer buff = new StringBuffer(length[i]);
|
||||||
|
for (int j = 0; j < length[i]; j++) {
|
||||||
|
buff.append("x");
|
||||||
|
}
|
||||||
|
HSSFRichTextString str = new HSSFRichTextString(buff.toString());
|
||||||
|
|
||||||
|
TextObjectRecord obj = new TextObjectRecord();
|
||||||
|
int frLength = ( str.numFormattingRuns() + 1 ) * 8;
|
||||||
|
obj.setFormattingRunLength( (short) frLength );
|
||||||
|
obj.setTextLength( (short) str.length() );
|
||||||
|
obj.setStr( str );
|
||||||
|
|
||||||
|
byte [] data = obj.serialize();
|
||||||
|
RecordInputStream is = new RecordInputStream(new ByteArrayInputStream(data));
|
||||||
|
is.nextRecord();
|
||||||
|
TextObjectRecord record = new TextObjectRecord(is);
|
||||||
|
str = record.getStr();
|
||||||
|
|
||||||
|
assertEquals(buff.length(), str.length());
|
||||||
|
assertEquals(buff.toString(), str.getString());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user