Posts Tagged ‘JavaScript’

JavaScript, CSS and PHP Compacter for Python

No Comments »Written on March 5th, 2011 by
Categories: Python

Here’s a port of the Hx compacter library. Originally written in PHP, it’s now available as a Python module.

You can get the original PHP version of the JavaScript compacter here.

This is a work-in-progress. It’s been superficially tested. Please report any issues you find!

#! /usr/bin/python

import re
import sys

__author__="Neil E. Pearson"
__date__ ="$04/05/2007 1:22:19 PM$"

def js(input):
	cd=re.match(r"^\s*/[/*]\s*<!\[CDATA\[\s*(?:\*/)?(.*)/[/*]\s*\]\]>\s*(?:\*/)?\s*$", input, re.DOTALL)
	if cd: input=cd.group(1)
	ph=0
	while str(ph) in input: ph+=1
	lines=re.split(r"(?s)\s*[\r\n]+\s*", input)
	subs=(
		(r"\s*//.*$"		, r""),
		(r"\s*([^\w$])\s*"	, r"\1"),
		(r";}"				, r"}")
	)
	s=[]
	cm=False
	for k in range(len(lines)-1):
		l=lines[k]
		if cm:
			if '*/' in l:
				l=re.sub(r"^.*?\*/","",l)
				cm=False
			else: l=""
		if not cm:
			c=0
			while c<len(l):
				n=l1
				if n in "\"'/":
					if re.search(r"([=(?:]\s*/|[\'\"])$", l[:c+1]):
						cl=findDelimiter(l,c)
						if cl is not False:
							s.append(l1)
							l="%s%s.%s%s" % (l[:c], ph, len(s)-1, l[cl+1:])
							c=0
				c+=1

			l=re.sub(r"/\*.*?\*/","",l)
			if "/*" in l:
				l=re.sub(r"/\*.*$","",l)
				cm=True
			for x in subs: l=re.sub(x[0], x[1], l)
			l=l.strip()
			if k<len(lines)-1:
				if lines[k+1]:
					if not re.search(r"^\s*[-{};:+=&|^,?.]", lines[k+1]):
						if re.search(r"([^-{};:+=&|^,.?([\\]]|[-+]{2})$", l): l+=";"
						m=re.search(r"(if|for|while|with)(\(.*?\));$", l, re.IGNORECASE)
						if m:
							if findDelimiter(m.group(2))==len(m.group(2))-1:
								l=l[:-1]
		l=l.replace('else;','else ')
		lines[k]=l
	ret="".join(lines)
	f=-1
	while(True):
		f=ret.lower().find("do{",f+1)
		if(f<0): break
		b=findDelimiter(ret,f+2)
		if b is not False:
			b=ret.lower().find("while(",b)
			if b is not False:
				b+=5
				b=findDelimiter(ret,b)
				ret="%s;%s" % (ret[:b+1], ret[b+1:])
	i=0
	while i<len(ret):
		if ret[i] in "=:":
			o=i+1
			if ret[o:o+9].lower()=="function(":
				o=findDelimiter(ret,o+8)+1
			if ret[o]=="{":
				cl=findDelimiter(ret,o)
				if cl<len(ret):
					if re.match(r"""[-\w(${["']""",ret[cl+1]):
						ret="%s;%s" % (ret[:cl+1], ret[cl+1:])
		i+=1
	ret=ret.replace(";}","}")
	ret=re.sub(str(ph)+r"\.(\d+)", lambda m: s[int(m.group(1))], ret)
	if cd:
		ret="/*<![CDATA[*/%s//]]>" % ret
	return ret

def css(input):
	subs=(
		(r"\s+"					, r" "),
		(r"([{;,}:])\s"			, r"\1"),
		(r"\s([}{,;])"			, r"\1"),
		(r"(?s)/\*.*?\*/"		, r""),
		(r";\s*(}|$)"			, r"\1"),
		(r"(?i)#([0-9a-f])\1([0-9a-f])\2([0-9a-f])\3(\s|\}|;|$)" , r"#\1\2\3\4"),
		(r"\b0[a-z]{2}\b"		, r"0"),
		(r"\b0(\.\d+[a-z]{2})\b", r"\1")
	)
	ret=input
	for i in subs: ret=re.sub(i[0],i[1],ret)
	return ret.strip()

def php(input):
	subs=(
		("(?m)(?<!:)//.*$",					""),
		(r"(?s)/\*.*\*/",					""),
		(r"(\s)\s+",						r"\1"),
		(r"""([^\w\s'"])\s+([^\w\s'"])""",	r"\1\2")
	)
	ret=input
	for i in subs: ret=re.sub(i[0],i[1],ret)
	return ret.strip()

def findDelimiter(str, pos=0, dir=0, esc="\\"):
	if pos>len(str): return False
	pairs="\"\"''//<>[]{}()"
	pp=pairs.find(str[pos])
	if pp<0: return False
	op=pp%2 and -1 or 1
	o=None
	if pairs[pp]!=pairs[pp+op]:
		dir=op
		o=pairs[pp]
	if not dir: dir=1
	c=pairs[pp+op]
	nest=1
	pos+=dir
	while pos>=0 and pos<len(str):
		if esc:
			if pos<len(str)-1:
				if str[pos+dir-1]==esc: pos+=dir*2
			if pos>=0 and pos<len(str):
				if str[pos]==o: nest+=1
				if str[pos]==c: nest-=1
		if not nest: return pos
		pos+=dir
	return False

if __name__ == "__main__":
	if len(sys.argv)==2:
		filename=sys.argv[1]
		func=css if filename.lower().endswith(".css") else js
		f=open(filename,"r")
		print func(f.read())
		f.close()

JavaScript Compactor for PHP

No Comments »Written on February 25th, 2011 by
Categories: JavaScript, PHP

There’s minify and a bunch of other JS compactors/compressors out there, but nothing that I’ve found works with any of the massive scripts I’ve written. Enter the Hx JavaScript compactor. It’s too easy to use so I won’t bother explaining it.

Right now it’s just PHP. I’ll be porting it to Python when I have a chance.

class Hx {
	public static function compact_js($js) {
		$cp='`^\s*/[/*]\s*<!\[CDATA\[\s*(?:\*/)?(.*)/[/*]\s*\]\]>\s*(?:\*/)?\s*$`s';
		$cd=0;
		if(preg_match($cp,$js)) {
			$cd=1;
			$js=preg_replace($cp,'\1',$js,1);
		}
		$ph=0;
		while(strpos($js,strval($ph))!==false) $ph++;
		$lines=preg_split('`\s*[\r\n]+\s*`',trim($js));
		$fr=array(
			'`\s*//.*$`'		=> '',
			'`\s*([^\w$])\s*`'	=> '\1',
			'`;}`'				=> '}'
		);
		$find=array_keys($fr);
		$repl=array_values($fr);
		$s=array();
		$cm=null;
		foreach($lines as $k=>$l) {
			if($cm) {
				if(strpos($l,'*/')!==false) {
					$l=preg_replace('`^.*?\*/`','',$l);
					$cm=0;
				} else $l='';
			}
			if(!$cm) {
				for($c=0;$c<strlen($l);$c++) {
					$n=$l{$c};
					if($n=='"'||$n=="'"||$n=='/') if(preg_match('`([=(?:]\s*/|[\'"])$`',substr($l,0,$c+1))) {
						$cl=self::find_delimiter($l,$c);
						if($cl!==false) {
							$s[]=substr($l,$c,$cl-$c+1);
							$l=substr($l,0,$c).$ph. '.' .strval(count($s)-1).substr($l,$cl+1);
						}
					}
				}
				$l=preg_replace('`/\*.*?\*/`','',$l);
				if(strpos($l,'/*')!==false) {
					$l=preg_replace('`/\*.*$`','',$l);
					$cm=1;
				}
				$l=trim(preg_replace($find,$repl,$l));
				if($k<count($lines)-1) if($nl=$lines[$k+1]) if(!preg_match('`^\s*[-{};:+=&|^,?.]`',$nl)) {
					if(preg_match('`([^-{};:+=&|^,.?([\\]]|[-+]{2})$`',$l)) $l.=';';
					if(preg_match('`^(.*)(if|for|while|with)(\(.*?\));$`i',$l,$r)) if(self::find_delimiter($r[3])==strlen($r[3])-1) $l=$r[1].$r[2].$r[3];
				}
			}
			$l=str_ireplace('else;','else ',$l);
			$lines[$k]=$l;
		}
		$ret=implode('',$lines);
		$f=-1;
		while($f=stripos($ret,'do{',$f+1)) {
			$b=self::find_delimiter($ret,$f+2);
			if($b!==false) {
				$b=strpos($ret,'while(',$b);
				if($b!==false) {
					$b+=5;
					$b=self::find_delimiter($ret,$b);
					$ret=substr($ret,0,$b+1).';'.substr($ret,$b+1);
				}
			}
		}
		for($i=0;$i<strlen($ret);$i++) if($ret{$i}==':'||$ret{$i}=='=') {
			$o=$i+1;
			if(strtolower(substr($ret, $o, 9))=='function(') $o=self::find_delimiter($ret,$o+8)+1;
			if($ret{$o}=='{') {
				$cl=self::find_delimiter($ret,$o);
				if($cl<strlen($ret)) if(preg_match('`[-\w(${["\']`',$ret{$cl+1})) $ret=substr($ret,0,$cl+1).';'.substr($ret,$cl+1);
			}
		}
		$ret=str_replace(';}','}',$ret);
		$ret=preg_replace("`$ph\.(\d+)`e",'$s[\1]',$ret);
		if($cd) $ret="/*<![CDATA[*/$ret//]]>";
		return $ret;
	}

	private static function find_delimiter($str,$pos=0,$dir=0,$esc='\\') {
		if($pos>=strlen($str)) return false;
		static $pairs='\'\'""//<>[]{}()';
		$pp=strpos($pairs,$str{$pos});
		if($pp===false) return false;
		$op=$pp%2?-1:1;
		$o=null;
		if($pairs{$pp}!=$pairs{$pp+$op}) {
			$dir=$op;
			$o=$pairs{$pp};
		}
		if(!$dir) $dir=1;
		$c=$pairs{$pp+$op};
		$nest=1;
		for($pos+=$dir;$pos>=0&&$pos<strlen($str);$pos+=$dir) {
			if($esc) if($pos<(strlen($str)-1)) if($str{$pos+$dir-1}==$esc) $pos+=($dir*2);
			if($str{$pos}==$o) $nest++;
			if($str{$pos}==$c) $nest--;
			if(!$nest) return $pos;
		}
		return false;
	}
}