deflate圧縮のzipファイルを作成するJavaScript(とHTML)のコード
deflate圧縮のzipファイルを作成するJavaScript(とHTML)のコードです。
最新の大体のブラウザで動作します。古いブラウザでは動作しない可能性が有ります。
電子書籍(ePub)の仕様では無圧縮かdeflate圧縮のzip圧縮と定められているので、将来、電子書籍(ePub)を作成するJavaScript(とHTML)のコードで利用できます。
最新の大体のブラウザで、JavaScriptの標準APIだけで、deflate圧縮が可能に成ったので、作ってみました。
コピペする場合は、2文字の全角空白を4文字の半角空白に置換してください。
DeflateZip.htmlなどの適当な名前の空の.htmlファイルの内容にコピペしてから、UTF-8という文字コードで保存し、ChromeやFirefoxといったブラウザで.htmlファイルを見ると、利用できます。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<title>deflate圧縮zipファイル作成</title>
</head>
<body>
<div>
ファイル選択 <input type="file" id="file" multiple />
</div>
<br />
<div>
zipファイル名 <input type="text" id="zipFileName" />
</div>
<br />
<div>
<button type="button" id="deflateZip">deflate圧縮zipファイルを作成</button>
</div>
<br />
<br />
<p id="message"></p>
<script>
const $ = (id) => {
return document.getElementById(id);
};
const crc32 = (uint8Array) => {
/*
const crc32Table = [];
for (let index = 0; index < 256; index++) {
let output = index;
for (let bit = 0; bit < 8; bit++) {
if ((output & 0x1) == 0) {
output = output >>> 1;
} else {
output = (output >>> 1) ^ 0xEDB88320;
}
}
crc32Table.push(output >>> 0);
}
*/
const crc32Table = [
0x00000000,
0x77073096,
0xEE0E612C,
0x990951BA,
0x076DC419,
0x706AF48F,
0xE963A535,
0x9E6495A3,
0x0EDB8832,
0x79DCB8A4,
0xE0D5E91E,
0x97D2D988,
0x09B64C2B,
0x7EB17CBD,
0xE7B82D07,
0x90BF1D91,
0x1DB71064,
0x6AB020F2,
0xF3B97148,
0x84BE41DE,
0x1ADAD47D,
0x6DDDE4EB,
0xF4D4B551,
0x83D385C7,
0x136C9856,
0x646BA8C0,
0xFD62F97A,
0x8A65C9EC,
0x14015C4F,
0x63066CD9,
0xFA0F3D63,
0x8D080DF5,
0x3B6E20C8,
0x4C69105E,
0xD56041E4,
0xA2677172,
0x3C03E4D1,
0x4B04D447,
0xD20D85FD,
0xA50AB56B,
0x35B5A8FA,
0x42B2986C,
0xDBBBC9D6,
0xACBCF940,
0x32D86CE3,
0x45DF5C75,
0xDCD60DCF,
0xABD13D59,
0x26D930AC,
0x51DE003A,
0xC8D75180,
0xBFD06116,
0x21B4F4B5,
0x56B3C423,
0xCFBA9599,
0xB8BDA50F,
0x2802B89E,
0x5F058808,
0xC60CD9B2,
0xB10BE924,
0x2F6F7C87,
0x58684C11,
0xC1611DAB,
0xB6662D3D,
0x76DC4190,
0x01DB7106,
0x98D220BC,
0xEFD5102A,
0x71B18589,
0x06B6B51F,
0x9FBFE4A5,
0xE8B8D433,
0x7807C9A2,
0x0F00F934,
0x9609A88E,
0xE10E9818,
0x7F6A0DBB,
0x086D3D2D,
0x91646C97,
0xE6635C01,
0x6B6B51F4,
0x1C6C6162,
0x856530D8,
0xF262004E,
0x6C0695ED,
0x1B01A57B,
0x8208F4C1,
0xF50FC457,
0x65B0D9C6,
0x12B7E950,
0x8BBEB8EA,
0xFCB9887C,
0x62DD1DDF,
0x15DA2D49,
0x8CD37CF3,
0xFBD44C65,
0x4DB26158,
0x3AB551CE,
0xA3BC0074,
0xD4BB30E2,
0x4ADFA541,
0x3DD895D7,
0xA4D1C46D,
0xD3D6F4FB,
0x4369E96A,
0x346ED9FC,
0xAD678846,
0xDA60B8D0,
0x44042D73,
0x33031DE5,
0xAA0A4C5F,
0xDD0D7CC9,
0x5005713C,
0x270241AA,
0xBE0B1010,
0xC90C2086,
0x5768B525,
0x206F85B3,
0xB966D409,
0xCE61E49F,
0x5EDEF90E,
0x29D9C998,
0xB0D09822,
0xC7D7A8B4,
0x59B33D17,
0x2EB40D81,
0xB7BD5C3B,
0xC0BA6CAD,
0xEDB88320,
0x9ABFB3B6,
0x03B6E20C,
0x74B1D29A,
0xEAD54739,
0x9DD277AF,
0x04DB2615,
0x73DC1683,
0xE3630B12,
0x94643B84,
0x0D6D6A3E,
0x7A6A5AA8,
0xE40ECF0B,
0x9309FF9D,
0x0A00AE27,
0x7D079EB1,
0xF00F9344,
0x8708A3D2,
0x1E01F268,
0x6906C2FE,
0xF762575D,
0x806567CB,
0x196C3671,
0x6E6B06E7,
0xFED41B76,
0x89D32BE0,
0x10DA7A5A,
0x67DD4ACC,
0xF9B9DF6F,
0x8EBEEFF9,
0x17B7BE43,
0x60B08ED5,
0xD6D6A3E8,
0xA1D1937E,
0x38D8C2C4,
0x4FDFF252,
0xD1BB67F1,
0xA6BC5767,
0x3FB506DD,
0x48B2364B,
0xD80D2BDA,
0xAF0A1B4C,
0x36034AF6,
0x41047A60,
0xDF60EFC3,
0xA867DF55,
0x316E8EEF,
0x4669BE79,
0xCB61B38C,
0xBC66831A,
0x256FD2A0,
0x5268E236,
0xCC0C7795,
0xBB0B4703,
0x220216B9,
0x5505262F,
0xC5BA3BBE,
0xB2BD0B28,
0x2BB45A92,
0x5CB36A04,
0xC2D7FFA7,
0xB5D0CF31,
0x2CD99E8B,
0x5BDEAE1D,
0x9B64C2B0,
0xEC63F226,
0x756AA39C,
0x026D930A,
0x9C0906A9,
0xEB0E363F,
0x72076785,
0x05005713,
0x95BF4A82,
0xE2B87A14,
0x7BB12BAE,
0x0CB61B38,
0x92D28E9B,
0xE5D5BE0D,
0x7CDCEFB7,
0x0BDBDF21,
0x86D3D2D4,
0xF1D4E242,
0x68DDB3F8,
0x1FDA836E,
0x81BE16CD,
0xF6B9265B,
0x6FB077E1,
0x18B74777,
0x88085AE6,
0xFF0F6A70,
0x66063BCA,
0x11010B5C,
0x8F659EFF,
0xF862AE69,
0x616BFFD3,
0x166CCF45,
0xA00AE278,
0xD70DD2EE,
0x4E048354,
0x3903B3C2,
0xA7672661,
0xD06016F7,
0x4969474D,
0x3E6E77DB,
0xAED16A4A,
0xD9D65ADC,
0x40DF0B66,
0x37D83BF0,
0xA9BCAE53,
0xDEBB9EC5,
0x47B2CF7F,
0x30B5FFE9,
0xBDBDF21C,
0xCABAC28A,
0x53B39330,
0x24B4A3A6,
0xBAD03605,
0xCDD70693,
0x54DE5729,
0x23D967BF,
0xB3667A2E,
0xC4614AB8,
0x5D681B02,
0x2A6F2B94,
0xB40BBE37,
0xC30C8EA1,
0x5A05DF1B,
0x2D02EF8D
];
let output = 0xFFFFFFFF;
for (let index = 0; index < uint8Array.length; index++) {
output = ((output >>> 8) ^ crc32Table[uint8Array[index] ^ (output & 0xFF)]) >>> 0; //Firefoxが浮動小数点数と誤解しないように>>> 0
}
return (~output) >>> 0; //Firefoxが浮動小数点数と誤解しないように>>> 0
};
class DeflateZip {
fileArray = [];
//deflate圧縮
async deflateFile(file) {
//一般的なdeflate圧縮の場合は、new CompressionStream("deflate")だが、
//zipファイル内のデータのdeflate圧縮の場合は、new CompressionStream("deflate-raw")
const compressionStream = new CompressionStream("deflate-raw");
//const blob = new Blob([object]);
//const readableStream = await blob.stream().pipeThrough(compressionStream);
const readableStream = await file.stream().pipeThrough(compressionStream);
const response = new Response(readableStream);
const arrayBuffer = await response.arrayBuffer();
return arrayBuffer;
}
add(file) {
this.fileArray.push(file);
}
async zip() {
const fileArray = this.fileArray;
const numberOfFiles = fileArray.length;
const date = new Date();
const hours = date.getHours();
const minutes = date.getMinutes();
const seconds = date.getSeconds();
const zipTime = (hours << 11) + (minutes << 5) + Math.floor(seconds / 2);
const year = date.getFullYear();
const month = (date.getMonth() + 1); //1以上12以下
const dayOfMonth = date.getDate();
const zipDate = ((year - 1980) << 9) + (month << 5) + dayOfMonth;
//zipのローカル ファイル ヘッダーは30バイト + ファイル名のサイズ + ファイルの内容のサイズ
//zipのセントラル ディレクトリ ヘッダーは46バイト + ファイル名のサイズ
//zipのセントラル ディレクトリの終端レコードは22バイト
let zipLocalFileHeaderStartIndex = 0;
let zipCentralDirectoryHeaderTotalSize = 0;
let zipFileSize = 22;
for (let fileIndex = 0; fileIndex < fileArray.length; fileIndex++) {
const file = fileArray[fileIndex];
file.zipLocalFileHeaderStartIndex = zipLocalFileHeaderStartIndex;
let fileName = file.name;
if (fileName.startsWith("/")) {
fileName = fileName.substring(1);
}
const fileNameByteArray = new TextEncoder("utf-8").encode(fileName);
const fileNameSize = fileNameByteArray.length;
//ファイルの場合
if (fileName.endsWith("/") == false) {
//const fileContentArrayBuffer = await file.arrayBuffer();
//const fileContentSize = fileContentArrayBuffer.byteLength;
const fileCompressedContentArrayBuffer = await this.deflateFile(file);
const fileCompressedContentSize = fileCompressedContentArrayBuffer.byteLength;
zipLocalFileHeaderStartIndex = zipLocalFileHeaderStartIndex + 30 + fileNameSize + fileCompressedContentSize;
zipCentralDirectoryHeaderTotalSize = zipCentralDirectoryHeaderTotalSize + 46 + fileNameSize;
zipFileSize = zipFileSize + 76 + (fileNameSize * 2) + fileCompressedContentSize;
//ディレクトリの場合
} else {
zipLocalFileHeaderStartIndex = zipLocalFileHeaderStartIndex + 30 + fileNameSize;
zipCentralDirectoryHeaderTotalSize = zipCentralDirectoryHeaderTotalSize + 46 + fileNameSize;
zipFileSize = zipFileSize + 76 + (fileNameSize * 2);
}
}
const zipCentralDirectoryHeaderStartIndex = zipLocalFileHeaderStartIndex;
const arrayBuffer = new ArrayBuffer(zipFileSize);
const dataView = new DataView(arrayBuffer);
let byteIndex = 0;
for (let fileIndex = 0; fileIndex < fileArray.length; fileIndex++) {
const file = fileArray[fileIndex];
let fileName = file.name;
if (fileName.startsWith("/")) {
fileName = fileName.substring(1);
}
const fileNameByteArray = new TextEncoder("utf-8").encode(fileName);
const fileNameSize = fileNameByteArray.length;
const fileContentArrayBuffer = await file.arrayBuffer();
const fileContentSize = fileContentArrayBuffer.byteLength;
const fileContentByteArray = new Uint8Array(fileContentArrayBuffer);
const fileContentCrc32 = crc32(fileContentByteArray);
const fileCompressedContentArrayBuffer = await this.deflateFile(file);
const fileCompressedContentSize = fileCompressedContentArrayBuffer.byteLength;
const fileCompressedContentByteArray = new Uint8Array(fileCompressedContentArrayBuffer);
//zipのローカル ファイル ヘッダー
const zipLocalFileHeader = [
//ローカル ファイル ヘッダーを表す固定値
0x50,
0x4B,
0x03,
0x04,
//当zipの展開に必要な最小バージョン
//
//無圧縮のSTOREDのバージョン1.0は16進数表記で順に0A、00。
//deflate圧縮可能なバージョン2.0は16進数表記で順に14、00。
//
0x14,
0x00,
//当zipの汎用ビット フラグ
//
//当zip内のファイル名の文字コードがUTF-8の場合は16進数表記で順に00、08。
//
0x00,
0x08,
//当zipの圧縮方法
//
//無圧縮のzipのSTOREDは16進数表記で順に00、00。
//deflate圧縮は16進数表記で順に08、00。
//
0x08,
0x00,
//当zip内の当ファイルの最終更新時刻
((zipTime << 8) >>> 8),
(zipTime >>> 8),
//当zip内の当ファイルの最終更新日
((zipDate << 8) >>> 8),
(zipDate >>> 8),
//当zip内の当ファイルの内容のCRC32
//
//ディレクトリの場合は16進数表記で順に00、00、00、00。
//
((fileContentCrc32 << 24) >>> 24),
((fileContentCrc32 << 16) >>> 24),
((fileContentCrc32 << 8) >>> 24),
(fileContentCrc32 >>> 24),
//圧縮後の当zip内の当ファイルのファイル サイズ
//
//ディレクトリの場合は16進数表記で順に00、00、00、00。
//
((fileCompressedContentSize << 24) >>> 24),
((fileCompressedContentSize << 16) >>> 24),
((fileCompressedContentSize << 8) >>> 24),
(fileCompressedContentSize >>> 24),
//圧縮前の当zip内の当ファイルのファイル サイズ
//
//ディレクトリの場合は16進数表記で順に00、00、00、00。
//
((fileContentSize << 24) >>> 24),
((fileContentSize << 16) >>> 24),
((fileContentSize << 8) >>> 24),
(fileContentSize >>> 24),
//当zip内の当ファイルの名前のサイズ
((fileNameSize << 8) >>> 8),
(fileNameSize >>> 8),
//zipのエクストラ フィールド
0x00,
0x00
];
//当zip内の当ファイルの名前
//
//ディレクトリの場合はディレクトリ名 + 半角スラッシュ記号(/)
//
//ディレクトリ内のファイルの場合はディレクトリ名 + 半角スラッシュ記号(/) + ファイル名
//
for (let index = 0; index < fileNameByteArray.length; index++) {
zipLocalFileHeader.push(fileNameByteArray[index]);
}
//当zip内の当ファイルの内容
//
//ディレクトリの場合は無し
//
//ファイルの場合
//
//ファイルの名前の最後の文字が半角スラッシュ記号ではない場合
//
if (fileName.endsWith("/") == false) {
for (let index = 0; index < fileCompressedContentByteArray.length; index++) {
zipLocalFileHeader.push(fileCompressedContentByteArray[index]);
}
}
for (let index = 0; index < zipLocalFileHeader.length; index++) {
dataView.setUint8(byteIndex, zipLocalFileHeader[index], /* リトル エンディアン */ true);
byteIndex++;
}
}
for (let fileIndex = 0; fileIndex < fileArray.length; fileIndex++) {
const file = fileArray[fileIndex];
const zipLocalFileHeaderStartIndex = file.zipLocalFileHeaderStartIndex;
let fileName = file.name;
if (fileName.startsWith("/")) {
fileName = fileName.substring(1);
}
const fileNameByteArray = new TextEncoder("utf-8").encode(fileName);
const fileNameSize = fileNameByteArray.length;
const fileContentArrayBuffer = await file.arrayBuffer();
const fileContentSize = fileContentArrayBuffer.byteLength;
const fileContentByteArray = new Uint8Array(fileContentArrayBuffer);
const fileContentCrc32 = crc32(fileContentByteArray);
const fileCompressedContentArrayBuffer = await this.deflateFile(file);
const fileCompressedContentSize = fileCompressedContentArrayBuffer.byteLength;
const fileCompressedContentByteArray = new Uint8Array(fileCompressedContentArrayBuffer);
//zipのセントラル ディレクトリ ヘッダー
const zipCentralDirectoryHeader = [
//セントラル ディレクトリ ヘッダーを表す固定値
0x50,
0x4B,
0x01,
0x02,
//当zipを作成したアプリケーションが対応可能なzipのバージョン
0x14,
//当zipを作成したOS
//
//Unixは16進数表記で03
//
0x03,
//当zipの展開に必要な最小バージョン
//
//無圧縮のSTOREDのバージョン1.0は16進数表記で順に0A、00。
//deflate圧縮可能なバージョン2.0は16進数表記で順に14、00。
//
0x14,
0x00,
//当zipの汎用ビット フラグ
//
//当zip内のファイル名の文字コードがUTF-8の場合は16進数表記で順に00、08。
//
0x00,
0x08,
//当zipの圧縮方法
//
//無圧縮のzipのSTOREDは16進数表記で順に00、00。
//deflate圧縮は16進数表記で順に08、00。
//
0x08,
0x00,
//当zip内の当ファイルの最終更新時刻
((zipTime << 8) >>> 8),
(zipTime >>> 8),
//当zip内の当ファイルの最終更新日
((zipDate << 8) >>> 8),
(zipDate >>> 8),
//当zip内の当ファイルの内容のCRC32
//
//ディレクトリの場合は16進数表記で順に00、00、00、00。
//
((fileContentCrc32 << 24) >>> 24),
((fileContentCrc32 << 16) >>> 24),
((fileContentCrc32 << 8) >>> 24),
(fileContentCrc32 >>> 24),
//圧縮後の当zip内の当ファイルのファイル サイズ
//
//ディレクトリの場合は16進数表記で順に00、00、00、00。
//
((fileCompressedContentSize << 24) >>> 24),
((fileCompressedContentSize << 16) >>> 24),
((fileCompressedContentSize << 8) >>> 24),
(fileCompressedContentSize >>> 24),
//圧縮前の当zip内の当ファイルのファイル サイズ
//
//ディレクトリの場合は16進数表記で順に00、00、00、00。
//
((fileContentSize << 24) >>> 24),
((fileContentSize << 16) >>> 24),
((fileContentSize << 8) >>> 24),
(fileContentSize >>> 24),
//当zip内の当ファイルの名前のサイズ
((fileNameSize << 8) >>> 8),
(fileNameSize >>> 8),
//zipのエクストラ フィールド
0x00,
0x00,
//zipのファイルのコメントのサイズ
//
//zipのファイルのコメントを利用しない場合は16進数表記で順に00、00。
//
0x00,
0x00,
//対応するローカル ファイル ヘッダーが存在するディスクの番号
//
//ディスク分割していない場合は16進数表記で順に00、00。
//
0x00,
0x00,
//(zip)内的ファイル属性
//
//バイナリーデータの場合は16進数表記で順に00、00。
//
//テキスト データの場合は16進数表記で順に01、00。
//
0x00,
0x00,
//当zipを作成したOS依存の(zip)外的ファイル属性
0x00,
0x00,
0xB4,
0x81,
//対応するローカル ファイル ヘッダーの開始バイトのインデックス番号
((zipLocalFileHeaderStartIndex << 24) >>> 24),
((zipLocalFileHeaderStartIndex << 16) >>> 24),
((zipLocalFileHeaderStartIndex << 8) >>> 24),
(zipLocalFileHeaderStartIndex >>> 24)
];
//当zip内の当ファイルの名前
//
//ディレクトリの場合はディレクトリ名 + 半角スラッシュ記号(/)
//
//ディレクトリ内のファイルの場合はディレクトリ名 + 半角スラッシュ記号(/) + ファイル名
//
for (let index = 0; index < fileNameByteArray.length; index++) {
zipCentralDirectoryHeader.push(fileNameByteArray[index]);
}
for (let index = 0; index < zipCentralDirectoryHeader.length; index++) {
dataView.setUint8(byteIndex, zipCentralDirectoryHeader[index], /* リトル エンディアン */ true);
byteIndex++;
}
}
//zipのセントラル ディレクトリの終端レコード
const zipEndOfCentralDirectoryRecord = [
//セントラル ディレクトリの終端レコードを表す固定値
0x50,
0x4B,
0x05,
0x06,
//当ディスクの番号
//
//ディスク分割していない場合は16進数表記で順に00、00。
//
0x00,
0x00,
//セントラル ディレクトリが開始されるディスクの番号
//
//ディスク分割していない場合は16進数表記で順に00、00。
//
0x00,
0x00,
//(当ディスクに存在する)セントラル ディレクトリの総数
((numberOfFiles << 8) >>> 8),
(numberOfFiles >>> 8),
//セントラル ディレクトリの総数
((numberOfFiles << 8) >>> 8),
(numberOfFiles >>> 8),
//セントラル ディレクトリの総サイズ
((zipCentralDirectoryHeaderTotalSize << 24) >>> 24),
((zipCentralDirectoryHeaderTotalSize << 16) >>> 24),
((zipCentralDirectoryHeaderTotalSize << 8) >>> 24),
(zipCentralDirectoryHeaderTotalSize >>> 24),
//セントラル ディレクトリの開始バイトのインデックス番号
((zipCentralDirectoryHeaderStartIndex << 24) >>> 24),
((zipCentralDirectoryHeaderStartIndex << 16) >>> 24),
((zipCentralDirectoryHeaderStartIndex << 8) >>> 24),
(zipCentralDirectoryHeaderStartIndex >>> 24),
//当zipのコメントのサイズ
0x00,
0x00
];
for (let index = 0; index < zipEndOfCentralDirectoryRecord.length; index++) {
dataView.setUint8(byteIndex, zipEndOfCentralDirectoryRecord[index], /* リトル エンディアン */ true);
byteIndex++;
}
return new Uint8Array(arrayBuffer);
}
}
$("deflateZip").onclick = async () => {
$("message").innerHTML = "";
if ($("zipFileName").value == "") {
$("message").innerHTML = "zipファイル名を記入してください。";
return;
}
const zipFileName = $("zipFileName").value + ".zip";
const deflateZip = new DeflateZip();
const files = $("file").files;
if (files.length == 0) {
$("message").innerHTML = "1つ以上のファイルを選択してください。";
return;
}
for (let index = 0; index < files.length; index++) {
const file = files[index];
deflateZip.add(file);
}
const blob = new Blob([await deflateZip.zip()], {type: "application/zip"});
const url = URL.createObjectURL(blob);
const aTagElement = document.createElement("a");
aTagElement.href = url;
aTagElement.download = zipFileName;
aTagElement.click();
URL.revokeObjectURL(url);
$("message").innerHTML = "zipファイルの作成を完了しました。";
};
</script>
</body>
</html>




