C#从 Excel 文件中提取图片与嵌入文件

C#从 Excel 文件中提取图片与嵌入文件

编码文章call10242025-04-27 14:12:367A+A-

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

准备工作

你需要安装以下 NuGet 包:

  • DocumentFormat.OpenXml

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

Install-Package DocumentFormat.OpenXml

代码结构

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

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

打开 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<ImagePart>())
                    {
                        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, #34;extracted_{Guid.NewGuid()}{extractedFile.Extension}");
            // 保存文件
            File.WriteAllBytes(filePath, extractedFile.Data);
            Console.WriteLine(#34;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, #34;image_{Guid.NewGuid()}{fileExtension}");
    using (var stream = imagePart.GetStream())
    {
        using (var fileStream = new FileStream(filePath, FileMode.Create))
        {
            // 将图片流内容复制到文件
            stream.CopyTo(fileStream);
        }
    }

    Console.WriteLine(#34;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 (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;
}
}
}

运行程序

另存文件

总结

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

点击这里复制本文地址 以上内容由文彬编程网整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!
qrcode

文彬编程网 © All Rights Reserved.  蜀ICP备2024111239号-4