Open xml 操作Excel 透视表(Pivot table)-- 实现Excel多语言报表

时间:2023-03-09 04:00:02
Open xml 操作Excel 透视表(Pivot table)-- 实现Excel多语言报表

我的一个ERP项目中,客户希望使用Excel Pivot table 做分析报表。 ERP 从数据库中读出数据,导出到Excel中的数据源表(统一命名为Data),刷新Pivot table!

客户还希望对Excel报表提供多语言支持, 根据用户的语言生成不同版本的Excel文件。

经过不断尝试,终于成功完成该任务, 本篇简要描述这个任务涉及到的知识点。

把一个包含透视表及透视图的Excel .xlsx文件重命名为.zip 文件,然后解压缩到某个文件夹下,就可以看到Excel是如何定义透视表及透视图了, 如下图所示,pivotTables 定义了透视表中行、列及数据字段等, PivotCache 中则定义了Pivot table 的数据源、字段匹配,以及缓存了上一次打开的数据

Open xml 操作Excel 透视表(Pivot table)-- 实现Excel多语言报表

                    (Excel文件结构)

Open xml 操作Excel 透视表(Pivot table)-- 实现Excel多语言报表

(pivotTables\pivotTable1.xml截图)Open xml 操作Excel 透视表(Pivot table)-- 实现Excel多语言报表

    (pivotCache\pivotCacheDefinition1.xml 截图)

根据以上描述, 在导入数据后. 还需要完成以下步骤:

  1. 重新设置透视表 的数据源到数据区域.

重新设置Pivot table 数据源的代码如下:

         //其中sheetName为作为数据源的工作表名,lastReference为数据源中最后一个单元格的引用名,比如最后一列为AG,共10行则为AG10
public static void SetPivotSource(WorkbookPart wbPart, string sheetName, string lastReference)
{
var pivottableCashes = wbPart.PivotTableCacheDefinitionParts;
foreach (PivotTableCacheDefinitionPart pivottablecachePart in pivottableCashes)
{
pivottablecachePart.PivotCacheDefinition.CacheSource.RemoveAllChildren();
//设置Pivot tabla的数据源为A1:lastReference
pivottablecachePart.PivotCacheDefinition.CacheSource.Append(new WorksheetSource() {
Sheet = sheetName, Reference = new StringValue("A1:" + lastReference) });
}
}
//假设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) ==)
{
theCell.InnerXml = "";
}
else
{
theCell.InnerXml = "";
}
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)==);
WorksheetPart worksheetPart = (WorksheetPart)wbPart.GetPartById(dataSheet.Id);
var headerRow = worksheetPart.Worksheet.GetFirstChild<SheetData>().Elements<Row>().
FirstOrDefault(c => c.RowIndex == );
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+);
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 > && matches[].Groups.Count >)
{
string newName = string.Format("[{0}]{1}!{2}", filename,
_translator.Translate(matches[].Groups[].Value), matches[].Groups[].Value);
pivotSource.PivotTableName = new PivotTableName(newName);
}
}

5. 翻译表格名, 需要翻译所有除了Data表外的工作表名。

  foreach (Sheet sheet in sheets)
{
if (string.Compare(sheet.Name, "data", true) != )
{
var translatedName = _translator.Translate(sheet.Name);
if (!string.IsNullOrEmpty(translatedName) && translatedName.Length > )
{
translatedName = translatedName.Substring(, );
}
sheet.Name = translatedName;
}
}