From b0d1df10d3e73cd6fe35b0d70bbf56e5d24d8e05 Mon Sep 17 00:00:00 2001 From: Danny Muid Date: Thu, 8 May 2003 00:02:03 +0000 Subject: [PATCH] Patch to support reading in Shared Formulas 1) Added new Record: SharedFormulaRecord that is basically a storage area 2) Enabled ExpPtg to store and serialize data for repeating formulas 3) Updated the aggregates to store the SharedFormulaRecord appropriately git-svn-id: https://svn.apache.org/repos/asf/jakarta/poi/trunk@353090 13f79535-47bb-0310-9956-ffa450edef68 --- .../org/apache/poi/hssf/dev/BiffViewer.java | 3 + .../hssf/eventmodel/EventRecordFactory.java | 13 +- .../apache/poi/hssf/record/RecordFactory.java | 4 +- .../poi/hssf/record/SharedFormulaRecord.java | 191 ++++++++++++++++++ .../aggregates/FormulaRecordAggregate.java | 28 ++- .../aggregates/ValueRecordsAggregate.java | 5 + .../poi/hssf/record/formula/ExpPtg.java | 7 + .../poi/hssf/record/TestFormulaRecord.java | 24 +++ .../aggregates/TestValueRecordsAggregate.java | 96 +++++++++ 9 files changed, 363 insertions(+), 8 deletions(-) create mode 100755 src/java/org/apache/poi/hssf/record/SharedFormulaRecord.java create mode 100755 src/testcases/org/apache/poi/hssf/record/aggregates/TestValueRecordsAggregate.java diff --git a/src/java/org/apache/poi/hssf/dev/BiffViewer.java b/src/java/org/apache/poi/hssf/dev/BiffViewer.java index a2ac1a767..da65d5920 100644 --- a/src/java/org/apache/poi/hssf/dev/BiffViewer.java +++ b/src/java/org/apache/poi/hssf/dev/BiffViewer.java @@ -630,6 +630,9 @@ public class BiffViewer { case PaneRecord.sid: retval = new PaneRecord( rectype, size, data ); break; + case SharedFormulaRecord.sid: + retval = new SharedFormulaRecord( rectype, size, data); + break; default: retval = new UnknownRecord( rectype, size, data ); } diff --git a/src/java/org/apache/poi/hssf/eventmodel/EventRecordFactory.java b/src/java/org/apache/poi/hssf/eventmodel/EventRecordFactory.java index 8bec71223..3b5765288 100644 --- a/src/java/org/apache/poi/hssf/eventmodel/EventRecordFactory.java +++ b/src/java/org/apache/poi/hssf/eventmodel/EventRecordFactory.java @@ -54,12 +54,14 @@ */ package org.apache.poi.hssf.eventmodel; -import java.io.InputStream; import java.io.IOException; - -import java.util.*; - +import java.io.InputStream; import java.lang.reflect.Constructor; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; import org.apache.poi.hssf.record.BOFRecord; import org.apache.poi.hssf.record.BackupRecord; @@ -126,6 +128,7 @@ import org.apache.poi.hssf.record.RowRecord; import org.apache.poi.hssf.record.SSTRecord; import org.apache.poi.hssf.record.SaveRecalcRecord; import org.apache.poi.hssf.record.SelectionRecord; +import org.apache.poi.hssf.record.SharedFormulaRecord; import org.apache.poi.hssf.record.StringRecord; import org.apache.poi.hssf.record.StyleRecord; import org.apache.poi.hssf.record.TabIdRecord; @@ -189,7 +192,7 @@ public class EventRecordFactory BoolErrRecord.class, ExternSheetRecord.class, NameRecord.class, LeftMarginRecord.class, RightMarginRecord.class, TopMarginRecord.class, BottomMarginRecord.class, - PaletteRecord.class, StringRecord.class + PaletteRecord.class, StringRecord.class, SharedFormulaRecord.class }; } diff --git a/src/java/org/apache/poi/hssf/record/RecordFactory.java b/src/java/org/apache/poi/hssf/record/RecordFactory.java index 83bd7c637..f12f903b0 100644 --- a/src/java/org/apache/poi/hssf/record/RecordFactory.java +++ b/src/java/org/apache/poi/hssf/record/RecordFactory.java @@ -112,7 +112,7 @@ public class RecordFactory FormulaRecord.class, BoolErrRecord.class, ExternSheetRecord.class, NameRecord.class, LeftMarginRecord.class, RightMarginRecord.class, TopMarginRecord.class, BottomMarginRecord.class, - PaletteRecord.class, StringRecord.class, RecalcIdRecord.class + PaletteRecord.class, StringRecord.class, RecalcIdRecord.class, SharedFormulaRecord.class }; } else { records = new Class[] @@ -143,7 +143,7 @@ public class RecordFactory BoolErrRecord.class, ExternSheetRecord.class, NameRecord.class, LeftMarginRecord.class, RightMarginRecord.class, TopMarginRecord.class, BottomMarginRecord.class, - PaletteRecord.class, StringRecord.class, RecalcIdRecord.class + PaletteRecord.class, StringRecord.class, RecalcIdRecord.class, SharedFormulaRecord.class }; } diff --git a/src/java/org/apache/poi/hssf/record/SharedFormulaRecord.java b/src/java/org/apache/poi/hssf/record/SharedFormulaRecord.java new file mode 100755 index 000000000..eb4482369 --- /dev/null +++ b/src/java/org/apache/poi/hssf/record/SharedFormulaRecord.java @@ -0,0 +1,191 @@ + +/* ==================================================================== + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2003 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.LittleEndian; + +/** + * Title: SharedFormulaRecord + * Description: Primarily used as an excel optimization so that multiple similar formulas + * are not written out too many times. We should recognize this record and + * serialize as is since this is used when reading templates. + *

+ * Note: the documentation says that the SID is BC where biffviewer reports 4BC. The hex dump shows + * that the two byte sid representation to be 'BC 04' that is consistent with the other high byte + * record types. + * @author Danny Mui at apache dot org + */ + +public class SharedFormulaRecord + extends Record +{ + public final static short sid = 0x4BC; + private short size = 0; + private byte[] thedata = null; + int offset = 0; + + public SharedFormulaRecord() + { + } + + /** + * construct the sharedformula record, save all the information + * @param id id of the record -not validated, just stored for serialization + * @param size size of the data + * @param data the data + */ + + public SharedFormulaRecord(short id, short size, byte [] data) + { + super(id, size, data); + + this.fillFields(data, size, 0); + } + + /** + * spit the record out AS IS. no interperatation or identification + */ + + public int serialize(int offset, byte [] data) + { + if (thedata == null) + { + thedata = new byte[ 0 ]; + } + LittleEndian.putShort(data, 0 + offset, sid); + LittleEndian.putShort(data, 2 + offset, ( short ) (thedata.length)); + if (thedata.length > 0) + { + System.arraycopy(thedata, 0, data, 4 + offset, thedata.length); + } + return getRecordSize(); + } + + public int getRecordSize() + { + int retval = 4; + + if (thedata != null) + { + retval += thedata.length; + } + return retval; + } + + + protected void validateSid(short id) + { + if (id != this.sid) + { + throw new RecordFormatException("Not a valid SharedFormula"); + } + + } + + /** + * print a sort of string representation ([SHARED FORMULA RECORD] id = x [/SHARED FORMULA RECORD]) + */ + + public String toString() + { + StringBuffer buffer = new StringBuffer(); + + buffer.append("[SHARED FORMULA RECORD:" + Integer.toHexString(sid) + "]\n"); + buffer.append(" .id = ").append(Integer.toHexString(sid)) + .append("\n"); + buffer.append("[/SHARED FORMULA RECORD]\n"); + return buffer.toString(); + } + + public short getSid() + { + return this.sid; + } + + /** + * Shared formulas are to treated like unknown records, and as a result d + */ + protected void fillFields(byte [] data, short size, int offset) + { + thedata = new byte[size]; + System.arraycopy(data, 0, thedata, 0, size); + + } + + /** + * Mirroring formula records so it is registered in the ValueRecordsAggregate + */ + public boolean isInValueSection() + { + return true; + } + + + /** + * Register it in the ValueRecordsAggregate so it can go into the FormulaRecordAggregate + */ + public boolean isValue() { + return true; + } + + public Object clone() { + SharedFormulaRecord rec = new SharedFormulaRecord(); + rec.offset = offset; + rec.size = size; + rec.thedata = thedata; + return rec; + } +} diff --git a/src/java/org/apache/poi/hssf/record/aggregates/FormulaRecordAggregate.java b/src/java/org/apache/poi/hssf/record/aggregates/FormulaRecordAggregate.java index 82d168bf8..7d5b0b048 100644 --- a/src/java/org/apache/poi/hssf/record/aggregates/FormulaRecordAggregate.java +++ b/src/java/org/apache/poi/hssf/record/aggregates/FormulaRecordAggregate.java @@ -58,7 +58,7 @@ import org.apache.poi.hssf.record.*; /** * The formula record aggregate is used to join together the formula record and it's - * (optional) string record. + * (optional) string record and (optional) Shared Formula Record (template reads, excel optimization). * * @author Glen Stampoultzis (glens at apache.org) */ @@ -70,6 +70,11 @@ public class FormulaRecordAggregate private FormulaRecord formulaRecord; private StringRecord stringRecord; + + /** + * will only be set through the RecordFactory + */ + private SharedFormulaRecord sharedFormulaRecord; public FormulaRecordAggregate( FormulaRecord formulaRecord, StringRecord stringRecord ) { @@ -105,7 +110,12 @@ public class FormulaRecordAggregate { pos += stringRecord.serialize(pos, data); } + if (this.getSharedFormulaRecord() != null) + { + pos += getSharedFormulaRecord().serialize(pos, data); + } return pos - offset; + } /** @@ -114,6 +124,7 @@ public class FormulaRecordAggregate public int getRecordSize() { int size = formulaRecord.getRecordSize() + (stringRecord == null ? 0 : stringRecord.getRecordSize()); + size += (getSharedFormulaRecord() == null) ? 0 : getSharedFormulaRecord().getRecordSize(); return size; } @@ -215,4 +226,19 @@ public class FormulaRecordAggregate + /** + * @return SharedFormulaRecord + */ + public SharedFormulaRecord getSharedFormulaRecord() { + return sharedFormulaRecord; + } + + /** + * Sets the sharedFormulaRecord, only set from RecordFactory since they are not generated by POI and are an Excel optimization + * @param sharedFormulaRecord The sharedFormulaRecord to set + */ + public void setSharedFormulaRecord(SharedFormulaRecord sharedFormulaRecord) { + this.sharedFormulaRecord = sharedFormulaRecord; + } + } diff --git a/src/java/org/apache/poi/hssf/record/aggregates/ValueRecordsAggregate.java b/src/java/org/apache/poi/hssf/record/aggregates/ValueRecordsAggregate.java index fae69130e..d985ac84f 100644 --- a/src/java/org/apache/poi/hssf/record/aggregates/ValueRecordsAggregate.java +++ b/src/java/org/apache/poi/hssf/record/aggregates/ValueRecordsAggregate.java @@ -157,6 +157,11 @@ public class ValueRecordsAggregate { lastFormulaAggregate.setStringRecord((StringRecord)rec); } + else if (rec instanceof SharedFormulaRecord) + { + //these follow the first formula in a group + lastFormulaAggregate.setSharedFormulaRecord((SharedFormulaRecord)rec); + } else if (rec.isValue()) { insertCell(( CellValueRecordInterface ) rec); diff --git a/src/java/org/apache/poi/hssf/record/formula/ExpPtg.java b/src/java/org/apache/poi/hssf/record/formula/ExpPtg.java index 8b182230e..576bce331 100644 --- a/src/java/org/apache/poi/hssf/record/formula/ExpPtg.java +++ b/src/java/org/apache/poi/hssf/record/formula/ExpPtg.java @@ -66,6 +66,7 @@ import org.apache.poi.hssf.util.SheetReferences; * * @author andy * @author Jason Height (jheight at chariot dot net dot au) + * @author dmui (save existing implementation) */ public class ExpPtg @@ -73,6 +74,7 @@ public class ExpPtg { private final static int SIZE = 5; public final static short sid = 0x1; + private byte[] existing = null; /** Creates new ExpPtg */ @@ -84,10 +86,15 @@ public class ExpPtg public ExpPtg(byte [] array, int offset) { + existing = new byte[this.getSize()]; + System.arraycopy(array, offset, existing, 0, this.getSize()); } public void writeBytes(byte [] array, int offset) { + if (existing != null) { + System.arraycopy(existing, 0, array, offset, existing.length); + } } public int getSize() diff --git a/src/testcases/org/apache/poi/hssf/record/TestFormulaRecord.java b/src/testcases/org/apache/poi/hssf/record/TestFormulaRecord.java index 0efdd7c71..2ab793d5e 100644 --- a/src/testcases/org/apache/poi/hssf/record/TestFormulaRecord.java +++ b/src/testcases/org/apache/poi/hssf/record/TestFormulaRecord.java @@ -121,6 +121,30 @@ public class TestFormulaRecord } + /** + * Tests to see if the shared formula cells properly reserialize the expPtg + * + */ + public void testExpFormula() { + byte[] formulaByte = new byte[27]; + + for (int i = 0; i < formulaByte.length; i++) formulaByte[i] = (byte)0; + + formulaByte[4] =(byte)0x0F; + formulaByte[14]=(byte)0x08; + formulaByte[18]=(byte)0xE0; + formulaByte[19]=(byte)0xFD; + formulaByte[20]=(byte)0x05; + formulaByte[22]=(byte)0x01; + FormulaRecord record = new FormulaRecord(FormulaRecord.sid, (short)27, formulaByte); + assertEquals("Row", 0, record.getRow()); + assertEquals("Column", 0, record.getColumn()); + byte[] output = record.serialize(); + assertEquals("Output size", 31, output.length); //includes sid+recordlength + assertEquals("Offset 22", 1, output[26]); + } + + public static void main(String [] ignored_args) { String filename = System.getProperty("HSSF.testdata.path"); diff --git a/src/testcases/org/apache/poi/hssf/record/aggregates/TestValueRecordsAggregate.java b/src/testcases/org/apache/poi/hssf/record/aggregates/TestValueRecordsAggregate.java new file mode 100755 index 000000000..9088f118d --- /dev/null +++ b/src/testcases/org/apache/poi/hssf/record/aggregates/TestValueRecordsAggregate.java @@ -0,0 +1,96 @@ + +/* ==================================================================== + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2003 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.aggregates; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.apache.poi.hssf.record.FormulaRecord; +import org.apache.poi.hssf.record.Record; +import org.apache.poi.hssf.record.SharedFormulaRecord; + +public class TestValueRecordsAggregate extends junit.framework.TestCase { + public TestValueRecordsAggregate(String name) { + super (name); + } + + /** + * Make sure the shared formula makes it to the FormulaRecordAggregate when being parsed + * as part of the value records + * + */ + public void testSharedFormula() { + List records = new ArrayList(); + records.add(new FormulaRecord()); + records.add(new SharedFormulaRecord()); + + ValueRecordsAggregate valueRecord = new ValueRecordsAggregate(); + valueRecord.construct(0, records); + Iterator iterator = valueRecord.getIterator(); + Record record = (Record)iterator.next(); + assertNotNull("Row contains a value", record); + assertTrue("First record is a FormulaRecordsAggregate", (record instanceof FormulaRecordAggregate)); + FormulaRecordAggregate aggregate = (FormulaRecordAggregate)record; + assertNotNull("SharedFormulaRecord is null", aggregate.getSharedFormulaRecord()); + + } + + public static void main(String [] args) { + System.out + .println("Testing org.apache.poi.hssf.record.aggregates.TestValueRecordAggregate"); + junit.textui.TestRunner.run(TestValueRecordsAggregate.class); + } +} \ No newline at end of file