简单快速导出word文档
最近,我寫公司項目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-documentprivate void ReplaceText(Paragraph paragraph, string find, string replaceWith){…. } }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++;}}}}}案例上手:
后臺代碼:
// 獲取數據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
總結
以上是生活随笔為你收集整理的简单快速导出word文档的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ASP.NET Core学习资源汇总
- 下一篇: 如何让多端口网站用一个nginx进行反向