Node.js Office 库 — 快速上手
office-oxide npm 包是 原生 Node.js 绑定:通过 koffi 动态加载 liboffice_oxide,并暴露简洁、地道的 JavaScript API。比 WebAssembly 构建更快、更小,但需要按平台分发预编译产物,而不是单一可移植二进制。
如果需要一个能跨浏览器、打包工具或 Node 运行的单一构件(且不需要原生库),请改用
office-oxide-wasm。
安装
npm install office-oxide
postinstall 时会在 node_modules/office-oxide/prebuilds/<platform>-<arch>/ 下查找匹配的预编译原生库。如果找不到,请在首次使用前配置以下任一项:
OFFICE_OXIDE_LIB— 库的绝对路径(例如/opt/office_oxide/liboffice_oxide.so)。- 预编译目录 — 把库放到包内的
prebuilds/<platform>-<arch>/(如linux-x64、darwin-arm64、win32-x64)。 - 系统库路径 — 安装到
LD_LIBRARY_PATH/DYLD_LIBRARY_PATH/PATH能找到的位置。
支持 Node 18+。
读取文档
import { Document } from 'office-oxide';
const doc = Document.open('report.docx');
try {
console.log(doc.plainText());
} finally {
doc.close();
}
或使用 TC39 显式资源管理(Node 22+):
import { Document } from 'office-oxide';
using doc = Document.open('report.docx');
console.log(doc.plainText());
// 离开作用域时会自动调用 doc.close()
一次性辅助函数:
import { extractText } from 'office-oxide';
console.log(extractText('report.docx'));
核心 API
import { Document } from 'office-oxide';
const doc = Document.open('file.xlsx');
try {
console.log(doc.format); // "xlsx"
console.log(doc.plainText());
console.log(doc.toMarkdown());
console.log(doc.toHtml());
const ir = doc.toIr(); // 解析后的对象
doc.saveAs('file.docx'); // 目标根据扩展名推断
} finally {
doc.close();
}
从原始字节(Uint8Array 或 Buffer)打开:
import { readFileSync } from 'node:fs';
import { Document } from 'office-oxide';
const data = readFileSync('report.pptx');
using doc = Document.fromBytes(data, 'pptx');
console.log(doc.toMarkdown());
format 必须是 'docx' | 'xlsx' | 'pptx' | 'doc' | 'xls' | 'ppt'。
模块级辅助函数:
import { extractText, toMarkdown, toHtml, detectFormat, version } from 'office-oxide';
extractText('file.docx'); // string
toMarkdown('file.pptx'); // string
toHtml('file.xlsx'); // string
detectFormat('mystery.bin'); // "docx" | ... | null
version(); // "0.1.0"
编辑
可编辑句柄在保存时保留所有未修改的 OPC 部件。仅支持 DOCX、XLSX、PPTX。
import { EditableDocument } from 'office-oxide';
using ed = EditableDocument.open('template.docx');
const n = ed.replaceText('{{name}}', 'Alice');
console.log(`${n} 处替换`);
ed.save('out.docx');
写入 XLSX 单元格
import { EditableDocument } from 'office-oxide';
using wb = EditableDocument.open('budget.xlsx');
wb.setCell(0, 'A1', 'Total'); // 字符串
wb.setCell(0, 'B1', 42.5); // 数字
wb.setCell(0, 'C1', true); // 布尔
wb.setCell(0, 'D1', null); // 清空
wb.save('budget.xlsx');
sheetIndex 从 0 开始;cellRef 使用标准电子表格记号。
与格式无关的 IR
using doc = Document.open('report.docx');
const ir = doc.toIr();
for (const section of ir.sections) {
console.log(section.title);
for (const el of section.elements) {
// el.kind: "Heading" | "Paragraph" | "Table" | "List" | ...
}
}
字节流管道
import { Document } from 'office-oxide';
const res = await fetch('https://example.com/report.docx');
const data = new Uint8Array(await res.arrayBuffer());
using doc = Document.fromBytes(data, 'docx');
console.log(doc.toMarkdown());
旧版格式
using doc = Document.open('old.xls');
doc.saveAs('modern.xlsx');
CommonJS
const { Document } = require('office-oxide');
const doc = Document.open('file.docx');
try { console.log(doc.plainText()); } finally { doc.close(); }
错误
失败会抛出带数字 code 和 operation 字段的 OfficeOxideError:
import { Document, OfficeOxideError } from 'office-oxide';
try {
using doc = Document.open('missing.docx');
} catch (e) {
if (e instanceof OfficeOxideError) {
console.error(`code=${e.code} op=${e.operation}`);
} else {
throw e;
}
}
| Code | 含义 |
|---|---|
| 0 | OK |
| 1 | 参数无效 |
| 2 | IO 错误 |
| 3 | 解析错误 |
| 4 | 提取失败 |
| 5 | 内部错误 |
| 6 | 不支持的格式 |
故障排查
| 症状 | 解决 |
|---|---|
office-oxide: failed to load native library |
把 OFFICE_OXIDE_LIB 设为绝对路径,或把匹配的预编译库放进包内。 |
koffi: ABI mismatch |
平台/架构与 Node 进程不匹配 — 重新安装或获取新的预编译。 |
TypeError: data must be a Uint8Array or Buffer |
Document.fromBytes 只接受二进制类型。用 Buffer.from(base64, 'base64')。 |
Document is closed |
在 close() 之后或离开 using 作用域之后调用了方法。开新句柄。 |
旧版 .doc 能打开但显示乱码 |
加密的 Word 97 文档不会被解密 — 请先用 LibreOffice 解密。 |