用openXML写xlsx文件,单元格样式,小数保留后两位和百分比样式

因公司报表需求,需将多个相同格式的xlsx报表文件合并到一个xlsx中,报表头和sheet表相同。项目紧急未深入研究,还不够完善。此外,使用openXml移除最后一行带公式内容后,Excel打开文件提示部分内容有问题需修复,WPS则无此问题,寻求解决思路。

sdfsdfsdfdffor随手记录!

源于公司的报表需求,需要将多个相同格式的报表xlsx文件,合并到同一个xlsx中。报表头一样(前三行为报表头),sheet表一样。

因为项目紧急,还有很多东西没有深入深究,还不够完善。另外问个问题,使用openXml移除最后一行(带公式)后,这个xlxs,用excel打开时,会提示部份内容有问题,并让修复,用wps则没有问题,看看有没有大佬能给点思路。

private void mergeXlsx()
        {
            string sourceFileName = "D:\\源报表.xlsx";//这个源报表无法用openxml打开,因为是别的组人导出的,所以未深究,先用npoi打开读取内容实现功能先
            string mergeFileName = "D:\\合并之后的报表.xlsx";//这个合并后的报表,是直接复制了报表模板,并把后面的数据行和公式去掉,只保留了文件头

            //使用npoi读取需要被合并的报表,这个报表无法用openxml打开,可能是报表生成的时候,里面公式有问题
            FileStream fileStream = new FileStream(sourceFileName, FileMode.Open, FileAccess.Read);
            XSSFWorkbook xssfWorkbook = new XSSFWorkbook(fileStream);
            int sheetCount = xssfWorkbook.NumberOfSheets;
            for (int sheetIndex = 0; sheetIndex < sheetCount; sheetIndex++)
            {
                ISheet sheet = xssfWorkbook.GetSheetAt(sheetIndex);

                string sheetName = sheet.SheetName;

                IRow row = sheet.GetRow(sheet.LastRowNum);
                using (SpreadsheetDocument tempateDocument = SpreadsheetDocument.Open(mergeFileName, true))
                {
                    WorkbookPart workbookPart = tempateDocument.WorkbookPart;
                    DocumentFormat.OpenXml.Spreadsheet.Workbook workbook = workbookPart.Workbook;
                    DocumentFormat.OpenXml.Spreadsheet.Sheet s = workbook.Descendants<DocumentFormat.OpenXml.Spreadsheet.Sheet>().Where(sht => sht.Name == sheetName).FirstOrDefault();
                    WorksheetPart wsPart = (WorksheetPart)workbookPart.GetPartById(s.Id);
                    DocumentFormat.OpenXml.Spreadsheet.SheetData sheetdata = wsPart.Worksheet.Elements<DocumentFormat.OpenXml.Spreadsheet.SheetData>().FirstOrDefault();

                    //复制模板出来的报表-合并后的报表
                    //-------------单元格样式---------------
                    WorkbookStylesPart workbookStylesPart = workbookPart.WorkbookStylesPart;
                    //Stylesheet stylesheet = new Stylesheet();

                    //定义格式-这里就不要定义新的CellFormats,目标报表本身里面就带了很多格式,如果定义新的格式,会把报表头那些全部冲掉,所以这里添加新的格式
                    //stylesheet.CellFormats = new CellFormats();
                    //stylesheet.CellFormats.Count = 2;
                    //styleIndex =0U
                    CellFormat cfDefault = new CellFormat();
                    cfDefault.Alignment = new Alignment();
                    cfDefault.NumberFormatId = 2;//2代表保留两位小数,对照表在后面
                    cfDefault.FontId = 0;
                    cfDefault.BorderId = 0;
                    cfDefault.FillId = 0;
                    cfDefault.ApplyAlignment = true;
                    cfDefault.ApplyBorder = true;
                    workbookStylesPart.Stylesheet.CellFormats.Append(cfDefault);//添加新的格式

                    //styleIndex =1U
                    CellFormat cfContent = new CellFormat();
                    cfContent.Alignment = new Alignment();
                    cfContent.NumberFormatId = 10;//10代表百分比,并保留两位小数,对照表在后面
                    cfContent.FontId = 0;
                    cfContent.BorderId = 0;
                    cfContent.FillId = 0;
                    cfContent.ApplyAlignment = true;
                    cfContent.ApplyBorder = true;
                    workbookStylesPart.Stylesheet.CellFormats.Append(cfContent);//添加新的格式

                    //------------------------------------------

                    for (int i = 3; i <= sheet.LastRowNum; i++)  //对工作表每一行读取
                    {
                        row = sheet.GetRow(i);
                        if (row != null)
                        {
                            DocumentFormat.OpenXml.Spreadsheet.Row lastrow = new DocumentFormat.OpenXml.Spreadsheet.Row();
                            bool isAddrow = true;
                            for (int j = 0; j < row.LastCellNum; j++)  //对工作表每一列读取
                            {
                                //获取i行j列数据 
                                if (row.GetCell(j) == null)
                                {
                                    //业务特殊约定-如果第一列为null或者值为空,本行不加入合并
                                    if (j == 0)
                                    {
                                        isAddrow = false;
                                        break;
                                    }

                                    DocumentFormat.OpenXml.Spreadsheet.Cell cell = new DocumentFormat.OpenXml.Spreadsheet.Cell() { CellReference = "", DataType = new DocumentFormat.OpenXml.EnumValue<CellValues>(CellValues.String) };
                                    DocumentFormat.OpenXml.Spreadsheet.CellValue cellValue = new DocumentFormat.OpenXml.Spreadsheet.CellValue();
                                    cellValue.Text = "";
                                    cell.Append(cellValue);
                                    lastrow.AppendChild(cell);
                                }
                                else
                                {
                                    string format = row.GetCell(j).CellStyle.GetDataFormatString().Trim();//通过npoi读出来的单元格格式
                                    object val = GetValueType(xssfWorkbook, row.GetCell(j));

                                    string disVal = val.ToString();

                                    //如果第一列为null或者值为空,不加入合并
                                    if (j == 0 && string.IsNullOrEmpty(disVal))
                                    {
                                        isAddrow = false;
                                        break;
                                    }
                                    if (val.GetType() == typeof(double))
                                    {
                                        DocumentFormat.OpenXml.Spreadsheet.Cell cell = new DocumentFormat.OpenXml.Spreadsheet.Cell() { CellReference = "", DataType = new DocumentFormat.OpenXml.EnumValue<CellValues>(CellValues.Number) };

                                        if (format == "0.00_")
                                        {
                                            //这里取index,不要用workbookStylesPart.Stylesheet.CellFormats.Count,虽然上面添加了cellformat,但是count不会变,ChildElements是count才是对的
                                            cell.StyleIndex = (uint)workbookStylesPart.Stylesheet.CellFormats.ChildElements.Count - 2;
                                        }
                                        else if (format == "0.00%")
                                        {
                                            //这里取index,不要用workbookStylesPart.Stylesheet.CellFormats.Count,虽然上面添加了cellformat,但是count不会变,ChildElements是count才是对的
                                            cell.StyleIndex = (uint)workbookStylesPart.Stylesheet.CellFormats.ChildElements.Count - 1;
                                        }

                                        DocumentFormat.OpenXml.Spreadsheet.CellValue cellValue = new DocumentFormat.OpenXml.Spreadsheet.CellValue(disVal);
                                        cell.Append(cellValue);
                                        lastrow.AppendChild(cell);

                                    }
                                    else
                                    {
                                        DocumentFormat.OpenXml.Spreadsheet.Cell cell = new DocumentFormat.OpenXml.Spreadsheet.Cell() { CellReference = "", DataType = new DocumentFormat.OpenXml.EnumValue<CellValues>(CellValues.String) };
                                        DocumentFormat.OpenXml.Spreadsheet.CellValue cellValue = new DocumentFormat.OpenXml.Spreadsheet.CellValue(disVal);
                                        cell.Append(cellValue);
                                        lastrow.AppendChild(cell);
                                    }

                                }
                            }
                            if (isAddrow)
                            {
                                sheetdata.AppendChild(lastrow);
                            }
                        }
                    }
                }
            }
            fileStream.Close();
            xssfWorkbook.Close();
        }

        /// <summary>
        /// 单元格值类型
        /// </summary>
        /// <param name="workbook"></param>
        /// <param name="cell"></param>
        /// <returns></returns>
        private object GetValueType(XSSFWorkbook workbook, ICell cell)
        {
            if (cell == null)
                return null;
            switch (cell.CellType)
            {
                case NPOI.SS.UserModel.CellType.Blank:
                    return "";
                case NPOI.SS.UserModel.CellType.Boolean:
                    return cell.BooleanCellValue;
                case NPOI.SS.UserModel.CellType.Numeric:
                    return cell.NumericCellValue;
                case NPOI.SS.UserModel.CellType.String:
                    return cell.StringCellValue;
                case NPOI.SS.UserModel.CellType.Error:
                    return cell.ErrorCellValue;
                case NPOI.SS.UserModel.CellType.Formula:
                    //针对公式列 进行动态计算;注意:公式暂时只支持 数值 字符串类型
                    XSSFFormulaEvaluator eva = new XSSFFormulaEvaluator(workbook);
                    var formulaValue = eva.Evaluate(cell);
                    if (formulaValue.CellType == NPOI.SS.UserModel.CellType.Numeric)
                    {
                        return formulaValue.NumberValue;
                    }
                    else if (formulaValue.CellType == NPOI.SS.UserModel.CellType.String)
                    {
                        return formulaValue.StringValue;
                    }
                    else
                    {
                        return "";
                    }

                default:
                    return "=" + cell.CellFormula;
            }
        }

NumberFormatId对照表

ID  Format Code
0   General
1   0
2   0.00
3   #,##0
4   #,##0.00
9   0%
10  0.00%
11  0.00E+00
12  # ?/?
13  # ??/??
14  d/m/yyyy
15  d-mmm-yy
16  d-mmm
17  mmm-yy
18  h:mm tt
19  h:mm:ss tt
20  H:mm
21  H:mm:ss
22  m/d/yyyy H:mm
37  #,##0 ;(#,##0)
38  #,##0 ;[Red](#,##0)
39  #,##0.00;(#,##0.00)
40  #,##0.00;[Red](#,##0.00)
45  mm:ss
46  [h]:mm:ss
47  mmss.0
48  ##0.0E+0
49  @

<think>我们使用OpenXML SDK来读取Excel文件(.xlsx)中的特定单元格内容。主要步骤包括: 1. 添加对DocumentFormat.OpenXmlWindowsBase的引用。 2. 使用SpreadsheetDocument类打开Excel文件。 3. 获取目标工作表(Worksheet)。 4. 根据单元格引用(如"A1")获取单元格(Cell)。 5. 读取单元格的值。注意:如果单元格是共享字符串(Shared String),则需要从共享字符串表中获取实际字符串。 以下是具体代码示例: ```csharp using DocumentFormat.OpenXml.Packaging; using DocumentFormat.OpenXml.Spreadsheet; using System; using System.Linq; class Program { static void Main(string[] args) { string filePath = @"C:\path\to\your\file.xlsx"; string sheetName = "Sheet1"; // 要读取的工作表名称 string cellAddress = "A1"; // 要读取的单元格地址,如A1 try { // 打开Excel文件 using (SpreadsheetDocument document = SpreadsheetDocument.Open(filePath, false)) { // 获取工作簿部分 WorkbookPart workbookPart = document.WorkbookPart; // 根据工作表名称获取工作表 Sheet sheet = workbookPart.Workbook.Descendants<Sheet>().FirstOrDefault(s => s.Name == sheetName); if (sheet == null) { Console.WriteLine("工作表不存在"); return; } // 获取工作表部分 WorksheetPart worksheetPart = (WorksheetPart)workbookPart.GetPartById(sheet.Id); Worksheet worksheet = worksheetPart.Worksheet; // 获取单元格 Cell cell = GetCell(worksheet, cellAddress); if (cell == null) { Console.WriteLine("单元格不存在"); return; } // 读取单元格的值 string cellValue = GetCellValue(cell, workbookPart); Console.WriteLine($"单元格 {cellAddress} 的值是: {cellValue}"); } } catch (Exception ex) { Console.WriteLine($"发生错误: {ex.Message}"); } } // 根据单元格地址获取单元格 private static Cell GetCell(Worksheet worksheet, string cellAddress) { string columnName = GetColumnName(cellAddress); uint rowIndex = GetRowIndex(cellAddress); // 在指定行中查找单元格 Row row = worksheet.Descendants<Row>().FirstOrDefault(r => r.RowIndex == rowIndex); if (row == null) return null; // 在行中根据列名查找单元格 Cell cell = row.Descendants<Cell>().FirstOrDefault(c => string.Compare(c.CellReference.Value, cellAddress, true) == 0); return cell; } // 从单元格地址中提取列名(例如,从"A1"中提取"A") private static string GetColumnName(string cellAddress) { return new string(cellAddress.Where(char.IsLetter).ToArray()); } // 从单元格地址中提取行号(例如,从"A1"中提取1) private static uint GetRowIndex(string cellAddress) { string rowPart = new string(cellAddress.Where(char.IsDigit).ToArray()); return uint.Parse(rowPart); } // 获取单元格的值,处理共享字符串 private static string GetCellValue(Cell cell, WorkbookPart workbookPart) { string value = cell.CellValue?.InnerText; if (cell.DataType != null && cell.DataType.Value == CellValues.SharedString) { // 共享字符串表中的索引 int index = int.Parse(value); SharedStringTablePart sharedStringPart = workbookPart.SharedStringTablePart; if (sharedStringPart != null) { value = sharedStringPart.SharedStringTable.Elements<SharedStringItem>().ElementAt(index).InnerText; } } return value ?? string.Empty; } } ``` ### 说明: 1. 首先,我们打开Excel文件(只读模式)。 2. 然后根据工作表名称找到目标工作表。 3. 通过单元格地址(如"A1")找到对应的单元格。 4. 读取单元格的值时,需要判断单元格类型是否为共享字符串(Shared String),如果是则需要从共享字符串表中获取实际字符串。 5. 如果单元格是数字或日期等,则直接返回字符串值(注意:日期在Excel中是以数字存储的,需要根据格式转换,这里只返回原始字符串)。 ### 注意事项: - 如果单元格是公式,则读取的是公式计算后的值(如果Excel保存了计算后的值)。如果需要读取公式本身,可以检查`Cell`的`CellFormula`属性。 - 日期时间的处理:Excel中日期是数字,需要转换为DateTime对象,这里没有处理,返回的是数字字符串。 ### 相关问题 1. 如何读取Excel中合并单元格的内容? 2. 如何写入Excel文件中的特定单元格? 3. 如何处理Excel中的日期时间格式? 4. 如何读取Excel中隐藏的工作表? 5. 使用OpenXML读取大型Excel文件时如何优化性能?</think>### 使用 OpenXML SDK 读取 Excel 特定单元格C# 实现 #### 核心步骤 1. **添加 NuGet 包引用** 在项目中安装以下包: ```bash Install-Package DocumentFormat.OpenXml Install-Package System.IO.Packaging ``` 2. **完整代码实现** ```csharp using DocumentFormat.OpenXml.Packaging; using DocumentFormat.OpenXml.Spreadsheet; using System; using System.Linq; public class ExcelReader { public static string ReadCellValue(string filePath, string sheetName, string cellAddress) { using (SpreadsheetDocument doc = SpreadsheetDocument.Open(filePath, false)) { WorkbookPart workbookPart = doc.WorkbookPart; // 获取目标工作表 Sheet sheet = workbookPart.Workbook.Descendants<Sheet>() .FirstOrDefault(s => s.Name.Value.Equals(sheetName, StringComparison.OrdinalIgnoreCase)); if (sheet == null) throw new Exception($"工作表 '{sheetName}' 不存在"); // 获取工作表部件 WorksheetPart worksheetPart = (WorksheetPart)workbookPart.GetPartById(sheet.Id); // 获取目标单元格 Cell cell = worksheetPart.Worksheet.Descendants<Cell>() .FirstOrDefault(c => c.CellReference.Value.Equals(cellAddress, StringComparison.OrdinalIgnoreCase)); if (cell == null) return null; // 单元格不存在时返回空 // 处理不同类型的数据 if (cell.DataType != null && cell.DataType.Value == CellValues.SharedString) { // 共享字符串(如文本) SharedStringTablePart stringTable = workbookPart.SharedStringTablePart; return stringTable.SharedStringTable.ElementAt(int.Parse(cell.InnerText)).InnerText; } else { // 直接返回值(数字、布尔值等) return cell.CellValue?.Text; } } } } // 使用示例 string value = ExcelReader.ReadCellValue( filePath: @"C:\Data\example.xlsx", sheetName: "SalesData", cellAddress: "B5" ); Console.WriteLine($"单元格值: {value}"); ``` #### 关键说明 1. **数据类型处理**: - 文本类型:从共享字符串表(`SharedStringTable`)中获取真实值 - 数值/布尔类型:直接读取 `CellValue` 属性 - 公式结果:默认读取计算后的值(需确保 Excel 保存时已缓存结果) 2. **性能优化**: - 使用 `using` 语句确保资源释放 - 通过 `Descendants` 进行高效查询 - 禁用编辑模式(`SpreadsheetDocument.Open` 的第二个参数设为 `false`) 3. **常见错误处理**: - 工作表不存在时抛出明确异常 - 单元格为空时返回 `null` - 忽略字符串大小(`StringComparison.OrdinalIgnoreCase`) > ⚠️ **注意**:单元格地址需使用 Excel 格式(如 `"A1"`, `"AB23"`),日期值将返回 OADate 数字格式(需额外转换)[^1]。 --- ### 相关问题 1. 如何读取合并单元格的值? 2. 怎样高效读取整个工作表的数据? 3. 如何处理 Excel 中的日期时间格式? 4. OpenXML 与 EPPlus 库的主要区别是什么? 5. 如何向现有 Excel 文件写入数据而不损坏原有格式?
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值