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-ffa450edef68tags/REL_3_5_BETA2
@@ -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> |
@@ -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> |
@@ -18,241 +18,60 @@ | |||
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; | |||
} | |||
} | |||
} | |||
} |
@@ -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); | |||
} |
@@ -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(); | |||
} |
@@ -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<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; | |||
} | |||
} |
@@ -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; |
@@ -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; |
@@ -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); |
@@ -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 { | |||
} |
@@ -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 ); | |||
} |
@@ -18,16 +18,16 @@ | |||
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]); | |||
} | |||
} |
@@ -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")); | |||
} | |||
} | |||
} |
@@ -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); |
@@ -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")); |