ClosedXML - Creating multiple pivot tables - c#

I am trying to export some data to an excel sheet S1 whose data would be shown as Pivoted views in the next two sheets S2 and S3. I am able to create a single pivot and it works perfect. But when I create two pivots, the consequent Excel file renders as corrupt.
By corrupt I mean,
On clicking yes, I get this -
Here is the code I am using to create the pivots -
using XL = ClosedXML.Excel;
...
XL.XLWorkbook wb = new XL.XLWorkbook();
dsData = Session["ExportData"] as DataSet;
var sheet1 = wb.Worksheets.Add("output table");
sheet1.Cell(1, 1).InsertTable(dsData.Tables[0], "output table", true);
// sheet1 is the reference sheet S1
var dataRange = sheet1.RangeUsed();
// First Pivot
XL.IXLWorksheet ptSheet1 = wb.Worksheets.Add("S2");
var pt1 = ptSheet1.PivotTables.AddNew("PivotTable1", ptSheet.Cell(3, 1), dataRange);
pt1.ReportFilters.Add("CX");
pt1.RowLabels.Add("C1");
pt1.RowLabels.Add("C2");
pt1.RowLabels.Add("C3");
pt1.RowLabels.Add("C4");
pt1.ColumnLabels.Add("CL1");
pt1.ColumnLabels.Add("CL2");
pt1.ColumnLabels.Add("CL3");
pt1.Values.Add("V").SummaryFormula = XL.XLPivotSummary.Sum;
// Second Pivot
XL.IXLWorksheet ptSheet2 = wb.Worksheets.Add("S3");
var pt2 = ptSheet2.PivotTables.AddNew("PivotTable2", ptSheet1.Cell(3, 1), dataRange);
pt2.ReportFilters.Add("QQ");
pt2.RowLabels.Add("C1");
pt2.RowLabels.Add("C2");
pt2.ColumnLabels.Add("CL1");
pt2.ColumnLabels.Add("CL2");
pt2.ColumnLabels.Add("CL3");
pt2.Values.Add("V").SummaryFormula = XL.XLPivotSummary.Sum;
C1, C2, C3. C4 and V are the column names in my reference sheet S1.

The issue is caused by a ClosedXML implementation bug.
It can easily be reproduced by using the following snippet (a modified version of their Pivot Tables example) and opening the resulting file in Excel:
static void CreateTestPivotTables(string filePath)
{
var wb = new XLWorkbook();
var wsData = wb.Worksheets.Add("Data");
wsData.Cell("A1").Value = "Category";
wsData.Cell("A2").Value = "A";
wsData.Cell("A3").Value = "B";
wsData.Cell("A4").Value = "B";
wsData.Cell("B1").Value = "Number";
wsData.Cell("B2").Value = 100;
wsData.Cell("B3").Value = 150;
wsData.Cell("B4").Value = 75;
var source = wsData.Range("A1:B4");
for (int i = 1; i <= 2; i++)
{
var name = "PT" + i;
var wsPT = wb.Worksheets.Add(name);
var pt = wsPT.PivotTables.AddNew(name, wsPT.Cell("A1"), source);
pt.RowLabels.Add("Category");
pt.Values.Add("Number")
.ShowAsPctFrom("Category").And("A")
.NumberFormat.Format = "0%";
}
wb.SaveAs(filePath);
}
The bug is located in XLWorkbook_Save.cs - GeneratePivotTables method:
private static void GeneratePivotTables(WorkbookPart workbookPart, WorksheetPart worksheetPart,
XLWorksheet xlWorksheet,
SaveContext context)
{
foreach (var pt in xlWorksheet.PivotTables)
{
var ptCdp = context.RelIdGenerator.GetNext(RelType.Workbook);
var pivotTableCacheDefinitionPart = workbookPart.AddNewPart<PivotTableCacheDefinitionPart>(ptCdp);
GeneratePivotTableCacheDefinitionPartContent(pivotTableCacheDefinitionPart, pt);
var pivotCaches = new PivotCaches();
var pivotCache = new PivotCache {CacheId = 0U, Id = ptCdp};
pivotCaches.AppendChild(pivotCache);
workbookPart.Workbook.AppendChild(pivotCaches);
var pivotTablePart =
worksheetPart.AddNewPart<PivotTablePart>(context.RelIdGenerator.GetNext(RelType.Workbook));
GeneratePivotTablePartContent(pivotTablePart, pt);
pivotTablePart.AddPart(pivotTableCacheDefinitionPart, context.RelIdGenerator.GetNext(RelType.Workbook));
}
}
by the line workbookPart.Workbook.AppendChild(pivotCaches); which adds multiple PivotCaches to workbookPart.Workbook while it's allowed to contain 0 or 1.
With that being said, the only way to fix it is inside the source code by modifying the above method as follows:
private static void GeneratePivotTables(WorkbookPart workbookPart, WorksheetPart worksheetPart,
XLWorksheet xlWorksheet,
SaveContext context)
{
var pivotCaches = workbookPart.Workbook.GetFirstChild<PivotCaches>();
foreach (var pt in xlWorksheet.PivotTables)
{
var ptCdp = context.RelIdGenerator.GetNext(RelType.Workbook);
var pivotTableCacheDefinitionPart = workbookPart.AddNewPart<PivotTableCacheDefinitionPart>(ptCdp);
GeneratePivotTableCacheDefinitionPartContent(pivotTableCacheDefinitionPart, pt);
if (pivotCaches == null)
workbookPart.Workbook.AppendChild(pivotCaches = new PivotCaches());
var pivotCache = new PivotCache { CacheId = (uint)pivotCaches.Count(), Id = ptCdp };
pivotCaches.AppendChild(pivotCache);
var pivotTablePart =
worksheetPart.AddNewPart<PivotTablePart>(context.RelIdGenerator.GetNext(RelType.Workbook));
GeneratePivotTablePartContent(pivotTablePart, pt);
pivotTablePart.PivotTableDefinition.CacheId = pivotCache.CacheId;
pivotTablePart.AddPart(pivotTableCacheDefinitionPart, context.RelIdGenerator.GetNext(RelType.Workbook));
}
}
Update: The good news are that my post triggered a ClosedXML source repository fix by Francois Botha (also credits to petelids who brought it up there), so you can take the code from there until their next release which hopefully will include it.

Try this modification. I made a note where I added an additional line. Also, I think the AddNew() method may have had the wrong worksheet reference? You may have been trying to add a pivot table on top of another one. That may have been the real issue rather than the additional line I added.
using XL = ClosedXML.Excel;
...
XL.XLWorkbook wb = new XL.XLWorkbook();
dsData = Session["ExportData"] as DataSet;
var sheet1 = wb.Worksheets.Add("output table");
sheet1.Cell(1, 1).InsertTable(dsData.Tables[0], "output table", true);
// sheet1 is the reference sheet S1
var dataRange = sheet1.RangeUsed();
PivotCache cache = wb.PivotCaches.Add(dataRange); //---THIS LINE HAS BEEN ADDED---
// First Pivot
XL.IXLWorksheet ptSheet1 = wb.Worksheets.Add("S2");
var pt1 = ptSheet1.PivotTables.AddNew("PivotTable1", ptSheet1.Cell(3, 1), cache);
//Changed ptSheet.Cell... to ptSheet1.Cell...
pt1.ReportFilters.Add("CX");
pt1.RowLabels.Add("C1");
pt1.RowLabels.Add("C2");
pt1.RowLabels.Add("C3");
pt1.RowLabels.Add("C4");
pt1.ColumnLabels.Add("CL1");
pt1.ColumnLabels.Add("CL2");
pt1.ColumnLabels.Add("CL3");
pt1.Values.Add("V").SummaryFormula = XL.XLPivotSummary.Sum;
// Second Pivot
XL.IXLWorksheet ptSheet2 = wb.Worksheets.Add("S3");
var pt2 = ptSheet2.PivotTables.AddNew("PivotTable2", ptSheet2.Cell(3, 1), cache);
//Changed ptSheet1.Cell... to ptSheet2.Cell...
pt2.ReportFilters.Add("QQ");
pt2.RowLabels.Add("C1");
pt2.RowLabels.Add("C2");
pt2.ColumnLabels.Add("CL1");
pt2.ColumnLabels.Add("CL2");
pt2.ColumnLabels.Add("CL3");
pt2.Values.Add("V").SummaryFormula = XL.XLPivotSummary.Sum;

Related

C# ClosedXML assign values from cells in a specific row to string

I'm using ClosedXML elsewhere in my script where I'm iterating through every row like this and it works.
var workbook = new XLWorkbook(ObjectRepPath);
var rows = workbook.Worksheet(1).RangeUsed().RowsUsed().Skip(1);
foreach (var row in rows)
{
objPage = row.Cell(1).GetString();
objElement = row.Cell(2).GetString();
if (objPage == page && objElement == element)
{
locType = row.Cell(3).GetString();
locParm = row.Cell(4).GetString();
}
}
After that I need to pull the data from the cells in a randomly selected row. Here's what I've got so far, which is not working...
var workbook = new XLWorkbook(extFile);
var ws = workbook.Worksheets.Add("Cell Values");
var rnd = new Random();
int rowNum = rnd.Next(2, workbook.Worksheet(1).RangeUsed().RowsUsed().Count());
var dataRow = ws.Row(rowNum);
string dangit = dataRow.Cell(1).GetString();
System.Diagnostics.Debug.WriteLine("Why is this dang thing not working... " + dangit);
Output: Why is this damn thing not working...
It just comes back empty. No error. Does anyone see something I don't?
Alright, I found the solution.
I changed the line ...
var ws = workbook.Worksheets.Add("Cell Values");
to ....
var ws = workbook.Worksheet(1);
and now this works ....
Storage.StreetAddress = ws.Cell(xlRow, 1).GetString();

NPOI converting XLS to XLSX throwing null reference

I'm trying to convert a file from XLS to XLSX using NPOI. As I'm not aware of an explicit conversion, I wrote this first implementation going through the rows and cells and copying from one to another:
public string ConvertToXlsx(string xlsPath)
{
var oldWorkbook = new HSSFWorkbook(new FileStream(xlsPath, FileMode.Open));
var oldWorkSheet = oldWorkbook.GetSheetAt(0);
var newExcelPath = xlsPath.Replace("xls", "xlsx");
using (var fileStream = new FileStream(newExcelPath, FileMode.Create))
{
var newWorkBook = new XSSFWorkbook();
var newWorkSheet = new XSSFSheet();
newWorkBook.Add(newWorkSheet);
foreach (HSSFRow oldRow in oldWorkSheet)
{
var newRow = newWorkSheet.CreateRow(oldRow.RowNum);
for (int ii = oldRow.FirstCellNum; ii < oldRow.LastCellNum; ii++)
{
var newCell = newRow.CreateCell(ii);
newCell = oldRow.Cells[ii];
}
}
newWorkBook.Write(fileStream);
}
return newExcelPath;
}
Yet, on line var newCell = newRow.CreateCell(ii); NPOI throws a NullReferenceException With the following stack trace:
at NPOI.XSSF.UserModel.XSSFCell..ctor(XSSFRow row, CT_Cell cell)
at NPOI.XSSF.UserModel.XSSFRow.CreateCell(Int32 columnIndex, CellType type)
at NPOI.XSSF.UserModel.XSSFRow.CreateCell(Int32 columnIndex)
at Ing2Ynab.Excel.IngExcelConverter.ConvertToXlsx(String xlsPath)
Which I don't get why it's happening, as XSSFRow should be in charge of creating the CT_Cell that gets passed on to XSSFCell constructor, from what I could read in NPOIs code.
Has anyone else tried to do this and/or has fixed it?
Thanks.
Looks like you have to explicitly call the Workbooks CreateSheet() method instead of calling .Add(). Additionally, you seem to have some out of range exceptions on your loop so keep an eye out for that.
public string ConvertToXlsx(string xlsPath)
{
var oldWorkbook = new HSSFWorkbook(new FileStream(xlsPath, FileMode.Open));
var oldWorkSheet = oldWorkbook.GetSheetAt(0);
var newExcelPath = xlsPath.Replace("xls", "xlsx");
using (var fileStream = new FileStream(newExcelPath, FileMode.Create))
{
var newWorkBook = new XSSFWorkbook();
var newWorkSheet = newWorkBook.CreateSheet("Sheet1");
foreach (HSSFRow oldRow in oldWorkSheet)
{
var newRow = newWorkSheet.CreateRow(oldRow.RowNum);
for (int ii = oldRow.FirstCellNum; ii < oldRow.LastCellNum; ii++)
{
var newCell = newRow.CreateCell(ii);
newCell = oldRow.Cells[ii];
}
}
newWorkBook.Write(fileStream);
}
return newExcelPath;
}

EPPlus: How can I move workbook property assignment commands to a helper function?

To provide more context, if I simply want to export my model to an Excel file in my ActionResult, I have that worked out. I learned the commends to assign values to the workbook properties and cell formatting and the exported Excel file appears correctly.
My problem is that all that code exists within the ActionResult block. I would like to move out the code that assigns property values to a separate function. The problem I encounter is the null reference error. Below is the paste of the full ActionResult block to provide full context and in case it suggests any secondary issues I hadn't considered.
I tried creating a function in the controller private ExcelPackage AssignWorkbookProperties(ExcelPackage ep, string exportName), and while I had no compile time errors, there was a null argument exception. Essentially, the function either received nothing or returned nothing.
Is there some way I can move the blocked code into a helper function? (I've indicated with comment blocks in the code shown here).
public ActionResult ExportToExcel()
{
string exportName = "FourCourseAudit";
// This allows me to export only the columns I want.
var exportQuery = query.Select(t => new { t.Campus, t.Student_Name, t.Course_Count });
// epp is the model where I gather the predefined property values
var epp = new ExportToExcelProperties();
var prop = epp.WorkbookProperties.Where(t => t.ExportName == exportName);
try
{
byte[] response;
using (var excelFile = new ExcelPackage())
{
// Define worksheet data.
var worksheet = excelFile.Workbook.Worksheets.Add("Sheet1");
/* ------------------------------------------------------------------ */
/* -------------Begin: Move to helper function ---------------------- */
/* ------------------------------------------------------------------ */
// Define workbook properties.
var workbookProperties = excelFile.Workbook.Properties;
workbookProperties.Author = HttpContext.User.Identity.Name;
workbookProperties.Title = prop.Select(t => t.Title).ToString();
workbookProperties.Comments = prop.Select(t => t.Comments).ToString();
workbookProperties.Created = DateTime.Now;
workbookProperties.Category = prop.Select(t => t.Category).ToString();
// Define worksheet contextual data.
worksheet.Cells["A1"].Value = "Title: ";
worksheet.Cells["A2"].Value = "Export Date: ";
worksheet.Cells["A1:A2"].Style.Font.Bold = true;
worksheet.Cells["B1"].Value = prop.Select(t => t.Title).ToString();
worksheet.Cells["B2"].Value = DateTime.Now;
worksheet.Cells["B2"].Style.Numberformat.Format = "mm/dd/yyyy hh:mm";
worksheet.Cells["A1:B2"].Style.Border.BorderAround(OfficeOpenXml.Style.ExcelBorderStyle.Medium);
worksheet.Cells["A1:B2"].AutoFitColumns();
Color bgColor = ColorTranslator.FromHtml("#2956B2");
worksheet.Cells["A1:B2"].Style.Fill.PatternType = ExcelFillStyle.Solid;
worksheet.Cells["A1:B2"].Style.Fill.BackgroundColor.SetColor(bgColor);
worksheet.Cells["A1:B2"].Style.Font.Color.SetColor(Color.White);
worksheet.Cells["D1:F1"].Style.Fill.PatternType = ExcelFillStyle.Solid;
worksheet.Cells["D1:F1"].Style.Fill.BackgroundColor.SetColor(bgColor);
worksheet.Cells["D1:F1"].Style.Font.Color.SetColor(Color.White);
/* ------------------------------------------------------------------ */
/* ---------------End: Move to helper function ---------------------- */
/* ------------------------------------------------------------------ */
worksheet
.Cells["D1"]
.LoadFromCollection(Collection: exportQuery, PrintHeaders: true)
.Style.Border.BorderAround(OfficeOpenXml.Style.ExcelBorderStyle.Thin);
worksheet.Cells["A1:F200"].AutoFitColumns();
response = excelFile.GetAsByteArray();
}
return File(response, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "Export.xlsx");
}
catch (NullReferenceException) {
return ViewBag.Errormsg = "There was a null reference exception.";
}
catch (ArgumentNullException)
{
return ViewBag.Errormsg = "There was an argument null exception.";
}
}
I found a resource that helped me figure out the problem.
The problem was that my function was returning and accepting the wrong object.
I created two different functions. The first function returns type ExcelWorksheet and the second function returns type OfficeProperties. Both functions have ExcelPackage as an input parameter.
So first, this is how clean the ActionResult is now:
public ActionResult ExportToExcel()
{
string exportName = "FourCourseAudit";
// This allows me to export only the columns I want.
var exportQuery = query.Select(t => new { t.Campus, t.Student_Name, t.Course_Count });
try
{
byte[] response;
using (var excelFile = new ExcelPackage())
{
// Use helper functions to fill worksheet and workbook definitions
var worksheet = CreateSheet(excelFile,exportName);
var workbook = AssignProperties(excelFile,exportName);
// Fill worksheet with data to export
worksheet
.Cells["D1"]
.LoadFromCollection(Collection: exportQuery, PrintHeaders: true)
.Style.Border.BorderAround(OfficeOpenXml.Style.ExcelBorderStyle.Thin);
worksheet.Cells["A1:F200"].AutoFitColumns();
response = excelFile.GetAsByteArray();
}
return File(response, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "Export.xlsx");
}
catch (NullReferenceException) {
return ViewBag.Errormsg = "There was a null reference exception.";
}
catch (ArgumentNullException)
{
return ViewBag.Errormsg = "There was an argument null exception.";
}
}
For now, these two functions are in the same controller. The more I work out how to generalize them, I might move them elsewhere.
private static ExcelWorksheet CreateSheet(ExcelPackage p,string exportName)
{
var epp = new ExportToExcelProperties().WorkbookProperties;
var prop = epp.Where(t => t.ExportName == "FourCourseAudit").Select(t=>t.Title).Single();
string sheetName = "Sheet1";
p.Workbook.Worksheets.Add(sheetName);
ExcelWorksheet worksheet = p.Workbook.Worksheets[1];
worksheet.Name = sheetName; //Setting Sheet's name
worksheet.Cells.Style.Font.Size = 11; //Default font size for whole sheet
worksheet.Cells.Style.Font.Name = "Calibri"; //Default Font name for whole sheet
// Define worksheet contextual data.
worksheet.Cells["A1"].Value = "Title: ";
worksheet.Cells["A2"].Value = "Export Date: ";
worksheet.Cells["A1:A2"].Style.Font.Bold = true;
worksheet.Cells["B1"].Value = prop;
worksheet.Cells["B2"].Value = DateTime.Now;
worksheet.Cells["B2"].Style.Numberformat.Format = "mm/dd/yyyy hh:mm";
worksheet.Cells["A1:B2"].Style.Border.BorderAround(OfficeOpenXml.Style.ExcelBorderStyle.Medium);
worksheet.Cells["A1:B2"].AutoFitColumns();
Color bgColor = ColorTranslator.FromHtml("#2956B2");
worksheet.Cells["A1:B2"].Style.Fill.PatternType = ExcelFillStyle.Solid;
worksheet.Cells["A1:B2"].Style.Fill.BackgroundColor.SetColor(bgColor);
worksheet.Cells["A1:B2"].Style.Font.Color.SetColor(Color.White);
worksheet.Cells["D1:F1"].Style.Fill.PatternType = ExcelFillStyle.Solid;
worksheet.Cells["D1:F1"].Style.Fill.BackgroundColor.SetColor(bgColor);
worksheet.Cells["D1:F1"].Style.Font.Color.SetColor(Color.White);
return worksheet;
}
private static OfficeProperties AssignProperties(ExcelPackage p, string exportName)
{
// epp is the model where I gather the predefined property values
var epp = new ExportToExcelProperties().WorkbookProperties;
var query = from t in epp
where t.ExportName == exportName
select new { t.Title, t.Comments, t.Category };
var title = query.Select(t=>t.Title).Single();
var comments = query.Select(t=>t.Comments).Single();
var category = query.Select(t=>t.Category).Single();
OfficeProperties workbookProperties = p.Workbook.Properties;
workbookProperties.Author = System.Web.HttpContext.Current.User.Identity.Name;
workbookProperties.Title = title;
workbookProperties.Comments = comments;
workbookProperties.Created = DateTime.Now;
workbookProperties.Category = category;
return workbookProperties;
}
This directly answers the question of how I can move the worksheet property code outside of the ActionResult, but I can clearly see there can be more done to generalize the code and make it more modular.

Importing excel file with all the conditional formatting rules to epplus

I have an excel file which contains lots of data along with icon sets and data bars based on the values in the cell. It looks like this:
I want to import this excel sheet along with the conditional formatting. Is there any library for this?? I went through this http://www.sitecorecleveland.com/resources/blogs-posts/easy_excel_interaction_pt6 but it only imports data not format.
If that's not possible is there code in epplus to have these iconsets in excel sheet. I can have arrows, traffic lights, etc but not these.
I dont think EPP supports custom conditional formatting which are stored as "Workbook Extensions" in the xml of the Excel file. You could copy the xml node of the "extLst" which contains the custom formatting from one worksheet to another. Just make sure there is nothing else beside the cond formatting xml in the node that you do not want copied in which case you will have to select only the child nodes you want.
To test, i created the following excel sheet (temp.xlsx), did a copy.paste of values only and saved to a new file (temp2.xlsx):
Then ran the following and it successfully copied the formatting over:
public void Custom_Condition_Copy_Test()
{
//http://stackoverflow.com/questions/28493050/importing-excel-file-with-all-the-conditional-formatting-rules-to-epplus
//File with custom conditional formatting
var existingFile = new FileInfo(#"c:\temp\temp.xlsx");
//Copy of the file with the conditonal formatting removed
var existingFile2 = new FileInfo(#"c:\temp\temp2.xlsx");
using (var package = new ExcelPackage(existingFile))
using (var package2 = new ExcelPackage(existingFile2))
{
//Make sure there are document element for the source
var worksheet = package.Workbook.Worksheets.First();
var xdoc = worksheet.WorksheetXml;
if (xdoc.DocumentElement == null)
return;
//Make sure there are document element for the destination
var worksheet2 = package2.Workbook.Worksheets.First();
var xdoc2 = worksheet2.WorksheetXml;
if (xdoc2.DocumentElement == null)
return;
//get the extension list node 'extLst' from the ws with the formatting
var extensionlistnode = xdoc
.DocumentElement
.GetElementsByTagName("extLst")[0];
//Create the import node and append it to the end of the xml document
var newnode = xdoc2.ImportNode(extensionlistnode, true);
xdoc2.LastChild.AppendChild(newnode);
package2.Save();
}
}
Might want to put some try's in there but this should get you close.
UPDATE: Based on OPs comment.
If you want to be able to add the custom conditional format without the need of the original file that contains it, I see two options.
Option 1, you do it the more "correct" way and use the DocumentFormat.OpenXml namespace. BUT, this would require you to have the Office Open XML library available which may or may not be so easy depending on the environment you are running this in. You can get it from here http://www.microsoft.com/en-us/download/details.aspx?id=30425 and it comes with a Reflection tool that can generate the code you want which gets you this:
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Spreadsheet;
using DocumentFormat.OpenXml;
using X14 = DocumentFormat.OpenXml.Office2010.Excel;
using Excel = DocumentFormat.OpenXml.Office.Excel;
......
WorksheetExtensionList worksheetExtensionList1 = new WorksheetExtensionList();
WorksheetExtension worksheetExtension1 = new WorksheetExtension(){ Uri = "{78C0D931-6437-407d-A8EE-F0AAD7539E65}" };
worksheetExtension1.AddNamespaceDeclaration("x14", "http://schemas.microsoft.com/office/spreadsheetml/2009/9/main");
X14.ConditionalFormattings conditionalFormattings1 = new X14.ConditionalFormattings();
X14.ConditionalFormatting conditionalFormatting1 = new X14.ConditionalFormatting();
conditionalFormatting1.AddNamespaceDeclaration("xm", "http://schemas.microsoft.com/office/excel/2006/main");
X14.ConditionalFormattingRule conditionalFormattingRule1 = new X14.ConditionalFormattingRule(){ Type = ConditionalFormatValues.IconSet, Priority = 2, Id = "{CD6B2710-0474-449D-881A-22CFE15D011D}" };
X14.IconSet iconSet1 = new X14.IconSet(){ IconSetTypes = X14.IconSetTypeValues.FiveArrows, Custom = true };
X14.ConditionalFormattingValueObject conditionalFormattingValueObject1 = new X14.ConditionalFormattingValueObject(){ Type = X14.ConditionalFormattingValueObjectTypeValues.Percent };
Excel.Formula formula1 = new Excel.Formula();
formula1.Text = "0";
conditionalFormattingValueObject1.Append(formula1);
X14.ConditionalFormattingValueObject conditionalFormattingValueObject2 = new X14.ConditionalFormattingValueObject(){ Type = X14.ConditionalFormattingValueObjectTypeValues.Percent };
Excel.Formula formula2 = new Excel.Formula();
formula2.Text = "20";
conditionalFormattingValueObject2.Append(formula2);
X14.ConditionalFormattingValueObject conditionalFormattingValueObject3 = new X14.ConditionalFormattingValueObject(){ Type = X14.ConditionalFormattingValueObjectTypeValues.Percent };
Excel.Formula formula3 = new Excel.Formula();
formula3.Text = "40";
conditionalFormattingValueObject3.Append(formula3);
X14.ConditionalFormattingValueObject conditionalFormattingValueObject4 = new X14.ConditionalFormattingValueObject(){ Type = X14.ConditionalFormattingValueObjectTypeValues.Percent };
Excel.Formula formula4 = new Excel.Formula();
formula4.Text = "60";
conditionalFormattingValueObject4.Append(formula4);
X14.ConditionalFormattingValueObject conditionalFormattingValueObject5 = new X14.ConditionalFormattingValueObject(){ Type = X14.ConditionalFormattingValueObjectTypeValues.Percent };
Excel.Formula formula5 = new Excel.Formula();
formula5.Text = "80";
conditionalFormattingValueObject5.Append(formula5);
X14.ConditionalFormattingIcon conditionalFormattingIcon1 = new X14.ConditionalFormattingIcon(){ IconSet = X14.IconSetTypeValues.ThreeSymbols, IconId = (UInt32Value)0U };
X14.ConditionalFormattingIcon conditionalFormattingIcon2 = new X14.ConditionalFormattingIcon(){ IconSet = X14.IconSetTypeValues.ThreeTrafficLights1, IconId = (UInt32Value)0U };
X14.ConditionalFormattingIcon conditionalFormattingIcon3 = new X14.ConditionalFormattingIcon(){ IconSet = X14.IconSetTypeValues.ThreeTriangles, IconId = (UInt32Value)0U };
X14.ConditionalFormattingIcon conditionalFormattingIcon4 = new X14.ConditionalFormattingIcon(){ IconSet = X14.IconSetTypeValues.ThreeTriangles, IconId = (UInt32Value)1U };
X14.ConditionalFormattingIcon conditionalFormattingIcon5 = new X14.ConditionalFormattingIcon(){ IconSet = X14.IconSetTypeValues.ThreeTriangles, IconId = (UInt32Value)2U };
iconSet1.Append(conditionalFormattingValueObject1);
iconSet1.Append(conditionalFormattingValueObject2);
iconSet1.Append(conditionalFormattingValueObject3);
iconSet1.Append(conditionalFormattingValueObject4);
iconSet1.Append(conditionalFormattingValueObject5);
iconSet1.Append(conditionalFormattingIcon1);
iconSet1.Append(conditionalFormattingIcon2);
iconSet1.Append(conditionalFormattingIcon3);
iconSet1.Append(conditionalFormattingIcon4);
iconSet1.Append(conditionalFormattingIcon5);
conditionalFormattingRule1.Append(iconSet1);
Excel.ReferenceSequence referenceSequence1 = new Excel.ReferenceSequence();
referenceSequence1.Text = "A1:C201";
conditionalFormatting1.Append(conditionalFormattingRule1);
conditionalFormatting1.Append(referenceSequence1);
conditionalFormattings1.Append(conditionalFormatting1);
worksheetExtension1.Append(conditionalFormattings1);
worksheetExtensionList1.Append(worksheetExtension1);
....
worksheet1.Append(worksheetExtensionList1);
Option 2 would be to do as you are asking and perform string manipulation. This is much easier but it is a slightly dirty in that you are messing with strings rather then objects but if the only thing you need to set is the cell range that doesnt seem so bad. I used the test method above to extract the string with = extensionlistnode.OuterXml:
[TestMethod]
public void Custom_Condition_From_String_Test()
{
//http://stackoverflow.com/questions/28493050/importing-excel-file-with-all-the-conditional-formatting-rules-to-epplus
//Throw in some data
var datatable = new DataTable("tblData");
datatable.Columns.Add(new DataColumn("Col1", typeof(int)));
datatable.Columns.Add(new DataColumn("Col2", typeof(int)));
datatable.Columns.Add(new DataColumn("Col3", typeof(int)));
for (var i = 0; i < 20; i++)
{
var row = datatable.NewRow();
row["Col1"] = i;
row["Col2"] = i * 10;
row["Col3"] = i * 100;
datatable.Rows.Add(row);
}
//Copy of the file with the conditonal formatting removed
var existingFile2 = new FileInfo(#"c:\temp\temp2.xlsx");
if (existingFile2.Exists)
existingFile2.Delete();
using (var package2 = new ExcelPackage(existingFile2))
{
//Add the data
var ws = package2.Workbook.Worksheets.Add("Content");
ws.Cells.LoadFromDataTable(datatable, true);
//The XML String extracted from the orginal excel doc using '= extensionlistnode.OuterXml'
var cellrange = "A1:C201";
var rawxml = String.Format(
"<extLst xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\"><ext uri=\"{{78C0D931-6437-407d-A8EE-F0AAD7539E65}}\" xmlns:x14=\"http://schemas.microsoft.com/office/spreadsheetml/2009/9/main\"><x14:conditionalFormattings><x14:conditionalFormatting xmlns:xm=\"http://schemas.microsoft.com/office/excel/2006/main\"><x14:cfRule type=\"iconSet\" priority=\"2\" id=\"{{CD6B2710-0474-449D-881A-22CFE15D011D}}\"><x14:iconSet iconSet=\"5Arrows\" custom=\"1\"><x14:cfvo type=\"percent\"><xm:f>0</xm:f></x14:cfvo><x14:cfvo type=\"percent\"><xm:f>20</xm:f></x14:cfvo><x14:cfvo type=\"percent\"><xm:f>40</xm:f></x14:cfvo><x14:cfvo type=\"percent\"><xm:f>60</xm:f></x14:cfvo><x14:cfvo type=\"percent\"><xm:f>80</xm:f></x14:cfvo><x14:cfIcon iconSet=\"3Symbols\" iconId=\"0\" /><x14:cfIcon iconSet=\"3TrafficLights1\" iconId=\"0\" /><x14:cfIcon iconSet=\"3Triangles\" iconId=\"0\" /><x14:cfIcon iconSet=\"3Triangles\" iconId=\"1\" /><x14:cfIcon iconSet=\"3Triangles\" iconId=\"2\" /></x14:iconSet></x14:cfRule><xm:sqref>{0}</xm:sqref></x14:conditionalFormatting></x14:conditionalFormattings></ext></extLst>"
, cellrange);
var newxdoc = new XmlDocument();
newxdoc.LoadXml(rawxml);
//Create the import node and append it to the end of the xml document
var xdoc2 = ws.WorksheetXml;
var newnode = xdoc2.ImportNode(newxdoc.FirstChild, true);
xdoc2.LastChild.AppendChild(newnode);
package2.Save();
}
}

EPPlus pivot tables/charts

I've been using EPPlus for .net for a while now but only for simple data manipulation.
Are there any examples somewhere on how to use it to create pivot tables/charts?
It seems to support it as I can see PivotTable in the intellisense but just unsure on the syntax.
I could only find the likes of pie/bar charts in the samples provided.
I've produced a similar solution from Tim's Answer. First, I defined a simple interface that I use as part of my export methods:
public interface IPivotTableCreator
{
void CreatePivotTable(
OfficeOpenXml.ExcelPackage pkg, // reference to the destination book
string tableName, // "tab" name used to generate names for related items
string pivotRangeName); // Named range in the Workbook refers to data
}
Then I implemented a simple class that hold the variable values and procedural code to do the work:
public class SimplePivotTable : IPivotTableCreator
{
List<string> _GroupByColumns;
List<string> _SummaryColumns;
/// <summary>
/// Constructor
/// </summary>
public SimplePivotTable(string[] groupByColumns, string[] summaryColumns)
{
_GroupByColumns = new List<string>(groupByColumns);
_SummaryColumns = new List<string>(summaryColumns);
}
/// <summary>
/// Call-back handler that builds simple PivatTable in Excel
/// http://stackoverflow.com/questions/11650080/epplus-pivot-tables-charts
/// </summary>
public void CreatePivotTable(OfficeOpenXml.ExcelPackage pkg, string tableName, string pivotRangeName)
{
string pageName = "Pivot-" + tableName.Replace(" ", "");
var wsPivot = pkg.Workbook.Worksheets.Add(pageName);
pkg.Workbook.Worksheets.MoveBefore(PageName, tableName);
var dataRange = pkg.Workbook./*Worksheets[tableName].*/Names[pivotRangeName];
var pivotTable = wsPivot.PivotTables.Add(wsPivot.Cells["C3"], dataRange, "Pivot_" + tableName.Replace(" ", ""));
pivotTable.ShowHeaders = true;
pivotTable.UseAutoFormatting = true;
pivotTable.ApplyWidthHeightFormats = true;
pivotTable.ShowDrill = true;
pivotTable.FirstHeaderRow = 1; // first row has headers
pivotTable.FirstDataCol = 1; // first col of data
pivotTable.FirstDataRow = 2; // first row of data
foreach (string row in _GroupByColumns)
{
var field = pivotTable.Fields[row];
pivotTable.RowFields.Add(field);
field.Sort = eSortType.Ascending;
}
foreach (string column in _SummaryColumns)
{
var field = pivotTable.Fields[column];
ExcelPivotTableDataField result = pivotTable.DataFields.Add(field);
}
pivotTable.DataOnRows = false;
}
}
Then I create an instance of my SimplePivotTable creator class:
IPivotTableCreator ptCreator = new SimplePivotTable(
new string[] { "OrganizationTitle", "GroupingTitle", "DetailTitle" }, /* collapsible rows */
new string[] { "Baseline", "Increase", "Decrease", "NetChange", "CurrentCount"}); /* summary columns */
I have a third class that currently exposes about six different methods to take one-or-more data sets (usually List objects) and turn each of the data sets into a worksheet of data with a named range for the data. Now, I'm adapting those export methods to allow me to generate Pivot Tables for any/all of those export methods. They all do something like this:
OfficeOpenXml.ExcelPackage pkg = new ExcelPackage();
ExportCollectionToExcel(pkg, tableName, dataset); // Create worksheet filled with data
// Creates a NamedRange of data
ptCreator.CreatePivotTable(pkg, tableName, GetPivotRangeName(tableName));
By using an interface, I leave open more opportunities (I think) to generate, for example, a different pivot table for multiple sheets. My basic SimplePivotTable class just used for a single table with some specific assumptions, but it wouldn't be hard to put the configuration data into a dictionary keyed to the Table names.
Hope that helps someone.
Here's the code of a pivot i've created recently, maybe it does help:
DataTable table = getDataSource();
FileInfo fileInfo = new FileInfo(path);
var excel = new ExcelPackage(fileInfo);
var wsData = excel.Workbook.Worksheets.Add("Data-Worksheetname");
var wsPivot = excel.Workbook.Worksheets.Add("Pivot-Worksheetname");
wsData.Cells["A1"].LoadFromDataTable(table, true, OfficeOpenXml.Table.TableStyles.Medium6);
if (table.Rows.Count != 0)
{
foreach (DataColumn col in table.Columns)
{
// format all dates in german format (adjust accordingly)
if (col.DataType == typeof(System.DateTime))
{
var colNumber = col.Ordinal + 1;
var range = wsData.Cells[2, colNumber, table.Rows.Count + 1, colNumber];
range.Style.Numberformat.Format = "dd.MM.yyyy";
}
}
}
var dataRange = wsData.Cells[wsData.Dimension.Address.ToString()];
dataRange.AutoFitColumns();
var pivotTable = wsPivot.PivotTables.Add(wsPivot.Cells["A3"], dataRange, "Pivotname");
pivotTable.MultipleFieldFilters = true;
pivotTable.RowGrandTotals = true;
pivotTable.ColumGrandTotals = true;
pivotTable.Compact = true;
pivotTable.CompactData = true;
pivotTable.GridDropZones = false;
pivotTable.Outline = false;
pivotTable.OutlineData = false;
pivotTable.ShowError = true;
pivotTable.ErrorCaption = "[error]";
pivotTable.ShowHeaders = true;
pivotTable.UseAutoFormatting = true;
pivotTable.ApplyWidthHeightFormats = true;
pivotTable.ShowDrill = true;
pivotTable.FirstDataCol = 3;
pivotTable.RowHeaderCaption = "Claims";
var modelField = pivotTable.Fields["Model"];
pivotTable.PageFields.Add(modelField);
modelField.Sort = OfficeOpenXml.Table.PivotTable.eSortType.Ascending;
var countField = pivotTable.Fields["Claims"];
pivotTable.DataFields.Add(countField);
var countryField = pivotTable.Fields["Country"];
pivotTable.RowFields.Add(countryField);
var gspField = pivotTable.Fields["GSP / DRSL"];
pivotTable.RowFields.Add(gspField);
var oldStatusField = pivotTable.Fields["Old Status"];
pivotTable.ColumnFields.Add(oldStatusField);
var newStatusField = pivotTable.Fields["New Status"];
pivotTable.ColumnFields.Add(newStatusField);
var submittedDateField = pivotTable.Fields["Claim Submitted Date"];
pivotTable.RowFields.Add(submittedDateField);
submittedDateField.AddDateGrouping(OfficeOpenXml.Table.PivotTable.eDateGroupBy.Months | OfficeOpenXml.Table.PivotTable.eDateGroupBy.Days);
var monthGroupField = pivotTable.Fields.GetDateGroupField(OfficeOpenXml.Table.PivotTable.eDateGroupBy.Months);
monthGroupField.ShowAll = false;
var dayGroupField = pivotTable.Fields.GetDateGroupField(OfficeOpenXml.Table.PivotTable.eDateGroupBy.Days);
dayGroupField.ShowAll = false;
excel.Save();

Categories