LoginSignup
0
0

主にhtmlやjsを圧縮する企画です。超高速に展開できる優れ物。圧縮率も悪くありません。
圧縮手法はLZを採用。binary tree match finderで速度、optimal parsingで圧縮力を稼ぎます。
[\0-\x7f]以外の文字列は特殊変換するため、性能が悪くなります。
設計図が薄汚いのは仕様です。気にしてはいけません。

/*
A2LZe(T,ws,ES6,IE,fix,type)
@T :[\0-\x7f]からなる文字列. それ以外は特殊変換される
@ws :最長一致.範囲は0~127. 大きくすると辞書が小さくなる
@ES6 :ECMAScriptのversionが5か6+かの真偽
@IE :InternetExplorer対応かどうかの真偽
@fix :[\x80-\uffff]の変換法.
	0はUnicode番号に応じて桁数調整するが復号器が複雑.
	1だと4桁固定で復号器が単純
@type: 復号法. 圧縮率が変わる
	0:for loop
	1:再帰(巨大文字列苦手)
	2:for loop & substr未使用
	3:for loop & longest match>255. 巨大文字列向き
*/
function A2LZe(T,ws,ES6,IE,fix,type){
	var e=(fix?/\1/:/[\1-\3]/).test(T),f=/[\x80-\uffff]/.test(T)&&[],pf=fix?["\x0100","\x010","\1"]:["\1","\2","\3"];
	e&&f?T=T.replace(fix?/\1/g:/[\1-\3]/g,function(a){return a+a}):e=0;
	T=(f?T.replace(/[\x80-\uffff]/g,function(a,b){
		a=a.charCodeAt();b=a.toString(16);
		return(a<256?f[0]=pf[0]:a<4096?f[1]=pf[1]:f[2]=pf[2])+b
	}):T).split("");
	var MIN=3,H=[],N=[],O=[],a=0,b=65308,c,d,h,i,k=0,l=32655,m,n,o=0,p,r,s,v,w,x,z=T.length;
	if(!z)return"";
	for(ws+=2;ws>b/--l>>0;);ws=l;
	var Max=b/ws|0,M2=Max+1,M3=Max,ex=ES6?{92:1,96:1}:{10:1,13:1,34:+/"/.test(T),39:+/'/.test(T),92:1};
	for(type>2&&(Max+=0xd7f1,M2=Max+1);Max>>++k;);
	// Pass 1: Find all matches
	for(x=(1<<k)-1;a<z;a++){
		v=0,m=z-a,p=o;
		if(m>2){
			r=a%ws,l=r+ws,b=c=0,w=a<ws?-1:a-ws,s=H[h=T[a]+T[a+1]+T[a+2]];
			m>M2&&(m=M2);
			for(H[h]=a;s>w;){
				i=b<c?b:c;
				if(T[s+i]===T[a+i]){
					for(;i++<m&&T[s+i]===T[a+i];);
					if(i>v){
						if(s+i<a||type>1)O[++o]=~s+a<<k|i;
						else{
							for(n=m,h=s,v=a;h>w&&T[h-1]===T[--v]&&n--;)--h;
							for(v=0;T[h+v]===T[a+v]&&h+v<a&&v<m;)++v;
							if(v<a-s)O[++o]=~s+a<<k|a-s;
							else if(v>2)O[++o]=~h+a<<k|v
						}v=i;
						if(i>m){N[r]=N[s%=ws];N[l]=N[s+ws];m=0;break}}
				}
				if(T[s+i]<T[a+i])N[r]=s,s=N[r=s%ws+ws],c=i;
				else N[l]=s,s=N[l=s%ws],b=i}
			if(m)N[l]=N[r]=-1}
		O[p]=o++-p}
	// Pass 2: Build the shortest path
	a=p=N[0]=N.length=H.length=0;
	for(v=M3+MIN-1;a<z;h>=N[a]||(N[a]=h,H[a]=c)){
		if(n=O[p++])for(c=N[a],h=MIN-1;n--;){
			l=O[p++];d=l>>>k;l&=x;r=d*M3;
			b=c+2;r-=MIN;d<<=k;
			if(type<3)for(;h<l;s>=N[a+h]||(N[a+h]=s,H[a+h]=d+h))m=++h+r,s=m<1923?b:m<55296||57343<m?b+1:b+4;
			else for(;h<l;s>=N[a+h]||(N[a+h]=s,H[a+h]=d+h))m=++h<v?h+r:v+r,s=m<1923?b:m<55296||57343<m?b+1:b+4,h<v||(s++,s+=ex[h-v+14]||0)
		}
		h=N[a]+1,c=T[a++]
	}
	for(o=p=O.length=0;c=N[p++]=H[a],a-=c.big?1:c&x;);
	// Pass 3: Output the codes
	H=String.fromCharCode;
	if(type<3)for(;l=N[--p];)
		O[o++]=l.big?l:H((l>>>k)*M3+(l&x)+125);
	else for(M2=M3+MIN-1;l=N[--p];)
		if(l.big)O[o++]=l;
		else O[o++]=H((l>>>k)*M3+(l&=x,l<M2?l:M2)+125),l<M2||(O[o++]=H(l-M2+14));

	// build decoder
	for(b=c=d=h=i=0;o;)s=O[--o],b+=s==="\n"||s==="\r",c+=s==='"',d+=s==="'",h+=s==="$"&&O[o+1]==="{",i+=s==="`";
	O=O.join("").replace(/\\/g,'\\\\').replace(/\0/g,'\\0').replace(/[\ud800-\udfff]/g,function(a){return"\\u"+a.charCodeAt().toString(16)}); // avoid '\' '\0' and surrogate pair

	if(ES6&&b+c+d>h+i)r="`",O=O.replace(/\$\{/g,"\\${").replace(/`/g,'\\`');
	else O=O.replace(/\n/g,'\\n').replace(/\r/g,'\\r').replace(RegExp(r=d>c?'"':"'","g"),"\\"+r).replace(/[\u2028\u2029]/g,function(a){return"\\u"+a.charCodeAt().toString(16)}); // LineTerminator can't be included

	if(f){
		if(fix)f=["\1([\\da-f]{4})"];
		else f[a=0]&&(f[a++]=f[0]+"([\\da-f]{2})"),f[1]&&(f[a++]=f[1]+"([\\da-f]{3})"),f[2]&&(f[a++]=f[2]+"([\\da-f]{4})"),f.length=a;
		s="String.fromCharCode('0x'+"+(a>1?"a.slice(1)":"b")+")",b=ES6?"(a,b)=>"+s:"function(a,b){return "+s+"}";
	}
	f=f?".replace(/"+f.join("|").replace(a>1?/[()]/g:"","")+"/g,"+b+")":"";
	e=e?".replace(/("+(fix?"\1":"[\1-\3]")+"){2}/g,"+(ES6?"(a,b)=>b":"function(a,b){return b}")+")"+f:f;
	if(!type){
		// for loop. it can decode big text
		a="{for(;c=i.charCodeAt(++p);)o+=c<128?i"+(IE?".charAt(p)":"[p]")+":o.substr(~((c-=128)/"+Max+")"+(IE?"+o.length":"")+",c%"+Max+"+3);return o}"+(ES6?")":"");
		return(ES6?"((i,o,p,c)=>":"function(i,o,p,c)")+a+"("+r+O+r+","+r+r+",-1)"+e
	}
	if(type<2){
		// recursive loop. best for small text
		a="(c=i.charCodeAt(p))?f(i,o+(c<128?i"+(IE?".charAt(p)":"[p]")+":o.substr(~((c-=128)/"+Max+")"+(IE?"+o.length":"")+",c%"+Max+"+3)),++p):o";
		return ES6?"(f=(i,o,p,c)=>"+a+")("+r+O+r+","+r+r+",0)"+e:"function f(i,o,p,c){return"+a+"}("+r+O+r+","+r+r+",0)"+e
	}
	// for loop & longest match>255. best for big text
	a="{for(;c=i[h](++p);)if(c<128)o[n++]=i"+(IE?".charAt(p)":"[p]")+";else for(a=~((c-=128)/"+M3+")+n,c%="+M3+(type<3?"":","+--M3+">c||(c+=i[h](++p)-14)")+";--c+4;)o[n++]=o[a++];return o.join('')}"+(ES6?")":"");
	return(ES6?"((i,o,p,n,h,c,a)=>":"function(i,o,p,n,h,c,a)")+a+"("+r+O+r+",[],-1,0,'charCodeAt')"+e
}

出力文字列は即時関数に囲まれているので、eval()document.write()に放り込んだり、変数に代入する等して下さい。ついでに言うとUTF8形式です。以下codepenによる実演。

See the Pen A2LZ by xezz (@xezz) on CodePen.

関数の説明的解説

  • Tは入力文字列。圧縮されます

  • wsは最長一致。範囲は0~127。大きくすると辞書が小さくなる(ws=0だと32640、ws=127だと506)

  • ES6はECMAScriptのversionが5か6+かの真偽。trueの方が復号器が小さい

  • IEはInternet Explorer対応かどうかの真偽。具体的にはsubstrの仕様対策

  • fix[\x80-\uffff]の変換法。0はUnicode番号に応じて桁数調整するが復号器が複雑。1だと4桁固定で復号器が単純

  • typeは復号法

    • 0: for loop
    • 1: 再帰。巨大文字列は復号できない
    • 2: for loop。0より圧縮しやすいが、復号器は大きくなる
    • 3: for loop。wsの値に関係なく最長一致無制限になる。巨大文字列に向いている
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0