123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545 |
- /* ====================================================================
- 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.Iterator;
- import java.util.List;
- import java.util.Map;
- import java.util.Spliterator;
- 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.ContinueRecord;
- import org.apache.poi.hssf.record.DBCellRecord;
- import org.apache.poi.hssf.record.DConRefRecord;
- import org.apache.poi.hssf.record.DimensionsRecord;
- import org.apache.poi.hssf.record.FormulaRecord;
- import org.apache.poi.hssf.record.HyperlinkRecord;
- import org.apache.poi.hssf.record.IndexRecord;
- import org.apache.poi.hssf.record.MergeCellsRecord;
- import org.apache.poi.hssf.record.MulBlankRecord;
- import org.apache.poi.hssf.record.Record;
- import org.apache.poi.hssf.record.RowRecord;
- import org.apache.poi.hssf.record.SharedFormulaRecord;
- import org.apache.poi.hssf.record.TableRecord;
- import org.apache.poi.hssf.record.UnknownRecord;
- import org.apache.poi.ss.SpreadsheetVersion;
- import org.apache.poi.ss.formula.FormulaShifter;
-
- public final class RowRecordsAggregate extends RecordAggregate {
- private int _firstrow = -1;
- private int _lastrow = -1;
- private final Map<Integer, RowRecord> _rowRecords;
- private final ValueRecordsAggregate _valuesAgg;
- private final List<org.apache.poi.hssf.record.Record> _unknownRecords;
- private final SharedValueManager _sharedValueManager;
-
- // Cache values to speed up performance of
- // getStartRowNumberForBlock / getEndRowNumberForBlock, see Bugzilla 47405
- private RowRecord[] _rowRecordValues;
-
- /** Creates a new instance of ValueRecordsAggregate */
- public RowRecordsAggregate() {
- this(SharedValueManager.createEmpty());
- }
- private RowRecordsAggregate(SharedValueManager svm) {
- if (svm == null) {
- throw new IllegalArgumentException("SharedValueManager must be provided.");
- }
- _rowRecords = new TreeMap<>();
- _valuesAgg = new ValueRecordsAggregate();
- _unknownRecords = new ArrayList<>();
- _sharedValueManager = svm;
- }
-
- /**
- * @param rs record stream with all {@link SharedFormulaRecord}
- * {@link ArrayRecord}, {@link TableRecord} {@link MergeCellsRecord} Records removed
- * @param svm an initialised {@link SharedValueManager} (from the shared formula, array
- * and table records of the current sheet). Never <code>null</code>.
- */
- public RowRecordsAggregate(RecordStream rs, SharedValueManager svm) {
- this(svm);
- while(rs.hasNext()) {
- Record rec = rs.getNext();
- switch (rec.getSid()) {
- case RowRecord.sid:
- insertRow((RowRecord) rec);
- continue;
- case DConRefRecord.sid:
- addUnknownRecord(rec);
- continue;
- case DBCellRecord.sid:
- // end of 'Row Block'. Should only occur after cell records
- // ignore DBCELL records because POI generates them upon re-serialization
- continue;
- case HyperlinkRecord.sid:
- // some files contain a HyperlinkRecord here which we ignore for now
- continue;
- }
- if (rec instanceof UnknownRecord) {
- // might need to keep track of where exactly these belong
- addUnknownRecord(rec);
- while (rs.peekNextSid() == ContinueRecord.sid) {
- addUnknownRecord(rs.getNext());
- }
- continue;
- }
- if (rec instanceof MulBlankRecord) {
- _valuesAgg.addMultipleBlanks((MulBlankRecord) rec);
- continue;
- }
- if (!(rec instanceof CellValueRecordInterface)) {
- throw new RuntimeException("Unexpected record type (" + rec.getClass().getName() + ")");
- }
- _valuesAgg.construct((CellValueRecordInterface)rec, rs, svm);
- }
- }
- /**
- * Handles UnknownRecords which appear within the row/cell records
- */
- private void addUnknownRecord(org.apache.poi.hssf.record.Record rec) {
- // ony a few distinct record IDs are encountered by the existing POI test cases:
- // 0x1065 // many
- // 0x01C2 // several
- // 0x0034 // few
- // No documentation could be found for these
-
- // keep the unknown records for re-serialization
- _unknownRecords.add(rec);
- }
- public void insertRow(RowRecord row) {
- // Integer integer = Integer.valueOf(row.getRowNumber());
- _rowRecords.put(Integer.valueOf(row.getRowNumber()), row);
- // Clear the cached values
- _rowRecordValues = null;
- if ((row.getRowNumber() < _firstrow) || (_firstrow == -1)) {
- _firstrow = row.getRowNumber();
- }
- if ((row.getRowNumber() > _lastrow) || (_lastrow == -1)) {
- _lastrow = row.getRowNumber();
- }
- }
-
- public void removeRow(RowRecord row) {
- int rowIndex = row.getRowNumber();
- _valuesAgg.removeAllCellsValuesForRow(rowIndex);
- Integer key = Integer.valueOf(rowIndex);
- RowRecord rr = _rowRecords.remove(key);
- if (rr == null) {
- throw new RuntimeException("Invalid row index (" + key.intValue() + ")");
- }
- if (row != rr) {
- _rowRecords.put(key, rr);
- throw new RuntimeException("Attempt to remove row that does not belong to this sheet");
- }
-
- // Clear the cached values
- _rowRecordValues = null;
- }
-
- public RowRecord getRow(int rowIndex) {
- int maxrow = SpreadsheetVersion.EXCEL97.getLastRowIndex();
- if (rowIndex < 0 || rowIndex > maxrow) {
- throw new IllegalArgumentException("The row number must be between 0 and " + maxrow + ", but had: " + rowIndex);
- }
- return _rowRecords.get(Integer.valueOf(rowIndex));
- }
-
- public int getPhysicalNumberOfRows()
- {
- return _rowRecords.size();
- }
-
- public int getFirstRowNum()
- {
- return _firstrow;
- }
-
- public int getLastRowNum()
- {
- return _lastrow;
- }
-
- /** Returns the number of row blocks.
- * <p>The row blocks are goupings of rows that contain the DBCell record
- * after them
- */
- public int getRowBlockCount() {
- int size = _rowRecords.size()/DBCellRecord.BLOCK_SIZE;
- if ((_rowRecords.size() % DBCellRecord.BLOCK_SIZE) != 0)
- size++;
- return size;
- }
-
- private int getRowBlockSize(int block) {
- return RowRecord.ENCODED_SIZE * getRowCountForBlock(block);
- }
-
- /** Returns the number of physical rows within a block*/
- public int getRowCountForBlock(int block) {
- int startIndex = block * DBCellRecord.BLOCK_SIZE;
- int endIndex = startIndex + DBCellRecord.BLOCK_SIZE - 1;
- if (endIndex >= _rowRecords.size())
- endIndex = _rowRecords.size()-1;
-
- return endIndex-startIndex+1;
- }
-
- /** Returns the physical row number of the first row in a block*/
- private int getStartRowNumberForBlock(int block) {
- int startIndex = block * DBCellRecord.BLOCK_SIZE;
-
- if (_rowRecordValues == null) {
- _rowRecordValues = _rowRecords.values().toArray(new RowRecord[0]);
- }
-
- try {
- return _rowRecordValues[startIndex].getRowNumber();
- } catch(ArrayIndexOutOfBoundsException e) {
- throw new RuntimeException("Did not find start row for block " + block);
- }
- }
-
- /** Returns the physical row number of the end row in a block*/
- private int getEndRowNumberForBlock(int block) {
- int endIndex = ((block + 1)*DBCellRecord.BLOCK_SIZE)-1;
- if (endIndex >= _rowRecords.size())
- endIndex = _rowRecords.size()-1;
-
- if (_rowRecordValues == null){
- _rowRecordValues = _rowRecords.values().toArray(new RowRecord[0]);
- }
-
- try {
- return _rowRecordValues[endIndex].getRowNumber();
- } catch(ArrayIndexOutOfBoundsException e) {
- throw new RuntimeException("Did not find end row for block " + block);
- }
- }
-
- private int visitRowRecordsForBlock(int blockIndex, RecordVisitor rv) {
- final int startIndex = blockIndex*DBCellRecord.BLOCK_SIZE;
- final int endIndex = startIndex + DBCellRecord.BLOCK_SIZE;
-
- Iterator<RowRecord> rowIterator = _rowRecords.values().iterator();
-
- //Given that we basically iterate through the rows in order,
- //For a performance improvement, it would be better to return an instance of
- //an iterator and use that instance throughout, rather than recreating one and
- //having to move it to the right position.
- int i=0;
- for (;i<startIndex;i++)
- rowIterator.next();
- int result = 0;
- while(rowIterator.hasNext() && (i++ < endIndex)) {
- Record rec = rowIterator.next();
- result += rec.getRecordSize();
- rv.visitRecord(rec);
- }
- return result;
- }
-
- @Override
- public void visitContainedRecords(RecordVisitor rv) {
-
- PositionTrackingVisitor stv = new PositionTrackingVisitor(rv, 0);
- //DBCells are serialized before row records.
- final int blockCount = getRowBlockCount();
- for (int blockIndex = 0; blockIndex < blockCount; blockIndex++) {
- // Serialize a block of rows.
- // Hold onto the position of the first row in the block
- int pos=0;
- // Hold onto the size of this block that was serialized
- final int rowBlockSize = visitRowRecordsForBlock(blockIndex, rv);
- pos += rowBlockSize;
- // Serialize a block of cells for those rows
- final int startRowNumber = getStartRowNumberForBlock(blockIndex);
- final int endRowNumber = getEndRowNumberForBlock(blockIndex);
-
- final List<Short> cellOffsets = new ArrayList<>();
-
- // Note: Cell references start from the second row...
- int cellRefOffset = (rowBlockSize - RowRecord.ENCODED_SIZE);
- for (int row = startRowNumber; row <= endRowNumber; row++) {
- if (_valuesAgg.rowHasCells(row)) {
- stv.setPosition(0);
- _valuesAgg.visitCellsForRow(row, stv);
- int rowCellSize = stv.getPosition();
- pos += rowCellSize;
- // Add the offset to the first cell for the row into the
- // DBCellRecord.
- cellOffsets.add((short)cellRefOffset);
- cellRefOffset = rowCellSize;
- }
- }
- // Calculate Offset from the start of a DBCellRecord to the first Row
- rv.visitRecord(new DBCellRecord(pos, shortListToArray(cellOffsets)));
- }
-
- // Potentially breaking the file here since we don't know exactly where to write these records
- _unknownRecords.forEach(rv::visitRecord);
- }
-
- private static short[] shortListToArray(List<Short> list) {
- final short[] arr = new short[list.size()];
- int idx = 0;
- for (Short s : list) {
- arr[idx++] = s;
- }
- return arr;
- }
-
- public Iterator<RowRecord> getIterator() {
- return _rowRecords.values().iterator();
- }
-
- /**
- * @since POI 5.2.0
- */
- public Spliterator<RowRecord> getSpliterator() {
- return _rowRecords.values().spliterator();
- }
-
- public int findStartOfRowOutlineGroup(int row) {
- // Find the start of the group.
- RowRecord rowRecord = this.getRow( row );
- int level = rowRecord.getOutlineLevel();
- int currentRow = row;
- while (currentRow >= 0 && this.getRow( currentRow ) != null) {
- rowRecord = this.getRow( currentRow );
- if (rowRecord.getOutlineLevel() < level) {
- return currentRow + 1;
- }
- currentRow--;
- }
-
- return currentRow + 1;
- }
-
- public int findEndOfRowOutlineGroup(int row) {
- int level = getRow( row ).getOutlineLevel();
- int currentRow;
- for (currentRow = row; currentRow < getLastRowNum(); currentRow++) {
- if (getRow(currentRow) == null || getRow(currentRow).getOutlineLevel() < level) {
- break;
- }
- }
-
- return currentRow-1;
- }
-
- /**
- * Hide all rows at or below the current outline level
- * @return index of the <em>next<em> row after the last row that gets hidden
- */
- private int writeHidden(RowRecord pRowRecord, int row) {
- int rowIx = row;
- RowRecord rowRecord = pRowRecord;
- int level = rowRecord.getOutlineLevel();
- while (rowRecord != null && getRow(rowIx).getOutlineLevel() >= level) {
- rowRecord.setZeroHeight(true);
- rowIx++;
- rowRecord = getRow(rowIx);
- }
- return rowIx;
- }
-
- public void collapseRow(int rowNumber) {
-
- // Find the start of the group.
- int startRow = findStartOfRowOutlineGroup(rowNumber);
- RowRecord rowRecord = getRow(startRow);
-
- // Hide all the columns until the end of the group
- int nextRowIx = writeHidden(rowRecord, startRow);
-
- RowRecord row = getRow(nextRowIx);
- if (row == null) {
- row = createRow(nextRowIx);
- insertRow(row);
- }
- // Write collapse field
- row.setColapsed(true);
- }
-
- /**
- * Create a row record.
- *
- * @param rowNumber row number
- * @return RowRecord created for the passed in row number
- * @see org.apache.poi.hssf.record.RowRecord
- */
- public static RowRecord createRow(int rowNumber) {
- return new RowRecord(rowNumber);
- }
-
- public boolean isRowGroupCollapsed(int row) {
- int collapseRow = findEndOfRowOutlineGroup(row) + 1;
-
- return getRow(collapseRow) != null && getRow(collapseRow).getColapsed();
- }
-
- public void expandRow(int rowNumber) {
- if (rowNumber == -1)
- return;
-
- // If it is already expanded do nothing.
- if (!isRowGroupCollapsed(rowNumber)) {
- return;
- }
-
- // Find the start of the group.
- int startIdx = findStartOfRowOutlineGroup(rowNumber);
- RowRecord row = getRow(startIdx);
-
- // Find the end of the group.
- int endIdx = findEndOfRowOutlineGroup(rowNumber);
-
- // expand:
- // collapsed bit must be unset
- // hidden bit gets unset _if_ surrounding groups are expanded you can determine
- // this by looking at the hidden bit of the enclosing group. You will have
- // to look at the start and the end of the current group to determine which
- // is the enclosing group
- // hidden bit only is altered for this outline level. ie. don't un-collapse contained groups
- if (!isRowGroupHiddenByParent(rowNumber)) {
- for (int i = startIdx; i <= endIdx; i++) {
- RowRecord otherRow = getRow(i);
- if (row.getOutlineLevel() == otherRow.getOutlineLevel() || !isRowGroupCollapsed(i)) {
- otherRow.setZeroHeight(false);
- }
- }
- }
-
- // Write collapse field
- getRow(endIdx + 1).setColapsed(false);
- }
-
- public boolean isRowGroupHiddenByParent(int row) {
- // Look out outline details of end
- int endLevel;
- boolean endHidden;
- int endOfOutlineGroupIdx = findEndOfRowOutlineGroup(row);
- if (getRow(endOfOutlineGroupIdx + 1) == null) {
- endLevel = 0;
- endHidden = false;
- } else {
- endLevel = getRow(endOfOutlineGroupIdx + 1).getOutlineLevel();
- endHidden = getRow(endOfOutlineGroupIdx + 1).getZeroHeight();
- }
-
- // Look out outline details of start
- int startLevel;
- boolean startHidden;
- int startOfOutlineGroupIdx = findStartOfRowOutlineGroup( row );
- if (startOfOutlineGroupIdx - 1 < 0 || getRow(startOfOutlineGroupIdx - 1) == null) {
- startLevel = 0;
- startHidden = false;
- } else {
- startLevel = getRow(startOfOutlineGroupIdx - 1).getOutlineLevel();
- startHidden = getRow(startOfOutlineGroupIdx - 1).getZeroHeight();
- }
-
- if (endLevel > startLevel) {
- return endHidden;
- }
-
- return startHidden;
- }
-
- /**
- * Returns an iterator for the cell values
- */
- public Iterator<CellValueRecordInterface> getCellValueIterator() {
- return _valuesAgg.iterator();
- }
-
- /**
- * Returns a spliterator for the cell values
- *
- * @since POI 5.2.0
- */
- public Spliterator<CellValueRecordInterface> getCellValueSpliterator() {
- return _valuesAgg.spliterator();
- }
-
- public IndexRecord createIndexRecord(int indexRecordOffset, int sizeOfInitialSheetRecords) {
- IndexRecord result = new IndexRecord();
- result.setFirstRow(_firstrow);
- result.setLastRowAdd1(_lastrow + 1);
- // Calculate the size of the records from the end of the BOF
- // and up to the RowRecordsAggregate...
-
- // Add the references to the DBCells in the IndexRecord (one for each block)
- // Note: The offsets are relative to the Workbook BOF. Assume that this is
- // 0 for now.....
-
- int blockCount = getRowBlockCount();
- // Calculate the size of this IndexRecord
- int indexRecSize = IndexRecord.getRecordSizeForBlockCount(blockCount);
-
- int currentOffset = indexRecordOffset + indexRecSize + sizeOfInitialSheetRecords;
-
- for (int block = 0; block < blockCount; block++) {
- // each row-block has a DBCELL record.
- // The offset of each DBCELL record needs to be updated in the INDEX record
-
- // account for row records in this row-block
- currentOffset += getRowBlockSize(block);
- // account for cell value records after those
- currentOffset += _valuesAgg.getRowCellBlockSize(
- getStartRowNumberForBlock(block), getEndRowNumberForBlock(block));
-
- // currentOffset is now the location of the DBCELL record for this row-block
- result.addDbcell(currentOffset);
- // Add space required to write the DBCELL record (whose reference was just added).
- currentOffset += (8 + (getRowCountForBlock(block) * 2));
- }
- return result;
- }
- public void insertCell(CellValueRecordInterface cvRec) {
- _valuesAgg.insertCell(cvRec);
- }
- public void removeCell(CellValueRecordInterface cvRec) {
- if (cvRec instanceof FormulaRecordAggregate) {
- ((FormulaRecordAggregate)cvRec).notifyFormulaChanging();
- }
- _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);
- }
- public void updateFormulasAfterRowShift(FormulaShifter formulaShifter, int currentExternSheetIndex) {
- _valuesAgg.updateFormulasAfterRowShift(formulaShifter, currentExternSheetIndex);
- }
- public DimensionsRecord createDimensions() {
- DimensionsRecord result = new DimensionsRecord();
- result.setFirstRow(_firstrow);
- result.setLastRow(_lastrow);
- result.setFirstCol((short) _valuesAgg.getFirstCellNum());
- result.setLastCol((short) _valuesAgg.getLastCellNum());
- return result;
- }
- }
|