ホームナレッジ / Excel帳票への自動書き込みでファイルが壊れる理由|消える部品とZIP直接置換のやり方

Excel帳票への​自動​書き込みで​ファイルが​壊れる​理由|​消える​部品と​ZIP直接置換の​やり方

既存のExcel帳票にプログラムで値を入れると、チェックボックスやマクロが消えることがあります。原因はコードのバグではなく、ライブラリがブック全体を保存し直す仕組みです。この記事は、xlsxの部品構成・壊れたかを数分で判定する方法・書き込むシートのXMLだけを差し替えるZIP直接置換の手順・書いて読み戻す全件検証までを、実測値つきで整理します。

著者: 村山 悠太(ゴムマリ) 最終更新: 2026年8月

目次
  1. .xlsxはZIPで、中身は役割の違うXML部品の束
  2. ライブラリで開いて保存し直すと、読めなかった部品が落ちる
  3. 壊れたかどうかは、パート一覧を突き合わせれば数分でわかる
  4. 直し方——シートのXMLだけを差し替え、残りは元バイトのまま詰め直す
  5. 文字列はinlineStrで入れて、sharedStrings.xmlに触らない
  6. 数式セルとcalcChain.xmlには触らない
  7. 落とし穴——実務で踏むのはこの5つ
  8. 読み取り側——チェックボックスの状態はセル値として現れない
  9. 検証——書いて、読み戻して、全件数える
  10. 自力でやるか、外注するか
  11. 次の一歩

既存のExcel帳票にプログラムで値を入れると、出力からチェックボックスやマクロが消えることがあります。原因はコードのバグではなく、表計算ライブラリがファイルを保存する仕組みそのものです。この記事は、社内の様式(マクロ・フォーム部品・関数入り)に自動でデータを書き込みたい実装者・情報システム担当に向けて、なぜ壊れるか・どう判定するか・どう書けば壊れないかを書きます。台帳自体をExcelから移す話はExcel台帳のデータベース化にあります。

.xlsxは​ZIPで、​中身は​役割の​違う​XML部品の​束

.xlsxは単一のバイナリファイルではありません。拡張子を.zipに変えて展開すると中身が見えます。Microsoftの開発者向け資料「Structure of a SpreadsheetML document」も、ブックの中身を確認する手順としてこの方法を案内しています。実務の帳票には、おおむね次の部品が入っています。

  • xl/workbook.xml — シート一覧と各シート定義への関係ID
  • xl/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で開いて見た目を確認する手順は、この検査の代わりになりません。消えるのは、見た目に出ない部品だからです。

直し方​——​シートの​XMLだけを​差し替え、​残りは​元バイトのまま​詰め直す

壊さない書き込みの原理は、ブックを組み立て直さないことに尽きます。

  1. 元ファイルをZIPとして開き、全パートをバイト列のまま読み出す
  2. 書き込み対象のシートXML(xl/worksheets/sheet1.xml)だけを文字列として取り出す
  3. そのXML内で、対象セルの<c>要素を目的の値に書き換える
  4. 新しいZIPを作り、書き換えたシートXML以外は読み出したバイト列をそのまま書き戻す
  5. 前節の検査を通してから納める

実測では、変更された部品はシートXMLの1つだけでした。60余りのパートのうち、残りはすべてバイト単位で同一です。使うのは標準ライブラリのZIP操作と文字列処理だけで、処理は1ファイルあたり0.3秒未満です。

文字列は​inlineStrで​入れて、​sharedStrings.xmlに​触らない

セルに文字列を入れる方法は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には​触らない

calcChain.xmlは、数式から値が計算されるすべてのセルへの順序付き参照を保持する部品です。ISO/IEC 29500の記述では、計算チェーン部品は1つを超えて含めてはならないとされています。仕様上この部品は必須ではなく、読み込み時にアプリケーション側が数式と依存関係から組み直すこともできる、とも述べられています。ただし、その振る舞いに依存する設計は勧めません。組み直してくれる保証と、食い違った記述を読んだときの挙動は別の話だからです。

安全な設計は単純です。書き込み対象を文字列セルと空セルに限定し、<f>要素を持つ数式セルには触れません。あわせて、ひな形を作る段階の注意が1つあります。数式セルにはキャッシュ済みの計算結果が<v>要素として残っており、除去しないと値を入れ替えても古い数字が表示され続けます。

落とし穴——​実務で​踏むのは​この​5つ

結合セルのアンカーは様式の世代で動きます。 同じ名前の帳票で、結合範囲がB14:P14の版とB13:P14の版が混在していた例があります。左上以外のセルに書くと、値は入るのに表示されません。座標を決め打ちせず、シートXMLのmergeCellsから動的に解決してください。

絶対セル位置での書き込みは、様式が動く帳票でラベルを値として拾います。 新しい世代の位置を決め打ちで実装し、古い世代のファイルで隣のラベル文字列を値として投入した事故が実際にあります。エラーは出ず、データは正常に見えます。ラベルセルを走査し、その直下または右方向の最初の実値を採ると世代差を吸収できます。

行挿入は使えません。 罫線が伝播せず結合範囲も追随しないため、表の途中に無罫線の穴が空きます。十分な空行を持つ固定枠に値だけを流し込み、超過分は件数を注記します。

ふりがながラベル照合を壊します。 共有文字列テーブルにはルビがrPh要素として同居するため、ラベル文字列で位置を決める実装はルビ込みのテキストを掴んで一致しなくなります。

ひな形は案件固有の値を全部消してから使います。 残っていると別案件の番号や日付が出力に現れます。表題に前の依頼番号が出た実例があります。

読み取り側——​チェックボックスの​状態は​セル値と​して​現れない

チェック欄がフォームコントロールで実装されていると、表計算ライブラリのセル値読み取りには一切現れません。図形であってセルではないからです。読むには2部品を突き合わせます。xl/ctrlProps/ctrlProp*.xmlがチェック済みかどうかの状態を持ち、xl/drawings/vmlDrawing*.vmlがその図形の行・列アンカーを持ちます。両方をZIPから直読みして結合すると、どのセル位置のチェックが入っているかが決まります。約100件の帳票で試したところ、新しい様式では有効でした。当該欄が構造的に存在しない古い様式では「不明」を返し、推測で埋めません。

検証——​書いて、​読み戻して、​全件​数える

書き込み処理の完了は、次の5つが通ったことで定義します。

  1. パート差分: 意図した1つ以外の全パートがバイト同一
  2. 書込値の読み戻し: 出力を読み込み直し、書き込んだ全セルの値が一致
  3. 様式不変: 結合セル・罫線セル数・列幅の機械照合
  4. レンダリング: PDFに変換して破損なし、かつ書き込んだ文字列が変換結果に存在する
  5. 全数スイープ: 対象の全ファイルに適用し、一致率と例外件数を数える

4つ目には注意点があります。オフィススイートのPDF変換はページ割りが決定的ではありません。同一ファイルを3回変換してページ数が食い違った実測があるため、ページ数を合否の条件にしないでください。

実測値です。様式3世代のラウンドトリップでセル値一致率100%、約100ファイルの全数スイープで加重一致率100%、欠落0でした。数式のキャッシュ値は約2,700件ありましたが、ブック全体を保存し直さない方式のため全件そのまま残っています。

自力で​やるか、​外注するか

自力で成立する条件は3つです。様式が1世代に固定されていること。書き込み先が文字列セルと空セルだけであること。ZIPとXMLを直接扱うコードを保守できる人が社内にいること。壁になるのは、様式が複数世代で混在すること、マクロやフォーム部品が含まれること、検証の工程を組めないことです。パート差分と全数スイープを回さない実装は、壊れていないことを一度も確かめていないのと同じです。

外注するなら、見積もりを比べる前に3点を確認してください。出力の検証にZIPパートの照合が入っているか。全件適用時の一致率をどう測るか。確定できない項目をどう扱うか。これが仕様にない書き込み処理は、様式を保つことを約束していません。

次の​一歩

まず手元の帳票を1つ、拡張子を.zipに変えて中を見てください。ctrlPropsembeddingsvbaProject.binのいずれかがあれば、その帳票はブック全体を保存し直す方式では扱えません。3世代の様式が混在する帳票をデータベース化し、既存様式への書き戻しまで通した事例は様式が混在する依頼書の全件データベース化にあります。解読が要るマクロ・アドインはマクロ・VBA解読/リファクタリング、取り出したデータの整形はデータ整理・名寄せとして承ります。進め方は相談からご連絡ください。

関連記事: Excel台帳のデータベース化発注明細の費目分類のやり方

この記事を書いた人

代表 村山悠太のポートレート

村山 悠太(ゴムマリ代表)

国産大手ERPベンダーで新卒からERPのエンタープライズ営業・プリセールス・導入コンサル、外資系SaaSでパートナーセールス部門の立ち上げ、国内大手放送局で新規事業開発(通販カタログ事業)、AIスタートアップ2社でビジネスサイドの要職を歴任。2024年10月から生成AIを自ら実装し、2025年にゴムマリ株式会社を設立。

現在は自らAIエージェントを実装するFDE。評価セット設計・専門家監修・Go/No-Go判定、CRM・会計・社内DBとの統合、減った時間の実測まで一貫します。要件から、運用まで——AIエージェントが、現場で成果を出す仕組みを。

FAQ

よく​ある​質問

openpyxlで開いて保存しただけなのに、Excelの帳票が壊れるのはなぜですか?

ライブラリはファイルを部分的に書き換えているのではなく、読み込んだ内容からブック全体を組み立て直して保存しているためです。読み込めなかった部品は組み立ての材料に入らないため、出力から消えます。openpyxlの公式ドキュメントも、すべての項目を読めるわけではないため既存ファイルを開いて同名で保存すると図形が失われる、と明記しています。

マクロ付きのxlsmを扱うときは何に注意すればよいですか?

openpyxlのload_workbookは既定でkeep_vba=Falseです。この既定のまま読み込んで保存するとマクロの中身が出力に含まれません。keep_vba=Trueを指定すればVBAの内容は保持されますが、公式ドキュメントはこれをもって「使える」という意味ではないと注記しています。マクロを確実に残すなら、ブック全体を保存し直さない方式が安全です。

ファイルが壊れていないことは、どうやって確認しますか?

出力ファイルと元ファイルのZIPパートを両方とも一覧にして、集合として突き合わせます。欠落したパート名がゼロで、かつ意図して変更した1つ以外がバイト単位で同一なら、様式は保たれています。Excelで開いて見た目を確認する方法では、消えた部品は見つかりません。

文字列はsharedStrings.xmlに追記しないといけないのですか?

必須ではありません。仕様上、共有文字列テーブルを使うことは有効なファイルを作る条件ではなく、セルに直接文字列を埋めるinlineStr形式も正当です。sharedStringsへの追記は変更部品が増えるうえ、件数を表すcount・uniqueCountの整合も必要になるため、書き込み用途ではinlineStrのほうが副作用が小さくなります。

関数が入っているセルに値を書き込んでもよいですか?

書き込み対象から外してください。数式セルを書き換えると、計算順序を記録したcalcChain.xmlの内容と実際の数式が食い違います。書き込み先は文字列セルと空セルに限定し、数式セルには触らない設計にするのが安全です。

行が足りないときに行を挿入して対応できますか?

既存様式では避けてください。プログラムでの行挿入は罫線が伝播せず、結合範囲も追随しないため、表の途中に無罫線の穴が空きます。十分な空行を持つ固定枠の様式に値だけを流し込み、超過分は挿入せず件数を注記する設計にします。

読むより、​動かす。

まずはお問い合わせから。現場で効く始め方をご提案します。

相談する