| /* ==================================================================== |
| 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.eventusermodel; |
| |
| import org.apache.poi.hssf.eventusermodel.dummyrecord.LastCellOfRowDummyRecord; |
| import org.apache.poi.hssf.eventusermodel.dummyrecord.MissingCellDummyRecord; |
| import org.apache.poi.hssf.eventusermodel.dummyrecord.MissingRowDummyRecord; |
| import org.apache.poi.hssf.record.BOFRecord; |
| import org.apache.poi.hssf.record.CellValueRecordInterface; |
| import org.apache.poi.hssf.record.MulBlankRecord; |
| import org.apache.poi.hssf.record.MulRKRecord; |
| import org.apache.poi.hssf.record.NoteRecord; |
| import org.apache.poi.hssf.record.Record; |
| import org.apache.poi.hssf.record.RecordFactory; |
| import org.apache.poi.hssf.record.RowRecord; |
| import org.apache.poi.hssf.record.SharedFormulaRecord; |
| import org.apache.poi.hssf.record.StringRecord; |
| |
| /** |
| * <p>A HSSFListener which tracks rows and columns, and will |
| * trigger your HSSFListener for all rows and cells, |
| * even the ones that aren't actually stored in the file.</p> |
| * <p>This allows your code to have a more "Excel" like |
| * view of the data in the file, and not have to worry |
| * (as much) about if a particular row/cell is in the |
| * file, or was skipped from being written as it was |
| * blank. |
| */ |
| public final class MissingRecordAwareHSSFListener implements HSSFListener { |
| private HSSFListener childListener; |
| |
| // Need to have different counters for cell rows and |
| // row rows, as you sometimes get a RowRecord in the |
| // middle of some cells, and that'd break everything |
| private int lastRowRow; |
| |
| private int lastCellRow; |
| private int lastCellColumn; |
| |
| /** |
| * Constructs a new MissingRecordAwareHSSFListener, which |
| * will fire processRecord on the supplied child |
| * HSSFListener for all Records, and missing records. |
| * @param listener The HSSFListener to pass records on to |
| */ |
| public MissingRecordAwareHSSFListener(HSSFListener listener) { |
| resetCounts(); |
| childListener = listener; |
| } |
| |
| public void processRecord(Record record) { |
| int thisRow; |
| int thisColumn; |
| CellValueRecordInterface[] expandedRecords = null; |
| |
| if (record instanceof CellValueRecordInterface) { |
| CellValueRecordInterface valueRec = (CellValueRecordInterface) record; |
| thisRow = valueRec.getRow(); |
| thisColumn = valueRec.getColumn(); |
| } else { |
| if (record instanceof StringRecord){ |
| //it contains only cashed result of the previous FormulaRecord evaluation |
| childListener.processRecord(record); |
| return; |
| } |
| thisRow = -1; |
| thisColumn = -1; |
| |
| switch (record.getSid()) { |
| // the BOFRecord can represent either the beginning of a sheet or |
| // the workbook |
| case BOFRecord.sid: |
| BOFRecord bof = (BOFRecord) record; |
| if (bof.getType() == BOFRecord.TYPE_WORKBOOK || |
| bof.getType() == BOFRecord.TYPE_WORKSHEET) { |
| // Reset the row and column counts - new workbook / worksheet |
| resetCounts(); |
| } |
| break; |
| case RowRecord.sid: |
| RowRecord rowrec = (RowRecord) record; |
| //System.out.println("Row " + rowrec.getRowNumber() + " found, first column at " |
| // + rowrec.getFirstCol() + " last column at " + rowrec.getLastCol()); |
| |
| // If there's a jump in rows, fire off missing row records |
| if (lastRowRow + 1 < rowrec.getRowNumber()) { |
| for (int i = (lastRowRow + 1); i < rowrec.getRowNumber(); i++) { |
| MissingRowDummyRecord dr = new MissingRowDummyRecord(i); |
| childListener.processRecord(dr); |
| } |
| } |
| |
| // Record this as the last row we saw |
| lastRowRow = rowrec.getRowNumber(); |
| lastCellColumn = -1; |
| break; |
| |
| case SharedFormulaRecord.sid: |
| // SharedFormulaRecord occurs after the first FormulaRecord of the cell range. |
| // There are probably (but not always) more cell records after this |
| // - so don't fire off the LastCellOfRowDummyRecord yet |
| childListener.processRecord(record); |
| return; |
| case MulBlankRecord.sid: |
| // These appear in the middle of the cell records, to |
| // specify that the next bunch are empty but styled |
| // Expand this out into multiple blank cells |
| MulBlankRecord mbr = (MulBlankRecord)record; |
| expandedRecords = RecordFactory.convertBlankRecords(mbr); |
| break; |
| case MulRKRecord.sid: |
| // This is multiple consecutive number cells in one record |
| // Exand this out into multiple regular number cells |
| MulRKRecord mrk = (MulRKRecord)record; |
| expandedRecords = RecordFactory.convertRKRecords(mrk); |
| break; |
| case NoteRecord.sid: |
| NoteRecord nrec = (NoteRecord) record; |
| thisRow = nrec.getRow(); |
| thisColumn = nrec.getColumn(); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| // First part of expanded record handling |
| if(expandedRecords != null && expandedRecords.length > 0) { |
| thisRow = expandedRecords[0].getRow(); |
| thisColumn = expandedRecords[0].getColumn(); |
| } |
| |
| // If we're on cells, and this cell isn't in the same |
| // row as the last one, then fire the |
| // dummy end-of-row records |
| if(thisRow != lastCellRow && thisRow > 0) { |
| if (lastCellRow == -1) lastCellRow = 0; |
| for(int i=lastCellRow; i<thisRow; i++) { |
| int cols = -1; |
| if(i == lastCellRow) { |
| cols = lastCellColumn; |
| } |
| childListener.processRecord(new LastCellOfRowDummyRecord(i, cols)); |
| } |
| } |
| |
| // If we've just finished with the cells, then fire the |
| // final dummy end-of-row record |
| if(lastCellRow != -1 && lastCellColumn != -1 && thisRow == -1) { |
| childListener.processRecord(new LastCellOfRowDummyRecord(lastCellRow, lastCellColumn)); |
| |
| lastCellRow = -1; |
| lastCellColumn = -1; |
| } |
| |
| // If we've moved onto a new row, the ensure we re-set |
| // the column counter |
| if(thisRow != lastCellRow) { |
| lastCellColumn = -1; |
| } |
| |
| // If there's a gap in the cells, then fire |
| // the dummy cell records |
| if(lastCellColumn != thisColumn-1) { |
| for(int i=lastCellColumn+1; i<thisColumn; i++) { |
| childListener.processRecord(new MissingCellDummyRecord(thisRow, i)); |
| } |
| } |
| |
| // Next part of expanded record handling |
| if(expandedRecords != null && expandedRecords.length > 0) { |
| thisColumn = expandedRecords[expandedRecords.length-1].getColumn(); |
| } |
| |
| |
| // Update cell and row counts as needed |
| if(thisColumn != -1) { |
| lastCellColumn = thisColumn; |
| lastCellRow = thisRow; |
| } |
| |
| // Pass along the record(s) |
| if(expandedRecords != null && expandedRecords.length > 0) { |
| for(CellValueRecordInterface r : expandedRecords) { |
| childListener.processRecord((Record)r); |
| } |
| } else { |
| childListener.processRecord(record); |
| } |
| } |
| |
| private void resetCounts() { |
| lastRowRow = -1; |
| lastCellRow = -1; |
| lastCellColumn = -1; |
| } |
| } |