linux下word模板操作及PDF处理笔记
最近尝试在linux下处理word文档模板,并转为PDF进行处理,网上搜罗不少资料,记录下来备忘。
业务需求:有一批WORD文档模板,通过系统将其中的某些信息动态替换,包括表格动态生成,然后合并成一份文档,添加页眉页脚及水印,页眉和水印为动态内容,页眉需要添加logo图片,完成后转换为PDF。
方案:最初想当然的考虑jacob,但考虑到系统需要部署在linux环境中,被否定,最终选择使用itext
方案一,WORD文档可以通过XML格式来进行操作,考虑使用模板freemarker进行WORD文档模板操作,包括内容替换和表格动态生成等。但遇到问题:一是合并文件功能实现未找到合适的实现方式,二是生成的文档格式openoffice无法正常转为PDF。
附上为解决合并WORD文档问题,网上搜集到的方案,通过分析RTF文件格式后以流的方式进行合并,将WORD转为RTF进行处理,成功实现了WORD文档合并问题,记录下来。
/** * word合并 * * @param toFilePath * @param sourceFilePath */ public static void mergeWord(String toFilePath, List<String> sourceFilePath,String outFile) { if (StringUtil.isEmpty(toFilePath)) { System.out.println("toFilePath is null"); return; } if (null == sourceFilePath || sourceFilePath.isEmpty()) { System.out.println("sourceFilePath is null"); return; } OutputStream out = null; try { out = new FileOutputStream(outFile); int len = sourceFilePath.size(); int i = 0; for (String path : sourceFilePath) { File f = new File(path); InputStream in = new FileInputStream(f); byte[] b = new byte[1024]; int tmp = 0; int fLen = 0; String str = ""; // 除了第一个文件,其他都处理掉头部 if (i > 0) { // 处理头开始 tmp = in.read(b); if (tmp == -1) { str = new String(b); } else { str = new String(b, 0, tmp); } b = str.replaceFirst("\\{", "").getBytes(); // 处理头结束 out.write(b); } while ((tmp = in.read(b)) != -1) { fLen += tmp; if (fLen + 1024 >= f.length()) { // 最后一批 if (i < len - 1) { // 除了最后一个文件,其他文件处理尾 tmp = in.read(b); if (tmp == -1) { str = new String(b); } else { str = new String(b, 0, tmp); } int index = str.lastIndexOf("}"); b = (str.substring(0, index) + "\\page").getBytes(); // 处理尾结束 out.write(b); break; } } out.write(b); } i++; } out.flush(); } catch (Exception e) { } finally { if (null != out) { try { out.close(); } catch (IOException e) { out = null; } } } }
方案二,通过rtftemplate对rtf模板文件进行内容处理,生成rtf文件,将rtf文件转为pdf,将所有pdf合并并添加页眉页脚水印。
- 通过rtftemplate将rtf模板文件进行内容处理,生成rtf文件
/** *因为需要中需要处理多个模板文件,此处使用批量操作 * 根据模板生成rtf文档 * * @param dataMap */ public static boolean createRTF(Map<String, Object> dataMap, Map<String, String> rtfTmpList) { if (null == rtfTmpList || rtfTmpList.isEmpty()) { logger.error("rtfTmpList is null"); return false; } // rtf生成器 RTFGenerator generator = new RTFGenerator(); generator.setContextMap(dataMap); Iterator<Entry<String, String>> it = rtfTmpList.entrySet().iterator(); while (it.hasNext()) { Entry<String, String> entry = it.next(); String src = entry.getKey(); String target = entry.getValue(); File inputFile = new File(src); if (inputFile.exists()) { // 找不到源文件, 则返回 File outputFile = new File(target); if (!outputFile.getParentFile().exists()) { // 假如目标路径不存在, 则新建该路径 outputFile.getParentFile().mkdirs(); } try { generator.run(src, target); logger.info("createRTF finish.inputFilePath=" + src + ",output=" + target); } catch (Exception e) { logger.error("createRTF failed.", e); } } else { logger.info("createRTF finish.inputFile not exist.src=" + src); } } return true; }
2.使用openoffice+jodconverter将rtf文件转为pdf格式
/** *因为需求中一次需要处理的文件比较多,此处在一次服务启动后批量处理所有文件,再关闭服务 * String inputFilePath, String outputFilePath * * @param rtfList * @return */ public static boolean rtf2pdf(Map<String, String> rtfList) { if (null == rtfList || rtfList.isEmpty()) { return false; } OfficeManager officeManager = null; try { // DefaultOfficeManagerConfiguration config = new // DefaultOfficeManagerConfiguration(); // // // String officeHome = getOfficeHome(); // config.setOfficeHome(officeHome); // // // officeManager = config.buildOfficeManager(); // officeManager.start(); officeManager = getConn(); if (null == officeManager) { logger.error("officeManager 为空"); return false; } OfficeDocumentConverter converter = new OfficeDocumentConverter( officeManager); Iterator<Entry<String, String>> it = rtfList.entrySet().iterator(); while (it.hasNext()) { Entry<String, String> entry = it.next(); String src = entry.getKey(); String target = entry.getValue(); File inputFile = new File(src); if (inputFile.exists()) { // 找不到源文件, 则返回 File outputFile = new File(target); if (!outputFile.getParentFile().exists()) { // 假如目标路径不存在, 则新建该路径 outputFile.getParentFile().mkdirs(); } converter.convert(inputFile, outputFile); logger.info("rtf2pdf finish.inputFilePath=" + src + ",output=" + target); } else { logger.info("rtf2pdf finish.inputFile not exist.src=" + src); } } } finally { if (null != officeManager) { officeManager.stop(); logger.info("停止office转换服务。"); } } return true; } /** *获取连接的方式,如果有已经启动的服务,直接连接已经启动的服务,如果没有启动的服务则启动服务。 */ public static OfficeManager getConn() { OfficeManager officeManager = null; try { // 默认本地2002端口,linux启动默认8100 int port = 2002; try { port = StringUtil.toInteger(getOfficePort()); } catch (Exception e) { port = 2002; } logger.info("准备启动服务...."); try { logger.info("尝试连接已启动的服务..."); ExternalOfficeManagerConfiguration externalProcessOfficeManager = new ExternalOfficeManagerConfiguration(); externalProcessOfficeManager.setConnectOnStart(true); externalProcessOfficeManager.setPortNumber(port); officeManager = externalProcessOfficeManager .buildOfficeManager(); officeManager.start(); logger.info("office转换服务启动成功!"); return officeManager; } catch (Exception ex) { ex.printStackTrace(); logger.info("没有已启动的服务..."); } logger.info("创建并连接新服务..."); DefaultOfficeManagerConfiguration configuration = new DefaultOfficeManagerConfiguration(); configuration.setOfficeHome(getOfficeHome()); configuration.setPortNumbers(port); configuration.setTaskExecutionTimeout(1000 * 60L); configuration.setTaskQueueTimeout(1000 * 60 * 2L); officeManager = configuration.buildOfficeManager(); officeManager.start(); logger.info("office转换服务启动成功!"); return officeManager; } catch (Exception ce) { ce.printStackTrace(); logger.error("office转换服务启动失败!详细信息:" + ce); return null; } }
3.使用itext合并所有pdf文件,对合并后的文件添加页眉页脚及水印处理
/** * 添加footer * * @param fileName * @param savepath * @return int -1:failed */ public static int addFooterAndWater(String fileName, String savepath, String waterMarkName, String pageHeade, String foot) { // 文档总页数 int num = 0; Document document = new Document(); try { PdfReader reader = new PdfReader(fileName); BaseFont base = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.EMBEDDED); num = reader.getNumberOfPages(); PdfCopy copy = new PdfCopy(document, new FileOutputStream(savepath)); document.open(); for (int i = 0; i < num;) { PdfImportedPage page = copy.getImportedPage(reader, ++i); PageStamp stamp = copy.createPageStamp(page); Font f = new Font(base); // 添加页脚,左侧文字,右侧页码 ColumnText.showTextAligned(stamp.getUnderContent(), Element.ALIGN_RIGHT, new Phrase(String.format("Page %d of %d", i, num), f), 550f, 28, 0); ColumnText.showTextAligned(stamp.getUnderContent(), Element.ALIGN_LEFT, new Phrase(foot, f), 50f, 28, 0); // 添加页眉 (文字页眉,居中) ColumnText.showTextAligned(stamp.getUnderContent(), Element.ALIGN_CENTER, new Phrase(pageHeade, f), 150f, 800, 0); // 页眉添加logo (图片页眉,居右) Image img = Image.getInstance("template/logo.png");// 选择图片 img.setAlignment(1); img.scaleAbsolute(436 / 5, 96 / 5);// 控制图片大小 img.setAbsolutePosition(450f, 800);// 控制图片位置 stamp.getUnderContent().addImage(img); // 添加水印 PdfContentByte under = stamp.getUnderContent(); under.beginText(); under.setColorFill(Color.LIGHT_GRAY); // 字符越长,字体越小,设置字体 int fontSize = getFontSize(waterMarkName.length()); under.setFontAndSize(base, fontSize); // 设置水印文字字体倾斜 开始 float pageWidth = reader.getPageSize(i).getWidth(); float pageHeight = reader.getPageSize(i).getHeight(); under.showTextAligned(Element.ALIGN_CENTER, waterMarkName, pageWidth / 2, pageHeight / 2, 60);// 水印文字成60度角倾斜,且页面居中展示 // 字体设置结束 under.endText(); stamp.alterContents(); copy.addPage(page); } } catch (Exception e) { logger.error("addFooter failed.msg=" + e.toString()); return -1; } finally { if (null != document) { document.close(); } } logger.info("pdf totalpages:" + num); return num; }
关于遇到的几个问题及解决:
1、openoffice启动时,默认端口8100,windows本地默认端口为2002
问题:在启动openoffice服务后,找不到8100的监听端口,或者启动服务失败。
尝试解决:安装图形化界面,在图形化界面下启动openoffice服务看是否可以解决,我们是通过此方式解决。
2.如果使用centos 6,由于centos 6系统自带的没有openoffice,尝试使用liberoffice,代码实现方面和openoffice一样使用,不需要任何修改,修改liberoffice对应的根路径配置即可。
3.将rtf转pdf时,原本的内容有30页,但转pdf后内容变大(因为页码发生变化,要求页码与模板一致),观察发现字体及样式发生变化导致。
解决:因为linux下没有对应的中文字体,需要添加相应的中文字体,记录解决方案;
将本地系统下的字体文件拷贝到linux系统上,本地在c:\windows\fonts,将需要的字体文件拷贝到linux上,在/usr/share/fonts目录下新建一个自定义目录即可,我比较偷懒,直接全部拷贝过去了。
执行以下命令刷新,重启openoffice服务,问题解决,本地转pdf后是多少页,服务器上一样的效果。
mkfontscale
mkfontdir//这两条命令是生成字体的索引信息
fc-cache //更新字体缓存
4.linux下openoffice默认页眉是奇偶页不一样,如果需要统一页眉,需要通过图形界面在linux上将openoffice的页眉设置为奇偶页一样,可以新建一个文档,在”格式“-"页面“-”页眉“属性中选中"奇偶页相同",再进行操作的时候就默认所有页面页眉一致了。
5.制作rtf模板的问题
a、在模板中添加循环时,始终不生效。
原因:如果需要循环时(startloop+循环对象或循环对象的属性+endloop),附件中的dot模板不适合在word2010,2007生成rtf模板,需要在word2003中生成rtf模板,在03下就可以正常的生成循环。
b、模板中中文乱码问题,
原因1:生成的模板中,对应的变量使用的字体默认为Times New Roman,替换后的中文也会使用该字体,该字体是不识别中文的,需要将字体修改为宋体后,就可以正常显示中文,删除变量两侧的符号。
原因2:通过rtf模板生成rtf文件时,需要将中文字体转为rtf的格式,通过将rtf使用文本编辑器打开看,发现中文都是转码过后的,将填充的中文信息转码后就可以正常显示中文。
c、已经生成的rtf模板,有时候需要进行修改,但经常发现,原本好好的rtf模板,比如再添加几个变量后,就发现总显示不对,要么就是无法显示,要么就是会多个<>。
解决:没有找到具体原因,1、如果已经有正常的变量,直接拷贝过来可以解决.2、可以通过dot新建一个空白的文档,在空白文档中添加变量,显示会多个<>,再拷贝一个正常的变量粘贴到该文档中后,都正常了,此时再按方法1解决。
主要jar包:
itext-rtf-2.1.7.jar
jodconverter-core-3.0-4.jar
iText-2.1.5.jar
rtftemplate-1.0.1-b13.jar
已有 0 人发表留言,猛击->> 这里<<-参与讨论
ITeye推荐