合同、报价单、演示文稿这类附件经常被直接嵌入到 Excel 工作簿中:在工作表上看到的只是一个图标或缩略图,真正的文档数据藏在工作簿的 xl/embeddings 里。要把这些附件从工作簿中取出来单独归档,逐个双击另存显然不现实。Spire.XLS for JavaScript 基于 WebAssembly 在浏览器端直接完成此操作,通过虚拟文件系统(VFS)管理输入输出文件,无需后端服务支持。
本文主要介绍以下两个核心功能点:
有关安装和项目配置,请参考 React 项目中集成 Spire.XLS for JavaScript。以下示例默认已安装 Spire.XLS 并完成 WebAssembly 模块初始化。
读取工作表中的 OLE 对象信息
提取之前先要弄清工作簿里到底嵌了什么。一份附件清单能把嵌入对象的类型、原始名称、所在位置和体积一次列全,充当附件的登记记录,归档或交接时不必逐个双击确认。
具体操作步骤如下:
- 用
HasOleObjects判断当前工作表是否含有嵌入对象,没有则提前结束 - 用
OleObjects集合遍历其中的每一个对象 - 从每个对象上读取四项信息:
ObjectType:对象类型,如WordDocument、PowerPointPresentationOleOriginName:嵌入前的原始文件名Location:对象所在的单元格位置OleData的长度:数据大小
- 把四项信息按行拼成文本清单,写入
ListOleObjects.txt
下面的示例把这些信息汇总成一份文本清单:
function App() {
const listOleObjects = async () => {
// 获取 Spire.XLS WASM 模块
const xlsModule = window.wasmModule?.spirexls;
// 检查模块是否就绪
if (!xlsModule) {
alert('Spire.Xls is not ready yet');
return;
}
// 将字体与输入文件载入 VFS
await window.spire.FetchFileToVFS('simsun.ttc', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
const inputFileName = 'OLEObjects.xlsx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);
// 加载工作簿,取第一个工作表
const workbook = new xlsModule.Workbook();
workbook.LoadFromFile({ fileName: inputFileName });
const sheet = workbook.Worksheets.get(0);
// 判断工作表中是否包含 OLE 对象
if (!sheet.HasOleObjects) {
alert('The worksheet contains no OLE objects.');
return;
}
// 把读取到的信息汇总成清单,首行为表头,字段之间用制表符分隔
const lines = [['序号', '对象类型', '原始文件名', '所在位置', '数据大小(字节)'].join('\t')];
// 遍历工作表中的 OLE 对象,每行记录一个对象
let index = 1;
for (const oleObject of sheet.OleObjects) {
lines.push([
index,
String(oleObject.ObjectType),
String(oleObject.OleOriginName),
String(oleObject.Location.RangeAddress),
oleObject.OleData.length,
].join('\t'));
index += 1;
}
// 释放 workbook 对象以释放资源
workbook.Dispose();
// 把清单写入 VFS 中的文本文件
const outputFileName = 'ListOleObjects.txt';
window.dotnetRuntime.Module.FS.writeFile(outputFileName, lines.join('\n'));
// 从 VFS 读取结果文件,触发下载
const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([fileArray], { type: 'text/plain;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>List OLE Objects</h1>
<button onClick={listOleObjects}>Start</button>
</div>
);
}
export default App;
运行后,读取工作表中的 OLE 对象信息的效果:

按类型提取 OLE 对象正文
清单只能说明嵌了什么,真正要归档还得把附件本身取出来。OLE 对象的正文以字节数组的形式存放在对象内部,原样写出即可还原成嵌入前的文档,无需重新排版或转换格式。
具体操作步骤如下:
- 用
HasOleObjects确认当前工作表含有嵌入对象 - 遍历
OleObjects集合中的每个对象,先由ObjectType判断它的类型,据此决定输出文件的扩展名与 MIME 类型 - 用
OleData取出对象的原始字节,写入虚拟文件系统并读回,包装成可下载的 Blob - 遍历结束后调用
Dispose释放工作簿,再逐个触发下载
下面的示例遍历工作表中的所有对象,把 Word、PowerPoint 与 PDF 三类附件分别写成 .docx、.pptx 与 .pdf 文件并提供下载:
function App() {
const extractOleObjects = async () => {
// 获取 Spire.XLS WASM 模块
const xlsModule = window.wasmModule?.spirexls;
// 检查模块是否就绪
if (!xlsModule) {
alert('Spire.Xls is not ready yet');
return;
}
// 将字体与输入文件载入 VFS
await window.spire.FetchFileToVFS('simsun.ttc', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
const inputFileName = 'OLEObjects.xlsx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);
// 加载工作簿,取第一个工作表
const workbook = new xlsModule.Workbook();
workbook.LoadFromFile({ fileName: inputFileName });
const sheet = workbook.Worksheets.get(0);
// 收集提取结果,供页面按类型提供下载
const results = [];
// 遍历工作表中的 OLE 对象,按对象类型写出对应格式的文件
if (sheet.HasOleObjects) {
for (const oleObject of sheet.OleObjects) {
const type = oleObject.ObjectType;
let outputFileName = '';
let mimeType = '';
// Word 文档
if (type === xlsModule.OleObjectType.WordDocument) {
outputFileName = 'ExtractWord.docx';
mimeType = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';
}
// PowerPoint 演示文稿,.pptx 与 .sldx 分属两个枚举成员
else if (
type === xlsModule.OleObjectType.PowerPointPresentation ||
type === xlsModule.OleObjectType.PowerPointSlide
) {
outputFileName = 'ExtractPowerPoint.pptx';
mimeType = 'application/vnd.openxmlformats-officedocument.presentationml.presentation';
}
// PDF 文档
else if (type === xlsModule.OleObjectType.AdobeAcrobatDocument) {
outputFileName = 'ExtractPdf.pdf';
mimeType = 'application/pdf';
}
// 其余类型不作处理
if (!outputFileName) continue;
// 把对象的原始数据写入虚拟文件系统
window.dotnetRuntime.Module.FS.writeFile(outputFileName, oleObject.OleData);
const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
results.push({
name: outputFileName,
url: URL.createObjectURL(new Blob([fileArray], { type: mimeType })),
});
}
}
// 释放 workbook 对象以释放资源
workbook.Dispose();
// 逐个触发下载
results.forEach(({ name, url }) => {
const a = document.createElement('a');
a.href = url;
a.download = name;
a.click();
URL.revokeObjectURL(url);
});
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>Extract OLE Objects</h1>
<button onClick={extractOleObjects}>Start</button>
</div>
);
}
export default App;
运行后,提取出的附件文档的效果:

常见问题
判断 PowerPoint 附件时 OleObjectType.PowerPointSlide 没有匹配上
原因:OleObjectType 按嵌入文件的真实格式细分,.pptx 演示文稿的对象类型是 PowerPointPresentation,只有 .sldx、.ppt 这类放映格式才落到 PowerPointSlide,只判断后者会漏掉绝大多数 PowerPoint 附件。
解决:分支中同时匹配两个枚举成员,例如:
else if (
type === xlsModule.OleObjectType.PowerPointPresentation ||
type === xlsModule.OleObjectType.PowerPointSlide
) {
outputFileName = 'ExtractPowerPoint.pptx';
}
只提取出第一个工作表里的附件
原因:OleObjects 是工作表级别的集合,示例先取 Worksheets.get(0) 拿到第一张表,再遍历这张表的 OleObjects,因此分布在其它工作表上的对象根本不在遍历范围内。附件跨表存放时,提取结果会缺少后面几张表的内容,而且不会有任何报错提示。
解决:改为逐张工作表遍历,对每张表分别取它的 OleObjects:
for (let i = 0; i < workbook.Worksheets.Count; i++) {
const worksheet = workbook.Worksheets.get(i);
for (const oleObject of worksheet.OleObjects) {
// 按类型写出附件
}
}
获取免费许可证
如果您希望删除结果文档中的评估消息,或者摆脱功能限制,请该Email地址已收到反垃圾邮件插件保护。要显示它您需要在浏览器中启用JavaScript。获取有效期 30 天的临时许可证。







