OOXMLパッケージ内のデータについて、書き換え候補がどれくらいの容量を占めるかを知りたい場面があります。ただし、集計できることと、圧縮後のファイルサイズを予測できることは別です。ここでは、すでに正規化されたメタデータを受け取り、圧縮済みエントリのペイロード容量を正確に足し合わせる小さな関数を扱います。
rewriteCandidate は呼び出し側が渡す真偽値です。関数は拡張子から画像を判定せず、候補を実際に置き換えられるかどうかも判断しません。
export function summarizePayloads(entries) {
if (!Array.isArray(entries) || entries.length > 1000) {
throw new TypeError('Expected at most 1000 metadata rows');
}
const seen = new Set();
let payloadBytes = 0;
let candidateBytes = 0;
for (const row of entries) {
if (!row || typeof row.path !== 'string' || row.path.length === 0 ||
row.path.length > 200 || row.path.includes('\0') || seen.has(row.path) ||
!Number.isSafeInteger(row.compressedBytes) || row.compressedBytes < 0 ||
typeof row.rewriteCandidate !== 'boolean') {
throw new TypeError('Invalid or duplicate metadata row');
}
seen.add(row.path);
if (row.compressedBytes > Number.MAX_SAFE_INTEGER - payloadBytes) {
throw new RangeError('Payload total exceeds safe integer range');
}
payloadBytes += row.compressedBytes;
if (row.rewriteCandidate) candidateBytes += row.compressedBytes;
}
return { entryCount: entries.length, payloadBytes, candidateBytes,
otherBytes: payloadBytes - candidateBytes };
}
次のブロックは合成データによる集計例で、別のOfficeファイルを使った独立した実験ではありません。試す場合は、上の関数の後ろにこのブロックを続けて .mjs ファイルに置きます。
import assert from 'node:assert/strict';
const fixture = [
{ path: 'ppt/presentation.xml', compressedBytes: 400, rewriteCandidate: false },
{ path: 'ppt/media/image1.jpeg', compressedBytes: 1200, rewriteCandidate: true },
{ path: 'ppt/media/video1.mp4', compressedBytes: 8400, rewriteCandidate: false },
];
const expected = { entryCount: 3, payloadBytes: 10000, candidateBytes: 1200, otherBytes: 8800 };
assert.deepEqual(summarizePayloads(fixture), expected);
assert.deepEqual(summarizePayloads([]), { entryCount: 0, payloadBytes: 0, candidateBytes: 0, otherBytes: 0 });
assert.equal(summarizePayloads([{ path: 'a', compressedBytes: 0, rewriteCandidate: true }]).candidateBytes, 0);
assert.equal(summarizePayloads([{ path: 'a', compressedBytes: Number.MAX_SAFE_INTEGER, rewriteCandidate: true }]).candidateBytes, Number.MAX_SAFE_INTEGER);
assert.throws(() => summarizePayloads([fixture[0], fixture[0]]), TypeError);
この入力では、合計10,000バイトのうち、呼び出し側が候補とした行は1,200バイト、残りは8,800バイトです。常に candidateBytes + otherBytes === payloadBytes となります。候補の1,200バイトをすべて削減できるという意味ではありません。実際には置き換えられない場合も、置き換えても小さくならない場合もあります。
重複と数値を先に拒否する
同じ path を2行受け入れると、同一キーの容量を二重に数える可能性があります。この関数は文字列の完全一致で重複を検出します。パスは集計用の不透明なキーであり、安全なファイル名や展開先として検証されたものではありません。
compressedBytes には、0以上の安全な整数だけを受け入れます。負数、小数、NaN、Infinity、安全な整数の範囲を超える値は拒否します。各行が安全でも、合計が Number.MAX_SAFE_INTEGER を超える場合があるため、加算前にも検査します。候補の値は非負で、合計の一部なので、合計が安全なら候補の合計もその範囲内です。
空文字やNULを含むキー、真偽値でないフラグも拒否します。1,000行と200文字の上限は、この例のために設けた制限であり、Office形式や製品の制限ではありません。空配列は集計関数への有効な入力ですが、有効なOfficeパッケージを意味しません。
集計値の範囲を越えて結論を出さない
ここで足しているのは圧縮済みエントリのペイロードで、ZIP全体のヘッダーなどのオーバーヘッドは含みません。そのため、この値だけから出力ZIPのサイズや達成可能な下限は求められません。この関数はOfficeやZIPのパーサー、セキュリティ検証器、ファイル入出力、最適化処理でもありません。集計値は、次に何を調べるかを考えるための材料として使います。
開示:本文はGPT-6.1 TerraがYan Wang / LensUpのために大部分を執筆し、エージェントが実行結果と事実を確認しました。Yan WangはLensUpに所属しています。ソースおよび公開ページのテキストの確認日は2026-10-30です。2026-10-30、別途保管した完全なメタデータ用テストハーネス code/checks.mjs をDarwin上のNode 22.23.4で実行し、有効入力のアサーション4件、拒否ケース16件、ネガティブコントロール2件が通過しました。本文に示した2つのコードブロックに含まれるのは、有効入力のアサーション4件と拒否アサーション1件のみです。実際のOffice最適化や、アーカイブ・リレーションシップ・表示の受け入れテストは行っていません。本文は独立した編集AIが推敲し、Codexが事実確認(コードを含む独立レビュー)を行いました。人間の編集者やオーナーによる確認・テストは行っていません。