Word报告自动生成(例如 导出数据库结构)

        将很早之前写的一个小组件重新整理优化一下,做成一个通用的功能。适用于导出数据库的结构(表、字段等)到word或将体检数据自动生成word版的体检报告等。代码:github

一、主要需要完成功能:

1. 灵活的配置规则及word样式设置(文本、表格、图表、颜色等).

2. 支持表格.

3. 支持图表.

4. 支持章节内容循环生成.

5. 支持目录.

6.支持文档结构图

7.更新指定位置的文字

8.支持pdf导出.

 

最后结果如下:

                                                                            图一

                                    图二

                                         图三

二、需求分析与实现方式

  功能主要涉及3个比较重要的部分:数据源、word样式、配置规则。 

     为了简单,数据源决定采用一个存储过程返回dataset的方式, 整张报告的数据来源于此dataset的多个datatable.

样式与配置:首先想到的是写一个config文件,所有配置都放到一个文件里,然后将数据按照这个规则生成word。但无疑这样的配置项太多了,关键是“样式”问题,比如字体、颜色、表格宽度.....想想就头大。而且没有“所见即所得”的效果,配置完都不知道啥样。

后来决定采取修改的方式, 先以一个word文件作为模板,在模板中定义好上面提到的“样式”,然后在模板中做一个个标记,然后将数据按照规则更新到对应的标记。

                                                       图四

    而这个标记我们用到了word的一个叫【书签】的功能,打开word按ctrl+shift+f5, 打开书签功能,如下图:

                                 图五

这样将【规则】通过一系列规则的【书签】定义到word模板中。

三、规则配置

思路确定了,那就开始设计如何通过【书签】将规则定义到word模板中去,这里决定将所有规则都通过【书签】实现,而放弃config文件的方式,这个更统一而且直观一些。

a.循环

      以图四为例,数据库有多少张表是不固定的,我们在制作模板的时候不可能先画好n(n为表的总数)个表格等待数据填充, 这里就会需要遍历数据源中提供的所有表结构数据,然后逐一形成表格。这里就需要将图四中的表格循环一下,自动复制生成多个这样的表格。当然,这只是一种情况,还有可能会出现循环嵌套循环的情况,那么我将这个循环定义成一个书签的时候按照这样的格式:

      loop_级别_表序号_filter_名称

含义如下:

     loop:代表这是一个循环。

     级别:默认文档级别为0,出现的第一层循环为1,其内部若再次嵌套循环则级别为2,依次类推。

     表序号:取dataset中的第几张表(从1开始)

     filter:循环的时候可能会用到对datatable的查找过滤,在此写出,多个字段用xx隔开(因为此处不允许有下划线外其他特殊字符, 就用这个xx吧 )

     名称:loop名称,方便与其他 loop区别

 b.更新指定位置的文字

    如图四中的【服务器名】、【表总数】等,只需要替换对应的文字即可:

    label_级别_名称

含义如下:

     label:代表这是一个label。

     级别:默认文档级别为0,出现的第一层循环为1,其内部若再次嵌套循环则级别为2,依次类推。

     名称:label名称

     注意这里省略了表序号,当级别为0的时候 ,自动取最后一个datatable中的数据,因为这个label经常会用到其他表汇总的数据,可能会用到之前几张表的数据,所以放在其他表都处理好后。当级别为1的时候,自然取该级别循环的行数据。

c.表格

     表格的配置原本也想对表格添加书签,后来发现有个表格属性, 觉得写在这里更好一些。

 如上图所示,【标题】格式为:table_级别_取dataset中的第几张表(从1开始)_filter字段多个用xx隔开(此处不允许有下划线外其他特殊字符, 就用这个xx吧 )_名称

【说明】为可选项,若需要合计行, 则需要标识, summary或缩写s: [合计]行是模板中表格的第几行   summaryfilter或缩写sf:数据集进一步filter到summary行的条件(因为一个表格只取一个datatable,通过一个标识指定了哪些datarow是用来作为合计的)

d.图表

同样为了方便将配置写在了【标题】,图表生成后会将名称修改过来。

配置格式为:chart_级别_取dataset中的第几张表(从1开始)_filter字段多个用xx隔开(此处不允许有下划线外其他特殊字符, 就用这个xx吧 )_chart名称_是否将datatable的columnname作为第一行_从datatable第几列开始(列起始为1)_截止列,

如下图所示配置即可。

 

e.目录

无需标识, 模板中添加目录, 当内容处理完成之后, 会根据处理后的结果动态更新目录.

 

四、主要代码

  1 using system;
  2 using system.collections.generic;
  3 using system.data;
  4 using system.diagnostics;
  5 using system.io;
  6 using system.linq;
  7 using system.reflection;
  8 using excel = microsoft.office.interop.excel;
  9 using word = microsoft.office.interop.word;
 10 
 11 namespace flylolo.wordreport.demo
 12 {
 13     public class wordreporthelper
 14     {
 15         private word.application wordapp = null;
 16         private word.document worddoc = null;
 17         private dataset datasource = null;
 18         private object line = word.wdunits.wdline;
 19         private string errormsg = "";
 20 
 21         /// <summary>
 22         /// 根据模板文件,创建数据报告
 23         /// </summary>
 24         /// <param name="templatefile">模板文件名(含路径)</param>
 25         /// <param name="newfilepath">新文件路径)</param>
 26         /// <param name="datasource">数据源,包含多个datatable</param>
 27         /// <param name="saveformat">新文件格式:</param>
 28         public bool createreport(string templatefile, dataset datasource, out string errormsg, string newfilepath, ref string newfilename, int saveformat = 16)
 29         {
 30             this.datasource = datasource;
 31             errormsg = this.errormsg;
 32             bool rtn = opentemplate(templatefile)
 33                 && setcontent(new wordelement(worddoc.range(), datarow: datasource.tables[datasource.tables.count - 1].rows[0]))
 34                 && updatetablesofcontents()
 35                 && savefile(newfilepath, ref newfilename, saveformat);
 36 
 37             closeandclear();
 38             return rtn;
 39         }
 40 
 41         /// <summary>
 42         /// 打开模板文件
 43         /// </summary>
 44         /// <param name="templatefile"></param>
 45         /// <returns></returns>
 46         private bool opentemplate(string templatefile)
 47         {
 48             if (!file.exists(templatefile))
 49             {
 50                 return false;
 51             }
 52 
 53             wordapp = new word.application();
 54             wordapp.visible = true;//使文档可见,调试用
 55             wordapp.displayalerts = word.wdalertlevel.wdalertsnone;
 56             object file = templatefile;
 57             worddoc = wordapp.documents.open(ref file, readonly: false);
 58             return true;
 59         }
 60 
 61         /// <summary>
 62         /// 为指定区域写入数据
 63         /// </summary>
 64         /// <param name="element"></param>
 65         /// <returns></returns>
 66         private bool setcontent(wordelement element)
 67         {
 68             string currbookmarkname = string.empty;
 69             string startwith = "loop_" + (element.level + 1).tostring() + "_";
 70             foreach (word.bookmark item in element.range.bookmarks)
 71             {
 72                 currbookmarkname = item.name;
 73 
 74                 if (currbookmarkname.startswith(startwith) && (!currbookmarkname.equals(element.elementname)))
 75                 {
 76                     setloop(new wordelement(item.range, currbookmarkname, element.datarow, element.groupby));
 77                 }
 78 
 79             }
 80 
 81             setlabel(element);
 82 
 83             settable(element);
 84 
 85             setchart(element);
 86 
 87             return true;
 88         }
 89 
 90         /// <summary>
 91         /// 处理循环
 92         /// </summary>
 93         /// <param name="element"></param>
 94         /// <returns></returns>
 95         private bool setloop(wordelement element)
 96         {
 97             datarow[] datarows = datasource.tables[element.tableindex].select(element.groupbystring);
 98             int count = datarows.count();
 99             element.range.select();
100 
101             //第0行作为模板  先从1开始  循环后处理0行;
102             for (int i = 0; i < count; i++)
103             {
104 
105                 element.range.copy();  //模板loop复制
106                 wordapp.selection.insertparagraphafter();//换行 不会清除选中的内容,typeparagraph 等同于回车,若当前有选中内容会被清除. typeparagraph 会跳到下一行,insertparagraphafter不会, 所以movedown一下.
107                 wordapp.selection.movedown(ref line, missing.value, missing.value);
108                 wordapp.selection.paste(); //换行后粘贴复制内容
109                 int offset = wordapp.selection.range.end - element.range.end; //计算偏移量
110 
111                 //复制书签,书签名 = 模板书签名 + 复制次数
112                 foreach (word.bookmark subbook in element.range.bookmarks)
113                 {
114                     if (subbook.name.equals(element.elementname))
115                     {
116                         continue;
117                     }
118 
119                     wordapp.selection.bookmarks.add(subbook.name + "_" + i.tostring(), worddoc.range(subbook.start + offset, subbook.end + offset));
120                 }
121 
122                 setcontent(new wordelement(worddoc.range(wordapp.selection.range.end - (element.range.end - element.range.start), wordapp.selection.range.end), element.elementname + "_" + i.tostring(), datarows[i], element.groupby));
123             }
124 
125             element.range.delete();
126 
127             return true;
128         }
129 
130         /// <summary>
131         /// 处理简单label
132         /// </summary>
133         /// <param name="element"></param>
134         /// <returns></returns>
135         private bool setlabel(wordelement element)
136         {
137             if (element.range.bookmarks != null && element.range.bookmarks.count > 0)
138             {
139                 string startwith = "label_" + element.level.tostring() + "_";
140                 string bookmarkname = string.empty;
141                 foreach (word.bookmark item in element.range.bookmarks)
142                 {
143                     bookmarkname = item.name;
144 
145                     if (bookmarkname.startswith(startwith))
146                     {
147                         bookmarkname = wordelement.getname(bookmarkname);
148 
149                         item.range.text = element.datarow[bookmarkname].tostring();
150                     }
151                 }
152             }
153 
154             return true;
155         }
156 
157         /// <summary>
158         /// 填充table
159         /// </summary>
160         /// <param name="element"></param>
161         /// <returns></returns>
162         private bool settable(wordelement element)
163         {
164             if (element.range.tables != null && element.range.tables.count > 0)
165             {
166                 string startwith = "table_" + element.level.tostring() + "_";
167                 foreach (word.table table in element.range.tables)
168                 {
169                     if (!string.isnullorempty(table.title) && table.title.startswith(startwith))
170                     {
171                         wordelement tableelement = new wordelement(null, table.title, element.datarow);
172 
173                         tableconfig config = new tableconfig(table.descr);
174 
175                         object datarowtemplate = table.rows[config.datarow];
176                         word.row summaryrow = null;
177                         datarow summarydatarow = null;
178                         datatable dt = datasource.tables[tableelement.tableindex];
179                         datarow[] datarows = datasource.tables[tableelement.tableindex].select(tableelement.groupbystring); ;
180 
181                         if (config.summaryrow > 0)
182                         {
183                             summaryrow = table.rows[config.summaryrow];
184                             summarydatarow = dt.select(string.isnullorempty(tableelement.groupbystring) ? config.summaryfilter : tableelement.groupbystring + " and  " + config.summaryfilter).firstordefault();
185                         }
186 
187                         foreach (datarow row in datarows)
188                         {
189                             if (row == summarydatarow)
190                             {
191                                 continue;
192                             }
193 
194                             word.row newrow = table.rows.add(ref datarowtemplate);
195                             for (int j = 0; j < table.columns.count; j++)
196                             {
197                                 newrow.cells[j + 1].range.text = row[j].tostring(); ;
198                             }
199 
200                         }
201 
202                         ((word.row)datarowtemplate).delete();
203 
204                         if (config.summaryrow > 0 && summarydatarow != null)
205                         {
206                             for (int j = 0; j < summaryrow.cells.count; j++)
207                             {
208                                 string temp = summaryrow.cells[j + 1].range.text.trim().replace("ra", "");
209 
210                                 if (!string.isnullorempty(temp) && temp.length > 2 && dt.columns.contains(temp.substring(1, temp.length - 2)))
211                                 {
212                                     summaryrow.cells[j + 1].range.text = summarydatarow[temp.substring(1, temp.length - 2)].tostring();
213                                 }
214                             }
215                         }
216 
217                         table.title = tableelement.name;
218                     }
219 
220 
221                 }
222             }
223 
224             return true;
225         }
226 
227         /// <summary>
228         /// 处理图表
229         /// </summary>
230         /// <param name="element"></param>
231         /// <returns></returns>
232         private bool setchart(wordelement element)
233         {
234             if (element.range.inlineshapes != null && element.range.inlineshapes.count > 0)
235             {
236                 list<word.inlineshape> chartlist = element.range.inlineshapes.cast<word.inlineshape>().where(m => m.type == word.wdinlineshapetype.wdinlineshapechart).tolist();
237                 string startwith = "chart_" + element.level.tostring() + "_";
238                 foreach (word.inlineshape item in chartlist)
239                 {
240                     word.chart chart = item.chart;
241                     if (!string.isnullorempty(chart.charttitle.text) && chart.charttitle.text.startswith(startwith))
242                     {
243                         wordelement chartelement = new wordelement(null, chart.charttitle.text, element.datarow);
244 
245                         datatable datatable = datasource.tables[chartelement.tableindex];
246                         datarow[] datarows = datatable.select(chartelement.groupbystring);
247 
248                         int columncount = datatable.columns.count;
249                         list<int> columns = new list<int>();
250 
251                         foreach (var dr in datarows)
252                         {
253                             for (int i = chartelement.columnstart == -1 ? 0 : chartelement.columnstart - 1; i < (chartelement.columnend == -1 ? columncount : chartelement.columnend); i++)
254                             {
255                                 if (columns.contains(i) || dr[i] == null || string.isnullorempty(dr[i].tostring()))
256                                 {
257 
258                                 }
259                                 else
260                                 {
261                                     columns.add(i);
262                                 }
263                             }
264                         }
265                         columns.sort();
266                         columncount = columns.count;
267                         int rowscount = datarows.length;
268 
269                         word.chartdata chartdata = chart.chartdata;
270 
271                         //chartdata.activate();
272                         //此处有个比较疑惑的问题, 不执行此条,生成的报告中的图表无法再次右键编辑数据. 执行后可以, 但有两个问题就是第一会弹出excel框, 处理完后会自动关闭. 第二部分chart的数据range设置总不对
273                         //不知道是不是版本的问题, 谁解决了分享一下,谢谢
274 
275                         excel.workbook dataworkbook = (excel.workbook)chartdata.workbook;
276                         dataworkbook.application.visible = false;
277 
278                         excel.worksheet datasheet = (excel.worksheet)dataworkbook.worksheets[1];
279                         //设定范围  
280                         string a = (chartelement.columnnameforhead ? rowscount + 1 : rowscount) + "|" + columncount;
281                         console.writeline(a);
282 
283                         excel.range trange = datasheet.range["a1", datasheet.cells[(chartelement.columnnameforhead ? rowscount + 1 : rowscount), columncount]];
284                         excel.listobject tbl1 = datasheet.listobjects[1];
285                         //datasheet.listobjects[1].delete(); //想过重新删除再添加  这样 原有数据清掉了, 但觉得性能应该会有所下降
286                         //excel.listobject tbl1 = datasheet.listobjects.addex();
287                         tbl1.resize(trange);
288                         for (int j = 0; j < rowscount; j++)
289                         {
290                             datarow row = datarows[j];
291                             for (int k = 0; k < columncount; k++)
292                             {
293                                 datasheet.cells[j + 2, k + 1].formular1c1 = row[columns[k]];
294                             }
295                         }
296 
297                         if (chartelement.columnnameforhead)
298                         {
299                             for (int k = 0; k < columns.count; k++)
300                             {
301                                 datasheet.cells[1, k + 1].formular1c1 = datatable.columns[columns[k]].columnname;
302                             }
303                         }
304                         chart.charttitle.text = chartelement.name;
305                         //datasheet.application.quit();
306                     }
307                 }
308             }
309 
310             return true;
311         }
312 
313         /// <summary>
314         /// 更新目录
315         /// </summary>
316         /// <returns></returns>
317         private bool updatetablesofcontents()
318         {
319             foreach (word.tableofcontents item in worddoc.tablesofcontents)
320             {
321                 item.update();
322             }
323 
324             return true;
325         }
326 
327         /// <summary>
328         /// 保存文件
329         /// </summary>
330         /// <param name="newfilepath"></param>
331         /// <param name="newfilename"></param>
332         /// <param name="saveformat"></param>
333         /// <returns></returns>
334         private bool savefile(string newfilepath, ref string newfilename, int saveformat = 16)
335         {
336             if (string.isnullorempty(newfilename))
337             {
338                 newfilename = datetime.now.tostring("yyyymmddhhmmss");
339 
340                 switch (saveformat)
341                 {
342                     case 0:// word.wdsaveformat.wdformatdocument
343                         newfilename += ".doc";
344                         break;
345                     case 16:// word.wdsaveformat.wdformatdocumentdefault
346                         newfilename += ".docx";
347                         break;
348                     case 17:// word.wdsaveformat.wdformatpdf
349                         newfilename += ".pdf";
350                         break;
351                     default:
352                         break;
353                 }
354             }
355 
356             object newfile = path.combine(newfilepath, newfilename);
357             object wdsaveformat = saveformat;
358             worddoc.saveas(ref newfile, ref wdsaveformat);
359             return true;
360         }
361 
362         /// <summary>
363         /// 清理
364         /// </summary>
365         private void closeandclear()
366         {
367             if (wordapp == null)
368             {
369                 return;
370             }
371             worddoc.close(word.wdsaveoptions.wddonotsavechanges);
372             wordapp.quit(word.wdsaveoptions.wddonotsavechanges);
373             system.runtime.interopservices.marshal.releasecomobject(worddoc);
374             system.runtime.interopservices.marshal.releasecomobject(wordapp);
375             worddoc = null;
376             wordapp = null;
377             gc.collect();
378             killprocess("excel", "winword");
379         }
380 
381         /// <summary>
382         /// 杀进程..
383         /// </summary>
384         /// <param name="processnames"></param>
385         private void killprocess(params string[] processnames)
386         {
387             //process myproc = new process();
388             //得到所有打开的进程  
389             try
390             {
391                 foreach (string name in processnames)
392                 {
393                     foreach (process thisproc in process.getprocessesbyname(name))
394                     {
395                         if (!thisproc.closemainwindow())
396                         {
397                             if (thisproc != null)
398                                 thisproc.kill();
399                         }
400                     }
401                 }
402             }
403             catch (exception)
404             {
405                 //throw exc;
406                 // msg.text+=  "杀死"  +  processname  +  "失败!";  
407             }
408         }
409     }
410 
411     /// <summary>
412     /// 封装的word元素
413     /// </summary>
414     public class wordelement
415     {
416         public wordelement(word.range range, string elementname = "", datarow datarow = null, dictionary<string, string> groupby = null, int tableindex = 0)
417         {
418             this.range = range;
419             this.elementname = elementname;
420             this.groupby = groupby;
421             this.datarow = datarow;
422             if (string.isnullorempty(elementname))
423             {
424                 this.level = 0;
425                 this.tableindex = tableindex;
426                 this.name = string.empty;
427                 this.columnnameforhead = false;
428             }
429             else
430             {
431                 string[] element = elementname.split('_');
432                 this.level = int.parse(element[1]);
433                 this.columnnameforhead = false;
434                 this.columnstart = -1;
435                 this.columnend = -1;
436 
437                 if (element[0].equals("label"))
438                 {
439                     this.name = element[2];
440                     this.tableindex = 0;
441                 }
442                 else
443                 {
444                     this.name = element[4];
445                     this.tableindex = int.parse(element[2]) - 1;
446 
447                     if (!string.isnullorempty(element[3]))
448                     {
449                         string[] filters = element[3].split(new string[] { "xx" }, stringsplitoptions.removeemptyentries);
450                         if (this.groupby == null)
451                         {
452                             this.groupby = new dictionary<string, string>();
453                         }
454                         foreach (string item in filters)
455                         {
456                             if (!this.groupby.keys.contains(item))
457                             {
458                                 this.groupby.add(item, datarow[item].tostring());
459                             }
460 
461                         }
462                     }
463 
464                     if (element[0].equals("chart") && element.count() > 5)
465                     {
466                         this.columnnameforhead = element[5].equals("1");
467                         this.columnstart = string.isnullorempty(element[6]) ? -1 : int.parse(element[6]);
468                         this.columnend = string.isnullorempty(element[7]) ? -1 : int.parse(element[7]);
469                     }
470                 }
471             }
472         }
473 
474         public word.range range { get; set; }
475         public int level { get; set; }
476         public int tableindex { get; set; }
477         public string elementname { get; set; }
478 
479         public datarow datarow { get; set; }
480         public dictionary<string, string> groupby { get; set; }
481 
482         public string name { get; set; }
483 
484         public bool columnnameforhead { get; set; }
485         public int columnstart { get; set; }
486         public int columnend { get; set; }
487 
488         public string groupbystring
489         {
490             get
491             {
492                 if (groupby == null || groupby.count == 0)
493                 {
494                     return string.empty;
495                 }
496 
497                 string rtn = string.empty;
498                 foreach (string key in this.groupby.keys)
499                 {
500                     rtn += "and " + key + " = '" + groupby[key] + "' ";
501                 }
502                 return rtn.substring(3);
503             }
504         }
505 
506         public static string getname(string elementname)
507         {
508             string[] element = elementname.split('_');
509 
510 
511             if (element[0].equals("label"))
512             {
513                 return element[2];
514             }
515             else
516             {
517                 return element[4];
518             }
519         }
520     }
521 
522     /// <summary>
523     /// table配置项
524     /// </summary>
525     public class tableconfig
526     {
527         public tableconfig(string tabledescr = "")
528         {
529             this.datarow = 2;
530             this.summaryrow = -1;
531 
532             if (!string.isnullorempty(tabledescr))
533             {
534                 string[] element = tabledescr.split(',');
535                 foreach (string item in element)
536                 {
537                     if (!string.isnullorempty(item))
538                     {
539                         string[] configs = item.split(':');
540                         if (configs.length == 2)
541                         {
542                             switch (configs[0].tolower())
543                             {
544                                 case "data":
545                                 case "d":
546                                     this.datarow = int.parse(configs[1]);
547                                     break;
548                                 case "summary":
549                                 case "s":
550                                     this.summaryrow = int.parse(configs[1]);
551                                     break;
552                                 case "summaryfilter":
553                                 case "sf":
554                                     this.summaryfilter = configs[1];
555                                     break;
556                                 default:
557                                     break;
558                             }
559                         }
560                     }
561                 }
562             }
563 
564         }
565         public int datarow { get; set; }
566         public int summaryrow { get; set; }
567         public string summaryfilter { get; set; }
568     }
569 }

 

本文链接:https://2i3i.com/wordreport.html ,转载请注明来源地址。
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇