.NET OpenXml+MiniWord Word 表格模板导出
目录
前言
我最近有一个模板Word文档导出的需求,我网上看了一下MiniWord,但是他的模板功能没有我这个需求的效果,所以还是得自己手写一个。
Word库选择
- NPOI:一个第三方库,下载量好像挺大的
- OpenXML:这个好像是微软官方的库,MiniWord也用这个库
看了一下,先打算试试OpenXML
功能需求
- 占位符替换
- 列表数据批量生成+替换
替换效果
模板文件
生成效果
OpenXML的基本操作
Word基础知识讲解
Word的文档结构
官方说的比较的玄学,简单来说,Word的本质就是xml,详细的可以看看这个文章
一个最简单的Word生成
using Bogus;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;
using MiniSoftware;
using MiniWordTest.Models;
namespace MiniWordTest
{
internal class Program
{
static void Main(string[] args)
{
//模板文件位置,暂时没用
string templateUrl = "Assets\\Template\\Template.docx";
//输出文件位置,用随机id作为文件名
var outPutUrl = $"Assets\\Export\\{Guid.NewGuid().ToString()}.docx";
//文件输出的位置
using (WordprocessingDocument doc = WordprocessingDocument.Create(outPutUrl, DocumentFormat.OpenXml.WordprocessingDocumentType.Document))
{
MainDocumentPart mainPart = doc.AddMainDocumentPart();
// Create the document structure and add some text.
mainPart.Document = new Document();
Body body = mainPart.Document.AppendChild(new Body());
Paragraph para = body.AppendChild(new Paragraph());
Run run = para.AppendChild(new Run());
// String msg contains the text, "Hello, Word!"
run.AppendChild(new Text("Hello World!"));
//在using的结束,就会自动创建文件
}
Console.WriteLine("Hello, World!");
Console.ReadKey();
}
}
}
生成效果
Word读取
测试文档内容
文本读取
static void Main(string[] args)
{
//模板文件位置,暂时没用
string templateUrl = "Assets\\Template\\Template.docx";
//输出文件位置,用随机id作为文件名
var outPutUrl = $"Assets\\Export\\{Guid.NewGuid().ToString()}.docx";
var textList = new List<string>() ;
//模板文件的位置
using (WordprocessingDocument doc = WordprocessingDocument.Open(templateUrl, true))
{
Body body = doc.MainDocumentPart.Document.Body;
if (body != null)
{
var paragraphs = body.Elements<Paragraph>();
foreach (var paragraph in paragraphs)
{
Console.WriteLine(paragraph.InnerText);
}
}
}
Console.WriteLine("Hello, World!");
Console.ReadKey();
}
表格读取
using Bogus;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;
namespace MiniWordTest
{
internal class Program
{
static void Main(string[] args)
{
//模板文件位置,暂时没用
string templateUrl = "Assets\\Template\\Template.docx";
//输出文件位置,用随机id作为文件名
var outPutUrl = $"Assets\\Export\\{Guid.NewGuid().ToString()}.docx";
var textList = new List<string>() ;
//模板文件的位置
using (WordprocessingDocument doc = WordprocessingDocument.Open(templateUrl, true))
{
Body body = doc.MainDocumentPart.Document.Body;
if (body != null)
{
foreach(var table in body.Elements<Table>())
{
foreach(var row in table.Elements<TableRow>())
{
foreach(var cell in row.Elements<TableCell>())
{
Console.Write(cell.InnerText + "\t");
}
Console.WriteLine();
}
}
}
}
Console.WriteLine("运行结束");
Console.ReadKey();
}
}
}
Word 替换导出
写到这里的时候,我突然想到,我为什么一定要重写一个呢?我可以在MiniWord的基础上面修改
文件复制模板
Word文件另存为
namespace MiniWordTest
{
internal class Program
{
static void Main(string[] args)
{
//模板文件位置,暂时没用
string templateUrl = "Assets\\Template\\Template.docx";
//输出文件位置,用随机id作为文件名
var outPutUrl = $"Assets\\Export\\{Guid.NewGuid().ToString()}.docx";
//另存文件
File.Copy(templateUrl, outPutUrl);
Console.WriteLine("运行结束");
Console.ReadKey();
}
}
}
指定位置后面插入
using Bogus;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;
using System.Xml.Linq;
namespace MiniWordTest
{
internal class Program
{
static void Main(string[] args)
{
//模板文件位置,暂时没用
string templateUrl = "Assets\\Template\\Template.docx";
//输出文件位置,用随机id作为文件名
var outPutUrl = $"Assets\\Export\\{Guid.NewGuid().ToString()}.docx";
Console.WriteLine(outPutUrl);
//另存文件
File.Copy(templateUrl, outPutUrl);
using (var doc = WordprocessingDocument.Open(outPutUrl, true))
{
Body body = doc.MainDocumentPart.Document.Body;
var count = body.Elements().Count();
Console.WriteLine("count = "+ count);
bool isCopy = false;
var copyTable = new Table();
//遍历所有的表格
foreach(var table in body.Elements<Table>())
{
var firstRow = table.Elements<TableRow>().First();
Console.WriteLine(table.InnerText);
//找到需要Copy的表格
if (firstRow.InnerText.Contains("@TableCopy"))
{
Console.WriteLine("找到需要查询的表格!");
isCopy = true;
//先在这个后面插入一个HelloWord
Paragraph para = new Paragraph();
Run r = new Run();
Text t = new Text();
t.Text = "Hello World";
r.Append(t);
para.Append(r);
//添加到表格后面
table.InsertAfterSelf(para);
}
}
}
Console.WriteLine("运行结束");
Console.ReadKey();
}
}
}
表格多次复制
using Bogus;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;
using NPOI.Util;
using System.Xml.Linq;
namespace MiniWordTest
{
internal class Program
{
static void Main(string[] args)
{
//模板文件位置,暂时没用
string templateUrl = "Assets\\Template\\Template.docx";
//输出文件位置,用随机id作为文件名
var outPutUrl = $"Assets\\Export\\{Guid.NewGuid().ToString()}.docx";
Console.WriteLine(outPutUrl);
//另存文件
File.Copy(templateUrl, outPutUrl);
using (var doc = WordprocessingDocument.Open(outPutUrl, true))
{
Body body = doc.MainDocumentPart.Document.Body;
var count = body.Elements().Count();
Console.WriteLine("count = "+ count);
//遍历所有的表格
foreach(var table in body.Elements<Table>())
{
var firstRow = table.Elements<TableRow>().First();
Console.WriteLine(table.InnerText);
//找到需要Copy的表格
if (firstRow.InnerText.Contains("@TableCopy"))
{
Console.WriteLine("找到需要查询的表格!");
//移除掉复刻的标识符
table.RemoveChild(firstRow);
for (var i = 0; i < 10; i++)
{
//做一个复刻
var copyTable = (Table)table.Clone();
Paragraph para = new Paragraph();
//在复刻表格
table.InsertAfterSelf(para);
table.InsertAfterSelf(copyTable);
}
//删除自己
table.Remove();
}
}
}
Console.WriteLine("运行结束");
Console.ReadKey();
}
}
}
占位符替换
using Bogus;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;
using MiniWordTest.Models;
using NPOI.Util;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Xml.Linq;
namespace MiniWordTest
{
internal class Program
{
static void Main(string[] args)
{
//模板文件位置,暂时没用
string templateUrl = "Assets\\Template\\Template.docx";
//输出文件位置,用随机id作为文件名
var outPutUrl = $"Assets\\Export\\{Guid.NewGuid().ToString()}.docx";
Console.WriteLine(outPutUrl);
//另存文件
File.Copy(templateUrl, outPutUrl);
using (var doc = WordprocessingDocument.Open(outPutUrl, true))
{
Body body = doc.MainDocumentPart.Document.Body;
var count = body.Elements().Count();
Console.WriteLine("count = " + count);
//随机数生成
var faker = new Faker<Student>()
.RuleFor(t => t.Id, f => f.IndexFaker)
.RuleFor(t => t.Name, f => f.Name.FirstName())
.RuleFor(t => t.Age, f => f.Random.Int(10, 30));
var students = faker.Generate(10);
//遍历所有的表格
foreach (var table in body.Elements<Table>())
{
var firstRow = table.Elements<TableRow>().First();
//找到需要Copy的表格
if (firstRow.InnerText.Contains("@TableCopy"))
{
Console.WriteLine("找到需要查询的表格!");
//移除掉复刻的标识符
table.RemoveChild(firstRow);
ListsTableCopy(table, students);
}
}
}
Console.WriteLine("运行结束");
Console.ReadKey();
}
/// <summary>
/// 表格复制
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="table"></param>
/// <param name="data"></param>
private static void ListsTableCopy<T>(Table table, List<T> data)
{
var matchStr = @"\[\[.*]]";
Console.WriteLine(table.InnerText);
for (var i = 0; i < data.Count; i++)
{
var tableCopy = (Table)table.Clone();
foreach (var row in tableCopy.Elements<TableRow>())
{
foreach (var cell in row.Elements<TableCell>())
{
foreach (var paras in cell.Elements<Paragraph>())
{
foreach(var run in paras.Elements<Run>())
{
foreach (var text in run.Elements<Text>())
{
Console.WriteLine(text.InnerText);
if (Regex.IsMatch(text.Text, matchStr))
{
text.Text = TextObjectReplace(text.Text, @"\[\[", @"\]\]", data[i]);
}
}
}
}
}
}
Paragraph para = new Paragraph();
//在复刻表格
table.InsertBeforeSelf(para);
table.InsertBeforeSelf(tableCopy);
}
table.Remove();
}
/// <summary>
/// 文本占位符替换
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="tempText"></param>
/// <param name="startTag"></param>
/// <param name="endTag"></param>
/// <param name="data"></param>
/// <returns></returns>
private static string TextObjectReplace<T>(string tempText,string startTag,string endTag,T data)
{
BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.Instance;
var propertyInfos = typeof(T).GetProperties();
var res = tempText;
foreach (var propertyInfo in propertyInfos)
{
var matchStr =startTag+ propertyInfo.Name+endTag;
res = Regex.Replace(res, matchStr, propertyInfo.GetValue(data).ToString());
}
return res;
}
}
}
剩下来的就交给MiniWord就行了
封装好的代码
工具类
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;
using MiniSoftware;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace MiniWordTest.Utils
{
public class WordTemplateHelper
{
public WordTemplateHelper()
{
}
public void MiniWordExtendListCreate<T>(string templateUrl, string listsUrl, List<T> lists, string outPutUrl, object value)
{
WordTemplateListsCreate(templateUrl, listsUrl, lists);
MiniWord.SaveAsByTemplate(outPutUrl, listsUrl, value);
}
/// <summary>
/// word文件模板生成,占位符为@TableCopy
/// </summary>
public void WordTemplateListsCreate<T>(string template, string output, List<T> lists)
{
//另存文件
File.Copy(template, output);
using (var doc = WordprocessingDocument.Open(output, true))
{
Body body = doc.MainDocumentPart.Document.Body;
var count = body.Elements().Count();
Console.WriteLine("count = " + count);
//遍历所有的表格
foreach (var table in body.Elements<Table>())
{
var firstRow = table.Elements<TableRow>().First();
//找到需要Copy的表格
if (firstRow.InnerText.Contains("@TableCopy"))
{
Console.WriteLine("找到需要查询的表格!");
//移除掉复刻的标识符
table.RemoveChild(firstRow);
ListsTableCopy(table, lists);
}
}
}
}
/// <summary>
/// 表格复制
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="table"></param>
/// <param name="data"></param>
private void ListsTableCopy<T>(Table table, List<T> data)
{
var matchStr = @"\[\[.*]]";
for (var i = 0; i < data.Count; i++)
{
var tableCopy = (Table)table.Clone();
foreach (var row in tableCopy.Elements<TableRow>())
{
foreach (var cell in row.Elements<TableCell>())
{
foreach (var paras in cell.Elements<Paragraph>())
{
foreach (var run in paras.Elements<Run>())
{
foreach (var text in run.Elements<Text>())
{
if (Regex.IsMatch(text.Text, matchStr))
{
text.Text = TextObjectReplace(text.Text, @"\[\[", @"\]\]", data[i]);
}
}
}
}
}
}
Paragraph para = new Paragraph();
//在复刻表格
table.InsertBeforeSelf(para);
table.InsertBeforeSelf(tableCopy);
}
table.Remove();
}
/// <summary>
/// 文本占位符替换
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="tempText"></param>
/// <param name="startTag"></param>
/// <param name="endTag"></param>
/// <param name="data"></param>
/// <returns></returns>
private string TextObjectReplace<T>(string tempText, string startTag, string endTag, T data)
{
BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.Instance;
var propertyInfos = typeof(T).GetProperties();
var res = tempText;
foreach (var propertyInfo in propertyInfos)
{
var matchStr = startTag + propertyInfo.Name + endTag;
res = Regex.Replace(res, matchStr, propertyInfo.GetValue(data).ToString());
}
return res;
}
}
}
使用
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MiniWordTest.Models
{
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MiniWordTest.Models
{
public class SchoolStudent
{
public int Count { get; set; }
public List<Student> Students { get; set; }
public string Summary { get; set; }
}
}
using Bogus;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;
using MiniSoftware;
using MiniWordTest.Models;
using MiniWordTest.Utils;
using NPOI.Util;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Xml.Linq;
namespace MiniWordTest
{
internal class Program
{
static void Main(string[] args)
{
//模板文件位置,暂时没用
string templateUrl = "Assets\\Template\\Template.docx";
//输出文件位置,用随机id作为文件名
var outPutUrl1 = $"Assets\\Export\\{Guid.NewGuid().ToString()}.docx";
var outPutUrl2 = $"Assets\\Export\\{Guid.NewGuid().ToString()}.docx";
Console.WriteLine(outPutUrl1);
Console.WriteLine(outPutUrl2);
//随机数生成
var faker = new Faker<Student>()
.RuleFor(t => t.Id, f => f.IndexFaker)
.RuleFor(t => t.Name, f => f.Name.FirstName())
.RuleFor(t => t.Age, f => f.Random.Int(10, 30));
var students = faker.Generate(10);
var data = new SchoolStudent()
{
Students = students,
Count = students.Count(),
Summary = "总结"
};
var templateHelper = new WordTemplateHelper();
templateHelper.MiniWordExtendListCreate(templateUrl, outPutUrl1,students,outPutUrl2, data);
Console.WriteLine("运行结束");
Console.ReadKey();
}
}
}
总结
其是我写的比较的粗糙,但是能用,至少满足了我的使用需求。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!