Technical Article

Udtrækning af dokumentsummeringsinformation fra Excel-filer i Delphi

Når du behandler store partier af Excel-regneark i en automatiseret pipeline, ønsker du sjældent at indlæse hele dokumentet i hukommelsen bare for at finde ud af, hvad det er. Ofte er de metadata, der er indlejret i filen, forfatter, titel, oprettelsesdato og brugerdefinerede egenskaber, nok til at dirigere, indeksere eller afvise dokumentet. I Microsoft Office-verdenen er disse metadata kendt som Document Summary Information.

At udtrække denne information direkte i Delphi uden at stole på OLE-automatisering (som kræver, at Excel er installeret på værtsmaskinen) kræver, at den underliggende filstruktur parses direkte. I denne artikel vil vi se på, hvordan dokumentsummeringer fungerer i Excel-filer, og hvordan man udtrækker dem effektivt ved hjælp af rå stream-parsing.

Forståelse af Excel metadata-streams

Historisk set gemmes ældre Excel-filer (.xls) i OLE Compound Document-formater, som effektivt fungerer som små filsystemer, der indeholder streams og lagre. Metadataene er placeret i to specifikke streams:

  • SummaryInformation: Indeholder standardegenskaber som titel, emne, forfatter, nøgleord og revisionsnummer.
  • DocumentSummaryInformation: Indeholder udvidede egenskaber som virksomhed, leder og brugerdefinerede egenskaber.

Moderne Excel-filer (.xlsx) bruger Office Open XML (OOXML)-formatet, som er en zippet XML-struktur. Metadataene her findes i docProps/core.xml, docProps/app.xml og docProps/custom.xml. En robust Delphi-parsingkomponent skal gnidningsløst håndtere begge interne strukturer, mens den eksponerer en samlet API for udvikleren.

Parsing af OLE Compound Documents i Delphi

For at læse SummaryInformation fra en ældre .xls-fil uden tredjepartsværktøjer, skal du parse OLE Structured Storage. Microsoft eksponerer dette gennem COM-grænsefladen IPropertySetStorage. Her er en rå Delphi-implementering, der undgår at starte Excel:

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;

Programmatisk udtrækning med HotXLS

Selvom Windows COM API fungerer for .xls-filer, fungerer det ikke for moderne .xlsx-filer (som er ZIP-arkiver). Desuden er brug af COM API på tværs af platforme (f.eks. på Linux eller macOS via FireMonkey) umuligt. Nylige opdateringer til HotXLS-komponenten introducerede dedikerede enheder (f.eks. lxXlsSummary) for at isolere og optimere læsningen af disse summerings-streams på tværs af begge formater, helt og holdent i indfødt Delphi-kode.

Et tværplatformseksempel

Ved hjælp af XlsReadDocumentSummaryInformation og XlsReadSummaryInformation-grænsefladerne kan du hurtigt gribe metadata-strengene fra både .xls og .xlsx uden at bekymre dig om den underliggende filsystemarkitektur.

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;

Hvorfor dedikeret udtrækning af summering er vigtig

Den primære fordel ved denne tilgang er ydeevne og hukommelsessikkerhed. Ved at undgå instantiering af den fulde arbejdsbog-DOM (Document Object Model) og kun parse docProps/core.xml eller OLE-egenskabs-streams, forbliver din applikations fodaftryk utroligt lille. Hvis du indekserer 10.000 Excel-filer på tværs af et netværksdrev, vil forsøg på at parse hver enkelt fuldt ud belaste din hukommelse og tage timer. Dedikeret udtrækning af summering fuldfører den samme opgave på sekunder.

Desuden sikrer indfødt læsning af streams, at din applikation kan køre som en baggrundstjeneste eller på en hovedløs Linux-server uden nogensinde at aktivere Excel.exe, hvilket er et kritisk krav for moderne skalerbare arkitekturer.

Bemærk: Omfattende Excel-parsing og værktøjer til metadataudtrækning er tilgængelige i HotXLS VCL Component.