GroupDocs Viewer Java 教程:將 Word 轉換為 HTML 並渲染帶有批註的文件

介紹

如果您需要 將 Word 轉換為 HTML 並保留每位審閱者的備註、評論或標註,您已來到正確的地方。許多 Java 開發人員在文件轉換時會失去原始檔案中寶貴的回饋。本教學將指導您使用 GroupDocs Viewer for Java 將 Word 轉換為 HTML,並渲染包括 Word、Excel、PowerPoint、PDF 等多種文件類型,且不遺失任何評論資料。

您將了解為何 GroupDocs Viewer 是可投入生產環境的選擇、如何設定環境、完整程式碼示例,以及在處理大型檔案時保持效能的實用技巧。

將帶有批註的文件渲染於 GroupDocs.Viewer for Java

將帶有批註的文件渲染於 GroupDocs.Viewer for Java

本教學您將掌握的內容:

  • 完整的 GroupDocs Viewer 設定與配置
  • 步驟式 將 Word 轉換為 HTML 並保留評論
  • 常見問題的解決方案與避免陷阱的技巧
  • 真實案例的實作模式與最佳實踐
  • 生產環境的效能優化技術

快速回答

  • GroupDocs Viewer 能將 Word 轉換為 HTML 嗎? 可以——只需一行程式碼即可啟用 HTML 渲染與評論支援。
  • 評論會保留在 HTML 輸出中嗎? 絕對會——setRenderComments(true) 會保留每一則評論與標註。
  • 需要哪個 Java 版本? JDK 8 或更高。
  • 生產環境需要授權嗎? 完整授權會移除浮水印並解鎖全部功能。
  • 如何提升渲染速度? 僅渲染特定頁面、使用外部資源,並增加 JVM heap 大小。

什麼是「將 Word 轉換為 HTML」並保留評論?

「將 Word 轉換為 HTML」 指的是將 Microsoft Word .docx 檔案轉換為可在瀏覽器中顯示的 HTML 文件,同時保留原始的版面配置、樣式以及任何內嵌的評論。此過程可讓瀏覽器如實呈現文件,並顯示審閱者的回饋。

為何選擇 GroupDocs Viewer for Java?

在深入程式碼之前,先來看看為何 GroupDocs Viewer 是 Java 文件渲染的首選:

  • 支援 170+ 格式 – 從 DOCX 到 CAD 檔案皆可處理,讓您只需一個相依套件即可滿足所有轉換需求。
  • 無需第三方 Office 安裝 – 可在任何作業系統上執行,無需 Microsoft Office、LibreOffice 或其他大型執行環境。
  • 保留格式與批註 – 評論、腳註與修訂痕跡在轉換後仍完整保留。
  • 快速且輕量的引擎 – 一般 100 頁文件在標準 4 核心伺服器上渲染時間低於 2 秒。
  • 完整文件與活躍社群 – 提供範例、論壇與即時支援,讓您在遇到問題時能快速取得協助。

何時使用此方式

  • 建置需要顯示審閱者備註的 Web 文件檢視器
  • 開發協作審閱平台,必須讓回饋保持可見
  • 將舊版合約轉換為線上顯示於法律入口網站
  • 開發在教材中嵌入講師批註的 e‑learning 解決方案

前置條件與環境設定

您需要的項目

  • Java Development Kit (JDK) 8+ – 為您的應用程式提供執行時環境。
  • Maven 3.6+ – 用於相依管理與專案建置。
  • 您慣用的 IDE – 如 IntelliJ IDEA、Eclipse 或 VS Code。
  • 帶有評論的範例文件 – 包含 DOCX、XLSX、PPTX 等檔案的審閱備註。

設定開發環境

步驟 1:驗證 Java 安裝

在終端機執行:

java -version

您應該會看到以 1.8 或更高開頭的版本字串。若未顯示,請從 Oracle 或 OpenJDK 官方網站下載最新的 JDK。

步驟 2:檢查 Maven 安裝

執行:

mvn -v

Maven 應回報其版本與使用的 Java 版本。若指令無法辨識,請從 Apache 官方網站安裝 Maven。

步驟 3:建立新的 Maven 專案

使用以下指令產生骨架專案:

mvn archetype:generate -DgroupId=com.example.viewer -DartifactId=viewer-demo -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false

進入新建立的 viewer-demo 資料夾,即可開始加入 GroupDocs Viewer。

設定 GroupDocs.Viewer for Java

新增相依

在任何 GroupDocs Viewer Java 教學的第一步,就是將函式庫加入專案。於 pom.xml 中加入以下設定:

<dependency>
    <groupId>com.groupdocs</groupId>
    <artifactId>groupdocs-viewer</artifactId>
    <version>25.2</version>
</dependency>

小技巧: 請隨時檢查 GroupDocs 釋出頁面 以取得最新版本。函式庫持續維護,會定期推出更新與錯誤修正。

授權選項說明

GroupDocs 提供彈性的授權模式,以符合不同專案需求:

  • 免費試用(適合學習): 30 天完整功能評估,並附帶評估浮水印。
  • 臨時授權(開發使用): 延長評估期且無浮水印,適合概念驗證專案。請於 GroupDocs 臨時授權頁面 申請。
  • 完整授權(正式上線): 無任何限制或浮水印,允許商業使用。可於 GroupDocs 購買頁面 取得。

基本初始化範本

以下是本教學中將持續使用的基本模式:

try (Viewer viewer = new Viewer("input.docx")) {
    // Rendering options will be set later
}

此模式有效的原因:

  • 自動資源管理 可防止記憶體洩漏。
  • 例外處理 捕捉常見的檔案存取問題。
  • 程式碼簡潔易讀,便於在大型專案中維護。

核心實作:渲染帶有評論的文件

流程說明

當您使用 GroupDocs Viewer 渲染文件時,函式庫會執行四個關鍵步驟:

  1. 文件分析 – 解析輸入檔案並建立內部表示。
  2. 評論抽取 – 識別所有評論、腳註與標註。
  3. HTML 產生 – 產生符合標準的乾淨 HTML,還原原始版面。
  4. 資源處理 – 將圖片、CSS、字型等資源內嵌或另存為外部檔案。

步驟式實作

步驟 1:設定檔案路徑

提前規劃輸入與輸出位置,可避免路徑相關錯誤:

Path inputPath = Paths.get("documents/sample-with-comments.docx");
Path outputDir = Paths.get("output/html");
Files.createDirectories(outputDir);

此作法的好處:

  • 使用現代的 Java NIO.2 Path API,比 java.io.File 更可靠。
  • 具描述性的變數名稱有助於除錯。
  • 輸出模式中的 {0} 佔位符會自動替換為頁碼。

步驟 2:設定 HTML 渲染選項

這裡就是魔法發生的地方。我們告訴 GroupDocs 具體的渲染需求:

HtmlViewOptions 用於設定文件渲染為 HTML 時的行為,包括資源處理與評論渲染。

HtmlViewOptions viewOptions = HtmlViewOptions.forEmbeddedResources(outputDir);
viewOptions.setRenderComments(true);   // Preserve every comment
viewOptions.setPageNumberPrefix("page_");

主要設定說明:

  • forEmbeddedResources() 會將 CSS、圖片與字型直接嵌入 HTML,使輸出檔案可攜帶。
  • setRenderComments(true) 這一行即確保 將 Word 轉換為 HTML 時保留所有審閱者備註。
  • 若偏好較輕量的 HTML,可改用 forExternalResources(),將資源另存為外部檔案。

步驟 3:執行渲染

將前述設定整合起來:

Viewer 是用來載入文件並執行渲染的主要類別。

try (Viewer viewer = new Viewer(inputPath.toFile())) {
    viewer.view(viewOptions);
}

view 方法會讀取 Word 檔案、抽取評論、產生 HTML 頁面,並寫入 output/html。每一頁會以 page_1.htmlpage_2.html 等檔名儲存。

完整範例程式

將上述所有片段組合,即可得到一個可執行的類別,負責將 Word 文件轉換為保留評論的 HTML(完整原始碼可於官方 GitHub 倉庫取得)。

進階設定與選項

動態輸出目錄設定

在較大型的應用程式中,您可能需要依使用者 ID 或時間戳記產生輸出目錄:

String userId = "12345";
Path dynamicOutput = Paths.get("output", userId, LocalDate.now().toString());
Files.createDirectories(dynamicOutput);
HtmlViewOptions dynamicOptions = HtmlViewOptions.forEmbeddedResources(dynamicOutput);

常見問題與除錯

問題 1:「找不到檔案」錯誤

請確保輸入路徑為絕對路徑或相對於工作目錄,並檢查檔案權限。使用 Path 物件可減少字串拼接錯誤。

問題 2:評論未出現在輸出中

再次確認在呼叫 viewer.view() 之前已執行 setRenderComments(true)。同時確定來源文件確實包含評論,可透過 viewer.getDocumentInfo().getComments() 進行檢查。

問題 3:大型文件記憶體不足

GroupDocs Viewer 會串流資料,但超過 500 頁的巨檔仍可能耗盡 JVM heap。可使用 -Xmx4g 增加堆積大小,或僅渲染必要頁面。

問題 4:渲染速度緩慢

使用 viewer.view(pageRange, viewOptions) 只渲染特定頁碼。外部資源 (forExternalResources()) 亦可減少 HTML 體積,加速瀏覽器載入。

真實案例實作模式

模式 1:Web 應用程式整合

將渲染邏輯嵌入 Spring Boot 控制器,即時提供 HTML 給前端:

@RestController
@RequestMapping("/api/view")
public class DocumentController {

    @GetMapping("/{id}")
    public ResponseEntity<Resource> renderDocument(@PathVariable String id) throws IOException {
        Path docPath = Paths.get("documents", id + ".docx");
        Path outDir = Files.createTempDirectory("viewer");
        HtmlViewOptions options = HtmlViewOptions.forEmbeddedResources(outDir);
        options.setRenderComments(true);
        try (Viewer viewer = new Viewer(docPath.toFile())) {
            viewer.view(options);
        }
        // Return the first HTML page as a Resource
        Path firstPage = outDir.resolve("page_1.html");
        Resource resource = new UrlResource(firstPage.toUri());
        return ResponseEntity.ok()
                .contentType(MediaType.TEXT_HTML)
                .body(resource);
    }
}

模式 2:批次處理多文件

若需一次轉換整個資料夾的 Word 檔,可遍歷目錄並重複使用同一個 HtmlViewOptions 實例,以降低物件建立開銷。

效能最佳化與最佳實踐

記憶體管理建議

  • 始終使用 try‑with‑resources 來管理 Viewer 實例。
  • 將大型文件分批處理,避免一次載入全部內容。
  • 使用 VisualVM 等工具監控 JVM heap,必要時調整 -Xmx
  • 對常用文件實作快取,減少重複渲染。

資源使用指引

小型應用(< 100 文件/天):

HtmlViewOptions options = HtmlViewOptions.forEmbeddedResources(Paths.get("output"));
options.setRenderComments(true);

高流量應用(1000+ 文件/天):

HtmlViewOptions options = HtmlViewOptions.forExternalResources(Paths.get("output"));
options.setRenderComments(true);
options.setCacheEnabled(true);

快取策略

將渲染後的 HTML 以文件雜湊為鍵存入分散式快取(如 Redis)。收到請求時先檢查快取,若命中則直接回傳快取內容,省去渲染步驟。

何時選擇 GroupDocs Viewer 而非其他方案

GroupDocs Viewer 的理想使用情境

  • 文件管理系統 – 需要顯示多種檔案類型且保留批註。
  • 協作審閱平台 – 必須讓所有參與者看到評論。
  • 教育工具 – 講師的註解會與投影片同步顯示。
  • 法律應用 – 合約中的律師批註需忠實呈現。

考慮其他方案的情況

  • 僅需簡易 PDF 顯示 – 瀏覽器內建的 PDF 檢視器已足夠。
  • 基本影像轉換ImageIO 或類似函式庫更輕量。
  • 純文字抽取 – Apache POI 或 iText 可能更合適。

常見問答

Q:可以不渲染評論嗎?
A:可以,只要省略 setRenderComments(true) 或將其設為 false

Q:哪些檔案格式支援評論渲染?
A:大多數主流格式皆支援,包括 DOC/DOCX、XLS/XLSX、PPT/PPTX、PDF 等。完整列表請參考官方文件

Q:我可以自訂 HTML 輸出的樣式嗎?
A:當然可以。使用 HtmlViewOptions.setEmbedResources(false) 產生外部 CSS,之後自行加入樣式表。

Q:如何處理受密碼保護的文件?
A:提供帶有密碼的 LoadOptions 實例:

LoadOptions 允許您指定文件載入參數,例如密碼。

LoadOptions loadOptions = new LoadOptions("myPassword");
try (Viewer viewer = new Viewer(inputPath.toFile(), loadOptions)) {
    viewer.view(viewOptions);
}

Q:能只渲染特定頁面嗎?
A:可以,使用接受 PageNumber 集合的重載 view 方法:

PageNumber 代表在渲染子集合時使用的頁碼索引。

viewer.view(new int[]{1, 3, 5}, viewOptions);

Q:為何大型文件渲染較慢?
A:檔案越大處理時間越長。可透過僅渲染必要頁面、使用外部資源、增加 JVM heap,或啟用非同步處理來提升速度。

Q:如何監控渲染進度?
A:GroupDocs Viewer 本身未提供回呼機制,您可在 viewer.view() 前後使用 System.nanoTime() 計時,並記錄耗時。

Q:若來源文件損毀會發生什麼?
A:函式庫會拋出 ViewerException。請將呼叫包在 try‑catch 中,並記錄錯誤以實現優雅降級。

Q:可以在商業應用中使用 GroupDocs Viewer 嗎?
A:可以,但必須購買商業授權。免費試用版會有浮水印,必須在正式上線前移除。

Q:有使用量限制嗎?
A:函式庫本身不設限制,唯授權合約可能規範使用上限,請參閱您的授權條款。

Q:我可以重新分發包含 GroupDocs Viewer 的應用程式嗎?
A:您可以分發自己的應用程式,但不得重新分發 GroupDocs 函式庫本身的二進位檔。請遵守授權條款。

後續步驟與進階主題

您已掌握 將 Word 轉換為 HTML 並保留評論的基礎。以下是進一步提升技能的方向:

  1. 加上浮水印 – 為渲染頁面加入自訂浮水印,以加強品牌或保密性。
  2. 擷取中繼資料 – 透過 viewer.getDocumentInfo() 取得作者、建立日期與頁數等資訊。
  3. 自訂檢視器 – 為 PDF、試算表或簡報建立專屬檢視器,針對評論顯示方式進行客製化。
  4. 雲端儲存整合 – 直接從 AWS S3、Azure Blob 或 Google Drive 讀取檔案渲染,無需先行下載至本機。

推薦學習路徑

  1. 嘗試不同檔案類型 – 測試 Excel、PowerPoint、PDF 等,觀察評論在各格式的處理情形。
  2. 建置簡易 Web 檢視器 – 建立一個最小化的 HTML 頁面,使用 <iframe> 或 AJAX 載入產生的 HTML。
  3. 探索 GroupDocs 生態系 – 了解 GroupDocs Annotation、Comparison、Signature 等套件,打造端到端的文件工作流程。
  4. 加入社群 – 參與 GroupDocs 論壇 交流技巧、範例與支援。

取得協助與支援

官方資源

社群資源

  • Stack Overflow(標籤:groupdocs-viewer
  • Reddit 程式設計社群
  • Java 開發者 Discord 伺服器

最後更新日期: 2026-05-21
測試版本: GroupDocs.Viewer 25.2 for Java
作者: GroupDocs

java -version
javac -version
mvn -version
mvn archetype:generate -DgroupId=com.example.documentviewer -DartifactId=groupdocs-viewer-demo -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false
<repositories>
   <repository>
      <id>repository.groupdocs.com</id>
      <name>GroupDocs Repository</name>
      <url>https://releases.groupdocs.com/viewer/java/</url>
   </repository>
</repositories>
<dependencies>
   <dependency>
      <groupId>com.groupdocs</groupId>
      <artifactId>groupdocs-viewer</artifactId>
      <version>25.2</version>
   </dependency>
</dependencies>
import com.groupdocs.viewer.Viewer;

// The try-with-resources pattern ensures proper cleanup
try (Viewer viewer = new Viewer("path/to/your/document.docx")) {
    // All rendering operations happen here
    // Resources are automatically closed when done
} catch (Exception e) {
    System.err.println("Error rendering document: " + e.getMessage());
    e.printStackTrace();
}
import java.nio.file.Path;
import java.nio.file.Paths;

// Create a descriptive output directory
Path outputDirectory = Paths.get("rendered-documents");
Path pageFilePathFormat = outputDirectory.resolve("page_{0}.html");
import com.groupdocs.viewer.options.HtmlViewOptions;

// Create HTML options with embedded resources
HtmlViewOptions viewOptions = HtmlViewOptions.forEmbeddedResources(pageFilePathFormat);

// The crucial setting – enable comment rendering!
viewOptions.setRenderComments(true);
import com.groupdocs.viewer.Viewer;

try (Viewer viewer = new Viewer("path/to/your/document.docx")) {
    // Create output directory if it doesn't exist
    if (!outputDirectory.toFile().exists()) {
        outputDirectory.toFile().mkdirs();
    }
    
    // Perform the actual rendering
    viewer.view(viewOptions);
    
    System.out.println("Document rendered successfully!");
    System.out.println("Output location: " + outputDirectory.toAbsolutePath());
    
} catch (Exception e) {
    System.err.println("Rendering failed: " + e.getMessage());
    e.printStackTrace();
}
package com.example.documentviewer;

import com.groupdocs.viewer.Viewer;
import com.groupdocs.viewer.options.HtmlViewOptions;
import java.nio.file.Path;
import java.nio.file.Paths;

public class DocumentRenderer {
    
    public static void main(String[] args) {
        renderDocumentWithComments("sample-document.docx", "output");
    }
    
    public static void renderDocumentWithComments(String inputFile, String outputDir) {
        // Set up paths
        Path outputDirectory = Paths.get(outputDir);
        Path pageFilePathFormat = outputDirectory.resolve("page_{0}.html");
        
        // Configure rendering options
        HtmlViewOptions viewOptions = HtmlViewOptions.forEmbeddedResources(pageFilePathFormat);
        viewOptions.setRenderComments(true);
        
        // Render the document
        try (Viewer viewer = new Viewer(inputFile)) {
            // Ensure output directory exists
            outputDirectory.toFile().mkdirs();
            
            // Execute rendering
            viewer.view(viewOptions);
            
            System.out.println("✓ Document rendered with comments preserved");
            System.out.println("📂 Output directory: " + outputDirectory.toAbsolutePath());
            
        } catch (Exception e) {
            System.err.println("❌ Rendering failed: " + e.getMessage());
            e.printStackTrace();
        }
    }
}
import java.nio.file.Path;
import java.nio.file.Paths;

public class PathManager {
    
    /**
     * Creates a structured output path based on document name and timestamp
     */
    public static Path getOutputDirectoryPath(String documentName) {
        String timestamp = String.valueOf(System.currentTimeMillis());
        String cleanDocName = documentName.replaceAll("[^a-zA-Z0-9]", "_");
        
        return Paths.get("rendered-docs")
                   .resolve(cleanDocName)
                   .resolve(timestamp);
    }
    
    /**
     * Simple output directory for basic use cases
     */
    public static Path getSimpleOutputPath(String folderName) {
        return Paths.get("output").resolve(folderName);
    }
}
// Always check if file exists before processing
Path inputPath = Paths.get("your-document.docx");
if (!inputPath.toFile().exists()) {
    throw new IllegalArgumentException("Input file not found: " + inputPath.toAbsolutePath());
}

// Check if file is readable
if (!inputPath.toFile().canRead()) {
    throw new IllegalArgumentException("Cannot read input file: " + inputPath.toAbsolutePath());
}
HtmlViewOptions viewOptions = HtmlViewOptions.forEmbeddedResources(pageFilePathFormat);
// This line is crucial – don't forget it!
viewOptions.setRenderComments(true);

// For debugging, you can verify the setting:
System.out.println("Comments enabled: " + viewOptions.isRenderComments());
// Increase JVM heap size when running
// java -Xmx2g -Xms1g YourApplication

// Or process documents page by page for very large files
HtmlViewOptions viewOptions = HtmlViewOptions.forEmbeddedResources(pageFilePathFormat);
viewOptions.setRenderComments(true);

// Render only specific pages if needed
viewer.view(viewOptions, 1, 2, 3); // Renders only pages 1, 2, and 3
// Use external resources for faster processing of multiple pages
HtmlViewOptions viewOptions = HtmlViewOptions.forExternalResources(
    pageFilePathFormat, 
    "resources/page_{0}/", 
    "resources/page_{0}/{0}"
);

// Enable caching if processing the same document multiple times
// (Note: Implement caching at application level)
@RestController
@RequestMapping("/api/documents")
public class DocumentController {
    
    @PostMapping("/render")
    public ResponseEntity<String> renderDocument(
            @RequestParam("file") MultipartFile file) {
        
        try {
            // Save uploaded file temporarily
            Path tempFile = Files.createTempFile("upload", ".tmp");
            file.transferTo(tempFile.toFile());
            
            // Render with comments
            String outputDir = renderDocumentWithComments(
                tempFile.toString(), 
                "web-output"
            );
            
            return ResponseEntity.ok("Document rendered: " + outputDir);
            
        } catch (Exception e) {
            return ResponseEntity.badRequest()
                .body("Rendering failed: " + e.getMessage());
        }
    }
}
public class BatchDocumentProcessor {
    
    public void processFolderWithComments(String inputFolder) {
        File folder = new File(inputFolder);
        File[] files = folder.listFiles((dir, name) -> 
            name.toLowerCase().endsWith(".docx") || 
            name.toLowerCase().endsWith(".xlsx") ||
            name.toLowerCase().endsWith(".pptx")
        );
        
        if (files == null) return;
        
        for (File file : files) {
            try {
                String outputDir = file.getName().replace(".", "_") + "_output";
                renderDocumentWithComments(file.getAbsolutePath(), outputDir);
                System.out.println("✓ Processed: " + file.getName());
                
            } catch (Exception e) {
                System.err.println("❌ Failed to process " + file.getName() + ": " + e.getMessage());
            }
        }
    }
}
// Simple approach works fine
try (Viewer viewer = new Viewer(documentPath)) {
    viewer.view(viewOptions);
}
public class DocumentRenderingService {
    private final ExecutorService executorService = 
        Executors.newFixedThreadPool(4); // Limit concurrent renderings
    
    public CompletableFuture<String> renderAsync(String documentPath) {
        return CompletableFuture.supplyAsync(() -> {
            try (Viewer viewer = new Viewer(documentPath)) {
                // Rendering logic here
                return "success";
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }, executorService);
    }
}
public class CachedDocumentRenderer {
    private final Map<String, String> renderCache = new ConcurrentHashMap<>();
    
    public String renderWithCaching(String documentPath) {
        String cacheKey = generateCacheKey(documentPath);
        
        return renderCache.computeIfAbsent(cacheKey, key -> {
            // Only render if not already cached
            return performActualRendering(documentPath);
        });
    }
    
    private String generateCacheKey(String documentPath) {
        // Include file modification time in cache key
        File file = new File(documentPath);
        return documentPath + "_" + file.lastModified();
    }
}
LoadOptions loadOptions = new LoadOptions();
loadOptions.setPassword("your-password");
try (Viewer viewer = new Viewer("protected-doc.docx", loadOptions)) {
    // Render as usual
}
viewer.view(viewOptions, 1, 3, 5); // Renders only pages 1, 3, and 5
System.out.println("Starting render for: " + documentName);
long startTime = System.currentTimeMillis();
viewer.view(viewOptions);
long endTime = System.currentTimeMillis();
System.out.println("Rendering completed in: " + (endTime - startTime) + "ms");
try (Viewer viewer = new Viewer(documentPath)) {
    viewer.view(viewOptions);
} catch (CorruptOrDamagedFileException e) {
    System.err.println("Document is corrupted: " + e.getMessage());
} catch (Exception e) {
    System.err.println("General error: " + e.getMessage());
}

相關教學