Podczas przetwarzania dużych partii arkuszy kalkulacyjnych Excel w zautomatyzowanym potoku, rzadko chcemy ładować cały dokument do pamięci tylko po to, aby dowiedzieć się, co w nim jest. Często metadane osadzone w pliku (autor, tytuł, data utworzenia i niestandardowe właściwości) wystarczą do odpowiedniego skierowania, zindeksowania lub odrzucenia dokumentu. W świecie Microsoft Office metadane te znane są jako informacje podsumowujące dokument (Document Summary Information).
Natywna ekstrakcja tych informacji w Delphi bez polegania na automatyzacji OLE (która wymaga zainstalowanego programu Excel na maszynie hosta) wymaga bezpośredniego parsowania struktury pliku. W tym artykule przyjrzymy się, jak działają podsumowania dokumentów w plikach Excel i jak można je wydajnie wyodrębnić przy użyciu bezpośredniego parsowania strumienia.
Zrozumienie strumieni metadanych w plikach Excel
Historycznie, starsze pliki Excel (.xls) są przechowywane w formacie OLE Compound Document, co w praktyce oznacza, że działają one jako mini-systemy plików zawierające strumienie i magazyny (storages). Metadane znajdują się w dwóch konkretnych strumieniach:
SummaryInformation: Zawiera standardowe właściwości, takie jak tytuł, temat, autor, słowa kluczowe i numer rewizji.DocumentSummaryInformation: Zawiera rozszerzone właściwości, takie jak firma, menedżer oraz niestandardowe właściwości zdefiniowane przez użytkownika.
Nowoczesne pliki Excel (.xlsx) używają formatu Office Open XML (OOXML), który ma strukturę skompresowanych plików XML (ZIP). Metadane znajdują się tutaj w plikach docProps/core.xml, docProps/app.xml oraz docProps/custom.xml. Niezawodny komponent parsujący w Delphi musi płynnie obsługiwać obie struktury wewnętrzne, udostępniając jednocześnie programiście ujednolicone API.
Parsowanie dokumentów OLE Compound w Delphi
Aby odczytać informacje z SummaryInformation z przestarzałego pliku `.xls` bez narzędzi innych firm, musisz sparsować struktury magazynowania OLE (OLE Structured Storage). Microsoft udostępnia to poprzez interfejs COM IPropertySetStorage. Oto surowa implementacja w Delphi, która unika uruchamiania programu 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;
Programowa ekstrakcja z użyciem HotXLS
Podczas gdy API COM w systemie Windows działa z plikami `.xls`, nie współpracuje ono z nowoczesnymi plikami `.xlsx` (które są archiwami ZIP). Co więcej, wieloplatformowe użycie API COM (np. na systemach Linux lub macOS za pośrednictwem FireMonkey) jest niemożliwe. Niedawne aktualizacje komponentu HotXLS wprowadziły dedykowane moduły (np. lxXlsSummary), aby wyizolować i zoptymalizować odczyt strumieni podsumowujących dla obu formatów, robione całkowicie natywnie w kodzie Delphi.
Przykład wieloplatformowy
Korzystając z interfejsów XlsReadDocumentSummaryInformation oraz XlsReadSummaryInformation, możesz bezproblemowo pobrać ciągi tekstowe z metadanymi zarówno z formatu `.xls`, jak i `.xlsx`, nie przejmując się architekturą bazowego systemu plików.
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;
Dlaczego dedykowana ekstrakcja podsumowań ma znaczenie
Główną zaletą tego podejścia jest wydajność i bezpieczeństwo pamięci. Dzięki uniknięciu pełnego instancjowania modelu obiektowego dokumentu (DOM) skoroszytu i parsowaniu wyłącznie pliku docProps/core.xml lub strumieni właściwości OLE, obciążenie pamięci przez aplikację pozostaje niezwykle małe. Jeśli zechcesz indeksować 10 000 plików Excel zgromadzonych na udziale sieciowym, próba pełnego sparsowania każdego z nich wyczerpie pamięć i potrwa wiele godzin. Dedykowana ekstrakcja podsumowań wykonuje to samo zadanie w kilka sekund.
Ponadto, natywny odczyt strumieni gwarantuje, że Twoja aplikacja może działać w tle jako usługa, a nawet na serwerze Linux pozbawionym interfejsu graficznego, bez jakiegokolwiek wywoływania procesu Excel.exe, co jest kluczowym wymogiem dla nowoczesnych i skalowalnych architektur.
Uwaga: Wszechstronne narzędzia do parsowania programu Excel i ekstrakcji metadanych są dostępne w bibliotece HotXLS VCL Component.