一、思路

1. 入口→分发:extractFromWord 是总入口,核心是 “格式分发”,将.doc 和.docx 分流到不同处理逻辑;

2. .doc 核心:绕开路径解析,用 “逐层遍历 + 兜底读取” 确保文件能读到,再交给extractFromOLE解析;

3. .docx 核心:直接遍历 PackagePart,利用 POI 对 OOXML 的原生支持,快速识别 Excel 附件;

4. 解析核心:extractFromOLE 是格式兼容关键,区分.xls/.xlsx 用不同 POI 模块,避免解析失败;

5. 稳定性保障:多层过滤(过小文件 / 特殊字符)+ 异常捕获(单个文件失败不中断)+ 格式适配,确保程序稳定运行。

二、核心依赖:

<!-- Apache POI 处理 Word 和 Excel -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.4</version>
</dependency>
<!-- Apache POI 处理 OLE 对象(嵌入式附件) -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-scratchpad</artifactId>
<version>5.2.4</version>
</dependency>
<!-- Apache Tika 识别文件类型(辅助提取附件) -->
<dependency>
<groupId>org.apache.tika</groupId>
<artifactId>tika-core</artifactId>
<version>2.9.1</version>
</dependency>

三、源码

import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.openxml4j.opc.PackagePart;
import org.apache.poi.poifs.filesystem.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.tika.Tika; import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern; public class WordExcelExtractor {
private static final Tika tika = new Tika();
private static final Pattern NON_PRINTABLE_CHAR_PATTERN = Pattern.compile("[^\\x20-\\x7E]");
private static final int MIN_EXCEL_SIZE = 100; public static List<byte[]> extractFromWord(File wordFile) throws IOException {
validateFile(wordFile);
List<byte[]> excelDataList = new ArrayList<>();
String fileName = wordFile.getName().toLowerCase(); try {
if (fileName.endsWith(".docx")) {
extractFromDocx(wordFile, excelDataList);
} else if (fileName.endsWith(".doc")) {
extractFromDocDirect(wordFile, excelDataList);
} else {
throw new IllegalArgumentException("不支持的格式!仅支持 .doc/.docx");
}
} catch (IllegalArgumentException e) {
throw e;
} catch (Exception e) {
throw new IOException("提取 Excel 附件失败:" + e.getMessage(), e);
}
return excelDataList;
} private static void extractFromDocDirect(File docFile, List<byte[]> excelDataList) throws IOException {
try (FileInputStream fis = new FileInputStream(docFile);
POIFSFileSystem poifs = new POIFSFileSystem(fis)) {
DirectoryEntry rootEntry = poifs.getRoot();
DirectoryEntry objectPool = getObjectPoolDirectory(rootEntry);
if (objectPool == null) {
System.out.println("无 ObjectPool 目录,无嵌入式附件");
return;
} for (Entry entry1 : objectPool) {
String dir1Name = filterSpecialChars(entry1.getName());
if (dir1Name.isEmpty() || !(entry1 instanceof DirectoryEntry)) {
continue;
}
DirectoryEntry subDir = (DirectoryEntry) entry1;
System.out.println("找到 OBJECTPOOL 子目录:" + dir1Name); for (Entry entry2 : subDir) {
String fileName = filterSpecialChars(entry2.getName());
if (fileName.isEmpty() || !(entry2 instanceof DocumentEntry)) {
continue;
}
DocumentEntry docEntry = (DocumentEntry) entry2;
long fileSize = docEntry.getSize();
System.out.println("找到文件:" + dir1Name + "/" + fileName + "(大小:" + fileSize + " 字节)"); if (fileSize < MIN_EXCEL_SIZE) {
System.out.println("️ 跳过过小文件(非 Excel):" + dir1Name + "/" + fileName);
continue;
} try {
// 主方案:路径读取
try (InputStream is = poifs.createDocumentInputStream(getEntryFullPath(docEntry))) {
byte[] oleData = is.readAllBytes();
extractAndAddExcel(oleData, dir1Name + "/" + fileName, excelDataList);
} catch (Exception e) {
System.out.println("️ 路径读取失败,尝试直接读取文件字节");
byte[] oleData = readDocumentEntryDirect(docEntry);
if (oleData != null && oleData.length >= MIN_EXCEL_SIZE) {
extractAndAddExcel(oleData, dir1Name + "/" + fileName, excelDataList);
} else {
System.out.println(" 兜底读取失败(数据无效):" + dir1Name + "/" + fileName);
}
}
} catch (Exception e) {
System.out.println(" 处理文件失败,跳过:" + dir1Name + "/" + fileName + " → " + e.getMessage());
}
}
}
}
} private static void extractAndAddExcel(byte[] oleData, String filePath, List<byte[]> excelDataList) {
try {
byte[] excelData = extractFromOLE(oleData);
if (excelData != null) {
excelDataList.add(excelData);
System.out.println(" 成功提取 Excel:" + filePath);
} else {
String fileHeader = getFileHeader(oleData);
System.out.println(" 非 Excel 文件(文件头:" + fileHeader + "):" + filePath);
}
} catch (Exception e) {
System.out.println(" 提取 Excel 失败:" + filePath + " → " + e.getMessage());
}
} private static byte[] readDocumentEntryDirect(DocumentEntry docEntry) {
try (InputStream is = new DocumentInputStream(docEntry)) {
byte[] data = new byte[(int) docEntry.getSize()];
int readLen = is.read(data);
return readLen > 0 ? data : null;
} catch (IOException e) {
System.out.println("️ 直接读取字节失败:" + e.getMessage());
return null;
}
} /**
* 核心修复:区分 .xls 和 .xlsx 格式,适配对应的解析模块
*/
private static byte[] extractFromOLE(byte[] oleData) {
// 1. 快速过滤非 Excel 文件
if (!isExcelFile(oleData)) {
return null;
} // 2. 判断是 .xls(OLE2)还是 .xlsx(OOXML)
boolean isXls = isXlsFile(oleData);
boolean isXlsx = isXlsxFile(oleData); // 3. 处理 .xlsx 格式(OOXML)
if (isXlsx) {
try (ByteArrayInputStream bais = new ByteArrayInputStream(oleData)) {
// 验证是否为有效 .xlsx(用 OOXML 专用的 OPCPackage)
try (OPCPackage opcPackage = OPCPackage.open(bais)) {
// 可选:进一步验证是否为 Excel 工作表(避免其他 OOXML 文件)
try (XSSFWorkbook workbook = new XSSFWorkbook(opcPackage)) {
// 能打开工作簿,说明是有效 .xlsx
return oleData;
}
}
} catch (Exception e) {
System.out.println("️ 无效的 .xlsx 文件:" + e.getMessage());
return null;
}
} // 4. 处理 .xls 格式(OLE2)
if (isXls) {
ByteArrayInputStream bais = null;
POIFSFileSystem poifs = null;
try {
bais = new ByteArrayInputStream(oleData);
poifs = new POIFSFileSystem(bais);
DirectoryEntry root = poifs.getRoot(); if (root.hasEntry("Package")) {
try (InputStream is = poifs.createDocumentInputStream("Package")) {
byte[] data = is.readAllBytes();
return isExcelFile(data) ? data : null;
}
} else if (root.hasEntry("Contents")) {
try (InputStream is = poifs.createDocumentInputStream("Contents")) {
byte[] data = is.readAllBytes();
return isExcelFile(data) ? data : null;
}
}
// 直接是 .xls 文件,无需额外解析
return oleData;
} catch (NotOLE2FileException e) {
System.out.println("️ 非 OLE2 格式文件:" + e.getMessage());
} catch (IOException e) {
System.out.println("️ 解析 .xls 文件失败:" + e.getMessage());
} finally {
if (poifs != null) {
try {
poifs.close();
} catch (IOException e) {}
}
if (bais != null) {
try {
bais.close();
} catch (IOException e) {}
}
}
} // 5. Tika 辅助验证
String fileType = tika.detect(oleData, "");
if (fileType.contains("excel") || fileType.contains("spreadsheet")) {
return oleData;
} return null;
} /**
* 单独判断是否为 .xls 文件(OLE2 格式)
*/
private static boolean isXlsFile(byte[] data) {
if (data.length < 4) return false;
byte b1 = data[0], b2 = data[1], b3 = data[2], b4 = data[3];
return (b1 == (byte) 0xD0 && b2 == (byte) 0xCF && b3 == (byte) 0x11 && b4 == (byte) 0xE0);
} /**
* 单独判断是否为 .xlsx 文件(OOXML 格式)
*/
private static boolean isXlsxFile(byte[] data) {
if (data.length < 4) return false;
byte b1 = data[0], b2 = data[1], b3 = data[2], b4 = data[3];
return (b1 == (byte) 0x50 && b2 == (byte) 0x4B && b3 == (byte) 0x03 && b4 == (byte) 0x04);
} // ---------------------- 工具方法 ----------------------
private static DirectoryEntry getObjectPoolDirectory(DirectoryEntry root) throws IOException {
if (root.hasEntry("ObjectPool")) {
return (DirectoryEntry) root.getEntry("ObjectPool");
} else if (root.hasEntry("OBJECTPOOL")) {
return (DirectoryEntry) root.getEntry("OBJECTPOOL");
}
return null;
} private static String filterSpecialChars(String name) {
return name == null ? "" : NON_PRINTABLE_CHAR_PATTERN.matcher(name).replaceAll("");
} private static boolean isExcelContentType(String contentType) {
return contentType.equals("application/vnd.ms-excel")
|| contentType.equals("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
|| contentType.equals("application/vnd.ms-excel.sheet.macroEnabled.12");
} private static boolean isExcelFile(byte[] data) {
return isXlsFile(data) || isXlsxFile(data);
} private static String getFileHeader(byte[] data) {
if (data.length < 4) return "不足4字节";
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 4; i++) {
sb.append(String.format("%02X ", data[i]));
}
return sb.toString().trim();
} private static void validateFile(File file) throws IOException {
if (!file.exists()) throw new FileNotFoundException("文件不存在:" + file.getAbsolutePath());
if (!file.isFile()) throw new IOException("路径不是文件:" + file.getAbsolutePath());
if (!file.canRead()) throw new IOException("文件不可读:" + file.getAbsolutePath());
} private static void extractFromDocx(File docxFile, List<byte[]> excelDataList) throws IOException {
try (XWPFDocument doc = new XWPFDocument(OPCPackage.open(docxFile))) {
for (PackagePart part : doc.getAllEmbeddedParts()) {
String contentType = part.getContentType();
try (InputStream is = part.getInputStream()) {
byte[] data = is.readAllBytes();
if (data.length < MIN_EXCEL_SIZE) continue;
if (isExcelContentType(contentType) || isExcelFile(data)) {
excelDataList.add(data);
System.out.println(" 提取 .docx 中的 Excel 附件");
} else if (contentType.contains("oleObject")) {
byte[] excelData = extractFromOLE(data);
if (excelData != null) {
excelDataList.add(excelData);
}
}
} catch (Exception e) {
System.out.println(" 处理 .docx 附件失败:" + e.getMessage());
}
}
} catch (Exception e) {
throw new IOException("解析 .docx 文件失败:" + e.getMessage(), e);
}
} /**
* 获取 Entry 的绝对路径(用于主方案路径读取,即使兜底方案常用,也需保留避免报红)
*/
private static String getEntryFullPath(Entry entry) {
List<String> pathParts = new ArrayList<>();
Entry current = entry;
while (current != null) {
String name = current.getName();
// 过滤根目录和无效名称
if (name != null && !name.isEmpty() && !"Root Entry".equals(name)) {
pathParts.add(name);
}
current = current.getParent();
}
// 反转路径部分,得到正确的绝对路径
StringBuilder path = new StringBuilder();
for (int i = pathParts.size() - 1; i >= 0; i--) {
if (path.length() > 0) {
path.append("/");
}
path.append(pathParts.get(i));
}
return path.toString();
}
}

java解析word中的excel的更多相关文章

  1. java向word中插入Excel附件

    1.word中插入对象的原理 编辑word,向word中插入图片.EXCEL.WORD等附件,再将word保存为xml格式,通过XML查看工具打开xml格式的word的源码,通过对比源码, 可以发现平 ...

  2. Java解析word,获取文档中图片位置

    前言(背景介绍): Apache POI是Apache基金会下一个开源的项目,用来处理office系列的文档,能够创建和解析word.excel.ppt格式的文档. 其中对word文档的处理有两个技术 ...

  3. Java解析word文档

    背景 在互联网教育行业,做内容相关的项目经常碰到的一个问题就是如何解析word文档. 因为系统如果无法智能的解析word,那么就只能通过其他方式手动录入word内容,效率低下,而且人工成本和录入出错率 ...

  4. Java读取word中表格

    因为要新建一个站,公司要把word表格的部分行列存到数据库中.之前用java操作过excel,本来打算用java从word表格中读取数据,再存到数据库中,结果因为权限不够,无法访问公司要写的那个数据库 ...

  5. Java 在Word中嵌入多媒体(视频、音频)文件

    Word中可将Office(Word/Excel/PowerPoint).PDF.txt等文件作为OLE对象插入到文档中,双击该对象可直接访问或编辑该文件,除了以上常见的文件格式对象,也可以插入多媒体 ...

  6. Java 实现word 中写入文字图片的解决方案

    JAVA生成WORD文件的方法目前有以下两种方式: 一种是jacob 但是局限于windows平台 往往许多JAVA程序运行于其他操作系统 在此不讨论该方案; 一种是poi但是他的excel处理很程序 ...

  7. java 解析上传的Excel文件

    java poi解析上传的Excel文件 package com.zhl.push.Utils; /** * @Author TAO * @ClassName ExcelData * @Descrip ...

  8. [转载]Java给word中的table赋值

    一.准备工作: 下载PageOffice for  Java:http://www.zhuozhengsoft.com/dowm/ 二. 实现方法: 要调用PageOffice操作Word中的tabl ...

  9. [原创]Java给word中的table赋值

    一.准备工作: 下载PageOffice for  Java:http://www.zhuozhengsoft.com/dowm/ 二. 实现方法: 要调用PageOffice操作Word中的tabl ...

  10. Java 提取Word中的文本和图片

    本文将介绍通过Java来提取或读取Word文档中文本和图片的方法.这里提取文本和图片包括同时提取文档正文当中以及页眉.页脚中的的文本和图片. 使用工具:Free Spire.Doc for Java ...

随机推荐

  1. 信而泰自动化OSPFv2测试小技巧

    ​OSPFv2协议简介 OSPFv2(开放式最短路径优先版本2)是互联网协议(IP)网络的路由协议.它使用链路状态路由(LSR)算法,并且属于在单个自治系统(AS)内运行的内部网关协议(IGP)组. ...

  2. 如何通过Python SDK 获取Collection

    本文介绍如何通过Python SDK获取已创建的Collection对象. 说明 通过Collection对象,后续可进行Doc相关操作,如插入Doc.检索Doc.管理Partition等 前提条件 ...

  3. UniApp自定义Android基座原理及流程

    概述 本文将详细讲解 UniApp 自定义 Android 基座的原理,并深入分析其与 Android 原生应用环境的交互过程.此文并非官方文档,仅供开发者参考.我们将通过对云基座和离线基座的对比,帮 ...

  4. H5游戏性能优化系列-----协议相关优化

    H5通讯协议这一块儿最长将的搭配应该是WebSocket+Protobuf这种模式吧,本篇就聊一下protobuf相关的优化. Protobuf基本流程 导入protobuf库 一般是后端定义协议文件 ...

  5. AI提示词遇见精密算法:TimeGuessr如何用数学魔法打造文化游戏新体验

    在人工智能与历史文化的美妙交融中,一套精密的评分算法正在重新定义游戏公平性与挑战性 当我们谈论AI生成的文化游戏时,很多人首先想到的是华丽的视觉效果和智能的内容生成.然而,真正让TimeGuessr( ...

  6. 解码C语言宏

    预处理概述 基本概念 预处理是C语言编译过程的第一步,所有以#开头的指令都由预处理器处理,这些指令不属于C语言语法本身. 预处理指令类型 头文件包含:#include 宏定义:#define 宏取消: ...

  7. 李子柒事件“余震”:网红纷纷翻合同,IP究竟是谁的?

    最近一段时间,在全球拥有上亿粉丝的顶流网红李子柒"消失",成为网络舆论的一个热点.该事件的起因,是自七月中旬开始,她在各平台的账号均陷入长时间停更状态.而李子柒本人,也是几天前参加 ...

  8. 创建一个VUE项目,正式开发之前要做的配置~

    一.使用vue-cli脚手架创建一个项目,根据我们开发所需生成固定的文件目录(可配置).  二.创建好项目之后,还并不能开始真正的开发,我们需要做一些开发前的准备,比如请求的axios封装,多环境的地 ...

  9. RoboMaster电控入门(5)麦克纳姆轮底盘控制

    底盘运动解算 麦克纳姆轮是瑞典麦克纳姆公司的专利,其特点是轮子上安装了很多辊子,轮毂轴和辊子之间夹角为45°(有时也可以是其他度数,视使用目的而定),由于搭载了麦轮的四轮底盘可以很容易的实现全向移动, ...

  10. XCVU13P板卡设计原理图:509-基于XCVU13P的4路QSFP28光纤PCIeX16收发卡

    基于XCVU13P的4路QSFP28光纤PCIeX16收发卡 一.板卡概述         基于XCVU13P的4路QSFP28光纤PCIeX16收发卡.该板卡要求符合PCIe 3.0标准,包含一片X ...