false to abort. This aborts
+ * out of the event loop should the listener return false
*/
- private boolean throwRecordEvent(Record record)
- {
- boolean result = true;
- Iterator i = listeners.iterator();
-
- while (i.hasNext()) {
- result = ((ERFListener) i.next()).processRecord(record);
- if (abortable == true && result == false) {
- break;
- }
- }
- return result;
+ private boolean processRecord(Record record) {
+ if (!isSidIncluded(record.getSid())) {
+ return true;
+ }
+ return _listener.processRecord(record);
}
/**
@@ -264,220 +83,39 @@ public class EventRecordFactory
* @exception RecordFormatException on error processing the
* InputStream
*/
- public void processRecords(InputStream in)
- throws RecordFormatException
- {
- Record last_record = null;
+ public void processRecords(InputStream in) throws RecordFormatException {
+ Record last_record = null;
RecordInputStream recStream = new RecordInputStream(in);
while (recStream.hasNextRecord()) {
- recStream.nextRecord();
- Record[] recs = createRecord(recStream); // handle MulRK records
- if (recs.length > 1)
- {
- for (int k = 0; k < recs.length; k++)
- {
- if ( last_record != null ) {
- if (throwRecordEvent(last_record) == false && abortable == true) {
- last_record = null;
- break;
- }
- }
- last_record =
- recs[ k ]; // do to keep the algorythm homogenous...you can't
- } // actually continue a number record anyhow.
- }
- else
- {
- Record record = recs[ 0 ];
-
- if (record != null)
- {
- if (last_record != null) {
- if (throwRecordEvent(last_record) == false && abortable == true) {
- last_record = null;
- break;
- }
- }
-
- last_record = record;
- }
+ recStream.nextRecord();
+ Record[] recs = RecordFactory.createRecord(recStream); // handle MulRK records
+ if (recs.length > 1) {
+ for (int k = 0; k < recs.length; k++) {
+ if ( last_record != null ) {
+ if (!processRecord(last_record)) {
+ return;
}
}
+ last_record = recs[ k ]; // do to keep the algorithm homogeneous...you can't
+ } // actually continue a number record anyhow.
+ } else {
+ Record record = recs[ 0 ];
-
- if (last_record != null) {
- throwRecordEvent(last_record);
- }
- }
-
- /**
- * create a record, if there are MUL records than multiple records
- * are returned digested into the non-mul form.
- */
- public static Record [] createRecord(RecordInputStream in)
- {
- Record retval = null;
- Record[] realretval = null;
-
- try
- {
- Constructor constructor =
- ( Constructor ) recordsMap.get(new Short(in.getSid()));
-
- if (constructor != null)
- {
- retval = ( Record ) constructor.newInstance(new Object[]
- {
- in
- });
- }
- else
- {
- retval = new UnknownRecord(in);
- }
- }
- catch (Exception introspectionException)
- {
- throw new RecordFormatException("Unable to construct record instance" , introspectionException);
- }
- if (retval instanceof RKRecord)
- {
- RKRecord rk = ( RKRecord ) retval;
- NumberRecord num = new NumberRecord();
-
- num.setColumn(rk.getColumn());
- num.setRow(rk.getRow());
- num.setXFIndex(rk.getXFIndex());
- num.setValue(rk.getRKNumber());
- retval = num;
- }
- else if (retval instanceof DBCellRecord)
- {
- retval = null;
- }
- else if (retval instanceof MulRKRecord)
- {
- MulRKRecord mrk = ( MulRKRecord ) retval;
-
- realretval = new Record[ mrk.getNumColumns() ];
- for (int k = 0; k < mrk.getNumColumns(); k++)
- {
- NumberRecord nr = new NumberRecord();
-
- nr.setColumn(( short ) (k + mrk.getFirstColumn()));
- nr.setRow(mrk.getRow());
- nr.setXFIndex(mrk.getXFAt(k));
- nr.setValue(mrk.getRKNumberAt(k));
- realretval[ k ] = nr;
- }
- }
- else if (retval instanceof MulBlankRecord)
- {
- MulBlankRecord mb = ( MulBlankRecord ) retval;
-
- realretval = new Record[ mb.getNumColumns() ];
- for (int k = 0; k < mb.getNumColumns(); k++)
- {
- BlankRecord br = new BlankRecord();
-
- br.setColumn(( short ) (k + mb.getFirstColumn()));
- br.setRow(mb.getRow());
- br.setXFIndex(mb.getXFAt(k));
- realretval[ k ] = br;
- }
- }
- if (realretval == null)
- {
- realretval = new Record[ 1 ];
- realretval[ 0 ] = retval;
- }
- return realretval;
- }
-
- /**
- * @return an array of all the SIDS for all known records
- */
- public static short [] getAllKnownRecordSIDs()
- {
- short[] results = new short[ recordsMap.size() ];
- int i = 0;
-
- for (Iterator iterator = recordsMap.keySet().iterator();
- iterator.hasNext(); )
- {
- Short sid = ( Short ) iterator.next();
-
- results[ i++ ] = sid.shortValue();
- }
- return results;
- }
-
- /**
- * gets the record constructors and sticks them in the map by SID
- * @return map of SIDs to short,short,byte[] constructors for Record classes
- * most of org.apache.poi.hssf.record.*
- */
- private static Map recordsToMap(Class [] records)
- {
- Map result = new HashMap();
- Constructor constructor;
-
- for (int i = 0; i < records.length; i++)
- {
- Class record = null;
- short sid = 0;
-
- record = records[ i ];
- try
- {
- sid = record.getField("sid").getShort(null);
- constructor = record.getConstructor(new Class[]
- {
- RecordInputStream.class
- });
- }
- catch (Exception illegalArgumentException)
- {
- throw new RecordFormatException(
- "Unable to determine record types");
- }
- result.put(new Short(sid), constructor);
- }
- return result;
- }
-
-}
-
-/**
- * ListenerWrapper just wraps an ERFListener and adds support for throwing
- * the event to multiple SIDs
- */
-class ListenerWrapper implements ERFListener {
- private ERFListener listener;
- private short[] sids;
- private boolean abortable;
-
- ListenerWrapper(ERFListener listener, short[] sids, boolean abortable) {
- this.listener = listener;
- this.sids = sids;
- this.abortable = abortable;
- }
-
-
- public boolean processRecord(Record rec)
- {
- boolean result = true;
- for (int k = 0; k < sids.length; k++) {
- if (sids[k] == rec.getSid()) {
- result = listener.processRecord(rec);
-
- if (abortable == true && result == false) {
- break;
+ if (record != null) {
+ if (last_record != null) {
+ if (!processRecord(last_record)) {
+ return;
+ }
+ }
+ last_record = record;
}
}
}
- return result;
- }
-}
+
+ if (last_record != null) {
+ processRecord(last_record);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/java/org/apache/poi/hssf/eventmodel/ModelFactory.java b/src/java/org/apache/poi/hssf/eventmodel/ModelFactory.java
index ade4532042..d75ca6c2e6 100644
--- a/src/java/org/apache/poi/hssf/eventmodel/ModelFactory.java
+++ b/src/java/org/apache/poi/hssf/eventmodel/ModelFactory.java
@@ -65,8 +65,7 @@ public class ModelFactory implements ERFListener
* Start processing the Workbook stream into Model events.
*/
public void run(InputStream stream) {
- EventRecordFactory factory = new EventRecordFactory(true);
- factory.registerListener(this,null);
+ EventRecordFactory factory = new EventRecordFactory(this,null);
lastEOF = true;
factory.processRecords(stream);
}
diff --git a/src/java/org/apache/poi/hssf/extractor/ExcelExtractor.java b/src/java/org/apache/poi/hssf/extractor/ExcelExtractor.java
index 75a73c654d..efa75e0d52 100644
--- a/src/java/org/apache/poi/hssf/extractor/ExcelExtractor.java
+++ b/src/java/org/apache/poi/hssf/extractor/ExcelExtractor.java
@@ -19,8 +19,11 @@ package org.apache.poi.hssf.extractor;
import java.io.IOException;
import org.apache.poi.POIOLE2TextExtractor;
+import org.apache.poi.hssf.usermodel.HeaderFooter;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFComment;
+import org.apache.poi.hssf.usermodel.HSSFFooter;
+import org.apache.poi.hssf.usermodel.HSSFHeader;
import org.apache.poi.hssf.usermodel.HSSFRichTextString;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
@@ -89,6 +92,13 @@ public class ExcelExtractor extends POIOLE2TextExtractor {
}
}
+ // Header text, if there is any
+ if(sheet.getHeader() != null) {
+ text.append(
+ extractHeaderFooter(sheet.getHeader())
+ );
+ }
+
int firstRow = sheet.getFirstRowNum();
int lastRow = sheet.getLastRowNum();
for(int j=firstRow;j<=lastRow;j++) {
@@ -154,8 +164,37 @@ public class ExcelExtractor extends POIOLE2TextExtractor {
// Finish off the row
text.append("\n");
}
+
+ // Finally Feader text, if there is any
+ if(sheet.getFooter() != null) {
+ text.append(
+ extractHeaderFooter(sheet.getFooter())
+ );
+ }
}
return text.toString();
}
+
+ private String extractHeaderFooter(HeaderFooter hf) {
+ StringBuffer text = new StringBuffer();
+
+ if(hf.getLeft() != null) {
+ text.append(hf.getLeft());
+ }
+ if(hf.getCenter() != null) {
+ if(text.length() > 0)
+ text.append("\t");
+ text.append(hf.getCenter());
+ }
+ if(hf.getRight() != null) {
+ if(text.length() > 0)
+ text.append("\t");
+ text.append(hf.getRight());
+ }
+ if(text.length() > 0)
+ text.append("\n");
+
+ return text.toString();
+ }
}
diff --git a/src/java/org/apache/poi/hssf/record/RecordFactory.java b/src/java/org/apache/poi/hssf/record/RecordFactory.java
index aaee94991d..6f03c2c11c 100644
--- a/src/java/org/apache/poi/hssf/record/RecordFactory.java
+++ b/src/java/org/apache/poi/hssf/record/RecordFactory.java
@@ -1,4 +1,3 @@
-
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
@@ -15,294 +14,359 @@
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
-
package org.apache.poi.hssf.record;
import java.io.InputStream;
import java.lang.reflect.Constructor;
-import java.util.*;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
/**
* Title: Record Factory* Description: Takes a stream and outputs an array of Record objects.
*
- * @deprecated use {@link org.apache.poi.hssf.eventmodel.EventRecordFactory} instead
* @see org.apache.poi.hssf.eventmodel.EventRecordFactory
* @author Andrew C. Oliver (acoliver at apache dot org)
* @author Marc Johnson (mjohnson at apache dot org)
* @author Glen Stampoultzis (glens at apache.org)
* @author Csaba Nagy (ncsaba at yahoo dot com)
*/
+public final class RecordFactory {
+ private static final int NUM_RECORDS = 512;
-public class RecordFactory
-{
- private static int NUM_RECORDS = 10000;
- private static final Class[] records;
+ private static final Class[] CONSTRUCTOR_ARGS = { RecordInputStream.class, };
- static {
- records = new Class[]
- {
- BOFRecord.class, InterfaceHdrRecord.class, MMSRecord.class,
- InterfaceEndRecord.class, WriteAccessRecord.class,
- CodepageRecord.class, DSFRecord.class, TabIdRecord.class,
- FnGroupCountRecord.class, WindowProtectRecord.class,
- ProtectRecord.class, PasswordRecord.class, ProtectionRev4Record.class,
- PasswordRev4Record.class, WindowOneRecord.class, BackupRecord.class,
- HideObjRecord.class, DateWindow1904Record.class,
- PrecisionRecord.class, RefreshAllRecord.class, BookBoolRecord.class,
- FontRecord.class, FormatRecord.class, ExtendedFormatRecord.class,
- StyleRecord.class, UseSelFSRecord.class, BoundSheetRecord.class,
- CountryRecord.class, SSTRecord.class, ExtSSTRecord.class,
- EOFRecord.class, IndexRecord.class, CalcModeRecord.class,
- CalcCountRecord.class, RefModeRecord.class, IterationRecord.class,
- DeltaRecord.class, SaveRecalcRecord.class, PrintHeadersRecord.class,
- PrintGridlinesRecord.class, GridsetRecord.class, GutsRecord.class,
- DefaultRowHeightRecord.class, WSBoolRecord.class, HeaderRecord.class,
- FooterRecord.class, HCenterRecord.class, VCenterRecord.class,
- PrintSetupRecord.class, DefaultColWidthRecord.class,
- DimensionsRecord.class, RowRecord.class, LabelSSTRecord.class,
- RKRecord.class, NumberRecord.class, DBCellRecord.class,
- WindowTwoRecord.class, SelectionRecord.class, ContinueRecord.class,
- LabelRecord.class, BlankRecord.class, ColumnInfoRecord.class,
- MulRKRecord.class, MulBlankRecord.class, MergeCellsRecord.class,
- FormulaRecord.class, BoolErrRecord.class, ExternSheetRecord.class,
- NameRecord.class, LeftMarginRecord.class, RightMarginRecord.class,
- TopMarginRecord.class, BottomMarginRecord.class,
- DrawingRecord.class, DrawingGroupRecord.class, DrawingSelectionRecord.class,
- ObjRecord.class, TextObjectRecord.class,
- PaletteRecord.class, StringRecord.class, RecalcIdRecord.class, SharedFormulaRecord.class,
- HorizontalPageBreakRecord.class, VerticalPageBreakRecord.class,
- WriteProtectRecord.class, FilePassRecord.class, PaneRecord.class,
- NoteRecord.class, ObjectProtectRecord.class, ScenarioProtectRecord.class,
- FileSharingRecord.class, ChartTitleFormatRecord.class,
- DVRecord.class, DVALRecord.class, UncalcedRecord.class,
- ChartRecord.class, LegendRecord.class, ChartTitleFormatRecord.class,
- SeriesRecord.class, SeriesTextRecord.class,
- HyperlinkRecord.class,
- ExternalNameRecord.class, // TODO - same changes in non-@deprecated version of this class
- SupBookRecord.class,
- CRNCountRecord.class,
- CRNRecord.class,
- CFHeaderRecord.class,
- CFRuleRecord.class,
- TableRecord.class
- };
- }
- private static Map recordsMap = recordsToMap(records);
+ /**
+ * contains the classes for all the records we want to parse.
+ * Note - this most but not *every* subclass of Record.
+ */
+ private static final Class[] records = {
+ BackupRecord.class,
+ BlankRecord.class,
+ BOFRecord.class,
+ BookBoolRecord.class,
+ BoolErrRecord.class,
+ BottomMarginRecord.class,
+ BoundSheetRecord.class,
+ CalcCountRecord.class,
+ CalcModeRecord.class,
+ CFHeaderRecord.class,
+ CFRuleRecord.class,
+ ChartRecord.class,
+ ChartTitleFormatRecord.class,
+ CodepageRecord.class,
+ ColumnInfoRecord.class,
+ ContinueRecord.class,
+ CountryRecord.class,
+ CRNCountRecord.class,
+ CRNRecord.class,
+ DateWindow1904Record.class,
+ DBCellRecord.class,
+ DefaultColWidthRecord.class,
+ DefaultRowHeightRecord.class,
+ DeltaRecord.class,
+ DimensionsRecord.class,
+ DrawingGroupRecord.class,
+ DrawingRecord.class,
+ DrawingSelectionRecord.class,
+ DSFRecord.class,
+ DVALRecord.class,
+ DVRecord.class,
+ EOFRecord.class,
+ ExtendedFormatRecord.class,
+ ExternalNameRecord.class,
+ ExternSheetRecord.class,
+ ExtSSTRecord.class,
+ FilePassRecord.class,
+ FileSharingRecord.class,
+ FnGroupCountRecord.class,
+ FontRecord.class,
+ FooterRecord.class,
+ FormatRecord.class,
+ FormulaRecord.class,
+ GridsetRecord.class,
+ GutsRecord.class,
+ HCenterRecord.class,
+ HeaderRecord.class,
+ HideObjRecord.class,
+ HorizontalPageBreakRecord.class,
+ HyperlinkRecord.class,
+ IndexRecord.class,
+ InterfaceEndRecord.class,
+ InterfaceHdrRecord.class,
+ IterationRecord.class,
+ LabelRecord.class,
+ LabelSSTRecord.class,
+ LeftMarginRecord.class,
+ LegendRecord.class,
+ MergeCellsRecord.class,
+ MMSRecord.class,
+ MulBlankRecord.class,
+ MulRKRecord.class,
+ NameRecord.class,
+ NoteRecord.class,
+ NumberRecord.class,
+ ObjectProtectRecord.class,
+ ObjRecord.class,
+ PaletteRecord.class,
+ PaneRecord.class,
+ PasswordRecord.class,
+ PasswordRev4Record.class,
+ PrecisionRecord.class,
+ PrintGridlinesRecord.class,
+ PrintHeadersRecord.class,
+ PrintSetupRecord.class,
+ ProtectionRev4Record.class,
+ ProtectRecord.class,
+ RecalcIdRecord.class,
+ RefModeRecord.class,
+ RefreshAllRecord.class,
+ RightMarginRecord.class,
+ RKRecord.class,
+ RowRecord.class,
+ SaveRecalcRecord.class,
+ ScenarioProtectRecord.class,
+ SelectionRecord.class,
+ SeriesRecord.class,
+ SeriesTextRecord.class,
+ SharedFormulaRecord.class,
+ SSTRecord.class,
+ StringRecord.class,
+ StyleRecord.class,
+ SupBookRecord.class,
+ TabIdRecord.class,
+ TableRecord.class,
+ TextObjectRecord.class,
+ TopMarginRecord.class,
+ UncalcedRecord.class,
+ UseSelFSRecord.class,
+ VCenterRecord.class,
+ VerticalPageBreakRecord.class,
+ WindowOneRecord.class,
+ WindowProtectRecord.class,
+ WindowTwoRecord.class,
+ WriteAccessRecord.class,
+ WriteProtectRecord.class,
+ WSBoolRecord.class,
+ };
+
+ /**
+ * cache of the recordsToMap();
+ */
+ private static Map recordsMap = recordsToMap(records);
- /**
- * changes the default capacity (10000) to handle larger files
- */
+ private static short[] _allKnownRecordSIDs;
+
+ /**
+ * create a record, if there are MUL records than multiple records
+ * are returned digested into the non-mul form.
+ */
+ public static Record [] createRecord(RecordInputStream in) {
+ Constructor constructor = (Constructor) recordsMap.get(new Short(in.getSid()));
- public static void setCapacity(int capacity)
- {
- NUM_RECORDS = capacity;
- }
+ if (constructor == null) {
+ return new Record[] { new UnknownRecord(in), };
+ }
+
+ Record retval;
- /**
- * Create an array of records from an input stream
- *
- * @param in the InputStream from which the records will be
- * obtained
- *
- * @return an array of Records created from the InputStream
- *
- * @exception RecordFormatException on error processing the
- * InputStream
- */
+ try {
+ retval = ( Record ) constructor.newInstance(new Object[] { in });
+ } catch (InvocationTargetException e) {
+ throw new RecordFormatException("Unable to construct record instance" , e.getTargetException());
+ } catch (IllegalArgumentException e) {
+ throw new RuntimeException(e);
+ } catch (InstantiationException e) {
+ throw new RuntimeException(e);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+
+ if (retval instanceof RKRecord) {
+ // RK record is a slightly smaller alternative to NumberRecord
+ // POI likes NumberRecord better
+ RKRecord rk = ( RKRecord ) retval;
+ NumberRecord num = new NumberRecord();
- public static List createRecords(InputStream in)
- throws RecordFormatException
- {
- ArrayList records = new ArrayList(NUM_RECORDS);
+ num.setColumn(rk.getColumn());
+ num.setRow(rk.getRow());
+ num.setXFIndex(rk.getXFIndex());
+ num.setValue(rk.getRKNumber());
+ return new Record[] { num, };
+ }
+ if (retval instanceof DBCellRecord) {
+ // Not needed by POI. Regenerated from scratch by POI when spreadsheet is written
+ return new Record[] { null, };
+ }
+ // expand multiple records where necessary
+ if (retval instanceof MulRKRecord) {
+ MulRKRecord mrk = ( MulRKRecord ) retval;
- RecordInputStream recStream = new RecordInputStream(in);
- DrawingRecord lastDrawingRecord = new DrawingRecord( );
- Record lastRecord = null;
- while (recStream.hasNextRecord()) {
- recStream.nextRecord();
- if (recStream.getSid() != 0)
- {
- Record[] recs = createRecord(recStream); // handle MulRK records
+ Record[] mulRecs = new Record[ mrk.getNumColumns() ];
+ for (int k = 0; k < mrk.getNumColumns(); k++) {
+ NumberRecord nr = new NumberRecord();
- if (recs.length > 1)
- {
- for (int k = 0; k < recs.length; k++)
- {
- records.add(
- recs[ k ]); // these will be number records
- }
- }
- else
- {
- Record record = recs[ 0 ];
+ nr.setColumn(( short ) (k + mrk.getFirstColumn()));
+ nr.setRow(mrk.getRow());
+ nr.setXFIndex(mrk.getXFAt(k));
+ nr.setValue(mrk.getRKNumberAt(k));
+ mulRecs[ k ] = nr;
+ }
+ return mulRecs;
+ }
+ if (retval instanceof MulBlankRecord) {
+ MulBlankRecord mb = ( MulBlankRecord ) retval;
- if (record != null)
- {
- if (record.getSid() == DrawingGroupRecord.sid
- && lastRecord instanceof DrawingGroupRecord)
- {
- DrawingGroupRecord lastDGRecord = (DrawingGroupRecord) lastRecord;
- lastDGRecord.join((AbstractEscherHolderRecord) record);
- }
- else if (record.getSid() == ContinueRecord.sid &&
- ((lastRecord instanceof ObjRecord) || (lastRecord instanceof TextObjectRecord))) {
- // Drawing records have a very strange continue behaviour.
- //There can actually be OBJ records mixed between the continues.
- lastDrawingRecord.processContinueRecord( ((ContinueRecord)record).getData() );
- //we must rememeber the position of the continue record.
- //in the serialization procedure the original structure of records must be preserved
- records.add(record);
- } else if (record.getSid() == ContinueRecord.sid &&
- (lastRecord instanceof DrawingGroupRecord)) {
- ((DrawingGroupRecord)lastRecord).processContinueRecord(((ContinueRecord)record).getData());
- } else if (record.getSid() == ContinueRecord.sid &&
- (lastRecord instanceof StringRecord)) {
- ((StringRecord)lastRecord).processContinueRecord(((ContinueRecord)record).getData());
- } else if (record.getSid() == ContinueRecord.sid) {
- if (lastRecord instanceof UnknownRecord) {
- //Gracefully handle records that we dont know about,
- //that happen to be continued
- records.add(record);
- } else
- throw new RecordFormatException("Unhandled Continue Record");
- }
- else {
- lastRecord = record;
- if (record instanceof DrawingRecord)
- lastDrawingRecord = (DrawingRecord) record;
- records.add(record);
- }
- }
- }
- }
- }
+ Record[] mulRecs = new Record[ mb.getNumColumns() ];
+ for (int k = 0; k < mb.getNumColumns(); k++) {
+ BlankRecord br = new BlankRecord();
- return records;
- }
+ br.setColumn(( short ) (k + mb.getFirstColumn()));
+ br.setRow(mb.getRow());
+ br.setXFIndex(mb.getXFAt(k));
+ mulRecs[ k ] = br;
+ }
+ return mulRecs;
+ }
+ return new Record[] { retval, };
+ }
- public static Record [] createRecord(RecordInputStream in)
- {
- Record retval;
- Record[] realretval = null;
+ /**
+ * @return an array of all the SIDS for all known records
+ */
+ public static short[] getAllKnownRecordSIDs() {
+ if (_allKnownRecordSIDs == null) {
+ short[] results = new short[ recordsMap.size() ];
+ int i = 0;
- try
- {
- Constructor constructor =
- ( Constructor ) recordsMap.get(new Short(in.getSid()));
+ for (Iterator iterator = recordsMap.keySet().iterator(); iterator.hasNext(); ) {
+ Short sid = (Short) iterator.next();
- if (constructor != null)
- {
- retval = ( Record ) constructor.newInstance(new Object[]
- {
- in
- });
- }
- else
- {
- retval = new UnknownRecord(in);
- }
- }
- catch (Exception introspectionException)
- {
- throw new RecordFormatException("Unable to construct record instance",introspectionException);
- }
- if (retval instanceof RKRecord)
- {
- RKRecord rk = ( RKRecord ) retval;
- NumberRecord num = new NumberRecord();
+ results[i++] = sid.shortValue();
+ }
+ Arrays.sort(results);
+ _allKnownRecordSIDs = results;
+ }
- num.setColumn(rk.getColumn());
- num.setRow(rk.getRow());
- num.setXFIndex(rk.getXFIndex());
- num.setValue(rk.getRKNumber());
- retval = num;
- }
- else if (retval instanceof DBCellRecord)
- {
- retval = null;
- }
- else if (retval instanceof MulRKRecord)
- {
- MulRKRecord mrk = ( MulRKRecord ) retval;
+ return (short[]) _allKnownRecordSIDs.clone();
+ }
- realretval = new Record[ mrk.getNumColumns() ];
- for (int k = 0; k < mrk.getNumColumns(); k++)
- {
- NumberRecord nr = new NumberRecord();
+ /**
+ * gets the record constructors and sticks them in the map by SID
+ * @return map of SIDs to short,short,byte[] constructors for Record classes
+ * most of org.apache.poi.hssf.record.*
+ */
+ private static Map recordsToMap(Class [] records) {
+ Map result = new HashMap();
+ Set uniqueRecClasses = new HashSet(records.length * 3 / 2);
- nr.setColumn(( short ) (k + mrk.getFirstColumn()));
- nr.setRow(mrk.getRow());
- nr.setXFIndex(mrk.getXFAt(k));
- nr.setValue(mrk.getRKNumberAt(k));
- realretval[ k ] = nr;
- }
- }
- else if (retval instanceof MulBlankRecord)
- {
- MulBlankRecord mb = ( MulBlankRecord ) retval;
+ for (int i = 0; i < records.length; i++) {
- realretval = new Record[ mb.getNumColumns() ];
- for (int k = 0; k < mb.getNumColumns(); k++)
- {
- BlankRecord br = new BlankRecord();
+ Class recClass = records[ i ];
+ if(!Record.class.isAssignableFrom(recClass)) {
+ throw new RuntimeException("Invalid record sub-class (" + recClass.getName() + ")");
+ }
+ if(Modifier.isAbstract(recClass.getModifiers())) {
+ throw new RuntimeException("Invalid record class (" + recClass.getName() + ") - must not be abstract");
+ }
+ if(!uniqueRecClasses.add(recClass)) {
+ throw new RuntimeException("duplicate record class (" + recClass.getName() + ")");
+ }
+
+ short sid;
+ Constructor constructor;
+ try {
+ sid = recClass.getField("sid").getShort(null);
+ constructor = recClass.getConstructor(CONSTRUCTOR_ARGS);
+ } catch (Exception illegalArgumentException) {
+ throw new RecordFormatException(
+ "Unable to determine record types");
+ }
+ Short key = new Short(sid);
+ if (result.containsKey(key)) {
+ Class prev = (Class)result.get(key);
+ throw new RuntimeException("duplicate record sid 0x" + Integer.toHexString(sid).toUpperCase()
+ + " for classes (" + recClass.getName() + ") and (" + prev.getName() + ")");
+ }
+ result.put(key, constructor);
+ }
+ return result;
+ }
- br.setColumn(( short ) (k + mb.getFirstColumn()));
- br.setRow(mb.getRow());
- br.setXFIndex(mb.getXFAt(k));
- realretval[ k ] = br;
- }
- }
- if (realretval == null)
- {
- realretval = new Record[ 1 ];
- realretval[ 0 ] = retval;
- }
- return realretval;
- }
+ /**
+ * Create an array of records from an input stream
+ *
+ * @param in the InputStream from which the records will be obtained
+ *
+ * @return an array of Records created from the InputStream
+ *
+ * @exception RecordFormatException on error processing the InputStream
+ */
+ public static List createRecords(InputStream in) throws RecordFormatException {
- public static short [] getAllKnownRecordSIDs()
- {
- short[] results = new short[ recordsMap.size() ];
- int i = 0;
+ List records = new ArrayList(NUM_RECORDS);
- for (Iterator iterator = recordsMap.keySet().iterator();
- iterator.hasNext(); )
- {
- Short sid = ( Short ) iterator.next();
+ RecordInputStream recStream = new RecordInputStream(in);
+ DrawingRecord lastDrawingRecord = new DrawingRecord( );
+ Record lastRecord = null;
+ while (recStream.hasNextRecord()) {
+ recStream.nextRecord();
+ if (recStream.getSid() != 0) {
+ Record[] recs = createRecord(recStream); // handle MulRK records
- results[ i++ ] = sid.shortValue();
- }
- return results;
- }
+ if (recs.length > 1) {
+ for (int k = 0; k < recs.length; k++) {
+ records.add(recs[ k ]); // these will be number records
+ }
+ } else {
+ Record record = recs[ 0 ];
- private static Map recordsToMap(Class [] records)
- {
- Map result = new HashMap();
- Constructor constructor;
-
- for (int i = 0; i < records.length; i++)
- {
- Class record;
- short sid;
-
- record = records[ i ];
- try
- {
- sid = record.getField("sid").getShort(null);
- constructor = record.getConstructor(new Class[]
- {
- RecordInputStream.class
- });
- }
- catch (Exception illegalArgumentException)
- {
- throw new RecordFormatException(
- "Unable to determine record types", illegalArgumentException);
- }
- result.put(new Short(sid), constructor);
- }
- return result;
- }
+ if (record != null) {
+ if (record.getSid() == DrawingGroupRecord.sid
+ && lastRecord instanceof DrawingGroupRecord) {
+ DrawingGroupRecord lastDGRecord = (DrawingGroupRecord) lastRecord;
+ lastDGRecord.join((AbstractEscherHolderRecord) record);
+ } else if (record.getSid() == ContinueRecord.sid &&
+ ((lastRecord instanceof ObjRecord) || (lastRecord instanceof TextObjectRecord))) {
+ // Drawing records have a very strange continue behaviour.
+ //There can actually be OBJ records mixed between the continues.
+ lastDrawingRecord.processContinueRecord( ((ContinueRecord)record).getData() );
+ //we must remember the position of the continue record.
+ //in the serialization procedure the original structure of records must be preserved
+ records.add(record);
+ } else if (record.getSid() == ContinueRecord.sid &&
+ (lastRecord instanceof DrawingGroupRecord)) {
+ ((DrawingGroupRecord)lastRecord).processContinueRecord(((ContinueRecord)record).getData());
+ } else if (record.getSid() == ContinueRecord.sid &&
+ (lastRecord instanceof StringRecord)) {
+ ((StringRecord)lastRecord).processContinueRecord(((ContinueRecord)record).getData());
+ } else if (record.getSid() == ContinueRecord.sid) {
+ if (lastRecord instanceof UnknownRecord) {
+ //Gracefully handle records that we dont know about,
+ //that happen to be continued
+ records.add(record);
+ } else
+ throw new RecordFormatException("Unhandled Continue Record");
+ } else {
+ lastRecord = record;
+ if (record instanceof DrawingRecord) {
+ lastDrawingRecord = (DrawingRecord) record;
+ }
+ records.add(record);
+ }
+ }
+ }
+ }
+ }
+ return records;
+ }
}
diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFFooter.java b/src/java/org/apache/poi/hssf/usermodel/HSSFFooter.java
index 4e62e8b474..d85f1b60e3 100644
--- a/src/java/org/apache/poi/hssf/usermodel/HSSFFooter.java
+++ b/src/java/org/apache/poi/hssf/usermodel/HSSFFooter.java
@@ -33,8 +33,7 @@ import org.apache.poi.ss.usermodel.Footer;
*
* @author Shawn Laubach (slaubach at apache dot org)
*/
-public class HSSFFooter implements Footer {
-
+public class HSSFFooter implements Footer, HeaderFooter {
FooterRecord footerRecord;
String left;
String center;
diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFHeader.java b/src/java/org/apache/poi/hssf/usermodel/HSSFHeader.java
index e1cd0b0091..535b70efa1 100644
--- a/src/java/org/apache/poi/hssf/usermodel/HSSFHeader.java
+++ b/src/java/org/apache/poi/hssf/usermodel/HSSFHeader.java
@@ -33,7 +33,7 @@ import org.apache.poi.ss.usermodel.Header;
*
* @author Shawn Laubach (slaubach at apache dot org)
*/
-public class HSSFHeader implements Header
+public class HSSFHeader implements Header, HeaderFooter
{
HeaderRecord headerRecord;
diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java b/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java
index d5b801c5c1..5422776fc1 100644
--- a/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java
+++ b/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java
@@ -34,7 +34,6 @@ import org.apache.poi.ddf.EscherBSERecord;
import org.apache.poi.ddf.EscherBitmapBlip;
import org.apache.poi.ddf.EscherBlipRecord;
import org.apache.poi.ddf.EscherRecord;
-import org.apache.poi.hssf.eventmodel.EventRecordFactory;
import org.apache.poi.hssf.model.Sheet;
import org.apache.poi.hssf.model.Workbook;
import org.apache.poi.hssf.record.AbstractEscherHolderRecord;
@@ -264,8 +263,6 @@ public class HSSFWorkbook extends POIDocument implements org.apache.poi.ss.userm
// it happens to be spelled.
InputStream stream = directory.createDocumentInputStream(workbookName);
- EventRecordFactory factory = new EventRecordFactory();
-
List records = RecordFactory.createRecords(stream);
workbook = Workbook.createWorkbook(records);
diff --git a/src/java/org/apache/poi/hssf/usermodel/HeaderFooter.java b/src/java/org/apache/poi/hssf/usermodel/HeaderFooter.java
new file mode 100644
index 0000000000..44dfd79567
--- /dev/null
+++ b/src/java/org/apache/poi/hssf/usermodel/HeaderFooter.java
@@ -0,0 +1,24 @@
+/* ====================================================================
+ 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.usermodel;
+
+/**
+ * Common interface for {@link HSSFHeader} and
+ * {@link HSSFFooter}.
+ */
+public interface HeaderFooter extends org.apache.poi.ss.usermodel.HeaderFooter {
+}
diff --git a/src/java/org/apache/poi/ss/usermodel/HeaderFooter.java b/src/java/org/apache/poi/ss/usermodel/HeaderFooter.java
new file mode 100644
index 0000000000..c58f929e43
--- /dev/null
+++ b/src/java/org/apache/poi/ss/usermodel/HeaderFooter.java
@@ -0,0 +1,32 @@
+/* ====================================================================
+ 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.ss.usermodel;
+
+/**
+ * Common interface for {@link Header} and
+ * {@link Footer}.
+ */
+public interface HeaderFooter {
+ public String getLeft();
+ public void setLeft( String newLeft );
+
+ public String getCenter();
+ public void setCenter( String newCenter );
+
+ public String getRight();
+ public void setRight( String newRight );
+}
diff --git a/src/testcases/org/apache/poi/hssf/data/45538_classic_Footer.xls b/src/testcases/org/apache/poi/hssf/data/45538_classic_Footer.xls
new file mode 100644
index 0000000000..2ab1d241ca
Binary files /dev/null and b/src/testcases/org/apache/poi/hssf/data/45538_classic_Footer.xls differ
diff --git a/src/testcases/org/apache/poi/hssf/data/45538_classic_Header.xls b/src/testcases/org/apache/poi/hssf/data/45538_classic_Header.xls
new file mode 100644
index 0000000000..fe67b84f85
Binary files /dev/null and b/src/testcases/org/apache/poi/hssf/data/45538_classic_Header.xls differ
diff --git a/src/testcases/org/apache/poi/hssf/data/45538_form_Footer.xls b/src/testcases/org/apache/poi/hssf/data/45538_form_Footer.xls
new file mode 100644
index 0000000000..6d1731d6a8
Binary files /dev/null and b/src/testcases/org/apache/poi/hssf/data/45538_form_Footer.xls differ
diff --git a/src/testcases/org/apache/poi/hssf/data/45538_form_Header.xls b/src/testcases/org/apache/poi/hssf/data/45538_form_Header.xls
new file mode 100644
index 0000000000..c2a28219ca
Binary files /dev/null and b/src/testcases/org/apache/poi/hssf/data/45538_form_Header.xls differ
diff --git a/src/testcases/org/apache/poi/hssf/eventmodel/TestEventRecordFactory.java b/src/testcases/org/apache/poi/hssf/eventmodel/TestEventRecordFactory.java
index 0449795b5d..c4a5ec84b3 100644
--- a/src/testcases/org/apache/poi/hssf/eventmodel/TestEventRecordFactory.java
+++ b/src/testcases/org/apache/poi/hssf/eventmodel/TestEventRecordFactory.java
@@ -18,80 +18,24 @@
package org.apache.poi.hssf.eventmodel;
import java.io.ByteArrayInputStream;
-import java.util.Iterator;
-
-import org.apache.poi.hssf.record.BOFRecord;
-import org.apache.poi.hssf.record.EOFRecord;
-import org.apache.poi.hssf.record.Record;
-import org.apache.poi.hssf.record.UnknownRecord;
-import org.apache.poi.hssf.record.ContinueRecord;
-import org.apache.poi.hssf.record.TestcaseRecordInputStream;
import junit.framework.TestCase;
+import org.apache.poi.hssf.record.BOFRecord;
+import org.apache.poi.hssf.record.ContinueRecord;
+import org.apache.poi.hssf.record.EOFRecord;
+import org.apache.poi.hssf.record.Record;
+import org.apache.poi.hssf.record.RecordFactory;
+import org.apache.poi.hssf.record.TestcaseRecordInputStream;
+import org.apache.poi.hssf.record.UnknownRecord;
+
/**
* enclosing_type describe the purpose here
*
* @author Andrew C. Oliver acoliver@apache.org
* @author Csaba Nagy (ncsaba at yahoo dot com)
*/
-public class TestEventRecordFactory extends TestCase
-{
- boolean wascalled;
-
- private EventRecordFactory factory;
- /**
- * Constructor for TestEventRecordFactory.
- * @param arg0
- */
- public TestEventRecordFactory(String arg0)
- {
- super(arg0);
- }
-
- public static void main(String[] args)
- {
- junit.textui.TestRunner.run(TestEventRecordFactory.class);
- }
-
- protected void setUp() throws Exception
- {
- super.setUp();
- factory = new EventRecordFactory();
- }
-
- protected void tearDown() throws Exception
- {
- super.tearDown();
- }
-
- /**
- * tests that a listener can be registered and that once
- * registered can be returned as expected.
- */
- public void testRegisterListener()
- {
- factory.registerListener(new ERFListener() {
- public boolean processRecord(Record rec) {
- return true;
- }
- },null);
-
- Iterator i = factory.listeners();
- assertTrue("iterator must have one",i.hasNext());
-
- factory.registerListener(new ERFListener() {
- public boolean processRecord(Record rec) {
- return true;
- }
- },null);
-
- i = factory.listeners();
-
- i.next();
- assertTrue("iterator must have two",i.hasNext());
- factory = new EventRecordFactory();
- }
+public final class TestEventRecordFactory extends TestCase {
/**
* tests that the records can be processed and properly return
@@ -99,17 +43,17 @@ public class TestEventRecordFactory extends TestCase
*/
public void testProcessRecords()
{
- byte[] bytes = null;
- int offset = 0;
- //boolean wascalled = false;
- factory.registerListener(new ERFListener() {
+ final boolean[] wascalled = { false, }; // hack to pass boolean by ref into inner class
+
+ ERFListener listener = new ERFListener() {
public boolean processRecord(Record rec) {
- wascalled = true;
+ wascalled[0] = true;
assertTrue("must be BOFRecord got SID="+rec.getSid(),
(rec.getSid() == BOFRecord.sid));
return true;
}
- }, new short[] {BOFRecord.sid});
+ };
+ EventRecordFactory factory = new EventRecordFactory(listener, new short[] {BOFRecord.sid});
BOFRecord bof = new BOFRecord();
bof.setBuild((short)0);
@@ -120,23 +64,20 @@ public class TestEventRecordFactory extends TestCase
bof.setHistoryBitMask(BOFRecord.HISTORY_MASK);
EOFRecord eof = new EOFRecord();
- bytes = new byte[bof.getRecordSize() + eof.getRecordSize()];
+ byte[] bytes = new byte[bof.getRecordSize() + eof.getRecordSize()];
+ int offset = 0;
offset = bof.serialize(offset,bytes);
offset = eof.serialize(offset,bytes);
factory.processRecords(new ByteArrayInputStream(bytes));
- assertTrue("The record listener must be called",wascalled);
+ assertTrue("The record listener must be called", wascalled[0]);
}
/**
* tests that the create record function returns a properly
* constructed record in the simple case.
*/
- public void testCreateRecord()
- {
- byte[] bytes = null;
- byte[] nbytes = null;
- Record[] records = null;
+ public void testCreateRecord() {
BOFRecord bof = new BOFRecord();
bof.setBuild((short)0);
bof.setBuildYear((short)1999);
@@ -145,11 +86,11 @@ public class TestEventRecordFactory extends TestCase
bof.setVersion((short)0x06);
bof.setHistoryBitMask(BOFRecord.HISTORY_MASK);
- bytes = bof.serialize();
- nbytes = new byte[bytes.length - 4];
+ byte[] bytes = bof.serialize();
+ byte[] nbytes = new byte[bytes.length - 4];
System.arraycopy(bytes,4,nbytes,0,nbytes.length);
- records = factory.createRecord(new TestcaseRecordInputStream(bof.getSid(),(short)nbytes.length,nbytes));
+ Record[] records = RecordFactory.createRecord(new TestcaseRecordInputStream(bof.getSid(),(short)nbytes.length,nbytes));
assertTrue("record.length must be 1, was ="+records.length,records.length == 1);
assertTrue("record is the same", compareRec(bof,records[0]));
@@ -162,24 +103,19 @@ public class TestEventRecordFactory extends TestCase
* @param second the second record to compare
* @return boolean whether or not the record where equal
*/
- private boolean compareRec(Record first, Record second)
- {
- boolean retval = true;
+ private static boolean compareRec(Record first, Record second) {
byte[] rec1 = first.serialize();
byte[] rec2 = second.serialize();
- if (rec1.length == rec2.length) {
- for (int k=0; k