スプレッドシートは2層の身元情報を持ちます。1つはセルのグリッド、もう1つはそれに寄り添う文書メタデータ、すなわちタイトル、作成者、会社、キーワード、各種のタイムスタンプです。Excelはこの2層目をグリッド上に表示しませんが、Windows Searchがインデックスするのはこの層であり、SharePointが文書の題名として読むのも、記録管理システムが分類の基準にするのもこの層です。生成されたワークブックが元になったテンプレートからAuthorとTitleを引き継ぐと、下流のあらゆるシステムは、4000通の顧客向け明細の作成者としてテンプレートの設計者を記録します。メタデータはどこでも正しくないのに、どこでも参照されるのです
HotXLSはこの層を、2つのエンジンのどちらでもワークブックレベルの普通のプロパティとして提供します。.xls向けのBIFFファサードと、.xlsx向けのOOXMLファサードです。ファイルを開いた後にフィールドを読み、保存する前にフィールドを書きます。値がどの物理コンテナに収まるかはライブラリが決めます。ジェネレーターを書く前に理解しておくべきなのは、各形式が実際にどのフィールドをサポートするか、それらが物理的にどこに存在するか、そして.xlsxがそもそもメタデータを記録するかどうかを左右する1つの門番の規則です
2つの形式、2つの保存モデル
スプレッドシートライブラリにメタデータの実装が2つ必要な理由、そして中途半端なツールが片方の形式には正しく刻印しつつもう片方を忘れる理由は、.xlsと.xlsxがプロパティをまったく別の場所に保持するからです。BIFFのワークブックはOLE複合ファイルのストリームへ書き込みます。主にExcelそのものより歴史の古いSummaryInformationプロパティセットと、最後に保存した人物を記録するストリーム内のWRITEACCESSレコードです。OOXMLのワークブックはzipパッケージの中のXMLパートとして保持し、用途で分かれています。docProps/core.xmlがDublin Coreのフィールド(タイトル、作成者、件名、キーワード、日付)を、docProps/app.xmlがECMA-376 Part 1に従って会社や生成アプリケーションといったアプリケーションレベルのフィールドを保持します
HotXLSはこの2つの保存モデルを、いずれもワークブックオブジェクトの直接のプロパティへと平坦化します。プロパティセットのストリームを開いたり、XMLパートを手で編集したりする必要はありません。文字列と日付をワークブックへ代入すれば、保存する形式に応じて正しいコンテナができあがります
業務レコードから生成ワークブックへ刻印する
XLSX側では、TXLSXWorkbookがTitle、Subject、Author、Keywords、Description、Category、LastModifiedBy、Company、Application、AppVersionを文字列として公開し、加えてCreatedとModifiedをTDateTimeの値として公開します。ゼロは未設定を意味します。継承の穴をふさぐ規則は一文で言えます。実行のたびに全フィールドを代入し、その値はテンプレートが持っていたものを信用せず業務レコードから取ることです
var
Book: TXLSXWorkbook;
begin
Book := TXLSXWorkbook.Create;
try
if Book.Open('statement-template.xlsx') <> 1 then
raise Exception.Create('Template not available');
// 全フィールドを上書きする: 触れずに残したものは
// テンプレートを設計した人物から継承される。
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;
Keywordsフィールドは、ふだん向けられている以上の考慮に報いてくれます。検索基盤はこれをそのままインデックスします。Windows Search、SharePoint、そしてほとんどのDMS製品が同様です。したがって、口座番号と対象期間を含むセミコロン区切りの規約を決めておけば、配布したワークブックはどれも、データベースへの問い合わせなしに見つけられる記録になります。同じ到達範囲が落とし穴でもあります。プロパティはファイルの複製すべてに付いて回り、それを書き込んだシステムのアクセス制御をはるかに越えて広がるので、個人データを入れる場所ではありません
タイムスタンプの組は、習慣任せにせずポリシーとして定めておく価値のある意味を持ちます。Createdはパイプラインが文書を生成した瞬間を刻み、その後は凍結しておくべきものです。Modifiedは受け取り手がファイルを保存するたびにExcelが更新するフィールドなので、配布後に両者が食い違っていれば、誰かが下流でワークブックを編集した確かな証拠になります。これは、転送されてきたスプレッドシートが本当は誰の数値を保持しているのかという争いを、いくつも決着させます。未設定の状態には1つ罠が潜んでいます。それは例外でもnullでもなく、文字どおりの値ゼロなので、監査コードは明示的にゼロを検査する必要があります。この防御を欠いたまま未設定のTDateTimeを書式化すると、ログは自信たっぷりに誤った1899年12月の日付で埋まります
DocPropsTouched:docPropsを持たずに出荷されるワークブック
読み取り専用のフラグDocPropsTouchedが、XLSXのプロパティライターの門番になります。1つもプロパティが代入されなかったワークブックはdocPropsパートをまったく生成しません。HotXLSは空のメタデータの骨組みを書くことを拒むのです。この挙動は整然としていますが、設計時に織り込むべき帰結が2つあります
受け取る側の取り込みコードは、どのパッケージにもcore.xmlが存在すると仮定してはいけません。それを必須とするツールは、まったく妥当な最小構成のファイルを拒否してしまいます。また、送り出すすべての文書が少なくとも生成元の身元を持つことをコンプライアンス方針が要求するなら、その要求は形式の性質ではなくコードの責務になります。保存経路でApplicationとAuthorを無条件に代入してください。何も触れていないワークブックは仕様上まったく合法でありながら、静かに方針に反するからです
旧来のXLSの面と、Commentsの罠
BIFFのファサードは、より古く小さいフィールド集合を備えます。Title、Subject、Author、Keywords、Comments、Company、Managerに加え、UserNameの別名であるLastSavedByがあります。これは、別のユーザーがファイルをロックしているときにExcelが表示するWRITEACCESSレコードを書き込みます
var
Legacy: IXLSWorkbook; // 参照カウント方式のインターフェイス: 手動の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'; // BIFFのWRITEACCESSレコード
Legacy.SaveAs('archive-1999-stamped.xls');
end;
ある名前の衝突が繰り返し混乱を招きます。ここで扱う文書レベルのCommentsプロパティは、ファイルのプロパティダイアログに表示される自由記述の注記です。セルのコメントとは何の関係もありません。あちらは、まったく別のAPIで範囲に紐づけられる描画レイヤーのオブジェクトです。「Commentsならもう書いている」という主張をどちらの意味か確かめずに受け入れたコードレビューは、別の機能についての主張を受け入れたことになります。しかも、名前が同じというだけの理由で、これは想像以上によく起こります。両者は4文字を共有していますが、保存領域は1バイトも共有していません
取り込み時にメタデータを読む、そして調査の隙間
読み取りは対称です。Openの後、同じプロパティにファイルの値が入って返ってくるので、届いたワークブックのメタデータ監査は短いループで済みます
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;
その際、1つの制約を前提に計画してください。プロパティだけを調べる手段はありません。GetSheetNamesはワークブックを読み込まずにシートを列挙できますが、TitleやAuthorを読むには完全なOpenが必要なので、大きなアーカイブ全体のメタデータの仕分けは、ファイルごとに解析コストを丸ごと支払うことになります。BIFF側では、読み取り専用の監査に限れば、開く前に_DisableGraphicsをtrueに設定してコストを削れます。これは描画レイヤーをまるごと省きます。プロパティとセルの統計だけを読むループには合いますが、同じインスタンスが保存する可能性がある瞬間からは完全に誤りになります。省かれた描画コンテンツが失われてしまうからです。シート構成だけで対象を事前に絞り込める場合、たとえば単一シートのエクスポートを飛ばすといった分かりやすい例では、シート一覧と軽量な検査に関する記事にある安価な手法が、高価な処理まで届くファイル数を減らします。また、数千件の出力を検査ではなく書き出す一括刻印のジョブでは、バッチジョブ向けのストリーミング書き込みに関する記事にある書き込み側のスループットの手法がそのまま通用します。プロパティの代入は保存時間に測定できるほどの負荷を加えないからです
形式をまたぐときと、漏れを抑える方法
プロパティは1つのファサードの内側では綺麗に往復します。.xlsxを開き、編集し、保存すれば、その集合は無傷で戻ってきます。同等性という前提が崩れるのは形式をまたぐときです。BIFFとOOXMLのフィールド集合は1対1に並ばないからです。BIFFにはManagerがあってタイムスタンプがなく、OOXMLにはCategory、Description、そしてCreatedとModifiedの組があります。やみくもにコピーするコンバーターは、変換先の形式が保持できないものをすべて失います。フィールドは明示的に対応づけ、その対応表を、旅を越えられない他のあらゆる項目と並べて変換チェックリストに載せてください
テンプレートの継承が開ける漏れは、逆向きにも走ります。出すつもりのなかった情報が出ていくのです。作成者の氏名、キーワードに置き去りにされた社内のプロジェクト名、誰も承認していない下書きのタイトルなどです。上のジェネレーターで示した「すべて上書きする」という作法がその全防御であり、外部の人と同じやり方で確認する価値があります。どの顧客でも開けるプロパティダイアログを開くか、.xlsxを展開してパッケージからdocProps/core.xmlをそのまま読むのです。そこに見えるものが、下流のあらゆるインデクサーに見えているものそのものです
この下流での可視性は、いくつかのフィールドが他より丁寧な扱いに値する理由でもあります。SharePointとWindows Searchでは、Title、Author、Keywords(タグとして現れます)、そしてCommentsまたはDescriptionがインデックスの重みの大半を担います。対象期間と口座を含み、文書ごとに本当に異なるTitleは、その上に積み重ねたどんなフォルダー命名規則よりも探しやすさに寄与し、しかもコストは保存1回につき1つの代入だけです
文書プロパティは、生成されたワークブックがまとえる最も安価な仕上げであり、誰も面倒を見なければ最も頻繁に出荷される欠陥でもあります。ここで述べた2つのプロパティ面はいずれもHotXLS Delphi Componentのものであり、Excelのオートメーションなしに、XLSとXLSXへネイティブに書き込みます