如何使用 OpenXmlWriter 配置 xlsx 格式和公式
How to configure xlsx format and formulas using OpenXmlWriter
我需要创建大型通用 xlsx 报告(超过 50 万行,约 50 列)。
我编写了生成此报告的方法,但我无法弄清楚如何设置单元格格式(数据 format/style(边框等))以及如何使用 OpenXmlWriter 将公式放入单元格。
private void FillExcelWorksheet<T>(String fileName, IEnumerable<T> source)
{
var properties = typeof(T).GetProperties();
using (SpreadsheetDocument document = SpreadsheetDocument.Create(fileName, SpreadsheetDocumentType.Workbook))
{
//this list of attributes will be used when writing a start element
List<OpenXmlAttribute> stringAttributes = new List<OpenXmlAttribute>()
{
new OpenXmlAttribute("t", null, "str")
};
OpenXmlWriter writer;
var workBookPart = document.AddWorkbookPart();
var workSheetPart = workBookPart.AddNewPart<WorksheetPart>();
writer = OpenXmlWriter.Create(workSheetPart);
writer.WriteStartElement(new Worksheet());
writer.WriteStartElement(new SheetData());
writer.WriteStartElement(new Row());
foreach (PropertyInfo property in properties)
{
var pAttributes = property.GetCustomAttributes<ReportColumnAttribute>();
if (pAttributes.Any())
{
var attribute = pAttributes.First();
writer.WriteStartElement(new Cell(), stringAttributes);
//write the cell value
writer.WriteElement(new CellValue(attribute.ColumnName));
// write the end cell element
writer.WriteEndElement();
}
}
// write the end row element
writer.WriteEndElement();
foreach (var item in source)
{
//write the row start element with the row index attribute
writer.WriteStartElement(new Row());
foreach (PropertyInfo property in properties)
{
var pAttributes = property.GetCustomAttributes<ReportColumnAttribute>();
if (pAttributes.Any())
{
//write the cell start element with the type and reference attributes
writer.WriteStartElement(new Cell(), stringAttributes);
//write the cell value
writer.WriteElement(new CellValue(property.GetValue(item) != null ?
property.GetValue(item).ToString() : String.Empty));
var attribute = pAttributes.First();
var format = attribute.Format;
//todo apply data format
// write the end cell element
writer.WriteEndElement();
}
}
// write the end row element
writer.WriteEndElement();
}
// write the end SheetData element
writer.WriteEndElement();
// write the end Worksheet element
writer.WriteEndElement();
writer.Close();
writer = OpenXmlWriter.Create(document.WorkbookPart);
writer.WriteStartElement(new Workbook());
writer.WriteStartElement(new Sheets());
writer.WriteElement(new Sheet()
{
Name = fileName,
SheetId = 1,
Id = document.WorkbookPart.GetIdOfPart(workSheetPart)
});
// End Sheets
writer.WriteEndElement();
// End Workbook
writer.WriteEndElement();
writer.Close();
document.Close();
}
}
result with styles
private DownloadResult FillExcelWorksheet<T>(String name, IEnumerable<T> source, Action<OpenXmlWriter, WorkbookStylesPart, SharedStringTablePart, Int32> addNonGenericReportInfo = null)
{
var properties = typeof(T).GetProperties();
var folder = ConfigurationManager.AppSettings["ReportsPath"];
if (!Directory.Exists(folder)) {
Directory.CreateDirectory(folder);
}
//remove old files
Directory.GetFiles(folder)
.Select(f => new FileInfo(f))
.Where(f => f.CreationTime < DateTime.Now.AddDays(-1))
.ToList()
.ForEach(f => f.Delete());
var fileName = String.Format("{0}_{1}.xlsx", name, Guid.NewGuid());
using (var document = SpreadsheetDocument.Create(folder + fileName, SpreadsheetDocumentType.Workbook))
{
//this list of attributes will be used when writing a start element
List<OpenXmlAttribute> stringAttributes = new List<OpenXmlAttribute>()
{
new OpenXmlAttribute("t", null, "str")
};
OpenXmlWriter writer;
var workBookPart = document.AddWorkbookPart();
var workSheetPart = workBookPart.AddNewPart<WorksheetPart>();
var sharedStringPart = workBookPart.AddNewPart<SharedStringTablePart>();
sharedStringPart.SharedStringTable = new SharedStringTable();
var stylesPart = document.WorkbookPart.AddNewPart<WorkbookStylesPart>();
stylesPart.Stylesheet = new Stylesheet();
stylesPart.Stylesheet.NumberingFormats = new NumberingFormats();
// create fills
stylesPart.Stylesheet.Fills = new Fills();
stylesPart.Stylesheet.Fills.AppendChild(new Fill { PatternFill = new PatternFill { PatternType = PatternValues.None } }); // required, reserved by Excel
//EXAMPLE DON'T REMOVE
//stylesPart.Stylesheet.Fills.AppendChild(new Fill { PatternFill = new PatternFill { PatternType = PatternValues.Gray125 } }); // required, reserved by Excel
//// create a solid red fill
//var solidRed = new PatternFill() { PatternType = PatternValues.Solid };
//solidRed.ForegroundColor = new ForegroundColor { Rgb = HexBinaryValue.FromString("FFFF0000") }; // red fill
//solidRed.BackgroundColor = new BackgroundColor { Indexed = 64 };
//stylesPart.Stylesheet.Fills.AppendChild(new Fill { PatternFill = solidRed });
stylesPart.Stylesheet.Fills.Count = 3;
// blank cell format list
stylesPart.Stylesheet.CellStyleFormats = new CellStyleFormats();
stylesPart.Stylesheet.CellStyleFormats.AppendChild(new CellFormat());
stylesPart.Stylesheet.CellStyleFormats.Count = 1;
//Borders
stylesPart.Stylesheet.Borders = new Borders();
stylesPart.Stylesheet.Borders.AppendChild(new Border());
var border = new Border();
border.Append(new BottomBorder() { Style = BorderStyleValues.Thin });
stylesPart.Stylesheet.Borders.AppendChild(border);
stylesPart.Stylesheet.Borders.Count = 2;
//Fonts
stylesPart.Stylesheet.Fonts = new Fonts();
stylesPart.Stylesheet.Fonts.AppendChild(new Font());
var font = new Font();
font.Bold = new Bold();
stylesPart.Stylesheet.Fonts.AppendChild(font);
stylesPart.Stylesheet.Fonts.Count = 2;
// cell format list
stylesPart.Stylesheet.CellFormats = new CellFormats();
// empty one for index 0, seems to be required
stylesPart.Stylesheet.CellFormats.AppendChild(new CellFormat());
// cell format references style format 0, font 0, border 0, fill 2 and applies the fill
stylesPart.Stylesheet.CellFormats.AppendChild(new CellFormat { FormatId = 0, FontId = 1, BorderId = 1, FillId = 0, ApplyFill = true }).AppendChild(new Alignment { Horizontal = HorizontalAlignmentValues.Center });
stylesPart.Stylesheet.CellFormats.Count = 2;
writer = OpenXmlWriter.Create(workSheetPart);
writer.WriteStartElement(new Worksheet());
writer.WriteStartElement(new SheetData());
writer.WriteStartElement(new Row());
foreach (PropertyInfo property in properties)
{
var pAttributes = property.GetCustomAttributes<ReportColumnAttribute>();
if (pAttributes.Any())
{
var attribute = pAttributes.First();
var cell = new Cell();
cell.DataType = CellValues.SharedString;
cell.StyleIndex = 1;
writer.WriteStartElement(cell);
//write the cell value
var index = InsertSharedStringItem(attribute.ColumnName, sharedStringPart);
writer.WriteElement(new CellValue(index.ToString()));
// write the end cell element
writer.WriteEndElement();
if (!String.IsNullOrEmpty(attribute.Format) && !_styleFormats.ContainsKey(attribute.Format))
{
//164 is first free format id
var formatId = 164 + (uint)_styleFormats.Count;
var format = new NumberingFormat { NumberFormatId = formatId, FormatCode = attribute.Format };
stylesPart.Stylesheet.NumberingFormats.AppendChild(format);
stylesPart.Stylesheet.CellFormats.AppendChild(new CellFormat { NumberFormatId = formatId, FontId = 0, BorderId = 0, FillId = 0, ApplyFill = true });
//stylesPart.Stylesheet.CellFormats.AppendChild(format);
stylesPart.Stylesheet.CellStyleFormats.Count++;
_styleFormats.Add(attribute.Format, new StyleIndexes(stylesPart.Stylesheet.CellFormats.Count, formatId));
stylesPart.Stylesheet.CellFormats.Count++;
}
}
}
// write the end row element
writer.WriteEndElement();
foreach (var item in source)
{
//write the row start element with the row index attribute
writer.WriteStartElement(new Row());
foreach (PropertyInfo property in properties)
{
var pAttributes = property.GetCustomAttributes<ReportColumnAttribute>();
if (pAttributes.Any())
{
var attribute = pAttributes.First();
var cell = new Cell();
if (!String.IsNullOrEmpty(attribute.Format))
{
cell.StyleIndex = (uint)_styleFormats[attribute.Format].StyleIndex;
}
var typeCode = Type.GetTypeCode(property.PropertyType);
//write the cell start element with the type and reference attributes
if (typeCode == TypeCode.String)
{
writer.WriteStartElement(cell, stringAttributes);
}
else
{
cell.DataType = CellValues.Number;
writer.WriteStartElement(cell);
}
var value = String.Empty;
if (typeCode == TypeCode.DateTime)
{
value = property.GetValue(item) != null ?
Convert.ToDateTime(property.GetValue(item)).ToOADate().ToString() : String.Empty;
}
else {
value = property.GetValue(item) != null ?
property.GetValue(item).ToString() : String.Empty;
}
//write the cell value
writer.WriteElement(new CellValue(value));
// write the end cell element
writer.WriteEndElement();
}
}
// write the end row element
writer.WriteEndElement();
}
if (addNonGenericReportInfo != null)
{
addNonGenericReportInfo(writer, stylesPart, sharedStringPart, source.Count());
}
sharedStringPart.SharedStringTable.Save();
stylesPart.Stylesheet.Save();
// write the end SheetData element
writer.WriteEndElement();
// write the end Worksheet element
writer.WriteEndElement();
writer.Close();
writer = OpenXmlWriter.Create(document.WorkbookPart);
writer.WriteStartElement(new Workbook());
writer.WriteStartElement(new Sheets());
writer.WriteElement(new Sheet()
{
Name = name,
SheetId = 1,
Id = document.WorkbookPart.GetIdOfPart(workSheetPart)
});
// End Sheets
writer.WriteEndElement();
// End Workbook
writer.WriteEndElement();
writer.Close();
document.Close();
}
return new DownloadResult
{
Name = name + ".xlsx",
PathToDownload = folder + fileName,
};
}
private static int InsertSharedStringItem(string text, SharedStringTablePart shareStringPart)
{
// If the part does not contain a SharedStringTable, create one.
if (shareStringPart.SharedStringTable == null)
{
shareStringPart.SharedStringTable = new SharedStringTable();
}
int i = 0;
// Iterate through all the items in the SharedStringTable. If the text already exists, return its index.
foreach (SharedStringItem item in shareStringPart.SharedStringTable.Elements<SharedStringItem>())
{
if (item.InnerText == text)
{
return i;
}
i++;
}
// The text does not exist in the part. Create the SharedStringItem and return its index.
shareStringPart.SharedStringTable.AppendChild(new SharedStringItem(new DocumentFormat.OpenXml.Spreadsheet.Text(text)));
shareStringPart.SharedStringTable.Save();
return i;
}
private Dictionary<String, StyleIndexes> _styleFormats;
private class StyleIndexes
{
public StyleIndexes(uint styleIndex, uint numberIndex)
{
StyleIndex = styleIndex;
NumberIndex = numberIndex;
}
public uint StyleIndex { get; set; }
public uint NumberIndex { get; set; }
}
我需要创建大型通用 xlsx 报告(超过 50 万行,约 50 列)。 我编写了生成此报告的方法,但我无法弄清楚如何设置单元格格式(数据 format/style(边框等))以及如何使用 OpenXmlWriter 将公式放入单元格。
private void FillExcelWorksheet<T>(String fileName, IEnumerable<T> source)
{
var properties = typeof(T).GetProperties();
using (SpreadsheetDocument document = SpreadsheetDocument.Create(fileName, SpreadsheetDocumentType.Workbook))
{
//this list of attributes will be used when writing a start element
List<OpenXmlAttribute> stringAttributes = new List<OpenXmlAttribute>()
{
new OpenXmlAttribute("t", null, "str")
};
OpenXmlWriter writer;
var workBookPart = document.AddWorkbookPart();
var workSheetPart = workBookPart.AddNewPart<WorksheetPart>();
writer = OpenXmlWriter.Create(workSheetPart);
writer.WriteStartElement(new Worksheet());
writer.WriteStartElement(new SheetData());
writer.WriteStartElement(new Row());
foreach (PropertyInfo property in properties)
{
var pAttributes = property.GetCustomAttributes<ReportColumnAttribute>();
if (pAttributes.Any())
{
var attribute = pAttributes.First();
writer.WriteStartElement(new Cell(), stringAttributes);
//write the cell value
writer.WriteElement(new CellValue(attribute.ColumnName));
// write the end cell element
writer.WriteEndElement();
}
}
// write the end row element
writer.WriteEndElement();
foreach (var item in source)
{
//write the row start element with the row index attribute
writer.WriteStartElement(new Row());
foreach (PropertyInfo property in properties)
{
var pAttributes = property.GetCustomAttributes<ReportColumnAttribute>();
if (pAttributes.Any())
{
//write the cell start element with the type and reference attributes
writer.WriteStartElement(new Cell(), stringAttributes);
//write the cell value
writer.WriteElement(new CellValue(property.GetValue(item) != null ?
property.GetValue(item).ToString() : String.Empty));
var attribute = pAttributes.First();
var format = attribute.Format;
//todo apply data format
// write the end cell element
writer.WriteEndElement();
}
}
// write the end row element
writer.WriteEndElement();
}
// write the end SheetData element
writer.WriteEndElement();
// write the end Worksheet element
writer.WriteEndElement();
writer.Close();
writer = OpenXmlWriter.Create(document.WorkbookPart);
writer.WriteStartElement(new Workbook());
writer.WriteStartElement(new Sheets());
writer.WriteElement(new Sheet()
{
Name = fileName,
SheetId = 1,
Id = document.WorkbookPart.GetIdOfPart(workSheetPart)
});
// End Sheets
writer.WriteEndElement();
// End Workbook
writer.WriteEndElement();
writer.Close();
document.Close();
}
}
result with styles
private DownloadResult FillExcelWorksheet<T>(String name, IEnumerable<T> source, Action<OpenXmlWriter, WorkbookStylesPart, SharedStringTablePart, Int32> addNonGenericReportInfo = null)
{
var properties = typeof(T).GetProperties();
var folder = ConfigurationManager.AppSettings["ReportsPath"];
if (!Directory.Exists(folder)) {
Directory.CreateDirectory(folder);
}
//remove old files
Directory.GetFiles(folder)
.Select(f => new FileInfo(f))
.Where(f => f.CreationTime < DateTime.Now.AddDays(-1))
.ToList()
.ForEach(f => f.Delete());
var fileName = String.Format("{0}_{1}.xlsx", name, Guid.NewGuid());
using (var document = SpreadsheetDocument.Create(folder + fileName, SpreadsheetDocumentType.Workbook))
{
//this list of attributes will be used when writing a start element
List<OpenXmlAttribute> stringAttributes = new List<OpenXmlAttribute>()
{
new OpenXmlAttribute("t", null, "str")
};
OpenXmlWriter writer;
var workBookPart = document.AddWorkbookPart();
var workSheetPart = workBookPart.AddNewPart<WorksheetPart>();
var sharedStringPart = workBookPart.AddNewPart<SharedStringTablePart>();
sharedStringPart.SharedStringTable = new SharedStringTable();
var stylesPart = document.WorkbookPart.AddNewPart<WorkbookStylesPart>();
stylesPart.Stylesheet = new Stylesheet();
stylesPart.Stylesheet.NumberingFormats = new NumberingFormats();
// create fills
stylesPart.Stylesheet.Fills = new Fills();
stylesPart.Stylesheet.Fills.AppendChild(new Fill { PatternFill = new PatternFill { PatternType = PatternValues.None } }); // required, reserved by Excel
//EXAMPLE DON'T REMOVE
//stylesPart.Stylesheet.Fills.AppendChild(new Fill { PatternFill = new PatternFill { PatternType = PatternValues.Gray125 } }); // required, reserved by Excel
//// create a solid red fill
//var solidRed = new PatternFill() { PatternType = PatternValues.Solid };
//solidRed.ForegroundColor = new ForegroundColor { Rgb = HexBinaryValue.FromString("FFFF0000") }; // red fill
//solidRed.BackgroundColor = new BackgroundColor { Indexed = 64 };
//stylesPart.Stylesheet.Fills.AppendChild(new Fill { PatternFill = solidRed });
stylesPart.Stylesheet.Fills.Count = 3;
// blank cell format list
stylesPart.Stylesheet.CellStyleFormats = new CellStyleFormats();
stylesPart.Stylesheet.CellStyleFormats.AppendChild(new CellFormat());
stylesPart.Stylesheet.CellStyleFormats.Count = 1;
//Borders
stylesPart.Stylesheet.Borders = new Borders();
stylesPart.Stylesheet.Borders.AppendChild(new Border());
var border = new Border();
border.Append(new BottomBorder() { Style = BorderStyleValues.Thin });
stylesPart.Stylesheet.Borders.AppendChild(border);
stylesPart.Stylesheet.Borders.Count = 2;
//Fonts
stylesPart.Stylesheet.Fonts = new Fonts();
stylesPart.Stylesheet.Fonts.AppendChild(new Font());
var font = new Font();
font.Bold = new Bold();
stylesPart.Stylesheet.Fonts.AppendChild(font);
stylesPart.Stylesheet.Fonts.Count = 2;
// cell format list
stylesPart.Stylesheet.CellFormats = new CellFormats();
// empty one for index 0, seems to be required
stylesPart.Stylesheet.CellFormats.AppendChild(new CellFormat());
// cell format references style format 0, font 0, border 0, fill 2 and applies the fill
stylesPart.Stylesheet.CellFormats.AppendChild(new CellFormat { FormatId = 0, FontId = 1, BorderId = 1, FillId = 0, ApplyFill = true }).AppendChild(new Alignment { Horizontal = HorizontalAlignmentValues.Center });
stylesPart.Stylesheet.CellFormats.Count = 2;
writer = OpenXmlWriter.Create(workSheetPart);
writer.WriteStartElement(new Worksheet());
writer.WriteStartElement(new SheetData());
writer.WriteStartElement(new Row());
foreach (PropertyInfo property in properties)
{
var pAttributes = property.GetCustomAttributes<ReportColumnAttribute>();
if (pAttributes.Any())
{
var attribute = pAttributes.First();
var cell = new Cell();
cell.DataType = CellValues.SharedString;
cell.StyleIndex = 1;
writer.WriteStartElement(cell);
//write the cell value
var index = InsertSharedStringItem(attribute.ColumnName, sharedStringPart);
writer.WriteElement(new CellValue(index.ToString()));
// write the end cell element
writer.WriteEndElement();
if (!String.IsNullOrEmpty(attribute.Format) && !_styleFormats.ContainsKey(attribute.Format))
{
//164 is first free format id
var formatId = 164 + (uint)_styleFormats.Count;
var format = new NumberingFormat { NumberFormatId = formatId, FormatCode = attribute.Format };
stylesPart.Stylesheet.NumberingFormats.AppendChild(format);
stylesPart.Stylesheet.CellFormats.AppendChild(new CellFormat { NumberFormatId = formatId, FontId = 0, BorderId = 0, FillId = 0, ApplyFill = true });
//stylesPart.Stylesheet.CellFormats.AppendChild(format);
stylesPart.Stylesheet.CellStyleFormats.Count++;
_styleFormats.Add(attribute.Format, new StyleIndexes(stylesPart.Stylesheet.CellFormats.Count, formatId));
stylesPart.Stylesheet.CellFormats.Count++;
}
}
}
// write the end row element
writer.WriteEndElement();
foreach (var item in source)
{
//write the row start element with the row index attribute
writer.WriteStartElement(new Row());
foreach (PropertyInfo property in properties)
{
var pAttributes = property.GetCustomAttributes<ReportColumnAttribute>();
if (pAttributes.Any())
{
var attribute = pAttributes.First();
var cell = new Cell();
if (!String.IsNullOrEmpty(attribute.Format))
{
cell.StyleIndex = (uint)_styleFormats[attribute.Format].StyleIndex;
}
var typeCode = Type.GetTypeCode(property.PropertyType);
//write the cell start element with the type and reference attributes
if (typeCode == TypeCode.String)
{
writer.WriteStartElement(cell, stringAttributes);
}
else
{
cell.DataType = CellValues.Number;
writer.WriteStartElement(cell);
}
var value = String.Empty;
if (typeCode == TypeCode.DateTime)
{
value = property.GetValue(item) != null ?
Convert.ToDateTime(property.GetValue(item)).ToOADate().ToString() : String.Empty;
}
else {
value = property.GetValue(item) != null ?
property.GetValue(item).ToString() : String.Empty;
}
//write the cell value
writer.WriteElement(new CellValue(value));
// write the end cell element
writer.WriteEndElement();
}
}
// write the end row element
writer.WriteEndElement();
}
if (addNonGenericReportInfo != null)
{
addNonGenericReportInfo(writer, stylesPart, sharedStringPart, source.Count());
}
sharedStringPart.SharedStringTable.Save();
stylesPart.Stylesheet.Save();
// write the end SheetData element
writer.WriteEndElement();
// write the end Worksheet element
writer.WriteEndElement();
writer.Close();
writer = OpenXmlWriter.Create(document.WorkbookPart);
writer.WriteStartElement(new Workbook());
writer.WriteStartElement(new Sheets());
writer.WriteElement(new Sheet()
{
Name = name,
SheetId = 1,
Id = document.WorkbookPart.GetIdOfPart(workSheetPart)
});
// End Sheets
writer.WriteEndElement();
// End Workbook
writer.WriteEndElement();
writer.Close();
document.Close();
}
return new DownloadResult
{
Name = name + ".xlsx",
PathToDownload = folder + fileName,
};
}
private static int InsertSharedStringItem(string text, SharedStringTablePart shareStringPart)
{
// If the part does not contain a SharedStringTable, create one.
if (shareStringPart.SharedStringTable == null)
{
shareStringPart.SharedStringTable = new SharedStringTable();
}
int i = 0;
// Iterate through all the items in the SharedStringTable. If the text already exists, return its index.
foreach (SharedStringItem item in shareStringPart.SharedStringTable.Elements<SharedStringItem>())
{
if (item.InnerText == text)
{
return i;
}
i++;
}
// The text does not exist in the part. Create the SharedStringItem and return its index.
shareStringPart.SharedStringTable.AppendChild(new SharedStringItem(new DocumentFormat.OpenXml.Spreadsheet.Text(text)));
shareStringPart.SharedStringTable.Save();
return i;
}
private Dictionary<String, StyleIndexes> _styleFormats;
private class StyleIndexes
{
public StyleIndexes(uint styleIndex, uint numberIndex)
{
StyleIndex = styleIndex;
NumberIndex = numberIndex;
}
public uint StyleIndex { get; set; }
public uint NumberIndex { get; set; }
}