]> source.dussan.org Git - poi.git/commitdiff
Merged revisions 638786-638802,638805-638811,638813-638814,638816-639230,639233-63924...
authorNick Burch <nick@apache.org>
Mon, 4 Aug 2008 21:31:51 +0000 (21:31 +0000)
committerNick Burch <nick@apache.org>
Mon, 4 Aug 2008 21:31:51 +0000 (21:31 +0000)
https://svn.apache.org/repos/asf/poi/trunk

........
  r682508 | josh | 2008-08-04 22:08:11 +0100 (Mon, 04 Aug 2008) | 1 line

  Patch 44894 - refactoring duplicate logic from EventRecordFactory to RecordFactory
........
  r682510 | josh | 2008-08-04 22:14:37 +0100 (Mon, 04 Aug 2008) | 1 line

  removed debugging code leftover from r682508 (patch 44894)
........
  r682511 | nick | 2008-08-04 22:21:16 +0100 (Mon, 04 Aug 2008) | 1 line

  Fix bug #45538 - Include excel headers and footers in the output of ExcelExtractor
........

git-svn-id: https://svn.apache.org/repos/asf/poi/branches/ooxml@682516 13f79535-47bb-0310-9956-ffa450edef68

19 files changed:
src/documentation/content/xdocs/changes.xml
src/documentation/content/xdocs/status.xml
src/java/org/apache/poi/hssf/eventmodel/EventRecordFactory.java
src/java/org/apache/poi/hssf/eventmodel/ModelFactory.java
src/java/org/apache/poi/hssf/extractor/ExcelExtractor.java
src/java/org/apache/poi/hssf/record/RecordFactory.java
src/java/org/apache/poi/hssf/usermodel/HSSFFooter.java
src/java/org/apache/poi/hssf/usermodel/HSSFHeader.java
src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java
src/java/org/apache/poi/hssf/usermodel/HeaderFooter.java [new file with mode: 0644]
src/java/org/apache/poi/ss/usermodel/HeaderFooter.java [new file with mode: 0644]
src/testcases/org/apache/poi/hssf/data/45538_classic_Footer.xls [new file with mode: 0644]
src/testcases/org/apache/poi/hssf/data/45538_classic_Header.xls [new file with mode: 0644]
src/testcases/org/apache/poi/hssf/data/45538_form_Footer.xls [new file with mode: 0644]
src/testcases/org/apache/poi/hssf/data/45538_form_Header.xls [new file with mode: 0644]
src/testcases/org/apache/poi/hssf/eventmodel/TestEventRecordFactory.java
src/testcases/org/apache/poi/hssf/extractor/TestExcelExtractor.java
src/testcases/org/apache/poi/hssf/model/TestSheet.java
src/testcases/org/apache/poi/hssf/usermodel/TestDataValidation.java

index 8e167940bc9350c2d40afdb72d41631552536beb..569691b6cf771e1c813dda479c4f7135a246d02a 100644 (file)
@@ -52,6 +52,8 @@
            <action dev="POI-DEVELOPERS" type="add">Created a common interface for handling Excel files, irrespective of if they are .xls or .xlsx</action>
         </release>
         <release version="3.1.1-alpha1" date="2008-??-??">
+           <action dev="POI-DEVELOPERS" type="fix">45538 - Include excel headers and footers in the output of ExcelExtractor</action>
+           <action dev="POI-DEVELOPERS" type="fix">44894 - refactor duplicate logic from EventRecordFactory to RecordFactory</action>
            <action dev="POI-DEVELOPERS" type="add">Support for Headers / Footers in HSLF</action>
            <action dev="POI-DEVELOPERS" type="fix">44953 - Extensive fixes for data validation</action>
            <action dev="POI-DEVELOPERS" type="fix">45519 - Fixed to keep datavalidation records together</action>
index 790390b0289274c83d83d856d63e29ca7e18d47d..1b665f1ae7d819a98caffb55ae22fd2c2fa38344 100644 (file)
@@ -49,6 +49,8 @@
            <action dev="POI-DEVELOPERS" type="add">Created a common interface for handling Excel files, irrespective of if they are .xls or .xlsx</action>
         </release>
         <release version="3.1.1-alpha1" date="2008-??-??">
+           <action dev="POI-DEVELOPERS" type="fix">45538 - Include excel headers and footers in the output of ExcelExtractor</action>
+           <action dev="POI-DEVELOPERS" type="fix">44894 - refactor duplicate logic from EventRecordFactory to RecordFactory</action>
            <action dev="POI-DEVELOPERS" type="add">Support for Headers / Footers in HSLF</action>
            <action dev="POI-DEVELOPERS" type="fix">44953 - Extensive fixes for data validation</action>
            <action dev="POI-DEVELOPERS" type="fix">45519 - Fixed to keep datavalidation records together</action>
index 8c4abb76ad8d6e9415c78b3ac224799e784c9a49..5cdd81370092e73e1de1df6088bf26518a52a8b3 100644 (file)
 package org.apache.poi.hssf.eventmodel;
 
 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;
-import org.apache.poi.hssf.record.BlankRecord;
-import org.apache.poi.hssf.record.BookBoolRecord;
-import org.apache.poi.hssf.record.BoolErrRecord;
-import org.apache.poi.hssf.record.BottomMarginRecord;
-import org.apache.poi.hssf.record.BoundSheetRecord;
-import org.apache.poi.hssf.record.CalcCountRecord;
-import org.apache.poi.hssf.record.CalcModeRecord;
-import org.apache.poi.hssf.record.CodepageRecord;
-import org.apache.poi.hssf.record.ColumnInfoRecord;
-import org.apache.poi.hssf.record.ContinueRecord;
-import org.apache.poi.hssf.record.CountryRecord;
-import org.apache.poi.hssf.record.DBCellRecord;
-import org.apache.poi.hssf.record.DSFRecord;
-import org.apache.poi.hssf.record.DateWindow1904Record;
-import org.apache.poi.hssf.record.DefaultColWidthRecord;
-import org.apache.poi.hssf.record.DefaultRowHeightRecord;
-import org.apache.poi.hssf.record.DeltaRecord;
-import org.apache.poi.hssf.record.DimensionsRecord;
-import org.apache.poi.hssf.record.EOFRecord;
-import org.apache.poi.hssf.record.ExtSSTRecord;
-import org.apache.poi.hssf.record.ExtendedFormatRecord;
-import org.apache.poi.hssf.record.ExternSheetRecord;
-import org.apache.poi.hssf.record.FnGroupCountRecord;
-import org.apache.poi.hssf.record.FontRecord;
-import org.apache.poi.hssf.record.FooterRecord;
-import org.apache.poi.hssf.record.FormatRecord;
-import org.apache.poi.hssf.record.GridsetRecord;
-import org.apache.poi.hssf.record.GutsRecord;
-import org.apache.poi.hssf.record.HCenterRecord;
-import org.apache.poi.hssf.record.HeaderRecord;
-import org.apache.poi.hssf.record.HideObjRecord;
-import org.apache.poi.hssf.record.IndexRecord;
-import org.apache.poi.hssf.record.InterfaceEndRecord;
-import org.apache.poi.hssf.record.InterfaceHdrRecord;
-import org.apache.poi.hssf.record.IterationRecord;
-import org.apache.poi.hssf.record.LabelRecord;
-import org.apache.poi.hssf.record.LabelSSTRecord;
-import org.apache.poi.hssf.record.LeftMarginRecord;
-import org.apache.poi.hssf.record.MMSRecord;
-import org.apache.poi.hssf.record.MergeCellsRecord;
-import org.apache.poi.hssf.record.MulBlankRecord;
-import org.apache.poi.hssf.record.MulRKRecord;
-import org.apache.poi.hssf.record.NameRecord;
-import org.apache.poi.hssf.record.NumberRecord;
-import org.apache.poi.hssf.record.PaneRecord;
-import org.apache.poi.hssf.record.PaletteRecord;
-import org.apache.poi.hssf.record.PasswordRecord;
-import org.apache.poi.hssf.record.PasswordRev4Record;
-import org.apache.poi.hssf.record.PrecisionRecord;
-import org.apache.poi.hssf.record.PrintGridlinesRecord;
-import org.apache.poi.hssf.record.PrintHeadersRecord;
-import org.apache.poi.hssf.record.PrintSetupRecord;
-import org.apache.poi.hssf.record.ProtectRecord;
-import org.apache.poi.hssf.record.ProtectionRev4Record;
-import org.apache.poi.hssf.record.RKRecord;
-import org.apache.poi.hssf.record.Record;
-import org.apache.poi.hssf.record.RecordFormatException;
-import org.apache.poi.hssf.record.RecordInputStream;
-import org.apache.poi.hssf.record.RefModeRecord;
-import org.apache.poi.hssf.record.RefreshAllRecord;
-import org.apache.poi.hssf.record.RightMarginRecord;
-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;
-import org.apache.poi.hssf.record.TableRecord;
-import org.apache.poi.hssf.record.TopMarginRecord;
-import org.apache.poi.hssf.record.UnknownRecord;
-import org.apache.poi.hssf.record.UseSelFSRecord;
-import org.apache.poi.hssf.record.VCenterRecord;
-import org.apache.poi.hssf.record.WSBoolRecord;
-import org.apache.poi.hssf.record.WindowOneRecord;
-import org.apache.poi.hssf.record.WindowProtectRecord;
-import org.apache.poi.hssf.record.WindowTwoRecord;
-import org.apache.poi.hssf.record.WriteAccessRecord;
-import org.apache.poi.hssf.record.WriteProtectRecord;
-import org.apache.poi.hssf.record.FilePassRecord;
-import org.apache.poi.hssf.record.NoteRecord;
+import java.util.Arrays;
 
+import org.apache.poi.hssf.record.*;
 
 /**
  * Event-based record factory.  As opposed to RecordFactory
- * this refactored version throws record events as it comes
- * accross the records.  I throws the "lazily" one record behind
- * to ensure that ContinueRecords are processed first.
+ * this version sends {@link ERFListener#processRecord(Record) } messages to 
+ * the supplied listener.  Record notifications are sent one record behind
+ * to ensure that {@link ContinueRecord}s are processed first.
  * 
  * @author Andrew C. Oliver (acoliver@apache.org) - probably to blame for the bugs (so yank his chain on the list)
  * @author Marc Johnson (mjohnson at apache dot org) - methods taken from RecordFactory
  * @author Glen Stampoultzis (glens at apache.org) - methods taken from RecordFactory
  * @author Csaba Nagy (ncsaba at yahoo dot com)
  */
-public class EventRecordFactory
-{
-    
-    /**
-     * contains the classes for all the records we want to parse.
-     */
-    private static final Class[] records;
+public final class EventRecordFactory {
 
-    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,
-                BoolErrRecord.class, ExternSheetRecord.class, NameRecord.class,
-                LeftMarginRecord.class, RightMarginRecord.class,
-                TopMarginRecord.class, BottomMarginRecord.class,
-                PaletteRecord.class, StringRecord.class, SharedFormulaRecord.class, 
-                WriteProtectRecord.class, FilePassRecord.class, PaneRecord.class,
-                NoteRecord.class, TableRecord.class
-            };
-       
-    }
-    
-    /**
-     * cache of the recordsToMap();
-     */
-    private static Map           recordsMap  = recordsToMap(records);
-
-    /**
-     * cache of the return of getAllKnownSids so that we don't have to
-     * expensively get them every time.
-     */
-    private static short[] sidscache;
-
-    /**
-     * List of the listners that are registred.  should all be ERFListener
-     */    
-    private List listeners;
+    private final ERFListener _listener;
+       private final short[] _sids;
 
     /**
-     * instance is abortable or not
-     */
-    private boolean abortable;
-    
-    /**
-     * Construct an abortable EventRecordFactory.  
-     * The same as calling new EventRecordFactory(true)
-     * @see #EventRecordFactory(boolean)
-     */
-    public EventRecordFactory() {
-        this(true);                  
-    }
-    
-    /**
-     * Create an EventRecordFactory
-     * @param abortable specifies whether the return from the listener 
-     * handler functions are obeyed.  False means they are ignored. True
-     * means the event loop exits on error.
-     */
-    public EventRecordFactory(boolean abortable) {
-        this.abortable = abortable;
-        listeners = new ArrayList(recordsMap.size());    
-        
-        if (sidscache == null) {
-         sidscache = getAllKnownRecordSIDs();   
-        }
-
-    }
-    
-    /**
-     * Register a listener for records.  These can be for all records 
-     * or just a subset.
-     * 
+     *
      * @param sids an array of Record.sid values identifying the records
      * the listener will work with.  Alternatively if this is "null" then 
-     * all records are passed.
+     * all records are passed. For all 'known' record types use {@link RecordFactory#getAllKnownRecordSIDs()}
      */
-    public void registerListener(ERFListener listener, short[] sids) {
-      if (sids == null)
-        sids = sidscache;
-      ERFListener wrapped = new ListenerWrapper(listener, sids, abortable);    
-      listeners.add(wrapped);
-    }
-    
-    /**
-     * used for unit tests to test the registration of record listeners.
-     * @return Iterator of ERFListeners
-     */
-    protected Iterator listeners() {
-        return listeners.iterator();
+    public EventRecordFactory(ERFListener listener, short[] sids) {
+        _listener = listener;
+        if (sids == null) {
+               _sids = null;
+        } else {
+            _sids = (short[]) sids.clone();
+            Arrays.sort(_sids); // for faster binary search
+        }
     }
+       private boolean isSidIncluded(short sid) {
+               if (_sids == null) {
+                       return true;
+               }
+               return Arrays.binarySearch(_sids, sid) >= 0;
+       }   
+
 
     /**
      * sends the record event to all registered listeners.
      * @param record the record to be thrown.
-     * @return boolean abort.  If exitability is turned on this aborts
-     * out of the event loop should any listener specify to do so.
+     * @return <code>false</code> 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.
+            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;   
+                        }
                     }
-                    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;
-                            }
+                    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 (record != null) {
+                    if (last_record != null) {
+                        if (!processRecord(last_record)) {
+                            return;   
                         }
                     }
-
-            
-            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");
+                     last_record = record;
+                }
             }
-            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 (last_record != null) {
+               processRecord(last_record);               
         }
-        return result;
-    }   
-}
+    }
+}
\ No newline at end of file
index ade4532042cca9075e09bcd9685d56c697994d83..d75ca6c2e6d8687687df97e6057c5b38b1d3991f 100644 (file)
@@ -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);
     }
index 75a73c654d5c0fef29715de2ed5293a0e81616a7..efa75e0d52b547b6cbcd8fe5124bdf2505c1484e 100644 (file)
@@ -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,7 +164,36 @@ 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();
        }
index aaee94991d6fbc3c05f3e3604130c08d252968b0..6f03c2c11cf9f28dd1ac57515d87ef358932068d 100644 (file)
@@ -1,4 +1,3 @@
-
 /* ====================================================================
    Licensed to the Apache Software Foundation (ASF) under one or more
    contributor license agreements.  See the NOTICE file distributed with
    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<P>
  * Description:  Takes a stream and outputs an array of Record objects.<P>
  *
- * @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;
-
-    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);
-
-    /**
-     * changes the default capacity (10000) to handle larger files
-     */
+       private static final Class[] CONSTRUCTOR_ARGS = { RecordInputStream.class, };
 
-    public static void setCapacity(int capacity)
-    {
-        NUM_RECORDS = capacity;
-    }
+       /**
+        * contains the classes for all the records we want to parse.<br/>
+        * 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);
 
-    /**
-     * 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
-     */
+       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 List createRecords(InputStream in)
-        throws RecordFormatException
-    {
-        ArrayList records     = new ArrayList(NUM_RECORDS);
+               if (constructor == null) {
+                       return new Record[] { new UnknownRecord(in), };
+               }
+               
+               Record 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
+               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();
 
-                    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 ];
+                       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;
 
-                        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[ mrk.getNumColumns() ];
+                       for (int k = 0; k < mrk.getNumColumns(); k++) {
+                               NumberRecord nr = new NumberRecord();
 
-        return records;
-    }
+                               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;
 
-    public static Record [] createRecord(RecordInputStream in)
-    {
-        Record   retval;
-        Record[] realretval = null;
+                       Record[] mulRecs = new Record[ mb.getNumColumns() ];
+                       for (int k = 0; k < mb.getNumColumns(); k++) {
+                               BlankRecord br = new BlankRecord();
 
-        try
-        {
-            Constructor constructor =
-                ( Constructor ) recordsMap.get(new Short(in.getSid()));
+                               br.setColumn(( short ) (k + mb.getFirstColumn()));
+                               br.setRow(mb.getRow());
+                               br.setXFIndex(mb.getXFAt(k));
+                               mulRecs[ k ] = br;
+                       }
+                       return mulRecs;
+               }
+               return new Record[] { retval, };
+       }
 
-            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();
+       /**
+        * @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;
 
-            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;
+                       for (Iterator iterator = recordsMap.keySet().iterator(); iterator.hasNext(); ) {
+                               Short sid = (Short) iterator.next();
 
-            realretval = new Record[ mrk.getNumColumns() ];
-            for (int k = 0; k < mrk.getNumColumns(); k++)
-            {
-                NumberRecord nr = new NumberRecord();
+                               results[i++] = sid.shortValue();
+                       }
+                       Arrays.sort(results);
+                       _allKnownRecordSIDs = results;
+               }
 
-                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;
+               return (short[]) _allKnownRecordSIDs.clone();
+       }
 
-            realretval = new Record[ mb.getNumColumns() ];
-            for (int k = 0; k < mb.getNumColumns(); k++)
-            {
-                BlankRecord br = new BlankRecord();
+       /**
+        * 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);
 
-                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;
-    }
+               for (int i = 0; i < records.length; i++) {
 
-    public static short [] getAllKnownRecordSIDs()
-    {
-        short[] results = new short[ recordsMap.size() ];
-        int     i       = 0;
+                       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;
+       }
 
-        for (Iterator iterator = recordsMap.keySet().iterator();
-                iterator.hasNext(); )
-        {
-            Short sid = ( Short ) iterator.next();
+       /**
+        * 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 {
 
-            results[ i++ ] = sid.shortValue();
-        }
-        return results;
-    }
+               List records = new ArrayList(NUM_RECORDS);
 
-    private static Map recordsToMap(Class [] records)
-    {
-        Map         result = new HashMap();
-        Constructor constructor;
+               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
 
-        for (int i = 0; i < records.length; i++)
-        {
-            Class record;
-            short sid;
+                               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 ];
 
-            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;
+       }
 }
index 4e62e8b4743109e01cf29d70e3883f65bffe76f9..d85f1b60e30a0ecc3e0667a5469fcbf9687e7941 100644 (file)
@@ -33,8 +33,7 @@ import org.apache.poi.ss.usermodel.Footer;
  * <P>
  * @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;
index e1cd0b009128dee89f593d885083e7776a8ba04e..535b70efa168449e83d26e7073a92b7d04f41ea5 100644 (file)
@@ -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;
index d5b801c5c1620ac164a46a4b9145d12dc47c88a5..5422776fc1a2a5a1f1bcf91222e3fd188e51ec5c 100644 (file)
@@ -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 (file)
index 0000000..44dfd79
--- /dev/null
@@ -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 (file)
index 0000000..c58f929
--- /dev/null
@@ -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 (file)
index 0000000..2ab1d24
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 (file)
index 0000000..fe67b84
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 (file)
index 0000000..6d1731d
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 (file)
index 0000000..c2a2821
Binary files /dev/null and b/src/testcases/org/apache/poi/hssf/data/45538_form_Header.xls differ
index 0449795b5d4ecb490a626556d8da7bb24732bcea..c4a5ec84b3a28d9e98b3b7cb8ebe5afa19fab17c 100644 (file)
 package org.apache.poi.hssf.eventmodel;
 
 import java.io.ByteArrayInputStream;
-import java.util.Iterator;
+
+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.UnknownRecord;
-import org.apache.poi.hssf.record.ContinueRecord;
+import org.apache.poi.hssf.record.RecordFactory;
 import org.apache.poi.hssf.record.TestcaseRecordInputStream;
-
-import junit.framework.TestCase;
+import org.apache.poi.hssf.record.UnknownRecord;
 
 /**
  * enclosing_type describe the purpose here
@@ -35,63 +35,7 @@ import junit.framework.TestCase;
  * @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<rec1.length; k++) {
-             if (rec1[k] != rec2[k]) {
-              retval = false;
-              break;  
-             }   
-            }
-        } else {
-            retval = false;   
+        if (rec1.length != rec2.length) {
+            return false;   
         }
-        
-        return retval;
+        for (int k=0; k<rec1.length; k++) {
+            if (rec1[k] != rec2[k]) {
+                return false;
+            }   
+        }
+        return true;
     }
 
     
@@ -202,10 +138,8 @@ public class TestEventRecordFactory extends TestCase
      * FAILURE:    The wrong records are created or contain the wrong values <P>
      *
      */
-     public void testContinuedUnknownRecord()
-     {
-        final byte[]   data    = new byte[]
-        {
+     public void testContinuedUnknownRecord() {
+        final byte[] data = {
             0, -1, 0, 0, // an unknown record with 0 length
             0x3C , 0, 3, 0, 1, 2, 3, // a continuation record with 3 bytes of data
             0x3C , 0, 1, 0, 4 // one more continuation record with 1 byte of data
@@ -213,8 +147,7 @@ public class TestEventRecordFactory extends TestCase
 
         final int[] recCnt = { 0 };
         final int[] offset = { 0 };
-        factory.registerListener(
-          new ERFListener() {
+        ERFListener listener = new ERFListener() {
               private String[] expectedRecordTypes = {
                   UnknownRecord.class.getName(),
                   ContinueRecord.class.getName(),
@@ -238,14 +171,11 @@ public class TestEventRecordFactory extends TestCase
                       assertEquals(message + " data byte " + i, data[offset[0]++], recData[i]);
                   }
               }
-          },
-          new short[] {-256, 0x3C}
-        );
+          };
+       EventRecordFactory factory = new EventRecordFactory(listener, new short[] {-256, 0x3C});
 
         factory.processRecords(new ByteArrayInputStream(data));
         assertEquals("nr. of processed records", 3, recCnt[0]);
         assertEquals("nr. of processed bytes", data.length, offset[0]);
     }
-
-
 }
index 9bb137ff69cd1b9959344381a546a4508c6e18aa..0913ab7250ce009d887a12ce30ff2cc1f6876c73 100644 (file)
@@ -255,4 +255,20 @@ public final class TestExcelExtractor extends TestCase {
                                ex.getText());
                assertEquals("Excel With Embeded", ex.getSummaryInformation().getTitle());
        }
+       
+       /**
+        * Test that we get text from headers and footers
+        */
+       public void test45538() throws Exception {
+               String[] files = new String[] {
+                       "45538_classic_Footer.xls", "45538_form_Footer.xls",    
+                       "45538_classic_Header.xls", "45538_form_Header.xls"
+               };
+               for(int i=0; i<files.length; i++) {
+                       ExcelExtractor extractor = createExtractor(files[i]);
+                       String text = extractor.getText();
+                       assertTrue("Unable to find expected word in text\n" + text, text.contains("testdoc"));
+                       assertTrue("Unable to find expected word in text\n" + text, text.contains("test phrase"));
+               }
+       }
 }
index 71881accec32469db02f99e6fff5c0f781d5021c..4589ee5b131a80874934e869c9dcb9965bbf0b55 100644 (file)
@@ -416,9 +416,9 @@ public final class TestSheet extends TestCase {
         int size = sheet.getSize();
         byte[] data = new byte[size];
         sheet.serialize(0, data);
-        EventRecordFactory erf = new EventRecordFactory();
+
         MyIndexRecordListener myIndexListener = new MyIndexRecordListener();
-        erf.registerListener(myIndexListener, new short[] { IndexRecord.sid, });
+        EventRecordFactory erf = new EventRecordFactory(myIndexListener, new short[] { IndexRecord.sid, });
         erf.processRecords(new ByteArrayInputStream(data));
         IndexRecord indexRecord = myIndexListener.getIndexRecord();
         int dbCellRecordPos = indexRecord.getDbcellAt(0);
index 91382af06ddf2823aba0d24dfcca0dd1f5c7da4b..299372c12171188751e5e0eeb1294f4c2f61bc57 100644 (file)
@@ -594,9 +594,9 @@ public final class TestDataValidation extends TestCase {
                byte[] wbData = baos.toByteArray();
                
                if (false) { // TODO (Jul 2008) fix EventRecordFactory to process unknown records, (and DV records for that matter)
-                       EventRecordFactory erf = new EventRecordFactory();
+
                        ERFListener erfListener = null; // new MyERFListener();
-                       erf.registerListener(erfListener, null);
+                       EventRecordFactory erf = new EventRecordFactory(erfListener, null);
                        try {
                                POIFSFileSystem fs = new POIFSFileSystem(new ByteArrayInputStream(baos.toByteArray()));
                                erf.processRecords(fs.createDocumentInputStream("Workbook"));