编辑
2025-02-03
C# 应用
00
请注意,本文编写于 92 天前,最后修改于 92 天前,其中某些信息可能已经过时。

目录

准备工作
代码结构
打开 Excel 文件
待读出的excel文件
提取 OLE 嵌入对象
提取图片
辅助方法和工具函数
运行程序
另存文件
总结

本文将介绍如何使用 C# 和 OpenXml SDK,从 Excel 文件中提取图片和嵌入对象。我们将以一个包含代码示例的完整项目为例,详细介绍实现过程。

准备工作

你需要安装以下 NuGet 包:

  • DocumentFormat.OpenXml

你可以通过 NuGet 安装这些依赖包。在 Visual Studio 的“工具” -> “NuGet 包管理器” -> “包管理器控制台”中运行以下命令:

Bash
Install-Package DocumentFormat.OpenXml

代码结构

本文将从以下几个方面展开:

  1. 打开 Excel 文件
  2. 提取 OLE 嵌入对象
  3. 提取图片
  4. 辅助方法和工具函数

打开 Excel 文件

我们首先需要打开 Excel 文件并遍历其工作表部分,为后续的提取操作做准备。

待读出的excel文件

image.png

C#
static void Main(string[] args) { // Excel 文件路径 var filePath = "1.xlsx"; // 保存提取内容的目录 var outputDir = @"d:\test"; // 打开 Excel 文件 using (SpreadsheetDocument document = SpreadsheetDocument.Open(filePath, false)) { // 遍历所有工作表部件 foreach (var worksheetPart in document.WorkbookPart.WorksheetParts) { // 提取 OLE 嵌入对象 foreach (var oleObjectPart in worksheetPart.EmbeddedObjectParts) { ExtractOLEObject(oleObjectPart, outputDir); } // 提取图片 foreach (var imagePart in worksheetPart.DrawingsPart?.ImageParts ?? Enumerable.Empty<ImagePart>()) { ExtractImage(imagePart, outputDir); } } } }

提取 OLE 嵌入对象

我们定义一个方法 ExtractOLEObject 来提取并保存 OLE 嵌入对象。该方法对每个嵌入对象进行分析,并将识别出的文件保存到输出目录。

C#
private static void ExtractOLEObject(EmbeddedObjectPart oleObjectPart, string outputDir) { // 获取嵌入对象的流 using (var stream = oleObjectPart.GetStream()) { // 读取全部字节 var oleBytes = ReadAllBytes(stream); // 提取文件 var extractedFile = ExtractFileFromOleObject(oleBytes); if (extractedFile != null) { // 生成文件路径 var filePath = Path.Combine(outputDir, $"extracted_{Guid.NewGuid()}{extractedFile.Extension}"); // 保存文件 File.WriteAllBytes(filePath, extractedFile.Data); Console.WriteLine($"Saved embedded object as {filePath}"); } else { Console.WriteLine("No known file signature found."); } } }

提取图片

我们同样定义一个方法 ExtractImage 来提取并保存图片。

C#
private static void ExtractImage(ImagePart imagePart, string outputDir) { // 获取图片扩展名 string fileExtension = GetImageExtension(imagePart.ContentType); if (fileExtension == null) { Console.WriteLine("Unknown image format."); return; } // 生成文件路径 var filePath = Path.Combine(outputDir, $"image_{Guid.NewGuid()}{fileExtension}"); using (var stream = imagePart.GetStream()) { using (var fileStream = new FileStream(filePath, FileMode.Create)) { // 将图片流内容复制到文件 stream.CopyTo(fileStream); } } Console.WriteLine($"Saved image as {filePath}"); } private static string GetImageExtension(string contentType) { // 映射 content type 到文件扩展名 switch (contentType) { case "image/jpeg": return ".jpg"; case "image/png": return ".png"; case "image/gif": return ".gif"; case "image/bmp": return ".bmp"; default: return null; } }

辅助方法和工具函数

我们还需要一些辅助方法来处理流、读取和识别 OLE 对象中的文件。

C#
private static byte[] ReadAllBytes(Stream input) { // 阅读流中的所有字节 using (var ms = new MemoryStream()) { input.CopyTo(ms); return ms.ToArray(); } } private static ExtractedFile ExtractFileFromOleObject(byte[] oleBytes) { // 定义文件签名以识别文件类型 var fileSignatures = new[] { new FileSignature("PDF", Encoding.ASCII.GetBytes("%PDF-"), null, ".pdf"), new FileSignature("DOCX", Encoding.ASCII.GetBytes("PK\x03\x04"), "word/", ".docx"), new FileSignature("ZIP", Encoding.ASCII.GetBytes("PK\x03\x04"), null, ".zip") }; // 遍历所有文件签名 foreach (var signature in fileSignatures) { // 根据签名查找文件 var fileData = FindFileWithSignature(oleBytes, signature); if (fileData != null) { return new ExtractedFile { Data = fileData, Extension = signature.Extension }; } } return null; } private static byte[] FindFileWithSignature(byte[] data, FileSignature signature) { // 查找签名的偏移量 int offset = FindSignatureOffset(data, signature.Signature); if (offset != -1) { if (signature.Extension == ".pdf") { return ExtractPdfData(data, offset); } else if (signature.Extension == ".docx") { return ExtractOpenXmlData(data, offset, signature.InternalSignature); } else if (signature.Extension == ".zip") { return ExtractZipData(data, offset); } } return null; } private static int FindSignatureOffset(byte[] data, byte[] signature) { // 查找数据中签名的位置 for (int i = 0; i <= data.Length - signature.Length; i++) { if (IsMatch(data, i, signature)) { return i; } } return -1; } private static bool IsMatch(byte[] data, int offset, byte[] signature) { // 验证指定偏移位置的签名是否匹配 for (int i = 0; i < signature.Length; i++) { if (data[offset + i] != signature[i]) { return false; } } return true; } private static byte[] ExtractPdfData(byte[] data, int offset) { const string pdfEnd = "%%EOF"; // 查找 PDF 文件结束标记 for (int i = offset; i <= data.Length - pdfEnd.Length; i++) { if (IsMatch(data, i, Encoding.ASCII.GetBytes(pdfEnd))) { int pdfLength = i + pdfEnd.Length - offset; var pdfData = new byte[pdfLength]; Array.Copy(data, offset, pdfData, 0, pdfLength); return pdfData; } } return null; } private static byte[] ExtractOpenXmlData(byte[] data, int offset, string internalSignature) { // 提取 OpenXML 格式文件 using (var ms = new MemoryStream(data, offset, data.Length - offset)) using (var archive = new System.IO.Compression.ZipArchive(ms, System.IO.Compression.ZipArchiveMode.Read)) { bool found = archive.Entries.Any(entry => entry.FullName.StartsWith(internalSignature)); if (found) { return data.Skip(offset).ToArray(); } } return null; } private static byte[] ExtractZipData(byte[] data, int offset) { return data.Skip(offset).ToArray(); } private static byte[] ExtractImageData(byte[] data, int offset) { var imageData = new byte[data.Length - offset]; Array.Copy(data, offset, imageData, 0, imageData.Length); return imageData; } } public class ExtractedFile { public byte[] Data { get; set; } public string Extension { get; set; } } public class FileSignature { public string Name { get; } public byte[] Signature { get; } public string InternalSignature { get; } public string Extension { get; } public FileSignature(string name, byte[] signature, string internalSignature, string extension) { Name = name; Signature = signature; InternalSignature = internalSignature; Extension = extension; } } }

运行程序

image.png

另存文件

image.png

总结

通过使用本文提供的代码示例,我们可以轻松从 Excel 文件中提取各种嵌入对象和图片。希望这篇文章对你有所帮助!如果你有任何问题或建议,欢迎与我们联系。

本文作者:rick

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!