ホーム / お役立ち記事 / xlutils で .xls に書くと書式が崩れる理由|レコードだけを差し替えて体裁を保つ
Knowledge
古い .xls(Excel 97-2003)の様式に Python の xlutils で値を書くと、書いたセルの罫線や塗りが消え、数式・入力規則・印刷範囲も落ちることがあります。LibreOffice で開いて保存し直すと、選択範囲内で中央の見出しが結合セルに変わります。原因、見分け方、書くセルのレコードだけを差し替える直し方、落とし穴、確かめ方を、弊社の実測と実際の Excel での確認でまとめます。
古い .xls(Excel 97-2003 の形式)の様式に、Python の xlutils で値を書くと、書いたセルの罫線や塗りが消えることがあります。数式・入力規則・オートフィルタ・印刷範囲も、何も言われずに落ちます。
原因は、xlutils が様式を「読めた分だけ読み、新しいブックとして書き直す」ことです。LibreOffice で開いて保存し直す方法も、ブック全体を書き直すので、選択範囲内で中央の見出しが結合セルに変わりました。
直し方は、ブックを書き直さず、書くセルのレコードだけを差し替えることです。弊社がお客様の .xls の様式3本で試したこの方式では、書いた欄以外の書式の差は0でした。
この記事は、既存の .xls の様式に値を書き込む仕組みを作る・任されている業務の担当者・情報システムの担当者に向けて書きます。
弊社の実測と見本での再現、[MS-XLS](Microsoft が公開する .xls の形式の仕様)、xlrd と xlutils の文書で整理します。
.xls の中身は、1つのファイルの中に複数のストリームを持つ複合文書(OLE の複合ファイル)です。[MS-XLS] は、ブックは Workbook ストリームを必ず持つ、と定めています(2.1.1・2.1.2 節)。
Workbook ストリームは、レコードと呼ぶ塊の並びで書きます(2.1.2 節)。セルの書式は XF レコードが持ち、XF を拡張する書式は XFExt レコードが持ちます(2.2.6.1.1・2.4.355 節)。
xlutils の文書は、xlutils.copy を「xlrd で読んだブックを xlwt のブックに写し、変えられるようにする」関数と説明しています。
文書の例は、写したブックに wb.get_sheet(0).write(0,0,'changed!') で書き、別のファイルに保存します。
xlrd の文書は、数式は読まず計算結果だけを取り出し、オートフィルタと入力規則などは読まずに無視する、と書いています。
つまり xlutils で保存した .xls には、xlrd が読まなかった部品が入りません。弊社の再現では、xlrd が読む印刷範囲の名前も、写しには入りませんでした。
LibreOffice で開いて保存し直す方法も、保存のときにブック全体を書き直します。
弊社は、お客様の担当の方が .xls の様式を1枚ずつ開いて手で直している作業を、直した様式そのものを出す形に置き換える仕組みを作っています。そのために、2つの書き方を比べました。
| 項目 | 内容 |
|---|---|
| 実測の日 | 2026年9月 |
| 様式 | お客様の .xls 3本(帳票2種とひな形1本) |
| 書いた欄 | 60セル |
| 比べた物 | 書いた欄以外の全セル(約2万7千セル)の書式、印刷の設定、名前の定義、図形、ファイルの中のストリーム |
| 書き方 | 結果 |
|---|---|
| LibreOffice で開いて保存し直す | 3本中2本で様式の作りが変わった |
| 書くセルのレコードだけを差し替える | 3本とも書式の差0・印刷の設定と名前と図形の差0 |
LibreOffice で保存し直した版で変わった所は、次のとおりです。
レコードだけを差し替えた版は、Workbook 以外のストリームがバイト単位で元のままでした。同じ入力で2回流すと、出力は2回ともバイト単位で同じでした。
2026年9月、この方式で書いた .xls 4本を実際の Excel で開き、修復を求める画面は出ませんでした。見え方と、Excel の版、Mac か Windows かは記録していません。
お客様の様式は使わずに、弊社で作った見本の帳票で、xlutils を含む4つの書き方を比べました。見本は、結合・罫線・塗り・列幅・入力規則・オートフィルタ・印刷範囲・数式2つを持つ2シートの .xls で、Excel に保存させたものです。
| 項目 | 内容 |
|---|---|
| 再現の日 | 2026年11月2日 |
| 書いた欄 | 17セル(文字・数値・日付) |
| 道具 | Python 3.14.6・xlrd 2.0.2・xlwt 1.3.0・xlutils 2.0.0・LibreOffice 26.2.4.2 |
| Excel | Mac 版の Microsoft Excel 16.114(AppleScript で開き、体裁を読んだ) |
結果は次のとおりです。「書式番号を戻す」は、書く前のセルの書式番号を、書いた後に戻したものです。
| 書き方 | 書いたセルの書式 | 数式2つ | 入力規則・オートフィルタ・印刷範囲 | 選択範囲内で中央 | 灰色の塗り(Excel で読んだ色) |
|---|---|---|---|---|---|
| xlutils(文書の例どおり write) | 17セルとも消えた | 数字の0に置き換わった | 3つとも消えた | 保った | 別の灰色に変わった |
| xlutils(書式番号を戻す) | 罫線・塗り・フォントは保った(日付の表示は変わった) | 数字の0に置き換わった | 3つとも消えた | 保った | 別の灰色に変わった |
| LibreOffice で保存し直す | 見た目は保った(日付の書式は作り変わった) | 数式のまま・計算し直された | 保った(名前が2つ増えた) | 結合セルに変わった | 別の灰色に変わった |
| レコードだけを差し替える | 保った | 数式のまま・結果を差し替えた | 保った | 保った | 元の灰色のまま |
xlutils の版で数式が0になったのは、xlrd が数式の計算結果だけを読み、写しに数字として書いたためです。原本の計算結果は、入力欄が空のときの0でした。
xlutils の文書の例どおりに書いた版では、書いたセルのフォントが Arial に変わり、日付は Excel で「46328」という数字のまま表示されました。
書式番号を戻した版でも、日付は、レコードだけを差し替えた版の「2026/11/2」でなく「11/2/26」と表示されました。
日付の書式番号が、組み込みの14から、xlwt が作った独自の165(m/d/yy)に変わったためです。「2026/11/2」は、組み込みの14をこの Mac の地域の設定で表示したもので、設定によって変わりえます。
書式番号を戻した版では、列幅の記録が使っていない列の分(1枚目248列・2枚目253列)だけ消えました。2行目と17行目は、高さの指定の無い状態から 255(原本の既定は 380・単位は1/20ポイント)になりました。
LibreOffice の版でも、書いた日付のセルの書式は、組み込みの14から独自の書式([$-411]yyyy/m/d)に作り変わりました。
原本の灰色は、XFExt に 166・166・166 を、XF に近い標準の色(150・150・150)の番号を持っていました。XFExt が落ちた版は、Excel で 150・150・150 と読めました。
レコードだけを差し替えた版は、弊社の比べる道具で差が0でした。Excel から読んだ結合の有無(5つのセル)・列幅(8列)・行の高さ(3行)・罫線(1か所)・塗り(5つのセル)・印刷範囲も、原本と同じでした。
| 見え方 | 疑う所 |
|---|---|
| 書いたセルだけ罫線と塗りが消え、文字が Arial になった | xlutils の write が、書く前の書式を引き継いでいない |
| 合計の欄が数字のままで、値を変えても動かない | xlrd が数式を読まず、計算結果を数字として写した |
| 印刷範囲・オートフィルタ・入力規則のプルダウンが無い | xlrd が読まないか、xlutils が写さない部品で、写しに入っていない |
| 選択範囲内で中央の見出しが、結合セルになった | LibreOffice で開いて保存し直した |
| 灰色などの塗りの色が、少しだけ変わった | 拡張色の XFExt が落ちた |
弊社が実際に通した手順は次のとおりです。
4の位置の情報は、[MS-XLS] が次のように定めています。
| レコード | 持っている位置 | 節 |
|---|---|---|
| BoundSheet8 | 各シートの先頭(BOF レコード)の、ストリームの中の位置 | 2.4.28 |
| Index | 行の塊(DBCell レコード)ごとの位置 | 2.4.144 |
| DBCell | 行の塊の中の、セルのレコードの位置 | 2.4.78 |
| ExtSST | 共有文字列表の中の、文字列の組の位置 | 2.4.107 |
5は位置の計算の検査です。弊社の道具は、お客様の様式3本と弊社の見本で、書かずに組み直した Workbook ストリームが原本とバイト単位で同じでした。
6は、弊社は LibreOffice で書く前と後を計算し、値が変わった数式のセルだけ結果を差し替えました。考え方はPython で値を書いた Excel の計算結果が古いままの理由と同じです。
弊社の道具は、値も書式も無いセルへの書き込み、文字列を返す数式の結果の差し替え、大きいファイル(複合文書の管理の表が足りなくなるもの)には対応していません。
当たったら書かずに止まるようにコードを作ってあります(今回の再現では、止まる場面は流していません)。
xlutils を使い続けるなら、書く前の書式番号を戻すと、書いたセルの罫線・塗り・フォントは保てました(日付の表示は変わりました)。弊社の再現で流した形です。
import xlrd
from xlutils.copy import copy
rb = xlrd.open_workbook("様式.xls", formatting_info=True)
wb = copy(rb)
ws = wb.get_sheet(0)
def write_keep(ws, r, c, v):
row = ws._Worksheet__rows.get(r) # xlwt の内部の属性(公開の使い方ではない)
old = row._Row__cells.get(c) if row else None
ws.write(r, c, v)
if old is not None:
ws._Worksheet__rows[r]._Row__cells[c].xf_idx = old.xf_idx
write_keep(ws, 2, 2, "TEST-001") # C3(行と列は0から数える)
wb.save("出力.xls")
この形でも、数式・入力規則・オートフィルタ・印刷範囲・拡張色は戻らず、日付の書式番号と列幅・行の高さの記録も変わります。xlwt の内部の属性を使うので、版が変わると動かなくなるおそれがあります。
4つ目の件の .xls(LibreOffice 26.2.4.2 で変換)には、組み込みの印(fBuiltin・[MS-XLS] 2.4.150 節)の無い「_xlnm.Print_Area」がありました。
弊社は今、Excel に開かせる前に、この印を道具で見ています。
書く前の様式と書いた後の出力を、全セルの書式と、書き直すと落ちたり増えたりする部品のレコードの数で比べます。次は、弊社が再現で使った道具です(xlrd 2.0.2 と olefile 0.47)。
import collections
import struct
import sys
import olefile
import xlrd
# Workbook ストリームの中で、書き直すと落ちたり増えたりする部品のレコード(数だけを比べる)
WATCH = {0x00E5: "結合セル", 0x087D: "拡張色(XFEXT)", 0x0092: "パレット", 0x0018: "名前の定義",
0x00A1: "印刷の設定", 0x0014: "ヘッダー", 0x0015: "フッター", 0x01BE: "入力規則",
0x009D: "オートフィルタ", 0x005D: "図形(OBJ)", 0x0006: "数式"}
def records(path):
data = olefile.OleFileIO(path).openstream("Workbook").read()
cnt, pos = collections.Counter(), 0
while pos + 4 <= len(data):
rt, ln = struct.unpack_from("<HH", data, pos)
if rt in WATCH:
cnt[WATCH[rt]] += 1
pos += 4 + ln
return cnt
def formats(bk):
out = {}
for sh in bk.sheets():
for r in range(sh.nrows):
for c in range(sh.ncols):
xf = bk.xf_list[sh.cell_xf_index(r, c)]
f, b, bg, al = bk.font_list[xf.font_index], xf.border, xf.background, xf.alignment
out[(sh.name, r, c)] = (
f.name, f.height, f.weight, f.italic, bk.colour_map.get(f.colour_index), # 太字は weight で見る(bold の印は書き手で違う)
b.left_line_style, b.right_line_style, b.top_line_style, b.bottom_line_style,
bg.fill_pattern, bk.colour_map.get(bg.pattern_colour_index),
al.hor_align, al.vert_align, bk.format_map[xf.format_key].format_str)
return out
def layout(bk):
out = {}
for sh in bk.sheets():
out[(sh.name, "結合")] = sorted(sh.merged_cells)
out[(sh.name, "列幅")] = {c: i.width for c, i in sh.colinfo_map.items()}
out[(sh.name, "行の高さ")] = {r: i.height for r, i in sh.rowinfo_map.items()}
return out
def main(orig, out):
a = xlrd.open_workbook(orig, formatting_info=True)
b = xlrd.open_workbook(out, formatting_info=True)
fa, fb = formats(a), formats(b)
cells = [k for k in sorted(set(fa) | set(fb)) if fa.get(k) != fb.get(k)]
print("セルの書式が変わった:", len(cells), "セル", cells[:5])
la, lb = layout(a), layout(b)
for k in sorted(set(la) | set(lb)):
x, y = la.get(k), lb.get(k)
if x == y:
continue
if isinstance(x, dict):
ch = sorted(i for i in set(x) | set(y or {}) if x.get(i) != (y or {}).get(i))
print("変わった:", k, len(ch), "か所", [(i, x.get(i), y.get(i)) for i in ch[:3]])
else:
print("変わった:", k, "増えた", sorted(set(y) - set(x))[:3], "減った", sorted(set(x) - set(y))[:3],
"(%d→%d)" % (len(x), len(y)))
ra, rb = records(orig), records(out)
for k in sorted(set(ra) | set(rb)):
if ra[k] != rb[k]:
print("レコードの数が変わった:", k, ra[k], "→", rb[k])
n = len(cells) + sum(la.get(k) != lb.get(k) for k in set(la) | set(lb)) + sum(ra[k] != rb[k] for k in set(ra) | set(rb))
print("差の数:", n)
return 1 if n else 0
if __name__ == "__main__":
sys.exit(main(sys.argv[1], sys.argv[2]))
使い方は python xls_diff.py 様式.xls 出力.xls で、差があれば終了コード1です。弊社の再現では、レコードだけを差し替えた版が差0、書式番号を戻した版が差9、xlutils の文書の例どおりの版が差26でした。
この道具は、セルの書式を xlrd が読める範囲で比べ、結合・名前・数式・入力規則などの部品はレコードの数だけを見ます。
日付の書式番号が組み込みから独自の物に変わっても、表示の文字が同じなら見逃します。レコードの数が同じでも、中身が変わっていれば見逃します。
最後は Excel で開いて確かめます。弊社は AppleScript で Excel に写しを開かせ、30秒で打ち切る問いが約2秒で返ることと、結合・列幅・塗り・印刷範囲を読んで原本と比べました。
書いたセルの罫線・塗り・フォントだけを保てばよく、日付の表示と列幅・行の高さの差を受け入れられるなら、xlutils で書いて書式番号を戻す形が使えます。
ただし、様式に数式・入力規則・オートフィルタ・印刷範囲・標準でない色の塗りがあれば落ちます。xlrd の文書が読まないと書くグラフ・マクロ・画像・コメント・ハイパーリンク・条件付き書式も落ちるはずですが、弊社は試していません。
様式を .xlsx に変えてよいなら、道具を選べます。.xlsx の様式に壊さず書く方法はExcel帳票への自動書き込みでファイルが壊れる理由にまとめています。
自力の壁は次の3つです。
外注の分かれ目は、.xls の様式を .xlsx に変えられず、様式の持ち主が体裁の崩れを許さないかです。外注するなら、見積もりを比べる前に次の3点を確かめてください。
ゴムマリでは、既存の Excel の様式へ書き出す業務を含め、1つの業務を回す AI エージェントを御社の業務に合わせて作る仕事を、AIエージェント導入として承ります。決めた確かめを通らない出力は、出さずに止める作りにします。
お渡しするのは、その業務の AI エージェント、判断の基準を書いた文書、使い方の手順です。料金は1つの業務ごとに初期50万円から、月額5万円から、初期構築は4週間からです。
月額には、出す前の確かめを保ち、足していくことを含みます。進め方は相談からご連絡ください。
値を書いている .xls の出力を1本選び、書く前の様式と並べて、確かめ方の節の道具を流してください。差の行を見れば、この記事の見分け方の表のどれに当たるかが分かります。
関連記事: Excel帳票への自動書き込みでファイルが壊れる理由/Python で値を書いた Excel の計算結果が古いままの理由/Python で書き出した Excel が「修復」を求める理由
ワークスアプリケーションズで大手企業向けERPの営業と導入を担当し、外資系SaaS企業と国内大手放送局で販路と新規事業の立ち上げ、AI学習用データを扱うAIスタートアップ2社でマーケティングと営業を担った。2024年10月から生成AIの実装に入り、いまは大手企業の現場で、AIエージェントを要件定義から運用まで手がけている。
FAQ
弊社の再現では残りませんでした。formatting_info=True で読んで xlutils.copy で写し、文書の例どおり write で書くと、書いた17セルの罫線・塗り・フォントが消えました。書く前のセルの書式番号を戻すと、罫線・塗り・フォントは残りましたが、日付の表示は変わりました。
弊社の再現では、書いたセルの罫線・塗り・フォントは保てましたが、日付の表示と列幅・行の高さの記録が変わり、数式・入力規則・オートフィルタ・印刷範囲は戻りませんでした。xlrd の文書は、数式は計算結果だけを読み、オートフィルタと入力規則は読まない、と書いています。読まない部品は写しに入りません。
弊社の再現では、数式は数式のまま残り、書いた値で計算し直されました。ただし、選択範囲内で中央の見出しが結合セルに変わり、灰色の塗りが別の色になり、名前の定義が2つ増えました。お客様の様式でも、3本中2本で様式の作りが変わりました。
保たれているとは限りません。弊社の再現では、4つの書き方の出力を Excel に開かせ、30秒で打ち切る問いがどれも約2秒で返りました(確認の画面で止まっていないとみています)。それでも、LibreOffice で保存し直した版は見出しが結合セルに変わり、xlutils の版は数式と印刷範囲が消えていました。
弊社の再現で Excel を使ったのは Mac 版の Excel 16.114 だけで、Windows 版では確かめていません。消えた部品(数式・入力規則・オートフィルタ・印刷範囲)は、ファイルの中のレコードの数で確かめたもので、開く Excel の版によりません。
Related
Excel の台帳を Notion へ移すには、表の形とキーを決め、型を決めてリレーションと一緒に取り込み、数式と集計を作り直してから、件数・キー・値・先頭のゼロ・空の値・リレーション・計算の結果の7つで照合します。Notion の公式ヘルプと API の文書の決まりと、弊社が管理表の約2千明細を移し、Notion で計算した残が移したすべてのロットで元の表と一致した照合の手順でまとめます。
活用ガイドNotion AI に書類を作らせて照合するときは、手順を Notion AI への指示書、読めば決まる処理を添付のコード、1回ごとの結果を記録のデータベースに分けます。弊社の導入の事例の試作では、同じ題材で、文章の指示は発注番号を3回のうち2回誤り、添付のコードは2回とも誤りませんでした。分け方の問い、進める手順、誤りを仕込む照合の進め方を、弊社の記録でまとめます。