Commits

Anonymous committed 7808bd8

Committing random stuff.

Comments (0)

Files changed (14)

+FF.BinaryParser = function(types, structure) {
+	this.types = types;
+	this.structure = structure;
+};
+(function() {
+	function isArray(obj) {
+		return typeof(obj) == 'object' && obj !== null && obj instanceof Array;
+	}
+	
+	function getTuple(obj) {
+		for (var k in obj) if (k.charAt(0) != '$')
+			return out = [k, obj[k], obj.$length, obj.$post, obj.$branch, obj.$default,
+					obj.$const];
+	}
+	
+	function escapeString(str) {
+		return str.replace(/[\000-\011\013-\037]/g, function(a) {return "\\" +
+				a.charCodeAt(0);});
+	}
+	
+	function getValue(objs, val) {
+		switch (typeof(val)) {
+			case 'undefined': case 'number':
+				return val;
+			case 'string':
+				if (val == '*') {
+					return '*';
+				} else {
+					var tmp = val.split(".");
+					
+			outer:	for (var j=objs.length-1; j>=0; j--) {
+						var obj = objs[j];
+						
+						for (var i=0; i<tmp.length; i++)
+							if (typeof(obj) == 'undefined') continue outer;
+							else obj = obj[tmp[i]];
+						return obj;
+					}
+					throw new Error("No previous variable '" + val + "' found.");
+				}
+		}
+	}
+	
+	function parse(str, types, structure, objs) {
+		var out = {}, ctr = 0;
+		
+		for (var i=0; i<structure.length; i++) {
+			var tmp, name, struct, length, func, $length, $post, $branch, $default, $const;
+			
+			tmp = getTuple(structure[i]);
+			name = tmp[0], struct = tmp[1], $length = tmp[2], $post = tmp[3],
+					$branch = tmp[4], $default = tmp[5], $const = tmp[6];
+			
+			var allObjs = objs.concat(out);
+			
+				/* Handle branching - $branch & $default. */
+			$branch = getValue(allObjs, $branch);
+			if (typeof($branch) != 'undefined') {
+				if ($branch in structure[i]) {
+					tmp = getTuple(structure[i][$branch]);
+					name = tmp[0], struct = tmp[1], $length = tmp[2], $post = tmp[3],
+							$branch = tmp[4], $default = tmp[5], $const = tmp[6];
+				} else if ('$default' in structure[i]) {
+					tmp = getTuple(structure[i].$default);
+					name = tmp[0], struct = tmp[1], $length = tmp[2], $post = tmp[3],
+							$branch = tmp[4], $default = tmp[5], $const = tmp[6];
+				} else {
+					throw new Error("Couldn't match " + escapeString($branch) +
+							" with branching structure. Structure was:\n" +
+							FF.Utils.toJson(structure[i]));
+				}
+			}
+			
+				/* Handle array-like structures - $length. */
+			$length = getValue(allObjs, $length);
+			var typeIsArray = typeof($length) != 'undefined';
+			var typeIsAutoArray = $length == '*';
+			if (typeof($length) != 'number') $length = 1;
+			
+				/* Handle the actual structure. */
+			var arr = [];
+			for (var j=0; j<$length; j++) {
+				if (isArray(struct)) {
+				//	length = lengthOf(struct, this.types, out);
+					tmp = parse(str.substr(ctr), types, struct, objs.concat(out));
+					arr.push(tmp[0]);
+					ctr += tmp[1];
+				} else if (typeof(struct) == 'string') {
+					tmp = types[struct];
+					arr.push(tmp[1].call(this, str.substring(ctr, ctr += tmp[0]), ctr));
+				} else {
+					throw new Error("Couldn't handle structure. Structure was:\n" +
+							FF.Utils.toJson(struct));
+				}
+				
+				if (typeIsAutoArray && ctr != str.length) j--;
+			}
+			
+			out[name] = typeIsArray ? arr : arr[0];
+			if (typeof($post) == 'function') out[name] = $post(out[name]);
+		}
+		
+		return [out, ctr];
+	}
+	
+	FF.BinaryParser.prototype.parse = function(str) {
+		return parse(str, this.types, this.structure, [])[0];
+	};
+	
+	function getReversedEndianCall(func) {
+		return function(str) {
+			var str2 = "";
+			for (var i=str.length-1; i>=0; i--) str2 += str.charAt(i);
+			return func(str2);
+		};
+	}
+	
+	var content = {
+		functions: {
+			u8: function(s) {
+				return s.charCodeAt(0);
+			},
+			u16: function(s) {
+				return s.charCodeAt(0)*0x100 + s.charCodeAt(1);
+			},
+			u32: function(s) {
+				return s.charCodeAt(0)*0x1000000 + s.charCodeAt(1)*0x10000 +
+						s.charCodeAt(2)*0x100 + s.charCodeAt(3);
+			},
+			i8: function(c) {
+				var n = c.charCodeAt(0);
+				return n<0x80 ? n : 1-(~n & 0xFF);
+			},
+			i16: function(s) {
+				var n = content.functions.sToU16b(s);
+				return n<0x8000 ? n : 1-(~n & 0xFFFF);
+			},
+			i32: function(s) {
+				var n = content.functions.sToU32b(s);
+				return n<0x800000 ? n : 1-(~n & 0xFFFFFF);
+			},
+			
+			char: function(s) {
+				return s;
+			},
+		}
+	};
+	
+		// Little-endian variants.
+	content.functions.u8l  = getReversedEndianCall(content.functions.u8);
+	content.functions.u16l = getReversedEndianCall(content.functions.u16);
+	content.functions.u32l = getReversedEndianCall(content.functions.u32);
+	content.functions.i8l  = getReversedEndianCall(content.functions.i8);
+	content.functions.i16l = getReversedEndianCall(content.functions.i16);
+	content.functions.i32l = getReversedEndianCall(content.functions.i32);
+	
+	content.standardTypes = {
+		u8:   [1, content.functions.u8],
+		u16:  [2, content.functions.u16],
+		u32:  [4, content.functions.u32],
+		i8:   [1, content.functions.i8],
+		i16:  [2, content.functions.i16],
+		i32:  [4, content.functions.i32],
+		
+		u8l:  [1, content.functions.u8l],
+		u16l: [2, content.functions.u16l],
+		u32l: [4, content.functions.u32l],
+		i8l:  [1, content.functions.i8l],
+		i16l: [2, content.functions.i16l],
+		i32l: [4, content.functions.i32l],
+		
+		char: [1, content.functions.char]
+	};
+	
+	for (var key in content) {
+		FF.BinaryParser[key] = content[key];
+	}
+})();

binaryreaderdoc.html

+<!DOCTYPE html>
+<html>
+<head>
+	<title>Documentation - BinaryParser - firefly.nu</title>
+	<meta charset="UTF-8" />
+	<style>
+	body {
+		max-width: 800px;
+		margin: 0 auto;
+	}
+	
+	p {
+		margin: 0.5em 0;
+		text-indent: 0.8em;
+	}
+	
+	h1, h2, h3 {
+		clear: both;
+		margin-bottom: 0;
+		border-bottom: 0 solid #F30;
+	}
+	h1 {border-bottom-width: 2px;}
+	h2 {border-bottom-width: 1px;}
+	
+	pre.example {
+		position: relative;
+		margin: 1.0em 0;
+		padding: 0.6em;
+		border: 1px solid #F90;
+		border-left-width: 6px;
+	}
+	pre.example::before {
+		position: absolute;
+		top: -0.7em;
+		padding: 0 0.3em;
+		content: "Example";
+		background: #FFF;
+	}
+	
+	code.example {
+		padding: 0 0.1em;
+		background: #DDD;
+	}
+	
+	#menu {
+		position: relative;
+		float: left;
+		padding: 0.7em 0.8em 0.7em 2.0em;
+		border: 1px solid #F30;
+	}
+	#menu::before {
+		position: absolute;
+		top: -0.7em;
+		left: 0.5em;
+		padding: 0 0.3em;
+		font-family: sans-serif;
+		font-size: 10pt;
+		content: "Table of contents";
+		background: #FFF;
+	}
+	#menu li, #menu a {
+		font-family: sans-serif;
+		font-size: 10pt;
+	}
+	#menu a {color: #F60;}
+	#menu a:visited {color: #F30;}
+	#menu ol {padding-left: 1.5em;}
+	</style>
+</head>
+<body>
+	<h1 id="BinaryParser_documentation"><code>BinaryParser</code> documentation</h1>
+	<p>
+		This document contains information about how the <code>FF.BinaryParser</code>
+		object works.
+	</p>
+	<ol id="menu">
+		<li><a href="#General_usage">General usage</a></li>
+		<li><a href="#types"><code>types</code></a></li>
+		<li>
+			<a href="#structure"><code>structure</code></a>
+			<ol>
+				<li><a href="#General_structure">General structure</a></li>
+				<li><a href="#Array-like_structures">Array-like structures
+						(<code>$length</code>)</a></li>
+				<li><a href="#Post-processing_functions">Post-processing functions
+						(<code>$post</code>)</a></li>
+				<li><a href="#Branching_objects">Branching objects
+						(<code>$branch</code> and <code>$default</code>)</a></li>
+			</ol>
+		</li>
+		<li><a href="#Copyright">Copyright</a></li>
+	</ol>
+	
+	<h2 id="General_usage">General usage</h2>
+	<p>
+		The <code>BinaryParser</code> constructor takes two arguments: '<code>types</code>'
+		and '<code>structure</code>.' These will be discussed more in-depth below.
+		The returned <code>BinaryParser</code> object can be used as such:
+		<code class="example">parser.parse(binaryData)</code>.
+	</p>
+	<pre class="example">var types, structure;
+/* Define types and structure; see below for information about these. */
+var parser = new FF.BinaryParser(types, structure);
+
+var data = "someBinaryData";
+var obj = parser.parse(data);</pre>
+	
+	<h2 id="types"><code>types</code></h2>
+	<p>
+		The <code>types</code> argument is an object defining the different "native"
+		datatypes to be identified and used in the structure description. Each key
+		in this object is treated as a datatype with the key as its name, and the
+		value should be an array of two elements: the first element is the size of
+		the datatype (in bytes/characters); the second is a function to be called
+		when this datatype is adressed, with the string containing the binary data
+		as its argument.
+	</p>
+	<p>
+		There is an object <code>FF.BinaryParser.standardTypes</code> defined with
+		a set of common datatypes (<code>u8</code>, <code>u16</code>, <code>u32</code>,
+		<code>i8</code>, <code>i16</code>, <code>i32</code>, <code>u8l</code>,
+		<code>u16l</code>, <code>u32l</code>, <code>i8l</code>, <code>i16l</code>,
+		<code>i32l</code>, <code>char</code> — the '<code>l</code>' represents
+		little-endian versions of the datatypes; '<code>u</code>' is for unsigned and
+		'<code>i</code>' is for signed integers).
+	</p>
+<pre class="example">var types = {
+	u8: [1, function(s) {return s.charCodeAt(0);}],
+	u16: [2, function(s) {return s.charCodeAt(0) * 256 + s.charCodeAt(1);}
+};</pre>
+	
+	<h2 id="structure"><code>structure</code></h2>
+	<p>
+		The <code>structure</code> argument is an array containing the structure of
+		the binary data. This array is quite sophisticated, so this section will be
+		split into different parts.
+	</p>
+	
+	<h3 id="General_structure">General structure</h3>
+	<p>
+		A structural object is an array. This array contains single-attribute properties,
+		whose key works as the property name and value represens the datatype. Datatypes
+		may either refer to a datatype defined in the <a href="#types"><code>types</code></a>
+		section, or simply contain yet another structural object (array of similar objects).
+	</p>
+<pre class="example">var structure = [
+	{magic: 'u32'},
+	{smallNumber: 'u8'},
+	{signedInteger: 'i32'}
+];</pre>
+<pre class="example">var structure = [
+	{magic: 'u32'},
+	{block: [
+		{character: 'char'},
+		{number: 'u16'}
+	]}
+];</pre>
+	
+	<h3 id="Array-like_structures">Array-like structures</h3>
+	<p>
+		The <code>BinaryParser</code> can handle a couple of different "advanced"
+		structures. One of these are the array-like structures; this is simply the same
+		datatype repeated over and over again, a set amount of times. The length of an
+		array structure may be defined either by a constant number, or by referring to
+		an earlier field containing a number.
+	</p>
+	<p>
+		A field is considered to be array-like if it contains the magic property
+		'<code>$length</code>.' This magic property gives the length of the array; either
+		by a number, or by a string referring to an older property.
+	</p>
+<pre class="example">var structure = [
+	{magic: 'char', $length: 4},
+	{strings: [
+		{length: 'u16'},
+		{data: 'char', $length: "length"}
+	], $length: 3}
+];</pre>
+	
+	<h3 id="Post-processing_functions">Post-processing functions</h3>
+	<p>
+		Sadly, a '<code>string</code>' datatype cannot be efficiently described as
+		a <a href="#types"><code>types</code></a>-esque datatype. However, with the
+		addition of <em>post-processing functions</em>, this is possible. A post-processing
+		function is simply a function that is executed as a filter on a field after
+		it has been processed in a regular manner. It is implemented as the magic
+		field '<code>$post</code>.'
+	</p>
+	<p>
+		This allows us to take an array of single-character strings,
+		<code class="example">{string: 'char', $length: 4}</code>, and join the array
+		together into a native JavaScript string:
+		<code class="example">{string: 'char', $length: 4, $post: join}</code>
+		(given a definition of join that joins strings together).
+	</p>
+<pre class="example">function stringify(arr) { // Useful for null-padded fixed-length strings.
+	return arr.join('').match(/[^\0]*/)[0]; // Joins the array together and strips nulls.
+}
+
+var structure = [
+	{header: [
+		{magic: 'char', $length: 4, $post: stringify},
+		{length: 'u16'}
+	]},
+	{string: [
+		{data: 'char', $length: 32, $post: stringify}
+	], $length: "header.length"}
+];</pre>
+	
+	<h3 id="Branching_objects">Branching objects</h3>
+	<p>
+		To read binary data, it's often useful to apply different structures depending
+		on the first value in a header. This applies to e.g. Java <code>.class</code>
+		files, <code>.zip</code> files etc. This is implemented in <code>BinaryParser</code>
+		with the use of <em>branching objects</em>. These branching objects feature
+		defines two new magic fields: '<code>$branch</code>' and '<code>$default</code>'.
+		Branching objects works a lot like the <code>switch</code> statement feature in
+		e.g. JavaScript.
+	</p>
+	<p>
+		If an object contains the '<code>$branch</code>' property, it is identified as a
+		branching object. The branching object resolves a resource out of the value of
+		the '<code>$branch</code>' object, using the same method as is used for fetching
+		'<a href="#Array-like_structures"><code>$length</code></a>' data from previously
+		parsed fields. If the resulting value exists as a property name in the branch
+		object, it uses its value as the structural object to continue processing with.
+		If it doesn't exist, and if a '<code>$default</code>' field is available in the
+		branch object, it uses the value of the '<code>$default</code>' field instead.
+		If neither is available, an error is raised.
+	</p>
+<pre class="example">var structure = [
+	{length: 'u16'},
+	{block: [
+		{type: 'u16'},
+		{$branch: "type",
+			0: {data: [
+				{number: 'u16'}
+			]},
+			1: {data: [
+				{chars: 'char', $length: 16}
+			]},
+			$default: {data: [
+				{unknown: 'u8', $length: 32}
+			]}
+		}
+	], $length: "length"}
+];</pre>
+	
+	<h2 id="Copyright">Copyright</h2>
+	<p>
+		This document, as well as the <code>BinaryParser</code> source code, was written
+		by Jonas Höglund, aka. FireFly, in May 2010.
+	</p>
+</body>
+</html>
+<!DOCTYPE html>
+<html>
+<head>
+	<title>Testy</title>
+	<meta charset="UTF-8" />
+	<script src="lib/core.js"></script>
+	<script src="lib/binaryParser.js"></script>
+	<script src="lib/json.js"></script>
+	<script src="lib/base64_ob.js"></script>
+	<script>
+	function init() {
+		function join(arr) {return arr.join('');}
+		function stripNulls(str) {return str.match(/[^\0]*/)[0];}
+		function chain() {
+			var args = arguments;
+			return function(obj) {
+				for (var i=0; i<args.length; i++) obj = args[i](obj);
+				return obj;
+			}
+		}
+	}
+	
+	function parseData() {
+		var data = FF.$('data').value.replace(/\s/g, '');
+		var types = FF.$('types').value.replace(/(?:^\s*|\s*$)/g, '');
+		var structure = FF.$('structure').value.replace(/(?:^\s*|\s*$)/g, '');
+		
+		try {
+			if (types == '') types = FF.BinaryParser.standardTypes;
+			else types = eval(types);
+			
+			var predef = 
+				   ["function join(arr) {return arr.join('');}",
+					"function stripNulls(str) {return str.match(/[^\\0]*/)[0];}",
+					"function chain() {",
+					"	var args = arguments;",
+					"	return function(obj) {",
+					"		for (var i=0; i<args.length; i++) obj = args[i](obj);",
+					"		return obj;",
+					"	}",
+					"}\n"].join('\n');
+			structure = eval("(function() {" + predef + "return " + structure + "})()");
+			
+			var parser = new FF.BinaryParser(types, structure);
+			var obj = parser.parse(debase64(data));
+			
+			FF.$('output').innerHTML = FF.Utils.toJson(obj);
+		} catch (err) {
+			FF.$('error').innerHTML = err.toString();
+			throw err;
+		}
+	}
+	
+	window.onload = init;
+	</script>
+	<style>
+	textarea, button, .docslink {display: block;}
+	* {font-family: monospace; font-size: 10pt;}
+	</style>
+</head>
+<body>
+	<a class="docslink" href="binaryreaderdoc.html">Documentation</a>
+	
+	<label for="data">Data (base64'd):</label>
+	<textarea cols="120" rows="10" id="data"></textarea>
+	
+	<label for="types">Types (default if left blank; do so!):</label>
+	<textarea cols="120" rows="5" id="types"></textarea>
+	
+	<label for="structure">Structure (is an array):</label>
+	<textarea cols="120" rows="30" id="structure">[]</textarea>
+	<button onclick="parseData()">Parse!</button>
+	
+	<label for="errors">Errors:</label>
+	<pre id="error"></pre>
+	
+	<label for="output">Output:</label>
+	<pre id="output"></pre>
+</body>
+</html>
+FF.Color = {};
+
+FF.Color = function(red, green, blue) {
+	this.red = red;
+	this.green = green;
+	this.blue = blue;
+};
+FF.Color.prototype.toString = function() {
+	return 'rgb(' + parseInt(this.red) + ', ' + parseInt(this.green) + ', '
+			+ parseInt(this.blue) + ')';
+};
+
+FF.Color.fromHSL = function(hue, sat, lum) {
+	var c = parseInt(hue % (360 / 6) * 6);
+	var sec = parseInt(hue / 360 * 6);
+	
+	switch (sec) {
+		case 0: return new FF.Color(255, c, 0); break;
+		case 1: return new FF.Color(255-c, 255, 0); break;
+		case 2: return new FF.Color(0, 255, c); break;
+		case 3: return new FF.Color(0, 255-c, 255); break;
+		case 4: return new FF.Color(c, 0, 255); break;
+		case 5: return new FF.Color(255, 0, 255-c); break;
+		default:
+			if (sec == 6 && c == 0) {
+				return new FF.Color(255, 0, 0);
+			}
+	}
+};
+	// Color picker.
+(function() {
+	FF.Color.createHuePicker = function(width, height, res, func) {
+		var el = document.createElement('canvas');
+		el.width = width;
+		el.height = height;
+		
+		var ctx = el.getContext('2d');
+		for (var i=0; i<width; i+=res) {
+			ctx.fillStyle = FF.Color.fromHSL(i/width * 360, 100, 100).toString();
+			ctx.fillRect(i, 0, res, height);
+		}
+		
+		var active = false;
+		function callback(ev) {
+			var c = parseInt((ev.clientX - FF.Dom.getX(el)) * 360 / width);
+		//	if (c > 255) c = 255;
+			
+			func(c);
+		}
+		
+		el.onmousedown = function(ev) {
+			active = true;
+			callback(ev);
+		};
+		el.onmouseup = function(ev) {
+			active = false;
+			callback(ev);
+		};
+		el.onmousemove = function(ev) {
+			if (active) {
+				callback(ev);
+			}
+		};
+		
+		return el;
+	};
+})();
+FF.Concurrency = {};
+
+(function() {
+	var C = FF.Concurrency;
+	
+	C.OnLoad = function(func) {
+		this.func = func;
+		this.done = 0;
+	};
+	
+	C.OnLoad.prototype.add = function(o) {
+		o.onload = function() {
+			this.done--;
+			
+			opera.postError(this.done);
+			
+			if (this.done == 0) {
+				this.func(this, o);
+			}
+		};
+	}
+	C.OnLoad.prototype.ready = function() {
+		this.done *= -1;
+	};
+})();
 	};
 	
 	FF.Core.include = function() {
-		var folder='', files=0, done=0, perfs=[];
+		var folder=".", files=0, done=0, perfs=[];
 		if (!HEAD) HEAD = document.getElementsByTagName('head')[0];
 		
 		function include(file) {
-			if (!((folder + '/' + file) in _included)) {
+			if (!((folder + "/" + file) in _included)) {
 				var el = document.createElement('script');
-				el.type = 'application/javascript';
-				el.src = folder + '/' + file;
+				el.type = "application/javascript";
+				el.src = folder + "/" + file;
 				HEAD.appendChild(el);
 				
 				files--;
+var FF = (function() {
+	var FF = {};
+	var _included = {};
+	
+	var HEAD;
+	
+	FF.Core = {};
+	FF.Dom = {};
+	
+	FF.Dom.instanceOf = function(x, y) {
+		return typeof(x) === 'object' && x !== null && x instanceof y;
+	};
+	FF.Dom.isArray = function(x) {
+		return FF.Dom.instanceOf(x, Array);
+	};
+	
+	FF.$ = function(s) {
+		return document.getElementById(s);
+	};
+	
+	FF.Core.include = function() {
+		var folder=".", files=0, done=0, perfs=[];
+		if (!HEAD) HEAD = document.getElementsByTagName('head')[0];
+		
+		function include(file) {
+			if (!((folder + "/" + file) in _included)) {
+				var el = document.createElement('script');
+				el.type = 'application/javascript';
+				el.src = folder + "/" + file;
+				HEAD.appendChild(el);
+				
+				files--;
+				el.onload = function() {
+					done++;
+					
+					if (files == done) {
+						for (var i=0; i<perfs.length; i++) {
+							perfs[i].call(this);
+						}
+					}
+				};
+			}
+		}
+		
+		for (var i=0; i<arguments.length; i++) {
+			if (typeof(arguments[i]) == 'string') {
+				if (arguments[i].match(/\.js$/)) {
+					include(arguments[i]);
+				} else {
+					folder = arguments[i];
+				}
+			} else if (FF.Dom.isArray(arguments[i])) {
+				for (var j=0; j<arguments[i].length; j++) {
+					include(arguments[i][j]);
+				}
+			} else if (typeof(arguments[i]) == 'function') {
+				perfs.push(arguments[i]);
+			} else {
+				throw new TypeError("Unsupported argument '" + arguments[i] + "'");
+			}
+		}
+		
+		files = -files;
+	};
+	
+	return FF;
+})();
-FF.Dom.create = function(name, attrs, chs, cb) {
-	if (FF.Dom.isArray(attrs)) {
-		attrs = {};
-		chs = attrs;
-	}
-	if (!FF.Dom.isArray(chs)) {
-		chs = [];
+(function() {
+	function setAttrs(obj, attrs) {
+		for (var key in attrs) {
+			if (typeof(attrs[key]) == 'object' && attrs[key] !== null) {
+				setAttrs(obj[key], attrs[key]);
+			} else {
+				obj[key] = attrs[key];
+			}
+		}
+		
+		return obj;
 	}
 	
-	var el = document.createElement(name);
-	
-	for (var key in attrs) {
-		el[key] = attrs[key];
-	}
-	
-	for (var i=0; i<chs.length; i++) {
-		if (typeof(chs[i]) == 'string') {
-			el.appendChild(FF.Dom.text(chs[i]));
-		} else {
-			el.appendChild(chs[i]);
+	FF.Dom.create = function(name, attrs, chs, cb) {
+		if (FF.Dom.isArray(attrs)) {
+			attrs = {};
+			chs = attrs;
 		}
-	}
-	
-	if (typeof(cb) == 'function') {
-		cb.call(el, attrs, chs);
-	}
-	
-	return el;
-};
+		if (!FF.Dom.isArray(chs)) {
+			chs = [];
+		}
+		
+		var el = document.createElement(name);
+		setAttrs(el, attrs);
+		
+		for (var i=0; i<chs.length; i++) {
+			if (typeof(chs[i]) == 'string') {
+				el.appendChild(FF.Dom.text(chs[i]));
+			} else {
+				el.appendChild(chs[i]);
+			}
+		}
+		
+		if (typeof(cb) == 'function') {
+			cb.call(el, attrs, chs);
+		}
+		
+		return el;
+	};
+})();
 
 FF.Dom.text = function(str) {
 	return document.createTextNode(str);
 	}
 	
 	for (var i=0; i<chs.length; i++) {
-		el.appendChild(chs[i]);
+		if (typeof(chs[i]) == 'string') {
+			el.appendChild(FF.Dom.text(chs[i]));
+		} else {
+			el.appendChild(chs[i]);
+		}
 	}
 	
 	if (typeof(cb) == 'function') {
+	// If not built-in.
+if (!Array.prototype.indexOf) {
+	Array.prototype.indexOf = function(x) {
+		for (var i=0; i<this.length; i++) {
+			if (this[i] == x) {
+				return i;
+			}
+		}
+		return -1;
+	};
+}
+if (!Array.prototype.lastIndexOf) {
+	Array.prototype.lastIndexOf = function(x) {
+		for (var i=this.length-1; i>0; i++) {
+			if (this[i] == x) {
+				return i;
+			}
+		}
+		return -1;
+	};
+}
+if (!Array.prototype.map) {
+	Array.prototype.map = function(f) {
+		var o = new Array(this.length);
+		for (var i=0; i<this.length; i++) {
+			o[i] = f(this[i], i, this);
+		}
+		return o;
+	};
+}
+if (!Array.prototype.filter) {
+	Array.prototype.filter = function(f) {
+		var o = [];
+		for (var i=0; i<this.length; i++) {
+			if (f(this[i], i, this)) {
+				o.push(this[i]);
+			}
+		}
+		return o;
+	};
+}
+if (!Array.prototype.forEach) {
+	Array.prototype.forEach = function(f) {
+		for (var i=0; i<this.length; i++) {
+			if (typeof(this[i]) !== 'undefined') {
+				f(this[i], i, this);
+			}
+		}
+	};
+}
+if (!Array.prototype.every) {
+	Array.prototype.every = function(f) {
+		for (var i=0; i<this.length; i++) {
+			if (!f(this[i], i, this)) {
+				return false;
+			}
+		}
+		return true;
+	};
+}
+if (!Array.prototype.some) {
+	Array.prototype.some = function(f) {
+		for (var i=0; i<this.length; i++) {
+			if (f(this[i], i, this)) {
+				return true;
+			}
+		}
+		return false;
+	};
+}
+if (!Array.prototype.reduce) {
+	Array.prototype.reduce = function(f) {
+		if (this.length == 1) {
+			return this[0];
+		} else {
+			return f(this[0], this.slice(1).reduce(f));
+		}
+	};
+}
+if (!Array.prototype.reduceRight) {
+	Array.prototype.reduceRight = function(f) {
+		if (this.length == 1) {
+			return this[0];
+		} else {
+			return f(this.slice(0, this.length-1).reduceRight(f),
+					this[this.length-1]);
+		}
+	};
+}
+
+	// Custom.
+if (!Array.prototype.rotate) {
+	Array.prototype.rotate = function(n) {
+		if (n === 0)
+			return this.copy();
+		
+		if (typeof(n) == 'undefined') {
+			n = 1;
+		} else {
+			n %= this.length;
+		}
+		return this.slice(this.length-n, this.length).
+				concat(this.slice(0, this.length-n));
+	};
+}
+if (!Array.prototype.copy) {
+	Array.prototype.copy = function() {
+		return this.slice(0);
+	};
+}
+if (!Array.prototype.multiply) {
+	Array.prototype.multiply = function(n) {
+		if (typeof(n) == 'undefined') n = 1;
+		var o = [];
+		
+		for (var i=0; i<n; i++) {
+			o.push(this.copy());
+		}
+		return o;
+	};
+}
+if (!Array.prototype.square) {
+	Array.prototype.square = function() {
+		return this.multiply(this.length);
+	};
+}
+if (!Array.prototype.mapDeep) {
+	Array.prototype.mapDeep = function(cb) {
+		var o = [];
+		for (var i=0; i<this.length; i++) {
+			if (typeof(this[i]) == 'object' && this[i] != null &&
+					this[i] instanceof Array) {
+				o.push(this[i].mapDeep(cb));
+			} else {
+				o.push(cb(this[i], i, this));
+			}
+		}
+		return o;
+	};
+}
+if (!Array.prototype.zip) {
+	Array.prototype.zip = function(other, cb) {
+		if (typeof(cb) == 'undefined') {
+			cb = function(a, b) {return [a, b]};
+		}
+		
+		if (typeof(other) != 'object' || other == null || !(other
+				instanceof Array) || this.length != other.length) {
+			throw new TypeError();
+		} else {
+			var o = new Array(this.length);
+			for (var i=0; i<this.length; i++) {
+				o[i] = cb(this[i], other[i], i, this, other);
+			}
+			return o;
+		}
+	};
+}
+if (!Array.prototype.inner) {
+	Array.prototype.inner = function(other, cb, cb2) {
+		if (typeof(cb) == 'undefined') {
+			cb = function(a, b) {return a*b};
+		}
+		if (typeof(cb2) == 'undefined') {
+			cb2 = function(a, b) {return a+b};
+		}
+		
+		return this.zip(other, cb).reduce(cb2);
+	};
+}
+if (!Array.prototype.outer) {
+	Array.prototype.outer = function(other, cb) {
+		if (typeof(other) != 'object' || other == null ||
+				!(other instanceof Array)) {
+			throw new TypeError();
+		} else {
+			var o = new Array(this.length);
+			for (var i=0; i<this.length; i++) {
+				o[i] = new Array(other.length);
+				for (var j=0; j<other.length; j++) {
+					o[i][j] = cb(this[i], other[j], i, j, this, other);
+				}
+			}
+			return o;
+		}
+	};
+}
+if (!Array.prototype.wrap) {
+	Array.prototype.wrap = function(n) {
+		if (typeof(n) == 'undefined') n = 1;
+		var c = this.copy(), o = [];
+		while (c.length > 0) {
+			var t = [];
+			for (var i=0; i<n && c.length>0; i++)
+				t.push(c.shift());
+			o.push(t);
+		}
+		return o;
+	};
+}
+if (!Array.prototype.permute) {
+	Array.prototype.permute = function() {
+		var ks = [], vs = [];
+		for (var i=0; i<this.length; i++) {
+			if (ks.indexOf(this[i]) >= 0) {
+				vs[ks.indexOf(this[i])]++;
+			} else {
+				ks.push(this[i]);
+				vs.push(1);
+			}
+		}
+		
+		function internalPermute(keys, vals) {
+			if (keys.length == 1) {
+				var o = [];
+				for (var i=0; i<vals[0]; i++) {
+					o.push(keys[0]);
+				}
+				return [o];
+			} else {
+				var o = [];
+				for (var i=0; i<keys.length; i++) {
+					var vali = vals.copy();
+					var keyi = keys.copy();
+					if (vals[i] == 1) {
+						vali.splice(i, 1);
+						keyi.splice(i, 1);
+					} else {
+						vali[i]--;
+					}
+					var perms = internalPermute(keyi, vali);
+					o = o.concat(perms.map(function(a) {
+						return [keys[i]].concat(a);
+					}));
+				}
+				return o;
+			}
+		};
+		
+		return internalPermute(ks, vs);
+	};
+}
+if (!Array.prototype.padBefore) {
+	Array.prototype.padBefore = function(pad, n) {
+		var o = [];
+		for (var i=0; i<this.length; i++) {
+			if (typeof(this[i]) != 'object' || this[i] == null ||
+					!(this[i] instanceof Array)) {
+				throw new TypeError();
+			} else {
+				var p = Array.range(n - this[i].length).map(function() {
+					return pad;
+				});
+				o.push(p.concat(this[i]));
+			}
+		}
+		return o;
+	}
+}
+if (!Array.prototype.interchange) {
+	Array.prototype.interchange = function(v, n) {
+		var i = 0;
+		return this.copy().map(function(a) {
+			if (a == v) {
+				return n[i++];
+			} else {
+				return a;
+			}
+		});
+	};
+}
+if (!Array.prototype.interchangeDeep) {
+	Array.prototype.interchangeDeep = function(v, n) {
+		var i = 0;
+		return this.copy().mapDeep(function(a) {
+			if (a == v) {
+				return n[i++];
+			} else {
+				return a;
+			}
+		});
+	};
+}
+if (!Array.prototype.count) {
+	Array.prototype.count = function(e) {
+		return this.filter(function(a) {return a==e}).length;
+	};
+}
+if (!Array.prototype.asSet) {
+	Array.prototype.asSet = function() {
+		var o = [];
+		for (var i=0; i<this.length; i++) {
+			if (o.realIndexOf(this[i]) == -1)
+				o.push(this[i]);
+		}
+		return o;
+	};
+}
+if (!Array.prototype.union) {
+	Array.prototype.union = function(other) {
+		return this.concat(other).asSet();
+	};
+}
+if (!Array.prototype.intersect) {
+	Array.prototype.intersect = function(other) {
+		var o = [];
+		for (var i=0; i<this.length; i++) {
+			if (other.indexOf(this[i]) != -1)
+				o.push(this[i]);
+		}
+		return o;
+	};
+}
+if (!Array.prototype.sans) {
+	Array.prototype.sans = function(arr) {
+		return this.filter(function(a) {
+			return arr.realIndexOf(a) == -1;
+		});
+	};
+}
+if (!Array.prototype.similarTo) {
+	Array.prototype.similarTo = function(other) {
+		if (this.length != other.length) {
+			return false;
+		} else {
+			for (var i=0; i<this.length; i++) {
+				if (typeof(this[i]) == 'object' && this[i] != null &&
+						this[i] instanceof Array) {
+					if (!this[i].similarTo(other[i]))
+						return false;
+				} else {
+					if (this[i] != other[i])
+						return false;
+				}
+			}
+			return true;
+		}
+	};
+}
+if (!Array.prototype.realIndexOf) {
+	Array.prototype.realIndexOf = function(el) {
+		if (typeof(el) == 'object' && el != null && el instanceof Array) {
+			for (var i=0; i<this.length; i++) {
+				if (this[i].similarTo(el))
+					return i;
+			}
+			return -1;
+		} else {
+			return this.indexOf(el);
+		}
+	};
+}
+if (!Array.prototype.flatten) {
+	Array.prototype.flatten = function() {
+		return this.reduce(function(a, b) {return a.concat(b)});
+	};
+}
+if (!Array.prototype.stitch) {
+	Array.prototype.stitch = function(other) {
+		var o = this.copy();
+		for (var i=0; i<other.length; i++) {
+			if (this.length <= i)
+				break;
+			
+			o[i] = o[i].concat(other[i]);
+		}
+		return o;
+	};
+}
+if (!Array.prototype.stitchFold) {
+	Array.prototype.stitchFold = function() {
+		return this.reduce(function(a, b) {return a.stitch(b)});
+	};
+}
+if (!Array.prototype.nIndexOf) {
+	Array.prototype.nIndexOf = function(other) {
+		if (other.length > this.length) {
+			return [-1];
+		} else {
+			if (typeof(this[0]) != 'object' && this[0] != null &&
+					!(this[0] instanceof Array)) {
+				var n = 0;
+				for (var i=0; i<this.length; i++) {
+					if (this[i] == other[n]) {
+						n++;
+						if (n == other.length) {
+							return [i-n+1];
+						}
+					} else {
+						n = 0;
+					}
+				}
+				return [-1];
+			} else {
+				var t;
+		outer:	for (var i=0; i<this.length; i++) {
+					if ((t = this[i].nIndexOf(other[0]))[0] >= 0) {
+							// See if done.
+						for (var j=1; j<other.length; j++) {
+							if (!this[i+j].slice(t[0], t[0] + other[j].length).
+									similarTo(other[j])) {
+								continue outer;
+							}
+							return [i].concat(t);
+						}
+					}
+				}
+				return [-1];
+			}
+		}
+	}
+}
+
+if (!Array.range) {
+	Array.range = function(a, b) {
+		if (!b) {
+			b = a;
+			a = 1;
+		}
+		
+		var out = [];
+		for (var i=a; i<=b; i++) {
+			out.push(i);
+		}
+		return out;
+	};
+}
+if (!Array.indices) {
+	Array.indices = function() {
+		var o = [];
+		for (var i=0; i<arguments.length; i++) {
+			for (var j=0; j<arguments[i]; j++) {
+				o.push(i);
+			}
+		}
+		return o;
+	};
+}
+if (!Array.radixNumbers) {
+	Array.radixNumbers = function(r, n) {
+		var o = [];
+		for (var i=0; i<n; i++) {
+			o.push((i).toString(r).split('').map(function(a) {
+				return parseInt(a);
+			}));
+		}
+		return o;
+	};
+}
+
+if (!Function.prototype.iterate) {
+	Function.prototype.iterate = function(x, n) {
+		if (n == 0) {
+			return [];
+		} else if (n < 0) {
+			throw new TypeError();
+		} else {
+			var o = [x];
+			for (var i=1; i<n; i++) {
+				o.push(this(o[i-1]));
+			}
+			return o;
+		}
+	};
+}
+
+if (!Math.add) {
+	Math.add = function(a, b) {return a+b};
+}
+if (!Math.multiply) {
+	Math.multiply = function(a, b) {return a*b};
+}
+if (!Math.subtract) {
+	Math.subtract = function(a, b) {return a-b};
+}
+if (!Math.divide) {
+	Math.divide = function(a, b) {return a/b};
+}
+if (!Math.square) {
+	Math.square = function(a) {return a*a};
+}
+if (!Math.getMultiplier) {
+	Math.getMultiplier = function(a) {
+		return function(b) {return a*b}
+	};
+}
+if (!Math.pow) {
+	Math.pow = function(a, b) {return Math.pow(a, b)};
+}
 if (typeof(FF) !== 'object') FF = {};
 if (typeof(FF.Utils) !== 'object') FF.Utils = {};
 
-FF.Utils.toJson = function(obj, tabsArg) {
-	var tabs = tabsArg || '';
-	
-	if (obj instanceof Array) {
+(function() {
+	function escapeString(str) {
+		return str.replace(/[\000-\011\013-\037]/g, function(a) {return "\\" +
+				a.charCodeAt(0);});
+	}
+	FF.Utils.toJson = function(obj, tabsArg) {
+		var tabs = tabsArg || '';
+		
+		if (obj instanceof Array) {
+			var out = [];
+			for (var i=0; i<obj.length; i++)
+				out.push(FF.Utils.toJson(obj[i], tabs));
+			
+			return "[" + out.join(', ') + "]";
+		} else if (typeof(obj) == 'string') {
+			return '"' + escapeString(obj.replace(/\\/g, "\\\\").replace(/"/g, '\\"')) + '"';
+		} else if (typeof(obj) == 'function' ||
+				typeof(obj) == 'undefined') {
+			return '"[' + typeof(obj) + ']"';
+		} else if (typeof(obj) == 'object' && obj !== null) {
+			if (obj instanceof Date) {
+				return '"' + obj.toString() + '"';
+			} else {
+				var out = "{\n";
+				for (var idx in obj) {
+					var index = idx;
+					if (!idx.match(/^\w+$/))
+						index = '"' + idx + '"';
+					
+					out += tabs + '\t' + index + ': ' + FF.Utils.toJson(obj[idx],
+							tabs+'\t') + ',\n';
+				}
+				out += tabs + "}";
+				
+				return out;
+			}
+		} else {
+			return obj.toString();
+		}
+	};
+	FF.Utils.keys = function(obj) {
 		var out = [];
-		for (var i=0; i<obj.length; i++)
-			out.push(Eldis.toJson(obj[i]));
-		
-		return '[' + out.join(', ') + ']';
-//	} else if (obj instanceof java.lang.String) {
-//		return '"' + obj.toString() + '"';
-	} else if (typeof(obj) == 'string') {
-		return '"' + obj.replace(/\\/g, '\\\\').
-				replace(/"/g, '\\"') + '"';
-	} else if (typeof(obj) == 'function' ||
-			typeof(obj) == 'undefined') {
-		return 'null';
-	} else if (typeof(obj) == 'object') {
-		var out = '{\n';
-		for (var idx in obj) {
-			var index = idx;
-			if (!idx.match(/^\w+$/))
-				index = '"' + idx + '"';
-			
-			out += tabs + '\t' + index + ': ' + FF.Utils.toJson(obj[idx],
-					tabs+'\t') + ',\n';
+		for (var key in obj) {
+			out.push(key);
 		}
-		out += tabs + '}';
-		
 		return out;
-	} else {
-		return obj.toString();
-	}
-};
-FF.Utils.keys = function(obj) {
-	var out = [];
-	for (var key in obj) {
-		out.push(key);
-	}
-	return out;
-};
+	};
+})();
+if (typeof(FF) !== 'object') FF = {};
+if (typeof(FF.Utils) !== 'object') FF.Utils = {};
+
+(function() {
+	function escapeString(str) {
+		return str.replace(/[\000-\011\013-\037]/g, function(a) {return "\\" +
+				a.charCodeAt(0);});
+	}
+	FF.Utils.toJson = function(obj, tabsArg) {
+		var tabs = tabsArg || '';
+		
+		if (obj instanceof Array) {
+			var out = [];
+			for (var i=0; i<obj.length; i++)
+				out.push(FF.Utils.toJson(obj[i], tabs));
+			
+			return "[" + out.join(', ') + "]";
+		} else if (typeof(obj) == 'string') {
+			return '"' + escapeString(obj.replace(/\\/g, "\\\\").replace(/"/g, '\\"')) + '"';
+		} else if (typeof(obj) == 'function' ||
+				typeof(obj) == 'undefined') {
+			return '"[' + typeof(obj) + ']"';
+		} else if (typeof(obj) == 'object' && obj !== null) {
+			if (obj instanceof Date) {
+				return '"' + obj.toString() + '"';
+			} else {
+				var out = "{\n";
+				for (var idx in obj) {
+					var index = idx;
+					if (!idx.match(/^\w+$/))
+						index = '"' + idx + '"';
+					
+					out += tabs + '\t' + index + ': ' + FF.Utils.toJson(obj[idx],
+							tabs+'\t') + ',\n';
+				}
+				out += tabs + "}";
+				
+				return out;
+			}
+		} else {
+			return obj.toString();
+		}
+	};
+	FF.Utils.keys = function(obj) {
+		var out = [];
+		for (var key in obj) {
+			out.push(key);
+		}
+		return out;
+	};
+})();
+var Tree = (function() {
+	var META = {};
+	
+	function TreeNode(tree, meta) {
+		this[META] = meta;
+		for (var k in tree) {
+			this[k] = new TreeNode(tree[k], meta);
+		}
+	}
+	
+	var Tree = function(tree, meta) {
+		if (typeof(meta) === 'undefined') meta = {};
+		for (var k in tree) {
+			this[k] = new TreeNode(tree[k], meta);
+		}
+	};
+	
+	function realTree(obj) {
+		var o = {};
+		for (var k in obj) {
+			if (obj[k] !== Tree.prototype[k] && k !== META) {
+				o[k] = obj[k];
+			}
+		}
+		return o;
+	}
+	
+	Tree.prototype.forEach = function(cb, depth) {
+		if (typeof(depth) !== 'number') depth = 0;
+		var tree = realTree(this);
+		
+		for (var k in tree) {
+			if (typeof(tree[k]) === 'object' && tree[k] !== null &&
+					tree[k] instanceof TreeNode) {
+				cb(this[k][META], k, depth);
+				Tree.prototype.forEach.call(tree[k], cb, depth+1);
+			}
+		}
+	}
+	Tree.prototype.forEachLeaf = function(cb, depth, name) {
+		if (typeof(depth) !== 'number') depth = 0;
+		var tree = realTree(this);
+		
+		var chk = true;
+		for (var k in tree) {
+			if (typeof(tree[k]) === 'object' && tree[k] !== null &&
+					tree[k] instanceof TreeNode) {
+				Tree.prototype.forEachLeaf.call(tree[k], cb, depth+1, k);
+				chk = false;
+			}
+		}
+		
+		if (chk) {cb(this, name, depth);}
+	}
+	Tree.prototype.map = function(cb, depth) {
+		if (typeof(depth) !== 'number') depth = 0;
+		var tree = realTree(this);
+		
+		for (var k in tree) {
+			if (typeof(tree[k]) === 'object' && tree[k] !== null &&
+					tree[k] instanceof TreeNode) {
+				this[k] = cb(this[k], k, depth);
+				Tree.prototype.map.call(tree[k], cb, depth+1);
+			}
+		}
+	}
+	
+	Tree.parse = function(root, cb) {
+		function innerParse(obj) {
+			var o = {};
+			var chs = cb(obj);
+			
+			for (var i=0; i<chs.length; i++) {
+				o[chs[i].name] = innerParse(chs[i].value, cb);
+			}
+			return o;
+		}
+		return new Tree(innerParse(root));
+	}
+	
+	return Tree;
+})();
+(function() {
+	var conds = [];
+	
+	function checkConds() {
+		for (var i=0; i<conds.length; i++) {
+			if (conds[i][0]()) {
+				conds[i][1].call(conds[i][2]);
+				conds.splice(i, 1);
+				i--;
+			}
+		}
+		
+		if (conds.length > 0) {
+			setTimeout(checkConds, 50);
+		}
+	}
+	
+	FF.Utils = {};
+	
+	FF.Utils.when = function(condArg, func, thisObj, thisObj2) {
+		var cond = condArg;
+		if (typeof(cond) === 'object') {
+			cond = function() {return condArg.value;};
+			if (typeof(func) === 'string') {
+				var key = func;
+				func = thisObj;
+				thisObj = thisObj2;
+				cond = function() {return condArg[key];};
+			} else {
+				cond = function() {return condArg.value;};
+			}
+		}
+		
+		conds.push([cond, func, thisObj]);
+		
+		if (conds.length == 1) {
+			checkConds();
+		}
+	};
+	
+	FF.Utils.urlParameters = function(s) {
+		var s = s || window.location;
+		
+		var args = {};
+		s.toString().replace(/[^?]*\??/, '').split(/&/).forEach(function(a) {
+			if (a.match(/\w+=/)) {
+				var key = a.replace(/=.*$/, '');
+				var value = a.replace(/[^=]*=/, '');
+				args[key] = unescape(value);
+			}
+		});
+		
+		return args;
+	}
+})();