TypeScript zipファイルの読み込みと作成 JavaScript

JavaScriptでzipファイルが使えると何かと便利なので作ってみました。圧縮展開はAPIを利用、ソースコードはすべて白紙の状態から作成しています。オープンソースや外部のライブラリ未使用です。

zipファイルからファイル情報取得

文章で説明するよりソースコードで
文字コード変換や圧縮展開などの実装は以前の記事または、この後の有料記事にあります。

// pkzip constant
const EOCD_SIZE = 22;//ファイルコメントなしの最小サイズ
const SIG_EOCD = (0x50) +(0x4b<<8) + (0x05<<16) + (0x06<<24);
const SIG_CENTRAL = (0x50) +(0x4b<<8) + (0x01<<16) + (0x02<<24);
const SIG_LOCAL = (0x50) +(0x4b<<8) + (0x03<<16) + (0x04<<24);
const COMP_DEFLATE = 0x8;
const COMP_NONE  = 0;
const LOCAL_HEADER_SIZE = 30;

interface FileReaderIF{
    arrayBuffer(begin : number, end : number) : Promise<ArrayBuffer>;
    size : number;
}

//---------------------------
// zip(pkzip)からファイル情報取得
export async function parsePkZip(reader : FileReaderIF){
    let files : PkZipFileHeader[] = [];
    let fileSize = reader.size;
    if(fileSize < EOCD_SIZE){
        throw "parsePkZip file size";
    }
    let eocd = await reader.arrayBuffer(fileSize-EOCD_SIZE, fileSize);
    if(!eocd || eocd.byteLength == 0){
        throw "parsePkZip eocd";
    }
    let readbin = new ReadBinary(new Uint8Array(eocd));
    
    //ファイルコメントありZIP未対応 PK0506を検索する必要あり
    if(readbin.getU32() != SIG_EOCD){
        // PK 05 06
        throw  "parsePkZip eocd sig";
    }
    readbin.getU16();//disk num
    readbin.getU16();//central disk no
    readbin.getU16();//entry num in disk
    readbin.getU16();// entryNum
    
    // セントラル読み込み
    let centralSize = readbin.getU32();
    let centralPtr = readbin.getU32();
    let centralData = await reader.arrayBuffer(centralPtr, centralPtr+centralSize);
    if(!centralData || centralData.byteLength != centralSize){
        throw  "parsePkZip central";
    }

    let readc = new ReadBinary(new Uint8Array(centralData));
    let texdec = new TextDecoderSJISorUTF8();
    while(!readc.isTerm()){
        if(readc.getU32() != SIG_CENTRAL){
            throw  "parsePkZip central sig";
        }
        let makeVer = readc.getU16();
        let needVer = readc.getU16();
        if(needVer > 20){
            throw  "parsePkZip version";
        }
        let bitFlag = readc.getU16();
        if(bitFlag != 0){
            throw  "parsePkZip bitFlag!=0";
        }
        let compType = readc.getU16();
        if(compType != COMP_DEFLATE && compType != COMP_NONE){
            throw  "parsePkZip comp";
        }
        let dostime = readc.getU32();//MSDOS date time
        let dateTime = dateFromMSDOS(dostime);
        let crc32 = readc.getU32();
        let compSize = readc.getU32();
        let fileSize = readc.getU32();
        let fileNameLen = readc.getU16();
        let extLen = readc.getU16();
        let commentLen = readc.getU16();
        
        readc.getU16(); // diskNo
        readc.getU16(); // attrIn
        readc.getU32(); // attrOut
       
        let fileOffset = readc.getU32(); 
        let nameBin = readc.getBin(fileNameLen); 
        let fileName = texdec.decode(nameBin);
        readc.skip(extLen);
        readc.skip(commentLen);

        // ローカルファイルヘッダとの照合
        let localheader = await reader.arrayBuffer(fileOffset, fileOffset+LOCAL_HEADER_SIZE);
        if(!localheader || localheader.byteLength == 0){
            throw  "parsePkZip localheader";
        }
        let rblocal = new ReadBinary(new Uint8Array(localheader));
        if(rblocal.getU32() != SIG_LOCAL){
            throw  "parsePkZip sig localheader";
        }
        rblocal.getU16();//ver
        rblocal.getU16();//bitFlag
        if(rblocal.getU16() != compType){
            throw  "PkZipFiles::parseFileInfo compType";
        }
        rblocal.getU32();//msdos time
        if(rblocal.getU32() != crc32){ throw  "parsePkZip crc32";}
        if(rblocal.getU32() != compSize){ throw  "parsePkZip compSize";}
        if(rblocal.getU32() != fileSize){ throw  "parsePkZip fileSize";}
        
        let name_len = rblocal.getU16();
        let ext_len = rblocal.getU16();
        let filePtr = fileOffset + LOCAL_HEADER_SIZE + name_len + ext_len;

        let header = new PkZipFileHeader(fileName, compType,compSize, fileSize, filePtr, crc32, dateTime);
        files.push(header);
    }
    return files;
}

圧縮展開とzipファイル作成(有料)

実践的なソースコードです。
文字コードはSJISとUTF-8に対応(Windowsとそれ以外)
APIを利用した圧縮展開(CompressionStream DecompressionStream)

機能テスト用ソースコード
zipファイルを読み込んで全ファイル展開、それをまたzipに変換

    let file = await (await fetch("./zip.zip")).arrayBuffer();
    
    // ファイルアクセスIF
    let reader : FileReaderIF= {
        arrayBuffer : async (b,e)=>{
            return file.slice(b,e);
        },
        size : file.byteLength
    };

    // zipからファイル情報取得
    let files = await parsePkZip(reader);
    console.log(files);

    // ファイル展開 DecompressionStreamを使用
    let zipfiles : [string, Date, ArrayBuffer][] = [];
    for(let f of files){
        let data = await decompressPkZipFile(f , reader);
        zipfiles.push([f.fileName, f.date, data]);
    }

    // zip作成 圧縮にはCompressionStream
    // Windowsなら"sjis" それ以外は"utf-8"
    let zip = await createPkZip(zipfiles, "sjis");

    // zipファイルダウンロード
    let a = document.createElement("a");
    a.href = URL.createObjectURL(new Blob([zip]));
    a.download = "zip.zip";
    document.body.append(a);
    a.textContent = "download"

クラス定義 関数定義
記事購入の際の参考にしてください。

// zip(pkzip)からファイル情報取得
export async function parsePkZip(reader : FileReaderIF);// PkZipFileHeader[]

// zip(pkzip)作成
export async function createPkZip(zipfiles : [string, Date, ArrayBuffer][], encode : "sjis"|"utf-8" = "sjis");

// zipファイルの展開
export async function decompressPkZipFile(header : PkZipFileHeader, reader : FileReaderIF);

// deflate展開
export async function decompressPkZipDeflate(blob : Blob);

// deflate圧縮
export async function compressPkZipDeflate(blob : Blob);

// Utility

// バイナリー読み込み  <- Uint8array
export class ReadBinary{}

// バイナリー書き込み -> Uint8array
export class WriteBinary{}

// MSDOS(FAT) -> JavaScript Data
function dateFromMSDOS(d : number);

// JavaScript Data -> MSDOS(FAT) 
function msdosFromDate(date : Date) : number;

// SJIS or UTF-8 -> string
export class TextDecoderSJISorUTF8{}

// 文字コード変換  String(UTF-16) -> SJIS
// 簡易版? プログラム実験用 サロゲートペア未対応
export class TextEncoderSJIS{}

// crc32
class CRC32{}

ソースコード

ここから先は

16,244字

¥ 600

この記事が役に立ったという方は、サポートお願いします。今後の製作の励みになります。