コード内で請求書ワークブックを構築し、それを後流のシステムがインポートするためのCSVとして出力する夜間ジョブを想像してください。Excelで開いたときは数値が正しく表示されます。テキストエディタで開いても、CSVは正常に表示されます。しかし、インポート処理で合計列の読み込みが失敗します。なぜなら、42行目の金額フィールドの値が =SUM(D2:D41) という数式のテキストになっており、計算されるべき数値ではないからです。何も壊れていません。これは仕様通りの動作であり、HotXLSからのエクスポートについて最初に理解しておくべきポイントです。ライター(書き出し処理)は、セルモデルをそのままでシリアライズするため、値が一度も計算されていない数式セルは、その数式テキストだけを書き出してしまいます
CSVに数値ではなく数式が含まれる理由
HotXLSは、数式テキストと計算された値を2つの異なるものとして保持します。仕様上、SaveAsCSV は出力の過程で計算エンジンを実行しません。エクスポート処理によってワークブックが書き換えられるべきではなく、異常な数式チェーンで処理が停止するリスクを避けるためです。Excel自体から保存されたファイルは、数式の隣に計算結果のキャッシュを保持しているため、それを再エクスポートすれば期待通りの動作になります。問題が発生するのは、コードで新規に生成された、数式が記述されたものの計算されていないワークブックの場合です。解決策は、エクスポートの前に、シート間参照やカスタム関数を解決するのと同じ Calculate エンジンを使用して、計算値を実体化させておくことです
var
Book: TXLSXWorkbook;
Sheet: TXLSXWorksheet;
R: Integer;
begin
Book := TXLSXWorkbook.Create;
try
Book.Open('invoice-run.xlsx');
Sheet := Book.Sheets[0];
// Materialize formula results so the CSV carries numbers, not '=...' text
for R := 2 to 41 do
if Sheet.Cells[R, 4].Formula <> '' then
Sheet.Cells[R, 4].Value := Book.Calculate(Sheet.Cells[R, 4].Formula);
Book.SaveAsCSV('feed.csv', 0, ','); // sheet 0, comma
Book.SaveAsCSV('feed.tsv', 0, #9); // same sheet as TSV
finally
Book.Free;
end;
end;
このループが実際に行っている処理に注目してください。数式セルをその計算された値で上書きしています。これは使い捨てのエクスポート処理としては正しいですが、エクスポート後にそのワークブックを再び .xlsx として保存する予定がある場合は不適切です。なぜなら、生きている数式を固定値で置き換えてしまったためです。エクスポート用のコピーを用意するか、または書き戻しの影響範囲がエクスポート処理だけになるようにスコープを制御してください。Calculate の背後にあるエンジンは、カスタム関数の登録なども含めてさらに多くの機能を提供しており、これについてはHotXLS数式エンジンとカスタム関数に関する記事で説明されています
区切り文字ライターが保証する仕様
CSVの出力パスは、BOM(バイトオーダーマーク)付きのUTF-8、CRLF改行、および RFC 4180 準拠 of クォート処理を生成します。区切り文字、ダブルクォーテーション、または改行コードを含むフィールドはクォートで囲まれ、埋め込まれたダブルクォーテーションは2重化されます。日付はセルの表示形式に関係なく yyyy-mm-dd hh:nn:ss としてレンダリングされます。これは機械による読み込みには適した設計ですが、画面上の表示形式が引き継がれることを期待していた人にとっては意外に思えるかもしれません。リッチテキストセルは、そのすべてのテキストラン(書式付きブロック)が結合されてプレーンテキストに平坦化されます
これらのデフォルト動作により、インポート処理との競合のほとんどが事前に解消されますが、そのうちの2つはインターフェースの契約に含めておくべきです。第一はBOMです。これにより、Excelでファイルを開いた際にもアクセント文字などが文字化けせずに表示されますが、厳格な一部のパーサーはこれら3バイトのBOMをデータとして扱ってしまうため、インポート側の制約がある場合は手前で除去してください。第二はTSV(タブ区切り値)です。これは独立した機能ではなく、区切り文字として #9 (タブ文字)を渡して呼び出される同じライターに過ぎないため、上記の仕様はすべてそのまま適用されます。エクスポートするシートは、複数引数のオーバーロードで0から始まるインデックスによって指定します。一方、引数が1つの SaveAsCSV(FileName) の省略形はアクティブシートを対象とします
HTMLエクスポートはスナップショットであり、データ交換用フォーマットではない
HTML export is a snapshot, not an interchange format
CSVが値以外のすべてを破棄するのに対して、SaveAsHTML は見栄えを維持しようと試みます。シートごとに1つの <table>、結合された領域は colspan および rowspan として表現され、基本的なセルのスタイルはインラインCSSとして書き出されます。テーマ依存のカラーは解決されずにスキップされるため、テーマスロットに依存しているテンプレートは、Excelで表示されるよりもプレーンに出力されます。エクスポート後も維持したいスタイルには、明示的なRGBカラーを設定してください。オプションオブジェクトで出力の構造を制御できます
var
Opts: TXLSXHtmlExportOptions;
begin
Opts := TXLSXHtmlExportOptions.Create;
try
Opts.Title := 'Weekly settlement';
Opts.TableClass := 'report-grid'; // hook for the host page stylesheet
Opts.WriteDocument := True; // full page, not a fragment
if Book.SaveAsHTML('settlement.html', 0, Opts) <> 0 then
raise Exception.Create('Sheet index out of range');
finally
Opts.Free;
end;
end;
上記のコード断片の2つの詳細に注目してください。WriteDocument を False に切り替えると、完全なHTMLページではなくプレーンなテーブルの断片(フラグメント)が出力されます。これは既存のレイアウトにプレビューを挿入したい場合に最適です。TableClass を設定して、ホスト側のスタイルシートでテーマを適用させてください。また、戻り値の仕様は他の多くのHotXLSの呼び出しと逆になっています。SaveAsHTML は成功時に0を返し、無効なシートインデックスの場合に-1を返すため、習慣的に = 1 (成功を1とするチェック)を行うと、すべての成功したエクスポートが失敗として報告されてしまいます。シート全体ではなく特定の領域だけが必要な場合(メール送信や特定のブロックの埋め込みなど)は、TXLSXRange.SaveAsHTML が同じレンダリング規則に基づいて任意の矩形範囲をエクスポートします
RTF出力と、それが今なお重宝される場面
4番目のターゲットは、SaveAsRTF を介して1回の呼び出しで1シートを出力する RTF 1.6 テーブルです。列幅は、列幅の文字数あたり約 96 twip で近似されます。注意すべき構造上の制限として、結合されたセルは出力結果でスパン(またがる処理)されません。アンカーセル(左上)のみが内容を保持し、カバーされた他のセルは空セルとして出力されます。これにより、レイアウトが複雑なテンプレートにはRTFは適していません。それでも、表形式の結果をワードプロセッサや、HTMLの取り込みに対応していないレガシーなドキュメント管理システムに流し込むための最も手軽な手段として、今なお重宝されています
往復処理:CSVインポートは仕様上破壊的である
CSVの読み込みにも独自の仕様があります。OpenCSV はワークブック全体をクリアし、Sheet1 という名前の単一シートとして再構築します。これは本質的にコンストラクタであり、マージ処理ではないため、未保存のコンテンツが残っているワークブックでは絶対に呼び出さないでください。区切り文字として #0 を渡すと、区切り文字の自動検出がトリガーされます。ADetectTypes フラグは型プロモーションを制御します。これを有効にすると、数値風の文字列は数値になり、ISO-8601風の文字列は日付になり、true/falseはブーリアンになります。フィードデータに先頭のゼロがあるID、郵便番号、または製品コードが含まれている場合は、このフラグを無効にしてください。プロモーション処理はこれらを数値に誤変換してしまうためです(先頭のゼロは 00123 が 123 になった瞬間に失われます)。どちらのファサードも同じインポート機能を公開しています。これを上記のエクスポート機能と組み合わせれば、パイプラインのどの段階にもExcelをインストールする必要のないフォーマットブリッジが完成します。これについては、HotXLSを使用したデータベースからExcelへのレポート生成に関する記事で説明されています
ストリームへの直接エクスポート
ここにあるすべてのライターは、ファイル名指定版の隣にストリーム用のオーバーロード(CSV、HTML、RTF、およびワークブック形式自体)を用意しています。サーバーのコードでは、これらのストリーム用オーバーロードを優先して使用してください。CSVダウンロードを提供するWebエンドポイントは、TMemoryStream に書き込んでそれをレスポンスオブジェクトに直接渡すことができます。テンポラリファイルも、その削除処理も不要であり、同じ生成名が重複することによるリクエスト間の衝突も発生しません。エクスポート結果をBlobストレージにアップロードしたり、送信メールに添付したりする場合も同様です。ファイルシステムはプロセスから完全に排除されます
このパターンは、ライブラリのデプロイ特性と相乗効果を生みます。どちらのファサードもネイティブなObject Pascalのリーダーおよびライターであるため、Excelのインストールも、COMオートメーションも、サーバー上でリクエストをシリアル化するプロセスごとのボトルネックも存在しません。各リクエストが自身のワークブックオブジェクトを所有し、最初のセクションで説明した計算の書き戻しを実行し、隣のリクエストと並行してエクスポートデータをストリーミングできます。監視すべきリソースはメモリです。エクスポート中、ワークブックモデルはRAM上に維持されるため、非常に大きなファイルを開いて単にCSVとして出力するようなサービスでは、アクセス集中によるメモリ逼迫を防ぐために、同時実行ジョブ数を制限するか、サイズ超過の処理をキューに回すなどの対策をとる必要があります
小さな設定項目として、出力されたHTMLフラグメントが、下流のツールによって文字コード検出される独立したファイルとして保存される場合は、HTMLオプションの IncludeBOM を設定してください。HTMLをHTTP経由で直接配信する場合は、文字コードの宣言は代わりにHTTPレスポンスヘッダーで行うようにします
開く際の文字化けの問題
CSVエクスポートに関する最も一般的なサポートへの質問は、開く際の文字化けの問題です。Excelで開いたときにアクセント文字などが文字化けして表示されます。ライターを疑いたくなりますが、ライターはまさにこのためにUTF-8 BOMを書き出しており、ファイルがコードから離れる時点ではほぼ常に正しい状態です。問題は、そこからExcelに届くまでのどこかでBOMが失われたことです。FTPでのテキストモード転送、最初の3バイトをスキップするストリームコピー、経由するプロキシによる再エンコードなど、これらはすべてBOMを除去し、Excelに文字コードを推測させ、結果として誤った表示を引き起こします。これについてはエクスポート呼び出しではなく、境界で診断してください。配信されたファイルをバイナリエディタで開き、先頭が EF BB BF になっていることを確認します
これが4つのフォーマットすべてに共通する本質です。エクスポートの呼び出し自体は簡単であり、HotXLSはライターが直面するそれぞれの判断において合理的な設計を採用しています。不具合が発生するのはその境界部分です。数式テキストを数値として解釈しようとするパーサー、BOMを維持しないトランスポート、結合セルを認識しないRTFの平坦なテーブルモデルなどが原因です。これらはすべて、エクスポート処理とその出力先との間のコントラクト(取り決め)に明記されるべき事実です。出力先は開発者の意図をバイトデータから推測することはできないからです。両方のワークブックファサードに対応する完全なメソッドリストについては、HotXLS Componentの製品ページに完全なリファレンスが掲載されています