https://svn.apache.org/repos/asf/poi/trunk ........ r690534 | nick | 2008-08-30 17:59:55 +0100 (Sat, 30 Aug 2008) | 1 line Start to support HPBF PLC parts ........ r690536 | nick | 2008-08-30 18:12:42 +0100 (Sat, 30 Aug 2008) | 1 line Further HPBF plc tests ........ r690626 | josh | 2008-08-31 02:53:47 +0100 (Sun, 31 Aug 2008) | 1 line changed serialize method on Sheet to visitContainedRecords to simplify serialization logic and also allow test code to inspect generated sheet records more directly ........ r690636 | josh | 2008-08-31 05:45:00 +0100 (Sun, 31 Aug 2008) | 1 line Fix for bugs 26321 and 44958 - preserve position of ArrayRecords and TableRecords among cell value records ........ r690721 | josh | 2008-08-31 17:27:35 +0100 (Sun, 31 Aug 2008) | 1 line Added junit to show bug 45717 is fixed. (Previously fixed by either of r683758, r683871) ........ r690726 | nick | 2008-08-31 17:37:39 +0100 (Sun, 31 Aug 2008) | 1 line Start to support HPBF hyperlinks ........ r690729 | nick | 2008-08-31 17:58:29 +0100 (Sun, 31 Aug 2008) | 1 line Add HPBF hyperlinks support to the extractor ........ git-svn-id: https://svn.apache.org/repos/asf/poi/branches/ooxml@690732 13f79535-47bb-0310-9956-ffa450edef68tags/REL_3_5_BETA3
@@ -64,6 +64,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="add">Support for HPBF Publisher hyperlinks, including during text extraction</action> | |||
<action dev="POI-DEVELOPERS" type="fix">26321 and 44958 - preserve position of ArrayRecords and TableRecords among cell value records</action> | |||
<action dev="POI-DEVELOPERS" type="fix">Impove empty header or footer handling in HWPF HeaderStories</action> | |||
<action dev="POI-DEVELOPERS" type="fix">Avoid NPE in hssf.usermodel.HeaderFooter when stripping fields out</action> | |||
<action dev="POI-DEVELOPERS" type="fix">Avoid NPE in EscherBSERecord on older escher records</action> |
@@ -61,6 +61,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="add">Support for HPBF Publisher hyperlinks, including during text extraction</action> | |||
<action dev="POI-DEVELOPERS" type="fix">26321 and 44958 - preserve position of ArrayRecords and TableRecords among cell value records</action> | |||
<action dev="POI-DEVELOPERS" type="fix">Impove empty header or footer handling in HWPF HeaderStories</action> | |||
<action dev="POI-DEVELOPERS" type="fix">Avoid NPE in hssf.usermodel.HeaderFooter when stripping fields out</action> | |||
<action dev="POI-DEVELOPERS" type="fix">Avoid NPE in EscherBSERecord on older escher records</action> |
@@ -0,0 +1,116 @@ | |||
/* ==================================================================== | |||
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.model; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
import org.apache.poi.hssf.record.ArrayRecord; | |||
import org.apache.poi.hssf.record.MergeCellsRecord; | |||
import org.apache.poi.hssf.record.Record; | |||
import org.apache.poi.hssf.record.SharedFormulaRecord; | |||
import org.apache.poi.hssf.record.TableRecord; | |||
import org.apache.poi.hssf.record.aggregates.MergedCellsTable; | |||
import org.apache.poi.hssf.record.aggregates.SharedValueManager; | |||
/** | |||
* Segregates the 'Row Blocks' section of a single sheet into plain row/cell records and | |||
* shared formula records. | |||
* | |||
* @author Josh Micich | |||
*/ | |||
public final class RowBlocksReader { | |||
private final List _plainRecords; | |||
private final SharedValueManager _sfm; | |||
private final MergeCellsRecord[] _mergedCellsRecords; | |||
private final int _totalNumberOfRecords; | |||
/** | |||
* Also collects any loose MergeCellRecords and puts them in the supplied | |||
* mergedCellsTable | |||
*/ | |||
public RowBlocksReader(List recs, int startIx) { | |||
List plainRecords = new ArrayList(); | |||
List shFrmRecords = new ArrayList(); | |||
List arrayRecords = new ArrayList(); | |||
List tableRecords = new ArrayList(); | |||
List mergeCellRecords = new ArrayList(); | |||
int endIx = -1; | |||
for (int i = startIx; i < recs.size(); i++) { | |||
Record rec = (Record) recs.get(i); | |||
if (RecordOrderer.isEndOfRowBlock(rec.getSid())) { | |||
// End of row/cell records for the current sheet | |||
// Note - It is important that this code does not inadvertently any sheet | |||
// records from a subsequent sheet. For example, if SharedFormulaRecords | |||
// are taken from the wrong sheet, this could cause bug 44449. | |||
endIx = i; | |||
break; | |||
} | |||
List dest; | |||
switch (rec.getSid()) { | |||
case MergeCellsRecord.sid: dest = mergeCellRecords; break; | |||
case SharedFormulaRecord.sid: dest = shFrmRecords; break; | |||
case ArrayRecord.sid: dest = arrayRecords; break; | |||
case TableRecord.sid: dest = tableRecords; break; | |||
default: dest = plainRecords; | |||
} | |||
dest.add(rec); | |||
} | |||
if (endIx < 0) { | |||
throw new RuntimeException("Failed to find end of row/cell records"); | |||
} | |||
SharedFormulaRecord[] sharedFormulaRecs = new SharedFormulaRecord[shFrmRecords.size()]; | |||
ArrayRecord[] arrayRecs = new ArrayRecord[arrayRecords.size()]; | |||
TableRecord[] tableRecs = new TableRecord[tableRecords.size()]; | |||
shFrmRecords.toArray(sharedFormulaRecs); | |||
arrayRecords.toArray(arrayRecs); | |||
tableRecords.toArray(tableRecs); | |||
_plainRecords = plainRecords; | |||
_sfm = SharedValueManager.create(sharedFormulaRecs, arrayRecs, tableRecs); | |||
_mergedCellsRecords = new MergeCellsRecord[mergeCellRecords.size()]; | |||
mergeCellRecords.toArray(_mergedCellsRecords); | |||
_totalNumberOfRecords = endIx - startIx; | |||
} | |||
/** | |||
* Some unconventional apps place {@link MergeCellsRecord}s within the row block. They | |||
* actually should be in the {@link MergedCellsTable} which is much later (see bug 45699). | |||
* @return any loose <tt>MergeCellsRecord</tt>s found | |||
*/ | |||
public MergeCellsRecord[] getLooseMergedCells() { | |||
return _mergedCellsRecords; | |||
} | |||
public int getTotalNumberOfRecords() { | |||
return _totalNumberOfRecords; | |||
} | |||
public SharedValueManager getSharedFormulaManager() { | |||
return _sfm; | |||
} | |||
/** | |||
* @return a {@link RecordStream} containing all the non-{@link SharedFormulaRecord} | |||
* non-{@link ArrayRecord} and non-{@link TableRecord} Records. | |||
*/ | |||
public RecordStream getPlainRecordStream() { | |||
return new RecordStream(_plainRecords, 0); | |||
} | |||
} |
@@ -27,7 +27,6 @@ import org.apache.poi.hssf.record.CalcCountRecord; | |||
import org.apache.poi.hssf.record.CalcModeRecord; | |||
import org.apache.poi.hssf.record.CellValueRecordInterface; | |||
import org.apache.poi.hssf.record.ColumnInfoRecord; | |||
import org.apache.poi.hssf.record.DBCellRecord; | |||
import org.apache.poi.hssf.record.DVALRecord; | |||
import org.apache.poi.hssf.record.DefaultColWidthRecord; | |||
import org.apache.poi.hssf.record.DefaultRowHeightRecord; | |||
@@ -56,7 +55,6 @@ import org.apache.poi.hssf.record.SCLRecord; | |||
import org.apache.poi.hssf.record.SaveRecalcRecord; | |||
import org.apache.poi.hssf.record.ScenarioProtectRecord; | |||
import org.apache.poi.hssf.record.SelectionRecord; | |||
import org.apache.poi.hssf.record.StringRecord; | |||
import org.apache.poi.hssf.record.UncalcedRecord; | |||
import org.apache.poi.hssf.record.WSBoolRecord; | |||
import org.apache.poi.hssf.record.WindowTwoRecord; | |||
@@ -64,10 +62,12 @@ import org.apache.poi.hssf.record.aggregates.CFRecordsAggregate; | |||
import org.apache.poi.hssf.record.aggregates.ColumnInfoRecordsAggregate; | |||
import org.apache.poi.hssf.record.aggregates.ConditionalFormattingTable; | |||
import org.apache.poi.hssf.record.aggregates.DataValidityTable; | |||
import org.apache.poi.hssf.record.aggregates.FormulaRecordAggregate; | |||
import org.apache.poi.hssf.record.aggregates.MergedCellsTable; | |||
import org.apache.poi.hssf.record.aggregates.PageSettingsBlock; | |||
import org.apache.poi.hssf.record.aggregates.RecordAggregate; | |||
import org.apache.poi.hssf.record.aggregates.RowRecordsAggregate; | |||
import org.apache.poi.hssf.record.aggregates.RecordAggregate.PositionTrackingVisitor; | |||
import org.apache.poi.hssf.record.aggregates.RecordAggregate.RecordVisitor; | |||
import org.apache.poi.hssf.util.CellRangeAddress; | |||
import org.apache.poi.hssf.util.PaneInformation; | |||
@@ -105,7 +105,6 @@ public final class Sheet implements Model { | |||
private static POILogger log = POILogFactory.getLogger(Sheet.class); | |||
protected ArrayList records = null; | |||
int preoffset = 0; // offset of the sheet in a new file | |||
protected int dimsloc = -1; // TODO - remove dimsloc | |||
protected PrintGridlinesRecord printGridlines = null; | |||
protected GridsetRecord gridset = null; | |||
@@ -148,7 +147,7 @@ public final class Sheet implements Model { | |||
* @see #createSheet(List,int,int) | |||
*/ | |||
public Sheet() { | |||
_mergedCellsTable = new MergedCellsTable(); | |||
_mergedCellsTable = new MergedCellsTable(); | |||
} | |||
/** | |||
@@ -181,18 +180,12 @@ public final class Sheet implements Model { | |||
for (int k = offset; k < inRecs.size(); k++) { | |||
Record rec = ( Record ) inRecs.get(k); | |||
if ( rec.getSid() == DBCellRecord.sid ) { | |||
continue; | |||
} | |||
if ( rec.getSid() == IndexRecord.sid ) { | |||
// ignore INDEX record because it is only needed by Excel, | |||
// and POI always re-calculates its contents | |||
continue; | |||
} | |||
if ( rec.getSid() == StringRecord.sid ) { | |||
continue; | |||
} | |||
if ( rec.getSid() == CFHeaderRecord.sid ) { | |||
RecordStream rs = new RecordStream(inRecs, k); | |||
retval.condFormatting = new ConditionalFormattingTable(rs); | |||
@@ -221,10 +214,11 @@ public final class Sheet implements Model { | |||
if (retval._rowsAggregate != null) { | |||
throw new RuntimeException("row/cell records found in the wrong place"); | |||
} | |||
int lastRowCellRec = findEndOfRowBlock(inRecs, k, retval._mergedCellsTable); | |||
retval._rowsAggregate = new RowRecordsAggregate(inRecs, k, lastRowCellRec); | |||
RowBlocksReader rbr = new RowBlocksReader(inRecs, k); | |||
retval._mergedCellsTable.addRecords(rbr.getLooseMergedCells()); | |||
retval._rowsAggregate = new RowRecordsAggregate(rbr.getPlainRecordStream(), rbr.getSharedFormulaManager()); | |||
records.add(retval._rowsAggregate); //only add the aggregate once | |||
k = lastRowCellRec -1; | |||
k += rbr.getTotalNumberOfRecords() - 1; | |||
continue; | |||
} | |||
@@ -245,13 +239,18 @@ public final class Sheet implements Model { | |||
} | |||
if (rec.getSid() == MergeCellsRecord.sid) { | |||
// when the MergedCellsTable is found in the right place, we expect those records to be contiguous | |||
// when the MergedCellsTable is found in the right place, we expect those records to be contiguous | |||
RecordStream rs = new RecordStream(inRecs, k); | |||
retval._mergedCellsTable.read(rs); | |||
k += rs.getCountRead()-1; | |||
continue; | |||
} | |||
if (rec.getSid() == UncalcedRecord.sid) { | |||
// don't add UncalcedRecord to the list | |||
retval._isUncalced = true; // this flag is enough | |||
continue; | |||
} | |||
if (rec.getSid() == BOFRecord.sid) | |||
{ | |||
bofEofNestingLevel++; | |||
@@ -269,9 +268,6 @@ public final class Sheet implements Model { | |||
break; | |||
} | |||
} | |||
else if (rec.getSid() == UncalcedRecord.sid) { | |||
retval._isUncalced = true; | |||
} | |||
else if (rec.getSid() == DimensionsRecord.sid) | |||
{ | |||
// Make a columns aggregate if one hasn't ready been created. | |||
@@ -342,26 +338,6 @@ public final class Sheet implements Model { | |||
return retval; | |||
} | |||
/** | |||
* Also collects any rogue MergeCellRecords | |||
* @return the index one after the last row/cell record | |||
*/ | |||
private static int findEndOfRowBlock(List recs, int startIx, MergedCellsTable mergedCellsTable) { | |||
for(int i=startIx; i<recs.size(); i++) { | |||
Record rec = (Record) recs.get(i); | |||
if (RecordOrderer.isEndOfRowBlock(rec.getSid())) { | |||
return i; | |||
} | |||
if (rec.getSid() == MergeCellsRecord.sid) { | |||
// Some apps scatter these records between the rows/cells but they are supposed to | |||
// be well after the row/cell records. We collect them here | |||
// see bug 45699 | |||
mergedCellsTable.add((MergeCellsRecord) rec); | |||
} | |||
} | |||
throw new RuntimeException("Failed to find end of row/cell records"); | |||
} | |||
private static final class RecordCloner implements RecordVisitor { | |||
private final List _destList; | |||
@@ -585,61 +561,23 @@ public final class Sheet implements Model { | |||
log.log(POILogger.DEBUG, "Sheet.setDimensions exiting"); | |||
} | |||
/** | |||
* Set the preoffset when using DBCELL records (currently unused) - this is | |||
* the position of this sheet within the whole file. | |||
* | |||
* @param offset the offset of the sheet's BOF within the file. | |||
*/ | |||
public void setPreOffset(int offset) | |||
{ | |||
this.preoffset = offset; | |||
} | |||
/** | |||
* get the preoffset when using DBCELL records (currently unused) - this is | |||
* the position of this sheet within the whole file. | |||
* | |||
* @return offset the offset of the sheet's BOF within the file. | |||
*/ | |||
public int getPreOffset() | |||
{ | |||
return preoffset; | |||
} | |||
public void visitContainedRecords(RecordVisitor rv, int offset) { | |||
/** | |||
* Serializes all records in the sheet into one big byte array. Use this to write | |||
* the sheet out. | |||
* | |||
* @param offset to begin write at | |||
* @param data array containing the binary representation of the records in this sheet | |||
* | |||
*/ | |||
public int serialize(int offset, byte [] data) | |||
{ | |||
if (log.check( POILogger.DEBUG )) | |||
log.log(POILogger.DEBUG, "Sheet.serialize using offsets"); | |||
int pos = offset; | |||
PositionTrackingVisitor ptv = new PositionTrackingVisitor(rv, offset); | |||
boolean haveSerializedIndex = false; | |||
for (int k = 0; k < records.size(); k++) | |||
{ | |||
RecordBase record = (RecordBase) records.get(k); | |||
// Don't write out UncalcedRecord entries, as | |||
// we handle those specially just below | |||
if (record instanceof UncalcedRecord) { | |||
continue; | |||
if (record instanceof RecordAggregate) { | |||
RecordAggregate agg = (RecordAggregate) record; | |||
agg.visitContainedRecords(ptv); | |||
} else { | |||
ptv.visitRecord((Record) record); | |||
} | |||
// Once the rows have been found in the list of records, start | |||
// writing out the blocked row information. This includes the DBCell references | |||
pos += record.serialize(pos, data); | |||
// If the BOF record was just serialized then add the IndexRecord | |||
if (record instanceof BOFRecord) { | |||
if (!haveSerializedIndex) { | |||
@@ -649,50 +587,42 @@ public final class Sheet implements Model { | |||
// If there are diagrams, they have their own BOFRecords, | |||
// and one shouldn't go in after that! | |||
if (_isUncalced) { | |||
UncalcedRecord rec = new UncalcedRecord(); | |||
pos += rec.serialize(pos, data); | |||
ptv.visitRecord(new UncalcedRecord()); | |||
} | |||
//Can there be more than one BOF for a sheet? If not then we can | |||
//remove this guard. So be safe it is left here. | |||
if (_rowsAggregate != null) { | |||
pos += serializeIndexRecord(k, pos, data); | |||
// find forward distance to first RowRecord | |||
int initRecsSize = getSizeOfInitialSheetRecords(k); | |||
int currentPos = ptv.getPosition(); | |||
ptv.visitRecord(_rowsAggregate.createIndexRecord(currentPos, initRecsSize)); | |||
} | |||
} | |||
} | |||
} | |||
if (log.check( POILogger.DEBUG )) { | |||
log.log(POILogger.DEBUG, "Sheet.serialize returning "); | |||
} | |||
return pos-offset; | |||
} | |||
/** | |||
* @param indexRecordOffset also happens to be the end of the BOF record | |||
* @return the size of the serialized INDEX record | |||
* 'initial sheet records' are between INDEX and the 'Row Blocks' | |||
* @param bofRecordIndex index of record after which INDEX record is to be placed | |||
* @return count of bytes from end of INDEX record to first ROW record. | |||
*/ | |||
private int serializeIndexRecord(int bofRecordIndex, int indexRecordOffset, byte[] data) { | |||
private int getSizeOfInitialSheetRecords(int bofRecordIndex) { | |||
// 'initial sheet records' are between INDEX and first ROW record. | |||
int sizeOfInitialSheetRecords = 0; | |||
int result = 0; | |||
// start just after BOF record (INDEX is not present in this list) | |||
for (int j = bofRecordIndex + 1; j < records.size(); j++) { | |||
RecordBase tmpRec = ((RecordBase) records.get(j)); | |||
if (tmpRec instanceof UncalcedRecord) { | |||
continue; | |||
} | |||
RecordBase tmpRec = (RecordBase) records.get(j); | |||
if (tmpRec instanceof RowRecordsAggregate) { | |||
break; | |||
} | |||
sizeOfInitialSheetRecords += tmpRec.getRecordSize(); | |||
result += tmpRec.getRecordSize(); | |||
} | |||
if (_isUncalced) { | |||
sizeOfInitialSheetRecords += UncalcedRecord.getStaticRecordSize(); | |||
result += UncalcedRecord.getStaticRecordSize(); | |||
} | |||
IndexRecord index = _rowsAggregate.createIndexRecord(indexRecordOffset, sizeOfInitialSheetRecords); | |||
return index.serialize(indexRecordOffset, data); | |||
return result; | |||
} | |||
/** | |||
* Create a row record. (does not add it to the records contained in this sheet) | |||
*/ | |||
@@ -1351,32 +1281,6 @@ public final class Sheet implements Model { | |||
} | |||
} | |||
/** | |||
* @return the serialized size of this sheet | |||
*/ | |||
public int getSize() { | |||
int retval = 0; | |||
for ( int k = 0; k < records.size(); k++) { | |||
RecordBase record = (RecordBase) records.get(k); | |||
if (record instanceof UncalcedRecord) { | |||
// skip the UncalcedRecord if present, it's only encoded if the isUncalced flag is set | |||
continue; | |||
} | |||
retval += record.getRecordSize(); | |||
} | |||
// add space for IndexRecord if needed | |||
if (_rowsAggregate != null) { | |||
// rowsAggregate knows how to make the index record | |||
retval += IndexRecord.getRecordSizeForBlockCount(_rowsAggregate.getRowBlockCount()); | |||
} | |||
// Add space for UncalcedRecord | |||
if (_isUncalced) { | |||
retval += UncalcedRecord.getStaticRecordSize(); | |||
} | |||
return retval; | |||
} | |||
public List getRecords() | |||
{ | |||
return records; | |||
@@ -1937,4 +1841,8 @@ public final class Sheet implements Model { | |||
} | |||
return _dataValidityTable; | |||
} | |||
public FormulaRecordAggregate createFormula(int row, int col) { | |||
return _rowsAggregate.createFormula(row, col); | |||
} | |||
} |
@@ -18,7 +18,6 @@ | |||
package org.apache.poi.hssf.record; | |||
import org.apache.poi.hssf.record.formula.Ptg; | |||
import org.apache.poi.hssf.util.CellRangeAddress8Bit; | |||
import org.apache.poi.util.HexDump; | |||
import org.apache.poi.util.LittleEndian; | |||
@@ -29,13 +28,11 @@ import org.apache.poi.util.LittleEndian; | |||
* | |||
* @author Josh Micich | |||
*/ | |||
public final class ArrayRecord extends Record { | |||
public final class ArrayRecord extends SharedValueRecordBase { | |||
public final static short sid = 0x0221; | |||
private static final int OPT_ALWAYS_RECALCULATE = 0x0001; | |||
private static final int OPT_CALCULATE_ON_OPEN = 0x0002; | |||
private CellRangeAddress8Bit _range; | |||
private int _options; | |||
private int _field3notUsed; | |||
@@ -43,6 +40,10 @@ public final class ArrayRecord extends Record { | |||
public ArrayRecord(RecordInputStream in) { | |||
super(in); | |||
_options = in.readUShort(); | |||
_field3notUsed = in.readInt(); | |||
int formulaLen = in.readUShort(); | |||
_formulaTokens = Ptg.readTokens(formulaLen, in); | |||
} | |||
public boolean isAlwaysRecalculate() { | |||
@@ -52,27 +53,12 @@ public final class ArrayRecord extends Record { | |||
return (_options & OPT_CALCULATE_ON_OPEN) != 0; | |||
} | |||
protected void validateSid(short id) { | |||
if (id != sid) { | |||
throw new RecordFormatException("NOT A valid Array RECORD"); | |||
} | |||
protected int getExtraDataSize() { | |||
return 2 + 4 | |||
+ 2 + Ptg.getEncodedSize(_formulaTokens); | |||
} | |||
private int getDataSize(){ | |||
return CellRangeAddress8Bit.ENCODED_SIZE | |||
+ 2 + 4 | |||
+ getFormulaSize(); | |||
} | |||
public int serialize( int offset, byte[] data ) { | |||
int dataSize = getDataSize(); | |||
LittleEndian.putShort(data, 0 + offset, sid); | |||
LittleEndian.putUShort(data, 2 + offset, dataSize); | |||
int pos = offset+4; | |||
_range.serialize(pos, data); | |||
pos += CellRangeAddress8Bit.ENCODED_SIZE; | |||
protected void serializeExtraData(int offset, byte[] data) { | |||
int pos = offset; | |||
LittleEndian.putUShort(data, pos, _options); | |||
pos+=2; | |||
LittleEndian.putInt(data, pos, _field3notUsed); | |||
@@ -81,29 +67,6 @@ public final class ArrayRecord extends Record { | |||
LittleEndian.putUShort(data, pos, tokenSize); | |||
pos+=2; | |||
Ptg.serializePtgs(_formulaTokens, data, pos); | |||
return dataSize + 4; | |||
} | |||
private int getFormulaSize() { | |||
int result = 0; | |||
for (int i = 0; i < _formulaTokens.length; i++) { | |||
result += _formulaTokens[i].getSize(); | |||
} | |||
return result; | |||
} | |||
public int getRecordSize(){ | |||
return 4 + getDataSize(); | |||
} | |||
protected void fillFields(RecordInputStream in) { | |||
_range = new CellRangeAddress8Bit(in); | |||
_options = in.readUShort(); | |||
_field3notUsed = in.readInt(); | |||
int formulaLen = in.readUShort(); | |||
_formulaTokens = Ptg.readTokens(formulaLen, in); | |||
} | |||
public short getSid() { | |||
@@ -113,12 +76,13 @@ public final class ArrayRecord extends Record { | |||
public String toString() { | |||
StringBuffer sb = new StringBuffer(); | |||
sb.append(getClass().getName()).append(" [ARRAY]\n"); | |||
sb.append(" range=").append(_range.toString()).append("\n"); | |||
sb.append(" range=").append(getRange().toString()).append("\n"); | |||
sb.append(" options=").append(HexDump.shortToHex(_options)).append("\n"); | |||
sb.append(" notUsed=").append(HexDump.intToHex(_field3notUsed)).append("\n"); | |||
sb.append(" formula:").append("\n"); | |||
for (int i = 0; i < _formulaTokens.length; i++) { | |||
sb.append(_formulaTokens[i].toString()); | |||
Ptg ptg = _formulaTokens[i]; | |||
sb.append(ptg.toString()).append(ptg.getRVAType()).append("\n"); | |||
} | |||
sb.append("]"); | |||
return sb.toString(); |
@@ -17,9 +17,6 @@ | |||
package org.apache.poi.hssf.record; | |||
import java.util.Arrays; | |||
import java.util.List; | |||
import org.apache.poi.hssf.record.formula.Ptg; | |||
import org.apache.poi.util.BitField; | |||
import org.apache.poi.util.BitFieldFactory; | |||
@@ -27,7 +24,7 @@ import org.apache.poi.util.HexDump; | |||
import org.apache.poi.util.LittleEndian; | |||
/** | |||
* Formula Record. | |||
* Formula Record (0x0006). | |||
* REFERENCE: PG 317/444 Microsoft Excel 97 Developer's Kit (ISBN: 1-57231-498-2)<P> | |||
* @author Andrew C. Oliver (acoliver at apache dot org) | |||
* @author Jason Height (jheight at chariot dot net dot au) | |||
@@ -270,7 +267,8 @@ public final class FormulaRecord extends Record implements CellValueRecordInterf | |||
for (int k = 0; k < field_8_parsed_expr.length; k++ ) { | |||
sb.append(" Ptg[").append(k).append("]="); | |||
sb.append(field_8_parsed_expr[k].toString()).append("\n"); | |||
Ptg ptg = field_8_parsed_expr[k]; | |||
sb.append(ptg.toString()).append(ptg.getRVAType()).append("\n"); | |||
} | |||
sb.append("[/FORMULA]\n"); | |||
return sb.toString(); |
@@ -41,7 +41,7 @@ import java.util.Set; | |||
* @author Csaba Nagy (ncsaba at yahoo dot com) | |||
*/ | |||
public final class RecordFactory { | |||
private static final int NUM_RECORDS = 512; | |||
private static final int NUM_RECORDS = 512; | |||
private static final Class[] CONSTRUCTOR_ARGS = { RecordInputStream.class, }; | |||
@@ -50,6 +50,7 @@ public final class RecordFactory { | |||
* Note - this most but not *every* subclass of Record. | |||
*/ | |||
private static final Class[] records = { | |||
ArrayRecord.class, | |||
BackupRecord.class, | |||
BlankRecord.class, | |||
BOFRecord.class, |
@@ -22,7 +22,6 @@ import org.apache.poi.hssf.record.formula.AreaPtg; | |||
import org.apache.poi.hssf.record.formula.Ptg; | |||
import org.apache.poi.hssf.record.formula.RefNPtg; | |||
import org.apache.poi.hssf.record.formula.RefPtg; | |||
import org.apache.poi.hssf.util.CellRangeAddress8Bit; | |||
import org.apache.poi.util.HexDump; | |||
/** | |||
@@ -36,59 +35,31 @@ import org.apache.poi.util.HexDump; | |||
* record types. | |||
* @author Danny Mui at apache dot org | |||
*/ | |||
public final class SharedFormulaRecord extends Record { | |||
public final class SharedFormulaRecord extends SharedValueRecordBase { | |||
public final static short sid = 0x04BC; | |||
private CellRangeAddress8Bit _range; | |||
private int field_5_reserved; | |||
private Ptg[] field_7_parsed_expr; | |||
public SharedFormulaRecord() { | |||
_range = new CellRangeAddress8Bit(0, 0, 0, 0); | |||
field_7_parsed_expr = Ptg.EMPTY_PTG_ARRAY; | |||
field_7_parsed_expr = Ptg.EMPTY_PTG_ARRAY; | |||
} | |||
/** | |||
* @param in the RecordInputstream to read the record from | |||
*/ | |||
public SharedFormulaRecord(RecordInputStream in) { | |||
super(in); | |||
} | |||
protected void validateSid(short id) { | |||
if (id != this.sid) { | |||
throw new RecordFormatException("Not a valid SharedFormula"); | |||
} | |||
} | |||
public int getFirstRow() { | |||
return _range.getFirstRow(); | |||
} | |||
public int getLastRow() { | |||
return _range.getLastRow(); | |||
} | |||
public short getFirstColumn() { | |||
return (short) _range.getFirstColumn(); | |||
} | |||
public short getLastColumn() { | |||
return (short) _range.getLastColumn(); | |||
super(in); | |||
field_5_reserved = in.readShort(); | |||
int field_6_expression_len = in.readShort(); | |||
field_7_parsed_expr = Ptg.readTokens(field_6_expression_len, in); | |||
} | |||
/** | |||
* spit the record out AS IS. no interpretation or identification | |||
*/ | |||
public int serialize(int offset, byte [] data) | |||
{ | |||
protected void serializeExtraData(int offset, byte[] data) { | |||
//Because this record is converted to individual Formula records, this method is not required. | |||
throw new UnsupportedOperationException("Cannot serialize a SharedFormulaRecord"); | |||
} | |||
public int getRecordSize() | |||
{ | |||
protected int getExtraDataSize() { | |||
//Because this record is converted to individual Formula records, this method is not required. | |||
throw new UnsupportedOperationException("Cannot get the size for a SharedFormulaRecord"); | |||
@@ -103,12 +74,13 @@ public final class SharedFormulaRecord extends Record { | |||
StringBuffer buffer = new StringBuffer(); | |||
buffer.append("[SHARED FORMULA (").append(HexDump.intToHex(sid)).append("]\n"); | |||
buffer.append(" .range = ").append(_range.toString()).append("\n"); | |||
buffer.append(" .range = ").append(getRange().toString()).append("\n"); | |||
buffer.append(" .reserved = ").append(HexDump.shortToHex(field_5_reserved)).append("\n"); | |||
for (int k = 0; k < field_7_parsed_expr.length; k++ ) { | |||
buffer.append("Formula[").append(k).append("]"); | |||
buffer.append(field_7_parsed_expr[k].toString()).append("\n"); | |||
Ptg ptg = field_7_parsed_expr[k]; | |||
buffer.append(ptg.toString()).append(ptg.getRVAType()).append("\n"); | |||
} | |||
buffer.append("[/SHARED FORMULA]\n"); | |||
@@ -119,23 +91,6 @@ public final class SharedFormulaRecord extends Record { | |||
return sid; | |||
} | |||
protected void fillFields(RecordInputStream in) { | |||
_range = new CellRangeAddress8Bit(in); | |||
field_5_reserved = in.readShort(); | |||
int field_6_expression_len = in.readShort(); | |||
field_7_parsed_expr = Ptg.readTokens(field_6_expression_len, in); | |||
} | |||
/** | |||
* Are we shared by the supplied formula record? | |||
*/ | |||
public boolean isFormulaInShared(FormulaRecord formula) { | |||
final int formulaRow = formula.getRow(); | |||
final int formulaColumn = formula.getColumn(); | |||
return ((getFirstRow() <= formulaRow) && (getLastRow() >= formulaRow) && | |||
(getFirstColumn() <= formulaColumn) && (getLastColumn() >= formulaColumn)); | |||
} | |||
/** | |||
* Creates a non shared formula from the shared formula | |||
* counter part | |||
@@ -176,9 +131,9 @@ public final class SharedFormulaRecord extends Record { | |||
areaNPtg.isFirstColRelative(), | |||
areaNPtg.isLastColRelative()); | |||
} else { | |||
if (false) {// do we need a ptg clone here? | |||
ptg = ptg.copy(); | |||
} | |||
if (false) {// do we need a ptg clone here? | |||
ptg = ptg.copy(); | |||
} | |||
} | |||
if (!ptg.isBaseToken()) { | |||
ptg.setClass(originalOperandClass); | |||
@@ -194,12 +149,12 @@ public final class SharedFormulaRecord extends Record { | |||
* counter part | |||
*/ | |||
public void convertSharedFormulaRecord(FormulaRecord formula) { | |||
//Sanity checks | |||
if (!isFormulaInShared(formula)) { | |||
int formulaRow = formula.getRow(); | |||
int formulaColumn = formula.getColumn(); | |||
//Sanity checks | |||
if (!isInRange(formulaRow, formulaColumn)) { | |||
throw new RuntimeException("Shared Formula Conversion: Coding Error"); | |||
} | |||
final int formulaRow = formula.getRow(); | |||
final int formulaColumn = formula.getColumn(); | |||
Ptg[] ptgs = convertSharedFormulas(field_7_parsed_expr, formulaRow, formulaColumn); | |||
formula.setParsedExpression(ptgs); | |||
@@ -223,22 +178,6 @@ public final class SharedFormulaRecord extends Record { | |||
return row; | |||
} | |||
/** | |||
* Mirroring formula records so it is registered in the ValueRecordsAggregate | |||
*/ | |||
public boolean isInValueSection() | |||
{ | |||
return true; | |||
} | |||
/** | |||
* Register it in the ValueRecordsAggregate so it can go into the FormulaRecordAggregate | |||
*/ | |||
public boolean isValue() { | |||
return true; | |||
} | |||
public Object clone() { | |||
//Because this record is converted to individual Formula records, this method is not required. | |||
throw new UnsupportedOperationException("Cannot clone a SharedFormulaRecord"); |
@@ -0,0 +1,134 @@ | |||
/* ==================================================================== | |||
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.record; | |||
import org.apache.poi.hssf.util.CellRangeAddress8Bit; | |||
import org.apache.poi.util.LittleEndian; | |||
/** | |||
* Common base class for {@link SharedFormulaRecord}, {@link ArrayRecord} and | |||
* {@link TableRecord} which are have similarities. | |||
* | |||
* @author Josh Micich | |||
*/ | |||
public abstract class SharedValueRecordBase extends Record { | |||
private CellRangeAddress8Bit _range; | |||
protected SharedValueRecordBase(CellRangeAddress8Bit range) { | |||
_range = range; | |||
} | |||
protected SharedValueRecordBase() { | |||
this(new CellRangeAddress8Bit(0, 0, 0, 0)); | |||
} | |||
/** | |||
* reads only the range (1 {@link CellRangeAddress8Bit}) from the stream | |||
*/ | |||
public SharedValueRecordBase(RecordInputStream in) { | |||
_range = new CellRangeAddress8Bit(in); | |||
} | |||
protected final void validateSid(short id) { | |||
if (id != getSid()) { | |||
throw new RecordFormatException("Not a valid SharedFormula"); | |||
} | |||
} | |||
public final CellRangeAddress8Bit getRange() { | |||
return _range; | |||
} | |||
public final int getFirstRow() { | |||
return _range.getFirstRow(); | |||
} | |||
public final int getLastRow() { | |||
return _range.getLastRow(); | |||
} | |||
public final int getFirstColumn() { | |||
return (short) _range.getFirstColumn(); | |||
} | |||
public final int getLastColumn() { | |||
return (short) _range.getLastColumn(); | |||
} | |||
public final int getRecordSize() { | |||
return 4 + CellRangeAddress8Bit.ENCODED_SIZE + getExtraDataSize(); | |||
} | |||
protected abstract int getExtraDataSize(); | |||
protected abstract void serializeExtraData(int offset, byte[] data); | |||
public final int serialize(int offset, byte[] data) { | |||
int dataSize = CellRangeAddress8Bit.ENCODED_SIZE + getExtraDataSize(); | |||
LittleEndian.putShort(data, 0 + offset, getSid()); | |||
LittleEndian.putUShort(data, 2 + offset, dataSize); | |||
int pos = offset + 4; | |||
_range.serialize(pos, data); | |||
pos += CellRangeAddress8Bit.ENCODED_SIZE; | |||
serializeExtraData(pos, data); | |||
return dataSize + 4; | |||
} | |||
protected final void fillFields(RecordInputStream in) { | |||
throw new RuntimeException("Should not be called. Fields are filled in constructor"); | |||
} | |||
/** | |||
* @return <code>true</code> if (rowIx, colIx) is within the range ({@link #getRange()}) | |||
* of this shared value object. | |||
*/ | |||
public final boolean isInRange(int rowIx, int colIx) { | |||
CellRangeAddress8Bit r = _range; | |||
return r.getFirstRow() <= rowIx | |||
&& r.getLastRow() >= rowIx | |||
&& r.getFirstColumn() <= colIx | |||
&& r.getLastColumn() >= colIx; | |||
} | |||
/** | |||
* @return <code>true</code> if (rowIx, colIx) describes the first cell in this shared value | |||
* object's range ({@link #getRange()}) | |||
*/ | |||
public final boolean isFirstCell(int rowIx, int colIx) { | |||
CellRangeAddress8Bit r = getRange(); | |||
return r.getFirstRow() == rowIx && r.getFirstColumn() == colIx; | |||
} | |||
/** | |||
* Mirroring formula records so it is registered in the | |||
* ValueRecordsAggregate | |||
*/ | |||
public final boolean isInValueSection() { | |||
return true; | |||
} | |||
/** | |||
* Register it in the ValueRecordsAggregate so it can go into the | |||
* FormulaRecordAggregate | |||
*/ | |||
public final boolean isValue() { | |||
return true; | |||
} | |||
} |
@@ -34,19 +34,15 @@ import org.apache.poi.util.LittleEndian; | |||
* | |||
* See p536 of the June 08 binary docs | |||
*/ | |||
public final class TableRecord extends Record { | |||
public final class TableRecord extends SharedValueRecordBase { | |||
public static final short sid = 0x0236; | |||
private static final BitField alwaysCalc = BitFieldFactory.getInstance(0x0001); | |||
private static final BitField reserved1 = BitFieldFactory.getInstance(0x0002); | |||
private static final BitField calcOnOpen = BitFieldFactory.getInstance(0x0002); | |||
private static final BitField rowOrColInpCell = BitFieldFactory.getInstance(0x0004); | |||
private static final BitField oneOrTwoVar = BitFieldFactory.getInstance(0x0008); | |||
private static final BitField rowDeleted = BitFieldFactory.getInstance(0x0010); | |||
private static final BitField colDeleted = BitFieldFactory.getInstance(0x0020); | |||
private static final BitField reserved2 = BitFieldFactory.getInstance(0x0040); | |||
private static final BitField reserved3 = BitFieldFactory.getInstance(0x0080); | |||
private CellRangeAddress8Bit _range; | |||
private int field_5_flags; | |||
private int field_6_res; | |||
@@ -55,9 +51,8 @@ public final class TableRecord extends Record { | |||
private int field_9_rowInputCol; | |||
private int field_10_colInputCol; | |||
protected void fillFields(RecordInputStream in) { | |||
_range = new CellRangeAddress8Bit(in); | |||
public TableRecord(RecordInputStream in) { | |||
super(in); | |||
field_5_flags = in.readByte(); | |||
field_6_res = in.readByte(); | |||
field_7_rowInputRow = in.readShort(); | |||
@@ -66,18 +61,11 @@ public final class TableRecord extends Record { | |||
field_10_colInputCol = in.readShort(); | |||
} | |||
public TableRecord(RecordInputStream in) { | |||
super(in); | |||
} | |||
public TableRecord(CellRangeAddress8Bit range) { | |||
_range = range; | |||
super(range); | |||
field_6_res = 0; | |||
} | |||
public CellRangeAddress8Bit getRange() { | |||
return _range; | |||
} | |||
public int getFlags() { | |||
return field_5_flags; | |||
} | |||
@@ -153,43 +141,24 @@ public final class TableRecord extends Record { | |||
public short getSid() { | |||
return sid; | |||
} | |||
public int serialize(int offset, byte[] data) { | |||
int dataSize = getDataSize(); | |||
LittleEndian.putShort(data, 0 + offset, sid); | |||
LittleEndian.putUShort(data, 2 + offset, dataSize); | |||
_range.serialize(4 + offset, data); | |||
LittleEndian.putByte(data, 10 + offset, field_5_flags); | |||
LittleEndian.putByte(data, 11 + offset, field_6_res); | |||
LittleEndian.putUShort(data, 12 + offset, field_7_rowInputRow); | |||
LittleEndian.putUShort(data, 14 + offset, field_8_colInputRow); | |||
LittleEndian.putUShort(data, 16 + offset, field_9_rowInputCol); | |||
LittleEndian.putUShort(data, 18 + offset, field_10_colInputCol); | |||
return 4 + dataSize; | |||
} | |||
private int getDataSize() { | |||
return CellRangeAddress8Bit.ENCODED_SIZE | |||
+ 2 // 2 byte fields | |||
+ 8; // 4 short fields | |||
} | |||
public int getRecordSize() { | |||
return 4+getDataSize(); | |||
protected int getExtraDataSize() { | |||
return | |||
2 // 2 byte fields | |||
+ 8; // 4 short fields | |||
} | |||
protected void validateSid(short id) { | |||
if (id != sid) | |||
{ | |||
throw new RecordFormatException("NOT A TABLE RECORD"); | |||
} | |||
protected void serializeExtraData(int offset, byte[] data) { | |||
LittleEndian.putByte(data, 0 + offset, field_5_flags); | |||
LittleEndian.putByte(data, 1 + offset, field_6_res); | |||
LittleEndian.putUShort(data, 2 + offset, field_7_rowInputRow); | |||
LittleEndian.putUShort(data, 4 + offset, field_8_colInputRow); | |||
LittleEndian.putUShort(data, 6 + offset, field_9_rowInputCol); | |||
LittleEndian.putUShort(data, 8 + offset, field_10_colInputCol); | |||
} | |||
public String toString() { | |||
StringBuffer buffer = new StringBuffer(); | |||
buffer.append("[TABLE]\n"); | |||
buffer.append(" .range = ").append(_range.toString()).append("\n"); | |||
buffer.append(" .range = ").append(getRange().toString()).append("\n"); | |||
buffer.append(" .flags = ") .append(HexDump.byteToHex(field_5_flags)).append("\n"); | |||
buffer.append(" .alwaysClc= ").append(isAlwaysCalc()).append("\n"); | |||
buffer.append(" .reserved = ").append(HexDump.intToHex(field_6_res)).append("\n"); |
@@ -17,12 +17,10 @@ | |||
package org.apache.poi.hssf.record.aggregates; | |||
import org.apache.poi.hssf.model.RecordStream; | |||
import org.apache.poi.hssf.record.CellValueRecordInterface; | |||
import org.apache.poi.hssf.record.FormulaRecord; | |||
import org.apache.poi.hssf.record.SharedFormulaRecord; | |||
import org.apache.poi.hssf.record.Record; | |||
import org.apache.poi.hssf.record.StringRecord; | |||
import org.apache.poi.hssf.record.TableRecord; | |||
/** | |||
* The formula record aggregate is used to join together the formula record and it's | |||
@@ -33,35 +31,29 @@ import org.apache.poi.hssf.record.TableRecord; | |||
public final class FormulaRecordAggregate extends RecordAggregate implements CellValueRecordInterface { | |||
private final FormulaRecord _formulaRecord; | |||
private SharedValueManager _sharedValueManager; | |||
/** caches the calculated result of the formula */ | |||
private StringRecord _stringRecord; | |||
private TableRecord _tableRecord; | |||
public FormulaRecordAggregate(FormulaRecord formulaRecord) { | |||
_formulaRecord = formulaRecord; | |||
_stringRecord = null; | |||
} | |||
public FormulaRecordAggregate(FormulaRecord formulaRecord, RecordStream rs) { | |||
_formulaRecord = formulaRecord; | |||
Class nextClass = rs.peekNextClass(); | |||
if (nextClass == SharedFormulaRecord.class) { | |||
// For (text) shared formulas, the SharedFormulaRecord comes before the StringRecord. | |||
// In any case it is OK to skip SharedFormulaRecords because they were collected | |||
// before constructing the ValueRecordsAggregate. | |||
rs.getNext(); // skip the shared formula record | |||
nextClass = rs.peekNextClass(); | |||
/** | |||
* @param stringRec may be <code>null</code> if this formula does not have a cached text | |||
* value. | |||
* @param svm the {@link SharedValueManager} for the current sheet | |||
*/ | |||
public FormulaRecordAggregate(FormulaRecord formulaRec, StringRecord stringRec, SharedValueManager svm) { | |||
if (svm == null) { | |||
throw new IllegalArgumentException("sfm must not be null"); | |||
} | |||
if (nextClass == StringRecord.class) { | |||
_stringRecord = (StringRecord) rs.getNext(); | |||
} else if (nextClass == TableRecord.class) { | |||
_tableRecord = (TableRecord) rs.getNext(); | |||
if (formulaRec.isSharedFormula()) { | |||
svm.convertSharedFormulaRecord(formulaRec); | |||
} | |||
_formulaRecord = formulaRec; | |||
_sharedValueManager = svm; | |||
_stringRecord = stringRec; | |||
} | |||
public void setStringRecord(StringRecord stringRecord) { | |||
_stringRecord = stringRecord; | |||
_tableRecord = null; // probably can't have both present at the same time | |||
// TODO - establish rules governing when each of these sub records may exist | |||
} | |||
public FormulaRecord getFormulaRecord() { | |||
@@ -102,12 +94,13 @@ public final class FormulaRecordAggregate extends RecordAggregate implements Cel | |||
public void visitContainedRecords(RecordVisitor rv) { | |||
rv.visitRecord(_formulaRecord); | |||
Record sharedFormulaRecord = _sharedValueManager.getRecordForFirstCell(_formulaRecord); | |||
if (sharedFormulaRecord != null) { | |||
rv.visitRecord(sharedFormulaRecord); | |||
} | |||
if (_stringRecord != null) { | |||
rv.visitRecord(_stringRecord); | |||
} | |||
if (_tableRecord != null) { | |||
rv.visitRecord(_tableRecord); | |||
} | |||
} | |||
public String getStringValue() { |
@@ -93,8 +93,13 @@ public final class MergedCellsTable extends RecordAggregate { | |||
rv.visitRecord(new MergeCellsRecord(cras, startIx, nLeftoverMergedRegions)); | |||
} | |||
} | |||
public void addRecords(MergeCellsRecord[] mcrs) { | |||
for (int i = 0; i < mcrs.length; i++) { | |||
addMergeCellsRecord(mcrs[i]); | |||
} | |||
} | |||
public void add(MergeCellsRecord mcr) { | |||
private void addMergeCellsRecord(MergeCellsRecord mcr) { | |||
int nRegions = mcr.getNumAreas(); | |||
for (int i = 0; i < nRegions; i++) { | |||
_mergedRegions.add(mcr.getAreaAt(i)); | |||
@@ -125,4 +130,5 @@ public final class MergedCellsTable extends RecordAggregate { | |||
public int getNumberOfMergedRegions() { | |||
return _mergedRegions.size(); | |||
} | |||
} |
@@ -36,10 +36,16 @@ public abstract class RecordAggregate extends RecordBase { | |||
protected final void fillFields(RecordInputStream in) { | |||
throw new RuntimeException("Should not be called"); | |||
} | |||
public final short getSid() { | |||
public final short getSid() { | |||
throw new RuntimeException("Should not be called"); | |||
} | |||
} | |||
/** | |||
* Visit each of the atomic BIFF records contained in this {@link RecordAggregate} in the order | |||
* that they should be written to file. Implementors may or may not return the actual | |||
* {@link Record}s being used to manage POI's internal implementation. Callers should not | |||
* assume either way, and therefore only attempt to modify those {@link Record}s after cloning | |||
*/ | |||
public abstract void visitContainedRecords(RecordVisitor rv); | |||
public final int serialize(int offset, byte[] data) { | |||
@@ -94,4 +100,27 @@ public abstract class RecordAggregate extends RecordBase { | |||
_totalSize += r.getRecordSize(); | |||
} | |||
} | |||
/** | |||
* A wrapper for {@link RecordVisitor} which accumulates the sizes of all | |||
* records visited. | |||
*/ | |||
public static final class PositionTrackingVisitor implements RecordVisitor { | |||
private final RecordVisitor _rv; | |||
private int _position; | |||
public PositionTrackingVisitor(RecordVisitor rv, int initialPosition) { | |||
_rv = rv; | |||
_position = initialPosition; | |||
} | |||
public void visitRecord(Record r) { | |||
_position += r.getRecordSize(); | |||
_rv.visitRecord(r); | |||
} | |||
public void setPosition(int position) { | |||
_position = position; | |||
} | |||
public int getPosition() { | |||
return _position; | |||
} | |||
} | |||
} |
@@ -23,12 +23,17 @@ import java.util.List; | |||
import java.util.Map; | |||
import java.util.TreeMap; | |||
import org.apache.poi.hssf.model.RecordStream; | |||
import org.apache.poi.hssf.record.ArrayRecord; | |||
import org.apache.poi.hssf.record.CellValueRecordInterface; | |||
import org.apache.poi.hssf.record.DBCellRecord; | |||
import org.apache.poi.hssf.record.FormulaRecord; | |||
import org.apache.poi.hssf.record.IndexRecord; | |||
import org.apache.poi.hssf.record.MergeCellsRecord; | |||
import org.apache.poi.hssf.record.Record; | |||
import org.apache.poi.hssf.record.RowRecord; | |||
import org.apache.poi.hssf.record.SharedFormulaRecord; | |||
import org.apache.poi.hssf.record.TableRecord; | |||
import org.apache.poi.hssf.record.UnknownRecord; | |||
/** | |||
@@ -42,36 +47,34 @@ public final class RowRecordsAggregate extends RecordAggregate { | |||
private final Map _rowRecords; | |||
private final ValueRecordsAggregate _valuesAgg; | |||
private final List _unknownRecords; | |||
private final SharedValueManager _sharedValueManager; | |||
/** Creates a new instance of ValueRecordsAggregate */ | |||
public RowRecordsAggregate() { | |||
this(new TreeMap(), new ValueRecordsAggregate()); | |||
this(SharedValueManager.EMPTY); | |||
} | |||
private RowRecordsAggregate(TreeMap rowRecords, ValueRecordsAggregate valuesAgg) { | |||
_rowRecords = rowRecords; | |||
_valuesAgg = valuesAgg; | |||
private RowRecordsAggregate(SharedValueManager svm) { | |||
_rowRecords = new TreeMap(); | |||
_valuesAgg = new ValueRecordsAggregate(); | |||
_unknownRecords = new ArrayList(); | |||
_sharedValueManager = svm; | |||
} | |||
public RowRecordsAggregate(List recs, int startIx, int endIx) { | |||
this(); | |||
// First up, locate all the shared formulas for this sheet | |||
SharedFormulaHolder sfh = SharedFormulaHolder.create(recs, startIx, endIx); | |||
for(int i=startIx; i<endIx; i++) { | |||
Record rec = (Record) recs.get(i); | |||
/** | |||
* @param rs record stream with all {@link SharedFormulaRecord} | |||
* {@link ArrayRecord}, {@link TableRecord} {@link MergeCellsRecord} Records removed | |||
*/ | |||
public RowRecordsAggregate(RecordStream rs, SharedValueManager svm) { | |||
this(svm); | |||
while(rs.hasNext()) { | |||
Record rec = rs.getNext(); | |||
switch (rec.getSid()) { | |||
case MergeCellsRecord.sid: | |||
// Some apps scatter these records between the rows/cells but they are supposed to | |||
// be well after the row/cell records. It is assumed such rogue MergeCellRecords | |||
// have already been collected by the caller, and can safely be ignored here. | |||
// see bug 45699 | |||
continue; | |||
case RowRecord.sid: | |||
insertRow((RowRecord) rec); | |||
continue; | |||
case DBCellRecord.sid: | |||
// end of 'Row Block'. Should only occur after cell records | |||
// ignore DBCELL records because POI generates them upon re-serialization | |||
continue; | |||
} | |||
if (rec instanceof UnknownRecord) { | |||
@@ -82,9 +85,8 @@ public final class RowRecordsAggregate extends RecordAggregate { | |||
if (!rec.isValue()) { | |||
throw new RuntimeException("Unexpected record type (" + rec.getClass().getName() + ")"); | |||
} | |||
i += _valuesAgg.construct(recs, i, endIx, sfh)-1; | |||
_valuesAgg.construct((CellValueRecordInterface)rec, rs, svm); | |||
} | |||
"".length(); | |||
} | |||
/** | |||
* Handles UnknownRecords which appear within the row/cell records | |||
@@ -95,7 +97,7 @@ public final class RowRecordsAggregate extends RecordAggregate { | |||
// 0x01C2 // several | |||
// 0x0034 // few | |||
// No documentation could be found for these | |||
// keep the unknown records for re-serialization | |||
_unknownRecords.add(rec); | |||
} | |||
@@ -147,7 +149,7 @@ public final class RowRecordsAggregate extends RecordAggregate { | |||
{ | |||
return _lastrow; | |||
} | |||
/** Returns the number of row blocks. | |||
* <p/>The row blocks are goupings of rows that contain the DBCell record | |||
* after them | |||
@@ -209,7 +211,7 @@ public final class RowRecordsAggregate extends RecordAggregate { | |||
} | |||
return row.getRowNumber(); | |||
} | |||
private int visitRowRecordsForBlock(int blockIndex, RecordVisitor rv) { | |||
final int startIndex = blockIndex*DBCellRecord.BLOCK_SIZE; | |||
final int endIndex = startIndex + DBCellRecord.BLOCK_SIZE; | |||
@@ -230,11 +232,11 @@ public final class RowRecordsAggregate extends RecordAggregate { | |||
rv.visitRecord(rec); | |||
} | |||
return result; | |||
} | |||
} | |||
public void visitContainedRecords(RecordVisitor rv) { | |||
ValueRecordsAggregate cells = _valuesAgg; | |||
PositionTrackingVisitor stv = new PositionTrackingVisitor(rv, 0); | |||
//DBCells are serialized before row records. | |||
final int blockCount = getRowBlockCount(); | |||
for (int blockIndex = 0; blockIndex < blockCount; blockIndex++) { | |||
@@ -251,8 +253,10 @@ public final class RowRecordsAggregate extends RecordAggregate { | |||
// Note: Cell references start from the second row... | |||
int cellRefOffset = (rowBlockSize - RowRecord.ENCODED_SIZE); | |||
for (int row = startRowNumber; row <= endRowNumber; row++) { | |||
if (cells.rowHasCells(row)) { | |||
final int rowCellSize = cells.visitCellsForRow(row, rv); | |||
if (_valuesAgg.rowHasCells(row)) { | |||
stv.setPosition(0); | |||
_valuesAgg.visitCellsForRow(row, stv); | |||
int rowCellSize = stv.getPosition(); | |||
pos += rowCellSize; | |||
// Add the offset to the first cell for the row into the | |||
// DBCellRecord. | |||
@@ -273,8 +277,7 @@ public final class RowRecordsAggregate extends RecordAggregate { | |||
public Iterator getIterator() { | |||
return _rowRecords.values().iterator(); | |||
} | |||
public Iterator getAllRecordsIterator() { | |||
List result = new ArrayList(_rowRecords.size() * 2); | |||
result.addAll(_rowRecords.values()); | |||
@@ -498,5 +501,10 @@ public final class RowRecordsAggregate extends RecordAggregate { | |||
public void removeCell(CellValueRecordInterface cvRec) { | |||
_valuesAgg.removeCell(cvRec); | |||
} | |||
public FormulaRecordAggregate createFormula(int row, int col) { | |||
FormulaRecord fr = new FormulaRecord(); | |||
fr.setRow(row); | |||
fr.setColumn((short) col); | |||
return new FormulaRecordAggregate(fr, null, _sharedValueManager); | |||
} | |||
} | |||
@@ -1,96 +0,0 @@ | |||
/* ==================================================================== | |||
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.record.aggregates; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
import org.apache.poi.hssf.record.FormulaRecord; | |||
import org.apache.poi.hssf.record.Record; | |||
import org.apache.poi.hssf.record.SharedFormulaRecord; | |||
/** | |||
* Temporarily holds SharedFormulaRecords while constructing a <tt>RowRecordsAggregate</tt> | |||
* | |||
* @author Josh Micich | |||
*/ | |||
final class SharedFormulaHolder { | |||
private static final SharedFormulaHolder EMPTY = new SharedFormulaHolder(new SharedFormulaRecord[0]); | |||
private final SharedFormulaRecord[] _sfrs; | |||
/** | |||
* @param recs list of sheet records (possibly contains records for other parts of the Excel file) | |||
* @param startIx index of first row/cell record for current sheet | |||
* @param endIx one past index of last row/cell record for current sheet. It is important | |||
* that this code does not inadvertently collect <tt>SharedFormulaRecord</tt>s from any other | |||
* sheet (which could happen if endIx is chosen poorly). (see bug 44449) | |||
*/ | |||
public static SharedFormulaHolder create(List recs, int startIx, int endIx) { | |||
List temp = new ArrayList(); | |||
for (int k = startIx; k < endIx; k++) | |||
{ | |||
Record rec = ( Record ) recs.get(k); | |||
if (rec instanceof SharedFormulaRecord) { | |||
temp.add(rec); | |||
} | |||
} | |||
if (temp.size() < 1) { | |||
return EMPTY; | |||
} | |||
SharedFormulaRecord[] sfrs = new SharedFormulaRecord[temp.size()]; | |||
temp.toArray(sfrs); | |||
return new SharedFormulaHolder(sfrs); | |||
} | |||
private SharedFormulaHolder(SharedFormulaRecord[] sfrs) { | |||
_sfrs = sfrs; | |||
} | |||
public void convertSharedFormulaRecord(FormulaRecord formula) { | |||
// Traverse the list of shared formulas in | |||
// reverse order, and try to find the correct one | |||
// for us | |||
for (int i=0; i<_sfrs.length; i++) { | |||
SharedFormulaRecord shrd = _sfrs[i]; | |||
if (shrd.isFormulaInShared(formula)) { | |||
shrd.convertSharedFormulaRecord(formula); | |||
return; | |||
} | |||
} | |||
// not found | |||
handleMissingSharedFormulaRecord(formula); | |||
} | |||
/** | |||
* Sometimes the shared formula flag "seems" to be erroneously set, in which case there is no | |||
* call to <tt>SharedFormulaRecord.convertSharedFormulaRecord</tt> and hence the | |||
* <tt>parsedExpression</tt> field of this <tt>FormulaRecord</tt> will not get updated.<br/> | |||
* As it turns out, this is not a problem, because in these circumstances, the existing value | |||
* for <tt>parsedExpression</tt> is perfectly OK.<p/> | |||
* | |||
* This method may also be used for setting breakpoints to help diagnose issues regarding the | |||
* abnormally-set 'shared formula' flags. | |||
* (see TestValueRecordsAggregate.testSpuriousSharedFormulaFlag()).<p/> | |||
* | |||
* The method currently does nothing but do not delete it without finding a nice home for this | |||
* comment. | |||
*/ | |||
private static void handleMissingSharedFormulaRecord(FormulaRecord formula) { | |||
// could log an info message here since this is a fairly unusual occurrence. | |||
} | |||
} |
@@ -0,0 +1,130 @@ | |||
/* ==================================================================== | |||
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.record.aggregates; | |||
import org.apache.poi.hssf.record.ArrayRecord; | |||
import org.apache.poi.hssf.record.FormulaRecord; | |||
import org.apache.poi.hssf.record.SharedFormulaRecord; | |||
import org.apache.poi.hssf.record.SharedValueRecordBase; | |||
import org.apache.poi.hssf.record.TableRecord; | |||
/** | |||
* Manages various auxiliary records while constructing a | |||
* {@link RowRecordsAggregate}: | |||
* <ul> | |||
* <li>{@link SharedFormulaRecord}s</li> | |||
* <li>{@link ArrayRecord}s</li> | |||
* <li>{@link TableRecord}s</li> | |||
* </ul> | |||
* | |||
* @author Josh Micich | |||
*/ | |||
public final class SharedValueManager { | |||
public static final SharedValueManager EMPTY = new SharedValueManager( | |||
new SharedFormulaRecord[0], new ArrayRecord[0], new TableRecord[0]); | |||
private final SharedFormulaRecord[] _sfrs; | |||
private final ArrayRecord[] _arrayRecords; | |||
private final TableRecord[] _tableRecords; | |||
private SharedValueManager(SharedFormulaRecord[] sharedFormulaRecords, | |||
ArrayRecord[] arrayRecords, TableRecord[] tableRecords) { | |||
_sfrs = sharedFormulaRecords; | |||
_arrayRecords = arrayRecords; | |||
_tableRecords = tableRecords; | |||
} | |||
/** | |||
* @param recs list of sheet records (possibly contains records for other parts of the Excel file) | |||
* @param startIx index of first row/cell record for current sheet | |||
* @param endIx one past index of last row/cell record for current sheet. It is important | |||
* that this code does not inadvertently collect <tt>SharedFormulaRecord</tt>s from any other | |||
* sheet (which could happen if endIx is chosen poorly). (see bug 44449) | |||
*/ | |||
public static SharedValueManager create(SharedFormulaRecord[] sharedFormulaRecords, | |||
ArrayRecord[] arrayRecords, TableRecord[] tableRecords) { | |||
if (sharedFormulaRecords.length + arrayRecords.length + tableRecords.length < 1) { | |||
return EMPTY; | |||
} | |||
return new SharedValueManager(sharedFormulaRecords, arrayRecords, tableRecords); | |||
} | |||
public void convertSharedFormulaRecord(FormulaRecord formula) { | |||
int row = formula.getRow(); | |||
int column = formula.getColumn(); | |||
// Traverse the list of shared formulas in | |||
// reverse order, and try to find the correct one | |||
// for us | |||
for (int i = 0; i < _sfrs.length; i++) { | |||
SharedFormulaRecord shrd = _sfrs[i]; | |||
if (shrd.isInRange(row, column)) { | |||
shrd.convertSharedFormulaRecord(formula); | |||
return; | |||
} | |||
} | |||
// not found | |||
handleMissingSharedFormulaRecord(formula); | |||
} | |||
/** | |||
* Sometimes the shared formula flag "seems" to be erroneously set, in which case there is no | |||
* call to <tt>SharedFormulaRecord.convertSharedFormulaRecord</tt> and hence the | |||
* <tt>parsedExpression</tt> field of this <tt>FormulaRecord</tt> will not get updated.<br/> | |||
* As it turns out, this is not a problem, because in these circumstances, the existing value | |||
* for <tt>parsedExpression</tt> is perfectly OK.<p/> | |||
* | |||
* This method may also be used for setting breakpoints to help diagnose issues regarding the | |||
* abnormally-set 'shared formula' flags. | |||
* (see TestValueRecordsAggregate.testSpuriousSharedFormulaFlag()).<p/> | |||
* | |||
* The method currently does nothing but do not delete it without finding a nice home for this | |||
* comment. | |||
*/ | |||
private static void handleMissingSharedFormulaRecord(FormulaRecord formula) { | |||
// could log an info message here since this is a fairly unusual occurrence. | |||
formula.setSharedFormula(false); // no point leaving the flag erroneously set | |||
} | |||
/** | |||
* Note - does not return SharedFormulaRecords currently, because the corresponding formula | |||
* records have been converted to 'unshared'. POI does not attempt to re-share formulas. On | |||
* the other hand, there is no such conversion for array or table formulas, so this method | |||
* returns the TABLE or ARRAY record (if it should be written after the specified | |||
* formulaRecord. | |||
* | |||
* @return the TABLE or ARRAY record for this formula cell, if it is the first cell of a | |||
* table or array region. | |||
*/ | |||
public SharedValueRecordBase getRecordForFirstCell(FormulaRecord formulaRecord) { | |||
int row = formulaRecord.getRow(); | |||
int column = formulaRecord.getColumn(); | |||
for (int i = 0; i < _tableRecords.length; i++) { | |||
TableRecord tr = _tableRecords[i]; | |||
if (tr.isFirstCell(row, column)) { | |||
return tr; | |||
} | |||
} | |||
for (int i = 0; i < _arrayRecords.length; i++) { | |||
ArrayRecord ar = _arrayRecords[i]; | |||
if (ar.isFirstCell(row, column)) { | |||
return ar; | |||
} | |||
} | |||
return null; | |||
} | |||
} |
@@ -23,16 +23,10 @@ import java.util.List; | |||
import org.apache.poi.hssf.model.RecordStream; | |||
import org.apache.poi.hssf.record.CellValueRecordInterface; | |||
import org.apache.poi.hssf.record.DBCellRecord; | |||
import org.apache.poi.hssf.record.FormulaRecord; | |||
import org.apache.poi.hssf.record.MergeCellsRecord; | |||
import org.apache.poi.hssf.record.Record; | |||
import org.apache.poi.hssf.record.RecordBase; | |||
import org.apache.poi.hssf.record.RowRecord; | |||
import org.apache.poi.hssf.record.SharedFormulaRecord; | |||
import org.apache.poi.hssf.record.StringRecord; | |||
import org.apache.poi.hssf.record.TableRecord; | |||
import org.apache.poi.hssf.record.UnknownRecord; | |||
import org.apache.poi.hssf.record.aggregates.RecordAggregate.RecordVisitor; | |||
/** | |||
@@ -143,63 +137,27 @@ public final class ValueRecordsAggregate { | |||
} | |||
/** | |||
* Processes a sequential group of cell value records. Stops at endIx or the first | |||
* non-value record encountered. | |||
* @param sfh used to resolve any shared formulas for the current sheet | |||
* @return the number of records consumed | |||
* Processes a single cell value record | |||
* @param sfh used to resolve any shared-formulas/arrays/tables for the current sheet | |||
*/ | |||
public int construct(List records, int offset, int endIx, SharedFormulaHolder sfh) { | |||
RecordStream rs = new RecordStream(records, offset, endIx); | |||
// Now do the main processing sweep | |||
while (rs.hasNext()) { | |||
Class recClass = rs.peekNextClass(); | |||
if (recClass == StringRecord.class) { | |||
throw new RuntimeException("Loose StringRecord found without preceding FormulaRecord"); | |||
public void construct(CellValueRecordInterface rec, RecordStream rs, SharedValueManager sfh) { | |||
if (rec instanceof FormulaRecord) { | |||
FormulaRecord formulaRec = (FormulaRecord)rec; | |||
if (formulaRec.isSharedFormula()) { | |||
sfh.convertSharedFormulaRecord(formulaRec); | |||
} | |||
if (recClass == TableRecord.class) { | |||
throw new RuntimeException("Loose TableRecord found without preceding FormulaRecord"); | |||
} | |||
if (recClass == UnknownRecord.class) { | |||
break; | |||
} | |||
if (recClass == RowRecord.class) { | |||
break; | |||
} | |||
if (recClass == DBCellRecord.class) { | |||
// end of 'Row Block'. This record is ignored by POI | |||
break; | |||
// read optional cached text value | |||
StringRecord cachedText; | |||
Class nextClass = rs.peekNextClass(); | |||
if (nextClass == StringRecord.class) { | |||
cachedText = (StringRecord) rs.getNext(); | |||
} else { | |||
cachedText = null; | |||
} | |||
Record rec = rs.getNext(); | |||
if (recClass == SharedFormulaRecord.class) { | |||
// Already handled, not to worry | |||
continue; | |||
} | |||
if (recClass == MergeCellsRecord.class) { | |||
// doesn't really belong here | |||
// can safely be ignored, because it has been processed in a higher method | |||
continue; | |||
} | |||
if (!rec.isValue()) { | |||
throw new RuntimeException("bad record type"); | |||
} | |||
if (rec instanceof FormulaRecord) { | |||
FormulaRecord formula = (FormulaRecord)rec; | |||
if (formula.isSharedFormula()) { | |||
sfh.convertSharedFormulaRecord(formula); | |||
} | |||
insertCell(new FormulaRecordAggregate((FormulaRecord)rec, rs)); | |||
continue; | |||
} | |||
insertCell(( CellValueRecordInterface ) rec); | |||
insertCell(new FormulaRecordAggregate(formulaRec, cachedText, sfh)); | |||
} else { | |||
insertCell(rec); | |||
} | |||
return rs.getCountRead(); | |||
} | |||
/** Tallies a count of the size of the cell records | |||
@@ -247,8 +205,8 @@ public final class ValueRecordsAggregate { | |||
return pos - offset; | |||
} | |||
public int visitCellsForRow(int rowIndex, RecordVisitor rv) { | |||
int result = 0; | |||
public void visitCellsForRow(int rowIndex, RecordVisitor rv) { | |||
CellValueRecordInterface[] cellRecs = records[rowIndex]; | |||
if (cellRecs != null) { | |||
for (int i = 0; i < cellRecs.length; i++) { | |||
@@ -256,24 +214,15 @@ public final class ValueRecordsAggregate { | |||
if (cvr == null) { | |||
continue; | |||
} | |||
if (cvr instanceof FormulaRecordAggregate) { | |||
FormulaRecordAggregate fmAgg = (FormulaRecordAggregate) cvr; | |||
Record fmAggRec = fmAgg.getFormulaRecord(); | |||
rv.visitRecord(fmAggRec); | |||
result += fmAggRec.getRecordSize(); | |||
fmAggRec = fmAgg.getStringRecord(); | |||
if (fmAggRec != null) { | |||
rv.visitRecord(fmAggRec); | |||
result += fmAggRec.getRecordSize(); | |||
} | |||
if (cvr instanceof RecordAggregate) { | |||
RecordAggregate agg = (RecordAggregate) cvr; | |||
agg.visitContainedRecords(rv); | |||
} else { | |||
Record rec = (Record) cvr; | |||
rv.visitRecord(rec); | |||
result += rec.getRecordSize(); | |||
} | |||
} | |||
} | |||
return result; | |||
} | |||
public CellValueRecordInterface[] getValueRecords() { |
@@ -292,23 +292,20 @@ public class HSSFCell implements Cell { | |||
{ | |||
case CELL_TYPE_FORMULA : | |||
FormulaRecordAggregate frec = null; | |||
if (cellType != this.cellType) | |||
{ | |||
frec = new FormulaRecordAggregate(new FormulaRecord()); | |||
} | |||
else | |||
{ | |||
frec = ( FormulaRecordAggregate ) record; | |||
FormulaRecordAggregate frec; | |||
if (cellType != this.cellType) { | |||
frec = sheet.createFormula(row, col); | |||
} else { | |||
frec = (FormulaRecordAggregate) record; | |||
frec.setRow(row); | |||
frec.setColumn(col); | |||
} | |||
frec.setColumn(col); | |||
if (setValue) | |||
{ | |||
frec.getFormulaRecord().setValue(getNumericCellValue()); | |||
} | |||
frec.setXFIndex(styleIndex); | |||
frec.setRow(row); | |||
record = frec; | |||
break; | |||
@@ -51,6 +51,7 @@ import org.apache.poi.hssf.record.RecordFactory; | |||
import org.apache.poi.hssf.record.SSTRecord; | |||
import org.apache.poi.hssf.record.UnicodeString; | |||
import org.apache.poi.hssf.record.UnknownRecord; | |||
import org.apache.poi.hssf.record.aggregates.RecordAggregate.RecordVisitor; | |||
import org.apache.poi.hssf.record.formula.Area3DPtg; | |||
import org.apache.poi.hssf.record.formula.MemFuncPtg; | |||
import org.apache.poi.hssf.record.formula.NameXPtg; | |||
@@ -109,7 +110,7 @@ public class HSSFWorkbook extends POIDocument implements org.apache.poi.ss.userm | |||
*/ | |||
private ArrayList names; | |||
/** | |||
* this holds the HSSFFont objects attached to this workbook. | |||
* We only create these from the low level records as required. | |||
@@ -129,7 +130,7 @@ public class HSSFWorkbook extends POIDocument implements org.apache.poi.ss.userm | |||
* someplace else. | |||
*/ | |||
private HSSFDataFormat formatter; | |||
/** | |||
* The policy to apply in the event of missing or | |||
* blank cells when fetching from a row. | |||
@@ -380,7 +381,7 @@ public class HSSFWorkbook extends POIDocument implements org.apache.poi.ss.userm | |||
/** | |||
* Sets the policy on what to do when | |||
* getting missing or blank cells from a row. | |||
* This will then apply to all calls to | |||
* This will then apply to all calls to | |||
* {@link HSSFRow.getCell()}. See | |||
* {@link MissingCellPolicy} | |||
*/ | |||
@@ -403,17 +404,17 @@ public class HSSFWorkbook extends POIDocument implements org.apache.poi.ss.userm | |||
private void validateSheetIndex(int index) { | |||
int lastSheetIx = _sheets.size() - 1; | |||
if (index < 0 || index > lastSheetIx) { | |||
throw new IllegalArgumentException("Sheet index (" | |||
throw new IllegalArgumentException("Sheet index (" | |||
+ index +") is out of range (0.." + lastSheetIx + ")"); | |||
} | |||
} | |||
/** | |||
* Selects a single sheet. This may be different to | |||
* the 'active' sheet (which is the sheet with focus). | |||
* the 'active' sheet (which is the sheet with focus). | |||
*/ | |||
public void setSelectedTab(int index) { | |||
validateSheetIndex(index); | |||
int nSheets = _sheets.size(); | |||
for (int i=0; i<nSheets; i++) { | |||
@@ -429,7 +430,7 @@ public class HSSFWorkbook extends POIDocument implements org.apache.poi.ss.userm | |||
setSelectedTab((int)index); | |||
} | |||
public void setSelectedTabs(int[] indexes) { | |||
for (int i = 0; i < indexes.length; i++) { | |||
validateSheetIndex(indexes[i]); | |||
} | |||
@@ -441,7 +442,7 @@ public class HSSFWorkbook extends POIDocument implements org.apache.poi.ss.userm | |||
bSelect = true; | |||
break; | |||
} | |||
} | |||
getSheetAt(i).setSelected(bSelect); | |||
} | |||
@@ -453,7 +454,7 @@ public class HSSFWorkbook extends POIDocument implements org.apache.poi.ss.userm | |||
* 'Selected' sheet(s) is a distinct concept. | |||
*/ | |||
public void setActiveSheet(int index) { | |||
validateSheetIndex(index); | |||
int nSheets = _sheets.size(); | |||
for (int i=0; i<nSheets; i++) { | |||
@@ -474,13 +475,13 @@ public class HSSFWorkbook extends POIDocument implements org.apache.poi.ss.userm | |||
} | |||
/** | |||
* deprecated May 2008 | |||
* @deprecated - Misleading name - use getActiveSheetIndex() | |||
* @deprecated - Misleading name - use getActiveSheetIndex() | |||
*/ | |||
public short getSelectedTab() { | |||
return (short) getActiveSheetIndex(); | |||
} | |||
/** | |||
* sets the first tab that is displayed in the list of tabs | |||
* in excel. | |||
@@ -491,7 +492,7 @@ public class HSSFWorkbook extends POIDocument implements org.apache.poi.ss.userm | |||
} | |||
/** | |||
* deprecated May 2008 | |||
* @deprecated - Misleading name - use setFirstVisibleTab() | |||
* @deprecated - Misleading name - use setFirstVisibleTab() | |||
*/ | |||
public void setDisplayedTab(short index) { | |||
setFirstVisibleTab(index); | |||
@@ -505,7 +506,7 @@ public class HSSFWorkbook extends POIDocument implements org.apache.poi.ss.userm | |||
} | |||
/** | |||
* deprecated May 2008 | |||
* @deprecated - Misleading name - use getFirstVisibleTab() | |||
* @deprecated - Misleading name - use getFirstVisibleTab() | |||
*/ | |||
public short getDisplayedTab() { | |||
return (short) getFirstVisibleTab(); | |||
@@ -698,7 +699,7 @@ public class HSSFWorkbook extends POIDocument implements org.apache.poi.ss.userm | |||
/** | |||
* create an HSSFSheet for this HSSFWorkbook, adds it to the sheets and | |||
* returns the high level representation. Use this to create new sheets. | |||
* | |||
* | |||
* @param sheetname | |||
* sheetname to set for the sheet. | |||
* @return HSSFSheet representing the new sheet. | |||
@@ -781,16 +782,16 @@ public class HSSFWorkbook extends POIDocument implements org.apache.poi.ss.userm | |||
/** | |||
* Removes sheet at the given index.<p/> | |||
* | |||
* Care must be taken if the removed sheet is the currently active or only selected sheet in | |||
* the workbook. There are a few situations when Excel must have a selection and/or active | |||
* | |||
* Care must be taken if the removed sheet is the currently active or only selected sheet in | |||
* the workbook. There are a few situations when Excel must have a selection and/or active | |||
* sheet. (For example when printing - see Bug 40414).<br/> | |||
* | |||
* | |||
* This method makes sure that if the removed sheet was active, another sheet will become | |||
* active in its place. Furthermore, if the removed sheet was the only selected sheet, another | |||
* sheet will become selected. The newly active/selected sheet will have the same index, or | |||
* sheet will become selected. The newly active/selected sheet will have the same index, or | |||
* one less if the removed sheet was the last in the workbook. | |||
* | |||
* | |||
* @param index of the sheet (0-based) | |||
*/ | |||
public void removeSheetAt(int index) { | |||
@@ -1023,7 +1024,7 @@ public class HSSFWorkbook extends POIDocument implements org.apache.poi.ss.userm | |||
if(fontindex == Short.MAX_VALUE){ | |||
throw new IllegalArgumentException("Maximum number of fonts was exceeded"); | |||
} | |||
// Ask getFontAt() to build it for us, | |||
// so it gets properly cached | |||
return getFontAt(fontindex); | |||
@@ -1039,7 +1040,7 @@ public class HSSFWorkbook extends POIDocument implements org.apache.poi.ss.userm | |||
for (short i=0; i<=getNumberOfFonts(); i++) { | |||
// Remember - there is no 4! | |||
if(i == 4) continue; | |||
HSSFFont hssfFont = getFontAt(i); | |||
if (hssfFont.getBoldweight() == boldWeight | |||
&& hssfFont.getColor() == color | |||
@@ -1089,7 +1090,7 @@ public class HSSFWorkbook extends POIDocument implements org.apache.poi.ss.userm | |||
return retval; | |||
} | |||
/** | |||
* Reset the fonts cache, causing all new calls | |||
* to getFontAt() to create new objects. | |||
@@ -1179,6 +1180,37 @@ public class HSSFWorkbook extends POIDocument implements org.apache.poi.ss.userm | |||
//poifs.writeFilesystem(stream); | |||
} | |||
/** | |||
* Totals the sizes of all sheet records and eventually serializes them | |||
*/ | |||
private static final class SheetRecordCollector implements RecordVisitor { | |||
private List _list; | |||
private int _totalSize; | |||
public SheetRecordCollector() { | |||
_totalSize = 0; | |||
_list = new ArrayList(128); | |||
} | |||
public int getTotalSize() { | |||
return _totalSize; | |||
} | |||
public void visitRecord(Record r) { | |||
_list.add(r); | |||
_totalSize+=r.getRecordSize(); | |||
} | |||
public int serialize(int offset, byte[] data) { | |||
int result = 0; | |||
int nRecs = _list.size(); | |||
for(int i=0; i<nRecs; i++) { | |||
Record rec = (Record)_list.get(i); | |||
result += rec.serialize(offset + result, data); | |||
} | |||
return result; | |||
} | |||
} | |||
/** | |||
* Method getBytes - get the bytes of just the HSSF portions of the XLS file. | |||
* Use this to construct a POI POIFSFileSystem yourself. | |||
@@ -1190,13 +1222,11 @@ public class HSSFWorkbook extends POIDocument implements org.apache.poi.ss.userm | |||
* @see org.apache.poi.hssf.model.Workbook | |||
* @see org.apache.poi.hssf.model.Sheet | |||
*/ | |||
public byte[] getBytes() | |||
{ | |||
public byte[] getBytes() { | |||
if (log.check( POILogger.DEBUG )) { | |||
log.log(DEBUG, "HSSFWorkbook.getBytes()"); | |||
} | |||
HSSFSheet[] sheets = getSheets(); | |||
int nSheets = sheets.length; | |||
@@ -1209,26 +1239,27 @@ public class HSSFWorkbook extends POIDocument implements org.apache.poi.ss.userm | |||
int totalsize = workbook.getSize(); | |||
// pre-calculate all the sheet sizes and set BOF indexes | |||
int[] estimatedSheetSizes = new int[nSheets]; | |||
SheetRecordCollector[] srCollectors = new SheetRecordCollector[nSheets]; | |||
for (int k = 0; k < nSheets; k++) { | |||
workbook.setSheetBof(k, totalsize); | |||
int sheetSize = sheets[k].getSheet().getSize(); | |||
estimatedSheetSizes[k] = sheetSize; | |||
totalsize += sheetSize; | |||
SheetRecordCollector src = new SheetRecordCollector(); | |||
sheets[k].getSheet().visitContainedRecords(src, totalsize); | |||
totalsize += src.getTotalSize(); | |||
srCollectors[k] = src; | |||
} | |||
byte[] retval = new byte[totalsize]; | |||
int pos = workbook.serialize(0, retval); | |||
for (int k = 0; k < nSheets; k++) { | |||
int serializedSize = sheets[k].getSheet().serialize(pos, retval); | |||
if (serializedSize != estimatedSheetSizes[k]) { | |||
SheetRecordCollector src = srCollectors[k]; | |||
int serializedSize = src.serialize(pos, retval); | |||
if (serializedSize != src.getTotalSize()) { | |||
// Wrong offset values have been passed in the call to setSheetBof() above. | |||
// For books with more than one sheet, this discrepancy would cause excel | |||
// For books with more than one sheet, this discrepancy would cause excel | |||
// to report errors and loose data while reading the workbook | |||
throw new IllegalStateException("Actual serialized sheet size (" + serializedSize | |||
+ ") differs from pre-calculated size (" + estimatedSheetSizes[k] | |||
throw new IllegalStateException("Actual serialized sheet size (" + serializedSize | |||
+ ") differs from pre-calculated size (" + src.getTotalSize() | |||
+ ") for sheet (" + k + ")"); | |||
// TODO - add similar sanity check to ensure that Sheet.serializeIndexRecord() does not write mis-aligned offsets either | |||
} | |||
@@ -1671,11 +1702,11 @@ public class HSSFWorkbook extends POIDocument implements org.apache.poi.ss.userm | |||
} | |||
/** | |||
* Note - This method should only used by POI internally. | |||
* Note - This method should only used by POI internally. | |||
* It may get deleted or change definition in future POI versions | |||
*/ | |||
public NameXPtg getNameXPtg(String name) { | |||
return workbook.getNameXPtg(name); | |||
} | |||
public NameXPtg getNameXPtg(String name) { | |||
return workbook.getNameXPtg(name); | |||
} | |||
} |
@@ -68,7 +68,7 @@ public class StringUtil { | |||
throw new ArrayIndexOutOfBoundsException("Illegal offset"); | |||
} | |||
if ((len < 0) || (((string.length - offset) / 2) < len)) { | |||
throw new IllegalArgumentException("Illegal length"); | |||
throw new IllegalArgumentException("Illegal length " + len); | |||
} | |||
try { |
@@ -20,17 +20,11 @@ import java.io.FileInputStream; | |||
import java.io.IOException; | |||
import java.io.InputStream; | |||
import org.apache.poi.ddf.DefaultEscherRecordFactory; | |||
import org.apache.poi.ddf.EscherRecord; | |||
import org.apache.poi.hpbf.HPBFDocument; | |||
import org.apache.poi.hpbf.model.QuillContents; | |||
import org.apache.poi.hpbf.model.qcbits.QCBit; | |||
import org.apache.poi.poifs.filesystem.DirectoryNode; | |||
import org.apache.poi.poifs.filesystem.DocumentEntry; | |||
import org.apache.poi.poifs.filesystem.POIFSFileSystem; | |||
import org.apache.poi.util.HexDump; | |||
import org.apache.poi.util.LittleEndian; | |||
import org.apache.poi.util.StringUtil; | |||
/** | |||
* For dumping out the PLC contents of QC Bits of a | |||
@@ -41,8 +35,8 @@ public class PLCDumper { | |||
private HPBFDocument doc; | |||
private QuillContents qc; | |||
public PLCDumper(HPBFDocument doc) { | |||
this.doc = doc; | |||
public PLCDumper(HPBFDocument hpbfDoc) { | |||
doc = hpbfDoc; | |||
qc = doc.getQuillContents(); | |||
} | |||
public PLCDumper(POIFSFileSystem fs) throws IOException { | |||
@@ -67,7 +61,6 @@ public class PLCDumper { | |||
} | |||
private void dumpPLC() { | |||
QuillContents qc = doc.getQuillContents(); | |||
QCBit[] bits = qc.getBits(); | |||
for(int i=0; i<bits.length; i++) { | |||
@@ -82,8 +75,8 @@ public class PLCDumper { | |||
System.out.println(""); | |||
System.out.println("Dumping " + bit.getBitType() + " bit at " + index); | |||
System.out.println(" Is a " + bit.getThingType() + ", number is " + bit.getOptA()); | |||
System.out.println(" Starts at " + bit.getDataOffset() + " (" + Integer.toHexString(bit.getDataOffset()) + ")"); | |||
System.out.println(" Runs for " + bit.getLength() + " (" + Integer.toHexString(bit.getLength()) + ")"); | |||
System.out.println(" Starts at " + bit.getDataOffset() + " (0x" + Integer.toHexString(bit.getDataOffset()) + ")"); | |||
System.out.println(" Runs for " + bit.getLength() + " (0x" + Integer.toHexString(bit.getLength()) + ")"); | |||
System.out.println(HexDump.dump(bit.getData(), 0, 0)); | |||
} |
@@ -24,6 +24,7 @@ import org.apache.poi.POIOLE2TextExtractor; | |||
import org.apache.poi.hpbf.HPBFDocument; | |||
import org.apache.poi.hpbf.model.qcbits.QCBit; | |||
import org.apache.poi.hpbf.model.qcbits.QCTextBit; | |||
import org.apache.poi.hpbf.model.qcbits.QCPLCBit.Type12; | |||
import org.apache.poi.poifs.filesystem.POIFSFileSystem; | |||
/** | |||
@@ -31,6 +32,7 @@ import org.apache.poi.poifs.filesystem.POIFSFileSystem; | |||
*/ | |||
public class PublisherTextExtractor extends POIOLE2TextExtractor { | |||
private HPBFDocument doc; | |||
private boolean hyperlinksByDefault = false; | |||
public PublisherTextExtractor(HPBFDocument doc) { | |||
super(doc); | |||
@@ -43,6 +45,16 @@ public class PublisherTextExtractor extends POIOLE2TextExtractor { | |||
this(new POIFSFileSystem(is)); | |||
} | |||
/** | |||
* Should a call to getText() return hyperlinks inline | |||
* with the text? | |||
* Default is no | |||
*/ | |||
public void setHyperlinksByDefault(boolean hyperlinksByDefault) { | |||
this.hyperlinksByDefault = hyperlinksByDefault; | |||
} | |||
public String getText() { | |||
StringBuffer text = new StringBuffer(); | |||
@@ -55,6 +67,24 @@ public class PublisherTextExtractor extends POIOLE2TextExtractor { | |||
} | |||
} | |||
// If requested, add in the hyperlinks | |||
// Ideally, we'd do these inline, but the hyperlink | |||
// positions are relative to the text area the | |||
// hyperlink is in, and we have yet to figure out | |||
// how to tie that together. | |||
if(hyperlinksByDefault) { | |||
for(int i=0; i<bits.length; i++) { | |||
if(bits[i] != null && bits[i] instanceof Type12) { | |||
Type12 hyperlinks = (Type12)bits[i]; | |||
for(int j=0; j<hyperlinks.getNumberOfHyperlinks(); j++) { | |||
text.append("<"); | |||
text.append(hyperlinks.getHyperlink(j)); | |||
text.append(">\n"); | |||
} | |||
} | |||
} | |||
} | |||
// Get more text | |||
// TODO | |||
@@ -19,6 +19,7 @@ package org.apache.poi.hpbf.model; | |||
import java.io.IOException; | |||
import org.apache.poi.hpbf.model.qcbits.QCBit; | |||
import org.apache.poi.hpbf.model.qcbits.QCPLCBit; | |||
import org.apache.poi.hpbf.model.qcbits.QCTextBit; | |||
import org.apache.poi.hpbf.model.qcbits.UnknownQCBit; | |||
import org.apache.poi.poifs.filesystem.DirectoryNode; | |||
@@ -64,6 +65,8 @@ public final class QuillContents extends HPBFPart { | |||
// Create | |||
if(bitType.equals("TEXT")) { | |||
bits[i] = new QCTextBit(thingType, bitType, bitData); | |||
} else if(bitType.equals("PLC ")) { | |||
bits[i] = QCPLCBit.createQCPLCBit(thingType, bitType, bitData); | |||
} else { | |||
bits[i] = new UnknownQCBit(thingType, bitType, bitData); | |||
} |
@@ -0,0 +1,274 @@ | |||
/* ==================================================================== | |||
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.hpbf.model.qcbits; | |||
import org.apache.poi.util.LittleEndian; | |||
import org.apache.poi.util.StringUtil; | |||
/** | |||
* A "PLC " (PLC) based bit of Quill Contents. The exact | |||
* format is determined by the type of the PLCs. | |||
*/ | |||
public class QCPLCBit extends QCBit { | |||
protected int numberOfPLCs; | |||
protected int typeOfPLCS; | |||
/** | |||
* The data which goes before the main PLC entries. | |||
* This is apparently always made up of 2 byte | |||
* un-signed ints.. | |||
*/ | |||
protected int[] preData; | |||
/** The first value of each PLC, normally 4 bytes */ | |||
protected long[] plcValA; | |||
/** The second value of each PLC, normally 4 bytes */ | |||
protected long[] plcValB; | |||
private QCPLCBit(String thingType, String bitType, byte[] data) { | |||
super(thingType, bitType, data); | |||
// First four bytes are the number | |||
numberOfPLCs = (int)LittleEndian.getUInt(data, 0); | |||
// Next four bytes are the type | |||
typeOfPLCS = (int)LittleEndian.getUInt(data, 4); | |||
// Init the arrays that we can | |||
plcValA = new long[numberOfPLCs]; | |||
plcValB = new long[numberOfPLCs]; | |||
} | |||
public int getNumberOfPLCs() { | |||
return numberOfPLCs; | |||
} | |||
public int getTypeOfPLCS() { | |||
return typeOfPLCS; | |||
} | |||
public int[] getPreData() { | |||
return preData; | |||
} | |||
public long[] getPlcValA() { | |||
return plcValA; | |||
} | |||
public long[] getPlcValB() { | |||
return plcValB; | |||
} | |||
public static QCPLCBit createQCPLCBit(String thingType, String bitType, byte[] data) { | |||
// Grab the type | |||
int type = (int)LittleEndian.getUInt(data, 4); | |||
switch(type) { | |||
case 0: | |||
return new Type0(thingType, bitType, data); | |||
case 4: | |||
return new Type4(thingType, bitType, data); | |||
case 8: | |||
return new Type8(thingType, bitType, data); | |||
case 12: // 0xc | |||
return new Type12(thingType, bitType, data); | |||
default: | |||
throw new IllegalArgumentException("Sorry, I don't know how to deal with PLCs of type " + type); | |||
} | |||
} | |||
/** | |||
* Type 0 seem to be somewhat rare. They have 8 bytes of pre-data, | |||
* then 2x 2 byte values. | |||
*/ | |||
public static class Type0 extends QCPLCBit { | |||
private Type0(String thingType, String bitType, byte[] data) { | |||
super(thingType, bitType, data); | |||
// Grab our 4x pre-data | |||
preData = new int[4]; | |||
preData[0] = LittleEndian.getUShort(data, 8+0); | |||
preData[1] = LittleEndian.getUShort(data, 8+2); | |||
preData[2] = LittleEndian.getUShort(data, 8+4); | |||
preData[3] = LittleEndian.getUShort(data, 8+6); | |||
// And grab the 2 byte values | |||
for(int i=0; i<numberOfPLCs; i++) { | |||
plcValA[i] = LittleEndian.getUShort(data, 16+(4*i)); | |||
plcValB[i] = LittleEndian.getUShort(data, 16+(4*i)+2); | |||
} | |||
} | |||
} | |||
/** | |||
* Type 4 is quite common. They have 8 bytes of pre-data, | |||
* then 2x 4 byte values. | |||
*/ | |||
public static class Type4 extends QCPLCBit { | |||
private Type4(String thingType, String bitType, byte[] data) { | |||
super(thingType, bitType, data); | |||
// Grab our 4x pre-data | |||
preData = new int[4]; | |||
preData[0] = LittleEndian.getUShort(data, 8+0); | |||
preData[1] = LittleEndian.getUShort(data, 8+2); | |||
preData[2] = LittleEndian.getUShort(data, 8+4); | |||
preData[3] = LittleEndian.getUShort(data, 8+6); | |||
// And grab the 4 byte values | |||
for(int i=0; i<numberOfPLCs; i++) { | |||
plcValA[i] = LittleEndian.getUInt(data, 16+(8*i)); | |||
plcValB[i] = LittleEndian.getUInt(data, 16+(8*i)+4); | |||
} | |||
} | |||
} | |||
/** | |||
* Type 8 is quite common. They have 14 bytes of pre-data, | |||
* then 2x 4 byte values. | |||
*/ | |||
public static class Type8 extends QCPLCBit { | |||
private Type8(String thingType, String bitType, byte[] data) { | |||
super(thingType, bitType, data); | |||
// Grab our 7x pre-data | |||
preData = new int[7]; | |||
preData[0] = LittleEndian.getUShort(data, 8+0); | |||
preData[1] = LittleEndian.getUShort(data, 8+2); | |||
preData[2] = LittleEndian.getUShort(data, 8+4); | |||
preData[3] = LittleEndian.getUShort(data, 8+6); | |||
preData[4] = LittleEndian.getUShort(data, 8+8); | |||
preData[5] = LittleEndian.getUShort(data, 8+10); | |||
preData[6] = LittleEndian.getUShort(data, 8+12); | |||
// And grab the 4 byte values | |||
for(int i=0; i<numberOfPLCs; i++) { | |||
plcValA[i] = LittleEndian.getUInt(data, 22+(8*i)); | |||
plcValB[i] = LittleEndian.getUInt(data, 22+(8*i)+4); | |||
} | |||
} | |||
} | |||
/** | |||
* Type 12 holds hyperlinks, and is very complex. | |||
* There is normally one of these for each text | |||
* area that contains at least one hyperlinks. | |||
* The character offsets are relative to the start | |||
* of the text area that this applies to. | |||
*/ | |||
public static class Type12 extends QCPLCBit { | |||
private String[] hyperlinks; | |||
private static final int oneStartsAt = 0x4c; | |||
private static final int twoStartsAt = 0x68; | |||
private static final int threePlusIncrement = 22; | |||
private Type12(String thingType, String bitType, byte[] data) { | |||
super(thingType, bitType, data); | |||
// How many hyperlinks do we really have? | |||
// (zero hyperlinks gets numberOfPLCs=1) | |||
if(data.length == 0x34) { | |||
hyperlinks = new String[0]; | |||
} else { | |||
hyperlinks = new String[numberOfPLCs]; | |||
} | |||
// We have 4 bytes, then the start point of each | |||
// hyperlink, then the end point of the text. | |||
preData = new int[1+numberOfPLCs+1]; | |||
for(int i=0; i<preData.length; i++) { | |||
preData[i] = (int)LittleEndian.getUInt(data, 8+(i*4)); | |||
} | |||
// Then we have a whole bunch of stuff, which grows | |||
// with the number of hyperlinks | |||
// For now, we think these are shorts | |||
int at = 8+4+(numberOfPLCs*4)+4; | |||
int until = 0x34; | |||
if(numberOfPLCs == 1 && hyperlinks.length == 1) { | |||
until = oneStartsAt; | |||
} else if(numberOfPLCs >= 2) { | |||
until = twoStartsAt + (numberOfPLCs-2)*threePlusIncrement; | |||
} | |||
plcValA = new long[(until-at)/2]; | |||
plcValB = new long[0]; | |||
for(int i=0; i<plcValA.length; i++) { | |||
plcValA[i] = LittleEndian.getUShort(data, at+(i*2)); | |||
} | |||
// Finally, we have a series of lengths + hyperlinks | |||
at = until; | |||
for(int i=0; i<hyperlinks.length; i++) { | |||
int len = LittleEndian.getUShort(data, at); | |||
int first = LittleEndian.getUShort(data, at+2); | |||
if(first == 0) { | |||
// Crazy special case | |||
// Length is in bytes, from the start | |||
// Hyperlink appears to be empty | |||
hyperlinks[i] = ""; | |||
at += len; | |||
} else { | |||
// Normal case. Length is in characters | |||
hyperlinks[i] = StringUtil.getFromUnicodeLE(data, at+2, len); | |||
at += 2 + (2*len); | |||
} | |||
} | |||
} | |||
/** | |||
* Returns the number of hyperlinks, which should | |||
* either be zero, or the number of PLC bits | |||
*/ | |||
public int getNumberOfHyperlinks() { | |||
return hyperlinks.length; | |||
} | |||
/** | |||
* Returns the URL of the hyperlink at the | |||
* given index. | |||
* @param number The hyperlink number, zero based | |||
*/ | |||
public String getHyperlink(int number) { | |||
return hyperlinks[number]; | |||
} | |||
/** | |||
* Returns where in the text (in characters) the | |||
* hyperlink at the given index starts | |||
* applying to. | |||
* This position is relative to the text area that this | |||
* PLCBit applies to. | |||
* @param number The hyperlink number, zero based | |||
*/ | |||
public int getTextStartAt(int number) { | |||
return preData[1+number]; | |||
} | |||
/** | |||
* Returns where in the text that this block | |||
* of hyperlinks stops applying to. Normally, | |||
* but not always the end of the text. | |||
* This position is relative to the text area that this | |||
* PLCBit applies to. | |||
*/ | |||
public int getAllTextEndAt() { | |||
return preData[numberOfPLCs+1]; | |||
} | |||
} | |||
} |
@@ -134,4 +134,41 @@ public class TextPublisherTextExtractor extends TestCase { | |||
assertEquals(s2007, s2000); | |||
assertEquals(s2007, s98); | |||
} | |||
/** | |||
* Test that the hyperlink extraction stuff works as well | |||
* as we can hope it to. | |||
*/ | |||
public void testWithHyperlinks() throws Exception { | |||
File f = new File(dir, "LinkAt10.pub"); | |||
HPBFDocument doc = new HPBFDocument( | |||
new FileInputStream(f) | |||
); | |||
PublisherTextExtractor ext = | |||
new PublisherTextExtractor(doc); | |||
ext.getText(); | |||
// Default is no hyperlinks | |||
assertEquals("1234567890LINK\n", ext.getText()); | |||
// Turn on | |||
ext.setHyperlinksByDefault(true); | |||
assertEquals("1234567890LINK\n<http://poi.apache.org/>\n", ext.getText()); | |||
// Now a much more complex document | |||
f = new File(dir, "Sample.pub"); | |||
ext = new PublisherTextExtractor(new FileInputStream(f)); | |||
ext.setHyperlinksByDefault(true); | |||
String text = ext.getText(); | |||
assertTrue(text.endsWith( | |||
"<http://poi.apache.org/>\n" + | |||
"<C:\\Documents and Settings\\Nick\\My Documents\\Booleans.xlsx>\n" + | |||
"<>\n" + | |||
"<mailto:dev@poi.apache.org?subject=HPBF>\n" + | |||
"<mailto:dev@poi.apache.org?subject=HPBF>\n" | |||
)); | |||
} | |||
} |
@@ -21,6 +21,10 @@ import java.io.FileInputStream; | |||
import org.apache.poi.hpbf.HPBFDocument; | |||
import org.apache.poi.hpbf.model.qcbits.QCTextBit; | |||
import org.apache.poi.hpbf.model.qcbits.QCPLCBit.Type12; | |||
import org.apache.poi.hpbf.model.qcbits.QCPLCBit.Type0; | |||
import org.apache.poi.hpbf.model.qcbits.QCPLCBit.Type4; | |||
import org.apache.poi.hpbf.model.qcbits.QCPLCBit.Type8; | |||
import junit.framework.TestCase; | |||
@@ -77,4 +81,354 @@ public class TestQuillContents extends TestCase { | |||
assertTrue(t.startsWith("This is some text on the first page")); | |||
assertTrue(t.endsWith("Within doc to page 1\r")); | |||
} | |||
public void testPLC() throws Exception { | |||
File f = new File(dir, "Simple.pub"); | |||
HPBFDocument doc = new HPBFDocument( | |||
new FileInputStream(f) | |||
); | |||
QuillContents qc = doc.getQuillContents(); | |||
assertEquals(20, qc.getBits().length); | |||
assertTrue(qc.getBits()[9] instanceof Type4); | |||
assertTrue(qc.getBits()[10] instanceof Type4); | |||
assertTrue(qc.getBits()[12] instanceof Type8); | |||
Type4 plc9 = (Type4)qc.getBits()[9]; | |||
Type4 plc10 = (Type4)qc.getBits()[10]; | |||
Type8 plc12 = (Type8)qc.getBits()[12]; | |||
assertEquals(1, plc9.getNumberOfPLCs()); | |||
assertEquals(4, plc9.getPreData().length); | |||
assertEquals(1, plc9.getPlcValA().length); | |||
assertEquals(1, plc9.getPlcValB().length); | |||
assertEquals(0, plc9.getPreData()[0]); | |||
assertEquals(0, plc9.getPreData()[1]); | |||
assertEquals(0, plc9.getPreData()[2]); | |||
assertEquals(0, plc9.getPreData()[3]); | |||
assertEquals(0x356, plc9.getPlcValA()[0]); | |||
assertEquals(0x600, plc9.getPlcValB()[0]); | |||
assertEquals(1, plc10.getNumberOfPLCs()); | |||
assertEquals(4, plc10.getPreData().length); | |||
assertEquals(1, plc10.getPlcValA().length); | |||
assertEquals(1, plc10.getPlcValB().length); | |||
assertEquals(0, plc10.getPreData()[0]); | |||
assertEquals(0, plc10.getPreData()[1]); | |||
assertEquals(0, plc10.getPreData()[2]); | |||
assertEquals(0, plc10.getPreData()[3]); | |||
assertEquals(0x356, plc10.getPlcValA()[0]); | |||
assertEquals(0x800, plc10.getPlcValB()[0]); | |||
assertEquals(2, plc12.getNumberOfPLCs()); | |||
assertEquals(7, plc12.getPreData().length); | |||
assertEquals(2, plc12.getPlcValA().length); | |||
assertEquals(2, plc12.getPlcValB().length); | |||
assertEquals(0xff, plc12.getPreData()[0]); | |||
assertEquals(0, plc12.getPreData()[1]); | |||
assertEquals(0x3d, plc12.getPreData()[2]); | |||
assertEquals(0, plc12.getPreData()[3]); | |||
assertEquals(0x6e, plc12.getPreData()[4]); | |||
assertEquals(0, plc12.getPreData()[5]); | |||
assertEquals(0, plc12.getPreData()[6]); | |||
assertEquals(0xa0000, plc12.getPlcValA()[0]); | |||
assertEquals(0x22000000, plc12.getPlcValB()[0]); | |||
assertEquals(0x05, plc12.getPlcValA()[1]); | |||
assertEquals(0x04, plc12.getPlcValB()[1]); | |||
} | |||
public void testComplexPLC() throws Exception { | |||
File f = new File(dir, "Sample.pub"); | |||
HPBFDocument doc = new HPBFDocument( | |||
new FileInputStream(f) | |||
); | |||
QuillContents qc = doc.getQuillContents(); | |||
assertEquals(20, qc.getBits().length); | |||
assertTrue(qc.getBits()[10] instanceof Type4); | |||
assertTrue(qc.getBits()[11] instanceof Type4); | |||
assertTrue(qc.getBits()[13] instanceof Type0); | |||
assertTrue(qc.getBits()[14] instanceof Type12); | |||
assertTrue(qc.getBits()[15] instanceof Type12); | |||
assertTrue(qc.getBits()[16] instanceof Type8); | |||
Type4 plc10 = (Type4)qc.getBits()[10]; | |||
Type4 plc11 = (Type4)qc.getBits()[11]; | |||
Type0 plc13 = (Type0)qc.getBits()[13]; | |||
Type12 plc14 = (Type12)qc.getBits()[14]; | |||
Type12 plc15 = (Type12)qc.getBits()[15]; | |||
Type8 plc16 = (Type8)qc.getBits()[16]; | |||
assertEquals(1, plc10.getNumberOfPLCs()); | |||
assertEquals(4, plc10.getPreData().length); | |||
assertEquals(1, plc10.getPlcValA().length); | |||
assertEquals(1, plc10.getPlcValB().length); | |||
assertEquals(0, plc10.getPreData()[0]); | |||
assertEquals(0, plc10.getPreData()[1]); | |||
assertEquals(0, plc10.getPreData()[2]); | |||
assertEquals(0, plc10.getPreData()[3]); | |||
assertEquals(0x5d0, plc10.getPlcValA()[0]); | |||
assertEquals(0x800, plc10.getPlcValB()[0]); | |||
assertEquals(2, plc11.getNumberOfPLCs()); | |||
assertEquals(4, plc11.getPreData().length); | |||
assertEquals(2, plc11.getPlcValA().length); | |||
assertEquals(2, plc11.getPlcValB().length); | |||
assertEquals(0, plc11.getPreData()[0]); | |||
assertEquals(0, plc11.getPreData()[1]); | |||
assertEquals(0, plc11.getPreData()[2]); | |||
assertEquals(0, plc11.getPreData()[3]); | |||
assertEquals(0x53a, plc11.getPlcValA()[0]); | |||
assertEquals(0x5d0, plc11.getPlcValB()[0]); | |||
assertEquals(0xa00, plc11.getPlcValA()[1]); | |||
assertEquals(0xc00, plc11.getPlcValB()[1]); | |||
assertEquals(5, plc13.getNumberOfPLCs()); | |||
assertEquals(4, plc13.getPreData().length); | |||
assertEquals(5, plc13.getPlcValA().length); | |||
assertEquals(5, plc13.getPlcValB().length); | |||
assertEquals(0xff00, plc13.getPreData()[0]); | |||
assertEquals(0, plc13.getPreData()[1]); | |||
assertEquals(0xf, plc13.getPreData()[2]); | |||
assertEquals(0, plc13.getPreData()[3]); | |||
assertEquals(0x19, plc13.getPlcValA()[0]); | |||
assertEquals(0x00, plc13.getPlcValB()[0]); | |||
assertEquals(0x27, plc13.getPlcValA()[1]); | |||
assertEquals(0x00, plc13.getPlcValB()[1]); | |||
assertEquals(0x36, plc13.getPlcValA()[2]); | |||
assertEquals(0x00, plc13.getPlcValB()[2]); | |||
assertEquals(0x42, plc13.getPlcValA()[3]); | |||
assertEquals(0x00, plc13.getPlcValB()[3]); | |||
assertEquals(0x50, plc13.getPlcValA()[4]); | |||
assertEquals(0x00, plc13.getPlcValB()[4]); | |||
// TODO - test the type 12s | |||
assertEquals(6, plc16.getNumberOfPLCs()); | |||
assertEquals(7, plc16.getPreData().length); | |||
assertEquals(6, plc16.getPlcValA().length); | |||
assertEquals(6, plc16.getPlcValB().length); | |||
assertEquals(0xff, plc16.getPreData()[0]); | |||
assertEquals(0, plc16.getPreData()[1]); | |||
assertEquals(0x56, plc16.getPreData()[2]); | |||
assertEquals(0, plc16.getPreData()[3]); | |||
assertEquals(0x62, plc16.getPreData()[4]); | |||
assertEquals(0, plc16.getPreData()[5]); | |||
assertEquals(0x3e, plc16.getPreData()[6]); | |||
assertEquals(0x500000, plc16.getPlcValA()[0]); | |||
assertEquals(0x570000, plc16.getPlcValB()[0]); | |||
assertEquals(0x4b0000, plc16.getPlcValA()[1]); | |||
assertEquals(0x000000, plc16.getPlcValB()[1]); | |||
assertEquals(0x0a0000, plc16.getPlcValA()[2]); | |||
assertEquals(0x22000000, plc16.getPlcValB()[2]); | |||
assertEquals(0x000005, plc16.getPlcValA()[3]); | |||
assertEquals(0x000004, plc16.getPlcValB()[3]); | |||
assertEquals(0x000004, plc16.getPlcValA()[4]); | |||
assertEquals(0x000004, plc16.getPlcValB()[4]); | |||
assertEquals(0x000004, plc16.getPlcValA()[5]); | |||
assertEquals(0x000004, plc16.getPlcValB()[5]); | |||
} | |||
public void testNoHyperlinks() throws Exception { | |||
File f = new File(dir, "SampleNewsletter.pub"); | |||
HPBFDocument doc = new HPBFDocument( | |||
new FileInputStream(f) | |||
); | |||
QuillContents qc = doc.getQuillContents(); | |||
assertEquals(20, qc.getBits().length); | |||
Type12 plc18 = (Type12)qc.getBits()[18]; | |||
assertEquals(1, plc18.getNumberOfPLCs()); | |||
assertEquals(0, plc18.getNumberOfHyperlinks()); | |||
assertEquals(0, plc18.getTextStartAt(0)); | |||
assertEquals(601, plc18.getAllTextEndAt()); | |||
} | |||
public void testSimpleHyperlink() throws Exception { | |||
File f; | |||
HPBFDocument doc; | |||
QuillContents qc; | |||
Type12 hlBit; | |||
// Link at 10 | |||
f = new File(dir, "LinkAt10.pub"); | |||
doc = new HPBFDocument( | |||
new FileInputStream(f) | |||
); | |||
qc = doc.getQuillContents(); | |||
hlBit = (Type12)qc.getBits()[12]; | |||
assertEquals(1, hlBit.getNumberOfPLCs()); | |||
assertEquals(1, hlBit.getNumberOfHyperlinks()); | |||
assertEquals(10, hlBit.getTextStartAt(0)); | |||
assertEquals(15, hlBit.getAllTextEndAt()); | |||
assertEquals("http://poi.apache.org/", hlBit.getHyperlink(0)); | |||
// Longer link at 10 | |||
f = new File(dir, "LinkAt10Longer.pub"); | |||
doc = new HPBFDocument( | |||
new FileInputStream(f) | |||
); | |||
qc = doc.getQuillContents(); | |||
hlBit = (Type12)qc.getBits()[12]; | |||
assertEquals(1, hlBit.getNumberOfPLCs()); | |||
assertEquals(1, hlBit.getNumberOfHyperlinks()); | |||
assertEquals(10, hlBit.getTextStartAt(0)); | |||
assertEquals(15, hlBit.getAllTextEndAt()); | |||
assertEquals("http://poi.apache.org/hpbf/", hlBit.getHyperlink(0)); | |||
// Link at 20 | |||
f = new File(dir, "LinkAt20.pub"); | |||
doc = new HPBFDocument( | |||
new FileInputStream(f) | |||
); | |||
qc = doc.getQuillContents(); | |||
hlBit = (Type12)qc.getBits()[12]; | |||
assertEquals(1, hlBit.getNumberOfPLCs()); | |||
assertEquals(1, hlBit.getNumberOfHyperlinks()); | |||
assertEquals(20, hlBit.getTextStartAt(0)); | |||
assertEquals(25, hlBit.getAllTextEndAt()); | |||
assertEquals("http://poi.apache.org/", hlBit.getHyperlink(0)); | |||
} | |||
public void testManyHyperlinks() throws Exception { | |||
File f; | |||
HPBFDocument doc; | |||
QuillContents qc; | |||
Type12 hlBit; | |||
// Link at 10 | |||
f = new File(dir, "LinkAt10.pub"); | |||
doc = new HPBFDocument( | |||
new FileInputStream(f) | |||
); | |||
qc = doc.getQuillContents(); | |||
hlBit = (Type12)qc.getBits()[12]; | |||
assertEquals(1, hlBit.getNumberOfPLCs()); | |||
assertEquals(1, hlBit.getNumberOfHyperlinks()); | |||
assertEquals(10, hlBit.getTextStartAt(0)); | |||
assertEquals(15, hlBit.getAllTextEndAt()); | |||
assertEquals("http://poi.apache.org/", hlBit.getHyperlink(0)); | |||
} | |||
public void testHyperlinkDifferentVersions() throws Exception { | |||
File f; | |||
HPBFDocument doc; | |||
QuillContents qc; | |||
Type12 hlBitA; | |||
Type12 hlBitB; | |||
// Latest version | |||
f = new File(dir, "Sample.pub"); | |||
doc = new HPBFDocument( | |||
new FileInputStream(f) | |||
); | |||
qc = doc.getQuillContents(); | |||
hlBitA = (Type12)qc.getBits()[14]; | |||
assertEquals(2, hlBitA.getNumberOfPLCs()); | |||
assertEquals(2, hlBitA.getNumberOfHyperlinks()); | |||
assertEquals(25, hlBitA.getTextStartAt(0)); | |||
assertEquals(72, hlBitA.getTextStartAt(1)); | |||
assertEquals(87, hlBitA.getAllTextEndAt()); | |||
assertEquals("http://poi.apache.org/", hlBitA.getHyperlink(0)); | |||
assertEquals("C:\\Documents and Settings\\Nick\\My Documents\\Booleans.xlsx", hlBitA.getHyperlink(1)); | |||
hlBitB = (Type12)qc.getBits()[15]; | |||
assertEquals(3, hlBitB.getNumberOfPLCs()); | |||
assertEquals(3, hlBitB.getNumberOfHyperlinks()); | |||
assertEquals(27, hlBitB.getTextStartAt(0)); | |||
assertEquals(37, hlBitB.getTextStartAt(1)); | |||
assertEquals(54, hlBitB.getTextStartAt(2)); | |||
assertEquals(75, hlBitB.getAllTextEndAt()); | |||
assertEquals("", hlBitB.getHyperlink(0)); | |||
assertEquals("mailto:dev@poi.apache.org?subject=HPBF", hlBitB.getHyperlink(1)); | |||
assertEquals("mailto:dev@poi.apache.org?subject=HPBF", hlBitB.getHyperlink(2)); | |||
// 2000 version | |||
f = new File(dir, "Sample2000.pub"); | |||
doc = new HPBFDocument( | |||
new FileInputStream(f) | |||
); | |||
qc = doc.getQuillContents(); | |||
hlBitA = (Type12)qc.getBits()[13]; | |||
assertEquals(2, hlBitA.getNumberOfPLCs()); | |||
assertEquals(2, hlBitA.getNumberOfHyperlinks()); | |||
assertEquals(25, hlBitA.getTextStartAt(0)); | |||
assertEquals(72, hlBitA.getTextStartAt(1)); | |||
assertEquals(87, hlBitA.getAllTextEndAt()); | |||
assertEquals("http://poi.apache.org/", hlBitA.getHyperlink(0)); | |||
assertEquals("C:\\Documents and Settings\\Nick\\My Documents\\Booleans.xlsx", hlBitA.getHyperlink(1)); | |||
hlBitB = (Type12)qc.getBits()[14]; | |||
assertEquals(3, hlBitB.getNumberOfPLCs()); | |||
assertEquals(3, hlBitB.getNumberOfHyperlinks()); | |||
assertEquals(27, hlBitB.getTextStartAt(0)); | |||
assertEquals(37, hlBitB.getTextStartAt(1)); | |||
assertEquals(54, hlBitB.getTextStartAt(2)); | |||
assertEquals(75, hlBitB.getAllTextEndAt()); | |||
assertEquals("", hlBitB.getHyperlink(0)); | |||
assertEquals("mailto:dev@poi.apache.org?subject=HPBF", hlBitB.getHyperlink(1)); | |||
assertEquals("mailto:dev@poi.apache.org?subject=HPBF", hlBitB.getHyperlink(2)); | |||
// 98 version | |||
f = new File(dir, "Sample98.pub"); | |||
doc = new HPBFDocument( | |||
new FileInputStream(f) | |||
); | |||
qc = doc.getQuillContents(); | |||
hlBitA = (Type12)qc.getBits()[13]; | |||
assertEquals(2, hlBitA.getNumberOfPLCs()); | |||
assertEquals(2, hlBitA.getNumberOfHyperlinks()); | |||
assertEquals(25, hlBitA.getTextStartAt(0)); | |||
assertEquals(72, hlBitA.getTextStartAt(1)); | |||
assertEquals(87, hlBitA.getAllTextEndAt()); | |||
assertEquals("http://poi.apache.org/", hlBitA.getHyperlink(0)); | |||
assertEquals("C:\\Documents and Settings\\Nick\\My Documents\\Booleans.xlsx", hlBitA.getHyperlink(1)); | |||
hlBitB = (Type12)qc.getBits()[14]; | |||
assertEquals(3, hlBitB.getNumberOfPLCs()); | |||
assertEquals(3, hlBitB.getNumberOfHyperlinks()); | |||
assertEquals(27, hlBitB.getTextStartAt(0)); | |||
assertEquals(37, hlBitB.getTextStartAt(1)); | |||
assertEquals(54, hlBitB.getTextStartAt(2)); | |||
assertEquals(75, hlBitB.getAllTextEndAt()); | |||
assertEquals("", hlBitB.getHyperlink(0)); | |||
assertEquals("mailto:dev@poi.apache.org?subject=HPBF", hlBitB.getHyperlink(1)); | |||
assertEquals("mailto:dev@poi.apache.org?subject=HPBF", hlBitB.getHyperlink(2)); | |||
} | |||
} |
@@ -17,7 +17,6 @@ | |||
package org.apache.poi.hssf.model; | |||
import java.io.ByteArrayInputStream; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
@@ -25,8 +24,6 @@ import junit.framework.AssertionFailedError; | |||
import junit.framework.TestCase; | |||
import org.apache.poi.hssf.HSSFTestDataSamples; | |||
import org.apache.poi.hssf.eventmodel.ERFListener; | |||
import org.apache.poi.hssf.eventmodel.EventRecordFactory; | |||
import org.apache.poi.hssf.record.BOFRecord; | |||
import org.apache.poi.hssf.record.BlankRecord; | |||
import org.apache.poi.hssf.record.CellValueRecordInterface; | |||
@@ -46,6 +43,7 @@ import org.apache.poi.hssf.record.aggregates.ColumnInfoRecordsAggregate; | |||
import org.apache.poi.hssf.record.aggregates.MergedCellsTable; | |||
import org.apache.poi.hssf.record.aggregates.PageSettingsBlock; | |||
import org.apache.poi.hssf.record.aggregates.RowRecordsAggregate; | |||
import org.apache.poi.hssf.record.aggregates.RecordAggregate.RecordVisitor; | |||
import org.apache.poi.hssf.usermodel.HSSFCell; | |||
import org.apache.poi.hssf.usermodel.HSSFRow; | |||
import org.apache.poi.hssf.usermodel.HSSFSheet; | |||
@@ -88,15 +86,16 @@ public final class TestSheet extends TestCase { | |||
return result; | |||
} | |||
private static final class MergedCellListener implements ERFListener { | |||
private static final class MergedCellListener implements RecordVisitor { | |||
private int _count; | |||
public MergedCellListener() { | |||
_count = 0; | |||
} | |||
public boolean processRecord(Record rec) { | |||
_count++; | |||
return true; | |||
public void visitRecord(Record r) { | |||
if (r instanceof MergeCellsRecord) { | |||
_count++; | |||
} | |||
} | |||
public int getCount() { | |||
return _count; | |||
@@ -118,12 +117,8 @@ public final class TestSheet extends TestCase { | |||
assertTrue(sheet.getNumMergedRegions() == regionsToAdd); | |||
//test that the regions were spread out over the appropriate number of records | |||
byte[] sheetData = new byte[sheet.getSize()]; | |||
sheet.serialize(0, sheetData); | |||
MergedCellListener mcListener = new MergedCellListener(); | |||
EventRecordFactory erf = new EventRecordFactory(mcListener, new short[] { MergeCellsRecord.sid, }); | |||
// POIFSFileSystem poifs = new POIFSFileSystem(new ByteArrayInputStream(sheetData)); | |||
erf.processRecords(new ByteArrayInputStream(sheetData)); | |||
sheet.visitContainedRecords(mcListener, 0); | |||
int recordsAdded = mcListener.getCount(); | |||
int recordsExpected = regionsToAdd/1027; | |||
if ((regionsToAdd % 1027) != 0) | |||
@@ -416,6 +411,27 @@ public final class TestSheet extends TestCase { | |||
assertEquals(DEFAULT_IDX, xfindex); | |||
} | |||
private static final class SizeCheckingRecordVisitor implements RecordVisitor { | |||
private int _totalSize; | |||
public SizeCheckingRecordVisitor() { | |||
_totalSize = 0; | |||
} | |||
public void visitRecord(Record r) { | |||
int estimatedSize=r.getRecordSize(); | |||
byte[] buf = new byte[estimatedSize]; | |||
int serializedSize = r.serialize(0, buf); | |||
if (estimatedSize != serializedSize) { | |||
throw new AssertionFailedError("serialized size mismatch for record (" | |||
+ r.getClass().getName() + ")"); | |||
} | |||
_totalSize += estimatedSize; | |||
} | |||
public int getTotalSize() { | |||
return _totalSize; | |||
} | |||
} | |||
/** | |||
* Prior to bug 45066, POI would get the estimated sheet size wrong | |||
* when an <tt>UncalcedRecord</tt> was present.<p/> | |||
@@ -429,13 +445,13 @@ public final class TestSheet extends TestCase { | |||
records.add(createWindow2Record()); | |||
records.add(EOFRecord.instance); | |||
Sheet sheet = Sheet.createSheet(records, 0, 0); | |||
int estimatedSize = sheet.getSize(); | |||
int serializedSize = sheet.serialize(0, new byte[estimatedSize]); | |||
if (serializedSize != estimatedSize) { | |||
throw new AssertionFailedError("Identified bug 45066 b"); | |||
} | |||
assertEquals(90, serializedSize); | |||
// The original bug was due to different logic for collecting records for sizing and | |||
// serialization. The code has since been refactored into a single method for visiting | |||
// all contained records. Now this test is much less interesting | |||
SizeCheckingRecordVisitor scrv = new SizeCheckingRecordVisitor(); | |||
sheet.visitContainedRecords(scrv, 0); | |||
assertEquals(90, scrv.getTotalSize()); | |||
} | |||
/** | |||
@@ -479,31 +495,31 @@ public final class TestSheet extends TestCase { | |||
* That value is found on the IndexRecord. | |||
*/ | |||
private static int getDbCellRecordPos(Sheet sheet) { | |||
int size = sheet.getSize(); | |||
byte[] data = new byte[size]; | |||
sheet.serialize(0, data); | |||
MyIndexRecordListener myIndexListener = new MyIndexRecordListener(); | |||
EventRecordFactory erf = new EventRecordFactory(myIndexListener, new short[] { IndexRecord.sid, }); | |||
erf.processRecords(new ByteArrayInputStream(data)); | |||
sheet.visitContainedRecords(myIndexListener, 0); | |||
IndexRecord indexRecord = myIndexListener.getIndexRecord(); | |||
int dbCellRecordPos = indexRecord.getDbcellAt(0); | |||
return dbCellRecordPos; | |||
} | |||
private static final class MyIndexRecordListener implements ERFListener { | |||
private static final class MyIndexRecordListener implements RecordVisitor { | |||
private IndexRecord _indexRecord; | |||
public MyIndexRecordListener() { | |||
// no-arg constructor | |||
} | |||
public boolean processRecord(Record rec) { | |||
_indexRecord = (IndexRecord)rec; | |||
return true; | |||
} | |||
public IndexRecord getIndexRecord() { | |||
return _indexRecord; | |||
} | |||
public void visitRecord(Record r) { | |||
if (r instanceof IndexRecord) { | |||
if (_indexRecord != null) { | |||
throw new RuntimeException("too many index records"); | |||
} | |||
_indexRecord = (IndexRecord)r; | |||
} | |||
} | |||
} | |||
/** | |||
@@ -541,5 +557,23 @@ public final class TestSheet extends TestCase { | |||
} | |||
assertEquals("Informations", cell.getRichStringCellValue().getString()); | |||
} | |||
/** | |||
* In 3.1, setting margins between creating first row and first cell caused an exception. | |||
*/ | |||
public void testSetMargins_bug45717() { | |||
HSSFWorkbook workbook = new HSSFWorkbook(); | |||
HSSFSheet sheet = workbook.createSheet("Vorschauliste"); | |||
HSSFRow row = sheet.createRow(0); | |||
sheet.setMargin(HSSFSheet.LeftMargin, 0.3); | |||
try { | |||
row.createCell((short) 0); | |||
} catch (IllegalStateException e) { | |||
if (e.getMessage().equals("Cannot create value records before row records exist")) { | |||
throw new AssertionFailedError("Identified bug 45717"); | |||
} | |||
throw e; | |||
} | |||
} | |||
} | |||
@@ -15,13 +15,10 @@ | |||
limitations under the License. | |||
==================================================================== */ | |||
/* | |||
* TestFormulaRecordAggregate.java | |||
* | |||
* Created on March 21, 2003, 12:32 AM | |||
*/ | |||
package org.apache.poi.hssf.record.aggregates; | |||
import junit.framework.TestCase; | |||
import org.apache.poi.hssf.record.FormulaRecord; | |||
import org.apache.poi.hssf.record.StringRecord; | |||
@@ -29,14 +26,13 @@ import org.apache.poi.hssf.record.StringRecord; | |||
* | |||
* @author avik | |||
*/ | |||
public final class TestFormulaRecordAggregate extends junit.framework.TestCase { | |||
public final class TestFormulaRecordAggregate extends TestCase { | |||
public void testBasic() throws Exception { | |||
FormulaRecord f = new FormulaRecord(); | |||
StringRecord s = new StringRecord(); | |||
s.setString("abc"); | |||
FormulaRecordAggregate fagg = new FormulaRecordAggregate(f); | |||
fagg.setStringRecord(s); | |||
FormulaRecordAggregate fagg = new FormulaRecordAggregate(f, s, SharedValueManager.EMPTY); | |||
assertEquals("abc", fagg.getStringValue()); | |||
} | |||
} |
@@ -17,25 +17,100 @@ | |||
package org.apache.poi.hssf.record.aggregates; | |||
import java.io.File; | |||
import java.io.FileOutputStream; | |||
import java.io.IOException; | |||
import java.io.OutputStream; | |||
import junit.framework.AssertionFailedError; | |||
import junit.framework.TestCase; | |||
import org.apache.poi.hssf.HSSFTestDataSamples; | |||
import org.apache.poi.hssf.record.ArrayRecord; | |||
import org.apache.poi.hssf.record.FormulaRecord; | |||
import org.apache.poi.hssf.record.Record; | |||
import org.apache.poi.hssf.record.RowRecord; | |||
import org.apache.poi.hssf.record.SharedFormulaRecord; | |||
import org.apache.poi.hssf.record.SharedValueRecordBase; | |||
import org.apache.poi.hssf.record.TableRecord; | |||
import org.apache.poi.hssf.usermodel.HSSFWorkbook; | |||
import org.apache.poi.hssf.usermodel.RecordInspector; | |||
import org.apache.poi.hssf.util.CellRangeAddress8Bit; | |||
/** | |||
* | |||
*/ | |||
public final class TestRowRecordsAggregate extends TestCase { | |||
public void testRowGet() { | |||
RowRecordsAggregate rra = new RowRecordsAggregate(); | |||
RowRecord rr = new RowRecord(4); | |||
rra.insertRow(rr); | |||
rra.insertRow(new RowRecord(1)); | |||
RowRecord rr1 = rra.getRow(4); | |||
assertNotNull(rr1); | |||
assertEquals("Row number is 1", 4, rr1.getRowNumber()); | |||
assertTrue("Row record retrieved is identical ", rr1 == rr); | |||
} | |||
public void testRowGet() { | |||
RowRecordsAggregate rra = new RowRecordsAggregate(); | |||
RowRecord rr = new RowRecord(4); | |||
rra.insertRow(rr); | |||
rra.insertRow(new RowRecord(1)); | |||
RowRecord rr1 = rra.getRow(4); | |||
assertNotNull(rr1); | |||
assertEquals("Row number is 1", 4, rr1.getRowNumber()); | |||
assertTrue("Row record retrieved is identical ", rr1 == rr); | |||
} | |||
/** | |||
* Prior to Aug 2008, POI would re-serialize spreadsheets with {@link ArrayRecord}s or | |||
* {@link TableRecord}s with those records out of order. Similar to | |||
* {@link SharedFormulaRecord}s, these records should appear immediately after the first | |||
* {@link FormulaRecord}s that they apply to (and only once).<br/> | |||
*/ | |||
public void testArraysAndTables() { | |||
HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook("testArraysAndTables.xls"); | |||
Record[] sheetRecs = RecordInspector.getRecords(wb.getSheetAt(0), 0); | |||
int countArrayFormulas = verifySharedValues(sheetRecs, ArrayRecord.class); | |||
assertEquals(5, countArrayFormulas); | |||
int countTableFormulas = verifySharedValues(sheetRecs, TableRecord.class); | |||
assertEquals(3, countTableFormulas); | |||
// Note - SharedFormulaRecords are currently not re-serialized by POI (each is extracted | |||
// into many non-shared formulas), but if they ever were, the same rules would apply. | |||
int countSharedFormulas = verifySharedValues(sheetRecs, SharedFormulaRecord.class); | |||
assertEquals(0, countSharedFormulas); | |||
if (false) { // set true to observe re-serialized file | |||
File f = new File(System.getProperty("java.io.tmpdir") + "/testArraysAndTables-out.xls"); | |||
try { | |||
OutputStream os = new FileOutputStream(f); | |||
wb.write(os); | |||
os.close(); | |||
} catch (IOException e) { | |||
throw new RuntimeException(e); | |||
} | |||
System.out.println("Output file to " + f.getAbsolutePath()); | |||
} | |||
} | |||
private static int verifySharedValues(Record[] recs, Class shfClass) { | |||
int result =0; | |||
for(int i=0; i<recs.length; i++) { | |||
Record rec = recs[i]; | |||
if (rec.getClass() == shfClass) { | |||
result++; | |||
Record prevRec = recs[i-1]; | |||
if (!(prevRec instanceof FormulaRecord)) { | |||
throw new AssertionFailedError("Bad record order at index " | |||
+ i + ": Formula record expected but got (" | |||
+ prevRec.getClass().getName() + ")"); | |||
} | |||
verifySharedFormula((FormulaRecord) prevRec, rec); | |||
} | |||
} | |||
return result; | |||
} | |||
private static void verifySharedFormula(FormulaRecord firstFormula, Record rec) { | |||
CellRangeAddress8Bit range = ((SharedValueRecordBase)rec).getRange(); | |||
assertEquals(range.getFirstRow(), firstFormula.getRow()); | |||
assertEquals(range.getFirstColumn(), firstFormula.getColumn()); | |||
} | |||
} |
@@ -28,10 +28,15 @@ import junit.framework.AssertionFailedError; | |||
import junit.framework.TestCase; | |||
import org.apache.poi.hssf.HSSFTestDataSamples; | |||
import org.apache.poi.hssf.model.RecordStream; | |||
import org.apache.poi.hssf.model.RowBlocksReader; | |||
import org.apache.poi.hssf.record.BlankRecord; | |||
import org.apache.poi.hssf.record.CellValueRecordInterface; | |||
import org.apache.poi.hssf.record.FormulaRecord; | |||
import org.apache.poi.hssf.record.Record; | |||
import org.apache.poi.hssf.record.RecordBase; | |||
import org.apache.poi.hssf.record.SharedFormulaRecord; | |||
import org.apache.poi.hssf.record.WindowTwoRecord; | |||
import org.apache.poi.hssf.usermodel.HSSFSheet; | |||
import org.apache.poi.hssf.usermodel.HSSFWorkbook; | |||
@@ -47,6 +52,7 @@ public final class TestValueRecordsAggregate extends TestCase { | |||
List records = new ArrayList(); | |||
records.add( new FormulaRecord() ); | |||
records.add( new SharedFormulaRecord() ); | |||
records.add(new WindowTwoRecord()); | |||
constructValueRecord(records); | |||
Iterator iterator = valueRecord.getIterator(); | |||
@@ -59,8 +65,13 @@ public final class TestValueRecordsAggregate extends TestCase { | |||
} | |||
private void constructValueRecord(List records) { | |||
SharedFormulaHolder sfrh = SharedFormulaHolder.create(records, 0, records.size()); | |||
valueRecord.construct(records, 0, records.size(), sfrh ); | |||
RowBlocksReader rbr = new RowBlocksReader(records, 0); | |||
SharedValueManager sfrh = rbr.getSharedFormulaManager(); | |||
RecordStream rs = rbr.getPlainRecordStream(); | |||
while(rs.hasNext()) { | |||
Record rec = rs.getNext(); | |||
valueRecord.construct((CellValueRecordInterface)rec, rs, sfrh); | |||
} | |||
} | |||
private static List testData() { | |||
@@ -73,6 +84,7 @@ public final class TestValueRecordsAggregate extends TestCase { | |||
blankRecord.setColumn( (short) 2 ); | |||
records.add( formulaRecord ); | |||
records.add( blankRecord ); | |||
records.add(new WindowTwoRecord()); | |||
return records; | |||
} | |||
@@ -0,0 +1,67 @@ | |||
/* ==================================================================== | |||
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; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
import org.apache.poi.hssf.record.Record; | |||
import org.apache.poi.hssf.record.aggregates.RecordAggregate.RecordVisitor; | |||
/** | |||
* Test utility class to get {@link Record}s out HSSF objects | |||
* | |||
* @author Josh Micich | |||
*/ | |||
public final class RecordInspector { | |||
private RecordInspector() { | |||
// no instances of this class | |||
} | |||
private static final class RecordCollector implements RecordVisitor { | |||
private List _list; | |||
public RecordCollector() { | |||
_list = new ArrayList(128); | |||
} | |||
public void visitRecord(Record r) { | |||
_list.add(r); | |||
} | |||
public Record[] getRecords() { | |||
Record[] result = new Record[_list.size()]; | |||
_list.toArray(result); | |||
return result; | |||
} | |||
} | |||
/** | |||
* @param streamOffset start position for serialization. This affects values in some | |||
* records such as INDEX, but most callers will be OK to pass zero. | |||
* @return the {@link Record}s (in order) which will be output when the | |||
* specified sheet is serialized | |||
*/ | |||
public static Record[] getRecords(HSSFSheet hSheet, int streamOffset) { | |||
RecordCollector rc = new RecordCollector(); | |||
hSheet.getSheet().visitContainedRecords(rc, streamOffset); | |||
return rc.getRecords(); | |||
} | |||
} |
@@ -17,23 +17,19 @@ | |||
package org.apache.poi.hssf.usermodel; | |||
import java.io.ByteArrayInputStream; | |||
import java.io.File; | |||
import java.io.FileInputStream; | |||
import java.io.FileOutputStream; | |||
import java.io.IOException; | |||
import java.util.Iterator; | |||
import junit.framework.TestCase; | |||
import org.apache.poi.hssf.HSSFTestDataSamples; | |||
import org.apache.poi.hssf.eventmodel.ERFListener; | |||
import org.apache.poi.hssf.eventmodel.EventRecordFactory; | |||
import org.apache.poi.hssf.model.Workbook; | |||
import org.apache.poi.hssf.record.BackupRecord; | |||
import org.apache.poi.hssf.record.LabelSSTRecord; | |||
import org.apache.poi.hssf.record.Record; | |||
import org.apache.poi.hssf.record.aggregates.RowRecordsAggregate; | |||
import org.apache.poi.hssf.record.aggregates.RecordAggregate.RecordVisitor; | |||
import org.apache.poi.poifs.filesystem.POIFSFileSystem; | |||
import org.apache.poi.ss.util.CellRangeAddress; | |||
import org.apache.poi.ss.util.Region; | |||
@@ -73,10 +69,7 @@ public final class TestWorkbook extends TestCase { | |||
* HSSFSheet last row or first row is incorrect. <P> | |||
* | |||
*/ | |||
public void testWriteSheetSimple() | |||
throws IOException | |||
{ | |||
public void testWriteSheetSimple() throws IOException { | |||
File file = TempFile.createTempFile("testWriteSheetSimple", | |||
".xls"); | |||
FileOutputStream out = new FileOutputStream(file); | |||
@@ -85,13 +78,10 @@ public final class TestWorkbook extends TestCase { | |||
HSSFRow r = null; | |||
HSSFCell c = null; | |||
for (short rownum = ( short ) 0; rownum < 100; rownum++) | |||
{ | |||
for (int rownum = 0; rownum < 100; rownum++) { | |||
r = s.createRow(rownum); | |||
// r.setRowNum(( short ) rownum); | |||
for (short cellnum = ( short ) 0; cellnum < 50; cellnum += 2) | |||
{ | |||
for (int cellnum = 0; cellnum < 50; cellnum += 2) { | |||
c = r.createCell(cellnum); | |||
c.setCellValue(rownum * 10000 + cellnum | |||
+ ((( double ) rownum / 1000) | |||
@@ -105,8 +95,6 @@ public final class TestWorkbook extends TestCase { | |||
sanityChecker.checkHSSFWorkbook(wb); | |||
assertEquals("LAST ROW == 99", 99, s.getLastRowNum()); | |||
assertEquals("FIRST ROW == 0", 0, s.getFirstRowNum()); | |||
// assert((s.getLastRowNum() == 99)); | |||
} | |||
/** | |||
@@ -131,13 +119,10 @@ public final class TestWorkbook extends TestCase { | |||
HSSFRow r = null; | |||
HSSFCell c = null; | |||
for (short rownum = ( short ) 0; rownum < 100; rownum++) | |||
{ | |||
for (int rownum = 0; rownum < 100; rownum++) { | |||
r = s.createRow(rownum); | |||
// r.setRowNum(( short ) rownum); | |||
for (short cellnum = ( short ) 0; cellnum < 50; cellnum += 2) | |||
{ | |||
for (int cellnum = 0; cellnum < 50; cellnum += 2) { | |||
c = r.createCell(cellnum); | |||
c.setCellValue(rownum * 10000 + cellnum | |||
+ ((( double ) rownum / 1000) | |||
@@ -146,13 +131,11 @@ public final class TestWorkbook extends TestCase { | |||
c.setCellValue(new HSSFRichTextString("TEST")); | |||
} | |||
} | |||
for (short rownum = ( short ) 0; rownum < 25; rownum++) | |||
{ | |||
for (int rownum = 0; rownum < 25; rownum++) { | |||
r = s.getRow(rownum); | |||
s.removeRow(r); | |||
} | |||
for (short rownum = ( short ) 75; rownum < 100; rownum++) | |||
{ | |||
for (int rownum = 75; rownum < 100; rownum++) { | |||
r = s.getRow(rownum); | |||
s.removeRow(r); | |||
} | |||
@@ -429,12 +412,10 @@ public final class TestWorkbook extends TestCase { | |||
HSSFWorkbook wb = new HSSFWorkbook(); | |||
HSSFSheet s = wb.createSheet(); | |||
for (short rownum = ( short ) 0; rownum < 100; rownum++) | |||
{ | |||
for (int rownum = 0; rownum < 100; rownum++) { | |||
HSSFRow r = s.createRow(rownum); | |||
for (short cellnum = ( short ) 0; cellnum < 50; cellnum += 2) | |||
{ | |||
for (int cellnum = 0; cellnum < 50; cellnum += 2) { | |||
HSSFCell c = r.createCell(cellnum); | |||
c.setCellValue(rownum * 10000 + cellnum | |||
+ ((( double ) rownum / 1000) | |||
@@ -466,13 +447,10 @@ public final class TestWorkbook extends TestCase { | |||
/** | |||
* Test the backup field gets set as expected. | |||
*/ | |||
public void testBackupRecord() | |||
throws Exception | |||
{ | |||
HSSFWorkbook wb = new HSSFWorkbook(); | |||
wb.createSheet(); | |||
Workbook workbook = wb.getWorkbook(); | |||
public void testBackupRecord() { | |||
HSSFWorkbook wb = new HSSFWorkbook(); | |||
wb.createSheet(); | |||
Workbook workbook = wb.getWorkbook(); | |||
BackupRecord record = workbook.getBackupRecord(); | |||
assertEquals(0, record.getBackup()); | |||
@@ -480,7 +458,7 @@ public final class TestWorkbook extends TestCase { | |||
assertEquals(1, record.getBackup()); | |||
} | |||
private static final class RecordCounter implements ERFListener { | |||
private static final class RecordCounter implements RecordVisitor { | |||
private int _count; | |||
public RecordCounter() { | |||
@@ -489,9 +467,10 @@ public final class TestWorkbook extends TestCase { | |||
public int getCount() { | |||
return _count; | |||
} | |||
public boolean processRecord(Record rec) { | |||
_count++; | |||
return true; | |||
public void visitRecord(Record r) { | |||
if (r instanceof LabelSSTRecord) { | |||
_count++; | |||
} | |||
} | |||
} | |||
@@ -500,9 +479,7 @@ public final class TestWorkbook extends TestCase { | |||
* | |||
* We need to make sure only one LabelSSTRecord is produced. | |||
*/ | |||
public void testRepeatingBug() | |||
throws Exception | |||
{ | |||
public void testRepeatingBug() { | |||
HSSFWorkbook workbook = new HSSFWorkbook(); | |||
HSSFSheet sheet = workbook.createSheet("Design Variants"); | |||
HSSFRow row = sheet.createRow(2); | |||
@@ -511,12 +488,8 @@ public final class TestWorkbook extends TestCase { | |||
cell.setCellValue(new HSSFRichTextString("Class")); | |||
cell = row.createCell(2); | |||
byte[] data = new byte[sheet.getSheet().getSize()]; | |||
sheet.getSheet().serialize(0, data); | |||
RecordCounter rc = new RecordCounter(); | |||
EventRecordFactory erf = new EventRecordFactory(rc, new short[] { LabelSSTRecord.sid, }); | |||
erf.processRecords(new ByteArrayInputStream(data)); | |||
sheet.getSheet().visitContainedRecords(rc, 0); | |||
assertEquals(1, rc.getCount()); | |||
} | |||