Technical Article

在 Delphi 中从 Excel 文件提取文档摘要信息

在自动化管道中处理大批量 Excel 电子表格时,您很少会为了弄清楚它是什么而将整个文档加载到内存中。通常,嵌入在文件中的元数据(作者、标题、创建日期和自定义属性)足以为文档进行路由、索引或拒绝。在 Microsoft Office 世界中,这种元数据被称为文档摘要信息(Document Summary Information)

在不依赖 OLE 自动化(需要主机上安装 Excel)的情况下在 Delphi 中原生地提取这些信息,需要直接解析底层文件结构。在本文中,我们将探讨文档摘要在 Excel 文件中的工作原理,以及如何使用原始流解析有效地提取它们。

了解 Excel 元数据流

从历史上看,较旧的 Excel 文件(.xls)以 OLE 复合文档(OLE Compound Document)格式存储,实际上充当包含流和存储的迷你文件系统。元数据存放在两个特定的流中:

  • SummaryInformation:包含标准属性,如标题、主题、作者、关键字和修订号。
  • DocumentSummaryInformation:包含扩展属性,如公司、经理和用户自定义的属性。

现代 Excel 文件(.xlsx)使用 Office Open XML(OOXML)格式,这是一种压缩的 XML 结构。这里的元数据位于 docProps/core.xmldocProps/app.xmldocProps/custom.xml 中。一个稳健的 Delphi 解析组件必须无缝处理这两种内部结构,同时向开发人员公开统一的 API。

在 Delphi 中解析 OLE 复合文档

要在不使用第三方工具的情况下从传统的 .xls 文件读取 SummaryInformation,您需要解析 OLE 结构化存储(OLE Structured Storage)。Microsoft 通过 COM 接口 IPropertySetStorage 公开了这一点。以下是一个避免启动 Excel 的原始 Delphi 实现:

uses
  System.SysUtils, System.Win.ComObj, Winapi.ActiveX, Winapi.Windows;

procedure ExtractXlsSummaryInfo(const FileName: string);
var
  Stg: IStorage;
  PropSetStg: IPropertySetStorage;
  PropStg: IPropertyStorage;
  PropSpec: TPropSpec;
  PropVariant: TPropVariant;
  Hr: HRESULT;
begin
  // Open the OLE Compound Document
  Hr := StgOpenStorage(PWideChar(WideString(FileName)), nil,
    STGM_READ or STGM_SHARE_DENY_WRITE, nil, 0, Stg);
    
  if Failed(Hr) then
    raise Exception.Create('Failed to open OLE storage. File may not be a valid .xls document.');

  // Query for the property set storage interface
  if Stg.QueryInterface(IPropertySetStorage, PropSetStg) = S_OK then
  begin
    // Open the SummaryInformation stream (FMTID_SummaryInformation)
    Hr := PropSetStg.Open(FMTID_SummaryInformation, STGM_READ or STGM_SHARE_EXCLUSIVE, PropStg);
    if Succeeded(Hr) then
    begin
      // Read the Author property (PIDSI_AUTHOR = 4)
      PropSpec.ulKind := PRSPEC_PROPID;
      PropSpec.propid := PIDSI_AUTHOR;
      
      if PropStg.ReadMultiple(1, @PropSpec, @PropVariant) = S_OK then
      begin
        if PropVariant.vt = VT_LPSTR then
          Writeln('Author: ', string(AnsiString(PropVariant.pszVal)));
        PropVariantClear(PropVariant);
      end;
    end;
  end;
end;

使用 HotXLS 进行编程提取

虽然 Windows COM API 适用于 .xls 文件,但它不适用于现代 .xlsx 文件(即 ZIP 压缩包)。此外,跨平台(例如,在 Linux 或通过 FireMonkey 在 macOS 上)使用 COM API 是不可能的。HotXLS 组件的最新更新引入了专用单元(例如 lxXlsSummary),以完全原生地在 Delphi 代码中隔离和优化跨这两种格式读取这些摘要流的操作。

跨平台示例

使用 XlsReadDocumentSummaryInformationXlsReadSummaryInformation 接口,您可以快速从 .xls.xlsx 中获取元数据字符串,而无需担心底层文件系统架构。

uses
  lxXlsSummary;

var
  Summary: TXlsSummaryInfo;
  ExtendedInfo: TXlsDocumentSummaryInfo;
begin
  // Extract standard summary from an OOXML format seamlessly
  Summary := XlsReadSummaryInformation('C:\Data\FinancialReport.xlsx');
  try
    Writeln('Title: ', Summary.Title);
    Writeln('Author: ', Summary.Author);
    Writeln('Creation Date: ', DateTimeToStr(Summary.CreateTime));
  finally
    Summary.Free;
  end;

  // Extract extended document summary
  ExtendedInfo := XlsReadDocumentSummaryInformation('C:\Data\FinancialReport.xlsx');
  try
    Writeln('Company: ', ExtendedInfo.Company);
    Writeln('Manager: ', ExtendedInfo.Manager);
  finally
    ExtendedInfo.Free;
  end;
end;

为什么专用的摘要提取很重要

这种方法的主要好处是性能和内存安全。通过避免实例化完整的作簿 DOM(文档对象模型)并仅解析 docProps/core.xml 或 OLE 属性流,您的应用程序内存占用将保持极小。如果您正在对网络共享中的 10,000 个 Excel 文件建立索引,尝试完全解析每一个文件将会耗尽您的内存并花费数小时。而专用的摘要提取则在几秒钟内就能完成相同的任务。

此外,原生地读取流可确保您的应用程序能作为后台服务或在无头(headless)Linux 服务器上运行,而无需调用 Excel.exe,这是现代可扩展架构的关键要求。

注意:HotXLS VCL Component 中提供了全面的 Excel 解析和元数据提取工具。