ホーム / お役立ち記事 / Python で書き出した Excel が「修復」を求める理由|消えた名前空間の宣言と出す前の検査
Knowledge
Python で書き出した Excel を開くと「一部の内容に問題が見つかりました」と出て、修復を求められることがあります。弊社が切り分けた事例では、XML を ElementTree で読み書きして名前空間の宣言が落ち、互換性の属性だけが残っていました。openpyxl で読み直せても、Excel は修復を求めます。原因の切り分け、見分け方、宣言を補う直し方、落とし穴、出す前に通す検査を、弊社が実際の Excel で測った結果と ECMA-376 の仕様でまとめます。
Python で書き出した Excel ファイルを開くと、「一部の内容に問題が見つかりました。可能な限り内容を回復しますか?」と出て、修復を求められることがあります。
弊社が切り分けた事例では、原因は XML を Python の ElementTree で読み書きしたことでした。使っていない名前空間の宣言が落ち、その名前を挙げた互換性の属性だけが残っていました。
直し方は、書き出した XML に、落ちた宣言を補うことです。弊社が試した構成では、宣言を補った版は、2つの書き出し方とも Excel で修復を求められずに開きました(別のブックを作る方式の版は、calcChain.xml も外しています)。
この記事は、既存の Excel の様式に値を書き込む仕組みを作る・任されている業務の担当者・情報システムの担当者に向けて書きます。
原因の切り分け、見分け方、直し方、落とし穴、出す前に通す検査を、弊社が実際の Excel で測った結果と、ECMA-376(Office のファイルの形式の国際規格)の仕様で整理します。
.xlsx の中身は、ZIP にまとめた XML のファイル(パート)の束です。各パートの先頭には、新しい版の Excel が足した情報の名前空間が、x14ac のような短い名前(接頭辞)で宣言されています。
同じ先頭に、mc:Ignorable="x14ac xr" のような互換性の属性があります。「この接頭辞の情報は、読めなければ無視してよい」という印です。
ECMA-376 の Part 3(互換性と拡張)は、この属性に挙げる接頭辞ごとに、その要素から見える範囲で、接頭辞を結びつけた名前空間が無ければならない、と定めています(7.2 節)。
AlternateContent の中の mc:Choice Requires="x14" という属性も、同じ制約です(7.6 節・7.4 節)。
挙げた要素から見える範囲に宣言が無い接頭辞を使った例は、仕様の付録(参考)で「非適合」とされています(A.1.3)。
仕様は、処理系が非適合を見つけたら、それを利用側に知らせるべき(should)としています(9.1 節)。
非適合のパートを Excel がどう扱うかは、仕様には書かれていません。弊社の実測では、Excel はそのパートを捨てるか置き換えました。
Python の標準の xml.etree.ElementTree で XML を読み、書き直すと、中で使われていない名前空間の宣言が出力から消えます。
属性の値は文字列なので、mc:Ignorable に並んだ接頭辞はそのまま残ります。
その結果、「宣言の無い接頭辞を互換性の属性に挙げた」XML ができます。弊社の調べ(2026年9月・Python 3.14)で、この形が修復の原因でした。
同じ動きは、短いコードで再現できます。次は、弊社が 2026年11月1日に Python 3.14.6 で流した結果です。
入力: <styleSheet xmlns="…/main" xmlns:mc="…" mc:Ignorable="x14ac x16r2 xr"
xmlns:x14ac="…" xmlns:x16r2="…" xmlns:xr="…"> …
出力: <ns0:styleSheet xmlns:ns0="…/main" xmlns:ns1="…" ns1:Ignorable="x14ac x16r2 xr"> …
x14ac などの宣言は消え、Ignorable の値だけが残りました。register_namespace で x14ac を登録しても、パートの中で使っていなければ宣言は出力されませんでした。
弊社は、お客様の既存の Excel の様式に値を書き込み、帳票として出す仕組みを作っています。その出力を開くと、毎回修復を求められる事象を切り分けました。
書き出しは2つの方式で、どちらも ElementTree で XML を書き直す工程を持っていました。
| 項目 | 内容 |
|---|---|
| 実測の日 | 2026年9月 |
| Excel | Mac 版の Microsoft Excel(AppleScript で開閉を自動化。版の番号は記録していない) |
| 書き出し | Python 3.14 の xml.etree.ElementTree |
| 検証の道具 | Open XML SDK 3.5.1(.NET) |
結果は次のとおりです。お客様の様式の原本は、同じ Excel で修復を求められずに開きました。原因は様式ではなく、書き出しの側にありました。
| ファイル | 修復 |
|---|---|
| お客様の様式の原本(対照) | 出ない |
| 別のブックを作る方式の出力 | 出る(シート3枚を置き換え・calcChain.xml の記録を削除) |
同・Ignorable の宣言だけ補った版 |
出る(シート1枚だけ置き換え) |
同・Requires の宣言も補い、calcChain.xml を外した版 |
出ない |
| シートを足す方式の出力 | 出る(styles.xml を削除) |
| 同・styles.xml の宣言だけ補った版 | 出ない |
Ignorable の宣言だけを補った版で残った1枚は、Requires="x14" を持つシートでした。x14 の宣言も落ちていたためです。
シートを足す方式で欠けていたのは、styles.xml の mc:Ignorable が挙げる x14ac と x16r2 の宣言でした。足したシート自体は、mc の属性と要素を落としてから書き出していたので、無傷でした。
修復のあと、Excel は修復の記録(ログ)を XML で残します。シートを足す方式の出力では、ログに次の行がありました。
削除されたパーツ: /xl/styles.xml パーツに XML エラーがありました。 (スタイル) HRESULT 0x808c0002
修復されたレコード: /xl/worksheets/sheet1.xml パーツ内のセル情報
この壊れ方は、弊社が出す前に回していた検査を、全部すり抜けました。実際の Excel と Open XML SDK だけが見つけました(SDK は後から確かめました)。
| 検査 | この壊れ方を見つけたか |
|---|---|
| パートの差分・値の読み戻し | 見つけない |
| openpyxl での読み直し | 見つけない |
| LibreOffice での PDF 化 | 見つけない |
| Open XML SDK の検証 | 見つけた(互換性の誤り) |
| 実際の Excel で開く | 見つけた(修復を求めた) |
Open XML SDK の OpenXmlValidator は、誤りの種類を MarkupCompatibility として返しました。
openpyxl の文書は、openpyxl が Excel のファイルのすべての項目を読むわけではない、と書いています。openpyxl で読み直せることは、Excel で修復が出ないことの証拠になりません。
修復を求められたら、まずログを読み、どのパートが捨てられたかで分けます。
| 見え方 | 疑う所 |
|---|---|
| 置き換えられた・削除されたパーツに「XML エラー」「0x808c0002」 | まず、そのパートの名前空間の宣言の欠け(弊社のログ3本は、どれもこれだった) |
| calcChain.xml の数式の記録が削除された | 上の巻き添えのことがある。宣言を直してから見直す |
| 宣言の欠けが0なのに、修復を求められる | 別の層の誤り(落とし穴の節のパート名など)。Open XML SDK で開けるかを見る |
Mac 版の Excel では、ログは次の場所に「結果の修復先」で始まる名前の XML で書かれました。
~/Library/Containers/com.microsoft.Excel/Data/tmp/
弊社が実際に通した手順は次のとおりです。
弊社の是正の後に作った出力4本(方式の違うものを含む)を同じ Excel で開き、4本とも修復を求められませんでした。
もう1つの直し方は、書き出すパートから mc の属性と要素を落とすことです。弊社が足したシートは、この形で無傷でした。
XML を文字列のまま書き換える方式なら、宣言は元のまま残ります。既存の様式に、パートを文字列で差し替えて書き込む方式は、Excel帳票への自動書き込みでファイルが壊れる理由にまとめています。
その記事の5つの検査は、部品が黙って消えていないかを見るものです。開くと修復を求められるかは、この記事の確かめ方の節の検査で見ます。
次のコードは、弊社が単離の実験で使った変換を縮め、欠けを数える check を足したものです。check は宣言の欠けを数え、fix は宣言を補い、ほかのパートは元のバイトのまま詰め直します。
import re
import sys
import zipfile
MC = "http://schemas.openxmlformats.org/markup-compatibility/2006"
KNOWN = {
"mc": MC,
"x14": "http://schemas.microsoft.com/office/spreadsheetml/2009/9/main",
"x14ac": "http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac",
"x15": "http://schemas.microsoft.com/office/spreadsheetml/2010/11/main",
"x15ac": "http://schemas.microsoft.com/office/spreadsheetml/2010/11/ac",
"x16": "http://schemas.microsoft.com/office/spreadsheetml/2014/main",
"x16r2": "http://schemas.microsoft.com/office/spreadsheetml/2015/02/main",
"xr": "http://schemas.microsoft.com/office/spreadsheetml/2014/revision",
"xr2": "http://schemas.microsoft.com/office/spreadsheetml/2015/revision2",
"xr3": "http://schemas.microsoft.com/office/spreadsheetml/2016/revision3",
"xr6": "http://schemas.microsoft.com/office/spreadsheetml/2016/revision6",
"xr10": "http://schemas.microsoft.com/office/spreadsheetml/2016/revision10",
}
ROOTS = "styleSheet|worksheet|workbook|sst|calcChain|comments|table|xdr:wsDr"
ROOT_RE = re.compile(rf"<({ROOTS})\b[^>]*>")
def missing_prefixes(xml):
m = ROOT_RE.search(xml)
if not m:
return None, []
tag = m.group(0)
# ElementTree は mc の接頭辞を ns1 などに付け替えることがある
mcp = re.findall(r'xmlns:([A-Za-z0-9]+)="' + re.escape(MC) + '"', xml)
listed = []
for p in mcp or ["mc"]:
mm = re.search(rf'{p}:Ignorable="([^"]*)"', tag)
if mm:
listed += mm.group(1).split()
for mm in re.finditer(rf'<{p}:Choice\b[^>]*Requires="([^"]*)"', xml):
listed += mm.group(1).split()
declared = set(re.findall(r"xmlns:([A-Za-z0-9]+)=", xml))
return m, [p for p in dict.fromkeys(listed) if p not in declared]
def fix_part(xml):
m, miss = missing_prefixes(xml)
if not miss:
return xml, []
unknown = [p for p in miss if p not in KNOWN]
if unknown:
raise SystemExit(f"URI が分からない接頭辞: {unknown}")
add = "".join(f' xmlns:{p}="{KNOWN[p]}"' for p in miss)
tag = m.group(0)
return xml[:m.start()] + tag[:-1] + add + ">" + xml[m.end():], miss
def main(mode, src, dst=None):
zin = zipfile.ZipFile(src)
rep, found = {}, []
for n in zin.namelist():
if not n.endswith(".xml"):
continue
try:
x = zin.read(n).decode("utf-8")
except UnicodeDecodeError:
continue
if mode == "check":
miss = missing_prefixes(x)[1]
else:
x2, miss = fix_part(x)
if miss:
rep[n] = x2.encode("utf-8")
if miss:
found.append((n, miss))
print(src, "宣言の無い接頭辞:", found or "なし")
if mode == "check":
return 1 if found else 0
with zipfile.ZipFile(dst, "w", zipfile.ZIP_DEFLATED) as zout:
for item in zin.infolist():
data = rep.get(item.filename) or zin.read(item.filename) # 直したパート以外は元のバイト
zi = zipfile.ZipInfo(item.filename, date_time=item.date_time)
zi.compress_type = zipfile.ZIP_DEFLATED
zi.external_attr = item.external_attr
zout.writestr(zi, data)
return 0
if __name__ == "__main__":
sys.exit(main(*sys.argv[1:]))
使い方は python mc_decl.py check 出力.xlsx(欠けがあれば終了コード1)と、python mc_decl.py fix 入力.xlsx 直した.xlsx です。
弊社は、宣言を欠かせた合成のファイルで、check が欠けを見つけ、fix の後に0になることを確かめました。
このコードは、先頭の要素を決まった8種の名前(styleSheet・worksheet など)で探します。
挙げていない種類のパートは、検査の外になります。ElementTree で既定の名前空間を登録せずに書き出すと、先頭が ns0:styleSheet になります。
その形では、このコードは欠けを見つけません(弊社の合成のファイルで確かめました)。書き出す前に、既定の名前空間を register_namespace("", …) で登録しておきます。
Ignorable だけ直しても、修復は残ったシートの中の mc:Choice Requires="x14" の宣言も落ちていました。先頭の要素の属性だけでなく、パートの中の Requires も見ます。最後の件では、Open XML SDK はファイルを開く段階で止まり、次の文を出しました。
Part URI is not valid per rules defined in the Open Packaging Conventions specification
ECMA-376 の Part 2(2021年の第5版)は、ZIP の中の名前を ASCII だけにし、ASCII でない文字はパーセントの形に変えると定めています(7.3.3・7.3.4 節)。
ASCII でないパート名への対応は、推奨にとどめ、実装に求めていません(6.2.2.2 節)。
弊社のファイルでは、生の日本語が、ZIP の中の名前・[Content_Types].xml・関係の定義(.rels)の3か所に入っていました。
パート名を sheet4.xml のような ASCII の名前にし、docProps/app.xml のシートの数と名前を実体に合わせて直しました。
弊社は、この件の後、新しい形の xlsx を出す前に次の3つを通す形にしました。1つ目と2つ目は機械で回し、3つ目は Excel で1本開きます。
check のように、互換性の属性が挙げる接頭辞が全部宣言されているかを数え、0を合格にする1つ目の検査を作ったら、正しいファイルで止まらない(誤検出が無い)ことも確かめます。弊社が実際に使っている検査(上のコードとは別に作ったもの)は、お客様の原本の約50本で誤検出が0でした。
2つ目は、弊社では次の形で流しています(.NET と Open XML SDK 3.5.1。要点だけに縮めたもの)。
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Validation;
foreach (var path in args)
{
try
{
using var doc = SpreadsheetDocument.Open(path, false);
var validator = new OpenXmlValidator(FileFormatVersions.Office2016);
foreach (var e in validator.Validate(doc))
Console.WriteLine($"{e.ErrorType} | {e.Part?.Uri} | {e.Description}");
}
catch (Exception ex)
{
Console.WriteLine("OPEN FAILED: " + ex.Message);
}
}
SDK の誤りの種類には、Schema・Semantic・Package・MarkupCompatibility の4つがあります(Microsoft の ValidationErrorType の文書)。
弊社は SDK の結果を Excel の判定の代わりにはせず、最後は Excel で開いて確かめます。
3つ目は、弊社の調べでは AppleScript で Excel を開閉し、修復の画面の有無とログを自動で読みました。手で1本開いて見るだけでも、この記事の壊れ方は見つかります。
書き出しのコードを自社で持っていて、XML を書き直す所が少なく、出す前に Excel で開く工程を足せるなら、自力で足ります。直しは、書き直した直後に宣言を補う関数を通すことです。
自力の壁は次の3つです。
外注の分かれ目は、帳票を出すたびに、Excel で修復が出ないことを確かめる工程を、自社で持ち続けられるかです。外注するなら、見積もりを比べる前に次の3点を確かめてください。
ゴムマリでは、既存の Excel の様式へ書き出す業務を含め、1つの業務を回す AI エージェントを御社の業務に合わせて作る仕事を、AIエージェント導入として承ります。決めた確かめを通らない出力は、出さずに止める作りにします。
お渡しするのは、その業務の AI エージェント、判断の基準を書いた文書、使い方の手順です。料金は1つの業務ごとに初期50万円から、月額5万円から、初期構築は4週間からです。
月額には、出す前の確かめを保ち、足していくことを含みます。進め方は相談からご連絡ください。
修復を求められる帳票を1本選び、ZIP として開いて、styles.xml とシートの XML の先頭を見てください。mc:Ignorable に並ぶ接頭辞のうち、xmlns: の宣言が無いものがあれば、この記事の原因です。
宣言を補った版を Excel で開き、修復が出なくなるかを確かめます。出なくなったら、書き出しのコードの、XML を書き直している所を全部洗い出します。
帳票を自動で作るときに、数字が正しいことを仕組みで確かめる方法は、既存Excel様式を自動生成するときの品質保証にまとめています。
関連記事: Excel帳票への自動書き込みでファイルが壊れる理由/既存Excel様式を自動生成するときの品質保証/Python で値を書いた Excel の計算結果が古いままの理由
ワークスアプリケーションズで大手企業向けERPの営業と導入を担当し、外資系SaaS企業と国内大手放送局で販路と新規事業の立ち上げ、AI学習用データを扱うAIスタートアップ2社でマーケティングと営業を担った。2024年10月から生成AIの実装に入り、いまは大手企業の現場で、AIエージェントを要件定義から運用まで手がけている。
FAQ
弊社が切り分けたのは、XML を ElementTree で読み書きした場合です。openpyxl の保存で修復が出るかは確かめていません。openpyxl の文書は、すべての項目を読むわけではないので、開いて同じ名前で保存すると図形が失われる、と警告しています。
捨てられたパートによります。弊社の修復のログには、シートのパートを置き換えた版と、書式の定義のパート(styles.xml)を削除した版がありました。回復した後の中身が元と同じとは限らないので、回復に頼らず、修復が出ないファイルを出してください。
弊社の実測では、calcChain.xml は原因ではなかったとみています。`Ignorable` の宣言だけを補った版でも、calcChain.xml を残したまま、その削除の記録が消えました。シートのパートが置き換えられた巻き添えで、計算の記録が削除されていたとみています。
弊社の実測は Mac 版の Microsoft Excel だけで、Windows 版では確かめていません。宣言の無い接頭辞を互換性の属性に挙げることは、ECMA-376 の仕様で非適合です。どの版で開くかによらず、出す前に宣言の欠けを検査で0にしておきます。
あります。弊社では、パートの差分・値の読み戻し・openpyxl での読み直し・LibreOffice での PDF 化がすべて通ったファイルで、Excel が修復を求めました。検査は、見ている層の壊れ方しか見つけません。出す前に Excel で1本開いて確かめます。
Related
Excel の台帳を Notion へ移すには、表の形とキーを決め、型を決めてリレーションと一緒に取り込み、数式と集計を作り直してから、件数・キー・値・先頭のゼロ・空の値・リレーション・計算の結果の7つで照合します。Notion の公式ヘルプと API の文書の決まりと、弊社が管理表の約2千明細を移し、Notion で計算した残が移したすべてのロットで元の表と一致した照合の手順でまとめます。
活用ガイドNotion AI に書類を作らせて照合するときは、手順を Notion AI への指示書、読めば決まる処理を添付のコード、1回ごとの結果を記録のデータベースに分けます。弊社の導入の事例の試作では、同じ題材で、文章の指示は発注番号を3回のうち2回誤り、添付のコードは2回とも誤りませんでした。分け方の問い、進める手順、誤りを仕込む照合の進め方を、弊社の記録でまとめます。