<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array"/>
  <modified type="array">
    <modified>
      <diff>@@ -15,7 +15,7 @@ var Builder = {
 
 	paths: {
 		source: 'Source',
-		specs: 'Specs',
+		specs: 'Specs'
 	},
 
 	included: {
@@ -27,13 +27,12 @@ var Builder = {
 	scripts: {
 		source: {
 			'Fx'        : ['Fx.Position'],
-			'Utilities'	: ['XmlParser']
+			'Utilities' : ['XmlParser']
 		},
 
 		specs: {
 			'Utilities' : ['XmlParser'],
 		}
-
 	},
 
 	initialize: function(root){</diff>
      <filename>Specs/Assets/Scripts/Builder.js</filename>
    </modified>
    <modified>
      <diff>@@ -4,19 +4,17 @@
  * Copyright 2006 Google Inc.
  * http://code.google.com/p/google-diff-match-patch/
  *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
+ * Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
  *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
+ *   http://www.apache.org/licenses/LICENSE-2.0
  *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 /**
@@ -54,7 +52,7 @@ function diff_match_patch() {
   /**
    * Compute the number of bits in an int.
    * The normal answer for JavaScript is 32.
-   * @return {Number} Max bits
+   * @return {number} Max bits
    */
   function getMaxBits() {
     var maxbits = 0;
@@ -71,6 +69,7 @@ function diff_match_patch() {
   this.Match_MaxBits = getMaxBits();
 }
 
+
 //  DIFF FUNCTIONS
 
 
@@ -87,12 +86,12 @@ var DIFF_EQUAL = 0;
 /**
  * Find the differences between two texts.  Simplifies the problem by stripping
  * any common prefix or suffix off the texts before diffing.
- * @param {String} text1 Old string to be diffed
- * @param {String} text2 New string to be diffed
- * @param {Boolean} opt_checklines Optional speedup flag.  If present and false,
+ * @param {string} text1 Old string to be diffed
+ * @param {string} text2 New string to be diffed
+ * @param {boolean} opt_checklines Optional speedup flag.  If present and false,
  *     then don't run a line-level diff first to identify the changed areas.
  *     Defaults to true, which does a faster, slightly less optimal diff
- * @return {Array} Array of diff tuples
+ * @return {Array.&lt;Array.&lt;*&gt;&gt;} Array of diff tuples
  */
 diff_match_patch.prototype.diff_main = function(text1, text2, opt_checklines) {
   // Check for equality (speedup)
@@ -133,13 +132,15 @@ diff_match_patch.prototype.diff_main = function(text1, text2, opt_checklines) {
 
 
 /**
- * Find the differences between two texts.
- * @param {String} text1 Old string to be diffed
- * @param {String} text2 New string to be diffed
- * @param {Boolean} checklines Speedup flag.  If false, then don't run a
+ * Find the differences between two texts.  Assumes that the texts do not
+ * have any common prefix or suffix.
+ * @param {string} text1 Old string to be diffed
+ * @param {string} text2 New string to be diffed
+ * @param {boolean} checklines Speedup flag.  If false, then don't run a
  *     line-level diff first to identify the changed areas.
  *     If true, then run a faster, slightly less optimal diff
- * @return {Array} Array of diff tuples
+ * @return {Array.&lt;Array.&lt;*&gt;&gt;} Array of diff tuples
+ * @private
  */
 diff_match_patch.prototype.diff_compute = function(text1, text2, checklines) {
   var diffs;
@@ -187,7 +188,7 @@ diff_match_patch.prototype.diff_compute = function(text1, text2, checklines) {
   }
 
   // Perform a real diff.
-  if (checklines &amp;&amp; text1.length + text2.length &lt; 250) {
+  if (checklines &amp;&amp; (text1.length &lt; 100 || text2.length &lt; 100)) {
     // Too trivial for the overhead.
     checklines = false;
   }
@@ -219,29 +220,33 @@ diff_match_patch.prototype.diff_compute = function(text1, text2, checklines) {
     var text_delete = '';
     var text_insert = '';
     while (pointer &lt; diffs.length) {
-      if (diffs[pointer][0] == DIFF_INSERT) {
-        count_insert++;
-        text_insert += diffs[pointer][1];
-      } else if (diffs[pointer][0] == DIFF_DELETE) {
-        count_delete++;
-        text_delete += diffs[pointer][1];
-      } else {
-        // Upon reaching an equality, check for prior redundancies.
-        if (count_delete &gt;= 1 &amp;&amp; count_insert &gt;= 1) {
-          // Delete the offending records and add the merged ones.
-          var a = this.diff_main(text_delete, text_insert, false);
-          diffs.splice(pointer - count_delete - count_insert,
-                       count_delete + count_insert);
-          pointer = pointer - count_delete - count_insert;
-          for (var j = a.length - 1; j &gt;= 0; j--) {
-            diffs.splice(pointer, 0, a[j]);
+      switch (diffs[pointer][0]) {
+        case DIFF_INSERT:
+          count_insert++;
+          text_insert += diffs[pointer][1];
+          break;
+        case DIFF_DELETE:
+          count_delete++;
+          text_delete += diffs[pointer][1];
+          break;
+        case DIFF_EQUAL:
+          // Upon reaching an equality, check for prior redundancies.
+          if (count_delete &gt;= 1 &amp;&amp; count_insert &gt;= 1) {
+            // Delete the offending records and add the merged ones.
+            var a = this.diff_main(text_delete, text_insert, false);
+            diffs.splice(pointer - count_delete - count_insert,
+                         count_delete + count_insert);
+            pointer = pointer - count_delete - count_insert;
+            for (var j = a.length - 1; j &gt;= 0; j--) {
+              diffs.splice(pointer, 0, a[j]);
+            }
+            pointer = pointer + a.length;
           }
-          pointer = pointer + a.length;
-        }
-        count_insert = 0;
-        count_delete = 0;
-        text_delete = '';
-        text_insert = '';
+          count_insert = 0;
+          count_delete = 0;
+          text_delete = '';
+          text_insert = '';
+          break;
       }
      pointer++;
     }
@@ -254,45 +259,53 @@ diff_match_patch.prototype.diff_compute = function(text1, text2, checklines) {
 /**
  * Split two texts into an array of strings.  Reduce the texts to a string of
  * hashes where each Unicode character represents one line.
- * @param {String} text1 First string
- * @param {String} text2 Second string
- * @return {Array} Three element Array, containing the encoded text1,
- *     the encoded text2 and the array of unique strings.  The zeroth element
- *     of the array of unique strings is intentionally blank.
+ * @param {string} text1 First string
+ * @param {string} text2 Second string
+ * @return {Array.&lt;string|Array.&lt;string&gt;&gt;} Three element Array, containing the
+ *     encoded text1, the encoded text2 and the array of unique strings.  The
+ *     zeroth element of the array of unique strings is intentionally blank.
+ * @private
  */
 diff_match_patch.prototype.diff_linesToChars = function(text1, text2) {
-  var linearray = [];  // e.g. linearray[4] == 'Hello\n'
-  var linehash = {};   // e.g. linehash['Hello\n'] == 4
+  var lineArray = [];  // e.g. lineArray[4] == 'Hello\n'
+  var lineHash = {};   // e.g. lineHash['Hello\n'] == 4
 
   // '\x00' is a valid character, but various debuggers don't like it.
   // So we'll insert a junk entry to avoid generating a null character.
-  linearray.push('');
+  lineArray[0] = '';
 
   /**
    * Split a text into an array of strings.  Reduce the texts to a string of
    * hashes where each Unicode character represents one line.
    * Modifies linearray and linehash through being a closure.
-   * @param {String} text String to encode
-   * @return {String} Encoded string
+   * @param {string} text String to encode
+   * @return {string} Encoded string
+   * @private
    */
   function diff_linesToCharsMunge(text) {
     var chars = '';
-    // text.split('\n') would work fine, but would temporarily double our
-    // memory footprint for minimal speed improvement.
-    while (text) {
-      var i = text.indexOf('\n');
-      if (i == -1) {
-        i = text.length;
+    // Walk the text, pulling out a substring for each line.
+    // text.split('\n') would would temporarily double our memory footprint.
+    // Modifying text would create many large strings to garbage collect.
+    var lineStart = 0;
+    var lineEnd = -1;
+    // Keeping our own length variable is faster than looking it up.
+    var lineArrayLength = lineArray.length;
+    while (lineEnd &lt; text.length - 1) {
+      lineEnd = text.indexOf('\n', lineStart);
+      if (lineEnd == -1) {
+        lineEnd = text.length - 1;
       }
-      var line = text.substring(0, i + 1);
-      text = text.substring(i + 1);
-      if (linehash.hasOwnProperty ? linehash.hasOwnProperty(line) :
-          (linehash[line] !== undefined)) {
-        chars += String.fromCharCode(linehash[line]);
+      var line = text.substring(lineStart, lineEnd + 1);
+      lineStart = lineEnd + 1;
+
+      if (lineHash.hasOwnProperty ? lineHash.hasOwnProperty(line) :
+          (lineHash[line] !== undefined)) {
+        chars += String.fromCharCode(lineHash[line]);
       } else {
-        linearray.push(line);
-        linehash[line] = linearray.length - 1;
-        chars += String.fromCharCode(linearray.length - 1);
+        chars += String.fromCharCode(lineArrayLength);
+        lineHash[line] = lineArrayLength;
+        lineArray[lineArrayLength++] = line;
       }
     }
     return chars;
@@ -300,22 +313,23 @@ diff_match_patch.prototype.diff_linesToChars = function(text1, text2) {
 
   var chars1 = diff_linesToCharsMunge(text1);
   var chars2 = diff_linesToCharsMunge(text2);
-  return [chars1, chars2, linearray];
+  return [chars1, chars2, lineArray];
 };
 
 
 /**
  * Rehydrate the text in a diff from a string of line hashes to real lines of
  * text.
- * @param {Array} diffs Array of diff tuples
- * @param {Array} linearray Array of unique strings
+ * @param {Array.&lt;Array.&lt;*&gt;&gt;} diffs Array of diff tuples
+ * @param {Array.&lt;string&gt;} lineArray Array of unique strings
+ * @private
  */
-diff_match_patch.prototype.diff_charsToLines = function(diffs, linearray) {
+diff_match_patch.prototype.diff_charsToLines = function(diffs, lineArray) {
   for (var x = 0; x &lt; diffs.length; x++) {
     var chars = diffs[x][1];
     var text = [];
     for (var y = 0; y &lt; chars.length; y++) {
-      text.push(linearray[chars.charCodeAt(y)]);
+      text[y] = lineArray[chars.charCodeAt(y)];
     }
     diffs[x][1] = text.join('');
   }
@@ -324,9 +338,11 @@ diff_match_patch.prototype.diff_charsToLines = function(diffs, linearray) {
 
 /**
  * Explore the intersection points between the two texts.
- * @param {String} text1 Old string to be diffed
- * @param {String} text2 New string to be diffed
- * @return {Array | Null} Array of diff tuples or null if no diff available
+ * @param {string} text1 Old string to be diffed
+ * @param {string} text2 New string to be diffed
+ * @return {Array.&lt;Array.&lt;*&gt;&gt;?} Array of diff tuples or null if no diff
+ *     available
+ * @private
  */
 diff_match_patch.prototype.diff_map = function(text1, text2) {
   // Don't run for too long.
@@ -456,15 +472,17 @@ diff_match_patch.prototype.diff_map = function(text1, text2) {
 
 /**
  * Work from the middle back to the start to determine the path.
- * @param {Array} v_map Array of paths.
- * @param {String} text1 Old string fragment to be diffed
- * @param {String} text2 New string fragment to be diffed
- * @return {Array} Array of diff tuples
+ * @param {Array.&lt;Object&gt;} v_map Array of paths.
+ * @param {string} text1 Old string fragment to be diffed
+ * @param {string} text2 New string fragment to be diffed
+ * @return {Array.&lt;Array.&lt;*&gt;&gt;} Array of diff tuples
+ * @private
  */
 diff_match_patch.prototype.diff_path1 = function(v_map, text1, text2) {
   var path = [];
   var x = text1.length;
   var y = text2.length;
+  /** @type {number?} */
   var last_op = null;
   for (var d = v_map.length - 2; d &gt;= 0; d--) {
     while (1) {
@@ -493,7 +511,7 @@ diff_match_patch.prototype.diff_path1 = function(v_map, text1, text2) {
         x--;
         y--;
         //if (text1.charAt(x) != text2.charAt(y)) {
-        //  return alert('No diagonal.  Can\'t happen. (diff_path1)');
+        //  throw new Error('No diagonal.  Can\'t happen. (diff_path1)');
         //}
         if (last_op === DIFF_EQUAL) {
           path[0][1] = text1.charAt(x) + path[0][1];
@@ -510,15 +528,18 @@ diff_match_patch.prototype.diff_path1 = function(v_map, text1, text2) {
 
 /**
  * Work from the middle back to the end to determine the path.
- * @param {Array} v_map Array of paths.
- * @param {String} text1 Old string fragment to be diffed
- * @param {String} text2 New string fragment to be diffed
- * @return {Array} Array of diff tuples
+ * @param {Array.&lt;Object&gt;} v_map Array of paths.
+ * @param {string} text1 Old string fragment to be diffed
+ * @param {string} text2 New string fragment to be diffed
+ * @return {Array.&lt;Array.&lt;*&gt;&gt;} Array of diff tuples
+ * @private
  */
 diff_match_patch.prototype.diff_path2 = function(v_map, text1, text2) {
   var path = [];
+  var pathLength = 0;
   var x = text1.length;
   var y = text2.length;
+  /** @type {number?} */
   var last_op = null;
   for (var d = v_map.length - 2; d &gt;= 0; d--) {
     while (1) {
@@ -526,9 +547,10 @@ diff_match_patch.prototype.diff_path2 = function(v_map, text1, text2) {
           (v_map[d][(x - 1) + ',' + y] !== undefined)) {
         x--;
         if (last_op === DIFF_DELETE) {
-          path[path.length - 1][1] += text1.charAt(text1.length - x - 1);
+          path[pathLength - 1][1] += text1.charAt(text1.length - x - 1);
         } else {
-          path.push([DIFF_DELETE, text1.charAt(text1.length - x - 1)]);
+          path[pathLength++] =
+              [DIFF_DELETE, text1.charAt(text1.length - x - 1)];
         }
         last_op = DIFF_DELETE;
         break;
@@ -537,9 +559,10 @@ diff_match_patch.prototype.diff_path2 = function(v_map, text1, text2) {
                  (v_map[d][x + ',' + (y - 1)] !== undefined)) {
         y--;
         if (last_op === DIFF_INSERT) {
-          path[path.length - 1][1] += text2.charAt(text2.length - y - 1);
+          path[pathLength - 1][1] += text2.charAt(text2.length - y - 1);
         } else {
-          path.push([DIFF_INSERT, text2.charAt(text2.length - y - 1)]);
+          path[pathLength++] =
+              [DIFF_INSERT, text2.charAt(text2.length - y - 1)];
         }
         last_op = DIFF_INSERT;
         break;
@@ -548,12 +571,13 @@ diff_match_patch.prototype.diff_path2 = function(v_map, text1, text2) {
         y--;
         //if (text1.charAt(text1.length - x - 1) !=
         //    text2.charAt(text2.length - y - 1)) {
-        //  return alert('No diagonal.  Can\'t happen. (diff_path2)');
+        //  throw new Error('No diagonal.  Can\'t happen. (diff_path2)');
         //}
         if (last_op === DIFF_EQUAL) {
-          path[path.length - 1][1] += text1.charAt(text1.length - x - 1);
+          path[pathLength - 1][1] += text1.charAt(text1.length - x - 1);
         } else {
-          path.push([DIFF_EQUAL, text1.charAt(text1.length - x - 1)]);
+          path[pathLength++] =
+              [DIFF_EQUAL, text1.charAt(text1.length - x - 1)];
         }
         last_op = DIFF_EQUAL;
       }
@@ -564,10 +588,10 @@ diff_match_patch.prototype.diff_path2 = function(v_map, text1, text2) {
 
 
 /**
- * Trim off common prefix
- * @param {String} text1 First string
- * @param {String} text2 Second string
- * @return {Number} The number of characters common to the start of each
+ * Determine the common prefix of two strings
+ * @param {string} text1 First string
+ * @param {string} text2 Second string
+ * @return {number} The number of characters common to the start of each
  *     string.
  */
 diff_match_patch.prototype.diff_commonPrefix = function(text1, text2) {
@@ -576,6 +600,7 @@ diff_match_patch.prototype.diff_commonPrefix = function(text1, text2) {
     return 0;
   }
   // Binary search.
+  // Performance analysis: http://neil.fraser.name/news/2007/10/09/
   var pointermin = 0;
   var pointermax = Math.min(text1.length, text2.length);
   var pointermid = pointermax;
@@ -595,10 +620,10 @@ diff_match_patch.prototype.diff_commonPrefix = function(text1, text2) {
 
 
 /**
- * Trim off common suffix
- * @param {String} text1 First string
- * @param {String} text2 Second string
- * @return {Number} The number of characters common to the end of each string.
+ * Determine the common suffix of two strings
+ * @param {string} text1 First string
+ * @param {string} text2 Second string
+ * @return {number} The number of characters common to the end of each string.
  */
 diff_match_patch.prototype.diff_commonSuffix = function(text1, text2) {
   // Quick check for common null cases.
@@ -607,6 +632,7 @@ diff_match_patch.prototype.diff_commonSuffix = function(text1, text2) {
     return 0;
   }
   // Binary search.
+  // Performance analysis: http://neil.fraser.name/news/2007/10/09/
   var pointermin = 0;
   var pointermax = Math.min(text1.length, text2.length);
   var pointermid = pointermax;
@@ -628,11 +654,11 @@ diff_match_patch.prototype.diff_commonSuffix = function(text1, text2) {
 /**
  * Do the two texts share a substring which is at least half the length of the
  * longer text?
- * @param {String} text1 First string
- * @param {String} text2 Second string
- * @return {Array} Five element Array, containing the prefix of text1, the
- *     suffix of text1, the prefix of text2, the suffix of text2 and the
- *     common middle.  Or null if there was no match.
+ * @param {string} text1 First string
+ * @param {string} text2 Second string
+ * @return {Array.&lt;string&gt;?} Five element Array, containing the prefix of
+ *     text1, the suffix of text1, the prefix of text2, the suffix of
+ *     text2 and the common middle.  Or null if there was no match.
  */
 diff_match_patch.prototype.diff_halfMatch = function(text1, text2) {
   var longtext = text1.length &gt; text2.length ? text1 : text2;
@@ -646,12 +672,13 @@ diff_match_patch.prototype.diff_halfMatch = function(text1, text2) {
    * Does a substring of shorttext exist within longtext such that the substring
    * is at least half the length of longtext?
    * Closure, but does not reference any external variables.
-   * @param {String} longtext Longer string
-   * @param {String} shorttext Shorter string
-   * @param {Number} i Start index of quarter length substring within longtext
-   * @return {Array} Five element Array, containing the prefix of longtext, the
-   *     suffix of longtext, the prefix of shorttext, the suffix of shorttext
-   *     and the common middle.  Or null if there was no match.
+   * @param {string} longtext Longer string
+   * @param {string} shorttext Shorter string
+   * @param {number} i Start index of quarter length substring within longtext
+   * @return {Array.&lt;string&gt;?} Five element Array, containing the prefix of
+   *     longtext, the suffix of longtext, the prefix of shorttext, the suffix
+   *     of shorttext and the common middle.  Or null if there was no match.
+   * @private
    */
   function diff_halfMatchI(longtext, shorttext, i) {
     // Start with a 1/4 length substring at position i as a seed.
@@ -683,10 +710,10 @@ diff_match_patch.prototype.diff_halfMatch = function(text1, text2) {
 
   // First check if the second quarter is the seed for a half-match.
   var hm1 = diff_halfMatchI(longtext, shorttext,
-                             Math.ceil(longtext.length / 4));
+                            Math.ceil(longtext.length / 4));
   // Check again based on the third quarter.
   var hm2 = diff_halfMatchI(longtext, shorttext,
-                             Math.ceil(longtext.length / 2));
+                            Math.ceil(longtext.length / 2));
   var hm;
   if (!hm1 &amp;&amp; !hm2) {
     return null;
@@ -719,12 +746,13 @@ diff_match_patch.prototype.diff_halfMatch = function(text1, text2) {
 
 /**
  * Reduce the number of edits by eliminating semantically trivial equalities.
- * @param {Array} diffs Array of diff tuples
+ * @param {Array.&lt;Array.&lt;*&gt;&gt;} diffs Array of diff tuples
  */
 diff_match_patch.prototype.diff_cleanupSemantic = function(diffs) {
   var changes = false;
   var equalities = [];  // Stack of indices where equalities are found.
-  var lastequality = null;  // Always equal to equalities[equalities.length-1][1]
+  var equalitiesLength = 0;  // Keeping our own length var is faster in JS.
+  var lastequality = null;  // Always equal to equalities[equalitiesLength-1][1]
   var pointer = 0;  // Index of current position.
   // Number of characters that changed prior to the equality.
   var length_changes1 = 0;
@@ -732,7 +760,7 @@ diff_match_patch.prototype.diff_cleanupSemantic = function(diffs) {
   var length_changes2 = 0;
   while (pointer &lt; diffs.length) {
     if (diffs[pointer][0] == DIFF_EQUAL) {  // equality found
-      equalities.push(pointer);
+      equalities[equalitiesLength++] = pointer;
       length_changes1 = length_changes2;
       length_changes2 = 0;
       lastequality = diffs[pointer][1];
@@ -740,17 +768,16 @@ diff_match_patch.prototype.diff_cleanupSemantic = function(diffs) {
       length_changes2 += diffs[pointer][1].length;
       if (lastequality !== null &amp;&amp; (lastequality.length &lt;= length_changes1) &amp;&amp;
           (lastequality.length &lt;= length_changes2)) {
-        //alert('Splitting: &quot;' + lastequality + '&quot;');
         // Duplicate record
-        diffs.splice(equalities[equalities.length - 1], 0,
+        diffs.splice(equalities[equalitiesLength - 1], 0,
                      [DIFF_DELETE, lastequality]);
         // Change second copy to insert.
-        diffs[equalities[equalities.length - 1] + 1][0] = DIFF_INSERT;
+        diffs[equalities[equalitiesLength - 1] + 1][0] = DIFF_INSERT;
         // Throw away the equality we just deleted.
-        equalities.pop();
+        equalitiesLength--;
         // Throw away the previous equality (it needs to be reevaluated).
-        equalities.pop();
-        pointer = equalities.length ? equalities[equalities.length - 1] : -1;
+        equalitiesLength--;
+        pointer = equalitiesLength &gt; 0 ? equalities[equalitiesLength - 1] : -1;
         length_changes1 = 0;  // Reset the counters.
         length_changes2 = 0;
         lastequality = null;
@@ -770,28 +797,55 @@ diff_match_patch.prototype.diff_cleanupSemantic = function(diffs) {
  * Look for single edits surrounded on both sides by equalities
  * which can be shifted sideways to align the edit to a word boundary.
  * e.g: The c&lt;ins&gt;at c&lt;/ins&gt;ame. -&gt; The &lt;ins&gt;cat &lt;/ins&gt;came.
- * @param {Array} diffs Array of diff tuples
+ * @param {Array.&lt;Array.&lt;*&gt;&gt;} diffs Array of diff tuples
  */
 diff_match_patch.prototype.diff_cleanupSemanticLossless = function(diffs) {
+  // Define some regex patterns for matching boundaries. 
+  var punctuation = /[^a-zA-Z0-9]/;
+  var whitespace = /\s/;
+  var linebreak = /[\r\n]/;
+  var blanklineEnd = /\n\r?\n$/;
+  var blanklineStart = /^\r?\n\r?\n/;
+
   /**
-   * Given three strings, compute a score representing whether the two internal
-   * boundaries fall on word boundaries.
-   * Closure, but does not reference any external variables.
-   * @param {String} one First string
-   * @param {String} two Second string
-   * @param {String} three Third string
-   * @return {Number} The score.
+   * Given two strings, compute a score representing whether the internal
+   * boundary falls on logical boundaries.
+   * Scores range from 5 (best) to 0 (worst).
+   * Closure, makes reference to regex patterns defined above.
+   * @param {string} one First string
+   * @param {string} two Second string
+   * @return {number} The score.
    */
-  function diff_cleanupSemanticScore(one, two, three) {
-    var whitespace = /\s/;
-    var score = 0;
-    if (one.charAt(one.length - 1).match(whitespace) ||
-        two.charAt(0).match(whitespace)) {
-      score++;
+  function diff_cleanupSemanticScore(one, two) {
+    if (!one || !two) {
+      // Edges are the best.
+      return 5;
     }
-    if (two.charAt(two.length - 1).match(whitespace) ||
-        three.charAt(0).match(whitespace)) {
+
+    // Each port of this function behaves slightly differently due to
+    // subtle differences in each language's definition of things like
+    // 'whitespace'.  Since this function's purpose is largely cosmetic,
+    // the choice has been made to use each language's native features
+    // rather than force total conformity.
+    var score = 0;
+    // One point for non-alphanumeric.
+    if (one.charAt(one.length - 1).match(punctuation) ||
+        two.charAt(0).match(punctuation)) {
       score++;
+      // Two points for whitespace.
+      if (one.charAt(one.length - 1).match(whitespace) ||
+          two.charAt(0).match(whitespace)) {
+        score++;
+        // Three points for line breaks.
+        if (one.charAt(one.length - 1).match(linebreak) ||
+            two.charAt(0).match(linebreak)) {
+          score++;
+          // Four points for blank lines.
+          if (one.match(blanklineEnd) || two.match(blanklineStart)) {
+            score++;
+          }
+        }
+      }
     }
     return score;
   }
@@ -819,12 +873,15 @@ diff_match_patch.prototype.diff_cleanupSemanticLossless = function(diffs) {
       var bestEquality1 = equality1;
       var bestEdit = edit;
       var bestEquality2 = equality2;
-      var bestScore = diff_cleanupSemanticScore(equality1, edit, equality2);
+      var bestScore = diff_cleanupSemanticScore(equality1, edit) +
+          diff_cleanupSemanticScore(edit, equality2);
       while (edit.charAt(0) === equality2.charAt(0)) {
         equality1 += edit.charAt(0);
         edit = edit.substring(1) + equality2.charAt(0);
         equality2 = equality2.substring(1);
-        var score = diff_cleanupSemanticScore(equality1, edit, equality2);
+        var score = diff_cleanupSemanticScore(equality1, edit) +
+            diff_cleanupSemanticScore(edit, equality2);
+        // The &gt;= encourages trailing rather than leading whitespace on edits.
         if (score &gt;= bestScore) {
           bestScore = score;
           bestEquality1 = equality1;
@@ -835,9 +892,19 @@ diff_match_patch.prototype.diff_cleanupSemanticLossless = function(diffs) {
 
       if (diffs[pointer - 1][1] != bestEquality1) {
         // We have an improvement, save it back to the diff.
-        diffs[pointer - 1][1] = bestEquality1;
+        if (bestEquality1) {
+          diffs[pointer - 1][1] = bestEquality1;
+        } else {
+          diffs.splice(pointer - 1, 1);
+          pointer--;
+        }
         diffs[pointer][1] = bestEdit;
-        diffs[pointer + 1][1] = bestEquality2;
+        if (bestEquality2) {
+          diffs[pointer + 1][1] = bestEquality2;
+        } else {
+          diffs.splice(pointer + 1, 1);
+          pointer--;
+        }
       }
     }
     pointer++;
@@ -847,12 +914,13 @@ diff_match_patch.prototype.diff_cleanupSemanticLossless = function(diffs) {
 
 /**
  * Reduce the number of edits by eliminating operationally trivial equalities.
- * @param {Array} diffs Array of diff tuples
+ * @param {Array.&lt;Array.&lt;*&gt;&gt;} diffs Array of diff tuples
  */
 diff_match_patch.prototype.diff_cleanupEfficiency = function(diffs) {
   var changes = false;
   var equalities = [];  // Stack of indices where equalities are found.
-  var lastequality = '';  // Always equal to equalities[equalities.length-1][1]
+  var equalitiesLength = 0;  // Keeping our own length var is faster in JS.
+  var lastequality = '';  // Always equal to equalities[equalitiesLength-1][1]
   var pointer = 0;  // Index of current position.
   // Is there an insertion operation before the last equality.
   var pre_ins = false;
@@ -867,13 +935,13 @@ diff_match_patch.prototype.diff_cleanupEfficiency = function(diffs) {
       if (diffs[pointer][1].length &lt; this.Diff_EditCost &amp;&amp;
           (post_ins || post_del)) {
         // Candidate found.
-        equalities.push(pointer);
+        equalities[equalitiesLength++] = pointer;
         pre_ins = post_ins;
         pre_del = post_del;
         lastequality = diffs[pointer][1];
       } else {
         // Not a candidate, and can never become one.
-        equalities = [];
+        equalitiesLength = 0;
         lastequality = '';
       }
       post_ins = post_del = false;
@@ -894,21 +962,20 @@ diff_match_patch.prototype.diff_cleanupEfficiency = function(diffs) {
       if (lastequality &amp;&amp; ((pre_ins &amp;&amp; pre_del &amp;&amp; post_ins &amp;&amp; post_del) ||
                            ((lastequality.length &lt; this.Diff_EditCost / 2) &amp;&amp;
                             (pre_ins + pre_del + post_ins + post_del) == 3))) {
-        //alert('Splitting: &quot;' + lastequality + '&quot;');
         // Duplicate record
-        diffs.splice(equalities[equalities.length - 1], 0,
+        diffs.splice(equalities[equalitiesLength - 1], 0,
                      [DIFF_DELETE, lastequality]);
         // Change second copy to insert.
-        diffs[equalities[equalities.length - 1] + 1][0] = DIFF_INSERT;
-        equalities.pop();  // Throw away the equality we just deleted;
+        diffs[equalities[equalitiesLength - 1] + 1][0] = DIFF_INSERT;
+        equalitiesLength--;  // Throw away the equality we just deleted;
         lastequality = '';
         if (pre_ins &amp;&amp; pre_del) {
           // No changes made which could affect previous entry, keep going.
           post_ins = post_del = true;
-          equalities = [];
+          equalitiesLength = 0;
         } else {
-          equalities.pop();  // Throw away the previous equality;
-          pointer = equalities.length ? equalities[equalities.length - 1] : -1;
+          equalitiesLength--;  // Throw away the previous equality;
+          pointer = equalitiesLength &gt; 0 ? equalities[equalitiesLength - 1] : -1;
           post_ins = post_del = false;
         }
         changes = true;
@@ -926,7 +993,7 @@ diff_match_patch.prototype.diff_cleanupEfficiency = function(diffs) {
 /**
  * Reorder and merge like edit sections.  Merge equalities.
  * Any edit section can move as long as it doesn't cross an equality.
- * @param {Array} diffs Array of diff tuples
+ * @param {Array.&lt;Array.&lt;*&gt;&gt;} diffs Array of diff tuples
  */
 diff_match_patch.prototype.diff_cleanupMerge = function(diffs) {
   diffs.push([DIFF_EQUAL, '']);  // Add a dummy entry at the end.
@@ -937,69 +1004,74 @@ diff_match_patch.prototype.diff_cleanupMerge = function(diffs) {
   var text_insert = '';
   var commonlength;
   while (pointer &lt; diffs.length) {
-    if (diffs[pointer][0] == DIFF_INSERT) {
-      count_insert++;
-      text_insert += diffs[pointer][1];
-      pointer++;
-    } else if (diffs[pointer][0] == DIFF_DELETE) {
-      count_delete++;
-      text_delete += diffs[pointer][1];
-      pointer++;
-    } else {  // Upon reaching an equality, check for prior redundancies.
-      if (count_delete !== 0 || count_insert !== 0) {
-        if (count_delete !== 0 &amp;&amp; count_insert !== 0) {
-          // Factor out any common prefixies.
-          commonlength = this.diff_commonPrefix(text_insert, text_delete);
-          if (commonlength !== 0) {
-            if ((pointer - count_delete - count_insert) &gt; 0 &amp;&amp;
-                diffs[pointer - count_delete - count_insert - 1][0] ==
-                DIFF_EQUAL) {
-              diffs[pointer - count_delete - count_insert - 1][1] +=
-                  text_insert.substring(0, commonlength);
-            } else {
-              diffs.splice(0, 0, [DIFF_EQUAL,
-                  text_insert.substring(0, commonlength)]);
-              pointer++;
+    switch (diffs[pointer][0]) {
+      case DIFF_INSERT:
+        count_insert++;
+        text_insert += diffs[pointer][1];
+        pointer++;
+        break;
+      case DIFF_DELETE:
+        count_delete++;
+        text_delete += diffs[pointer][1];
+        pointer++;
+        break;
+      case DIFF_EQUAL:
+        // Upon reaching an equality, check for prior redundancies.
+        if (count_delete !== 0 || count_insert !== 0) {
+          if (count_delete !== 0 &amp;&amp; count_insert !== 0) {
+            // Factor out any common prefixies.
+            commonlength = this.diff_commonPrefix(text_insert, text_delete);
+            if (commonlength !== 0) {
+              if ((pointer - count_delete - count_insert) &gt; 0 &amp;&amp;
+                  diffs[pointer - count_delete - count_insert - 1][0] ==
+                  DIFF_EQUAL) {
+                diffs[pointer - count_delete - count_insert - 1][1] +=
+                    text_insert.substring(0, commonlength);
+              } else {
+                diffs.splice(0, 0, [DIFF_EQUAL,
+                    text_insert.substring(0, commonlength)]);
+                pointer++;
+              }
+              text_insert = text_insert.substring(commonlength);
+              text_delete = text_delete.substring(commonlength);
+            }
+            // Factor out any common suffixies.
+            commonlength = this.diff_commonSuffix(text_insert, text_delete);
+            if (commonlength !== 0) {
+              diffs[pointer][1] = text_insert.substring(text_insert.length -
+                  commonlength) + diffs[pointer][1];
+              text_insert = text_insert.substring(0, text_insert.length -
+                  commonlength);
+              text_delete = text_delete.substring(0, text_delete.length -
+                  commonlength);
             }
-            text_insert = text_insert.substring(commonlength);
-            text_delete = text_delete.substring(commonlength);
           }
-          // Factor out any common suffixies.
-          commonlength = this.diff_commonSuffix(text_insert, text_delete);
-          if (commonlength !== 0) {
-            diffs[pointer][1] = text_insert.substring(text_insert.length
-                - commonlength) + diffs[pointer][1];
-            text_insert = text_insert.substring(0, text_insert.length
-                - commonlength);
-            text_delete = text_delete.substring(0, text_delete.length
-                - commonlength);
+          // Delete the offending records and add the merged ones.
+          if (count_delete === 0) {
+            diffs.splice(pointer - count_delete - count_insert,
+                         count_delete + count_insert, [DIFF_INSERT, text_insert]);
+          } else if (count_insert === 0) {
+            diffs.splice(pointer - count_delete - count_insert,
+                         count_delete + count_insert, [DIFF_DELETE, text_delete]);
+          } else {
+            diffs.splice(pointer - count_delete - count_insert,
+                         count_delete + count_insert, [DIFF_DELETE, text_delete],
+                         [DIFF_INSERT, text_insert]);
           }
-        }
-        // Delete the offending records and add the merged ones.
-        if (count_delete === 0) {
-          diffs.splice(pointer - count_delete - count_insert,
-                       count_delete + count_insert, [DIFF_INSERT, text_insert]);
-        } else if (count_insert === 0) {
-          diffs.splice(pointer - count_delete - count_insert,
-                       count_delete + count_insert, [DIFF_DELETE, text_delete]);
+          pointer = pointer - count_delete - count_insert +
+                    (count_delete ? 1 : 0) + (count_insert ? 1 : 0) + 1;
+        } else if (pointer !== 0 &amp;&amp; diffs[pointer - 1][0] == DIFF_EQUAL) {
+          // Merge this equality with the previous one.
+          diffs[pointer - 1][1] += diffs[pointer][1];
+          diffs.splice(pointer, 1);
         } else {
-          diffs.splice(pointer - count_delete - count_insert,
-                       count_delete + count_insert, [DIFF_DELETE, text_delete],
-                       [DIFF_INSERT, text_insert]);
+          pointer++;
         }
-        pointer = pointer - count_delete - count_insert +
-                  (count_delete ? 1 : 0) + (count_insert ? 1 : 0) + 1;
-      } else if (pointer !== 0 &amp;&amp; diffs[pointer - 1][0] == DIFF_EQUAL) {
-        // Merge this equality with the previous one.
-        diffs[pointer - 1][1] += diffs[pointer][1];
-        diffs.splice(pointer, 1);
-      } else {
-        pointer++;
-      }
-      count_insert = 0;
-      count_delete = 0;
-      text_delete = '';
-      text_insert = '';
+        count_insert = 0;
+        count_delete = 0;
+        text_delete = '';
+        text_insert = '';
+        break;
     }
   }
   if (diffs[diffs.length - 1][1] === '') {
@@ -1046,28 +1118,12 @@ diff_match_patch.prototype.diff_cleanupMerge = function(diffs) {
 
 
 /**
- * Add an index to each tuple, represents where the tuple is located in text2.
- * e.g. [[DIFF_DELETE, 'h', 0], [DIFF_INSERT, 'c', 0], [DIFF_EQUAL, 'at', 1]]
- * @param {Array} diffs Array of diff tuples
- */
-diff_match_patch.prototype.diff_addIndex = function(diffs) {
-  var i = 0;
-  for (var x = 0; x &lt; diffs.length; x++) {
-    diffs[x].push(i);
-    if (diffs[x][0] !== DIFF_DELETE) {
-      i += diffs[x][1].length;
-    }
-  }
-};
-
-
-/**
  * loc is a location in text1, compute and return the equivalent location in
  * text2.
  * e.g. 'The cat' vs 'The big cat', 1-&gt;1, 5-&gt;8
- * @param {Array} diffs Array of diff tuples
- * @param {Number} loc Location within text1
- * @return {Number} Location within text2
+ * @param {Array.&lt;Array.&lt;*&gt;&gt;} diffs Array of diff tuples
+ * @param {number} loc Location within text1
+ * @return {number} Location within text2
  */
 diff_match_patch.prototype.diff_xIndex = function(diffs, loc) {
   var chars1 = 0;
@@ -1099,26 +1155,32 @@ diff_match_patch.prototype.diff_xIndex = function(diffs, loc) {
 
 /**
  * Convert a diff array into a pretty HTML report.
- * @param {Array} diffs Array of diff tuples
- * @return {String} HTML representation
+ * @param {Array.&lt;Array.&lt;*&gt;&gt;} diffs Array of diff tuples
+ * @return {string} HTML representation
  */
 diff_match_patch.prototype.diff_prettyHtml = function(diffs) {
-  this.diff_addIndex(diffs);
   var html = [];
+  var i = 0;
   for (var x = 0; x &lt; diffs.length; x++) {
-    var m = diffs[x][0]; // Mode (delete, equal, insert)
-    var t = diffs[x][1]; // Text of change.
-    var i = diffs[x][2]; // Index of change.
-    t = t.replace(/&amp;/g, '&amp;amp;').replace(/&lt;/g, '&amp;lt;').replace(/&gt;/g, '&amp;gt;');
-    t = t.replace(/\n/g, '&amp;para;&lt;BR&gt;');
-    if (m === DIFF_DELETE) {
-      html.push('&lt;DEL STYLE=&quot;background:#FFE6E6;&quot; TITLE=&quot;i=', i, '&quot;&gt;',
-              t, '&lt;/DEL&gt;');
-    } else if (m === DIFF_INSERT) {
-      html.push('&lt;INS STYLE=&quot;background:#E6FFE6;&quot; TITLE=&quot;i=', i, '&quot;&gt;',
-              t, '&lt;/INS&gt;');
-    } else {
-      html.push('&lt;SPAN TITLE=&quot;i=', i, '&quot;&gt;', t, '&lt;/SPAN&gt;');
+    var op = diffs[x][0];   // Operation (insert, delete, equal)
+    var data = diffs[x][1]; // Text of change.
+    var text = data.replace(/&amp;/g, '&amp;amp;').replace(/&lt;/g, '&amp;lt;')
+        .replace(/&gt;/g, '&amp;gt;').replace(/\n/g, '&amp;para;&lt;BR&gt;');
+    switch (op) {
+      case DIFF_INSERT:
+        html[x] = '&lt;INS STYLE=&quot;background:#E6FFE6;&quot; TITLE=&quot;i=' + i + '&quot;&gt;' +
+                text + '&lt;/INS&gt;';
+        break;
+      case DIFF_DELETE:
+        html[x] = '&lt;DEL STYLE=&quot;background:#FFE6E6;&quot; TITLE=&quot;i=' + i + '&quot;&gt;' +
+                text + '&lt;/DEL&gt;';
+        break;
+      case DIFF_EQUAL:
+        html[x] = '&lt;SPAN TITLE=&quot;i=' + i + '&quot;&gt;' + text + '&lt;/SPAN&gt;';
+        break;
+    }
+    if (op !== DIFF_DELETE) {
+      i += data.length;
     }
   }
   return html.join('');
@@ -1127,14 +1189,14 @@ diff_match_patch.prototype.diff_prettyHtml = function(diffs) {
 
 /**
  * Compute and return the source text (all equalities and deletions).
- * @param {Array} diffs Array of diff tuples
- * @return {String} Source text
+ * @param {Array.&lt;Array.&lt;*&gt;&gt;} diffs Array of diff tuples
+ * @return {string} Source text
  */
 diff_match_patch.prototype.diff_text1 = function(diffs) {
   var txt = [];
   for (var x = 0; x &lt; diffs.length; x++) {
     if (diffs[x][0] !== DIFF_INSERT) {
-      txt.push(diffs[x][1]);
+      txt[x] = diffs[x][1];
     }
   }
   return txt.join('');
@@ -1143,14 +1205,14 @@ diff_match_patch.prototype.diff_text1 = function(diffs) {
 
 /**
  * Compute and return the destination text (all equalities and insertions).
- * @param {Array} diffs Array of diff tuples
- * @return {String} Destination text
+ * @param {Array.&lt;Array.&lt;*&gt;&gt;} diffs Array of diff tuples
+ * @return {string} Destination text
  */
 diff_match_patch.prototype.diff_text2 = function(diffs) {
   var txt = [];
   for (var x = 0; x &lt; diffs.length; x++) {
     if (diffs[x][0] !== DIFF_DELETE) {
-      txt.push(diffs[x][1]);
+      txt[x] = diffs[x][1];
     }
   }
   return txt.join('');
@@ -1160,74 +1222,85 @@ diff_match_patch.prototype.diff_text2 = function(diffs) {
 /**
  * Crush the diff into an encoded string which describes the operations
  * required to transform text1 into text2.
- * E.g. =3\t-2\t+ing\t  -&gt; Keep 3 chars, delete 2 chars, insert 'ing'.
+ * E.g. =3\t-2\t+ing  -&gt; Keep 3 chars, delete 2 chars, insert 'ing'.
  * Operations are tab-separated.  Inserted text is escaped using %xx notation.
- * @param {Array} diffs Array of diff tuples
- * @return {String} Delta text
+ * @param {Array.&lt;Array.&lt;*&gt;&gt;} diffs Array of diff tuples
+ * @return {string} Delta text
  */
 diff_match_patch.prototype.diff_toDelta = function(diffs) {
   var txt = [];
   for (var x = 0; x &lt; diffs.length; x++) {
-    switch(diffs[x][0]) {
+    switch (diffs[x][0]) {
+      case DIFF_INSERT:
+        txt[x] = '+' + encodeURI(diffs[x][1]);
+        break;
       case DIFF_DELETE:
-        txt.push('-', diffs[x][1].length, '\t');
+        txt[x] = '-' + diffs[x][1].length;
         break;
       case DIFF_EQUAL:
-        txt.push('=', diffs[x][1].length, '\t');
+        txt[x] = '=' + diffs[x][1].length;
         break;
-      case DIFF_INSERT:
-        txt.push('+', encodeURI(diffs[x][1]), '\t');
-        break;
-      default:
-        alert('Invalid diff operation in diff_toDelta()');
     }
   }
-  return txt.join('').replace(/%20/g, ' ');
+  // Opera doesn't know how to encode char 0.
+  return txt.join('\t').replace(/\0/g, '%00').replace(/%20/g, ' ');
 };
 
 
 /**
  * Given the original text1, and an encoded string which describes the
  * operations required to transform text1 into text2, compute the full diff.
- * @param {String} text1 Source string for the diff
- * @param {String} delta Delta text
- * @return {Array} Array of diff tuples
+ * @param {string} text1 Source string for the diff
+ * @param {string} delta Delta text
+ * @return {Array.&lt;Array.&lt;*&gt;&gt;} Array of diff tuples
+ * @throws {Error} If invalid input
  */
 diff_match_patch.prototype.diff_fromDelta = function(text1, delta) {
   var diffs = [];
+  var diffsLength = 0;  // Keeping our own length var is faster in JS.
   var pointer = 0;  // Cursor in text1
+  // Opera doesn't know how to decode char 0.
+  delta = delta.replace(/%00/g, '\0');
   var tokens = delta.split(/\t/g);
   for (var x = 0; x &lt; tokens.length; x++) {
+    // Each token begins with a one character parameter which specifies the
+    // operation of this token (delete, insert, equality).
     var param = tokens[x].substring(1);
-    switch(tokens[x].charAt(0)) {
+    switch (tokens[x].charAt(0)) {
+      case '+':
+        try {
+          diffs[diffsLength++] = [DIFF_INSERT, decodeURI(param)];
+        } catch (ex) {
+          // Malformed URI sequence.
+          throw new Error('Illegal escape in diff_fromDelta: ' + param);
+        }
+        break;
       case '-':
         // Fall through.
       case '=':
         var n = parseInt(param, 10);
         if (isNaN(n) || n &lt; 0) {
-          alert('Invalid number in diff_fromDelta()');
+          throw new Error('Invalid number in diff_fromDelta: ' + param);
+        }
+        var text = text1.substring(pointer, pointer += n);
+        if (tokens[x].charAt(0) == '=') {
+          diffs[diffsLength++] = [DIFF_EQUAL, text];
         } else {
-          var text = text1.substring(pointer, pointer += n);
-          if (tokens[x].charAt(0) == '=') {
-            diffs.push([DIFF_EQUAL, text]);
-          } else {
-            diffs.push([DIFF_DELETE, text]);
-          }
+          diffs[diffsLength++] = [DIFF_DELETE, text];
         }
         break;
-      case '+':
-        diffs.push([DIFF_INSERT, decodeURI(param)]);
-        break;
       default:
         // Blank tokens are ok (from a trailing \t).
         // Anything else is an error.
         if (tokens[x]) {
-          alert('Invalid diff operation in diff_fromDelta()');
+          throw new Error('Invalid diff operation in diff_fromDelta: ' +
+                          tokens[x]);
         }
     }
   }
   if (pointer != text1.length) {
-    alert('Text length mismatch in diff_fromDelta()');
+    throw new Error('Delta length (' + pointer +
+        ') does not equal source text length (' + text1.length + ').');
   }
   return diffs;
 };
@@ -1238,10 +1311,10 @@ diff_match_patch.prototype.diff_fromDelta = function(text1, delta) {
 
 /**
  * Locate the best instance of 'pattern' in 'text' near 'loc'.
- * @param {String} text The text to search
- * @param {String} pattern The pattern to search for
- * @param {Number} loc The location to search around
- * @return {Number | Null} Best match index or null
+ * @param {string} text The text to search
+ * @param {string} pattern The pattern to search for
+ * @param {number} loc The location to search around
+ * @return {number?} Best match index or null
  */
 diff_match_patch.prototype.match_main = function(text, pattern, loc) {
   loc = Math.max(0, Math.min(loc, text.length - pattern.length));
@@ -1264,14 +1337,15 @@ diff_match_patch.prototype.match_main = function(text, pattern, loc) {
 /**
  * Locate the best instance of 'pattern' in 'text' near 'loc' using the
  * Bitap algorithm.
- * @param {String} text The text to search
- * @param {String} pattern The pattern to search for
- * @param {Number} loc The location to search around
- * @return {Number | Null} Best match index or null
+ * @param {string} text The text to search
+ * @param {string} pattern The pattern to search for
+ * @param {number} loc The location to search around
+ * @return {number?} Best match index or null
+ * @private
  */
 diff_match_patch.prototype.match_bitap = function(text, pattern, loc) {
   if (pattern.length &gt; this.Match_MaxBits) {
-    return alert('Pattern too long for this browser.');
+    throw new Error('Pattern too long for this browser.');
   }
 
   // Initialise the alphabet.
@@ -1287,9 +1361,10 @@ diff_match_patch.prototype.match_bitap = function(text, pattern, loc) {
   /**
    * Compute and return the score for a match with e errors and x location.
    * Accesses loc, score_text_length and pattern through being a closure.
-   * @param {Number} e Number of errors in match
-   * @param {Number} x Location of match
-   * @return {Number} Overall score for match
+   * @param {number} e Number of errors in match
+   * @param {number} x Location of match
+   * @return {number} Overall score for match
+   * @private
    */
   function match_bitapScore(e, x) {
     var d = Math.abs(loc - x);
@@ -1383,11 +1458,12 @@ diff_match_patch.prototype.match_bitap = function(text, pattern, loc) {
 
 /**
  * Initialise the alphabet for the Bitap algorithm.
- * @param {String} pattern The text to encode
+ * @param {string} pattern The text to encode
  * @return {Object} Hash of character locations
+ * @private
  */
 diff_match_patch.prototype.match_alphabet = function(pattern) {
-  var s = Object();
+  var s = {};
   for (var i = 0; i &lt; pattern.length; i++) {
     s[pattern.charAt(i)] = 0;
   }
@@ -1404,8 +1480,9 @@ diff_match_patch.prototype.match_alphabet = function(pattern) {
 /**
  * Increase the context until it is unique,
  * but don't let the pattern expand beyond Match_MaxBits.
- * @param {Patch} patch The patch to grow
- * @param {String} text Source text
+ * @param {patch_obj} patch The patch to grow
+ * @param {string} text Source text
+ * @private
  */
 diff_match_patch.prototype.patch_addContext = function(patch, text) {
   var pattern = text.substring(patch.start2, patch.start2 + patch.length1);
@@ -1443,20 +1520,35 @@ diff_match_patch.prototype.patch_addContext = function(patch, text) {
 /**
  * Compute a list of patches to turn text1 into text2.
  * Use diffs if provided, otherwise compute it ourselves.
- * @param {String} text1 Old text
- * @param {String} text2 New text
- * @param {Array} opt_diffs Optional array of diff tuples for text1 to text2.
- * @return {Array} Array of patch objects.
+ * There are two ways to call this function:
+ * Method 1:
+ * a = Old text, b = New text, c = array of diff tuplle for a to b
+ * Method 2:
+ * a = Array of diff tuples for text 1 to text 2, b and c undefined
+ * @param {string|Array.&lt;Array.&lt;*&gt;&gt;} a Old text (method 1) or Array of diff
+ * tuples for text1 to text2 (method 2)
+ * @param {string?} b New text (method 1)
+ * @param {Array.&lt;Array.&lt;*&gt;&gt;} c Optional array of diff tuples for text1 to text2
+ * (method 1)
+ * @return {Array.&lt;patch_obj&gt;} Array of patch objects
  */
-diff_match_patch.prototype.patch_make = function(text1, text2, opt_diffs) {
-  var diffs;
-  if (typeof opt_diffs != 'undefined') {
-    diffs = opt_diffs;
+diff_match_patch.prototype.patch_make = function(a, b, c) {
+  var text1, text2, diffs;
+  if (typeof b == 'undefined') {
+    diffs = a;
+    text1 = this.diff_text1(diffs);
+    text2 = '';  // text2 is not actually used.
   } else {
-    diffs = this.diff_main(text1, text2, true);
-    if (diffs.length &gt; 2) {
-      this.diff_cleanupSemantic(diffs);
-      this.diff_cleanupEfficiency(diffs);
+    text1 = a;
+    text2 = b;
+    if (typeof c != 'undefined') {
+      diffs = c;
+    } else {
+      diffs = this.diff_main(text1, text2, true);
+      if (diffs.length &gt; 2) {
+        this.diff_cleanupSemantic(diffs);
+        this.diff_cleanupEfficiency(diffs);
+      }
     }
   }
 
@@ -1465,6 +1557,7 @@ diff_match_patch.prototype.patch_make = function(text1, text2, opt_diffs) {
   }
   var patches = [];
   var patch = new patch_obj();
+  var patchDiffLength = 0;  // Keeping our own length var is faster in JS.
   var char_count1 = 0;  // Number of characters into the text1 string.
   var char_count2 = 0;  // Number of characters into the text2 string.
   var prepatch_text = text1;  // Recreate the patches to determine context info.
@@ -1473,41 +1566,43 @@ diff_match_patch.prototype.patch_make = function(text1, text2, opt_diffs) {
     var diff_type = diffs[x][0];
     var diff_text = diffs[x][1];
 
-    if (patch.diffs.length === 0 &amp;&amp; diff_type !== DIFF_EQUAL) {
+    if (!patchDiffLength &amp;&amp; diff_type !== DIFF_EQUAL) {
       // A new patch starts here.
       patch.start1 = char_count1;
       patch.start2 = char_count2;
     }
 
-    if (diff_type === DIFF_INSERT) {
-      // Insertion
-      patch.diffs.push(diffs[x]);
-      patch.length2 += diff_text.length;
-      postpatch_text = postpatch_text.substring(0, char_count2) + diff_text +
-                       postpatch_text.substring(char_count2);
-    } else if (diff_type === DIFF_DELETE) {
-      // Deletion.
-      patch.length1 += diff_text.length;
-      patch.diffs.push(diffs[x]);
-      postpatch_text = postpatch_text.substring(0, char_count2) +
-                       postpatch_text.substring(char_count2 + diff_text.length);
-    } else if (diff_type === DIFF_EQUAL &amp;&amp;
-               diff_text.length &lt;= 2 * this.Patch_Margin &amp;&amp;
-               patch.diffs.length !== 0 &amp;&amp; diffs.length != x + 1) {
-      // Small equality inside a patch.
-      patch.diffs.push(diffs[x]);
-      patch.length1 += diff_text.length;
-      patch.length2 += diff_text.length;
-    }
-
-    if (diff_type === DIFF_EQUAL &amp;&amp; diff_text.length &gt;= 2 * this.Patch_Margin) {
-      // Time for a new patch.
-      if (patch.diffs.length !== 0) {
-        this.patch_addContext(patch, prepatch_text);
-        patches.push(patch);
-        patch = new patch_obj();
-        prepatch_text = postpatch_text;
-      }
+    switch (diff_type) {
+      case DIFF_INSERT:
+        patch.diffs[patchDiffLength++] = diffs[x];
+        patch.length2 += diff_text.length;
+        postpatch_text = postpatch_text.substring(0, char_count2) + diff_text +
+                         postpatch_text.substring(char_count2);
+        break;
+      case DIFF_DELETE:
+        patch.length1 += diff_text.length;
+        patch.diffs[patchDiffLength++] = diffs[x];
+        postpatch_text = postpatch_text.substring(0, char_count2) +
+                         postpatch_text.substring(char_count2 + diff_text.length);
+        break;
+      case DIFF_EQUAL:
+        if (diff_text.length &lt;= 2 * this.Patch_Margin &amp;&amp;
+            patchDiffLength &amp;&amp; diffs.length != x + 1) {
+          // Small equality inside a patch.
+          patch.diffs[patchDiffLength++] = diffs[x];
+          patch.length1 += diff_text.length;
+          patch.length2 += diff_text.length;
+        } else if (diff_text.length &gt;= 2 * this.Patch_Margin) {
+          // Time for a new patch.
+          if (patchDiffLength) {
+            this.patch_addContext(patch, prepatch_text);
+            patches.push(patch);
+            patch = new patch_obj();
+            patchDiffLength = 0;
+            prepatch_text = postpatch_text;
+          }
+        }
+        break;
     }
 
     // Update the current character count.
@@ -1519,7 +1614,7 @@ diff_match_patch.prototype.patch_make = function(text1, text2, opt_diffs) {
     }
   }
   // Pick up the leftover patch if not empty.
-  if (patch.diffs.length !== 0) {
+  if (patchDiffLength) {
     this.patch_addContext(patch, prepatch_text);
     patches.push(patch);
   }
@@ -1531,25 +1626,50 @@ diff_match_patch.prototype.patch_make = function(text1, text2, opt_diffs) {
 /**
  * Merge a set of patches onto the text.  Return a patched text, as well
  * as a list of true/false values indicating which patches were applied.
- * @param {Array} patches Array of patch objects
- * @param {String} text Old text
- * @return {Array} Two element Array, containing the new text and an array of
- *      boolean values
+ * @param {Array.&lt;patch_obj&gt;} patches Array of patch objects
+ * @param {string} text Old text
+ * @return {Array.&lt;string|Array.&lt;boolean&gt;&gt;} Two element Array, containing the
+ *      new text and an array of boolean values
  */
 diff_match_patch.prototype.patch_apply = function(patches, text) {
+  if (patches.length == 0) {
+    return [text, []];
+  }
+
+  // Deep copy the patches so that no changes are made to originals.
+  var patchesCopy = [];
+  for (var x = 0; x &lt; patches.length; x++) {
+    var patch = patches[x];
+    var patchCopy = new patch_obj();
+    patchCopy.diffs = patch.diffs.slice();
+    patchCopy.start1 = patch.start1;
+    patchCopy.start2 = patch.start2;
+    patchCopy.length1 = patch.length1;
+    patchCopy.length2 = patch.length2;
+    patchesCopy[x] = patchCopy;
+  }
+  patches = patchesCopy;
+
+  var nullPadding = this.patch_addPadding(patches);
+  text = nullPadding + text + nullPadding;
+
   this.patch_splitMax(patches);
-  var results = [];
+  // delta keeps track of the offset between the expected and actual location
+  // of the previous patch.  If there are patches expected at positions 10 and
+  // 20, but the first patch was found at 12, delta is 2 and the second patch
+  // has an effective expected position of 22.
   var delta = 0;
+  var results = [];
   for (var x = 0; x &lt; patches.length; x++) {
     var expected_loc = patches[x].start2 + delta;
     var text1 = this.diff_text1(patches[x].diffs);
     var start_loc = this.match_main(text, text1, expected_loc);
     if (start_loc === null) {
       // No match found.  :(
-      results.push(false);
+      results[x] = false;
     } else {
       // Found a match.  :)
-      results.push(true);
+      results[x] = true;
       delta = start_loc - expected_loc;
       var text2 = text.substring(start_loc, start_loc + text1.length);
       if (text1 == text2) {
@@ -1584,14 +1704,74 @@ diff_match_patch.prototype.patch_apply = function(patches, text) {
       }
     }
   }
+  // Strip the padding off.
+  text = text.substring(nullPadding.length, text.length - nullPadding.length);
   return [text, results];
 };
 
 
 /**
+ * Add some padding on text start and end so that edges can match something.
+ * @param {Array.&lt;patch_obj&gt;} patches Array of patch objects
+ * @return {string} The padding string added to each side.
+ * @private
+ */
+diff_match_patch.prototype.patch_addPadding = function(patches) {
+  var nullPadding = '';
+  for (var x = 0; x &lt; this.Patch_Margin; x++) {
+    nullPadding += String.fromCharCode(x);
+  }
+
+  // Bump all the patches forward.
+  for (var x = 0; x &lt; patches.length; x++) {
+    patches[x].start1 += nullPadding.length;
+    patches[x].start2 += nullPadding.length;
+  }
+
+  // Add some padding on start of first diff.
+  var patch = patches[0];
+  var diffs = patch.diffs;
+  if (diffs.length == 0 || diffs[0][0] != DIFF_EQUAL) {
+    // Add nullPadding equality.
+    diffs.unshift([DIFF_EQUAL, nullPadding]);
+    patch.start1 -= nullPadding.length;  // Should be 0.
+    patch.start2 -= nullPadding.length;  // Should be 0.
+    patch.length1 += nullPadding.length;
+    patch.length2 += nullPadding.length;
+  } else if (nullPadding.length &gt; diffs[0][1].length) {
+    // Grow first equality.
+    var extraLength = nullPadding.length - diffs[0][1].length;
+    diffs[0][1] = nullPadding.substring(diffs[0][1].length) + diffs[0][1];
+    patch.start1 -= extraLength;
+    patch.start2 -= extraLength;
+    patch.length1 += extraLength;
+    patch.length2 += extraLength;
+  }
+
+  // Add some padding on end of last diff.
+  patch = patches[patches.length - 1];
+  diffs = patch.diffs;
+  if (diffs.length == 0 || diffs[diffs.length - 1][0] != DIFF_EQUAL) {
+    // Add nullPadding equality.
+    diffs.push([DIFF_EQUAL, nullPadding]);
+    patch.length1 += nullPadding.length;
+    patch.length2 += nullPadding.length;
+  } else if (nullPadding.length &gt; diffs[diffs.length - 1][1].length) {
+    // Grow last equality.
+    var extraLength = nullPadding.length - diffs[diffs.length - 1][1].length;
+    diffs[diffs.length - 1][1] += nullPadding.substring(0, extraLength);
+    patch.length1 += extraLength;
+    patch.length2 += extraLength;
+  }
+
+  return nullPadding;
+};
+
+
+/**
  * Look through the patches and break up any which are longer than the maximum
  * limit of the match algorithm.
- * @param {Array} patches Array of patch objects.
+ * @param {Array.&lt;patch_obj&gt;} patches Array of patch objects
  */
 diff_match_patch.prototype.patch_splitMax = function(patches) {
   for (var x = 0; x &lt; patches.length; x++) {
@@ -1649,7 +1829,8 @@ diff_match_patch.prototype.patch_splitMax = function(patches) {
         precontext =
             precontext.substring(precontext.length - this.Patch_Margin);
         // Append the end context for this patch.
-        var postcontext = this.diff_text1(bigpatch.diffs).substring(0, this.Patch_Margin);
+        var postcontext = this.diff_text1(bigpatch.diffs)
+                              .substring(0, this.Patch_Margin);
         if (postcontext !== '') {
           patch.length1 += postcontext.length;
           patch.length2 += postcontext.length;
@@ -1671,13 +1852,13 @@ diff_match_patch.prototype.patch_splitMax = function(patches) {
 
 /**
  * Take a list of patches and return a textual representation.
- * @param {Array} patches Array of patch objects.
- * @return {String} Text representation of patches.
+ * @param {Array.&lt;patch_obj&gt;} patches Array of patch objects
+ * @return {string} Text representation of patches
  */
 diff_match_patch.prototype.patch_toText = function(patches) {
   var text = [];
   for (var x = 0; x &lt; patches.length; x++) {
-    text.push(patches[x]);
+    text[x] = patches[x];
   }
   return text.join('');
 };
@@ -1685,16 +1866,23 @@ diff_match_patch.prototype.patch_toText = function(patches) {
 
 /**
  * Parse a textual representation of patches and return a list of patch objects.
- * @param {String} textline Text representation of patches.
- * @return {Array} Array of patch objects.
+ * @param {string} textline Text representation of patches
+ * @return {Array.&lt;patch_obj&gt;} Array of patch objects
+ * @throws {Error} If invalid input
  */
 diff_match_patch.prototype.patch_fromText = function(textline) {
   var patches = [];
+  if (!textline) {
+    return patches;
+  }
+  // Opera doesn't know how to decode char 0.
+  textline = textline.replace(/%00/g, '\0');
   var text = textline.split('\n');
-  while (text.length !== 0) {
-    var m = text[0].match(/^@@ -(\d+),?(\d*) \+(\d+),?(\d*) @@$/);
+  var textPointer = 0;
+  while (textPointer &lt; text.length) {
+    var m = text[textPointer].match(/^@@ -(\d+),?(\d*) \+(\d+),?(\d*) @@$/);
     if (!m) {
-      return alert('Invalid patch string:\n' + text[0]);
+      throw new Error('Invalid patch string: ' + text[textPointer]);
     }
     var patch = new patch_obj();
     patches.push(patch);
@@ -1719,11 +1907,16 @@ diff_match_patch.prototype.patch_fromText = function(textline) {
       patch.start2--;
       patch.length2 = parseInt(m[4], 10);
     }
-    text.shift();
-
-    while (text.length !== 0) {
-      var sign = text[0].charAt(0);
-      var line = decodeURIComponent(text[0].substring(1));
+    textPointer++;
+
+    while (textPointer &lt; text.length) {
+      var sign = text[textPointer].charAt(0);
+      try {
+        var line = decodeURI(text[textPointer].substring(1));
+      } catch (ex) {
+        // Malformed URI sequence.
+        throw new Error('Illegal escape in patch_fromText: ' + line);
+      }
       if (sign == '-') {
         // Deletion.
         patch.diffs.push([DIFF_DELETE, line]);
@@ -1740,9 +1933,9 @@ diff_match_patch.prototype.patch_fromText = function(textline) {
         // Blank line?  Whatever.
       } else {
         // WTF?
-        return alert('Invalid patch mode: &quot;' + sign + '&quot;\n' + line);
+        throw new Error('Invalid patch mode &quot;' + sign + '&quot; in: ' + line);
       }
-      text.shift();
+      textPointer++;
     }
   }
   return patches;
@@ -1755,7 +1948,9 @@ diff_match_patch.prototype.patch_fromText = function(textline) {
  */
 function patch_obj() {
   this.diffs = [];
+  /** @type {number?} */
   this.start1 = null;
+  /** @type {number?} */
   this.start2 = null;
   this.length1 = 0;
   this.length2 = 0;
@@ -1766,7 +1961,7 @@ function patch_obj() {
  * Emmulate GNU diff's format.
  * Header: @@ -382,8 +481,9 @@
  * Indicies are printed as 1-based, not 0-based.
- * @return {String} The GNU diff string
+ * @return {string} The GNU diff string
  */
 patch_obj.prototype.toString = function() {
   var coords1, coords2;
@@ -1784,24 +1979,24 @@ patch_obj.prototype.toString = function() {
   } else {
     coords2 = (this.start2 + 1) + ',' + this.length2;
   }
-  var txt = ['@@ -', coords1, ' +', coords2, ' @@\n'];
+  var txt = ['@@ -' + coords1 + ' +' + coords2 + ' @@\n'];
+  var op;
   // Escape the body of the patch with %xx notation.
   for (var x = 0; x &lt; this.diffs.length; x++) {
-    switch(this.diffs[x][0]) {
+    switch (this.diffs[x][0]) {
+      case DIFF_INSERT:
+        op = '+';
+        break;
       case DIFF_DELETE:
-        txt.push('-');
+        op = '-';
         break;
       case DIFF_EQUAL:
-        txt.push(' ');
+        op = ' ';
         break;
-      case DIFF_INSERT:
-        txt.push('+');
-        break;
-      default:
-        alert('Invalid diff operation in patch_obj.toString()');
     }
-    txt.push(encodeURI(this.diffs[x][1]), '\n');
+    txt[x + 1] = op + encodeURI(this.diffs[x][1]) + '\n';
   }
-  return txt.join('').replace(/%20/g, ' ');
+  // Opera doesn't know how to encode char 0.
+  return txt.join('').replace(/\0/g, '%00').replace(/%20/g, ' ');
 };
 </diff>
      <filename>Specs/Assets/Scripts/DiffMatchPatch.js</filename>
    </modified>
    <modified>
      <diff>@@ -1,1512 +1,1512 @@
-/**
- * JSSpec
- *
- * Copyright 2007 Alan Kang
- *  - mailto:jania902@gmail.com
- *  - http://jania.pe.kr
- *
- * http://jania.pe.kr/aw/moin.cgi/JSSpec
- *
- * Dependencies:
- *  - diff_match_patch.js ( http://code.google.com/p/diff_match_patch )
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
- */
-
-/**
- * Namespace
- */
-
-var JSSpec = {
-	specs: [],
-
-	EMPTY_FUNCTION: function(){},
-
-	Browser: {
-		Trident: navigator.appName == &quot;Microsoft Internet Explorer&quot;,
-		Webkit: navigator.userAgent.indexOf('AppleWebKit/') &gt; -1,
-		Gecko: navigator.userAgent.indexOf('Gecko') &gt; -1 &amp;&amp; navigator.userAgent.indexOf('KHTML') == -1,
-		Presto: navigator.appName == &quot;Opera&quot;
-	}
-};
-
-/**
- * Executor
- */
-
-JSSpec.Executor = function(target, onSuccess, onException){
-	this.target = target;
-	this.onSuccess = typeof onSuccess == 'function' ? onSuccess : JSSpec.EMPTY_FUNCTION;
-	this.onException = typeof onException == 'function' ? onException : JSSpec.EMPTY_FUNCTION;
-
-	if(JSSpec.Browser.Trident){
-		// Exception handler for Trident. It helps to collect exact line number where exception occured.
-		window.onerror = function(message, fileName, lineNumber){
-			var self = window._curExecutor;
-			var ex = {message:message, fileName:fileName, lineNumber:lineNumber};
-
-			if(JSSpec._secondPass)  {
-				ex = self.mergeExceptions(JSSpec._assertionFailure, ex);
-				delete JSSpec._secondPass;
-				delete JSSpec._assertionFailure;
-
-				ex.type = &quot;failure&quot;;
-				self.onException(self, ex);
-			} else if(JSSpec._assertionFailure){
-				JSSpec._secondPass = true;
-				self.run();
-			} else {
-				self.onException(self, ex);
-			}
-
-			return true;
-		};
-	}
-};
-
-JSSpec.Executor.prototype.mergeExceptions = function(assertionFailure, normalException){
-	var merged = {
-		message:assertionFailure.message,
-		fileName:normalException.fileName,
-		lineNumber:normalException.lineNumber
-	};
-
-	return merged;
-};
-
-JSSpec.Executor.prototype.run = function(){
-	var self = this;
-	var target = this.target;
-	var onSuccess = this.onSuccess;
-	var onException = this.onException;
-
-	window.setTimeout(
-		function(){
-			var result;
-			if(JSSpec.Browser.Trident){
-				window._curExecutor = self;
-
-				result = self.target();
-				self.onSuccess(self, result);
-			} else {
-				try {
-					result = self.target();
-					self.onSuccess(self, result);
-				} catch(ex){
-					if(JSSpec.Browser.Webkit) ex = {message:ex.message, fileName:ex.sourceURL, lineNumber:ex.line};
-
-					if(JSSpec._secondPass)  {
-						ex = self.mergeExceptions(JSSpec._assertionFailure, ex);
-						delete JSSpec._secondPass;
-						delete JSSpec._assertionFailure;
-
-						ex.type = &quot;failure&quot;;
-						self.onException(self, ex);
-					} else if(JSSpec._assertionFailure){
-						JSSpec._secondPass = true;
-						self.run();
-					} else {
-						self.onException(self, ex);
-					}
-				}
-			}
-		},
-		0
-	);
-};
-
-
-
-/**
- * CompositeExecutor composites one or more executors and execute them sequencially.
- */
-JSSpec.CompositeExecutor = function(onSuccess, onException, continueOnException){
-	this.queue = [];
-	this.onSuccess = typeof onSuccess == 'function' ? onSuccess : JSSpec.EMPTY_FUNCTION;
-	this.onException = typeof onException == 'function' ? onException : JSSpec.EMPTY_FUNCTION;
-	this.continueOnException = !!continueOnException;
-};
-
-JSSpec.CompositeExecutor.prototype.addFunction = function(func){
-	this.addExecutor(new JSSpec.Executor(func));
-};
-
-JSSpec.CompositeExecutor.prototype.addExecutor = function(executor){
-	var last = this.queue.length == 0 ? null : this.queue[this.queue.length - 1];
-	if(last){
-		last.next = executor;
-	}
-
-	executor.parent = this;
-	executor.onSuccessBackup = executor.onSuccess;
-	executor.onSuccess = function(result){
-		this.onSuccessBackup(result);
-		if(this.next){
-			this.next.run();
-		} else {
-			this.parent.onSuccess();
-		}
-	};
-	executor.onExceptionBackup = executor.onException;
-	executor.onException = function(executor, ex){
-		this.onExceptionBackup(executor, ex);
-
-		if(this.parent.continueOnException){
-			if(this.next){
-				this.next.run();
-			} else {
-				this.parent.onSuccess();
-			}
-		} else {
-			this.parent.onException(executor, ex);
-		}
-	};
-
-	this.queue.push(executor);
-};
-
-JSSpec.CompositeExecutor.prototype.run = function(){
-	if(this.queue.length &gt; 0){
-		this.queue[0].run();
-	}
-};
-
-/**
- * Spec is a set of Examples in a specific context
- */
-
-JSSpec.Spec = function(context, entries){
-	this.id = JSSpec.Spec.id++;
-	this.context = context;
-	this.url = location.href;
-	this.filterEntriesByEmbeddedExpressions(entries);
-	this.extractOutSpecialEntries(entries);
-	this.examples = this.makeExamplesFromEntries(entries);
-	this.examplesMap = this.makeMapFromExamples(this.examples);
-};
-
-JSSpec.Spec.id = 0;
-JSSpec.Spec.prototype.getExamples = function(){
-	return this.examples;
-};
-
-JSSpec.Spec.prototype.hasException = function(){
-	return this.getTotalFailures() &gt; 0 || this.getTotalErrors() &gt; 0;
-};
-
-JSSpec.Spec.prototype.getTotalFailures = function(){
-	var examples = this.examples;
-	var failures = 0;
-	for(var i = 0; i &lt; examples.length; i++){
-		if(examples[i].isFailure()) failures++;
-	}
-	return failures;
-};
-
-JSSpec.Spec.prototype.getTotalErrors = function(){
-	var examples = this.examples;
-	var errors = 0;
-	for(var i = 0; i &lt; examples.length; i++){
-		if(examples[i].isError()) errors++;
-	}
-	return errors;
-};
-
-JSSpec.Spec.prototype.filterEntriesByEmbeddedExpressions = function(entries){
-	var isTrue;
-	for(name in entries){
-		var m = name.match(/\[\[(.+)\]\]/);
-		if(m &amp;&amp; m[1]){
-			eval(&quot;isTrue = (&quot; + m[1] + &quot;)&quot;);
-			if(!isTrue) delete entries[name];
-		}
-	}
-};
-
-JSSpec.Spec.prototype.extractOutSpecialEntries = function(entries){
-	this.beforeEach = JSSpec.EMPTY_FUNCTION;
-	this.beforeAll = JSSpec.EMPTY_FUNCTION;
-	this.afterEach = JSSpec.EMPTY_FUNCTION;
-	this.afterAll = JSSpec.EMPTY_FUNCTION;
-
-	for(name in entries){
-		if(name == 'before' || name == 'before each'){
-			this.beforeEach = entries[name];
-		} else if(name == 'before all' || name == 'before_all'){
-			this.beforeAll = entries[name];
-		} else if(name == 'after' || name == 'after each'){
-			this.afterEach = entries[name];
-		} else if(name == 'after all' || name == 'after_all'){
-			this.afterAll = entries[name];
-		}
-	}
-
-	delete entries['before'];
-	delete entries['before each'];
-	delete entries['before all'];
-	delete entries['before_all'];
-	delete entries['after'];
-	delete entries['after each'];
-	delete entries['after all'];
-	delete entries['after_all'];
-};
-
-JSSpec.Spec.prototype.makeExamplesFromEntries = function(entries){
-	var examples = [];
-	for(name in entries){
-		examples.push(new JSSpec.Example(name, entries[name], this.beforeEach, this.afterEach));
-	}
-	return examples;
-};
-
-JSSpec.Spec.prototype.makeMapFromExamples = function(examples){
-	var map = {};
-	for(var i = 0; i &lt; examples.length; i++){
-		var example = examples[i];
-		map[example.id] = examples[i];
-	}
-	return map;
-};
-
-JSSpec.Spec.prototype.getExampleById = function(id){
-	return this.examplesMap[id];
-};
-
-JSSpec.Spec.prototype.getExecutor = function(){
-	var self = this;
-	var onException = function(executor, ex){
-		self.exception = ex;
-	};
-
-	var composite = new JSSpec.CompositeExecutor();
-	composite.addFunction(function(){JSSpec.log.onSpecStart(self);});
-	composite.addExecutor(new JSSpec.Executor(this.beforeAll, null, function(exec, ex){
-		self.exception = ex;
-		JSSpec.log.onSpecEnd(self);
-	}));
-
-	var exampleAndAfter = new JSSpec.CompositeExecutor(null,null,true);
-	for(var i = 0; i &lt; this.examples.length; i++){
-		exampleAndAfter.addExecutor(this.examples[i].getExecutor());
-	}
-	exampleAndAfter.addExecutor(new JSSpec.Executor(this.afterAll, null, onException));
-	exampleAndAfter.addFunction(function(){JSSpec.log.onSpecEnd(self);});
-	composite.addExecutor(exampleAndAfter);
-
-	return composite;
-};
-
-/**
- * Example
- */
-JSSpec.Example = function(name, target, before, after){
-	this.id = JSSpec.Example.id++;
-	this.name = name;
-	this.target = target;
-	this.empty = (target + '' == function(){} + '');
-	this.before = before;
-	this.after = after;
-};
-
-JSSpec.Example.id = 0;
-JSSpec.Example.prototype.isFailure = function(){
-	return this.exception &amp;&amp; this.exception.type &amp;&amp; this.exception.type == &quot;failure&quot;;
-};
-
-JSSpec.Example.prototype.isError = function(){
-	return this.exception &amp;&amp; !this.exception.type;
-};
-
-JSSpec.Example.prototype.getExecutor = function(){
-	var self = this;
-	var onException = function(executor, ex){
-		self.exception = ex;
-	};
-
-	var composite = new JSSpec.CompositeExecutor();
-	composite.addFunction(function(){JSSpec.log.onExampleStart(self);});
-	composite.addExecutor(new JSSpec.Executor(this.before, null, function(exec, ex){
-		self.exception = ex;
-		JSSpec.log.onExampleEnd(self);
-	}));
-
-	var targetAndAfter = new JSSpec.CompositeExecutor(null,null,true);
-
-	targetAndAfter.addExecutor(new JSSpec.Executor(this.target, null, onException));
-	targetAndAfter.addExecutor(new JSSpec.Executor(this.after, null, onException));
-	targetAndAfter.addFunction(function(){JSSpec.log.onExampleEnd(self);});
-
-	composite.addExecutor(targetAndAfter);
-
-	return composite;
-};
-
-/**
- * Runner
- */
-
-JSSpec.Runner = function(specs, logger){
-	JSSpec.log = logger;
-
-	this.totalExamples = 0;
-	this.specs = [];
-	this.specsMap = {};
-	this.addAllSpecs(specs);
-};
-
-JSSpec.Runner.prototype.addAllSpecs = function(specs){
-	for(var i = 0; i &lt; specs.length; i++){
-		this.addSpec(specs[i]);
-	}
-};
-
-JSSpec.Runner.prototype.addSpec = function(spec){
-	this.specs.push(spec);
-	this.specsMap[spec.id] = spec;
-	this.totalExamples += spec.getExamples().length;
-};
-
-JSSpec.Runner.prototype.getSpecById = function(id){
-	return this.specsMap[id];
-};
-
-JSSpec.Runner.prototype.getSpecByContext = function(context){
-	for(var i = 0; i &lt; this.specs.length; i++){
-		if(this.specs[i].context == context) return this.specs[i];
-	}
-	return null;
-};
-
-JSSpec.Runner.prototype.getSpecs = function(){
-	return this.specs;
-};
-
-JSSpec.Runner.prototype.hasException = function(){
-	return this.getTotalFailures() &gt; 0 || this.getTotalErrors() &gt; 0;
-};
-
-JSSpec.Runner.prototype.getTotalFailures = function(){
-	var specs = this.specs;
-	var failures = 0;
-	for(var i = 0; i &lt; specs.length; i++){
-		failures += specs[i].getTotalFailures();
-	}
-	return failures;
-};
-
-JSSpec.Runner.prototype.getTotalErrors = function(){
-	var specs = this.specs;
-	var errors = 0;
-	for(var i = 0; i &lt; specs.length; i++){
-		errors += specs[i].getTotalErrors();
-	}
-	return errors;
-};
-
-JSSpec.Runner.prototype.run = function(){
-	JSSpec.log.onRunnerStart();
-	this.executor = new JSSpec.CompositeExecutor(function(){JSSpec.log.onRunnerEnd();},null,true);
-	for(var i = 0; i &lt; this.specs.length; i++){
-		this.executor.addExecutor(this.specs[i].getExecutor());
-	}
-};
-
-JSSpec.Runner.prototype.rerun = function(context){
-	JSSpec.runner = new JSSpec.Runner([this.getSpecByContext(context)], JSSpec.log);
-	JSSpec.runner.run();
-};
-
-/**
- * Logger
- */
-
-JSSpec.Logger = function(){
-	this.finishedExamples = 0;
-	this.startedAt = null;
-};
-
-JSSpec.Logger.prototype.onRunnerStart = function(){
-	this.startedAt = new Date();
-	var container = document.getElementById('container');
-	if(container){
-		container.innerHTML = &quot;&quot;;
-	} else {
-		container = document.createElement(&quot;DIV&quot;);
-		container.id = &quot;container&quot;;
-		document.body.appendChild(container);
-	}
-
-	var title = document.createElement(&quot;DIV&quot;);
-	title.id = &quot;title&quot;;
-	title.innerHTML = [
-		'&lt;h1&gt;JSSpec &lt;span&gt;Runner&lt;/span&gt;&lt;/h1&gt;',
-		'&lt;ul&gt;',
-		'	&lt;li&gt;&lt;span id=&quot;total_examples&quot;&gt;' + JSSpec.runner.totalExamples + '&lt;/span&gt; examples&lt;/li&gt;',
-		'	&lt;li&gt;&lt;span id=&quot;total_failures&quot;&gt;0&lt;/span&gt; failures&lt;/li&gt;',
-		'	&lt;li&gt;&lt;span id=&quot;total_errors&quot;&gt;0&lt;/span&gt; errors&lt;/li&gt;',
-		'	&lt;li&gt;&lt;span id=&quot;progress&quot;&gt;0&lt;/span&gt;% done&lt;/li&gt;',
-		'	&lt;li&gt;&lt;span id=&quot;total_elapsed&quot;&gt;0&lt;/span&gt; secs&lt;/li&gt;',
-		'&lt;/ul&gt;'
-	].join(&quot;&quot;);
-	container.appendChild(title);
-
-	var list = document.createElement(&quot;DIV&quot;);
-	list.id = &quot;list&quot;;
-	list.innerHTML = [
-		'&lt;div id=&quot;list-wrapper&quot;&gt;',
-		'&lt;h2 id=&quot;runner&quot;&gt;Run All Examples&lt;/h2&gt;',
-		'&lt;h2&gt;List&lt;/h2&gt;',
-		'&lt;ul class=&quot;specs&quot;&gt;',
-		function(){
-			var specs = JSSpec.runner.getSpecs();
-			var sb = [];
-			for(var i = 0; i &lt; specs.length; i++){
-				var spec = specs[i];
-				sb.push('&lt;li id=&quot;spec_' + specs[i].id + '_list&quot;&gt;&lt;h3&gt;&lt;a href=&quot;#spec_' + specs[i].id + '&quot;&gt;' + specs[i].context + '&lt;/a&gt;&lt;/h3&gt;&lt;/li&gt;');
-			}
-			return sb.join(&quot;&quot;);
-		}(),
-		'&lt;/ul&gt;',
-		'&lt;/div&gt;',
-		'&lt;span class=&quot;spc&quot;&gt;&lt;/span&gt;'
-	].join(&quot;&quot;);
-	container.appendChild(list);
-
-	var log = document.createElement(&quot;DIV&quot;);
-	log.id = &quot;log&quot;;
-	log.innerHTML = [
-		'&lt;div id=&quot;log-wrapper&quot;&gt;',
-		'&lt;h2&gt;Log&lt;/h2&gt;',
-		'&lt;ul class=&quot;specs&quot;&gt;',
-		function(){
-			var specs = JSSpec.runner.getSpecs();
-			var sb = [];
-			for(var i = 0; i &lt; specs.length; i++){
-				var spec = specs[i];
-				sb.push('	&lt;li id=&quot;spec_' + specs[i].id + '&quot;&gt;');
-				sb.push('		&lt;h3&gt;' + specs[i].context + '&lt;/h3&gt;');
-				sb.push('		&lt;ul id=&quot;spec_' + specs[i].id + '_examples&quot; class=&quot;examples&quot;&gt;');
-				for(var j = 0; j &lt; spec.examples.length; j++){
-					var example = spec.examples[j];
-					sb.push('			&lt;li id=&quot;example_' + example.id + '&quot;&gt;');
-					sb.push('				&lt;h4&gt;' + example.name.replace(/_/g, ' ') + '&lt;/h4&gt;');
-					sb.push('			&lt;/li&gt;');
-				}
-				sb.push('		&lt;/ul&gt;');
-				sb.push('	&lt;/li&gt;');
-			}
-			return sb.join(&quot;&quot;);
-		}(),
-		'&lt;/ul&gt;',
-		'&lt;/div&gt;',
-		'&lt;p id=&quot;footer&quot;&gt;powered by &lt;a href=&quot;http://jania.pe.kr/aw/moin.cgi/JSSpec&quot;&gt;JSSpec&lt;/a&gt;&lt;/p&gt;&lt;span class=&quot;spc&quot;&gt;&lt;/span&gt;'
-	].join(&quot;&quot;);
-	container.appendChild(log);
-};
-
-JSSpec.Logger.prototype.onRunnerEnd = function(){
-
-};
-
-JSSpec.Logger.prototype.onSpecStart = function(spec){
-	var spec_list = document.getElementById(&quot;spec_&quot; + spec.id + &quot;_list&quot;);
-	var spec_log = document.getElementById(&quot;spec_&quot; + spec.id);
-
-	spec_list.className = &quot;ongoing&quot;;
-	spec_log.className = &quot;ongoing&quot;;
-};
-
-JSSpec.Logger.prototype.onSpecEnd = function(spec){
-	var spec_list = document.getElementById(&quot;spec_&quot; + spec.id + &quot;_list&quot;);
-	var spec_log = document.getElementById(&quot;spec_&quot; + spec.id);
-	var examples = document.getElementById(&quot;spec_&quot; + spec.id + &quot;_examples&quot;);
-	var className = spec.hasException() ? &quot;exception&quot; : &quot;success&quot;;
-
-	spec_list.className = className;
-	spec_log.className = className;
-
-	if(JSSpec.options.autocollapse &amp;&amp; !spec.hasException()) examples.style.display = &quot;none&quot;;
-
-	if(spec.exception){
-		heading.appendChild(document.createTextNode(&quot; - &quot; + spec.exception.message));
-	}
-};
-
-JSSpec.Logger.prototype.onExampleStart = function(example){
-	var li = document.getElementById(&quot;example_&quot; + example.id);
-	li.className = &quot;ongoing&quot;;
-};
-
-JSSpec.Logger.prototype.onExampleEnd = function(example){
-	var li = document.getElementById(&quot;example_&quot; + example.id);
-	li.className = example.exception ? &quot;exception&quot; : example.empty ? 'stub' : &quot;success&quot;;
-
-	if(example.exception){
-		var div = document.createElement(&quot;DIV&quot;);
-		div.innerHTML = example.exception.message + &quot;&lt;p class='uri'&gt;&lt;br /&gt;&quot; + &quot; at &quot; + example.exception.fileName + &quot;, line &quot; + example.exception.lineNumber + &quot;&lt;/p&gt;&quot;;
-		li.appendChild(div);
-	}
-
-	var title = document.getElementById(&quot;title&quot;);
-	var runner = JSSpec.runner;
-
-	title.className = runner.hasException() ? &quot;exception&quot; : &quot;success&quot;;
-
-	this.finishedExamples++;
-	document.getElementById(&quot;total_failures&quot;).innerHTML = runner.getTotalFailures();
-	document.getElementById(&quot;total_errors&quot;).innerHTML = runner.getTotalErrors();
-	document.getElementById(&quot;progress&quot;).innerHTML = parseInt(this.finishedExamples / runner.totalExamples * 100);
-	document.getElementById(&quot;total_elapsed&quot;).innerHTML = (new Date().getTime() - this.startedAt.getTime()) / 1000;
-};
-
-/**
- * IncludeMatcher
- */
-
-JSSpec.IncludeMatcher = function(actual, expected, condition){
-	this.actual = actual;
-	this.expected = expected;
-	this.condition = condition;
-	this.match = false;
-	this.explaination = this.makeExplain();
-};
-
-JSSpec.IncludeMatcher.createInstance = function(actual, expected, condition){
-	return new JSSpec.IncludeMatcher(actual, expected, condition);
-};
-
-JSSpec.IncludeMatcher.prototype.matches = function(){
-	return this.match;
-};
-
-JSSpec.IncludeMatcher.prototype.explain = function(){
-	return this.explaination;
-};
-
-JSSpec.IncludeMatcher.prototype.makeExplain = function(){
-	if(typeof this.actual.length == 'undefined'){
-		return this.makeExplainForNotArray();
-	} else {
-		return this.makeExplainForArray();
-	}
-};
-
-JSSpec.IncludeMatcher.prototype.makeExplainForNotArray = function(){
-	var sb = [];
-	sb.push('&lt;p&gt;actual value:&lt;/p&gt;');
-	sb.push('&lt;p class=&quot;left&quot;&gt;' + JSSpec.util.inspect(this.actual) + '&lt;/p&gt;');
-	sb.push('&lt;p&gt;should ' + (this.condition ? '' : 'not') + ' include:&lt;/p&gt;');
-	sb.push('&lt;p class=&quot;left&quot;&gt;' + JSSpec.util.inspect(this.expected) + '&lt;/p&gt;');
-	sb.push('&lt;p&gt;but since it\s not an array, include or not doesn\'t make any sense.&lt;/p&gt;');
-	return sb.join(&quot;&quot;);
-};
-
-JSSpec.IncludeMatcher.prototype.makeExplainForArray = function(){
-	var matches;
-	if(this.condition){
-		for(var i = 0; i &lt; this.actual.length; i++){
-			matches = JSSpec.EqualityMatcher.createInstance(this.expected, this.actual[i]).matches();
-			if(matches){
-				this.match = true;
-				break;
-			}
-		}
-	} else {
-		for(var j = 0; j &lt; this.actual.length; j++){
-			matches = JSSpec.EqualityMatcher.createInstance(this.expected, this.actual[j]).matches();
-			if(matches){
-				this.match = false;
-				break;
-			}
-		}
-	}
-
-	if(this.match) return &quot;&quot;;
-
-	var sb = [];
-	sb.push('&lt;p&gt;actual value:&lt;/p&gt;');
-	sb.push('&lt;p class=&quot;left&quot;&gt;' + JSSpec.util.inspect(this.actual, false, this.condition ? null : i) + '&lt;/p&gt;');
-	sb.push('&lt;p&gt;should ' + (this.condition ? '' : 'not') + ' include:&lt;/p&gt;');
-	sb.push('&lt;p class=&quot;left&quot;&gt;' + JSSpec.util.inspect(this.expected) + '&lt;/p&gt;');
-	return sb.join(&quot;&quot;);
-};
-
-/**
- * PropertyLengthMatcher
- */
-
-JSSpec.PropertyLengthMatcher = function(num, property, o, condition){
-	this.num = num;
-	this.o = o;
-	this.property = property;
-	if((property == 'characters' || property == 'items') &amp;&amp; typeof o.length != 'undefined'){
-		this.property = 'length';
-	}
-
-	this.condition = condition;
-	this.conditionMet = function(x){
-		if(condition == 'exactly') return x.length == num;
-		if(condition == 'at least') return x.length &gt;= num;
-		if(condition == 'at most') return x.length &lt;= num;
-
-		throw &quot;Unknown condition '&quot; + condition + &quot;'&quot;;
-	};
-	this.match = false;
-	this.explaination = this.makeExplain();
-};
-
-JSSpec.PropertyLengthMatcher.prototype.makeExplain = function(){
-
-	if(this.o._type &amp;&amp; this.o._type == 'String' &amp;&amp; this.property == 'length'){
-		this.match = this.conditionMet(this.o);
-		return this.match ? '' : this.makeExplainForString();
-	} else if(typeof this.o.length != 'undefined' &amp;&amp; this.property == &quot;length&quot;){
-		this.match = this.conditionMet(this.o);
-		return this.match ? '' : this.makeExplainForArray();
-	} else if(typeof this.o[this.property] != 'undefined' &amp;&amp; this.o[this.property] != null){
-		this.match = this.conditionMet(this.o[this.property]);
-		return this.match ? '' : this.makeExplainForObject();
-	} else if(typeof this.o[this.property] == 'undefined' || this.o[this.property] == null){
-		this.match = false;
-		return this.makeExplainForNoProperty();
-	}
-
-	this.match = true;
-
-	return null;
-};
-
-JSSpec.PropertyLengthMatcher.prototype.makeExplainForString = function(){
-	var sb = [];
-
-	var exp = this.num == 0 ?
-		'be an &lt;strong&gt;empty string&lt;/strong&gt;' :
-		'have &lt;strong&gt;' + this.condition + ' ' + this.num + ' characters&lt;/strong&gt;';
-
-	sb.push('&lt;p&gt;actual value has &lt;strong&gt;' + this.o.length + ' characters&lt;/strong&gt;:&lt;/p&gt;');
-	sb.push('&lt;p class=&quot;left&quot;&gt;' + JSSpec.util.inspect(this.o) + '&lt;/p&gt;');
-	sb.push('&lt;p&gt;but it should ' + exp + '.&lt;/p&gt;');
-
-	return sb.join(&quot;&quot;);
-};
-
-JSSpec.PropertyLengthMatcher.prototype.makeExplainForArray = function(){
-	var sb = [];
-
-	var exp = this.num == 0 ?
-		'be an &lt;strong&gt;empty array&lt;/strong&gt;' :
-		'have &lt;strong&gt;' + this.condition + ' ' + this.num + ' items&lt;/strong&gt;';
-
-	sb.push('&lt;p&gt;actual value has &lt;strong&gt;' + this.o.length + ' items&lt;/strong&gt;:&lt;/p&gt;');
-	sb.push('&lt;p class=&quot;left&quot;&gt;' + JSSpec.util.inspect(this.o) + '&lt;/p&gt;');
-	sb.push('&lt;p&gt;but it should ' + exp + '.&lt;/p&gt;');
-
-	return sb.join(&quot;&quot;);
-};
-
-JSSpec.PropertyLengthMatcher.prototype.makeExplainForObject = function(){
-	var sb = [];
-
-	var exp = this.num == 0 ?
-		'be &lt;strong&gt;empty&lt;/strong&gt;' :
-		'have &lt;strong&gt;' + this.condition + ' ' + this.num + ' ' + this.property + '.&lt;/strong&gt;';
-
-	sb.push('&lt;p&gt;actual value has &lt;strong&gt;' + this.o[this.property].length + ' ' + this.property + '&lt;/strong&gt;:&lt;/p&gt;');
-	sb.push('&lt;p class=&quot;left&quot;&gt;' + JSSpec.util.inspect(this.o, false, this.property) + '&lt;/p&gt;');
-	sb.push('&lt;p&gt;but it should ' + exp + '.&lt;/p&gt;');
-
-	return sb.join(&quot;&quot;);
-};
-
-JSSpec.PropertyLengthMatcher.prototype.makeExplainForNoProperty = function(){
-	var sb = [];
-
-	sb.push('&lt;p&gt;actual value:&lt;/p&gt;');
-	sb.push('&lt;p class=&quot;left&quot;&gt;' + JSSpec.util.inspect(this.o) + '&lt;/p&gt;');
-	sb.push('&lt;p&gt;should have &lt;strong&gt;' + this.condition + ' ' + this.num + ' ' + this.property + '&lt;/strong&gt; but there\'s no such property.&lt;/p&gt;');
-
-	return sb.join(&quot;&quot;);
-};
-
-JSSpec.PropertyLengthMatcher.prototype.matches = function(){
-	return this.match;
-};
-
-JSSpec.PropertyLengthMatcher.prototype.explain = function(){
-	return this.explaination;
-};
-
-JSSpec.PropertyLengthMatcher.createInstance = function(num, property, o, condition){
-	return new JSSpec.PropertyLengthMatcher(num, property, o, condition);
-};
-
-
-
-
-/**
- * EqualityMatcher
- */
-
-JSSpec.EqualityMatcher = {};
-
-JSSpec.EqualityMatcher.createInstance = function(expected, actual){
-
-	if(expected == null || actual == null){
-		return new JSSpec.NullEqualityMatcher(expected, actual);
-	} else if(expected._type &amp;&amp; expected._type == actual._type){
-		if(expected._type == &quot;String&quot;){
-			return new JSSpec.StringEqualityMatcher(expected, actual);
-		} else if(expected._type == &quot;Date&quot;){
-			return new JSSpec.DateEqualityMatcher(expected, actual);
-		} else if(expected._type == &quot;Number&quot;){
-			return new JSSpec.NumberEqualityMatcher(expected, actual);
-		} else if(expected._type == &quot;Array&quot;){
-			return new JSSpec.ArrayEqualityMatcher(expected, actual);
-		} else if(expected._type == &quot;Boolean&quot;){
-			return new JSSpec.BooleanEqualityMatcher(expected, actual);
-		}
-	}
-
-	return new JSSpec.ObjectEqualityMatcher(expected, actual);
-};
-
-JSSpec.EqualityMatcher.basicExplain = function(expected, actual, expectedDesc, actualDesc){
-	var sb = [];
-
-	sb.push(actualDesc || '&lt;p&gt;actual value:&lt;/p&gt;');
-	sb.push('&lt;p class=&quot;left&quot;&gt;' + JSSpec.util.inspect(actual) + '&lt;/p&gt;');
-	sb.push(expectedDesc || '&lt;p&gt;should be:&lt;/p&gt;');
-	sb.push('&lt;p class=&quot;left&quot;&gt;' + JSSpec.util.inspect(expected) + '&lt;/p&gt;');
-
-	return sb.join(&quot;&quot;);
-};
-
-JSSpec.EqualityMatcher.diffExplain = function(expected, actual){
-	var sb = [];
-
-	sb.push('&lt;p&gt;diff:&lt;/p&gt;');
-	sb.push('&lt;p class=&quot;left&quot;&gt;');
-
-	var dmp = new diff_match_patch();
-	var diff = dmp.diff_main(expected, actual);
-	dmp.diff_cleanupEfficiency(diff);
-
-	sb.push(JSSpec.util.inspect(dmp.diff_prettyHtml(diff), true));
-
-	sb.push('&lt;/p&gt;');
-
-	return sb.join(&quot;&quot;);
-};
-
-/**
- * BooleanEqualityMatcher
- */
-
-JSSpec.BooleanEqualityMatcher = function(expected, actual){
-	this.expected = expected;
-	this.actual = actual;
-};
-
-JSSpec.BooleanEqualityMatcher.prototype.explain = function(){
-	var sb = [];
-
-	sb.push('&lt;p&gt;actual value:&lt;/p&gt;');
-	sb.push('&lt;p class=&quot;left&quot;&gt;' + JSSpec.util.inspect(this.actual) + '&lt;/p&gt;');
-	sb.push('&lt;p&gt;should be:&lt;/p&gt;');
-	sb.push('&lt;p class=&quot;left&quot;&gt;' + JSSpec.util.inspect(this.expected) + '&lt;/p&gt;');
-
-	return sb.join(&quot;&quot;);
-};
-
-JSSpec.BooleanEqualityMatcher.prototype.matches = function(){
-	return this.expected == this.actual;
-};
-
-/**
- * NullEqualityMatcher
- */
-
-JSSpec.NullEqualityMatcher = function(expected, actual){
-	this.expected = expected;
-	this.actual = actual;
-};
-
-JSSpec.NullEqualityMatcher.prototype.matches = function(){
-	return this.expected == this.actual &amp;&amp; typeof this.expected == typeof this.actual;
-};
-
-JSSpec.NullEqualityMatcher.prototype.explain = function(){
-	return JSSpec.EqualityMatcher.basicExplain(this.expected, this.actual);
-};
-
-JSSpec.DateEqualityMatcher = function(expected, actual){
-	this.expected = expected;
-	this.actual = actual;
-};
-
-JSSpec.DateEqualityMatcher.prototype.matches = function(){
-	return this.expected.getTime() == this.actual.getTime();
-};
-
-JSSpec.DateEqualityMatcher.prototype.explain = function(){
-	var sb = [];
-
-	sb.push(JSSpec.EqualityMatcher.basicExplain(this.expected, this.actual));
-	sb.push(JSSpec.EqualityMatcher.diffExplain(this.expected.toString(), this.actual.toString()));
-
-	return sb.join(&quot;&quot;);
-};
-
-/**
- * ObjectEqualityMatcher
- */
-
-JSSpec.ObjectEqualityMatcher = function(expected, actual){
-	this.expected = expected;
-	this.actual = actual;
-	this.match = this.expected == this.actual;
-	this.explaination = this.makeExplain();
-};
-
-JSSpec.ObjectEqualityMatcher.prototype.matches = function(){return this.match;};
-
-JSSpec.ObjectEqualityMatcher.prototype.explain = function(){return this.explaination;};
-
-JSSpec.ObjectEqualityMatcher.prototype.makeExplain = function(){
-	if(this.expected == this.actual){
-		this.match = true;
-		return &quot;&quot;;
-	}
-
-	if(JSSpec.util.isDomNode(this.expected)){
-		return this.makeExplainForDomNode();
-	}
-
-	var key, expectedHasItem, actualHasItem;
-
-	for(key in this.expected){
-		expectedHasItem = this.expected[key] != null &amp;&amp; typeof this.expected[key] != 'undefined';
-		actualHasItem = this.actual[key] != null &amp;&amp; typeof this.actual[key] != 'undefined';
-		if(expectedHasItem &amp;&amp; !actualHasItem) return this.makeExplainForMissingItem(key);
-	}
-	for(key in this.actual){
-		expectedHasItem = this.expected[key] != null &amp;&amp; typeof this.expected[key] != 'undefined';
-		actualHasItem = this.actual[key] != null &amp;&amp; typeof this.actual[key] != 'undefined';
-		if(actualHasItem &amp;&amp; !expectedHasItem) return this.makeExplainForUnknownItem(key);
-	}
-
-	for(key in this.expected){
-		var matcher = JSSpec.EqualityMatcher.createInstance(this.expected[key], this.actual[key]);
-		if(!matcher.matches()) return this.makeExplainForItemMismatch(key);
-	}
-
-	this.match = true;
-
-	return null;
-};
-
-JSSpec.ObjectEqualityMatcher.prototype.makeExplainForDomNode = function(key){
-	var sb = [];
-
-	sb.push(JSSpec.EqualityMatcher.basicExplain(this.expected, this.actual));
-
-	return sb.join(&quot;&quot;);
-};
-
-JSSpec.ObjectEqualityMatcher.prototype.makeExplainForMissingItem = function(key){
-	var sb = [];
-
-	sb.push('&lt;p&gt;actual value has no item named &lt;strong&gt;' + JSSpec.util.inspect(key) + '&lt;/strong&gt;&lt;/p&gt;');
-	sb.push('&lt;p class=&quot;left&quot;&gt;' + JSSpec.util.inspect(this.actual, false, key) + '&lt;/p&gt;');
-	sb.push('&lt;p&gt;but it should have the item whose value is &lt;strong&gt;' + JSSpec.util.inspect(this.expected[key]) + '&lt;/strong&gt;&lt;/p&gt;');
-	sb.push('&lt;p class=&quot;left&quot;&gt;' + JSSpec.util.inspect(this.expected, false, key) + '&lt;/p&gt;');
-
-	return sb.join(&quot;&quot;);
-};
-
-JSSpec.ObjectEqualityMatcher.prototype.makeExplainForUnknownItem = function(key){
-	var sb = [];
-
-	sb.push('&lt;p&gt;actual value has item named &lt;strong&gt;' + JSSpec.util.inspect(key) + '&lt;/strong&gt;&lt;/p&gt;');
-	sb.push('&lt;p class=&quot;left&quot;&gt;' + JSSpec.util.inspect(this.actual, false, key) + '&lt;/p&gt;');
-	sb.push('&lt;p&gt;but there should be no such item&lt;/p&gt;');
-	sb.push('&lt;p class=&quot;left&quot;&gt;' + JSSpec.util.inspect(this.expected, false, key) + '&lt;/p&gt;');
-
-	return sb.join(&quot;&quot;);
-};
-
-JSSpec.ObjectEqualityMatcher.prototype.makeExplainForItemMismatch = function(key){
-	var sb = [];
-
-	sb.push('&lt;p&gt;actual value has an item named &lt;strong&gt;' + JSSpec.util.inspect(key) + '&lt;/strong&gt; whose value is &lt;strong&gt;' + JSSpec.util.inspect(this.actual[key]) + '&lt;/strong&gt;&lt;/p&gt;');
-	sb.push('&lt;p class=&quot;left&quot;&gt;' + JSSpec.util.inspect(this.actual, false, key) + '&lt;/p&gt;');
-	sb.push('&lt;p&gt;but it\'s value should be &lt;strong&gt;' + JSSpec.util.inspect(this.expected[key]) + '&lt;/strong&gt;&lt;/p&gt;');
-	sb.push('&lt;p class=&quot;left&quot;&gt;' + JSSpec.util.inspect(this.expected, false, key) + '&lt;/p&gt;');
-
-	return sb.join(&quot;&quot;);
-};
-
-/**
- * ArrayEqualityMatcher
- */
-JSSpec.ArrayEqualityMatcher = function(expected, actual){
-	this.expected = expected;
-	this.actual = actual;
-	this.match = this.expected == this.actual;
-	this.explaination = this.makeExplain();
-};
-
-JSSpec.ArrayEqualityMatcher.prototype.matches = function(){return this.match;};
-
-JSSpec.ArrayEqualityMatcher.prototype.explain = function(){return this.explaination;};
-
-JSSpec.ArrayEqualityMatcher.prototype.makeExplain = function(){
-	if(this.expected.length != this.actual.length) return this.makeExplainForLengthMismatch();
-
-	for(var i = 0; i &lt; this.expected.length; i++){
-		var matcher = JSSpec.EqualityMatcher.createInstance(this.expected[i], this.actual[i]);
-		if(!matcher.matches()) return this.makeExplainForItemMismatch(i);
-	}
-
-	this.match = true;
-
-	return null;
-};
-
-JSSpec.ArrayEqualityMatcher.prototype.makeExplainForLengthMismatch = function(){
-	return JSSpec.EqualityMatcher.basicExplain(
-		this.expected,
-		this.actual,
-		'&lt;p&gt;but it should be &lt;strong&gt;' + this.expected.length + '&lt;/strong&gt;&lt;/p&gt;',
-		'&lt;p&gt;actual value has &lt;strong&gt;' + this.actual.length + '&lt;/strong&gt; items&lt;/p&gt;'
-	);
-};
-
-JSSpec.ArrayEqualityMatcher.prototype.makeExplainForItemMismatch = function(index){
-	var postfix = [&quot;th&quot;, &quot;st&quot;, &quot;nd&quot;, &quot;rd&quot;, &quot;th&quot;][Math.min((index + 1) % 10,4)];
-
-	var sb = [];
-
-	sb.push('&lt;p&gt;' + (index + 1) + postfix + ' item (index ' + index + ') of actual value is &lt;strong&gt;' + JSSpec.util.inspect(this.actual[index]) + '&lt;/strong&gt;:&lt;/p&gt;');
-	sb.push('&lt;p class=&quot;left&quot;&gt;' + JSSpec.util.inspect(this.actual, false, index) + '&lt;/p&gt;');
-	sb.push('&lt;p&gt;but it should be &lt;strong&gt;' + JSSpec.util.inspect(this.expected[index]) + '&lt;/strong&gt;:&lt;/p&gt;');
-	sb.push('&lt;p class=&quot;left&quot;&gt;' + JSSpec.util.inspect(this.expected, false, index) + '&lt;/p&gt;');
-
-	return sb.join(&quot;&quot;);
-};
-
-/**
- * NumberEqualityMatcher
- */
-
-JSSpec.NumberEqualityMatcher = function(expected, actual){
-	this.expected = expected;
-	this.actual = actual;
-};
-
-JSSpec.NumberEqualityMatcher.prototype.matches = function(){
-	return (this.expected == this.actual);
-};
-
-JSSpec.NumberEqualityMatcher.prototype.explain = function(){
-	return JSSpec.EqualityMatcher.basicExplain(this.expected, this.actual);
-};
-
-/**
- * StringEqualityMatcher
- */
-
-JSSpec.StringEqualityMatcher = function(expected, actual){
-	this.expected = expected;
-	this.actual = actual;
-};
-
-JSSpec.StringEqualityMatcher.prototype.matches = function(){
-	return (this.expected == this.actual);
-};
-
-JSSpec.StringEqualityMatcher.prototype.explain = function(){
-	var sb = [];
-
-	sb.push(JSSpec.EqualityMatcher.basicExplain(this.expected, this.actual));
-	sb.push(JSSpec.EqualityMatcher.diffExplain(this.expected, this.actual));
-	return sb.join(&quot;&quot;);
-};
-
-/**
- * PatternMatcher
- */
-
-JSSpec.PatternMatcher = function(actual, pattern, condition){
-	this.actual = actual;
-	this.pattern = pattern;
-	this.condition = condition;
-	this.match = false;
-	this.explaination = this.makeExplain();
-};
-
-JSSpec.PatternMatcher.createInstance = function(actual, pattern, condition){
-	return new JSSpec.PatternMatcher(actual, pattern, condition);
-};
-
-JSSpec.PatternMatcher.prototype.makeExplain = function(){
-	var sb;
-	if(this.actual == null || this.actual._type != 'String'){
-		sb = [];
-		sb.push('&lt;p&gt;actual value:&lt;/p&gt;');
-		sb.push('&lt;p class=&quot;left&quot;&gt;' + JSSpec.util.inspect(this.actual) + '&lt;/p&gt;');
-		sb.push('&lt;p&gt;should ' + (this.condition ? '' : 'not') + ' match with pattern:&lt;/p&gt;');
-		sb.push('&lt;p class=&quot;left&quot;&gt;' + JSSpec.util.inspect(this.pattern) + '&lt;/p&gt;');
-		sb.push('&lt;p&gt;but pattern matching cannot be performed.&lt;/p&gt;');
-		return sb.join(&quot;&quot;);
-	} else {
-		this.match = this.condition == !!this.actual.match(this.pattern);
-		if(this.match) return &quot;&quot;;
-
-		sb = [];
-		sb.push('&lt;p&gt;actual value:&lt;/p&gt;');
-		sb.push('&lt;p class=&quot;left&quot;&gt;' + JSSpec.util.inspect(this.actual) + '&lt;/p&gt;');
-		sb.push('&lt;p&gt;should ' + (this.condition ? '' : 'not') + ' match with pattern:&lt;/p&gt;');
-		sb.push('&lt;p class=&quot;left&quot;&gt;' + JSSpec.util.inspect(this.pattern) + '&lt;/p&gt;');
-		return sb.join(&quot;&quot;);
-	}
-
-};
-
-JSSpec.PatternMatcher.prototype.matches = function(){
-	return this.match;
-};
-
-JSSpec.PatternMatcher.prototype.explain = function(){
-	return this.explaination;
-};
-
-/**
- * Domain Specific Languages
- */
-JSSpec.DSL = {};
-
-JSSpec.DSL.forString = {
-	asHtml: function(){
-		var html = this;
-
-		// Uniformize quotation, turn tag names and attribute names into lower case
-		html = html.replace(/&lt;(\/?)(\w+)([^&gt;]*?)&gt;/img, function(str, closingMark, tagName, attrs){
-			var sortedAttrs = JSSpec.util.sortHtmlAttrs(JSSpec.util.correctHtmlAttrQuotation(attrs).toLowerCase());
-			return &quot;&lt;&quot; + closingMark + tagName.toLowerCase() + sortedAttrs + &quot;&gt;&quot;;
-		});
-
-		// validation self-closing tags
-		html = html.replace(/&lt;br([^&gt;]*?)&gt;/mg, function(str, attrs){
-			return &quot;&lt;br&quot; + attrs + &quot; /&gt;&quot;;
-		});
-
-		html = html.replace(/&lt;hr([^&gt;]*?)&gt;/mg, function(str, attrs){
-			return &quot;&lt;hr&quot; + attrs + &quot; /&gt;&quot;;
-		});
-
-		html = html.replace(/&lt;img([^&gt;]*?)&gt;/mg, function(str, attrs){
-			return &quot;&lt;img&quot; + attrs + &quot; /&gt;&quot;;
-		});
-
-		// append semi-colon at the end of style value
-		html = html.replace(/style=&quot;(.*)&quot;/mg, function(str, styleStr){
-			styleStr = JSSpec.util.sortStyleEntries(styleStr.strip()); // for Safari
-			if(styleStr.charAt(styleStr.length - 1) != ';') styleStr += &quot;;&quot;;
-
-			return 'style=&quot;' + styleStr + '&quot;';
-		});
-
-		// sort style entries
-
-		// remove empty style attributes
-		html = html.replace(/ style=&quot;;&quot;/mg, &quot;&quot;);
-
-		// remove new-lines
-		html = html.replace(/\r/mg, '');
-		html = html.replace(/\n/mg, '');
-
-		// TODO remove this?
-		//html = html.replace(/(&gt;[^&lt;&gt;]*?)\s+([^&lt;&gt;]*?&lt;)/mg, '$1$2')
-
-		return html;
-	}
-};
-
-JSSpec.DSL.describe = function(context, entries){
-	JSSpec.specs.push(new JSSpec.Spec(context, entries));
-};
-
-JSSpec.DSL.expect = function(target){
-	if(JSSpec._secondPass) return {};
-
-	var subject = new JSSpec.DSL.Subject(target);
-	return subject;
-};
-
-JSSpec.DSL.Subject = function(target){
-	this.target = target;
-};
-
-JSSpec.DSL.Subject.prototype._type = 'Subject';
-JSSpec.DSL.Subject.prototype.should_fail = function(message){
-	JSSpec._assertionFailure = {message:message};
-	throw JSSpec._assertionFailure;
-};
-
-JSSpec.DSL.Subject.prototype.should_be = function(expected){
-	var matcher = JSSpec.EqualityMatcher.createInstance(expected, this.target);
-	if(!matcher.matches()){
-		JSSpec._assertionFailure = {message:matcher.explain()};
-		throw JSSpec._assertionFailure;
-	}
-};
-
-JSSpec.DSL.Subject.prototype.should_not_be = function(expected){
-	// TODO JSSpec.EqualityMatcher should support 'condition'
-	var matcher = JSSpec.EqualityMatcher.createInstance(expected, this.target);
-	if(matcher.matches()){
-		JSSpec._assertionFailure = {message:&quot;'&quot; + this.target + &quot;' should not be '&quot; + expected + &quot;'&quot;};
-		throw JSSpec._assertionFailure;
-	}
-};
-
-JSSpec.DSL.Subject.prototype.should_be_empty = function(){
-	this.should_have(0, this.getType() == 'String' ? 'characters' : 'items');
-};
-
-JSSpec.DSL.Subject.prototype.should_not_be_empty = function(){
-	this.should_have_at_least(1, this.getType() == 'String' ? 'characters' : 'items');
-};
-
-JSSpec.DSL.Subject.prototype.should_be_true = function(){
-	this.should_be(true);
-};
-
-JSSpec.DSL.Subject.prototype.should_be_false = function(){
-	this.should_be(false);
-};
-
-JSSpec.DSL.Subject.prototype.should_be_null = function(){
-	this.should_be(null);
-};
-
-JSSpec.DSL.Subject.prototype.should_be_undefined = function(){
-	this.should_be(undefined);
-};
-
-JSSpec.DSL.Subject.prototype.should_not_be_null = function(){
-	this.should_not_be(null);
-};
-
-JSSpec.DSL.Subject.prototype.should_not_be_undefined = function(){
-	this.should_not_be(undefined);
-};
-
-JSSpec.DSL.Subject.prototype._should_have = function(num, property, condition){
-	var matcher = JSSpec.PropertyLengthMatcher.createInstance(num, property, this.target, condition);
-	if(!matcher.matches()){
-		JSSpec._assertionFailure = {message:matcher.explain()};
-		throw JSSpec._assertionFailure;
-	}
-};
-
-JSSpec.DSL.Subject.prototype.should_have = function(num, property){
-	this._should_have(num, property, &quot;exactly&quot;);
-};
-
-JSSpec.DSL.Subject.prototype.should_have_exactly = function(num, property){
-	this._should_have(num, property, &quot;exactly&quot;);
-};
-
-JSSpec.DSL.Subject.prototype.should_have_at_least = function(num, property){
-	this._should_have(num, property, &quot;at least&quot;);
-};
-
-JSSpec.DSL.Subject.prototype.should_have_at_most = function(num, property){
-	this._should_have(num, property, &quot;at most&quot;);
-};
-
-JSSpec.DSL.Subject.prototype.should_include = function(expected){
-	var matcher = JSSpec.IncludeMatcher.createInstance(this.target, expected, true);
-	if(!matcher.matches()){
-		JSSpec._assertionFailure = {message:matcher.explain()};
-		throw JSSpec._assertionFailure;
-	}
-};
-
-JSSpec.DSL.Subject.prototype.should_not_include = function(expected){
-	var matcher = JSSpec.IncludeMatcher.createInstance(this.target, expected, false);
-	if(!matcher.matches()){
-		JSSpec._assertionFailure = {message:matcher.explain()};
-		throw JSSpec._assertionFailure;
-	}
-};
-
-JSSpec.DSL.Subject.prototype.should_match = function(pattern){
-	var matcher = JSSpec.PatternMatcher.createInstance(this.target, pattern, true);
-	if(!matcher.matches()){
-		JSSpec._assertionFailure = {message:matcher.explain()};
-		throw JSSpec._assertionFailure;
-	}
-};
-
-JSSpec.DSL.Subject.prototype.should_not_match = function(pattern){
-	var matcher = JSSpec.PatternMatcher.createInstance(this.target, pattern, false);
-	if(!matcher.matches()){
-		JSSpec._assertionFailure = {message:matcher.explain()};
-		throw JSSpec._assertionFailure;
-	}
-};
-
-JSSpec.DSL.Subject.prototype.getType = function(){
-	if(typeof this.target == 'undefined'){
-		return 'undefined';
-	} else if(this.target == null){
-		return 'null';
-	} else if(this.target._type){
-		return this.target._type;
-	} else if(JSSpec.util.isDomNode(this.target)){
-		return 'DomNode';
-	} else {
-		return 'object';
-	}
-};
-
-/**
- * Utilities
- */
-
-JSSpec.util = {
-	parseOptions: function(defaults){
-		var options = defaults;
-
-		var url = location.href;
-		var queryIndex = url.indexOf('?');
-		if(queryIndex == -1) return options;
-
-		var query = url.substring(queryIndex + 1);
-		var pairs = query.split('&amp;');
-		for(var i = 0; i &lt; pairs.length; i++){
-			var tokens = pairs[i].split('=');
-			options[tokens[0]] = tokens[1];
-		}
-
-		return options;
-	},
-	correctHtmlAttrQuotation: function(html){
-		html = html.replace(/(\w+)=['&quot;]([^'&quot;]+)['&quot;]/mg,function (str, name, value){return name + '=' + '&quot;' + value + '&quot;';});
-		html = html.replace(/(\w+)=([^ '&quot;]+)/mg,function (str, name, value){return name + '=' + '&quot;' + value + '&quot;';});
-		html = html.replace(/'/mg, '&quot;');
-
-		return html;
-	},
-	sortHtmlAttrs: function(html){
-		var attrs = [];
-		html.replace(/((\w+)=&quot;[^&quot;]+&quot;)/mg, function(str, matched){
-			attrs.push(matched);
-		});
-		return attrs.length == 0 ? &quot;&quot; : &quot; &quot; + attrs.sort().join(&quot; &quot;);
-	},
-	sortStyleEntries: function(styleText){
-		var entries = styleText.split(/; /);
-		return entries.sort().join(&quot;; &quot;);
-	},
-	escapeHtml: function(str){
-		if(!this._div){
-			this._div = document.createElement(&quot;DIV&quot;);
-			this._text = document.createTextNode('');
-			this._div.appendChild(this._text);
-		}
-		this._text.data = str;
-		return this._div.innerHTML;
-	},
-	isDomNode: function(o){
-		// TODO: make it more stricter
-		return (typeof o.nodeName == 'string') &amp;&amp; (typeof o.nodeType == 'number');
-	},
-	inspectDomPath: function(o){
-		var sb = [];
-		while(o &amp;&amp; o.nodeName != '#document' &amp;&amp; o.parent){
-			var siblings = o.parentNode.childNodes;
-			for(var i = 0; i &lt; siblings.length; i++){
-				if(siblings[i] == o){
-					sb.push(o.nodeName + (i == 0 ? '' : '[' + i + ']'));
-					break;
-				}
-			}
-			o = o.parentNode;
-		}
-		return sb.join(&quot; &amp;gt; &quot;);
-	},
-	inspectDomNode: function(o){
-		if(o.nodeType == 1){
-			var sb = [];
-			sb.push('&lt;span class=&quot;dom_value&quot;&gt;');
-			sb.push(&quot;&amp;lt;&quot;);
-			sb.push(o.nodeName);
-
-			var attrs = o.attributes;
-			for(var i = 0; i &lt; attrs.length; i++){
-				if(
-					attrs[i].nodeValue &amp;&amp;
-					attrs[i].nodeName != 'contentEditable' &amp;&amp;
-					attrs[i].nodeName != 'style' &amp;&amp;
-					typeof attrs[i].nodeValue != 'function'
-				) sb.push(' &lt;span class=&quot;dom_attr_name&quot;&gt;' + attrs[i].nodeName + '&lt;/span&gt;=&lt;span class=&quot;dom_attr_value&quot;&gt;&quot;' + attrs[i].nodeValue + '&quot;&lt;/span&gt;');
-			}
-			if(o.style &amp;&amp; o.style.cssText){
-				sb.push(' &lt;span class=&quot;dom_attr_name&quot;&gt;style&lt;/span&gt;=&lt;span class=&quot;dom_attr_value&quot;&gt;&quot;' + o.style.cssText + '&quot;&lt;/span&gt;');
-			}
-			sb.push('&amp;gt; &lt;span class=&quot;dom_path&quot;&gt;(' + JSSpec.util.inspectDomPath(o) + ')&lt;/span&gt;' );
-			sb.push('&lt;/span&gt;');
-			return sb.join(&quot;&quot;);
-		} else if(o.nodeType == 3){
-			return '&lt;span class=&quot;dom_value&quot;&gt;#text ' + o.nodeValue + '&lt;/span&gt;';
-		} else {
-			return '&lt;span class=&quot;dom_value&quot;&gt;UnknownDomNode&lt;/span&gt;';
-		}
-	},
-	inspect: function(o, dontEscape, emphasisKey){
-		var sb, inspected;
-
-		if(typeof o == 'undefined') return '&lt;span class=&quot;undefined_value&quot;&gt;undefined&lt;/span&gt;';
-
-		if(o == null) return '&lt;span class=&quot;null_value&quot;&gt;null&lt;/span&gt;';
-
-		if(o._type &amp;&amp; o._type == 'String') return '&lt;span class=&quot;string_value&quot;&gt;&quot;' + (dontEscape ? o : JSSpec.util.escapeHtml(o)) + '&quot;&lt;/span&gt;';
-
-		if(o._type &amp;&amp; o._type == 'Date'){
-			return '&lt;span class=&quot;date_value&quot;&gt;&quot;' + o.toString() + '&quot;&lt;/span&gt;';
-		}
-
-		if(o._type &amp;&amp; o._type == 'Number') return '&lt;span class=&quot;number_value&quot;&gt;' + (dontEscape ? o : JSSpec.util.escapeHtml(o)) + '&lt;/span&gt;';
-
-		if(o._type &amp;&amp; o._type == 'Boolean') return '&lt;span class=&quot;boolean_value&quot;&gt;' + o + '&lt;/span&gt;';
-
-		if(o._type &amp;&amp; o._type == 'RegExp') return '&lt;span class=&quot;regexp_value&quot;&gt;' + JSSpec.util.escapeHtml(o.toString()) + '&lt;/span&gt;';
-
-		if(JSSpec.util.isDomNode(o)) return JSSpec.util.inspectDomNode(o);
-
-		if(o._type &amp;&amp; o._type == 'Array' || typeof o.length != 'undefined'){
-			sb = [];
-			for(var i = 0; i &lt; o.length; i++){
-				var oi = o[i];
-				inspected = JSSpec.util.inspect(oi);
-				sb.push(i == emphasisKey ? ('&lt;strong&gt;' + inspected + '&lt;/strong&gt;') : inspected);
-			}
-			return '&lt;span class=&quot;array_value&quot;&gt;[' + sb.join(', ') + ']&lt;/span&gt;';
-		}
-
-		// object
-		sb = [];
-		for(var key in o){
-			if(key == 'should') continue;
-
-			inspected = JSSpec.util.inspect(key) + &quot;:&quot; + JSSpec.util.inspect(o[key]);
-			sb.push(key == emphasisKey ? ('&lt;strong&gt;' + inspected + '&lt;/strong&gt;') : inspected);
-		}
-		return '&lt;span class=&quot;object_value&quot;&gt;{' + sb.join(', ') + '}&lt;/span&gt;';
-	}
-};
-
-var describe = JSSpec.DSL.describe;
-var expect = JSSpec.DSL.expect;
-
-var behavior_of = JSSpec.DSL.describe;
-var value_of = JSSpec.DSL.expect;
-
-String.prototype._type = &quot;String&quot;;
-Number.prototype._type = &quot;Number&quot;;
-Date.prototype._type = &quot;Date&quot;;
-Array.prototype._type = &quot;Array&quot;;
-Boolean.prototype._type = &quot;Boolean&quot;;
-RegExp.prototype._type = &quot;RegExp&quot;;
-
-var targets = [Array.prototype, Date.prototype, Number.prototype, String.prototype, Boolean.prototype, RegExp.prototype];
-
-String.prototype.asHtml = JSSpec.DSL.forString.asHtml;
-
-/**
- * Main
- */
-
-JSSpec.defaultOptions = {
-	autorun: 1,
-	specIdBeginsWith: 0,
-	exampleIdBeginsWith: 0,
-	autocollapse: 0
-};
-
-JSSpec.options = JSSpec.util.parseOptions(JSSpec.defaultOptions);
-
-JSSpec.Spec.id = JSSpec.options.specIdBeginsWith;
-JSSpec.Example.id = JSSpec.options.exampleIdBeginsWith;
-
-
-
-window.onload = function(){
-	if(JSSpec.specs.length &gt; 0){
-		if(!JSSpec.options.inSuite){
-			JSSpec.runner = new JSSpec.Runner(JSSpec.specs, new JSSpec.Logger());
-			if(JSSpec.options.rerun){
-				JSSpec.runner.rerun(decodeURIComponent(JSSpec.options.rerun));
-			} else {
-				JSSpec.runner.run();
-				customFunction();
-			}
-		} else {
-			// in suite, send all specs to parent
-			var parentWindow = window.frames.parent.window;
-			for(var i = 0; i &lt; JSSpec.specs.length; i++){
-				parentWindow.JSSpec.specs.push(JSSpec.specs[i]);
-			}
-		}
-	} else {
-		var links = document.getElementById('list').getElementsByTagName('A');
-		var frameContainer = document.createElement('DIV');
-		frameContainer.style.display = 'none';
-		document.body.appendChild(frameContainer);
-
-		for(var j = 0; j &lt; links.length; j++){
-			var frame = document.createElement('IFRAME');
-			frame.src = links[j].href + '?inSuite=0&amp;specIdBeginsWith=' + (j * 10000) + '&amp;exampleIdBeginsWith=' + (j * 10000);
-			frameContainer.appendChild(frame);
-		}
-	}
-};
-
-function customFunction(){
-	var runner = document.getElementById('runner');
-	runner.onclick = function(){
-		JSSpec.runner.executor.run();
-		runner.className = &quot;disabled&quot;;
-		runner.onclick = null;
-		return false;
-	};
-
-	var h4s = document.getElementsByTagName('h4');
-
-	function toggler(h4){
-		h4.onclick = function(){
-			var div = h4.parentNode.getElementsByTagName('div')[0];
-			if (!div || div.parentNode.className != 'exception') return false;
-			div.style.display = (div.style.display == 'block') ? 'none' : 'block';
-			return false;
-		};
-	};
-
-	for (var i = 0, l = h4s.length; i &lt; l; i++) toggler(h4s[i]);
-};
\ No newline at end of file
+/**
+ * JSSpec
+ *
+ * Copyright 2007 Alan Kang
+ *  - mailto:jania902@gmail.com
+ *  - http://jania.pe.kr
+ *
+ * http://jania.pe.kr/aw/moin.cgi/JSSpec
+ *
+ * Dependencies:
+ *  - diff_match_patch.js ( http://code.google.com/p/google-diff-match-patch )
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/**
+ * Namespace
+ */
+
+var JSSpec = {
+	specs: [],
+	
+	EMPTY_FUNCTION: function() {},
+	
+	Browser: {
+		Trident: navigator.appName == &quot;Microsoft Internet Explorer&quot;,
+		Webkit: navigator.userAgent.indexOf('AppleWebKit/') &gt; -1,
+		Gecko: navigator.userAgent.indexOf('Gecko') &gt; -1 &amp;&amp; navigator.userAgent.indexOf('KHTML') == -1,
+		Presto: navigator.appName == &quot;Opera&quot;
+	}
+};
+
+
+
+/**
+ * Executor
+ */
+JSSpec.Executor = function(target, onSuccess, onException) {
+	this.target = target;
+	this.onSuccess = typeof onSuccess == 'function' ? onSuccess : JSSpec.EMPTY_FUNCTION;
+	this.onException = typeof onException == 'function' ? onException : JSSpec.EMPTY_FUNCTION;
+	
+	if(JSSpec.Browser.Trident) {
+		// Exception handler for Trident. It helps to collect exact line number where exception occured.
+		window.onerror = function(message, fileName, lineNumber) {
+			var self = window._curExecutor;
+			var ex = {message:message, fileName:fileName, lineNumber:lineNumber};
+
+			if(JSSpec._secondPass)  {
+				ex = self.mergeExceptions(JSSpec._assertionFailure, ex);
+				delete JSSpec._secondPass;
+				delete JSSpec._assertionFailure;
+				
+				ex.type = &quot;failure&quot;;
+				self.onException(self, ex);
+			} else if(JSSpec._assertionFailure) {
+				JSSpec._secondPass = true;
+				self.run();
+			} else {
+				self.onException(self, ex);
+			}
+			
+			return true;
+		};
+	}
+};
+JSSpec.Executor.prototype.mergeExceptions = function(assertionFailure, normalException) {
+	var merged = {
+		message:assertionFailure.message,
+		fileName:normalException.fileName,
+		lineNumber:normalException.lineNumber
+	};
+	
+	return merged;
+};
+
+JSSpec.Executor.prototype.run = function() {
+	var self = this;
+	var target = this.target;
+	var onSuccess = this.onSuccess;
+	var onException = this.onException;
+	
+	window.setTimeout(
+		function() {
+			var result;
+			if(JSSpec.Browser.Trident) {
+				window._curExecutor = self;
+				
+				result = self.target();
+				self.onSuccess(self, result);
+			} else {
+				try {
+					result = self.target();
+					self.onSuccess(self, result);
+				} catch(ex) {
+					if(JSSpec.Browser.Webkit) ex = {message:ex.message, fileName:ex.sourceURL, lineNumber:ex.line};
+					
+					if(JSSpec._secondPass)  {
+						ex = self.mergeExceptions(JSSpec._assertionFailure, ex);
+						delete JSSpec._secondPass;
+						delete JSSpec._assertionFailure;
+						
+						ex.type = &quot;failure&quot;;
+						self.onException(self, ex);
+					} else if(JSSpec._assertionFailure) {
+						JSSpec._secondPass = true;
+						self.run();
+					} else {
+						self.onException(self, ex);
+					}
+				}
+			}
+		},
+		0
+	);
+};
+
+
+
+/**
+ * CompositeExecutor composites one or more executors and execute them sequencially.
+ */
+JSSpec.CompositeExecutor = function(onSuccess, onException, continueOnException) {
+	this.queue = [];
+	this.onSuccess = typeof onSuccess == 'function' ? onSuccess : JSSpec.EMPTY_FUNCTION;
+	this.onException = typeof onException == 'function' ? onException : JSSpec.EMPTY_FUNCTION;
+	this.continueOnException = !!continueOnException;
+};
+
+JSSpec.CompositeExecutor.prototype.addFunction = function(func) {
+	this.addExecutor(new JSSpec.Executor(func));
+};
+
+JSSpec.CompositeExecutor.prototype.addExecutor = function(executor) {
+	var last = this.queue.length == 0 ? null : this.queue[this.queue.length - 1];
+	if(last) {
+		last.next = executor;
+	}
+	
+	executor.parent = this;
+	executor.onSuccessBackup = executor.onSuccess;
+	executor.onSuccess = function(result) {
+		this.onSuccessBackup(result);
+		if(this.next) {
+			this.next.run();
+		} else {
+			this.parent.onSuccess();
+		}
+	};
+	executor.onExceptionBackup = executor.onException;
+	executor.onException = function(executor, ex) {
+		this.onExceptionBackup(executor, ex);
+
+		if(this.parent.continueOnException) {
+			if(this.next) {
+				this.next.run();
+			} else {
+				this.parent.onSuccess();
+			}
+		} else {
+			this.parent.onException(executor, ex);
+		}
+	};
+
+	this.queue.push(executor);
+};
+
+JSSpec.CompositeExecutor.prototype.run = function() {
+	if(this.queue.length &gt; 0) {
+		this.queue[0].run();
+	}
+};
+
+/**
+ * Spec is a set of Examples in a specific context
+ */
+JSSpec.Spec = function(context, entries) {
+	this.id = JSSpec.Spec.id++;
+	this.context = context;
+	this.url = location.href;
+	
+	this.filterEntriesByEmbeddedExpressions(entries);
+	this.extractOutSpecialEntries(entries);
+	this.examples = this.makeExamplesFromEntries(entries);
+	this.examplesMap = this.makeMapFromExamples(this.examples);
+};
+
+JSSpec.Spec.id = 0;
+JSSpec.Spec.prototype.getExamples = function() {
+	return this.examples;
+};
+
+JSSpec.Spec.prototype.hasException = function() {
+	return this.getTotalFailures() &gt; 0 || this.getTotalErrors() &gt; 0;
+};
+
+JSSpec.Spec.prototype.getTotalFailures = function() {
+	var examples = this.examples;
+	var failures = 0;
+	for(var i = 0; i &lt; examples.length; i++) {
+		if(examples[i].isFailure()) failures++;
+	}
+	return failures;
+};
+
+JSSpec.Spec.prototype.getTotalErrors = function() {
+	var examples = this.examples;
+	var errors = 0;
+	for(var i = 0; i &lt; examples.length; i++) {
+		if(examples[i].isError()) errors++;
+	}
+	return errors;
+};
+
+JSSpec.Spec.prototype.filterEntriesByEmbeddedExpressions = function(entries) {
+	var isTrue;
+	for(name in entries) {
+		var m = name.match(/\[\[(.+)\]\]/);
+		if(m &amp;&amp; m[1]) {
+			eval(&quot;isTrue = (&quot; + m[1] + &quot;)&quot;);
+			if(!isTrue) delete entries[name];
+		}
+	}
+};
+
+JSSpec.Spec.prototype.extractOutSpecialEntries = function(entries) {
+	this.beforeEach = JSSpec.EMPTY_FUNCTION;
+	this.beforeAll = JSSpec.EMPTY_FUNCTION;
+	this.afterEach = JSSpec.EMPTY_FUNCTION;
+	this.afterAll = JSSpec.EMPTY_FUNCTION;
+	
+	for(name in entries) {
+		if(name == 'before' || name == 'before each' || name == 'before_each') {
+			this.beforeEach = entries[name];
+		} else if(name == 'before all' || name == 'before_all') {
+			this.beforeAll = entries[name];
+		} else if(name == 'after' || name == 'after each' || name == 'after_each') {
+			this.afterEach = entries[name];
+		} else if(name == 'after all' || name == 'after_all') {
+			this.afterAll = entries[name];
+		}
+	}
+	
+	delete entries['before'];
+	delete entries['before each'];
+	delete entries['before_each'];
+	delete entries['before all'];
+	delete entries['before_all'];
+	delete entries['after'];
+	delete entries['after each'];
+	delete entries['after_each'];
+	delete entries['after all'];
+	delete entries['after_all'];
+};
+
+JSSpec.Spec.prototype.makeExamplesFromEntries = function(entries) {
+	var examples = [];
+	for(name in entries) {
+		examples.push(new JSSpec.Example(name, entries[name], this.beforeEach, this.afterEach));
+	}
+	return examples;
+};
+
+JSSpec.Spec.prototype.makeMapFromExamples = function(examples) {
+	var map = {};
+	for(var i = 0; i &lt; examples.length; i++) {
+		var example = examples[i];
+		map[example.id] = examples[i];
+	}
+	return map;
+};
+
+JSSpec.Spec.prototype.getExampleById = function(id) {
+	return this.examplesMap[id];
+};
+
+JSSpec.Spec.prototype.getExecutor = function() {
+	var self = this;
+	var onException = function(executor, ex) {
+		self.exception = ex;
+	};
+	
+	var composite = new JSSpec.CompositeExecutor();
+	composite.addFunction(function() {JSSpec.log.onSpecStart(self);});
+	composite.addExecutor(new JSSpec.Executor(this.beforeAll, null, function(exec, ex) {
+		self.exception = ex;
+		JSSpec.log.onSpecEnd(self);
+	}));
+	
+	var exampleAndAfter = new JSSpec.CompositeExecutor(null,null,true);
+	for(var i = 0; i &lt; this.examples.length; i++) {
+		exampleAndAfter.addExecutor(this.examples[i].getExecutor());
+	}
+	exampleAndAfter.addExecutor(new JSSpec.Executor(this.afterAll, null, onException));
+	exampleAndAfter.addFunction(function() {JSSpec.log.onSpecEnd(self);});
+	composite.addExecutor(exampleAndAfter);
+	
+	return composite;
+};
+
+/**
+ * Example
+ */
+JSSpec.Example = function(name, target, before, after) {
+	this.id = JSSpec.Example.id++;
+	this.name = name;
+	this.target = target;
+	this.before = before;
+	this.after = after;
+};
+
+JSSpec.Example.id = 0;
+JSSpec.Example.prototype.isFailure = function() {
+	return this.exception &amp;&amp; this.exception.type == &quot;failure&quot;;
+};
+
+JSSpec.Example.prototype.isError = function() {
+	return this.exception &amp;&amp; !this.exception.type;
+};
+
+JSSpec.Example.prototype.getExecutor = function() {
+	var self = this;
+	var onException = function(executor, ex) {
+		self.exception = ex;
+	};
+	
+	var composite = new JSSpec.CompositeExecutor();
+	composite.addFunction(function() {JSSpec.log.onExampleStart(self);});
+	composite.addExecutor(new JSSpec.Executor(this.before, null, function(exec, ex) {
+		self.exception = ex;
+		JSSpec.log.onExampleEnd(self);
+	}));
+	
+	var targetAndAfter = new JSSpec.CompositeExecutor(null,null,true);
+	
+	targetAndAfter.addExecutor(new JSSpec.Executor(this.target, null, onException));
+	targetAndAfter.addExecutor(new JSSpec.Executor(this.after, null, onException));
+	targetAndAfter.addFunction(function() {JSSpec.log.onExampleEnd(self);});
+	
+	composite.addExecutor(targetAndAfter);
+	
+	return composite;
+};
+
+/**
+ * Runner
+ */
+JSSpec.Runner = function(specs, logger) {
+	JSSpec.log = logger;
+	
+	this.totalExamples = 0;
+	this.specs = [];
+	this.specsMap = {};
+	this.addAllSpecs(specs);
+};
+
+JSSpec.Runner.prototype.addAllSpecs = function(specs) {
+	for(var i = 0; i &lt; specs.length; i++) {
+		this.addSpec(specs[i]);
+	}
+};
+
+JSSpec.Runner.prototype.addSpec = function(spec) {
+	this.specs.push(spec);
+	this.specsMap[spec.id] = spec;
+	this.totalExamples += spec.getExamples().length;
+};
+
+JSSpec.Runner.prototype.getSpecById = function(id) {
+	return this.specsMap[id];
+};
+
+JSSpec.Runner.prototype.getSpecByContext = function(context) {
+	for(var i = 0; i &lt; this.specs.length; i++) {
+		if(this.specs[i].context == context) return this.specs[i];
+	}
+	return null;
+};
+
+JSSpec.Runner.prototype.getSpecs = function() {
+	return this.specs;
+};
+
+JSSpec.Runner.prototype.hasException = function() {
+	return this.getTotalFailures() &gt; 0 || this.getTotalErrors() &gt; 0;
+};
+
+JSSpec.Runner.prototype.getTotalFailures = function() {
+	var specs = this.specs;
+	var failures = 0;
+	for(var i = 0; i &lt; specs.length; i++) {
+		failures += specs[i].getTotalFailures();
+	}
+	return failures;
+};
+
+JSSpec.Runner.prototype.getTotalErrors = function() {
+	var specs = this.specs;
+	var errors = 0;
+	for(var i = 0; i &lt; specs.length; i++) {
+		errors += specs[i].getTotalErrors();
+	}
+	return errors;
+};
+
+
+JSSpec.Runner.prototype.run = function() {
+	JSSpec.log.onRunnerStart();
+	var executor = new JSSpec.CompositeExecutor(function() {JSSpec.log.onRunnerEnd()},null,true);
+	for(var i = 0; i &lt; this.specs.length; i++) {
+		executor.addExecutor(this.specs[i].getExecutor());
+	}
+	executor.run();
+};
+
+
+JSSpec.Runner.prototype.rerun = function(context) {
+	JSSpec.runner = new JSSpec.Runner([this.getSpecByContext(context)], JSSpec.log);
+	JSSpec.runner.run();
+};
+
+/**
+ * Logger
+ */
+JSSpec.Logger = function() {
+	this.finishedExamples = 0;
+	this.startedAt = null;
+};
+
+JSSpec.Logger.prototype.onRunnerStart = function() {
+	this._title = document.title;
+
+	this.startedAt = new Date();
+	var container = document.getElementById('jsspec_container');
+	if(container) {
+		container.innerHTML = &quot;&quot;;
+	} else {
+		container = document.createElement(&quot;DIV&quot;);
+		container.id = &quot;jsspec_container&quot;;
+		document.body.appendChild(container);
+	}
+	
+	var title = document.createElement(&quot;DIV&quot;);
+	title.id = &quot;title&quot;;
+	title.innerHTML = [
+		'&lt;h1&gt;JSSpec&lt;/h1&gt;',
+		'&lt;ul&gt;',
+		JSSpec.options.rerun ? '&lt;li&gt;[&lt;a href=&quot;?&quot; title=&quot;rerun all specs&quot;&gt;X&lt;/a&gt;] ' + JSSpec.util.escapeTags(decodeURIComponent(JSSpec.options.rerun)) + '&lt;/li&gt;' : '',
+		'	&lt;li&gt;&lt;span id=&quot;total_examples&quot;&gt;' + JSSpec.runner.totalExamples + '&lt;/span&gt; examples&lt;/li&gt;',
+		'	&lt;li&gt;&lt;span id=&quot;total_failures&quot;&gt;0&lt;/span&gt; failures&lt;/li&gt;',
+		'	&lt;li&gt;&lt;span id=&quot;total_errors&quot;&gt;0&lt;/span&gt; errors&lt;/li&gt;',
+		'	&lt;li&gt;&lt;span id=&quot;progress&quot;&gt;0&lt;/span&gt;% done&lt;/li&gt;',
+		'	&lt;li&gt;&lt;span id=&quot;total_elapsed&quot;&gt;0&lt;/span&gt; secs&lt;/li&gt;',
+		'&lt;/ul&gt;',
+		'&lt;p&gt;&lt;a href=&quot;http://jania.pe.kr/aw/moin.cgi/JSSpec&quot;&gt;JSSpec homepage&lt;/a&gt;&lt;/p&gt;',
+	].join(&quot;&quot;);
+	container.appendChild(title);
+
+	var list = document.createElement(&quot;DIV&quot;);
+	list.id = &quot;list&quot;;
+	list.innerHTML = [
+		'&lt;h2&gt;List&lt;/h2&gt;',
+		'&lt;ul class=&quot;specs&quot;&gt;',
+		function() {
+			var specs = JSSpec.runner.getSpecs();
+			var sb = [];
+			for(var i = 0; i &lt; specs.length; i++) {
+				var spec = specs[i];
+				sb.push('&lt;li id=&quot;spec_' + specs[i].id + '_list&quot;&gt;&lt;h3&gt;&lt;a href=&quot;#spec_' + specs[i].id + '&quot;&gt;' + JSSpec.util.escapeTags(specs[i].context) + '&lt;/a&gt; [&lt;a href=&quot;?rerun=' + encodeURIComponent(specs[i].context) + '&quot;&gt;rerun&lt;/a&gt;]&lt;/h3&gt;&lt;/li&gt;');
+			}
+			return sb.join(&quot;&quot;);
+		}(),
+		'&lt;/ul&gt;'
+	].join(&quot;&quot;);
+	container.appendChild(list);
+	
+	var log = document.createElement(&quot;DIV&quot;);
+	log.id = &quot;log&quot;;
+	log.innerHTML = [
+		'&lt;h2&gt;Log&lt;/h2&gt;',
+		'&lt;ul class=&quot;specs&quot;&gt;',
+		function() {
+			var specs = JSSpec.runner.getSpecs();
+			var sb = [];
+			for(var i = 0; i &lt; specs.length; i++) {
+				var spec = specs[i];
+				sb.push('	&lt;li id=&quot;spec_' + specs[i].id + '&quot;&gt;');
+				sb.push('		&lt;h3&gt;' + JSSpec.util.escapeTags(specs[i].context) + ' [&lt;a href=&quot;?rerun=' + encodeURIComponent(specs[i].context) + '&quot;&gt;rerun&lt;/a&gt;]&lt;/h3&gt;');
+				sb.push('		&lt;ul id=&quot;spec_' + specs[i].id + '_examples&quot; class=&quot;examples&quot;&gt;');
+				for(var j = 0; j &lt; spec.examples.length; j++) {
+					var example = spec.examples[j];
+					sb.push('			&lt;li id=&quot;example_' + example.id + '&quot;&gt;')
+					sb.push('				&lt;h4&gt;' + JSSpec.util.escapeTags(example.name) + '&lt;/h4&gt;')
+					sb.push('			&lt;/li&gt;')
+				}
+				sb.push('		&lt;/ul&gt;');
+				sb.push('	&lt;/li&gt;');
+			}
+			return sb.join(&quot;&quot;);
+		}(),
+		'&lt;/ul&gt;'
+	].join(&quot;&quot;);
+	
+	container.appendChild(log);
+	
+	// add event handler for toggling
+	var specs = JSSpec.runner.getSpecs();
+	var sb = [];
+	for(var i = 0; i &lt; specs.length; i++) {
+		var spec = document.getElementById(&quot;spec_&quot; + specs[i].id);
+		var title = spec.getElementsByTagName(&quot;H3&quot;)[0];
+		title.onclick = function(e) {
+			var target = document.getElementById(this.parentNode.id + &quot;_examples&quot;);
+			target.style.display = target.style.display == &quot;none&quot; ? &quot;block&quot; : &quot;none&quot;;
+			return true;
+		}
+	}
+};
+
+JSSpec.Logger.prototype.onRunnerEnd = function() {
+	if(JSSpec.runner.hasException()) {
+		var times = 4;
+		var title1 = &quot;*&quot; + this._title;
+		var title2 = &quot;*F&quot; + JSSpec.runner.getTotalFailures() + &quot; E&quot; + JSSpec.runner.getTotalErrors() + &quot;* &quot; + this._title;
+	} else {
+		var times = 2;
+		var title1 = this._title;
+		var title2 = &quot;Success&quot;;
+	}
+	this.blinkTitle(times,title1,title2);
+};
+
+JSSpec.Logger.prototype.blinkTitle = function(times, title1, title2) {
+	var times = times * 2;
+	var mode = true;
+	
+	var f = function() {
+		if(times &gt; 0) {
+			document.title = mode ? title1 : title2;
+			mode = !mode;
+			times--;
+			window.setTimeout(f, 500);
+		} else {
+			document.title = title1;
+		}
+	};
+	
+	f();
+};
+
+JSSpec.Logger.prototype.onSpecStart = function(spec) {
+	var spec_list = document.getElementById(&quot;spec_&quot; + spec.id + &quot;_list&quot;);
+	var spec_log = document.getElementById(&quot;spec_&quot; + spec.id);
+	
+	spec_list.className = &quot;ongoing&quot;;
+	spec_log.className = &quot;ongoing&quot;;
+};
+
+JSSpec.Logger.prototype.onSpecEnd = function(spec) {
+	var spec_list = document.getElementById(&quot;spec_&quot; + spec.id + &quot;_list&quot;);
+	var spec_log = document.getElementById(&quot;spec_&quot; + spec.id);
+	var examples = document.getElementById(&quot;spec_&quot; + spec.id + &quot;_examples&quot;);
+	var className = spec.hasException() ? &quot;exception&quot; : &quot;success&quot;;
+
+	spec_list.className = className;
+	spec_log.className = className;
+
+	if(JSSpec.options.autocollapse &amp;&amp; !spec.hasException()) examples.style.display = &quot;none&quot;;
+	
+	if(spec.exception) {
+		heading.appendChild(document.createTextNode(&quot; - &quot; + spec.exception.message));
+	}
+};
+
+JSSpec.Logger.prototype.onExampleStart = function(example) {
+	var li = document.getElementById(&quot;example_&quot; + example.id);
+	li.className = &quot;ongoing&quot;;
+};
+
+JSSpec.Logger.prototype.onExampleEnd = function(example) {
+	var li = document.getElementById(&quot;example_&quot; + example.id);
+	li.className = example.exception ? &quot;exception&quot; : &quot;success&quot;;
+	
+	if(example.exception) {
+		var div = document.createElement(&quot;DIV&quot;);
+		div.innerHTML = example.exception.message + &quot;&lt;p&gt;&lt;br /&gt;&quot; + &quot; at &quot; + example.exception.fileName + &quot;, line &quot; + example.exception.lineNumber + &quot;&lt;/p&gt;&quot;;
+		li.appendChild(div);
+	}
+	
+	var title = document.getElementById(&quot;title&quot;);
+	var runner = JSSpec.runner;
+	
+	title.className = runner.hasException() ? &quot;exception&quot; : &quot;success&quot;;
+	
+	this.finishedExamples++;
+	document.getElementById(&quot;total_failures&quot;).innerHTML = runner.getTotalFailures();
+	document.getElementById(&quot;total_errors&quot;).innerHTML = runner.getTotalErrors();
+	var progress = parseInt(this.finishedExamples / runner.totalExamples * 100);
+	document.getElementById(&quot;progress&quot;).innerHTML = progress;
+	document.getElementById(&quot;total_elapsed&quot;).innerHTML = (new Date().getTime() - this.startedAt.getTime()) / 1000;
+	
+	document.title = progress + &quot;%: &quot; + this._title;
+};
+
+/**
+ * IncludeMatcher
+ */
+JSSpec.IncludeMatcher = function(actual, expected, condition) {
+	this.actual = actual;
+	this.expected = expected;
+	this.condition = condition;
+	this.match = false;
+	this.explaination = this.makeExplain();
+};
+
+JSSpec.IncludeMatcher.createInstance = function(actual, expected, condition) {
+	return new JSSpec.IncludeMatcher(actual, expected, condition);
+};
+
+JSSpec.IncludeMatcher.prototype.matches = function() {
+	return this.match;
+};
+
+JSSpec.IncludeMatcher.prototype.explain = function() {
+	return this.explaination;
+};
+
+JSSpec.IncludeMatcher.prototype.makeExplain = function() {
+	if(typeof this.actual.length == 'undefined') {
+		return this.makeExplainForNotArray();
+	} else {
+		return this.makeExplainForArray();
+	}
+};
+
+JSSpec.IncludeMatcher.prototype.makeExplainForNotArray = function() {
+	if(this.condition) {
+		this.match = !!this.actual[this.expected];
+	} else {
+		this.match = !this.actual[this.expected];
+	}
+	
+	var sb = [];
+	sb.push('&lt;p&gt;actual value:&lt;/p&gt;');
+	sb.push('&lt;p style=&quot;margin-left:2em;&quot;&gt;' + JSSpec.util.inspect(this.actual, false, this.expected) + '&lt;/p&gt;');
+	sb.push('&lt;p&gt;should ' + (this.condition ? '' : 'not') + ' include:&lt;/p&gt;');
+	sb.push('&lt;p style=&quot;margin-left:2em;&quot;&gt;' + JSSpec.util.inspect(this.expected) + '&lt;/p&gt;');
+	return sb.join(&quot;&quot;);
+}
+;
+JSSpec.IncludeMatcher.prototype.makeExplainForArray = function() {
+	var matches;
+	if(this.condition) {
+		for(var i = 0; i &lt; this.actual.length; i++) {
+			matches = JSSpec.EqualityMatcher.createInstance(this.expected, this.actual[i]).matches();
+			if(matches) {
+				this.match = true;
+				break;
+			}
+		}
+	} else {
+		for(var i = 0; i &lt; this.actual.length; i++) {
+			matches = JSSpec.EqualityMatcher.createInstance(this.expected, this.actual[i]).matches();
+			if(matches) {
+				this.match = false;
+				break;
+			}
+		}
+	}
+	
+	if(this.match) return &quot;&quot;;
+	
+	var sb = [];
+	sb.push('&lt;p&gt;actual value:&lt;/p&gt;');
+	sb.push('&lt;p style=&quot;margin-left:2em;&quot;&gt;' + JSSpec.util.inspect(this.actual, false, this.condition ? null : i) + '&lt;/p&gt;');
+	sb.push('&lt;p&gt;should ' + (this.condition ? '' : 'not') + ' include:&lt;/p&gt;');
+	sb.push('&lt;p style=&quot;margin-left:2em;&quot;&gt;' + JSSpec.util.inspect(this.expected) + '&lt;/p&gt;');
+	return sb.join(&quot;&quot;);
+};
+
+/**
+ * PropertyLengthMatcher
+ */
+JSSpec.PropertyLengthMatcher = function(num, property, o, condition) {
+	this.num = num;
+	this.o = o;
+	this.property = property;
+	if((property == 'characters' || property == 'items') &amp;&amp; typeof o.length != 'undefined') {
+		this.property = 'length';
+	}
+	
+	this.condition = condition;
+	this.conditionMet = function(x) {
+		if(condition == 'exactly') return x.length == num;
+		if(condition == 'at least') return x.length &gt;= num;
+		if(condition == 'at most') return x.length &lt;= num;
+
+		throw &quot;Unknown condition '&quot; + condition + &quot;'&quot;;
+	};
+	this.match = false;
+	this.explaination = this.makeExplain();
+};
+
+JSSpec.PropertyLengthMatcher.prototype.makeExplain = function() {
+	if(this.o._type == 'String' &amp;&amp; this.property == 'length') {
+		this.match = this.conditionMet(this.o);
+		return this.match ? '' : this.makeExplainForString();
+	} else if(typeof this.o.length != 'undefined' &amp;&amp; this.property == &quot;length&quot;) {
+		this.match = this.conditionMet(this.o);
+		return this.match ? '' : this.makeExplainForArray();
+	} else if(typeof this.o[this.property] != 'undefined' &amp;&amp; this.o[this.property] != null) {
+		this.match = this.conditionMet(this.o[this.property]);
+		return this.match ? '' : this.makeExplainForObject();
+	} else if(typeof this.o[this.property] == 'undefined' || this.o[this.property] == null) {
+		this.match = false;
+		return this.makeExplainForNoProperty();
+	}
+
+	this.match = true;
+};
+
+JSSpec.PropertyLengthMatcher.prototype.makeExplainForString = function() {
+	var sb = [];
+	
+	var exp = this.num == 0 ?
+		'be an &lt;strong&gt;empty string&lt;/strong&gt;' :
+		'have &lt;strong&gt;' + this.condition + ' ' + this.num + ' characters&lt;/strong&gt;';
+	
+	sb.push('&lt;p&gt;actual value has &lt;strong&gt;' + this.o.length + ' characters&lt;/strong&gt;:&lt;/p&gt;');
+	sb.push('&lt;p style=&quot;margin-left:2em;&quot;&gt;' + JSSpec.util.inspect(this.o) + '&lt;/p&gt;');
+	sb.push('&lt;p&gt;but it should ' + exp + '.&lt;/p&gt;');
+	
+	return sb.join(&quot;&quot;);
+};
+
+JSSpec.PropertyLengthMatcher.prototype.makeExplainForArray = function() {
+	var sb = [];
+	
+	var exp = this.num == 0 ?
+		'be an &lt;strong&gt;empty array&lt;/strong&gt;' :
+		'have &lt;strong&gt;' + this.condition + ' ' + this.num + ' items&lt;/strong&gt;';
+
+	sb.push('&lt;p&gt;actual value has &lt;strong&gt;' + this.o.length + ' items&lt;/strong&gt;:&lt;/p&gt;');
+	sb.push('&lt;p style=&quot;margin-left:2em;&quot;&gt;' + JSSpec.util.inspect(this.o) + '&lt;/p&gt;');
+	sb.push('&lt;p&gt;but it should ' + exp + '.&lt;/p&gt;');
+	
+	return sb.join(&quot;&quot;);
+};
+
+JSSpec.PropertyLengthMatcher.prototype.makeExplainForObject = function() {
+	var sb = [];
+
+	var exp = this.num == 0 ?
+		'be &lt;strong&gt;empty&lt;/strong&gt;' :
+		'have &lt;strong&gt;' + this.condition + ' ' + this.num + ' ' + this.property + '.&lt;/strong&gt;';
+
+	sb.push('&lt;p&gt;actual value has &lt;strong&gt;' + this.o[this.property].length + ' ' + this.property + '&lt;/strong&gt;:&lt;/p&gt;');
+	sb.push('&lt;p style=&quot;margin-left:2em;&quot;&gt;' + JSSpec.util.inspect(this.o, false, this.property) + '&lt;/p&gt;');
+	sb.push('&lt;p&gt;but it should ' + exp + '.&lt;/p&gt;');
+	
+	return sb.join(&quot;&quot;);
+};
+
+JSSpec.PropertyLengthMatcher.prototype.makeExplainForNoProperty = function() {
+	var sb = [];
+	
+	sb.push('&lt;p&gt;actual value:&lt;/p&gt;');
+	sb.push('&lt;p style=&quot;margin-left:2em;&quot;&gt;' + JSSpec.util.inspect(this.o) + '&lt;/p&gt;');
+	sb.push('&lt;p&gt;should have &lt;strong&gt;' + this.condition + ' ' + this.num + ' ' + this.property + '&lt;/strong&gt; but there\'s no such property.&lt;/p&gt;');
+	
+	return sb.join(&quot;&quot;);
+};
+
+JSSpec.PropertyLengthMatcher.prototype.matches = function() {
+	return this.match;
+};
+
+JSSpec.PropertyLengthMatcher.prototype.explain = function() {
+	return this.explaination;
+};
+
+JSSpec.PropertyLengthMatcher.createInstance = function(num, property, o, condition) {
+	return new JSSpec.PropertyLengthMatcher(num, property, o, condition);
+};
+
+/**
+ * EqualityMatcher
+ */
+JSSpec.EqualityMatcher = {};
+
+JSSpec.EqualityMatcher.createInstance = function(expected, actual) {
+	if(expected == null || actual == null) {
+		return new JSSpec.NullEqualityMatcher(expected, actual);
+	} else if(expected._type &amp;&amp; expected._type == actual._type) {
+		if(expected._type == &quot;String&quot;) {
+			return new JSSpec.StringEqualityMatcher(expected, actual);
+		} else if(expected._type == &quot;Date&quot;) {
+			return new JSSpec.DateEqualityMatcher(expected, actual);
+		} else if(expected._type == &quot;Number&quot;) {
+			return new JSSpec.NumberEqualityMatcher(expected, actual);
+		} else if(expected._type == &quot;Array&quot;) {
+			return new JSSpec.ArrayEqualityMatcher(expected, actual);
+		} else if(expected._type == &quot;Boolean&quot;) {
+			return new JSSpec.BooleanEqualityMatcher(expected, actual);
+		}
+	}
+	
+	return new JSSpec.ObjectEqualityMatcher(expected, actual);
+};
+
+JSSpec.EqualityMatcher.basicExplain = function(expected, actual, expectedDesc, actualDesc) {
+	var sb = [];
+	
+	sb.push(actualDesc || '&lt;p&gt;actual value:&lt;/p&gt;');
+	sb.push('&lt;p style=&quot;margin-left:2em;&quot;&gt;' + JSSpec.util.inspect(actual) + '&lt;/p&gt;');
+	sb.push(expectedDesc || '&lt;p&gt;should be:&lt;/p&gt;');
+	sb.push('&lt;p style=&quot;margin-left:2em;&quot;&gt;' + JSSpec.util.inspect(expected) + '&lt;/p&gt;');
+	
+	return sb.join(&quot;&quot;);
+};
+
+JSSpec.EqualityMatcher.diffExplain = function(expected, actual) {
+	var sb = [];
+
+	sb.push('&lt;p&gt;diff:&lt;/p&gt;');
+	sb.push('&lt;p style=&quot;margin-left:2em;&quot;&gt;');
+	
+	var dmp = new diff_match_patch();
+	var diff = dmp.diff_main(expected, actual);
+	dmp.diff_cleanupEfficiency(diff);
+	
+	sb.push(JSSpec.util.inspect(dmp.diff_prettyHtml(diff), true));
+	
+	sb.push('&lt;/p&gt;');
+	
+	return sb.join(&quot;&quot;);
+};
+
+/**
+ * BooleanEqualityMatcher
+ */
+JSSpec.BooleanEqualityMatcher = function(expected, actual) {
+	this.expected = expected;
+	this.actual = actual;
+};
+
+JSSpec.BooleanEqualityMatcher.prototype.explain = function() {
+	var sb = [];
+	
+	sb.push('&lt;p&gt;actual value:&lt;/p&gt;');
+	sb.push('&lt;p style=&quot;margin-left:2em;&quot;&gt;' + JSSpec.util.inspect(this.actual) + '&lt;/p&gt;');
+	sb.push('&lt;p&gt;should be:&lt;/p&gt;');
+	sb.push('&lt;p style=&quot;margin-left:2em;&quot;&gt;' + JSSpec.util.inspect(this.expected) + '&lt;/p&gt;');
+	
+	return sb.join(&quot;&quot;);
+};
+
+JSSpec.BooleanEqualityMatcher.prototype.matches = function() {
+	return this.expected == this.actual;
+};
+
+/**
+ * NullEqualityMatcher
+ */
+JSSpec.NullEqualityMatcher = function(expected, actual) {
+	this.expected = expected;
+	this.actual = actual;
+};
+
+JSSpec.NullEqualityMatcher.prototype.matches = function() {
+	return this.expected == this.actual &amp;&amp; typeof this.expected == typeof this.actual;
+};
+
+JSSpec.NullEqualityMatcher.prototype.explain = function() {
+	return JSSpec.EqualityMatcher.basicExplain(this.expected, this.actual);
+};
+
+JSSpec.DateEqualityMatcher = function(expected, actual) {
+	this.expected = expected;
+	this.actual = actual;
+};
+
+JSSpec.DateEqualityMatcher.prototype.matches = function() {
+	return this.expected.getTime() == this.actual.getTime();
+};
+
+JSSpec.DateEqualityMatcher.prototype.explain = function() {
+	var sb = [];
+	
+	sb.push(JSSpec.EqualityMatcher.basicExplain(this.expected, this.actual));
+	sb.push(JSSpec.EqualityMatcher.diffExplain(this.expected.toString(), this.actual.toString()));
+
+	return sb.join(&quot;&quot;);
+};
+
+/**
+ * ObjectEqualityMatcher
+ */
+JSSpec.ObjectEqualityMatcher = function(expected, actual) {
+	this.expected = expected;
+	this.actual = actual;
+	this.match = this.expected == this.actual;
+	this.explaination = this.makeExplain();
+};
+
+JSSpec.ObjectEqualityMatcher.prototype.matches = function() {return this.match};
+
+JSSpec.ObjectEqualityMatcher.prototype.explain = function() {return this.explaination};
+
+JSSpec.ObjectEqualityMatcher.prototype.makeExplain = function() {
+	if(this.expected == this.actual) {
+		this.match = true;
+		return &quot;&quot;;
+	}
+	
+	if(JSSpec.util.isDomNode(this.expected)) {
+		return this.makeExplainForDomNode();
+	}
+	
+	var key, expectedHasItem, actualHasItem;
+
+	for(key in this.expected) {
+		expectedHasItem = this.expected[key] != null &amp;&amp; typeof this.expected[key] != 'undefined';
+		actualHasItem = this.actual[key] != null &amp;&amp; typeof this.actual[key] != 'undefined';
+		if(expectedHasItem &amp;&amp; !actualHasItem) return this.makeExplainForMissingItem(key);
+	}
+	for(key in this.actual) {
+		expectedHasItem = this.expected[key] != null &amp;&amp; typeof this.expected[key] != 'undefined';
+		actualHasItem = this.actual[key] != null &amp;&amp; typeof this.actual[key] != 'undefined';
+		if(actualHasItem &amp;&amp; !expectedHasItem) return this.makeExplainForUnknownItem(key);
+	}
+	
+	for(key in this.expected) {
+		var matcher = JSSpec.EqualityMatcher.createInstance(this.expected[key], this.actual[key]);
+		if(!matcher.matches()) return this.makeExplainForItemMismatch(key);
+	}
+		
+	this.match = true;
+};
+
+JSSpec.ObjectEqualityMatcher.prototype.makeExplainForDomNode = function(key) {
+	var sb = [];
+	
+	sb.push(JSSpec.EqualityMatcher.basicExplain(this.expected, this.actual));
+	
+	return sb.join(&quot;&quot;);
+};
+
+JSSpec.ObjectEqualityMatcher.prototype.makeExplainForMissingItem = function(key) {
+	var sb = [];
+
+	sb.push('&lt;p&gt;actual value has no item named &lt;strong&gt;' + JSSpec.util.inspect(key) + '&lt;/strong&gt;&lt;/p&gt;');
+	sb.push('&lt;p style=&quot;margin-left:2em;&quot;&gt;' + JSSpec.util.inspect(this.actual, false, key) + '&lt;/p&gt;');
+	sb.push('&lt;p&gt;but it should have the item whose value is &lt;strong&gt;' + JSSpec.util.inspect(this.expected[key]) + '&lt;/strong&gt;&lt;/p&gt;');
+	sb.push('&lt;p style=&quot;margin-left:2em;&quot;&gt;' + JSSpec.util.inspect(this.expected, false, key) + '&lt;/p&gt;');
+	
+	return sb.join(&quot;&quot;);
+};
+
+JSSpec.ObjectEqualityMatcher.prototype.makeExplainForUnknownItem = function(key) {
+	var sb = [];
+
+	sb.push('&lt;p&gt;actual value has item named &lt;strong&gt;' + JSSpec.util.inspect(key) + '&lt;/strong&gt;&lt;/p&gt;');
+	sb.push('&lt;p style=&quot;margin-left:2em;&quot;&gt;' + JSSpec.util.inspect(this.actual, false, key) + '&lt;/p&gt;');
+	sb.push('&lt;p&gt;but there should be no such item&lt;/p&gt;');
+	sb.push('&lt;p style=&quot;margin-left:2em;&quot;&gt;' + JSSpec.util.inspect(this.expected, false, key) + '&lt;/p&gt;');
+	
+	return sb.join(&quot;&quot;);
+};
+
+JSSpec.ObjectEqualityMatcher.prototype.makeExplainForItemMismatch = function(key) {
+	var sb = [];
+
+	sb.push('&lt;p&gt;actual value has an item named &lt;strong&gt;' + JSSpec.util.inspect(key) + '&lt;/strong&gt; whose value is &lt;strong&gt;' + JSSpec.util.inspect(this.actual[key]) + '&lt;/strong&gt;&lt;/p&gt;');
+	sb.push('&lt;p style=&quot;margin-left:2em;&quot;&gt;' + JSSpec.util.inspect(this.actual, false, key) + '&lt;/p&gt;');
+	sb.push('&lt;p&gt;but it\'s value should be &lt;strong&gt;' + JSSpec.util.inspect(this.expected[key]) + '&lt;/strong&gt;&lt;/p&gt;');
+	sb.push('&lt;p style=&quot;margin-left:2em;&quot;&gt;' + JSSpec.util.inspect(this.expected, false, key) + '&lt;/p&gt;');
+	
+	return sb.join(&quot;&quot;);
+};
+
+
+
+
+/**
+ * ArrayEqualityMatcher
+ */
+JSSpec.ArrayEqualityMatcher = function(expected, actual) {
+	this.expected = expected;
+	this.actual = actual;
+	this.match = this.expected == this.actual;
+	this.explaination = this.makeExplain();
+};
+
+JSSpec.ArrayEqualityMatcher.prototype.matches = function() {return this.match};
+
+JSSpec.ArrayEqualityMatcher.prototype.explain = function() {return this.explaination};
+
+JSSpec.ArrayEqualityMatcher.prototype.makeExplain = function() {
+	if(this.expected.length != this.actual.length) return this.makeExplainForLengthMismatch();
+	
+	for(var i = 0; i &lt; this.expected.length; i++) {
+		var matcher = JSSpec.EqualityMatcher.createInstance(this.expected[i], this.actual[i]);
+		if(!matcher.matches()) return this.makeExplainForItemMismatch(i);
+	}
+		
+	this.match = true;
+};
+
+JSSpec.ArrayEqualityMatcher.prototype.makeExplainForLengthMismatch = function() {
+	return JSSpec.EqualityMatcher.basicExplain(
+		this.expected,
+		this.actual,
+		'&lt;p&gt;but it should be &lt;strong&gt;' + this.expected.length + '&lt;/strong&gt;&lt;/p&gt;',
+		'&lt;p&gt;actual value has &lt;strong&gt;' + this.actual.length + '&lt;/strong&gt; items&lt;/p&gt;'
+	);
+};
+
+JSSpec.ArrayEqualityMatcher.prototype.makeExplainForItemMismatch = function(index) {
+	var postfix = [&quot;th&quot;, &quot;st&quot;, &quot;nd&quot;, &quot;rd&quot;, &quot;th&quot;][Math.min((index + 1) % 10,4)];
+	
+	var sb = [];
+
+	sb.push('&lt;p&gt;' + (index + 1) + postfix + ' item (index ' + index + ') of actual value is &lt;strong&gt;' + JSSpec.util.inspect(this.actual[index]) + '&lt;/strong&gt;:&lt;/p&gt;');
+	sb.push('&lt;p style=&quot;margin-left:2em;&quot;&gt;' + JSSpec.util.inspect(this.actual, false, index) + '&lt;/p&gt;');
+	sb.push('&lt;p&gt;but it should be &lt;strong&gt;' + JSSpec.util.inspect(this.expected[index]) + '&lt;/strong&gt;:&lt;/p&gt;');
+	sb.push('&lt;p style=&quot;margin-left:2em;&quot;&gt;' + JSSpec.util.inspect(this.expected, false, index) + '&lt;/p&gt;');
+	
+	return sb.join(&quot;&quot;);
+};
+
+/**
+ * NumberEqualityMatcher
+ */
+JSSpec.NumberEqualityMatcher = function(expected, actual) {
+	this.expected = expected;
+	this.actual = actual;
+};
+
+JSSpec.NumberEqualityMatcher.prototype.matches = function() {
+	if(this.expected == this.actual) return true;
+};
+
+JSSpec.NumberEqualityMatcher.prototype.explain = function() {
+	return JSSpec.EqualityMatcher.basicExplain(this.expected, this.actual);
+};
+
+/**
+ * StringEqualityMatcher
+ */
+JSSpec.StringEqualityMatcher = function(expected, actual) {
+	this.expected = expected;
+	this.actual = actual;
+};
+
+JSSpec.StringEqualityMatcher.prototype.matches = function() {
+	if(this.expected == this.actual) return true;
+};
+
+JSSpec.StringEqualityMatcher.prototype.explain = function() {
+	var sb = [];
+
+	sb.push(JSSpec.EqualityMatcher.basicExplain(this.expected, this.actual));
+	sb.push(JSSpec.EqualityMatcher.diffExplain(this.expected, this.actual));	
+	return sb.join(&quot;&quot;);
+};
+
+/**
+ * PatternMatcher
+ */
+JSSpec.PatternMatcher = function(actual, pattern, condition) {
+	this.actual = actual;
+	this.pattern = pattern;
+	this.condition = condition;
+	this.match = false;
+	this.explaination = this.makeExplain();
+};
+
+JSSpec.PatternMatcher.createInstance = function(actual, pattern, condition) {
+	return new JSSpec.PatternMatcher(actual, pattern, condition);
+};
+
+JSSpec.PatternMatcher.prototype.makeExplain = function() {
+	var sb;
+	if(this.actual == null || this.actual._type != 'String') {
+		sb = [];
+		sb.push('&lt;p&gt;actual value:&lt;/p&gt;');
+		sb.push('&lt;p style=&quot;margin-left:2em;&quot;&gt;' + JSSpec.util.inspect(this.actual) + '&lt;/p&gt;');
+		sb.push('&lt;p&gt;should ' + (this.condition ? '' : 'not') + ' match with pattern:&lt;/p&gt;');
+		sb.push('&lt;p style=&quot;margin-left:2em;&quot;&gt;' + JSSpec.util.inspect(this.pattern) + '&lt;/p&gt;');
+		sb.push('&lt;p&gt;but pattern matching cannot be performed.&lt;/p&gt;');
+		return sb.join(&quot;&quot;);
+	} else {
+		this.match = this.condition == !!this.actual.match(this.pattern);
+		if(this.match) return &quot;&quot;;
+		
+		sb = [];
+		sb.push('&lt;p&gt;actual value:&lt;/p&gt;');
+		sb.push('&lt;p style=&quot;margin-left:2em;&quot;&gt;' + JSSpec.util.inspect(this.actual) + '&lt;/p&gt;');
+		sb.push('&lt;p&gt;should ' + (this.condition ? '' : 'not') + ' match with pattern:&lt;/p&gt;');
+		sb.push('&lt;p style=&quot;margin-left:2em;&quot;&gt;' + JSSpec.util.inspect(this.pattern) + '&lt;/p&gt;');
+		return sb.join(&quot;&quot;);
+	}
+};
+
+JSSpec.PatternMatcher.prototype.matches = function() {
+	return this.match;
+};
+
+JSSpec.PatternMatcher.prototype.explain = function() {
+	return this.explaination;
+};
+
+/**
+ * Domain Specific Languages
+ */
+JSSpec.DSL = {};
+
+JSSpec.DSL.forString = {
+	normalizeHtml: function() {
+		var html = this;
+		
+		// Uniformize quotation, turn tag names and attribute names into lower case
+		html = html.replace(/&lt;(\/?)(\w+)([^&gt;]*?)&gt;/img, function(str, closingMark, tagName, attrs) {
+			var sortedAttrs = JSSpec.util.sortHtmlAttrs(JSSpec.util.correctHtmlAttrQuotation(attrs).toLowerCase())
+			return &quot;&lt;&quot; + closingMark + tagName.toLowerCase() + sortedAttrs + &quot;&gt;&quot;
+		});
+		
+		// validation self-closing tags
+		html = html.replace(/&lt;(br|hr|img)([^&gt;]*?)&gt;/mg, function(str, tag, attrs) {
+			return &quot;&lt;&quot; + tag + attrs + &quot; /&gt;&quot;;
+		});
+		
+		// append semi-colon at the end of style value
+		html = html.replace(/style=&quot;(.*?)&quot;/mg, function(str, styleStr) {
+			styleStr = JSSpec.util.sortStyleEntries(styleStr.strip()); // for Safari
+			if(styleStr.charAt(styleStr.length - 1) != ';') styleStr += &quot;;&quot;
+			
+			return 'style=&quot;' + styleStr + '&quot;'
+		});
+		
+		// sort style entries
+		
+		// remove empty style attributes
+		html = html.replace(/ style=&quot;;&quot;/mg, &quot;&quot;);
+		
+		// remove new-lines
+		html = html.replace(/\r/mg, '');
+		html = html.replace(/\n/mg, '');
+			
+		return html;
+	}
+};
+
+
+
+JSSpec.DSL.describe = function(context, entries) {
+	JSSpec.specs.push(new JSSpec.Spec(context, entries));
+};
+
+JSSpec.DSL.value_of = function(target) {
+	if(JSSpec._secondPass) return {};
+	
+	var subject = new JSSpec.DSL.Subject(target);
+	return subject;
+};
+
+JSSpec.DSL.Subject = function(target) {
+	this.target = target;
+};
+
+JSSpec.DSL.Subject.prototype._type = 'Subject';
+
+JSSpec.DSL.Subject.prototype.should_fail = function(message) {
+	JSSpec._assertionFailure = {message:message};
+	throw JSSpec._assertionFailure;
+};
+
+JSSpec.DSL.Subject.prototype.should_be = function(expected) {
+	var matcher = JSSpec.EqualityMatcher.createInstance(expected, this.target);
+	if(!matcher.matches()) {
+		JSSpec._assertionFailure = {message:matcher.explain()};
+		throw JSSpec._assertionFailure;
+	}
+};
+
+JSSpec.DSL.Subject.prototype.should_not_be = function(expected) {
+	// TODO JSSpec.EqualityMatcher should support 'condition'
+	var matcher = JSSpec.EqualityMatcher.createInstance(expected, this.target);
+	if(matcher.matches()) {
+		JSSpec._assertionFailure = {message:&quot;'&quot; + this.target + &quot;' should not be '&quot; + expected + &quot;'&quot;};
+		throw JSSpec._assertionFailure;
+	}
+};
+
+JSSpec.DSL.Subject.prototype.should_be_empty = function() {
+	this.should_have(0, this.getType() == 'String' ? 'characters' : 'items');
+};
+
+JSSpec.DSL.Subject.prototype.should_not_be_empty = function() {
+	this.should_have_at_least(1, this.getType() == 'String' ? 'characters' : 'items');
+};
+
+JSSpec.DSL.Subject.prototype.should_be_true = function() {
+	this.should_be(true);
+};
+
+JSSpec.DSL.Subject.prototype.should_be_false = function() {
+	this.should_be(false);
+};
+
+JSSpec.DSL.Subject.prototype.should_be_null = function() {
+	this.should_be(null);
+};
+
+JSSpec.DSL.Subject.prototype.should_be_undefined = function() {
+	this.should_be(undefined);
+};
+
+JSSpec.DSL.Subject.prototype.should_not_be_null = function() {
+	this.should_not_be(null);
+};
+
+JSSpec.DSL.Subject.prototype.should_not_be_undefined = function() {
+	this.should_not_be(undefined);
+};
+
+JSSpec.DSL.Subject.prototype._should_have = function(num, property, condition) {
+	var matcher = JSSpec.PropertyLengthMatcher.createInstance(num, property, this.target, condition);
+	if(!matcher.matches()) {
+		JSSpec._assertionFailure = {message:matcher.explain()};
+		throw JSSpec._assertionFailure;
+	}
+};
+
+JSSpec.DSL.Subject.prototype.should_have = function(num, property) {
+	this._should_have(num, property, &quot;exactly&quot;);
+};
+
+JSSpec.DSL.Subject.prototype.should_have_exactly = function(num, property) {
+	this._should_have(num, property, &quot;exactly&quot;);
+};
+
+JSSpec.DSL.Subject.prototype.should_have_at_least = function(num, property) {
+	this._should_have(num, property, &quot;at least&quot;);
+};
+
+JSSpec.DSL.Subject.prototype.should_have_at_most = function(num, property) {
+	this._should_have(num, property, &quot;at most&quot;);
+};
+
+JSSpec.DSL.Subject.prototype.should_include = function(expected) {
+	var matcher = JSSpec.IncludeMatcher.createInstance(this.target, expected, true);
+	if(!matcher.matches()) {
+		JSSpec._assertionFailure = {message:matcher.explain()};
+		throw JSSpec._assertionFailure;
+	}
+};
+
+JSSpec.DSL.Subject.prototype.should_not_include = function(expected) {
+	var matcher = JSSpec.IncludeMatcher.createInstance(this.target, expected, false);
+	if(!matcher.matches()) {
+		JSSpec._assertionFailure = {message:matcher.explain()};
+		throw JSSpec._assertionFailure;
+	}
+};
+
+JSSpec.DSL.Subject.prototype.should_match = function(pattern) {
+	var matcher = JSSpec.PatternMatcher.createInstance(this.target, pattern, true);
+	if(!matcher.matches()) {
+		JSSpec._assertionFailure = {message:matcher.explain()};
+		throw JSSpec._assertionFailure;
+	}
+}
+JSSpec.DSL.Subject.prototype.should_not_match = function(pattern) {
+	var matcher = JSSpec.PatternMatcher.createInstance(this.target, pattern, false);
+	if(!matcher.matches()) {
+		JSSpec._assertionFailure = {message:matcher.explain()};
+		throw JSSpec._assertionFailure;
+	}
+};
+
+JSSpec.DSL.Subject.prototype.getType = function() {
+	if(typeof this.target == 'undefined') {
+		return 'undefined';
+	} else if(this.target == null) {
+		return 'null';
+	} else if(this.target._type) {
+		return this.target._type;
+	} else if(JSSpec.util.isDomNode(this.target)) {
+		return 'DomNode';
+	} else {
+		return 'object';
+	}
+};
+
+/**
+ * Utilities
+ */
+JSSpec.util = {
+	escapeTags: function(string) {
+		return string.replace(/&lt;/img, '&amp;lt;').replace(/&gt;/img, '&amp;gt;');
+	},
+	parseOptions: function(defaults) {
+		var options = defaults;
+		
+		var url = location.href;
+		var queryIndex = url.indexOf('?');
+		if(queryIndex == -1) return options;
+		
+		var query = url.substring(queryIndex + 1);
+		var pairs = query.split('&amp;');
+		for(var i = 0; i &lt; pairs.length; i++) {
+			var tokens = pairs[i].split('=');
+			options[tokens[0]] = tokens[1];
+		}
+		
+		return options;
+	},
+	correctHtmlAttrQuotation: function(html) {
+		html = html.replace(/(\w+)=['&quot;]([^'&quot;]+)['&quot;]/mg,function (str, name, value) {return name + '=' + '&quot;' + value + '&quot;';});
+		html = html.replace(/(\w+)=([^ '&quot;]+)/mg,function (str, name, value) {return name + '=' + '&quot;' + value + '&quot;';});
+		html = html.replace(/'/mg, '&quot;');
+		
+		return html;
+	},
+	sortHtmlAttrs: function(html) {
+		var attrs = [];
+		html.replace(/((\w+)=&quot;[^&quot;]+&quot;)/mg, function(str, matched) {
+			attrs.push(matched);
+		});
+		return attrs.length == 0 ? &quot;&quot; : &quot; &quot; + attrs.sort().join(&quot; &quot;);
+	},
+	sortStyleEntries: function(styleText) {
+		var entries = styleText.split(/; /);
+		return entries.sort().join(&quot;; &quot;);
+	},
+	escapeHtml: function(str) {
+		if(!this._div) {
+			this._div = document.createElement(&quot;DIV&quot;);
+			this._text = document.createTextNode('');
+			this._div.appendChild(this._text);
+		}
+		this._text.data = str;
+		return this._div.innerHTML;
+	},
+	isDomNode: function(o) {
+		// TODO: make it more stricter
+		return (typeof o.nodeName == 'string') &amp;&amp; (typeof o.nodeType == 'number');
+	},
+	inspectDomPath: function(o) {
+		var sb = [];
+		while(o &amp;&amp; o.nodeName != '#document' &amp;&amp; o.parent) {
+			var siblings = o.parentNode.childNodes;
+			for(var i = 0; i &lt; siblings.length; i++) {
+				if(siblings[i] == o) {
+					sb.push(o.nodeName + (i == 0 ? '' : '[' + i + ']'));
+					break;
+				}
+			}
+			o = o.parentNode;
+		}
+		return sb.join(&quot; &amp;gt; &quot;);
+	},
+	inspectDomNode: function(o) {
+		if(o.nodeType == 1) {
+			var nodeName = o.nodeName.toLowerCase();
+			var sb = [];
+			sb.push('&lt;span class=&quot;dom_value&quot;&gt;');
+			sb.push(&quot;&amp;lt;&quot;);
+			sb.push(nodeName);
+			
+			var attrs = o.attributes;
+			for(var i = 0; i &lt; attrs.length; i++) {
+				if(
+					attrs[i].nodeValue &amp;&amp;
+					attrs[i].nodeName != 'contentEditable' &amp;&amp;
+					attrs[i].nodeName != 'style' &amp;&amp;
+					typeof attrs[i].nodeValue != 'function'
+				) sb.push(' &lt;span class=&quot;dom_attr_name&quot;&gt;' + attrs[i].nodeName.toLowerCase() + '&lt;/span&gt;=&lt;span class=&quot;dom_attr_value&quot;&gt;&quot;' + attrs[i].nodeValue + '&quot;&lt;/span&gt;');
+			}
+			if(o.style &amp;&amp; o.style.cssText) {
+				sb.push(' &lt;span class=&quot;dom_attr_name&quot;&gt;style&lt;/span&gt;=&lt;span class=&quot;dom_attr_value&quot;&gt;&quot;' + o.style.cssText + '&quot;&lt;/span&gt;');
+			}
+			sb.push('&amp;gt;');
+			sb.push(JSSpec.util.escapeHtml(o.innerHTML));
+			sb.push('&amp;lt;/' + nodeName + '&amp;gt;');
+			sb.push(' &lt;span class=&quot;dom_path&quot;&gt;(' + JSSpec.util.inspectDomPath(o) + ')&lt;/span&gt;' );
+			sb.push('&lt;/span&gt;');
+			return sb.join(&quot;&quot;);
+		} else if(o.nodeType == 3) {
+			return '&lt;span class=&quot;dom_value&quot;&gt;#text ' + o.nodeValue + '&lt;/span&gt;';
+		} else {
+			return '&lt;span class=&quot;dom_value&quot;&gt;UnknownDomNode&lt;/span&gt;';
+		}
+	},
+	inspect: function(o, dontEscape, emphasisKey) {
+		var sb, inspected;
+
+		if(typeof o == 'undefined') return '&lt;span class=&quot;undefined_value&quot;&gt;undefined&lt;/span&gt;';
+		if(o == null) return '&lt;span class=&quot;null_value&quot;&gt;null&lt;/span&gt;';
+		if(o._type == 'String') return '&lt;span class=&quot;string_value&quot;&gt;&quot;' + (dontEscape ? o : JSSpec.util.escapeHtml(o)) + '&quot;&lt;/span&gt;';
+
+		if(o._type == 'Date') {
+			return '&lt;span class=&quot;date_value&quot;&gt;&quot;' + o.toString() + '&quot;&lt;/span&gt;';
+		}
+		
+		if(o._type == 'Number') return '&lt;span class=&quot;number_value&quot;&gt;' + (dontEscape ? o : JSSpec.util.escapeHtml(o)) + '&lt;/span&gt;';
+		
+		if(o._type == 'Boolean') return '&lt;span class=&quot;boolean_value&quot;&gt;' + o + '&lt;/span&gt;';
+
+		if(o._type == 'RegExp') return '&lt;span class=&quot;regexp_value&quot;&gt;' + JSSpec.util.escapeHtml(o.toString()) + '&lt;/span&gt;';
+
+		if(JSSpec.util.isDomNode(o)) return JSSpec.util.inspectDomNode(o);
+
+		if(o._type == 'Array' || typeof o.length != 'undefined') {
+			sb = [];
+			for(var i = 0; i &lt; o.length; i++) {
+				inspected = JSSpec.util.inspect(o[i]);
+				sb.push(i == emphasisKey ? ('&lt;strong&gt;' + inspected + '&lt;/strong&gt;') : inspected);
+			}
+			return '&lt;span class=&quot;array_value&quot;&gt;[' + sb.join(', ') + ']&lt;/span&gt;';
+		}
+		
+		// object
+		sb = [];
+		for(var key in o) {
+			if(key == 'should') continue;
+			
+			inspected = JSSpec.util.inspect(key) + &quot;:&quot; + JSSpec.util.inspect(o[key]);
+			sb.push(key == emphasisKey ? ('&lt;strong&gt;' + inspected + '&lt;/strong&gt;') : inspected);
+		}
+		return '&lt;span class=&quot;object_value&quot;&gt;{' + sb.join(', ') + '}&lt;/span&gt;';
+	}
+};
+
+describe = JSSpec.DSL.describe;
+behavior_of = JSSpec.DSL.describe;
+value_of = JSSpec.DSL.value_of;
+expect = JSSpec.DSL.value_of; // @deprecated
+
+String.prototype._type = &quot;String&quot;;
+Number.prototype._type = &quot;Number&quot;;
+Date.prototype._type = &quot;Date&quot;;
+Array.prototype._type = &quot;Array&quot;;
+Boolean.prototype._type = &quot;Boolean&quot;;
+RegExp.prototype._type = &quot;RegExp&quot;;
+
+var targets = [Array.prototype, Date.prototype, Number.prototype, String.prototype, Boolean.prototype, RegExp.prototype];
+
+String.prototype.normalizeHtml = JSSpec.DSL.forString.normalizeHtml;
+String.prototype.asHtml = String.prototype.normalizeHtml; //@deprecated
+
+
+
+/**
+ * Main
+ */
+JSSpec.defaultOptions = {
+	autorun: 1,
+	specIdBeginsWith: 0,
+	exampleIdBeginsWith: 0,
+	autocollapse: 1
+};
+JSSpec.options = JSSpec.util.parseOptions(JSSpec.defaultOptions);
+
+JSSpec.Spec.id = JSSpec.options.specIdBeginsWith;
+JSSpec.Example.id = JSSpec.options.exampleIdBeginsWith;
+
+
+
+window.onload = function() {
+	if(JSSpec.specs.length &gt; 0) {
+		if(!JSSpec.options.inSuite) {
+			JSSpec.runner = new JSSpec.Runner(JSSpec.specs, new JSSpec.Logger());
+			if(JSSpec.options.rerun) {
+				JSSpec.runner.rerun(decodeURIComponent(JSSpec.options.rerun));
+			} else {
+				JSSpec.runner.run();
+			}
+		} else {
+			// in suite, send all specs to parent
+			var parentWindow = window.frames.parent.window;
+			for(var i = 0; i &lt; JSSpec.specs.length; i++) {
+				parentWindow.JSSpec.specs.push(JSSpec.specs[i]);
+			}
+		}
+	} else {
+		var links = document.getElementById('list').getElementsByTagName('A');
+		var frameContainer = document.createElement('DIV');
+		frameContainer.style.display = 'none';
+		document.body.appendChild(frameContainer);
+		
+		for(var i = 0; i &lt; links.length; i++) {
+			var frame = document.createElement('IFRAME');
+			frame.src = links[i].href + '?inSuite=0&amp;specIdBeginsWith=' + (i * 10000) + '&amp;exampleIdBeginsWith=' + (i * 10000);
+			frameContainer.appendChild(frame);
+		}
+	}
+}
\ No newline at end of file</diff>
      <filename>Specs/Assets/Scripts/JSSpec.js</filename>
    </modified>
    <modified>
      <diff>@@ -210,10 +210,6 @@ p.left, p.uri {
 	font-family: Monaco, Courier New, monospace;
 }
 
-li div {
-	display: none;
-}
-
 p#footer {
 	text-align: right;
 	padding: 10px 16px 0 0;</diff>
      <filename>Specs/Assets/Styles/Specs.css</filename>
    </modified>
  </modified>
  <removed type="array"/>
  <parents type="array">
    <parent>
      <id>311fdd0bb3440c9d3299c3848066f8d40771f2d6</id>
    </parent>
  </parents>
  <author>
    <name>Bryan J Swift</name>
    <email>bryan.j.swift@gmail.com</email>
  </author>
  <url>http://github.com/bryanjswift/mootools-plugins/commit/b7e00b52e8c8177f8ca7cb7b48f710324510ff34</url>
  <id>b7e00b52e8c8177f8ca7cb7b48f710324510ff34</id>
  <committed-date>2008-07-26T16:18:24-07:00</committed-date>
  <authored-date>2008-07-26T16:18:24-07:00</authored-date>
  <message>updated asset folder from mootools-core</message>
  <tree>912811402e549451c804b961edc92782e6190a7c</tree>
  <committer>
    <name>Bryan J Swift</name>
    <email>bryan.j.swift@gmail.com</email>
  </committer>
</commit>
