C# / .NET Office 库 — 快速上手
OfficeOxide 是 Rust office_oxide 库的 .NET 绑定。它给 .NET 应用提供高速、对内存友好的 DOCX / XLSX / PPTX / DOC / XLS / PPT 解析、转换与编辑能力 — 底层使用 AOT 兼容的 LibraryImport P/Invoke。
安装
dotnet add package OfficeOxide --version 0.1.0
需要 .NET 8 或 .NET 10。NuGet 包在 runtimes/<rid>/native/ 下随附 win-x64、linux-x64、linux-arm64、osx-x64、osx-arm64 的预编译原生库。dotnet publish 会自动把对应的库放在二进制旁边。
读取文档
using OfficeOxide;
using var doc = Document.Open("report.docx");
Console.WriteLine(doc.PlainText());
核心 API
Document 是只读句柄;用 using 释放以归还原生内存。
using OfficeOxide;
using var doc = Document.Open("file.xlsx");
Console.WriteLine(doc.Format); // "xlsx"
Console.WriteLine(doc.PlainText());
Console.WriteLine(doc.ToMarkdown());
Console.WriteLine(doc.ToHtml());
Console.WriteLine(doc.ToIrJson());
doc.SaveAs("file.docx"); // 目标格式从扩展名推断
阻塞 IO 的异步包装:
using var doc = await Document.OpenAsync("huge.pptx", ct);
从字节打开(无需临时文件):
byte[] data = File.ReadAllBytes("report.docx");
using var doc = Document.FromBytes(data, "docx");
format 必须是 "docx"、"xlsx"、"pptx"、"doc"、"xls"、"ppt"。
一次性调用的静态辅助:
string text = OfficeOxide.ExtractText("file.docx");
string md = OfficeOxide.ToMarkdown("file.pptx");
string html = OfficeOxide.ToHtml("file.xlsx");
string? fmt = Document.DetectFormat("mystery.bin"); // 不支持时为 null
Console.WriteLine(Document.Version); // "0.1.0"
编辑
编辑会保留所有未修改的 OPC 部件(图片、图表、关系)。仅支持 DOCX、XLSX、PPTX。
using OfficeOxide;
using var ed = EditableDocument.Open("template.docx");
long n = ed.ReplaceText("{{name}}", "Alice");
Console.WriteLine($"{n} 处替换");
ed.Save("out.docx");
ReplaceText 返回替换次数(XLSX 返回 0 — 改用 SetCell)。
在 DOCX / PPTX 中替换文本
using var ed = EditableDocument.Open("slides.pptx");
ed.ReplaceText("Q3", "Q4");
ed.ReplaceText("2024", "2025");
byte[] bytes = ed.SaveToBytes();
File.WriteAllBytes("slides_q4.pptx", bytes);
设置 XLSX 单元格(四种重载)
using var wb = EditableDocument.Open("budget.xlsx");
wb.SetCell(0u, "A1", "Total"); // 字符串重载
wb.SetCell(0u, "B1", 42.5); // double 重载
wb.SetCell(0u, "C1", true); // bool 重载
wb.SetCellEmpty(0u, "D1"); // 清空
wb.Save("budget.xlsx");
sheetIndex 从 0 开始;cellRef 是标准电子表格记号(A1、AA12)。
与格式无关的 IR
ToIrJson() 返回符合 Rust DocumentIR 形状的 JSON 字符串:
using System.Text.Json;
using var doc = Document.Open("report.docx");
string json = doc.ToIrJson();
using var ir = JsonDocument.Parse(json);
foreach (var section in ir.RootElement.GetProperty("sections").EnumerateArray())
{
if (section.TryGetProperty("title", out var t) && t.ValueKind != JsonValueKind.Null)
Console.WriteLine(t.GetString());
}
字节流管道
using var http = new HttpClient();
byte[] data = await http.GetByteArrayAsync("https://example.com/file.docx");
using var doc = Document.FromBytes(data, "docx");
Console.WriteLine(doc.ToMarkdown());
旧版格式
using var legacy = Document.Open("old.xls");
legacy.SaveAs("modern.xlsx");
AOT / 裁剪
项目设置了 IsAotCompatible=true 和 IsTrimmable=true。所有 P/Invoke 使用 LibraryImport 源生成器,因此 dotnet publish -c Release -p:PublishAot=true 会产出单一自包含可执行文件。
错误
失败抛出带类型化 Code 属性的 OfficeOxideException:
try
{
using var doc = Document.Open("missing.docx");
}
catch (OfficeOxideException ex)
{
Console.WriteLine($"code={ex.Code} op={ex.Operation}");
}
在已 dispose 的句柄上调用方法会抛 ObjectDisposedException。
| Code | 名称 | 含义 |
|---|---|---|
| 0 | Ok |
成功 |
| 1 | InvalidArg |
null / 空 / 错误的 format 字符串 |
| 2 | Io |
文件系统错误 |
| 3 | Parse |
文档损坏 |
| 4 | Extraction |
解析成功但渲染失败 |
| 5 | Internal |
bug — 请提 issue |
| 6 | Unsupported |
扩展名/特性不支持 |
故障排查
| 症状 | 解决 |
|---|---|
DllNotFoundException: office_oxide |
原生库没有被复制到二进制旁。请运行 dotnet publish 而不是 dotnet build。 |
Windows 上 BadImageFormatException |
CPU 架构不匹配 — 部署对应的 win-x64 或 win-arm64 构建。 |
.doc 上 OfficeOxideException 代码 Unsupported |
确认扩展名是小写,或通过 FromBytes 显式传 format。 |
| 裁剪后符号缺失 | 把 OfficeOxide 加入 <TrimmerRootAssembly>。 |
| macOS “无法验证开发者” | 运行 xattr -d com.apple.quarantine /path/to/liboffice_oxide.dylib 或为 bundle 签名。 |