<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
最近工作上需要自己完成word檔案變數替換的問題
把裡面的變數給替換成資料庫裡的值,但是由於在word檔案渲染成xml的時候,會通過某些原因把欄位放在不同層次的xml標籤
上面是docx4j檔案說的原因,大概是字型格式不同(我的問題是用了粗體 ${ 和 正常中文是不同格式的),拼寫語法問題,編輯順序。
在StackOverflow 找了很久解決方案,Variableprepare.prepare方法確實測試後能解決部分替換問題,但還是不能滿足我的需求。
閱讀原始碼後重新清掃了一下字串。
public static void main(String[] args) throws Exception { File file = new File("C:\Users\scoli\Desktop\t.docx"); WordprocessingMLPackage doc = WordprocessingMLPackage.load(new FileInputStream(file)); Docx4jUtils.cleanDocumentPart(doc.getMainDocumentPart()); Map map = new HashMap(); map.put("訂單編號", "1"); OutputStream outputStream = new FileOutputStream(new File("C:\Users\scoli\Desktop\test1.docx")); MainDocumentPart mainDocumentPart = doc.getMainDocumentPart(); if (!map.isEmpty()) { // 替換文字內容 mainDocumentPart.variableReplace(map); } // 輸出word檔案 doc.save(outputStream); outputStream.flush(); }
<!--docx4j支援 start--> <dependency> <groupId>org.docx4j</groupId> <artifactId>docx4j</artifactId> <version>3.3.6</version> <exclusions> <exclusion> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.docx4j</groupId> <artifactId>docx4j-export-fo</artifactId> <version>3.3.6</version> </dependency> <!--docx4j支援 end-->
package zwy.saas.common.util; import org.docx4j.XmlUtils; import org.docx4j.jaxb.Context; import org.docx4j.openpackaging.packages.WordprocessingMLPackage; import org.docx4j.openpackaging.parts.WordprocessingML.MainDocumentPart; import org.docx4j.wml.Document; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.servlet.http.HttpServletResponse; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import java.io.InputStream; import java.io.OutputStream; import java.net.URLEncoder; import java.util.Map; import java.util.regex.Pattern; /** * 關於檔案操作的工具類 * * @author kaizen * @date 2018-10-23 17:21:36 */ public final class Docx4jUtils { private static final Logger logger = LoggerFactory.getLogger(Docx4jUtils.class); /** * 替換變數並下載word檔案 * * @param inputStream * @param map * @param response * @param fileName */ public static void downloadDocUseDoc4j(InputStream inputStream, Map<String, String> map, HttpServletResponse response, String fileName) { try { // 設定響應頭 fileName = URLEncoder.encode(fileName, "UTF-8"); response.setContentType("application/octet-stream;charset=UTF-8"); response.setCharacterEncoding("utf-8"); response.setHeader("Content-Disposition", "attachment; filename=" + fileName + ".docx"); response.setHeader("Access-Control-Expose-Headers", "Content-Disposition"); OutputStream outs = response.getOutputStream(); Docx4jUtils.replaceDocUseDoc4j(inputStream,map,outs); } catch (Exception e) { logger.error(e.getMessage(), e); } } /** * 替換變數並輸出word檔案 * @param inputStream * @param map * @param outputStream */ public static void replaceDocUseDoc4j(InputStream inputStream, Map<String, String> map, OutputStream outputStream) { try { WordprocessingMLPackage doc = WordprocessingMLPackage.load(inputStream); MainDocumentPart mainDocumentPart = doc.getMainDocumentPart(); if (null != map && !map.isEmpty()) { // 將${}裡的內容結構層次替換為一層 Docx4jUtils .cleanDocumentPart(mainDocumentPart); // 替換文字內容 mainDocumentPart.variableReplace(map); } // 輸出word檔案 doc.save(outputStream); outputStream.flush(); } catch (Exception e) { logger.error(e.getMessage(), e); } } /** * cleanDocumentPart * * @param documentPart */ 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(DocxVariableClearUtils.doCleanDocumentPart(wmlTemplate, Context.jc)); documentPart.setContents(document); return true; } /** * 清掃 docx4j 模板變數字元,通常以${variable}形式 * <p> * XXX: 主要在上傳模板時處理一下, 後續 * * @author liliang * @since 2018-11-07 */ private static class DocxVariableClearUtils { /** * 去任意XML標籤 */ private static final Pattern XML_PATTERN = Pattern.compile("<[^>]*>"); private DocxVariableClearUtils() { } /** * 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 */ private 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: // TODO 不管其何狀態直接修改指標,這也意味著變數名稱裡面不能有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); } // FIXME 要求變數連在一起 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); } } }
以上為個人經驗,希望能給大家一個參考,也希望大家多多支援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