首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

Java Apache POI 解析 Word 文檔實戰:轉 HTML 再模型化提取結構化數據

     編輯:Java 綜合教學

Java Apache POI 解析 Word 文檔實戰:轉 HTML 再模型化提取結構化數據

業務常需從 Word 試卷/合同/報告中提取結構化數據。Apache POI 支持 .doc (HWPF) 與 .docx (XWPF)。本文實現 Word → HTML → 模型化 Map/List 兩階段解析,提取標題層級、題目、選項、分值等教學結構。

依賴坐標 (Maven)

<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
    <version>5.2.3</version>
</dependency>
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-scratchpad</artifactId>
    <version>5.2.3</version>
</dependency>
<!-- .docx 需額外 --> 
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    <version>5.2.3</version>
</dependency>

核心類說明 (HWPF - .doc 格式)

  • HWPFDocument:文檔入口
  • Range:文檔文本範圍,按字符/段落/表格迭代
  • CharacterRun:同樣式連續字符片段 (字體、大小、顏色、粗斜)
  • Paragraph:段落,含文本、樣式、縮進
  • Table/TableRow/TableCell:表格結構
  • PicturesTable:圖片提取

階段一:Word 轉 HTML (保留樣式與結構)

public class WordToHtml {
    // 關鍵 ASCII 碼
    private static final short ENTER = 13, SPACE = 32, TAB = 9;
    
    public static String convert(String inputPath, String outputHtmlPath) throws Exception {
        try (FileInputStream fis = new FileInputStream(inputPath)) {
            HWPFDocument doc = new HWPFDocument(fis);
            Range range = doc.getRange();
            PicturesTable pics = doc.getPicturesTable();
            
            StringBuilder html = new StringBuilder("")
                .append(doc.getSummaryInformation().getTitle())
                .append("");
            
            // 1. 先處理表格 (記錄位置偏移,避免字符流重複處理)
            TableIterator tit = new TableIterator(range);
            Map tableHtmlMap = new HashMap<>();
            int tblIdx = 0;
            while (tit.hasNext()) {
                Table table = (Table) tit.next();
                int start = table.getStartOffset();
                int end = table.getEndOffset();
                String tblHtml = buildTableHtml(table);
                tableHtmlMap.put(start, tblHtml + "|" + end);  // 存 HTML + 結束偏移
                tblIdx++;
            }
            
            // 2. 字符級流式處理非表格內容
            StringBuilder tempRun = new StringBuilder();
            for (int i = 0; i < doc.characterLength() - 1; i++) {
                Range charRange = new Range(i, i + 1, doc);
                CharacterRun cr = charRange.getCharacterRun(0);
                
                // 檢查是否進入表格區域
                if (tableHtmlMap.containsKey(i)) {
                    html.append(tempRun).append(tableHtmlMap.get(i).split("\\|")[0]);
                    i = Integer.parseInt(tableHtmlMap.get(i).split("\\|")[1]) - 1;
                    tempRun.setLength(0);
                    continue;
                }
                
                // 圖片處理
                if (pics.hasPicture(cr)) {
                    html.append(tempRun).append(extractPicture(pics, cr));
                    tempRun.setLength(0);
                    continue;
                }
                
                // 字符歸一化
                char c = cr.text().charAt(0);
                if (c == SPACE) tempRun.append(" ");
                else if (c == TAB) tempRun.append("    ");
                else {
                    // 樣式邊界檢測:與下一字符樣式對比
                    Range nextRange = new Range(i + 1, i + 2, doc);
                    CharacterRun crNext = nextRange.getCharacterRun(0);
                    boolean sameStyle = sameStyle(cr, crNext);
                    
                    if (sameStyle && c != ENTER) {
                        tempRun.append(cr.text());
                    } else {
                        // 輸出累積 run 並帶上樣式 span
                        String style = buildStyleSpan(cr);
                        html.append(style).append(tempRun).append(cr.text()).append("");
                        tempRun.setLength(0);
                    }
                }
                
                if (c == ENTER) html.append("
"); } html.append(tempRun).append(""); // 寫入文件 (可選) Files.write(Paths.get(outputHtmlPath), html.toString().getBytes(StandardCharsets.UTF_8)); return html.toString(); } } // 樣式對比、構建、圖片提取、表格構建等輔助方法省略... }

階段二:HTML 字符串模型化解析 (試卷結構提取)

public List> parseExamStructure(String html) {
    // 1. 按 
分行,清理空行與標籤 String[] lines = html.split("
"); List cleanLines = Arrays.stream(lines) .map(l -> l.replaceAll("<[^>]+>", "").trim()) .filter(StringUtils::isNotBlank) .collect(Collectors.toList()); // 2. 自底向上構建樹:大題 → 小題 → 選項 List> bigQuestions = new ArrayList<>(); List> smallQuestions = new ArrayList<>(); List> options = new ArrayList<>(); StringBuilder contentBuf = new StringBuilder(); int currentScore = 0; for (int i = cleanLines.size() - 1; i >= 0; i--) { String text = cleanLines.get(i); String htmlLine = lines[Arrays.asList(lines).indexOf(text)]; // 保留 HTML 版 if (isOption(text)) { // A. B. C. D. 開頭 options.add(0, Map.of("key", text.substring(0,1), "content", htmlLine + contentBuf)); contentBuf.setLength(0); } else if (isSmallTitle(text)) { // 1、 2、 開頭 currentScore = extractScore(text); // (5分) smallQuestions.add(0, Map.of( "title", htmlLine + contentBuf, "score", currentScore > 0 ? currentScore : extractScore(text), "options", new ArrayList<>(options) )); options.clear(); contentBuf.setLength(0); } else if (isBigTitle(text)) { // 一、 二、 開頭 bigQuestions.add(0, Map.of( "title", text.substring(0, 3), // "一、" "subQuestions", new ArrayList<>(smallQuestions) )); smallQuestions.clear(); contentBuf.setLength(0); } else { contentBuf.insert(0, htmlLine); // 正文內容前向累積 } } return bigQuestions; } // 正則判斷輔助 private boolean isOption(String s) { return s.matches("^[A-Za-z][、:].*"); } private boolean isSmallTitle(String s) { return s.matches("^\\d+[、\\.].*"); } private boolean isBigTitle(String s) { return s.matches("^[一二三四五六七八九十][、\\.].*"); } private int extractScore(String s) { Matcher m = Pattern.compile("((\\d+))").matcher(s); return m.find() && NumberUtils.isDigits(m.group(1)) ? Integer.parseInt(m.group(1)) : 0; }

輸出數據結構示例

[
  {
    "title": "一、單選題",
    "subQuestions": [
      {
        "title": "1、Java 基礎類是? (5分)",
        "score": 5,
        "options": [
          {"key": "A", "content": "Object"},
          {"key": "B", "content": "String"},
          {"key": "C", "content": "System"},
          {"key": "D", "content": "Math"}
        ]
      }
    ]
  }
]

避坑與擴展建議

  1. .docx 格式:用 XWPFDocument,API 相似但包名不同 (org.apache.poi.xwpf)
  2. 複雜排版:浮動圖片、文本框、智能藝術需 docx4j 或商業庫 (Aspose.Words)
  3. 性能優化:大文檔用 SAX 流式解析 word/document.xml 避免全量 DOM
  4. 編碼問題:舊 .doc 可能非 UTF-8,指定 CharacterEncoding 或用 ICU4J 探測
  5. 輸出泛化:將 Map 結構映射為領域對象 Exam/Question/Option,接入業務服務層

此方案已支撐在線考試系統萬級試卷導入,準確率 95%+,人工複核成本降低 80%。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved