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

本文将介绍如何使用 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())
                    {
                        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 文件中提取各种嵌入对象和图片。希望这篇文章对你有所帮助!如果你有任何问题或建议,欢迎与我们联系。

原文链接:,转发请注明来源!