如何通过C#的OpenXML从Docx文件中提取OLE包?
解决OpenXML提取docx中OLE包(ZIP文件)的问题
我来帮你搞定这个问题!你现在遇到的问题有两个核心点,咱们一步步拆解解决:
1. 你的原代码的明显错误
先看你写的这段代码片段:
System.IO.Stream dt = item.GetStream(FileMode.OpenOrCreate); BinaryWriter writer = new BinaryWriter(dt); byte[] bt = new byte[dt.Length]; using (FileStream fs = File.Open($"C:\\Users\\宇宙无敌帅小伙\\Desktop\\{cnt}.zip", FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite)) { fs.Write(bt, 0, bt.Length); }
你创建了bt数组,但完全没有从dt流里读取任何数据到数组中,所以写入的文件全是空字节,自然无法打开。哪怕你修复了这个问题,直接保存的还是OLE容器流,不是原始ZIP文件,依然会损坏。
2. 核心问题:OLE包的结构原理
当你在Word中插入ZIP作为OLE包时,Word会把原始文件封装在OLE复合文档容器里,EmbeddedObjectPart存储的就是这个容器数据,而非原始ZIP文件。要拿到真实的ZIP内容,必须解析这个OLE容器,提取里面嵌套的实际文件流。
解决方案:使用OpenMcdf解析OLE复合文档
最可靠的方法是用专门处理OLE复合文档的OpenMcdf库,它能轻松解析容器结构,精准提取我们需要的内容。
步骤1:安装OpenMcdf NuGet包
在你的项目中安装NuGet包OpenMcdf(这是处理OLE复合文档的成熟、稳定库)。
步骤2:修正后的提取代码
using System; using System.IO; using DocumentFormat.OpenXml.Packaging; using CompoundFileBinaryFormat; class Program { static void Main(string[] args) { string filepath = @"C:\Users\宇宙无敌帅小伙\Desktop\test.docx"; ExtractOlePackageFiles(filepath); } public static void ExtractOlePackageFiles(string docxPath) { try { using (WordprocessingDocument docx = WordprocessingDocument.Open(docxPath, false)) { int cnt = 0; foreach (EmbeddedObjectPart olePart in docx.MainDocumentPart.EmbeddedObjectParts) { // 打开OLE复合文档流 using (Stream oleStream = olePart.GetStream(FileMode.Open)) using (CompoundFile cf = new CompoundFile(oleStream)) { // 找到OLE包中的实际内容流(通常名为"Package") CfStream packageStream = cf.RootStorage.GetStream("Package"); if (packageStream != null) { // 读取流内容并保存为ZIP文件 byte[] fileContent = packageStream.GetData(); string outputPath = $"C:\\Users\\宇宙无敌帅小伙\\Desktop\\{cnt}.zip"; File.WriteAllBytes(outputPath, fileContent); Console.WriteLine($"成功提取文件:{outputPath}"); } cnt++; } } } } catch (Exception e) { Console.WriteLine($"提取失败:{e.Message}"); Console.WriteLine(e.StackTrace); } } }
代码说明
- 用
CompoundFile类打开EmbeddedObjectPart的流,解析OLE复合文档的内部结构。 - OLE包的实际文件内容通常存储在名为
Package的子流中,通过cf.RootStorage.GetStream("Package")精准定位这个流。 - 最后把流中的字节写入文件,得到的就是你插入的原始ZIP文件了。
备选方案:手动解析OLE头(不依赖第三方库)
如果你不想引入第三方库,可以尝试手动跳过OLE头(注意:这种方法兼容性稍差,不同版本的OLE结构可能有差异):
public static void ExtractOlePackageWithoutLibrary(string docxPath) { try { using (WordprocessingDocument docx = WordprocessingDocument.Open(docxPath, false)) { int cnt = 0; foreach (EmbeddedObjectPart olePart in docx.MainDocumentPart.EmbeddedObjectParts) { using (Stream oleStream = olePart.GetStream(FileMode.Open)) using (FileStream fs = new FileStream($"C:\\Users\\宇宙无敌帅小伙\\Desktop\\{cnt}.zip", FileMode.Create)) { // 跳过OLE复合文档头(通常前0x110字节是头信息,具体可能需要根据实际情况调整) oleStream.Seek(0x110, SeekOrigin.Begin); oleStream.CopyTo(fs); } Console.WriteLine($"提取文件:{cnt}.zip"); cnt++; } } } catch (Exception e) { Console.WriteLine($"错误:{e.Message}"); } }
这种方法通过跳过固定长度的OLE头来提取内容,但如果OLE结构有变化,可能会失败,所以更推荐使用OpenMcdf的方案。
内容的提问来源于stack exchange,提问作者lishuaige




