在很多业务场景中需要在 word 文档中嵌入报表。比如下图这个报告:
这是一个某大学年度毕业生就业报告,其中表格和统计图的数据来自数据库,如果通过报表工具,制作这样的表格和统计图是轻而易举的事情,但如果要把这些报表和统计图做到 word 报告里就麻烦很多。以往有两个办法:一个是每次做好报表和统计图之后,导出为 word,再手工复制粘贴到 word 报告中;第二个是把整个报告都做成一个报表模板,然后再一起导出为 word。 方法一,纯手工操作效率低;方法二,报表工具排版能力有限,生成的 word 版面效果不够完美。
那么,还有什么好办法呢?
通常这类报告都有规定的模板样式,只是要定期替换里面的个别信息,比如上图所示的文档编号,报告时间,标题里的年度,图片,每个章节下的报表和统计图,这些信息是动态变化的,而其他文字描述部分以及整体样式都是固定不变的。所以,如果能把报表嵌入 word 文档做成流水线式的自动化过程,那就是一件两全其美,事半功倍的事情。
润乾报表就提供了把报表嵌入 word 的功能,实现步骤如下:
1、制作 word 模板,将需要插入内容的位置设置好书签
比如我们开篇看到的大学毕业生就业报告,我们可以先做成如上图所示的 word 模板,图示绿色线框位置就是需要定期更新的部分,预先在这些位置插入书签(比如书签名为:编号,时间,logo,年度,报表,统计图),以此标记要插入到 word 的内容对应插入到什么位置。
2、制作报表,这一步就不详述了。
3、调用润乾报表的 raqsoft.report.view.oxml.word.DocxChanger 里的方法,将图片,文本,报表等内容插入到指定书签位置,生成新的 word 报告。
eg:
//设置æ¥è¡¨æææä»¶ File flic = new File("c:/tmp/report5.lic"); FileInputStream lis = new FileInputStream(flic); Sequence.readLicense( Sequence.P_RPT, lis); File f = new File("E:/test.docx"); //æ¨¡æ¿æä»¶ File of = new File("D:/out.docx"); //è¾åºæä»¶ ⦠⦠FileOutputStream fos = new FileOutputStream(of); DocxChanger dc = new DocxChanger(f, fos); //å®ä¾åDocxChanger //å¨ä¹¦ç¾âç¼å·âï¼âæ¶é´âï¼â年度â夿奿å dc.insertText("ç¼å·", "12345678"); dc.insertText("æ¶é´", "20170730"); dc.insertText("年度", "2017"); //å¨ä¹¦ç¾âlogoâ夿å¥å¾çæä»¶ File f1 = new File("d:/logo.png"); dc.insertImage("logo", f1); //å¨ä¹¦ç¾âæ¥è¡¨âï¼âç»è®¡å¾â夿奿¥è¡¨åç»è®¡å¾ File f2 = new File("d:/æ¯ä¸å»å.rpx"); FileInputStream fis = new FileInputStream(f2); IReport report = ReportUtils.read(fis); fis.close(); Context context = new Context(); ⦠⦠Engine engine = new Engine((ReportDefine) report, context); report = engine.calc(); dc.insertReport("æ¥è¡¨", report); File f3 = new File("d:/çå¦.rpx"); FileInputStream fis2 = new FileInputStream(f3); IReport report2 = ReportUtils.read(fis2); fis2.close(); Context context2 = new Context(); ⦠⦠Engine engine2 = new Engine((ReportDefine) report2, context2); report2 = engine2.calc(); dc.insertReport("ç»è®¡å¾", report2); //æ§è¡ææä¿®æ¹å¨ä½ï¼ç¶åå³éè¾åºæä»¶æµ dc.execute(); fos.close();
//设置报表授权文件 File flic = new File("c:/tmp/report5.lic"); FileInputStream lis = new FileInputStream(flic); Sequence.readLicense( Sequence.P_RPT, lis); File f = new File("E:/test.docx"); //模板文件 File of = new File("D:/out.docx"); //输出文件 … … FileOutputStream fos = new FileOutputStream(of); DocxChanger dc = new DocxChanger(f, fos); //实例化DocxChanger //在书签“编号”,“时间”,“年度”处插入文字 dc.insertText("编号", "12345678"); dc.insertText("时间", "20170730"); dc.insertText("年度", "2017"); //在书签“logo”处插入图片文件 File f1 = new File("d:/logo.png"); dc.insertImage("logo", f1); //在书签“报表”,“统计图”处插入报表和统计图 File f2 = new File("d:/毕业去向.rpx"); FileInputStream fis = new FileInputStream(f2); IReport report = ReportUtils.read(fis); fis.close(); Context context = new Context(); … … Engine engine = new Engine((ReportDefine) report, context); report = engine.calc(); dc.insertReport("报表", report); File f3 = new File("d:/留学.rpx"); FileInputStream fis2 = new FileInputStream(f3); IReport report2 = ReportUtils.read(fis2); fis2.close(); Context context2 = new Context(); … … Engine engine2 = new Engine((ReportDefine) report2, context2); report2 = engine2.calc(); dc.insertReport("统计图", report2); //执行所有修改动作,然后关闭输出文件流 dc.execute(); fos.close();
至此,word 报告就自动生成了,以后每次只要执行一遍这段程序就行了,是不是方便了不少?
不过,这个办法还有个缺点,当插入内容变化时,我们就需要修改 java 代码,而改了代码之后又得重编译部署,难以做到热切换。这个办法还是不够方便。
为此,润乾报表还提供了外部配置的方法来实现 word 报表,可以预先编辑一个 xml 文件,在里面写个需要替代的书签等内容,然后程序会读取这个配置文件生成相应的 word 文档。
这样,当插入内容变化的时候,只要修改 xml 配置信息即可,而不用修改代码再编译了。我们一起来看一下:
1、编辑配置文件 xml
该文件中可配置多个书签和插入对象,当对象来源于内存时,可配置成 map,通过 key 从内存中取值,key 值可以是 IReport、byte[]、Image、String,值的类型程序会自动判断。
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <!-- file表示模æ¿docxæä»¶åï¼å¯éç½®ç»å¯¹è·¯å¾æç¸å¯¹è·¯å¾(web端éç½®æ¶ç¸å¯¹äºraqsoftConfig.xmlä¸çreportFileHome) --> <docx file="D:/test.doc"> <!-- name表示docx书ç¾ï¼ä¸ºç©ºåä¸ä¼æå¥ã file表示å¾çæä»¶åï¼å¯éç½®ç»å¯¹è·¯å¾æç¸å¯¹è·¯å¾(web端éç½®æ¶ç¸å¯¹äºraqsoftConfig.xmlä¸çreportFileHome) --> <bookmark name="logo" type="image" file="D:/logo.png"></bookmark> <!-- name表示docx书ç¾ï¼ textè¦æå¥çææ¬æå --> <bookmark name="ç¼å·" type="text" text="12345678"></bookmark> <bookmark name="æ¶é´" type="text" text="20170730"></bookmark> <bookmark name="年度" type="text" text="2017"></bookmark> <!-- key表示ç»å®mapä¸çkeyï¼æªå¡«æ¶è·nameç¸åï¼å¯¹åºçvalueåªè½æ¯IReportãbyte\[\]ãImageãStringï¼ --> <bookmark name="ç»è®¡å¾" type="map" key="f"></bookmark> <!-- reportFile表示æ¥è¡¨æ¨¡æ¿æä»¶åï¼å¯éç½®ç»å¯¹è·¯å¾æç¸å¯¹è·¯å¾(web端éç½®æ¶ç¸å¯¹äºraqsoftConfig.xmlä¸çreportFileHome) --> <bookmark name="æ¥è¡¨" type="report" reportFile="D:/æ¯ä¸å»å.rpx"> <!-- value表示æ¥è¡¨åæ°å¼(串) --> <reportParam name="arg1" type="value" value="设å®åæ°1"></reportParam> <!-- type为mapæ¶ä¼ä»åå䏿 ¹æ®name读åkeyï¼keyæªå¡«åæ¶è·nameç¸å,key=ââæ¶ä¼åmapä¸key为空çå¼ --> <reportParam name="arg2" type="map"></reportParam> </bookmark> </docx>
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <!-- file表示模板docx文件名,可配置绝对路径或相对路径(web端配置时相对于raqsoftConfig.xml中的reportFileHome) --> <docx file="D:/test.doc"> <!-- name表示docx书签,为空则不会插入。 file表示图片文件名,可配置绝对路径或相对路径(web端配置时相对于raqsoftConfig.xml中的reportFileHome) --> <bookmark name="logo" type="image" file="D:/logo.png"/> <!-- name表示docx书签, text要插入的文本文字 --> <bookmark name="编号" type="text" text="12345678"/> <bookmark name="时间" type="text" text="20170730"/> <bookmark name="年度" type="text" text="2017"/> <!-- key表示给定map中的key(未填时跟name相同,对应的value只能是IReport、byte\[\]、Image、String) --> <bookmark name="统计图" type="map" key="f"/> <!-- reportFile表示报表模板文件名,可配置绝对路径或相对路径(web端配置时相对于raqsoftConfig.xml中的reportFileHome) --> <bookmark name="报表" type="report" reportFile="D:/毕业去向.rpx"> <!-- value表示报表参数值(串) --> <reportParam name="arg1" type="value" value="设定参数1"/> <!-- type为map时会从内存中根据name读取key,key未填写时跟name相同,key=””时会取map中key为空的值 --> <reportParam name="arg2" type="map"/> </bookmark> </docx>
注:在 web 应用中,如果 web.xml 里配置了 reportServlet,那么程序会自动加载 raqsoftconfig.xml,读取该文件里配置的 reportFileHome,数据源信息,授权文件等信息。
2、根据配置信息生成 Word 文档
try{ File of = new File("D:/out.docx");//è¾åºæä»¶ ⦠⦠FileOutputStream fos = new FileOutputStream(of); //å è½½xml,batch.xmlå容å¦ä¸ä¸å°èæç¤º String xmlConfig = DocxChanger.xmlFile2String("D:/batch.xml"); //彿å¥ç对象æ¥èªååï¼æ¯å¦IReport对象 File f4 = new File("d:/çå¦.rpx"); FileInputStream fis = new FileInputStream(f4); IReport report = ReportUtils.read(fis); fis.close(); Context context = new Context(); Engine engine = new Engine((ReportDefine) report, context); report = engine.calc(); //å½å¯¹è±¡æ¥æºäºååæ¶ï¼å¯éç½®æ map HashMap map =new HashMap(); map.put("f", report);//设置xmlä¸key为fçå¼ map.put("arg2", "2014-12-15 12:00:23");//彿å¥å¯¹è±¡æ¥èªååï¼æ¯å¦Stringï¼è®¾ç½®xmlä¸key为arg2çå¼ DocxChanger.insert(map, xmlConfig, fos); fos.close(); }catch(Throwable x) { x.printStackTrace(); }
try{ File of = new File("D:/out.docx");//输出文件 … … FileOutputStream fos = new FileOutputStream(of); //加载xml,batch.xml内容如上一小节所示 String xmlConfig = DocxChanger.xmlFile2String("D:/batch.xml"); //当插入的对象来自内存,比如IReport对象 File f4 = new File("d:/留学.rpx"); FileInputStream fis = new FileInputStream(f4); IReport report = ReportUtils.read(fis); fis.close(); Context context = new Context(); Engine engine = new Engine((ReportDefine) report, context); report = engine.calc(); //当对象来源于内存时,可配置成 map HashMap map =new HashMap(); map.put("f", report);//设置xml中key为f的值 map.put("arg2", "2014-12-15 12:00:23");//当插入对象来自内存,比如String,设置xml中key为arg2的值 DocxChanger.insert(map, xmlConfig, fos); fos.close(); }catch(Throwable x) { x.printStackTrace(); }
说了这么多,大家肯定会觉得理想很丰满,现实很骨感,这个功能是很完美,确实可以帮我解决这些个棘手的问题,但是都知道报表工具价格昂贵,再加上这样小奢的功能,岂不是贵上加贵,为此特意买一套昂贵的报表工具,似乎就不划算了,而开源报表里面又没有这个功能。但是你不知道的是,现在报表工具已经低端化了,润乾率先开始了 5000 元 / 套的低价报表了,恰巧这个功能里面有,恰巧你还看到了。