既存のExcel帳票にプログラムで値を入れると、出力からチェックボックスやマクロが消えることがあります。原因はコードのバグではなく、表計算ライブラリがファイルを保存する仕組みそのものです。
この記事は、社内の様式(マクロ・フォーム部品・関数入り)に自動でデータを書き込みたい実装者・情報システム担当に向けて、なぜ壊れるか・どう判定するか・どう書けば壊れないかを書きます。
書き込んだ数字そのものが正しいかの保証は、既存Excel様式を自動生成するときの品質保証で別に扱っています。台帳自体をExcelから移す話はExcel台帳のデータベース化にあります。
.xlsxは単一のバイナリファイルではありません。拡張子を.zipに変えて展開すると中身が見えます。Microsoftの開発者向け資料「Structure of a SpreadsheetML document」も、ブックの中身を確認する手順としてこの方法を案内しています。実務の帳票には、おおむね次の部品が入っています。
xl/workbook.xml — シート一覧と各シート定義への関係IDxl/worksheets/sheet1.xml — セルの値・数式・結合範囲・書式参照xl/sharedStrings.xml — 全シートの文字列を一意に1回ずつ格納した表xl/calcChain.xml — 数式セルを最後に計算した順序xl/ctrlProps/ctrlProp1.xml — フォームコントロールの状態xl/drawings/vmlDrawing1.vml — フォームコントロールの行・列アンカーxl/embeddings/oleObject1.bin — 埋め込みオブジェクトxl/printerSettings/printerSettings1.bin — 印刷設定xl/vbaProject.bin — マクロ帳票が様式として成立しているのはこれらが揃っているからで、セルの値は束のうち1部品にすぎません。実際の帳票は、1ファイルが60を超える部品でできています。
ライブラリの読み込みと保存は、ファイルを部分的に書き換えているのではありません。読み込んだ内容からブックを組み立て直し、新しいZIPとして出力しています。読み込めなかった部品は材料に入らず、出力から消えます。openpyxlの公式ドキュメントもこれを明記しています。すべての項目を読めるわけではないため、既存ファイルを開いて同名で保存すると図形が失われる、という趣旨の記述です。フォームコントロールのチェックボックスはセルではなく図形なので、この対象に入ります。
実際の帳票で計測したところ、ブック全体を保存し直した出力では約50の部品が欠落していました。内訳はフォームコントロールの状態、埋め込みオブジェクト、VML描画、外部データ接続、印刷設定、customXmlです。マクロはload_workbookの引数keep_vbaが既定でFalseであることも効きます。公式の説明は「VBAの内容を保持する(これは使えるという意味ではない)」という但し書きつきです。
やっかいなのは、この壊れ方が目視で見つからないことです。セル値・書式・罫線・結合・画像・コメントは残るため、出力をExcelで開くと正しく見えます。動いたと判断して本番に載せ、現場が使う段でチェック欄の消失に気づきます。
元ファイルと出力ファイルをZIPとして開き、パート名を集合として比べます。元にあって出力にない要素が、そのまま失われた機能です。次に共通パートのバイト列を比較し、変更が書き込み対象のシートXML1つだけであることを確認します。Excelで開いて見た目を確認する手順は、この検査の代わりになりません。消えるのは、見た目に出ない部品だからです。
壊さない書き込みの原理は、ブックを組み立て直さないことに尽きます。
xl/worksheets/sheet1.xml)だけを文字列として取り出す<c>要素を目的の値に書き換える実測では、変更された部品はシートXMLの1つだけでした。60余りのパートのうち、残りはすべてバイト単位で同一です。使うのは標準ライブラリのZIP操作と文字列処理だけで、処理は1ファイルあたり0.3秒未満です。
セルに文字列を入れる方法は2つあり、選択で変更部品の数が変わります。
共有文字列テーブル(sharedStrings.xml)は、ブック内の一意な文字列を1回ずつ格納する表です。ISO/IEC 29500の記述では、この部品はパッケージにちょうど1つと定められています。この方式ではシートXMLとsharedStrings.xmlの2部品を変更することになり、さらに総数を表すcountと一意数を表すuniqueCountの整合も自分で保つ必要があります。誤るとブック全体の文字列表示がずれます。
もう1つがinlineStr形式です。セルの型属性をt="inlineStr"にして、シートXML内に文字列を直接埋め込みます。Open XML SDKのセル型定義でも、InlineStringはXML出力時にinlineStrとなる正式な型です。
Microsoftの解説も、共有文字列テーブルを使うことは有効なファイルを作る条件ではないと述べています。この表は文字列の繰り返しによる容量と読み書き時間を減らす最適化であって、正しさの要件ではありません。
帳票への書き込みは同じ文字列が何千回も出る用途ではないため、変更部品が1つで済むinlineStrを選びます。
calcChain.xmlは、数式から値が計算されるすべてのセルへの順序付き参照を保持する部品です。ISO/IEC 29500の記述では、計算チェーン部品は1つを超えて含めてはならないとされています。仕様上この部品は必須ではなく、読み込み時にアプリケーション側が数式と依存関係から組み直すこともできる、とも述べられています。ただし、その振る舞いに依存する設計は勧めません。組み直してくれる保証と、食い違った記述を読んだときの挙動は別の話だからです。
安全な設計は単純です。書き込み対象を文字列セルと空セルに限定し、<f>要素を持つ数式セルには触れません。
あわせて、ひな形を作る段階の注意が1つあります。数式セルにはキャッシュ済みの計算結果が<v>要素として残っており、除去しないと値を入れ替えても古い数字が表示され続けます。
結合セルのアンカーは様式の世代で動きます。 同じ名前の帳票で、結合範囲がB14:P14の版とB13:P14の版が混在していた例があります。左上以外のセルに書くと、値は入るのに表示されません。座標を決め打ちせず、シートXMLのmergeCellsから動的に解決してください。
絶対セル位置での書き込みは、様式が動く帳票でラベルを値として拾います。 新しい世代の位置を決め打ちで実装し、古い世代のファイルで隣のラベル文字列を値として投入した事故が実際にあります。エラーは出ず、データは正常に見えます。ラベルセルを走査し、その直下または右方向の最初の実値を採ると世代差を吸収できます。
行挿入は使えません。 罫線が伝播せず結合範囲も追随しないため、表の途中に無罫線の穴が空きます。十分な空行を持つ固定枠に値だけを流し込み、超過分は件数を注記します。
ふりがながラベル照合を壊します。 共有文字列テーブルにはルビがrPh要素として同居するため、ラベル文字列で位置を決める実装はルビ込みのテキストを掴んで一致しなくなります。
ひな形は案件固有の値を全部消してから使います。 残っていると別案件の番号や日付が出力に現れます。表題に前の依頼番号が出た実例があります。
チェック欄がフォームコントロールで実装されていると、表計算ライブラリのセル値読み取りには一切現れません。図形であってセルではないからです。読むには2部品を突き合わせます。xl/ctrlProps/ctrlProp*.xmlがチェック済みかどうかの状態を持ち、xl/drawings/vmlDrawing*.vmlがその図形の行・列アンカーを持ちます。両方をZIPから直読みして結合すると、どのセル位置のチェックが入っているかが決まります。約100件の帳票で試したところ、新しい様式では有効でした。当該欄が構造的に存在しない古い様式では「不明」を返し、推測で埋めません。
書き込み処理の完了は、次の5つが通ったことで定義します。
4つ目には注意点があります。オフィススイートのPDF変換はページ割りが決定的ではありません。同一ファイルを3回変換してページ数が食い違った実測があるため、ページ数を合否の条件にしないでください。
実測値です。様式3世代のラウンドトリップでセル値一致率100%、約100ファイルの全数スイープで加重一致率100%、欠落0でした。数式のキャッシュ値は約2,700件ありましたが、ブック全体を保存し直さない方式のため全件そのまま残っています。
自力で成立する条件は3つです。
壁になるのは、様式が複数世代で混在すること、マクロやフォーム部品が含まれること、検証の工程を組めないことです。パート差分と全数スイープを回さない実装は、壊れていないことを一度も確かめていないのと同じです。
外注するなら、見積もりを比べる前に3点を確認してください。
これが仕様にない書き込み処理は、様式を保つことを約束していません。
まず手元の帳票を1つ、拡張子を.zipに変えて中を見てください。ctrlProps・embeddings・vbaProject.binのいずれかがあれば、その帳票はブック全体を保存し直す方式では扱えません。
3世代の様式が混在する帳票をデータベース化し、既存様式への書き戻しまで通した事例は様式が混在する依頼書の全件データベース化にあります。
解読が要るマクロ・アドインはマクロ・VBA解読/リファクタリング、取り出したデータの整形はデータ整理・名寄せとして承ります。進め方は相談からご連絡ください。
関連記事: Excel台帳のデータベース化/発注明細の費目分類のやり方/マクロ入りのブックを比べると全部「違う」と出る理由/Python で書き出した Excel が「修復」を求める理由/Python で値を書いた Excel の計算結果が古いままの理由
ワークスアプリケーションズで大手企業向けERPの営業と導入を担当し、外資系SaaS企業と国内大手放送局で販路と新規事業の立ち上げ、AI学習用データを扱うAIスタートアップ2社でマーケティングと営業を担った。2024年10月から生成AIの実装に入り、いまは大手企業の現場で、AIエージェントを要件定義から運用まで手がけている。
FAQ
ライブラリはファイルを部分的に書き換えているのではなく、読み込んだ内容からブック全体を組み立て直して保存しているためです。読み込めなかった部品は組み立ての材料に入らないため、出力から消えます。openpyxlの公式ドキュメントも、すべての項目を読めるわけではないため既存ファイルを開いて同名で保存すると図形が失われる、と明記しています。
openpyxlのload_workbookは既定でkeep_vba=Falseです。この既定のまま読み込んで保存するとマクロの中身が出力に含まれません。keep_vba=Trueを指定すればVBAの内容は保持されますが、公式ドキュメントはこれをもって「使える」という意味ではないと注記しています。マクロを確実に残すなら、ブック全体を保存し直さない方式が安全です。
出力ファイルと元ファイルのZIPパートを両方とも一覧にして、集合として突き合わせます。欠落したパート名がゼロで、かつ意図して変更した1つ以外がバイト単位で同一なら、様式は保たれています。Excelで開いて見た目を確認する方法では、消えた部品は見つかりません。
必須ではありません。仕様上、共有文字列テーブルを使うことは有効なファイルを作る条件ではなく、セルに直接文字列を埋めるinlineStr形式も正当です。sharedStringsへの追記は変更部品が増えるうえ、件数を表すcount・uniqueCountの整合も必要になるため、書き込み用途ではinlineStrのほうが副作用が小さくなります。
書き込み対象から外してください。数式セルを書き換えると、計算順序を記録したcalcChain.xmlの内容と実際の数式が食い違います。書き込み先は文字列セルと空セルに限定し、数式セルには触らない設計にするのが安全です。
既存様式では避けてください。プログラムでの行挿入は罫線が伝播せず、結合範囲も追随しないため、表の途中に無罫線の穴が空きます。十分な空行を持つ固定枠の様式に値だけを流し込み、超過分は挿入せず件数を注記する設計にします。
Related
Excel の台帳を Notion へ移すには、表の形とキーを決め、型を決めてリレーションと一緒に取り込み、数式と集計を作り直してから、件数・キー・値・先頭のゼロ・空の値・リレーション・計算の結果の7つで照合します。Notion の公式ヘルプと API の文書の決まりと、弊社が管理表の約2千明細を移し、Notion で計算した残が移したすべてのロットで元の表と一致した照合の手順でまとめます。
活用ガイドNotion AI に書類を作らせて照合するときは、手順を Notion AI への指示書、読めば決まる処理を添付のコード、1回ごとの結果を記録のデータベースに分けます。弊社の導入の事例の試作では、同じ題材で、文章の指示は発注番号を3回のうち2回誤り、添付のコードは2回とも誤りませんでした。分け方の問い、進める手順、誤りを仕込む照合の進め方を、弊社の記録でまとめます。