.NET OpenXml+MiniWord Word 表格模板导出

前言

我最近有一个模板Word文档导出的需求,我网上看了一下MiniWord,但是他的模板功能没有我这个需求的效果,所以还是得自己手写一个。

Word库选择

  • NPOI:一个第三方库,下载量好像挺大的
  • OpenXML:这个好像是微软官方的库,MiniWord也用这个库

看了一下,先打算试试OpenXML

功能需求

  • 占位符替换
  • 列表数据批量生成+替换

替换效果

模板文件

生成效果

OpenXML的基本操作

微软官方 OpenXML Word 操作文档

OpenXml SDK学习笔记(1):Word的基本结构

Word基础知识讲解

Word的文档结构

官方说的比较的玄学,简单来说,Word的本质就是xml,详细的可以看看这个文章

OpenXml SDK学习笔记(1):Word的基本结构

一个最简单的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();
        }


        
        
    }
}

总结

其是我写的比较的粗糙,但是能用,至少满足了我的使用需求。

posted @   gclove2000  阅读(239)  评论(0编辑  收藏  举报
编辑推荐:
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!
点击右上角即可分享
微信分享提示