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
<action dev="POI-DEVELOPERS" type="add">Created a common interface for handling Excel files, irrespective of if they are .xls or .xlsx</action> | <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> | ||||
<release version="3.1.1-alpha1" date="2008-??-??"> | <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">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 hssf.usermodel.HeaderFooter when stripping fields out</action> | ||||
<action dev="POI-DEVELOPERS" type="fix">Avoid NPE in EscherBSERecord on older escher records</action> | <action dev="POI-DEVELOPERS" type="fix">Avoid NPE in EscherBSERecord on older escher records</action> |
<action dev="POI-DEVELOPERS" type="add">Created a common interface for handling Excel files, irrespective of if they are .xls or .xlsx</action> | <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> | ||||
<release version="3.1.1-alpha1" date="2008-??-??"> | <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">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 hssf.usermodel.HeaderFooter when stripping fields out</action> | ||||
<action dev="POI-DEVELOPERS" type="fix">Avoid NPE in EscherBSERecord on older escher records</action> | <action dev="POI-DEVELOPERS" type="fix">Avoid NPE in EscherBSERecord on older escher records</action> |
/* ==================================================================== | |||||
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); | |||||
} | |||||
} |
import org.apache.poi.hssf.record.CalcModeRecord; | import org.apache.poi.hssf.record.CalcModeRecord; | ||||
import org.apache.poi.hssf.record.CellValueRecordInterface; | import org.apache.poi.hssf.record.CellValueRecordInterface; | ||||
import org.apache.poi.hssf.record.ColumnInfoRecord; | 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.DVALRecord; | ||||
import org.apache.poi.hssf.record.DefaultColWidthRecord; | import org.apache.poi.hssf.record.DefaultColWidthRecord; | ||||
import org.apache.poi.hssf.record.DefaultRowHeightRecord; | import org.apache.poi.hssf.record.DefaultRowHeightRecord; | ||||
import org.apache.poi.hssf.record.SaveRecalcRecord; | import org.apache.poi.hssf.record.SaveRecalcRecord; | ||||
import org.apache.poi.hssf.record.ScenarioProtectRecord; | import org.apache.poi.hssf.record.ScenarioProtectRecord; | ||||
import org.apache.poi.hssf.record.SelectionRecord; | 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.UncalcedRecord; | ||||
import org.apache.poi.hssf.record.WSBoolRecord; | import org.apache.poi.hssf.record.WSBoolRecord; | ||||
import org.apache.poi.hssf.record.WindowTwoRecord; | import org.apache.poi.hssf.record.WindowTwoRecord; | ||||
import org.apache.poi.hssf.record.aggregates.ColumnInfoRecordsAggregate; | import org.apache.poi.hssf.record.aggregates.ColumnInfoRecordsAggregate; | ||||
import org.apache.poi.hssf.record.aggregates.ConditionalFormattingTable; | import org.apache.poi.hssf.record.aggregates.ConditionalFormattingTable; | ||||
import org.apache.poi.hssf.record.aggregates.DataValidityTable; | 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.MergedCellsTable; | ||||
import org.apache.poi.hssf.record.aggregates.PageSettingsBlock; | import org.apache.poi.hssf.record.aggregates.PageSettingsBlock; | ||||
import org.apache.poi.hssf.record.aggregates.RecordAggregate; | import org.apache.poi.hssf.record.aggregates.RecordAggregate; | ||||
import org.apache.poi.hssf.record.aggregates.RowRecordsAggregate; | 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.record.aggregates.RecordAggregate.RecordVisitor; | ||||
import org.apache.poi.hssf.util.CellRangeAddress; | import org.apache.poi.hssf.util.CellRangeAddress; | ||||
import org.apache.poi.hssf.util.PaneInformation; | import org.apache.poi.hssf.util.PaneInformation; | ||||
private static POILogger log = POILogFactory.getLogger(Sheet.class); | private static POILogger log = POILogFactory.getLogger(Sheet.class); | ||||
protected ArrayList records = null; | protected ArrayList records = null; | ||||
int preoffset = 0; // offset of the sheet in a new file | |||||
protected int dimsloc = -1; // TODO - remove dimsloc | protected int dimsloc = -1; // TODO - remove dimsloc | ||||
protected PrintGridlinesRecord printGridlines = null; | protected PrintGridlinesRecord printGridlines = null; | ||||
protected GridsetRecord gridset = null; | protected GridsetRecord gridset = null; | ||||
* @see #createSheet(List,int,int) | * @see #createSheet(List,int,int) | ||||
*/ | */ | ||||
public Sheet() { | public Sheet() { | ||||
_mergedCellsTable = new MergedCellsTable(); | |||||
_mergedCellsTable = new MergedCellsTable(); | |||||
} | } | ||||
/** | /** | ||||
for (int k = offset; k < inRecs.size(); k++) { | for (int k = offset; k < inRecs.size(); k++) { | ||||
Record rec = ( Record ) inRecs.get(k); | Record rec = ( Record ) inRecs.get(k); | ||||
if ( rec.getSid() == DBCellRecord.sid ) { | |||||
continue; | |||||
} | |||||
if ( rec.getSid() == IndexRecord.sid ) { | if ( rec.getSid() == IndexRecord.sid ) { | ||||
// ignore INDEX record because it is only needed by Excel, | // ignore INDEX record because it is only needed by Excel, | ||||
// and POI always re-calculates its contents | // and POI always re-calculates its contents | ||||
continue; | continue; | ||||
} | } | ||||
if ( rec.getSid() == StringRecord.sid ) { | |||||
continue; | |||||
} | |||||
if ( rec.getSid() == CFHeaderRecord.sid ) { | if ( rec.getSid() == CFHeaderRecord.sid ) { | ||||
RecordStream rs = new RecordStream(inRecs, k); | RecordStream rs = new RecordStream(inRecs, k); | ||||
retval.condFormatting = new ConditionalFormattingTable(rs); | retval.condFormatting = new ConditionalFormattingTable(rs); | ||||
if (retval._rowsAggregate != null) { | if (retval._rowsAggregate != null) { | ||||
throw new RuntimeException("row/cell records found in the wrong place"); | 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 | records.add(retval._rowsAggregate); //only add the aggregate once | ||||
k = lastRowCellRec -1; | |||||
k += rbr.getTotalNumberOfRecords() - 1; | |||||
continue; | continue; | ||||
} | } | ||||
} | } | ||||
if (rec.getSid() == MergeCellsRecord.sid) { | 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); | RecordStream rs = new RecordStream(inRecs, k); | ||||
retval._mergedCellsTable.read(rs); | retval._mergedCellsTable.read(rs); | ||||
k += rs.getCountRead()-1; | k += rs.getCountRead()-1; | ||||
continue; | 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) | if (rec.getSid() == BOFRecord.sid) | ||||
{ | { | ||||
bofEofNestingLevel++; | bofEofNestingLevel++; | ||||
break; | break; | ||||
} | } | ||||
} | } | ||||
else if (rec.getSid() == UncalcedRecord.sid) { | |||||
retval._isUncalced = true; | |||||
} | |||||
else if (rec.getSid() == DimensionsRecord.sid) | else if (rec.getSid() == DimensionsRecord.sid) | ||||
{ | { | ||||
// Make a columns aggregate if one hasn't ready been created. | // Make a columns aggregate if one hasn't ready been created. | ||||
return retval; | 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 static final class RecordCloner implements RecordVisitor { | ||||
private final List _destList; | private final List _destList; | ||||
log.log(POILogger.DEBUG, "Sheet.setDimensions exiting"); | 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; | boolean haveSerializedIndex = false; | ||||
for (int k = 0; k < records.size(); k++) | for (int k = 0; k < records.size(); k++) | ||||
{ | { | ||||
RecordBase record = (RecordBase) records.get(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 the BOF record was just serialized then add the IndexRecord | ||||
if (record instanceof BOFRecord) { | if (record instanceof BOFRecord) { | ||||
if (!haveSerializedIndex) { | if (!haveSerializedIndex) { | ||||
// If there are diagrams, they have their own BOFRecords, | // If there are diagrams, they have their own BOFRecords, | ||||
// and one shouldn't go in after that! | // and one shouldn't go in after that! | ||||
if (_isUncalced) { | 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 | //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. | //remove this guard. So be safe it is left here. | ||||
if (_rowsAggregate != null) { | 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) | // start just after BOF record (INDEX is not present in this list) | ||||
for (int j = bofRecordIndex + 1; j < records.size(); j++) { | 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) { | if (tmpRec instanceof RowRecordsAggregate) { | ||||
break; | break; | ||||
} | } | ||||
sizeOfInitialSheetRecords += tmpRec.getRecordSize(); | |||||
result += tmpRec.getRecordSize(); | |||||
} | } | ||||
if (_isUncalced) { | 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) | * Create a row record. (does not add it to the records contained in this sheet) | ||||
*/ | */ | ||||
} | } | ||||
} | } | ||||
/** | |||||
* @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() | public List getRecords() | ||||
{ | { | ||||
return records; | return records; | ||||
} | } | ||||
return _dataValidityTable; | return _dataValidityTable; | ||||
} | } | ||||
public FormulaRecordAggregate createFormula(int row, int col) { | |||||
return _rowsAggregate.createFormula(row, col); | |||||
} | |||||
} | } |
package org.apache.poi.hssf.record; | package org.apache.poi.hssf.record; | ||||
import org.apache.poi.hssf.record.formula.Ptg; | 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.HexDump; | ||||
import org.apache.poi.util.LittleEndian; | import org.apache.poi.util.LittleEndian; | ||||
* | * | ||||
* @author Josh Micich | * @author Josh Micich | ||||
*/ | */ | ||||
public final class ArrayRecord extends Record { | |||||
public final class ArrayRecord extends SharedValueRecordBase { | |||||
public final static short sid = 0x0221; | public final static short sid = 0x0221; | ||||
private static final int OPT_ALWAYS_RECALCULATE = 0x0001; | private static final int OPT_ALWAYS_RECALCULATE = 0x0001; | ||||
private static final int OPT_CALCULATE_ON_OPEN = 0x0002; | private static final int OPT_CALCULATE_ON_OPEN = 0x0002; | ||||
private CellRangeAddress8Bit _range; | |||||
private int _options; | private int _options; | ||||
private int _field3notUsed; | private int _field3notUsed; | ||||
public ArrayRecord(RecordInputStream in) { | public ArrayRecord(RecordInputStream in) { | ||||
super(in); | super(in); | ||||
_options = in.readUShort(); | |||||
_field3notUsed = in.readInt(); | |||||
int formulaLen = in.readUShort(); | |||||
_formulaTokens = Ptg.readTokens(formulaLen, in); | |||||
} | } | ||||
public boolean isAlwaysRecalculate() { | public boolean isAlwaysRecalculate() { | ||||
return (_options & OPT_CALCULATE_ON_OPEN) != 0; | 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); | LittleEndian.putUShort(data, pos, _options); | ||||
pos+=2; | pos+=2; | ||||
LittleEndian.putInt(data, pos, _field3notUsed); | LittleEndian.putInt(data, pos, _field3notUsed); | ||||
LittleEndian.putUShort(data, pos, tokenSize); | LittleEndian.putUShort(data, pos, tokenSize); | ||||
pos+=2; | pos+=2; | ||||
Ptg.serializePtgs(_formulaTokens, data, pos); | 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() { | public short getSid() { | ||||
public String toString() { | public String toString() { | ||||
StringBuffer sb = new StringBuffer(); | StringBuffer sb = new StringBuffer(); | ||||
sb.append(getClass().getName()).append(" [ARRAY]\n"); | 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(" options=").append(HexDump.shortToHex(_options)).append("\n"); | ||||
sb.append(" notUsed=").append(HexDump.intToHex(_field3notUsed)).append("\n"); | sb.append(" notUsed=").append(HexDump.intToHex(_field3notUsed)).append("\n"); | ||||
sb.append(" formula:").append("\n"); | sb.append(" formula:").append("\n"); | ||||
for (int i = 0; i < _formulaTokens.length; i++) { | 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("]"); | sb.append("]"); | ||||
return sb.toString(); | return sb.toString(); |
package org.apache.poi.hssf.record; | 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.hssf.record.formula.Ptg; | ||||
import org.apache.poi.util.BitField; | import org.apache.poi.util.BitField; | ||||
import org.apache.poi.util.BitFieldFactory; | import org.apache.poi.util.BitFieldFactory; | ||||
import org.apache.poi.util.LittleEndian; | 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> | * 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 Andrew C. Oliver (acoliver at apache dot org) | ||||
* @author Jason Height (jheight at chariot dot net dot au) | * @author Jason Height (jheight at chariot dot net dot au) | ||||
for (int k = 0; k < field_8_parsed_expr.length; k++ ) { | for (int k = 0; k < field_8_parsed_expr.length; k++ ) { | ||||
sb.append(" Ptg[").append(k).append("]="); | 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"); | sb.append("[/FORMULA]\n"); | ||||
return sb.toString(); | return sb.toString(); |
* @author Csaba Nagy (ncsaba at yahoo dot com) | * @author Csaba Nagy (ncsaba at yahoo dot com) | ||||
*/ | */ | ||||
public final class RecordFactory { | 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, }; | private static final Class[] CONSTRUCTOR_ARGS = { RecordInputStream.class, }; | ||||
* Note - this most but not *every* subclass of Record. | * Note - this most but not *every* subclass of Record. | ||||
*/ | */ | ||||
private static final Class[] records = { | private static final Class[] records = { | ||||
ArrayRecord.class, | |||||
BackupRecord.class, | BackupRecord.class, | ||||
BlankRecord.class, | BlankRecord.class, | ||||
BOFRecord.class, | BOFRecord.class, |
import org.apache.poi.hssf.record.formula.Ptg; | import org.apache.poi.hssf.record.formula.Ptg; | ||||
import org.apache.poi.hssf.record.formula.RefNPtg; | import org.apache.poi.hssf.record.formula.RefNPtg; | ||||
import org.apache.poi.hssf.record.formula.RefPtg; | import org.apache.poi.hssf.record.formula.RefPtg; | ||||
import org.apache.poi.hssf.util.CellRangeAddress8Bit; | |||||
import org.apache.poi.util.HexDump; | import org.apache.poi.util.HexDump; | ||||
/** | /** | ||||
* record types. | * record types. | ||||
* @author Danny Mui at apache dot org | * @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; | public final static short sid = 0x04BC; | ||||
private CellRangeAddress8Bit _range; | |||||
private int field_5_reserved; | private int field_5_reserved; | ||||
private Ptg[] field_7_parsed_expr; | private Ptg[] field_7_parsed_expr; | ||||
public SharedFormulaRecord() { | 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 | * @param in the RecordInputstream to read the record from | ||||
*/ | */ | ||||
public SharedFormulaRecord(RecordInputStream in) { | 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. | //Because this record is converted to individual Formula records, this method is not required. | ||||
throw new UnsupportedOperationException("Cannot serialize a SharedFormulaRecord"); | 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. | //Because this record is converted to individual Formula records, this method is not required. | ||||
throw new UnsupportedOperationException("Cannot get the size for a SharedFormulaRecord"); | throw new UnsupportedOperationException("Cannot get the size for a SharedFormulaRecord"); | ||||
StringBuffer buffer = new StringBuffer(); | StringBuffer buffer = new StringBuffer(); | ||||
buffer.append("[SHARED FORMULA (").append(HexDump.intToHex(sid)).append("]\n"); | 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"); | buffer.append(" .reserved = ").append(HexDump.shortToHex(field_5_reserved)).append("\n"); | ||||
for (int k = 0; k < field_7_parsed_expr.length; k++ ) { | for (int k = 0; k < field_7_parsed_expr.length; k++ ) { | ||||
buffer.append("Formula[").append(k).append("]"); | 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"); | buffer.append("[/SHARED FORMULA]\n"); | ||||
return sid; | 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 | * Creates a non shared formula from the shared formula | ||||
* counter part | * counter part | ||||
areaNPtg.isFirstColRelative(), | areaNPtg.isFirstColRelative(), | ||||
areaNPtg.isLastColRelative()); | areaNPtg.isLastColRelative()); | ||||
} else { | } 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()) { | if (!ptg.isBaseToken()) { | ||||
ptg.setClass(originalOperandClass); | ptg.setClass(originalOperandClass); | ||||
* counter part | * counter part | ||||
*/ | */ | ||||
public void convertSharedFormulaRecord(FormulaRecord formula) { | 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"); | 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); | Ptg[] ptgs = convertSharedFormulas(field_7_parsed_expr, formulaRow, formulaColumn); | ||||
formula.setParsedExpression(ptgs); | formula.setParsedExpression(ptgs); | ||||
return row; | 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() { | public Object clone() { | ||||
//Because this record is converted to individual Formula records, this method is not required. | //Because this record is converted to individual Formula records, this method is not required. | ||||
throw new UnsupportedOperationException("Cannot clone a SharedFormulaRecord"); | throw new UnsupportedOperationException("Cannot clone a SharedFormulaRecord"); |
/* ==================================================================== | |||||
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; | |||||
} | |||||
} |
* | * | ||||
* See p536 of the June 08 binary docs | * 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; | public static final short sid = 0x0236; | ||||
private static final BitField alwaysCalc = BitFieldFactory.getInstance(0x0001); | 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 rowOrColInpCell = BitFieldFactory.getInstance(0x0004); | ||||
private static final BitField oneOrTwoVar = BitFieldFactory.getInstance(0x0008); | private static final BitField oneOrTwoVar = BitFieldFactory.getInstance(0x0008); | ||||
private static final BitField rowDeleted = BitFieldFactory.getInstance(0x0010); | private static final BitField rowDeleted = BitFieldFactory.getInstance(0x0010); | ||||
private static final BitField colDeleted = BitFieldFactory.getInstance(0x0020); | 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_5_flags; | ||||
private int field_6_res; | private int field_6_res; | ||||
private int field_9_rowInputCol; | private int field_9_rowInputCol; | ||||
private int field_10_colInputCol; | 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_5_flags = in.readByte(); | ||||
field_6_res = in.readByte(); | field_6_res = in.readByte(); | ||||
field_7_rowInputRow = in.readShort(); | field_7_rowInputRow = in.readShort(); | ||||
field_10_colInputCol = in.readShort(); | field_10_colInputCol = in.readShort(); | ||||
} | } | ||||
public TableRecord(RecordInputStream in) { | |||||
super(in); | |||||
} | |||||
public TableRecord(CellRangeAddress8Bit range) { | public TableRecord(CellRangeAddress8Bit range) { | ||||
_range = range; | |||||
super(range); | |||||
field_6_res = 0; | field_6_res = 0; | ||||
} | } | ||||
public CellRangeAddress8Bit getRange() { | |||||
return _range; | |||||
} | |||||
public int getFlags() { | public int getFlags() { | ||||
return field_5_flags; | return field_5_flags; | ||||
} | } | ||||
public short getSid() { | public short getSid() { | ||||
return sid; | 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() { | public String toString() { | ||||
StringBuffer buffer = new StringBuffer(); | StringBuffer buffer = new StringBuffer(); | ||||
buffer.append("[TABLE]\n"); | 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(" .flags = ") .append(HexDump.byteToHex(field_5_flags)).append("\n"); | ||||
buffer.append(" .alwaysClc= ").append(isAlwaysCalc()).append("\n"); | buffer.append(" .alwaysClc= ").append(isAlwaysCalc()).append("\n"); | ||||
buffer.append(" .reserved = ").append(HexDump.intToHex(field_6_res)).append("\n"); | buffer.append(" .reserved = ").append(HexDump.intToHex(field_6_res)).append("\n"); |
package org.apache.poi.hssf.record.aggregates; | 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.CellValueRecordInterface; | ||||
import org.apache.poi.hssf.record.FormulaRecord; | 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.StringRecord; | ||||
import org.apache.poi.hssf.record.TableRecord; | |||||
/** | /** | ||||
* The formula record aggregate is used to join together the formula record and it's | * The formula record aggregate is used to join together the formula record and it's | ||||
public final class FormulaRecordAggregate extends RecordAggregate implements CellValueRecordInterface { | public final class FormulaRecordAggregate extends RecordAggregate implements CellValueRecordInterface { | ||||
private final FormulaRecord _formulaRecord; | private final FormulaRecord _formulaRecord; | ||||
private SharedValueManager _sharedValueManager; | |||||
/** caches the calculated result of the formula */ | /** caches the calculated result of the formula */ | ||||
private StringRecord _stringRecord; | 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) { | public void setStringRecord(StringRecord stringRecord) { | ||||
_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() { | public FormulaRecord getFormulaRecord() { | ||||
public void visitContainedRecords(RecordVisitor rv) { | public void visitContainedRecords(RecordVisitor rv) { | ||||
rv.visitRecord(_formulaRecord); | rv.visitRecord(_formulaRecord); | ||||
Record sharedFormulaRecord = _sharedValueManager.getRecordForFirstCell(_formulaRecord); | |||||
if (sharedFormulaRecord != null) { | |||||
rv.visitRecord(sharedFormulaRecord); | |||||
} | |||||
if (_stringRecord != null) { | if (_stringRecord != null) { | ||||
rv.visitRecord(_stringRecord); | rv.visitRecord(_stringRecord); | ||||
} | } | ||||
if (_tableRecord != null) { | |||||
rv.visitRecord(_tableRecord); | |||||
} | |||||
} | } | ||||
public String getStringValue() { | public String getStringValue() { |
rv.visitRecord(new MergeCellsRecord(cras, startIx, nLeftoverMergedRegions)); | 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(); | int nRegions = mcr.getNumAreas(); | ||||
for (int i = 0; i < nRegions; i++) { | for (int i = 0; i < nRegions; i++) { | ||||
_mergedRegions.add(mcr.getAreaAt(i)); | _mergedRegions.add(mcr.getAreaAt(i)); | ||||
public int getNumberOfMergedRegions() { | public int getNumberOfMergedRegions() { | ||||
return _mergedRegions.size(); | return _mergedRegions.size(); | ||||
} | } | ||||
} | } |
protected final void fillFields(RecordInputStream in) { | protected final void fillFields(RecordInputStream in) { | ||||
throw new RuntimeException("Should not be called"); | throw new RuntimeException("Should not be called"); | ||||
} | } | ||||
public final short getSid() { | |||||
public final short getSid() { | |||||
throw new RuntimeException("Should not be called"); | 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 abstract void visitContainedRecords(RecordVisitor rv); | ||||
public final int serialize(int offset, byte[] data) { | public final int serialize(int offset, byte[] data) { | ||||
_totalSize += r.getRecordSize(); | _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; | |||||
} | |||||
} | |||||
} | } |
import java.util.Map; | import java.util.Map; | ||||
import java.util.TreeMap; | 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.CellValueRecordInterface; | ||||
import org.apache.poi.hssf.record.DBCellRecord; | 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.IndexRecord; | ||||
import org.apache.poi.hssf.record.MergeCellsRecord; | import org.apache.poi.hssf.record.MergeCellsRecord; | ||||
import org.apache.poi.hssf.record.Record; | import org.apache.poi.hssf.record.Record; | ||||
import org.apache.poi.hssf.record.RowRecord; | 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; | import org.apache.poi.hssf.record.UnknownRecord; | ||||
/** | /** | ||||
private final Map _rowRecords; | private final Map _rowRecords; | ||||
private final ValueRecordsAggregate _valuesAgg; | private final ValueRecordsAggregate _valuesAgg; | ||||
private final List _unknownRecords; | private final List _unknownRecords; | ||||
private final SharedValueManager _sharedValueManager; | |||||
/** Creates a new instance of ValueRecordsAggregate */ | /** Creates a new instance of ValueRecordsAggregate */ | ||||
public RowRecordsAggregate() { | 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(); | _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()) { | 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: | case RowRecord.sid: | ||||
insertRow((RowRecord) rec); | insertRow((RowRecord) rec); | ||||
continue; | continue; | ||||
case DBCellRecord.sid: | case DBCellRecord.sid: | ||||
// end of 'Row Block'. Should only occur after cell records | // end of 'Row Block'. Should only occur after cell records | ||||
// ignore DBCELL records because POI generates them upon re-serialization | |||||
continue; | continue; | ||||
} | } | ||||
if (rec instanceof UnknownRecord) { | if (rec instanceof UnknownRecord) { | ||||
if (!rec.isValue()) { | if (!rec.isValue()) { | ||||
throw new RuntimeException("Unexpected record type (" + rec.getClass().getName() + ")"); | 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 | * Handles UnknownRecords which appear within the row/cell records | ||||
// 0x01C2 // several | // 0x01C2 // several | ||||
// 0x0034 // few | // 0x0034 // few | ||||
// No documentation could be found for these | // No documentation could be found for these | ||||
// keep the unknown records for re-serialization | // keep the unknown records for re-serialization | ||||
_unknownRecords.add(rec); | _unknownRecords.add(rec); | ||||
} | } | ||||
{ | { | ||||
return _lastrow; | return _lastrow; | ||||
} | } | ||||
/** Returns the number of row blocks. | /** Returns the number of row blocks. | ||||
* <p/>The row blocks are goupings of rows that contain the DBCell record | * <p/>The row blocks are goupings of rows that contain the DBCell record | ||||
* after them | * after them | ||||
} | } | ||||
return row.getRowNumber(); | return row.getRowNumber(); | ||||
} | } | ||||
private int visitRowRecordsForBlock(int blockIndex, RecordVisitor rv) { | private int visitRowRecordsForBlock(int blockIndex, RecordVisitor rv) { | ||||
final int startIndex = blockIndex*DBCellRecord.BLOCK_SIZE; | final int startIndex = blockIndex*DBCellRecord.BLOCK_SIZE; | ||||
final int endIndex = startIndex + DBCellRecord.BLOCK_SIZE; | final int endIndex = startIndex + DBCellRecord.BLOCK_SIZE; | ||||
rv.visitRecord(rec); | rv.visitRecord(rec); | ||||
} | } | ||||
return result; | return result; | ||||
} | |||||
} | |||||
public void visitContainedRecords(RecordVisitor rv) { | public void visitContainedRecords(RecordVisitor rv) { | ||||
ValueRecordsAggregate cells = _valuesAgg; | |||||
PositionTrackingVisitor stv = new PositionTrackingVisitor(rv, 0); | |||||
//DBCells are serialized before row records. | //DBCells are serialized before row records. | ||||
final int blockCount = getRowBlockCount(); | final int blockCount = getRowBlockCount(); | ||||
for (int blockIndex = 0; blockIndex < blockCount; blockIndex++) { | for (int blockIndex = 0; blockIndex < blockCount; blockIndex++) { | ||||
// Note: Cell references start from the second row... | // Note: Cell references start from the second row... | ||||
int cellRefOffset = (rowBlockSize - RowRecord.ENCODED_SIZE); | int cellRefOffset = (rowBlockSize - RowRecord.ENCODED_SIZE); | ||||
for (int row = startRowNumber; row <= endRowNumber; row++) { | 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; | pos += rowCellSize; | ||||
// Add the offset to the first cell for the row into the | // Add the offset to the first cell for the row into the | ||||
// DBCellRecord. | // DBCellRecord. | ||||
public Iterator getIterator() { | public Iterator getIterator() { | ||||
return _rowRecords.values().iterator(); | return _rowRecords.values().iterator(); | ||||
} | } | ||||
public Iterator getAllRecordsIterator() { | public Iterator getAllRecordsIterator() { | ||||
List result = new ArrayList(_rowRecords.size() * 2); | List result = new ArrayList(_rowRecords.size() * 2); | ||||
result.addAll(_rowRecords.values()); | result.addAll(_rowRecords.values()); | ||||
public void removeCell(CellValueRecordInterface cvRec) { | public void removeCell(CellValueRecordInterface cvRec) { | ||||
_valuesAgg.removeCell(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); | |||||
} | |||||
} | } | ||||
/* ==================================================================== | |||||
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. | |||||
} | |||||
} |
/* ==================================================================== | |||||
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; | |||||
} | |||||
} |
import org.apache.poi.hssf.model.RecordStream; | import org.apache.poi.hssf.model.RecordStream; | ||||
import org.apache.poi.hssf.record.CellValueRecordInterface; | 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.FormulaRecord; | ||||
import org.apache.poi.hssf.record.MergeCellsRecord; | |||||
import org.apache.poi.hssf.record.Record; | import org.apache.poi.hssf.record.Record; | ||||
import org.apache.poi.hssf.record.RecordBase; | 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.StringRecord; | ||||
import org.apache.poi.hssf.record.TableRecord; | |||||
import org.apache.poi.hssf.record.UnknownRecord; | |||||
import org.apache.poi.hssf.record.aggregates.RecordAggregate.RecordVisitor; | import org.apache.poi.hssf.record.aggregates.RecordAggregate.RecordVisitor; | ||||
/** | /** | ||||
} | } | ||||
/** | /** | ||||
* 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 | /** Tallies a count of the size of the cell records | ||||
return pos - offset; | return pos - offset; | ||||
} | } | ||||
public int visitCellsForRow(int rowIndex, RecordVisitor rv) { | |||||
int result = 0; | |||||
public void visitCellsForRow(int rowIndex, RecordVisitor rv) { | |||||
CellValueRecordInterface[] cellRecs = records[rowIndex]; | CellValueRecordInterface[] cellRecs = records[rowIndex]; | ||||
if (cellRecs != null) { | if (cellRecs != null) { | ||||
for (int i = 0; i < cellRecs.length; i++) { | for (int i = 0; i < cellRecs.length; i++) { | ||||
if (cvr == null) { | if (cvr == null) { | ||||
continue; | 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 { | } else { | ||||
Record rec = (Record) cvr; | Record rec = (Record) cvr; | ||||
rv.visitRecord(rec); | rv.visitRecord(rec); | ||||
result += rec.getRecordSize(); | |||||
} | } | ||||
} | } | ||||
} | } | ||||
return result; | |||||
} | } | ||||
public CellValueRecordInterface[] getValueRecords() { | public CellValueRecordInterface[] getValueRecords() { |
{ | { | ||||
case CELL_TYPE_FORMULA : | 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) | if (setValue) | ||||
{ | { | ||||
frec.getFormulaRecord().setValue(getNumericCellValue()); | frec.getFormulaRecord().setValue(getNumericCellValue()); | ||||
} | } | ||||
frec.setXFIndex(styleIndex); | frec.setXFIndex(styleIndex); | ||||
frec.setRow(row); | |||||
record = frec; | record = frec; | ||||
break; | break; | ||||
import org.apache.poi.hssf.record.SSTRecord; | import org.apache.poi.hssf.record.SSTRecord; | ||||
import org.apache.poi.hssf.record.UnicodeString; | import org.apache.poi.hssf.record.UnicodeString; | ||||
import org.apache.poi.hssf.record.UnknownRecord; | 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.Area3DPtg; | ||||
import org.apache.poi.hssf.record.formula.MemFuncPtg; | import org.apache.poi.hssf.record.formula.MemFuncPtg; | ||||
import org.apache.poi.hssf.record.formula.NameXPtg; | import org.apache.poi.hssf.record.formula.NameXPtg; | ||||
*/ | */ | ||||
private ArrayList names; | private ArrayList names; | ||||
/** | /** | ||||
* this holds the HSSFFont objects attached to this workbook. | * this holds the HSSFFont objects attached to this workbook. | ||||
* We only create these from the low level records as required. | * We only create these from the low level records as required. | ||||
* someplace else. | * someplace else. | ||||
*/ | */ | ||||
private HSSFDataFormat formatter; | private HSSFDataFormat formatter; | ||||
/** | /** | ||||
* The policy to apply in the event of missing or | * The policy to apply in the event of missing or | ||||
* blank cells when fetching from a row. | * blank cells when fetching from a row. | ||||
/** | /** | ||||
* Sets the policy on what to do when | * Sets the policy on what to do when | ||||
* getting missing or blank cells from a row. | * 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 HSSFRow.getCell()}. See | ||||
* {@link MissingCellPolicy} | * {@link MissingCellPolicy} | ||||
*/ | */ | ||||
private void validateSheetIndex(int index) { | private void validateSheetIndex(int index) { | ||||
int lastSheetIx = _sheets.size() - 1; | int lastSheetIx = _sheets.size() - 1; | ||||
if (index < 0 || index > lastSheetIx) { | if (index < 0 || index > lastSheetIx) { | ||||
throw new IllegalArgumentException("Sheet index (" | |||||
throw new IllegalArgumentException("Sheet index (" | |||||
+ index +") is out of range (0.." + lastSheetIx + ")"); | + index +") is out of range (0.." + lastSheetIx + ")"); | ||||
} | } | ||||
} | } | ||||
/** | /** | ||||
* Selects a single sheet. This may be different to | * 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) { | public void setSelectedTab(int index) { | ||||
validateSheetIndex(index); | validateSheetIndex(index); | ||||
int nSheets = _sheets.size(); | int nSheets = _sheets.size(); | ||||
for (int i=0; i<nSheets; i++) { | for (int i=0; i<nSheets; i++) { | ||||
setSelectedTab((int)index); | setSelectedTab((int)index); | ||||
} | } | ||||
public void setSelectedTabs(int[] indexes) { | public void setSelectedTabs(int[] indexes) { | ||||
for (int i = 0; i < indexes.length; i++) { | for (int i = 0; i < indexes.length; i++) { | ||||
validateSheetIndex(indexes[i]); | validateSheetIndex(indexes[i]); | ||||
} | } | ||||
bSelect = true; | bSelect = true; | ||||
break; | break; | ||||
} | } | ||||
} | } | ||||
getSheetAt(i).setSelected(bSelect); | getSheetAt(i).setSelected(bSelect); | ||||
} | } | ||||
* 'Selected' sheet(s) is a distinct concept. | * 'Selected' sheet(s) is a distinct concept. | ||||
*/ | */ | ||||
public void setActiveSheet(int index) { | public void setActiveSheet(int index) { | ||||
validateSheetIndex(index); | validateSheetIndex(index); | ||||
int nSheets = _sheets.size(); | int nSheets = _sheets.size(); | ||||
for (int i=0; i<nSheets; i++) { | for (int i=0; i<nSheets; i++) { | ||||
} | } | ||||
/** | /** | ||||
* deprecated May 2008 | * deprecated May 2008 | ||||
* @deprecated - Misleading name - use getActiveSheetIndex() | |||||
* @deprecated - Misleading name - use getActiveSheetIndex() | |||||
*/ | */ | ||||
public short getSelectedTab() { | public short getSelectedTab() { | ||||
return (short) getActiveSheetIndex(); | return (short) getActiveSheetIndex(); | ||||
} | } | ||||
/** | /** | ||||
* sets the first tab that is displayed in the list of tabs | * sets the first tab that is displayed in the list of tabs | ||||
* in excel. | * in excel. | ||||
} | } | ||||
/** | /** | ||||
* deprecated May 2008 | * deprecated May 2008 | ||||
* @deprecated - Misleading name - use setFirstVisibleTab() | |||||
* @deprecated - Misleading name - use setFirstVisibleTab() | |||||
*/ | */ | ||||
public void setDisplayedTab(short index) { | public void setDisplayedTab(short index) { | ||||
setFirstVisibleTab(index); | setFirstVisibleTab(index); | ||||
} | } | ||||
/** | /** | ||||
* deprecated May 2008 | * deprecated May 2008 | ||||
* @deprecated - Misleading name - use getFirstVisibleTab() | |||||
* @deprecated - Misleading name - use getFirstVisibleTab() | |||||
*/ | */ | ||||
public short getDisplayedTab() { | public short getDisplayedTab() { | ||||
return (short) getFirstVisibleTab(); | return (short) getFirstVisibleTab(); | ||||
/** | /** | ||||
* create an HSSFSheet for this HSSFWorkbook, adds it to the sheets and | * create an HSSFSheet for this HSSFWorkbook, adds it to the sheets and | ||||
* returns the high level representation. Use this to create new sheets. | * returns the high level representation. Use this to create new sheets. | ||||
* | |||||
* | |||||
* @param sheetname | * @param sheetname | ||||
* sheetname to set for the sheet. | * sheetname to set for the sheet. | ||||
* @return HSSFSheet representing the new sheet. | * @return HSSFSheet representing the new sheet. | ||||
/** | /** | ||||
* Removes sheet at the given index.<p/> | * 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/> | * sheet. (For example when printing - see Bug 40414).<br/> | ||||
* | |||||
* | |||||
* This method makes sure that if the removed sheet was active, another sheet will become | * 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 | * 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. | * one less if the removed sheet was the last in the workbook. | ||||
* | |||||
* | |||||
* @param index of the sheet (0-based) | * @param index of the sheet (0-based) | ||||
*/ | */ | ||||
public void removeSheetAt(int index) { | public void removeSheetAt(int index) { | ||||
if(fontindex == Short.MAX_VALUE){ | if(fontindex == Short.MAX_VALUE){ | ||||
throw new IllegalArgumentException("Maximum number of fonts was exceeded"); | throw new IllegalArgumentException("Maximum number of fonts was exceeded"); | ||||
} | } | ||||
// Ask getFontAt() to build it for us, | // Ask getFontAt() to build it for us, | ||||
// so it gets properly cached | // so it gets properly cached | ||||
return getFontAt(fontindex); | return getFontAt(fontindex); | ||||
for (short i=0; i<=getNumberOfFonts(); i++) { | for (short i=0; i<=getNumberOfFonts(); i++) { | ||||
// Remember - there is no 4! | // Remember - there is no 4! | ||||
if(i == 4) continue; | if(i == 4) continue; | ||||
HSSFFont hssfFont = getFontAt(i); | HSSFFont hssfFont = getFontAt(i); | ||||
if (hssfFont.getBoldweight() == boldWeight | if (hssfFont.getBoldweight() == boldWeight | ||||
&& hssfFont.getColor() == color | && hssfFont.getColor() == color | ||||
return retval; | return retval; | ||||
} | } | ||||
/** | /** | ||||
* Reset the fonts cache, causing all new calls | * Reset the fonts cache, causing all new calls | ||||
* to getFontAt() to create new objects. | * to getFontAt() to create new objects. | ||||
//poifs.writeFilesystem(stream); | //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. | * Method getBytes - get the bytes of just the HSSF portions of the XLS file. | ||||
* Use this to construct a POI POIFSFileSystem yourself. | * Use this to construct a POI POIFSFileSystem yourself. | ||||
* @see org.apache.poi.hssf.model.Workbook | * @see org.apache.poi.hssf.model.Workbook | ||||
* @see org.apache.poi.hssf.model.Sheet | * @see org.apache.poi.hssf.model.Sheet | ||||
*/ | */ | ||||
public byte[] getBytes() | |||||
{ | |||||
public byte[] getBytes() { | |||||
if (log.check( POILogger.DEBUG )) { | if (log.check( POILogger.DEBUG )) { | ||||
log.log(DEBUG, "HSSFWorkbook.getBytes()"); | log.log(DEBUG, "HSSFWorkbook.getBytes()"); | ||||
} | } | ||||
HSSFSheet[] sheets = getSheets(); | HSSFSheet[] sheets = getSheets(); | ||||
int nSheets = sheets.length; | int nSheets = sheets.length; | ||||
int totalsize = workbook.getSize(); | int totalsize = workbook.getSize(); | ||||
// pre-calculate all the sheet sizes and set BOF indexes | // 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++) { | for (int k = 0; k < nSheets; k++) { | ||||
workbook.setSheetBof(k, totalsize); | 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]; | byte[] retval = new byte[totalsize]; | ||||
int pos = workbook.serialize(0, retval); | int pos = workbook.serialize(0, retval); | ||||
for (int k = 0; k < nSheets; k++) { | 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. | // 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 | // 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 + ")"); | + ") for sheet (" + k + ")"); | ||||
// TODO - add similar sanity check to ensure that Sheet.serializeIndexRecord() does not write mis-aligned offsets either | // TODO - add similar sanity check to ensure that Sheet.serializeIndexRecord() does not write mis-aligned offsets either | ||||
} | } | ||||
} | } | ||||
/** | /** | ||||
* 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 | * 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); | |||||
} | |||||
} | } |
throw new ArrayIndexOutOfBoundsException("Illegal offset"); | throw new ArrayIndexOutOfBoundsException("Illegal offset"); | ||||
} | } | ||||
if ((len < 0) || (((string.length - offset) / 2) < len)) { | if ((len < 0) || (((string.length - offset) / 2) < len)) { | ||||
throw new IllegalArgumentException("Illegal length"); | |||||
throw new IllegalArgumentException("Illegal length " + len); | |||||
} | } | ||||
try { | try { |
import java.io.IOException; | import java.io.IOException; | ||||
import java.io.InputStream; | 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.HPBFDocument; | ||||
import org.apache.poi.hpbf.model.QuillContents; | import org.apache.poi.hpbf.model.QuillContents; | ||||
import org.apache.poi.hpbf.model.qcbits.QCBit; | 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.poifs.filesystem.POIFSFileSystem; | ||||
import org.apache.poi.util.HexDump; | 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 | * For dumping out the PLC contents of QC Bits of a | ||||
private HPBFDocument doc; | private HPBFDocument doc; | ||||
private QuillContents qc; | private QuillContents qc; | ||||
public PLCDumper(HPBFDocument doc) { | |||||
this.doc = doc; | |||||
public PLCDumper(HPBFDocument hpbfDoc) { | |||||
doc = hpbfDoc; | |||||
qc = doc.getQuillContents(); | qc = doc.getQuillContents(); | ||||
} | } | ||||
public PLCDumper(POIFSFileSystem fs) throws IOException { | public PLCDumper(POIFSFileSystem fs) throws IOException { | ||||
} | } | ||||
private void dumpPLC() { | private void dumpPLC() { | ||||
QuillContents qc = doc.getQuillContents(); | |||||
QCBit[] bits = qc.getBits(); | QCBit[] bits = qc.getBits(); | ||||
for(int i=0; i<bits.length; i++) { | for(int i=0; i<bits.length; i++) { | ||||
System.out.println(""); | System.out.println(""); | ||||
System.out.println("Dumping " + bit.getBitType() + " bit at " + index); | System.out.println("Dumping " + bit.getBitType() + " bit at " + index); | ||||
System.out.println(" Is a " + bit.getThingType() + ", number is " + bit.getOptA()); | 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)); | System.out.println(HexDump.dump(bit.getData(), 0, 0)); | ||||
} | } |
import org.apache.poi.hpbf.HPBFDocument; | import org.apache.poi.hpbf.HPBFDocument; | ||||
import org.apache.poi.hpbf.model.qcbits.QCBit; | import org.apache.poi.hpbf.model.qcbits.QCBit; | ||||
import org.apache.poi.hpbf.model.qcbits.QCTextBit; | import org.apache.poi.hpbf.model.qcbits.QCTextBit; | ||||
import org.apache.poi.hpbf.model.qcbits.QCPLCBit.Type12; | |||||
import org.apache.poi.poifs.filesystem.POIFSFileSystem; | import org.apache.poi.poifs.filesystem.POIFSFileSystem; | ||||
/** | /** | ||||
*/ | */ | ||||
public class PublisherTextExtractor extends POIOLE2TextExtractor { | public class PublisherTextExtractor extends POIOLE2TextExtractor { | ||||
private HPBFDocument doc; | private HPBFDocument doc; | ||||
private boolean hyperlinksByDefault = false; | |||||
public PublisherTextExtractor(HPBFDocument doc) { | public PublisherTextExtractor(HPBFDocument doc) { | ||||
super(doc); | super(doc); | ||||
this(new POIFSFileSystem(is)); | 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() { | public String getText() { | ||||
StringBuffer text = new StringBuffer(); | StringBuffer text = new StringBuffer(); | ||||
} | } | ||||
} | } | ||||
// 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 | // Get more text | ||||
// TODO | // TODO | ||||
import java.io.IOException; | import java.io.IOException; | ||||
import org.apache.poi.hpbf.model.qcbits.QCBit; | 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.QCTextBit; | ||||
import org.apache.poi.hpbf.model.qcbits.UnknownQCBit; | import org.apache.poi.hpbf.model.qcbits.UnknownQCBit; | ||||
import org.apache.poi.poifs.filesystem.DirectoryNode; | import org.apache.poi.poifs.filesystem.DirectoryNode; | ||||
// Create | // Create | ||||
if(bitType.equals("TEXT")) { | if(bitType.equals("TEXT")) { | ||||
bits[i] = new QCTextBit(thingType, bitType, bitData); | bits[i] = new QCTextBit(thingType, bitType, bitData); | ||||
} else if(bitType.equals("PLC ")) { | |||||
bits[i] = QCPLCBit.createQCPLCBit(thingType, bitType, bitData); | |||||
} else { | } else { | ||||
bits[i] = new UnknownQCBit(thingType, bitType, bitData); | bits[i] = new UnknownQCBit(thingType, bitType, bitData); | ||||
} | } |
/* ==================================================================== | |||||
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]; | |||||
} | |||||
} | |||||
} |
assertEquals(s2007, s2000); | assertEquals(s2007, s2000); | ||||
assertEquals(s2007, s98); | 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" | |||||
)); | |||||
} | |||||
} | } |
import org.apache.poi.hpbf.HPBFDocument; | import org.apache.poi.hpbf.HPBFDocument; | ||||
import org.apache.poi.hpbf.model.qcbits.QCTextBit; | 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; | import junit.framework.TestCase; | ||||
assertTrue(t.startsWith("This is some text on the first page")); | assertTrue(t.startsWith("This is some text on the first page")); | ||||
assertTrue(t.endsWith("Within doc to page 1\r")); | 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)); | |||||
} | |||||
} | } |
package org.apache.poi.hssf.model; | package org.apache.poi.hssf.model; | ||||
import java.io.ByteArrayInputStream; | |||||
import java.util.ArrayList; | import java.util.ArrayList; | ||||
import java.util.List; | import java.util.List; | ||||
import junit.framework.TestCase; | import junit.framework.TestCase; | ||||
import org.apache.poi.hssf.HSSFTestDataSamples; | 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.BOFRecord; | ||||
import org.apache.poi.hssf.record.BlankRecord; | import org.apache.poi.hssf.record.BlankRecord; | ||||
import org.apache.poi.hssf.record.CellValueRecordInterface; | import org.apache.poi.hssf.record.CellValueRecordInterface; | ||||
import org.apache.poi.hssf.record.aggregates.MergedCellsTable; | import org.apache.poi.hssf.record.aggregates.MergedCellsTable; | ||||
import org.apache.poi.hssf.record.aggregates.PageSettingsBlock; | import org.apache.poi.hssf.record.aggregates.PageSettingsBlock; | ||||
import org.apache.poi.hssf.record.aggregates.RowRecordsAggregate; | 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.HSSFCell; | ||||
import org.apache.poi.hssf.usermodel.HSSFRow; | import org.apache.poi.hssf.usermodel.HSSFRow; | ||||
import org.apache.poi.hssf.usermodel.HSSFSheet; | import org.apache.poi.hssf.usermodel.HSSFSheet; | ||||
return result; | return result; | ||||
} | } | ||||
private static final class MergedCellListener implements ERFListener { | |||||
private static final class MergedCellListener implements RecordVisitor { | |||||
private int _count; | private int _count; | ||||
public MergedCellListener() { | public MergedCellListener() { | ||||
_count = 0; | _count = 0; | ||||
} | } | ||||
public boolean processRecord(Record rec) { | |||||
_count++; | |||||
return true; | |||||
public void visitRecord(Record r) { | |||||
if (r instanceof MergeCellsRecord) { | |||||
_count++; | |||||
} | |||||
} | } | ||||
public int getCount() { | public int getCount() { | ||||
return _count; | return _count; | ||||
assertTrue(sheet.getNumMergedRegions() == regionsToAdd); | assertTrue(sheet.getNumMergedRegions() == regionsToAdd); | ||||
//test that the regions were spread out over the appropriate number of records | //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(); | 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 recordsAdded = mcListener.getCount(); | ||||
int recordsExpected = regionsToAdd/1027; | int recordsExpected = regionsToAdd/1027; | ||||
if ((regionsToAdd % 1027) != 0) | if ((regionsToAdd % 1027) != 0) | ||||
assertEquals(DEFAULT_IDX, xfindex); | 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 | * Prior to bug 45066, POI would get the estimated sheet size wrong | ||||
* when an <tt>UncalcedRecord</tt> was present.<p/> | * when an <tt>UncalcedRecord</tt> was present.<p/> | ||||
records.add(createWindow2Record()); | records.add(createWindow2Record()); | ||||
records.add(EOFRecord.instance); | records.add(EOFRecord.instance); | ||||
Sheet sheet = Sheet.createSheet(records, 0, 0); | 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()); | |||||
} | } | ||||
/** | /** | ||||
* That value is found on the IndexRecord. | * That value is found on the IndexRecord. | ||||
*/ | */ | ||||
private static int getDbCellRecordPos(Sheet sheet) { | private static int getDbCellRecordPos(Sheet sheet) { | ||||
int size = sheet.getSize(); | |||||
byte[] data = new byte[size]; | |||||
sheet.serialize(0, data); | |||||
MyIndexRecordListener myIndexListener = new MyIndexRecordListener(); | 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(); | IndexRecord indexRecord = myIndexListener.getIndexRecord(); | ||||
int dbCellRecordPos = indexRecord.getDbcellAt(0); | int dbCellRecordPos = indexRecord.getDbcellAt(0); | ||||
return dbCellRecordPos; | return dbCellRecordPos; | ||||
} | } | ||||
private static final class MyIndexRecordListener implements ERFListener { | |||||
private static final class MyIndexRecordListener implements RecordVisitor { | |||||
private IndexRecord _indexRecord; | private IndexRecord _indexRecord; | ||||
public MyIndexRecordListener() { | public MyIndexRecordListener() { | ||||
// no-arg constructor | // no-arg constructor | ||||
} | } | ||||
public boolean processRecord(Record rec) { | |||||
_indexRecord = (IndexRecord)rec; | |||||
return true; | |||||
} | |||||
public IndexRecord getIndexRecord() { | public IndexRecord getIndexRecord() { | ||||
return _indexRecord; | return _indexRecord; | ||||
} | } | ||||
public void visitRecord(Record r) { | |||||
if (r instanceof IndexRecord) { | |||||
if (_indexRecord != null) { | |||||
throw new RuntimeException("too many index records"); | |||||
} | |||||
_indexRecord = (IndexRecord)r; | |||||
} | |||||
} | |||||
} | } | ||||
/** | /** | ||||
} | } | ||||
assertEquals("Informations", cell.getRichStringCellValue().getString()); | 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; | |||||
} | |||||
} | |||||
} | } | ||||
limitations under the License. | limitations under the License. | ||||
==================================================================== */ | ==================================================================== */ | ||||
/* | |||||
* TestFormulaRecordAggregate.java | |||||
* | |||||
* Created on March 21, 2003, 12:32 AM | |||||
*/ | |||||
package org.apache.poi.hssf.record.aggregates; | package org.apache.poi.hssf.record.aggregates; | ||||
import junit.framework.TestCase; | |||||
import org.apache.poi.hssf.record.FormulaRecord; | import org.apache.poi.hssf.record.FormulaRecord; | ||||
import org.apache.poi.hssf.record.StringRecord; | import org.apache.poi.hssf.record.StringRecord; | ||||
* | * | ||||
* @author avik | * @author avik | ||||
*/ | */ | ||||
public final class TestFormulaRecordAggregate extends junit.framework.TestCase { | |||||
public final class TestFormulaRecordAggregate extends TestCase { | |||||
public void testBasic() throws Exception { | public void testBasic() throws Exception { | ||||
FormulaRecord f = new FormulaRecord(); | FormulaRecord f = new FormulaRecord(); | ||||
StringRecord s = new StringRecord(); | StringRecord s = new StringRecord(); | ||||
s.setString("abc"); | s.setString("abc"); | ||||
FormulaRecordAggregate fagg = new FormulaRecordAggregate(f); | |||||
fagg.setStringRecord(s); | |||||
FormulaRecordAggregate fagg = new FormulaRecordAggregate(f, s, SharedValueManager.EMPTY); | |||||
assertEquals("abc", fagg.getStringValue()); | assertEquals("abc", fagg.getStringValue()); | ||||
} | } | ||||
} | } |
package org.apache.poi.hssf.record.aggregates; | 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 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.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 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()); | |||||
} | |||||
} | } |
import junit.framework.TestCase; | import junit.framework.TestCase; | ||||
import org.apache.poi.hssf.HSSFTestDataSamples; | 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.BlankRecord; | ||||
import org.apache.poi.hssf.record.CellValueRecordInterface; | |||||
import org.apache.poi.hssf.record.FormulaRecord; | 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.RecordBase; | ||||
import org.apache.poi.hssf.record.SharedFormulaRecord; | 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.HSSFSheet; | ||||
import org.apache.poi.hssf.usermodel.HSSFWorkbook; | import org.apache.poi.hssf.usermodel.HSSFWorkbook; | ||||
List records = new ArrayList(); | List records = new ArrayList(); | ||||
records.add( new FormulaRecord() ); | records.add( new FormulaRecord() ); | ||||
records.add( new SharedFormulaRecord() ); | records.add( new SharedFormulaRecord() ); | ||||
records.add(new WindowTwoRecord()); | |||||
constructValueRecord(records); | constructValueRecord(records); | ||||
Iterator iterator = valueRecord.getIterator(); | Iterator iterator = valueRecord.getIterator(); | ||||
} | } | ||||
private void constructValueRecord(List records) { | 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() { | private static List testData() { | ||||
blankRecord.setColumn( (short) 2 ); | blankRecord.setColumn( (short) 2 ); | ||||
records.add( formulaRecord ); | records.add( formulaRecord ); | ||||
records.add( blankRecord ); | records.add( blankRecord ); | ||||
records.add(new WindowTwoRecord()); | |||||
return records; | return records; | ||||
} | } | ||||
/* ==================================================================== | |||||
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(); | |||||
} | |||||
} |
package org.apache.poi.hssf.usermodel; | package org.apache.poi.hssf.usermodel; | ||||
import java.io.ByteArrayInputStream; | |||||
import java.io.File; | import java.io.File; | ||||
import java.io.FileInputStream; | import java.io.FileInputStream; | ||||
import java.io.FileOutputStream; | import java.io.FileOutputStream; | ||||
import java.io.IOException; | import java.io.IOException; | ||||
import java.util.Iterator; | |||||
import junit.framework.TestCase; | import junit.framework.TestCase; | ||||
import org.apache.poi.hssf.HSSFTestDataSamples; | 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.model.Workbook; | ||||
import org.apache.poi.hssf.record.BackupRecord; | import org.apache.poi.hssf.record.BackupRecord; | ||||
import org.apache.poi.hssf.record.LabelSSTRecord; | import org.apache.poi.hssf.record.LabelSSTRecord; | ||||
import org.apache.poi.hssf.record.Record; | 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.poifs.filesystem.POIFSFileSystem; | ||||
import org.apache.poi.ss.util.CellRangeAddress; | import org.apache.poi.ss.util.CellRangeAddress; | ||||
import org.apache.poi.ss.util.Region; | import org.apache.poi.ss.util.Region; | ||||
* HSSFSheet last row or first row is incorrect. <P> | * HSSFSheet last row or first row is incorrect. <P> | ||||
* | * | ||||
*/ | */ | ||||
public void testWriteSheetSimple() | |||||
throws IOException | |||||
{ | |||||
public void testWriteSheetSimple() throws IOException { | |||||
File file = TempFile.createTempFile("testWriteSheetSimple", | File file = TempFile.createTempFile("testWriteSheetSimple", | ||||
".xls"); | ".xls"); | ||||
FileOutputStream out = new FileOutputStream(file); | FileOutputStream out = new FileOutputStream(file); | ||||
HSSFRow r = null; | HSSFRow r = null; | ||||
HSSFCell c = null; | HSSFCell c = null; | ||||
for (short rownum = ( short ) 0; rownum < 100; rownum++) | |||||
{ | |||||
for (int rownum = 0; rownum < 100; rownum++) { | |||||
r = s.createRow(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 = r.createCell(cellnum); | ||||
c.setCellValue(rownum * 10000 + cellnum | c.setCellValue(rownum * 10000 + cellnum | ||||
+ ((( double ) rownum / 1000) | + ((( double ) rownum / 1000) | ||||
sanityChecker.checkHSSFWorkbook(wb); | sanityChecker.checkHSSFWorkbook(wb); | ||||
assertEquals("LAST ROW == 99", 99, s.getLastRowNum()); | assertEquals("LAST ROW == 99", 99, s.getLastRowNum()); | ||||
assertEquals("FIRST ROW == 0", 0, s.getFirstRowNum()); | assertEquals("FIRST ROW == 0", 0, s.getFirstRowNum()); | ||||
// assert((s.getLastRowNum() == 99)); | |||||
} | } | ||||
/** | /** | ||||
HSSFRow r = null; | HSSFRow r = null; | ||||
HSSFCell c = null; | HSSFCell c = null; | ||||
for (short rownum = ( short ) 0; rownum < 100; rownum++) | |||||
{ | |||||
for (int rownum = 0; rownum < 100; rownum++) { | |||||
r = s.createRow(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 = r.createCell(cellnum); | ||||
c.setCellValue(rownum * 10000 + cellnum | c.setCellValue(rownum * 10000 + cellnum | ||||
+ ((( double ) rownum / 1000) | + ((( double ) rownum / 1000) | ||||
c.setCellValue(new HSSFRichTextString("TEST")); | c.setCellValue(new HSSFRichTextString("TEST")); | ||||
} | } | ||||
} | } | ||||
for (short rownum = ( short ) 0; rownum < 25; rownum++) | |||||
{ | |||||
for (int rownum = 0; rownum < 25; rownum++) { | |||||
r = s.getRow(rownum); | r = s.getRow(rownum); | ||||
s.removeRow(r); | s.removeRow(r); | ||||
} | } | ||||
for (short rownum = ( short ) 75; rownum < 100; rownum++) | |||||
{ | |||||
for (int rownum = 75; rownum < 100; rownum++) { | |||||
r = s.getRow(rownum); | r = s.getRow(rownum); | ||||
s.removeRow(r); | s.removeRow(r); | ||||
} | } | ||||
HSSFWorkbook wb = new HSSFWorkbook(); | HSSFWorkbook wb = new HSSFWorkbook(); | ||||
HSSFSheet s = wb.createSheet(); | HSSFSheet s = wb.createSheet(); | ||||
for (short rownum = ( short ) 0; rownum < 100; rownum++) | |||||
{ | |||||
for (int rownum = 0; rownum < 100; rownum++) { | |||||
HSSFRow r = s.createRow(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); | HSSFCell c = r.createCell(cellnum); | ||||
c.setCellValue(rownum * 10000 + cellnum | c.setCellValue(rownum * 10000 + cellnum | ||||
+ ((( double ) rownum / 1000) | + ((( double ) rownum / 1000) | ||||
/** | /** | ||||
* Test the backup field gets set as expected. | * 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(); | BackupRecord record = workbook.getBackupRecord(); | ||||
assertEquals(0, record.getBackup()); | assertEquals(0, record.getBackup()); | ||||
assertEquals(1, record.getBackup()); | assertEquals(1, record.getBackup()); | ||||
} | } | ||||
private static final class RecordCounter implements ERFListener { | |||||
private static final class RecordCounter implements RecordVisitor { | |||||
private int _count; | private int _count; | ||||
public RecordCounter() { | public RecordCounter() { | ||||
public int getCount() { | public int getCount() { | ||||
return _count; | return _count; | ||||
} | } | ||||
public boolean processRecord(Record rec) { | |||||
_count++; | |||||
return true; | |||||
public void visitRecord(Record r) { | |||||
if (r instanceof LabelSSTRecord) { | |||||
_count++; | |||||
} | |||||
} | } | ||||
} | } | ||||
* | * | ||||
* We need to make sure only one LabelSSTRecord is produced. | * We need to make sure only one LabelSSTRecord is produced. | ||||
*/ | */ | ||||
public void testRepeatingBug() | |||||
throws Exception | |||||
{ | |||||
public void testRepeatingBug() { | |||||
HSSFWorkbook workbook = new HSSFWorkbook(); | HSSFWorkbook workbook = new HSSFWorkbook(); | ||||
HSSFSheet sheet = workbook.createSheet("Design Variants"); | HSSFSheet sheet = workbook.createSheet("Design Variants"); | ||||
HSSFRow row = sheet.createRow(2); | HSSFRow row = sheet.createRow(2); | ||||
cell.setCellValue(new HSSFRichTextString("Class")); | cell.setCellValue(new HSSFRichTextString("Class")); | ||||
cell = row.createCell(2); | cell = row.createCell(2); | ||||
byte[] data = new byte[sheet.getSheet().getSize()]; | |||||
sheet.getSheet().serialize(0, data); | |||||
RecordCounter rc = new RecordCounter(); | 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()); | assertEquals(1, rc.getCount()); | ||||
} | } | ||||