Chuyển đổi tài liệu RTF và DOC sang HTML bằng Java với Apache POI

RTF (Rich Text Format) là định dạng văn bản nâng cao hỗ trợ định dạng, hình ảnh, bảng biểu và các thành phần đa phương tiện khác. Mặc dù có thể mở trực tiếp bằng Microsoft Word hoặc WPS, RTF cũng có thể được xem dưới dạng văn bản thuần — giúp phân tích cấu trúc mã nguồn dễ dàng hơn. Tuy nhiên, việc xử lý RTF trong Java không phổ biến như DOC/DOCX, do đó giải pháp hiệu quả thường kết hợp hai bước: chuẩn hóa định dạng đầu vào rồi chuyển đổi sang HTML.

Một cách tiếp cận thực tiễn là chuyển đổi RTF → DOC → HTML. Trong đó, bước đầu tiên (RTF → DOC) có thể tự động hóa bằng giao diện lập trình COM trên Windows (qua Jacob hoặc JACOB), hoặc sử dụng công cụ dòng lệnh như LibreOffice headless. Tuy nhiên, để đơn giản hóa và đảm bảo tính khả chuyển, bài viết tập trung vào bước thứ hai: chuyển đổi tài liệu Word (định dạng .doc) sang HTML với đầy đủ định dạng và nhúng hình ảnh dưới dạng Base64 — loại bỏ phụ thuộc vào thư mục tài nguyên bên ngoài.

Dưới đây là cấu hình Maven cần thiết cho dự án sử dụng Apache POI phiên bản 5.2.4 (cập nhật so với phiên bản cũ 3.17 nhằm đảm bảo tương thích JDK 11+ và vá lỗi bảo mật):

<dependencies>
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi</artifactId>
        <version>5.2.4</version>
    </dependency>
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-scratchpad</artifactId>
        <version>5.2.4</version>
    </dependency>
</dependencies>

Để thực hiện chuyển đổi, ta xây dựng lớp DocToHtmlConverter với logic sau:

  • Sử dụng HWPFDocument để đọc file .doc (không hỗ trợ .docx — cần dùng XWPFDocument riêng).
  • Tùy chỉnh PicturesManager để mã hóa từng ảnh thành chuỗi Base64 và nhúng trực tiếp vào thuộc tính src của thẻ <img>.
  • Sử dụng Transformer để xuất DOM thành HTML có thụt lề và mã hóa UTF-8.
  • Hỗ trợ ghi file với xử lý ngoại lệ rõ ràng và đóng tài nguyên đúng cách.

Dưới đây là triển khai lớp chuyển đổi:

package converter;

import org.apache.poi.hwpf.HWPFDocument;
import org.apache.poi.hwpf.converter.PicturesManager;
import org.apache.poi.hwpf.converter.WordToHtmlConverter;
import org.apache.poi.hwpf.usermodel.PictureType;
import org.w3c.dom.Document;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.*;
import java.nio.file.Files;
import java.util.Base64;

public class DocToHtmlConverter {

    public static void convert(String inputPath, String outputPath) 
            throws Exception {
        try (FileInputStream fis = new FileInputStream(inputPath);
             FileOutputStream fos = new FileOutputStream(outputPath)) {

            HWPFDocument doc = new HWPFDocument(fis);
            WordToHtmlConverter converter = new WordToHtmlConverter(
                    DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument()
            );

            converter.setPicturesManager(new PicturesManager() {
                @Override
                public String savePicture(byte[] content, PictureType pictureType,
                                          String suggestedName, float widthInches, float heightInches) {
                    String base64Data = Base64.getEncoder().encodeToString(content);
                    return "data:" + pictureType.getMimeType() + ";base64," + base64Data;
                }
            });

            converter.processDocument(doc);

            Document htmlDoc = converter.getDocument();
            Transformer transformer = TransformerFactory.newInstance().newTransformer();
            transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
            transformer.setOutputProperty(OutputKeys.INDENT, "yes");
            transformer.setOutputProperty(OutputKeys.METHOD, "html");

            try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
                transformer.transform(new DOMSource(htmlDoc), new StreamResult(baos));
                Files.write(new File(outputPath).toPath(), baos.toByteArray());
            }
        }
    }

    public static void main(String[] args) {
        try {
            convert("src/test/resources/sample.doc", "output/sample.html");
            System.out.println("Chuyển đổi hoàn tất.");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Đối với xử lý hàng loạt, ta tạo lớp BatchDocProcessor duyệt thư mục đầu vào, lọc các file có đuôi .doc, và gọi DocToHtmlConverter.convert() cho từng tệp:

package converter;

import java.io.File;
import java.io.IOException;

public class BatchDocProcessor {

    public static void processDirectory(String sourceDir, String targetDir) throws IOException {
        File src = new File(sourceDir);
        File dst = new File(targetDir);
        if (!dst.exists()) dst.mkdirs();

        for (File file : src.listFiles((dir, name) -> name.toLowerCase().endsWith(".doc"))) {
            String baseName = removeExtension(file.getName());
            String htmlPath = targetDir + File.separator + baseName + ".html";
            try {
                DocToHtmlConverter.convert(file.getAbsolutePath(), htmlPath);
                System.out.printf("✓ Đã xử lý: %s → %s%n", file.getName(), baseName + ".html");
            } catch (Exception e) {
                System.err.printf("✗ Lỗi khi xử lý %s: %s%n", file.getName(), e.getMessage());
            }
        }
    }

    private static String removeExtension(String filename) {
        int dotIndex = filename.lastIndexOf('.');
        return dotIndex == -1 ? filename : filename.substring(0, dotIndex);
    }

    public static void main(String[] args) {
        processDirectory("input/docs", "output/html");
    }
}

Lưu ý: Giải pháp này chỉ hỗ trợ định dạng .doc (Word 97–2003). Với tài liệu .docx, cần thay thế bằng XWPFDocumentXWPFWordToHtmlConverter từ gói poi-ooxml, cùng cơ chế nhúng ảnh tương tự.

Thẻ: apache-poi rtf doc html-conversion Java

Đăng vào ngày 11 tháng 7 lúc 13:03