技術記事

DelphiでISO 29500 Strict形式のXLSXファイルを書き出す

HotXLSは、Delphiおよび C++Builderから、保存前にStrictOOXMLという1つのプロパティを設定するだけで、ISO/IEC 29500 Strict形式のOpen XMLワークブックを書き出します。xl/workbook.xmlからリレーションシップファイルやコンテンツタイプに至るまで、パッケージ内のすべてのパーツは、移行用のschemas.openxmlformats.org語彙ではなく、strict用のpurl.oclc.org語彙で書き出されます。Strictが許可していない機能は、そのまま書き出されるのではなく、明示的な例外によって拒否されます

多くの開発者は、この要件に調達文書を通じて出会います。いくつかの法域の公共部門の入札では、Officeがデフォルトで保存する移行用の形式ではなく、ISOで標準化されたOpen XMLの形式が求められます。ISO 29500 Strictを義務付けるアーカイブは、Excelでは問題なく開ける通常の.xlsxであっても拒否します。移行用の名前空間はレガシーなバイナリの挙動を取り込むために存在するものであり、strict用の名前空間こそが正式な標準です

StrictとTransitionalは実際に何が違うのか?

目に見える違いは語彙です。strictなワークブックパートは、ルート名前空間としてhttp://purl.oclc.org/ooxml/spreadsheetml/mainを、リレーションシップ参照としてhttp://purl.oclc.org/ooxml/officeDocument/relationshipsを宣言し、パッケージ内のどこにも移行用の名前空間が残っていてはなりません。リレーションシップの種類もそれに伴って変わり、ルートのリレーションシップパートは、おなじみのopenxmlformats相当の名称ではなく.../ooxml/officeDocument/relationships/officeDocumentを使用し、拡張プロパティの種類はキャメルケースのextendedPropertiesになります

目に見えない違いは範囲です。Strictは、レガシーなバイナリファイルを往復変換するためだけに存在していた移行用スキーマの部分や、その後Officeが追加したベンダー拡張を、意図的に省いています。だからこそ、この変換は文字列の検索置換ではありません。一部の機能には単純にstrictでの書き方が存在せず、まったく書き出してはならないのです

有効にする

通常のワークブック作成コードは変わりません。いつもどおりワークブックを構築し、フラグを設定して保存するだけです:

var
  Wb: TXLSXWorkbook;
  Sh: TXLSXWorksheet;
begin
  Wb := TXLSXWorkbook.Create;
  try
    Sh := Wb.Sheets.Add('Data');
    Sh.Cells[1, 1].Value := 'Product';
    Sh.Cells[1, 2].Value := 'Amount';
    Sh.Cells[2, 1].Value := 'Widget';
    Sh.Cells[2, 2].Value := 17;
    Sh.Cells[3, 2].Formula := '=SUM(B2:B2)';

    Wb.StrictOOXML := True;          // ISO/IEC 29500 Strict形式で出力する
    if Wb.SaveAs('archive-copy.xlsx') <> 1 then
      raise Exception.Create('strict save failed');
  finally
    Wb.Free;
  end;
end;

このフラグは、保存操作の開始時に毎回リセットされ、ワークブックのプロパティから再度割り当てられます。そのため、ある保存中の例外が、次の保存にstrictモードを漏らしてしまうことはありません。この点は、1つのワークブックオブジェクトが複数のエクスポートリクエストを処理するサーバープロセスで重要になります

なぜstrict保存は実行を拒否することがあるのか?

4つの機能ファミリーはMicrosoft独自の拡張機能であり、ISO 29500 Strictに相当するものがありません。HotXLSは、strict準拠だと主張しながら実際にはそうではないパッケージを出力するのではなく、保存時に例外を発生させます:

// Strict出力はVBAプロジェクトを埋め込めない
//   -> マクロ有効ワークブックは移行用の.xlsmとして保存する
// Strict出力はフォームコントロールを保持できない
//   -> ボタン、チェックボックス、コンボボックスとそのctrlProps
// Strict出力はスレッド化コメントを保持できない
//   -> 最新のpersons/threadsモデル、従来のノートではない
// Strict出力は動的配列のメタデータを保持できない
//   -> メタデータパートに記録されるスピル範囲

ここでは、はっきりと失敗を知らせることが正しいトレードオフです。VBAプロジェクトが黙って削除されると、動作していたワークブックが、開けはするものの壊れたものに変わってしまい、その障害の報告は数週間後にユーザーから届くことになります。例外は、呼び出し側のコードがまだ何をエクスポートしていたか把握しているうちに、問題の機能と変更すべきプロパティを名指しします。移行用パスにおけるマクロと外部リンクの保持については、VBAプロジェクトと外部リンクの保持で説明しています

2つの拡張機能ファミリーは異なる扱いを受けており、その理由を知っておく価値があります。データバー、スパークライン、および類似の機能はx14xmの語彙に存在し、画像のSVGバリアントはc15に存在します。これらは、名前空間が自己記述的な拡張リスト形式のコンテンツであり、一般的なスプレッドシートパーサーはこれらを許容します。そして、これらを変換すべきISO相当の形式は存在しません。HotXLSは、ユーザーのコンテンツを切り捨てるのではなく、これらを保持します。パイプライン内のバリデーターが名前空間だけでなく拡張機能についても厳格である場合は、エクスポートする前にソースのワークブックからこれらの機能を削除してください

変換は、普段誰も書き直さないパーツにも及ばなければならない

strict出力における興味深いエンジニアリング上の課題は、ワークシートのXMLではありません。高速なライターであれば逐語的にコピーしたいと思うようなパーツにあります。HotXLSは、テーマ、接続、外部リンク、チャート、ピボットのブロブを、元の圧縮バイト列をそのままコピーすることで保持しています。これは、忠実度の観点ではまさに正しいやり方であり、strict出力の観点ではまさに間違ったやり方です。コピーされたバイト列は移行用の名前空間を保持したままだからです

StrictOOXMLの下では、これら5つの保持系パスは、バイトコピーによる高速パスを迂回して、再構築または変換を伴う再生に切り替わります。すべてのXMLは単一の変換ルーチンを通り、このルーチンは二重引用符で囲まれた属性値を基準にするため、セル内にあるURIらしき文字列が誤って書き換えられることは決してありません。同じURIを含むセルテキストはXML内でエンティティとしてエスケープされているため、基準に基づく置換処理からは見えません。ストリーミングライターは、まずスケルトン部分を変換し、その後sheetDataの位置で分割します。行ブロックには語彙URIがまったく含まれていないからです。保持系パスの関連する仕組みについては、テーマ・拡張リスト・calcChainの無損失ラウンドトリップで説明しています

Excelがstrictとして保存したファイルを読み込む

出力は話の半分にすぎません。Excelには保存オプションとして「Strict Open XML Spreadsheet」があり、その方法で生成されたファイルは正しく開けなければなりません。HotXLSは、ルート、外部リンク、ワークシート、図形描画、ピボットテーブルなど、パッケージ内のあらゆるリレーションシップ解析箇所でリレーションシップの種類を正規化します。そのため、strictなリレーションシップの種類は、移行用の対応物と同じ内部定数に一致します

読み込み側の対応する処理は、名前空間プレフィックスの正規化です。これにより、任意のプレフィックスと両方の語彙を、1つの正規名テーブルへ解決できます。この処理は、サードパーティ製の生成ツールがプレフィックスを自由に割り当てるため、strictなファイルだけでなく通常のファイルにも恩恵をもたらします。これは、XLSXパッケージにおけるOPCリレーションシップ解決で説明したのと同じ仕組みです

strict出力を出荷する前の簡単なチェックリスト

検証はExcelではなくパッケージ自体で行ってください。Excelはどちらの形式も問題なく開けてしまうため、開けたという事実だけでは適合性について何も証明されません。結果を展開して、xl/workbook.xmlがpurl名前空間を宣言していること、どのパーツにもschemas.openxmlformats.org/spreadsheetmlが含まれていないこと、_rels/.relsxl/_rels/workbook.xml.relsのリレーションシップの種類がstrict形式を使用していることを確認してください

次に、そのファイルをHotXLSで再度開き、値・数式・書式・ハイパーリンクを元のファイルと比較してください。この読み戻しテストは、変換がコンテンツを損なっていないことを証明する唯一の手軽な方法であり、同時に読み込み側の正規化処理も検証できます。ワークブックにチャートが含まれている場合は、それも確認してください。チャートパートは、strictモードで再構築パスに切り替わる保持系パートの1つだからです

strict出力、寛容な読み込み、無損失な保持は、いずれもDelphiおよび C++Builder向けの同じOOXMLエンジンの一部です。完全な機能一覧はHotXLS Delphiスプレッドシートコンポーネントページに掲載されています