${image_N}。HTML輸入
→ Jsoup.parse() 標準化
→ 遍歷 DOM 替換
為 ${image_N},收集 (佔位符 -> 本地圖片路徑) Map
→ 寫入 template.doc
→ Jacob 打開 template.doc,複製內容
→ 新建文檔,粘貼內容
→ 遍歷 Map,replaceText2Image(佔位符, 圖片路徑)
→ 設置標題、字體、格式
→ 另存為最終 .doc
Document doc = Jsoup.parse(html);
Map imgMap = new HashMap<>();
int imgIndex = 1;
for (Element el : doc.body().select("img")) {
String placeholder = "${image_" + imgIndex++ + "}";
String src = el.attr("src");
// 根據業務規則拼接本地絕對路徑
String localPath = "D:/WebPic/" + src.substring(0,8) + "/" + src.substring(0,10) + "/" + src;
el.after("" + placeholder + "
"); // 插入佔位符段落
el.remove(); // 移除原 img
imgMap.put(placeholder, localPath);
}
// 寫入模板文件
Files.write(Paths.get("template.doc"), doc.html().getBytes());
public class WordConverter {
private ActiveXComponent word;
private Dispatch document, selection;
public WordConverter(boolean visible) {
word = new ActiveXComponent("Word.Application");
word.setProperty("Visible", new Variant(visible));
Dispatch documents = word.getProperty("Documents").toDispatch();
document = Dispatch.call(documents, "Add").toDispatch(); // 新建文檔
selection = word.getProperty("Selection").toDispatch();
}
public void pasteFromTemplate(String templatePath) {
Dispatch docTemplate = Dispatch.call(word.getProperty("Documents").toDispatch(), "Open", templatePath).toDispatch();
Dispatch.call(docTemplate, "Select"); // 全選
Dispatch.call(selection, "Copy");
Dispatch.call(docTemplate, "Close", new Variant(false));
Dispatch.call(selection, "Paste");
}
public void replaceText2Image(String placeholder, String imgPath) {
moveStart();
while (find(placeholder)) {
Dispatch.call(Dispatch.get(selection, "InLineShapes").toDispatch(), "AddPicture", imgPath);
moveRight(1); // 移出圖片位置
}
}
public void setTitle(String title) {
setAlignment(1); // 居中
setFont(true, false, false, "0,0,0", "20", "宋體");
Dispatch.put(selection, "Text", title);
enterDown(1);
}
// 輔助方法:find, moveStart, moveRight, enterDown, setFont, setAlignment...
// 保存、關閉、退出
public void saveAs(String path) { Dispatch.call(document, "SaveAs", path); }
public void close() { Dispatch.call(document, "Close", new Variant(false)); }
public void quit() { word.invoke("Quit", new Variant[0]); ComThread.Release(); }
}
public static void main(String[] args) throws Exception {
String html = "...包含
的HTML...";
String title = "報告標題";
// 1. Jsoup 處理
Map imgMap = processHtml(html); // 返回 imgMap 並產生 template.doc
// 2. Jacob 轉換
WordConverter converter = new WordConverter(false); // 後台運行
converter.pasteFromTemplate("template.doc");
for (Map.Entry e : imgMap.entrySet()) {
converter.replaceText2Image(e.getKey(), e.getValue());
}
converter.setTitle(title);
converter.saveAs("report_" + System.currentTimeMillis() + ".doc");
converter.close();
converter.quit();
}
| 問題 | 原因 | 解決 |
|---|---|---|
| Word 彈出「大量剪貼簿數據」對話框 | 關閉 Word 時剪貼板有大量數據 | 退出前創建空文檔、複製少量文本、關閉:createNewDocument(); insertText("x"); copy(); close(); quit(); |
| 圖片顯示為紅叉 | 模板 doc 使用相對路徑/臨時路徑 | 佔位符替換法:模板只存佔位符,Jacob 運行時插入絕對路徑圖片 |
| Jacob 無法啟動 Word | 未安裝 Office / 位數不匹配 (32/64bit) / 無 COM 權限 | 安裝 Office;Jacob dll 需與 JVM 位數一致;DCOM 配置權限 |
| 中文亂碼 | 編碼不一致 | HTML 指定 UTF-8,Jacob 操作無編碼問題 |
| 方案 | 優點 | 缺點 | 適用場景 |
|---|---|---|---|
| Jsoup + Jacob | 完美還原 Word 排版、支援複雜表格/圖片 | 依賴 Windows + Office、部署重、併發差 | 服務端生成報表、需精確排版 |
| Apache POI (XWPF) | 純 Java、跨平台、無 Office 依賴 | 複雜排版/圖片支持弱、API 繁瑣 | 跨平台、簡單文檔生成 |
| FreeMarker + docx4j | 模板引擎分離、純 Java | 學習曲線陡、圖片處理仍受限 | 基於模板的文檔生成 |
Jsoup + Jacob 是「HTML 轉 Word 高保真」的務實選擇:Jsoup 負責 HTML 清洗與圖片佔位,Jacob 負責利用 Word 強大的渲染引擎生成最終文檔。核心關鍵在於佔位符替換圖片解決路徑依賴,以及剪貼板清理避免退出卡頓。生產環境建議封裝為獨立微服務,限制併發,監控 Word 進程殘留。