/* ==================================================================== 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. ==================================================================== */ namespace HH.WMS.Utils.NPOI.HSSF.UserModel { using System; using System.IO; using System.Collections; using System.Collections.Generic; using HH.WMS.Utils.NPOI.HSSF.Record; using HH.WMS.Utils.NPOI.HSSF.Model; using HH.WMS.Utils.NPOI.Util; using HH.WMS.Utils.NPOI.SS.UserModel; using HH.WMS.Utils.NPOI.SS; /// /// High level representation of a row of a spReadsheet. /// Only rows that have cells should be Added to a Sheet. /// @author Andrew C. Oliver (acoliver at apache dot org) /// @author Glen Stampoultzis (glens at apache.org) /// [Serializable] public class HSSFRow : IComparable,IRow { /// /// used for collections /// public const int INITIAL_CAPACITY = 5; private int rowNum; private SortedDictionary cells = new SortedDictionary(); /** * reference to low level representation */ [NonSerialized] private RowRecord row; /** * reference to containing low level Workbook */ private HSSFWorkbook book; /** * reference to containing Sheet */ private HSSFSheet sheet; // TODO - ditch this constructor [Obsolete] public HSSFRow() { } /// /// Creates new HSSFRow from scratch. Only HSSFSheet should do this. /// /// low-level Workbook object containing the sheet that Contains this row /// low-level Sheet object that Contains this Row /// the row number of this row (0 based) /// public HSSFRow(HSSFWorkbook book, HSSFSheet sheet, int rowNum):this(book, sheet, new RowRecord(rowNum)) { } /// /// Creates an HSSFRow from a low level RowRecord object. Only HSSFSheet should do /// this. HSSFSheet uses this when an existing file is Read in. /// /// low-level Workbook object containing the sheet that Contains this row /// low-level Sheet object that Contains this Row /// the low level api object this row should represent /// public HSSFRow(HSSFWorkbook book, HSSFSheet sheet, RowRecord record) { this.book = book; this.sheet = sheet; row = record; RowNum=(record.RowNumber); // Don't trust colIx boundaries as read by other apps // set the RowRecord empty for the moment record.SetEmpty(); } /// /// Use this to create new cells within the row and return it. /// The cell that is returned is a CELL_TYPE_BLANK (/). /// The type can be changed either through calling SetCellValue or SetCellType. /// /// the column number this cell represents /// a high level representation of the created cell. public ICell CreateCell(int column) { return this.CreateCell(column, CellType.BLANK); } /// /// Use this to create new cells within the row and return it. /// The cell that is returned is a CELL_TYPE_BLANK. The type can be changed /// either through calling setCellValue or setCellType. /// /// the column number this cell represents /// a high level representation of the created cell. /// public ICell CreateCell(int columnIndex, CellType type) { short shortCellNum = (short)columnIndex; if (columnIndex > 0x7FFF) { shortCellNum = (short)(0xffff - columnIndex); } ICell cell = new HSSFCell(book, sheet, RowNum, (short)columnIndex, type); AddCell(cell); sheet.Sheet.AddValueRecord(RowNum, ((HSSFCell)cell).CellValueRecord); return cell; } /// /// Remove the Cell from this row. /// /// The cell to Remove. public void RemoveCell(ICell cell) { if (cell == null) { throw new ArgumentException("cell must not be null"); } RemoveCell((HSSFCell)cell, true); } /// /// Removes the cell. /// /// The cell. /// if set to true [also remove records]. private void RemoveCell(ICell cell, bool alsoRemoveRecords) { int column = cell.ColumnIndex; if (column < 0) { throw new Exception("Negative cell indexes not allowed"); } //if (column >= cells.Count || cell != cells[column]) if(!cells.ContainsKey(column)|| cell!=cells[column]) { throw new Exception("Specified cell is not from this row"); } if (cell.IsPartOfArrayFormulaGroup) { ((HSSFCell)cell).NotifyArrayFormulaChanging(); } cells.Remove(column); if (alsoRemoveRecords) { CellValueRecordInterface cval = ((HSSFCell)cell).CellValueRecord; sheet.Sheet.RemoveValueRecord(RowNum, cval); } if (cell.ColumnIndex + 1 == row.LastCol) { row.LastCol = CalculateNewLastCellPlusOne(row.LastCol); } if (cell.ColumnIndex == row.FirstCol) { row.FirstCol = CalculateNewFirstCell(row.FirstCol); } } /** * used internally to refresh the "last cell plus one" when the last cell is removed. * @return 0 when row contains no cells */ private int CalculateNewLastCellPlusOne(int lastcell) { int cellIx = lastcell - 1; ICell r = RetrieveCell(cellIx); while (r == null) { if (cellIx < 0) { return 0; } r = RetrieveCell(--cellIx); } return cellIx + 1; } /** * used internally to refresh the "first cell" when the first cell is removed. * @return 0 when row contains no cells (also when first cell is occupied) */ private int CalculateNewFirstCell(int firstcell) { int cellIx = firstcell + 1; ICell r = RetrieveCell(cellIx); if (cells.Count == 0) return 0; while (r == null) { if (cellIx <= cells.Count) { return 0; } r = RetrieveCell(++cellIx); } return cellIx; } /// /// Create a high level Cell object from an existing low level record. Should /// only be called from HSSFSheet or HSSFRow itself. /// /// The low level cell to Create the high level representation from /// the low level record passed in public ICell CreateCellFromRecord(CellValueRecordInterface cell) { ICell hcell = new HSSFCell(book, sheet, cell); AddCell(hcell); int colIx = cell.Column; if (row.IsEmpty) { row.FirstCol=(colIx); row.LastCol=(colIx + 1); } else { if (colIx < row.FirstCol) { row.FirstCol = (colIx); } else if (colIx > row.LastCol) { row.LastCol = (colIx + 1); } else { // added cell is within first and last cells } } // TODO - RowRecord column boundaries need to be updated for cell comments too return hcell; } /// /// true, when the row is invisible. This is the case when the height is zero. /// public bool IsHidden { get { return this.ZeroHeight; } set { this.ZeroHeight = value; } } /// /// Removes all the cells from the row, and their /// records too. /// public void RemoveAllCells() { int initialLen = cells.Count; for (int i = 0; i < initialLen; i++) { RemoveCell(cells[i], true); } //cells = new HSSFCell[INITIAL_CAPACITY]; } /// /// Get row number this row represents /// /// the row number (0 based) public int RowNum { get { return rowNum; } set { int maxrow = SpreadsheetVersion.EXCEL97.LastRowIndex; if ((value < 0) || (value > maxrow)) { throw new ArgumentException("Invalid row number (" + value + ") outside allowable range (0.." + maxrow + ")"); } this.rowNum = value; if (row != null) { row.RowNumber = (value); // used only for KEY comparison (HSSFRow) } } } /// /// Returns the rows outline level. Increased as you /// put it into more Groups (outlines), reduced as /// you take it out of them. /// /// The outline level. public int OutlineLevel { get { return row.OutlineLevel; } } /// /// Moves the supplied cell to a new column, which /// must not already have a cell there! /// /// The cell to move /// The new column number (0 based) public void MoveCell(ICell cell, int newColumn) { // Ensure the destination is free //if (cells.Count > newColumn && cells[newColumn] != null) if(cells.ContainsKey(newColumn)) { throw new ArgumentException("Asked to move cell to column " + newColumn + " but there's already a cell there"); } // Check it's one of ours bool existflag = false; foreach (ICell cellinrow in cells.Values) { if (cellinrow.Equals(cell)) { existflag = true; break; } } if (!existflag) { throw new ArgumentException("Asked to move a cell, but it didn't belong to our row"); } // Move the cell to the new position // (Don't Remove the records though) RemoveCell(cell, false); ((HSSFCell)cell).UpdateCellNum(newColumn); AddCell(cell); } /** * Returns the HSSFSheet this row belongs to * * @return the HSSFSheet that owns this row */ public ISheet Sheet { get { return sheet; } } /// /// used internally to Add a cell. /// /// The cell. private void AddCell(ICell cell) { int column = cell.ColumnIndex; // re-allocate cells array as required. //if (column >= cells.Count) //{ // HSSFCell[] oldCells = cells; // int newSize = oldCells.Length * 2; // if (newSize < column + 1) // { // newSize = column + 1; // } // cells = new HSSFCell[newSize]; // Array.Copy(oldCells, 0, cells, 0, oldCells.Length); //} if (cells.ContainsKey(column)) { cells.Remove(column); } cells.Add(column, cell); // fix up firstCol and lastCol indexes if (row.IsEmpty|| column < row.FirstCol) { row.FirstCol=(column); } if (row.IsEmpty || column >= row.LastCol) { row.LastCol=((short)(column + 1)); // +1 -> for one past the last index } } /// /// Get the hssfcell representing a given column (logical cell) /// 0-based. If you ask for a cell that is not defined, then /// you Get a null. /// This is the basic call, with no policies applied /// /// 0 based column number /// Cell representing that column or null if Undefined. private ICell RetrieveCell(int cellnum) { if (!cells.ContainsKey(cellnum)) return null; //if (cellnum < 0 || cellnum >= cells.Count) return null; return cells[cellnum]; } /// /// Get the hssfcell representing a given column (logical cell) /// 0-based. If you ask for a cell that is not defined then /// you get a null, unless you have set a different /// MissingCellPolicy on the base workbook. /// /// Short method signature provided to retain binary /// compatibility. /// /// 0 based column number /// Cell representing that column or null if undefined. [Obsolete] public ICell GetCell(short cellnum) { int ushortCellNum = cellnum & 0x0000FFFF; // avoid sign extension return GetCell(ushortCellNum); } /// /// Get the hssfcell representing a given column (logical cell) /// 0-based. If you ask for a cell that is not defined then /// you get a null, unless you have set a different /// MissingCellPolicy on the base workbook. /// /// 0 based column number /// Cell representing that column or null if undefined. public ICell GetCell(int cellnum) { return GetCell(cellnum, book.MissingCellPolicy); } /// /// Get the hssfcell representing a given column (logical cell) /// 0-based. If you ask for a cell that is not defined, then /// your supplied policy says what to do /// /// 0 based column number /// Policy on blank / missing cells /// that column or null if Undefined + policy allows. public ICell GetCell(int cellnum, MissingCellPolicy policy) { ICell cell = RetrieveCell(cellnum); if (policy == MissingCellPolicy.RETURN_NULL_AND_BLANK) { return cell; } if (policy == MissingCellPolicy.RETURN_BLANK_AS_NULL) { if (cell == null) return cell; if (cell.CellType == CellType.BLANK) { return null; } return cell; } if (policy == MissingCellPolicy.CREATE_NULL_AS_BLANK) { if (cell == null) { return CreateCell(cellnum, CellType.BLANK); } return cell; } throw new ArgumentException("Illegal policy " + policy + " (" + policy.id + ")"); } /// /// Get the number of the first cell contained in this row. /// /// the first logical cell in the row, or -1 if the row does not contain any cells. public short FirstCellNum { get { if (row.IsEmpty) return -1; else return (short)row.FirstCol; } } /// /// Gets the index of the last cell contained in this row PLUS ONE /// . The result also happens to be the 1-based column number of the last cell. This value can be used as a /// standard upper bound when iterating over cells: /// /// /// short representing the last logical cell in the row PLUS ONE, or -1 if the /// row does not contain any cells. /// /// /// short minColIx = row.GetFirstCellNum(); /// short maxColIx = row.GetLastCellNum(); /// for(short colIx=minColIx; colIx<maxColIx; colIx++) { /// Cell cell = row.GetCell(colIx); /// if(cell == null) { /// continue; /// } /// //... do something with cell /// } /// public short LastCellNum { get { if (row.IsEmpty) { return -1; } return (short)row.LastCol; } } /// /// Gets the number of defined cells (NOT number of cells in the actual row!). /// That is to say if only columns 0,4,5 have values then there would be 3. /// /// the number of defined cells in the row. public int PhysicalNumberOfCells { get { return cells.Count; } } /// /// Gets or sets whether or not to Display this row with 0 height /// /// height is zero or not. public bool ZeroHeight { get { return row.ZeroHeight; } set { row.ZeroHeight=(value); } } /// /// Get or sets the row's height or ff (-1) for undefined/default-height in twips (1/20th of a point) /// /// rowheight or 0xff for Undefined (use sheet default) public short Height { get { short height = row.Height; //The low-order 15 bits contain the row height. //The 0x8000 bit indicates that the row is standard height (optional) if ((height & 0x8000) != 0) height = sheet.Sheet.DefaultRowHeight; else height &= 0x7FFF; return height; } set { if (value == -1) { row.Height = 20 * 20; } else { row.BadFontHeight = true; row.Height = value; } } } /// /// is this row formatted? Most aren't, but some rows /// do have whole-row styles. For those that do, you /// can get the formatting from {@link #getRowStyle()} /// /// /// true if this instance is formatted; otherwise, false. /// public bool IsFormatted { get { return row.Formatted; } } /// /// Returns the whole-row cell styles. Most rows won't /// have one of these, so will return null. Call IsFormmated to check first /// /// The row style. public ICellStyle RowStyle { get { if (!IsFormatted) { return null; } short styleIndex = row.XFIndex; ExtendedFormatRecord xf = book.Workbook.GetExFormatAt(styleIndex); return new HSSFCellStyle(styleIndex, xf, book); } set { row.Formatted=(true); row.XFIndex=(value.Index); } } /// /// Get the row's height or ff (-1) for Undefined/default-height in points (20*Height) /// /// row height or 0xff for Undefined (use sheet default). public float HeightInPoints { get { return (row.Height / 20f); } set { if (value == -1) { row.Height = 20; } else { row.BadFontHeight = (true); row.Height = (short)(value * 20); } } } /// /// Get the lowlevel RowRecord represented by this object - should only be called /// by other parts of the high level API /// /// RowRecord this row represents public RowRecord RowRecord { get { return row; } } /// /// used internally to refresh the "first cell" when the first cell is Removed. /// /// The first cell index. /// [Obsolete] private short FindFirstCell(int firstcell) { short cellnum = (short)(firstcell + 1); ICell r = GetCell(cellnum); while (r == null && cellnum <= LastCellNum) { r = GetCell(++cellnum); } if (cellnum > LastCellNum) return -1; return cellnum; } /// /// Get cells in the row /// public List Cells { get { return new List(this.cells.Values); } } /// /// Gets the cell enumerator of the physically defined cells. /// /// /// Note that the 4th element might well not be cell 4, as the iterator /// will not return Un-defined (null) cells. /// Call CellNum on the returned cells to know which cell they are. /// public IEnumerator GetEnumerator() { //return //new CellEnumerator(this.cells); return this.cells.Values.GetEnumerator(); } ///// ///// Alias for {@link CellEnumerator} to allow ///// foreach loops ///// ///// //public IEnumerator GetEnumerator() //{ // return GetCellEnumerator(); //} /* * An iterator over the (physical) cells in the row. */ //private class CellEnumerator : IEnumerator //{ // int thisId = -1; // int nextId = -1; // private HSSFCell[] cells; // public CellEnumerator() // { // } // public CellEnumerator(HSSFCell[] cells) // { // this.cells = cells; // } // public bool MoveNext() // { // FindNext(); // return nextId < cells.Length; // } // public Object Current // { // get // { // thisId = nextId; // Cell cell = cells[thisId]; // return cell; // } // } // public void Remove() // { // if (thisId == -1) // throw new InvalidOperationException("Remove() called before next()"); // cells[thisId] = null; // } // private void FindNext() // { // int i = nextId + 1; // for (; i < cells.Length; i++) // { // if (cells[i] != null) break; // } // nextId = i; // } // public void Reset() // { // thisId = -1; // nextId = -1; // } //} /// /// Compares the current instance with another object of the same type and returns an integer that indicates whether the current instance precedes, follows, or occurs in the same position in the sort order as the other object. /// /// An object to compare with this instance. /// /// A 32-bit signed integer that indicates the relative order of the objects being compared. The return value has these meanings: /// Value /// Meaning /// Less than zero /// This instance is less than . /// Zero /// This instance is equal to . /// Greater than zero /// This instance is greater than . /// /// /// is not the same type as this instance. /// public int CompareTo(Object obj) { HSSFRow loc = (HSSFRow)obj; if (this.RowNum == loc.RowNum) { return 0; } if (this.RowNum < loc.RowNum) { return -1; } if (this.RowNum > loc.RowNum) { return 1; } return -1; } /// /// Determines whether the specified is equal to the current . /// /// The to compare with the current . /// /// true if the specified is equal to the current ; otherwise, false. /// /// /// The parameter is null. /// public override bool Equals(Object obj) { if (!(obj is HSSFRow)) { return false; } HSSFRow loc = (HSSFRow)obj; if (this.RowNum == loc.RowNum) { return true; } return false; } /// /// Returns a hash code. In this case it is the number of the row. /// public override int GetHashCode () { return RowNum; } } }