/* export HashParams */
(function(window) {
    "use strict";
    function HashParams() {
        var params;
        if (Array.isArray(arguments[0])) {
            params = arguments[0];
        } else {
            params = Array.prototype.slice.call(arguments);
        }
        this.params = params.map(function(param, index) {
            if (typeof param === "string") {
                var matches = /^([^:]*):?(.*)$/.exec(param);
                var paramName = matches[1];
                if (paramName === "") {
                    throw new Error("HashParams: Invalid parameter name: " + param);
                }
                var paramType = matches[2] || "scalar";
                if (!(paramType in HashParams.types)) {
                    throw new Error("HashParams: Invalid parameter type: " + param);
                }
                return new HashParams.types[paramType](paramName);
            } else if (param && param.name) {
                return param;
            } else {
                throw new Error("HashParams: Invalid parameter definition at index " + index);
            }
        });
        this.setHash("");
    }
    HashParams.prototype = {
        _cloneValues: function() {
            var newValues = {};
            this.params.forEach(function(param) {
                if (param.name in this.values) {
                    newValues[param.name] = this.values[param.name];
                }
            }, this);
            return newValues;
        },
        _encode: function(string) {
            // Based on RFC 3986 (see http://stackoverflow.com/a/2849800/87399), but
            // we also encode ',', ';', and '=' since we give them special meaning.
            return string.replace(/[^-!$&'()*+./0-9:?@A-Z_a-z~]/g, encodeURIComponent);
        },
        _findParam: function(name) {
            for (var i = 0; i < this.params.length; ++i) {
                var param = this.params[i];
                if (param.name === name) {
                    return param;
                }
            }
        },
        _getEmptyValues: function() {
            var values = {};
            this.params.forEach(function(param) {
                values[param.name] = param.getEmptyValue();
            });
            return values;
        },
        _hashToStrings: function(hash) {
            var hashData = (hash || "").replace(/^#/, "");
            var result = {};
            hashData.split(";").forEach(function(arg) {
                var pair = arg.split("=");
                result[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]);
            });
            return result;
        },
        _mergeHashString: function(values, name, hashString) {
            var param = this._findParam(name);
            if (param) {
                values[name] = hashString;
            }
        },
        getHash: function() {
            var segments = [];
            this.params.forEach(function(param) {
                if (this.values[param.name]) {
                    var segment = this._encode(param.name) + "=" + this._encode(this.values[param.name]);
                    segments.push(segment);
                }
            }, this);
            return "#" + segments.join(";");
        },
        setHash: function(hash) {
            var values = this._getEmptyValues();
            var hashStrings = this._hashToStrings(hash);
            for (var name in hashStrings) {
                this._mergeHashString(values, name, hashStrings[name]);
            }
            this.values = values;
        },
        with: function(name, value) {
            var newParams = new HashParams(this.params);
            var newValues = this._cloneValues();
            this._mergeHashString(newValues, name, value);
            newParams.values = newValues;
            return newParams;
        }
    };

    HashParams.types = {};
    HashParams.defineType = function(properties) {
        var requiredProperties = "name getEmptyValue";
        requiredProperties.split(" ").forEach(function(requiredProperty) {
            if (!(requiredProperty in properties)) {
                throw new Error("Call to defineType is missing required property " + requiredProperty);
            }
        });

        function paramType(name) {
            this.name = name;
        }
        paramType.prototype = {
            getEmptyValue: properties.getEmptyValue
        };
        HashParams.types[properties.name] = paramType;
    };
    HashParams.defineType({
        name: "scalar",
        getEmptyValue: function() { return ""; }
    });
    HashParams.defineType({
        name: "set",
        getEmptyValue: function() { return new Set(); }
    });

    window.HashParams = HashParams;
})(this);

@@ -2073,89 +2073,173 @@
    _
-getEmptyValues: function() %7B%0A            var values = %7B%7D;%0A            this.params
+forEachHashString: function(hash, callback) %7B%0A            var hashData = (hash %7C%7C %22%22).replace(/%5E#/, %22%22);%0A            var result = %7B%7D;%0A            hashData.split(%22;%22)
 .for
@@ -2244,37 +2244,35 @@
 orEach(function(
-p
 ar
-am
+g
 ) %7B%0A            
@@ -2281,47 +2281,114 @@
   va
-lues%5Bparam.name%5D
+r pair
  = 
-p
 ar
-am.getEmptyValue(
+g.split(%22=%22);%0A                callback(decodeURIComponent(pair%5B0%5D), decodeURIComponent(pair%5B1%5D)
 );%0A 
@@ -2421,22 +2421,22 @@
  return 
-values
+result
 ;%0A      
@@ -2449,28 +2449,29 @@
        _
-hashToString
+getEmptyValue
 s: funct
@@ -2466,36 +2466,32 @@
 alues: function(
-hash
 ) %7B%0A            
@@ -2498,111 +2498,44 @@
 var 
-hashData = (hash %7C%7C %22%22).replace(/%5E#/, %22%22);%0A            var result = %7B%7D;%0A            hashData.split(%22;%22)
+values = %7B%7D;%0A            this.params
 .for
@@ -2540,35 +2540,37 @@
 orEach(function(
+p
 ar
-g
+am
 ) %7B%0A            
@@ -2579,113 +2579,47 @@
   va
-r pair
+lues%5Bparam.name%5D
  = 
+p
 ar
-g.split(%22=%22);%0A                result%5BdecodeURIComponent(pair%5B0%5D)%5D = decodeURIComponent(pair%5B1%5D
+am.getEmptyValue(
 );%0A 
@@ -2644,38 +2644,38 @@
          return 
-result
+values
 ;%0A        %7D,%0A   
@@ -3308,24 +3308,53 @@
 ion(hash) %7B%0A
+            var self = this;%0A
             
@@ -3406,93 +3406,64 @@
     
-var hashStrings = this._h
+this._forEachH
 ash
-To
 String
-s
 (hash
-);%0A            for (var name in hash
+, function(name, param
 String
-s
 ) %7B%0A
@@ -3470,36 +3470,36 @@
                 
-this
+self
 ._mergeHashStrin
@@ -3518,25 +3518,19 @@
 me, 
-hash
+param
 String
-s%5Bname%5D
 );%0A 
@@ -3533,32 +3533,34 @@
 );%0A            %7D
+);
 %0A            thi

Replace _hashToStrings with _forEachHashString.