<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
專案中有個需求大體意思是,上傳一個word模板,根據word模板合成word檔案,再將word檔案轉為pdf。
Spire.Doc for Java這個是商用收費的,不過API檔案豐富且整合簡單,免費版僅支援3頁轉換。類似的還有ITEXT,這個商用也是受限制的。
開源可商用,僅支援docx格式的word。
開源可商用,呼叫本地office服務,進行pdf轉換,類似的還有jodconverter+openOffice。
至於其他的由於不支援跨平臺不做考慮。
首先嚐試了docx4j,因為docx4j本身支援模板替換的操作,可一次性做替換及檔案型別轉換,而且僅支援docx型別,對於本次需求問題不大。
1.依賴僅需要一個即可
<dependency> <groupId>org.docx4j</groupId> <artifactId>docx4j-export-fo</artifactId> <version>6.1.0</version> </dependency>
2.主要程式碼
@Slf4j public class PdfUtil { public static <T> void exportByLocalPath(HttpServletResponse response, String fileName, String path, Map<String,String> params){ try (InputStream in = PdfUtil.class.getClassLoader().getResourceAsStream(path)) { convertDocxToPdf(in, response,fileName,params); } catch (Exception e) { log.error("docx檔案轉換為PDF失敗", e.getMessage()); } } /** * docx檔案轉換為PDF * @param in * @param response * @return */ public static void convertDocxToPdf(InputStream in, HttpServletResponse response, String fileName, Map<String,String> params) throws Exception { response.setContentType("application/pdf"); String fullFileName = new String(fileName.getBytes(), StandardCharsets.ISO_8859_1); response.setHeader("Content-disposition", "attachment;filename=" + fullFileName + ".pdf"); WordprocessingMLPackage wmlPackage = WordprocessingMLPackage.load(in); if (params!=null&&!params.isEmpty()) { MainDocumentPart documentPart = wmlPackage.getMainDocumentPart(); cleanDocumentPart(documentPart); documentPart.variableReplace(params); } setFontMapper(wmlPackage); Docx4J.toPDF(wmlPackage,response.getOutputStream()); } /** * 清除檔案空白預留位置 * @param documentPart * @return {@link boolean} */ public static boolean cleanDocumentPart(MainDocumentPart documentPart) throws Exception { if (documentPart == null) { return false; } Document document = documentPart.getContents(); String wmlTemplate = XmlUtils.marshaltoString(document, true, false, Context.jc); document = (Document) XmlUtils.unwrap(DocxVariableClearUtil.doCleanDocumentPart(wmlTemplate, Context.jc)); documentPart.setContents(document); return true; } /** * 設定字型樣式 * @param mlPackage */ private static void setFontMapper(WordprocessingMLPackage mlPackage) throws Exception { Mapper fontMapper = new IdentityPlusMapper(); fontMapper.put("隸書", PhysicalFonts.get("LiSu")); fontMapper.put("宋體", PhysicalFonts.get("SimSun")); fontMapper.put("微軟雅黑", PhysicalFonts.get("Microsoft Yahei")); fontMapper.put("黑體", PhysicalFonts.get("SimHei")); fontMapper.put("楷體", PhysicalFonts.get("KaiTi")); fontMapper.put("新宋體", PhysicalFonts.get("NSimSun")); fontMapper.put("華文行楷", PhysicalFonts.get("STXingkai")); fontMapper.put("華文仿宋", PhysicalFonts.get("STFangsong")); fontMapper.put("宋體擴充套件", PhysicalFonts.get("simsun-extB")); fontMapper.put("仿宋", PhysicalFonts.get("FangSong")); fontMapper.put("仿宋_GB2312", PhysicalFonts.get("FangSong_GB2312")); fontMapper.put("幼圓", PhysicalFonts.get("YouYuan")); fontMapper.put("華文宋體", PhysicalFonts.get("STSong")); fontMapper.put("華文中宋", PhysicalFonts.get("STZhongsong")); mlPackage.setFontMapper(fontMapper); } }
清除工具類,用於處理預留位置替換不生效的問題,這裡參考文章
public class DocxVariableClearUtil { /** * 去任意XML標籤 */ private static final Pattern XML_PATTERN = Pattern.compile("<[^>]*>"); private DocxVariableClearUtil() { } /** * start符號 */ private static final char PREFIX = '$'; /** * 中包含 */ private static final char LEFT_BRACE = '{'; /** * 結尾 */ private static final char RIGHT_BRACE = '}'; /** * 未開始 */ private static final int NONE_START = -1; /** * 未開始 */ private static final int NONE_START_INDEX = -1; /** * 開始 */ private static final int PREFIX_STATUS = 1; /** * 左括號 */ private static final int LEFT_BRACE_STATUS = 2; /** * 右括號 */ private static final int RIGHT_BRACE_STATUS = 3; /** * doCleanDocumentPart * * @param wmlTemplate * @param jc * @return * @throws JAXBException */ public static Object doCleanDocumentPart(String wmlTemplate, JAXBContext jc) throws JAXBException { // 進入變數塊位置 int curStatus = NONE_START; // 開始位置 int keyStartIndex = NONE_START_INDEX; // 當前位置 int curIndex = 0; char[] textCharacters = wmlTemplate.toCharArray(); StringBuilder documentBuilder = new StringBuilder(textCharacters.length); documentBuilder.append(textCharacters); // 新檔案 StringBuilder newDocumentBuilder = new StringBuilder(textCharacters.length); // 最後一次寫位置 int lastWriteIndex = 0; for (char c : textCharacters) { switch (c) { case PREFIX: // 不管其何狀態直接修改指標,這也意味著變數名稱裡面不能有PREFIX keyStartIndex = curIndex; curStatus = PREFIX_STATUS; break; case LEFT_BRACE: if (curStatus == PREFIX_STATUS) { curStatus = LEFT_BRACE_STATUS; } break; case RIGHT_BRACE: if (curStatus == LEFT_BRACE_STATUS) { // 接上之前的字元 newDocumentBuilder.append(documentBuilder.substring(lastWriteIndex, keyStartIndex)); // 結束位置 int keyEndIndex = curIndex + 1; // 替換 String rawKey = documentBuilder.substring(keyStartIndex, keyEndIndex); // 幹掉多餘標籤 String mappingKey = XML_PATTERN.matcher(rawKey).replaceAll(""); if (!mappingKey.equals(rawKey)) { char[] rawKeyChars = rawKey.toCharArray(); // 保留原格式 StringBuilder rawStringBuilder = new StringBuilder(rawKey.length()); // 去掉變數參照字元 for (char rawChar : rawKeyChars) { if (rawChar == PREFIX || rawChar == LEFT_BRACE || rawChar == RIGHT_BRACE) { continue; } rawStringBuilder.append(rawChar); } // 要求變數連在一起 String variable = mappingKey.substring(2, mappingKey.length() - 1); int variableStart = rawStringBuilder.indexOf(variable); if (variableStart > 0) { rawStringBuilder = rawStringBuilder.replace(variableStart, variableStart + variable.length(), mappingKey); } newDocumentBuilder.append(rawStringBuilder.toString()); } else { newDocumentBuilder.append(mappingKey); } lastWriteIndex = keyEndIndex; curStatus = NONE_START; keyStartIndex = NONE_START_INDEX; } default: break; } curIndex++; } // 餘部 if (lastWriteIndex < documentBuilder.length()) { newDocumentBuilder.append(documentBuilder.substring(lastWriteIndex)); } return XmlUtils.unmarshalString(newDocumentBuilder.toString(), jc); } }
poi-tl這個是專門用來進行word模板合成的開源庫,檔案很詳細。
LibreOffice 下載最新的穩定版本即可。
1.maven依賴
<!-- word合成 --> <!-- 這裡注意版本,1.5版本依賴的poi 3.x的版本 --> <dependency> <groupId>com.deepoove</groupId> <artifactId>poi-tl</artifactId> <version>1.5.1</version> </dependency> <!-- jodconverter word轉pdf --> <!-- jodconverter-core這個依賴,理論上不用加的,jodconverter-local已經依賴了,但測試的時候不新增依賴找不到 --> <dependency> <groupId>org.jodconverter</groupId> <artifactId>jodconverter-core</artifactId> <version>4.2.0</version> </dependency> <dependency> <groupId>org.jodconverter</groupId> <artifactId>jodconverter-local</artifactId> <version>4.2.0</version> </dependency> <dependency> <groupId>org.jodconverter</groupId> <artifactId>jodconverter-spring-boot-starter</artifactId> <version>4.2.0</version> </dependency> <!-- 工具類,非必須 --> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.4.3</version> </dependency>
2.主要程式碼
JodConverterConfig設定類
@Configuration public class JodConverterConfig { @Autowired private OfficeManager officeManager; @Bean public DocumentConverter documentConverter() { return LocalConverter.builder() .officeManager(officeManager) .build(); } }
yml組態檔
jodconverter: local: enabled: true office-home: "C:\Program Files\LibreOffice"
PdfService合成匯出程式碼
@Slf4j @Component public class PdfService { @Autowired private DocumentConverter documentConverter; public void docxToPDF(InputStream inputStream,HttpServletResponse response,String fileName) { response.setContentType("application/pdf"); try { String fullFileName = new String(fileName.getBytes(), StandardCharsets.ISO_8859_1); response.setHeader("Content-disposition","attachment;filename=\"+fullFileName+".pdf\"); documentConverter .convert(inputStream) .as(DefaultDocumentFormatRegistry.DOCX) .to(response.getOutputStream()) .as(DefaultDocumentFormatRegistry.PDF) .execute(); } catch (OfficeException |IOException e) { log.error("word轉pdf失敗:{}",e.getMessage()); } } public void exportByLocalPath(HttpServletResponse response, String fileName, String path, Object params) throws Exception { BufferedOutputStream outputStream = null; BufferedInputStream wordInputStream = null; try (InputStream in = PdfService.class.getClassLoader().getResourceAsStream(path)) { // 生成臨時檔案 String outPutWordPath = System.getProperty("java.io.tmpdir").replaceAll(File.separator + "$", "") + fileName+".docx"; File tempFile = FileUtil.touch(outPutWordPath); outputStream = FileUtil.getOutputStream(tempFile); // word模板合成寫到臨時檔案 WordUtil.replaceWord(outputStream, in, params); // word 轉pdf wordInputStream = FileUtil.getInputStream(tempFile); docxToPDF(wordInputStream, response,fileName); // 移除臨時檔案 FileUtil.del(tempFile); } catch (Exception e) { log.error("docx檔案轉換為PDF失敗", e.getMessage()); } finally { IoUtil.close(outputStream); IoUtil.close(wordInputStream); } }
以上為個人經驗,希望能給大家一個參考,也希望大家多多支援it145.com。
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45