diff options
author | Josh Micich <josh@apache.org> | 2008-08-06 01:39:44 +0000 |
---|---|---|
committer | Josh Micich <josh@apache.org> | 2008-08-06 01:39:44 +0000 |
commit | f28c5a8f21966a89903abb77a47d52340ee908fe (patch) | |
tree | 4d211adadc16f8517e69bcced8d6896a4282abe7 /src/java | |
parent | 1746dea8b1bcdaeff556d92de87c5c9c882cad95 (diff) | |
download | poi-f28c5a8f21966a89903abb77a47d52340ee908fe.tar.gz poi-f28c5a8f21966a89903abb77a47d52340ee908fe.zip |
refactoring aggregate records to a separate hierarchy. just starting
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@683081 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'src/java')
17 files changed, 1306 insertions, 971 deletions
diff --git a/src/java/org/apache/poi/hssf/model/RecordOrderer.java b/src/java/org/apache/poi/hssf/model/RecordOrderer.java new file mode 100644 index 0000000000..ae445597d1 --- /dev/null +++ b/src/java/org/apache/poi/hssf/model/RecordOrderer.java @@ -0,0 +1,326 @@ +/* ==================================================================== + 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.List; + +import org.apache.poi.hssf.record.BOFRecord; +import org.apache.poi.hssf.record.CalcCountRecord; +import org.apache.poi.hssf.record.CalcModeRecord; +import org.apache.poi.hssf.record.DateWindow1904Record; +import org.apache.poi.hssf.record.DefaultRowHeightRecord; +import org.apache.poi.hssf.record.DeltaRecord; +import org.apache.poi.hssf.record.DimensionsRecord; +import org.apache.poi.hssf.record.EOFRecord; +import org.apache.poi.hssf.record.GridsetRecord; +import org.apache.poi.hssf.record.GutsRecord; +import org.apache.poi.hssf.record.HorizontalPageBreakRecord; +import org.apache.poi.hssf.record.HyperlinkRecord; +import org.apache.poi.hssf.record.IndexRecord; +import org.apache.poi.hssf.record.IterationRecord; +import org.apache.poi.hssf.record.PaneRecord; +import org.apache.poi.hssf.record.PrecisionRecord; +import org.apache.poi.hssf.record.PrintGridlinesRecord; +import org.apache.poi.hssf.record.PrintHeadersRecord; +import org.apache.poi.hssf.record.Record; +import org.apache.poi.hssf.record.RecordBase; +import org.apache.poi.hssf.record.RefModeRecord; +import org.apache.poi.hssf.record.SCLRecord; +import org.apache.poi.hssf.record.SaveRecalcRecord; +import org.apache.poi.hssf.record.SelectionRecord; +import org.apache.poi.hssf.record.UncalcedRecord; +import org.apache.poi.hssf.record.VerticalPageBreakRecord; +import org.apache.poi.hssf.record.WindowTwoRecord; +import org.apache.poi.hssf.record.aggregates.ConditionalFormattingTable; +import org.apache.poi.hssf.record.aggregates.DataValidityTable; +import org.apache.poi.hssf.record.aggregates.MergedCellsTable; + +/** + * Finds correct insert positions for records in workbook streams<p/> + * + * See OOO excelfileformat.pdf sec. 4.2.5 'Record Order in a BIFF8 Workbook Stream' + * + * @author Josh Micich + */ +final class RecordOrderer { + // TODO - add UninterpretedRecord as base class for many of these + // unimplemented sids + + // TODO - simplify logic using a generalised record ordering + + private RecordOrderer() { + // no instances of this class + } + /** + * Adds the specified new record in the correct place in sheet records list + * + */ + public static void addNewSheetRecord(List sheetRecords, RecordBase newRecord) { + int index = findSheetInsertPos(sheetRecords, newRecord.getClass()); + sheetRecords.add(index, newRecord); + } + + private static int findSheetInsertPos(List records, Class recClass) { + if (recClass == DataValidityTable.class) { + return findDataValidationTableInsertPos(records); + } + if (recClass == MergedCellsTable.class) { + return findInsertPosForNewMergedRecordTable(records); + } + if (recClass == ConditionalFormattingTable.class) { + return findInsertPosForNewCondFormatTable(records); + } + if (recClass == GutsRecord.class) { + return getGutsRecordInsertPos(records); + } + if (recClass == HorizontalPageBreakRecord.class) { + return getPageBreakRecordInsertPos(records, true); + } + if (recClass == VerticalPageBreakRecord.class) { + return getPageBreakRecordInsertPos(records, false); + } + throw new RuntimeException("Unexpected record class (" + recClass.getName() + ")"); + } + + private static int getPageBreakRecordInsertPos(List records, boolean isHorizonal) { + int dimensionsIndex = getDimensionsIndex(records); + int i = dimensionsIndex-1; + while (i > 0) { + i--; + Object rb = records.get(i); + if (isPageBreakPriorRecord(rb, isHorizonal)) { + return i+1; + } + } + throw new RuntimeException("Did not find insert point for GUTS"); + } + private static boolean isPageBreakPriorRecord(Object rb, boolean newRecIsHorizontal) { + if (rb instanceof Record) { + Record record = (Record) rb; + switch (record.getSid()) { + case BOFRecord.sid: + case IndexRecord.sid: + // calc settings block + case UncalcedRecord.sid: + case CalcCountRecord.sid: + case CalcModeRecord.sid: + case PrecisionRecord.sid: + case RefModeRecord.sid: + case DeltaRecord.sid: + case IterationRecord.sid: + case DateWindow1904Record.sid: + case SaveRecalcRecord.sid: + // end calc settings + case PrintHeadersRecord.sid: + case PrintGridlinesRecord.sid: + case GridsetRecord.sid: + case DefaultRowHeightRecord.sid: + case 0x0081: // SHEETPR + return true; + } + switch (record.getSid()) { + // page settings block + case HorizontalPageBreakRecord.sid: + if (!newRecIsHorizontal) { + return true; + } + return false; + case VerticalPageBreakRecord.sid: + return false; + // next is case HeaderRecord.sid: case FooterRecord.sid: + // then more records in page settings block + + } + } + return false; + } + /** + * Find correct position to add new CFHeader record + */ + private static int findInsertPosForNewCondFormatTable(List records) { + + for (int i = records.size() - 2; i >= 0; i--) { // -2 to skip EOF record + Object rb = records.get(i); + if (rb instanceof MergedCellsTable) { + return i + 1; + } + Record rec = (Record) rb; + switch (rec.getSid()) { + case WindowTwoRecord.sid: + case SCLRecord.sid: + case PaneRecord.sid: + case SelectionRecord.sid: + case 0x0099:// STANDARDWIDTH + // MergedCellsTable usually here + case 0x015f:// LABELRANGES + case 0x00ef:// PHONETICPR + return i + 1; + } + } + throw new RuntimeException("Did not find Window2 record"); + } + + private static int findInsertPosForNewMergedRecordTable(List records) { + for (int i = records.size() - 2; i >= 0; i--) { // -2 to skip EOF record + Object rb = records.get(i); + Record rec = (Record) rb; + switch (rec.getSid()) { + case WindowTwoRecord.sid: + case SCLRecord.sid: + case PaneRecord.sid: + case SelectionRecord.sid: + case 0x0099:// STANDARDWIDTH + return i + 1; + } + } + throw new RuntimeException("Did not find Window2 record"); + } + + + /** + * Finds the index where the sheet validations header record should be inserted + * @param records the records for this sheet + * + * + WINDOW2 + * o SCL + * o PANE + * oo SELECTION + * o STANDARDWIDTH + * oo MERGEDCELLS + * o LABELRANGES + * o PHONETICPR + * o Conditional Formatting Table + * o Hyperlink Table + * o Data Validity Table + * o SHEETLAYOUT + * o SHEETPROTECTION + * o RANGEPROTECTION + * + EOF + */ + private static int findDataValidationTableInsertPos(List records) { + int i = records.size() - 1; + if (!(records.get(i) instanceof EOFRecord)) { + throw new IllegalStateException("Last sheet record should be EOFRecord"); + } + while (i > 0) { + i--; + Object rb = records.get(i); + if (isDVTPriorRecord(rb)) { + Record nextRec = (Record) records.get(i + 1); + if (!isDVTSubsequentRecord(nextRec.getSid())) { + throw new IllegalStateException("Unexpected (" + nextRec.getClass().getName() + + ") found after (" + rb.getClass().getName() + ")"); + } + return i+1; + } + Record rec = (Record) rb; + if (!isDVTSubsequentRecord(rec.getSid())) { + throw new IllegalStateException("Unexpected (" + rec.getClass().getName() + + ") while looking for DV Table insert pos"); + } + } + return 0; + } + + + private static boolean isDVTPriorRecord(Object rb) { + if (rb instanceof MergedCellsTable || rb instanceof ConditionalFormattingTable) { + return true; + } + short sid = ((Record)rb).getSid(); + switch(sid) { + case WindowTwoRecord.sid: + case 0x00A0: // SCL + case PaneRecord.sid: + case SelectionRecord.sid: + case 0x0099: // STANDARDWIDTH + // MergedCellsTable + case 0x015F: // LABELRANGES + case 0x00EF: // PHONETICPR + // ConditionalFormattingTable + case HyperlinkRecord.sid: + case 0x0800: // QUICKTIP + return true; + } + return false; + } + + private static boolean isDVTSubsequentRecord(short sid) { + switch(sid) { + case 0x0862: // SHEETLAYOUT + case 0x0867: // SHEETPROTECTION + case 0x0868: // RANGEPROTECTION + case EOFRecord.sid: + return true; + } + return false; + } + /** + * DIMENSIONS record is always present + */ + private static int getDimensionsIndex(List records) { + int nRecs = records.size(); + for(int i=0; i<nRecs; i++) { + if(records.get(i) instanceof DimensionsRecord) { + return i; + } + } + // worksheet stream is seriously broken + throw new RuntimeException("DimensionsRecord not found"); + } + + private static int getGutsRecordInsertPos(List records) { + int dimensionsIndex = getDimensionsIndex(records); + int i = dimensionsIndex-1; + while (i > 0) { + i--; + Object rb = records.get(i); + if (isGutsPriorRecord(rb)) { + return i+1; + } + } + throw new RuntimeException("Did not find insert point for GUTS"); + } + + private static boolean isGutsPriorRecord(Object rb) { + if (rb instanceof Record) { + Record record = (Record) rb; + switch (record.getSid()) { + case BOFRecord.sid: + case IndexRecord.sid: + // calc settings block + case UncalcedRecord.sid: + case CalcCountRecord.sid: + case CalcModeRecord.sid: + case PrecisionRecord.sid: + case RefModeRecord.sid: + case DeltaRecord.sid: + case IterationRecord.sid: + case DateWindow1904Record.sid: + case SaveRecalcRecord.sid: + // end calc settings + case PrintHeadersRecord.sid: + case PrintGridlinesRecord.sid: + case GridsetRecord.sid: + return true; + // DefaultRowHeightRecord.sid is next + } + } + return false; + } +} diff --git a/src/java/org/apache/poi/hssf/model/Sheet.java b/src/java/org/apache/poi/hssf/model/Sheet.java index 993dfd7b31..3d74c9f889 100644 --- a/src/java/org/apache/poi/hssf/model/Sheet.java +++ b/src/java/org/apache/poi/hssf/model/Sheet.java @@ -17,23 +17,78 @@ package org.apache.poi.hssf.model; -import org.apache.poi.hssf.record.*; // normally I don't do this, buy we literally mean ALL +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.apache.poi.hssf.record.BOFRecord; +import org.apache.poi.hssf.record.BottomMarginRecord; +import org.apache.poi.hssf.record.CFHeaderRecord; +import org.apache.poi.hssf.record.CalcCountRecord; +import org.apache.poi.hssf.record.CalcModeRecord; +import org.apache.poi.hssf.record.CellValueRecordInterface; +import org.apache.poi.hssf.record.ColumnInfoRecord; +import org.apache.poi.hssf.record.DBCellRecord; +import org.apache.poi.hssf.record.DVALRecord; +import org.apache.poi.hssf.record.DefaultColWidthRecord; +import org.apache.poi.hssf.record.DefaultRowHeightRecord; +import org.apache.poi.hssf.record.DeltaRecord; +import org.apache.poi.hssf.record.DimensionsRecord; +import org.apache.poi.hssf.record.DrawingRecord; +import org.apache.poi.hssf.record.EOFRecord; +import org.apache.poi.hssf.record.EscherAggregate; +import org.apache.poi.hssf.record.FooterRecord; +import org.apache.poi.hssf.record.GridsetRecord; +import org.apache.poi.hssf.record.GutsRecord; +import org.apache.poi.hssf.record.HCenterRecord; +import org.apache.poi.hssf.record.HeaderRecord; +import org.apache.poi.hssf.record.HorizontalPageBreakRecord; +import org.apache.poi.hssf.record.IndexRecord; +import org.apache.poi.hssf.record.IterationRecord; +import org.apache.poi.hssf.record.LeftMarginRecord; +import org.apache.poi.hssf.record.Margin; +import org.apache.poi.hssf.record.MergeCellsRecord; +import org.apache.poi.hssf.record.ObjRecord; +import org.apache.poi.hssf.record.ObjectProtectRecord; +import org.apache.poi.hssf.record.PageBreakRecord; +import org.apache.poi.hssf.record.PaneRecord; +import org.apache.poi.hssf.record.PasswordRecord; +import org.apache.poi.hssf.record.PrintGridlinesRecord; +import org.apache.poi.hssf.record.PrintHeadersRecord; +import org.apache.poi.hssf.record.PrintSetupRecord; +import org.apache.poi.hssf.record.ProtectRecord; +import org.apache.poi.hssf.record.Record; +import org.apache.poi.hssf.record.RecordBase; +import org.apache.poi.hssf.record.RefModeRecord; +import org.apache.poi.hssf.record.RightMarginRecord; +import org.apache.poi.hssf.record.RowRecord; +import org.apache.poi.hssf.record.SCLRecord; +import org.apache.poi.hssf.record.SaveRecalcRecord; +import org.apache.poi.hssf.record.ScenarioProtectRecord; +import org.apache.poi.hssf.record.SelectionRecord; +import org.apache.poi.hssf.record.SharedFormulaRecord; +import org.apache.poi.hssf.record.StringRecord; +import org.apache.poi.hssf.record.TopMarginRecord; +import org.apache.poi.hssf.record.UncalcedRecord; +import org.apache.poi.hssf.record.VCenterRecord; +import org.apache.poi.hssf.record.VerticalPageBreakRecord; +import org.apache.poi.hssf.record.WSBoolRecord; +import org.apache.poi.hssf.record.WindowTwoRecord; +import org.apache.poi.hssf.record.aggregates.CFRecordsAggregate; import org.apache.poi.hssf.record.aggregates.ColumnInfoRecordsAggregate; +import org.apache.poi.hssf.record.aggregates.ConditionalFormattingTable; import org.apache.poi.hssf.record.aggregates.DataValidityTable; import org.apache.poi.hssf.record.aggregates.FormulaRecordAggregate; +import org.apache.poi.hssf.record.aggregates.MergedCellsTable; +import org.apache.poi.hssf.record.aggregates.RecordAggregate; import org.apache.poi.hssf.record.aggregates.RowRecordsAggregate; import org.apache.poi.hssf.record.aggregates.ValueRecordsAggregate; -import org.apache.poi.hssf.record.aggregates.CFRecordsAggregate; +import org.apache.poi.hssf.record.aggregates.RecordAggregate.RecordVisitor; import org.apache.poi.hssf.util.CellRangeAddress; import org.apache.poi.hssf.util.PaneInformation; - import org.apache.poi.util.POILogFactory; import org.apache.poi.util.POILogger; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; - /** * Low level model implementation of a Sheet (one workbook contains many sheets) * This file contains the low level binary records starting at the sheets BOF and @@ -72,30 +127,30 @@ public final class Sheet implements Model { protected DefaultColWidthRecord defaultcolwidth = null; protected DefaultRowHeightRecord defaultrowheight = null; protected GridsetRecord gridset = null; + private GutsRecord _gutsRecord; protected PrintSetupRecord printSetup = null; protected HeaderRecord header = null; protected FooterRecord footer = null; protected PrintGridlinesRecord printGridlines = null; protected WindowTwoRecord windowTwo = null; - protected MergeCellsRecord merged = null; protected Margin[] margins = null; - protected List mergedRecords = new ArrayList(); - protected int numMergedRegions = 0; + private MergedCellsTable _mergedCellsTable; protected SelectionRecord selection = null; - protected ColumnInfoRecordsAggregate columns = null; + /** always present in this POI object, not always written to Excel file */ + /*package*/ColumnInfoRecordsAggregate _columnInfos; protected ValueRecordsAggregate cells = null; - protected RowRecordsAggregate rows = null; + protected RowRecordsAggregate _rowsAggregate = null; private Iterator valueRecIterator = null; private Iterator rowRecIterator = null; protected int eofLoc = 0; protected ProtectRecord protect = null; - protected PageBreakRecord rowBreaks = null; - protected PageBreakRecord colBreaks = null; + protected PageBreakRecord _rowBreaksRecord; + protected PageBreakRecord _columnBreaksRecord; private DataValidityTable _dataValidityTable= null; protected ObjectProtectRecord objprotect = null; protected ScenarioProtectRecord scenprotect = null; protected PasswordRecord password = null; - protected List condFormatting = new ArrayList(); + private ConditionalFormattingTable condFormatting; /** Add an UncalcedRecord if not true indicating formulas have not been calculated */ protected boolean _isUncalced = false; @@ -139,13 +194,70 @@ public final class Sheet implements Model { Sheet retval = new Sheet(); ArrayList records = new ArrayList(recs.size() / 5); boolean isfirstcell = true; - boolean isfirstrow = true; int bofEofNestingLevel = 0; for (int k = offset; k < recs.size(); k++) { Record rec = ( Record ) recs.get(k); + if (rec.isValue() != (rec instanceof CellValueRecordInterface)) { + if (rec instanceof SharedFormulaRecord) { + + } else { + "".length(); + } + } + if ( rec.getSid() == DBCellRecord.sid ) { + continue; + } + if ( rec.getSid() == IndexRecord.sid ) { + // ignore INDEX record because it is only needed by Excel, + // and POI always re-calculates its contents + continue; + } + if ( rec.getSid() == StringRecord.sid ) { + continue; + } + + if ( rec.getSid() == CFHeaderRecord.sid ) { + RecordStream rs = new RecordStream(recs, k); + retval.condFormatting = new ConditionalFormattingTable(rs); + k += rs.getCountRead()-1; + records.add(retval.condFormatting); + continue; + } + + if (rec.getSid() == ColumnInfoRecord.sid) { + RecordStream rs = new RecordStream(recs, k); + retval._columnInfos = new ColumnInfoRecordsAggregate(rs); + k += rs.getCountRead()-1; + records.add(retval._columnInfos); + continue; + } + if ( rec.getSid() == DVALRecord.sid) { + RecordStream rs = new RecordStream(recs, k); + retval._dataValidityTable = new DataValidityTable(rs); + k += rs.getCountRead() - 1; // TODO - convert this method result to be zero based + records.add(retval._dataValidityTable); + continue; // TODO + } + if ( rec.getSid() == RowRecord.sid ) + { + RowRecord row = (RowRecord)rec; + if (retval._rowsAggregate == null) { + retval._rowsAggregate = new RowRecordsAggregate(); + records.add(retval._rowsAggregate); //only add the aggregate once + } + retval._rowsAggregate.insertRow(row); + continue; + } + if (rec.getSid() == MergeCellsRecord.sid) { + RecordStream rs = new RecordStream(recs, k); + retval._mergedCellsTable = new MergedCellsTable(rs); + records.add(retval._mergedCellsTable); + continue; // TODO + } + if (rec.getSid() == BOFRecord.sid) { bofEofNestingLevel++; @@ -169,53 +281,15 @@ public final class Sheet implements Model { else if (rec.getSid() == DimensionsRecord.sid) { // Make a columns aggregate if one hasn't ready been created. - if (retval.columns == null) + if (retval._columnInfos == null) { - retval.columns = new ColumnInfoRecordsAggregate(); - records.add(retval.columns); + retval._columnInfos = new ColumnInfoRecordsAggregate(); + records.add(retval._columnInfos); } retval.dims = ( DimensionsRecord ) rec; retval.dimsloc = records.size(); } - else if (rec.getSid() == MergeCellsRecord.sid) - { - retval.mergedRecords.add(rec); - retval.merged = ( MergeCellsRecord ) rec; - retval.numMergedRegions += retval.merged.getNumAreas(); - } - else if ( rec.getSid() == CFHeaderRecord.sid ) - { - CFRecordsAggregate cfAgg = CFRecordsAggregate.createCFAggregate(recs, k); - retval.condFormatting.add(cfAgg); - rec = cfAgg; - } - else if ( rec.getSid() == CFRuleRecord.sid ) - { - // Skip it since it is processed by CFRecordsAggregate - rec = null; - } - else if (rec.getSid() == ColumnInfoRecord.sid) - { - ColumnInfoRecord col = (ColumnInfoRecord)rec; - if (retval.columns != null) - { - rec = null; //only add the aggregate once - } - else - { - rec = retval.columns = new ColumnInfoRecordsAggregate(); - } - retval.columns.insertColumn(col); - } - else if (rec.getSid() == DefaultColWidthRecord.sid) - { - retval.defaultcolwidth = ( DefaultColWidthRecord ) rec; - } - else if (rec.getSid() == DefaultRowHeightRecord.sid) - { - retval.defaultrowheight = ( DefaultRowHeightRecord ) rec; - } else if ( rec.isValue() && bofEofNestingLevel == 1 ) { if ( isfirstcell ) @@ -230,22 +304,13 @@ public final class Sheet implements Model { rec = null; } } - else if ( rec.getSid() == StringRecord.sid ) + else if (rec.getSid() == DefaultColWidthRecord.sid) { - rec = null; + retval.defaultcolwidth = ( DefaultColWidthRecord ) rec; } - else if ( rec.getSid() == RowRecord.sid ) + else if (rec.getSid() == DefaultRowHeightRecord.sid) { - RowRecord row = (RowRecord)rec; - if (!isfirstrow) rec = null; //only add the aggregate once - - if ( isfirstrow ) - { - retval.rows = new RowRecordsAggregate(); - rec = retval.rows; - isfirstrow = false; - } - retval.rows.insertRow(row); + retval.defaultrowheight = ( DefaultRowHeightRecord ) rec; } else if ( rec.getSid() == PrintGridlinesRecord.sid ) { @@ -291,22 +356,6 @@ public final class Sheet implements Model { { retval.windowTwo = (WindowTwoRecord) rec; } - else if ( rec.getSid() == DBCellRecord.sid ) - { - rec = null; - } - else if ( rec.getSid() == IndexRecord.sid ) - { - // ignore INDEX record because it is only needed by Excel, - // and POI always re-calculates its contents - rec = null; - } - else if ( rec.getSid() == DVALRecord.sid) { - RecordStream rs = new RecordStream(recs, k); - retval._dataValidityTable = new DataValidityTable(rs); - k += rs.getCountRead() - 1; // TODO - convert this method result to be zero based - rec = retval._dataValidityTable; - } else if ( rec.getSid() == ProtectRecord.sid ) { retval.protect = (ProtectRecord) rec; @@ -323,13 +372,13 @@ public final class Sheet implements Model { { retval.password = (PasswordRecord) rec; } - else if (rec.getSid() == PageBreakRecord.HORIZONTAL_SID) + else if (rec.getSid() == HorizontalPageBreakRecord.sid) { - retval.rowBreaks = (PageBreakRecord)rec; + retval._rowBreaksRecord = (HorizontalPageBreakRecord)rec; } - else if (rec.getSid() == PageBreakRecord.VERTICAL_SID) + else if (rec.getSid() == VerticalPageBreakRecord.sid) { - retval.colBreaks = (PageBreakRecord)rec; + retval._columnBreaksRecord = (VerticalPageBreakRecord)rec; } if (rec != null) @@ -337,6 +386,9 @@ public final class Sheet implements Model { records.add(rec); } } + if (retval.dimsloc < 0) { + throw new RuntimeException("DimensionsRecord was not found"); + } retval.records = records; retval.checkRows(); retval.checkCells(); @@ -345,6 +397,17 @@ public final class Sheet implements Model { return retval; } + private static final class RecordCloner implements RecordVisitor { + + private final List _destList; + + public RecordCloner(List destList) { + _destList = destList; + } + public void visitRecord(Record r) { + _destList.add(r.clone()); + } + } /** * Clones the low level records of this sheet and returns the new sheet instance. * This method is implemented by adding methods for deep cloning to all records that @@ -356,7 +419,13 @@ public final class Sheet implements Model { { ArrayList clonedRecords = new ArrayList(this.records.size()); for (int i=0; i<this.records.size();i++) { - Record rec = (Record)((Record)this.records.get(i)).clone(); + RecordBase rb = (RecordBase) this.records.get(i); + if (rb instanceof RecordAggregate) { + ((RecordAggregate)rb).visitContainedRecords(new RecordCloner(clonedRecords)); + // TODO - make sure this logic works for the other RecordAggregates + continue; + } + Record rec = (Record)((Record)rb).clone(); //Need to pull out the Row record and the Value records from their //Aggregates. //This is probably the best way to do it since we probably dont want the createSheet @@ -452,10 +521,10 @@ public final class Sheet implements Model { records.add( retval.createWSBool() ); // 'Page Settings Block' - retval.rowBreaks = new PageBreakRecord(PageBreakRecord.HORIZONTAL_SID); - records.add(retval.rowBreaks); - retval.colBreaks = new PageBreakRecord(PageBreakRecord.VERTICAL_SID); - records.add(retval.colBreaks); + retval._rowBreaksRecord = new HorizontalPageBreakRecord(); + records.add(retval._rowBreaksRecord); + retval._columnBreaksRecord = new VerticalPageBreakRecord(); + records.add(retval._columnBreaksRecord); retval.header = createHeader(); records.add( retval.header ); @@ -473,7 +542,7 @@ public final class Sheet implements Model { records.add( retval.defaultcolwidth); ColumnInfoRecordsAggregate columns = new ColumnInfoRecordsAggregate(); records.add( columns ); - retval.columns = columns; + retval._columnInfos = columns; retval.dims = createDimensions(); records.add(retval.dims); retval.dimsloc = records.size()-1; @@ -509,14 +578,23 @@ public final class Sheet implements Model { private void checkRows() { - if (rows == null) + if (_rowsAggregate == null) { - rows = new RowRecordsAggregate(); - records.add(getDimsLoc() + 1, rows); + _rowsAggregate = new RowRecordsAggregate(); + records.add(getDimsLoc() + 1, _rowsAggregate); } } + private MergedCellsTable getMergedRecords() { + if (_mergedCellsTable == null) { + MergedCellsTable mct = new MergedCellsTable(); + RecordOrderer.addNewSheetRecord(records, mct); + _mergedCellsTable = mct; + } + return _mergedCellsTable; + } - public int addMergedRegion(int rowFrom, int colFrom, int rowTo, int colTo) { + + public int addMergedRegion(int rowFrom, int colFrom, int rowTo, int colTo) { // Validate input if (rowTo < rowFrom) { throw new IllegalArgumentException("The 'to' row (" + rowTo @@ -527,159 +605,57 @@ public final class Sheet implements Model { + ") must not be less than the 'from' col (" + colFrom + ")"); } - if (merged == null || merged.getNumAreas() == 1027) - { - merged = createMergedCells(); - mergedRecords.add(merged); - records.add(records.size() - 1, merged); - } - merged.addArea(rowFrom, colFrom, rowTo, colTo); - return numMergedRegions++; + MergedCellsTable mrt = getMergedRecords(); + mrt.addArea(rowFrom, colFrom, rowTo, colTo); + return mrt.getNumberOfMergedRegions()-1; } public void removeMergedRegion(int index) { //safety checks - if (index >= numMergedRegions || mergedRecords.size() == 0) - return; - - int pos = 0; - int startNumRegions = 0; - - //optimisation for current record - if (numMergedRegions - index < merged.getNumAreas()) - { - pos = mergedRecords.size() - 1; - startNumRegions = numMergedRegions - merged.getNumAreas(); - } - else - { - for (int n = 0; n < mergedRecords.size(); n++) - { - MergeCellsRecord record = (MergeCellsRecord) mergedRecords.get(n); - if (startNumRegions + record.getNumAreas() > index) - { - pos = n; - break; - } - startNumRegions += record.getNumAreas(); - } - } - - MergeCellsRecord rec = (MergeCellsRecord) mergedRecords.get(pos); - rec.removeAreaAt(index - startNumRegions); - numMergedRegions--; - if (rec.getNumAreas() == 0) - { - mergedRecords.remove(pos); - //get rid of the record from the sheet - records.remove(merged); - if (merged == rec) { - //pull up the LAST record for operations when we finally - //support continue records for mergedRegions - if (mergedRecords.size() > 0) { - merged = (MergeCellsRecord) mergedRecords.get(mergedRecords.size() - 1); - } else { - merged = null; - } - } - } + MergedCellsTable mrt = getMergedRecords(); + if (index >= mrt.getNumberOfMergedRegions()) { + return; + } + mrt.remove(index); } - public CellRangeAddress getMergedRegionAt(int index) - { + public CellRangeAddress getMergedRegionAt(int index) { //safety checks - if (index >= numMergedRegions || mergedRecords.size() == 0) - return null; - - int pos = 0; - int startNumRegions = 0; - - //optimisation for current record - if (numMergedRegions - index < merged.getNumAreas()) - { - pos = mergedRecords.size() - 1; - startNumRegions = numMergedRegions - merged.getNumAreas(); - } - else - { - for (int n = 0; n < mergedRecords.size(); n++) - { - MergeCellsRecord record = (MergeCellsRecord) mergedRecords.get(n); - if (startNumRegions + record.getNumAreas() > index) - { - pos = n; - break; - } - startNumRegions += record.getNumAreas(); - } - } - return ((MergeCellsRecord) mergedRecords.get(pos)).getAreaAt(index - startNumRegions); + MergedCellsTable mrt = getMergedRecords(); + if (index >= mrt.getNumberOfMergedRegions()) { + return null; + } + return mrt.get(index); } - public int getNumMergedRegions() - { - return numMergedRegions; + public int getNumMergedRegions() { + return getMergedRecords().getNumberOfMergedRegions(); + } + private ConditionalFormattingTable getConditionalFormattingTable() { + if (condFormatting == null) { + condFormatting = new ConditionalFormattingTable(); + RecordOrderer.addNewSheetRecord(records, condFormatting); + } + return condFormatting; } - // Find correct position to add new CF record - private int findConditionalFormattingPosition() - { - // This is default. - // If the algorithm does not find the right position, - // this one will be used (this is a position before EOF record) - int index = records.size()-2; - - for( int i=index; i>=0; i-- ) - { - Record rec = (Record)records.get(i); - short sid = rec.getSid(); - - // CFRecordsAggregate records already exist, just add to the end - if (rec instanceof CFRecordsAggregate) { return i+1; } - if( sid == (short)0x00ef ) { return i+1; }// PHONETICPR - if( sid == (short)0x015f ) { return i+1; }// LABELRANGES - if( sid == MergeCellsRecord.sid ) { return i+1; } - if( sid == (short)0x0099 ) { return i+1; }// STANDARDWIDTH - if( sid == SelectionRecord.sid ) { return i+1; } - if( sid == PaneRecord.sid ) { return i+1; } - if( sid == SCLRecord.sid ) { return i+1; } - if( sid == WindowTwoRecord.sid ) { return i+1; } - } - return index; + public int addConditionalFormatting(CFRecordsAggregate cfAggregate) { + ConditionalFormattingTable cft = getConditionalFormattingTable(); + return cft.add(cfAggregate); } - public int addConditionalFormatting(CFRecordsAggregate cfAggregate) - { - int index = findConditionalFormattingPosition(); - records.add(index, cfAggregate); - condFormatting.add(cfAggregate); - return condFormatting.size()-1; + public void removeConditionalFormatting(int index) { + getConditionalFormattingTable().remove(index); } - public void removeConditionalFormatting(int index) - { - if (index >= 0 && index <= condFormatting.size()-1 ) - { - CFRecordsAggregate cfAggregate = getCFRecordsAggregateAt(index); - records.remove(cfAggregate); - condFormatting.remove(index); - } - } - - public CFRecordsAggregate getCFRecordsAggregateAt(int index) - { - if (index >= 0 && index <= condFormatting.size()-1 ) - { - return (CFRecordsAggregate) condFormatting.get(index); - } - return null; + public CFRecordsAggregate getCFRecordsAggregateAt(int index) { + return getConditionalFormattingTable().get(index); } - public int getNumConditionalFormattings() - { - return condFormatting.size(); + public int getNumConditionalFormattings() { + return getConditionalFormattingTable().size(); } /** @@ -699,13 +675,13 @@ public final class Sheet implements Model { log.logFormatted(POILogger.DEBUG, "returning % + % + % - 2 = %", new int[] { records.size(), cells.getPhysicalNumberOfCells(), - rows.getPhysicalNumberOfRows(), + _rowsAggregate.getPhysicalNumberOfRows(), records.size() + cells.getPhysicalNumberOfCells() - + rows.getPhysicalNumberOfRows() - 2 + + _rowsAggregate.getPhysicalNumberOfRows() - 2 }); } return records.size() + cells.getPhysicalNumberOfCells() - + rows.getPhysicalNumberOfRows() - 2; + + _rowsAggregate.getPhysicalNumberOfRows() - 2; } /** @@ -814,7 +790,7 @@ public final class Sheet implements Model { for (int k = 0; k < records.size(); k++) { - Record record = (( Record ) records.get(k)); + RecordBase record = (RecordBase) records.get(k); // Don't write out UncalcedRecord entries, as // we handle those specially just below @@ -833,7 +809,7 @@ public final class Sheet implements Model { } // If the BOF record was just serialized then add the IndexRecord - if (record.getSid() == BOFRecord.sid) { + if (record instanceof BOFRecord) { if (!haveSerializedIndex) { haveSerializedIndex = true; // Add an optional UncalcedRecord. However, we should add @@ -846,7 +822,7 @@ public final class Sheet implements Model { } //Can there be more than one BOF for a sheet? If not then we can //remove this guard. So be safe it is left here. - if (rows != null) { + if (_rowsAggregate != null) { pos += serializeIndexRecord(k, pos, data); } } @@ -865,8 +841,8 @@ public final class Sheet implements Model { private int serializeIndexRecord(final int bofRecordIndex, final int indexRecordOffset, byte[] data) { IndexRecord index = new IndexRecord(); - index.setFirstRow(rows.getFirstRowNum()); - index.setLastRowAdd1(rows.getLastRowNum() + 1); + index.setFirstRow(_rowsAggregate.getFirstRowNum()); + index.setLastRowAdd1(_rowsAggregate.getLastRowNum() + 1); // Calculate the size of the records from the end of the BOF // and up to the RowRecordsAggregate... @@ -874,7 +850,7 @@ public final class Sheet implements Model { int sizeOfInitialSheetRecords = 0; // start just after BOF record (INDEX is not present in this list) for (int j = bofRecordIndex + 1; j < records.size(); j++) { - Record tmpRec = ((Record) records.get(j)); + RecordBase tmpRec = ((RecordBase) records.get(j)); if (tmpRec instanceof UncalcedRecord) { continue; } @@ -891,7 +867,7 @@ public final class Sheet implements Model { // Note: The offsets are relative to the Workbook BOF. Assume that this is // 0 for now..... - int blockCount = rows.getRowBlockCount(); + int blockCount = _rowsAggregate.getRowBlockCount(); // Calculate the size of this IndexRecord int indexRecSize = IndexRecord.getRecordSizeForBlockCount(blockCount); @@ -902,15 +878,15 @@ public final class Sheet implements Model { // The offset of each DBCELL record needs to be updated in the INDEX record // account for row records in this row-block - currentOffset += rows.getRowBlockSize(block); + currentOffset += _rowsAggregate.getRowBlockSize(block); // account for cell value records after those - currentOffset += null == cells ? 0 : cells.getRowCellBlockSize(rows - .getStartRowNumberForBlock(block), rows.getEndRowNumberForBlock(block)); + currentOffset += null == cells ? 0 : cells.getRowCellBlockSize(_rowsAggregate + .getStartRowNumberForBlock(block), _rowsAggregate.getEndRowNumberForBlock(block)); // currentOffset is now the location of the DBCELL record for this row-block index.addDbcell(currentOffset); // Add space required to write the DBCELL record (whose reference was just added). - currentOffset += (8 + (rows.getRowCountForBlock(block) * 2)); + currentOffset += (8 + (_rowsAggregate.getRowCountForBlock(block) * 2)); } return index.serialize(indexRecordOffset, data); } @@ -1031,11 +1007,11 @@ public final class Sheet implements Model { } //IndexRecord index = null; //If the row exists remove it, so that any cells attached to the row are removed - RowRecord existingRow = rows.getRow(row.getRowNumber()); + RowRecord existingRow = _rowsAggregate.getRow(row.getRowNumber()); if (existingRow != null) - rows.removeRow(existingRow); + _rowsAggregate.removeRow(existingRow); - rows.insertRow(row); + _rowsAggregate.insertRow(row); if (log.check( POILogger.DEBUG )) log.log(POILogger.DEBUG, "exit addRow"); @@ -1054,7 +1030,7 @@ public final class Sheet implements Model { checkRows(); setLoc(getDimsLoc()); - rows.removeRow(row); + _rowsAggregate.removeRow(row); } /** @@ -1108,7 +1084,7 @@ public final class Sheet implements Model { log.log(POILogger.DEBUG, "getNextRow loc= " + loc); if (rowRecIterator == null) { - rowRecIterator = rows.getIterator(); + rowRecIterator = _rowsAggregate.getIterator(); } if (!rowRecIterator.hasNext()) { @@ -1136,7 +1112,7 @@ public final class Sheet implements Model { public RowRecord getRow(int rownum) { if (log.check( POILogger.DEBUG )) log.log(POILogger.DEBUG, "getNextRow loc= " + loc); - return rows.getRow(rownum); + return _rowsAggregate.getRow(rownum); } /** @@ -1258,6 +1234,15 @@ public final class Sheet implements Model { retval.setColLevelMax(( short ) 0); return retval; } + private GutsRecord getGutsRecord() { + if (_gutsRecord == null) { + GutsRecord result = createGuts(); + RecordOrderer.addNewSheetRecord(records, result); + _gutsRecord = result; + } + + return _gutsRecord; + } /** * creates the DefaultRowHeight Record and sets its options to 0 and rowheight to 0xff @@ -1422,43 +1407,22 @@ public final class Sheet implements Model { /** * get the width of a given column in units of 1/256th of a character width - * @param column index + * @param columnIndex index * @see org.apache.poi.hssf.record.DefaultColWidthRecord * @see org.apache.poi.hssf.record.ColumnInfoRecord * @see #setColumnWidth(short,short) * @return column width in units of 1/256th of a character width */ - public short getColumnWidth(short column) - { - short retval = 0; - ColumnInfoRecord ci = null; + public short getColumnWidth(short columnIndex) { - if (columns != null) - { - int count=columns.getNumColumns(); - for ( int k=0;k<count;k++ ) - { - ci = columns.getColInfo(k); - if ((ci.getFirstColumn() <= column) - && (column <= ci.getLastColumn())) - { - break; - } - ci = null; - } + ColumnInfoRecord ci = _columnInfos.findColumnInfo(columnIndex); + if (ci != null) { + return ci.getColumnWidth(); } - if (ci != null) - { - retval = ci.getColumnWidth(); - } - else - { - //default column width is measured in characters - //multiply - retval = (short)(256*defaultcolwidth.getColWidth()); - } - return retval; + //default column width is measured in characters + //multiply + return (short)(256*defaultcolwidth.getColWidth()); } /** @@ -1470,37 +1434,28 @@ public final class Sheet implements Model { * Returns the index to the default ExtendedFormatRecord (0xF) * if no ColumnInfoRecord exists that includes the column * index specified. - * @param column + * @param columnIndex * @return index of ExtendedFormatRecord associated with * ColumnInfoRecord that includes the column index or the * index of the default ExtendedFormatRecord (0xF) */ - public short getXFIndexForColAt(short column) { - short retval = 0; - ColumnInfoRecord ci = null; - if (columns != null) { - int count=columns.getNumColumns(); - for ( int k=0;k<count;k++ ) - { - ci = columns.getColInfo(k); - if ((ci.getFirstColumn() <= column) - && (column <= ci.getLastColumn())) { - break; - } - ci = null; - } - } - retval = (ci != null) ? ci.getXFIndex() : 0xF; - return retval; - } - - /** - * set the width for a given column in 1/256th of a character width units - * @param column - the column number - * @param width (in units of 1/256th of a character width) - */ - public void setColumnWidth(short column, short width) - { + public short getXFIndexForColAt(short columnIndex) { + ColumnInfoRecord ci = _columnInfos.findColumnInfo(columnIndex); + if (ci != null) { + return ci.getXFIndex(); + } + return 0xF; + } + + /** + * set the width for a given column in 1/256th of a character width units + * + * @param column - + * the column number + * @param width + * (in units of 1/256th of a character width) + */ + public void setColumnWidth(short column, short width) { setColumn( column, new Short(width), null, null, null); } @@ -1512,30 +1467,12 @@ public final class Sheet implements Model { * @see #setColumnHidden(short,boolean) * @return whether the column is hidden or not. */ - - public boolean isColumnHidden(short column) - { - boolean retval = false; - ColumnInfoRecord ci = null; - - if (columns != null) - { - for ( Iterator iterator = columns.getIterator(); iterator.hasNext(); ) - { - ci = ( ColumnInfoRecord ) iterator.next(); - if ((ci.getFirstColumn() <= column) - && (column <= ci.getLastColumn())) - { - break; - } - ci = null; - } - } - if (ci != null) - { - retval = ci.getHidden(); - } - return retval; + public boolean isColumnHidden(short columnIndex) { + ColumnInfoRecord cir = _columnInfos.findColumnInfo(columnIndex); + if (cir == null) { + return false; + } + return cir.getHidden(); } /** @@ -1548,20 +1485,12 @@ public final class Sheet implements Model { setColumn( column, null, null, new Boolean(hidden), null); } - public void setColumn(short column, Short width, Integer level, Boolean hidden, Boolean collapsed) - { - if (columns == null) - columns = new ColumnInfoRecordsAggregate(); - - columns.setColumn( column, null, width, level, hidden, collapsed ); + public void setColumn(short column, Short width, Integer level, Boolean hidden, Boolean collapsed) { + _columnInfos.setColumn( column, null, width, level, hidden, collapsed ); } - public void setColumn(short column, Short xfStyle, Short width, Integer level, Boolean hidden, Boolean collapsed) - { - if (columns == null) - columns = new ColumnInfoRecordsAggregate(); - - columns.setColumn( column, xfStyle, width, level, hidden, collapsed ); + public void setColumn(short column, Short xfStyle, Short width, Integer level, Boolean hidden, Boolean collapsed) { + _columnInfos.setColumn( column, xfStyle, width, level, hidden, collapsed ); } @@ -1576,18 +1505,12 @@ public final class Sheet implements Model { { // Set the level for each column - columns.groupColumnRange( fromColumn, toColumn, indent); + _columnInfos.groupColumnRange( fromColumn, toColumn, indent); // Determine the maximum overall level - int maxLevel = 0; - int count=columns.getNumColumns(); - for ( int k=0;k<count;k++ ) - { - ColumnInfoRecord columnInfoRecord = columns.getColInfo(k); - maxLevel = Math.max(columnInfoRecord.getOutlineLevel(), maxLevel); - } + int maxLevel = _columnInfos.getMaxOutlineLevel(); - GutsRecord guts = (GutsRecord) findFirstRecordBySid( GutsRecord.sid ); + GutsRecord guts = getGutsRecord(); guts.setColLevelMax( (short) ( maxLevel+1 ) ); if (maxLevel == 0) { guts.setTopColGutter( (short)0 ); @@ -1721,10 +1644,6 @@ public final class Sheet implements Model { } } - private static MergeCellsRecord createMergedCells() { - return new MergeCellsRecord(); - } - /** * get the location of the DimensionsRecord (which is the last record before the value section) * @return location in the array of records of the DimensionsRecord @@ -1750,24 +1669,27 @@ public final class Sheet implements Model { } } + /** + * @return the serialized size of this sheet + */ public int getSize() { int retval = 0; for ( int k = 0; k < records.size(); k++) { - Record record = (Record) records.get(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(); } - if (rows != null) { + if (_rowsAggregate != null) { // Add space for the IndexRecord and DBCell records - final int nBlocks = rows.getRowBlockCount(); + final int nBlocks = _rowsAggregate.getRowBlockCount(); int nRows = 0; if (cells != null) { - for (Iterator itr = rows.getIterator(); itr.hasNext();) { + for (Iterator itr = _rowsAggregate.getIterator(); itr.hasNext();) { RowRecord row = (RowRecord)itr.next(); if (cells.rowHasCells(row.getRowNumber())) { nRows++; @@ -1804,16 +1726,11 @@ public final class Sheet implements Model { public Record findFirstRecordBySid(short sid) { - for (Iterator iterator = records.iterator(); iterator.hasNext(); ) - { - Record record = ( Record ) iterator.next(); - - if (record.getSid() == sid) - { - return record; - } - } - return null; + int ix = findFirstRecordLocBySid(sid); + if (ix < 0) { + return null; + } + return (Record) records.get(ix); } /** @@ -1839,24 +1756,23 @@ public final class Sheet implements Model { } /** - * Finds the first occurance of a record matching a particular sid and + * Finds the first occurrence of a record matching a particular sid and * returns it's position. * @param sid the sid to search for * @return the record position of the matching record or -1 if no match * is made. */ - public int findFirstRecordLocBySid( short sid ) - { - int index = 0; - for (Iterator iterator = records.iterator(); iterator.hasNext(); ) - { - Record record = ( Record ) iterator.next(); - - if (record.getSid() == sid) - { - return index; + public int findFirstRecordLocBySid( short sid ) { // TODO - remove this method + int max = records.size(); + for (int i=0; i< max; i++) { + Object rb = records.get(i); + if (!(rb instanceof Record)) { + continue; + } + Record record = (Record) rb; + if (record.getSid() == sid) { + return i; } - index++; } return -1; } @@ -2324,7 +2240,7 @@ public final class Sheet implements Model { { for ( Iterator iterator = getRecords().iterator(); iterator.hasNext(); ) { - Record r = (Record) iterator.next(); + RecordBase r = (RecordBase) iterator.next(); if (r instanceof EscherAggregate) r.getRecordSize(); // Trigger flatterning of user model and corresponding update of dgg record. } @@ -2337,16 +2253,14 @@ public final class Sheet implements Model { * @param stop Ending "main" value to shift breaks * @param count number of units (rows/columns) to shift by */ - public void shiftBreaks(PageBreakRecord breaks, short start, short stop, int count) { + private static void shiftBreaks(PageBreakRecord breaks, int start, int stop, int count) { - if(rowBreaks == null) - return; Iterator iterator = breaks.getBreaksIterator(); List shiftedBreak = new ArrayList(); while(iterator.hasNext()) { PageBreakRecord.Break breakItem = (PageBreakRecord.Break)iterator.next(); - short breakLocation = breakItem.main; + int breakLocation = breakItem.main; boolean inStart = (breakLocation >= start); boolean inEnd = (breakLocation <= stop); if(inStart && inEnd) @@ -2361,17 +2275,31 @@ public final class Sheet implements Model { } } + private PageBreakRecord getRowBreaksRecord() { + if (_rowBreaksRecord == null) { + _rowBreaksRecord = new HorizontalPageBreakRecord(); + RecordOrderer.addNewSheetRecord(records, _rowBreaksRecord); + dimsloc++; + } + return _rowBreaksRecord; + } + + private PageBreakRecord getColumnBreaksRecord() { + if (_columnBreaksRecord == null) { + _columnBreaksRecord = new VerticalPageBreakRecord(); + RecordOrderer.addNewSheetRecord(records, _columnBreaksRecord); + dimsloc++; + } + return _columnBreaksRecord; + } + + /** * Sets a page break at the indicated row * @param row */ public void setRowBreak(int row, short fromCol, short toCol) { - if (rowBreaks == null) { - int loc = findFirstRecordLocBySid(WindowTwoRecord.sid); - rowBreaks = new PageBreakRecord(PageBreakRecord.HORIZONTAL_SID); - records.add(loc, rowBreaks); - } - rowBreaks.addBreak((short)row, fromCol, toCol); + getRowBreaksRecord().addBreak((short)row, fromCol, toCol); } /** @@ -2379,9 +2307,9 @@ public final class Sheet implements Model { * @param row */ public void removeRowBreak(int row) { - if (rowBreaks == null) + if (getRowBreaks() == null) throw new IllegalArgumentException("Sheet does not define any row breaks"); - rowBreaks.removeBreak((short)row); + getRowBreaksRecord().removeBreak((short)row); } /** @@ -2390,7 +2318,7 @@ public final class Sheet implements Model { * @return true if the specified row has a page break */ public boolean isRowBroken(int row) { - return (rowBreaks == null) ? false : rowBreaks.getBreak((short)row) != null; + return getRowBreaksRecord().getBreak(row) != null; } /** @@ -2398,12 +2326,7 @@ public final class Sheet implements Model { * */ public void setColumnBreak(short column, short fromRow, short toRow) { - if (colBreaks == null) { - int loc = findFirstRecordLocBySid(WindowTwoRecord.sid); - colBreaks = new PageBreakRecord(PageBreakRecord.VERTICAL_SID); - records.add(loc, colBreaks); - } - colBreaks.addBreak(column, fromRow, toRow); + getColumnBreaksRecord().addBreak(column, fromRow, toRow); } /** @@ -2411,19 +2334,16 @@ public final class Sheet implements Model { * */ public void removeColumnBreak(short column) { - if (colBreaks == null) - throw new IllegalArgumentException("Sheet does not define any column breaks"); - - colBreaks.removeBreak(column); + getColumnBreaksRecord().removeBreak(column); } /** * Queries if the specified column has a page break * - * @return true if the specified column has a page break + * @return <code>true</code> if the specified column has a page break */ public boolean isColumnBroken(short column) { - return (colBreaks == null) ? false : colBreaks.getBreak(column) != null; + return getColumnBreaksRecord().getBreak(column) != null; } /** @@ -2433,7 +2353,7 @@ public final class Sheet implements Model { * @param count */ public void shiftRowBreaks(int startingRow, int endingRow, int count) { - shiftBreaks(rowBreaks, (short)startingRow, (short)endingRow, (short)count); + shiftBreaks(getRowBreaksRecord(), startingRow, endingRow, count); } /** @@ -2443,50 +2363,46 @@ public final class Sheet implements Model { * @param count */ public void shiftColumnBreaks(short startingCol, short endingCol, short count) { - shiftBreaks(colBreaks, startingCol, endingCol, count); + shiftBreaks(getColumnBreaksRecord(), startingCol, endingCol, count); } /** - * Returns all the row page breaks - * @return all the row page breaks + * @return all the horizontal page breaks, never <code>null</code> */ - public Iterator getRowBreaks() { - return rowBreaks.getBreaksIterator(); + public int[] getRowBreaks() { + return getRowBreaksRecord().getBreaks(); } /** - * Returns the number of row page breaks * @return the number of row page breaks */ public int getNumRowBreaks(){ - return (rowBreaks == null) ? 0 : (int)rowBreaks.getNumBreaks(); + return getRowBreaksRecord().getNumBreaks(); } /** - * Returns all the column page breaks - * @return all the column page breaks + * @return all the column page breaks, never <code>null</code> */ - public Iterator getColumnBreaks(){ - return colBreaks.getBreaksIterator(); + public int[] getColumnBreaks(){ + return getColumnBreaksRecord().getBreaks(); } /** - * Returns the number of column page breaks * @return the number of column page breaks */ public int getNumColumnBreaks(){ - return (colBreaks == null) ? 0 : (int)colBreaks.getNumBreaks(); + return getColumnBreaksRecord().getNumBreaks(); } public void setColumnGroupCollapsed( short columnNumber, boolean collapsed ) { if (collapsed) { - columns.collapseColumn( columnNumber ); + _columnInfos.collapseColumn( columnNumber ); } else { - columns.expandColumn( columnNumber ); + _columnInfos.expandColumn( columnNumber ); } } @@ -2577,7 +2493,7 @@ public final class Sheet implements Model { private void recalcRowGutter() { int maxLevel = 0; - Iterator iterator = rows.getIterator(); + Iterator iterator = _rowsAggregate.getIterator(); while ( iterator.hasNext() ) { RowRecord rowRecord = (RowRecord) iterator.next(); @@ -2585,11 +2501,7 @@ public final class Sheet implements Model { } // Grab the guts record, adding if needed - GutsRecord guts = (GutsRecord) findFirstRecordBySid( GutsRecord.sid ); - if(guts == null) { - guts = new GutsRecord(); - records.add(guts); - } + GutsRecord guts = getGutsRecord(); // Set the levels onto it guts.setRowLevelMax( (short) ( maxLevel + 1 ) ); guts.setLeftRowGutter( (short) ( 29 + (12 * (maxLevel)) ) ); @@ -2599,16 +2511,18 @@ public final class Sheet implements Model { { if (collapse) { - rows.collapseRow( row ); + _rowsAggregate.collapseRow( row ); } else { - rows.expandRow( row ); + _rowsAggregate.expandRow( row ); } } public DataValidityTable getOrCreateDataValidityTable() { if (_dataValidityTable == null) { - _dataValidityTable = DataValidityTable.createForSheet(records); + DataValidityTable result = new DataValidityTable(); + RecordOrderer.addNewSheetRecord(records, result); + _dataValidityTable = result; } return _dataValidityTable; } diff --git a/src/java/org/apache/poi/hssf/record/HorizontalPageBreakRecord.java b/src/java/org/apache/poi/hssf/record/HorizontalPageBreakRecord.java index 05b642a44d..a6846300cb 100644 --- a/src/java/org/apache/poi/hssf/record/HorizontalPageBreakRecord.java +++ b/src/java/org/apache/poi/hssf/record/HorizontalPageBreakRecord.java @@ -1,60 +1,67 @@ - /* ==================================================================== - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -==================================================================== */ - + 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 java.util.Iterator; + /** - * HorizontalPageBreak record that stores page breaks at rows - * <p> - * This class is just used so that SID compares work properly in the RecordFactory + * HorizontalPageBreak (0x001B) record that stores page breaks at rows <p/> + * * @see PageBreakRecord - * @author Danny Mui (dmui at apache dot org) + * @author Danny Mui (dmui at apache dot org) */ -public class HorizontalPageBreakRecord extends PageBreakRecord { +public final class HorizontalPageBreakRecord extends PageBreakRecord { + + public static final short sid = 0x001B; - public static final short sid = PageBreakRecord.HORIZONTAL_SID; - /** - * + * Creates an empty horizontal page break record */ public HorizontalPageBreakRecord() { - super(); + // } /** - * @param sid - */ - public HorizontalPageBreakRecord(short sid) { - super(sid); - } - - /** - * @param in the RecordInputstream to read the record from + * @param in + * the RecordInputstream to read the record from */ public HorizontalPageBreakRecord(RecordInputStream in) { super(in); } - /* (non-Javadoc) - * @see org.apache.poi.hssf.record.Record#getSid() - */ + protected void validateSid(short id) { + if (id != getSid()) { + throw new RecordFormatException( + "NOT A HorizontalPageBreak or VerticalPageBreak RECORD!! " + id); + } + } + public short getSid() { return sid; } + public Object clone() { + PageBreakRecord result = new HorizontalPageBreakRecord(); + Iterator iterator = getBreaksIterator(); + while (iterator.hasNext()) { + Break original = (Break) iterator.next(); + result.addBreak(original.main, original.subFrom, original.subTo); + } + return result; + } } diff --git a/src/java/org/apache/poi/hssf/record/MergeCellsRecord.java b/src/java/org/apache/poi/hssf/record/MergeCellsRecord.java index bf41d2fc91..27ebbd118e 100644 --- a/src/java/org/apache/poi/hssf/record/MergeCellsRecord.java +++ b/src/java/org/apache/poi/hssf/record/MergeCellsRecord.java @@ -32,68 +32,51 @@ import org.apache.poi.util.LittleEndian; */ public final class MergeCellsRecord extends Record { public final static short sid = 0x00E5; - private CellRangeAddressList _regions; - - /** - * Creates an empty <tt>MergedCellsRecord</tt> - */ - public MergeCellsRecord() { - _regions = new CellRangeAddressList(); + /** sometimes the regions array is shared with other MergedCellsRecords */ + private CellRangeAddress[] _regions; + private final int _startIndex; + private final int _numberOfRegions; + + public MergeCellsRecord(CellRangeAddress[] regions, int startIndex, int numberOfRegions) { + _regions = regions; + _startIndex = startIndex; + _numberOfRegions = numberOfRegions; } - /** * Constructs a MergedCellsRecord and sets its fields appropriately * @param in the RecordInputstream to read the record from */ public MergeCellsRecord(RecordInputStream in) { - super(in); + int nRegions = in.readUShort(); + CellRangeAddress[] cras = new CellRangeAddress[nRegions]; + for (int i = 0; i < nRegions; i++) { + cras[i] = new CellRangeAddress(in); + } + _numberOfRegions = nRegions; + _startIndex = 0; + _regions = cras; } - protected void fillFields(RecordInputStream in) { - _regions = new CellRangeAddressList(in); + throw new RuntimeException("obsolete"); } - /** * get the number of merged areas. If this drops down to 0 you should just go * ahead and delete the record. * @return number of areas */ public short getNumAreas() { - return (short)_regions.countRanges(); - } - - /** - * Add an area to consider a merged cell. The index returned is only gauranteed to - * be correct provided you do not add ahead of or remove ahead of it (in which case - * you should increment or decrement appropriately....in other words its an arrayList) - * - * @param firstRow - the upper left hand corner's row - * @param firstCol - the upper left hand corner's col - * @param lastRow - the lower right hand corner's row - * @param lastCol - the lower right hand corner's col - * @return new index of said area (don't depend on it if you add/remove) - */ - public void addArea(int firstRow, int firstCol, int lastRow, int lastCol) { - _regions.addCellRangeAddress(firstRow, firstCol, lastRow, lastCol); - } - - /** - * essentially unmerge the cells in the "area" stored at the passed in index - * @param areaIndex - */ - public void removeAreaAt(int areaIndex) { - _regions.remove(areaIndex); + return (short)_numberOfRegions; } /** * @return MergedRegion at the given index representing the area that is Merged (r1,c1 - r2,c2) */ public CellRangeAddress getAreaAt(int index) { - return _regions.getCellRangeAddress(index); + return _regions[_startIndex + index]; } public int getRecordSize() { - return 4 + _regions.getSize(); + return 4 + CellRangeAddressList.getEncodedSize(_numberOfRegions); } public short getSid() { @@ -101,11 +84,16 @@ public final class MergeCellsRecord extends Record { } public int serialize(int offset, byte [] data) { - int dataSize = _regions.getSize(); + int dataSize = CellRangeAddressList.getEncodedSize(_numberOfRegions); - LittleEndian.putShort(data, offset + 0, sid); + LittleEndian.putUShort(data, offset + 0, sid); LittleEndian.putUShort(data, offset + 2, dataSize); - _regions.serialize(offset + 4, data); + int nItems = _numberOfRegions; + LittleEndian.putUShort(data, offset + 4, nItems); + int pos = 6; + for (int i = 0; i < _numberOfRegions; i++) { + pos += _regions[_startIndex + i].serialize(offset+pos, data); + } return 4 + dataSize; } @@ -113,17 +101,16 @@ public final class MergeCellsRecord extends Record { StringBuffer retval = new StringBuffer(); retval.append("[MERGEDCELLS]").append("\n"); - retval.append(" .sid =").append(sid).append("\n"); retval.append(" .numregions =").append(getNumAreas()) .append("\n"); - for (int k = 0; k < _regions.countRanges(); k++) { - CellRangeAddress region = _regions.getCellRangeAddress(k); + for (int k = 0; k < _numberOfRegions; k++) { + CellRangeAddress region = _regions[_startIndex + k]; retval.append(" .rowfrom =").append(region.getFirstRow()) .append("\n"); - retval.append(" .colfrom =").append(region.getFirstColumn()) - .append("\n"); retval.append(" .rowto =").append(region.getLastRow()) + .append("\n"); + retval.append(" .colfrom =").append(region.getFirstColumn()) .append("\n"); retval.append(" .colto =").append(region.getLastColumn()) .append("\n"); @@ -140,13 +127,11 @@ public final class MergeCellsRecord extends Record { } public Object clone() { - MergeCellsRecord rec = new MergeCellsRecord(); - for (int k = 0; k < _regions.countRanges(); k++) { - CellRangeAddress oldRegion = _regions.getCellRangeAddress(k); - rec.addArea(oldRegion.getFirstRow(), oldRegion.getFirstColumn(), - oldRegion.getLastRow(), oldRegion.getLastColumn()); - } - - return rec; + int nRegions = _numberOfRegions; + CellRangeAddress[] clonedRegions = new CellRangeAddress[nRegions]; + for (int i = 0; i < clonedRegions.length; i++) { + clonedRegions[i] = _regions[_startIndex + i].copy(); + } + return new MergeCellsRecord(clonedRegions, 0, nRegions); } } diff --git a/src/java/org/apache/poi/hssf/record/PageBreakRecord.java b/src/java/org/apache/poi/hssf/record/PageBreakRecord.java index 83eade95d2..c86f460b1f 100644 --- a/src/java/org/apache/poi/hssf/record/PageBreakRecord.java +++ b/src/java/org/apache/poi/hssf/record/PageBreakRecord.java @@ -1,4 +1,3 @@ - /* ==================================================================== Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with @@ -15,11 +14,10 @@ See the License for the specific language governing permissions and limitations under the License. ==================================================================== */ - + package org.apache.poi.hssf.record; import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -38,13 +36,12 @@ import org.apache.poi.util.LittleEndian; * @see VerticalPageBreakRecord * @author Danny Mui (dmui at apache dot org) */ -public class PageBreakRecord extends Record { - public static final short HORIZONTAL_SID = (short)0x1B; - public static final short VERTICAL_SID = (short)0x1A; - public short sid; - private short numBreaks; - private List breaks; - private Map BreakMap; +public abstract class PageBreakRecord extends Record { + private static final boolean IS_EMPTY_RECORD_WRITTEN = false; //TODO - flip + private static final int[] EMPTY_INT_ARRAY = { }; + + private List _breaks; + private Map _breakMap; /** * Since both records store 2byte integers (short), no point in @@ -53,116 +50,105 @@ public class PageBreakRecord extends Record { * The subs (rows or columns, don't seem to be able to set but excel sets * them automatically) */ - public class Break - { + public class Break { - public short main; - public short subFrom; - public short subTo; + public static final int ENCODED_SIZE = 6; + public int main; + public int subFrom; + public int subTo; - public Break(short main, short subFrom, short subTo) + public Break(int main, int subFrom, int subTo) { this.main = main; this.subFrom = subFrom; this.subTo = subTo; } + + public Break(RecordInputStream in) { + main = in.readUShort() - 1; + subFrom = in.readUShort(); + subTo = in.readUShort(); + } + + public int serialize(int offset, byte[] data) { + LittleEndian.putUShort(data, offset + 0, main + 1); + LittleEndian.putUShort(data, offset + 2, subFrom); + LittleEndian.putUShort(data, offset + 4, subTo); + return ENCODED_SIZE; + } } - public PageBreakRecord() - { - - } - - /** - * - * @param sid - */ - public PageBreakRecord(short sid) { - super(); - this.sid = sid; + protected PageBreakRecord() { + _breaks = new ArrayList(); + _breakMap = new HashMap(); } - public PageBreakRecord(RecordInputStream in) - { + protected PageBreakRecord(RecordInputStream in) { super(in); - this.sid = in.getSid(); } protected void fillFields(RecordInputStream in) { - short loadedBreaks = in.readShort(); - setNumBreaks(loadedBreaks); - for(int k = 0; k < loadedBreaks; k++) - { - addBreak((short)(in.readShort()-1), in.readShort(), in.readShort()); + int nBreaks = in.readShort(); + _breaks = new ArrayList(nBreaks + 2); + _breakMap = new HashMap(); + + for(int k = 0; k < nBreaks; k++) { + Break br = new Break(in); + _breaks.add(br); + _breakMap.put(new Integer(br.main), br); } } - - public short getSid() - { - return sid; + + private int getDataSize() { + return 2 + _breaks.size() * Break.ENCODED_SIZE; } - - public int serialize(int offset, byte data[]) - { - int recordsize = getRecordSize(); - int pos = 6; - LittleEndian.putShort(data, offset + 0, getSid()); - LittleEndian.putShort(data, offset + 2, (short)(recordsize - 4)); - LittleEndian.putShort(data, offset + 4, getNumBreaks()); - for(Iterator iterator = getBreaksIterator(); iterator.hasNext();) - { - Break Break = (Break)iterator.next(); - LittleEndian.putShort(data, offset + pos, (short)(Break.main + 1)); - pos += 2; - LittleEndian.putShort(data, offset + pos, Break.subFrom); - pos += 2; - LittleEndian.putShort(data, offset + pos, Break.subTo); - pos += 2; + public int getRecordSize() { + int nBreaks = _breaks.size(); + if (!IS_EMPTY_RECORD_WRITTEN && nBreaks < 1) { + return 0; } - - return recordsize; + return 4 + getDataSize(); } - protected void validateSid(short id) - { - if(id != HORIZONTAL_SID && id != VERTICAL_SID) - throw new RecordFormatException("NOT A HorizontalPageBreak or VerticalPageBreak RECORD!! " + id); - else - return; - } - public short getNumBreaks() - { - return breaks != null ? (short)breaks.size() : numBreaks; + public final int serialize(int offset, byte data[]) { + int nBreaks = _breaks.size(); + if (!IS_EMPTY_RECORD_WRITTEN && nBreaks < 1) { + return 0; + } + int dataSize = getDataSize(); + LittleEndian.putUShort(data, offset + 0, getSid()); + LittleEndian.putUShort(data, offset + 2, dataSize); + LittleEndian.putUShort(data, offset + 4, nBreaks); + int pos = 6; + for (int i=0; i<nBreaks; i++) { + Break br = (Break)_breaks.get(i); + pos += br.serialize(offset+pos, data); + } + + return 4 + dataSize; } - public void setNumBreaks(short numBreaks) - { - this.numBreaks = numBreaks; + public int getNumBreaks() { + return _breaks.size(); } - public Iterator getBreaksIterator() - { - if(breaks == null) - return Collections.EMPTY_LIST.iterator(); - else - return breaks.iterator(); + public final Iterator getBreaksIterator() { + return _breaks.iterator(); } public String toString() { StringBuffer retval = new StringBuffer(); - if (getSid() != HORIZONTAL_SID && getSid()!= VERTICAL_SID) - return "[INVALIDPAGEBREAK]\n .sid ="+getSid()+"[INVALIDPAGEBREAK]"; - + String label; String mainLabel; String subLabel; - if (getSid() == HORIZONTAL_SID) { + if (getSid() == HorizontalPageBreakRecord.sid) { label = "HORIZONTALPAGEBREAK"; mainLabel = "row"; subLabel = "col"; @@ -192,46 +178,33 @@ public class PageBreakRecord extends Record { /** * Adds the page break at the specified parameters * @param main Depending on sid, will determine row or column to put page break (zero-based) - * @param subFrom No user-interface to set (defaults to minumum, 0) + * @param subFrom No user-interface to set (defaults to minimum, 0) * @param subTo No user-interface to set */ - public void addBreak(short main, short subFrom, short subTo) - { - if(breaks == null) - { - breaks = new ArrayList(getNumBreaks() + 10); - BreakMap = new HashMap(); - } + public void addBreak(int main, int subFrom, int subTo) { + Integer key = new Integer(main); - Break region = (Break)BreakMap.get(key); - if(region != null) - { + Break region = (Break)_breakMap.get(key); + if(region == null) { + region = new Break(main, subFrom, subTo); + _breakMap.put(key, region); + _breaks.add(region); + } else { region.main = main; region.subFrom = subFrom; region.subTo = subTo; - } else - { - region = new Break(main, subFrom, subTo); - breaks.add(region); } - BreakMap.put(key, region); } /** * Removes the break indicated by the parameter * @param main (zero-based) */ - public void removeBreak(short main) - { + public final void removeBreak(int main) { Integer rowKey = new Integer(main); - Break region = (Break)BreakMap.get(rowKey); - breaks.remove(region); - BreakMap.remove(rowKey); - } - - public int getRecordSize() - { - return 6 + getNumBreaks() * 6; + Break region = (Break)_breakMap.get(rowKey); + _breaks.remove(region); + _breakMap.remove(rowKey); } /** @@ -239,26 +212,22 @@ public class PageBreakRecord extends Record { * @param main FIXME: Document this! * @return The Break or null if no break exists at the row/col specified. */ - public Break getBreak(short main) - { - if (BreakMap == null) - return null; + public final Break getBreak(int main) { Integer rowKey = new Integer(main); - return (Break)BreakMap.get(rowKey); + return (Break)_breakMap.get(rowKey); } - /* Clones the page break record - * @see java.lang.Object#clone() - */ - public Object clone() { - PageBreakRecord record = new PageBreakRecord(getSid()); - Iterator iterator = getBreaksIterator(); - while (iterator.hasNext()) { - Break original = (Break)iterator.next(); - record.addBreak(original.main, original.subFrom, original.subTo); - } - return record; - } - + public final int[] getBreaks() { + int count = getNumBreaks(); + if (count < 1) { + return EMPTY_INT_ARRAY; + } + int[] result = new int[count]; + for (int i=0; i<count; i++) { + Break breakItem = (Break)_breaks.get(i); + result[i] = breakItem.main; + } + return result; + } } diff --git a/src/java/org/apache/poi/hssf/record/Record.java b/src/java/org/apache/poi/hssf/record/Record.java index c1b8a0cfda..3af38601ff 100644 --- a/src/java/org/apache/poi/hssf/record/Record.java +++ b/src/java/org/apache/poi/hssf/record/Record.java @@ -1,4 +1,3 @@ - /* ==================================================================== Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with @@ -15,7 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. ==================================================================== */ - package org.apache.poi.hssf.record; @@ -32,15 +30,13 @@ import java.io.ByteArrayInputStream; * @author Jason Height (jheight at chariot dot net dot au) * @version 2.0-pre */ - -public abstract class Record -{ +public abstract class Record extends RecordBase { /** * instantiates a blank record strictly for ID matching */ - public Record() + protected Record() { } @@ -49,7 +45,7 @@ public abstract class Record * * @param in the RecordInputstream to read the record from */ - public Record(RecordInputStream in) + protected Record(RecordInputStream in) { validateSid(in.getSid()); fillFields(in); @@ -89,17 +85,6 @@ public abstract class Record return retval; } - /** - * called by the class that is responsible for writing this sucker. - * Subclasses should implement this so that their data is passed back in a - * byte array. - * - * @param offset to begin writing at - * @param data byte array containing instance data - * @return number of bytes written - */ - - public abstract int serialize(int offset, byte [] data); /** * gives the current serialized size of the record. Should include the sid and reclength (4 bytes). diff --git a/src/java/org/apache/poi/hssf/record/RecordBase.java b/src/java/org/apache/poi/hssf/record/RecordBase.java new file mode 100644 index 0000000000..6314eb0578 --- /dev/null +++ b/src/java/org/apache/poi/hssf/record/RecordBase.java @@ -0,0 +1,42 @@ +/* ==================================================================== + 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; + +/** + * Common base class of {@link Record} and {@link RecordAggregate} + * + * @author Josh Micich + */ +public abstract class RecordBase { + /** + * called by the class that is responsible for writing this sucker. + * Subclasses should implement this so that their data is passed back in a + * byte array. + * + * @param offset to begin writing at + * @param data byte array containing instance data + * @return number of bytes written + */ + public abstract int serialize(int offset, byte[] data); + + /** + * gives the current serialized size of the record. Should include the sid + * and reclength (4 bytes). + */ + public abstract int getRecordSize(); +} diff --git a/src/java/org/apache/poi/hssf/record/VerticalPageBreakRecord.java b/src/java/org/apache/poi/hssf/record/VerticalPageBreakRecord.java index 6c715494f8..fccb7ccdaa 100644 --- a/src/java/org/apache/poi/hssf/record/VerticalPageBreakRecord.java +++ b/src/java/org/apache/poi/hssf/record/VerticalPageBreakRecord.java @@ -1,4 +1,3 @@ - /* ==================================================================== Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with @@ -15,46 +14,53 @@ See the License for the specific language governing permissions and limitations under the License. ==================================================================== */ - + package org.apache.poi.hssf.record; +import java.util.Iterator; + /** - * VerticalPageBreak record that stores page breaks at columns - * <p> - * This class is just used so that SID compares work properly in the RecordFactory + * VerticalPageBreak (0x001A) record that stores page breaks at columns<p/> + * * @see PageBreakRecord - * @author Danny Mui (dmui at apache dot org) + * @author Danny Mui (dmui at apache dot org) */ -public class VerticalPageBreakRecord extends PageBreakRecord { - - public static final short sid = PageBreakRecord.VERTICAL_SID; - +public final class VerticalPageBreakRecord extends PageBreakRecord { + + public static final short sid = 0x001A; + /** - * + * Creates an empty vertical page break record */ public VerticalPageBreakRecord() { - super(); - } - /** - * @param sid - */ - public VerticalPageBreakRecord(short sid) { - super(sid); } /** - * @param in the RecordInputstream to read the record from + * @param in the RecordInputstream to read the record from */ public VerticalPageBreakRecord(RecordInputStream in) { super(in); } - /* (non-Javadoc) - * @see org.apache.poi.hssf.record.Record#getSid() - */ + protected void validateSid(short id) { + if (id != getSid()) { + throw new RecordFormatException( + "NOT A HorizontalPageBreak or VerticalPageBreak RECORD!! " + id); + } + } + public short getSid() { return sid; } + public Object clone() { + PageBreakRecord result = new VerticalPageBreakRecord(); + Iterator iterator = getBreaksIterator(); + while (iterator.hasNext()) { + Break original = (Break) iterator.next(); + result.addBreak(original.main, original.subFrom, original.subTo); + } + return result; + } } diff --git a/src/java/org/apache/poi/hssf/record/aggregates/CFRecordsAggregate.java b/src/java/org/apache/poi/hssf/record/aggregates/CFRecordsAggregate.java index 71754db961..eb117eae4c 100644 --- a/src/java/org/apache/poi/hssf/record/aggregates/CFRecordsAggregate.java +++ b/src/java/org/apache/poi/hssf/record/aggregates/CFRecordsAggregate.java @@ -14,19 +14,19 @@ 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 org.apache.poi.hssf.model.RecordStream; import org.apache.poi.hssf.record.CFHeaderRecord; import org.apache.poi.hssf.record.CFRuleRecord; import org.apache.poi.hssf.record.Record; import org.apache.poi.hssf.record.RecordInputStream; import org.apache.poi.hssf.util.CellRangeAddress; -import org.apache.poi.util.POILogFactory; -import org.apache.poi.util.POILogger; /** * CFRecordsAggregate - aggregates Conditional Formatting records CFHeaderRecord @@ -36,15 +36,12 @@ import org.apache.poi.util.POILogger; * @author Dmitriy Kumshayev * */ -public final class CFRecordsAggregate extends Record -{ +public final class CFRecordsAggregate extends Record { /** Excel allows up to 3 conditional formating rules */ private static final int MAX_CONDTIONAL_FORMAT_RULES = 3; public final static short sid = -2008; // not a real BIFF record - private static POILogger log = POILogFactory.getLogger(CFRecordsAggregate.class); - private final CFHeaderRecord header; /** List of CFRuleRecord objects */ @@ -78,9 +75,8 @@ public final class CFRecordsAggregate extends Record * @param offset - position of {@link CFHeaderRecord} object in the list of Record objects * @return CFRecordsAggregate object */ - public static CFRecordsAggregate createCFAggregate(List recs, int pOffset) - { - Record rec = ( Record ) recs.get(pOffset); + public static CFRecordsAggregate createCFAggregate(RecordStream rs) { + Record rec = rs.getNext(); if (rec.getSid() != CFHeaderRecord.sid) { throw new IllegalStateException("next record sid was " + rec.getSid() + " instead of " + CFHeaderRecord.sid + " as expected"); @@ -90,35 +86,10 @@ public final class CFRecordsAggregate extends Record int nRules = header.getNumberOfConditionalFormats(); CFRuleRecord[] rules = new CFRuleRecord[nRules]; - int offset = pOffset; - int countFound = 0; - while (countFound < rules.length) { - offset++; - if(offset>=recs.size()) { - break; - } - rec = (Record)recs.get(offset); - if(rec instanceof CFRuleRecord) { - rules[countFound] = (CFRuleRecord) rec; - countFound++; - } else { - break; - } - } - - if (countFound < nRules) - { // TODO -(MAR-2008) can this ever happen? write junit - - if (log.check(POILogger.DEBUG)) - { - log.log(POILogger.DEBUG, "Expected " + nRules + " Conditional Formats, " - + "but found " + countFound + " rules"); - } - header.setNumberOfConditionalFormats(nRules); - CFRuleRecord[] lessRules = new CFRuleRecord[countFound]; - System.arraycopy(rules, 0, lessRules, 0, countFound); - rules = lessRules; + for (int i = 0; i < rules.length; i++) { + rules[i] = (CFRuleRecord) rs.getNext(); } + return new CFRecordsAggregate(header, rules); } diff --git a/src/java/org/apache/poi/hssf/record/aggregates/ColumnInfoRecordsAggregate.java b/src/java/org/apache/poi/hssf/record/aggregates/ColumnInfoRecordsAggregate.java index 6df796c2aa..b24d8c5b45 100644 --- a/src/java/org/apache/poi/hssf/record/aggregates/ColumnInfoRecordsAggregate.java +++ b/src/java/org/apache/poi/hssf/record/aggregates/ColumnInfoRecordsAggregate.java @@ -1,72 +1,52 @@ -/* -* 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; +/* ==================================================================== + 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. +==================================================================== */ -import org.apache.poi.hssf.record.ColumnInfoRecord; -import org.apache.poi.hssf.record.Record; -import org.apache.poi.hssf.record.RecordInputStream; +package org.apache.poi.hssf.record.aggregates; import java.util.ArrayList; -import java.util.Iterator; import java.util.List; +import org.apache.poi.hssf.model.RecordStream; +import org.apache.poi.hssf.record.ColumnInfoRecord; +import org.apache.poi.hssf.record.Record; + /** * @author Glen Stampoultzis * @version $Id$ */ -public class ColumnInfoRecordsAggregate - extends Record -{ -// int size = 0; - List records = null; - - public ColumnInfoRecordsAggregate() - { - records = new ArrayList(); - } - - /** You never fill an aggregate */ - protected void fillFields(RecordInputStream in) - { - } - - /** Not required by an aggregate */ - protected void validateSid(short id) - { - } - - /** It's an aggregate... just made something up */ - public short getSid() - { - return -1012; - } - - public int getRecordSize() - { - int size = 0; - for ( Iterator iterator = records.iterator(); iterator.hasNext(); ) - size += ( (ColumnInfoRecord) iterator.next() ).getRecordSize(); - return size; - } - - public Iterator getIterator() - { - return records.iterator(); - } +public final class ColumnInfoRecordsAggregate extends RecordAggregate { + private final List records; + + /** + * Creates an empty aggregate + */ + public ColumnInfoRecordsAggregate() { + records = new ArrayList(); + } + public ColumnInfoRecordsAggregate(RecordStream rs) { + this(); + + while(rs.peekNextClass() == ColumnInfoRecord.class) { + records.add(rs.getNext()); + } + if (records.size() < 1) { + throw new RuntimeException("No column info records found"); + } + } /** * Performs a deep clone of the record @@ -105,25 +85,14 @@ public class ColumnInfoRecordsAggregate return records.size(); } - /** - * called by the class that is responsible for writing this sucker. - * Subclasses should implement this so that their data is passed back in a - * byte array. - * - * @param offset offset to begin writing at - * @param data byte array containing instance data - * @return number of bytes written - */ - public int serialize(int offset, byte [] data) - { - Iterator itr = records.iterator(); - int pos = offset; - - while (itr.hasNext()) - { - pos += (( Record ) itr.next()).serialize(pos, data); - } - return pos - offset; + public void visitContainedRecords(RecordVisitor rv) { + int nItems = records.size(); + if (nItems < 1) { + return; + } + for(int i=0; i<nItems; i++) { + rv.visitRecord((Record)records.get(i)); + } } public int findStartOfColumnOutlineGroup(int idx) @@ -178,8 +147,7 @@ public class ColumnInfoRecordsAggregate return idx; } - public ColumnInfoRecord getColInfo(int idx) - { + private ColumnInfoRecord getColInfo(int idx) { return (ColumnInfoRecord) records.get( idx ); } @@ -191,7 +159,7 @@ public class ColumnInfoRecordsAggregate columnInfo.setHidden( hidden ); if (idx + 1 < records.size()) { - ColumnInfoRecord nextColumnInfo = (ColumnInfoRecord) records.get( idx + 1 ); + ColumnInfoRecord nextColumnInfo = getColInfo(idx + 1); if (columnInfo.getLastColumn() + 1 == nextColumnInfo.getFirstColumn()) { if (nextColumnInfo.getOutlineLevel() < level) @@ -279,7 +247,7 @@ public class ColumnInfoRecordsAggregate return; // Find the start of the group. - ColumnInfoRecord columnInfo = (ColumnInfoRecord) records.get( findStartOfColumnOutlineGroup( idx ) ); + ColumnInfoRecord columnInfo = getColInfo( findStartOfColumnOutlineGroup( idx ) ); // Hide all the columns until the end of the group columnInfo = writeHidden( columnInfo, idx, true ); @@ -331,7 +299,7 @@ public class ColumnInfoRecordsAggregate * @see org.apache.poi.hssf.record.ColumnInfoRecord * @return record containing a ColumnInfoRecord */ - public static Record createColInfo() + public static ColumnInfoRecord createColInfo() { ColumnInfoRecord retval = new ColumnInfoRecord(); @@ -452,7 +420,7 @@ public class ColumnInfoRecordsAggregate ci.setCollapsed( collapsed.booleanValue() ); } - public int findColumnIdx(int column, int fromIdx) + private int findColumnIdx(int column, int fromIdx) { if (column < 0) throw new IllegalArgumentException( "column parameter out of range: " + column ); @@ -462,7 +430,7 @@ public class ColumnInfoRecordsAggregate ColumnInfoRecord ci; for (int k = fromIdx; k < records.size(); k++) { - ci = ( ColumnInfoRecord ) records.get(k); + ci = getColInfo(k); if ((ci.getFirstColumn() <= column) && (column <= ci.getLastColumn())) { @@ -477,8 +445,8 @@ public class ColumnInfoRecordsAggregate { if (columnIdx == 0) return; - ColumnInfoRecord previousCol = (ColumnInfoRecord) records.get( columnIdx - 1); - ColumnInfoRecord currentCol = (ColumnInfoRecord) records.get( columnIdx ); + ColumnInfoRecord previousCol = getColInfo( columnIdx - 1); + ColumnInfoRecord currentCol = getColInfo( columnIdx ); boolean adjacentColumns = previousCol.getLastColumn() == currentCol.getFirstColumn() - 1; if (!adjacentColumns) return; @@ -513,7 +481,7 @@ public class ColumnInfoRecordsAggregate int columnIdx = findColumnIdx( i, Math.max(0,fromIdx) ); if (columnIdx != -1) { - level = ((ColumnInfoRecord)records.get( columnIdx )).getOutlineLevel(); + level = getColInfo(columnIdx).getOutlineLevel(); if (indent) level++; else level--; level = Math.max(0, level); level = Math.min(7, level); @@ -525,6 +493,30 @@ public class ColumnInfoRecordsAggregate } } + /** + * Finds the <tt>ColumnInfoRecord</tt> which contains the specified columnIndex + * @param columnIndex index of the column (not the index of the ColumnInfoRecord) + * @return <code>null</code> if no column info found for the specified column + */ + public ColumnInfoRecord findColumnInfo(int columnIndex) { + int nInfos = records.size(); + for(int i=0; i< nInfos; i++) { + ColumnInfoRecord ci = getColInfo(i); + if (ci.getFirstColumn() <= columnIndex && columnIndex <= ci.getLastColumn()) { + return ci; + } + } + return null; + } + public int getMaxOutlineLevel() { + int result = 0; + int count=records.size(); + for (int i=0; i<count; i++) { + ColumnInfoRecord columnInfoRecord = getColInfo(i); + result = Math.max(columnInfoRecord.getOutlineLevel(), result); + } + return result; + } } diff --git a/src/java/org/apache/poi/hssf/record/aggregates/ConditionalFormattingTable.java b/src/java/org/apache/poi/hssf/record/aggregates/ConditionalFormattingTable.java new file mode 100644 index 0000000000..34e9ca837f --- /dev/null +++ b/src/java/org/apache/poi/hssf/record/aggregates/ConditionalFormattingTable.java @@ -0,0 +1,88 @@ +/* ==================================================================== + 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.model.RecordStream; +import org.apache.poi.hssf.record.CFHeaderRecord; +import org.apache.poi.hssf.record.Record; + +/** + * Holds all the conditional formatting for a workbook sheet.<p/> + * + * See OOO exelfileformat.pdf sec 4.12 'Conditional Formatting Table' + * + * @author Josh Micich + */ +public final class ConditionalFormattingTable extends RecordAggregate { + + private final List _cfHeaders; + + /** + * Creates an empty ConditionalFormattingTable + */ + public ConditionalFormattingTable() { + _cfHeaders = new ArrayList(); + } + + public ConditionalFormattingTable(RecordStream rs) { + + List temp = new ArrayList(); + while (rs.peekNextClass() == CFHeaderRecord.class) { + temp.add(CFRecordsAggregate.createCFAggregate(rs)); + } + _cfHeaders = temp; + } + + public void visitContainedRecords(RecordVisitor rv) { + for (int i = 0; i < _cfHeaders.size(); i++) { + rv.visitRecord((Record) _cfHeaders.get(i)); + } + } + + /** + * @return index of the newly added CF header aggregate + */ + public int add(CFRecordsAggregate cfAggregate) { + _cfHeaders.add(cfAggregate); + return _cfHeaders.size() - 1; + } + + public int size() { + return _cfHeaders.size(); + } + + public CFRecordsAggregate get(int index) { + checkIndex(index); + return (CFRecordsAggregate) _cfHeaders.get(index); + } + + public void remove(int index) { + checkIndex(index); + _cfHeaders.remove(index); + } + + private void checkIndex(int index) { + if (index < 0 || index >= _cfHeaders.size()) { + throw new IllegalArgumentException("Specified CF index " + index + + " is outside the allowable range (0.." + (_cfHeaders.size() - 1) + ")"); + } + } +} diff --git a/src/java/org/apache/poi/hssf/record/aggregates/DataValidityTable.java b/src/java/org/apache/poi/hssf/record/aggregates/DataValidityTable.java index 133386d7df..f4861b011b 100644 --- a/src/java/org/apache/poi/hssf/record/aggregates/DataValidityTable.java +++ b/src/java/org/apache/poi/hssf/record/aggregates/DataValidityTable.java @@ -21,17 +21,9 @@ import java.util.ArrayList; import java.util.List;
import org.apache.poi.hssf.model.RecordStream;
-import org.apache.poi.hssf.record.CFHeaderRecord;
-import org.apache.poi.hssf.record.CFRuleRecord;
import org.apache.poi.hssf.record.DVALRecord;
import org.apache.poi.hssf.record.DVRecord;
-import org.apache.poi.hssf.record.EOFRecord;
-import org.apache.poi.hssf.record.HyperlinkRecord;
-import org.apache.poi.hssf.record.MergeCellsRecord;
-import org.apache.poi.hssf.record.PaneRecord;
import org.apache.poi.hssf.record.Record;
-import org.apache.poi.hssf.record.SelectionRecord;
-import org.apache.poi.hssf.record.WindowTwoRecord;
/**
* Manages the DVALRecord and DVRecords for a single sheet<br/>
@@ -40,7 +32,6 @@ import org.apache.poi.hssf.record.WindowTwoRecord; */
public final class DataValidityTable extends RecordAggregate {
- private static final short sid = -0x01B2; // not a real record
private final DVALRecord _headerRec;
/**
* The list of data validations for the current sheet.
@@ -57,120 +48,21 @@ public final class DataValidityTable extends RecordAggregate { _validationList = temp;
}
- private DataValidityTable() {
+ public DataValidityTable() {
_headerRec = new DVALRecord();
_validationList = new ArrayList();
}
- public short getSid() {
- return sid;
- }
-
- public int serialize(int offset, byte[] data) {
- int result = _headerRec.serialize(offset, data);
- for (int i = 0; i < _validationList.size(); i++) {
- result += ((Record) _validationList.get(i)).serialize(offset + result, data);
+ public void visitContainedRecords(RecordVisitor rv) {
+ if (_validationList.isEmpty()) {
+ return;
}
- return result;
- }
-
- public int getRecordSize() {
- int result = _headerRec.getRecordSize();
- for (int i = _validationList.size() - 1; i >= 0; i--) {
- result += ((Record) _validationList.get(i)).getRecordSize();
+ rv.visitRecord(_headerRec);
+ for (int i = 0; i < _validationList.size(); i++) {
+ rv.visitRecord((Record) _validationList.get(i));
}
- return result;
- }
-
- /**
- * Creates a new <tt>DataValidityTable</tt> and inserts it in the right
- * place in the sheetRecords list.
- */
- public static DataValidityTable createForSheet(List sheetRecords) {
- int index = findDVTableInsertPos(sheetRecords);
-
- DataValidityTable result = new DataValidityTable();
- sheetRecords.add(index, result);
- return result;
}
- /**
- * Finds the index where the sheet validations header record should be inserted
- * @param records the records for this sheet
- *
- * + WINDOW2
- * o SCL
- * o PANE
- * oo SELECTION
- * o STANDARDWIDTH
- * oo MERGEDCELLS
- * o LABELRANGES
- * o PHONETICPR
- * o Conditional Formatting Table
- * o Hyperlink Table
- * o Data Validity Table
- * o SHEETLAYOUT
- * o SHEETPROTECTION
- * o RANGEPROTECTION
- * + EOF
- */
- private static int findDVTableInsertPos(List records) {
- int i = records.size() - 1;
- if (!(records.get(i) instanceof EOFRecord)) {
- throw new IllegalStateException("Last sheet record should be EOFRecord");
- }
- while (i > 0) {
- i--;
- Record rec = (Record) records.get(i);
- if (isPriorRecord(rec.getSid())) {
- Record nextRec = (Record) records.get(i + 1);
- if (!isSubsequentRecord(nextRec.getSid())) {
- throw new IllegalStateException("Unexpected (" + nextRec.getClass().getName()
- + ") found after (" + rec.getClass().getName() + ")");
- }
- return i;
- }
- if (!isSubsequentRecord(rec.getSid())) {
- throw new IllegalStateException("Unexpected (" + rec.getClass().getName()
- + ") while looking for DV Table insert pos");
- }
- }
- return 0;
- }
-
- // TODO - add UninterpretedRecord as base class for many of these
- // unimplemented sids
-
- private static boolean isPriorRecord(short sid) {
- switch(sid) {
- case WindowTwoRecord.sid:
- case 0x00A0: // SCL
- case PaneRecord.sid:
- case SelectionRecord.sid:
- case 0x0099: // STANDARDWIDTH
- case MergeCellsRecord.sid:
- case 0x015F: // LABELRANGES
- case 0x00EF: // PHONETICPR
- case CFHeaderRecord.sid:
- case CFRuleRecord.sid:
- case HyperlinkRecord.sid:
- case 0x0800: // QUICKTIP
- return true;
- }
- return false;
- }
-
- private static boolean isSubsequentRecord(short sid) {
- switch(sid) {
- case 0x0862: // SHEETLAYOUT
- case 0x0867: // SHEETPROTECTION
- case 0x0868: // RANGEPROTECTION
- case EOFRecord.sid:
- return true;
- }
- return false;
- }
-
public void addDataValidation(DVRecord dvRecord) {
_validationList.add(dvRecord);
_headerRec.setDVRecNo(_validationList.size());
diff --git a/src/java/org/apache/poi/hssf/record/aggregates/MergedCellsTable.java b/src/java/org/apache/poi/hssf/record/aggregates/MergedCellsTable.java new file mode 100644 index 0000000000..b7384a0194 --- /dev/null +++ b/src/java/org/apache/poi/hssf/record/aggregates/MergedCellsTable.java @@ -0,0 +1,122 @@ +/* ==================================================================== + 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.model.RecordStream; +import org.apache.poi.hssf.record.MergeCellsRecord; +import org.apache.poi.hssf.util.CellRangeAddress; +import org.apache.poi.hssf.util.CellRangeAddressList; + +/** + * + * @author Josh Micich + */ +public final class MergedCellsTable extends RecordAggregate { + private static int MAX_MERGED_REGIONS = 1027; // enforced by the 8224 byte limit + + private final List _mergedRegions; + + /** + * Creates an empty aggregate + */ + public MergedCellsTable() { + _mergedRegions = new ArrayList(); + } + + public MergedCellsTable(RecordStream rs) { + List temp = new ArrayList(); + while (rs.peekNextClass() == MergeCellsRecord.class) { + MergeCellsRecord mcr = (MergeCellsRecord) rs.getNext(); + int nRegions = mcr.getNumAreas(); + for (int i = 0; i < nRegions; i++) { + temp.add(mcr.getAreaAt(i)); + } + } + _mergedRegions = temp; + } + + public int getRecordSize() { + // a bit cheaper than the default impl + int nRegions = _mergedRegions.size(); + if (nRegions < 1) { + // no need to write a single empty MergeCellsRecord + return 0; + } + int nMergedCellsRecords = nRegions / MAX_MERGED_REGIONS; + int nLeftoverMergedRegions = nRegions % MAX_MERGED_REGIONS; + + int result = nMergedCellsRecords + * (4 + CellRangeAddressList.getEncodedSize(MAX_MERGED_REGIONS)) + 4 + + CellRangeAddressList.getEncodedSize(nLeftoverMergedRegions); + return result; + } + + public void visitContainedRecords(RecordVisitor rv) { + int nRegions = _mergedRegions.size(); + if (nRegions < 1) { + // no need to write a single empty MergeCellsRecord + return; + } + + int nFullMergedCellsRecords = nRegions / MAX_MERGED_REGIONS; + int nLeftoverMergedRegions = nRegions % MAX_MERGED_REGIONS; + CellRangeAddress[] cras = new CellRangeAddress[nRegions]; + _mergedRegions.toArray(cras); + + for (int i = 0; i < nFullMergedCellsRecords; i++) { + int startIx = i * MAX_MERGED_REGIONS; + rv.visitRecord(new MergeCellsRecord(cras, startIx, MAX_MERGED_REGIONS)); + } + if (nLeftoverMergedRegions > 0) { + int startIx = nFullMergedCellsRecords * MAX_MERGED_REGIONS; + rv.visitRecord(new MergeCellsRecord(cras, startIx, nLeftoverMergedRegions)); + } + } + + public void add(MergeCellsRecord mcr) { + _mergedRegions.add(mcr); + } + + public CellRangeAddress get(int index) { + checkIndex(index); + return (CellRangeAddress) _mergedRegions.get(index); + } + + public void remove(int index) { + checkIndex(index); + _mergedRegions.remove(index); + } + + private void checkIndex(int index) { + if (index < 0 || index >= _mergedRegions.size()) { + throw new IllegalArgumentException("Specified CF index " + index + + " is outside the allowable range (0.." + (_mergedRegions.size() - 1) + ")"); + } + } + + public void addArea(int rowFrom, int colFrom, int rowTo, int colTo) { + _mergedRegions.add(new CellRangeAddress(rowFrom, rowTo, colFrom, colTo)); + } + + public int getNumberOfMergedRegions() { + return _mergedRegions.size(); + } +} diff --git a/src/java/org/apache/poi/hssf/record/aggregates/RecordAggregate.java b/src/java/org/apache/poi/hssf/record/aggregates/RecordAggregate.java index 3a86871e8b..ce0bf89452 100644 --- a/src/java/org/apache/poi/hssf/record/aggregates/RecordAggregate.java +++ b/src/java/org/apache/poi/hssf/record/aggregates/RecordAggregate.java @@ -18,6 +18,7 @@ package org.apache.poi.hssf.record.aggregates;
import org.apache.poi.hssf.record.Record;
+import org.apache.poi.hssf.record.RecordBase;
import org.apache.poi.hssf.record.RecordInputStream;
/**
@@ -27,15 +28,66 @@ import org.apache.poi.hssf.record.RecordInputStream; *
* @author Josh Micich
*/
-public abstract class RecordAggregate extends Record {
- // TODO - convert existing aggregate classes to proper subclasses of this one
+public abstract class RecordAggregate extends RecordBase {
+ // TODO - delete these methods when all subclasses have been converted
protected final void validateSid(short id) {
- // TODO - break class hierarchy and make separate from Record
throw new RuntimeException("Should not be called");
}
protected final void fillFields(RecordInputStream in) {
throw new RuntimeException("Should not be called");
}
- // force subclassses to provide better implementation than default
- public abstract int getRecordSize();
+ public final short getSid() {
+ throw new RuntimeException("Should not be called");
+ }
+
+ public abstract void visitContainedRecords(RecordVisitor rv);
+
+ public final int serialize(int offset, byte[] data) {
+ SerializingRecordVisitor srv = new SerializingRecordVisitor(data, offset);
+ visitContainedRecords(srv);
+ return srv.countBytesWritten();
+ }
+ public int getRecordSize() {
+ RecordSizingVisitor rsv = new RecordSizingVisitor();
+ visitContainedRecords(rsv);
+ return rsv.getTotalSize();
+ }
+
+ public interface RecordVisitor {
+ void visitRecord(Record r);
+ }
+
+ private static final class SerializingRecordVisitor implements RecordVisitor {
+
+ private final byte[] _data;
+ private final int _startOffset;
+ private int _countBytesWritten;
+
+ public SerializingRecordVisitor(byte[] data, int startOffset) {
+ _data = data;
+ _startOffset = startOffset;
+ _countBytesWritten = 0;
+ }
+ public int countBytesWritten() {
+ return _countBytesWritten;
+ }
+ public void visitRecord(Record r) {
+ int currentOffset = _startOffset + _countBytesWritten;
+ _countBytesWritten += r.serialize(currentOffset, _data);
+ }
+ }
+ private static final class RecordSizingVisitor implements RecordVisitor {
+
+ private int _totalSize;
+
+ public RecordSizingVisitor() {
+ _totalSize = 0;
+ }
+ public int getTotalSize() {
+ return _totalSize;
+ }
+ public void visitRecord(Record r) {
+ _totalSize += r.getRecordSize();
+ }
+ }
}
diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java b/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java index c4c9ea6190..261198d444 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java @@ -48,6 +48,7 @@ import org.apache.poi.hssf.record.NoteRecord; import org.apache.poi.hssf.record.NumberRecord; import org.apache.poi.hssf.record.ObjRecord; import org.apache.poi.hssf.record.Record; +import org.apache.poi.hssf.record.RecordBase; import org.apache.poi.hssf.record.StringRecord; import org.apache.poi.hssf.record.SubRecord; import org.apache.poi.hssf.record.TextObjectRecord; @@ -1144,7 +1145,7 @@ public class HSSFCell HSSFComment comment = null; HashMap txshapes = new HashMap(); //map shapeId and TextObjectRecord for (Iterator it = sheet.getRecords().iterator(); it.hasNext(); ) { - Record rec = ( Record ) it.next(); + RecordBase rec = (RecordBase) it.next(); if (rec instanceof NoteRecord){ NoteRecord note = (NoteRecord)rec; if (note.getRow() == row && note.getColumn() == column){ @@ -1186,7 +1187,7 @@ public class HSSFCell */ public HSSFHyperlink getHyperlink(){ for (Iterator it = sheet.getRecords().iterator(); it.hasNext(); ) { - Record rec = ( Record ) it.next(); + RecordBase rec = (RecordBase) it.next(); if (rec instanceof HyperlinkRecord){ HyperlinkRecord link = (HyperlinkRecord)rec; if(link.getFirstColumn() == record.getColumn() && link.getFirstRow() == record.getRow()){ diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java b/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java index ec104ded96..772b0a58c4 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java @@ -1446,43 +1446,19 @@ public final class HSSFSheet { } /** - * Retrieves all the horizontal page breaks - * @return all the horizontal page breaks, or null if there are no row page breaks + * @return row indexes of all the horizontal page breaks, never <code>null</code> */ public int[] getRowBreaks(){ //we can probably cache this information, but this should be a sparsely used function - int count = sheet.getNumRowBreaks(); - if (count > 0) { - int[] returnValue = new int[count]; - Iterator iterator = sheet.getRowBreaks(); - int i = 0; - while (iterator.hasNext()) { - PageBreakRecord.Break breakItem = (PageBreakRecord.Break)iterator.next(); - returnValue[i++] = breakItem.main; - } - return returnValue; - } - return null; + return sheet.getRowBreaks(); } /** - * Retrieves all the vertical page breaks - * @return all the vertical page breaks, or null if there are no column page breaks + * @return column indexes of all the vertical page breaks, never <code>null</code> */ - public short[] getColumnBreaks(){ + public int[] getColumnBreaks(){ //we can probably cache this information, but this should be a sparsely used function - int count = sheet.getNumColumnBreaks(); - if (count > 0) { - short[] returnValue = new short[count]; - Iterator iterator = sheet.getColumnBreaks(); - int i = 0; - while (iterator.hasNext()) { - PageBreakRecord.Break breakItem = (PageBreakRecord.Break)iterator.next(); - returnValue[i++] = breakItem.main; - } - return returnValue; - } - return null; + return sheet.getColumnBreaks(); } diff --git a/src/java/org/apache/poi/hssf/util/CellRangeAddressList.java b/src/java/org/apache/poi/hssf/util/CellRangeAddressList.java index f901be4c4d..b29a8bf3a4 100644 --- a/src/java/org/apache/poi/hssf/util/CellRangeAddressList.java +++ b/src/java/org/apache/poi/hssf/util/CellRangeAddressList.java @@ -125,7 +125,14 @@ public final class CellRangeAddressList { } public int getSize() { - return 2 + CellRangeAddress.getEncodedSize(_list.size()); + return getEncodedSize(_list.size()); + } + /** + * @return the total size of for the specified number of ranges, + * including the initial 2 byte range count + */ + public static int getEncodedSize(int numberOfRanges) { + return 2 + CellRangeAddress.getEncodedSize(numberOfRanges); } public CellRangeAddressList copy() { CellRangeAddressList result = new CellRangeAddressList(); |