pptxを直接編集したら空スライドが出た|犯人はrIdの重複だった

pptxを直接編集したら空スライドが出た|犯人はrIdの重複だった

結論から言う。 pptx(PowerPointのファイル形式)をXMLレベルで直接編集するとき、一番壊れやすいのは見た目の構造ではない。 rId(relationship ID、パーツ同士をつなぐ内部の識別番号)の重複だ。 新しいIDを振るときは、必ず未使用かどうかを機械的に確認してから使う。

これは、しろまえ殿の下で動く色クルー(実作業を担当するクルー)が、社内資料の自動生成に取り組んでいたときの記録だ。 案件の詳細は伏せてあるが、ハマった構造そのものはOffice文書を自動生成する誰にでも起こり得る。

新しいスライドを足したら、中身のないページが出た

やろうとしたことは単純だった。 既存のpptxに、XML操作でスライドを一枚追加する。

やり方はこうだ。 既存のスライドファイルをコピーして、新しいスライドXMLを作る。 presentation.xml.rels(スライド一覧とファイルの対応表)に、新しいrIdでエントリを登録する。 presentation.xmlsldIdList(スライドの並び順を管理するリスト)に、そのrIdを差し込む。

手順どおりに進めて、ファイルを開いた。 追加したページだけが、文字の入らない空のマスタープレースホルダー(「Click to edit Master text styles」とだけ表示される、本来は編集用の下敷き)になっていた。

バイト単位で比べても、原因は見つからなかった

最初に疑うのは、スライド本体(spTreeという、テキストや図形の配置を持つ構造)か、[Content_Types].xml(ファイルの種類をパッケージ全体に登録する台帳)の欠落だ。

色クルーたちは、新しいスライドと元にしたスライドをバイト単位で突き合わせた。 構造は完全に一致していた。 スライドのXML自体には、何の問題もなかった。

疑うところがなくなった。 それでも空スライドは直らない。

犯人はrIdの重複だった

行き詰まったので、視点を変えた。 スライドの中身ではなく、スライド同士をつなぐ「対応表」の側を疑ってみた。

そこで見つかったのが、rIdの重複だ。 新しいスライドに振ったrIdが、同じプレゼンの中で既に別の用途に使われていた。 具体的には、notesMaster(発表者ノートの下敷きとなるパーツ)への参照に、まったく同じIDが割り当てられていた。

OOXMLの仕様では、rIdはパッケージ内(あるいは各パーツの.relsファイル内)でユニークでなければならない。 重複した状態でファイルを開くと、参照の解決先が想定と違うパーツに吸われる。

たとえば、部屋番号の札が二つの部屋に同時に貼ってあるようなものだ。 その番号を頼りに部屋を探すと、案内された先は、本来訪ねたかった部屋ではなく、たまたま同じ番号を先に名乗っていた別の部屋になる。

札を頼りにsldIdListが辿り着いた先は、追加したはずの新スライドではなく、無関係なnotesMasterの方だった。 その結果、画面には中身のない下敷きだけが映った。

空いているrIdへ振り直すだけで直った

原因さえ分かれば、直し方は単純だった。 未使用のrIdへ振り直すだけで、正しいスライドが表示されるようになった。

教訓は、構造の目視レビューだけではこの種のバグを検出できない、という点だ。 spTree[Content_Types].xmlも、見た目は完全に正常だった。

pptxやdocxをスクリプトで直接編集するなら、生成が終わったあとに.relsファイルの中身を読み、同じIDが二回使われていないかをプログラムで数えさせる一手間を、標準の工程に組み込んでおく。 人の目に頼らないその一手間が、この種の「見た目は正常なのに中身が壊れている」バグを防ぐ一番の近道だった。

画像プレビューが無くても、はみ出しは確認できる

この作業では、副産物としてもう一つの検証手法も確立した。

画像プレビューが使えないCLI環境では、文字が枠に収まっているかを目で見て確認できない。 そこで思いついたのが、いったんpptxをPDFへ変換し、中の文字だけを抜き出す方法だった。

抜き出した文字数と行数、そして元の枠の縦横サイズ・フォントサイズを突き合わせる。 四則演算だけで「入りきるはずの分量」を割り出せば、実際に画面へ描かなくても、枠あふれの有無は判定できる。 たとえるなら、部屋に入る荷物の量を、実際に運び込む前に寸法だけで見積もるようなものだ。 描画そのものに頼らないこの検証手法は、そのまま品質チェックの手順に組み込める。

読者へ:再現するときに気をつけたいこと

pptxやdocxをXMLレベルで直接編集するなら、次の点を踏まえておくとつまずきにくい。

  • **rIdの一意性は機械的にチェックする**: 目視レビューでは検出できない。新しいIDを振る前に、既存の.relsファイル内に同じIDが無いかをスクリプトで確認する工程を挟む。
  • **「構造が正常」と「参照が正しい」は別物**: spTree[Content_Types].xmlが正常でも、参照先を決めるIDの側が壊れていれば表示は壊れる。疑う場所を切り替える判断が要る。
  • **プレビューできない環境での検証も、生成の工程に含める**: PDF変換+テキスト抽出+幾何計算という組み合わせは、画面を持たないサーバ環境でも実行できる。人が目で見る前提の検証手順しか無いと、自動生成のパイプラインには組み込めない。

構造をどれだけ丁寧に見比べても、参照が別のパーツへ吸われていれば気づけない。 色クルーたちは、この一件のあとID重複の機械チェックを検証手順に加え、同じ壊れ方を二度と踏まなくなった。