Bug37630: SQUASHED! Array Ptgs now implemented (at least the read and write functionality. No means to modify (yet!))

git-svn-id: https://svn.apache.org/repos/asf/jakarta/poi/trunk@370987 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Jason Height 2006-01-21 05:40:07 +00:00
parent 1d00be095e
commit 84e47bd7bb
9 changed files with 504 additions and 88 deletions

View File

@ -100,26 +100,12 @@ public class FormulaRecord
field_6_zero = in.readInt();
field_7_expression_len = in.readShort();
field_8_parsed_expr = getParsedExpressionTokens(in, field_7_expression_len);
field_8_parsed_expr = Ptg.createParsedExpressionTokens(field_7_expression_len, in);
} catch (java.lang.UnsupportedOperationException uoe) {
throw new RecordFormatException(uoe.toString());
}
}
private Stack getParsedExpressionTokens(RecordInputStream in, short size)
{
Stack stack = new Stack();
int pos = 0;
while (pos < size)
{
Ptg ptg = Ptg.createPtg(in);
pos += ptg.getSize();
stack.push(ptg);
}
return stack;
}
//public void setRow(short row)
public void setRow(int row)
{
@ -330,7 +316,7 @@ public class FormulaRecord
//Microsoft Excel Developer's Kit Page 318
LittleEndian.putInt(data, 20 + offset, 0);
LittleEndian.putShort(data, 24 + offset, getExpressionLength());
serializePtgs(data, 26+offset);
Ptg.serializePtgStack(field_8_parsed_expr, data, 26+offset);
} else {
System.arraycopy(all_data,0,data,offset,all_data.length);
}
@ -368,19 +354,6 @@ public class FormulaRecord
return retval;
}
private void serializePtgs(byte [] data, int offset)
{
int pos = offset;
for (int k = 0; k < field_8_parsed_expr.size(); k++)
{
Ptg ptg = ( Ptg ) field_8_parsed_expr.get(k);
ptg.writeBytes(data, pos);
pos += ptg.getSize();
}
}
public boolean isBefore(CellValueRecordInterface i)
{
if (this.getRow() > i.getRow())

View File

@ -49,7 +49,7 @@ public class LinkedDataFormulaField
public int fillField( RecordInputStream in )
{
short tokenSize = in.readShort();
formulaTokens = getParsedExpressionTokens(tokenSize, in);
formulaTokens = Ptg.createParsedExpressionTokens(tokenSize, in);
return tokenSize + 2;
}
@ -80,12 +80,7 @@ public class LinkedDataFormulaField
int size = getSize();
LittleEndian.putShort(data, offset, (short)(size - 2));
int pos = offset + 2;
for ( Iterator iterator = formulaTokens.iterator(); iterator.hasNext(); )
{
Ptg ptg = (Ptg) iterator.next();
ptg.writeBytes(data, pos);
pos += ptg.getSize();
}
pos += Ptg.serializePtgStack(formulaTokens, data, pos);
return size;
}
@ -103,19 +98,6 @@ public class LinkedDataFormulaField
}
}
private Stack getParsedExpressionTokens(short size, RecordInputStream in )
{
Stack stack = new Stack();
int pos = 0;
while ( pos < size )
{
Ptg ptg = Ptg.createPtg( in );
pos += ptg.getSize();
stack.push( ptg );
}
return stack;
}
public void setFormulaTokens( Stack formulaTokens )
{
this.formulaTokens = (Stack) formulaTokens.clone();

View File

@ -331,7 +331,7 @@ public class NameRecord extends Record {
/** get the definition length
* @return definition length
*/
public short getDefinitionTextLength(){
public short getDefinitionLength(){
return field_4_length_name_definition;
}
@ -488,7 +488,7 @@ public class NameRecord extends Record {
throw new RecordFormatException("NOT A valid Name RECORD");
}
}
/**
* called by the class that is responsible for writing this sucker.
* Subclasses should implement this so that their data is passed back in a
@ -501,11 +501,13 @@ public class NameRecord extends Record {
public int serialize( int offset, byte[] data )
{
LittleEndian.putShort( data, 0 + offset, sid );
short size = (short)( 15 + getTextsLength() + getNameDefinitionSize());
LittleEndian.putShort( data, 2 + offset, size );
// size defined below
LittleEndian.putShort( data, 4 + offset, getOptionFlag() );
data[6 + offset] = getKeyboardShortcut();
data[7 + offset] = getNameTextLength();
LittleEndian.putShort( data, 8 + offset, getDefinitionTextLength() );
LittleEndian.putShort( data, 8 + offset, getDefinitionLength() );
LittleEndian.putShort( data, 10 + offset, getUnused() );
LittleEndian.putShort( data, 12 + offset, getEqualsToIndexToSheet() );
data[14 + offset] = getCustomMenuLength();
@ -525,8 +527,7 @@ public class NameRecord extends Record {
return 20 + field_13_raw_name_definition.length;
}
else
{ */
LittleEndian.putShort( data, 2 + offset, (short) ( 15 + getTextsLength() ) );
{ */
int start_of_name_definition = 19 + field_3_length_name_text;
@ -539,7 +540,7 @@ public class NameRecord extends Record {
}
serializePtgs( data, start_of_name_definition + offset );
Ptg.serializePtgStack(field_13_name_definition, data, start_of_name_definition + offset );
int start_of_custom_menu_text = start_of_name_definition + field_4_length_name_definition;
@ -558,37 +559,39 @@ public class NameRecord extends Record {
/* } */
}
private void serializePtgs(byte [] data, int offset) {
int pos = offset;
for (int k = 0; k < field_13_name_definition.size(); k++) {
Ptg ptg = ( Ptg ) field_13_name_definition.get(k);
ptg.writeBytes(data, pos);
pos += ptg.getSize();
}
}
/** gets the length of all texts
* @return total length
*/
public int getTextsLength(){
int result;
result = getNameTextLength() + getDefinitionTextLength() + getDescriptionTextLength() +
result = getNameTextLength() + getDescriptionTextLength() +
getHelpTopicLength() + getStatusBarLength();
return result;
}
private int getNameDefinitionSize() {
int result = 0;
List list = field_13_name_definition;
for (int k = 0; k < list.size(); k++)
{
Ptg ptg = ( Ptg ) list.get(k);
result += ptg.getSize();
}
return result;
}
/** returns the record size
*/
public int getRecordSize(){
int result;
result = 19 + getTextsLength();
result = 19 + getTextsLength() + getNameDefinitionSize();
return result;
}
@ -733,7 +736,7 @@ public class NameRecord extends Record {
}
}
field_13_name_definition = getParsedExpressionTokens(in, field_4_length_name_definition);
field_13_name_definition = Ptg.createParsedExpressionTokens(field_4_length_name_definition, in);
//Who says that this can only ever be compressed unicode???
field_14_custom_menu_text = in.readCompressedUnicode(LittleEndian.ubyteToInt(field_7_length_custom_menu));
@ -746,23 +749,6 @@ public class NameRecord extends Record {
/*} */
}
private Stack getParsedExpressionTokens(RecordInputStream in, short size) {
Stack stack = new Stack();
int sizeCounter = 0;
try {
while (sizeCounter < size) {
Ptg ptg = Ptg.createPtg(in);
sizeCounter += ptg.getSize();
stack.push(ptg);
}
} catch (java.lang.UnsupportedOperationException uoe) {
throw new RecordFormatException(uoe.toString());
}
return stack;
}
/**
* return the non static version of the id for this record.
*/

View File

@ -0,0 +1,246 @@
/* ====================================================================
Copyright 2003-2004 Apache Software Foundation
Licensed 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.formula;
import org.apache.poi.util.LittleEndian;
import org.apache.poi.util.BitField;
import org.apache.poi.util.BitFieldFactory;
import org.apache.poi.util.StringUtil;
import org.apache.poi.hssf.util.CellReference;
import org.apache.poi.hssf.model.Workbook;
import org.apache.poi.hssf.record.RecordFormatException;
import org.apache.poi.hssf.record.RecordInputStream;
import org.apache.poi.hssf.record.SSTRecord;
import org.apache.poi.hssf.record.UnicodeString;
/**
* ArrayPtg - handles arrays
*
* The ArrayPtg is a little wierd, the size of the Ptg when parsing initially only
* includes the Ptg sid and the reserved bytes. The next Ptg in the expression then follows.
* It is only after the "size" of all the Ptgs is met, that the ArrayPtg data is actually
* held after this. So Ptg.createParsedExpression keeps track of the number of
* ArrayPtg elements and need to parse the data upto the FORMULA record size.
*
* @author Jason Height (jheight at chariot dot net dot au)
*/
public class ArrayPtg extends Ptg
{
public final static byte sid = 0x20;
protected byte field_1_reserved;
protected byte field_2_reserved;
protected byte field_3_reserved;
protected byte field_4_reserved;
protected byte field_5_reserved;
protected byte field_6_reserved;
protected byte field_7_reserved;
protected short token_1_columns;
protected short token_2_rows;
protected Object[][] token_3_arrayValues;
protected ArrayPtg() {
//Required for clone methods
}
public ArrayPtg(RecordInputStream in)
{
field_1_reserved = in.readByte();
field_2_reserved = in.readByte();
field_3_reserved = in.readByte();
field_4_reserved = in.readByte();
field_5_reserved = in.readByte();
field_6_reserved = in.readByte();
field_7_reserved = in.readByte();
}
/** Read in the actual token (array) values. This occurs AFTER the last
* Ptg in the expression.
*/
public void readTokenValues(RecordInputStream in) {
token_1_columns = (short)(0x00ff & in.readByte());
token_2_rows = in.readShort();
//The token_1_columns and token_2_rows do not follow the documentation.
//The number of physical rows and columns is actually +1 of these values.
//Which is not explicitly documented.
token_1_columns++;
token_2_rows++;
token_3_arrayValues = new Object[token_1_columns][token_2_rows];
for (int x=0;x<token_1_columns;x++) {
for (int y=0;y<token_2_rows;y++) {
byte grbit = in.readByte();
if (grbit == 0x01) {
token_3_arrayValues[x][y] = Double.valueOf(in.readDouble());
} else if (grbit == 0x02) {
//Ignore the doco, it is actually a unicode string with all the
//trimmings ie 16 bit size, option byte etc
token_3_arrayValues[x][y] = in.readUnicodeString();
} else throw new RecordFormatException("Unknown grbit '"+grbit+"'");
}
}
}
public String toString()
{
StringBuffer buffer = new StringBuffer("[ArrayPtg]\n");
buffer.append("columns = ").append(getColumnCount()).append("\n");
buffer.append("rows = ").append(getRowCount()).append("\n");
for (int x=0;x<getColumnCount();x++) {
for (int y=0;y<getRowCount();y++) {
Object o = token_3_arrayValues[x][y];
buffer.append("[").append(x).append("][").append(y).append("] = ").append(o).append("\n");
}
}
return buffer.toString();
}
public void writeBytes(byte [] array, int offset)
{
array[offset++] = (byte) (sid + ptgClass);
array[offset++] = field_1_reserved;
array[offset++] = field_2_reserved;
array[offset++] = field_3_reserved;
array[offset++] = field_4_reserved;
array[offset++] = field_5_reserved;
array[offset++] = field_6_reserved;
array[offset++] = field_7_reserved;
}
public int writeTokenValueBytes(byte [] array, int offset) {
int pos = 0;
array[pos + offset] = (byte)(token_1_columns-1);
pos++;
LittleEndian.putShort(array, pos+offset, (short)(token_2_rows-1));
pos += 2;
for (int x=0;x<getColumnCount();x++) {
for (int y=0;y<getRowCount();y++) {
Object o = token_3_arrayValues[x][y];
if (o instanceof Double) {
array[pos+offset] = 0x01;
pos++;
LittleEndian.putDouble(array, pos+offset, ((Double)o).doubleValue());
pos+=8;
} else if (o instanceof UnicodeString) {
array[pos+offset] = 0x02;
pos++;
UnicodeString s = (UnicodeString)o;
//JMH TBD Handle string continuation. Id do it now but its 4am.
UnicodeString.UnicodeRecordStats stats = new UnicodeString.UnicodeRecordStats();
s.serialize(stats, pos + offset, array);
pos += stats.recordSize;
} else throw new RuntimeException("Coding error");
}
}
return pos;
}
public void setRowCount(short row)
{
token_2_rows = row;
}
public short getRowCount()
{
return token_2_rows;
}
public void setColumnCount(short col)
{
token_1_columns = (byte)col;
}
public short getColumnCount()
{
return token_1_columns;
}
/** This size includes the size of the array Ptg plus the Array Ptg Token value size*/
public int getSize()
{
int size = 1+7+1+2;
for (int x=0;x<getColumnCount();x++) {
for (int y=0;y<getRowCount();y++) {
Object o = token_3_arrayValues[x][y];
if (o instanceof UnicodeString) {
size++;
UnicodeString.UnicodeRecordStats rs = new UnicodeString.UnicodeRecordStats();
((UnicodeString)o).getRecordSize(rs);
size += rs.recordSize;
} else if (o instanceof Double) {
size += 9;
}
}
}
return size;
}
public String toFormulaString(Workbook book)
{
StringBuffer b = new StringBuffer();
b.append("{");
for (int x=0;x<getColumnCount();x++) {
for (int y=0;y<getRowCount();y++) {
Object o = token_3_arrayValues[x][y];
if (o instanceof String) {
b.append((String)o);
} else if (o instanceof Double) {
b.append(((Double)o).doubleValue());
}
if (y != getRowCount())
b.append(",");
}
if (x != getColumnCount())
b.append(";");
}
b.append("}");
return b.toString();
}
public byte getDefaultOperandClass() {
return Ptg.CLASS_ARRAY;
}
public Object clone() {
ArrayPtg ptg = new ArrayPtg();
ptg.field_1_reserved = field_1_reserved;
ptg.field_2_reserved = field_2_reserved;
ptg.field_3_reserved = field_3_reserved;
ptg.field_4_reserved = field_4_reserved;
ptg.field_5_reserved = field_5_reserved;
ptg.field_6_reserved = field_6_reserved;
ptg.field_7_reserved = field_7_reserved;
ptg.token_1_columns = token_1_columns;
ptg.token_2_rows = token_2_rows;
ptg.token_3_arrayValues = new Object[getColumnCount()][getRowCount()];
for (int x=0;x<getColumnCount();x++) {
for (int y=0;y<getRowCount();y++) {
ptg.token_3_arrayValues[x][y] = token_3_arrayValues[x][y];
}
}
ptg.setClass(ptgClass);
return ptg;
}
}

View File

@ -0,0 +1,72 @@
/* ====================================================================
Copyright 2003-2004 Apache Software Foundation
Licensed 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.formula;
import org.apache.poi.util.LittleEndian;
import org.apache.poi.util.BitField;
import org.apache.poi.util.BitFieldFactory;
import org.apache.poi.util.StringUtil;
import org.apache.poi.hssf.util.CellReference;
import org.apache.poi.hssf.model.Workbook;
import org.apache.poi.hssf.record.RecordFormatException;
import org.apache.poi.hssf.record.RecordInputStream;
import org.apache.poi.hssf.record.SSTRecord;
import org.apache.poi.hssf.record.UnicodeString;
/**
* ArrayPtgA - handles arrays
*
* @author Jason Height (jheight at chariot dot net dot au)
*/
public class ArrayPtgA extends ArrayPtg
{
public final static byte sid = 0x60;
protected ArrayPtgA() {
super();
//Required for clone methods
}
public ArrayPtgA(RecordInputStream in)
{
super(in);
}
public Object clone() {
ArrayPtgA ptg = new ArrayPtgA();
ptg.field_1_reserved = field_1_reserved;
ptg.field_2_reserved = field_2_reserved;
ptg.field_3_reserved = field_3_reserved;
ptg.field_4_reserved = field_4_reserved;
ptg.field_5_reserved = field_5_reserved;
ptg.field_6_reserved = field_6_reserved;
ptg.field_7_reserved = field_7_reserved;
ptg.token_1_columns = token_1_columns;
ptg.token_2_rows = token_2_rows;
ptg.token_3_arrayValues = new Object[getColumnCount()][getRowCount()];
for (int x=0;x<getColumnCount();x++) {
for (int y=0;y<getRowCount();y++) {
ptg.token_3_arrayValues[x][y] = token_3_arrayValues[x][y];
}
}
ptg.setClass(ptgClass);
return ptg;
}
}

View File

@ -0,0 +1,77 @@
/* ====================================================================
Copyright 2003-2004 Apache Software Foundation
Licensed 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.formula;
import org.apache.poi.util.LittleEndian;
import org.apache.poi.util.BitField;
import org.apache.poi.util.BitFieldFactory;
import org.apache.poi.util.StringUtil;
import org.apache.poi.hssf.util.CellReference;
import org.apache.poi.hssf.model.Workbook;
import org.apache.poi.hssf.record.RecordFormatException;
import org.apache.poi.hssf.record.RecordInputStream;
import org.apache.poi.hssf.record.SSTRecord;
import org.apache.poi.hssf.record.UnicodeString;
/**
* ArrayPtg - handles arrays
*
* The ArrayPtg is a little wierd, the size of the Ptg when parsing initially only
* includes the Ptg sid and the reserved bytes. The next Ptg in the expression then follows.
* It is only after the "size" of all the Ptgs is met, that the ArrayPtg data is actually
* held after this. So Ptg.createParsedExpression keeps track of the number of
* ArrayPtg elements and need to parse the data upto the FORMULA record size.
*
* @author Jason Height (jheight at chariot dot net dot au)
*/
public class ArrayPtgV extends ArrayPtg
{
public final static byte sid = 0x40;
protected ArrayPtgV() {
//Required for clone methods
}
public ArrayPtgV(RecordInputStream in)
{
super(in);
}
public Object clone() {
ArrayPtgV ptg = new ArrayPtgV();
ptg.field_1_reserved = field_1_reserved;
ptg.field_2_reserved = field_2_reserved;
ptg.field_3_reserved = field_3_reserved;
ptg.field_4_reserved = field_4_reserved;
ptg.field_5_reserved = field_5_reserved;
ptg.field_6_reserved = field_6_reserved;
ptg.field_7_reserved = field_7_reserved;
ptg.token_1_columns = token_1_columns;
ptg.token_2_rows = token_2_rows;
ptg.token_3_arrayValues = new Object[getColumnCount()][getRowCount()];
for (int x=0;x<getColumnCount();x++) {
for (int y=0;y<getRowCount();y++) {
ptg.token_3_arrayValues[x][y] = token_3_arrayValues[x][y];
}
}
ptg.setClass(ptgClass);
return ptg;
}
}

View File

@ -18,6 +18,7 @@ package org.apache.poi.hssf.record.formula;
import java.util.List;
import java.util.ArrayList;
import java.util.Stack;
import org.apache.poi.hssf.model.Workbook;
import org.apache.poi.hssf.record.RecordInputStream;
@ -85,8 +86,33 @@ public abstract class Ptg
return retval;
}
*/
public static Stack createParsedExpressionTokens(short size, RecordInputStream in )
{
Stack stack = new Stack();
int pos = 0;
List arrayPtgs = null;
while ( pos < size )
{
Ptg ptg = Ptg.createPtg( in );
if (ptg instanceof ArrayPtg) {
if (arrayPtgs == null)
arrayPtgs = new ArrayList(5);
arrayPtgs.add(ptg);
pos += 8;
} else pos += ptg.getSize();
stack.push( ptg );
}
if (arrayPtgs != null) {
for (int i=0;i<arrayPtgs.size();i++) {
ArrayPtg p = (ArrayPtg)arrayPtgs.get(i);
p.readTokenValues(in);
}
}
return stack;
}
public static Ptg createPtg(RecordInputStream in)
private static Ptg createPtg(RecordInputStream in)
{
byte id = in.readByte();
Ptg retval = null;
@ -157,6 +183,16 @@ public abstract class Ptg
case ConcatPtg.sid :
retval = new ConcatPtg(in);
break;
case ArrayPtg.sid:
retval = new ArrayPtg(in);
break;
case ArrayPtgV.sid:
retval = new ArrayPtgV(in);
break;
case ArrayPtgA.sid:
retval = new ArrayPtgA(in);
break;
case AreaPtg.sid :
retval = new AreaPtg(in);
@ -304,6 +340,34 @@ public abstract class Ptg
return retval;
}
public static int serializePtgStack(Stack expression, byte[] array, int offset) {
int pos = 0;
int size = 0;
if (expression != null)
size = expression.size();
List arrayPtgs = null;
for (int k = 0; k < size; k++) {
Ptg ptg = ( Ptg ) expression.get(k);
ptg.writeBytes(array, pos + offset);
if (ptg instanceof ArrayPtg) {
if (arrayPtgs == null)
arrayPtgs = new ArrayList(5);
arrayPtgs.add(ptg);
pos += 8;
} else pos += ptg.getSize();
}
if (arrayPtgs != null) {
for (int i=0;i<arrayPtgs.size();i++) {
ArrayPtg p = (ArrayPtg)arrayPtgs.get(i);
pos += p.writeTokenValueBytes(array, pos + offset);
}
}
return pos;
}
public abstract int getSize();

Binary file not shown.

View File

@ -522,6 +522,22 @@ extends TestCase {
HSSFWorkbook wb = new HSSFWorkbook(in);
assertTrue("Read book fine!" , true);
}
/** Error when reading then writing ArrayValues in NameRecord's*/
public void test37630() throws java.io.IOException {
String filename = System.getProperty("HSSF.testdata.path");
filename=filename+"/37630.xls";
FileInputStream in = new FileInputStream(filename);
HSSFWorkbook wb = new HSSFWorkbook(in);
File file = TempFile.createTempFile("test37630",".xls");
FileOutputStream out = new FileOutputStream(file);
wb.write(out);
assertTrue("Read book fine!" , true);
}
}