Artykuł techniczny

Ustawianie właściwości dokumentu Excel w Delphi z HotXLS

Arkusz niesie dwie warstwy tożsamości. Jest siatka komórek i są metadane dokumentu, które jadą obok niej: tytuł, autor, firma, słowa kluczowe, znaczniki czasu. Excel nigdy nie pokazuje tej drugiej warstwy w siatce, a jednak to właśnie ją indeksuje Windows Search, to ją czyta SharePoint, żeby zatytułować dokument, i to według niej porządkuje akta system zarządzania dokumentacją. Kiedy generowany skoroszyt dziedziczy Author i Title po szablonie, z którego powstał, każdy system niżej w potoku zapisuje projektanta szablonu jako autora czterech tysięcy zestawień klienckich. Metadane są poprawne nigdzie, a sprawdzane wszędzie

HotXLS wystawia tę warstwę jako zwykłe właściwości na poziomie skoroszytu w obu swoich silnikach: fasadzie BIFF dla .xls i fasadzie OOXML dla .xlsx. Odczytujesz pole po otwarciu pliku i zapisujesz pole przed jego zapisaniem. To biblioteka decyduje, w którym fizycznym kontenerze wyląduje wartość. Zanim napiszesz generator, warto zrozumieć, które pola każdy z formatów naprawdę obsługuje, gdzie te pola fizycznie mieszkają i jaka jedna reguła bramkująca rozstrzyga, czy .xlsx zapisze jakiekolwiek metadane

Dwa formaty, dwa modele składowania

Powodem, dla którego biblioteka arkuszowa potrzebuje dwóch implementacji metadanych, i powodem, dla którego niedokończone narzędzia stemplują jeden format poprawnie, a o drugim zapominają, jest to, że .xls i .xlsx trzymają swoje właściwości w niepowiązanych miejscach. Skoroszyt BIFF zapisuje je do strumieni pliku złożonego OLE, głównie do zestawu właściwości SummaryInformation, który jest starszy niż sam Excel, obok rekordu WRITEACCESS w strumieniu, nazywającego tego, kto ostatni zapisał plik. Skoroszyt OOXML trzyma je jako części XML wewnątrz pakietu zip, rozdzielone wedle przeznaczenia: docProps/core.xml mieści pola Dublin Core (tytuł, twórca, temat, słowa kluczowe, daty), a docProps/app.xml mieści pola na poziomie aplikacji, takie jak firma i aplikacja generująca, zgodnie z ECMA-376 Part 1

HotXLS spłaszcza oba te modele składowania do bezpośrednich właściwości obiektu skoroszytu. Nigdy nie otwierasz strumienia zestawu właściwości ani nie edytujesz ręcznie części XML. Przypisujesz do skoroszytu ciągi i daty, a właściwy kontener materializuje się dla tego formatu, w którym zapisujesz

Diagram HotXLS dla Delphi porównujący składowanie w BIFF SummaryInformation z częściami docProps OOXML dla właściwości dokumentu Excel
HotXLS spłaszcza dwa niepowiązane modele składowania do jednej powierzchni właściwości skoroszytu — silnik wybiera fizyczny kontener w chwili zapisu pliku

Stemplowanie generowanych skoroszytów z rekordu biznesowego

Po stronie XLSX TXLSXWorkbook udostępnia Title, Subject, Author, Keywords, Description, Category, LastModifiedBy, Company, Application i AppVersion jako ciągi, plus Created oraz Modified jako wartości TDateTime, gdzie zero oznacza brak ustawienia. Reguła, która zamyka dziurę dziedziczenia, mieści się w jednym zdaniu: przypisuj każde pole przy każdym przebiegu, biorąc wartości z rekordu biznesowego, zamiast ufać temu, co akurat niósł szablon

var
  Book: TXLSXWorkbook;
begin
  Book := TXLSXWorkbook.Create;
  try
    if Book.Open('statement-template.xlsx') <> 1 then
      raise Exception.Create('Template not available');

    // Nadpisz każde pole: wszystko, czego nie dotkniesz,
    // jest dziedziczone po tym, kto zaprojektował szablon.
    Book.Title := 'Account Statement 2026-06 / ACME Corp';
    Book.Subject := 'Monthly account statement';
    Book.Author := 'Billing Service 4.2';
    Book.LastModifiedBy := 'Billing Service 4.2';
    Book.Company := 'Northwind Financial';
    Book.Category := 'Customer Delivery';
    Book.Keywords := 'statement;billing;2026-06;acct-10024';
    Book.Description := 'Generated document - manual edits are not retained';
    Book.Created := Now;
    Book.Modified := Now;

    Book.SaveAs('statement-10024.xlsx');
  finally
    Book.Free;
  end;
end;

Pole Keywords wynagradza więcej namysłu, niż zwykle dostaje. Infrastruktura wyszukiwania indeksuje je dosłownie, tak samo Windows Search, SharePoint, jak i większość produktów DMS, więc konwencja rozdzielana średnikami, niosąca numer konta i okres, zamienia każdy dostarczony skoroszyt w odnajdywalny rekord bez ani jednego zapytania do bazy. Ten sam zasięg jest haczykiem. Właściwości podróżują z każdą kopią pliku, daleko poza kontrolę dostępu systemu, który je zapisał, więc dane osobowe do nich nie należą

Para znaczników czasu niesie semantykę wartą utrwalenia w polityce, a nie zostawiania nawykowi. Created powinno znaczyć chwilę, w której twój potok wygenerował dokument, i potem pozostać zamrożone. Modified to pole, które Excel aktualizuje, ilekroć odbiorca zapisze plik, więc rozbieżność między nimi po dostawie jest pozytywnym dowodem, że ktoś edytował skoroszyt dalej w łańcuchu, a to rozstrzyga niejeden spór o to, czyje liczby naprawdę niesie przekazany dalej arkusz. Jedna pułapka kryje się w stanie nieustawionym: jest nim dosłowna wartość zero, a nie wyjątek i nie null, więc kod audytowy musi jawnie testować zero. Sformatuj nieustawione TDateTime bez tej ochrony, a twoje logi zapełnią się pewną siebie, błędną datą z grudnia 1899

DocPropsTouched: skoroszyt, który wychodzi bez docProps

Flaga tylko do odczytu, DocPropsTouched, bramkuje zapisywanie właściwości w XLSX. Skoroszyt, w którym nigdy nie przypisano żadnej właściwości, nie produkuje w ogóle żadnych części docProps; HotXLS odmawia zapisania pustego szkieletu metadanych. Zachowanie jest schludne i ma dwie konsekwencje warte uwzględnienia w projekcie

Kod przyjmujący pliki po stronie konsumenta nie może zakładać, że core.xml istnieje w każdym pakiecie. Narzędzie, które twardo go wymaga, odrzuci całkowicie poprawne pliki minimalne. A jeśli twoja postawa zgodnościowa żąda, by każdy wychodzący dokument niósł przynajmniej tożsamość generatora, żądanie to staje się kodem, a nie właściwością formatu: przypisuj Application i Author bezwarunkowo w ścieżce zapisu, bo nietknięty skoroszyt jest w pełni legalny wedle specyfikacji, po cichu łamiąc twoją politykę

Diagram przepływu HotXLS dla Delphi pokazujący flagę DocPropsTouched bramkującą wyjście docProps w zapisywanych skoroszytach XLSX
DocPropsTouched bramkuje zapis docProps w XLSX — przypisuj Application i Author bezwarunkowo, gdy polityka wymaga tożsamości generatora

Powierzchnia starszego XLS i pułapka Comments

Fasada BIFF niesie starszy, mniejszy zestaw pól: Title, Subject, Author, Keywords, Comments, Company i Manager, plus LastSavedBy, alias UserName, który zapisuje rekord WRITEACCESS wyświetlany przez Excela, gdy plik trzyma zablokowany inny użytkownik

var
  Legacy: IXLSWorkbook;     // interfejs zliczany referencyjnie: bez ręcznego Free
begin
  Legacy := TXLSWorkbook.Create;
  if Legacy.Open('archive-1999.xls') <= 0 then
    raise Exception.Create('Cannot open archive file');

  Legacy.Title := 'FY1999 ledger (migrated copy)';
  Legacy.Author := 'Archive Migration Batch';
  Legacy.Company := 'Northwind Financial';
  Legacy.Comments := 'Migrated 2026-06-11; source retained in cold storage';
  Legacy.LastSavedBy := 'migration-svc';   // rekord BIFF WRITEACCESS

  Legacy.SaveAs('archive-1999-stamped.xls');
end;

Jedna kolizja nazw powoduje nawracające zamieszanie. Właściwość Comments na poziomie dokumentu to tutaj swobodna uwaga tekstowa pokazywana w oknie właściwości pliku. Nie ma nic wspólnego z komentarzami w komórkach, które są obiektami warstwy rysunkowej doczepionymi do zakresów przez zupełnie odrębne API. Przegląd kodu, który przyjmuje „przecież już zapisujemy Comments” bez sprawdzenia, o które chodzi, przyjął twierdzenie o niewłaściwej funkcji, a zdarza się to częściej, niż wspólna nazwa by sugerowała. Te dwie rzeczy dzielą osiem liter i ani jednego bajtu składowania

Odczyt metadanych na przyjęciu i luka w sondowaniu

Odczyt jest symetryczny. Po Open te same właściwości wracają wypełnione z pliku, co zamienia audyt metadanych przychodzących skoroszytów w krótką pętlę

var
  Book: TXLSXWorkbook;
begin
  Book := TXLSXWorkbook.Create;
  try
    if Book.Open(FileName) = 1 then
    begin
      Writeln(Format('%s | title="%s" author="%s" created=%s',
        [ExtractFileName(FileName), Book.Title, Book.Author,
         FormatDateTime('yyyy-mm-dd', Book.Created)]));
      if Book.Created = 0 then
        Writeln('  no creation date recorded');
    end;
  finally
    Book.Free;
  end;
end;

Zaplanuj przy okazji obejście jednego ograniczenia. Nie ma sondy wyłącznie do właściwości. GetSheetNames potrafi wypisać arkusze bez wczytywania skoroszytu, ale odczyt Title albo Author oznacza pełne Open, więc segregacja metadanych w dużym archiwum płaci pełny koszt parsowania na każdym pliku. Po stronie BIFF możesz przyciąć ten koszt dla audytów tylko do odczytu, ustawiając _DisableGraphics na prawdę przed otwarciem, co pomija warstwę rysunkową wprost. Pasuje to do pętli, która czyta wyłącznie właściwości i statystyki komórek, i jest dokładnie błędne w chwili, gdy ta sama instancja mogłaby zapisywać, bo pominięta zawartość rysunkowa zostałaby upuszczona. Gdy sama struktura arkuszy potrafi wstępnie odsiać zbiór, a eksporty jednoarkuszowe są oczywistą rzeczą do pominięcia, tanie techniki z naszego artykułu o wypisywaniu arkuszy i lekkiej inspekcji zmniejszają liczbę plików docierających do kosztownego przebiegu. A przy masowym stemplowaniu, gdzie tysiące wyjść się zapisuje, a nie ogląda, wzorce przepustowości po stronie zapisu z naszego artykułu o zapisie strumieniowym dla zadań wsadowych przenoszą się bez zmian, bo przypisanie właściwości nie dokłada do czasu zapisu nic mierzalnego

Przekraczanie formatów i powstrzymywanie wycieku

Właściwości przechodzą czysto pełny cykl wewnątrz jednej fasady: otwórz .xlsx, wyedytuj, zapisz, a zestaw wraca nienaruszony. Przekraczanie formatów jest miejscem, w którym założenie o parzystości pęka, bo zestawy pól BIFF i OOXML nie pokrywają się jeden do jednego. BIFF ma Manager i nie ma znaczników czasu; OOXML ma Category, Description i parę Created/Modified. Konwerter kopiujący na ślepo traci to, czego format docelowy nie zdoła utrzymać, więc mapuj pola jawnie i wpisz to mapowanie do listy kontrolnej konwersji obok wszystkiego innego, co nie przeżywa podróży

Mapa pól HotXLS dla Delphi pokazująca, które właściwości dokumentu Excel przeżywają konwersję międzyformatową między XLS a XLSX
Ślepa kopia międzyformatowa upuszcza każde pole, którego cel nie zdoła utrzymać — zmapuj zestawy właściwości BIFF i OOXML jawnie w liście kontrolnej konwersji

Wyciek, który otwiera dziedziczenie po szablonie, biegnie w drugą stronę: to informacja, której nigdy nie zamierzałeś wysłać. Nazwiska autorów, wewnętrzne etykiety projektów zaparkowane w słowach kluczowych, roboczy tytuł, którego nikt nie zatwierdził. Dyscyplina nadpisywania wszystkiego z powyższego generatora jest całą obroną, a warto ją zweryfikować tak, jak zrobiłby to ktoś z zewnątrz: otwierając okno Properties, do którego dotrze każdy klient, albo rozpakowując .xlsx i czytając docProps/core.xml prosto z pakietu. To, co tam widzisz, jest dokładnie tym, co widzi każdy indekser dalej w łańcuchu

Ta widoczność w dole łańcucha jest też powodem, dla którego kilka pól zasługuje na więcej troski niż reszta. Title, Author, Keywords (które pojawiają się jako Tags) oraz Comments albo Description niosą większość ciężaru indeksowania w SharePoint i Windows Search. Title naprawdę odrębny dla każdego dokumentu, niosący okres i konto, robi dla odnajdywalności więcej niż jakikolwiek schemat nazywania folderów nadbudowany nad nim, a kosztuje jedno przypisanie na zapis

Właściwości dokumentu to najtańszy profesjonalny szlif, jaki może nieść generowany skoroszyt, i najczęściej wysyłana wada, gdy nikt się nimi nie zajmuje. Obie opisane tutaj powierzchnie właściwości należą do HotXLS Delphi Component, który zapisuje je natywnie dla XLS i XLSX bez automatyzacji Excela