厳密に言う自己展開圧縮書庫。htmlなのでbrowserで書庫の中身を取り出す事が可能です。自己展開という事で外部js不要。programを以下に示す。御覧の通りhtml+JavaScriptなのでbrowserで動作させます(CompressionStreamを実装しているbrowser専用)。
<input multiple type=file id=i><script>
//書庫のhtml部品群
const D0=`<meta charset=l1><noscript id=s>`,
D1='</noscript><script>new Response(new Blob([Uint8Array.from(s.innerText',
D2=',(c,i)=>(i=c.charCodeAt()%65533)>>8?129+" \x82\x83\x84\x85\x86\x87\x88\x89\x8A\x8B\x8C \x8E \x91\x92\x93\x94\x95\x96\x97\x98\x99\x9A\x9B\x9C \x9E\x9F".indexOf(c):i)]).stream().pipeThrough(new DecompressionStream("deflate-raw"))).arrayBuffer().then((A,a=0)=>{for(A=new Uint8Array(A);A[a]+1;l.innerHTML+=`<li><a download="${f}"href=${URL.createObjectURL(new Blob([A.subarray(a,a+=d)]))}>${f}</a> ${d}`){var c=32,d=A[a++],f=d>>5;for(d&=31;f--;c*=256)d+=c*A[a++];for(f=a;A[a++];);f=new TextDecoder().decode(new Uint8Array(A.subarray(f,a-1)))}})<\/script><input type=button value=save onclick="for(let a of links)a.click()"><ol id=l>';
i.onchange=async function(e){
var a=0,c,d,f=this.files,i,r,n=[],o;
//file名かき集める
for(e of f)n.push(r=new TextEncoder().encode(e.name)),a+=r.length+e.size+6;
o=new Uint8Array(a);a=0;
//file尺とfile中身かき集める
for(e of f){
e=new Uint8Array(await e.arrayBuffer(i=a));
for(c=32,d=r=e.length;d-=o[a++]=d&c-1;c=256)d/=c,o[i]+=32;
o.set(c=n.shift(),a,a-=~c.length);
o.set(e,a,a+=r)
}
//それら全て圧縮
o=new Uint8Array(await new Response(new Blob([o.subarray(0,a)]).stream().pipeThrough(new CompressionStream("deflate-raw"))).arrayBuffer());
//文字頻度表作成
f=new Uint32Array(256);
for(e of o)f[e]++;r=D1;
//escape文字の候補選出
for(e=256;e;a>c&&e^13&&(a=c,d=e))c=f[--e];
c=f[13];e=String.fromCharCode(d);n=+!d;
if(c)r+=`.replace(/${e+e}|${e+String.fromCharCode(n)}/g,a=>a[0]!=a[1]?\`\\r\`:\`${e}\`)`;
r+=D2;
f=new Uint8Array(a+c+o.length+D0.length+r.length);
//meta + noscript
a=0;for(e of D0)f[a++]=e.charCodeAt();
//write archive data
if(c)for(e of o)e==13?(f[a++]=d,e=n):e==d&&(f[a++]=d,e=d),f[a++]=e;
else f.set(o,a,a+=o.length);
//write decoder + html
for(e of r)f[a++]=e.charCodeAt();
l.innerText=a;l.href=URL.createObjectURL(new Blob([f]))
}
</script><a id=l download=a.htm>
2024.11.22更新版
書庫のnoscript
要素をhtmlの末端に追いやり、安全性を高め、script
要素を排除してbody
要素のonload
属性に展開処理を全て詰め込み、記述量を削減
<input multiple type=file id=i><script>
//書庫のhtml部品群
const D1="<meta charset=l1><body onload='new Response(new Blob([Uint8Array.from(s.innerText",
D2=',(c,i)=>(i=c.charCodeAt()%65533)>>8?129+" \x82\x83\x84\x85\x86\x87\x88\x89\x8A\x8B\x8C \x8E \x91\x92\x93\x94\x95\x96\x97\x98\x99\x9A\x9B\x9C \x9E\x9F".indexOf(c):i)]).stream().pipeThrough(new DecompressionStream("deflate-raw"))).arrayBuffer().then((A,a=0)=>{for(A=new Uint8Array(A);A[a]+1;l.innerHTML+=`<li><a download="${f}"href=${URL.createObjectURL(new Blob([A.subarray(a,a+=d)]))}>${f}</a> ${d}`){var c=32,d=A[a++],f=d>>5;for(d&=31;f--;c*=256)d+=c*A[a++];for(f=a;A[a++];);f=new TextDecoder().decode(new Uint8Array(A.subarray(f,a-1)))}})\'><button onclick="for(let a of links)a.click()">save</button><ol id=l></ol><noscript id=s>';
i.oninput=async function(e){
var a=0,c,d,f=this.files,i,r,n=[],o;
//file名かき集める
for(e of f)n.push(r=new TextEncoder().encode(e.name)),a+=r.length+e.size+6;
o=new Uint8Array(a);a=0;
//file尺とfile中身かき集める
for(e of f){
e=new Uint8Array(await e.arrayBuffer(i=a));
for(c=32,d=r=e.length;d-=o[a++]=d&c-1;c=256)d/=c,o[i]+=32;
o.set(c=n.shift(),a,a-=~c.length);
o.set(e,a,a+=r)
}
//それら全て圧縮
o=new Uint8Array(await new Response(new Blob([o.subarray(0,a)]).stream().pipeThrough(new CompressionStream("deflate-raw"))).arrayBuffer());
//文字頻度表作成
f=new Uint32Array(256);
for(e of o)f[e]++;r=D1;
//escape文字の候補選出
for(e=256;e;a>c&&e^39&&e^13&&(a=c,d=e))c=f[--e];
c=f[13];e=String.fromCharCode(d);n=+!d;
if(c)r+=`.replace(/${e+e}|${e+String.fromCharCode(n)}/g,a=>a[0]!=a[1]?\`\\r\`:\`${e}\`)`;
f=new Uint8Array(a+c+o.length+(r+=D2).length);a=0;
//wirte decoder+html
for(e of r)f[a++]=e.charCodeAt();
//write archive data
if(c)for(e of o)e==13?(f[a++]=d,e=n):e==d&&(f[a++]=d,e=d),f[a++]=e;
else f.set(o,a,a+=o.length);
l.innerText=a;l.href=URL.createObjectURL(new Blob([f]));this.value=""
}
</script><a id=l download=a.htm>
file選択buttonを押下して複数fileを選択、または複数fileをbuttonにDrag & Dropすると書庫を作り始めやがります。無事に書庫が完成したらdownload linkが生成されやがるので、どうしても書庫が欲しければclickでもして下さいな。
標準でa.htmという名前の書庫を落とせるはずなので、保存したらbrowserで開いてみて下さい。先程選び抜いたfileどもの一覧が表示されるはずです。file名の横の数字はfileの大きさを表しています。
saveを押下すると全file連続download可能。file名をclickすると個別downloadとなります。
実演
See the Pen sfx archive creater by xezz (@xezz) on CodePen.
過去に投稿した JavaScriptで独自の書庫を作る とかいう記事では自己展開しない書庫の作り方を網羅しています。本記事のprogramの参考になるので、解説が気になる人は読んでみて下さい。
それからbinary dataをhtmlに埋め込む方法はこの記事で解説