我的一個ERP項目中,客戶希望使用Excel Pivot table 做分析報表。 ERP 從數據庫中讀出數據,導出到Excel中的數據源表(統一命名為Data),刷新Pivot table!
客戶還希望對Excel報表提供多語言支持, 根據用戶的語言生成不同版本的Excel文件。
經過不斷嘗試,終於成功完成該任務, 本篇簡要描述這個任務涉及到的知識點。
把一個包含透視表及透視圖的Excel .xlsx文件重命名為.zip 文件,然后解壓縮到某個文件夾下,就可以看到Excel是如何定義透視表及透視圖了, 如下圖所示,pivotTables 定義了透視表中行、列及數據字段等, PivotCache 中則定義了Pivot table 的數據源、字段匹配,以及緩存了上一次打開的數據
(Excel文件結構)

(pivotTables\pivotTable1.xml截圖)
(pivotCache\pivotCacheDefinition1.xml 截圖)
根據以上描述, 在導入數據后. 還需要完成以下步驟:
- 重新設置透視表 的數據源到數據區域.
重新設置Pivot table 數據源的代碼如下:
1 //其中sheetName為作為數據源的工作表名,lastReference為數據源中最后一個單元格的引用名,比如最后一列為AG,共10行則為AG10 2 public static void SetPivotSource(WorkbookPart wbPart, string sheetName, string lastReference) 3 { 4 var pivottableCashes = wbPart.PivotTableCacheDefinitionParts; 5 foreach (PivotTableCacheDefinitionPart pivottablecachePart in pivottableCashes) 6 { 7 pivottablecachePart.PivotCacheDefinition.CacheSource.RemoveAllChildren(); 8 //設置Pivot tabla的數據源為A1:lastReference 9 pivottablecachePart.PivotCacheDefinition.CacheSource.Append(new WorksheetSource() { 10 Sheet = sheetName, Reference = new StringValue("A1:" + lastReference) }); 11 } 12 }
//假設Data表格中的最后一列的Reference為AG,總共有100行(加上列頭行共101行),則導入數據后調用 using (SpreadsheetDocument document = SpreadsheetDocument.Open(rawFileName, true)) { WorkbookPart wbPart = document.WorkbookPart; SetPivotSource(wbPart,"data","AG101"); }
2. 翻譯Excel 數據源表字段名
也就是翻譯及修改Data表格中第一行的單元格內容
public static void UpdateCellValue(WorkbookPart wbPart, Cell theCell,string newValue) { string value = theCell.InnerText; if (theCell.DataType != null) { switch (theCell.DataType.Value) { case CellValues.SharedString: var stringTable = wbPart.GetPartsOfType<SharedStringTablePart>().FirstOrDefault(); if (stringTable != null) { var ele = stringTable.SharedStringTable.ElementAt(int.Parse(value)); ele.RemoveAllChildren(); ele.Append(new DocumentFormat.OpenXml.Spreadsheet.Text(newValue)); } break; case CellValues.Boolean: if (string.Compare(value,"FALSE",true) ==0) { theCell.InnerXml = "1"; } else { theCell.InnerXml = "0"; } break; default: theCell.InnerXml = newValue; break; } } } using (SpreadsheetDocument document = SpreadsheetDocument.Open(rawFileName, true)) { WorkbookPart wbPart = document.WorkbookPart; var dataSheet = wbPart.Workbook.Descendants<Sheet>().FirstOrDefault(c => string.Compare(c.Name, "Data",true)==0); WorksheetPart worksheetPart = (WorksheetPart)wbPart.GetPartById(dataSheet.Id); var headerRow = worksheetPart.Worksheet.GetFirstChild<SheetData>().Elements<Row>(). FirstOrDefault(c => c.RowIndex == 1); var cells = headerRow.Elements<Cell>().ToList(); foreach (var cell in cells) { var rawText = ExcelHelper.GetCellValue(wbPart, cell); ExcelHelper.UpdateCellValue(wbPart, cell, _translator.Translate(rawText)); //cell.CellValue = new CellValue(_translator.Translate(rawText)); //cell.DataType = new EnumValue<CellValues>(CellValues.String); } worksheetPart.Worksheet.Save(); }
3. 翻譯pivotCacheDefinition緩存區的字段定義。
using (SpreadsheetDocument document = SpreadsheetDocument.Open(rawFileName, true)) { WorkbookPart wbPart = document.WorkbookPart; var pivottableCashes = wbPart.PivotTableCacheDefinitionParts; foreach (PivotTableCacheDefinitionPart pivottablecachePart in pivottableCashes) { pivottablecachePart.PivotCacheDefinition.RefreshOnLoad = true; var pivotCacheFields = pivottablecachePart.PivotCacheDefinition.CacheFields; foreach (OpenXmlElement pivotCacheField in pivotCacheFields) { OpenXmlAttribute nameEle = pivotCacheField.GetAttribute("name", ""); nameEle.Value = _translator.Translate(nameEle.Value); pivotCacheField.SetAttribute(nameEle); } } }
4. 翻譯Pivot Table 透視表定義區域的數據字段名,以及圖表區的數據源表名
//sheet為Sheet類型對象 oxPart = wbPart.GetPartById(sheet.Id); //Translate Pivot table data(numeric) field defination, such as "Sum of [Vat...]" if (oxPart.ContentType.Contains("worksheet")) { wsP = (WorksheetPart)oxPart; tbDefParts = wsP.PivotTableParts; foreach (PivotTablePart ptPart in tbDefParts) { dataFileds = ptPart.PivotTableDefinition.DataFields; foreach (DataField df in dataFileds) { if (df.Name.Value.StartsWith(SUM_OF)) { df.Name = new StringValue(_translator.Translate(SUM_OF) + " " + _translator.Translate(df.Name.Value.Replace(SUM_OF, "").Trim())); } } } } var index = rawFileName.LastIndexOf(@"\"); var filename = rawFileName.Substring(index+1); foreach(ChartsheetPart cspart in wbPart.ChartsheetParts) { var chartparts = cspart.DrawingsPart.ChartParts ; foreach(ChartPart cp in chartparts) { PivotSource pivotSource = cp.RootElement.OfType<PivotSource>().First(); string originalName = pivotSource.PivotTableName.InnerText; Regex reg = new Regex(@"^[[]([^]]+)[]]([^!]+)!(.*)$"); var matches = reg.Matches(originalName); if (matches.Count > 0 && matches[0].Groups.Count >3) { string newName = string.Format("[{0}]{1}!{2}", filename, _translator.Translate(matches[0].Groups[2].Value), matches[0].Groups[3].Value); pivotSource.PivotTableName = new PivotTableName(newName); } }
5. 翻譯表格名, 需要翻譯所有除了Data表外的工作表名。
foreach (Sheet sheet in sheets) { if (string.Compare(sheet.Name, "data", true) != 0) { var translatedName = _translator.Translate(sheet.Name); if (!string.IsNullOrEmpty(translatedName) && translatedName.Length > 30) { translatedName = translatedName.Substring(0, 30); } sheet.Name = translatedName; } }
