diff --git a/OpenXmlFormats/Spreadsheet/Sheet/CT_Col.cs b/OpenXmlFormats/Spreadsheet/Sheet/CT_Col.cs index 02844e308..ccbeb90c9 100644 --- a/OpenXmlFormats/Spreadsheet/Sheet/CT_Col.cs +++ b/OpenXmlFormats/Spreadsheet/Sheet/CT_Col.cs @@ -107,12 +107,18 @@ public bool IsSetHidden() } public bool IsSetStyle() { - return this.styleField!=null; + return this.styleField != null && this.styleField != 0; } public bool IsSetWidth() { return this.widthField > 0; } + + public bool IsSetNumber() + { + return min > 0 && max > 0; + } + public bool IsSetCollapsed() { return this.collapsedSpecifiedField; @@ -125,6 +131,13 @@ public bool IsSetOutlineLevel() { return this.outlineLevelField != 0; } + + public void SetNumber(uint number) + { + min = number; + max = number; + } + public void UnsetHidden() { this.hiddenField = false; @@ -135,7 +148,20 @@ public void UnsetCollapsed() this.collapsedSpecified = false; } + public void UnsetWidth() + { + this.widthField = -1; + } + + public void UnsetCustomWidth() + { + this.customWidthField = false; + } + public void UnsetStyle() + { + this.styleField = 0; + } [XmlAttribute] public uint? style @@ -286,9 +312,21 @@ internal void Write(StreamWriter sw, string nodeName) sw.Write("/>"); } - - - + public void Set(CT_Col col) + { + bestFitField = col.bestFitField; + collapsedField = col.collapsedField; + collapsedSpecifiedField = col.collapsedSpecifiedField; + customWidthField = col.customWidthField; + hiddenField = col.hiddenField; + maxField = col.maxField; + minField = col.minField; + outlineLevelField = col.outlineLevelField; + phoneticField = col.phoneticField; + styleField = col.styleField; + widthField = col.widthField; + widthSpecifiedField = col.widthSpecifiedField; + } public CT_Col Copy() { diff --git a/OpenXmlFormats/Spreadsheet/Sheet/CT_Cols.cs b/OpenXmlFormats/Spreadsheet/Sheet/CT_Cols.cs index 00275b66e..1fe35ef4f 100644 --- a/OpenXmlFormats/Spreadsheet/Sheet/CT_Cols.cs +++ b/OpenXmlFormats/Spreadsheet/Sheet/CT_Cols.cs @@ -26,12 +26,23 @@ public void SetColArray(List array) } public CT_Col AddNewCol() { + if (null == colField) + { + colField = new List(); + } + CT_Col newCol = new CT_Col(); this.colField.Add(newCol); return newCol; } + public CT_Col InsertNewCol(int index) { + if (null == colField) + { + colField = new List(); + } + CT_Col newCol = new CT_Col(); this.colField.Insert(index, newCol); return newCol; @@ -40,7 +51,14 @@ public void RemoveCol(int index) { this.colField.RemoveAt(index); } - + public void RemoveCols(IList toRemove) + { + if (colField == null) return; + foreach (CT_Col c in toRemove) + { + colField.Remove(c); + } + } public int sizeOfColArray() { return col.Count; diff --git a/OpenXmlFormats/Spreadsheet/Sheet/CT_Comment.cs b/OpenXmlFormats/Spreadsheet/Sheet/CT_Comment.cs index 08da1716f..b5f79ab38 100644 --- a/OpenXmlFormats/Spreadsheet/Sheet/CT_Comment.cs +++ b/OpenXmlFormats/Spreadsheet/Sheet/CT_Comment.cs @@ -105,5 +105,11 @@ public string guid this.guidField = value; } } + + public void Set(CT_Comment comment) + { + this.authorId = comment.authorId; + this.text = comment.text; + } } } diff --git a/OpenXmlFormats/Vml/Main.cs b/OpenXmlFormats/Vml/Main.cs index 233d7ae97..200143488 100644 --- a/OpenXmlFormats/Vml/Main.cs +++ b/OpenXmlFormats/Vml/Main.cs @@ -1078,6 +1078,36 @@ public CT_TextPath textpath } } + public void Set(CT_Shape shape) + { + this.adj = shape.adj; + this.anchorlock = shape.anchorlock; + this.borderbottom = shape.borderbottom; + this.borderleft = shape.borderleft; + this.borderright = shape.borderright; + this.bordertop = shape.bordertop; + this.equationxml = shape.equationxml; + this.fill = shape.fill; + this.fillcolor = shape.fillcolor; + this.formulas = shape.formulas; + this.handles = shape.handles; + this.imagedata = shape.imagedata; + this.insetmode = shape.insetmode; + this.iscomment = shape.iscomment; + this.@lock = shape.@lock; + this.path = shape.path; + this.shadow = shape.shadow; + this.spid = shape.spid; + this.stroke = shape.stroke; + this.stroked = shape.stroked; + this.style = shape.style; + this.textbox = shape.textbox; + this.textdata = shape.textdata; + this.textpath = shape.textpath; + this.wrap = shape.wrap; + this.wrapcoords = shape.wrapcoords; + } + public CT_TextPath AddNewTextpath() { this.textpathField = new CT_TextPath(); diff --git a/main/HSSF/UserModel/HSSFSheet.cs b/main/HSSF/UserModel/HSSFSheet.cs index bd102ec2d..c82061e83 100644 --- a/main/HSSF/UserModel/HSSFSheet.cs +++ b/main/HSSF/UserModel/HSSFSheet.cs @@ -152,6 +152,20 @@ public IRow CopyRow(int sourceIndex, int targetIndex) { return SheetUtil.CopyRow(this, sourceIndex, targetIndex); } + + /// + /// Copies comment from one cell to another + /// + /// Cell with a comment to copy + /// Cell to paste the comment to + /// Copied comment + public IComment CopyComment(ICell sourceCell, ICell targetCell) + { + targetCell.CellComment = sourceCell.CellComment; + + return targetCell.CellComment; + } + /// /// used internally to Set the properties given a Sheet object /// diff --git a/main/SS/Formula/FormulaShifter.cs b/main/SS/Formula/FormulaShifter.cs index 21bb7cefa..a9381bff1 100644 --- a/main/SS/Formula/FormulaShifter.cs +++ b/main/SS/Formula/FormulaShifter.cs @@ -15,14 +15,12 @@ the License. You may obtain a copy of the License at limitations under the License. ==================================================================== */ - - namespace NPOI.SS.Formula { + using NPOI.SS.Formula.PTG; using System; using System.Text; - using NPOI.SS.Formula.PTG; /** * @author Josh Micich */ @@ -34,40 +32,54 @@ private enum ShiftMode RowCopy, SheetMove } - /** - * Extern sheet index of sheet where moving is occurring - */ - private int _externSheetIndex; - /** - * Sheet name of the sheet where moving is occurring, - * used for updating XSSF style 3D references on row shifts. - */ - private String _sheetName; - private int _firstMovedIndex; - private int _lastMovedIndex; - private int _amountToMove; - private int _srcSheetIndex; - private int _dstSheetIndex; - private SpreadsheetVersion _version; - - private ShiftMode _mode; - - /** - * Create an instance for Shifting row. - * - * For example, this will be called on {@link NPOI.HSSF.UserModel.HSSFSheet#ShiftRows(int, int, int)} } - */ - private FormulaShifter(int externSheetIndex, String sheetName, int firstMovedIndex, int lastMovedIndex, int amountToMove, + /// + /// Extern sheet index of sheet where moving is occurring + /// + private readonly int _externSheetIndex; + /// + /// Sheet name of the sheet where moving is occurring, used for + /// updating XSSF style 3D references on row shifts. + /// + private readonly string _sheetName; + private readonly int _firstMovedIndex; + private readonly int _lastMovedIndex; + private readonly int _amountToMove; + private readonly int _srcSheetIndex; + private readonly int _dstSheetIndex; + private readonly SpreadsheetVersion _version; + private readonly ShiftMode _mode; + + /// + /// Create an instance for Shifting row. For example, this will be + /// called on + /// + /// + /// + /// + /// + /// + /// + /// + /// + private FormulaShifter( + int externSheetIndex, + string sheetName, + int firstMovedIndex, + int lastMovedIndex, + int amountToMove, ShiftMode mode, SpreadsheetVersion version) { if (amountToMove == 0) { throw new ArgumentException("amountToMove must not be zero"); } + if (firstMovedIndex > lastMovedIndex) { - throw new ArgumentException("firstMovedIndex, lastMovedIndex out of order"); + throw new ArgumentException("firstMovedIndex, lastMovedIndex " + + "out of order"); } + _externSheetIndex = externSheetIndex; _sheetName = sheetName; _firstMovedIndex = firstMovedIndex; @@ -79,14 +91,18 @@ private FormulaShifter(int externSheetIndex, String sheetName, int firstMovedInd _srcSheetIndex = _dstSheetIndex = -1; } - /** - * Create an instance for shifting sheets. - * - * For example, this will be called on {@link org.apache.poi.hssf.usermodel.HSSFWorkbook#setSheetOrder(String, int)} - */ + /// + /// Create an instance for shifting sheets. For example, this will be + /// called on + /// + /// + /// private FormulaShifter(int srcSheetIndex, int dstSheetIndex) { - _externSheetIndex = _firstMovedIndex = _lastMovedIndex = _amountToMove = -1; + _externSheetIndex = + _firstMovedIndex = + _lastMovedIndex = + _amountToMove = -1; _sheetName = null; _version = null; @@ -96,28 +112,65 @@ private FormulaShifter(int srcSheetIndex, int dstSheetIndex) } [Obsolete("deprecated As of 3.14 beta 1 (November 2015), replaced by CreateForRowShift(int, String, int, int, int, SpreadsheetVersion)")] - public static FormulaShifter CreateForRowShift(int externSheetIndex, String sheetName, int firstMovedRowIndex, int lastMovedRowIndex, int numberOfRowsToMove) + public static FormulaShifter CreateForRowShift( + int externSheetIndex, + string sheetName, + int firstMovedRowIndex, + int lastMovedRowIndex, + int numberOfRowsToMove) { - return CreateForRowShift(externSheetIndex, sheetName, firstMovedRowIndex, lastMovedRowIndex, numberOfRowsToMove, SpreadsheetVersion.EXCEL97); + return CreateForRowShift( + externSheetIndex, + sheetName, + firstMovedRowIndex, + lastMovedRowIndex, + numberOfRowsToMove, + SpreadsheetVersion.EXCEL97); } - public static FormulaShifter CreateForRowShift(int externSheetIndex, String sheetName, int firstMovedRowIndex, int lastMovedRowIndex, int numberOfRowsToMove, + public static FormulaShifter CreateForRowShift( + int externSheetIndex, + string sheetName, + int firstMovedRowIndex, + int lastMovedRowIndex, + int numberOfRowsToMove, SpreadsheetVersion version) { - return new FormulaShifter(externSheetIndex, sheetName, firstMovedRowIndex, lastMovedRowIndex, numberOfRowsToMove, ShiftMode.RowMove, version); + return new FormulaShifter( + externSheetIndex, + sheetName, + firstMovedRowIndex, + lastMovedRowIndex, + numberOfRowsToMove, + ShiftMode.RowMove, + version); } - public static FormulaShifter CreateForRowCopy(int externSheetIndex, String sheetName, int firstMovedRowIndex, int lastMovedRowIndex, int numberOfRowsToMove, - SpreadsheetVersion version) + public static FormulaShifter CreateForRowCopy( + int externSheetIndex, + string sheetName, + int firstMovedRowIndex, + int lastMovedRowIndex, + int numberOfRowsToMove, + SpreadsheetVersion version) { - return new FormulaShifter(externSheetIndex, sheetName, firstMovedRowIndex, lastMovedRowIndex, numberOfRowsToMove, ShiftMode.RowCopy, version); + return new FormulaShifter( + externSheetIndex, + sheetName, + firstMovedRowIndex, + lastMovedRowIndex, + numberOfRowsToMove, + ShiftMode.RowCopy, + version); } - public static FormulaShifter CreateForSheetShift(int srcSheetIndex, int dstSheetIndex) + public static FormulaShifter CreateForSheetShift( + int srcSheetIndex, + int dstSheetIndex) { return new FormulaShifter(srcSheetIndex, dstSheetIndex); } - public override String ToString() + public override string ToString() { StringBuilder sb = new StringBuilder(); @@ -129,11 +182,13 @@ public override String ToString() return sb.ToString(); } - /** - * @param ptgs - if necessary, will get modified by this method - * @param currentExternSheetIx - the extern sheet index of the sheet that contains the formula being adjusted - * @return true if a change was made to the formula tokens - */ + /// + /// + /// + /// if necessary, will get modified by this method + /// the extern sheet index of the + /// sheet that contains the formula being adjusted + /// true if a change was made to the formula tokens public bool AdjustFormula(Ptg[] ptgs, int currentExternSheetIx) { bool refsWereChanged = false; @@ -146,6 +201,7 @@ public bool AdjustFormula(Ptg[] ptgs, int currentExternSheetIx) ptgs[i] = newPtg; } } + return refsWereChanged; } @@ -164,7 +220,8 @@ private Ptg AdjustPtg(Ptg ptg, int currentExternSheetIx) case ShiftMode.SheetMove: return AdjustPtgDueToSheetMove(ptg); default: - throw new InvalidOperationException("Unsupported shift mode: " + _mode); + throw new InvalidOperationException( + "Unsupported shift mode: " + _mode); } } @@ -173,134 +230,143 @@ private Ptg AdjustPtg(Ptg ptg, int currentExternSheetIx) /// /// /// - /// in-place modified ptg (if row move would cause Ptg to change), - /// deleted ref ptg (if row move causes an error), + /// in-place modified ptg (if row move would cause Ptg to + /// change), deleted ref ptg (if row move causes an error), /// or null (if no Ptg change is needed) private Ptg AdjustPtgDueToRowMove(Ptg ptg, int currentExternSheetIx) { - if (ptg is RefPtg) + if (ptg is RefPtg refPtg) { if (currentExternSheetIx != _externSheetIndex) { // local refs on other sheets are unaffected return null; } - RefPtg rptg = (RefPtg)ptg; - return RowMoveRefPtg(rptg); + + return RowMoveRefPtg(refPtg); } - if (ptg is Ref3DPtg) + + if (ptg is Ref3DPtg rptg) { - Ref3DPtg rptg = (Ref3DPtg)ptg; if (_externSheetIndex != rptg.ExternSheetIndex) { - // only move 3D refs that refer to the sheet with cells being moved - // (currentExternSheetIx is irrelevant) + // only move 3D refs that refer to the sheet with + // cells being moved (currentExternSheetIx is irrelevant) return null; } + return RowMoveRefPtg(rptg); } - if (ptg is Ref3DPxg) + if (ptg is Ref3DPxg rpxg) { - Ref3DPxg rpxg = (Ref3DPxg)ptg; if (rpxg.ExternalWorkbookNumber > 0 || !_sheetName.Equals(rpxg.SheetName)) { - // only move 3D refs that refer to the sheet with cells being moved + // only move 3D refs that refer to the sheet with cells + // being moved return null; } + return RowMoveRefPtg(rpxg); } - if (ptg is Area2DPtgBase) + + if (ptg is Area2DPtgBase areaPtgBase) { if (currentExternSheetIx != _externSheetIndex) { // local refs on other sheets are unaffected return ptg; } - return RowMoveAreaPtg((Area2DPtgBase)ptg); + + return RowMoveAreaPtg(areaPtgBase); } - if (ptg is Area3DPtg) + + if (ptg is Area3DPtg aptg) { - Area3DPtg aptg = (Area3DPtg)ptg; if (_externSheetIndex != aptg.ExternSheetIndex) { - // only move 3D refs that refer to the sheet with cells being moved - // (currentExternSheetIx is irrelevant) + // only move 3D refs that refer to the sheet with cells + // being moved (currentExternSheetIx is irrelevant) return null; } + return RowMoveAreaPtg(aptg); } - if (ptg is Area3DPxg) + if (ptg is Area3DPxg apxg) { - Area3DPxg apxg = (Area3DPxg)ptg; if (apxg.ExternalWorkbookNumber > 0 || !_sheetName.Equals(apxg.SheetName)) { - // only move 3D refs that refer to the sheet with cells being moved + // only move 3D refs that refer to the sheet with cells + // being moved return null; } + return RowMoveAreaPtg(apxg); } + return null; } - /** - * Call this on any ptg reference contained in a row of cells that was copied. - * If the ptg reference is relative, the references will be shifted by the distance - * that the rows were copied. - * In the future similar functions could be written due to column copying or - * individual cell copying. Just make sure to only call adjustPtgDueToRowCopy on - * formula cells that are copied (unless row shifting, where references outside - * of the shifted region need to be updated to reflect the shift, a copy is self-contained). - * - * @param ptg the ptg to shift - * @return deleted ref ptg, in-place modified ptg, or null - * If Ptg would be shifted off the first or last row of a sheet, return deleted ref - * If Ptg needs to be changed, modifies Ptg in-place - * If Ptg doesn't need to be changed, returns null - */ + /// + /// Call this on any ptg reference contained in a row of cells that was + /// copied. If the ptg reference is relative, the references will be + /// shifted by the distance that the rows were copied. In the future + /// similar functions could be written due to column copying or + /// individual cell copying. Just make sure to only call + /// adjustPtgDueToRowCopy on formula cells that are copied (unless row + /// shifting, where references outside of the shifted region need to be + /// updated to reflect the shift, a copy is self-contained). + /// + /// the ptg to shift + /// deleted ref ptg, in-place modified ptg, or null + /// If Ptg would be shifted off the first or last row of a sheet, + /// return deleted ref + /// If Ptg needs to be changed, modifies Ptg in-place + /// If Ptg doesn't need to be changed, returns null + /// private Ptg AdjustPtgDueToRowCopy(Ptg ptg) { - if (ptg is RefPtg) + if (ptg is RefPtg refPtg) { - RefPtg rptg = (RefPtg)ptg; - return RowCopyRefPtg(rptg); + return RowCopyRefPtg(refPtg); } - if (ptg is Ref3DPtg) + + if (ptg is Ref3DPtg rptg) { - Ref3DPtg rptg = (Ref3DPtg)ptg; return RowCopyRefPtg(rptg); } - if (ptg is Ref3DPxg) + + if (ptg is Ref3DPxg rpxg) { - Ref3DPxg rpxg = (Ref3DPxg)ptg; return RowCopyRefPtg(rpxg); } - if (ptg is Area2DPtgBase) + + if (ptg is Area2DPtgBase areaPtgBase) { - return RowCopyAreaPtg((Area2DPtgBase)ptg); + return RowCopyAreaPtg(areaPtgBase); } - if (ptg is Area3DPtg) + + if (ptg is Area3DPtg aptg) { - Area3DPtg aptg = (Area3DPtg)ptg; return RowCopyAreaPtg(aptg); } - if (ptg is Area3DPxg) + + if (ptg is Area3DPxg apxg) { - Area3DPxg apxg = (Area3DPxg)ptg; return RowCopyAreaPtg(apxg); } + return null; } private Ptg AdjustPtgDueToSheetMove(Ptg ptg) { - if (ptg is Ref3DPtg) + if (ptg is Ref3DPtg refPtg) { - Ref3DPtg ref1 = (Ref3DPtg)ptg; - int oldSheetIndex = ref1.ExternSheetIndex; + int oldSheetIndex = refPtg.ExternSheetIndex; // we have to handle a few cases here @@ -310,6 +376,7 @@ private Ptg AdjustPtgDueToSheetMove(Ptg ptg) { return null; } + if (oldSheetIndex > _srcSheetIndex && oldSheetIndex > _dstSheetIndex) { @@ -319,24 +386,25 @@ private Ptg AdjustPtgDueToSheetMove(Ptg ptg) // 2. ptg refers to the moved sheet if (oldSheetIndex == _srcSheetIndex) { - ref1.ExternSheetIndex = (_dstSheetIndex); - return ref1; + refPtg.ExternSheetIndex = _dstSheetIndex; + return refPtg; } // 3. new index is lower than old one => sheets get moved up if (_dstSheetIndex < _srcSheetIndex) { - ref1.ExternSheetIndex = (oldSheetIndex + 1); - return ref1; + refPtg.ExternSheetIndex = oldSheetIndex + 1; + return refPtg; } // 4. new index is higher than old one => sheets get moved down if (_dstSheetIndex > _srcSheetIndex) { - ref1.ExternSheetIndex = (oldSheetIndex - 1); - return ref1; + refPtg.ExternSheetIndex = oldSheetIndex - 1; + return refPtg; } } + return null; } private Ptg RowMoveRefPtg(RefPtgBase rptg) @@ -344,12 +412,13 @@ private Ptg RowMoveRefPtg(RefPtgBase rptg) int refRow = rptg.Row; if (_firstMovedIndex <= refRow && refRow <= _lastMovedIndex) { - // Rows being moved completely enclose the ref. - // - move the area ref along with the rows regardless of destination - rptg.Row = (refRow + _amountToMove); + // Rows being moved completely enclose the ref. - move the area + // ref along with the rows regardless of destination + rptg.Row = refRow + _amountToMove; return rptg; } - // else rules for adjusting area may also depend on the destination of the moved rows + // else rules for adjusting area may also depend on the destination + // of the moved rows int destFirstRowIndex = _firstMovedIndex + _amountToMove; int destLastRowIndex = _lastMovedIndex + _amountToMove; @@ -368,8 +437,11 @@ private Ptg RowMoveRefPtg(RefPtgBase rptg) // destination rows enclose the area (possibly exactly) return CreateDeletedRef(rptg); } - throw new InvalidOperationException("Situation not covered: (" + _firstMovedIndex + ", " + - _lastMovedIndex + ", " + _amountToMove + ", " + refRow + ", " + refRow + ")"); + + throw new InvalidOperationException( + "Situation not covered: (" + _firstMovedIndex + ", " + + _lastMovedIndex + ", " + _amountToMove + ", " + + refRow + ", " + refRow + ")"); } private Ptg RowMoveAreaPtg(AreaPtgBase aptg) @@ -378,13 +450,14 @@ private Ptg RowMoveAreaPtg(AreaPtgBase aptg) int aLastRow = aptg.LastRow; if (_firstMovedIndex <= aFirstRow && aLastRow <= _lastMovedIndex) { - // Rows being moved completely enclose the area ref. - // - move the area ref along with the rows regardless of destination - aptg.FirstRow = (aFirstRow + _amountToMove); - aptg.LastRow = (aLastRow + _amountToMove); + // Rows being moved completely enclose the area ref. - move the + // area ref along with the rows regardless of destination + aptg.FirstRow = aFirstRow + _amountToMove; + aptg.LastRow = aLastRow + _amountToMove; return aptg; } - // else rules for adjusting area may also depend on the destination of the moved rows + // else rules for adjusting area may also depend on the destination + // of the moved rows int destFirstRowIndex = _firstMovedIndex + _amountToMove; int destLastRowIndex = _lastMovedIndex + _amountToMove; @@ -395,88 +468,102 @@ private Ptg RowMoveAreaPtg(AreaPtgBase aptg) // If the destination of the rows overlaps either the top // or bottom of the area ref there will be a change - if (destFirstRowIndex < aFirstRow && aFirstRow <= destLastRowIndex) + if (destFirstRowIndex < aFirstRow + && aFirstRow <= destLastRowIndex) { // truncate the top of the area by the moved rows - aptg.FirstRow = (destLastRowIndex + 1); + aptg.FirstRow = destLastRowIndex + 1; return aptg; } - else if (destFirstRowIndex <= aLastRow && aLastRow < destLastRowIndex) + else if (destFirstRowIndex <= aLastRow + && aLastRow < destLastRowIndex) { // truncate the bottom of the area by the moved rows - aptg.LastRow = (destFirstRowIndex - 1); + aptg.LastRow = destFirstRowIndex - 1; return aptg; } // else - rows have moved completely outside the area ref, // or still remain completely within the area ref return null; // - no change to the area } + if (_firstMovedIndex <= aFirstRow && aFirstRow <= _lastMovedIndex) { - // Rows moved include the first row of the area ref, but not the last row - // btw: (aLastRow > _lastMovedIndex) + // Rows moved include the first row of the area ref, but not + // the last row btw: (aLastRow > _lastMovedIndex) if (_amountToMove < 0) { // simple case - expand area by shifting top upward - aptg.FirstRow = (aFirstRow + _amountToMove); + aptg.FirstRow = aFirstRow + _amountToMove; return aptg; } + if (destFirstRowIndex > aLastRow) { // in this case, excel ignores the row move return null; } + int newFirstRowIx = aFirstRow + _amountToMove; if (destLastRowIndex < aLastRow) { // end of area is preserved (will remain exact same row) // the top area row is moved simply - aptg.FirstRow = (newFirstRowIx); + aptg.FirstRow = newFirstRowIx; return aptg; } - // else - bottom area row has been replaced - both area top and bottom may move now + // else - bottom area row has been replaced - both area top and + // bottom may move now int areaRemainingTopRowIx = _lastMovedIndex + 1; if (destFirstRowIndex > areaRemainingTopRowIx) { - // old top row of area has moved deep within the area, and exposed a new top row + // old top row of area has moved deep within the area, and + // exposed a new top row newFirstRowIx = areaRemainingTopRowIx; } - aptg.FirstRow = (newFirstRowIx); - aptg.LastRow = (Math.Max(aLastRow, destLastRowIndex)); + + aptg.FirstRow = newFirstRowIx; + aptg.LastRow = Math.Max(aLastRow, destLastRowIndex); return aptg; } + if (_firstMovedIndex <= aLastRow && aLastRow <= _lastMovedIndex) { - // Rows moved include the last row of the area ref, but not the first - // btw: (aFirstRow < _firstMovedIndex) + // Rows moved include the last row of the area ref, but not the + // first. btw: (aFirstRow < _firstMovedIndex) if (_amountToMove > 0) { // simple case - expand area by shifting bottom downward - aptg.LastRow = (aLastRow + _amountToMove); + aptg.LastRow = aLastRow + _amountToMove; return aptg; } + if (destLastRowIndex < aFirstRow) { // in this case, excel ignores the row move return null; } + int newLastRowIx = aLastRow + _amountToMove; if (destFirstRowIndex > aFirstRow) { // top of area is preserved (will remain exact same row) // the bottom area row is moved simply - aptg.LastRow = (newLastRowIx); + aptg.LastRow = newLastRowIx; return aptg; } - // else - top area row has been replaced - both area top and bottom may move now + // else - top area row has been replaced - both area top and + // bottom may move now int areaRemainingBottomRowIx = _firstMovedIndex - 1; if (destLastRowIndex < areaRemainingBottomRowIx) { - // old bottom row of area has moved up deep within the area, and exposed a new bottom row + // old bottom row of area has moved up deep within the + // area, and exposed a new bottom row newLastRowIx = areaRemainingBottomRowIx; } - aptg.FirstRow = (Math.Min(aFirstRow, destFirstRowIndex)); - aptg.LastRow = (newLastRowIx); + + aptg.FirstRow = Math.Min(aFirstRow, destFirstRowIndex); + aptg.LastRow = newLastRowIx; return aptg; } // else source rows include none of the rows of the area ref @@ -496,7 +583,8 @@ private Ptg RowMoveAreaPtg(AreaPtgBase aptg) if (aFirstRow <= destFirstRowIndex && destLastRowIndex <= aLastRow) { - // destination rows are within area ref (possibly exact on top or bottom, but not both) + // destination rows are within area ref (possibly exact on top + // or bottom, but not both) return null; // - no change to area } @@ -504,29 +592,31 @@ private Ptg RowMoveAreaPtg(AreaPtgBase aptg) { // dest rows overlap top of area // - truncate the top - aptg.FirstRow = (destLastRowIndex + 1); + aptg.FirstRow = destLastRowIndex + 1; return aptg; } + if (destFirstRowIndex <= aLastRow && aLastRow < destLastRowIndex) { // dest rows overlap bottom of area // - truncate the bottom - aptg.LastRow = (destFirstRowIndex - 1); + aptg.LastRow = destFirstRowIndex - 1; return aptg; } - throw new InvalidOperationException("Situation not covered: (" + _firstMovedIndex + ", " + - _lastMovedIndex + ", " + _amountToMove + ", " + aFirstRow + ", " + aLastRow + ")"); - } + throw new InvalidOperationException( + "Situation not covered: (" + _firstMovedIndex + ", " + + _lastMovedIndex + ", " + _amountToMove + ", " + + aFirstRow + ", " + aLastRow + ")"); + } - /** - * Modifies rptg in-place and return a reference to rptg if the cell reference - * would move due to a row copy operation - * Returns null or {@link #RefErrorPtg} if no change was made - * - * @param aptg - * @return - */ + /// + /// Modifies rptg in-place and return a reference to rptg if the cell + /// reference would move due to a row copy operation + /// + /// + /// null or {@link #RefErrorPtg} if no change was + /// made private Ptg RowCopyRefPtg(RefPtgBase rptg) { int refRow = rptg.Row; @@ -534,21 +624,25 @@ private Ptg RowCopyRefPtg(RefPtgBase rptg) { int destRowIndex = _firstMovedIndex + _amountToMove; if (destRowIndex < 0 || _version.LastRowIndex < destRowIndex) + { return CreateDeletedRef(rptg); - rptg.Row = (refRow + _amountToMove); + } + + rptg.Row = refRow + _amountToMove; return rptg; } + return null; } - /** - * Modifies aptg in-place and return a reference to aptg if the first or last row of - * of the Area reference would move due to a row copy operation - * Returns null or {@link #AreaErrPtg} if no change was made - * - * @param aptg - * @return null, AreaErrPtg, or modified aptg - */ + /// + /// Modifies aptg in-place and return a reference to aptg if the first + /// or last row of of the Area reference would move due to a row + /// copy operation + /// + /// + /// null or if no change + /// was made private Ptg RowCopyAreaPtg(AreaPtgBase aptg) { bool changed = false; @@ -559,19 +653,29 @@ private Ptg RowCopyAreaPtg(AreaPtgBase aptg) if (aptg.IsFirstRowRelative) { int destFirstRowIndex = aFirstRow + _amountToMove; - if (destFirstRowIndex < 0 || _version.LastRowIndex < destFirstRowIndex) + if (destFirstRowIndex < 0 + || _version.LastRowIndex < destFirstRowIndex) + { return CreateDeletedRef(aptg); - aptg.FirstRow = (destFirstRowIndex); + } + + aptg.FirstRow = destFirstRowIndex; changed = true; } + if (aptg.IsLastRowRelative) { int destLastRowIndex = aLastRow + _amountToMove; - if (destLastRowIndex < 0 || _version.LastRowIndex < destLastRowIndex) + if (destLastRowIndex < 0 + || _version.LastRowIndex < destLastRowIndex) + { return CreateDeletedRef(aptg); - aptg.LastRow = (destLastRowIndex); + } + + aptg.LastRow = destLastRowIndex; changed = true; } + if (changed) { aptg.SortTopLeftToBottomRight(); @@ -586,31 +690,37 @@ private static Ptg CreateDeletedRef(Ptg ptg) { return new RefErrorPtg(); } - if (ptg is Ref3DPtg) + + if (ptg is Ref3DPtg rptg) { - Ref3DPtg rptg = (Ref3DPtg)ptg; return new DeletedRef3DPtg(rptg.ExternSheetIndex); } + if (ptg is AreaPtg) { return new AreaErrPtg(); } - if (ptg is Area3DPtg) + + if (ptg is Area3DPtg area3DPtg) { - Area3DPtg area3DPtg = (Area3DPtg)ptg; return new DeletedArea3DPtg(area3DPtg.ExternSheetIndex); } - if (ptg is Ref3DPxg) + + if (ptg is Ref3DPxg pxg) { - Ref3DPxg pxg = (Ref3DPxg)ptg; - return new Deleted3DPxg(pxg.ExternalWorkbookNumber, pxg.SheetName); + return + new Deleted3DPxg(pxg.ExternalWorkbookNumber, pxg.SheetName); } - if (ptg is Area3DPxg) + + if (ptg is Area3DPxg areaPxg) { - Area3DPxg pxg = (Area3DPxg)ptg; - return new Deleted3DPxg(pxg.ExternalWorkbookNumber, pxg.SheetName); + return new Deleted3DPxg( + areaPxg.ExternalWorkbookNumber, + areaPxg.SheetName); } - throw new ArgumentException("Unexpected ref ptg class (" + ptg.GetType().Name + ")"); + + throw new ArgumentException( + "Unexpected ref ptg class (" + ptg.GetType().Name + ")"); } } } \ No newline at end of file diff --git a/main/SS/UserModel/Helpers/RowShifter.cs b/main/SS/UserModel/Helpers/RowShifter.cs index 6a7b9b6dc..bb7adb06c 100644 --- a/main/SS/UserModel/Helpers/RowShifter.cs +++ b/main/SS/UserModel/Helpers/RowShifter.cs @@ -15,20 +15,19 @@ the License. You may obtain a copy of the License at limitations under the License. ==================================================================== */ +using NPOI.SS.Formula; +using NPOI.SS.Util; +using System; +using System.Collections.Generic; +using System.Linq; + namespace NPOI.SS.UserModel.Helpers { - using NPOI.SS.Formula; - using NPOI.SS.UserModel; - using NPOI.SS.Util; - using System; - using System.Collections.Generic; - using System.Linq; - - /** - * Helper for Shifting rows up or down - * - * This abstract class exists to consolidate duplicated code between XSSFRowShifter and HSSFRowShifter (currently methods sprinkled throughout HSSFSheet) - */ + /// + /// Helper for Shifting rows up or down + /// This abstract class exists to consolidate duplicated code between XSSFRowShifter + /// and HSSFRowShifter(currently methods sprinkled throughout HSSFSheet) + /// public abstract class RowShifter { protected ISheet sheet; @@ -38,35 +37,39 @@ public RowShifter(ISheet sh) sheet = sh; } - /** - * Shifts, grows, or shrinks the merged regions due to a row Shift. - * Merged regions that are completely overlaid by Shifting will be deleted. - * - * @param startRow the row to start Shifting - * @param endRow the row to end Shifting - * @param n the number of rows to shift - * @return an array of affected merged regions, doesn't contain deleted ones - */ + /// + /// Shifts, grows, or shrinks the merged regions due to a row Shift. + /// Merged regions that are completely overlaid by Shifting will be deleted. + /// + /// the row to start Shifting + /// the row to end Shifting + /// the number of rows to shift + /// an array of affected merged regions, doesn't contain deleted ones public List ShiftMergedRegions(int startRow, int endRow, int n) { - List ShiftedRegions = new List(); + var ShiftedRegions = new List(); ISet removedIndices = new HashSet(); //move merged regions completely if they fall within the new region boundaries when they are Shifted - int size = sheet.NumMergedRegions; - for (int i = 0; i < size; i++) + var size = sheet.NumMergedRegions; + + for (var i = 0; i < size; i++) { - CellRangeAddress merged = sheet.GetMergedRegion(i); + var merged = sheet.GetMergedRegion(i); // remove merged region that overlaps Shifting - var lastCol=sheet.GetRow(startRow) != null ? sheet.GetRow(startRow).LastCellNum : sheet.GetRow(endRow) != null ? sheet.GetRow(endRow).LastCellNum : 0; - if (removalNeeded(merged, startRow, endRow, n, lastCol )) + var lastCol = sheet.GetRow(startRow) != null + ? sheet.GetRow(startRow).LastCellNum + : sheet.GetRow(endRow) != null + ? sheet.GetRow(endRow).LastCellNum + : 0; + if (RemovalNeeded(merged, startRow, endRow, n, lastCol)) { removedIndices.Add(i); continue; } - bool inStart = (merged.FirstRow >= startRow || merged.LastRow >= startRow); - bool inEnd = (merged.FirstRow <= endRow || merged.LastRow <= endRow); + var inStart = merged.FirstRow >= startRow || merged.LastRow >= startRow; + var inEnd = merged.FirstRow <= endRow || merged.LastRow <= endRow; //don't check if it's not within the Shifted area if (!inStart || !inEnd) @@ -77,80 +80,82 @@ public List ShiftMergedRegions(int startRow, int endRow, int n //only shift if the region outside the Shifted rows is not merged too if (!merged.ContainsRow(startRow - 1) && !merged.ContainsRow(endRow + 1)) { - merged.FirstRow = merged.FirstRow + n; - merged.LastRow =merged.LastRow + n; + merged.FirstRow += n; + merged.LastRow += n; //have to Remove/add it back ShiftedRegions.Add(merged); removedIndices.Add(i); } } - if (removedIndices.Count!=0) + if (removedIndices.Count != 0) { sheet.RemoveMergedRegions(removedIndices.ToList()); } //read so it doesn't Get Shifted again - foreach (CellRangeAddress region in ShiftedRegions) + foreach (var region in ShiftedRegions) { sheet.AddMergedRegion(region); } + return ShiftedRegions; } // Keep in sync with {@link ColumnShifter#removalNeeded} - private bool removalNeeded(CellRangeAddress merged, int startRow, int endRow, int n, int lastCol) + private bool RemovalNeeded(CellRangeAddress merged, int startRow, int endRow, int n, int lastCol) { - int movedRows = endRow - startRow + 1; + var movedRows = endRow - startRow + 1; // build a range of the rows that are overwritten, i.e. the target-area, but without // rows that are moved along - CellRangeAddress overwrite; + CellRangeAddress overwrite; if (n > 0) { // area is moved down => overwritten area is [endRow + n - movedRows, endRow + n] - int firstRow = Math.Max(endRow + 1, endRow + n - movedRows); - int lastRow = endRow + n; + var firstRow = Math.Max(endRow + 1, endRow + n - movedRows); + var lastRow = endRow + n; overwrite = new CellRangeAddress(firstRow, lastRow, 0, lastCol); } else { // area is moved up => overwritten area is [startRow + n, startRow + n + movedRows] - int firstRow = startRow + n; - int lastRow = Math.Min(startRow - 1, startRow + n + movedRows); + var firstRow = startRow + n; + var lastRow = Math.Min(startRow - 1, startRow + n + movedRows); overwrite = new CellRangeAddress(firstRow, lastRow, 0, lastCol); } // if the merged-region and the overwritten area intersect, we need to remove it return merged.Intersects(overwrite); } - - /** - * Verify that the given column indices and step denote a valid range of columns to shift - * - * @param firstShiftColumnIndex the column to start shifting - * @param lastShiftColumnIndex the column to end shifting - * @param step length of the shifting step - */ + + /// + /// Verify that the given column indices and step denote a valid range of columns to shift + /// + /// the column to start shifting + /// the column to end shifting + /// length of the shifting step + /// public static void ValidateShiftParameters(int firstShiftColumnIndex, int lastShiftColumnIndex, int step) { if (step < 0) { throw new ArgumentException("Shifting step may not be negative, but had " + step); } + if (firstShiftColumnIndex > lastShiftColumnIndex) { - throw new ArgumentException(String.Format("Incorrect shifting range : %d-%d", firstShiftColumnIndex, lastShiftColumnIndex)); + throw new ArgumentException(string.Format("Incorrect shifting range : %d-%d", firstShiftColumnIndex, lastShiftColumnIndex)); } } - - /** - * Verify that the given column indices and step denote a valid range of columns to shift to the left - * - * @param firstShiftColumnIndex the column to start shifting - * @param lastShiftColumnIndex the column to end shifting - * @param step length of the shifting step - */ + + /// + /// Verify that the given column indices and step denote a valid range of columns to shift to the left + /// + /// the column to start shifting + /// the column to end shifting + /// length of the shifting step + /// public static void ValidateShiftLeftParameters(int firstShiftColumnIndex, int lastShiftColumnIndex, int step) { ValidateShiftParameters(firstShiftColumnIndex, lastShiftColumnIndex, step); @@ -160,36 +165,34 @@ public static void ValidateShiftLeftParameters(int firstShiftColumnIndex, int la throw new InvalidOperationException("Column index less than zero: " + (firstShiftColumnIndex + step)); } } - /** - * Updated named ranges - */ + + /// + /// Updated named ranges + /// + /// public abstract void UpdateNamedRanges(FormulaShifter Shifter); - - /** - * Update formulas. - */ + + /// + /// Update formulas. + /// + /// public abstract void UpdateFormulas(FormulaShifter Shifter); - /** - * Update the formulas in specified row using the formula Shifting policy specified by Shifter - * - * @param row the row to update the formulas on - * @param Shifter the formula Shifting policy - */ - + /// + /// Update the formulas in specified row using the formula Shifting policy specified by Shifter + /// + /// the row to update the formulas on + /// the formula Shifting policy public abstract void UpdateRowFormulas(IRow row, FormulaShifter Shifter); public abstract void UpdateConditionalFormatting(FormulaShifter Shifter); - - /** - * Shift the Hyperlink anchors (not the hyperlink text, even if the hyperlink - * is of type LINK_DOCUMENT and refers to a cell that was Shifted). Hyperlinks - * do not track the content they point to. - * - * @param Shifter the formula Shifting policy - */ + + /// + /// Shift the Hyperlink anchors (not the hyperlink text, even if the hyperlink + /// is of type LINK_DOCUMENT and refers to a cell that was Shifted). Hyperlinks + /// do not track the content they point to. + /// + /// the formula Shifting policy public abstract void UpdateHyperlinks(FormulaShifter Shifter); - } - } \ No newline at end of file diff --git a/main/SS/UserModel/Sheet.cs b/main/SS/UserModel/Sheet.cs index 1863db667..4dfc3275e 100644 --- a/main/SS/UserModel/Sheet.cs +++ b/main/SS/UserModel/Sheet.cs @@ -151,6 +151,15 @@ public interface ISheet /// target index /// the new copied row object IRow CopyRow(int sourceIndex, int targetIndex); + + /// + /// Copies comment from one cell to another + /// + /// Cell with a comment to copy + /// Cell to paste the comment to + /// Copied comment + IComment CopyComment(ICell sourceCell, ICell targetCell); + /// /// Set the width (in units of 1/256th of a character width) /// diff --git a/main/SS/Util/CellUtil.cs b/main/SS/Util/CellUtil.cs index ba8bd19ec..9b2fa02af 100644 --- a/main/SS/Util/CellUtil.cs +++ b/main/SS/Util/CellUtil.cs @@ -107,8 +107,6 @@ private CellUtil() public static ICell CopyCell(IRow row, int sourceIndex, int targetIndex) { - if (sourceIndex == targetIndex) - throw new ArgumentException("sourceIndex and targetIndex cannot be same"); // Grab a copy of the old/new cell ICell oldCell = row.GetCell(sourceIndex); @@ -127,6 +125,15 @@ public static ICell CopyCell(IRow row, int sourceIndex, int targetIndex) { //TODO:shift cells } + + return CopyCell(oldCell, newCell, sourceIndex, targetIndex); + } + + private static ICell CopyCell(ICell oldCell, ICell newCell, int sourceIndex, int targetIndex) + { + if (sourceIndex == targetIndex) + throw new ArgumentException("sourceIndex and targetIndex cannot be same"); + // Copy style from old cell and apply to new cell if (oldCell.CellStyle != null) { diff --git a/main/SS/Util/SheetUtil.cs b/main/SS/Util/SheetUtil.cs index 43123ba52..4d12ad5a8 100644 --- a/main/SS/Util/SheetUtil.cs +++ b/main/SS/Util/SheetUtil.cs @@ -119,7 +119,7 @@ public static IRow CopyRow(ISheet sourceSheet, int sourceRowIndex, ISheet target // If there is a cell comment, copy if (oldCell.CellComment != null) { - newCell.CellComment = oldCell.CellComment; + sourceSheet.CopyComment(oldCell, newCell); } // If there is a cell hyperlink, copy @@ -173,6 +173,7 @@ public static IRow CopyRow(ISheet sourceSheet, int sourceRowIndex, ISheet target } return newRow; } + public static IRow CopyRow(ISheet sheet, int sourceRowIndex, int targetRowIndex) { if (sourceRowIndex == targetRowIndex) @@ -211,7 +212,7 @@ public static IRow CopyRow(ISheet sheet, int sourceRowIndex, int targetRowIndex) // If there is a cell comment, copy if (oldCell.CellComment != null) { - newCell.CellComment = oldCell.CellComment; + sheet.CopyComment(oldCell, newCell); } // If there is a cell hyperlink, copy diff --git a/ooxml/XSSF/Streaming/SXSSFSheet.cs b/ooxml/XSSF/Streaming/SXSSFSheet.cs index 1569c2f9c..5ddf4cdd0 100644 --- a/ooxml/XSSF/Streaming/SXSSFSheet.cs +++ b/ooxml/XSSF/Streaming/SXSSFSheet.cs @@ -592,6 +592,17 @@ public IRow CopyRow(int sourceIndex, int targetIndex) throw new NotImplementedException(); } + /// + /// Copies comment from one cell to another + /// + /// Cell with a comment to copy + /// Cell to paste the comment to + /// Copied comment + public IComment CopyComment(ICell sourceCell, ICell targetCell) + { + throw new NotImplementedException(); + } + public ISheet CopySheet(string Name) { throw new NotImplementedException(); diff --git a/ooxml/XSSF/UserModel/Helpers/ColumnHelper.cs b/ooxml/XSSF/UserModel/Helpers/ColumnHelper.cs index 33cdf87d4..d0ef9f5a6 100644 --- a/ooxml/XSSF/UserModel/Helpers/ColumnHelper.cs +++ b/ooxml/XSSF/UserModel/Helpers/ColumnHelper.cs @@ -15,100 +15,310 @@ the License. You may obtain a copy of the License at limitations under the License. ==================================================================== */ -using System; -using System.Collections.Generic; -using System.Linq; using NPOI.OpenXmlFormats.Spreadsheet; using NPOI.SS.UserModel; -using NPOI.Util; using NPOI.XSSF.Util; +using System; +using System.Collections.Generic; + namespace NPOI.XSSF.UserModel.Helpers { - /** - * Helper class for dealing with the Column Settings on - * a CT_Worksheet (the data part of a sheet). - * Note - within POI, we use 0 based column indexes, but - * the column defInitions in the XML are 1 based! - */ + /// + /// Helper class for dealing with the Column Settings on a CT_Worksheet + /// (the data part of a sheet). Note - within POI, we use 0 based column + /// indexes, but the column defInitions in the XML are 1 based! + /// public class ColumnHelper { - private CT_Worksheet worksheet; - //private CT_Cols newCols; + private readonly CT_Worksheet worksheet; + #region Constructor public ColumnHelper(CT_Worksheet worksheet) { this.worksheet = worksheet; CleanColumns(); } + #endregion + + #region Public methods public void CleanColumns() { - TreeSet trackedCols = new TreeSet(CTColComparator.BY_MIN_MAX); + TreeSet trackedCols = + new TreeSet(CTColComparator.BY_MIN_MAX); CT_Cols newCols = new CT_Cols(); CT_Cols[] colsArray = worksheet.GetColsList().ToArray(); - int i = 0; + int i; + for (i = 0; i < colsArray.Length; i++) { CT_Cols cols = colsArray[i]; CT_Col[] colArray = cols.GetColList().ToArray(); + foreach (CT_Col col in colArray) { AddCleanColIntoCols(newCols, col, trackedCols); } } + for (int y = i - 1; y >= 0; y--) { worksheet.RemoveCols(y); } - newCols.SetColArray(trackedCols.ToArray(new CT_Col[trackedCols.Count])); + newCols.SetColArray( + trackedCols.ToArray(new CT_Col[trackedCols.Count])); worksheet.AddNewCols(); worksheet.SetColsArray(0, newCols); - //this.newCols = new CT_Cols(); - - //CT_Cols aggregateCols = new CT_Cols(); - //List colsList = worksheet.GetColsList(); - //if (colsList == null) - //{ - // return; - //} - - //foreach (CT_Cols cols in colsList) - //{ - // foreach (CT_Col col in cols.GetColList()) - // { - // CloneCol(aggregateCols, col); - // } - //} - - //SortColumns(aggregateCols); - - //CT_Col[] colArray = aggregateCols.GetColList().ToArray(); - //SweepCleanColumns(newCols, colArray, null); - - //int i = colsList.Count; - //for (int y = i - 1; y >= 0; y--) - //{ - // worksheet.RemoveCols(y); - //} - //worksheet.AddNewCols(); - //worksheet.SetColsArray(0, newCols); } public CT_Cols AddCleanColIntoCols(CT_Cols cols, CT_Col newCol) { - // Performance issue. If we encapsulated management of min/max in this - // class then we could keep trackedCols as state, - // making this log(N) rather than Nlog(N). We do this for the initial + // Performance issue. If we encapsulated management of min/max in + // this class then we could keep trackedCols as state, making this + // log(N) rather than Nlog(N). We do this for the initial // read above. - TreeSet trackedCols = new TreeSet(CTColComparator.BY_MIN_MAX); + TreeSet trackedCols = + new TreeSet(CTColComparator.BY_MIN_MAX); trackedCols.AddAll(cols.GetColList()); AddCleanColIntoCols(cols, newCol, trackedCols); cols.SetColArray(trackedCols.ToArray(new CT_Col[0])); return cols; } + public static void SortColumns(CT_Cols newCols) + { + List colArray = newCols.GetColList(); + colArray.Sort(new CTColComparator()); + newCols.SetColArray(colArray); + } + + public CT_Col CloneCol(CT_Cols cols, CT_Col col) + { + CT_Col newCol = cols.AddNewCol(); + newCol.min = col.min; + newCol.max = col.max; + SetColumnAttributes(col, newCol); + return newCol; + } + + /// + /// Returns the Column at the given 0 based index + /// + /// + /// + /// + public CT_Col GetColumn(long index, bool splitColumns) + { + return GetColumn1Based(index + 1, splitColumns); + } + + /// + /// Returns the Column at the given 1 based index. POI default is 0 + /// based, but the file stores as 1 based. + /// + /// + /// + /// + public CT_Col GetColumn1Based(long index1, bool splitColumns) + { + CT_Cols colsArray = worksheet.GetColsArray(0); + + // Fetching the array is quicker than working on the new style + // list, assuming we need to read many of them (which we often do), + // and assuming we're not making many changes (which we're not) + CT_Col[] cols = colsArray.GetColList().ToArray(); + for (int i = 0; i < cols.Length; i++) + { + CT_Col colArray = cols[i]; + long colMin = colArray.min; + long colMax = colArray.max; + if (colMin <= index1 && colMax >= index1) + { + if (splitColumns) + { + if (colMin < index1) + { + InsertCol( + colsArray, + colMin, + index1 - 1, + new CT_Col[] { colArray }); + } + + if (colMax > index1) + { + InsertCol( + colsArray, + index1 + 1, + colMax, + new CT_Col[] { colArray }); + } + + colArray.min = (uint)index1; + colArray.max = (uint)index1; + } + + return colArray; + } + } + + return null; + } + + /// + /// Does the column at the given 0 based index exist in the supplied + /// list of column defInitions? + /// + /// + /// + /// + public bool ColumnExists(CT_Cols cols, long index) + { + return ColumnExists1Based(cols, index + 1); + } + + public void SetColumnAttributes(CT_Col fromCol, CT_Col toCol) + { + + if (fromCol.IsSetBestFit()) + { + toCol.bestFit = fromCol.bestFit; + } + + if (fromCol.IsSetCustomWidth()) + { + toCol.customWidth = fromCol.customWidth; + } + + if (fromCol.IsSetHidden()) + { + toCol.hidden = fromCol.hidden; + } + + if (fromCol.IsSetStyle()) + { + toCol.style = fromCol.style; + } + + if (fromCol.IsSetWidth()) + { + toCol.width = fromCol.width; + toCol.widthSpecified = fromCol.widthSpecified; + } + + if (fromCol.IsSetCollapsed()) + { + toCol.collapsed = fromCol.collapsed; + toCol.collapsedSpecified = fromCol.collapsedSpecified; + } + + if (fromCol.IsSetPhonetic()) + { + toCol.phonetic = fromCol.phonetic; + } + + if (fromCol.IsSetOutlineLevel()) + { + toCol.outlineLevel = fromCol.outlineLevel; + } + + if (fromCol.IsSetCollapsed()) + { + toCol.collapsed = fromCol.collapsed; + } + } + + public void SetColBestFit(long index, bool bestFit) + { + CT_Col col = GetOrCreateColumn1Based(index + 1, false); + col.bestFit = bestFit; + } + public void SetCustomWidth(long index, bool width) + { + CT_Col col = GetOrCreateColumn1Based(index + 1, true); + col.customWidth = width; + } + + public void SetColWidth(long index, double width) + { + CT_Col col = GetOrCreateColumn1Based(index + 1, true); + col.width = width; + } + + public void SetColHidden(long index, bool hidden) + { + CT_Col col = GetOrCreateColumn1Based(index + 1, true); + col.hidden = hidden; + } + + public void SetColDefaultStyle(long index, ICellStyle style) + { + SetColDefaultStyle(index, style.Index); + } + + public void SetColDefaultStyle(long index, int styleId) + { + CT_Col col = GetOrCreateColumn1Based(index + 1, true); + col.style = (uint)styleId; + } + + /// + /// Returns -1 if no column is found for the given index + /// + /// + /// + public int GetColDefaultStyle(long index) + { + CT_Col column = GetColumn(index, false); + + if (column != null && column.style != null) + { + return (int)column.style; + } + + return -1; + } + + public int GetIndexOfColumn(CT_Cols cols, CT_Col col) + { + for (int i = 0; i < cols.sizeOfColArray(); i++) + { + if (cols.GetColArray(i).min == col.min + && cols.GetColArray(i).max == col.max) + { + return i; + } + } + + return -1; + } + #endregion + + #region Internal methods + /// + /// Return the CT_Col at the given (0 based) column index, creating + /// it if required. + /// + /// + /// + /// + internal CT_Col GetOrCreateColumn1Based(long index1, bool splitColumns) + { + CT_Col col = GetColumn1Based(index1, splitColumns); + if (col == null) + { + col = worksheet.GetColsArray(0).AddNewCol(); + col.min = (uint)index1; + col.max = (uint)index1; + } + + return col; + } + #endregion + + #region Private methods private void AddCleanColIntoCols(CT_Cols cols, CT_Col newCol, TreeSet trackedCols) { List overlapping = GetOverlappingCols(newCol, trackedCols); @@ -121,8 +331,8 @@ private void AddCleanColIntoCols(CT_Cols cols, CT_Col newCol, TreeSet tr trackedCols.RemoveAll(overlapping); foreach (CT_Col existing in overlapping) { - // We add up to three columns for each existing one: non-overlap - // before, overlap, non-overlap after. + // We add up to three columns for each existing one: + // non-overlap before, overlap, non-overlap after. long[] overlap = GetOverlap(newCol, existing); CT_Col overlapCol = CloneCol(cols, existing, overlap); @@ -153,8 +363,8 @@ private void AddCleanColIntoCols(CT_Cols cols, CT_Col newCol, TreeSet tr private CT_Col CloneCol(CT_Cols cols, CT_Col col, long[] newRange) { CT_Col cloneCol = CloneCol(cols, col); - cloneCol.min = (uint)(newRange[0]); - cloneCol.max = (uint)(newRange[1]); + cloneCol.min = (uint)newRange[0]; + cloneCol.max = (uint)newRange[1]; return cloneCol; } @@ -166,7 +376,10 @@ private long[] GetOverlap(CT_Col col1, CT_Col col2) private List GetOverlappingCols(CT_Col newCol, TreeSet trackedCols) { CT_Col lower = trackedCols.Lower(newCol); - TreeSet potentiallyOverlapping = lower == null ? trackedCols : trackedCols.TailSet(lower, Overlaps(lower, newCol)); + TreeSet potentiallyOverlapping = + lower == null + ? trackedCols + : trackedCols.TailSet(lower, Overlaps(lower, newCol)); List overlapping = new List(); foreach (CT_Col existing in potentiallyOverlapping) { @@ -179,6 +392,7 @@ private List GetOverlappingCols(CT_Col newCol, TreeSet trackedCo break; } } + return overlapping; } @@ -197,125 +411,151 @@ private long[] ToRange(CT_Col col) return new long[] { col.min, col.max }; } - //YK: GetXYZArray() array accessors are deprecated in xmlbeans with JDK 1.5 support - public static void SortColumns(CT_Cols newCols) + /// + /// Insert a new CT_Col at position 0 into cols, Setting min=min, + /// max=max and copying all the colsWithAttributes array cols + /// attributes into newCol + /// + /// + /// + /// + /// + /// + private CT_Col InsertCol(CT_Cols cols, long min, long max, + CT_Col[] colsWithAttributes) { - List colArray = newCols.GetColList(); - colArray.Sort(new CTColComparator()); - newCols.SetColArray(colArray); + return InsertCol(cols, min, max, colsWithAttributes, false, null); } - public CT_Col CloneCol(CT_Cols cols, CT_Col col) + private CT_Col InsertCol( + CT_Cols cols, + long min, + long max, + CT_Col[] colsWithAttributes, + bool ignoreExistsCheck, + CT_Col overrideColumn) { - CT_Col newCol = cols.AddNewCol(); - newCol.min = (uint)(col.min); - newCol.max = (uint)(col.max); - SetColumnAttributes(col, newCol); - return newCol; + if (ignoreExistsCheck || !ColumnExists(cols, min, max)) + { + CT_Col newCol = cols.InsertNewCol(0); + newCol.min = (uint)min; + newCol.max = (uint)max; + foreach (CT_Col col in colsWithAttributes) + { + SetColumnAttributes(col, newCol); + } + + if (overrideColumn != null) + { + SetColumnAttributes(overrideColumn, newCol); + } + + return newCol; + } + + return null; } - /** - * Returns the Column at the given 0 based index - */ - public CT_Col GetColumn(long index, bool splitColumns) + private bool ColumnExists1Based(CT_Cols cols, long index1) { - return GetColumn1Based(index + 1, splitColumns); + for (int i = 0; i < cols.sizeOfColArray(); i++) + { + if (cols.GetColArray(i).min == index1) + { + return true; + } + } + + return false; } - /** - * Returns the Column at the given 1 based index. - * POI default is 0 based, but the file stores - * as 1 based. - */ - public CT_Col GetColumn1Based(long index1, bool splitColumns) - { - CT_Cols colsArray = worksheet.GetColsArray(0); - // Fetching the array is quicker than working on the new style - // list, assuming we need to read many of them (which we often do), - // and assuming we're not making many changes (which we're not) - CT_Col[] cols = colsArray.GetColList().ToArray(); - for (int i = 0; i < cols.Length; i++) + private bool ColumnExists(CT_Cols cols, long min, long max) + { + for (int i = 0; i < cols.sizeOfColArray(); i++) { - CT_Col colArray = cols[i]; - long colMin = colArray.min; - long colMax = colArray.max; - if (colMin <= index1 && colMax >= index1) + if (cols.GetColArray(i).min == min + && cols.GetColArray(i).max == max) { - if (splitColumns) - { - if (colMin < index1) - { - InsertCol(colsArray, colMin, (index1 - 1), new CT_Col[] { colArray }); - } - if (colMax > index1) - { - InsertCol(colsArray, (index1 + 1), colMax, new CT_Col[] { colArray }); - } - colArray.min = (uint)(index1); - colArray.max = (uint)(index1); - } - return colArray; + return true; } } - return null; + + return false; } + #endregion + #region TreeSet class public class TreeSet { - private SortedList innerObj; - private IComparer comparer; + private readonly SortedList innerObj; + private readonly IComparer comparer; + public TreeSet(IComparer comparer) { this.comparer = comparer; innerObj = new SortedList(comparer); } + public T First() { - IEnumerator enumerator = this.innerObj.Keys.GetEnumerator(); + IEnumerator enumerator = innerObj.Keys.GetEnumerator(); if (enumerator.MoveNext()) + { return enumerator.Current; - return default(T); + } + + return default; } + public T Higher(T element) { - IEnumerator enumerator = this.innerObj.Keys.GetEnumerator(); + IEnumerator enumerator = innerObj.Keys.GetEnumerator(); while (enumerator.MoveNext()) { - if (this.innerObj.Comparer.Compare(enumerator.Current, element) > 0) + if (innerObj.Comparer.Compare(enumerator.Current, element) > 0) + { return enumerator.Current; + } } - return default(T); + + return default; } + public void Add(T item) { - if(!this.innerObj.ContainsKey(item)) - this.innerObj.Add(item,null); + if (!innerObj.ContainsKey(item)) + { + innerObj.Add(item, null); + } } + public bool Remove(T item) { - return this.innerObj.Remove(item); + return innerObj.Remove(item); } + public int Count { - get { return this.innerObj.Count; } + get { return innerObj.Count; } } + public void CopyTo(T[] target) - { - for (int i = 0; i < this.innerObj.Count; i++) + { + for (int i = 0; i < innerObj.Count; i++) { - target[i] = (T)this.innerObj.Keys[i]; + target[i] = innerObj.Keys[i]; } } public IEnumerator GetEnumerator() { - return this.innerObj.Keys.GetEnumerator(); + return innerObj.Keys.GetEnumerator(); } public T[] ToArray(T[] a) { List ts = new List(); - ts.AddRange(this.innerObj.Keys); + ts.AddRange(innerObj.Keys); if (a.Length < Count) { // Make a new array of a's runtime type, but my contents: @@ -324,43 +564,52 @@ public T[] ToArray(T[] a) Array.Copy(ts.ToArray(), 0, a, 0, Count); if (a.Length > Count) - a[Count] = default(T); + { + a[Count] = default; + } + return a; } internal void AddAll(List list) { - foreach(var item in list) + foreach (T item in list) { - if (!this.innerObj.ContainsKey(item)) - this.innerObj.Add(item, null); + if (!innerObj.ContainsKey(item)) + { + innerObj.Add(item, null); + } } } internal void RemoveAll(List list) { - foreach (var item in list) - this.innerObj.Remove(item); + foreach (T item in list) + { + innerObj.Remove(item); + } } internal T Lower(T element) { - IEnumerator enumerator = this.innerObj.Keys.GetEnumerator(); - T prevElement = default(T); + IEnumerator enumerator = innerObj.Keys.GetEnumerator(); + T prevElement = default; while (enumerator.MoveNext()) { - if (this.innerObj.Comparer.Compare(enumerator.Current, element) >= 0) + if (innerObj.Comparer.Compare(enumerator.Current, element) >= 0) { return prevElement; } + prevElement = enumerator.Current; } + return prevElement; } /// - /// Returns a view of the portion of this map whose keys are greater than (or - /// equal to, if inclusive is true) fromKey. + /// Returns a view of the portion of this map whose keys are + /// greater than (or equal to, if inclusive is true) fromKey. /// /// /// @@ -369,19 +618,19 @@ internal TreeSet TailSet(T fromElement, bool inclusive) { TreeSet set = new TreeSet(comparer); - IEnumerator enumerator = this.innerObj.Keys.GetEnumerator(); + IEnumerator enumerator = innerObj.Keys.GetEnumerator(); while (enumerator.MoveNext()) { if (inclusive) { - if (this.innerObj.Comparer.Compare(enumerator.Current, fromElement) >= 0) + if (innerObj.Comparer.Compare(enumerator.Current, fromElement) >= 0) { set.Add(enumerator.Current); } } else { - if (this.innerObj.Comparer.Compare(enumerator.Current, fromElement) > 0) + if (innerObj.Comparer.Compare(enumerator.Current, fromElement) > 0) { set.Add(enumerator.Current); } @@ -391,401 +640,6 @@ internal TreeSet TailSet(T fromElement, bool inclusive) return set; } } - /** - * @see Sweep line algorithm - */ - private void SweepCleanColumns(CT_Cols cols, CT_Col[] flattenedColsArray, CT_Col overrideColumn) - { - List flattenedCols = new List(flattenedColsArray); - TreeSet currentElements = new TreeSet(CTColComparator.BY_MAX); - IEnumerator flIter = flattenedCols.GetEnumerator(); - CT_Col haveOverrideColumn = null; - long lastMaxIndex = 0; - long currentMax = 0; - IList toRemove = new List(); - int pos = -1; - //while (flIter.hasNext()) - while ((pos + 1) < flattenedCols.Count) - { - //CTCol col = flIter.next(); - pos++; - CT_Col col = flattenedCols[pos]; - - long currentIndex = col.min; - long colMax = col.max; - long nextIndex = (colMax > currentMax) ? colMax : currentMax; - //if (flIter.hasNext()) { - if((pos+1) iter = currentElements.GetEnumerator(); - toRemove.Clear(); - while (iter.MoveNext()) - { - CT_Col elem = iter.Current; - if (currentIndex <= elem.max) - break; // all passed elements have been purged - - toRemove.Add(elem); - } - - foreach (CT_Col rc in toRemove) - { - currentElements.Remove(rc); - } - - if (!(currentElements.Count == 0) && lastMaxIndex < currentIndex) - { - // we need to process previous elements first - CT_Col[] copyCols = new CT_Col[currentElements.Count]; - currentElements.CopyTo(copyCols); - InsertCol(cols, lastMaxIndex, currentIndex - 1, copyCols, true, haveOverrideColumn); - } - currentElements.Add(col); - if (colMax > currentMax) currentMax = colMax; - if (col.Equals(overrideColumn)) haveOverrideColumn = overrideColumn; - while (currentIndex <= nextIndex && !(currentElements.Count == 0)) - { - NPOI.Util.Collections.HashSet currentIndexElements = new NPOI.Util.Collections.HashSet(); - long currentElemIndex; - - { - // narrow scope of currentElem - CT_Col currentElem = currentElements.First(); - currentElemIndex = currentElem.max; - currentIndexElements.Add(currentElem); - - while (true) - { - CT_Col higherElem = currentElements.Higher(currentElem); - if (higherElem == null || higherElem.max != currentElemIndex) - break; - currentElem = higherElem; - currentIndexElements.Add(currentElem); - if (colMax > currentMax) currentMax = colMax; - if (col.Equals(overrideColumn)) haveOverrideColumn = overrideColumn; - } - } - - //if (currentElemIndex < nextIndex || !flIter.hasNext()) { - if (currentElemIndex < nextIndex || !((pos + 1) < flattenedCols.Count)) - { - CT_Col[] copyCols = new CT_Col[currentElements.Count]; - currentElements.CopyTo(copyCols); - InsertCol(cols, currentIndex, currentElemIndex, copyCols, true, haveOverrideColumn); - //if (flIter.hasNext()) { - if ((pos + 1) < flattenedCols.Count) - { - if (nextIndex > currentElemIndex) - { - //currentElements.removeAll(currentIndexElements); - foreach (CT_Col rc in currentIndexElements) - currentElements.Remove(rc); - if (currentIndexElements.Contains(overrideColumn)) haveOverrideColumn = null; - } - } - else - { - //currentElements.removeAll(currentIndexElements); - foreach (CT_Col rc in currentIndexElements) - currentElements.Remove(rc); - if (currentIndexElements.Contains(overrideColumn)) haveOverrideColumn = null; - } - lastMaxIndex = currentIndex = currentElemIndex + 1; - } - else - { - lastMaxIndex = currentIndex; - currentIndex = nextIndex + 1; - } - - } - } - SortColumns(cols); - } - - //public CT_Cols AddCleanColIntoCols(CT_Cols cols, CT_Col col) - //{ - // bool colOverlaps = false; - // // a Map to remember overlapping columns - // Dictionary overlappingCols = new Dictionary(); - // int sizeOfColArray = cols.sizeOfColArray(); - // for (int i = 0; i < sizeOfColArray; i++) - // { - // CT_Col ithCol = cols.GetColArray(i); - // long[] range1 = { ithCol.min, ithCol.max }; - // long[] range2 = { col.min, col.max }; - // long[] overlappingRange = NumericRanges.GetOverlappingRange(range1, - // range2); - // int overlappingType = NumericRanges.GetOverlappingType(range1, - // range2); - // // different behavior required for each of the 4 different - // // overlapping types - // if (overlappingType == NumericRanges.OVERLAPS_1_MINOR) - // { - // // move the max border of the ithCol - // // and insert a new column within the overlappingRange with merged column attributes - // ithCol.max = (uint)(overlappingRange[0] - 1); - // insertCol(cols, overlappingRange[0], - // overlappingRange[1], new CT_Col[] { ithCol, col }); - // i++; - // //CT_Col newCol = insertCol(cols, (overlappingRange[1] + 1), col - // // .max, new CT_Col[] { col }); - // //i++; - // } - // else if (overlappingType == NumericRanges.OVERLAPS_2_MINOR) - // { - // // move the min border of the ithCol - // // and insert a new column within the overlappingRange with merged column attributes - // ithCol.min = (uint)(overlappingRange[1] + 1); - // insertCol(cols, overlappingRange[0], - // overlappingRange[1], new CT_Col[] { ithCol, col }); - // i++; - // //CT_Col newCol = insertCol(cols, col.min, - // // (overlappingRange[0] - 1), new CT_Col[] { col }); - // //i++; - // } - // else if (overlappingType == NumericRanges.OVERLAPS_2_WRAPS) - // { - // // merge column attributes, no new column is needed - // SetColumnAttributes(col, ithCol); - - // } - // else if (overlappingType == NumericRanges.OVERLAPS_1_WRAPS) - // { - // // split the ithCol in three columns: before the overlappingRange, overlappingRange, and after the overlappingRange - // // before overlappingRange - // if (col.min != ithCol.min) - // { - // insertCol(cols, ithCol.min, (col - // .min - 1), new CT_Col[] { ithCol }); - // i++; - // } - // // after the overlappingRange - // if (col.max != ithCol.max) - // { - // insertCol(cols, (col.max + 1), - // ithCol.max, new CT_Col[] { ithCol }); - // i++; - // } - // // within the overlappingRange - // ithCol.min = (uint)(overlappingRange[0]); - // ithCol.max = (uint)(overlappingRange[1]); - // SetColumnAttributes(col, ithCol); - // } - // if (overlappingType != NumericRanges.NO_OVERLAPS) - // { - // colOverlaps = true; - // // remember overlapped columns - // for (long j = overlappingRange[0]; j <= overlappingRange[1]; j++) - // { - // overlappingCols.Add(j, true); - // } - // } - // } - // if (!colOverlaps) - // { - // CloneCol(cols, col); - // } - // else - // { - // // insert new columns for ranges without overlaps - // long colMin = -1; - // for (long j = col.min; j <= col.max; j++) - // { - // if (overlappingCols.ContainsKey(j) && !overlappingCols[j]) - // { - // if (colMin < 0) - // { - // colMin = j; - // } - // if ((j + 1) > col.max || overlappingCols[(j + 1)]) - // { - // insertCol(cols, colMin, j, new CT_Col[] { col }); - // colMin = -1; - // } - // } - // } - // } - // SortColumns(cols); - // return cols; - //} - - /* - * Insert a new CT_Col at position 0 into cols, Setting min=min, max=max and - * copying all the colsWithAttributes array cols attributes into newCol - */ - private CT_Col InsertCol(CT_Cols cols, long min, long max, - CT_Col[] colsWithAttributes) - { - return InsertCol(cols, min, max, colsWithAttributes, false, null); - } - private CT_Col InsertCol(CT_Cols cols, long min, long max, - CT_Col[] colsWithAttributes, bool ignoreExistsCheck, CT_Col overrideColumn) - { - if (ignoreExistsCheck || !ColumnExists(cols, min, max)) - { - CT_Col newCol = cols.InsertNewCol(0); - newCol.min = (uint)(min); - newCol.max = (uint)(max); - foreach (CT_Col col in colsWithAttributes) - { - SetColumnAttributes(col, newCol); - } - if (overrideColumn != null) SetColumnAttributes(overrideColumn, newCol); - return newCol; - } - return null; - } - /** - * Does the column at the given 0 based index exist - * in the supplied list of column defInitions? - */ - public bool ColumnExists(CT_Cols cols, long index) - { - return ColumnExists1Based(cols, index + 1); - } - private bool ColumnExists1Based(CT_Cols cols, long index1) - { - for (int i = 0; i < cols.sizeOfColArray(); i++) - { - if (cols.GetColArray(i).min == index1) - { - return true; - } - } - return false; - } - - public void SetColumnAttributes(CT_Col fromCol, CT_Col toCol) - { - - if (fromCol.IsSetBestFit()) - { - toCol.bestFit = (fromCol.bestFit); - } - if (fromCol.IsSetCustomWidth()) - { - toCol.customWidth = (fromCol.customWidth); - } - if (fromCol.IsSetHidden()) - { - toCol.hidden = (fromCol.hidden); - } - if (fromCol.IsSetStyle()) - { - toCol.style = (fromCol.style); - } - if (fromCol.IsSetWidth()) - { - toCol.width = (fromCol.width); - toCol.widthSpecified = fromCol.widthSpecified; - } - if (fromCol.IsSetCollapsed()) - { - toCol.collapsed = (fromCol.collapsed); - toCol.collapsedSpecified = fromCol.collapsedSpecified; - } - if (fromCol.IsSetPhonetic()) - { - toCol.phonetic = (fromCol.phonetic); - } - if (fromCol.IsSetOutlineLevel()) - { - toCol.outlineLevel = (fromCol.outlineLevel); - } - if (fromCol.IsSetCollapsed()) - { - toCol.collapsed = fromCol.collapsed; - } - } - - public void SetColBestFit(long index, bool bestFit) - { - CT_Col col = GetOrCreateColumn1Based(index + 1, false); - col.bestFit = (bestFit); - } - public void SetCustomWidth(long index, bool width) - { - CT_Col col = GetOrCreateColumn1Based(index + 1, true); - col.customWidth = (width); - } - - public void SetColWidth(long index, double width) - { - CT_Col col = GetOrCreateColumn1Based(index + 1, true); - col.width = (width); - } - - public void SetColHidden(long index, bool hidden) - { - CT_Col col = GetOrCreateColumn1Based(index + 1, true); - col.hidden = (hidden); - } - - /** - * Return the CT_Col at the given (0 based) column index, - * creating it if required. - */ - internal CT_Col GetOrCreateColumn1Based(long index1, bool splitColumns) - { - CT_Col col = GetColumn1Based(index1, splitColumns); - if (col == null) - { - col = worksheet.GetColsArray(0).AddNewCol(); - col.min = (uint)(index1); - col.max = (uint)(index1); - } - return col; - } - - public void SetColDefaultStyle(long index, ICellStyle style) - { - SetColDefaultStyle(index, style.Index); - } - - public void SetColDefaultStyle(long index, int styleId) - { - CT_Col col = GetOrCreateColumn1Based(index + 1, true); - col.style = (uint)styleId; - } - - // Returns -1 if no column is found for the given index - public int GetColDefaultStyle(long index) - { - if (GetColumn(index, false) != null) - { - return (int)GetColumn(index, false).style; - } - return -1; - } - - private bool ColumnExists(CT_Cols cols, long min, long max) - { - for (int i = 0; i < cols.sizeOfColArray(); i++) - { - if (cols.GetColArray(i).min == min && cols.GetColArray(i).max == max) - { - return true; - } - } - return false; - } - - public int GetIndexOfColumn(CT_Cols cols, CT_Col col) - { - for (int i = 0; i < cols.sizeOfColArray(); i++) - { - if (cols.GetColArray(i).min == col.min && cols.GetColArray(i).max == col.max) - { - return i; - } - } - return -1; - } + #endregion } - } diff --git a/ooxml/XSSF/UserModel/Helpers/XSSFRowColShifter.cs b/ooxml/XSSF/UserModel/Helpers/XSSFRowColShifter.cs index 38e064054..cc0d62a99 100644 --- a/ooxml/XSSF/UserModel/Helpers/XSSFRowColShifter.cs +++ b/ooxml/XSSF/UserModel/Helpers/XSSFRowColShifter.cs @@ -1,13 +1,12 @@ using NPOI.OpenXmlFormats.Spreadsheet; using NPOI.SS.Formula; -using NPOI.SS.Formula.PTG; using NPOI.SS.UserModel; using NPOI.SS.UserModel.Helpers; using NPOI.SS.Util; using NPOI.XSSF.UserModel; using System; using System.Collections.Generic; -using System.Text; +using System.Linq; namespace NPOI.OOXML.XSSF.UserModel.Helpers { @@ -23,17 +22,20 @@ public static void UpdateNamedRanges(ISheet sheet, FormulaShifter shifter) XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.Create(wb); foreach (IName name in wb.GetAllNames()) { - String formula = name.RefersToFormula; + string formula = name.RefersToFormula; int sheetIndex = name.SheetIndex; - Ptg[] ptgs = FormulaParser.Parse(formula, fpb, FormulaType.NamedRange, sheetIndex, -1); + SS.Formula.PTG.Ptg[] ptgs = + FormulaParser.Parse(formula, fpb, FormulaType.NamedRange, sheetIndex, -1); + if (shifter.AdjustFormula(ptgs, sheetIndex)) { - String shiftedFmla = FormulaRenderer.ToFormulaString(fpb, ptgs); + string shiftedFmla = FormulaRenderer.ToFormulaString(fpb, ptgs); name.RefersToFormula = shiftedFmla; } } } + /// /// Update formulas. /// @@ -48,7 +50,11 @@ public static void UpdateFormulas(ISheet sheet, FormulaShifter shifter) IWorkbook wb = sheet.Workbook; foreach (XSSFSheet sh in wb) { - if (sheet == sh) continue; + if (sheet == sh) + { + continue; + } + UpdateSheetFormulas(sh, shifter); } } @@ -61,8 +67,10 @@ public static void UpdateSheetFormulas(ISheet sh, FormulaShifter Shifter) UpdateRowFormulas(row, Shifter); } } + /// - /// Update the formulas in specified row using the formula shifting policy specified by shifter + /// Update the formulas in specified row using the formula shifting + /// policy specified by shifter /// /// the row to update the formulas on /// the formula shifting policy @@ -77,40 +85,46 @@ public static void UpdateRowFormulas(IRow row, FormulaShifter Shifter) if (ctCell.IsSetF()) { CT_CellFormula f = ctCell.f; - String formula = f.Value; + string formula = f.Value; if (formula.Length > 0) { - String ShiftedFormula = ShiftFormula(row, formula, Shifter); + string ShiftedFormula = ShiftFormula(row, formula, Shifter); if (ShiftedFormula != null) { - f.Value = (ShiftedFormula); + f.Value = ShiftedFormula; if (f.t == ST_CellFormulaType.shared) { int si = (int)f.si; CT_CellFormula sf = sheet.GetSharedFormula(si); - sf.Value = (ShiftedFormula); + sf.Value = ShiftedFormula; } } } if (f.isSetRef()) { //Range of cells which the formula applies to. - String ref1 = f.@ref; - String ShiftedRef = ShiftFormula(row, ref1, Shifter); - if (ShiftedRef != null) f.@ref = ShiftedRef; + string ref1 = f.@ref; + string ShiftedRef = ShiftFormula(row, ref1, Shifter); + if (ShiftedRef != null) + { + f.@ref = ShiftedRef; } } - } } + } + /// /// Shift a formula using the supplied FormulaShifter /// - /// the row of the cell this formula belongs to. Used to get a reference to the parent workbook. + /// the row of the cell this formula belongs to. + /// Used to get a reference to the parent workbook. /// the formula to shift - /// the FormulaShifter object that operates on the Parsed formula tokens - /// the Shifted formula if the formula was changed, null if the formula wasn't modified - private static String ShiftFormula(IRow row, String formula, FormulaShifter Shifter) + /// the FormulaShifter object that operates on + /// the Parsed formula tokens + /// the Shifted formula if the formula was changed, null if + /// the formula wasn't modified + private static string ShiftFormula(IRow row, string formula, FormulaShifter Shifter) { ISheet sheet = row.Sheet; IWorkbook wb = sheet.Workbook; @@ -118,21 +132,24 @@ private static String ShiftFormula(IRow row, String formula, FormulaShifter Shif XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.Create(wb); try { - Ptg[] ptgs = FormulaParser.Parse(formula, fpb, FormulaType.Cell, sheetIndex, -1); - String ShiftedFmla = null; + SS.Formula.PTG.Ptg[] ptgs = + FormulaParser.Parse(formula, fpb, FormulaType.Cell, sheetIndex, -1); + string ShiftedFmla = null; if (Shifter.AdjustFormula(ptgs, sheetIndex)) { ShiftedFmla = FormulaRenderer.ToFormulaString(fpb, ptgs); } + return ShiftedFmla; } catch (FormulaParseException fpe) { // Log, but don't change, rather than breaking - Console.WriteLine("Error shifting formula on row {0}, {1}", row.RowNum, fpe); + Console.WriteLine($"Error shifting formula on row {row.RowNum}, {fpe}"); return formula; } } + /// /// Shift the Hyperlink anchors(not the hyperlink text, even if the hyperlink is of type LINK_DOCUMENT and refers to a cell that was shifted). Hyperlinks do not track the content they point to. /// @@ -147,9 +164,11 @@ public static void UpdateHyperlinks(ISheet sheet, FormulaShifter shifter) foreach (IHyperlink hyperlink1 in hyperlinkList) { XSSFHyperlink hyperlink = hyperlink1 as XSSFHyperlink; - String cellRef = hyperlink.CellRef; + string cellRef = hyperlink.CellRef; CellRangeAddress cra = CellRangeAddress.ValueOf(cellRef); - CellRangeAddress shiftedRange = BaseRowColShifter.ShiftRange(shifter, cra, sheetIndex); + CellRangeAddress shiftedRange = + BaseRowColShifter.ShiftRange(shifter, cra, sheetIndex); + if (shiftedRange != null && shiftedRange != cra) { // shiftedRange should not be null. If shiftedRange is null, that means @@ -159,23 +178,24 @@ public static void UpdateHyperlinks(ISheet sheet, FormulaShifter shifter) } } } + public static void UpdateConditionalFormatting(ISheet sheet, FormulaShifter Shifter) { XSSFSheet xsheet = (XSSFSheet)sheet; XSSFWorkbook wb = xsheet.Workbook as XSSFWorkbook; int sheetIndex = wb.GetSheetIndex(sheet); - - XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.Create(wb); CT_Worksheet ctWorksheet = xsheet.GetCTWorksheet(); - List conditionalFormattingArray = ctWorksheet.conditionalFormatting; + List conditionalFormattingArray = + ctWorksheet.conditionalFormatting; + // iterate backwards due to possible calls to ctWorksheet.removeConditionalFormatting(j) for (int j = conditionalFormattingArray.Count - 1; j >= 0; j--) { CT_ConditionalFormatting cf = conditionalFormattingArray[j]; - List cellRanges = new List(); - String[] regions = cf.sqref.ToString().Split(new char[] { ' ' }); + string[] regions = cf.sqref.ToString().Split(new char[] { ' ' }); + for (int i = 0; i < regions.Length; i++) { cellRanges.Add(CellRangeAddress.ValueOf(regions[i])); @@ -186,12 +206,15 @@ public static void UpdateConditionalFormatting(ISheet sheet, FormulaShifter Shif for (int i = 0; i < cellRanges.Count; i++) { CellRangeAddress craOld = cellRanges[i]; - CellRangeAddress craNew = BaseRowColShifter.ShiftRange(Shifter, craOld, sheetIndex); + CellRangeAddress craNew = + BaseRowColShifter.ShiftRange(Shifter, craOld, sheetIndex); + if (craNew == null) { Changed = true; continue; } + temp.Add(craNew); if (craNew != craOld) { @@ -207,27 +230,35 @@ public static void UpdateConditionalFormatting(ISheet sheet, FormulaShifter Shif conditionalFormattingArray.RemoveAt(j); continue; } + string refs = string.Empty; foreach (CellRangeAddress a in temp) { if (refs.Length == 0) + { refs = a.FormatAsString(); + } else + { refs += " " + a.FormatAsString(); } + } + cf.sqref = refs; } foreach (CT_CfRule cfRule in cf.cfRule) { - List formulas = cfRule.formula; + List formulas = cfRule.formula; for (int i = 0; i < formulas.Count; i++) { - String formula = formulas[i]; - Ptg[] ptgs = FormulaParser.Parse(formula, fpb, FormulaType.Cell, sheetIndex, -1); + string formula = formulas[i]; + SS.Formula.PTG.Ptg[] ptgs = + FormulaParser.Parse(formula, fpb, FormulaType.Cell, sheetIndex, -1); + if (Shifter.AdjustFormula(ptgs, sheetIndex)) { - String ShiftedFmla = FormulaRenderer.ToFormulaString(fpb, ptgs); + string ShiftedFmla = FormulaRenderer.ToFormulaString(fpb, ptgs); formulas[i] = ShiftedFmla; } } diff --git a/ooxml/XSSF/UserModel/XSSFCell.cs b/ooxml/XSSF/UserModel/XSSFCell.cs index 5bee6acb9..803a9ebff 100644 --- a/ooxml/XSSF/UserModel/XSSFCell.cs +++ b/ooxml/XSSF/UserModel/XSSFCell.cs @@ -191,14 +191,13 @@ public void CopyCellFrom(ICell srcCell, CellCopyPolicy policy) { // overwrite the hyperlink at dest cell with srcCell's hyperlink // if srcCell doesn't have a hyperlink, clear the hyperlink (if one exists) at destCell - IHyperlink srcHyperlink = srcCell.Hyperlink; - if (srcHyperlink == null) + if (srcCell == null || srcCell.Hyperlink == null) { Hyperlink = (null); } else { - Hyperlink = new XSSFHyperlink(srcHyperlink); + Hyperlink = new XSSFHyperlink(srcCell.Hyperlink); } } } diff --git a/ooxml/XSSF/UserModel/XSSFRow.cs b/ooxml/XSSF/UserModel/XSSFRow.cs index 2e57845b1..d2a46397b 100644 --- a/ooxml/XSSF/UserModel/XSSFRow.cs +++ b/ooxml/XSSF/UserModel/XSSFRow.cs @@ -21,6 +21,7 @@ limitations under the License. using NPOI.SS.UserModel; using NPOI.SS.Util; using NPOI.Util; +using NPOI.XSSF.Model; using NPOI.XSSF.UserModel.Helpers; using System; using System.Collections; @@ -53,6 +54,8 @@ public class XSSFRow : IRow, IComparable /// the parent sheet /// private readonly XSSFSheet _sheet; + + private readonly StylesTable _stylesSource; #endregion #region Public properties @@ -227,21 +230,15 @@ public ICellStyle RowStyle { get { - if (!IsFormatted) + if (IsFormatted && _stylesSource != null + && _stylesSource.NumCellStyles > 0) { - return null; + return _stylesSource.GetStyleAt((int)_row.s); } - Model.StylesTable stylesSource = ((XSSFWorkbook)Sheet.Workbook).GetStylesSource(); - if (stylesSource.NumCellStyles > 0) - { - return stylesSource.GetStyleAt((int)_row.s); - } - else - { return null; } - } + set { if (value == null) @@ -254,18 +251,20 @@ public ICellStyle RowStyle } else { - Model.StylesTable styleSource = ((XSSFWorkbook)Sheet.Workbook).GetStylesSource(); - XSSFCellStyle xStyle = (XSSFCellStyle)value; - xStyle.VerifyBelongsToStylesSource(styleSource); + xStyle.VerifyBelongsToStylesSource(_stylesSource); - long idx = styleSource.PutStyle(xStyle); + long idx = _stylesSource.PutStyle(xStyle); _row.s = (uint)idx; _row.customFormat = true; } + + foreach (var cell in _cells.Values) + { + cell.CellStyle = value; + } } } - #endregion #region Constructor @@ -301,6 +300,8 @@ public XSSFRow(CT_Row row, XSSFSheet sheet) row.r = (uint)nextRowNum; } + + _stylesSource = ((XSSFWorkbook)sheet.Workbook).GetStylesSource(); } #endregion @@ -348,6 +349,11 @@ public ICell CreateCell(int columnIndex, CellType type) xcell.SetCellType(type); } + if (IsFormatted) + { + xcell.CellStyle = RowStyle; + } + _cells[columnIndex] = xcell; return xcell; } @@ -601,6 +607,25 @@ internal void Shift(int n) RowNum = rownum; } + + internal void RebuildCells() + { + Dictionary map = new Dictionary(); + foreach (XSSFCell c in _cells.Values) + { + map.Add(c.ColumnIndex, c); + } + + _cells.Clear(); + + foreach (KeyValuePair kv in map) + { + _cells.Add(kv.Key, kv.Value); + } + + // Sort CT_Cols by index asc. + _row.c.Sort((col1, col2) => col1.r.CompareTo(col2.r)); + } #endregion #region IEnumerable and IComparable members diff --git a/ooxml/XSSF/UserModel/XSSFSheet.cs b/ooxml/XSSF/UserModel/XSSFSheet.cs index e5532b815..ad88f63ec 100644 --- a/ooxml/XSSF/UserModel/XSSFSheet.cs +++ b/ooxml/XSSF/UserModel/XSSFSheet.cs @@ -15,18 +15,11 @@ the License. You may obtain a copy of the License at limitations under the License. ==================================================================== */ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.IO; -using System.Text; -using System.Xml; -using NPOI.HSSF.Record; using NPOI.OpenXml4Net.Exceptions; using NPOI.OpenXml4Net.OPC; -using NPOI.OpenXmlFormats; +using NPOI.OpenXmlFormats.Dml.Spreadsheet; using NPOI.OpenXmlFormats.Spreadsheet; +using NPOI.POIFS.Crypt; using NPOI.SS; using NPOI.SS.Formula; using NPOI.SS.UserModel; @@ -34,86 +27,73 @@ limitations under the License. using NPOI.Util; using NPOI.XSSF.Model; using NPOI.XSSF.UserModel.Helpers; -using NPOI.POIFS.Crypt; +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Xml; +using CT_Shape = NPOI.OpenXmlFormats.Vml.CT_Shape; +using ST_EditAs = NPOI.OpenXmlFormats.Dml.Spreadsheet.ST_EditAs; namespace NPOI.XSSF.UserModel { - /** - * High level representation of a SpreadsheetML worksheet. - * - *

- * Sheets are the central structures within a workbook, and are where a user does most of his spreadsheet work. - * The most common type of sheet is the worksheet, which is represented as a grid of cells. Worksheet cells can - * contain text, numbers, dates, and formulas. Cells can also be formatted. - *

- */ - public class XSSFSheet : POIXMLDocumentPart, ISheet + /// + /// High level representation of a SpreadsheetML worksheet. Sheets are the + /// central structures within a workbook, and are where a user does most of + /// his spreadsheet work. The most common type of sheet is the worksheet, + /// which is represented as a grid of cells.Worksheet cells can contain + /// text, numbers, dates, and formulas. Cells can also be formatted. + /// + public partial class XSSFSheet : POIXMLDocumentPart, ISheet { - private static POILogger logger = POILogFactory.GetLogger(typeof(XSSFSheet)); - - private static double DEFAULT_ROW_HEIGHT = 15.0; - private static double DEFAULT_MARGIN_HEADER = 0.3; - private static double DEFAULT_MARGIN_FOOTER = 0.3; - private static double DEFAULT_MARGIN_TOP = 0.75; - private static double DEFAULT_MARGIN_BOTTOM = 0.75; - private static double DEFAULT_MARGIN_LEFT = 0.7; - private static double DEFAULT_MARGIN_RIGHT = 0.7; + private static readonly POILogger logger = POILogFactory.GetLogger(typeof(XSSFSheet)); + + private static readonly double DEFAULT_ROW_HEIGHT = 15.0; + private static readonly double DEFAULT_MARGIN_HEADER = 0.3; + private static readonly double DEFAULT_MARGIN_FOOTER = 0.3; + private static readonly double DEFAULT_MARGIN_TOP = 0.75; + private static readonly double DEFAULT_MARGIN_BOTTOM = 0.75; + private static readonly double DEFAULT_MARGIN_LEFT = 0.7; + private static readonly double DEFAULT_MARGIN_RIGHT = 0.7; public static int TWIPS_PER_POINT = 20; //TODO make the two variable below private! internal CT_Sheet sheet; internal CT_Worksheet worksheet; - private SortedList _rows = new SortedList(); + private readonly SortedList _rows = new SortedList(); private List hyperlinks; private ColumnHelper columnHelper; private CommentsTable sheetComments; - /** - * cache of master shared formulas in this sheet. - * Master shared formula is the first formula in a group of shared formulas is saved in the f element. - */ + /// + /// cache of master shared formulas in this sheet. Master shared + /// formula is the first formula in a group of shared formulas is saved + /// in the f element. + /// private Dictionary sharedFormulas; - private Dictionary tables; + private Dictionary tables; private List arrayFormulas; - private XSSFDataValidationHelper dataValidationHelper; - - /** - * Creates new XSSFSheet - called by XSSFWorkbook to create a sheet from scratch. - * - * @see NPOI.XSSF.usermodel.XSSFWorkbook#CreateSheet() - */ - public XSSFSheet() - : base() - { - - dataValidationHelper = new XSSFDataValidationHelper(this); - OnDocumentCreate(); - } - - /** - * Creates an XSSFSheet representing the given namespace part and relationship. - * Should only be called by XSSFWorkbook when Reading in an exisiting file. - * - * @param part - The namespace part that holds xml data represenring this sheet. - * @param rel - the relationship of the given namespace part in the underlying OPC namespace - */ - protected internal XSSFSheet(PackagePart part) - : base(part) + private readonly XSSFDataValidationHelper dataValidationHelper; + private XSSFDrawing drawing = null; + private CT_Pane Pane { - dataValidationHelper = new XSSFDataValidationHelper(this); - } + get + { + if (GetDefaultSheetView().pane == null) + { + GetDefaultSheetView().AddNewPane(); + } - [Obsolete("deprecated in POI 3.14, scheduled for removal in POI 3.16")] - internal XSSFSheet(PackagePart part, PackageRelationship rel) - : this(part) - { + return GetDefaultSheetView().pane; + } } - /** - * Returns the parent XSSFWorkbook - * - * @return the parent XSSFWorkbook - */ + #region Public properties + /// + /// Returns the parent XSSFWorkbook + /// public IWorkbook Workbook { get @@ -123,3032 +103,2666 @@ public IWorkbook Workbook } } - /** - * Initialize worksheet data when Reading in an exisiting file. - */ - - internal override void OnDocumentRead() + /// + /// Returns the name of this sheet + /// + public string SheetName { - try - { - Read(GetPackagePart().GetInputStream()); - } - catch (IOException e) + get { - throw new POIXMLException(e); + return sheet.name; } } - internal virtual void Read(Stream is1) + /// + /// Vertical page break information used for print layout view, page + /// layout view, drawing print breaksin normal view, and for printing + /// the worksheet. + /// + // YK: GetXYZArray() array accessors are deprecated in xmlbeans with + // JDK 1.5 support + public int[] ColumnBreaks { - try - { - XmlDocument doc = ConvertStreamToXml(is1); - worksheet = WorksheetDocument.Parse(doc, NamespaceManager).GetWorksheet(); - } - catch (XmlException e) - { - throw new POIXMLException(e); - } - - InitRows(worksheet); - columnHelper = new ColumnHelper(worksheet); - - // Look for bits we're interested in - foreach (RelationPart rp in RelationParts) + get { - POIXMLDocumentPart p = rp.DocumentPart; - if (p is CommentsTable) - { - sheetComments = (CommentsTable)p; - //break; - } - if (p is XSSFTable) - { - tables[rp.Relationship.Id] = (XSSFTable)p; - } - if (p is XSSFPivotTable) + if (!worksheet.IsSetColBreaks() || worksheet.colBreaks.sizeOfBrkArray() == 0) { - GetWorkbook().PivotTables.Add((XSSFPivotTable)p); + return new int[0]; } - } - - // Process external hyperlinks for the sheet, if there are any - InitHyperlinks(); - } - - /** - * Initialize worksheet data when creating a new sheet. - */ - internal override void OnDocumentCreate() - { - worksheet = NewSheet(); - InitRows(worksheet); - columnHelper = new ColumnHelper(worksheet); - hyperlinks = new List(); - } + List brkArray = worksheet.colBreaks.brk; - private void InitRows(CT_Worksheet worksheetParam) - { - _rows.Clear(); - tables = new Dictionary(); - sharedFormulas = new Dictionary(); - arrayFormulas = new List(); - if (0 < worksheetParam.sheetData.SizeOfRowArray()) - { - foreach (CT_Row row in worksheetParam.sheetData.row) + int[] breaks = new int[brkArray.Count]; + for (int i = 0; i < brkArray.Count; i++) { - XSSFRow r = new XSSFRow(row, this); - if (!_rows.ContainsKey(r.RowNum)) - _rows.Add(r.RowNum, r); + CT_Break brk = brkArray[i]; + breaks[i] = (int)brk.id - 1; } + + return breaks; } } - /** - * Read hyperlink relations, link them with CT_Hyperlink beans in this worksheet - * and Initialize the internal array of XSSFHyperlink objects - */ - //YK: GetXYZArray() array accessors are deprecated in xmlbeans with JDK 1.5 support - private void InitHyperlinks() + /// + /// Get the default column width for the sheet (if the columns do not + /// define their own width) in characters. Note, this value is different + /// from . The latter is always greater and + /// includes 4 pixels of margin pAdding(two on each side), plus 1 pixel + /// pAdding for the gridlines. + /// + public int DefaultColumnWidth { - hyperlinks = new List(); - - if (!worksheet.IsSetHyperlinks()) return; - - try + get { - PackageRelationshipCollection hyperRels = - GetPackagePart().GetRelationshipsByType(XSSFRelation.SHEET_HYPERLINKS.Relation); - - // Turn each one into a XSSFHyperlink - foreach (CT_Hyperlink hyperlink in worksheet.hyperlinks.hyperlink) - { - PackageRelationship hyperRel = null; - if (hyperlink.id != null) - { - hyperRel = hyperRels.GetRelationshipByID(hyperlink.id); - } - - hyperlinks.Add(new XSSFHyperlink(hyperlink, hyperRel)); - } + CT_SheetFormatPr pr = worksheet.sheetFormatPr; + return pr == null ? 8 : (int)pr.baseColWidth; } - catch (InvalidFormatException e) + set { - throw new POIXMLException(e); + GetSheetTypeSheetFormatPr().baseColWidth = (uint)value; } } - /** - * Create a new CT_Worksheet instance with all values set to defaults - * - * @return a new instance - */ - private static CT_Worksheet NewSheet() - { - CT_Worksheet worksheet = new CT_Worksheet(); - CT_SheetFormatPr ctFormat = worksheet.AddNewSheetFormatPr(); - ctFormat.defaultRowHeight = DEFAULT_ROW_HEIGHT; - - CT_SheetView ctView = worksheet.AddNewSheetViews().AddNewSheetView(); - ctView.workbookViewId = (0); - - worksheet.AddNewDimension().@ref = "A1"; - - worksheet.AddNewSheetData(); - - CT_PageMargins ctMargins = worksheet.AddNewPageMargins(); - ctMargins.bottom = DEFAULT_MARGIN_BOTTOM; - ctMargins.footer = DEFAULT_MARGIN_FOOTER; - ctMargins.header = DEFAULT_MARGIN_HEADER; - ctMargins.left = DEFAULT_MARGIN_LEFT; - ctMargins.right = DEFAULT_MARGIN_RIGHT; - ctMargins.top = DEFAULT_MARGIN_TOP; - - return worksheet; - } - - /** - * Provide access to the CT_Worksheet bean holding this sheet's data - * - * @return the CT_Worksheet bean holding this sheet's data - */ - - public CT_Worksheet GetCTWorksheet() - { - return this.worksheet; - } - - public ColumnHelper GetColumnHelper() + /// + /// Get the default row height for the sheet (if the rows do not define + /// their own height) in twips(1/20 of a point) + /// + public short DefaultRowHeight { - return columnHelper; + get + { + return (short)((decimal)DefaultRowHeightInPoints * TWIPS_PER_POINT); + } + set + { + DefaultRowHeightInPoints = (float)value / TWIPS_PER_POINT; + } } - /** - * Returns the name of this sheet - * - * @return the name of this sheet - */ - public String SheetName + /// + /// Get the default row height for the sheet measued in point size + /// (if the rows do not define their own height). + /// + public float DefaultRowHeightInPoints { get { - return sheet.name; + CT_SheetFormatPr pr = worksheet.sheetFormatPr; + return (float)(pr == null ? 0 : pr.defaultRowHeight); + } + set + { + CT_SheetFormatPr pr = GetSheetTypeSheetFormatPr(); + pr.defaultRowHeight = value; + pr.customHeight = true; } } /// - /// Adds a merged region of cells on a sheet. + /// Whether the text is displayed in right-to-left mode in the window /// - /// region to merge - /// index of this region - /// if region contains fewer than 2 cells - /// if region intersects with an existing merged region - /// or multi-cell array formula on this sheet - public int AddMergedRegion(CellRangeAddress region) + public bool RightToLeft { - return AddMergedRegion(region, true); + get + { + CT_SheetView view = GetDefaultSheetView(); + return view != null && view.rightToLeft; + } + set + { + CT_SheetView view = GetDefaultSheetView(); + view.rightToLeft = value; + } } /// - /// Adds a merged region of cells (hence those cells form one). - /// Skips validation.It is possible to create overlapping merged regions - /// or create a merged region that intersects a multi-cell array formula - /// with this formula, which may result in a corrupt workbook. + /// Get whether to display the guts or not, default value is true /// - /// region to merge - /// index of this region - /// if region contains fewer than 2 cells - public int AddMergedRegionUnsafe(CellRangeAddress region) + public bool DisplayGuts { - return AddMergedRegion(region, false); + get + { + CT_SheetPr sheetPr = GetSheetTypeSheetPr(); + CT_OutlinePr outlinePr = sheetPr.outlinePr ?? new CT_OutlinePr(); + return outlinePr.showOutlineSymbols; + } + set + { + CT_SheetPr sheetPr = GetSheetTypeSheetPr(); + CT_OutlinePr outlinePr = sheetPr.outlinePr ?? sheetPr.AddNewOutlinePr(); + outlinePr.showOutlineSymbols = value; + } } /// - /// Adds a merged region of cells (hence those cells form one). + /// Gets the flag indicating whether the window should show 0 (zero) + /// in cells Containing zero value. When false, cells with zero value + /// appear blank instead of Showing the number zero. /// - /// region (rowfrom/colfrom-rowto/colto) to merge - /// whether to validate merged region - /// index of this region - /// if region intersects with a multi-cell array formula or - /// if region intersects with an existing region on this sheet - /// if region contains fewer than 2 cells - private int AddMergedRegion(CellRangeAddress region, bool validate) + public bool DisplayZeros { - if (region.NumberOfCells < 2) + get { - throw new ArgumentException("Merged region " + region.FormatAsString() + " must contain 2 or more cells"); + CT_SheetView view = GetDefaultSheetView(); + return view == null || view.showZeros; } - region.Validate(SpreadsheetVersion.EXCEL2007); - if (validate) + set { - // throw InvalidOperationException if the argument CellRangeAddress intersects with - // a multi-cell array formula defined in this sheet - ValidateArrayFormulas(region); - // Throw InvalidOperationException if the argument CellRangeAddress intersects with - // a merged region already in this sheet - ValidateMergedRegions(region); + CT_SheetView view = GetSheetTypeSheetView(); + view.showZeros = value; } - - CT_MergeCells ctMergeCells = worksheet.IsSetMergeCells() ? worksheet.mergeCells : worksheet.AddNewMergeCells(); - CT_MergeCell ctMergeCell = ctMergeCells.AddNewMergeCell(); - ctMergeCell.@ref = (region.FormatAsString()); - return ctMergeCells.sizeOfMergeCellArray(); } - /** - * Verify that the candidate region does not intersect with an existing multi-cell array formula in this sheet - * - * @param region - * @throws InvalidOperationException if candidate region intersects an existing array formula in this sheet - */ - private void ValidateArrayFormulas(CellRangeAddress region) + + /// + /// Gets the first row on the sheet + /// + public int FirstRowNum { - // FIXME: this may be faster if it looped over array formulas directly rather than looping over each cell in - // the region and searching if that cell belongs to an array formula - int firstRow = region.FirstRow; - int firstColumn = region.FirstColumn; - int lastRow = region.LastRow; - int lastColumn = region.LastColumn; - // for each cell in sheet, if cell belongs to an array formula, check if merged region intersects array formula cells - for (int rowIn = firstRow; rowIn <= lastRow; rowIn++) + get { - IRow row = GetRow(rowIn); - if (row == null) continue; - for (int colIn = firstColumn; colIn <= lastColumn; colIn++) + if (_rows.Count == 0) { - ICell cell = row.GetCell(colIn); - if (cell == null) continue; - - if (cell.IsPartOfArrayFormulaGroup) + return 0; + } + else + { + foreach (int key in _rows.Keys) { - CellRangeAddress arrayRange = cell.ArrayFormulaRange; - if (arrayRange.NumberOfCells > 1 && region.Intersects(arrayRange)) - { - String msg = "The range " + region.FormatAsString() + " intersects with a multi-cell array formula. " + - "You cannot merge cells of an array."; - throw new InvalidOperationException(msg); - } + return key; } + + throw new ArgumentOutOfRangeException(); } } - } - /** - * Verify that none of the merged regions intersect a multi-cell array formula in this sheet - * - * @param region - * @throws InvalidOperationException if candidate region intersects an existing array formula in this sheet - */ - private void CheckForMergedRegionsIntersectingArrayFormulas() + /// + /// Flag indicating whether the Fit to Page print option is enabled. + /// + public bool FitToPage { - foreach (CellRangeAddress region in MergedRegions) + get { - ValidateArrayFormulas(region); + CT_SheetPr sheetPr = GetSheetTypeSheetPr(); + CT_PageSetUpPr psSetup = (sheetPr == null || !sheetPr.IsSetPageSetUpPr()) + ? new CT_PageSetUpPr() + : sheetPr.pageSetUpPr; + return psSetup.fitToPage; + } + set + { + GetSheetTypePageSetUpPr().fitToPage = value; } } - /** - * Verify that candidate region does not intersect with an existing merged region in this sheet - * - * @param candidateRegion - * @throws InvalidOperationException if candidate region intersects an existing merged region in this sheet - */ - private void ValidateMergedRegions(CellRangeAddress candidateRegion) + /// + /// Returns the default footer for the sheet, creating one as needed. + /// You may also want to look at , + /// and + /// + public IFooter Footer { - foreach (CellRangeAddress existingRegion in MergedRegions) + get { - if (existingRegion.Intersects(candidateRegion)) - { - throw new InvalidOperationException("Cannot add merged region " + candidateRegion.FormatAsString() + - " to sheet because it overlaps with an existing merged region (" + existingRegion.FormatAsString() + ")."); - } + // The default footer is an odd footer + return OddFooter; } } - /** - * Verify that no merged regions intersect another merged region in this sheet. - * - * @throws InvalidOperationException if at least one region intersects with another merged region in this sheet - */ - private void CheckForIntersectingMergedRegions() + /// + /// Returns the default header for the sheet, creating one as needed. + /// You may also want to look at , + /// and + /// + public IHeader Header { - List regions = MergedRegions; - int size = regions.Count; - for (int i = 0; i < size; i++) + get { - CellRangeAddress region = regions[i]; - foreach (CellRangeAddress other in regions.Skip(i+1)) //regions.subList(i+1, regions.size() - { - if (region.Intersects(other)) - { - String msg = "The range " + region.FormatAsString() + - " intersects with another merged region " + - other.FormatAsString() + " in this sheet"; - throw new InvalidOperationException(msg); - } - } + // The default header is an odd header + return OddHeader; } } - /** - * Verify that merged regions do not intersect multi-cell array formulas and - * no merged regions intersect another merged region in this sheet. - * - * @throws InvalidOperationException if region intersects with a multi-cell array formula - * @throws InvalidOperationException if at least one region intersects with another merged region in this sheet - */ - public void ValidateMergedRegions() + /// + /// Returns the odd footer. Used on all pages unless other footers + /// also present, when used on only odd pages. + /// + public IFooter OddFooter { - CheckForMergedRegionsIntersectingArrayFormulas(); - CheckForIntersectingMergedRegions(); + get + { + return new XSSFOddFooter(GetSheetTypeHeaderFooter()); + } } - /** - * Adjusts the column width to fit the contents. - * - * This process can be relatively slow on large sheets, so this should - * normally only be called once per column, at the end of your - * Processing. - * - * @param column the column index - */ - public void AutoSizeColumn(int column) + + /// + /// Returns the even footer. Not there by default, but when Set, + /// used on even pages. + /// + public IFooter EvenFooter { - AutoSizeColumn(column, false); + get + { + return new XSSFEvenFooter(GetSheetTypeHeaderFooter()); + } } - /** - * Adjusts the column width to fit the contents. - *

- * This process can be relatively slow on large sheets, so this should - * normally only be called once per column, at the end of your - * Processing. - *

- * You can specify whether the content of merged cells should be considered or ignored. - * Default is to ignore merged cells. - * - * @param column the column index - * @param useMergedCells whether to use the contents of merged cells when calculating the width of the column - */ - public void AutoSizeColumn(int column, bool useMergedCells) + /// + /// Returns the first page footer. Not there by default, but when + /// Set, used on the first page. + /// + public IFooter FirstFooter { - double width = SheetUtil.GetColumnWidth(this, column, useMergedCells); - - if (width != -1) + get { - width *= 256; - int maxColumnWidth = 255 * 256; // The maximum column width for an individual cell is 255 characters - if (width > maxColumnWidth) - { - width = maxColumnWidth; - } - SetColumnWidth(column, (int)(width)); - columnHelper.SetColBestFit(column, true); + return new XSSFFirstFooter(GetSheetTypeHeaderFooter()); } } - /** - * Adjusts the row height to fit the contents. - * - * This process can be relatively slow on large sheets, so this should - * normally only be called once per row, at the end of your - * Processing. - * - * @param row the row index - */ - public void AutoSizeRow(int row) + /// + /// Returns the odd header. Used on all pages unless other headers + /// also present, when used on only odd pages. + /// + public IHeader OddHeader { - AutoSizeRow(row, false); + get + { + return new XSSFOddHeader(GetSheetTypeHeaderFooter()); + } } - /** - * Adjusts the row height to fit the contents. - *

- * This process can be relatively slow on large sheets, so this should - * normally only be called once per row, at the end of your - * Processing. - *

- * You can specify whether the content of merged cells should be considered or ignored. - * Default is to ignore merged cells. - * - * @param row the row index - * @param useMergedCells whether to use the contents of merged cells when calculating the height of the row - */ - public void AutoSizeRow(int row, bool useMergedCells) + /// + /// Returns the even header. Not there by default, but when + /// Set, used on even pages. + /// + public IHeader EvenHeader { - var targetRow = GetRow(row) ?? CreateRow(row); - - double height = SheetUtil.GetRowHeight(this, row, useMergedCells); - - if (height != -1 && height != 0) + get { - height *= 20; - - int maxRowHeight = 409 * 20; // The maximum row height for an individual cell is 409 points - - if (height > maxRowHeight) - { - height = maxRowHeight; - } - - targetRow.Height = (short)height; + return new XSSFEvenHeader(GetSheetTypeHeaderFooter()); } } - XSSFDrawing drawing = null; - /** - * Return the sheet's existing Drawing, or null if there isn't yet one. - * - * Use {@link #CreateDrawingPatriarch()} to Get or create - * - * @return a SpreadsheetML Drawing - */ - public XSSFDrawing GetDrawingPatriarch() + /// + /// Returns the first page header. Not there by default, but when + /// Set, used on the first page. + /// + public IHeader FirstHeader { - CT_Drawing ctDrawing = GetCTDrawing(); - if (ctDrawing != null) + get { - // Search the referenced Drawing in the list of the sheet's relations - foreach (RelationPart rp in RelationParts) - { - POIXMLDocumentPart p = rp.DocumentPart; - if (p is XSSFDrawing) - { - XSSFDrawing dr = (XSSFDrawing)p; - String drId = rp.Relationship.Id; - if (drId.Equals(ctDrawing.id)) - { - return dr; - } - break; - } - } - logger.Log(POILogger.ERROR, "Can't find Drawing with id=" + ctDrawing.id + " in the list of the sheet's relationships"); + return new XSSFFirstHeader(GetSheetTypeHeaderFooter()); } - return null; } - /** - * Create a new SpreadsheetML Drawing. If this sheet already Contains a Drawing - return that. - * - * @return a SpreadsheetML Drawing - */ - - public IDrawing CreateDrawingPatriarch() + /// + /// Determine whether printed output for this sheet will be + /// horizontally centered. + /// + public bool HorizontallyCenter { - CT_Drawing ctDrawing = GetCTDrawing(); - if (ctDrawing != null) + get { - return GetDrawingPatriarch(); + CT_PrintOptions opts = worksheet.printOptions; + return opts != null && opts.horizontalCentered; } + set + { + CT_PrintOptions opts = worksheet.IsSetPrintOptions() ? + worksheet.printOptions : worksheet.AddNewPrintOptions(); + opts.horizontalCentered = value; - //drawingNumber = #drawings.Count + 1 - int DrawingNumber = GetPackagePart().Package.GetPartsByContentType(XSSFRelation.DRAWINGS.ContentType).Count + 1; - RelationPart rp = CreateRelationship(XSSFRelation.DRAWINGS, XSSFFactory.GetInstance(), DrawingNumber, false); - XSSFDrawing drawing = rp.DocumentPart as XSSFDrawing; - String relId = rp.Relationship.Id; - - //add CT_Drawing element which indicates that this sheet Contains Drawing components built on the DrawingML platform. - //The relationship Id references the part Containing the DrawingML defInitions. - ctDrawing = worksheet.AddNewDrawing(); - ctDrawing.id = (/*setter*/relId); - - // Return the newly Created Drawing - return drawing; + } } - - /** - * Get VML drawing for this sheet (aka 'legacy' drawig) - * - * @param autoCreate if true, then a new VML drawing part is Created - * - * @return the VML drawing of null if the drawing was not found and autoCreate=false - */ - internal XSSFVMLDrawing GetVMLDrawing(bool autoCreate) + public int LastRowNum { - XSSFVMLDrawing drawing = null; - NPOI.OpenXmlFormats.Spreadsheet.CT_LegacyDrawing ctDrawing = GetCTLegacyDrawing(); - if (ctDrawing == null) + get { - if (autoCreate) - { - //drawingNumber = #drawings.Count + 1 - int drawingNumber = GetPackagePart().Package.GetPartsByContentType(XSSFRelation.VML_DRAWINGS.ContentType).Count + 1; - RelationPart rp = CreateRelationship(XSSFRelation.VML_DRAWINGS, XSSFFactory.GetInstance(), drawingNumber, false); - drawing = rp.DocumentPart as XSSFVMLDrawing; - String relId = rp.Relationship.Id; - - //add CT_LegacyDrawing element which indicates that this sheet Contains drawing components built on the drawingML platform. - //The relationship Id references the part Containing the drawing defInitions. - ctDrawing = worksheet.AddNewLegacyDrawing(); - ctDrawing.id = relId; - } + return _rows.Count == 0 ? 0 : GetLastKey(_rows.Keys); } - else + } + + /// + /// Returns the list of merged regions. If you want multiple regions, + /// this is faster than calling {@link #getMergedRegion(int)} each time. + /// + public List MergedRegions + { + get { - //search the referenced drawing in the list of the sheet's relations - String id = ctDrawing.id; - foreach (RelationPart rp in RelationParts) + List addresses = new List(); + CT_MergeCells ctMergeCells = worksheet.mergeCells; + if (ctMergeCells == null) { - POIXMLDocumentPart p = rp.DocumentPart; - if (p is XSSFVMLDrawing) - { - XSSFVMLDrawing dr = (XSSFVMLDrawing)p; - String drId = rp.Relationship.Id; - if (drId.Equals(id)) - { - drawing = dr; - break; - } - // do not break here since drawing has not been found yet (see bug 52425) - } + return addresses; } - if (drawing == null) + + foreach (CT_MergeCell ctMergeCell in ctMergeCells.mergeCell) { - logger.Log(POILogger.ERROR, "Can't find VML drawing with id=" + id + " in the list of the sheet's relationships"); + string ref1 = ctMergeCell.@ref; + addresses.Add(CellRangeAddress.ValueOf(ref1)); } + + return addresses; } - return drawing; } - protected virtual CT_Drawing GetCTDrawing() - { - return worksheet.drawing; - } - protected virtual CT_LegacyDrawing GetCTLegacyDrawing() + /// + /// Returns the number of merged regions defined in this worksheet + /// + public int NumMergedRegions { - return worksheet.legacyDrawing; + get + { + CT_MergeCells ctMergeCells = worksheet.mergeCells; + return ctMergeCells != null + ? ctMergeCells.sizeOfMergeCellArray() + : 0; + } } - /** - * Creates a split (freezepane). Any existing freezepane or split pane is overwritten. - * @param colSplit Horizonatal position of split. - * @param rowSplit Vertical position of split. - */ - public void CreateFreezePane(int colSplit, int rowSplit) + public int NumHyperlinks { - CreateFreezePane(colSplit, rowSplit, colSplit, rowSplit); + get + { + return hyperlinks.Count; + } } - /** - * Creates a split (freezepane). Any existing freezepane or split pane is overwritten. - * - *

- * If both colSplit and rowSplit are zero then the existing freeze pane is Removed - *

- * - * @param colSplit Horizonatal position of split. - * @param rowSplit Vertical position of split. - * @param leftmostColumn Left column visible in right pane. - * @param topRow Top row visible in bottom pane - */ - public void CreateFreezePane(int colSplit, int rowSplit, int leftmostColumn, int topRow) + /// + /// Returns the information regarding the currently configured pane + /// (split or freeze). + /// + public PaneInformation PaneInformation { - CT_SheetView ctView = GetDefaultSheetView(); - - // If both colSplit and rowSplit are zero then the existing freeze pane is Removed - if (colSplit == 0 && rowSplit == 0) + get { + CT_Pane pane = GetDefaultSheetView().pane; + // no pane configured + if (pane == null) + { + return null; + } - if (ctView.IsSetPane()) ctView.UnsetPane(); - ctView.SetSelectionArray(null); - return; + CellReference cellRef = pane.IsSetTopLeftCell() ? + new CellReference(pane.topLeftCell) : null; + return new PaneInformation((short)pane.xSplit, + (short)pane.ySplit, + cellRef == null + ? (short)0 + : (short)cellRef.Row, + cellRef == null + ? (short)0 + : cellRef.Col, + (byte)pane.activePane, + pane.state == ST_PaneState.frozen); } + } - if (!ctView.IsSetPane()) - { - ctView.AddNewPane(); - } - CT_Pane pane = ctView.pane; - - if (colSplit > 0) - { - pane.xSplit = (colSplit); - } - else - { - - if (pane.IsSetXSplit()) pane.UnsetXSplit(); - } - if (rowSplit > 0) - { - pane.ySplit = (rowSplit); - } - else - { - if (pane.IsSetYSplit()) pane.UnsetYSplit(); - } - - pane.state = (ST_PaneState.frozen); - if (rowSplit == 0) - { - pane.topLeftCell = (new CellReference(0, leftmostColumn).FormatAsString()); - pane.activePane = (ST_Pane.topRight); - } - else if (colSplit == 0) - { - pane.topLeftCell = (new CellReference(topRow, 0).FormatAsString()); - pane.activePane = (ST_Pane.bottomLeft); - } - else - { - pane.topLeftCell = (new CellReference(topRow, leftmostColumn).FormatAsString()); - pane.activePane = (ST_Pane.bottomRight); - } - - ctView.selection = (null); - CT_Selection sel = ctView.AddNewSelection(); - sel.pane = (pane.activePane); - } - - int GetLastKey(IList keys) - { - int i = keys.Count; - return keys[keys.Count - 1]; - } - - int HeadMapCount(SortedList rows, int rownum) - { - int count = 0; - foreach (int key in rows.Keys) - { - if (key < rownum) - { - count++; - } - else - { - break; - } - } - return count; - } - - /** - * Create a new row within the sheet and return the high level representation - * - * @param rownum row number - * @return High level {@link XSSFRow} object representing a row in the sheet - * @see #RemoveRow(NPOI.SS.usermodel.Row) - */ - public virtual IRow CreateRow(int rownum) - { - CT_Row ctRow; - XSSFRow prev = _rows.ContainsKey(rownum) ? _rows[rownum] : null; - if (prev != null) - { - // the Cells in an existing row are invalidated on-purpose, in order to clean up correctly, we - // need to call the remove, so things like ArrayFormulas and CalculationChain updates are done - // correctly. - // We remove the cell this way as the internal cell-list is changed by the remove call and - // thus would cause ConcurrentModificationException otherwise - while (prev.FirstCellNum != -1) - { - prev.RemoveCell(prev.GetCell(prev.FirstCellNum)); - } - ctRow = prev.GetCTRow(); - ctRow.Set(new CT_Row()); - } - else - { - if (_rows.Count == 0 || rownum > GetLastKey(_rows.Keys)) - { - // we can append the new row at the end - ctRow = worksheet.sheetData.AddNewRow(); - } - else - { - // get number of rows where row index < rownum - // --> this tells us where our row should go - int idx = HeadMapCount(_rows, rownum); - ctRow = worksheet.sheetData.InsertNewRow(idx); - } - } - XSSFRow r = new XSSFRow(ctRow, this); - r.RowNum = rownum; - _rows[rownum] = r; - return r; - } - - /** - * Creates a split pane. Any existing freezepane or split pane is overwritten. - * @param xSplitPos Horizonatal position of split (in 1/20th of a point). - * @param ySplitPos Vertical position of split (in 1/20th of a point). - * @param topRow Top row visible in bottom pane - * @param leftmostColumn Left column visible in right pane. - * @param activePane Active pane. One of: PANE_LOWER_RIGHT, - * PANE_UPPER_RIGHT, PANE_LOWER_LEFT, PANE_UPPER_LEFT - * @see NPOI.SS.usermodel.Sheet#PANE_LOWER_LEFT - * @see NPOI.SS.usermodel.Sheet#PANE_LOWER_RIGHT - * @see NPOI.SS.usermodel.Sheet#PANE_UPPER_LEFT - * @see NPOI.SS.usermodel.Sheet#PANE_UPPER_RIGHT - */ - public void CreateSplitPane(int xSplitPos, int ySplitPos, int leftmostColumn, int topRow, PanePosition activePane) - { - CreateFreezePane(xSplitPos, ySplitPos, leftmostColumn, topRow); - GetPane().state = (ST_PaneState.split); - GetPane().activePane = (ST_Pane)(activePane); - } - /// - /// Returns cell comment for the specified row and column - /// - /// The row. - /// The column. - /// cell comment or null if not found - [Obsolete("deprecated as of 2015-11-23 (circa POI 3.14beta1). Use {@link #getCellComment(CellAddress)} instead.")] - public IComment GetCellComment(int row, int column) - { - return GetCellComment(new CellAddress(row, column)); - } /// - /// Returns cell comment for the specified location + /// Returns the number of phsyically defined rows (NOT the number of + /// rows in the sheet) /// - /// cell location - /// return cell comment or null if not found - public IComment GetCellComment(CellAddress address) + public int PhysicalNumberOfRows { - if (sheetComments == null) + get { - return null; + return _rows.Count; } - - int row = address.Row; - int column = address.Column; - - CellAddress ref1 = new CellAddress(row, column); - CT_Comment ctComment = sheetComments.GetCTComment(ref1); - if (ctComment == null) return null; - - XSSFVMLDrawing vml = GetVMLDrawing(false); - return new XSSFComment(sheetComments, ctComment, - vml == null ? null : vml.FindCommentShape(row, column)); } + /// - /// Returns all cell comments on this sheet. + /// Gets the print Setup object. /// - /// return A Dictionary of each Comment in the sheet, keyed on the cell address where the comment is located. - public Dictionary GetCellComments() + public IPrintSetup PrintSetup { - if (sheetComments == null) + get { - return new Dictionary(); + return new XSSFPrintSetup(worksheet); } - return sheetComments.GetCellComments(); - } - - /// - /// Get a Hyperlink in this sheet anchored at row, column - /// - /// - /// - /// return hyperlink if there is a hyperlink anchored at row, column; otherwise returns null - public IHyperlink GetHyperlink(int row, int column) - { - return GetHyperlink(new CellAddress(row, column)); } /// - /// Get a Hyperlink in this sheet located in a cell specified by {code addr} + /// Answer whether protection is enabled or disabled /// - /// The address of the cell containing the hyperlink - /// return hyperlink if there is a hyperlink anchored at {@code addr}; otherwise returns {@code null} - public IHyperlink GetHyperlink(CellAddress addr) + public bool Protect { - String ref1 = addr.FormatAsString(); - foreach (XSSFHyperlink hyperlink in hyperlinks) + get { - if (hyperlink.CellRef.Equals(ref1)) - { - return hyperlink; - } + return IsSheetLocked; } - return null; } /// - /// Get a list of Hyperlinks in this sheet + /// Horizontal page break information used for print layout view, page + /// layout view, drawing print breaks in normal view, and for printing + /// the worksheet. /// - /// - public List GetHyperlinkList() - { - return (hyperlinks.ToList()); - } - - /** - * Vertical page break information used for print layout view, page layout view, drawing print breaks - * in normal view, and for printing the worksheet. - * - * @return column indexes of all the vertical page breaks, never null - */ //YK: GetXYZArray() array accessors are deprecated in xmlbeans with JDK 1.5 support - public int[] ColumnBreaks + public int[] RowBreaks { get { - if (!worksheet.IsSetColBreaks() || worksheet.colBreaks.sizeOfBrkArray() == 0) + if (!worksheet.IsSetRowBreaks() || worksheet.rowBreaks.sizeOfBrkArray() == 0) { return new int[0]; } - List brkArray = worksheet.colBreaks.brk; - + List brkArray = worksheet.rowBreaks.brk; int[] breaks = new int[brkArray.Count]; for (int i = 0; i < brkArray.Count; i++) { CT_Break brk = brkArray[i]; breaks[i] = (int)brk.id - 1; } + return breaks; } } - /** - * Get the actual column width (in units of 1/256th of a character width ) - * - *

- * Note, the returned value is always gerater that {@link #GetDefaultColumnWidth()} because the latter does not include margins. - * Actual column width measured as the number of characters of the maximum digit width of the - * numbers 0, 1, 2, ..., 9 as rendered in the normal style's font. There are 4 pixels of margin - * pAdding (two on each side), plus 1 pixel pAdding for the gridlines. - *

- * - * @param columnIndex - the column to set (0-based) - * @return width - the width in units of 1/256th of a character width - */ - public int GetColumnWidth(int columnIndex) - { - CT_Col col = columnHelper.GetColumn(columnIndex, false); - double width = (col == null || !col.IsSetWidth()) ? this.DefaultColumnWidth : col.width; - return (int)(width * 256); - } - - /** - * Get the actual column width in pixels - * - *

- * Please note, that this method works correctly only for workbooks - * with the default font size (Calibri 11pt for .xlsx). - *

- */ - public float GetColumnWidthInPixels(int columnIndex) - { - float widthIn256 = GetColumnWidth(columnIndex); - return (float)(widthIn256 / 256.0 * XSSFWorkbook.DEFAULT_CHARACTER_WIDTH); - } - /** - * Get the default column width for the sheet (if the columns do not define their own width) in - * characters. - *

- * Note, this value is different from {@link #GetColumnWidth(int)}. The latter is always greater and includes - * 4 pixels of margin pAdding (two on each side), plus 1 pixel pAdding for the gridlines. - *

- * @return column width, default value is 8 - */ - public int DefaultColumnWidth + /// + /// Flag indicating whether summary rows appear below detail in an + /// outline, when Applying an outline. When true a summary row is + /// inserted below the detailed data being summarized and a new outline + /// level is established on that row. When false a summary row is + /// inserted above the detailed data being summarized and a new outline + /// level is established on that row. + /// + public bool RowSumsBelow { get { - CT_SheetFormatPr pr = worksheet.sheetFormatPr; - return pr == null ? 8 : (int)pr.baseColWidth; + CT_SheetPr sheetPr = worksheet.sheetPr; + CT_OutlinePr outlinePr = (sheetPr != null && sheetPr.IsSetOutlinePr()) + ? sheetPr.outlinePr : null; + return outlinePr == null || outlinePr.summaryBelow; } set { - GetSheetTypeSheetFormatPr().baseColWidth = (uint)value; + EnsureOutlinePr().summaryBelow = value; } } - /** - * Get the default row height for the sheet (if the rows do not define their own height) in - * twips (1/20 of a point) - * - * @return default row height - */ - public short DefaultRowHeight + /// + /// When true a summary column is inserted to the right of the detailed + /// data being summarized and a new outline level is established on + /// that column. When false a summary column is inserted to the left of + /// the detailed data being summarized and a new outline level is + /// established on that column. + /// + public bool RowSumsRight { get { - return (short)((decimal)DefaultRowHeightInPoints * TWIPS_PER_POINT); + CT_SheetPr sheetPr = worksheet.sheetPr; + CT_OutlinePr outlinePr = (sheetPr != null && sheetPr.IsSetOutlinePr()) + ? sheetPr.outlinePr : new CT_OutlinePr(); + return outlinePr.summaryRight; } set { - DefaultRowHeightInPoints = (float)value / TWIPS_PER_POINT; + EnsureOutlinePr().summaryRight = value; } } - /** - * Get the default row height for the sheet measued in point size (if the rows do not define their own height). - * - * @return default row height in points - */ - public float DefaultRowHeightInPoints + /// + /// A flag indicating whether scenarios are locked when the sheet + /// is protected. + /// + public bool ScenarioProtect { get { - CT_SheetFormatPr pr = worksheet.sheetFormatPr; - return (float)(pr == null ? 0 : pr.defaultRowHeight); - } - set - { - CT_SheetFormatPr pr = GetSheetTypeSheetFormatPr(); - pr.defaultRowHeight = (value); - pr.customHeight = (true); + return worksheet.IsSetSheetProtection() + && worksheet.sheetProtection.scenarios; } } - - private CT_SheetFormatPr GetSheetTypeSheetFormatPr() + public short LeftCol { - return worksheet.IsSetSheetFormatPr() ? - worksheet.sheetFormatPr : - worksheet.AddNewSheetFormatPr(); - } + get + { + string cellRef = GetPane().topLeftCell; + if (cellRef == null) + { + return 0; + } - /** - * Returns the CellStyle that applies to the given - * (0 based) column, or null if no style has been - * set for that column - */ - public ICellStyle GetColumnStyle(int column) - { - int idx = columnHelper.GetColDefaultStyle(column); - return Workbook.GetCellStyleAt(idx == -1 ? 0 : idx); + CellReference cellReference = new CellReference(cellRef); + return cellReference.Col; + } + set + { + throw new NotImplementedException(); + } } - - /** - * Whether the text is displayed in right-to-left mode in the window - * - * @return whether the text is displayed in right-to-left mode in the window - */ - public bool RightToLeft + /// + /// The top row in the visible view when the sheet is first viewed + /// after opening it in a viewer + /// + public short TopRow { get { - CT_SheetView view = GetDefaultSheetView(); - return view == null ? false : view.rightToLeft; + string cellRef = GetSheetTypeSheetView().topLeftCell; + if (cellRef == null) + { + return 0; + } + + CellReference cellReference = new CellReference(cellRef); + return (short)cellReference.Row; } set { - CT_SheetView view = GetDefaultSheetView(); - view.rightToLeft = (value); + throw new NotImplementedException(); } } - /** - * Get whether to display the guts or not, - * default value is true - * - * @return bool - guts or no guts - */ - public bool DisplayGuts + /// + /// Determine whether printed output for this sheet will be vertically + /// centered. + /// + public bool VerticallyCenter { get { - CT_SheetPr sheetPr = GetSheetTypeSheetPr(); - CT_OutlinePr outlinePr = sheetPr.outlinePr == null ? new CT_OutlinePr() : sheetPr.outlinePr; - return outlinePr.showOutlineSymbols; + CT_PrintOptions opts = worksheet.printOptions; + return opts != null && opts.verticalCentered; } set { - CT_SheetPr sheetPr = GetSheetTypeSheetPr(); - CT_OutlinePr outlinePr = sheetPr.outlinePr == null ? sheetPr.AddNewOutlinePr() : sheetPr.outlinePr; - outlinePr.showOutlineSymbols = (value); + CT_PrintOptions opts = worksheet.IsSetPrintOptions() ? + worksheet.printOptions : worksheet.AddNewPrintOptions(); + opts.verticalCentered = value; + } } - /** - * Gets the flag indicating whether the window should show 0 (zero) in cells Containing zero value. - * When false, cells with zero value appear blank instead of Showing the number zero. - * - * @return whether all zero values on the worksheet are displayed - */ - public bool DisplayZeros + /// + /// Gets the flag indicating whether this sheet should display formulas. + /// + public bool DisplayFormulas { get { - CT_SheetView view = GetDefaultSheetView(); - return view == null ? true : view.showZeros; + return GetSheetTypeSheetView().showFormulas; } set { - CT_SheetView view = GetSheetTypeSheetView(); - view.showZeros = (value); + GetSheetTypeSheetView().showFormulas = value; } } - /** - * Gets the first row on the sheet - * - * @return the number of the first logical row on the sheet, zero based - */ - public int FirstRowNum + /// + /// Gets the flag indicating whether this sheet displays the lines + /// between rows and columns to make editing and Reading easier. + /// + public bool DisplayGridlines { get { - if (_rows.Count == 0) - return 0; - else - { - foreach (int key in _rows.Keys) - { - return key; - } - throw new ArgumentOutOfRangeException(); - } + return GetSheetTypeSheetView().showGridLines; + } + set + { + GetSheetTypeSheetView().showGridLines = value; } } - /** - * Flag indicating whether the Fit to Page print option is enabled. - * - * @return true - */ - public bool FitToPage + /// + /// Gets the flag indicating whether this sheet should display row and + /// column headings. Row heading are the row numbers to the side of the + /// sheet Column heading are the letters or numbers that appear above + /// the columns of the sheet + /// + public bool DisplayRowColHeadings { get { - CT_SheetPr sheetPr = GetSheetTypeSheetPr(); - CT_PageSetUpPr psSetup = (sheetPr == null || !sheetPr.IsSetPageSetUpPr()) ? - new CT_PageSetUpPr() : sheetPr.pageSetUpPr; - return psSetup.fitToPage; + return GetSheetTypeSheetView().showRowColHeaders; } set { - GetSheetTypePageSetUpPr().fitToPage = value; + GetSheetTypeSheetView().showRowColHeaders = value; } } - private CT_SheetPr GetSheetTypeSheetPr() + /// + /// Returns whether gridlines are printed. + /// + public bool IsPrintGridlines { - if (worksheet.sheetPr == null) + get { - worksheet.sheetPr = new CT_SheetPr(); + CT_PrintOptions opts = worksheet.printOptions; + return opts != null && opts.gridLines; + } + set + { + CT_PrintOptions opts = worksheet.IsSetPrintOptions() + ? worksheet.printOptions + : worksheet.AddNewPrintOptions(); + opts.gridLines = value; } - return worksheet.sheetPr; } - private CT_HeaderFooter GetSheetTypeHeaderFooter() + /// + /// Returns whether row and column headings are printed. + /// + public bool IsPrintRowAndColumnHeadings { - if (worksheet.headerFooter == null) + get { - worksheet.headerFooter = new CT_HeaderFooter(); + CT_PrintOptions opts = worksheet.printOptions; + return opts != null && opts.headings; + } + set + { + CT_PrintOptions opts = worksheet.IsSetPrintOptions() + ? worksheet.printOptions + : worksheet.AddNewPrintOptions(); + opts.headings = value; } - return worksheet.headerFooter; } + /// + /// Whether Excel will be asked to recalculate all formulas when the + /// workbook is opened. + /// + public bool ForceFormulaRecalculation + { + get + { + if (worksheet.IsSetSheetCalcPr()) + { + CT_SheetCalcPr calc = worksheet.sheetCalcPr; + return calc.fullCalcOnLoad; + } + + return false; + } + set + { + CT_CalcPr calcPr = (Workbook as XSSFWorkbook).GetCTWorkbook().calcPr; + if (worksheet.IsSetSheetCalcPr()) + { + // Change the current Setting + CT_SheetCalcPr calc = worksheet.sheetCalcPr; + calc.fullCalcOnLoad = value; + } + else if (value) + { + // Add the Calc block and set it + CT_SheetCalcPr calc = worksheet.AddNewSheetCalcPr(); + calc.fullCalcOnLoad = value; + } + if (value && calcPr != null + && calcPr.calcMode == ST_CalcMode.manual) + { + calcPr.calcMode = ST_CalcMode.auto; + } + } + } - /** - * Returns the default footer for the sheet, - * creating one as needed. - * You may also want to look at - * {@link #GetFirstFooter()}, - * {@link #GetOddFooter()} and - * {@link #GetEvenFooter()} - */ - public IFooter Footer + /// + /// Flag indicating whether the sheet displays Automatic Page Breaks. + /// + public bool Autobreaks { get { - // The default footer is an odd footer - return OddFooter; + CT_SheetPr sheetPr = GetSheetTypeSheetPr(); + CT_PageSetUpPr psSetup = (sheetPr == null || !sheetPr.IsSetPageSetUpPr()) + ? new CT_PageSetUpPr() + : sheetPr.pageSetUpPr; + return psSetup.autoPageBreaks; + } + set + { + CT_SheetPr sheetPr = GetSheetTypeSheetPr(); + CT_PageSetUpPr psSetup = sheetPr.IsSetPageSetUpPr() + ? sheetPr.pageSetUpPr + : sheetPr.AddNewPageSetUpPr(); + psSetup.autoPageBreaks = value; } } - /** - * Returns the default header for the sheet, - * creating one as needed. - * You may also want to look at - * {@link #GetFirstHeader()}, - * {@link #GetOddHeader()} and - * {@link #GetEvenHeader()} - */ - public IHeader Header + /// + /// Returns a flag indicating whether this sheet is selected. + /// + /// When only 1 sheet is selected and active, this value should be in + /// synch with the activeTab value. In case of a conflict, the Start + /// Part Setting wins and Sets the active sheet tab. + /// + /// Note: multiple sheets can be selected, but only one sheet can be + /// active at one time. + /// + public bool IsSelected { get { - // The default header is an odd header - return OddHeader; + CT_SheetView view = GetDefaultSheetView(); + return view != null && view.tabSelected; + } + set + { + CT_SheetViews views = GetSheetTypeSheetViews(); + foreach (CT_SheetView view in views.sheetView) + { + view.tabSelected = value; + } } } - /** - * Returns the odd footer. Used on all pages unless - * other footers also present, when used on only - * odd pages. - */ - public IFooter OddFooter + /// + /// Return location of the active cell, e.g. A1. + /// + public CellAddress ActiveCell { get { - return new XSSFOddFooter(GetSheetTypeHeaderFooter()); + string address = GetSheetTypeSelection().activeCell; + if (address == null) + { + return null; + } + + return new CellAddress(address); + } + set + { + string ref1 = value.FormatAsString(); + CT_Selection ctsel = GetSheetTypeSelection(); + ctsel.activeCell = ref1; + ctsel.SetSqref(new string[] { ref1 }); } } - /** - * Returns the even footer. Not there by default, but - * when Set, used on even pages. - */ - public IFooter EvenFooter + + /// + /// Does this sheet have any comments on it? We need to know, so we can + /// decide about writing it to disk or not + /// + public bool HasComments { get { - return new XSSFEvenFooter(GetSheetTypeHeaderFooter()); + if (sheetComments == null) + { + return false; + } + + return sheetComments.GetNumberOfComments() > 0; } } - /** - * Returns the first page footer. Not there by - * default, but when Set, used on the first page. - */ - public IFooter FirstFooter + + internal int NumberOfComments { get { - return new XSSFFirstFooter(GetSheetTypeHeaderFooter()); + if (sheetComments == null) + { + return 0; + } + + return sheetComments.GetNumberOfComments(); } } - /** - * Returns the odd header. Used on all pages unless - * other headers also present, when used on only - * odd pages. - */ - public IHeader OddHeader + /// + /// true when Autofilters are locked and the sheet is protected. + /// + public bool IsAutoFilterLocked { get { - return new XSSFOddHeader(GetSheetTypeHeaderFooter()); + return IsSheetLocked && SafeGetProtectionField().autoFilter; } } - /** - * Returns the even header. Not there by default, but - * when Set, used on even pages. - */ - public IHeader EvenHeader + + /// + /// true when Deleting columns is locked and the sheet is protected. + /// + public bool IsDeleteColumnsLocked { get { - return new XSSFEvenHeader(GetSheetTypeHeaderFooter()); + return IsSheetLocked && SafeGetProtectionField().deleteColumns; } } - /** - * Returns the first page header. Not there by - * default, but when Set, used on the first page. - */ - public IHeader FirstHeader + + /// + /// true when Deleting rows is locked and the sheet is protected. + /// + public bool IsDeleteRowsLocked { get { - return new XSSFFirstHeader(GetSheetTypeHeaderFooter()); + return IsSheetLocked && SafeGetProtectionField().deleteRows; } } - - /** - * Determine whether printed output for this sheet will be horizontally centered. - */ - public bool HorizontallyCenter + /// + /// true when Formatting cells is locked and the sheet is protected. + /// + public bool IsFormatCellsLocked { get { - CT_PrintOptions opts = worksheet.printOptions; - return opts != null && opts.horizontalCentered; + return IsSheetLocked && SafeGetProtectionField().formatCells; } - set - { - CT_PrintOptions opts = worksheet.IsSetPrintOptions() ? - worksheet.printOptions : worksheet.AddNewPrintOptions(); - opts.horizontalCentered = (value); + } + /// + /// true when Formatting columns is locked and the sheet is protected. + /// + public bool IsFormatColumnsLocked + { + get + { + return IsSheetLocked && SafeGetProtectionField().formatColumns; } } - public int LastRowNum + /// + /// true when Formatting rows is locked and the sheet is protected. + /// + public bool IsFormatRowsLocked { get { - return _rows.Count == 0 ? 0 : GetLastKey(_rows.Keys); + return IsSheetLocked && SafeGetProtectionField().formatRows; } } - - /** - * Gets the size of the margin in inches. - * - * @param margin which margin to get - * @return the size of the margin - * @see Sheet#LeftMargin - * @see Sheet#RightMargin - * @see Sheet#TopMargin - * @see Sheet#BottomMargin - * @see Sheet#HeaderMargin - * @see Sheet#FooterMargin - */ - public double GetMargin(MarginType margin) + /// + /// true when Inserting columns is locked and the sheet is protected. + /// + public bool IsInsertColumnsLocked { - if (!worksheet.IsSetPageMargins()) return 0; - - CT_PageMargins pageMargins = worksheet.pageMargins; - switch (margin) + get { - case MarginType.LeftMargin: - return pageMargins.left; - case MarginType.RightMargin: - return pageMargins.right; - case MarginType.TopMargin: - return pageMargins.top; - case MarginType.BottomMargin: - return pageMargins.bottom; - case MarginType.HeaderMargin: - return pageMargins.header; - case MarginType.FooterMargin: - return pageMargins.footer; - default: - throw new ArgumentException("Unknown margin constant: " + margin); - } - } - - /** - * Sets the size of the margin in inches. - * - * @param margin which margin to get - * @param size the size of the margin - * @see Sheet#LeftMargin - * @see Sheet#RightMargin - * @see Sheet#TopMargin - * @see Sheet#BottomMargin - * @see Sheet#HeaderMargin - * @see Sheet#FooterMargin - */ - public void SetMargin(MarginType margin, double size) + return IsSheetLocked && SafeGetProtectionField().insertColumns; + } + } + + /// + /// true when Inserting hyperlinks is locked and the sheet is protected. + /// + public bool IsInsertHyperlinksLocked { - CT_PageMargins pageMargins = worksheet.IsSetPageMargins() ? - worksheet.pageMargins : worksheet.AddNewPageMargins(); - switch (margin) + get { - case MarginType.LeftMargin: - pageMargins.left = (size); - break; - case MarginType.RightMargin: - pageMargins.right = (size); - break; - case MarginType.TopMargin: - pageMargins.top = (size); - break; - case MarginType.BottomMargin: - pageMargins.bottom = (size); - break; - case MarginType.HeaderMargin: - pageMargins.header = (size); - break; - case MarginType.FooterMargin: - pageMargins.footer = (size); - break; - default: - throw new InvalidOperationException("Unknown margin constant: " + margin); + return IsSheetLocked && SafeGetProtectionField().insertHyperlinks; } } - /** - * @return the merged region at the specified index - * @throws InvalidOperationException if this worksheet does not contain merged regions - */ - public CellRangeAddress GetMergedRegion(int index) + /// + /// true when Inserting rows is locked and the sheet is protected. + /// + public bool IsInsertRowsLocked { - CT_MergeCells ctMergeCells = worksheet.mergeCells; - if (ctMergeCells == null) throw new InvalidOperationException("This worksheet does not contain merged regions"); + get + { + return IsSheetLocked && SafeGetProtectionField().insertRows; + } + } - CT_MergeCell ctMergeCell = ctMergeCells.GetMergeCellArray(index); - - if (ctMergeCell == null) { return null; } - - String ref1 = ctMergeCell.@ref; - return CellRangeAddress.ValueOf(ref1); - } - - public CellRangeAddress GetMergedRegion(CellRangeAddress mergedRegion) - { - if (worksheet.mergeCells == null || worksheet.mergeCells.mergeCell == null) - return null; - foreach (CT_MergeCell mc in worksheet.mergeCells.mergeCell) - { - if (mc!=null && !string.IsNullOrEmpty(mc.@ref)) - { - CellRangeAddress range = CellRangeAddress.ValueOf(mc.@ref); - if (range.FirstColumn <= mergedRegion.FirstColumn - && range.LastColumn >= mergedRegion.LastColumn - && range.FirstRow <= mergedRegion.FirstRow - && range.LastRow >= mergedRegion.LastRow) - { - return range; - } - } - } - return null; - } - - /** - * Returns the list of merged regions. If you want multiple regions, this is - * faster than calling {@link #getMergedRegion(int)} each time. - * - * @return the list of merged regions - * @throws InvalidOperationException if this worksheet does not contain merged regions - */ - - public List MergedRegions + /// + /// true when Pivot tables are locked and the sheet is protected. + /// + public bool IsPivotTablesLocked { get { - List addresses = new List(); - CT_MergeCells ctMergeCells = worksheet.mergeCells; - if (ctMergeCells == null) return addresses; - - foreach (CT_MergeCell ctMergeCell in ctMergeCells.mergeCell) - { - String ref1 = ctMergeCell.@ref; - addresses.Add(CellRangeAddress.ValueOf(ref1)); - } - return addresses; + return IsSheetLocked && SafeGetProtectionField().pivotTables; } - } - /** - * Returns the number of merged regions defined in this worksheet - * - * @return number of merged regions in this worksheet - */ - public int NumMergedRegions + /// + /// true when Sorting is locked and the sheet is protected. + /// + public bool IsSortLocked { get { - CT_MergeCells ctMergeCells = worksheet.mergeCells; - return ctMergeCells == null ? 0 : ctMergeCells.sizeOfMergeCellArray(); + return IsSheetLocked && SafeGetProtectionField().sort; } } - public int NumHyperlinks + /// + /// true when Objects are locked and the sheet is protected. + /// + public bool IsObjectsLocked { get { - return hyperlinks.Count; + return IsSheetLocked && SafeGetProtectionField().objects; } } - /** - * Returns the information regarding the currently configured pane (split or freeze). - * - * @return null if no pane configured, or the pane information. - */ - public PaneInformation PaneInformation + /// + /// true when Scenarios are locked and the sheet is protected. + /// + public bool IsScenariosLocked { get { - CT_Pane pane = GetDefaultSheetView().pane; - // no pane configured - if (pane == null) return null; - - CellReference cellRef = pane.IsSetTopLeftCell() ? - new CellReference(pane.topLeftCell) : null; - return new PaneInformation((short)pane.xSplit, (short)pane.ySplit, - (cellRef == null ? (short)0 : (short)cellRef.Row), (cellRef == null ? (short)0 : (short)cellRef.Col), - //in java the frist enum value is 1,but 0 in c# - (byte)(pane.activePane /*- 1*/), pane.state == ST_PaneState.frozen); + return IsSheetLocked && SafeGetProtectionField().scenarios; } } - /** - * Returns the number of phsyically defined rows (NOT the number of rows in the sheet) - * - * @return the number of phsyically defined rows - */ - public int PhysicalNumberOfRows + /// + /// true when Selection of locked cells is locked and the sheet is + /// protected. + /// + public bool IsSelectLockedCellsLocked { get { - return _rows.Count; + return IsSheetLocked && SafeGetProtectionField().selectLockedCells; } } - /** - * Gets the print Setup object. - * - * @return The user model for the print Setup object. - */ - public IPrintSetup PrintSetup + /// + /// true when Selection of unlocked cells is locked and the sheet is + /// protected. + /// + public bool IsSelectUnlockedCellsLocked { get { - return new XSSFPrintSetup(worksheet); + return IsSheetLocked && SafeGetProtectionField().selectUnlockedCells; } } - /** - * Answer whether protection is enabled or disabled - * - * @return true => protection enabled; false => protection disabled - */ - public bool Protect + /// + /// true when Sheet is Protected. + /// + public bool IsSheetLocked { get { - return IsSheetLocked; - } - } - - /** - * Enables sheet protection and Sets the password for the sheet. - * Also Sets some attributes on the {@link CT_SheetProtection} that correspond to - * the default values used by Excel - * - * @param password to set for protection. Pass null to remove protection - */ - public void ProtectSheet(String password) - { - - if (password != null) - { - CT_SheetProtection sheetProtection = worksheet.AddNewSheetProtection(); - SetSheetPassword(password, null); // defaults to xor password - sheetProtection.sheet = (true); - sheetProtection.scenarios = (true); - sheetProtection.objects = (true); - } - else - { - worksheet.UnsetSheetProtection(); - } - } - - /** - * Sets the sheet password. - * - * @param password if null, the password will be removed - * @param hashAlgo if null, the password will be set as XOR password (Excel 2010 and earlier) - * otherwise the given algorithm is used for calculating the hash password (Excel 2013) - */ - public void SetSheetPassword(String password, HashAlgorithm hashAlgo) - { - if (password == null && !IsSheetProtectionEnabled()) - { - return; + return worksheet.IsSetSheetProtection() && SafeGetProtectionField().sheet; } - XSSFPasswordHelper.SetPassword(SafeGetProtectionField(), password, hashAlgo, null); } - /** - * Validate the password against the stored hash, the hashing method will be determined - * by the existing password attributes - * @return true, if the hashes match (... though original password may differ ...) - */ - public bool ValidateSheetPassword(String password) + public ISheetConditionalFormatting SheetConditionalFormatting { - if (!IsSheetProtectionEnabled()) + get { - return (password == null); + return new XSSFSheetConditionalFormatting(this); } - return XSSFPasswordHelper.ValidatePassword(SafeGetProtectionField(), password, null); } - /** - * Returns the logical row ( 0-based). If you ask for a row that is not - * defined you get a null. This is to say row 4 represents the fifth row on a sheet. - * - * @param rownum row to get - * @return XSSFRow representing the rownumber or null if its not defined on the sheet - */ - public IRow GetRow(int rownum) - { - if (_rows.ContainsKey(rownum)) - return _rows[rownum]; - return null; - } - - - /** - * returns all rows between startRow and endRow, inclusive. - * Rows between startRow and endRow that haven't been created are not included - * in result unless createRowIfMissing is true - * - * @param startRow the first row number in this sheet to return - * @param endRow the last row number in this sheet to return - * @param createRowIfMissing - * @return - * @throws IllegalArgumentException if startRowNum and endRowNum are not in ascending order - */ - private List GetRows(int startRowNum, int endRowNum, bool createRowIfMissing) + /// + /// Get or set background color of the sheet tab. The value is null + /// if no sheet tab color is set. + /// + public XSSFColor TabColor { - if (startRowNum > endRowNum) - { - throw new ArgumentException("getRows: startRowNum must be less than or equal to endRowNum"); - } - List rows = new List(); - if (createRowIfMissing) + get { - for (int i = startRowNum; i <= endRowNum; i++) + CT_SheetPr pr = worksheet.sheetPr; + if (pr == null) { - XSSFRow row = GetRow(i) as XSSFRow; - if (row == null) - { - row = CreateRow(i) as XSSFRow; - } - rows.Add(row); + pr = worksheet.AddNewSheetPr(); } - } - else - { - //rows.addAll(_rows.subMap(startRowNum, endRowNum + 1).values()); - rows.AddRange(_rows.SkipWhile(x => x.Key < startRowNum) - .TakeWhile(x => x.Key < endRowNum + 1) - .Select(x => x.Value)); - } - return rows; - } - /** - * Horizontal page break information used for print layout view, page layout view, drawing print breaks in normal - * view, and for printing the worksheet. - * - * @return row indexes of all the horizontal page breaks, never null - */ - //YK: GetXYZArray() array accessors are deprecated in xmlbeans with JDK 1.5 support - public int[] RowBreaks - { - get - { - if (!worksheet.IsSetRowBreaks() || worksheet.rowBreaks.sizeOfBrkArray() == 0) + if (!pr.IsSetTabColor()) { - return new int[0]; + return null; } - List brkArray = worksheet.rowBreaks.brk; - int[] breaks = new int[brkArray.Count]; - for (int i = 0; i < brkArray.Count; i++) + return new XSSFColor(pr.tabColor); + } + set + { + CT_SheetPr pr = worksheet.sheetPr; + if (pr == null) { - CT_Break brk = brkArray[i]; - breaks[i] = (int)brk.id - 1; + pr = worksheet.AddNewSheetPr(); } - return breaks; + + pr.tabColor = value.GetCTColor(); } } - /** - * Flag indicating whether summary rows appear below detail in an outline, when Applying an outline. - * - *

- * When true a summary row is inserted below the detailed data being summarized and a - * new outline level is established on that row. - *

- *

- * When false a summary row is inserted above the detailed data being summarized and a new outline level - * is established on that row. - *

- * @return true if row summaries appear below detail in the outline - */ - public bool RowSumsBelow + public CellRangeAddress RepeatingRows { get { - CT_SheetPr sheetPr = worksheet.sheetPr; - CT_OutlinePr outlinePr = (sheetPr != null && sheetPr.IsSetOutlinePr()) - ? sheetPr.outlinePr : null; - return outlinePr == null || outlinePr.summaryBelow; + return GetRepeatingRowsOrColums(true); } set { - ensureOutlinePr().summaryBelow = (value); - } - } - /** - * Flag indicating whether summary columns appear to the right of detail in an outline, when Applying an outline. - * - *

- * When true a summary column is inserted to the right of the detailed data being summarized - * and a new outline level is established on that column. - *

- *

- * When false a summary column is inserted to the left of the detailed data being - * summarized and a new outline level is established on that column. - *

- * @return true if col summaries appear right of the detail in the outline - */ - public bool RowSumsRight + CellRangeAddress columnRangeRef = RepeatingColumns; + SetRepeatingRowsAndColumns(value, columnRangeRef); + } + } + + public CellRangeAddress RepeatingColumns { get { - CT_SheetPr sheetPr = worksheet.sheetPr; - CT_OutlinePr outlinePr = (sheetPr != null && sheetPr.IsSetOutlinePr()) - ? sheetPr.outlinePr : new CT_OutlinePr(); - return outlinePr.summaryRight; + return GetRepeatingRowsOrColums(false); } set { - ensureOutlinePr().summaryRight = (value); + CellRangeAddress rowRangeRef = RepeatingRows; + SetRepeatingRowsAndColumns(rowRangeRef, value); } } + #endregion - /** - * Ensure CT_Worksheet.CT_SheetPr.CT_OutlinePr - */ - private CT_OutlinePr ensureOutlinePr() + #region Constructors + /// + /// Creates new XSSFSheet - called by XSSFWorkbook to create a sheet + /// from scratch. See + /// + public XSSFSheet() + : base() { - CT_SheetPr sheetPr = worksheet.IsSetSheetPr() ? worksheet.sheetPr : worksheet.AddNewSheetPr(); - return sheetPr.IsSetOutlinePr() ? - sheetPr.outlinePr : sheetPr.AddNewOutlinePr(); + dataValidationHelper = new XSSFDataValidationHelper(this); + OnDocumentCreate(); } /// - /// A flag indicating whether scenarios are locked when the sheet is protected. + /// Creates an XSSFSheet representing the given namespace part and + /// relationship. Should only be called by XSSFWorkbook when Reading in + /// an exisiting file. /// - public bool ScenarioProtect + /// The namespace part that holds xml data + /// represenring this sheet. + protected internal XSSFSheet(PackagePart part) + : base(part) { - get - { - return worksheet.IsSetSheetProtection() - && (bool)worksheet.sheetProtection.scenarios; - } + dataValidationHelper = new XSSFDataValidationHelper(this); } - public short LeftCol + + [Obsolete("deprecated in POI 3.14, scheduled for removal in POI 3.16")] + internal XSSFSheet(PackagePart part, PackageRelationship rel) + : this(part) { - get - { - String cellRef = GetPane().topLeftCell; - if (cellRef == null) - return 0; - CellReference cellReference = new CellReference(cellRef); - return cellReference.Col; - } - set - { - throw new NotImplementedException(); - } } + #endregion + + #region Internal methods /// - /// The top row in the visible view when the sheet is first viewed after opening it in a viewer + /// Initialize worksheet data when Reading in an exisiting file. /// - public short TopRow + /// + internal override void OnDocumentRead() { - get + try { - String cellRef = GetSheetTypeSheetView().topLeftCell; - if (cellRef == null) - return 0; - CellReference cellReference = new CellReference(cellRef); - return (short)cellReference.Row; + Read(GetPackagePart().GetInputStream()); } - set + catch (IOException e) { - throw new NotImplementedException(); + throw new POIXMLException(e); } } - /** - * Determine whether printed output for this sheet will be vertically centered. - * - * @return whether printed output for this sheet will be vertically centered. - */ - public bool VerticallyCenter + internal virtual void Read(Stream is1) { - get + try { - CT_PrintOptions opts = worksheet.printOptions; - return opts != null && opts.verticalCentered; + XmlDocument doc = ConvertStreamToXml(is1); + worksheet = WorksheetDocument.Parse(doc, NamespaceManager).GetWorksheet(); } - set + catch (XmlException e) { - CT_PrintOptions opts = worksheet.IsSetPrintOptions() ? - worksheet.printOptions : worksheet.AddNewPrintOptions(); - opts.verticalCentered = (value); - + throw new POIXMLException(e); } - } - /** - * Group between (0 based) columns - */ - public void GroupColumn(int fromColumn, int toColumn) - { - GroupColumn1Based(fromColumn + 1, toColumn + 1); - } - private void GroupColumn1Based(int fromColumn, int toColumn) - { - CT_Cols ctCols = worksheet.GetColsArray(0); - CT_Col ctCol = new CT_Col(); + InitRows(worksheet); + columnHelper = new ColumnHelper(worksheet); - // copy attributes, as they might be removed by merging with the new column - // TODO: check if this fix is really necessary or if the sweeping algorithm - // in addCleanColIntoCols needs to be adapted ... - CT_Col fixCol_before = this.columnHelper.GetColumn1Based(toColumn, false); - if (fixCol_before != null) + // Look for bits we're interested in + foreach (RelationPart rp in RelationParts) { - fixCol_before = (CT_Col)fixCol_before.Copy(); - } + POIXMLDocumentPart p = rp.DocumentPart; + if (p is CommentsTable commentsTable) + { + sheetComments = commentsTable; + //break; + } - ctCol.min = (uint)fromColumn; - ctCol.max = (uint)toColumn; - this.columnHelper.AddCleanColIntoCols(ctCols, ctCol); + if (p is XSSFTable xssfTable) + { + tables[rp.Relationship.Id] = xssfTable; + } - CT_Col fixCol_after = this.columnHelper.GetColumn1Based(toColumn, false); - if (fixCol_before != null && fixCol_after != null) - { - this.columnHelper.SetColumnAttributes(fixCol_before, fixCol_after); + if (p is XSSFPivotTable pivotTable) + { + GetWorkbook().PivotTables.Add(pivotTable); + } } - for (int index = fromColumn; index <= toColumn; index++) - { - CT_Col col = columnHelper.GetColumn1Based(index, false); - //col must exist - short outlineLevel = col.outlineLevel; - col.outlineLevel = (byte)(outlineLevel + 1); - index = (int)col.max; - } - worksheet.SetColsArray(0, ctCols); - SetSheetFormatPrOutlineLevelCol(); + // Process external hyperlinks for the sheet, if there are any + InitHyperlinks(); } - /** - * Do not leave the width attribute undefined (see #52186). - */ - private void SetColWidthAttribute(CT_Cols ctCols) + + /// + /// Initialize worksheet data when creating a new sheet. + /// + internal override void OnDocumentCreate() { - foreach (CT_Col col in ctCols.GetColList()) + worksheet = NewSheet(); + InitRows(worksheet); + columnHelper = new ColumnHelper(worksheet); + hyperlinks = new List(); + } + + /// + /// Get VML drawing for this sheet (aka 'legacy' drawig) + /// + /// if true, then a new VML drawing part + /// is Created + /// the VML drawing of null if the drawing was not found and + /// autoCreate=false + internal XSSFVMLDrawing GetVMLDrawing(bool autoCreate) + { + XSSFVMLDrawing drawing = null; + OpenXmlFormats.Spreadsheet.CT_LegacyDrawing ctDrawing = GetCTLegacyDrawing(); + if (ctDrawing == null) { - if (!col.IsSetWidth()) + if (autoCreate) { - col.width = (DefaultColumnWidth); - col.customWidth = (false); + //drawingNumber = #drawings.Count + 1 + int drawingNumber = GetPackagePart() + .Package.GetPartsByContentType(XSSFRelation.VML_DRAWINGS.ContentType).Count + 1; + RelationPart rp = CreateRelationship( + XSSFRelation.VML_DRAWINGS, + XSSFFactory.GetInstance(), + drawingNumber, + false); + drawing = rp.DocumentPart as XSSFVMLDrawing; + string relId = rp.Relationship.Id; + + // add CT_LegacyDrawing element which indicates that this + // sheet Contains drawing components built on the drawingML + // platform. The relationship Id references the part + // Containing the drawing defInitions. + ctDrawing = worksheet.AddNewLegacyDrawing(); + ctDrawing.id = relId; } } - } - /** - * Tie a range of cell toGether so that they can be collapsed or expanded - * - * @param fromRow start row (0-based) - * @param toRow end row (0-based) - */ - public void GroupRow(int fromRow, int toRow) - { - for (int i = fromRow; i <= toRow; i++) + else { - XSSFRow xrow = (XSSFRow)GetRow(i); - if (xrow == null) + //search the referenced drawing in the list of the sheet's relations + string id = ctDrawing.id; + foreach (RelationPart rp in RelationParts) { - xrow = (XSSFRow)CreateRow(i); + POIXMLDocumentPart p = rp.DocumentPart; + if (p is XSSFVMLDrawing dr) + { + string drId = rp.Relationship.Id; + if (drId.Equals(id)) + { + drawing = dr; + break; + } + // do not break here since drawing has not been found yet (see bug 52425) + } } - CT_Row ctrow = xrow.GetCTRow(); - short outlineLevel = ctrow.outlineLevel; - ctrow.outlineLevel = ((byte)(outlineLevel + 1)); - } - SetSheetFormatPrOutlineLevelRow(); - } - private short GetMaxOutlineLevelRows() - { - short outlineLevel = 0; - foreach (XSSFRow xrow in _rows.Values) - { - outlineLevel = xrow.GetCTRow().outlineLevel > outlineLevel ? xrow.GetCTRow().outlineLevel : outlineLevel; + if (drawing == null) + { + logger.Log(POILogger.ERROR, "Can't find VML drawing with " + + "id=" + id + " in the list of the sheet's relationships"); + } } - return outlineLevel; - } - - //YK: GetXYZArray() array accessors are deprecated in xmlbeans with JDK 1.5 support - [Obsolete] - private short GetMaxOutlineLevelCols() - { - CT_Cols ctCols = worksheet.GetColsArray(0); - short outlineLevel = 0; - foreach (CT_Col col in ctCols.GetColList()) - { - outlineLevel = col.outlineLevel > outlineLevel ? col.outlineLevel : outlineLevel; - } - return outlineLevel; + return drawing; } - /** - * Determines if there is a page break at the indicated column - */ - public bool IsColumnBroken(int column) + /// + /// Returns the sheet's comments object if there is one, or null if not + /// + /// create a new comments table if it does not + /// exist + /// + protected internal CommentsTable GetCommentsTable(bool create) { - int[] colBreaks = ColumnBreaks; - for (int i = 0; i < colBreaks.Length; i++) + if (sheetComments == null && create) { - if (colBreaks[i] == column) + // Try to create a comments table with the same number as the + // sheet has (i.e. sheet 1 -> comments 1) + try { - return true; + sheetComments = (CommentsTable)CreateRelationship( + XSSFRelation.SHEET_COMMENTS, XSSFFactory.GetInstance(), (int)sheet.sheetId); + } + catch (PartAlreadyExistsException) + { + // Technically a sheet doesn't need the same number as it's + // comments, and clearly someone has already pinched our + // number! Go for the next available one instead + sheetComments = (CommentsTable)CreateRelationship( + XSSFRelation.SHEET_COMMENTS, XSSFFactory.GetInstance(), -1); } } - return false; + + return sheetComments; } - /** - * Get the hidden state for a given column. - * - * @param columnIndex - the column to set (0-based) - * @return hidden - false if the column is visible - */ - public bool IsColumnHidden(int columnIndex) + /// + /// Return a master shared formula by index + /// + /// shared group index + /// a CT_CellFormula bean holding shared formula or + /// null if not found + internal CT_CellFormula GetSharedFormula(int sid) { - CT_Col col = columnHelper.GetColumn(columnIndex, false); - return col != null && (bool)col.hidden; + return sharedFormulas[sid]; } - /** - * Gets the flag indicating whether this sheet should display formulas. - * - * @return true if this sheet should display formulas. - */ - public bool DisplayFormulas + internal void OnReadCell(XSSFCell cell) { - get + //collect cells holding shared formulas + CT_Cell ct = cell.GetCTCell(); + CT_CellFormula f = ct.f; + if (f != null && f.t == ST_CellFormulaType.shared + && f.isSetRef() && f.Value != null) { - return GetSheetTypeSheetView().showFormulas; + // save a detached copy to avoid XmlValueDisconnectedException, + // this may happen when the master cell of a shared formula + // is Changed + CT_CellFormula sf = f.Copy(); + CellRangeAddress sfRef = CellRangeAddress.ValueOf(sf.@ref); + CellReference cellRef = new CellReference(cell); + // If the shared formula range preceeds the master cell then + // the preceding part is discarded, e.g. if the cell is E60 + // and the shared formula range is C60:M85 then the effective + // range is E60:M85 see more details in + // https://issues.apache.org/bugzilla/show_bug.cgi?id=51710 + if (cellRef.Col > sfRef.FirstColumn || cellRef.Row > sfRef.FirstRow) + { + string effectiveRef = new CellRangeAddress( + Math.Max(cellRef.Row, sfRef.FirstRow), sfRef.LastRow, + Math.Max(cellRef.Col, sfRef.FirstColumn), sfRef.LastColumn) + .FormatAsString(); + sf.@ref = effectiveRef; + } + + sharedFormulas[(int)f.si] = sf; } - set + + if (f != null && f.t == ST_CellFormulaType.array && f.@ref != null) { - GetSheetTypeSheetView().showFormulas = value; + arrayFormulas.Add(CellRangeAddress.ValueOf(f.@ref)); } } - /** - * Gets the flag indicating whether this sheet displays the lines - * between rows and columns to make editing and Reading easier. - * - * @return true if this sheet displays gridlines. - * @see #isPrintGridlines() to check if printing of gridlines is turned on or off - */ - public bool DisplayGridlines + protected internal override void Commit() { - get - { - return GetSheetTypeSheetView().showGridLines; - } - set - { - GetSheetTypeSheetView().showGridLines = value; - } + PackagePart part = GetPackagePart(); + Stream out1 = part.GetOutputStream(); + Write(out1); + out1.Close(); } + protected virtual OpenXmlFormats.Spreadsheet.CT_Drawing GetCTDrawing() + { + return worksheet.drawing; + } + protected virtual OpenXmlFormats.Spreadsheet.CT_LegacyDrawing GetCTLegacyDrawing() + { + return worksheet.legacyDrawing; + } - /** - * Gets the flag indicating whether this sheet should display row and column headings. - *

- * Row heading are the row numbers to the side of the sheet - *

- *

- * Column heading are the letters or numbers that appear above the columns of the sheet - *

- * - * @return true if this sheet should display row and column headings. - */ - public bool DisplayRowColHeadings + internal virtual void Write(Stream stream, bool leaveOpen = false) { - get + bool setToNull = false; + if (worksheet.sizeOfColsArray() == 1) { - return GetSheetTypeSheetView().showRowColHeaders; + CT_Cols col = worksheet.GetColsArray(0); + if (col.sizeOfColArray() == 0) + { + setToNull = true; + // this is necessary so that we do not write an empty + // item into the sheet-xml in the xlsx-file Excel + // complains about a corrupted file if this shows up there! + worksheet.SetColsArray(null); + } + else + { + SetColWidthAttribute(col); + } } - set + + // Now re-generate our CT_Hyperlinks, if needed + if (hyperlinks.Count > 0) { - GetSheetTypeSheetView().showRowColHeaders = value; - } - } + if (worksheet.hyperlinks == null) + { + worksheet.AddNewHyperlinks(); + } + CT_Hyperlink[] ctHls + = new CT_Hyperlink[hyperlinks.Count]; + for (int i = 0; i < ctHls.Length; i++) + { + // If our sheet has hyperlinks, have them add + // any relationships that they might need + XSSFHyperlink hyperlink = hyperlinks[i]; + hyperlink.GenerateRelationIfNeeded(GetPackagePart()); + // Now grab their underling object + ctHls[i] = hyperlink.GetCTHyperlink(); + } - /** - * Returns whether gridlines are printed. - * - * @return whether gridlines are printed - */ - public bool IsPrintGridlines - { - get - { - CT_PrintOptions opts = worksheet.printOptions; - return opts != null && opts.gridLines; + worksheet.hyperlinks.SetHyperlinkArray(ctHls); } - set + + foreach (XSSFRow row in _rows.Values) { - CT_PrintOptions opts = worksheet.IsSetPrintOptions() ? - worksheet.printOptions : worksheet.AddNewPrintOptions(); - opts.gridLines = (value); + row.OnDocumentWrite(); } - } + new WorksheetDocument(worksheet).Save(stream, leaveOpen); - /** - * Returns whether row and column headings are printed. - * - * @return whether row and column headings are printed - */ - public bool IsPrintRowAndColumnHeadings - { - get - { - CT_PrintOptions opts = worksheet.printOptions; - return opts != null && opts.headings; - } - set + // Bug 52233: Ensure that we have a col-array even if write() removed it + if (setToNull) { - CT_PrintOptions opts = worksheet.IsSetPrintOptions() ? - worksheet.printOptions : worksheet.AddNewPrintOptions(); - opts.headings = value; + worksheet.AddNewCols(); } } - /** - * Tests if there is a page break at the indicated row - * - * @param row index of the row to test - * @return true if there is a page break at the indicated row - */ - public bool IsRowBroken(int row) + internal bool IsCellInArrayFormulaContext(ICell cell) { - int[] rowBreaks = RowBreaks; - for (int i = 0; i < rowBreaks.Length; i++) + foreach (CellRangeAddress range in arrayFormulas) { - if (rowBreaks[i] == row) + if (range.IsInRange(cell.RowIndex, cell.ColumnIndex)) { return true; } } + return false; } - /** - * Sets a page break at the indicated row - * Breaks occur above the specified row and left of the specified column inclusive. - * - * For example, sheet.SetColumnBreak(2); breaks the sheet into two parts - * with columns A,B,C in the first and D,E,... in the second. Simuilar, sheet.SetRowBreak(2); - * breaks the sheet into two parts with first three rows (rownum=1...3) in the first part - * and rows starting with rownum=4 in the second. - * - * @param row the row to break, inclusive - */ - public void SetRowBreak(int row) + internal XSSFCell GetFirstCellInArrayFormula(ICell cell) { - - CT_PageBreak pgBreak = worksheet.IsSetRowBreaks() ? worksheet.rowBreaks : worksheet.AddNewRowBreaks(); - if (!IsRowBroken(row)) + foreach (CellRangeAddress range in arrayFormulas) { - CT_Break brk = pgBreak.AddNewBrk(); - brk.id = (uint)row + 1; // this is id of the row element which is 1-based: - brk.man = (true); - brk.max = (uint)(SpreadsheetVersion.EXCEL2007.LastColumnIndex); //end column of the break - - pgBreak.count = (uint)pgBreak.sizeOfBrkArray(); - pgBreak.manualBreakCount = (uint)pgBreak.sizeOfBrkArray(); + if (range.IsInRange(cell.RowIndex, cell.ColumnIndex)) + { + return (XSSFCell)GetRow(range.FirstRow) + .GetCell(range.FirstColumn); + } } + + return null; } - /** - * Removes a page break at the indicated column - */ - //YK: GetXYZArray() array accessors are deprecated in xmlbeans with JDK 1.5 support - public void RemoveColumnBreak(int column) + /// + /// when a cell with a 'master' shared formula is removed, the next + /// cell in the range becomes the master + /// + /// The cell that is removed + /// in use, if one exists + internal void OnDeleteFormula(XSSFCell cell, XSSFEvaluationWorkbook evalWb) { - if (!worksheet.IsSetColBreaks()) - { - // no breaks - return; - } - CT_PageBreak pgBreak = worksheet.colBreaks; - List brkArray = pgBreak.brk; - for (int i = 0; i < brkArray.Count; i++) + CT_CellFormula f = cell.GetCTCell().f; + if (f != null + && f.t == ST_CellFormulaType.shared + && f.isSetRef() + && f.Value != null) { - if (brkArray[i].id == (column + 1)) + bool breakit = false; + CellRangeAddress ref1 = CellRangeAddress.ValueOf(f.@ref); + if (ref1.NumberOfCells > 1) { - pgBreak.RemoveBrk(i); + for (int i = cell.RowIndex; i <= ref1.LastRow; i++) + { + XSSFRow row = (XSSFRow)GetRow(i); + if (row != null) + { + for (int j = cell.ColumnIndex; j <= ref1.LastColumn; j++) + { + XSSFCell nextCell = (XSSFCell)row.GetCell(j); + if (nextCell != null + && nextCell != cell + && nextCell.CellType == CellType.Formula) + { + CT_CellFormula nextF = nextCell.GetCTCell().f; + if (nextF.t == ST_CellFormulaType.shared + && nextF.si == f.si) + { + nextF.Value = nextCell.GetCellFormula(evalWb); + CellRangeAddress nextRef = new CellRangeAddress( + nextCell.RowIndex, ref1.LastRow, + nextCell.ColumnIndex, ref1.LastColumn); + nextF.@ref = nextRef.FormatAsString(); + + sharedFormulas[(int)nextF.si] = nextF; + breakit = true; + break; + } + } + } + + if (breakit) + { + break; + } + } + } } } } + #endregion - /** - * Removes a merged region of cells (hence letting them free) - * - * @param index of the region to unmerge - */ - public void RemoveMergedRegion(int index) + #region Public methods + /// + /// Provide access to the CT_Worksheet bean holding this sheet's data + /// + /// the CT_Worksheet bean holding this sheet's data + public CT_Worksheet GetCTWorksheet() { - CT_MergeCells ctMergeCells = worksheet.mergeCells; + return worksheet; + } - int size = ctMergeCells.sizeOfMergeCellArray(); - CT_MergeCell[] mergeCellsArray = new CT_MergeCell[size - 1]; - for (int i = 0; i < size; i++) + public ColumnHelper GetColumnHelper() + { + return columnHelper; + } + + /// + /// Adds a merged region of cells on a sheet. + /// + /// region to merge + /// index of this region + /// if region contains fewer + /// than 2 cells + /// if region intersects + /// with an existing merged region or multi-cell array formula on + /// this sheet + public int AddMergedRegion(CellRangeAddress region) + { + return AddMergedRegion(region, true); + } + + /// + /// Adds a merged region of cells (hence those cells form one). + /// Skips validation.It is possible to create overlapping merged regions + /// or create a merged region that intersects a multi-cell array formula + /// with this formula, which may result in a corrupt workbook. + /// + /// region to merge + /// index of this region + /// if region contains fewer + /// than 2 cells + public int AddMergedRegionUnsafe(CellRangeAddress region) + { + return AddMergedRegion(region, false); + } + + /// + /// Verify that merged regions do not intersect multi-cell array + /// formulas and no merged regions intersect another merged region + /// in this sheet. + /// + public void ValidateMergedRegions() + { + CheckForMergedRegionsIntersectingArrayFormulas(); + CheckForIntersectingMergedRegions(); + } + + /// + /// Adjusts the column width to fit the contents. + /// This process can be relatively slow on large sheets, so this should + /// normally only be called once per column, at the end of your + /// Processing. + /// + /// the column index + public void AutoSizeColumn(int column) + { + AutoSizeColumn(column, false); + } + + /// + /// Adjusts the column width to fit the contents. + /// This process can be relatively slow on large sheets, so this should + /// normally only be called once per column, at the end of your + /// Processing. + /// + /// the column index + /// whether to use the contents of merged cells + /// when calculating the width of the column + public void AutoSizeColumn(int column, bool useMergedCells) + { + double width = SheetUtil.GetColumnWidth(this, column, useMergedCells); + + if (width != -1) { - if (i < index) - { - mergeCellsArray[i] = ctMergeCells.GetMergeCellArray(i); - } - else if (i > index) + width *= 256; + // The maximum column width for an individual cell is 255 characters + int maxColumnWidth = 255 * 256; + if (width > maxColumnWidth) { - mergeCellsArray[i - 1] = ctMergeCells.GetMergeCellArray(i); + width = maxColumnWidth; } - } - if (mergeCellsArray.Length > 0) - { - ctMergeCells.SetMergeCellArray(mergeCellsArray); - } - else - { - worksheet.UnsetMergeCells(); + + SetColumnWidth(column, (int)width); + columnHelper.SetColBestFit(column, true); } } - /** - * Removes a number of merged regions of cells (hence letting them free) - * - * This method can be used to bulk-remove merged regions in a way - * much faster than calling RemoveMergedRegion() for every single - * merged region. - * - * @param indices A Set of the regions to unmerge - */ - public void RemoveMergedRegions(IList indices) + /// + /// Adjusts the row height to fit the contents. + /// This process can be relatively slow on large sheets, so this should + /// normally only be called once per row, at the end of your + /// Processing. + /// + /// the row index + public void AutoSizeRow(int row) + { + AutoSizeRow(row, false); + } + + /// + /// Adjusts the row height to fit the contents. This process can be + /// relatively slow on large sheets, so this should normally only be + /// called once per row, at the end of your Processing. You can specify + /// whether the content of merged cells should be considered or + /// ignored. Default is to ignore merged cells. + /// + /// the row index + /// whether to use the contents of merged + /// cells when calculating the height of the row + public void AutoSizeRow(int row, bool useMergedCells) { - if (!worksheet.IsSetMergeCells()) return; + IRow targetRow = GetRow(row) ?? CreateRow(row); - CT_MergeCells ctMergeCells = worksheet.mergeCells; - //TODO: The following codes are not same as poi, re-do it?. - int size = ctMergeCells.sizeOfMergeCellArray(); - List newMergeCells = new List(ctMergeCells.sizeOfMergeCellArray()); + double height = SheetUtil.GetRowHeight(this, row, useMergedCells); - for (int i = 0, d = 0; i < size; i++) + if (height != -1 && height != 0) { - if (!indices.Contains(i)) + height *= 20; + // The maximum row height for an individual cell is 409 points + int maxRowHeight = 409 * 20; + + if (height > maxRowHeight) { - //newMergeCells[d] = ctMergeCells.GetMergeCellArray(i); - newMergeCells.Add(ctMergeCells.GetMergeCellArray(i)); - d++; + height = maxRowHeight; } - } - if (ListIsEmpty(newMergeCells)) - { - worksheet.UnsetMergeCells(); - } - else - { - ctMergeCells.SetMergeCellArray(newMergeCells.ToArray()); + + targetRow.Height = (short)height; } } - private bool ListIsEmpty(List list) + + /// + /// Return the sheet's existing Drawing, or null if there isn't yet one. + /// Use to Get or create + /// + /// a SpreadsheetML Drawing + public XSSFDrawing GetDrawingPatriarch() { - foreach (CT_MergeCell mc in list) + OpenXmlFormats.Spreadsheet.CT_Drawing ctDrawing = GetCTDrawing(); + if (ctDrawing != null) { - if (mc != null) - return false; + // Search the referenced Drawing in the list of the sheet's relations + foreach (RelationPart rp in RelationParts) + { + POIXMLDocumentPart p = rp.DocumentPart; + if (p is XSSFDrawing dr) + { + string drId = rp.Relationship.Id; + if (drId.Equals(ctDrawing.id)) + { + return dr; + } + + break; + } + } + + logger.Log(POILogger.ERROR, "Can't find Drawing with id=" + + ctDrawing.id + " in the list of the sheet's relationships"); } - return true; + + return null; } - /** - * Remove a row from this sheet. All cells Contained in the row are Removed as well - * - * @param row the row to Remove. - */ - public void RemoveRow(IRow row) + /// + /// Create a new SpreadsheetML Drawing. If this sheet already + /// Contains a Drawing - return that. + /// + /// a SpreadsheetML Drawing + public IDrawing CreateDrawingPatriarch() { - if (row.Sheet != this) + OpenXmlFormats.Spreadsheet.CT_Drawing ctDrawing = GetCTDrawing(); + if (ctDrawing != null) { - throw new ArgumentException("Specified row does not belong to this sheet"); + return GetDrawingPatriarch(); } - // collect cells into a temporary array to avoid ConcurrentModificationException - List cellsToDelete = new List(); - foreach (ICell cell in row) cellsToDelete.Add((XSSFCell)cell); - foreach (XSSFCell cell in cellsToDelete) row.RemoveCell(cell); + //drawingNumber = #drawings.Count + 1 + int DrawingNumber = GetPackagePart() + .Package.GetPartsByContentType(XSSFRelation.DRAWINGS.ContentType).Count + 1; + RelationPart rp = CreateRelationship( + XSSFRelation.DRAWINGS, + XSSFFactory.GetInstance(), + DrawingNumber, + false); + XSSFDrawing drawing = rp.DocumentPart as XSSFDrawing; + string relId = rp.Relationship.Id; + // add CT_Drawing element which indicates that this sheet Contains + // Drawing components built on the DrawingML platform. The + // relationship Id references the part Containing the + // DrawingML defInitions. + ctDrawing = worksheet.AddNewDrawing(); + ctDrawing.id = /*setter*/relId; - int idx = _rows.Count(p => p.Key < row.RowNum);// _rows.headMap(row.getRowNum()).size(); - _rows.Remove(row.RowNum); - worksheet.sheetData.RemoveRow(row.RowNum+1); // Note that rows in worksheet.sheetData is 1-based. + // Return the newly Created Drawing + return drawing; + } - // also remove any comment located in that row - if (sheetComments != null) + /// + /// Creates a split (freezepane). Any existing freezepane or split + /// pane is overwritten. + /// + /// Horizonatal position of split. + /// Vertical position of split. + public void CreateFreezePane(int colSplit, int rowSplit) + { + CreateFreezePane(colSplit, rowSplit, colSplit, rowSplit); + } + + /// + /// Creates a split (freezepane). Any existing freezepane or split pane + /// is overwritten. If both colSplit and rowSplit are zero then the + /// existing freeze pane is Removed + /// + /// Horizonatal position of split. + /// Vertical position of split. + /// Left column visible in right pane. + /// Top row visible in bottom pane + public void CreateFreezePane(int colSplit, int rowSplit, int leftmostColumn, int topRow) + { + CT_SheetView ctView = GetDefaultSheetView(); + + // If both colSplit and rowSplit are zero then the existing freeze pane is Removed + if (colSplit == 0 && rowSplit == 0) { - foreach (CellAddress ref1 in GetCellComments().Keys) + + if (ctView.IsSetPane()) { - if (ref1.Row == idx) - { - sheetComments.RemoveComment(ref1); - } + ctView.UnsetPane(); } + + ctView.SetSelectionArray(null); + return; } - } - /** - * Removes the page break at the indicated row - */ - //YK: GetXYZArray() array accessors are deprecated in xmlbeans with JDK 1.5 support - public void RemoveRowBreak(int row) - { - if (!worksheet.IsSetRowBreaks()) + if (!ctView.IsSetPane()) { - return; + ctView.AddNewPane(); } - CT_PageBreak pgBreak = worksheet.rowBreaks; - List brkArray = pgBreak.brk; - for (int i = 0; i < brkArray.Count; i++) + + CT_Pane pane = ctView.pane; + + if (colSplit > 0) { - if (brkArray[i].id == (row + 1)) + pane.xSplit = colSplit; + } + else + { + + if (pane.IsSetXSplit()) { - pgBreak.RemoveBrk(i); + pane.UnsetXSplit(); } } - } - /** - * Whether Excel will be asked to recalculate all formulas when the - * workbook is opened. - */ - public bool ForceFormulaRecalculation - { - get + if (rowSplit > 0) { - if (worksheet.IsSetSheetCalcPr()) + pane.ySplit = rowSplit; + } + else + { + if (pane.IsSetYSplit()) { - CT_SheetCalcPr calc = worksheet.sheetCalcPr; - return calc.fullCalcOnLoad; - } - return false; - } - set - { - CT_CalcPr calcPr = (Workbook as XSSFWorkbook).GetCTWorkbook().calcPr; - if (worksheet.IsSetSheetCalcPr()) - { - // Change the current Setting - CT_SheetCalcPr calc = worksheet.sheetCalcPr; - calc.fullCalcOnLoad = value; - } - else if (value) - { - // Add the Calc block and set it - CT_SheetCalcPr calc = worksheet.AddNewSheetCalcPr(); - calc.fullCalcOnLoad = value; - } - - if (value && calcPr != null && calcPr.calcMode == ST_CalcMode.manual) - { - calcPr.calcMode = ST_CalcMode.auto; + pane.UnsetYSplit(); } } - } - /** - * Flag indicating whether the sheet displays Automatic Page Breaks. - * - * @return true if the sheet displays Automatic Page Breaks. - */ - public bool Autobreaks - { - get - { - CT_SheetPr sheetPr = GetSheetTypeSheetPr(); - CT_PageSetUpPr psSetup = (sheetPr == null || !sheetPr.IsSetPageSetUpPr()) ? - new CT_PageSetUpPr() : sheetPr.pageSetUpPr; - return psSetup.autoPageBreaks; - } - set - { - CT_SheetPr sheetPr = GetSheetTypeSheetPr(); - CT_PageSetUpPr psSetup = sheetPr.IsSetPageSetUpPr() ? - sheetPr.pageSetUpPr : sheetPr.AddNewPageSetUpPr(); - psSetup.autoPageBreaks = (value); - } - } - - /** - * Sets a page break at the indicated column. - * Breaks occur above the specified row and left of the specified column inclusive. - * - * For example, sheet.SetColumnBreak(2); breaks the sheet into two parts - * with columns A,B,C in the first and D,E,... in the second. Simuilar, sheet.SetRowBreak(2); - * breaks the sheet into two parts with first three rows (rownum=1...3) in the first part - * and rows starting with rownum=4 in the second. - * - * @param column the column to break, inclusive - */ - public void SetColumnBreak(int column) - { - if (!IsColumnBroken(column)) + pane.state = ST_PaneState.frozen; + if (rowSplit == 0) { - CT_PageBreak pgBreak = worksheet.IsSetColBreaks() ? - worksheet.colBreaks : worksheet.AddNewColBreaks(); - CT_Break brk = pgBreak.AddNewBrk(); - brk.id = (uint)column + 1; // this is id of the row element which is 1-based: - brk.man = (true); - brk.max = (uint)SpreadsheetVersion.EXCEL2007.LastRowIndex; //end row of the break - - pgBreak.count = (uint)pgBreak.sizeOfBrkArray(); - pgBreak.manualBreakCount = (uint)(pgBreak.sizeOfBrkArray()); + pane.topLeftCell = new CellReference(0, leftmostColumn).FormatAsString(); + pane.activePane = ST_Pane.topRight; } - } - - public void SetColumnGroupCollapsed(int columnNumber, bool collapsed) - { - if (collapsed) + else if (colSplit == 0) { - CollapseColumn(columnNumber); + pane.topLeftCell = new CellReference(topRow, 0).FormatAsString(); + pane.activePane = ST_Pane.bottomLeft; } else { - ExpandColumn(columnNumber); - } - } - - private void CollapseColumn(int columnNumber) - { - CT_Cols cols = worksheet.GetColsArray(0); - CT_Col col = columnHelper.GetColumn(columnNumber, false); - int colInfoIx = columnHelper.GetIndexOfColumn(cols, col); - if (colInfoIx == -1) - { - return; + pane.topLeftCell = new CellReference(topRow, leftmostColumn).FormatAsString(); + pane.activePane = ST_Pane.bottomRight; } - // Find the start of the group. - int groupStartColInfoIx = FindStartOfColumnOutlineGroup(colInfoIx); - - CT_Col columnInfo = cols.GetColArray(groupStartColInfoIx); - - // Hide all the columns until the end of the group - int lastColMax = SetGroupHidden(groupStartColInfoIx, columnInfo - .outlineLevel, true); - - // write collapse field - SetColumn(lastColMax + 1, null, 0, null, null, true); + ctView.selection = null; + CT_Selection sel = ctView.AddNewSelection(); + sel.pane = pane.activePane; } - private void SetColumn(int targetColumnIx, short? xfIndex, int? style, - int? level, Boolean? hidden, Boolean? collapsed) + /// + /// Create a new row within the sheet and return the high level + /// representation. See + /// + /// row number + /// High level object representing a + /// row in the sheet + public virtual IRow CreateRow(int rownum) { - CT_Cols cols = worksheet.GetColsArray(0); - CT_Col ci = null; - int k = 0; - for (k = 0; k < cols.sizeOfColArray(); k++) + CT_Row ctRow; + XSSFRow prev = _rows.ContainsKey(rownum) ? _rows[rownum] : null; + if (prev != null) { - CT_Col tci = cols.GetColArray(k); - if (tci.min >= targetColumnIx - && tci.max <= targetColumnIx) - { - ci = tci; - break; - } - if (tci.min > targetColumnIx) + // the Cells in an existing row are invalidated on-purpose, in + // order to clean up correctly, we need to call the remove, so + // things like ArrayFormulas and CalculationChain updates are + // done correctly. We remove the cell this way as the internal + // cell-list is changed by the remove call and thus would cause + // ConcurrentModificationException otherwise + while (prev.FirstCellNum != -1) { - // call column infos after k are for later columns - break; // exit now so k will be the correct insert pos + prev.RemoveCell(prev.GetCell(prev.FirstCellNum)); } - } - - if (ci == null) - { - // okay so there ISN'T a column info record that covers this column - // so lets create one! - CT_Col nci = new CT_Col(); - nci.min = (uint)targetColumnIx; - nci.max = (uint)targetColumnIx; - UnsetCollapsed((bool)collapsed, nci); - this.columnHelper.AddCleanColIntoCols(cols, nci); - return; - } - - bool styleChanged = style != null - && ci.style != style; - bool levelChanged = level != null - && ci.outlineLevel != level; - bool hiddenChanged = hidden != null - && ci.hidden != hidden; - bool collapsedChanged = collapsed != null - && ci.collapsed != collapsed; - bool columnChanged = levelChanged || hiddenChanged - || collapsedChanged || styleChanged; - if (!columnChanged) - { - // do nothing...nothing Changed. - return; - } - if (ci.min == targetColumnIx && ci.max == targetColumnIx) - { - // ColumnInfo ci for a single column, the target column - UnsetCollapsed((bool)collapsed, ci); - return; + ctRow = prev.GetCTRow(); + ctRow.Set(new CT_Row()); } - - if (ci.min == targetColumnIx || ci.max == targetColumnIx) + else { - // The target column is at either end of the multi-column ColumnInfo - // ci - // we'll just divide the info and create a new one - if (ci.min == targetColumnIx) + if (_rows.Count == 0 || rownum > GetLastKey(_rows.Keys)) { - ci.min = (uint)(targetColumnIx + 1); + // we can append the new row at the end + ctRow = worksheet.sheetData.AddNewRow(); } else { - ci.max = (uint)(targetColumnIx - 1); - k++; // adjust insert pos to insert after + // get number of rows where row index < rownum + // --> this tells us where our row should go + int idx = HeadMapCount(_rows.Keys, rownum); + ctRow = worksheet.sheetData.InsertNewRow(idx); } - CT_Col nci = columnHelper.CloneCol(cols, ci); - nci.min = (uint)(targetColumnIx); - UnsetCollapsed((bool)collapsed, nci); - this.columnHelper.AddCleanColIntoCols(cols, nci); - } - else - { - // split to 3 records - CT_Col ciStart = ci; - CT_Col ciMid = columnHelper.CloneCol(cols, ci); - CT_Col ciEnd = columnHelper.CloneCol(cols, ci); - int lastcolumn = (int)ci.max; - ciStart.max = (uint)(targetColumnIx - 1); + XSSFRow r = new XSSFRow(ctRow, this) + { + RowNum = rownum + }; + _rows[rownum] = r; + return r; + } - ciMid.min = (uint)(targetColumnIx); - ciMid.max = (uint)(targetColumnIx); - UnsetCollapsed((bool)collapsed, ciMid); - this.columnHelper.AddCleanColIntoCols(cols, ciMid); + /// + /// Creates a split pane. Any existing freezepane or split pane is + /// overwritten. + /// + /// Horizonatal position of split (in 1/20th + /// of a point). + /// Vertical position of split (in 1/20th of + /// a point). + /// Left column visible in right pane. + /// Top row visible in bottom pane + /// Active pane. One of: PANE_LOWER_RIGHT, + /// PANE_UPPER_RIGHT, PANE_LOWER_LEFT, PANE_UPPER_LEFT + public void CreateSplitPane(int xSplitPos, int ySplitPos, int leftmostColumn, int topRow, PanePosition activePane) + { + CreateFreezePane(xSplitPos, ySplitPos, leftmostColumn, topRow); + GetPane().state = ST_PaneState.split; + GetPane().activePane = (ST_Pane)activePane; + } - ciEnd.min = (uint)(targetColumnIx + 1); - ciEnd.max = (uint)(lastcolumn); - this.columnHelper.AddCleanColIntoCols(cols, ciEnd); - } + /// + /// Returns cell comment for the specified row and column + /// + /// The row. + /// The column. + /// cell comment or null if not found + [Obsolete("deprecated as of 2015-11-23 (circa POI 3.14beta1). Use {@link #getCellComment(CellAddress)} instead.")] + public IComment GetCellComment(int row, int column) + { + return GetCellComment(new CellAddress(row, column)); } - private void UnsetCollapsed(bool collapsed, CT_Col ci) + /// + /// Returns cell comment for the specified location + /// + /// cell location + /// return cell comment or null if not found + public IComment GetCellComment(CellAddress address) { - if (collapsed) + if (sheetComments == null) { - ci.collapsed = (collapsed); + return null; } - else + + int row = address.Row; + int column = address.Column; + + CellAddress ref1 = new CellAddress(row, column); + CT_Comment ctComment = sheetComments.GetCTComment(ref1); + if (ctComment == null) { - ci.UnsetCollapsed(); + return null; } + + XSSFVMLDrawing vml = GetVMLDrawing(false); + return new XSSFComment(sheetComments, ctComment, + vml?.FindCommentShape(row, column)); } - /** - * Sets all adjacent columns of the same outline level to the specified - * hidden status. - * - * @param pIdx - * the col info index of the start of the outline group - * @return the column index of the last column in the outline group - */ - private int SetGroupHidden(int pIdx, int level, bool hidden) + /// + /// Returns all cell comments on this sheet. + /// + /// return A Dictionary of each Comment in the sheet, keyed on + /// the cell address where the comment is located. + public Dictionary GetCellComments() { - CT_Cols cols = worksheet.GetColsArray(0); - int idx = pIdx; - CT_Col columnInfo = cols.GetColArray(idx); - while (idx < cols.sizeOfColArray()) + if (sheetComments == null) { - columnInfo.hidden = (hidden); - if (idx + 1 < cols.sizeOfColArray()) - { - CT_Col nextColumnInfo = cols.GetColArray(idx + 1); - - if (!IsAdjacentBefore(columnInfo, nextColumnInfo)) - { - break; - } - - if (nextColumnInfo.outlineLevel < level) - { - break; - } - columnInfo = nextColumnInfo; - } - idx++; + return new Dictionary(); } - return (int)columnInfo.max; + + return sheetComments.GetCellComments(); } - private bool IsAdjacentBefore(CT_Col col, CT_Col other_col) + /// + /// Get a Hyperlink in this sheet anchored at row, column + /// + /// + /// + /// return hyperlink if there is a hyperlink anchored at row, + /// column; otherwise returns null + public IHyperlink GetHyperlink(int row, int column) { - return (col.max == (other_col.min - 1)); + return GetHyperlink(new CellAddress(row, column)); } - private int FindStartOfColumnOutlineGroup(int pIdx) + /// + /// Get a Hyperlink in this sheet located in a cell specified + /// by {code addr} + /// + /// The address of the cell containing the + /// hyperlink + /// return hyperlink if there is a hyperlink anchored at + /// {@code addr}; otherwise returns {@code null} + public IHyperlink GetHyperlink(CellAddress addr) { - // Find the start of the group. - CT_Cols cols = worksheet.GetColsArray(0); - CT_Col columnInfo = cols.GetColArray(pIdx); - int level = columnInfo.outlineLevel; - int idx = pIdx; - while (idx != 0) + string ref1 = addr.FormatAsString(); + foreach (XSSFHyperlink hyperlink in hyperlinks) { - CT_Col prevColumnInfo = cols.GetColArray(idx - 1); - if (!IsAdjacentBefore(prevColumnInfo, columnInfo)) - { - break; - } - if (prevColumnInfo.outlineLevel < level) + if (hyperlink.CellRef.Equals(ref1)) { - break; + return hyperlink; } - idx--; - columnInfo = prevColumnInfo; } - return idx; + + return null; } - private int FindEndOfColumnOutlineGroup(int colInfoIndex) + /// + /// Get a list of Hyperlinks in this sheet + /// + /// + public List GetHyperlinkList() { - CT_Cols cols = worksheet.GetColsArray(0); - // Find the end of the group. - CT_Col columnInfo = cols.GetColArray(colInfoIndex); - int level = columnInfo.outlineLevel; - int idx = colInfoIndex; - while (idx < cols.sizeOfColArray() - 1) - { - CT_Col nextColumnInfo = cols.GetColArray(idx + 1); - if (!IsAdjacentBefore(columnInfo, nextColumnInfo)) - { - break; - } - if (nextColumnInfo.outlineLevel < level) - { - break; - } - idx++; - columnInfo = nextColumnInfo; - } - return idx; + return hyperlinks.ToList(); } - private void ExpandColumn(int columnIndex) + /// + /// Get the actual column width (in units of 1/256th of a character width) + /// Note, the returned value is always gerater that + /// because the latter does not include margins. Actual column width + /// measured as the number of characters of the maximum digit width of + /// thenumbers 0, 1, 2, ..., 9 as rendered in the normal style's font. + /// There are 4 pixels of margin pAdding(two on each side), plus 1 pixel + /// pAdding for the gridlines. + /// + /// the column to set (0-based) + /// the width in units of 1/256th of a character width + public int GetColumnWidth(int columnIndex) { - CT_Cols cols = worksheet.GetColsArray(0); CT_Col col = columnHelper.GetColumn(columnIndex, false); - int colInfoIx = columnHelper.GetIndexOfColumn(cols, col); + double width = (col == null || !col.IsSetWidth()) + ? DefaultColumnWidth + : col.width; + return (int)(width * 256); + } - int idx = FindColInfoIdx((int)col.max, colInfoIx); - if (idx == -1) + /// + /// Get the actual column width in pixels + /// Please note, that this method works correctly only for workbooks + /// with the default font size(Calibri 11pt for .xlsx). + /// + /// + /// + public float GetColumnWidthInPixels(int columnIndex) + { + float widthIn256 = GetColumnWidth(columnIndex); + return (float)(widthIn256 / 256.0 * XSSFWorkbook.DEFAULT_CHARACTER_WIDTH); + } + + /// + /// Gets the size of the margin in inches. + /// + /// which margin to get + /// the size of the margin + /// + public double GetMargin(MarginType margin) + { + if (!worksheet.IsSetPageMargins()) { - return; + return 0; } - // If it is already expanded do nothing. - if (!IsColumnGroupCollapsed(idx)) + CT_PageMargins pageMargins = worksheet.pageMargins; + switch (margin) { - return; + case MarginType.LeftMargin: + return pageMargins.left; + case MarginType.RightMargin: + return pageMargins.right; + case MarginType.TopMargin: + return pageMargins.top; + case MarginType.BottomMargin: + return pageMargins.bottom; + case MarginType.HeaderMargin: + return pageMargins.header; + case MarginType.FooterMargin: + return pageMargins.footer; + default: + throw new ArgumentException( + "Unknown margin constant: " + margin); } + } - // Find the start/end of the group. - int startIdx = FindStartOfColumnOutlineGroup(idx); - int endIdx = FindEndOfColumnOutlineGroup(idx); - - // expand: - // colapsed bit must be unset - // hidden bit Gets unset _if_ surrounding groups are expanded you can - // determine - // this by looking at the hidden bit of the enclosing group. You will - // have - // to look at the start and the end of the current group to determine - // which - // is the enclosing group - // hidden bit only is altered for this outline level. ie. don't - // uncollapse Contained groups - CT_Col columnInfo = cols.GetColArray(endIdx); - if (!IsColumnGroupHiddenByParent(idx)) + /// + /// Sets the size of the margin in inches. + /// + /// which margin to get + /// the size of the margin + /// + public void SetMargin(MarginType margin, double size) + { + CT_PageMargins pageMargins = worksheet.IsSetPageMargins() ? + worksheet.pageMargins : worksheet.AddNewPageMargins(); + switch (margin) { - int outlineLevel = columnInfo.outlineLevel; - bool nestedGroup = false; - for (int i = startIdx; i <= endIdx; i++) - { - CT_Col ci = cols.GetColArray(i); - if (outlineLevel == ci.outlineLevel) - { - ci.UnsetHidden(); - if (nestedGroup) - { - nestedGroup = false; - ci.collapsed = (true); - } - } - else - { - nestedGroup = true; - } - } + case MarginType.LeftMargin: + pageMargins.left = size; + break; + case MarginType.RightMargin: + pageMargins.right = size; + break; + case MarginType.TopMargin: + pageMargins.top = size; + break; + case MarginType.BottomMargin: + pageMargins.bottom = size; + break; + case MarginType.HeaderMargin: + pageMargins.header = size; + break; + case MarginType.FooterMargin: + pageMargins.footer = size; + break; + default: + throw new InvalidOperationException( + "Unknown margin constant: " + margin); } - // Write collapse flag (stored in a single col info record after this - // outline group) - SetColumn((int)columnInfo.max + 1, null, null, null, - false, false); } - private bool IsColumnGroupHiddenByParent(int idx) + /// + /// + /// + /// + /// the merged region at the specified index + /// if this worksheet + /// does not contain merged regions + public CellRangeAddress GetMergedRegion(int index) { - CT_Cols cols = worksheet.GetColsArray(0); - // Look out outline details of end - int endLevel = 0; - bool endHidden = false; - int endOfOutlineGroupIdx = FindEndOfColumnOutlineGroup(idx); - if (endOfOutlineGroupIdx < cols.sizeOfColArray()) + CT_MergeCells ctMergeCells = worksheet.mergeCells; + if (ctMergeCells == null) { - CT_Col nextInfo = cols.GetColArray(endOfOutlineGroupIdx + 1); - if (IsAdjacentBefore(cols.GetColArray(endOfOutlineGroupIdx), - nextInfo)) - { - endLevel = nextInfo.outlineLevel; - endHidden = (bool)nextInfo.hidden; - } + throw new InvalidOperationException("This worksheet does not contain merged regions"); } - // Look out outline details of start - int startLevel = 0; - bool startHidden = false; - int startOfOutlineGroupIdx = FindStartOfColumnOutlineGroup(idx); - if (startOfOutlineGroupIdx > 0) - { - CT_Col prevInfo = cols.GetColArray(startOfOutlineGroupIdx - 1); - if (IsAdjacentBefore(prevInfo, cols - .GetColArray(startOfOutlineGroupIdx))) - { - startLevel = prevInfo.outlineLevel; - startHidden = (bool)prevInfo.hidden; - } + CT_MergeCell ctMergeCell = ctMergeCells.GetMergeCellArray(index); - } - if (endLevel > startLevel) + if (ctMergeCell == null) { - return endHidden; + return null; } - return startHidden; + + string ref1 = ctMergeCell.@ref; + return CellRangeAddress.ValueOf(ref1); } - private int FindColInfoIdx(int columnValue, int fromColInfoIdx) + public CellRangeAddress GetMergedRegion(CellRangeAddress mergedRegion) { - CT_Cols cols = worksheet.GetColsArray(0); - - if (columnValue < 0) - { - throw new ArgumentException( - "column parameter out of range: " + columnValue); - } - if (fromColInfoIdx < 0) + if (worksheet.mergeCells == null || worksheet.mergeCells.mergeCell == null) { - throw new ArgumentException( - "fromIdx parameter out of range: " + fromColInfoIdx); + return null; } - for (int k = fromColInfoIdx; k < cols.sizeOfColArray(); k++) + foreach (CT_MergeCell mc in worksheet.mergeCells.mergeCell) { - CT_Col ci = cols.GetColArray(k); - - if (ContainsColumn(ci, columnValue)) - { - return k; - } - - if (ci.min > fromColInfoIdx) + if (mc != null && !string.IsNullOrEmpty(mc.@ref)) { - break; + CellRangeAddress range = CellRangeAddress.ValueOf(mc.@ref); + if (range.FirstColumn <= mergedRegion.FirstColumn + && range.LastColumn >= mergedRegion.LastColumn + && range.FirstRow <= mergedRegion.FirstRow + && range.LastRow >= mergedRegion.LastRow) + { + return range; + } } - } - return -1; - } - private bool ContainsColumn(CT_Col col, int columnIndex) - { - return col.min <= columnIndex && columnIndex <= col.max; + return null; } - /** - * 'Collapsed' state is stored in a single column col info record - * immediately after the outline group - * - * @param idx - * @return a bool represented if the column is collapsed - */ - private bool IsColumnGroupCollapsed(int idx) + /// + /// Enables sheet protection and Sets the password for the sheet. + /// Also Sets some attributes on the { @link CT_SheetProtection } + /// that correspond to the default values used by Excel + /// + /// password to set for protection. Pass null + /// to remove protection + public void ProtectSheet(string password) { - CT_Cols cols = worksheet.GetColsArray(0); - int endOfOutlineGroupIdx = FindEndOfColumnOutlineGroup(idx); - int nextColInfoIx = endOfOutlineGroupIdx + 1; - if (nextColInfoIx >= cols.sizeOfColArray()) + + if (password != null) { - return false; + CT_SheetProtection sheetProtection = worksheet.AddNewSheetProtection(); + SetSheetPassword(password, null); // defaults to xor password + sheetProtection.sheet = true; + sheetProtection.scenarios = true; + sheetProtection.objects = true; } - CT_Col nextColInfo = cols.GetColArray(nextColInfoIx); + else + { + worksheet.UnsetSheetProtection(); + } + } - CT_Col col = cols.GetColArray(endOfOutlineGroupIdx); - if (!IsAdjacentBefore(col, nextColInfo)) + /// + /// Sets the sheet password. + /// + /// if null, the password will be removed + /// if null, the password will be set as XOR + /// password (Excel 2010 and earlier)otherwise the given algorithm is + /// used for calculating the hash password (Excel 2013) + public void SetSheetPassword(string password, HashAlgorithm hashAlgo) + { + if (password == null && !IsSheetProtectionEnabled()) { - return false; + return; } - return nextColInfo.collapsed; + XSSFPasswordHelper.SetPassword(SafeGetProtectionField(), + password, hashAlgo, null); } - /** - * Get the visibility state for a given column. - * - * @param columnIndex - the column to get (0-based) - * @param hidden - the visiblity state of the column - */ - public void SetColumnHidden(int columnIndex, bool hidden) + /// + /// Validate the password against the stored hash, the hashing method + /// will be determined by the existing password attributes + /// + /// + /// true, if the hashes match (... though original password + /// may differ ...) + public bool ValidateSheetPassword(string password) { - columnHelper.SetColHidden(columnIndex, hidden); + if (!IsSheetProtectionEnabled()) + { + return password == null; + } + + return XSSFPasswordHelper.ValidatePassword(SafeGetProtectionField(), + password, null); } - /** - * Set the width (in units of 1/256th of a character width) - * - *

- * The maximum column width for an individual cell is 255 characters. - * This value represents the number of characters that can be displayed - * in a cell that is formatted with the standard font (first font in the workbook). - *

- * - *

- * Character width is defined as the maximum digit width - * of the numbers 0, 1, 2, ... 9 as rendered - * using the default font (first font in the workbook). - *
- * Unless you are using a very special font, the default character is '0' (zero), - * this is true for Arial (default font font in HSSF) and Calibri (default font in XSSF) - *

- * - *

- * Please note, that the width set by this method includes 4 pixels of margin pAdding (two on each side), - * plus 1 pixel pAdding for the gridlines (Section 3.3.1.12 of the OOXML spec). - * This results is a slightly less value of visible characters than passed to this method (approx. 1/2 of a character). - *

- *

- * To compute the actual number of visible characters, - * Excel uses the following formula (Section 3.3.1.12 of the OOXML spec): - *

- * - * width = TRuncate([{Number of Visible Characters} * - * {Maximum Digit Width} + {5 pixel pAdding}]/{Maximum Digit Width}*256)/256 - * - *

Using the Calibri font as an example, the maximum digit width of 11 point font size is 7 pixels (at 96 dpi). - * If you set a column width to be eight characters wide, e.g. SetColumnWidth(columnIndex, 8*256), - * then the actual value of visible characters (the value Shown in Excel) is derived from the following equation: - * - TRuncate([numChars*7+5]/7*256)/256 = 8; - * - * - * which gives 7.29. - *

- * @param columnIndex - the column to set (0-based) - * @param width - the width in units of 1/256th of a character width - * @throws ArgumentException if width > 255*256 (the maximum column width in Excel is 255 characters) - */ - public void SetColumnWidth(int columnIndex, int width) + /// + /// Returns the logical row ( 0-based). If you ask for a row that is + /// not defined you get a null. This is to say row 4 represents the + /// fifth row on a sheet. + /// + /// row to get + /// representing the rownumber or null + /// if its not defined on the sheet + public IRow GetRow(int rownum) { - if (width > 255 * 256) throw new ArgumentException("The maximum column width for an individual cell is 255 characters."); + if (_rows.ContainsKey(rownum)) + { + return _rows[rownum]; + } - columnHelper.SetColWidth(columnIndex, (double)width / 256); - columnHelper.SetCustomWidth(columnIndex, true); + return null; } - public void SetDefaultColumnStyle(int column, ICellStyle style) + /// + /// Group between (0 based) columns + /// + /// + /// + public void GroupColumn(int fromColumn, int toColumn) { - columnHelper.SetColDefaultStyle(column, style); + GroupColumn1Based(fromColumn + 1, toColumn + 1); } - - private CT_SheetView GetSheetTypeSheetView() + /// + /// Determines if there is a page break at the indicated column + /// + /// + /// + public bool IsColumnBroken(int column) { - if (GetDefaultSheetView() == null) + int[] colBreaks = ColumnBreaks; + for (int i = 0; i < colBreaks.Length; i++) { - GetSheetTypeSheetViews().SetSheetViewArray(0, new CT_SheetView()); + if (colBreaks[i] == column) + { + return true; + } } - return GetDefaultSheetView(); - } - + return false; + } - /** - * group the row It is possible for collapsed to be false and yet still have - * the rows in question hidden. This can be achieved by having a lower - * outline level collapsed, thus hiding all the child rows. Note that in - * this case, if the lowest level were expanded, the middle level would - * remain collapsed. - * - * @param rowIndex - - * the row involved, 0 based - * @param collapse - - * bool value for collapse - */ - public void SetRowGroupCollapsed(int rowIndex, bool collapse) + /// + /// Get the hidden state for a given column. + /// + /// the column to set (0-based) + /// hidden - false if the column is visible + public bool IsColumnHidden(int columnIndex) { - if (collapse) - { - CollapseRow(rowIndex); - } - else - { - ExpandRow(rowIndex); - } + CT_Col col = columnHelper.GetColumn(columnIndex, false); + return col != null && col.hidden; } - /** - * @param rowIndex the zero based row index to collapse - */ - private void CollapseRow(int rowIndex) + /// + /// Tests if there is a page break at the indicated row + /// + /// index of the row to test + /// true if there is a page break at the indicated row + public bool IsRowBroken(int row) { - XSSFRow row = (XSSFRow)GetRow(rowIndex); - if (row != null) + int[] rowBreaks = RowBreaks; + for (int i = 0; i < rowBreaks.Length; i++) { - int startRow = FindStartOfRowOutlineGroup(rowIndex); - - // Hide all the columns until the end of the group - int lastRow = WriteHidden(row, startRow, true); - if (GetRow(lastRow) != null) - { - ((XSSFRow)GetRow(lastRow)).GetCTRow().collapsed = (true); - } - else + if (rowBreaks[i] == row) { - XSSFRow newRow = (XSSFRow)CreateRow(lastRow); - newRow.GetCTRow().collapsed = (true); + return true; } } - } - /** - * @param rowIndex the zero based row index to find from - */ - private int FindStartOfRowOutlineGroup(int rowIndex) - { - // Find the start of the group. - int level = ((XSSFRow)GetRow(rowIndex)).GetCTRow().outlineLevel; - int currentRow = rowIndex; - while (GetRow(currentRow) != null) - { - if (((XSSFRow)GetRow(currentRow)).GetCTRow().outlineLevel < level) - return currentRow + 1; - currentRow--; - } - return currentRow; + return false; } - private int WriteHidden(XSSFRow xRow, int rowIndex, bool hidden) + /// + /// Sets a page break at the indicated row Breaks occur above the + /// specified row and left of the specified column inclusive. For + /// example, sheet.SetColumnBreak(2); breaks the sheet into two parts + /// with columns A,B,C in the first and D,E,... in the second. + /// Simuilar, sheet.SetRowBreak(2); breaks the sheet into two parts + /// with first three rows (rownum=1...3) in the first part and rows + /// starting with rownum=4 in the second. + /// + /// the row to break, inclusive + public void SetRowBreak(int row) { - int level = xRow.GetCTRow().outlineLevel; - for (IEnumerator it = this.GetRowEnumerator(); it.MoveNext();) + + CT_PageBreak pgBreak = worksheet.IsSetRowBreaks() + ? worksheet.rowBreaks + : worksheet.AddNewRowBreaks(); + + if (!IsRowBroken(row)) { - xRow = (XSSFRow)it.Current; - if (xRow.GetCTRow().outlineLevel >= level) - { - xRow.GetCTRow().hidden = (hidden); - rowIndex++; - } + CT_Break brk = pgBreak.AddNewBrk(); + brk.id = (uint)row + 1; // this is id of the row element which is 1-based: + brk.man = true; + brk.max = (uint)SpreadsheetVersion.EXCEL2007.LastColumnIndex; //end column of the break + pgBreak.count = (uint)pgBreak.sizeOfBrkArray(); + pgBreak.manualBreakCount = (uint)pgBreak.sizeOfBrkArray(); } - return rowIndex; } - /** - * @param rowNumber the zero based row index to expand - */ - private void ExpandRow(int rowNumber) + /// + /// Removes a page break at the indicated column + /// + /// + //YK: GetXYZArray() array accessors are deprecated in xmlbeans with JDK 1.5 support + public void RemoveColumnBreak(int column) { - if (rowNumber == -1) - return; - XSSFRow row = (XSSFRow)GetRow(rowNumber); - // If it is already expanded do nothing. - if (!row.GetCTRow().IsSetHidden()) + if (!worksheet.IsSetColBreaks()) { + // no breaks return; } - // Find the start of the group. - int startIdx = FindStartOfRowOutlineGroup(rowNumber); - // Find the end of the group. - int endIdx = FindEndOfRowOutlineGroup(rowNumber); - - // expand: - // collapsed must be unset - // hidden bit Gets unset _if_ surrounding groups are expanded you can - // determine - // this by looking at the hidden bit of the enclosing group. You will - // have - // to look at the start and the end of the current group to determine - // which - // is the enclosing group - // hidden bit only is altered for this outline level. ie. don't - // un-collapse Contained groups - if (!IsRowGroupHiddenByParent(rowNumber)) - { - for (int i = startIdx; i < endIdx; i++) - { - if (row.GetCTRow().outlineLevel == ((XSSFRow)GetRow(i)).GetCTRow() - .outlineLevel) - { - ((XSSFRow)GetRow(i)).GetCTRow().UnsetHidden(); - } - else if (!IsRowGroupCollapsed(i)) - { - ((XSSFRow)GetRow(i)).GetCTRow().UnsetHidden(); - } - } - } - // Write collapse field - row = GetRow(endIdx) as XSSFRow; - if (row != null) + CT_PageBreak pgBreak = worksheet.colBreaks; + List brkArray = pgBreak.brk; + for (int i = 0; i < brkArray.Count; i++) { - CT_Row ctRow = row.GetCTRow(); - // This avoids an IndexOutOfBounds if multiple nested groups are collapsed/expanded - if (ctRow.collapsed) + if (brkArray[i].id == (column + 1)) { - ctRow.UnsetCollapsed(); + pgBreak.RemoveBrk(i); } } } - /** - * @param row the zero based row index to find from - */ - public int FindEndOfRowOutlineGroup(int row) + /// + /// Removes a merged region of cells (hence letting them free) + /// + /// + public void RemoveMergedRegion(int index) { - int level = ((XSSFRow)GetRow(row)).GetCTRow().outlineLevel; - int currentRow; - int lastRowNum = LastRowNum; - for (currentRow = row; currentRow < lastRowNum; currentRow++) + CT_MergeCells ctMergeCells = worksheet.mergeCells; + + int size = ctMergeCells.sizeOfMergeCellArray(); + CT_MergeCell[] mergeCellsArray = new CT_MergeCell[size - 1]; + for (int i = 0; i < size; i++) { - if (GetRow(currentRow) == null - || ((XSSFRow)GetRow(currentRow)).GetCTRow().outlineLevel < level) + if (i < index) { - break; + mergeCellsArray[i] = ctMergeCells.GetMergeCellArray(i); + } + else if (i > index) + { + mergeCellsArray[i - 1] = ctMergeCells.GetMergeCellArray(i); } } - return currentRow; - } - /** - * @param row the zero based row index to find from - */ - private bool IsRowGroupHiddenByParent(int row) - { - // Look out outline details of end - int endLevel; - bool endHidden; - int endOfOutlineGroupIdx = FindEndOfRowOutlineGroup(row); - if (GetRow(endOfOutlineGroupIdx) == null) + if (mergeCellsArray.Length > 0) { - endLevel = 0; - endHidden = false; + ctMergeCells.SetMergeCellArray(mergeCellsArray); } else { - endLevel = ((XSSFRow)GetRow(endOfOutlineGroupIdx)).GetCTRow().outlineLevel; - endHidden = (bool)((XSSFRow)GetRow(endOfOutlineGroupIdx)).GetCTRow().hidden; + worksheet.UnsetMergeCells(); } + } - // Look out outline details of start - int startLevel; - bool startHidden; - int startOfOutlineGroupIdx = FindStartOfRowOutlineGroup(row); - if (startOfOutlineGroupIdx < 0 - || GetRow(startOfOutlineGroupIdx) == null) + /// + /// Removes a number of merged regions of cells (hence letting them + /// free) This method can be used to bulk-remove merged regions in a + /// way much faster than calling RemoveMergedRegion() for every single + /// merged region. + /// + /// A Set of the regions to unmerge + public void RemoveMergedRegions(IList indices) + { + if (!worksheet.IsSetMergeCells()) { - startLevel = 0; - startHidden = false; + return; } - else + + CT_MergeCells ctMergeCells = worksheet.mergeCells; + //TODO: The following codes are not same as poi, re-do it?. + int size = ctMergeCells.sizeOfMergeCellArray(); + List newMergeCells = + new List(ctMergeCells.sizeOfMergeCellArray()); + + for (int i = 0, d = 0; i < size; i++) { - startLevel = ((XSSFRow)GetRow(startOfOutlineGroupIdx)).GetCTRow() - .outlineLevel; - startHidden = (bool)((XSSFRow)GetRow(startOfOutlineGroupIdx)).GetCTRow() - .hidden; + if (!indices.Contains(i)) + { + //newMergeCells[d] = ctMergeCells.GetMergeCellArray(i); + newMergeCells.Add(ctMergeCells.GetMergeCellArray(i)); + d++; + } } - if (endLevel > startLevel) + + if (ListIsEmpty(newMergeCells)) { - return endHidden; + worksheet.UnsetMergeCells(); } - return startHidden; - } - - /** - * @param row the zero based row index to find from - */ - private bool IsRowGroupCollapsed(int row) - { - int collapseRow = FindEndOfRowOutlineGroup(row) + 1; - if (GetRow(collapseRow) == null) + else { - return false; + ctMergeCells.SetMergeCellArray(newMergeCells.ToArray()); } - return (bool)((XSSFRow)GetRow(collapseRow)).GetCTRow().collapsed; - } - - /** - * Sets the zoom magnification for the sheet. The zoom is expressed as a - * fraction. For example to express a zoom of 75% use 3 for the numerator - * and 4 for the denominator. - * - * @param numerator The numerator for the zoom magnification. - * @param denominator The denominator for the zoom magnification. - * @see #SetZoom(int) - */ - [Obsolete("deprecated 2015-11-23 (circa POI 3.14beta1). Use {@link #setZoom(int)} instead.")] - public void SetZoom(int numerator, int denominator) - { - int zoom = 100 * numerator / denominator; - SetZoom(zoom); - } - - /** - * Window zoom magnification for current view representing percent values. - * Valid values range from 10 to 400. Horizontal & Vertical scale toGether. - * - * For example: - *
-         * 10 - 10%
-         * 20 - 20%
-         * ...
-         * 100 - 100%
-         * ...
-         * 400 - 400%
-         * 
- * - * Current view can be Normal, Page Layout, or Page Break Preview. - * - * @param scale window zoom magnification - * @throws ArgumentException if scale is invalid - */ - public void SetZoom(int scale) - { - if (scale < 10 || scale > 400) - throw new ArgumentException("Valid scale values range from 10 to 400"); - GetSheetTypeSheetView().zoomScale = (uint)scale; } - /** - * copyRows rows from srcRows to this sheet starting at destStartRow - * - * Additionally copies merged regions that are completely defined in these - * rows (ie. merged 2 cells on a row to be shifted). - * @param srcRows the rows to copy. Formulas will be offset by the difference - * in the row number of the first row in srcRows and destStartRow (even if srcRows - * are from a different sheet). - * @param destStartRow the row in this sheet to paste the first row of srcRows - * the remainder of srcRows will be pasted below destStartRow per the cell copy policy - * @param policy is the cell copy policy, which can be used to merge the source and destination - * when the source is blank, copy styles only, paste as value, etc - */ - - public void CopyRows(List srcRows, int destStartRow, CellCopyPolicy policy) + /// + /// Remove a row from this sheet. All cells Contained in the row are + /// Removed as well + /// + /// the row to Remove. + /// + public void RemoveRow(IRow row) { - if (srcRows == null || srcRows.Count == 0) + if (row.Sheet != this) { - throw new ArgumentException("No rows to copy"); + throw new ArgumentException("Specified row does not belong to" + + " this sheet"); + } + // collect cells into a temporary array to avoid ConcurrentModificationException + List cellsToDelete = new List(); + foreach (ICell cell in row) + { + cellsToDelete.Add((XSSFCell)cell); } - IRow srcStartRow = srcRows[0]; - IRow srcEndRow = srcRows[srcRows.Count - 1]; - if (srcStartRow == null) + foreach (XSSFCell cell in cellsToDelete) { - throw new ArgumentException("copyRows: First row cannot be null"); + row.RemoveCell(cell); } - int srcStartRowNum = srcStartRow.RowNum; - int srcEndRowNum = srcEndRow.RowNum; + int idx = _rows.Count(p => p.Key < row.RowNum);// _rows.headMap(row.getRowNum()).size(); + _rows.Remove(row.RowNum); + worksheet.sheetData.RemoveRow(row.RowNum + 1); // Note that rows in worksheet.sheetData is 1-based. - // check row numbers to make sure they are continuous and increasing (monotonic) - // and srcRows does not contain null rows - int size = srcRows.Count; - for (int index = 1; index < size; index++) + // also remove any comment located in that row + if (sheetComments != null) { - IRow curRow = srcRows[(index)]; - if (curRow == null) - { - throw new ArgumentException("srcRows may not contain null rows. Found null row at index " + index + "."); - //} else if (curRow.RowNum != prevRow.RowNum + 1) { - // throw new IllegalArgumentException("srcRows must contain continuously increasing row numbers. " + - // "Got srcRows[" + (index-1) + "]=Row " + prevRow.RowNum + ", srcRows[" + index + "]=Row " + curRow.RowNum + "."); - // FIXME: assumes row objects belong to non-null sheets and sheets belong to non-null workbooks. - } - else if (srcStartRow.Sheet.Workbook != curRow.Sheet.Workbook) - { - throw new ArgumentException("All rows in srcRows must belong to the same sheet in the same workbook." + - "Expected all rows from same workbook (" + srcStartRow.Sheet.Workbook + "). " + - "Got srcRows[" + index + "] from different workbook (" + curRow.Sheet.Workbook + ")."); - } - else if (srcStartRow.Sheet != curRow.Sheet) + foreach (CellAddress ref1 in GetCellComments().Keys) { - throw new ArgumentException("All rows in srcRows must belong to the same sheet. " + - "Expected all rows from " + srcStartRow.Sheet.SheetName + ". " + - "Got srcRows[" + index + "] from " + curRow.Sheet.SheetName); + if (ref1.Row == idx) + { + sheetComments.RemoveComment(ref1); + } } } + } - // FIXME: is special behavior needed if srcRows and destRows belong to the same sheets and the regions overlap? + /// + /// Removes the page break at the indicated row + /// + /// + //YK: GetXYZArray() array accessors are deprecated in xmlbeans with JDK 1.5 support + public void RemoveRowBreak(int row) + { + if (!worksheet.IsSetRowBreaks()) + { + return; + } - CellCopyPolicy options = new CellCopyPolicy(policy); - // avoid O(N^2) performance scanning through all regions for each row - // merged regions will be copied after all the rows have been copied - options.IsCopyMergedRegions = (false); + CT_PageBreak pgBreak = worksheet.rowBreaks; + List brkArray = pgBreak.brk; + for (int i = 0; i < brkArray.Count; i++) + { + if (brkArray[i].id == (row + 1)) + { + pgBreak.RemoveBrk(i); + } + } + } + + /// + /// Sets a page break at the indicated column. Breaks occur above the + /// specified row and left of the specified column inclusive. For + /// example, sheet.SetColumnBreak(2); breaks the sheet into two parts + /// with columns A,B,C in the first and D,E,... in the second. + /// Simuilar, sheet.SetRowBreak(2); breaks the sheet into two parts + /// with first three rows (rownum=1...3) in the first part and rows + /// starting with rownum=4 in the second. + /// + /// the column to break, inclusive + public void SetColumnBreak(int column) + { + if (!IsColumnBroken(column)) + { + CT_PageBreak pgBreak = worksheet.IsSetColBreaks() ? + worksheet.colBreaks : worksheet.AddNewColBreaks(); + CT_Break brk = pgBreak.AddNewBrk(); + brk.id = (uint)column + 1; // this is id of the row element which is 1-based: + brk.man = true; + brk.max = (uint)SpreadsheetVersion.EXCEL2007.LastRowIndex; //end row of the break + + pgBreak.count = (uint)pgBreak.sizeOfBrkArray(); + pgBreak.manualBreakCount = (uint)pgBreak.sizeOfBrkArray(); + } + } + + public void SetColumnGroupCollapsed(int columnNumber, bool collapsed) + { + if (collapsed) + { + CollapseColumn(columnNumber); + } + else + { + ExpandColumn(columnNumber); + } + } + + /// + /// Get the visibility state for a given column. + /// + /// the column to get (0-based) + /// the visiblity state of the column + public void SetColumnHidden(int columnIndex, bool hidden) + { + columnHelper.SetColHidden(columnIndex, hidden); + } + + /// + /// Set the width (in units of 1/256th of a character width) + /// + /// The maximum column width for an individual cell is 255 + /// characters. This value represents the number of characters that can + /// be displayed in a cell that is formatted with the standard font + /// (first font in the workbook). + /// + /// + /// Character width is defined as the maximum digit width of the + /// numbers + /// + /// 0, 1, 2, ... 9 + /// + /// as rendered using the default + /// font (first font in the workbook). Unless you are using a very + /// special font, the default character is '0' (zero), this is true for + /// Arial (default font font in HSSF) and Calibri (default font in + /// XSSF) + /// + /// + /// Please note, that the width set by this method includes 4 + /// pixels of margin pAdding (two on each side), plus 1 pixel pAdding + /// for the gridlines (Section 3.3.1.12 of the OOXML spec). This + /// results is a slightly less value of visible characters than passed + /// to this method (approx. 1/2 of a character). + /// + /// + /// To compute + /// the actual number of visible characters, Excel uses the following + /// formula (Section 3.3.1.12 of the OOXML spec): + /// + /// + /// width = TRuncate([{Number of Visible Characters} * + /// {Maximum Digit Width} + {5 pixel pAdding}]/{Maximum Digit Width}*256)/256 + /// + /// + /// Using the Calibri font as an example, the maximum digit width + /// of 11 point font size is 7 pixels (at 96 dpi). If you set a column + /// width to be eight characters wide, e.g. + /// + /// SetColumnWidth(columnIndex, 8*256) + /// + /// , then the actual value of visible characters (the value Shown in + /// Excel) is derived from the following equation: + /// + /// TRuncate([numChars*7+5]/7*256)/256 = 8; + /// + /// which gives + /// + /// 7.29 + /// . + /// + /// + /// the column to set (0-based) + /// the width in units of 1/256th of a character + /// width + /// if width more than 255*256 (the + /// maximum column width in Excel is 255 characters) + public void SetColumnWidth(int columnIndex, int width) + { + if (width > 255 * 256) + { + throw new ArgumentException("The maximum column width for an " + + "individual cell is 255 characters."); + } + + columnHelper.SetColWidth(columnIndex, (double)width / 256); + columnHelper.SetCustomWidth(columnIndex, true); + } + + public void SetDefaultColumnStyle(int column, ICellStyle style) + { + columnHelper.SetColDefaultStyle(column, style); + } + + /// + /// group the row It is possible for collapsed to be false and yet + /// still have the rows in question hidden. This can be achieved by + /// having a lower outline level collapsed, thus hiding all the child + /// rows. Note that in this case, if the lowest level were expanded, + /// the middle level would remain collapsed. + /// + /// the row involved, 0 based + /// bool value for collapse + public void SetRowGroupCollapsed(int rowIndex, bool collapse) + { + if (collapse) + { + CollapseRow(rowIndex); + } + else + { + ExpandRow(rowIndex); + } + } + + /// + /// Sets the zoom magnification for the sheet. The zoom is expressed + /// as a fraction. For example to express a zoom of 75% use 3 for the + /// numerator and 4 for the denominator. + /// + /// The numerator for the zoom + /// magnification. + /// The denominator for the zoom + /// magnification. + [Obsolete("deprecated 2015-11-23 (circa POI 3.14beta1). Use {@link #setZoom(int)} instead.")] + public void SetZoom(int numerator, int denominator) + { + int zoom = 100 * numerator / denominator; + SetZoom(zoom); + } + + /// + /// Window zoom magnification for current view representing percent + /// values. Valid values range from 10 to 400. Horizontal & + /// Vertical scale toGether. For example: + /// + /// 10 - 10% + /// 20 - 20% + /// ... + /// 100 - 100% + /// ... + /// 400 - 400% + /// + /// Current view can be Normal, Page Layout, or Page Break Preview. + /// + /// window zoom magnification + /// if scale is invalid + public void SetZoom(int scale) + { + if (scale < 10 || scale > 400) + { + throw new ArgumentException("Valid scale values range from 10 to 400"); + } + + GetSheetTypeSheetView().zoomScale = (uint)scale; + } + + /// + /// copyRows rows from srcRows to this sheet starting at destStartRow + /// Additionally copies merged regions that are completely defined in + /// these rows (ie. merged 2 cells on a row to be shifted). + /// + /// the rows to copy. Formulas will be offset by + /// the difference in the row number of the first row in srcRows and + /// destStartRow (even if srcRows are from a different sheet). + /// the row in this sheet to paste the first + /// row of srcRows the remainder of srcRows will be pasted below + /// destStartRow per the cell copy policy + /// is the cell copy policy, which can be used to + /// merge the source and destination when the source is blank, copy + /// styles only, paste as value, etc + /// + public void CopyRows(List srcRows, int destStartRow, CellCopyPolicy policy) + { + if (srcRows == null || srcRows.Count == 0) + { + throw new ArgumentException("No rows to copy"); + } + + IRow srcStartRow = srcRows[0]; + IRow srcEndRow = srcRows[srcRows.Count - 1]; + + if (srcStartRow == null) + { + throw new ArgumentException("copyRows: First row cannot be null"); + } + + int srcStartRowNum = srcStartRow.RowNum; + int srcEndRowNum = srcEndRow.RowNum; + + // check row numbers to make sure they are continuous and + // increasing (monotonic) and srcRows does not contain null rows + int size = srcRows.Count; + for (int index = 1; index < size; index++) + { + IRow curRow = srcRows[index]; + if (curRow == null) + { + throw new ArgumentException( + "srcRows may not contain null rows. Found null row at" + + " index " + index + "."); + } + else if (srcStartRow.Sheet.Workbook != curRow.Sheet.Workbook) + { + throw new ArgumentException( + "All rows in srcRows must belong to the same sheet in" + + " the same workbook. Expected all rows from same " + + "workbook (" + srcStartRow.Sheet.Workbook + "). " + + "Got srcRows[" + index + "] from different workbook (" + + curRow.Sheet.Workbook + ")."); + } + else if (srcStartRow.Sheet != curRow.Sheet) + { + throw new ArgumentException( + "All rows in srcRows must belong to the same sheet. " + + "Expected all rows from " + srcStartRow.Sheet.SheetName + + ". Got srcRows[" + index + "] from " + curRow.Sheet.SheetName); + } + } + + // FIXME: is special behavior needed if srcRows and destRows belong to the same sheets and the regions overlap? + + CellCopyPolicy options = new CellCopyPolicy(policy) + { + // avoid O(N^2) performance scanning through all regions for + // each row merged regions will be copied after all the rows + // have been copied + IsCopyMergedRegions = false + }; // FIXME: if srcRows contains gaps or null values, clear out those rows that will be overwritten // how will this work with merging (copy just values, leave cell styles in place?) @@ -3163,7 +2777,7 @@ public void CopyRows(List srcRows, int destStartRow, CellCopyPolicy pol } else { - int shift = (srcRow.RowNum - srcStartRowNum); + int shift = srcRow.RowNum - srcStartRowNum; destRowNum = destStartRow + shift; } //removeRow(destRowNum); //this probably clears all external formula references to destRow, causing unwanted #REF! errors @@ -3171,10 +2785,9 @@ public void CopyRows(List srcRows, int destStartRow, CellCopyPolicy pol destRow.CopyRowFrom(srcRow, options); } - // ====================== - // Only do additional copy operations here that cannot be done with Row.copyFromRow(Row, options) - // reasons: operation needs to interact with multiple rows or sheets - + // Only do additional copy operations here that cannot be done with + // Row.copyFromRow(Row, options) reasons: operation needs to + // interact with multiple rows or sheets. // Copy merged regions that are contained within the copy region if (policy.IsCopyMergedRegions) { @@ -3186,76 +2799,77 @@ public void CopyRows(List srcRows, int destStartRow, CellCopyPolicy pol { // srcRegion is fully inside the copied rows CellRangeAddress destRegion = srcRegion.Copy(); - destRegion.FirstRow = (destRegion.FirstRow + shift); - destRegion.LastRow = (destRegion.LastRow + shift); + destRegion.FirstRow += shift; + destRegion.LastRow += shift; AddMergedRegion(destRegion); } } } } - /** - * Copies rows between srcStartRow and srcEndRow to the same sheet, starting at destStartRow - * Convenience function for {@link #copyRows(List, int, CellCopyPolicy)} - * - * Equivalent to copyRows(getRows(srcStartRow, srcEndRow, false), destStartRow, cellCopyPolicy) - * - * @param srcStartRow the index of the first row to copy the cells from in this sheet - * @param srcEndRow the index of the last row to copy the cells from in this sheet - * @param destStartRow the index of the first row to copy the cells to in this sheet - * @param cellCopyPolicy the policy to use to determine how cells are copied - */ - + /// + /// Copies rows between srcStartRow and srcEndRow to the same sheet, + /// starting at destStartRow Convenience function for + /// Equivalent to + /// copyRows(getRows(srcStartRow, srcEndRow, false), destStartRow, cellCopyPolicy) + /// + /// the index of the first row to copy the + /// cells from in this sheet + /// the index of the last row to copy the cells + /// from in this sheet + /// the index of the first row to copy the + /// cells to in this sheet + /// the policy to use to determine how + /// cells are copied public void CopyRows(int srcStartRow, int srcEndRow, int destStartRow, CellCopyPolicy cellCopyPolicy) { - List srcRows = GetRows(srcStartRow, srcEndRow, false); //FIXME: should be false, no need to create rows where src is only to copy them to dest + List srcRows = GetRows(srcStartRow, srcEndRow, false); CopyRows(srcRows, destStartRow, cellCopyPolicy); } - - /** - * Shifts rows between startRow and endRow n number of rows. - * If you use a negative number, it will shift rows up. - * Code ensures that rows don't wrap around. - * - * Calls ShiftRows(startRow, endRow, n, false, false); - * - *

- * Additionally Shifts merged regions that are completely defined in these - * rows (ie. merged 2 cells on a row to be Shifted).

- * @param startRow the row to start Shifting - * @param endRow the row to end Shifting - * @param n the number of rows to shift - */ + /// + /// Shifts rows between startRow and endRow n number of rows. If you + /// use a negative number, it will shift rows up. Code ensures that + /// rows don't wrap around. + /// Calls ShiftRows(startRow, endRow, n, false, false); + /// + /// Additionally Shifts merged regions that are completely defined in + /// these rows (ie. merged 2 cells on a row to be Shifted). + /// + /// + /// the row to start Shifting + /// the row to end Shifting + /// the number of rows to shift public void ShiftRows(int startRow, int endRow, int n) { ShiftRows(startRow, endRow, n, false, false); } - /** - * Shifts rows between startRow and endRow n number of rows. - * If you use a negative number, it will shift rows up. - * Code ensures that rows don't wrap around - * - *

- * Additionally Shifts merged regions that are completely defined in these - * rows (ie. merged 2 cells on a row to be Shifted).

- * - * @param startRow the row to start Shifting - * @param endRow the row to end Shifting - * @param n the number of rows to shift - * @param copyRowHeight whether to copy the row height during the shift - * @param reSetOriginalRowHeight whether to set the original row's height to the default - */ + /// + /// Shifts rows between startRow and endRow n number of rows. If you + /// use a negative number, it will shift rows up. Code ensures that + /// rows don't wrap around + /// + /// Additionally Shifts merged regions thatare completely defined in + /// these rows (ie. merged 2 cells on a row to be Shifted). + /// + /// + /// the row to start Shifting + /// the row to end Shifting + /// the number of rows to shift + /// whether to copy the row height during + /// the shift + /// whether to set the original + /// row's height to the default //YK: GetXYZArray() array accessors are deprecated in xmlbeans with JDK 1.5 support public void ShiftRows(int startRow, int endRow, int n, bool copyRowHeight, bool resetOriginalRowHeight) { int sheetIndex = Workbook.GetSheetIndex(this); - String sheetName = Workbook.GetSheetName(sheetIndex); + string sheetName = Workbook.GetSheetName(sheetIndex); FormulaShifter shifter = FormulaShifter.CreateForRowShift( sheetIndex, sheetName, startRow, endRow, n, SpreadsheetVersion.EXCEL2007); - removeOverwritten(startRow, endRow, n); - shiftCommentsAndRows(startRow, endRow, n, copyRowHeight); + RemoveOverwrittenRows(startRow, endRow, n); + ShiftCommentsAndRows(startRow, endRow, n, copyRowHeight); XSSFRowShifter rowShifter = new XSSFRowShifter(this); rowShifter.ShiftMergedRegions(startRow, endRow, n); @@ -3263,1465 +2877,2487 @@ public void ShiftRows(int startRow, int endRow, int n, bool copyRowHeight, bool rowShifter.UpdateFormulas(shifter); rowShifter.UpdateConditionalFormatting(shifter); rowShifter.UpdateHyperlinks(shifter); - rebuildRows(); + RebuildRows(); } - private void removeOverwritten(int startRow, int endRow, int n) + + /// + /// Returns the CellStyle that applies to the given (0 based) column, + /// or null if no style has been set for that column + /// + /// + /// + public ICellStyle GetColumnStyle(int column) { - XSSFVMLDrawing vml = GetVMLDrawing(false); - List rowsToRemove = new List(); - List commentsToRemove = new List(); - List ctRowsToRemove = new List(); - // first remove all rows which will be overwritten - foreach (KeyValuePair rowDict in _rows) - { - XSSFRow row = rowDict.Value; - int rownum = row.RowNum; + int idx = columnHelper.GetColDefaultStyle(column); + return Workbook.GetCellStyleAt(idx == -1 ? 0 : idx); + } - // check if we should remove this row as it will be overwritten by the data later - if (ShouldRemoveRow(startRow, endRow, n, rownum)) + /// + /// Tie a range of cell toGether so that they can be collapsed + /// or expanded + /// + /// start row (0-based) + /// end row (0-based) + public void GroupRow(int fromRow, int toRow) + { + for (int i = fromRow; i <= toRow; i++) + { + XSSFRow xrow = (XSSFRow)GetRow(i); + if (xrow == null) { - // remove row from worksheet.GetSheetData row array - //int idx = _rows.headMap(row.getRowNum()).size(); - int idx = _rows.IndexOfValue(row); - //worksheet.sheetData.RemoveRow(idx); - ctRowsToRemove.Add(worksheet.sheetData.GetRowArray(idx)); - - // remove row from _rows - rowsToRemove.Add(rowDict.Key); + xrow = (XSSFRow)CreateRow(i); + } - commentsToRemove.Clear(); - // FIXME: (performance optimization) this should be moved outside the for-loop so that comments only needs to be iterated over once. - // also remove any comments associated with this row - if (sheetComments != null) - { - CT_CommentList lst = sheetComments.GetCTComments().commentList; - foreach (CT_Comment comment in lst.comment) - { - String strRef = comment.@ref; - CellAddress ref1 = new CellAddress(strRef); + CT_Row ctrow = xrow.GetCTRow(); + short outlineLevel = ctrow.outlineLevel; + ctrow.outlineLevel = (byte)(outlineLevel + 1); + } - // is this comment part of the current row? - if (ref1.Row == rownum) - { - //sheetComments.RemoveComment(strRef); - //vml.RemoveCommentShape(ref1.Row, ref1.Col); - commentsToRemove.Add(ref1); - } - } - } - foreach (CellAddress ref1 in commentsToRemove) - { - sheetComments.RemoveComment(ref1); - vml.RemoveCommentShape(ref1.Row, ref1.Column); - } + SetSheetFormatPrOutlineLevelRow(); + } - // FIXME: (performance optimization) this should be moved outside the for-loop so that hyperlinks only needs to be iterated over once. - // also remove any hyperlinks associated with this row - if (hyperlinks != null) - { - foreach (XSSFHyperlink link in new List(hyperlinks)) - { - CellReference ref1 = new CellReference(link.CellRef); - if (ref1.Row == rownum) - { - hyperlinks.Remove(link); - } - } - } - } - } - foreach (int rowKey in rowsToRemove) + /// + /// + /// + /// the zero based row index to find from + /// + public int FindEndOfRowOutlineGroup(int row) + { + int level = ((XSSFRow)GetRow(row)).GetCTRow().outlineLevel; + int currentRow; + int lastRowNum = LastRowNum; + for (currentRow = row; currentRow < lastRowNum; currentRow++) { - _rows.Remove(rowKey); + if (GetRow(currentRow) == null + || ((XSSFRow)GetRow(currentRow)).GetCTRow().outlineLevel < level) + { + break; + } } - worksheet.sheetData.RemoveRows(ctRowsToRemove); + + return currentRow; } - private void rebuildRows() + + public void UngroupColumn(int fromColumn, int toColumn) { - //rebuild the _rows map - Dictionary map = new Dictionary(); - foreach (XSSFRow r in _rows.Values) + CT_Cols cols = worksheet.GetColsArray(0); + for (int index = fromColumn; index <= toColumn; index++) { - map.Add(r.RowNum, r); - } - _rows.Clear(); - //_rows.putAll(map); - foreach (KeyValuePair kv in map) + CT_Col col = columnHelper.GetColumn(index, false); + if (col != null) + { + short outlineLevel = col.outlineLevel; + col.outlineLevel = (byte)(outlineLevel - 1); + index = (int)col.max; + + if (col.outlineLevel <= 0) + { + int colIndex = columnHelper.GetIndexOfColumn(cols, col); + worksheet.GetColsArray(0).RemoveCol(colIndex); + } + } + } + + worksheet.SetColsArray(0, cols); + SetSheetFormatPrOutlineLevelCol(); + } + + /// + /// Ungroup a range of rows that were previously groupped + /// + /// start row (0-based) + /// end row (0-based) + public void UngroupRow(int fromRow, int toRow) + { + for (int i = fromRow; i <= toRow; i++) + { + XSSFRow xrow = (XSSFRow)GetRow(i); + if (xrow != null) + { + CT_Row ctrow = xrow.GetCTRow(); + short outlinelevel = ctrow.outlineLevel; + ctrow.outlineLevel = (byte)(outlinelevel - 1); + // remove a row only if the row has no cell and if the + // outline level is 0 + if (ctrow.outlineLevel == 0 && xrow.FirstCellNum == -1) + { + RemoveRow(xrow); + } + } + } + + SetSheetFormatPrOutlineLevelRow(); + } + + /// + /// Register a hyperlink in the collection of hyperlinks on this sheet + /// + /// the link to add + public void AddHyperlink(XSSFHyperlink hyperlink) + { + hyperlinks.Add(hyperlink); + } + + /// + /// Removes a hyperlink in the collection of hyperlinks on this sheet + /// + /// row index + /// column index + public void RemoveHyperlink(int row, int column) + { + // CTHyperlinks is regenerated from scratch when writing out the + // spreadsheet so don't worry about maintaining hyperlinks and + // CTHyperlinks in parallel. only maintain hyperlinks + string ref1 = new CellReference(row, column).FormatAsString(); + for (int index = 0; index < hyperlinks.Count; index++) + { + XSSFHyperlink hyperlink = hyperlinks[index]; + if (hyperlink.CellRef.Equals(ref1)) + { + hyperlinks.RemoveAt(index); + return; + } + } + } + + [Obsolete("deprecated 3.14beta2 (circa 2015-12-05). Use {@link #setActiveCell(CellAddress)} instead.")] + public void SetActiveCell(string cellref) + { + CT_Selection ctsel = GetSheetTypeSelection(); + ctsel.activeCell = cellref; + ctsel.SetSqref(new string[] { cellref }); + } + + /// + /// Enable sheet protection + /// + public void EnableLocking() + { + SafeGetProtectionField().sheet = true; + } + + /// + /// Disable sheet protection + /// + public void DisableLocking() + { + SafeGetProtectionField().sheet = false; + } + + /// + /// Enable or disable Autofilters locking. This does not modify sheet + /// protection status. To enforce this un-/locking, call + /// or + /// + /// + public void LockAutoFilter(bool enabled) + { + SafeGetProtectionField().autoFilter = enabled; + } + + /// + /// Enable or disable Deleting columns locking. This does not modify + /// sheet protection status. To enforce this un-/locking, call + /// or + /// + /// + public void LockDeleteColumns(bool enabled) + { + SafeGetProtectionField().deleteColumns = enabled; + } + + /// + /// Enable or disable Deleting rows locking. This does not modify + /// sheet protection status. To enforce this un-/locking, call + /// + /// + /// + public void LockDeleteRows(bool enabled) + { + SafeGetProtectionField().deleteRows = enabled; + } + + /// + /// Enable or disable Formatting cells locking. This does not modify + /// sheet protection status. To enforce this un-/locking, call + /// or + /// + /// + public void LockFormatCells(bool enabled) + { + SafeGetProtectionField().formatCells = enabled; + } + + /// + /// Enable or disable Formatting columns locking. This does not modify + /// sheet protection status. To enforce this un-/locking, call + /// or + /// + /// + public void LockFormatColumns(bool enabled) + { + SafeGetProtectionField().formatColumns = enabled; + } + + /// + /// Enable or disable Formatting rows locking. This does not modify + /// sheet protection status. To enforce this un-/locking, call + /// or + /// + /// + public void LockFormatRows(bool enabled) + { + SafeGetProtectionField().formatRows = enabled; + } + + /// + /// Enable or disable Inserting columns locking. This does not modify + /// sheet protection status. To enforce this un-/locking, call + /// or + /// + /// + public void LockInsertColumns(bool enabled) + { + SafeGetProtectionField().insertColumns = enabled; + } + + /// + /// Enable or disable Inserting hyperlinks locking. This does not + /// modify sheet protection status. To enforce this un-/locking, call + /// or + /// + /// + public void LockInsertHyperlinks(bool enabled) + { + SafeGetProtectionField().insertHyperlinks = enabled; + } + + /// + /// Enable or disable Inserting rows locking. This does not modify + /// sheet protection status. To enforce this un-/locking, call + /// or + /// + /// + public void LockInsertRows(bool enabled) + { + SafeGetProtectionField().insertRows = enabled; + } + + /// + /// Enable or disable Pivot Tables locking. This does not modify sheet + /// protection status. To enforce this un-/locking, call + /// or + /// + /// + public void LockPivotTables(bool enabled) + { + SafeGetProtectionField().pivotTables = enabled; + } + + /// + /// Enable or disable Sort locking. This does not modify sheet + /// protection status. To enforce this un-/locking, call + /// or + /// + /// + public void LockSort(bool enabled) + { + SafeGetProtectionField().sort = enabled; + } + + /// + /// Enable or disable Objects locking. This does not modify sheet + /// protection status. To enforce this un-/locking, call + /// or + /// + /// + public void LockObjects(bool enabled) + { + SafeGetProtectionField().objects = enabled; + } + + /// + /// Enable or disable Scenarios locking. This does not modify sheet + /// protection status. To enforce this un-/locking, call + /// or + /// + /// + public void LockScenarios(bool enabled) + { + SafeGetProtectionField().scenarios = enabled; + } + + /// + /// Enable or disable Selection of locked cells locking. This does not + /// modify sheet protection status. To enforce this un-/locking, call + /// + /// + /// + public void LockSelectLockedCells(bool enabled) + { + SafeGetProtectionField().selectLockedCells = enabled; + } + + /// + /// Enable or disable Selection of unlocked cells locking. This does + /// not modify sheet protection status. To enforce this un-/locking, + /// call or + /// + /// + public void LockSelectUnlockedCells(bool enabled) + { + SafeGetProtectionField().selectUnlockedCells = enabled; + } + + public ICellRange SetArrayFormula(string formula, CellRangeAddress range) + { + + ICellRange cr = GetCellRange(range); + + ICell mainArrayFormulaCell = cr.TopLeftCell; + ((XSSFCell)mainArrayFormulaCell).SetCellArrayFormula(formula, range); + arrayFormulas.Add(range); + return cr; + } + + public ICellRange RemoveArrayFormula(ICell cell) + { + if (cell.Sheet != this) + { + throw new ArgumentException("Specified cell does not belong " + + "to this sheet."); + } + + foreach (CellRangeAddress range in arrayFormulas) + { + if (range.IsInRange(cell.RowIndex, cell.ColumnIndex)) + { + arrayFormulas.Remove(range); + ICellRange cr = GetCellRange(range); + foreach (ICell c in cr) + { + c.SetCellType(CellType.Blank); + } + + return cr; + } + } + + string ref1 = ((XSSFCell)cell).GetCTCell().r; + throw new ArgumentException( + "Cell " + ref1 + " is not part of an array formula."); + } + + public IDataValidationHelper GetDataValidationHelper() + { + return dataValidationHelper; + } + + //YK: GetXYZArray() array accessors are deprecated in xmlbeans with JDK 1.5 support + public List GetDataValidations() + { + List xssfValidations = new List(); + CT_DataValidations dataValidations = worksheet.dataValidations; + if (dataValidations != null && dataValidations.count > 0) + { + foreach (CT_DataValidation ctDataValidation in dataValidations.dataValidation) + { + CellRangeAddressList addressList = new CellRangeAddressList(); + + string[] regions = ctDataValidation.sqref.Split(new char[] { ' ' }); + for (int i = 0; i < regions.Length; i++) + { + if (regions[i].Length == 0) + { + continue; + } + + string[] parts = regions[i].Split(new char[] { ':' }); + CellReference begin = new CellReference(parts[0]); + CellReference end = parts.Length > 1 + ? new CellReference(parts[1]) + : begin; + CellRangeAddress cellRangeAddress = new CellRangeAddress( + begin.Row, + end.Row, + begin.Col, + end.Col); + addressList.AddCellRangeAddress(cellRangeAddress); + } + + XSSFDataValidation xssfDataValidation = + new XSSFDataValidation(addressList, ctDataValidation); + xssfValidations.Add(xssfDataValidation); + } + } + + return xssfValidations; + } + + public void AddValidationData(IDataValidation dataValidation) + { + XSSFDataValidation xssfDataValidation = (XSSFDataValidation)dataValidation; + CT_DataValidations dataValidations = worksheet.dataValidations; + if (dataValidations == null) + { + dataValidations = worksheet.AddNewDataValidations(); + } + + int currentCount = dataValidations.sizeOfDataValidationArray(); + CT_DataValidation newval = dataValidations.AddNewDataValidation(); + newval.Set(xssfDataValidation.GetCTDataValidation()); + dataValidations.count = (uint)currentCount + 1; + + } + + public IAutoFilter SetAutoFilter(CellRangeAddress range) + { + CT_AutoFilter af = worksheet.autoFilter; + if (af == null) + { + af = worksheet.AddNewAutoFilter(); + } + + CellRangeAddress norm = new CellRangeAddress(range.FirstRow, range.LastRow, + range.FirstColumn, range.LastColumn); + string ref1 = norm.FormatAsString(); + af.@ref = ref1; + + XSSFWorkbook wb = (XSSFWorkbook)Workbook; + int sheetIndex = Workbook.GetSheetIndex(this); + XSSFName name = wb.GetBuiltInName(XSSFName.BUILTIN_FILTER_DB, sheetIndex); + if (name == null) + { + name = wb.CreateBuiltInName(XSSFName.BUILTIN_FILTER_DB, sheetIndex); + } + + name.GetCTName().hidden = true; + CellReference r1 = new CellReference(SheetName, range.FirstRow, range.FirstColumn, true, true); + CellReference r2 = new CellReference(null, range.LastRow, range.LastColumn, true, true); + string fmla = r1.FormatAsString() + ":" + r2.FormatAsString(); + name.RefersToFormula = fmla; + + return new XSSFAutoFilter(this); + } + + /// + /// Creates a new Table, and associates it with this Sheet + /// + /// + public XSSFTable CreateTable() + { + if (!worksheet.IsSetTableParts()) + { + worksheet.AddNewTableParts(); + } + + CT_TableParts tblParts = worksheet.tableParts; + CT_TablePart tbl = tblParts.AddNewTablePart(); + + // Table numbers need to be unique in the file, not just + // unique within the sheet. Find the next one + int tableNumber = GetPackagePart().Package + .GetPartsByContentType(XSSFRelation.TABLE.ContentType).Count + 1; + RelationPart rp = CreateRelationship( + XSSFRelation.TABLE, + XSSFFactory.GetInstance(), + tableNumber, + false); + XSSFTable table = rp.DocumentPart as XSSFTable; + tbl.id = rp.Relationship.Id; + + tables[tbl.id] = table; + + return table; + } + + /// + /// Returns any tables associated with this Sheet + /// + /// + public List GetTables() + { + List tableList = new List( + tables.Values + ); + return tableList; + } + + /// + /// Set background color of the sheet tab + /// + /// the indexed color to set, must be a + /// constant from + [Obsolete("deprecated 3.15-beta2. Removed in 3.17. Use {@link #setTabColor(XSSFColor)}.")] + public void SetTabColor(int colorIndex) + { + CT_SheetPr pr = worksheet.sheetPr; + if (pr == null) + { + pr = worksheet.AddNewSheetPr(); + } + + CT_Color color = new CT_Color + { + indexed = (uint)colorIndex + }; + pr.tabColor = color; + } + + #region ISheet Members + public IDrawing DrawingPatriarch + { + get + { + if (drawing == null) + { + OpenXmlFormats.Spreadsheet.CT_Drawing ctDrawing = GetCTDrawing(); + if (ctDrawing == null) + { + return null; + } + + foreach (RelationPart rp in RelationParts) + { + POIXMLDocumentPart p = rp.DocumentPart; + if (p is XSSFDrawing dr) + { + string drId = rp.Relationship.Id; + if (drId.Equals(ctDrawing.id)) + { + drawing = dr; + break; + } + + break; + } + } + } + + return drawing; + } + } + + public IEnumerator GetEnumerator() + { + return _rows.Values.GetEnumerator(); + } + + public IEnumerator GetRowEnumerator() + { + return GetEnumerator(); + } + + public bool IsActive + { + get + { + return IsSelected; + } + set + { + IsSelected = value; + } + } + + public bool IsMergedRegion(CellRangeAddress mergedRegion) + { + if (worksheet.mergeCells == null || worksheet.mergeCells.mergeCell == null) + { + return false; + } + + foreach (CT_MergeCell mc in worksheet.mergeCells.mergeCell) + { + if (!string.IsNullOrEmpty(mc.@ref)) + { + CellRangeAddress range = CellRangeAddress.ValueOf(mc.@ref); + if (range.FirstColumn <= mergedRegion.FirstColumn + && range.LastColumn >= mergedRegion.LastColumn + && range.FirstRow <= mergedRegion.FirstRow + && range.LastRow >= mergedRegion.LastRow) + { + return true; + } + } + } + + return false; + } + public void SetActive(bool value) + { + IsSelected = value; + } + + public void SetActiveCellRange(List cellranges, int activeRange, int activeRow, int activeColumn) + { + throw new NotImplementedException(); + } + + public void SetActiveCellRange(int firstRow, int lastRow, int firstColumn, int lastColumn) + { + throw new NotImplementedException(); + } + + public short TabColorIndex + { + get + { + throw new NotImplementedException("Use XSSFSheet.TabColor instead"); + } + set + { + throw new NotImplementedException("Use XSSFSheet.TabColor instead"); + } + } + + public bool IsRightToLeft + { + get + { + CT_SheetView view = GetDefaultSheetView(); + return view != null && view.rightToLeft; + } + set + { + CT_SheetView view = GetDefaultSheetView(); + view.rightToLeft = value; + } + } + #endregion + + public IRow CopyRow(int sourceIndex, int targetIndex) + { + return SheetUtil.CopyRow(this, sourceIndex, targetIndex); + } + + public void ShowInPane(int toprow, int leftcol) + { + CellReference cellReference = new CellReference(toprow, leftcol); + string cellRef = cellReference.FormatAsString(); + Pane.topLeftCell = cellRef; + } + + public ISheet CopySheet(string Name) + { + return CopySheet(Name, true); + } + + public ISheet CopySheet(string name, bool copyStyle) + { + string clonedName = SheetUtil.GetUniqueSheetName(Workbook, name); + XSSFSheet clonedSheet = (XSSFSheet)Workbook.CreateSheet(clonedName); + + try + { + using (MemoryStream ms = RecyclableMemory.GetStream()) + { + Write(ms, true); + ms.Position = 0; + clonedSheet.Read(ms); + } + } + catch (IOException e) + { + throw new POIXMLException("Failed to clone sheet", e); + } + + CT_Worksheet ct = clonedSheet.GetCTWorksheet(); + if (ct.IsSetLegacyDrawing()) + { + logger.Log(POILogger.WARN, "Cloning sheets with comments is not yet supported."); + ct.UnsetLegacyDrawing(); + } + + clonedSheet.IsSelected = false; + + // copy sheet's relations + List rels = GetRelations(); + // if the sheet being cloned has a drawing then remember it and re-create too + XSSFDrawing dg = null; + foreach (POIXMLDocumentPart r in rels) + { + // do not copy the drawing relationship, it will be re-created + if (r is XSSFDrawing dr) + { + dg = dr; + continue; + } + //skip printerSettings.bin part + if (r.GetPackagePart().PartName.Name.StartsWith("/xl/printerSettings/printerSettings")) + { + continue; + } + + PackageRelationship rel = r.GetPackageRelationship(); + clonedSheet.GetPackagePart().AddRelationship( + rel.TargetUri, (TargetMode)rel.TargetMode, rel.RelationshipType); + clonedSheet.AddRelation(rel.Id, r); + } + + // copy hyperlinks + clonedSheet.hyperlinks = new List(hyperlinks); + + // clone the sheet drawing along with its relationships + if (dg != null) + { + if (ct.IsSetDrawing()) + { + // unset the existing reference to the drawing, so that + // subsequent call of clonedSheet.createDrawingPatriarch() + // will create a new one + ct.UnsetDrawing(); + } + + XSSFDrawing clonedDg = clonedSheet.CreateDrawingPatriarch() as XSSFDrawing; + // copy drawing contents + clonedDg.GetCTDrawing().Set(dg.GetCTDrawing()); + + clonedDg = clonedSheet.CreateDrawingPatriarch() as XSSFDrawing; + + // Clone drawing relations + List srcRels = dg.GetRelations(); + foreach (POIXMLDocumentPart rel in srcRels) + { + PackageRelationship relation = rel.GetPackageRelationship(); + clonedDg.AddRelation(relation.Id, rel); + clonedDg + .GetPackagePart() + .AddRelationship(relation.TargetUri, relation.TargetMode.Value, + relation.RelationshipType, relation.Id); + } + } + + return clonedSheet; + } + + public void CopyTo(IWorkbook dest, string name, bool copyStyle, bool keepFormulas) + { + StylesTable styles = ((XSSFWorkbook)dest).GetStylesSource(); + if (copyStyle && Workbook.NumberOfFonts > 0) + { + foreach (XSSFFont font in ((XSSFWorkbook)Workbook).GetStylesSource().GetFonts()) + { + styles.PutFont(font); + } + } + + XSSFSheet newSheet = (XSSFSheet)dest.CreateSheet(name); + newSheet.sheet.state = sheet.state; + IDictionary styleMap = copyStyle ? new Dictionary() : null; + for (int i = FirstRowNum; i <= LastRowNum; i++) + { + XSSFRow srcRow = (XSSFRow)GetRow(i); + XSSFRow destRow = (XSSFRow)newSheet.CreateRow(i); + if (srcRow != null) + { + CopyRow(this, newSheet, srcRow, destRow, styleMap, keepFormulas); + } + } + + List srcCols = worksheet.GetColsList(); + List dstCols = newSheet.worksheet.GetColsList(); + dstCols.Clear(); //Should already be empty since this is a new sheet. + foreach (CT_Cols srcCol in srcCols) + { + CT_Cols dstCol = new CT_Cols(); + foreach (CT_Col column in srcCol.col) + { + dstCol.col.Add(column.Copy()); + } + + dstCols.Add(dstCol); + } + + newSheet.ForceFormulaRecalculation = true; + newSheet.PrintSetup.Landscape = PrintSetup.Landscape; + newSheet.PrintSetup.HResolution = PrintSetup.HResolution; + newSheet.PrintSetup.VResolution = PrintSetup.VResolution; + newSheet.SetMargin( + MarginType.LeftMargin, + GetMargin(MarginType.LeftMargin)); + newSheet.SetMargin( + MarginType.RightMargin, + GetMargin(MarginType.RightMargin)); + newSheet.SetMargin( + MarginType.TopMargin, + GetMargin(MarginType.TopMargin)); + newSheet.SetMargin( + MarginType.BottomMargin, + GetMargin(MarginType.BottomMargin)); + newSheet.PrintSetup.HeaderMargin = PrintSetup.HeaderMargin; + newSheet.PrintSetup.FooterMargin = PrintSetup.FooterMargin; + newSheet.Header.Left = Header.Left; + newSheet.Header.Center = Header.Center; + newSheet.Header.Right = Header.Right; + newSheet.Footer.Left = Footer.Left; + newSheet.Footer.Center = Footer.Center; + newSheet.Footer.Right = Footer.Right; + newSheet.PrintSetup.Scale = PrintSetup.Scale; + newSheet.PrintSetup.FitHeight = PrintSetup.FitHeight; + newSheet.PrintSetup.FitWidth = PrintSetup.FitWidth; + newSheet.DisplayGridlines = DisplayGridlines; + if (worksheet.IsSetSheetPr()) { - _rows.Add(kv.Key, kv.Value); + newSheet.worksheet.sheetPr = worksheet.sheetPr.Clone(); } - // Sort CTRows by index asc. - // not found at poi 3.15 - if (worksheet.sheetData.row != null) - worksheet.sheetData.row.Sort((row1, row2) => row1.r.CompareTo(row2.r)); + if (GetDefaultSheetView().pane != null) + { + CT_Pane oldPane = GetDefaultSheetView().pane; + CT_Pane newPane = newSheet.GetPane(); + newPane.activePane = oldPane.activePane; + newPane.state = oldPane.state; + newPane.topLeftCell = oldPane.topLeftCell; + newPane.xSplit = oldPane.xSplit; + newPane.ySplit = oldPane.ySplit; + } + + CopySheetImages(dest as XSSFWorkbook, newSheet); + } + + public XSSFWorkbook GetWorkbook() + { + return (XSSFWorkbook)GetParent(); } - private void shiftCommentsAndRows(int startRow, int endRow, int n, bool copyRowHeight) + + /// + /// Create a pivot table using the AreaReference range on sourceSheet, + /// at the given position. If the source reference contains a sheet + /// name, it must match the sourceSheet + /// + /// location of pivot data + /// A reference to the top left cell where the + /// pivot table will start + /// The sheet containing the source data, if + /// the source reference doesn't contain a sheet name + /// The pivot table + /// if source references a sheet + /// different than sourceSheet + public XSSFPivotTable CreatePivotTable(AreaReference source, CellReference position, ISheet sourceSheet) { - SortedDictionary commentsToShift = new SortedDictionary(new ShiftCommentComparator(n)); + string sourceSheetName = source.FirstCell.SheetName; + if (sourceSheetName != null + && !sourceSheetName.Equals(sourceSheet.SheetName, StringComparison.InvariantCultureIgnoreCase)) + { + throw new ArgumentException( + "The area is referenced in another sheet than the defined" + + " source sheet " + sourceSheet.SheetName + "."); + } - foreach (KeyValuePair rowDict in _rows) + XSSFPivotTable.IPivotTableReferenceConfigurator refConfig = new PivotTableReferenceConfigurator1(source); + return CreatePivotTable(position, sourceSheet, refConfig); + } + + /// + /// Create a pivot table using the AreaReference range, at the given + /// position. If the source reference contains a sheet name, that sheet + /// is used, otherwise this sheet is assumed as the source sheet. + /// + /// location of pivot data + /// A reference to the top left cell where the + /// pivot table will start + /// The pivot table + public XSSFPivotTable CreatePivotTable(AreaReference source, CellReference position) + { + string sourceSheetName = source.FirstCell.SheetName; + if (sourceSheetName != null && !sourceSheetName.Equals(SheetName, StringComparison.InvariantCultureIgnoreCase)) { - XSSFRow row = rowDict.Value; - int rownum = row.RowNum; + XSSFSheet sourceSheet = Workbook.GetSheet(sourceSheetName) as XSSFSheet; + return CreatePivotTable(source, position, sourceSheet); + } - if (sheetComments != null) + return CreatePivotTable(source, position, this); + } + + /// + /// Create a pivot table using the Name range reference on sourceSheet, + /// at the given position. If the source reference contains a sheet + /// name, it must match the sourceSheet + /// + /// location of pivot data + /// A reference to the top left cell where the + /// pivot table will start + /// The sheet containing the source data, + /// if the source reference doesn't contain a sheet name + /// The pivot table + /// + public XSSFPivotTable CreatePivotTable(IName source, CellReference position, ISheet sourceSheet) + { + if (source.SheetName != null + && !source.SheetName.Equals(sourceSheet.SheetName)) + { + throw new ArgumentException( + "The named range references another sheet than the defined" + + " source sheet " + sourceSheet.SheetName + "."); + } + + return CreatePivotTable(position, sourceSheet, new PivotTableReferenceConfigurator2(source)); + } + + /// + /// Create a pivot table using the Name range, at the given position. + /// If the source reference contains a sheet name, that sheet is used, + /// otherwise this sheet is assumed as the source sheet. + /// + /// location of pivot data + /// A reference to the top left cell where the + /// pivot table will start + /// The pivot table + public XSSFPivotTable CreatePivotTable(IName source, CellReference position) + { + return CreatePivotTable( + source, + position, + GetWorkbook().GetSheet(source.SheetName)); + } + + /// + /// Create a pivot table using the Table, at the given position. Tables + /// are required to have a sheet reference, so no additional logic + /// around reference sheet is needed. + /// + /// location of pivot data + /// A reference to the top left cell where the + /// pivot table will start + /// The pivot table + public XSSFPivotTable CreatePivotTable(ITable source, CellReference position) + { + return CreatePivotTable( + position, + GetWorkbook().GetSheet(source.SheetName), + new PivotTableReferenceConfigurator3(source)); + } + + /// + /// Returns all the pivot tables for this Sheet + /// + /// + public List GetPivotTables() + { + List tables = new List(); + foreach (XSSFPivotTable table in GetWorkbook().PivotTables) + { + if (table.GetParent() == this) { - // calculate the new rownum - int newrownum = ShiftedRowNum(startRow, endRow, n, rownum); + tables.Add(table); + } + } - // is there a change necessary for the current row? - if (newrownum != rownum) + return tables; + } + + public int GetColumnOutlineLevel(int columnIndex) + { + CT_Col col = columnHelper.GetColumn(columnIndex, false); + if (col == null) + { + return 0; + } + + return col.outlineLevel; + } + + public bool IsDate1904() + { + throw new NotImplementedException(); + } + + /// + /// Add ignored errors (usually to suppress them in the UI of a + /// consuming application). + /// + /// Cell + /// Types of error to ignore there. + public void AddIgnoredErrors(CellReference cell, params IgnoredErrorType[] ignoredErrorTypes) + { + AddIgnoredErrors(cell.FormatAsString(), ignoredErrorTypes); + } + + /// + /// Ignore errors across a range of cells. + /// + /// Range of cells. + /// Types of error to ignore there. + public void AddIgnoredErrors(CellRangeAddress region, params IgnoredErrorType[] ignoredErrorTypes) + { + region.Validate(SpreadsheetVersion.EXCEL2007); + AddIgnoredErrors(region.FormatAsString(), ignoredErrorTypes); + } + + /// + /// Returns the errors currently being ignored and the ranges where + /// they are ignored. + /// + /// Map of error type to the range(s) where they are ignored. + public Dictionary> GetIgnoredErrors() + { + Dictionary> result = new Dictionary>(); + if (worksheet.IsSetIgnoredErrors()) + { + foreach (CT_IgnoredError err in worksheet.ignoredErrors.ignoredError) + { + foreach (IgnoredErrorType errType in GetErrorTypes(err)) { - var commentAddresses = sheetComments.GetCellAddresses(); - foreach (var cellAddress in commentAddresses) + if (!result.ContainsKey(errType)) { - if (cellAddress.Row == rownum) - { - XSSFComment oldComment = sheetComments.FindCellComment(cellAddress); - if (oldComment!=null) - { - XSSFComment xssfComment = new XSSFComment(sheetComments, oldComment.GetCTComment(), - oldComment.GetCTShape()); - if (commentsToShift.ContainsKey(xssfComment)) - commentsToShift[xssfComment] = newrownum; - else - commentsToShift.Add(xssfComment, newrownum); - } - } + result.Add(errType, new HashSet()); + } + + foreach (object ref1 in err.sqref) + { + result[errType].Add(CellRangeAddress.ValueOf(ref1.ToString())); } } } + } - if (rownum < startRow || rownum > endRow) continue; + return result; + } - if (!copyRowHeight) + /// + /// Copies comment from one cell to another + /// + /// Cell with a comment to copy + /// Cell to paste the comment to + /// Copied comment + public IComment CopyComment(ICell sourceCell, ICell targetCell) + { + ValidateCellsForCopyComment(sourceCell, targetCell); + + XSSFComment sourceComment = + sheetComments.FindCellComment(sourceCell.Address); + CT_Comment sourceCtComment = sourceComment.GetCTComment(); + CT_Shape sourceCommentShape = GetVMLDrawing(false).FindCommentShape( + sourceComment.Row, + sourceComment.Column); + + CT_Comment targetCtComment = sheetComments.NewComment(targetCell.Address); + targetCtComment.Set(sourceCtComment); + + CT_Shape targetCommentShape = GetVMLDrawing(false).newCommentShape(); + targetCommentShape.Set(sourceCommentShape); + + IComment targetComment = new XSSFComment( + sheetComments, + targetCtComment, + targetCommentShape); + + targetCell.CellComment = targetComment; + + return targetComment; + } + #endregion + + #region Private methods + /// + /// Read hyperlink relations, link them with CT_Hyperlink beans in this + /// worksheet and Initialize the internal array of XSSFHyperlink objects + /// + /// + //YK: GetXYZArray() array accessors are deprecated in xmlbeans with JDK 1.5 support + private void InitHyperlinks() + { + hyperlinks = new List(); + + if (!worksheet.IsSetHyperlinks()) + { + return; + } + + try + { + PackageRelationshipCollection hyperRels = + GetPackagePart().GetRelationshipsByType(XSSFRelation.SHEET_HYPERLINKS.Relation); + + // Turn each one into a XSSFHyperlink + foreach (CT_Hyperlink hyperlink in worksheet.hyperlinks.hyperlink) { - row.Height = (/*setter*/(short)-1); - } + PackageRelationship hyperRel = null; + if (hyperlink.id != null) + { + hyperRel = hyperRels.GetRelationshipByID(hyperlink.id); + } - row.Shift(n); + hyperlinks.Add(new XSSFHyperlink(hyperlink, hyperRel)); + } + } + catch (InvalidFormatException e) + { + throw new POIXMLException(e); } + } - // adjust all the affected comment-structures now - // the Map is sorted and thus provides them in the order that we need here, - // i.e. from down to up if Shifting down, vice-versa otherwise - foreach (KeyValuePair entry in commentsToShift) + private void InitRows(CT_Worksheet worksheetParam) + { + _rows.Clear(); + tables = new Dictionary(); + sharedFormulas = new Dictionary(); + arrayFormulas = new List(); + if (0 < worksheetParam.sheetData.SizeOfRowArray()) { - entry.Key.Row = entry.Value; + foreach (CT_Row row in worksheetParam.sheetData.row) + { + XSSFRow r = new XSSFRow(row, this); + if (!_rows.ContainsKey(r.RowNum)) + { + _rows.Add(r.RowNum, r); + } + } } - rebuildRows(); } - private class ShiftCommentComparator : IComparer + + /// + /// Create a new CT_Worksheet instance with all values set to defaults + /// + /// a new instance + private static CT_Worksheet NewSheet() + { + CT_Worksheet worksheet = new CT_Worksheet(); + CT_SheetFormatPr ctFormat = worksheet.AddNewSheetFormatPr(); + ctFormat.defaultRowHeight = DEFAULT_ROW_HEIGHT; + + CT_SheetView ctView = worksheet.AddNewSheetViews().AddNewSheetView(); + ctView.workbookViewId = 0; + + worksheet.AddNewDimension().@ref = "A1"; + + worksheet.AddNewSheetData(); + + CT_PageMargins ctMargins = worksheet.AddNewPageMargins(); + ctMargins.bottom = DEFAULT_MARGIN_BOTTOM; + ctMargins.footer = DEFAULT_MARGIN_FOOTER; + ctMargins.header = DEFAULT_MARGIN_HEADER; + ctMargins.left = DEFAULT_MARGIN_LEFT; + ctMargins.right = DEFAULT_MARGIN_RIGHT; + ctMargins.top = DEFAULT_MARGIN_TOP; + + return worksheet; + } + + /// + /// Adds a merged region of cells (hence those cells form one). + /// + /// region (rowfrom/colfrom-rowto/colto) to merge + /// whether to validate merged region + /// index of this region + /// if region intersects + /// with a multi-cell array formula or if region intersects with an + /// existing region on this sheet + /// if region contains fewer + /// than 2 cells + private int AddMergedRegion(CellRangeAddress region, bool validate) { - private int shiftDir; - public ShiftCommentComparator(int shiftDir) + if (region.NumberOfCells < 2) { - this.shiftDir = shiftDir; + throw new ArgumentException("Merged region " + + region.FormatAsString() + " must contain 2 or more cells"); } - public int Compare(XSSFComment o1, XSSFComment o2) + + region.Validate(SpreadsheetVersion.EXCEL2007); + if (validate) { - int row1 = o1.Row; - int row2 = o2.Row; + // throw InvalidOperationException if the argument + // CellRangeAddress intersects with a multi-cell array formula + // defined in this sheet + ValidateArrayFormulas(region); + // Throw InvalidOperationException if the argument + // CellRangeAddress intersects with a merged region already + // in this sheet + ValidateMergedRegions(region); + } - if (row1 == row2) - { - // ordering is not important when row is equal, but don't return zero to still - // get multiple comments per row into the map - return o1.GetHashCode() - o2.GetHashCode(); - } + CT_MergeCells ctMergeCells = worksheet.IsSetMergeCells() + ? worksheet.mergeCells + : worksheet.AddNewMergeCells(); + CT_MergeCell ctMergeCell = ctMergeCells.AddNewMergeCell(); + ctMergeCell.@ref = region.FormatAsString(); + return ctMergeCells.sizeOfMergeCellArray(); + } - // when Shifting down, sort higher row-values first - if (shiftDir > 0) + /// + /// Verify that the candidate region does not intersect with an + /// existing multi-cell array formula in this sheet + /// + /// + /// if candidate region + /// intersects an existing array formula in this sheet + private void ValidateArrayFormulas(CellRangeAddress region) + { + // FIXME: this may be faster if it looped over array formulas + // directly rather than looping over each cell in the region and + // searching if that cell belongs to an array formula + int firstRow = region.FirstRow; + int firstColumn = region.FirstColumn; + int lastRow = region.LastRow; + int lastColumn = region.LastColumn; + // for each cell in sheet, if cell belongs to an array formula, + // check if merged region intersects array formula cells + for (int rowIn = firstRow; rowIn <= lastRow; rowIn++) + { + IRow row = GetRow(rowIn); + if (row == null) { - return row1 < row2 ? 1 : -1; + continue; } - else + + for (int colIn = firstColumn; colIn <= lastColumn; colIn++) { - // sort lower-row values first when Shifting up - return row1 > row2 ? 1 : -1; + ICell cell = row.GetCell(colIn); + if (cell == null) + { + continue; + } + + if (cell.IsPartOfArrayFormulaGroup) + { + CellRangeAddress arrayRange = cell.ArrayFormulaRange; + if (arrayRange.NumberOfCells > 1 && region.Intersects(arrayRange)) + { + string msg = "The range " + region.FormatAsString() + + " intersects with a multi-cell array formula. " + + "You cannot merge cells of an array."; + throw new InvalidOperationException(msg); + } + } } } } - private int ShiftedRowNum(int startRow, int endRow, int n, int rownum) - { - // no change if before any affected row - if (rownum < startRow && (n > 0 || (startRow - rownum) > n)) - { - return rownum; - } - // no change if After any affected row - if (rownum > endRow && (n < 0 || (rownum - endRow) > n)) - { - return rownum; - } - - // row before and things are Moved up - if (rownum < startRow) + /// + /// Verify that none of the merged regions intersect a multi-cell array + /// formula in this sheet + /// + private void CheckForMergedRegionsIntersectingArrayFormulas() + { + foreach (CellRangeAddress region in MergedRegions) { - // row is Moved down by the Shifting - return rownum + (endRow - startRow); + ValidateArrayFormulas(region); } + } - // row is After and things are Moved down - if (rownum > endRow) + /// + /// Verify that candidate region does not intersect with an existing + /// merged region in this sheet + /// + /// + /// if candidate region + /// intersects an existing merged region in this sheet + private void ValidateMergedRegions(CellRangeAddress candidateRegion) + { + foreach (CellRangeAddress existingRegion in MergedRegions) { - // row is Moved up by the Shifting - return rownum - (endRow - startRow); + if (existingRegion.Intersects(candidateRegion)) + { + throw new InvalidOperationException( + "Cannot add merged region " + candidateRegion.FormatAsString() + + " to sheet because it overlaps with an existing merged " + + "region (" + existingRegion.FormatAsString() + ")."); + } } - - // row is part of the Shifted block - return rownum + n; } - public void UngroupColumn(int fromColumn, int toColumn) + /// + /// Verify that no merged regions intersect another merged + /// region in this sheet. + /// + /// if at least one region + /// intersects with another merged region in this sheet + private void CheckForIntersectingMergedRegions() { - CT_Cols cols = worksheet.GetColsArray(0); - for (int index = fromColumn; index <= toColumn; index++) + List regions = MergedRegions; + int size = regions.Count; + for (int i = 0; i < size; i++) { - CT_Col col = columnHelper.GetColumn(index, false); - if (col != null) + CellRangeAddress region = regions[i]; + foreach (CellRangeAddress other in regions.Skip(i + 1)) //regions.subList(i+1, regions.size() { - short outlineLevel = col.outlineLevel; - col.outlineLevel = (byte)(outlineLevel - 1); - index = (int)col.max; - - if (col.outlineLevel <= 0) + if (region.Intersects(other)) { - int colIndex = columnHelper.GetIndexOfColumn(cols, col); - worksheet.GetColsArray(0).RemoveCol(colIndex); + string msg = "The range " + region.FormatAsString() + + " intersects with another merged region " + + other.FormatAsString() + " in this sheet"; + throw new InvalidOperationException(msg); } } } - worksheet.SetColsArray(0, cols); - SetSheetFormatPrOutlineLevelCol(); } - /** - * Ungroup a range of rows that were previously groupped - * - * @param fromRow start row (0-based) - * @param toRow end row (0-based) - */ - public void UngroupRow(int fromRow, int toRow) + private int GetLastKey(IList keys) { - for (int i = fromRow; i <= toRow; i++) + _ = keys.Count; + return keys[keys.Count - 1]; + } + + private int HeadMapCount(IList keys, int rownum) + { + int count = 0; + foreach (int key in keys) { - XSSFRow xrow = (XSSFRow)GetRow(i); - if (xrow != null) + if (key < rownum) { - CT_Row ctrow = xrow.GetCTRow(); - short outlinelevel = ctrow.outlineLevel; - ctrow.outlineLevel = (byte)(outlinelevel - 1); - //remove a row only if the row has no cell and if the outline level is 0 - if (ctrow.outlineLevel == 0 && xrow.FirstCellNum == -1) - { - RemoveRow(xrow); - } + count++; + } + else + { + break; } } - SetSheetFormatPrOutlineLevelRow(); - } - private void SetSheetFormatPrOutlineLevelRow() - { - short maxLevelRow = GetMaxOutlineLevelRows(); - GetSheetTypeSheetFormatPr().outlineLevelRow = (byte)maxLevelRow; + return count; } - private void SetSheetFormatPrOutlineLevelCol() + private CT_SheetFormatPr GetSheetTypeSheetFormatPr() { - short maxLevelCol = GetMaxOutlineLevelCols(); - GetSheetTypeSheetFormatPr().outlineLevelCol = (byte)maxLevelCol; + return worksheet.IsSetSheetFormatPr() ? + worksheet.sheetFormatPr : + worksheet.AddNewSheetFormatPr(); } - private CT_SheetViews GetSheetTypeSheetViews() + private CT_SheetPr GetSheetTypeSheetPr() { - if (worksheet.sheetViews == null) + if (worksheet.sheetPr == null) { - worksheet.sheetViews = new CT_SheetViews(); - worksheet.sheetViews.AddNewSheetView(); + worksheet.sheetPr = new CT_SheetPr(); } - return worksheet.sheetViews; + + return worksheet.sheetPr; } - /** - * Returns a flag indicating whether this sheet is selected. - *

- * When only 1 sheet is selected and active, this value should be in synch with the activeTab value. - * In case of a conflict, the Start Part Setting wins and Sets the active sheet tab. - *

- * Note: multiple sheets can be selected, but only one sheet can be active at one time. - * - * @return true if this sheet is selected - */ - public bool IsSelected + private CT_HeaderFooter GetSheetTypeHeaderFooter() { - get - { - CT_SheetView view = GetDefaultSheetView(); - return view != null && view.tabSelected; - } - set + if (worksheet.headerFooter == null) { - CT_SheetViews views = GetSheetTypeSheetViews(); - foreach (CT_SheetView view in views.sheetView) - { - view.tabSelected = (value); - } + worksheet.headerFooter = new CT_HeaderFooter(); } - } - - /** - * Register a hyperlink in the collection of hyperlinks on this sheet - * - * @param hyperlink the link to add - */ - public void AddHyperlink(XSSFHyperlink hyperlink) - { - hyperlinks.Add(hyperlink); + return worksheet.headerFooter; } - /** - * Removes a hyperlink in the collection of hyperlinks on this sheet - * - * @param row row index - * @param column column index - */ - public void RemoveHyperlink(int row, int column) + /// + /// returns all rows between startRow and endRow, inclusive. Rows + /// between startRow and endRow that haven't been created are not + /// included in result unless createRowIfMissing is true + /// + /// the first row number in this + /// sheet to return + /// the last row number in this + /// sheet to return + /// + /// + /// if startRowNum and endRowNum + /// are not in ascending order + private List GetRows(int startRowNum, int endRowNum, bool createRowIfMissing) { - // CTHyperlinks is regenerated from scratch when writing out the spreadsheet - // so don't worry about maintaining hyperlinks and CTHyperlinks in parallel. - // only maintain hyperlinks - String ref1 = new CellReference(row, column).FormatAsString(); - for (int index = 0; index < hyperlinks.Count; index++) + if (startRowNum > endRowNum) { - XSSFHyperlink hyperlink = hyperlinks[index]; - if (hyperlink.CellRef.Equals(ref1)) - { - hyperlinks.RemoveAt(index); - return; - } + throw new ArgumentException("getRows: startRowNum must be " + + "less than or equal to endRowNum"); } - } - /** - * Return location of the active cell, e.g. A1. - * - * @return the location of the active cell. - */ - public CellAddress ActiveCell - { - get + + List rows = new List(); + if (createRowIfMissing) { - String address = GetSheetTypeSelection().activeCell; - if (address == null) + for (int i = startRowNum; i <= endRowNum; i++) { - return null; + if (!(GetRow(i) is XSSFRow row)) + { + row = CreateRow(i) as XSSFRow; + } + + rows.Add(row); } - return new CellAddress(address); } - set + else { - String ref1 = value.FormatAsString(); - CT_Selection ctsel = GetSheetTypeSelection(); - ctsel.activeCell = (ref1); - ctsel.SetSqref(new string[] { ref1 }); + //rows.addAll(_rows.subMap(startRowNum, endRowNum + 1).values()); + rows.AddRange(_rows.SkipWhile(x => x.Key < startRowNum) + .TakeWhile(x => x.Key < endRowNum + 1) + .Select(x => x.Value)); } - } - [Obsolete("deprecated 3.14beta2 (circa 2015-12-05). Use {@link #setActiveCell(CellAddress)} instead.")] - public void SetActiveCell(string cellref) - { - CT_Selection ctsel = GetSheetTypeSelection(); - ctsel.activeCell = cellref; - ctsel.SetSqref(new string[] { cellref }); + + return rows; } - //public void SetActiveCell(int row, int column) - //{ - // CellReference cellref = new CellReference(row, column); - // SetActiveCell(cellref.FormatAsString()); - //} - /** - * Does this sheet have any comments on it? We need to know, - * so we can decide about writing it to disk or not - */ - public bool HasComments + /// + /// Ensure CT_Worksheet.CT_SheetPr.CT_OutlinePr + /// + /// + private CT_OutlinePr EnsureOutlinePr() { - get - { - if (sheetComments == null) { return false; } - return (sheetComments.GetNumberOfComments() > 0); - } + CT_SheetPr sheetPr = worksheet.IsSetSheetPr() + ? worksheet.sheetPr + : worksheet.AddNewSheetPr(); + return sheetPr.IsSetOutlinePr() + ? sheetPr.outlinePr + : sheetPr.AddNewOutlinePr(); + } - internal int NumberOfComments + private void GroupColumn1Based(int fromColumn, int toColumn) { - get + CT_Cols ctCols = worksheet.GetColsArray(0); + CT_Col ctCol = new CT_Col(); + + // copy attributes, as they might be removed by merging with the + // new column TODO: check if this fix is really necessary or if the + // sweeping algorithm in addCleanColIntoCols needs to be adapted... + CT_Col fixCol_before = columnHelper.GetColumn1Based(toColumn, false); + if (fixCol_before != null) { - if (sheetComments == null) { return 0; } - return sheetComments.GetNumberOfComments(); + fixCol_before = fixCol_before.Copy(); } - } - private CT_Selection GetSheetTypeSelection() - { - if (GetSheetTypeSheetView().SizeOfSelectionArray() == 0) + ctCol.min = (uint)fromColumn; + ctCol.max = (uint)toColumn; + columnHelper.AddCleanColIntoCols(ctCols, ctCol); + + CT_Col fixCol_after = columnHelper.GetColumn1Based(toColumn, false); + if (fixCol_before != null && fixCol_after != null) { - GetSheetTypeSheetView().InsertNewSelection(0); + columnHelper.SetColumnAttributes(fixCol_before, fixCol_after); } - return GetSheetTypeSheetView().GetSelectionArray(0); - } - - /** - * Return the default sheet view. This is the last one if the sheet's views, according to sec. 3.3.1.83 - * of the OOXML spec: "A single sheet view defInition. When more than 1 sheet view is defined in the file, - * it means that when opening the workbook, each sheet view corresponds to a separate window within the - * spreadsheet application, where each window is Showing the particular sheet. Containing the same - * workbookViewId value, the last sheetView defInition is loaded, and the others are discarded. - * When multiple windows are viewing the same sheet, multiple sheetView elements (with corresponding - * workbookView entries) are saved." - */ - private CT_SheetView GetDefaultSheetView() - { - CT_SheetViews views = GetSheetTypeSheetViews(); - int sz = views == null ? 0 : views.sizeOfSheetViewArray(); - if (sz == 0) + for (int index = fromColumn; index <= toColumn; index++) { - return null; + CT_Col col = columnHelper.GetColumn1Based(index, false); + //col must exist + short outlineLevel = col.outlineLevel; + col.outlineLevel = (byte)(outlineLevel + 1); + index = (int)col.max; } - return views.GetSheetViewArray(sz - 1); + + worksheet.SetColsArray(0, ctCols); + SetSheetFormatPrOutlineLevelCol(); } - /** - * Returns the sheet's comments object if there is one, - * or null if not - * - * @param create create a new comments table if it does not exist - */ - protected internal CommentsTable GetCommentsTable(bool create) + /// + /// Do not leave the width attribute undefined (see #52186). + /// + /// + private void SetColWidthAttribute(CT_Cols ctCols) { - if (sheetComments == null && create) + foreach (CT_Col col in ctCols.GetColList()) { - // Try to create a comments table with the same number as - // the sheet has (i.e. sheet 1 -> comments 1) - try - { - sheetComments = (CommentsTable)CreateRelationship( - XSSFRelation.SHEET_COMMENTS, XSSFFactory.GetInstance(), (int)sheet.sheetId); - } - catch (PartAlreadyExistsException) + if (!col.IsSetWidth()) { - // Technically a sheet doesn't need the same number as - // it's comments, and clearly someone has already pinched - // our number! Go for the next available one instead - sheetComments = (CommentsTable)CreateRelationship( - XSSFRelation.SHEET_COMMENTS, XSSFFactory.GetInstance(), -1); + col.width = DefaultColumnWidth; + col.customWidth = false; } } - return sheetComments; } - private CT_PageSetUpPr GetSheetTypePageSetUpPr() - { - CT_SheetPr sheetPr = GetSheetTypeSheetPr(); - return sheetPr.IsSetPageSetUpPr() ? sheetPr.pageSetUpPr : sheetPr.AddNewPageSetUpPr(); - } - - private static bool ShouldRemoveRow(int startRow, int endRow, int n, int rownum) + private short GetMaxOutlineLevelRows() { - if (rownum >= (startRow + n) && rownum <= (endRow + n)) + short outlineLevel = 0; + foreach (XSSFRow xrow in _rows.Values) { - if (n > 0 && rownum > endRow) - { - return true; - } - else if (n < 0 && rownum < startRow) - { - return true; - } + outlineLevel = xrow.GetCTRow().outlineLevel > outlineLevel + ? xrow.GetCTRow().outlineLevel + : outlineLevel; } - return false; + + return outlineLevel; } - private CT_Pane GetPane() + //YK: GetXYZArray() array accessors are deprecated in xmlbeans with JDK 1.5 support + [Obsolete] + private short GetMaxOutlineLevelCols() { - if (GetDefaultSheetView().pane == null) + CT_Cols ctCols = worksheet.GetColsArray(0); + short outlineLevel = 0; + foreach (CT_Col col in ctCols.GetColList()) { - GetDefaultSheetView().AddNewPane(); + outlineLevel = col.outlineLevel > outlineLevel + ? col.outlineLevel + : outlineLevel; } - return GetDefaultSheetView().pane; - } - /** - * Return a master shared formula by index - * - * @param sid shared group index - * @return a CT_CellFormula bean holding shared formula or null if not found - */ - internal CT_CellFormula GetSharedFormula(int sid) - { - return sharedFormulas[sid]; + return outlineLevel; } - internal void OnReadCell(XSSFCell cell) + private bool ListIsEmpty(List list) { - //collect cells holding shared formulas - CT_Cell ct = cell.GetCTCell(); - CT_CellFormula f = ct.f; - if (f != null && f.t == ST_CellFormulaType.shared && f.isSetRef() && f.Value != null) + foreach (CT_MergeCell mc in list) { - // save a detached copy to avoid XmlValueDisconnectedException, - // this may happen when the master cell of a shared formula is Changed - CT_CellFormula sf = (CT_CellFormula)f.Copy(); - CellRangeAddress sfRef = CellRangeAddress.ValueOf(sf.@ref); - CellReference cellRef = new CellReference(cell); - // If the shared formula range preceeds the master cell then the preceding part is discarded, e.g. - // if the cell is E60 and the shared formula range is C60:M85 then the effective range is E60:M85 - // see more details in https://issues.apache.org/bugzilla/show_bug.cgi?id=51710 - if (cellRef.Col > sfRef.FirstColumn || cellRef.Row > sfRef.FirstRow) + if (mc != null) { - String effectiveRef = new CellRangeAddress( - Math.Max(cellRef.Row, sfRef.FirstRow), sfRef.LastRow, - Math.Max(cellRef.Col, sfRef.FirstColumn), sfRef.LastColumn).FormatAsString(); - sf.@ref = (effectiveRef); + return false; } - sharedFormulas[(int)f.si] = sf; } - if (f != null && f.t == ST_CellFormulaType.array && f.@ref != null) + + return true; + } + + private void CollapseColumn(int columnNumber) + { + CT_Cols cols = worksheet.GetColsArray(0); + CT_Col col = columnHelper.GetColumn(columnNumber, false); + int colInfoIx = columnHelper.GetIndexOfColumn(cols, col); + if (colInfoIx == -1) { - arrayFormulas.Add(CellRangeAddress.ValueOf(f.@ref)); + return; } - } + // Find the start of the group. + int groupStartColInfoIx = FindStartOfColumnOutlineGroup(colInfoIx); + + CT_Col columnInfo = cols.GetColArray(groupStartColInfoIx); + // Hide all the columns until the end of the group + int lastColMax = SetGroupHidden(groupStartColInfoIx, columnInfo + .outlineLevel, true); + + // write collapse field + SetColumn(lastColMax + 1, 0, null, null, true); - protected internal override void Commit() - { - PackagePart part = GetPackagePart(); - Stream out1 = part.GetOutputStream(); - Write(out1); - out1.Close(); } - internal virtual void Write(Stream stream, bool leaveOpen=false) + private void SetColumn(int targetColumnIx, int? style, + int? level, bool? hidden, bool? collapsed) { - bool setToNull = false; - if (worksheet.sizeOfColsArray() == 1) + CT_Cols cols = worksheet.GetColsArray(0); + CT_Col ci = null; + + for (int k = 0; k < cols.sizeOfColArray(); k++) { - CT_Cols col = worksheet.GetColsArray(0); - if (col.sizeOfColArray() == 0) + CT_Col tci = cols.GetColArray(k); + if (tci.min >= targetColumnIx + && tci.max <= targetColumnIx) { - setToNull = true; - // this is necessary so that we do not write an empty item into the sheet-xml in the xlsx-file - // Excel complains about a corrupted file if this shows up there! - worksheet.SetColsArray(null); + ci = tci; + break; } - else + + if (tci.min > targetColumnIx) { - SetColWidthAttribute(col); + // call column infos after k are for later columns + break; // exit now so k will be the correct insert pos } } + if (ci == null) + { + // okay so there ISN'T a column info record that covers this + // column so lets create one! + CT_Col nci = new CT_Col + { + min = (uint)targetColumnIx, + max = (uint)targetColumnIx + }; + UnsetCollapsed((bool)collapsed, nci); + columnHelper.AddCleanColIntoCols(cols, nci); + return; + } - // Now re-generate our CT_Hyperlinks, if needed - if (hyperlinks.Count > 0) + bool styleChanged = style != null + && ci.style != style; + bool levelChanged = level != null + && ci.outlineLevel != level; + bool hiddenChanged = hidden != null + && ci.hidden != hidden; + bool collapsedChanged = collapsed != null + && ci.collapsed != collapsed; + bool columnChanged = levelChanged || hiddenChanged + || collapsedChanged || styleChanged; + if (!columnChanged) { - if (worksheet.hyperlinks == null) + // do nothing...nothing Changed. + return; + } + + if (ci.min == targetColumnIx && ci.max == targetColumnIx) + { + // ColumnInfo ci for a single column, the target column + UnsetCollapsed((bool)collapsed, ci); + return; + } + + if (ci.min == targetColumnIx || ci.max == targetColumnIx) + { + // The target column is at either end of the multi-column + // ColumnInfo ci we'll just divide the info and create a + // new one + if (ci.min == targetColumnIx) { - worksheet.AddNewHyperlinks(); + ci.min = (uint)(targetColumnIx + 1); } - NPOI.OpenXmlFormats.Spreadsheet.CT_Hyperlink[] ctHls - = new NPOI.OpenXmlFormats.Spreadsheet.CT_Hyperlink[hyperlinks.Count]; - for (int i = 0; i < ctHls.Length; i++) + else { - // If our sheet has hyperlinks, have them add - // any relationships that they might need - XSSFHyperlink hyperlink = hyperlinks[i]; - hyperlink.GenerateRelationIfNeeded(GetPackagePart()); - // Now grab their underling object - ctHls[i] = hyperlink.GetCTHyperlink(); + ci.max = (uint)(targetColumnIx - 1); } - worksheet.hyperlinks.SetHyperlinkArray(ctHls); - } - foreach (XSSFRow row in _rows.Values) - { - row.OnDocumentWrite(); + CT_Col nci = columnHelper.CloneCol(cols, ci); + nci.min = (uint)targetColumnIx; + UnsetCollapsed((bool)collapsed, nci); + columnHelper.AddCleanColIntoCols(cols, nci); + } + else + { + // split to 3 records + CT_Col ciStart = ci; + CT_Col ciMid = columnHelper.CloneCol(cols, ci); + CT_Col ciEnd = columnHelper.CloneCol(cols, ci); + int lastcolumn = (int)ci.max; - //XmlOptions xmlOptions = new XmlOptions(DEFAULT_XML_OPTIONS); - //xmlOptions.SetSaveSyntheticDocumentElement(new QName(CT_Worksheet.type.GetName().getNamespaceURI(), "worksheet")); - Dictionary map = new Dictionary(); - map[ST_RelationshipId.NamespaceURI] = "r"; - //xmlOptions.SetSaveSuggestedPrefixes(map); + ciStart.max = (uint)(targetColumnIx - 1); - new WorksheetDocument(worksheet).Save(stream, leaveOpen); + ciMid.min = (uint)targetColumnIx; + ciMid.max = (uint)targetColumnIx; + UnsetCollapsed((bool)collapsed, ciMid); + columnHelper.AddCleanColIntoCols(cols, ciMid); - // Bug 52233: Ensure that we have a col-array even if write() removed it - if (setToNull) - { - worksheet.AddNewCols(); + ciEnd.min = (uint)(targetColumnIx + 1); + ciEnd.max = (uint)lastcolumn; + columnHelper.AddCleanColIntoCols(cols, ciEnd); } } - /** - * @return true when Autofilters are locked and the sheet is protected. - */ - public bool IsAutoFilterLocked + private void UnsetCollapsed(bool collapsed, CT_Col ci) { - get + if (collapsed) { - return IsSheetLocked && SafeGetProtectionField().autoFilter; + ci.collapsed = collapsed; } - } - - /** - * @return true when Deleting columns is locked and the sheet is protected. - */ - public bool IsDeleteColumnsLocked - { - get + else { - return IsSheetLocked && SafeGetProtectionField().deleteColumns; + ci.UnsetCollapsed(); } } - /** - * @return true when Deleting rows is locked and the sheet is protected. - */ - public bool IsDeleteRowsLocked + /// + /// Sets all adjacent columns of the same outline level to the + /// specified hidden status. + /// + /// the col info index of the start of the + /// outline group + /// + /// + /// the column index of the last column in the + /// outline group + private int SetGroupHidden(int pIdx, int level, bool hidden) { - get + CT_Cols cols = worksheet.GetColsArray(0); + int idx = pIdx; + CT_Col columnInfo = cols.GetColArray(idx); + while (idx < cols.sizeOfColArray()) { - return IsSheetLocked && SafeGetProtectionField().deleteRows; - } - } + columnInfo.hidden = hidden; + if (idx + 1 < cols.sizeOfColArray()) + { + CT_Col nextColumnInfo = cols.GetColArray(idx + 1); - /** - * @return true when Formatting cells is locked and the sheet is protected. - */ - public bool IsFormatCellsLocked - { - get - { - return IsSheetLocked && SafeGetProtectionField().formatCells; + if (!IsAdjacentBefore(columnInfo, nextColumnInfo)) + { + break; + } + + if (nextColumnInfo.outlineLevel < level) + { + break; + } + + columnInfo = nextColumnInfo; + } + + idx++; } + + return (int)columnInfo.max; } - /** - * @return true when Formatting columns is locked and the sheet is protected. - */ - public bool IsFormatColumnsLocked + private bool IsAdjacentBefore(CT_Col col, CT_Col other_col) { - get - { - return IsSheetLocked && SafeGetProtectionField().formatColumns; - } + return col.max == (other_col.min - 1); } - /** - * @return true when Formatting rows is locked and the sheet is protected. - */ - public bool IsFormatRowsLocked + private int FindStartOfColumnOutlineGroup(int pIdx) { - get + // Find the start of the group. + CT_Cols cols = worksheet.GetColsArray(0); + CT_Col columnInfo = cols.GetColArray(pIdx); + int level = columnInfo.outlineLevel; + int idx = pIdx; + while (idx != 0) { - return IsSheetLocked && SafeGetProtectionField().formatRows; - } - } + CT_Col prevColumnInfo = cols.GetColArray(idx - 1); + if (!IsAdjacentBefore(prevColumnInfo, columnInfo)) + { + break; + } + + if (prevColumnInfo.outlineLevel < level) + { + break; + } - /** - * @return true when Inserting columns is locked and the sheet is protected. - */ - public bool IsInsertColumnsLocked - { - get - { - return IsSheetLocked && SafeGetProtectionField().insertColumns; + idx--; + columnInfo = prevColumnInfo; } - } - /** - * @return true when Inserting hyperlinks is locked and the sheet is protected. - */ - public bool IsInsertHyperlinksLocked - { - get - { - return IsSheetLocked && SafeGetProtectionField().insertHyperlinks; - } + return idx; } - /** - * @return true when Inserting rows is locked and the sheet is protected. - */ - public bool IsInsertRowsLocked + private int FindEndOfColumnOutlineGroup(int colInfoIndex) { - get + CT_Cols cols = worksheet.GetColsArray(0); + // Find the end of the group. + CT_Col columnInfo = cols.GetColArray(colInfoIndex); + int level = columnInfo.outlineLevel; + int idx = colInfoIndex; + while (idx < cols.sizeOfColArray() - 1) { - return IsSheetLocked && SafeGetProtectionField().insertRows; - } - } + CT_Col nextColumnInfo = cols.GetColArray(idx + 1); + if (!IsAdjacentBefore(columnInfo, nextColumnInfo)) + { + break; + } - /** - * @return true when Pivot tables are locked and the sheet is protected. - */ - public bool IsPivotTablesLocked - { - get - { - return IsSheetLocked && SafeGetProtectionField().pivotTables; + if (nextColumnInfo.outlineLevel < level) + { + break; + } + + idx++; + columnInfo = nextColumnInfo; } + + return idx; } - /** - * @return true when Sorting is locked and the sheet is protected. - */ - public bool IsSortLocked + private void ExpandColumn(int columnIndex) { - get + CT_Cols cols = worksheet.GetColsArray(0); + CT_Col col = columnHelper.GetColumn(columnIndex, false); + int colInfoIx = columnHelper.GetIndexOfColumn(cols, col); + + int idx = FindColInfoIdx((int)col.max, colInfoIx); + if (idx == -1) { - return IsSheetLocked && SafeGetProtectionField().sort; + return; } - } - /** - * @return true when Objects are locked and the sheet is protected. - */ - public bool IsObjectsLocked - { - get + // If it is already expanded do nothing. + if (!IsColumnGroupCollapsed(idx)) { - return IsSheetLocked && SafeGetProtectionField().objects; + return; } - } - /** - * @return true when Scenarios are locked and the sheet is protected. - */ - public bool IsScenariosLocked - { - get + // Find the start/end of the group. + int startIdx = FindStartOfColumnOutlineGroup(idx); + int endIdx = FindEndOfColumnOutlineGroup(idx); + + // expand: colapsed bit must be unset hidden bit Gets unset _if_ + // surrounding groups are expanded you can determine this by + // looking at the hidden bit of the enclosing group. You will have + // to look at the start and the end of the current group to + // determine which is the enclosing group hidden bit only is + // altered for this outline level. ie. don't uncollapse Contained + // groups + CT_Col columnInfo = cols.GetColArray(endIdx); + if (!IsColumnGroupHiddenByParent(idx)) { - return IsSheetLocked && SafeGetProtectionField().scenarios; + int outlineLevel = columnInfo.outlineLevel; + bool nestedGroup = false; + for (int i = startIdx; i <= endIdx; i++) + { + CT_Col ci = cols.GetColArray(i); + if (outlineLevel == ci.outlineLevel) + { + ci.UnsetHidden(); + if (nestedGroup) + { + nestedGroup = false; + ci.collapsed = true; + } + } + else + { + nestedGroup = true; + } + } } + // Write collapse flag (stored in a single col info record after + // this outline group) + SetColumn((int)columnInfo.max + 1, null, null, + false, false); } - /** - * @return true when Selection of locked cells is locked and the sheet is protected. - */ - public bool IsSelectLockedCellsLocked + private bool IsColumnGroupHiddenByParent(int idx) { - get + CT_Cols cols = worksheet.GetColsArray(0); + // Look out outline details of end + int endLevel = 0; + bool endHidden = false; + int endOfOutlineGroupIdx = FindEndOfColumnOutlineGroup(idx); + if (endOfOutlineGroupIdx < cols.sizeOfColArray()) { - return IsSheetLocked && SafeGetProtectionField().selectLockedCells; + CT_Col nextInfo = cols.GetColArray(endOfOutlineGroupIdx + 1); + if (IsAdjacentBefore(cols.GetColArray(endOfOutlineGroupIdx), + nextInfo)) + { + endLevel = nextInfo.outlineLevel; + endHidden = nextInfo.hidden; + } } - } - - /** - * @return true when Selection of unlocked cells is locked and the sheet is protected. - */ - public bool IsSelectUnlockedCellsLocked - { - get + // Look out outline details of start + int startLevel = 0; + bool startHidden = false; + int startOfOutlineGroupIdx = FindStartOfColumnOutlineGroup(idx); + if (startOfOutlineGroupIdx > 0) { - return IsSheetLocked && SafeGetProtectionField().selectUnlockedCells; + CT_Col prevInfo = cols.GetColArray(startOfOutlineGroupIdx - 1); + + if (IsAdjacentBefore(prevInfo, cols + .GetColArray(startOfOutlineGroupIdx))) + { + startLevel = prevInfo.outlineLevel; + startHidden = prevInfo.hidden; + } } - } - /** - * @return true when Sheet is Protected. - */ - public bool IsSheetLocked - { - get + if (endLevel > startLevel) { - return worksheet.IsSetSheetProtection() && SafeGetProtectionField().sheet; + return endHidden; } - } - /** - * Enable sheet protection - */ - public void EnableLocking() - { - SafeGetProtectionField().sheet = true; + return startHidden; } - /** - * Disable sheet protection - */ - public void DisableLocking() + private int FindColInfoIdx(int columnValue, int fromColInfoIdx) { - SafeGetProtectionField().sheet = false; - } + CT_Cols cols = worksheet.GetColsArray(0); - /** - * Enable or disable Autofilters locking. - * This does not modify sheet protection status. - * To enforce this un-/locking, call {@link #disableLocking()} or {@link #enableLocking()} - */ - public void LockAutoFilter(bool enabled) - { - SafeGetProtectionField().autoFilter = enabled; - } + if (columnValue < 0) + { + throw new ArgumentException( + "column parameter out of range: " + columnValue); + } - /** - * Enable or disable Deleting columns locking. - * This does not modify sheet protection status. - * To enforce this un-/locking, call {@link #disableLocking()} or {@link #enableLocking()} - */ - public void LockDeleteColumns(bool enabled) - { - SafeGetProtectionField().deleteColumns = enabled; - } + if (fromColInfoIdx < 0) + { + throw new ArgumentException( + "fromIdx parameter out of range: " + fromColInfoIdx); + } - /** - * Enable or disable Deleting rows locking. - * This does not modify sheet protection status. - * To enforce this un-/locking, call {@link #disableLocking()} or {@link #enableLocking()} - */ - public void LockDeleteRows(bool enabled) - { - SafeGetProtectionField().deleteRows = enabled; - } + for (int k = fromColInfoIdx; k < cols.sizeOfColArray(); k++) + { + CT_Col ci = cols.GetColArray(k); - /** - * Enable or disable Formatting cells locking. - * This does not modify sheet protection status. - * To enforce this un-/locking, call {@link #disableLocking()} or {@link #enableLocking()} - */ - public void LockFormatCells(bool enabled) - { - SafeGetProtectionField().formatCells = enabled; - } + if (ContainsColumn(ci, columnValue)) + { + return k; + } - /** - * Enable or disable Formatting columns locking. - * This does not modify sheet protection status. - * To enforce this un-/locking, call {@link #disableLocking()} or {@link #enableLocking()} - */ - public void LockFormatColumns(bool enabled) - { - SafeGetProtectionField().formatColumns = enabled; - } + if (ci.min > fromColInfoIdx) + { + break; + } + } - /** - * Enable or disable Formatting rows locking. - * This does not modify sheet protection status. - * To enforce this un-/locking, call {@link #disableLocking()} or {@link #enableLocking()} - */ - public void LockFormatRows(bool enabled) - { - SafeGetProtectionField().formatRows = enabled; + return -1; } - /** - * Enable or disable Inserting columns locking. - * This does not modify sheet protection status. - * To enforce this un-/locking, call {@link #disableLocking()} or {@link #enableLocking()} - */ - public void LockInsertColumns(bool enabled) + private bool ContainsColumn(CT_Col col, int columnIndex) { - SafeGetProtectionField().insertColumns = enabled; + return col.min <= columnIndex && columnIndex <= col.max; } - /** - * Enable or disable Inserting hyperlinks locking. - * This does not modify sheet protection status. - * To enforce this un-/locking, call {@link #disableLocking()} or {@link #enableLocking()} - */ - public void LockInsertHyperlinks(bool enabled) + /// + /// 'Collapsed' state is stored in a single column col info record + /// immediately after the outline group + /// + /// + /// a bool represented if the column is collapsed + private bool IsColumnGroupCollapsed(int idx) { - SafeGetProtectionField().insertHyperlinks = enabled; - } + CT_Cols cols = worksheet.GetColsArray(0); + int endOfOutlineGroupIdx = FindEndOfColumnOutlineGroup(idx); + int nextColInfoIx = endOfOutlineGroupIdx + 1; + if (nextColInfoIx >= cols.sizeOfColArray()) + { + return false; + } - /** - * Enable or disable Inserting rows locking. - * This does not modify sheet protection status. - * To enforce this un-/locking, call {@link #disableLocking()} or {@link #enableLocking()} - */ - public void LockInsertRows(bool enabled) - { - SafeGetProtectionField().insertRows = enabled; - } + CT_Col nextColInfo = cols.GetColArray(nextColInfoIx); - /** - * Enable or disable Pivot Tables locking. - * This does not modify sheet protection status. - * To enforce this un-/locking, call {@link #disableLocking()} or {@link #enableLocking()} - */ - public void LockPivotTables(bool enabled) - { - SafeGetProtectionField().pivotTables = enabled; - } + CT_Col col = cols.GetColArray(endOfOutlineGroupIdx); + if (!IsAdjacentBefore(col, nextColInfo)) + { + return false; + } - /** - * Enable or disable Sort locking. - * This does not modify sheet protection status. - * To enforce this un-/locking, call {@link #disableLocking()} or {@link #enableLocking()} - */ - public void LockSort(bool enabled) - { - SafeGetProtectionField().sort = enabled; + return nextColInfo.collapsed; } - /** - * Enable or disable Objects locking. - * This does not modify sheet protection status. - * To enforce this un-/locking, call {@link #disableLocking()} or {@link #enableLocking()} - */ - public void LockObjects(bool enabled) + private CT_SheetView GetSheetTypeSheetView() { - SafeGetProtectionField().objects = enabled; - } + if (GetDefaultSheetView() == null) + { + GetSheetTypeSheetViews().SetSheetViewArray(0, new CT_SheetView()); + } - /** - * Enable or disable Scenarios locking. - * This does not modify sheet protection status. - * To enforce this un-/locking, call {@link #disableLocking()} or {@link #enableLocking()} - */ - public void LockScenarios(bool enabled) - { - SafeGetProtectionField().scenarios = enabled; + return GetDefaultSheetView(); } - /** - * Enable or disable Selection of locked cells locking. - * This does not modify sheet protection status. - * To enforce this un-/locking, call {@link #disableLocking()} or {@link #enableLocking()} - */ - public void LockSelectLockedCells(bool enabled) + /// + /// + /// + /// the zero based row index to collapse + private void CollapseRow(int rowIndex) { - SafeGetProtectionField().selectLockedCells = enabled; - } + XSSFRow row = (XSSFRow)GetRow(rowIndex); + if (row != null) + { + int startRow = FindStartOfRowOutlineGroup(rowIndex); - /** - * Enable or disable Selection of unlocked cells locking. - * This does not modify sheet protection status. - * To enforce this un-/locking, call {@link #disableLocking()} or {@link #enableLocking()} - */ - public void LockSelectUnlockedCells(bool enabled) - { - SafeGetProtectionField().selectUnlockedCells = enabled; + // Hide all the columns until the end of the group + int lastRow = WriteHidden(row, startRow, true); + if (GetRow(lastRow) != null) + { + ((XSSFRow)GetRow(lastRow)).GetCTRow().collapsed = true; + } + else + { + XSSFRow newRow = (XSSFRow)CreateRow(lastRow); + newRow.GetCTRow().collapsed = true; + } + } } - private CT_SheetProtection SafeGetProtectionField() + /// + /// + /// + /// the zero based row index to find from + /// + private int FindStartOfRowOutlineGroup(int rowIndex) { - if (!IsSheetProtectionEnabled()) + // Find the start of the group. + int level = ((XSSFRow)GetRow(rowIndex)).GetCTRow().outlineLevel; + int currentRow = rowIndex; + while (GetRow(currentRow) != null) { - return worksheet.AddNewSheetProtection(); + if (((XSSFRow)GetRow(currentRow)).GetCTRow().outlineLevel < level) + { + return currentRow + 1; + } + + currentRow--; } - return worksheet.sheetProtection; - } - /* package */ - bool IsSheetProtectionEnabled() - { - return (worksheet.IsSetSheetProtection()); + return currentRow; } - - /* namespace */ - internal bool IsCellInArrayFormulaContext(ICell cell) + private int WriteHidden(XSSFRow xRow, int rowIndex, bool hidden) { - foreach (CellRangeAddress range in arrayFormulas) + int level = xRow.GetCTRow().outlineLevel; + for (IEnumerator it = GetRowEnumerator(); it.MoveNext();) { - if (range.IsInRange(cell.RowIndex, cell.ColumnIndex)) + xRow = (XSSFRow)it.Current; + if (xRow.GetCTRow().outlineLevel >= level) { - return true; + xRow.GetCTRow().hidden = hidden; + rowIndex++; } } - return false; + + return rowIndex; } - /* namespace */ - internal XSSFCell GetFirstCellInArrayFormula(ICell cell) + /// + /// + /// + /// the zero based row index to expand + private void ExpandRow(int rowNumber) { - foreach (CellRangeAddress range in arrayFormulas) + if (rowNumber == -1) { - if (range.IsInRange(cell.RowIndex, cell.ColumnIndex)) - { - return (XSSFCell)GetRow(range.FirstRow).GetCell(range.FirstColumn); - } + return; } - return null; - } - /** - * Also Creates cells if they don't exist - */ - private ICellRange GetCellRange(CellRangeAddress range) - { - int firstRow = range.FirstRow; - int firstColumn = range.FirstColumn; - int lastRow = range.LastRow; - int lastColumn = range.LastColumn; - int height = lastRow - firstRow + 1; - int width = lastColumn - firstColumn + 1; - List temp = new List(height * width); - for (int rowIn = firstRow; rowIn <= lastRow; rowIn++) + XSSFRow row = (XSSFRow)GetRow(rowNumber); + // If it is already expanded do nothing. + if (!row.GetCTRow().IsSetHidden()) { - for (int colIn = firstColumn; colIn <= lastColumn; colIn++) + return; + } + // Find the start of the group. + int startIdx = FindStartOfRowOutlineGroup(rowNumber); + + // Find the end of the group. + int endIdx = FindEndOfRowOutlineGroup(rowNumber); + + // expand: collapsed must be unset hidden bit Gets unset _if_ + // surrounding groups are expanded you can determine this by + // looking at the hidden bit of the enclosing group. You will have + // to look at the start and the end of the current group to + // determine which is the enclosing group hidden bit only is + // altered for this outline level. ie. don't un-collapse Contained + // groups + if (!IsRowGroupHiddenByParent(rowNumber)) + { + for (int i = startIdx; i < endIdx; i++) { - IRow row = GetRow(rowIn); - if (row == null) + if (row.GetCTRow().outlineLevel == ((XSSFRow)GetRow(i)) + .GetCTRow() + .outlineLevel) { - row = CreateRow(rowIn); + ((XSSFRow)GetRow(i)).GetCTRow().UnsetHidden(); } - ICell cell = row.GetCell(colIn); - if (cell == null) + else if (!IsRowGroupCollapsed(i)) { - cell = row.CreateCell(colIn); + ((XSSFRow)GetRow(i)).GetCTRow().UnsetHidden(); } - temp.Add(cell); } } - return SSCellRange.Create(firstRow, firstColumn, height, width, temp, typeof(ICell)); + // Write collapse field + row = GetRow(endIdx) as XSSFRow; + if (row != null) + { + CT_Row ctRow = row.GetCTRow(); + // This avoids an IndexOutOfBounds if multiple nested groups + // are collapsed/expanded + if (ctRow.collapsed) + { + ctRow.UnsetCollapsed(); + } + } } - public ICellRange SetArrayFormula(String formula, CellRangeAddress range) + /// + /// + /// + /// the zero based row index to find from + /// + private bool IsRowGroupHiddenByParent(int row) { + // Look out outline details of end + int endLevel; + bool endHidden; + int endOfOutlineGroupIdx = FindEndOfRowOutlineGroup(row); + if (GetRow(endOfOutlineGroupIdx) == null) + { + endLevel = 0; + endHidden = false; + } + else + { + endLevel = ((XSSFRow)GetRow(endOfOutlineGroupIdx)) + .GetCTRow().outlineLevel; + endHidden = ((XSSFRow)GetRow(endOfOutlineGroupIdx)) + .GetCTRow().hidden; + } - ICellRange cr = GetCellRange(range); - - ICell mainArrayFormulaCell = cr.TopLeftCell; - ((XSSFCell)mainArrayFormulaCell).SetCellArrayFormula(formula, range); - arrayFormulas.Add(range); - return cr; - } - - public ICellRange RemoveArrayFormula(ICell cell) - { - if (cell.Sheet != this) + // Look out outline details of start + int startLevel; + bool startHidden; + int startOfOutlineGroupIdx = FindStartOfRowOutlineGroup(row); + if (startOfOutlineGroupIdx < 0 + || GetRow(startOfOutlineGroupIdx) == null) { - throw new ArgumentException("Specified cell does not belong to this sheet."); + startLevel = 0; + startHidden = false; } - foreach (CellRangeAddress range in arrayFormulas) + else { - if (range.IsInRange(cell.RowIndex, cell.ColumnIndex)) - { - arrayFormulas.Remove(range); - ICellRange cr = GetCellRange(range); - foreach (ICell c in cr) - { - c.SetCellType(CellType.Blank); - } - return cr; - } + startLevel = ((XSSFRow)GetRow(startOfOutlineGroupIdx)) + .GetCTRow().outlineLevel; + startHidden = ((XSSFRow)GetRow(startOfOutlineGroupIdx)) + .GetCTRow().hidden; } - String ref1 = ((XSSFCell)cell).GetCTCell().r; - throw new ArgumentException("Cell " + ref1 + " is not part of an array formula."); - } + if (endLevel > startLevel) + { + return endHidden; + } - public IDataValidationHelper GetDataValidationHelper() - { - return dataValidationHelper; + return startHidden; } - //YK: GetXYZArray() array accessors are deprecated in xmlbeans with JDK 1.5 support - public List GetDataValidations() + /// + /// + /// + /// the zero based row index to find from + /// + private bool IsRowGroupCollapsed(int row) { - List xssfValidations = new List(); - CT_DataValidations dataValidations = this.worksheet.dataValidations; - if (dataValidations != null && dataValidations.count > 0) + int collapseRow = FindEndOfRowOutlineGroup(row) + 1; + if (GetRow(collapseRow) == null) { - foreach (CT_DataValidation ctDataValidation in dataValidations.dataValidation) - { - CellRangeAddressList addressList = new CellRangeAddressList(); - - String[] regions = ctDataValidation.sqref.Split(new char[] { ' ' }); - for (int i = 0; i < regions.Length; i++) - { - if (regions[i].Length == 0) - continue; - String[] parts = regions[i].Split(new char[] { ':' }); - CellReference begin = new CellReference(parts[0]); - CellReference end = parts.Length > 1 ? new CellReference(parts[1]) : begin; - CellRangeAddress cellRangeAddress = new CellRangeAddress(begin.Row, end.Row, begin.Col, end.Col); - addressList.AddCellRangeAddress(cellRangeAddress); - } - XSSFDataValidation xssfDataValidation = new XSSFDataValidation(addressList, ctDataValidation); - xssfValidations.Add(xssfDataValidation); - } + return false; } - return xssfValidations; + + return ((XSSFRow)GetRow(collapseRow)).GetCTRow().collapsed; } - public void AddValidationData(IDataValidation dataValidation) + private void RemoveOverwrittenRows(int startRow, int endRow, int n) { - XSSFDataValidation xssfDataValidation = (XSSFDataValidation)dataValidation; - CT_DataValidations dataValidations = worksheet.dataValidations; - if (dataValidations == null) + XSSFVMLDrawing vml = GetVMLDrawing(false); + List rowsToRemove = new List(); + List commentsToRemove = new List(); + List ctRowsToRemove = new List(); + // first remove all rows which will be overwritten + foreach (KeyValuePair rowDict in _rows) { - dataValidations = worksheet.AddNewDataValidations(); - } + XSSFRow row = rowDict.Value; + int rownum = row.RowNum; - int currentCount = dataValidations.sizeOfDataValidationArray(); - CT_DataValidation newval = dataValidations.AddNewDataValidation(); - newval.Set(xssfDataValidation.GetCTDataValidation()); - dataValidations.count = (uint)currentCount + 1; + // check if we should remove this row as it will be overwritten + // by the data later + if (ShouldRemoveAtIndex(startRow, endRow, n, rownum)) + { + // remove row from worksheet.GetSheetData row array + //int idx = _rows.headMap(row.getRowNum()).size(); + int idx = _rows.IndexOfValue(row); + //worksheet.sheetData.RemoveRow(idx); + ctRowsToRemove.Add(worksheet.sheetData.GetRowArray(idx)); - } + // remove row from _rows + rowsToRemove.Add(rowDict.Key); - public IAutoFilter SetAutoFilter(CellRangeAddress range) - { - CT_AutoFilter af = worksheet.autoFilter; - if (af == null) af = worksheet.AddNewAutoFilter(); + commentsToRemove.Clear(); + // FIXME: (performance optimization) this should be moved outside the for-loop so that comments only needs to be iterated over once. + // also remove any comments associated with this row + if (sheetComments != null) + { + CT_CommentList lst = sheetComments.GetCTComments().commentList; + foreach (CT_Comment comment in lst.comment) + { + string strRef = comment.@ref; + CellAddress ref1 = new CellAddress(strRef); - CellRangeAddress norm = new CellRangeAddress(range.FirstRow, range.LastRow, - range.FirstColumn, range.LastColumn); - String ref1 = norm.FormatAsString(); - af.@ref = (ref1); + // is this comment part of the current row? + if (ref1.Row == rownum) + { + //sheetComments.RemoveComment(strRef); + //vml.RemoveCommentShape(ref1.Row, ref1.Col); + commentsToRemove.Add(ref1); + } + } + } - XSSFWorkbook wb = (XSSFWorkbook)Workbook; - int sheetIndex = Workbook.GetSheetIndex(this); - XSSFName name = wb.GetBuiltInName(XSSFName.BUILTIN_FILTER_DB, sheetIndex); - if (name == null) - { - name = wb.CreateBuiltInName(XSSFName.BUILTIN_FILTER_DB, sheetIndex); + foreach (CellAddress ref1 in commentsToRemove) + { + sheetComments.RemoveComment(ref1); + vml.RemoveCommentShape(ref1.Row, ref1.Column); + } + + // FIXME: (performance optimization) this should be moved outside the for-loop so that hyperlinks only needs to be iterated over once. + // also remove any hyperlinks associated with this row + if (hyperlinks != null) + { + foreach (XSSFHyperlink link in new List(hyperlinks)) + { + CellReference ref1 = new CellReference(link.CellRef); + if (ref1.Row == rownum) + { + hyperlinks.Remove(link); + } + } + } + } } - name.GetCTName().hidden = true; - CellReference r1 = new CellReference(SheetName, range.FirstRow, range.FirstColumn, true, true); - CellReference r2 = new CellReference(null, range.LastRow, range.LastColumn, true, true); - String fmla = r1.FormatAsString() + ":" + r2.FormatAsString(); - name.RefersToFormula = fmla; - - return new XSSFAutoFilter(this); - } - /** - * Creates a new Table, and associates it with this Sheet - */ - public XSSFTable CreateTable() - { - if (!worksheet.IsSetTableParts()) + foreach (int rowKey in rowsToRemove) { - worksheet.AddNewTableParts(); + _rows.Remove(rowKey); } - CT_TableParts tblParts = worksheet.tableParts; - CT_TablePart tbl = tblParts.AddNewTablePart(); - - // Table numbers need to be unique in the file, not just - // unique within the sheet. Find the next one - int tableNumber = GetPackagePart().Package.GetPartsByContentType(XSSFRelation.TABLE.ContentType).Count + 1; - RelationPart rp = CreateRelationship(XSSFRelation.TABLE, XSSFFactory.GetInstance(), tableNumber, false); - XSSFTable table = rp.DocumentPart as XSSFTable; - tbl.id = rp.Relationship.Id; - - tables[tbl.id] = table; - - return table; - } - - /** - * Returns any tables associated with this Sheet - */ - public List GetTables() - { - List tableList = new List( - tables.Values - ); - return tableList; + worksheet.sheetData.RemoveRows(ctRowsToRemove); } - - public ISheetConditionalFormatting SheetConditionalFormatting + private void RebuildRows() { - get + //rebuild the _rows map + Dictionary map = new Dictionary(); + foreach (XSSFRow r in _rows.Values) { - return new XSSFSheetConditionalFormatting(this); + map.Add(r.RowNum, r); } - } - /** - * Set background color of the sheet tab - * - * @param colorIndex the indexed color to set, must be a constant from {@link IndexedColors} - */ - [Obsolete("deprecated 3.15-beta2. Removed in 3.17. Use {@link #setTabColor(XSSFColor)}.")] - public void SetTabColor(int colorIndex) - { - CT_SheetPr pr = worksheet.sheetPr; - if (pr == null) pr = worksheet.AddNewSheetPr(); - NPOI.OpenXmlFormats.Spreadsheet.CT_Color color = new OpenXmlFormats.Spreadsheet.CT_Color(); - color.indexed = (uint)(colorIndex); - pr.tabColor = (color); - } - /* - * Get background color of the sheet tab. - * Returns null if no sheet tab color is set. - * - * @return the background color of the sheet tab - */ - /// - /// Get or set background color of the sheet tab. - /// The value is null if no sheet tab color is set. - /// - public XSSFColor TabColor - { - get + _rows.Clear(); + //_rows.putAll(map); + foreach (KeyValuePair kv in map) { - CT_SheetPr pr = worksheet.sheetPr; - if (pr == null) pr = worksheet.AddNewSheetPr(); - if (!pr.IsSetTabColor()) - { - return null; - } - return new XSSFColor(pr.tabColor); + _rows.Add(kv.Key, kv.Value); } - set + + // Sort CTRows by index asc. + // not found at poi 3.15 + if (worksheet.sheetData.row != null) { - CT_SheetPr pr = worksheet.sheetPr; - if (pr == null) pr = worksheet.AddNewSheetPr(); - pr.tabColor = value.GetCTColor(); + worksheet.sheetData.row.Sort((row1, row2) => row1.r.CompareTo(row2.r)); } } - - #region ISheet Members - - - public IDrawing DrawingPatriarch + private void ShiftCommentsAndRows(int startRow, int endRow, int n, bool copyRowHeight) { - get + SortedDictionary commentsToShift = + new SortedDictionary(new ShiftCommentComparator(n)); + + foreach (KeyValuePair rowDict in _rows) { - if (drawing == null) + XSSFRow row = rowDict.Value; + int rownum = row.RowNum; + + if (sheetComments != null) { - NPOI.OpenXmlFormats.Spreadsheet.CT_Drawing ctDrawing = GetCTDrawing(); - if (ctDrawing == null) - { - return null; - } + // calculate the new rownum + int newrownum = ShiftedRowOrColumnNumber(startRow, endRow, n, rownum); - foreach (RelationPart rp in RelationParts) + // is there a change necessary for the current row? + if (newrownum != rownum) { - POIXMLDocumentPart p = rp.DocumentPart; - if (p is XSSFDrawing) + List commentAddresses = sheetComments.GetCellAddresses(); + foreach (CellAddress cellAddress in commentAddresses) { - XSSFDrawing dr = (XSSFDrawing)p; - String drId = rp.Relationship.Id; - if (drId.Equals(ctDrawing.id)) + if (cellAddress.Row == rownum) { - drawing = dr; - break; + XSSFComment oldComment = sheetComments + .FindCellComment(cellAddress); + if (oldComment != null) + { + XSSFComment xssfComment = + new XSSFComment( + sheetComments, + oldComment.GetCTComment(), + oldComment.GetCTShape()); + if (commentsToShift.ContainsKey(xssfComment)) + { + commentsToShift[xssfComment] = newrownum; + } + else + { + commentsToShift.Add(xssfComment, newrownum); + } + } } - break; } } } - return drawing; + + if (rownum < startRow || rownum > endRow) + { + continue; + } + + if (!copyRowHeight) + { + row.Height = /*setter*/-1; + } + + row.Shift(n); } - } - public IEnumerator GetEnumerator() - { - return _rows.Values.GetEnumerator(); - } + // adjust all the affected comment-structures now + // the Map is sorted and thus provides them in the order that we need here, + // i.e. from down to up if Shifting down, vice-versa otherwise + foreach (KeyValuePair entry in commentsToShift) + { + entry.Key.Row = entry.Value; + } - public IEnumerator GetRowEnumerator() - { - return GetEnumerator(); + RebuildRows(); } - public bool IsActive + private void ValidateCellsForCopyComment(ICell sourceCell, ICell targetCell) { - get + if (sourceCell == null || targetCell == null) { - return IsSelected; + throw new ArgumentException("Cells can not be null"); } - set + + if (sourceCell.Sheet != targetCell.Sheet || sourceCell.Sheet != this) { - IsSelected = value; + throw new ArgumentException("Cells should belong to the same worksheet"); } - } - public bool IsMergedRegion(CellRangeAddress mergedRegion) - { - if (worksheet.mergeCells == null || worksheet.mergeCells.mergeCell == null) - return false; - foreach (CT_MergeCell mc in worksheet.mergeCells.mergeCell) + if (sheetComments == null) { - if (!string.IsNullOrEmpty(mc.@ref)) - { - CellRangeAddress range = CellRangeAddress.ValueOf(mc.@ref); - if (range.FirstColumn <= mergedRegion.FirstColumn - && range.LastColumn >= mergedRegion.LastColumn - && range.FirstRow <= mergedRegion.FirstRow - && range.LastRow >= mergedRegion.LastRow) - { - return true; - } - } + throw new ArgumentException("Source cell doesn't have a comment"); } - return false; - } - public void SetActive(bool value) - { - this.IsSelected = value; - } - public void SetActiveCellRange(List cellranges, int activeRange, int activeRow, int activeColumn) - { - throw new NotImplementedException(); + if (sheetComments.FindCellComment(sourceCell.Address) == null) + { + throw new ArgumentException("Source cell doesn't have a comment"); + } } - public void SetActiveCellRange(int firstRow, int lastRow, int firstColumn, int lastColumn) + private int ShiftedRowOrColumnNumber(int startIndex, int endIndex, int n, int index) { - throw new NotImplementedException(); - } + // no change if before any affected index + if (index < startIndex && (n > 0 || (startIndex - index) > n)) + { + return index; + } + // no change if After any affected index + if (index > endIndex && (n < 0 || (index - endIndex) > n)) + { + return index; + } - public short TabColorIndex - { - get + // index before and things are Moved up/left + if (index < startIndex) { - throw new NotImplementedException("Use XSSFSheet.TabColor instead"); + // index is Moved down/right by the Shifting + return index + (endIndex - startIndex); } - set + + // index is After and things are Moved down/right + if (index > endIndex) { - throw new NotImplementedException("Use XSSFSheet.TabColor instead"); + // index is Moved up/left by the Shifting + return index - (endIndex - startIndex); } + + // index is part of the Shifted block + return index + n; } - public bool IsRightToLeft + private CT_Selection GetSheetTypeSelection() { - get - { - CT_SheetView view = this.GetDefaultSheetView(); - return view == null ? false : view.rightToLeft; - } - set + if (GetSheetTypeSheetView().SizeOfSelectionArray() == 0) { - CT_SheetView view = this.GetDefaultSheetView(); - view.rightToLeft = value; + GetSheetTypeSheetView().InsertNewSelection(0); } + + return GetSheetTypeSheetView().GetSelectionArray(0); } - #endregion + /// + /// Return the default sheet view. This is the last one if the sheet's + /// views, according to sec. 3.3.1.83 of the OOXML spec: "A single + /// sheet view defInition. When more than 1 sheet view is defined in + /// the file, it means that when opening the workbook, each sheet view + /// corresponds to a separate window within the spreadsheet + /// application, where each window is Showing the particular sheet. + /// Containing the same workbookViewId value, the last sheetView + /// defInition is loaded, and the others are discarded. When multiple + /// windows are viewing the same sheet, multiple sheetView elements + /// (with corresponding workbookView entries) are saved." + /// + /// + private CT_SheetView GetDefaultSheetView() + { + CT_SheetViews views = GetSheetTypeSheetViews(); + int sz = views == null ? 0 : views.sizeOfSheetViewArray(); + if (sz == 0) + { + return null; + } + return views.GetSheetViewArray(sz - 1); + } - public IRow CopyRow(int sourceIndex, int targetIndex) + private CT_PageSetUpPr GetSheetTypePageSetUpPr() { - return SheetUtil.CopyRow(this, sourceIndex, targetIndex); + CT_SheetPr sheetPr = GetSheetTypeSheetPr(); + return sheetPr.IsSetPageSetUpPr() ? sheetPr.pageSetUpPr : sheetPr.AddNewPageSetUpPr(); } - public CellRangeAddress RepeatingRows + private static bool ShouldRemoveAtIndex(int startIndex, int endIndex, int n, int currentIndex) { - get + if (currentIndex >= (startIndex + n) && currentIndex <= (endIndex + n)) { - return GetRepeatingRowsOrColums(true); + if (n > 0 && currentIndex > endIndex) + { + return true; + } + else if (n < 0 && currentIndex < startIndex) + { + return true; + } } - set + + return false; + } + + private CT_Pane GetPane() + { + if (GetDefaultSheetView().pane == null) { - CellRangeAddress columnRangeRef = RepeatingColumns; - SetRepeatingRowsAndColumns(value, columnRangeRef); + GetDefaultSheetView().AddNewPane(); } + + return GetDefaultSheetView().pane; + } + + private void SetSheetFormatPrOutlineLevelRow() + { + short maxLevelRow = GetMaxOutlineLevelRows(); + GetSheetTypeSheetFormatPr().outlineLevelRow = (byte)maxLevelRow; } + private void SetSheetFormatPrOutlineLevelCol() + { + short maxLevelCol = GetMaxOutlineLevelCols(); + GetSheetTypeSheetFormatPr().outlineLevelCol = (byte)maxLevelCol; + } - public CellRangeAddress RepeatingColumns + private CT_SheetViews GetSheetTypeSheetViews() { - get - { - return GetRepeatingRowsOrColums(false); - } - set + if (worksheet.sheetViews == null) { - CellRangeAddress rowRangeRef = RepeatingRows; - SetRepeatingRowsAndColumns(rowRangeRef, value); + worksheet.sheetViews = new CT_SheetViews(); + worksheet.sheetViews.AddNewSheetView(); } + + return worksheet.sheetViews; } - private CT_Pane Pane + + private CT_SheetProtection SafeGetProtectionField() { - get + if (!IsSheetProtectionEnabled()) { - if (GetDefaultSheetView().pane == null) - { - GetDefaultSheetView().AddNewPane(); - } - return GetDefaultSheetView().pane; + return worksheet.AddNewSheetProtection(); } + + return worksheet.sheetProtection; } - public void ShowInPane(int toprow, int leftcol) + + private bool IsSheetProtectionEnabled() { - CellReference cellReference = new CellReference(toprow, leftcol); - String cellRef = cellReference.FormatAsString(); - Pane.topLeftCell = cellRef; + return worksheet.IsSetSheetProtection(); } - private void SetRepeatingRowsAndColumns( - CellRangeAddress rowDef, CellRangeAddress colDef) - { - int col1 = -1; - int col2 = -1; - int row1 = -1; - int row2 = -1; - if (rowDef != null) - { - row1 = rowDef.FirstRow; - row2 = rowDef.LastRow; - if ((row1 == -1 && row2 != -1) - || row1 < -1 || row2 < -1 || row1 > row2) - { - throw new ArgumentException("Invalid row range specification"); - } - } - if (colDef != null) + /// + /// Also Creates cells if they don't exist + /// + /// + /// + private ICellRange GetCellRange(CellRangeAddress range) + { + int firstRow = range.FirstRow; + int firstColumn = range.FirstColumn; + int lastRow = range.LastRow; + int lastColumn = range.LastColumn; + int height = lastRow - firstRow + 1; + int width = lastColumn - firstColumn + 1; + List temp = new List(height * width); + for (int rowIn = firstRow; rowIn <= lastRow; rowIn++) { - col1 = colDef.FirstColumn; - col2 = colDef.LastColumn; - if ((col1 == -1 && col2 != -1) - || col1 < -1 || col2 < -1 || col1 > col2) + for (int colIn = firstColumn; colIn <= lastColumn; colIn++) { - throw new ArgumentException( - "Invalid column range specification"); - } - } + IRow row = GetRow(rowIn); + if (row == null) + { + row = CreateRow(rowIn); + } - int sheetIndex = Workbook.GetSheetIndex(this); + ICell cell = row.GetCell(colIn); + if (cell == null) + { + cell = row.CreateCell(colIn); + } - bool removeAll = rowDef == null && colDef == null; - XSSFWorkbook xwb = Workbook as XSSFWorkbook; - if (xwb == null) - throw new RuntimeException("Workbook should not be null"); - XSSFName name = xwb.GetBuiltInName(XSSFName.BUILTIN_PRINT_TITLE, sheetIndex); - if (removeAll) - { - if (name != null) - { - xwb.RemoveName(name); + temp.Add(cell); } - return; - } - if (name == null) - { - name = xwb.CreateBuiltInName( - XSSFName.BUILTIN_PRINT_TITLE, sheetIndex); } - String reference = GetReferenceBuiltInRecord( - name.SheetName, col1, col2, row1, row2); - name.RefersToFormula = (reference); - - // If the print setup isn't currently defined, then add it - // in but without printer defaults - // If it's already there, leave it as-is! - if (worksheet.IsSetPageSetup() && worksheet.IsSetPageMargins()) - { - // Everything we need is already there - } - else - { - // Have initial ones put in place - PrintSetup.ValidSettings = (false); - } + return SSCellRange.Create(firstRow, firstColumn, height, width, temp, typeof(ICell)); } - private static String GetReferenceBuiltInRecord( - String sheetName, int startC, int endC, int startR, int endR) + private static string GetReferenceBuiltInRecord( + string sheetName, int startC, int endC, int startR, int endR) { // Excel example for built-in title: // 'second sheet'!$E:$F,'second sheet'!$2:$3 @@ -4735,22 +5371,22 @@ private static String GetReferenceBuiltInRecord( CellReference rowRef2 = new CellReference(sheetName, endR, 0, true, true); - String escapedName = SheetNameFormatter.Format(sheetName); + string escapedName = SheetNameFormatter.Format(sheetName); - String c = ""; - String r = ""; + string c = ""; + string r = ""; if (startC != -1 || endC != -1) { - String col1 = colRef.CellRefParts[2]; - String col2 = colRef2.CellRefParts[2]; + string col1 = colRef.CellRefParts[2]; + string col2 = colRef2.CellRefParts[2]; c = escapedName + "!$" + col1 + ":$" + col2; } if (startR != -1 || endR != -1) { - String row1 = rowRef.CellRefParts[1]; - String row2 = rowRef2.CellRefParts[1]; + string row1 = rowRef.CellRefParts[1]; + string row2 = rowRef2.CellRefParts[1]; if (!row1.Equals("0") && !row2.Equals("0")) { r = escapedName + "!$" + row1 + ":$" + row2; @@ -4763,31 +5399,35 @@ private static String GetReferenceBuiltInRecord( { rng.Append(','); } + rng.Append(r); return rng.ToString(); } - private CellRangeAddress GetRepeatingRowsOrColums(bool rows) { int sheetIndex = Workbook.GetSheetIndex(this); - XSSFWorkbook xwb = Workbook as XSSFWorkbook; - if (xwb == null) + if (!(Workbook is XSSFWorkbook xwb)) + { throw new RuntimeException("Workbook should not be null"); + } + XSSFName name = xwb.GetBuiltInName(XSSFName.BUILTIN_PRINT_TITLE, sheetIndex); if (name == null) { return null; } - String refStr = name.RefersToFormula; + + string refStr = name.RefersToFormula; if (refStr == null) { return null; } - String[] parts = refStr.Split(",".ToCharArray()); + + string[] parts = refStr.Split(",".ToCharArray()); int maxRowIndex = SpreadsheetVersion.EXCEL2007.LastRowIndex; int maxColIndex = SpreadsheetVersion.EXCEL2007.LastColumnIndex; - foreach (String part in parts) + foreach (string part in parts) { CellRangeAddress range = CellRangeAddress.ValueOf(part); if ((range.FirstColumn == 0 @@ -4800,8 +5440,8 @@ private CellRangeAddress GetRepeatingRowsOrColums(bool rows) return range; } } - else if (range.FirstRow == 0 - && range.LastRow == maxRowIndex + else if ((range.FirstRow == 0 + && range.LastRow == maxRowIndex) || (range.FirstRow == -1 && range.LastRow == -1)) { @@ -4811,93 +5451,82 @@ private CellRangeAddress GetRepeatingRowsOrColums(bool rows) } } } - return null; - } - public ISheet CopySheet(String Name) - { - return CopySheet(Name, true); + return null; } - public ISheet CopySheet(String name, Boolean copyStyle) + private void SetRepeatingRowsAndColumns( + CellRangeAddress rowDef, CellRangeAddress colDef) { - String clonedName = SheetUtil.GetUniqueSheetName(this.Workbook, name); - XSSFSheet clonedSheet = (XSSFSheet)this.Workbook.CreateSheet(clonedName); + int col1 = -1; + int col2 = -1; + int row1 = -1; + int row2 = -1; - try + if (rowDef != null) { - using (MemoryStream ms = RecyclableMemory.GetStream()) + row1 = rowDef.FirstRow; + row2 = rowDef.LastRow; + if ((row1 == -1 && row2 != -1) + || row1 < -1 || row2 < -1 || row1 > row2) { - this.Write(ms, true); - ms.Position = 0; - clonedSheet.Read(ms); + throw new ArgumentException("Invalid row range specification"); } } - catch (IOException e) + + if (colDef != null) { - throw new POIXMLException("Failed to clone sheet", e); + col1 = colDef.FirstColumn; + col2 = colDef.LastColumn; + if ((col1 == -1 && col2 != -1) + || col1 < -1 || col2 < -1 || col1 > col2) + { + throw new ArgumentException( + "Invalid column range specification"); + } } - CT_Worksheet ct = clonedSheet.GetCTWorksheet(); - if (ct.IsSetLegacyDrawing()) + int sheetIndex = Workbook.GetSheetIndex(this); + + bool removeAll = rowDef == null && colDef == null; + if (!(Workbook is XSSFWorkbook xwb)) { - logger.Log(POILogger.WARN, "Cloning sheets with comments is not yet supported."); - ct.UnsetLegacyDrawing(); + throw new RuntimeException("Workbook should not be null"); } - clonedSheet.IsSelected = false; - // copy sheet's relations - List rels = this.GetRelations(); - // if the sheet being cloned has a drawing then remember it and re-create too - XSSFDrawing dg = null; - foreach (POIXMLDocumentPart r in rels) + XSSFName name = xwb.GetBuiltInName(XSSFName.BUILTIN_PRINT_TITLE, sheetIndex); + if (removeAll) { - // do not copy the drawing relationship, it will be re-created - if (r is XSSFDrawing) + if (name != null) { - dg = (XSSFDrawing)r; - continue; + xwb.RemoveName(name); } - //skip printerSettings.bin part - if (r.GetPackagePart().PartName.Name.StartsWith("/xl/printerSettings/printerSettings")) - continue; - PackageRelationship rel = r.GetPackageRelationship(); - clonedSheet.GetPackagePart().AddRelationship( - rel.TargetUri, (TargetMode)rel.TargetMode, rel.RelationshipType); - clonedSheet.AddRelation(rel.Id, r); - } - // copy hyperlinks - clonedSheet.hyperlinks = new List(hyperlinks); + return; + } - // clone the sheet drawing along with its relationships - if (dg != null) + if (name == null) { - if (ct.IsSetDrawing()) - { - // unset the existing reference to the drawing, - // so that subsequent call of clonedSheet.createDrawingPatriarch() will create a new one - ct.UnsetDrawing(); - } - XSSFDrawing clonedDg = clonedSheet.CreateDrawingPatriarch() as XSSFDrawing; - // copy drawing contents - clonedDg.GetCTDrawing().Set(dg.GetCTDrawing()); + name = xwb.CreateBuiltInName( + XSSFName.BUILTIN_PRINT_TITLE, sheetIndex); + } - clonedDg = clonedSheet.CreateDrawingPatriarch() as XSSFDrawing; + string reference = GetReferenceBuiltInRecord( + name.SheetName, col1, col2, row1, row2); + name.RefersToFormula = reference; - // Clone drawing relations - List srcRels = dg.GetRelations(); - foreach (POIXMLDocumentPart rel in srcRels) - { - PackageRelationship relation = rel.GetPackageRelationship(); - clonedDg.AddRelation(relation.Id, rel); - clonedDg - .GetPackagePart() - .AddRelationship(relation.TargetUri, relation.TargetMode.Value, - relation.RelationshipType, relation.Id); - } + // If the print setup isn't currently defined, then add it + // in but without printer defaults + // If it's already there, leave it as-is! + if (worksheet.IsSetPageSetup() && worksheet.IsSetPageMargins()) + { + // Everything we need is already there + } + else + { + // Have initial ones put in place + PrintSetup.ValidSettings = false; } - return clonedSheet; } private void CopySheetImages(XSSFWorkbook destWorkbook, XSSFSheet destSheet) @@ -4908,25 +5537,32 @@ private void CopySheetImages(XSSFWorkbook destWorkbook, XSSFSheet destSheet) IDrawing destDraw = destSheet.CreateDrawingPatriarch(); List sheetPictures = sheetDrawing.GetRelations(); Dictionary pictureIdMapping = new Dictionary(); - foreach (OpenXmlFormats.Dml.Spreadsheet.IEG_Anchor anchor in sheetDrawing.GetCTDrawing().CellAnchors) + foreach (IEG_Anchor anchor in sheetDrawing.GetCTDrawing().CellAnchors) { - OpenXmlFormats.Dml.Spreadsheet.CT_TwoCellAnchor cellAnchor = anchor as OpenXmlFormats.Dml.Spreadsheet.CT_TwoCellAnchor; - if (cellAnchor != null) + if (anchor is CT_TwoCellAnchor cellAnchor) { - XSSFClientAnchor newAnchor = new XSSFClientAnchor((int)cellAnchor.from.colOff, (int)cellAnchor.from.rowOff, - (int)cellAnchor.to.colOff, (int)cellAnchor.to.rowOff, cellAnchor.from.col, cellAnchor.from.row, cellAnchor.to.col, cellAnchor.to.row); + XSSFClientAnchor newAnchor = new XSSFClientAnchor( + (int)cellAnchor.from.colOff, + (int)cellAnchor.from.rowOff, + (int)cellAnchor.to.colOff, + (int)cellAnchor.to.rowOff, + cellAnchor.from.col, + cellAnchor.from.row, + cellAnchor.to.col, + cellAnchor.to.row); + if (cellAnchor.editAsSpecified) { switch (cellAnchor.editAs) { - case OpenXmlFormats.Dml.Spreadsheet.ST_EditAs.twoCell: + case ST_EditAs.twoCell: newAnchor.AnchorType = AnchorType.MoveAndResize; break; - case OpenXmlFormats.Dml.Spreadsheet.ST_EditAs.oneCell: + case ST_EditAs.oneCell: newAnchor.AnchorType = AnchorType.MoveDontResize; break; - case OpenXmlFormats.Dml.Spreadsheet.ST_EditAs.absolute: - case OpenXmlFormats.Dml.Spreadsheet.ST_EditAs.NONE: + case ST_EditAs.absolute: + case ST_EditAs.NONE: default: newAnchor.AnchorType = AnchorType.DontMoveAndResize; break; @@ -4935,113 +5571,54 @@ private void CopySheetImages(XSSFWorkbook destWorkbook, XSSFSheet destSheet) string oldPictureId = anchor.picture?.blipFill?.blip.embed; if (oldPictureId == null) - continue; - if (!pictureIdMapping.ContainsKey(oldPictureId)) { - XSSFPictureData srcPic = FindPicture(sheetPictures, oldPictureId); - if (srcPic != null && srcPic.PictureType != PictureType.None) - { - pictureIdMapping.Add(oldPictureId, (uint)destWorkbook.AddPicture(srcPic.Data, srcPic.PictureType)); - } - else - { - continue; //Unable to find this picture, so skip it - } + continue; } - destDraw.CreatePicture(newAnchor, (int)pictureIdMapping[oldPictureId]); - } - } - } - } - private XSSFPictureData FindPicture(List sheetPictures, string id) - { - foreach (POIXMLDocumentPart item in sheetPictures) - { - if (item.GetPackageRelationship().Id == id) - { - return item as XSSFPictureData; - } - } - return null; - } - public void CopyTo(IWorkbook dest, string name, bool copyStyle, bool keepFormulas) - { - StylesTable styles = ((XSSFWorkbook)dest).GetStylesSource(); - if (copyStyle && Workbook.NumberOfFonts > 0) - { - foreach (var font in((XSSFWorkbook)Workbook).GetStylesSource().GetFonts()) - { - styles.PutFont(font); //TODO::create real font mapping, the correct logic may be wrong - } - } - XSSFSheet newSheet = (XSSFSheet)dest.CreateSheet(name); - newSheet.sheet.state = sheet.state; - IDictionary styleMap = (copyStyle) ? new Dictionary() : null; - for (int i = FirstRowNum; i <= LastRowNum; i++) - { - XSSFRow srcRow = (XSSFRow)GetRow(i); - XSSFRow destRow = (XSSFRow)newSheet.CreateRow(i); - if (srcRow != null) - { - CopyRow(this, newSheet, srcRow, destRow, styleMap, keepFormulas); + + if (!pictureIdMapping.ContainsKey(oldPictureId)) + { + XSSFPictureData srcPic = FindPicture(sheetPictures, oldPictureId); + if (srcPic != null && srcPic.PictureType != PictureType.None) + { + pictureIdMapping.Add( + oldPictureId, + (uint)destWorkbook.AddPicture(srcPic.Data, srcPic.PictureType)); + } + else + { + continue; //Unable to find this picture, so skip it + } + } + + destDraw.CreatePicture(newAnchor, (int)pictureIdMapping[oldPictureId]); + } } } - List srcCols = worksheet.GetColsList(); - List dstCols = newSheet.worksheet.GetColsList(); - dstCols.Clear(); //Should already be empty since this is a new sheet. - foreach (CT_Cols srcCol in srcCols) + } + + private XSSFPictureData FindPicture(List sheetPictures, string id) + { + foreach (POIXMLDocumentPart item in sheetPictures) { - CT_Cols dstCol = new CT_Cols(); - foreach (var column in srcCol.col) + if (item.GetPackageRelationship().Id == id) { - dstCol.col.Add(column.Copy()); + return item as XSSFPictureData; } - dstCols.Add(dstCol); - } - newSheet.ForceFormulaRecalculation = true; - newSheet.PrintSetup.Landscape = PrintSetup.Landscape; - newSheet.PrintSetup.HResolution = PrintSetup.HResolution; - newSheet.PrintSetup.VResolution = PrintSetup.VResolution; - newSheet.SetMargin(MarginType.LeftMargin, GetMargin(MarginType.LeftMargin)); - newSheet.SetMargin(MarginType.RightMargin, GetMargin(MarginType.RightMargin)); - newSheet.SetMargin(MarginType.TopMargin, GetMargin(MarginType.TopMargin)); - newSheet.SetMargin(MarginType.BottomMargin, GetMargin(MarginType.BottomMargin)); - newSheet.PrintSetup.HeaderMargin = PrintSetup.HeaderMargin; - newSheet.PrintSetup.FooterMargin = PrintSetup.FooterMargin; - newSheet.Header.Left = Header.Left; - newSheet.Header.Center = Header.Center; - newSheet.Header.Right = Header.Right; - newSheet.Footer.Left = Footer.Left; - newSheet.Footer.Center = Footer.Center; - newSheet.Footer.Right = Footer.Right; - newSheet.PrintSetup.Scale = PrintSetup.Scale; - newSheet.PrintSetup.FitHeight = PrintSetup.FitHeight; - newSheet.PrintSetup.FitWidth = PrintSetup.FitWidth; - newSheet.DisplayGridlines = DisplayGridlines; - if (worksheet.IsSetSheetPr()) - { - newSheet.worksheet.sheetPr = worksheet.sheetPr.Clone(); - } - if (GetDefaultSheetView().pane != null) - { - var oldPane = GetDefaultSheetView().pane; - var newPane = newSheet.GetPane(); - newPane.activePane = oldPane.activePane; - newPane.state = oldPane.state; - newPane.topLeftCell = oldPane.topLeftCell; - newPane.xSplit = oldPane.xSplit; - newPane.ySplit = oldPane.ySplit; } - CopySheetImages(dest as XSSFWorkbook, newSheet); + + return null; } - private static void CopyRow(XSSFSheet srcSheet, XSSFSheet destSheet, XSSFRow srcRow, XSSFRow destRow, IDictionary styleMap, bool keepFormulas) + + private static void CopyRow(XSSFSheet srcSheet, XSSFSheet destSheet, XSSFRow srcRow, XSSFRow destRow, IDictionary styleMap, bool keepFormulas) { destRow.Height = srcRow.Height; if (!srcRow.GetCTRow().IsSetCustomHeight()) { - //Copying height sets the custom height flag, but Excel will set a value for height even if it's auto-sized. + //Copying height sets the custom height flag, but Excel will + //set a value for height even if it's auto-sized. destRow.GetCTRow().UnsetCustomHeight(); } + destRow.Hidden = srcRow.Hidden; destRow.Collapsed = srcRow.Collapsed; destRow.OutlineLevel = srcRow.OutlineLevel; @@ -5050,6 +5627,7 @@ private static void CopyRow(XSSFSheet srcSheet, XSSFSheet destSheet, XSSFRow src { return; //Row has no cells, this sometimes happens with hidden or blank rows } + for (int j = srcRow.FirstCellNum; j <= srcRow.LastCellNum; j++) { XSSFCell oldCell = (XSSFCell)srcRow.GetCell(j); @@ -5058,18 +5636,27 @@ private static void CopyRow(XSSFSheet srcSheet, XSSFSheet destSheet, XSSFRow src { newCell = (XSSFCell)destRow.GetCell(j); } + if (oldCell != null) { if (newCell == null) { newCell = (XSSFCell)destRow.CreateCell(j); } - XSSFSheet.CopyCell(oldCell, newCell, styleMap, keepFormulas); - CellRangeAddress mergedRegion = srcSheet.GetMergedRegion(new CellRangeAddress(srcRow.RowNum, srcRow.RowNum, (short)oldCell.ColumnIndex, (short)oldCell.ColumnIndex)); + + CopyCell(oldCell, newCell, styleMap, keepFormulas); + CellRangeAddress mergedRegion = srcSheet.GetMergedRegion( + new CellRangeAddress(srcRow.RowNum, srcRow.RowNum, + (short)oldCell.ColumnIndex, + (short)oldCell.ColumnIndex)); + if (mergedRegion != null) { - CellRangeAddress newMergedRegion = new CellRangeAddress(mergedRegion.FirstRow, - mergedRegion.LastRow, mergedRegion.FirstColumn, mergedRegion.LastColumn); + CellRangeAddress newMergedRegion = new CellRangeAddress( + mergedRegion.FirstRow, + mergedRegion.LastRow, + mergedRegion.FirstColumn, + mergedRegion.LastColumn); if (!destSheet.IsMergedRegion(newMergedRegion)) { @@ -5079,7 +5666,8 @@ private static void CopyRow(XSSFSheet srcSheet, XSSFSheet destSheet, XSSFRow src } } } - private static void CopyCell(ICell oldCell, ICell newCell, IDictionary styleMap, Boolean keepFormulas) + + private static void CopyCell(ICell oldCell, ICell newCell, IDictionary styleMap, bool keepFormulas) { if (styleMap != null) { @@ -5098,7 +5686,7 @@ private static void CopyCell(ICell oldCell, ICell newCell, IDictionary + /// Creates an empty XSSFPivotTable and Sets up all its relationships + /// including: pivotCacheDefInition, pivotCacheRecords + /// + /// a pivotTable private XSSFPivotTable CreatePivotTable() { XSSFWorkbook wb = GetWorkbook(); List pivotTables = wb.PivotTables; int tableId = GetWorkbook().PivotTables.Count + 1; //Create relationship between pivotTable and the worksheet - XSSFPivotTable pivotTable = (XSSFPivotTable)CreateRelationship(XSSFRelation.PIVOT_TABLE, - XSSFFactory.GetInstance(), tableId); + XSSFPivotTable pivotTable = (XSSFPivotTable)CreateRelationship( + XSSFRelation.PIVOT_TABLE, + XSSFFactory.GetInstance(), + tableId); pivotTable.SetParentSheet(this); pivotTables.Add(pivotTable); XSSFWorkbook workbook = GetWorkbook(); //Create relationship between the pivot cache defintion and the workbook XSSFPivotCacheDefinition pivotCacheDefinition = (XSSFPivotCacheDefinition)workbook. - CreateRelationship(XSSFRelation.PIVOT_CACHE_DEFINITION, XSSFFactory.GetInstance(), tableId); - String rId = workbook.GetRelationId(pivotCacheDefinition); - //Create relationship between pivotTable and pivotCacheDefInition without creating a new instance + CreateRelationship( + XSSFRelation.PIVOT_CACHE_DEFINITION, + XSSFFactory.GetInstance(), + tableId); + string rId = workbook.GetRelationId(pivotCacheDefinition); + //Create relationship between pivotTable and pivotCacheDefInition + //without creating a new instance PackagePart pivotPackagePart = pivotTable.GetPackagePart(); - pivotPackagePart.AddRelationship(pivotCacheDefinition.GetPackagePart().PartName, - TargetMode.Internal, XSSFRelation.PIVOT_CACHE_DEFINITION.Relation); + pivotPackagePart.AddRelationship( + pivotCacheDefinition.GetPackagePart().PartName, + TargetMode.Internal, + XSSFRelation.PIVOT_CACHE_DEFINITION.Relation); pivotTable.SetPivotCacheDefinition(pivotCacheDefinition); @@ -5209,65 +5805,31 @@ private XSSFPivotTable CreatePivotTable() //Create relationship between pivotcacherecord and pivotcachedefInition XSSFPivotCacheRecords pivotCacheRecords = (XSSFPivotCacheRecords)pivotCacheDefinition. - CreateRelationship(XSSFRelation.PIVOT_CACHE_RECORDS, XSSFFactory.GetInstance(), tableId); + CreateRelationship( + XSSFRelation.PIVOT_CACHE_RECORDS, + XSSFFactory.GetInstance(), + tableId); //Set relationships id for pivotCacheDefInition to pivotCacheRecords - pivotTable.GetPivotCacheDefinition().GetCTPivotCacheDefinition().id = (/*setter*/pivotCacheDefinition.GetRelationId(pivotCacheRecords)); + pivotTable.GetPivotCacheDefinition().GetCTPivotCacheDefinition().id = + pivotCacheDefinition.GetRelationId(pivotCacheRecords); - wb.PivotTables = (/*setter*/pivotTables); + wb.PivotTables = /*setter*/pivotTables; return pivotTable; } - /** - * Create a pivot table using the AreaReference range on sourceSheet, at the given position. - * If the source reference contains a sheet name, it must match the sourceSheet - * @param source location of pivot data - * @param position A reference to the top left cell where the pivot table will start - * @param sourceSheet The sheet containing the source data, if the source reference doesn't contain a sheet name - * @throws IllegalArgumentException if source references a sheet different than sourceSheet - * @return The pivot table - */ - public XSSFPivotTable CreatePivotTable(AreaReference source, CellReference position, ISheet sourceSheet) - { - String sourceSheetName = source.FirstCell.SheetName; - if (sourceSheetName != null && !sourceSheetName.Equals(sourceSheet.SheetName, StringComparison.InvariantCultureIgnoreCase)) - { - throw new ArgumentException("The area is referenced in another sheet than the " - + "defined source sheet " + sourceSheet.SheetName + "."); - } - XSSFPivotTable.IPivotTableReferenceConfigurator refConfig = new PivotTableReferenceConfigurator1(source); - return CreatePivotTable(position, sourceSheet, refConfig); - } - public class PivotTableReferenceConfigurator1 : XSSFPivotTable.IPivotTableReferenceConfigurator - { - AreaReference source; - public PivotTableReferenceConfigurator1(AreaReference source) - { - this.source = source; - } - public void ConfigureReference(CT_WorksheetSource wsSource) - { - String[] firstCell = source.FirstCell.CellRefParts; - String firstRow = firstCell[1]; - String firstCol = firstCell[2]; - String[] lastCell = source.LastCell.CellRefParts; - String lastRow = lastCell[1]; - String lastCol = lastCell[2]; - String ref1 = firstCol + firstRow + ':' + lastCol + lastRow; //or just source.formatAsString() - wsSource.@ref = ref1; - } - } - /** - * Create a pivot table using the AreaReference or named/table range on sourceSheet, at the given position. - * If the source reference contains a sheet name, it must match the sourceSheet. - * @param sourceRef location of pivot data - mutually exclusive with SourceName - * @param sourceName range or table name for pivot data - mutually exclusive with SourceRef - * @param position A reference to the top left cell where the pivot table will start - * @param sourceSheet The sheet containing the source data, if the source reference doesn't contain a sheet name - * @throws IllegalArgumentException if source references a sheet different than sourceSheet - * @return The pivot table - */ + /// + /// Create a pivot table using the AreaReference or named/table range + /// on sourceSheet, at the given position. If the source reference + /// contains a sheet name, it must match the sourceSheet. + /// + /// A reference to the top left cell where the + /// pivot table will start + /// The sheet containing the source data, + /// if the source reference doesn't contain a sheet name + /// + /// The pivot table private XSSFPivotTable CreatePivotTable(CellReference position, ISheet sourceSheet, XSSFPivotTable.IPivotTableReferenceConfigurator refConfig) { @@ -5285,246 +5847,108 @@ private XSSFPivotTable CreatePivotTable(CellReference position, ISheet sourceShe return pivotTable; } - /** - * Create a pivot table using the AreaReference range, at the given position. - * If the source reference contains a sheet name, that sheet is used, otherwise this sheet is assumed as the source sheet. - * @param source location of pivot data - * @param position A reference to the top left cell where the pivot table will start - * @return The pivot table - */ - public XSSFPivotTable CreatePivotTable(AreaReference source, CellReference position) + private void AddIgnoredErrors(string ref1, params IgnoredErrorType[] ignoredErrorTypes) { - String sourceSheetName = source.FirstCell.SheetName; - if (sourceSheetName != null && !sourceSheetName.Equals(this.SheetName, StringComparison.InvariantCultureIgnoreCase)) - { - XSSFSheet sourceSheet = Workbook.GetSheet(sourceSheetName) as XSSFSheet; - return CreatePivotTable(source, position, sourceSheet); - } - return CreatePivotTable(source, position, this); + CT_IgnoredErrors ctIgnoredErrors = worksheet.IsSetIgnoredErrors() ? worksheet.ignoredErrors : worksheet.AddNewIgnoredErrors(); + CT_IgnoredError ctIgnoredError = ctIgnoredErrors.AddNewIgnoredError(); + XSSFIgnoredErrorHelper.AddIgnoredErrors(ctIgnoredError, ref1, ignoredErrorTypes); } - /** - * Create a pivot table using the Name range reference on sourceSheet, at the given position. - * If the source reference contains a sheet name, it must match the sourceSheet - * @param source location of pivot data - * @param position A reference to the top left cell where the pivot table will start - * @param sourceSheet The sheet containing the source data, if the source reference doesn't contain a sheet name - * @ if source references a sheet different than sourceSheet - * @return The pivot table - */ - - public XSSFPivotTable CreatePivotTable(IName source, CellReference position, ISheet sourceSheet) + private ISet GetErrorTypes(CT_IgnoredError err) { - if (source.SheetName != null && !source.SheetName.Equals(sourceSheet.SheetName)) + ISet result = new HashSet(); + + foreach (IgnoredErrorType errType in IgnoredErrorTypeValues.Values) { - throw new ArgumentException("The named range references another sheet than the " - + "defined source sheet " + sourceSheet.SheetName + "."); + if (XSSFIgnoredErrorHelper.IsSet(errType, err)) + { + result.Add(errType); + } } - return CreatePivotTable(position, sourceSheet, new PivotTableReferenceConfigurator2(source)); + return result; } + #endregion + + #region Helper classes public class PivotTableReferenceConfigurator2 : XSSFPivotTable.IPivotTableReferenceConfigurator { - IName source; + private readonly IName source; public PivotTableReferenceConfigurator2(IName source) { this.source = source; } public void ConfigureReference(CT_WorksheetSource wsSource) { - wsSource.name = (source.NameName); + wsSource.name = source.NameName; } } - /** - * Create a pivot table using the Name range, at the given position. - * If the source reference contains a sheet name, that sheet is used, otherwise this sheet is assumed as the source sheet. - * @param source location of pivot data - * @param position A reference to the top left cell where the pivot table will start - * @return The pivot table - */ - public XSSFPivotTable CreatePivotTable(IName source, CellReference position) - { - return CreatePivotTable(source, position, GetWorkbook().GetSheet(source.SheetName)); - } - - /** - * Create a pivot table using the Table, at the given position. - * Tables are required to have a sheet reference, so no additional logic around reference sheet is needed. - * @param source location of pivot data - * @param position A reference to the top left cell where the pivot table will start - * @return The pivot table - */ - - public XSSFPivotTable CreatePivotTable(ITable source, CellReference position) + public class PivotTableReferenceConfigurator1 : XSSFPivotTable.IPivotTableReferenceConfigurator { - return CreatePivotTable(position, GetWorkbook().GetSheet(source.SheetName), new PivotTableReferenceConfigurator3(source)); + private readonly AreaReference source; + public PivotTableReferenceConfigurator1(AreaReference source) + { + this.source = source; + } + public void ConfigureReference(CT_WorksheetSource wsSource) + { + string[] firstCell = source.FirstCell.CellRefParts; + string firstRow = firstCell[1]; + string firstCol = firstCell[2]; + string[] lastCell = source.LastCell.CellRefParts; + string lastRow = lastCell[1]; + string lastCol = lastCell[2]; + string ref1 = firstCol + firstRow + ':' + lastCol + lastRow; //or just source.formatAsString() + wsSource.@ref = ref1; + } } public class PivotTableReferenceConfigurator3 : XSSFPivotTable.IPivotTableReferenceConfigurator { - ITable source; + private readonly ITable source; public PivotTableReferenceConfigurator3(ITable source) { this.source = source; } public void ConfigureReference(CT_WorksheetSource wsSource) { - wsSource.name = (source.Name); - } - } - /** - * Returns all the pivot tables for this Sheet - */ - public List GetPivotTables() - { - List tables = new List(); - foreach (XSSFPivotTable table in GetWorkbook().PivotTables) - { - if (table.GetParent() == this) - { - tables.Add(table); - } + wsSource.name = source.Name; } - return tables; } - public int GetColumnOutlineLevel(int columnIndex) + private class ShiftCommentComparator : IComparer { - CT_Col col = columnHelper.GetColumn(columnIndex, false); - if (col == null) + private readonly int shiftDir; + public ShiftCommentComparator(int shiftDir) { - return 0; + this.shiftDir = shiftDir; } - return col.outlineLevel; - } - - public bool IsDate1904() - { - throw new NotImplementedException(); - } - - - /** - * Add ignored errors (usually to suppress them in the UI of a consuming - * application). - * - * @param cell Cell. - * @param ignoredErrorTypes Types of error to ignore there. - */ - public void AddIgnoredErrors(CellReference cell, params IgnoredErrorType[] ignoredErrorTypes) - { - AddIgnoredErrors(cell.FormatAsString(), ignoredErrorTypes); - } - - /** - * Ignore errors across a range of cells. - * - * @param region Range of cells. - * @param ignoredErrorTypes Types of error to ignore there. - */ - public void AddIgnoredErrors(CellRangeAddress region, params IgnoredErrorType[] ignoredErrorTypes) - { - region.Validate(SpreadsheetVersion.EXCEL2007); - AddIgnoredErrors(region.FormatAsString(), ignoredErrorTypes); - } - - /** - * Returns the errors currently being ignored and the ranges - * where they are ignored. - * - * @return Map of error type to the range(s) where they are ignored. - */ - public Dictionary> GetIgnoredErrors() - { - Dictionary> result = new Dictionary>(); - if (worksheet.IsSetIgnoredErrors()) + public int Compare(XSSFComment o1, XSSFComment o2) { - foreach (CT_IgnoredError err in worksheet.ignoredErrors.ignoredError) + int row1 = o1.Row; + int row2 = o2.Row; + + if (row1 == row2) { - foreach (IgnoredErrorType errType in GetErrorTypes(err)) - { - if (!result.ContainsKey(errType)) - { - result.Add(errType, new HashSet()); - } - foreach (Object ref1 in err.sqref) - { - result[errType].Add(CellRangeAddress.ValueOf(ref1.ToString())); - } - } + // ordering is not important when row is equal, but don't + // return zero to still get multiple comments per row into + // the map + return o1.GetHashCode() - o2.GetHashCode(); } - } - return result; - } - - private void AddIgnoredErrors(String ref1, params IgnoredErrorType[] ignoredErrorTypes) - { - CT_IgnoredErrors ctIgnoredErrors = worksheet.IsSetIgnoredErrors() ? worksheet.ignoredErrors : worksheet.AddNewIgnoredErrors(); - CT_IgnoredError ctIgnoredError = ctIgnoredErrors.AddNewIgnoredError(); - XSSFIgnoredErrorHelper.AddIgnoredErrors(ctIgnoredError, ref1, ignoredErrorTypes); - } - - private ISet GetErrorTypes(CT_IgnoredError err) - { - ISet result = new HashSet(); - foreach (IgnoredErrorType errType in IgnoredErrorTypeValues.Values) - { - if (XSSFIgnoredErrorHelper.IsSet(errType, err)) + // when Shifting down, sort higher row-values first + if (shiftDir > 0) { - result.Add(errType); + return row1 < row2 ? 1 : -1; } - } - return result; - } - /** - * when a cell with a 'master' shared formula is removed, the next cell in the range becomes the master - * @param cell The cell that is removed - * @param evalWb BaseXSSFEvaluationWorkbook in use, if one exists - */ - internal void OnDeleteFormula(XSSFCell cell, XSSFEvaluationWorkbook evalWb) - { - - CT_CellFormula f = cell.GetCTCell().f; - if (f != null && f.t == ST_CellFormulaType.shared && f.isSetRef() && f.Value != null) - { - bool breakit = false; - CellRangeAddress ref1 = CellRangeAddress.ValueOf(f.@ref); - if (ref1.NumberOfCells > 1) + else { - for (int i = cell.RowIndex; i <= ref1.LastRow; i++) - { - XSSFRow row = (XSSFRow)GetRow(i); - if (row != null) - { - for (int j = cell.ColumnIndex; j <= ref1.LastColumn; j++) - { - XSSFCell nextCell = (XSSFCell)row.GetCell(j); - if (nextCell != null && nextCell != cell && nextCell.CellType == CellType.Formula) - { - CT_CellFormula nextF = nextCell.GetCTCell().f; - if (nextF.t == ST_CellFormulaType.shared && nextF.si == f.si) - { - nextF.Value = nextCell.GetCellFormula(evalWb); - CellRangeAddress nextRef = new CellRangeAddress( - nextCell.RowIndex, ref1.LastRow, - nextCell.ColumnIndex, ref1.LastColumn); - nextF.@ref=nextRef.FormatAsString(); - - sharedFormulas[(int)nextF.si]= nextF; - breakit = true; - break; - } - } - } - if (breakit) - break; - } - } + // sort lower-row values first when Shifting up + return row1 > row2 ? 1 : -1; } } } + #endregion } - } diff --git a/testcases/main/HSSF/UserModel/TestHSSFSheet.cs b/testcases/main/HSSF/UserModel/TestHSSFSheet.cs index cae46bab1..f627f1d05 100644 --- a/testcases/main/HSSF/UserModel/TestHSSFSheet.cs +++ b/testcases/main/HSSF/UserModel/TestHSSFSheet.cs @@ -849,7 +849,7 @@ public void TestAutoSizeRow() sheet.AutoSizeRow(row.RowNum); Assert.AreNotEqual(100, row.Height); - Assert.AreEqual(460, row.Height); + Assert.AreEqual(506, row.Height); workbook.Close(); } diff --git a/testcases/ooxml/XSSF/UserModel/TestXSSFSheet.cs b/testcases/ooxml/XSSF/UserModel/TestXSSFSheet.cs index 33936c4fc..4244d0f80 100644 --- a/testcases/ooxml/XSSF/UserModel/TestXSSFSheet.cs +++ b/testcases/ooxml/XSSF/UserModel/TestXSSFSheet.cs @@ -192,7 +192,7 @@ public void TestAutoSizeRow() sheet.AutoSizeRow(row.RowNum); Assert.AreNotEqual(100, row.Height); - Assert.AreEqual(500, row.Height); + Assert.AreEqual(550, row.Height); workbook.Close(); }