最近一个项目涉及到 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 请参照相应文档。