C# 使用 DocumentFormat.OpenXml 读写 Word 文档

最近一个项目涉及到 Word 文档的读写操作,考虑相关工具:

1、Microsoft.Office.Interop.Word : 需要用到 COM 组件

2、NPOI : Java POI 对应 .net 版本

3、DocumentFormat.OpenXml :无需第三方组件,可通过 NuGet  引用

因项目环境考虑,这里选择 DocumentFormat.OpenXml 进行 Word 文档的读写操作。

从文件流 FileStream 中读取 Word 文档,填充内容后,返回字节数组 byte[] :

//相关引用
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;

//读写 Word 
public byte[] ExportWord(DataModel dbData)
{
    //Word 模板物理路径 
    string filePath = Path.Combine("physical path", "filename");
    var docOrgStream = File.OpenRead(filePath);
    //读取文件 byte[]
    byte[] bytes = new byte[docOrgStream.Length];
    docOrgStream.Read(bytes, 0, bytes.Length);
    docOrgStream.Close();
    var docNewStream = new MemoryStream(bytes, true);
    //将数据写入 Word
    using (WordprocessingDocument wpdoc = WordprocessingDocument.Open(docNewStream, true))
    {
        var mainPart = wpdoc.MainDocumentPart;

        //填充页眉数据
        var headerParts = mainPart.HeaderParts.ToList();
        headerParts.ForEach(headerPart =>
        {
            var sdtElementsHeader = headerPart.Header.Descendants<SdtElement>().ToList();
            SetWordDataByContentTag(sdtElementsHeader, dbData);
        });
        //填充正文数据
        var sdtElementsBody = mainPart.Document.Body.Descendants<SdtElement>().ToList();
        SetWordDataByContentTag(sdtElementsBody, dbData);

        mainPart.Document.Save();
        wpdoc.Save();
    }
    
    return docNewStream.ToArray();
}

//通过 Tag 标识填充 Word 页眉(Header)和正文(Body)
private void SetWordDataByContentTag(List<SdtElement> sdtElements, DataModel dbData)
{
    //Word 中需要填充的内容控件的 Tag 对应配置项
    var config = new
    {
        tables = new List<string>
        {
            "table1",
            "table2",
            "table3",
        },
        fields = new List<List<List<string>>>
        {
            //Table table1- Column Names
            new List<List<string>>
            {
                new List<string>{ "column1", "EntityName1" } ,
                new List<string>{ "column2", "EntityName2" },
                new List<string>{ "column3", "EntityName3" },
                new List<string>{ "column4", "EntityName4" },
                new List<string>{ "column5", "EntityName5" },
            },
            //Table table2- Column Names
            new List<List<string>>
            {
                new List<string>{ "column1", "EntityName1" } ,
                new List<string>{ "column2", "EntityName2" },
                new List<string>{ "column3", "EntityName3" },
                new List<string>{ "column4", "EntityName4" },
                new List<string>{ "column5", "EntityName5" },
            },
            //Table table3- Column Names
            new List<List<string>>
            {
                new List<string>{ "column1", "EntityName1" } ,
                new List<string>{ "column2", "EntityName2" },
                new List<string>{ "column3", "EntityName3" },
                new List<string>{ "column4", "EntityName4" },
                new List<string>{ "column5", "EntityName5" },
            },
        }
    };
    if (config.tables.Count == config.fields.Count)
    {
        config.tables.ForEach(tableName =>
        {
            //config 中 Table Name 的索引
            int tableIndex = config.tables.IndexOf(tableName);
            config.fields[tableIndex].ForEach(columnName =>
            {
                var tagSdtElements = sdtElements.Where(d =>
                {
                    var tag = d.Descendants<Tag>().ToList();
                    //查找 Content Tag 满足条件的元素 - Tag格式: TableName_ColumnName, 比如: table1_column1
                    return tag != null && tag.Find(d => d.Val == string.Format("{0}_{1}", tableName, columnName[0])) != null;
                 }).ToList();
                 tagSdtElements.ForEach(sdt =>
                 {
                     //通过 Run 替换文本
                     var runList = sdt.Descendants<Run>().ToList();
                     if (runList.Count > 0)
                     {
                         //先将所有 Run 元素的文本移除
                         runList.ForEach(run =>
                         {
                             run.RemoveAllChildren();
                         });
                         //获取 Entity(DataModel) 字段内容
                         var textData = tableIndex switch
                         {
                             0 => GetEntityValueByName(dbData.table1Data, columnName[1]) ?? "",
                             1 => GetEntityValueByName(dbData.table2Data, columnName[1]) ?? "",
                             2 => GetEntityValueByName(dbData.table3Data, columnName[1]) ?? "",
                             _ => ""
                         };
                         //按换行符 \n 拆分字符串
                         List<string> textSplitByBreak = textData.Split(new string[] { "\n" }, StringSplitOptions.None).ToList();
                         textSplitByBreak.ForEach(text =>
                         {
                             runList[0].AppendChild(new Text(text));
                             var index = textSplitByBreak.IndexOf(text);
                             if ((index + 1) != textSplitByBreak.Count)
                             {
                                  //添加换行
                                  runList[0].AppendChild(new Break());
                             }
                         });
                      }
                  });
               });
            });
         }
}

//根据实体属性名获取属性值 - 反射
private string GetEntityValueByName<T>(T data, string name)
{
    try
    {
        return Convert.ToString(data.GetType().GetProperty(name).GetValue(data));
    }
    catch (Exception)
    {
        return "";
    }
}

其他两个工具 Microsoft.Office.Interop.Word、NPOI 请参照相应文档。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值