Another day and another little tidbit on using NPOI. I was doing tool mock-up at work today when I ran across a need for a copy row function. After searching high and low, I realized NPOI does not currently offer this capability. After looking around (Google, NPOI and POI threads) I decided to create my own helper function. I’m sure there might be a few things I missed in my routine since the library is a bit new to me, but after testing this against a bunch of different scenarios I’m pretty confident this will work for 99% of my needs and maybe a high percent of yours as well.
Here is the function in all it’s glory, I thought about modify the NPOI source but since I’m not sure where it’s going I figured I’d just add this in my own little NPOI.CustomHelpers class that I can use with my NPOI project.
/// <summary>
/// HSSFRow Copy Command
///
/// Description: Inserts a existing row into a new row, will automatically push down
/// any existing rows. Copy is done cell by cell and supports, and the
/// command tries to copy all properties available (style, merged cells, values, etc...)
/// </summary>
/// <param name="workbook">Workbook containing the worksheet that will be changed</param>
/// <param name="worksheet">WorkSheet containing rows to be copied</param>
/// <param name="sourceRowNum">Source Row Number</param>
/// <param name="destinationRowNum">Destination Row Number</param>
private void CopyRow(HSSFWorkbook workbook, HSSFSheet worksheet, int sourceRowNum, int destinationRowNum)
{
// Get the source / new row
HSSFRow newRow = worksheet.GetRow(destinationRowNum);
HSSFRow sourceRow = worksheet.GetRow(sourceRowNum);
// If the row exist in destination, push down all rows by 1 else create a new row
if (newRow != null)
{
worksheet.ShiftRows(destinationRowNum, worksheet.LastRowNum, 1);
}
else
{
newRow = worksheet.CreateRow(destinationRowNum);
}
// Loop through source columns to add to new row
for (int i = 0; i < sourceRow.LastCellNum; i++)
{
// Grab a copy of the old/new cell
HSSFCell oldCell = sourceRow.GetCell(i);
HSSFCell newCell = newRow.CreateCell(i);
// If the old cell is null jump to next cell
if (oldCell == null)
{
newCell = null;
continue;
}
// Copy style from old cell and apply to new cell
HSSFCellStyle newCellStyle = workbook.CreateCellStyle();
newCellStyle.CloneStyleFrom(oldCell.CellStyle); ;
newCell.CellStyle = newCellStyle;
// If there is a cell comment, copy
if (newCell.CellComment != null) newCell.CellComment = oldCell.CellComment;
// If there is a cell hyperlink, copy
if (oldCell.Hyperlink != null) newCell.Hyperlink = oldCell.Hyperlink;
// Set the cell data type
newCell.SetCellType(oldCell.CellType);
// Set the cell data value
switch (oldCell.CellType)
{
case HSSFCellType.BLANK:
newCell.SetCellValue(oldCell.StringCellValue);
break;
case HSSFCellType.BOOLEAN:
newCell.SetCellValue(oldCell.BooleanCellValue);
break;
case HSSFCellType.ERROR:
newCell.SetCellErrorValue(oldCell.ErrorCellValue);
break;
case HSSFCellType.FORMULA:
newCell.SetCellFormula(oldCell.CellFormula);
break;
case HSSFCellType.NUMERIC:
newCell.SetCellValue(oldCell.NumericCellValue);
break;
case HSSFCellType.STRING:
newCell.SetCellValue(oldCell.RichStringCellValue);
break;
case HSSFCellType.Unknown:
newCell.SetCellValue(oldCell.StringCellValue);
break;
}
}
// If there are are any merged regions in the source row, copy to new row
for (int i = 0; i < worksheet.NumMergedRegions; i++)
{
CellRangeAddress cellRangeAddress = worksheet.GetMergedRegion(i);
if (cellRangeAddress.FirstRow == sourceRow.RowNum)
{
CellRangeAddress newCellRangeAddress = new CellRangeAddress(newRow.RowNum,
(newRow.RowNum +
(cellRangeAddress.FirstRow -
cellRangeAddress.LastRow)),
cellRangeAddress.FirstColumn,
cellRangeAddress.LastColumn);
worksheet.AddMergedRegion(newCellRangeAddress);
}
}
}
The code comments above should give you a good idea of what I’m doing, if something doesn’t make sense just ask. The key things I wanted to make sure got copied were; Cell Style, Cell Value, Cell Type, Merged Cell Settings. In the end I noticed a few other things that I thought I might use in the future, so I included them as well. Here is an example of how to call to CopyRow along with a snapshot of the end result.
// Grab my NPOI workbook memorystream
HSSFWorkbook workbook = new HSSFWorkbook(memoryStream);
// Grab my test worksheet
HSSFSheet sheet = workbook.GetSheet("Sheet1");
// Copy Excel Row 1 to Excel Row 3
CopyRow(workbook, sheet, 0, 2);
// Copy Excel Row 2 to Excel Row 4
CopyRow(workbook, sheet, 1, 3);
#1 by Siva Kakularam on September 12, 2013 - 11:18 am
JExcelAPI (jxl) has a deep copy feature using WritableCell.copyTo(int col, int row) method.
I couldn’t find anything like this in Apache POI .
I need to copy the formula cells and formula should change relative way in the new cell using Apache POI.
For example if A1=A1+B1 when I copy A1 Cell to A2 the formula should be A2=A2+B2.
Your help would be appreciated greatly.
Thanks
Siva
#2 by Colson Driemel on February 20, 2013 - 10:11 am
Hey this has helped me a ton, thanks a lot. I was wondering if you knew of a way to clone cell styles between HSSF and XSSF. It seems that i will have to create a subroutine that loads all of the specific values and creates a new cellstyle with them. It’s a bummer that copycell isn’t cross supported.
#3 by Zach on February 21, 2013 - 5:39 pm
I only use HSSF with NPOI, sorry! You got the right idea, you’ll just need to spend some time figuring out how to copy all the values and styles!
#4 by Michal on October 4, 2012 - 1:23 pm
This looks very good, maybe you can submit it to the NPOI team so they will include it in the next release?
#5 by Lucian on October 2, 2012 - 7:46 am
Multumesc(Thank you, your code gave me a big big smile)
#6 by Dutchman on July 18, 2012 - 5:50 am
Problems solved over here !
Microsoft Office couldn’t cope with the number of cellstyles.
–> use of a dictionary as earlier mentioned and creating a new cellstyle only when it isn’t already in the dictionary.
Openoffice however could handle the outputfile without the dictionary solution.
#7 by Zach on July 18, 2012 - 7:49 am
Yea, in general it’s a known issue Excel chokes when you start doing cell by cell styles… I’ve had lots of user spread sheets become corrupt because of this, your approach is the right way to do it if you are going to re-use the style all over the place at the same time. Great tip, thanks for sharing!
#8 by Dutchman on July 18, 2012 - 1:10 am
Hi Guys,
My problem at the moment. I have 2 workbooks, one with a template file (18 rows with a table in it and different cellstyles) and the other workbook empty.
When I loop once through the template file and copy the contents the data in the new workbook looks allright, but when I try to open the newly created .xls file, it says missing dataformats, data corrupted warning and then opens the file.
When I loop twice or more through the template file, the newly created data look different and the error warnings stay the same.
What could be the problem ?
The optimise method didn’t have any effect.
#9 by Guillaume Loiselle on July 17, 2012 - 2:29 pm
You may note that RowStyle used just like I wrote earlier is probably wrong and may complain when you try to assign a RowStyle from Workbook A to Workbook B (but it shouldn’t be an issue if you stay in the same one). A dictionary is advisable there too. Also, since you work within the same workbook, I’m pretty sure cloning the CellStyle isn’t necessary. A simple shared reference should do it and would avoid clogging the file with many equal styles in the first place.
About a Copy() sample : I copy a whole sheet so I simply do :
for (int i = 0; i < sheetToCopy.NumMergedRegions; i++)
{
newSheet.AddMergedRegion(sheetToCopy.GetMergedRegion(i).Copy());
}
You can add your condition to only treat specific rows. Copy() is just more elegant and it reduces the probability someone messes up the constructor call. Talking about that…
Current method signature :
public CellRangeAddress(int firstRow, int lastRow, int firstCol, int lastCol);
Your call : (knowing that cellRangeAddress.FirstRow == sourceRow.RowNum)
new CellRangeAddress(
newRow.RowNum,
(newRow.RowNum +(cellRangeAddress.FirstRow-cellRangeAddress.LastRow)),
cellRangeAddress.FirstColumn,
cellRangeAddress.LastColumn);
Therefor, your new merged region would have a "lastRow" of 2*OldFirstRow-OldLastRow rather than OldLastRow (keeping the same merged region) or OldFirstRow (keeping only the part from the row we are interested in). I will admit that I am a bit puzzled by that.
Finally, some interesting things are held in columns, such as their width.
SortedSet columns = new SortedSet();
foreach (ICell cellToCopy in rowToCopy)
{
columns.Add(cellToCopy.ColumnIndex);
}
foreach (var columnIndex in columns)
{
newSheet.SetColumnHidden(columnIndex, sheetToCopy.IsColumnHidden(columnIndex));
newSheet.SetColumnWidth(columnIndex, sheetToCopy.GetColumnWidth(columnIndex));
}
And there is still a lot of stuff to do if you wanna copy a whole sheet instead of a set of row!
#10 by Guillaume Loiselle on July 17, 2012 - 5:34 am
If you copy a lot of rows, you will have a lot of duplicate styles. Make sure to run NPOI.HSSF.UserModel.HSSFOptimiser.OptimiseCellStyles() in that situation. But if you copy a whole workbook you should probably only add new styles when you really meet new ones by using a dictionary.
Little improvement : there’s a Copy() method to CellRangeAddress : no need to call the constructor directly.
There are also other things that can be copied such as :
newRow.Height = rowToCopy.Height;
if (rowToCopy.IsFormatted)
{
newRow.RowStyle = rowToCopy.RowStyle;
}
#11 by Zach on July 17, 2012 - 12:59 pm
Great tips, I’ve included the row style/height you mentioned in my production code… As for the Copy(), do you have a sample using it for a copy with the HSSF class?
As for calling “NPOI.HSSF.UserModel.HSSFOptimiser.OptimiseCellStyles(workbook);”, this does not work for me. On a simple workbook with 2 styles, it generates the following error in Excel 2010 (“Excel found unreadable content in ‘test.xls’. Do you want to recover the contents of this workbook? If you trust the source of this workbook, click Yes.”). The results of clicking yes is bad styling of my workbook. Maybe this works in later versions of NPOI, but my modified version of 1.2.2 does not create valid workbooks when this function is called.
#12 by Dutchman on July 17, 2012 - 2:07 am
Great Code.
I have the same problem as Poster #13.
I have a template file in one workbook and want to copy this in a loop multiple times to another (empty) workbook.
Can I do that with your code ?
#13 by Zach on July 17, 2012 - 11:31 am
CopyRow() is designed to copy rows within the same worksheet, not between workbooks/worksheets. This can easily be done, but the code above is not designed for this purpose.
#14 by isp on April 18, 2012 - 7:55 pm
Great! Thank you.
Is it possible to copy row from different workbook?
#15 by Zach on April 18, 2012 - 9:17 pm
It’s possible, but my function is not designed to do it. If you want to copy a row into a new worksheet, you’ll need to have both workbooks/sheets open to perform the source row read & destination row write. It’d be pretty easy to do, if you have trouble figuring it out let me know and I’ll post up some working code…
#16 by Bill Comer on April 18, 2012 - 8:19 am
Excellent thank you.
I have refactored it ever so slightly to return the interfaces Workbook, Sheet, Cell etc which makes it more generic
Other than that unchanged.
Great work – thanks again.
#17 by Zach on March 23, 2012 - 4:19 pm
Interesting, I don’t use XSSF normally but good to know it has slightly different behaviors… will save somebody a lot of time knowing what to change for XSSF. Thanks!
#18 by Luis Mesquita on March 16, 2012 - 4:50 am
my previous comment apllies to XSSFSheet forgot to mention it. The sheet just don’t acknowledge the existence of a row on the gap created so if you do (after shifting)
newRow = currentSheet.getRow(destinationRowNum);
you’ll get a null pointer exception. cool ah? hope this helps someone using XSSF not a great trick but can spare some time 🙂
#19 by Luis Mesquita on March 16, 2012 - 4:44 am
// If the row exist in destination, push down all rows by 1 else create a new row
if (newRow != null)
{
worksheet.ShiftRows(destinationRowNum, worksheet.LastRowNum, 1);
// you shifted one row down so the newRow will be shifted as well put this to avoid it
newRow = worksheet.CreateRow(destinationRowNum);
}
else
{
newRow = worksheet.CreateRow(destinationRowNum);
}
#20 by Toby on January 5, 2012 - 3:37 pm
Great, thanks very much.
Do you know how we’d support adapting formulas as they’re copied? I’m copying say, row 1 to row 2 and want to include the formula. So the original formula might be sum(A1:K1) for row 1 and now I want my row 2 (copy) to have sum(A2:K2).
At the moment, it copies the formula verbatim so I get both using sum(A1:K1) for example.
Any ideas?
Cheers
#21 by Ikutsin on December 22, 2011 - 12:44 pm
It seems that NPOI has been slightly refactored. Now it use IRow and ICell instead of HSSFRow HSSFCell classes. The rest works just fine. Thank you for sharing the code.
#22 by Gan on August 23, 2011 - 8:59 pm
Hi Zach,
Is there a way for us to get your complete NPOI custom helper class? Couldn’t find it anywhere. 🙂
Thanks.
#23 by AqD on April 28, 2011 - 9:57 pm
Thanks a lot!
#24 by Dave on April 26, 2011 - 8:38 am
I am trying your code and getting the following error.
Unable to cast object of type ‘NPOI.HSSF.Record.Aggregates.DataValidityTable’ to type ‘NPOI.HSSF.Record.Record’.
Any idea what might be causing this?
#25 by Zach on April 26, 2011 - 3:15 pm
In the routine for the copy, I’m assuming your copying a standard row of standalone data. It looks like your copying a row that is associated with an aggregate (Sum, Count, Min, Max, etc…). I’m not sure how to do this off the top of my head, but it may be as easy as copying some additional aggregate properties about the row.
#26 by Dezz on March 27, 2011 - 2:19 am
Thank you too much!
#27 by Tony Qu on July 25, 2010 - 12:50 am
This is really helpful!