zoukankan      html  css  js  c++  java
  • 转:替换word文档

    https://www.cnblogs.com/toolgood/p/13512091.html

    说明:

     /******************
         * 单个变量用{dd}表示,数字用#[Id]#表示,但经测试用{Id}也可以。
         * 单个变量每个变量作为DataTable的一个字段,然后用 openXmlTemplate.SetData(datatable)设置,只dt的第一行有效
         *
         * 表格用{{pp[i].Name }}两个括号循环,i自动循环,Name表示类的成员变量,也可用#pp[2].Name#表示取第几行的值
         * 表格的数据源用泛型类List<TableTest>表示,然后 JsonConvert.SerializeObject进行序列化
         * 再用 openXmlTemplate.SetListData("pp", JsonConvert.SerializeObject(lst))操作,其中参数pp表示模板中的名字
         *
         * 若表格中定义有:{{pp[i].Name }},但程序中未SetListData这个pp标签,则此行被删除

    保存时,用这个比较好
                var bs = openXmlTemplate.BuildTemplate("test.docx");
                File.WriteAllBytes("openxml_2.docx", bs);


         * */

    最近,我写公司项目word导出功能,应该只有2小时的工作量,却被硬生生的拉长2天,项目上线到业务正常运行也被拉长到2个星期。

    为什么如此浪费时间呢?

        1)公司的项目比较老,采用硬编码模式,意味着word改一个字就要发布一次代码。发布检验就浪时间了。

        2)由于硬编码,采用的是<html>这种格式,手写代码比较废时,而且编写表格时会遇到单元格字数变多被撑大,表格变形的情况。表格长度需要人工计算。这类意想不到的问题。

        3)公司测试库数据不全,测试库数据无法全面覆盖线上环境。这又拉长了检验时间。

        4)项目分支被正在开发的分支合并了,一下子被拉长了4天。

    这简单功能浪费太多时间了,我在网上搜了一下word导出的方案:

        第一种:硬编码,就是公司的方案,问题太多了不用考虑。

        第二种:通过Sql查询数据,存入字典,再通过第三方组件替换word的文字。这种方案,简单容操作,sql查询可以换成存储过程,也存在缺点,1)存储过程要写提很细,逻辑算法都写在存储过程,存储过程可能变得很复杂。2)不支持表格内插入多条数据。

        第三种:通过Sql查询数据,使用Razor模板引擎生成word。这种方案解决了存储过程复杂问题,但Razor模板内使用<html>这种格式,所以写模板时很麻烦。

        第四种:通过Sql查询数据,存入字典,再通过第三方组件替换word的域。这种方案与第二种方案类似,对我个人来说,我不喜欢修改域。

    但是,我想要一个简单、容易控制、表格内能插入多条数据、可商用的方案。

        简单:类似第二种方案,数据存入字典,循环替换word的文字,存储过程可以写得简单。

        容易控制:模板不能使用<html>这种格式,最好能用office直接控制表格文字大小、颜色。

        表格内能插入多条数据:我写的组件内必须有索引。

        可商用:拒绝商用组件。

    经过几天琢磨,我找到可行的方案:存储过程+模板+算法可控

    依赖组件:

        DocumentFormat.OpenXml,微软官方开源组件,支持docx文件,MIT协议。

        ToolGood.Algorithm,本人的Excel计算引擎组件,MIT协议,可简化存储过程。

    核心代码:

        ReplaceTemplate 替换Word文字

        ReplaceTable 替换Word表格并支持插入

    ReplaceTemplate 替换Word文字

        public class WordTemplate : AlgorithmEngine
        {
            private readonly static Regex _tempEngine = new Regex("^###([^::]*)[::](.*)$");// 定义临时变量
            private readonly static Regex _tempMatch = new Regex("(#[^#]+#)");// 
            private readonly static Regex _simplifyMatch = new Regex(@"({[^{}]*})");//简化文本 只读取字段
    
    
            private void ReplaceTemplate(Body body)
            {
                
                var tempMatches = new List<string>();
                List<Paragraph> deleteParagraph = new List<Paragraph>();
                foreach (var paragraph in body.Descendants<Paragraph>()) {
                    var text = paragraph.InnerText.Trim();
                    var m = _tempEngine.Match(text);
                    if (m.Success) {
                        var name = m.Groups[1].Value.Trim();
                        var engine = m.Groups[2].Value.Trim();
                        var value = this.TryEvaluate(engine, "");
                        this.AddParameter(name, value);
                        deleteParagraph.Add(paragraph);
                        continue;
                    }
                    var m2 = _tempMatch.Match(text);
                    if (m2.Success) {
                        tempMatches.Add(m2.Groups[1].Value);
                        continue;
                    }
                    var m3 = _simplifyMatch.Match(text);
                    if (m3.Success) {
                        tempMatches.Add(m3.Groups[1].Value);
                        continue;
                    }
                }
                foreach (var paragraph in deleteParagraph) {
                    paragraph.Remove();
                }
    
                Regex nameReg = new Regex(string.Join("|", listNames));
                foreach (var m in tempMatches) {
                    string value;
                    if (m.StartsWith("#")) {
                        var eval = m.Trim('#');
                        ……
                        value = this.TryEvaluate(eval, "");
                    } else {
                        value = this.TryEvaluate(m.Replace("{", "[").Replace("}", "]"), "");
                    }
                    foreach (var paragraph in body.Descendants<Paragraph>()) {
                        ReplaceText(paragraph, m, value);
                    }
                }
            }
    // 代码来源 https://stackoverflow.com/questions/19094388/openxml-replace-text-in-all-document
            private void ReplaceText(Paragraph paragraph, string find, string replaceWith){
        ….
    }
    }
    View Code

    ReplaceTable 替换Word表格并支持插入

           private readonly static Regex _rowMatch = new Regex(@"({{(.*?)}})");//
    
            private int _idx;
            private List<string> listNames = new List<string>();
    
            private void ReplaceTable(Body body)
            {
                foreach (Table table in body.Descendants<Table>()) {
    
                    foreach (TableRow row in table.Descendants<TableRow>()) {
                        bool isRowData = false;
                        foreach (var paragraph in row.Descendants<Paragraph>()) {
                            var text = paragraph.InnerText.Trim();
                            if (_rowMatch.IsMatch(text)) {
                                isRowData = true;
                                break;
                            }
                        }
                        if (isRowData) {
                            // 防止 list[i].Id 写成  [list][[i]].Id 这种繁杂的方式
                            Regex nameReg = new Regex(string.Join("|", listNames));
                            Dictionary<string, string> tempMatches = new Dictionary<string, string>();
                            foreach (Paragraph ph in row.Descendants<Paragraph>()) {
                                var m2 = _rowMatch.Match(ph.InnerText.Trim());
                                if (m2.Success) {
                                    var txt = m2.Groups[1].Value;
                                    var eval = txt.Substring(2, txt.Length - 4).Trim();
                                    eval = nameReg.Replace(eval, new MatchEvaluator((k) => {
                                        return "[" + k.Value + "]";
                                    }));
                                    tempMatches[txt] = eval;
                                }
                            }
    
                            TableRow tpl = row.CloneNode(true) as TableRow;
                            TableRow lastRow = row;
                            TableRow opRow = row;
                            var startIndex = UseExcelIndex ? 1 : 0;
                            _idx = startIndex;
    
                            while (true) {
                                if (_idx > startIndex) { opRow = tpl.CloneNode(true) as TableRow; }
    
                                bool isMatch = true;
                                foreach (var m in tempMatches) {
                                    string value = this.TryEvaluate(m.Value, null);
                                    if (value == null) {
                                        isMatch = false;
                                        break;
                                    }
                                    foreach (var ph in opRow.Descendants<Paragraph>()) {
                                        ReplaceText(ph, m.Key, value);
                                    }
                                }
                                if (isMatch==false) {
                                    //当数据为空时,清空数据
                                    if (_idx == startIndex) {
                                        foreach (var ph in opRow.Descendants<Paragraph>()) {
                                            ph.RemoveAllChildren();
                                        }
                                    }
                                    break;
                                }
    
                                if (_idx > startIndex) { table.InsertAfter(opRow, lastRow); }
                                lastRow = opRow;
                                _idx++;
                            }
    
                        }
                    }
                }
            }
    View Code

    案例上手:

    后台代码:

    复制代码
                // 获取数据
                var helper = SqlHelperFactory.OpenSqliteFile("test.db");
            .......
                var dt = helper.ExecuteDataTable("select * from Introduction");
                var tableTests = helper.Select<TableTest>("select * from TableTest");
    
                ToolGood.OutputWord.WordTemplate openXmlTemplate = new ToolGood.OutputWord.WordTemplate();
                // 加载数据
                openXmlTemplate.SetData(dt);
                openXmlTemplate.SetListData("list", JsonConvert.SerializeObject(tableTests));
    
                // 生成模板 一
                openXmlTemplate.BuildTemplate("test.docx", "openxml_2.docx");
    
                // 生成模板 二
                var bs = openXmlTemplate.BuildTemplate("test.docx");
                File.WriteAllBytes("openxml_1.docx", bs);
    复制代码

    Word模板:

    Word生成后:

     后记:

    WordTemplate 类,主要实现了三个功能:

        1、自定义替换word中的文字标签,当标签不存在,则设置为空字符串;

        2、可以有word中定义公式,替换所对应的值;

        3、在表格插入多行数据,当数据为0时清空单元格。

    通过上面三个功能,WordTemplate 类将代码中的word生成方法分离出来。

    系统后台需要配置 存储过程与word模板信息,就可以将word生成与系统更新完成分离开了。

    系统后台可以配置公式,则公式修改不需要更新word模板。

    注:一般业务人员是看得懂四则运算的,部分财务人员更是了解Excel公式,可以减少开发协助时间。

    完整代码:https://github.com/toolgood/ToolGood.OutputWord

    该组件已上传到Nuget:Install-Package ToolGood.OutputWord

    Excel公式参考:https://github.com/toolgood/ToolGood.Algorithm

    JAVA版本:暂时没有,ToolGood.Algorithm已支持JAVA版本。

  • 相关阅读:
    高并发下缓存失效问题及解决方案
    行为型设计模式
    Redisson
    行为型设计模式
    Docker 安装 Elasticsearch 和 Kibana
    行为型设计模式
    C# 使用 WebBrowser 实现 HTML 转图片功能
    .NET 程序下锐浪报表 (Grid++ Report) 的绿色发布指南
    .NET 程序员的 Playground :LINQPad
    Windows 服务器上的 WordPress 站点优化笔记
  • 原文地址:https://www.cnblogs.com/81/p/13640163.html
Copyright © 2011-2022 走看看