本文将介绍如何使用 C# 和 OpenXml SDK,从 Excel 文件中提取图片和嵌入对象。我们将以一个包含代码示例的完整项目为例,详细介绍实现过程。
准备工作
你需要安装以下 NuGet 包:
- DocumentFormat.OpenXml
你可以通过 NuGet 安装这些依赖包。在 Visual Studio 的“工具” -> “NuGet 包管理器” -> “包管理器控制台”中运行以下命令:
Install-Package DocumentFormat.OpenXml
代码结构
本文将从以下几个方面展开:
- 打开 Excel 文件
- 提取 OLE 嵌入对象
- 提取图片
- 辅助方法和工具函数
打开 Excel 文件
我们首先需要打开 Excel 文件并遍历其工作表部分,为后续的提取操作做准备。
待读出的excel文件
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())
{
ExtractImage(imagePart, outputDir);
}
}
}
}
提取 OLE 嵌入对象
我们定义一个方法 ExtractOLEObject 来提取并保存 OLE 嵌入对象。该方法对每个嵌入对象进行分析,并将识别出的文件保存到输出目录。
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 来提取并保存图片。
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 对象中的文件。
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 ismatchdata i encoding.ascii.getbytespdfend int pdflength='i' pdfend.length - offset var pdfdata='new' bytepdflength array.copydata offset pdfdata 0 pdflength return pdfdata return null private static byte extractopenxmldatabyte data int offset string internalsignature openxml using var ms='new' memorystreamdata offset data.length - offset using var archive='new' system.io.compression.ziparchivems 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;
}
}
}
运行程序
另存文件
总结
通过使用本文提供的代码示例,我们可以轻松从 Excel 文件中提取各种嵌入对象和图片。希望这篇文章对你有所帮助!如果你有任何问题或建议,欢迎与我们联系。