001/* 002 * Copyright (c) 2023-2026, Agents-Flex (fuhai999@gmail.com). 003 * <p> 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * <p> 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * <p> 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package com.agentsflex.core.file2text.extractor.impl; 017 018 019import com.agentsflex.core.file2text.extractor.FileExtractor; 020import com.agentsflex.core.file2text.source.DocumentSource; 021import org.apache.poi.xwpf.usermodel.*; 022 023import java.io.IOException; 024import java.io.InputStream; 025import java.util.Collections; 026import java.util.HashSet; 027import java.util.List; 028import java.util.Set; 029import java.util.stream.Collectors; 030 031/** 032 * DOCX 文档提取器(.docx, .dotx) 033 * 支持段落、表格、列表文本提取 034 */ 035public class DocxExtractor implements FileExtractor { 036 037 private static final Set<String> KNOWN_MIME_TYPES; 038 private static final String MIME_PREFIX = "application/vnd.openxmlformats-officedocument.wordprocessingml"; 039 private static final Set<String> SUPPORTED_EXTENSIONS; 040 041 static { 042 // 精确 MIME(可选) 043 Set<String> mimeTypes = new HashSet<>(); 044 mimeTypes.add("application/vnd.openxmlformats-officedocument.wordprocessingml.document"); 045 KNOWN_MIME_TYPES = Collections.unmodifiableSet(mimeTypes); 046 047 // 支持的扩展名 048 Set<String> extensions = new HashSet<>(); 049 extensions.add("docx"); 050 extensions.add("dotx"); 051 SUPPORTED_EXTENSIONS = Collections.unmodifiableSet(extensions); 052 } 053 054 @Override 055 public boolean supports(DocumentSource source) { 056 String mimeType = source.getMimeType(); 057 String fileName = source.getFileName(); 058 059 // 1. MIME 精确匹配 060 if (mimeType != null && KNOWN_MIME_TYPES.contains(mimeType)) { 061 return true; 062 } 063 064 // 2. MIME 前缀匹配 065 if (mimeType != null && mimeType.startsWith(MIME_PREFIX)) { 066 return true; 067 } 068 069 // 3. 扩展名匹配 070 if (fileName != null) { 071 String ext = getExtension(fileName); 072 if (ext != null && SUPPORTED_EXTENSIONS.contains(ext.toLowerCase())) { 073 return true; 074 } 075 } 076 077 return false; 078 } 079 080 @Override 081 public String extractText(DocumentSource source) throws IOException { 082 StringBuilder text = new StringBuilder(); 083 084 try (InputStream is = source.openStream(); 085 XWPFDocument document = new XWPFDocument(is)) { 086 087 // 提取段落 088 for (XWPFParagraph paragraph : document.getParagraphs()) { 089 String paraText = getParagraphText(paragraph); 090 if (paraText != null && !paraText.trim().isEmpty()) { 091 text.append(paraText).append("\n"); 092 } 093 } 094 095 // 提取表格 096 for (XWPFTable table : document.getTables()) { 097 text.append("\n[Table Start]\n"); 098 for (XWPFTableRow row : table.getRows()) { 099 List<String> cellTexts = row.getTableCells().stream() 100 .map(this::getCellText) 101 .map(String::trim) 102 .collect(Collectors.toList()); 103 text.append(cellTexts).append("\n"); 104 } 105 text.append("[Table End]\n\n"); 106 } 107 108 } catch (Exception e) { 109 throw new IOException("Failed to extract DOCX: " + e.getMessage(), e); 110 } 111 112 return text.toString().trim(); 113 } 114 115 private String getParagraphText(XWPFParagraph paragraph) { 116 StringBuilder text = new StringBuilder(); 117 for (XWPFRun run : paragraph.getRuns()) { 118 String runText = run.text(); 119 if (runText != null) { 120 text.append(runText); 121 } 122 } 123 return text.length() > 0 ? text.toString() : null; 124 } 125 126 private String getCellText(XWPFTableCell cell) { 127 String simpleText = cell.getText(); 128 if (simpleText != null && !simpleText.isEmpty()) { 129 return simpleText; 130 } 131 StringBuilder text = new StringBuilder(); 132 for (XWPFParagraph p : cell.getParagraphs()) { 133 String pt = getParagraphText(p); 134 if (pt != null) { 135 text.append(pt).append(" "); 136 } 137 } 138 return text.toString().trim(); 139 } 140 141 @Override 142 public int getOrder() { 143 return 10; 144 } 145 146 private String getExtension(String fileName) { 147 if (fileName == null || !fileName.contains(".")) return null; 148 int lastDot = fileName.lastIndexOf('.'); 149 return fileName.substring(lastDot + 1); 150 } 151}