/** * Represents a sheet within a workbook. Provides a handle to the individual cells, or lines of * cells (grouped by Row or Column) In order to simplify this class due to code bloat, the actual * reading logic has been delegated to the SheetReaderClass. This class' main responsibility is now * to implement the API methods declared in the Sheet interface */ public class DefaultSheet implements Sheet { /** The logger */ private static Logger logger = LoggerFactory.getLog(DefaultSheet.class); /** The excel file */ private File excelFile; /** A handle to the shared string table */ private SSTRecord sharedStrings; /** A handle to the sheet BOF record, which indicates the stream type */ private BOFRecord sheetBof; /** A handle to the workbook BOF record, which indicates the stream type */ private BOFRecord workbookBof; /** A handle to the formatting records */ private FormattingRecords formattingRecords; /** The name of this sheet */ private String name; /** The number of rows */ private int numRows; /** The number of columns */ private int numCols; /** The cells */ private Cell[][] cells; /** The start position in the stream of this sheet */ private int startPosition; /** The list of specified (ie. non default) column widths */ private ColumnInfoRecord[] columnInfos; /** The array of row records */ private RowRecord[] rowRecords; /** The list of non-default row properties */ private ArrayList rowProperties; /** * An array of column info records. They are held this way before they are transferred to the more * convenient array */ private ArrayList columnInfosArray; /** A list of shared formula groups */ private ArrayList sharedFormulas; /** A list of hyperlinks on this page */ private ArrayList hyperlinks; /** A list of charts on this page */ private ArrayList charts; /** A list of drawings on this page */ private ArrayList drawings; /** A list of drawings (as opposed to comments/validation/charts) on this page */ private ArrayList images; /** A list of data validations on this page */ private DataValidation dataValidation; /** A list of merged cells on this page */ private Range[] mergedCells; /** Indicates whether the columnInfos array has been initialized */ private boolean columnInfosInitialized; /** Indicates whether the rowRecords array has been initialized */ private boolean rowRecordsInitialized; /** Indicates whether or not the dates are based around the 1904 date system */ private boolean nineteenFour; /** The workspace options */ private WorkspaceInformationRecord workspaceOptions; /** The hidden flag */ private boolean hidden; /** The environment specific print record */ private PLSRecord plsRecord; /** The property set record associated with this workbook */ private ButtonPropertySetRecord buttonPropertySet; /** The sheet settings */ private SheetSettings settings; /** The horizontal page breaks contained on this sheet */ private int[] rowBreaks; /** The vertical page breaks contained on this sheet */ private int[] columnBreaks; /** The maximum row outline level */ private int maxRowOutlineLevel; /** The maximum column outline level */ private int maxColumnOutlineLevel; /** The list of local names for this sheet */ private ArrayList localNames; /** The list of conditional formats for this sheet */ private ArrayList conditionalFormats; /** The autofilter information */ private AutoFilter autoFilter; /** * A handle to the workbook which contains this sheet. Some of the records need this in order to * reference external sheets */ private WorkbookParser workbook; /** A handle to the workbook settings */ private WorkbookSettings workbookSettings; /** * Constructor * * @param f the excel file * @param sst the shared string table * @param fr formatting records * @param sb the bof record which indicates the start of the sheet * @param wb the bof record which indicates the start of the sheet * @param nf the 1904 flag * @param wp the workbook which this sheet belongs to * @throws BiffException */ DefaultSheet( File f, SSTRecord sst, FormattingRecords fr, BOFRecord sb, BOFRecord wb, boolean nf, WorkbookParser wp) throws BiffException { excelFile = f; sharedStrings = sst; formattingRecords = fr; sheetBof = sb; workbookBof = wb; columnInfosArray = new ArrayList(); sharedFormulas = new ArrayList(); hyperlinks = new ArrayList(); rowProperties = new ArrayList(10); columnInfosInitialized = false; rowRecordsInitialized = false; nineteenFour = nf; workbook = wp; workbookSettings = workbook.getSettings(); // Mark the position in the stream, and then skip on until the end startPosition = f.getPos(); if (sheetBof.isChart()) { // Set the start pos to include the bof so the sheet reader can handle it startPosition -= (sheetBof.getLength() + 4); } Record r = null; int bofs = 1; while (bofs >= 1) { r = f.next(); // use this form for quick performance if (r.getCode() == Type.EOF.value) { bofs--; } if (r.getCode() == Type.BOF.value) { bofs++; } } } /** * Returns the cell for the specified location eg. "A4", using the CellReferenceHelper * * @param loc the cell reference * @return the cell at the specified co-ordinates */ public Cell getCell(String loc) { return getCell(CellReferenceHelper.getColumn(loc), CellReferenceHelper.getRow(loc)); } /** * Returns the cell specified at this row and at this column * * @param row the row number * @param column the column number * @return the cell at the specified co-ordinates */ public Cell getCell(int column, int row) { // just in case this has been cleared, but something else holds // a reference to it if (cells == null) { readSheet(); } Cell c = cells[row][column]; if (c == null) { c = new EmptyCell(column, row); cells[row][column] = c; } return c; } /** * Gets the cell whose contents match the string passed in. If no match is found, then null is * returned. The search is performed on a row by row basis, so the lower the row number, the more * efficiently the algorithm will perform * * @param contents the string to match * @return the Cell whose contents match the paramter, null if not found */ public Cell findCell(String contents) { CellFinder cellFinder = new CellFinder(this); return cellFinder.findCell(contents); } /** * Gets the cell whose contents match the string passed in. If no match is found, then null is * returned. The search is performed on a row by row basis, so the lower the row number, the more * efficiently the algorithm will perform * * @param contents the string to match * @param firstCol the first column within the range * @param firstRow the first row of the range * @param lastCol the last column within the range * @param lastRow the last row within the range * @param reverse indicates whether to perform a reverse search or not * @return the Cell whose contents match the parameter, null if not found */ public Cell findCell( String contents, int firstCol, int firstRow, int lastCol, int lastRow, boolean reverse) { CellFinder cellFinder = new CellFinder(this); return cellFinder.findCell(contents, firstCol, firstRow, lastCol, lastRow, reverse); } /** * Gets the cell whose contents match the regular expressionstring passed in. If no match is * found, then null is returned. The search is performed on a row by row basis, so the lower the * row number, the more efficiently the algorithm will perform * * @param pattern the regular expression string to match * @param firstCol the first column within the range * @param firstRow the first row of the range * @param lastRow the last row within the range * @param lastCol the last column within the ranage * @param reverse indicates whether to perform a reverse search or not * @return the Cell whose contents match the parameter, null if not found */ public Cell findCell( Pattern pattern, int firstCol, int firstRow, int lastCol, int lastRow, boolean reverse) { CellFinder cellFinder = new CellFinder(this); return cellFinder.findCell(pattern, firstCol, firstRow, lastCol, lastRow, reverse); } /** * Gets the cell whose contents match the string passed in. If no match is found, then null is * returned. The search is performed on a row by row basis, so the lower the row number, the more * efficiently the algorithm will perform. This method differs from the findCell methods in that * only cells with labels are queried - all numerical cells are ignored. This should therefore * improve performance. * * @param contents the string to match * @return the Cell whose contents match the paramter, null if not found */ public LabelCell findLabelCell(String contents) { CellFinder cellFinder = new CellFinder(this); return cellFinder.findLabelCell(contents); } /** * Returns the number of rows in this sheet * * @return the number of rows in this sheet */ public int getRows() { // just in case this has been cleared, but something else holds // a reference to it if (cells == null) { readSheet(); } return numRows; } /** * Returns the number of columns in this sheet * * @return the number of columns in this sheet */ public int getColumns() { // just in case this has been cleared, but something else holds // a reference to it if (cells == null) { readSheet(); } return numCols; } /** * Gets all the cells on the specified row. The returned array will be stripped of all trailing * empty cells * * @param row the rows whose cells are to be returned * @return the cells on the given row */ public Cell[] getRow(int row) { // just in case this has been cleared, but something else holds // a reference to it if (cells == null) { readSheet(); } // Find the last non-null cell boolean found = false; int col = numCols - 1; while (col >= 0 && !found) { if (cells[row][col] != null) { found = true; } else { col--; } } // Only create entries for non-null cells Cell[] c = new Cell[col + 1]; for (int i = 0; i <= col; i++) { c[i] = getCell(i, row); } return c; } /** * Gets all the cells on the specified column. The returned array will be stripped of all trailing * empty cells * * @param col the column whose cells are to be returned * @return the cells on the specified column */ public Cell[] getColumn(int col) { // just in case this has been cleared, but something else holds // a reference to it if (cells == null) { readSheet(); } // Find the last non-null cell boolean found = false; int row = numRows - 1; while (row >= 0 && !found) { if (cells[row][col] != null) { found = true; } else { row--; } } // Only create entries for non-null cells Cell[] c = new Cell[row + 1]; for (int i = 0; i <= row; i++) { c[i] = getCell(col, i); } return c; } /** * Gets the name of this sheet * * @return the name of the sheet */ public String getName() { return name; } /** * Sets the name of this sheet * * @param s the sheet name */ final void setName(String s) { name = s; } /** * Determines whether the sheet is hidden * * @return whether or not the sheet is hidden * @deprecated in favour of the getSettings function */ public boolean isHidden() { return hidden; } /** * Gets the column info record for the specified column. If no column is specified, null is * returned * * @param col the column * @return the ColumnInfoRecord if specified, NULL otherwise */ public ColumnInfoRecord getColumnInfo(int col) { if (!columnInfosInitialized) { // Initialize the array Iterator i = columnInfosArray.iterator(); ColumnInfoRecord cir = null; while (i.hasNext()) { cir = (ColumnInfoRecord) i.next(); int startcol = Math.max(0, cir.getStartColumn()); int endcol = Math.min(columnInfos.length - 1, cir.getEndColumn()); for (int c = startcol; c <= endcol; c++) { columnInfos[c] = cir; } if (endcol < startcol) { columnInfos[startcol] = cir; } } columnInfosInitialized = true; } return col < columnInfos.length ? columnInfos[col] : null; } /** * Gets all the column info records * * @return the ColumnInfoRecordArray */ public ColumnInfoRecord[] getColumnInfos() { // Just chuck all the column infos we have into an array ColumnInfoRecord[] infos = new ColumnInfoRecord[columnInfosArray.size()]; for (int i = 0; i < columnInfosArray.size(); i++) { infos[i] = (ColumnInfoRecord) columnInfosArray.get(i); } return infos; } /** * Sets the visibility of this sheet * * @param h hidden flag */ final void setHidden(boolean h) { hidden = h; } /** * Clears out the array of cells. This is done for memory allocation reasons when reading very * large sheets */ final void clear() { cells = null; mergedCells = null; columnInfosArray.clear(); sharedFormulas.clear(); hyperlinks.clear(); columnInfosInitialized = false; if (!workbookSettings.getGCDisabled()) { System.gc(); } } /** Reads in the contents of this sheet */ final void readSheet() { // If this sheet contains only a chart, then set everything to // empty and do not bother parsing the sheet // Thanks to steve.brophy for spotting this if (!sheetBof.isWorksheet()) { numRows = 0; numCols = 0; cells = new Cell[0][0]; // return; } SheetReader reader = new SheetReader( excelFile, sharedStrings, formattingRecords, sheetBof, workbookBof, nineteenFour, workbook, startPosition, this); reader.read(); // Take stuff that was read in numRows = reader.getNumRows(); numCols = reader.getNumCols(); cells = reader.getCells(); rowProperties = reader.getRowProperties(); columnInfosArray = reader.getColumnInfosArray(); hyperlinks = reader.getHyperlinks(); conditionalFormats = reader.getConditionalFormats(); autoFilter = reader.getAutoFilter(); charts = reader.getCharts(); drawings = reader.getDrawings(); dataValidation = reader.getDataValidation(); mergedCells = reader.getMergedCells(); settings = reader.getSettings(); settings.setHidden(hidden); rowBreaks = reader.getRowBreaks(); columnBreaks = reader.getColumnBreaks(); workspaceOptions = reader.getWorkspaceOptions(); plsRecord = reader.getPLS(); buttonPropertySet = reader.getButtonPropertySet(); maxRowOutlineLevel = reader.getMaxRowOutlineLevel(); maxColumnOutlineLevel = reader.getMaxColumnOutlineLevel(); reader = null; if (!workbookSettings.getGCDisabled()) { System.gc(); } if (columnInfosArray.size() > 0) { ColumnInfoRecord cir = (ColumnInfoRecord) columnInfosArray.get(columnInfosArray.size() - 1); columnInfos = new ColumnInfoRecord[cir.getEndColumn() + 1]; } else { columnInfos = new ColumnInfoRecord[0]; } // Add any local names if (localNames != null) { for (Iterator it = localNames.iterator(); it.hasNext(); ) { NameRecord nr = (NameRecord) it.next(); if (nr.getBuiltInName() == BuiltInName.PRINT_AREA) { if (nr.getRanges().length > 0) { NameRecord.NameRange rng = nr.getRanges()[0]; settings.setPrintArea( rng.getFirstColumn(), rng.getFirstRow(), rng.getLastColumn(), rng.getLastRow()); } } else if (nr.getBuiltInName() == BuiltInName.PRINT_TITLES) { // There can be 1 or 2 entries. // Row entries have hardwired column entries (first and last // possible column) // Column entries have hardwired row entries (first and last // possible row) for (int i = 0; i < nr.getRanges().length; i++) { NameRecord.NameRange rng = nr.getRanges()[i]; if (rng.getFirstColumn() == 0 && rng.getLastColumn() == 255) { settings.setPrintTitlesRow(rng.getFirstRow(), rng.getLastRow()); } else { settings.setPrintTitlesCol(rng.getFirstColumn(), rng.getLastColumn()); } } } } } } /** * Gets the hyperlinks on this sheet * * @return an array of hyperlinks */ public Hyperlink[] getHyperlinks() { Hyperlink[] hl = new Hyperlink[hyperlinks.size()]; for (int i = 0; i < hyperlinks.size(); i++) { hl[i] = (Hyperlink) hyperlinks.get(i); } return hl; } /** * Gets the cells which have been merged on this sheet * * @return an array of range objects */ public Range[] getMergedCells() { if (mergedCells == null) { return new Range[0]; } return mergedCells; } /** * Gets the non-default rows. Used when copying spreadsheets * * @return an array of row properties */ public RowRecord[] getRowProperties() { RowRecord[] rp = new RowRecord[rowProperties.size()]; for (int i = 0; i < rp.length; i++) { rp[i] = (RowRecord) rowProperties.get(i); } return rp; } /** * Gets the data validations. Used when copying sheets * * @return the data validations */ public DataValidation getDataValidation() { return dataValidation; } /** * Gets the row record. Usually called by the cell in the specified row in order to determine its * size * * @param r the row * @return the RowRecord for the specified row */ RowRecord getRowInfo(int r) { if (!rowRecordsInitialized) { rowRecords = new RowRecord[getRows()]; Iterator i = rowProperties.iterator(); int rownum = 0; RowRecord rr = null; while (i.hasNext()) { rr = (RowRecord) i.next(); rownum = rr.getRowNumber(); if (rownum < rowRecords.length) { rowRecords[rownum] = rr; } } rowRecordsInitialized = true; } return r < rowRecords.length ? rowRecords[r] : null; } /** * Gets the row breaks. Called when copying sheets * * @return the explicit row breaks */ public final int[] getRowPageBreaks() { return rowBreaks; } /** * Gets the row breaks. Called when copying sheets * * @return the explicit row breaks */ public final int[] getColumnPageBreaks() { return columnBreaks; } /** * Gets the charts. Called when copying sheets * * @return the charts on this page */ public final Chart[] getCharts() { Chart[] ch = new Chart[charts.size()]; for (int i = 0; i < ch.length; i++) { ch[i] = (Chart) charts.get(i); } return ch; } /** * Gets the drawings. Called when copying sheets * * @return the drawings on this page */ public final DrawingGroupObject[] getDrawings() { DrawingGroupObject[] dr = new DrawingGroupObject[drawings.size()]; dr = (DrawingGroupObject[]) drawings.toArray(dr); return dr; } /** * Determines whether the sheet is protected * * @return whether or not the sheet is protected * @deprecated in favour of the getSettings() api */ public boolean isProtected() { return settings.isProtected(); } /** * Gets the workspace options for this sheet. Called during the copy process * * @return the workspace options */ public WorkspaceInformationRecord getWorkspaceOptions() { return workspaceOptions; } /** * Accessor for the sheet settings * * @return the settings for this sheet */ public SheetSettings getSettings() { return settings; } /** * Accessor for the workbook. In addition to be being used by this package, it is also used during * the importSheet process * * @return the workbook */ public WorkbookParser getWorkbook() { return workbook; } /** * Gets the column format for the specified column * * @param col the column number * @return the column format, or NULL if the column has no specific format * @deprecated use getColumnView instead */ public CellFormat getColumnFormat(int col) { CellView cv = getColumnView(col); return cv.getFormat(); } /** * Gets the column width for the specified column * * @param col the column number * @return the column width, or the default width if the column has no specified format */ public int getColumnWidth(int col) { return getColumnView(col).getSize() / 256; } /** * Gets the column width for the specified column * * @param col the column number * @return the column format, or the default format if no override is specified */ public CellView getColumnView(int col) { ColumnInfoRecord cir = getColumnInfo(col); CellView cv = new CellView(); if (cir != null) { cv.setDimension(cir.getWidth() / 256); // deprecated cv.setSize(cir.getWidth()); cv.setHidden(cir.getHidden()); cv.setFormat(formattingRecords.getXFRecord(cir.getXFIndex())); } else { cv.setDimension(settings.getDefaultColumnWidth()); // deprecated cv.setSize(settings.getDefaultColumnWidth() * 256); } return cv; } /** * Gets the row height for the specified column * * @param row the row number * @return the row height, or the default height if the row has no specified format * @deprecated use getRowView instead */ public int getRowHeight(int row) { return getRowView(row).getDimension(); } /** * Gets the row view for the specified row * * @param row the row number * @return the row format, or the default format if no override is specified */ public CellView getRowView(int row) { RowRecord rr = getRowInfo(row); CellView cv = new CellView(); if (rr != null) { cv.setDimension(rr.getRowHeight()); // deprecated cv.setSize(rr.getRowHeight()); cv.setHidden(rr.isCollapsed()); if (rr.hasDefaultFormat()) { cv.setFormat(formattingRecords.getXFRecord(rr.getXFIndex())); } } else { cv.setDimension(settings.getDefaultRowHeight()); cv.setSize(settings.getDefaultRowHeight()); // deprecated } return cv; } /** * Used when copying sheets in order to determine the type of this sheet * * @return the BOF Record */ public BOFRecord getSheetBof() { return sheetBof; } /** * Used when copying sheets in order to determine the type of the containing workboook * * @return the workbook BOF Record */ public BOFRecord getWorkbookBof() { return workbookBof; } /** * Accessor for the environment specific print record, invoked when copying sheets * * @return the environment specific print record */ public PLSRecord getPLS() { return plsRecord; } /** * Accessor for the button property set, used during copying * * @return the button property set */ public ButtonPropertySetRecord getButtonPropertySet() { return buttonPropertySet; } /** * Accessor for the number of images on the sheet * * @return the number of images on this sheet */ public int getNumberOfImages() { if (images == null) { initializeImages(); } return images.size(); } /** * Accessor for the image * * @param i the 0 based image number * @return the image at the specified position */ public Image getDrawing(int i) { if (images == null) { initializeImages(); } return (Image) images.get(i); } /** Initializes the images */ private void initializeImages() { if (images != null) { return; } images = new ArrayList(); DrawingGroupObject[] dgos = getDrawings(); for (int i = 0; i < dgos.length; i++) { if (dgos[i] instanceof Drawing) { images.add(dgos[i]); } } } /** Used by one of the demo programs for debugging purposes only */ public DrawingData getDrawingData() { SheetReader reader = new SheetReader( excelFile, sharedStrings, formattingRecords, sheetBof, workbookBof, nineteenFour, workbook, startPosition, this); reader.read(); return reader.getDrawingData(); } /** * Adds a local name to this shate * * @param nr the local name to add */ void addLocalName(NameRecord nr) { if (localNames == null) { localNames = new ArrayList(); } localNames.add(nr); } /** * Gets the conditional formats * * @return the conditional formats */ public ConditionalFormat[] getConditionalFormats() { ConditionalFormat[] formats = new ConditionalFormat[conditionalFormats.size()]; formats = (ConditionalFormat[]) conditionalFormats.toArray(formats); return formats; } /** * Returns the autofilter * * @return the autofilter */ public AutoFilter getAutoFilter() { return autoFilter; } /** * Accessor for the maximum column outline level. Used during a copy * * @return the maximum column outline level, or 0 if no outlines/groups */ public int getMaxColumnOutlineLevel() { return maxColumnOutlineLevel; } /** * Accessor for the maximum row outline level. Used during a copy * * @return the maximum row outline level, or 0 if no outlines/groups */ public int getMaxRowOutlineLevel() { return maxRowOutlineLevel; } }
/** An options record in the escher stream */ class Opt extends EscherAtom { /** The logger */ private static Logger logger = LoggerFactory.getLog(Opt.class); /** The binary data */ private byte[] data; /** The number of properties */ private int numProperties; /** The list of properties */ private ArrayList properties; /** Properties enumeration inner class */ static final class Property { int id; boolean blipId; boolean complex; int value; String stringValue; /** * Constructor * * @param i the property id * @param bl the blip id * @param co complex flag * @param v the value */ public Property(int i, boolean bl, boolean co, int v) { id = i; blipId = bl; complex = co; value = v; } /** * Constructor * * @param i the property id * @param bl the blip id * @param co complex flag * @param v the value * @param s the property string */ public Property(int i, boolean bl, boolean co, int v, String s) { id = i; blipId = bl; complex = co; value = v; stringValue = s; } } /** * Constructor * * @param erd the escher record data */ public Opt(EscherRecordData erd) { super(erd); numProperties = getInstance(); readProperties(); } /** Reads the properties */ private void readProperties() { properties = new ArrayList(); int pos = 0; byte[] bytes = getBytes(); for (int i = 0; i < numProperties; i++) { int val = IntegerHelper.getInt(bytes[pos], bytes[pos + 1]); int id = val & 0x3fff; int value = IntegerHelper.getInt(bytes[pos + 2], bytes[pos + 3], bytes[pos + 4], bytes[pos + 5]); Property p = new Property(id, (val & 0x4000) != 0, (val & 0x8000) != 0, value); pos += 6; properties.add(p); } for (Iterator i = properties.iterator(); i.hasNext(); ) { Property p = (Property) i.next(); if (p.complex) { p.stringValue = StringHelper.getUnicodeString(bytes, p.value / 2, pos); pos += p.value; } } } /** Constructor */ public Opt() { super(EscherRecordType.OPT); properties = new ArrayList(); setVersion(3); } /** * Accessor for the binary data * * @return the binary data */ byte[] getData() { numProperties = properties.size(); setInstance(numProperties); data = new byte[numProperties * 6]; int pos = 0; // Add in the root data for (Iterator i = properties.iterator(); i.hasNext(); ) { Property p = (Property) i.next(); int val = p.id & 0x3fff; if (p.blipId) { val |= 0x4000; } if (p.complex) { val |= 0x8000; } IntegerHelper.getTwoBytes(val, data, pos); IntegerHelper.getFourBytes(p.value, data, pos + 2); pos += 6; } // Add in any complex data for (Iterator i = properties.iterator(); i.hasNext(); ) { Property p = (Property) i.next(); if (p.complex && p.stringValue != null) { byte[] newData = new byte[data.length + p.stringValue.length() * 2]; System.arraycopy(data, 0, newData, 0, data.length); StringHelper.getUnicodeBytes(p.stringValue, newData, data.length); data = newData; } } return setHeaderData(data); } /** * Adds a property into the options * * @param id the property id * @param blip the blip id * @param complex whether it's a complex property * @param val the value */ void addProperty(int id, boolean blip, boolean complex, int val) { Property p = new Property(id, blip, complex, val); properties.add(p); } /** * Adds a property into the options * * @param id the property id * @param blip the blip id * @param complex whether it's a complex property * @param val the value * @param s the value string */ void addProperty(int id, boolean blip, boolean complex, int val, String s) { Property p = new Property(id, blip, complex, val, s); properties.add(p); } /** * Accessor for the property * * @param id the property id * @return the property */ Property getProperty(int id) { boolean found = false; Property p = null; for (Iterator i = properties.iterator(); i.hasNext() && !found; ) { p = (Property) i.next(); if (p.id == id) { found = true; } } return found ? p : null; } }
/** A record which merely holds the OBJ data. Used when copying files which contain images */ public class ObjRecord extends WritableRecordData { /** The logger */ private static final Logger logger = LoggerFactory.getLog(ObjRecord.class); /** The object type */ private ObjType type; /** Indicates whether this record was read in */ private boolean read; /** The object id */ private int objectId; /** Object type enumeration */ private static final class ObjType { public int value; public String desc; private static ObjType[] types = new ObjType[0]; ObjType(int v, String d) { value = v; desc = d; ObjType[] oldtypes = types; types = new ObjType[types.length + 1]; System.arraycopy(oldtypes, 0, types, 0, oldtypes.length); types[oldtypes.length] = this; } public String toString() { return desc; } public static ObjType getType(int val) { ObjType retval = UNKNOWN; for (int i = 0; i < types.length && retval == UNKNOWN; i++) { if (types[i].value == val) { retval = types[i]; } } return retval; } } // The object types public static final ObjType GROUP = new ObjType(0x0, "Group"); public static final ObjType LINE = new ObjType(0x01, "Line"); public static final ObjType RECTANGLE = new ObjType(0x02, "Rectangle"); public static final ObjType OVAL = new ObjType(0x03, "Oval"); public static final ObjType ARC = new ObjType(0x04, "Arc"); public static final ObjType CHART = new ObjType(0x05, "Chart"); public static final ObjType TEXT = new ObjType(0x06, "Text"); public static final ObjType BUTTON = new ObjType(0x07, "Button"); public static final ObjType PICTURE = new ObjType(0x08, "Picture"); public static final ObjType POLYGON = new ObjType(0x09, "Polygon"); public static final ObjType CHECKBOX = new ObjType(0x0b, "Checkbox"); public static final ObjType OPTION = new ObjType(0x0c, "Option"); public static final ObjType EDITBOX = new ObjType(0x0d, "Edit Box"); public static final ObjType LABEL = new ObjType(0x0e, "Label"); public static final ObjType DIALOGUEBOX = new ObjType(0x0f, "Dialogue Box"); public static final ObjType SPINBOX = new ObjType(0x10, "Spin Box"); public static final ObjType SCROLLBAR = new ObjType(0x11, "Scrollbar"); public static final ObjType LISTBOX = new ObjType(0x12, "List Box"); public static final ObjType GROUPBOX = new ObjType(0x13, "Group Box"); public static final ObjType COMBOBOX = new ObjType(0x14, "Combo Box"); public static final ObjType MSOFFICEDRAWING = new ObjType(0x1e, "MS Office Drawing"); public static final ObjType FORMCONTROL = new ObjType(0x14, "Form Combo Box"); public static final ObjType EXCELNOTE = new ObjType(0x19, "Excel Note"); public static final ObjType UNKNOWN = new ObjType(0xff, "Unknown"); // Field sub records private static final int COMMON_DATA_LENGTH = 22; private static final int CLIPBOARD_FORMAT_LENGTH = 6; private static final int PICTURE_OPTION_LENGTH = 6; private static final int NOTE_STRUCTURE_LENGTH = 26; private static final int COMBOBOX_STRUCTURE_LENGTH = 44; private static final int END_LENGTH = 4; /** * Constructs this object from the raw data * * @param t the raw data */ public ObjRecord(Record t) { super(t); byte[] data = t.getData(); int objtype = IntegerHelper.getInt(data[4], data[5]); read = true; type = ObjType.getType(objtype); if (type == UNKNOWN) { logger.warn("unknown object type code " + objtype); } objectId = IntegerHelper.getInt(data[6], data[7]); } /** * Constructor * * @param objId the object id * @param t the object type */ ObjRecord(int objId, ObjType t) { super(Type.OBJ); objectId = objId; type = t; } /** * Expose the protected function to the DefaultSheet in this package * * @return the raw record data */ public byte[] getData() { if (read) { return getRecord().getData(); } if (type == PICTURE || type == CHART) { return getPictureData(); } else if (type == EXCELNOTE) { return getNoteData(); } else if (type == COMBOBOX) { return getComboBoxData(); } else { Assert.verify(false); } return null; } /** * Gets the ObjRecord subrecords for a picture * * @return the binary data for the picture */ private byte[] getPictureData() { int dataLength = COMMON_DATA_LENGTH + CLIPBOARD_FORMAT_LENGTH + PICTURE_OPTION_LENGTH + END_LENGTH; int pos = 0; byte[] data = new byte[dataLength]; // The org.areasy.common.parser.documents.excel.common.data // record id IntegerHelper.getTwoBytes(0x15, data, pos); // record length IntegerHelper.getTwoBytes(COMMON_DATA_LENGTH - 4, data, pos + 2); // object type IntegerHelper.getTwoBytes(type.value, data, pos + 4); // object id IntegerHelper.getTwoBytes(objectId, data, pos + 6); // the options IntegerHelper.getTwoBytes(0x6011, data, pos + 8); pos += COMMON_DATA_LENGTH; // The clipboard format // record id IntegerHelper.getTwoBytes(0x7, data, pos); // record length IntegerHelper.getTwoBytes(CLIPBOARD_FORMAT_LENGTH - 4, data, pos + 2); // the data IntegerHelper.getTwoBytes(0xffff, data, pos + 4); pos += CLIPBOARD_FORMAT_LENGTH; // Picture option flags // record id IntegerHelper.getTwoBytes(0x8, data, pos); // record length IntegerHelper.getTwoBytes(PICTURE_OPTION_LENGTH - 4, data, pos + 2); // the data IntegerHelper.getTwoBytes(0x1, data, pos + 4); pos += CLIPBOARD_FORMAT_LENGTH; // End record id IntegerHelper.getTwoBytes(0x0, data, pos); // record length IntegerHelper.getTwoBytes(END_LENGTH - 4, data, pos + 2); // the data pos += END_LENGTH; return data; } /** * Gets the ObjRecord subrecords for a note * * @return the note data */ private byte[] getNoteData() { int dataLength = COMMON_DATA_LENGTH + NOTE_STRUCTURE_LENGTH + END_LENGTH; int pos = 0; byte[] data = new byte[dataLength]; // The org.areasy.common.parser.documents.excel.common.data // record id IntegerHelper.getTwoBytes(0x15, data, pos); // record length IntegerHelper.getTwoBytes(COMMON_DATA_LENGTH - 4, data, pos + 2); // object type IntegerHelper.getTwoBytes(type.value, data, pos + 4); // object id IntegerHelper.getTwoBytes(objectId, data, pos + 6); // the options IntegerHelper.getTwoBytes(0x4011, data, pos + 8); pos += COMMON_DATA_LENGTH; // The note structure // record id IntegerHelper.getTwoBytes(0xd, data, pos); // record length IntegerHelper.getTwoBytes(NOTE_STRUCTURE_LENGTH - 4, data, pos + 2); // the data pos += NOTE_STRUCTURE_LENGTH; // End // record id IntegerHelper.getTwoBytes(0x0, data, pos); // record length IntegerHelper.getTwoBytes(END_LENGTH - 4, data, pos + 2); // the data pos += END_LENGTH; return data; } /** * Gets the ObjRecord subrecords for a combo box * * @return returns the binary data for a combo box */ private byte[] getComboBoxData() { int dataLength = COMMON_DATA_LENGTH + COMBOBOX_STRUCTURE_LENGTH + END_LENGTH; int pos = 0; byte[] data = new byte[dataLength]; // The org.areasy.common.parser.documents.excel.common.data // record id IntegerHelper.getTwoBytes(0x15, data, pos); // record length IntegerHelper.getTwoBytes(COMMON_DATA_LENGTH - 4, data, pos + 2); // object type IntegerHelper.getTwoBytes(type.value, data, pos + 4); // object id IntegerHelper.getTwoBytes(objectId, data, pos + 6); // the options IntegerHelper.getTwoBytes(0x0, data, pos + 8); pos += COMMON_DATA_LENGTH; // The combo box structure // record id IntegerHelper.getTwoBytes(0xc, data, pos); // record length IntegerHelper.getTwoBytes(0x14, data, pos + 2); // the data data[pos + 14] = 0x01; data[pos + 16] = 0x04; data[pos + 20] = 0x10; data[pos + 24] = 0x13; data[pos + 26] = (byte) 0xee; data[pos + 27] = 0x1f; data[pos + 30] = 0x04; data[pos + 34] = 0x01; data[pos + 35] = 0x06; data[pos + 38] = 0x02; data[pos + 40] = 0x08; data[pos + 42] = 0x40; pos += COMBOBOX_STRUCTURE_LENGTH; // End // record id IntegerHelper.getTwoBytes(0x0, data, pos); // record length IntegerHelper.getTwoBytes(END_LENGTH - 4, data, pos + 2); // the data pos += END_LENGTH; return data; } /** * Expose the protected function to the DefaultSheet in this package * * @return the raw record data */ public Record getRecord() { return super.getRecord(); } /** * Accessor for the object type * * @return the object type */ public ObjType getType() { return type; } /** * Accessor for the object id * * @return accessor for the object id */ public int getObjectId() { return objectId; } }
/** Abstract class for all records which actually contain cell values */ public abstract class CellValue extends RecordData implements Cell, CellFeaturesAccessor { /** The logger */ private static Logger logger = LoggerFactory.getLog(CellValue.class); /** The row number of this cell record */ private int row; /** The column number of this cell record */ private int column; /** The XF index */ private int xfIndex; /** A handle to the formatting records, so that we can retrieve the formatting information */ private FormattingRecords formattingRecords; /** A lazy initialize flag for the cell format */ private boolean initialized; /** The cell format */ private XFRecord format; /** A handle back to the sheet */ private DefaultSheet sheet; /** The cell features */ private CellFeatures features; /** * Constructs this object from the raw cell data * * @param t the raw cell data * @param fr the formatting records * @param si the sheet containing this cell */ protected CellValue(Record t, FormattingRecords fr, DefaultSheet si) { super(t); byte[] data = getRecord().getData(); row = IntegerHelper.getInt(data[0], data[1]); column = IntegerHelper.getInt(data[2], data[3]); xfIndex = IntegerHelper.getInt(data[4], data[5]); sheet = si; formattingRecords = fr; initialized = false; } /** * Interface method which returns the row number of this cell * * @return the zero base row number */ public final int getRow() { return row; } /** * Interface method which returns the column number of this cell * * @return the zero based column number */ public final int getColumn() { return column; } /** * Gets the XFRecord corresponding to the index number. Used when copying a spreadsheet * * @return the xf index for this cell */ public final int getXFIndex() { return xfIndex; } /** * Gets the CellFormat object for this cell. Used by the WritableWorkbook API * * @return the CellFormat used for this cell */ public CellFormat getCellFormat() { if (!initialized) { format = formattingRecords.getXFRecord(xfIndex); initialized = true; } return format; } /** * Determines whether or not this cell has been hidden * * @return TRUE if this cell has been hidden, FALSE otherwise */ public boolean isHidden() { ColumnInfoRecord cir = sheet.getColumnInfo(column); if (cir != null && (cir.getWidth() == 0 || cir.getHidden())) { return true; } RowRecord rr = sheet.getRowInfo(row); if (rr != null && (rr.getRowHeight() == 0 || rr.isCollapsed())) { return true; } return false; } /** * Accessor for the sheet * * @return the sheet */ protected DefaultSheet getSheet() { return sheet; } /** * Accessor for the cell features * * @return the cell features or NULL if this cell doesn't have any */ public CellFeatures getCellFeatures() { return features; } /** * Sets the cell features during the reading process * * @param cf the cell features */ public void setCellFeatures(CellFeatures cf) { if (features != null) { logger.warn("current cell features not null - overwriting"); } features = cf; } }
/** A Note (TXO) record which contains the information for comments */ public class NoteRecord extends WritableRecordData { /** The logger */ private static Logger logger = LoggerFactory.getLog(NoteRecord.class); /** The raw drawing data which was read in */ private byte[] data; /** The row */ private int row; /** The column */ private int column; /** The object id */ private int objectId; /** * Constructs this object from the raw data * * @param t the raw data */ public NoteRecord(Record t) { super(t); data = getRecord().getData(); row = IntegerHelper.getInt(data[0], data[1]); column = IntegerHelper.getInt(data[2], data[3]); objectId = IntegerHelper.getInt(data[6], data[7]); } /** * Constructor * * @param d the drawing data */ public NoteRecord(byte[] d) { super(Type.NOTE); data = d; } /** * Constructor used when writing a Note * * @param c the column * @param r the row * @param id the object id */ public NoteRecord(int c, int r, int id) { super(Type.NOTE); row = r; column = c; objectId = id; } /** * Expose the protected function to the DefaultSheet in this package * * @return the raw record data */ public byte[] getData() { if (data != null) { return data; } String author = ""; data = new byte[8 + author.length() + 4]; // the row IntegerHelper.getTwoBytes(row, data, 0); // the column IntegerHelper.getTwoBytes(column, data, 2); // the object id IntegerHelper.getTwoBytes(objectId, data, 6); // the length of the string IntegerHelper.getTwoBytes(author.length(), data, 8); // the string // StringHelper.getBytes(author, data, 11); // data[data.length-1]=(byte)0x24; return data; } /** * Accessor for the row * * @return the row */ int getRow() { return row; } /** * Accessor for the column * * @return the column */ int getColumn() { return column; } /** * Accessor for the object id * * @return the object id */ public int getObjectId() { return objectId; } }