/* IMPORTANT NOTE:  
 *
 * IF MODIFYING ANY FUNCTIONS IN THIS LIBARAY FILE, UPDATE 
   THE fn AND deps ARGS IF APPLICABLE. ANY NEW FUNCTIONS 
   SHOULD ALSO INCLUDE THE BUILD_JS_PACKAGES WRAPPER
   TAGS SO THAT THE FUNCTIONS CAN BE BUILT INTO JS PACKAGES.
 */
 
/* NOTE 2 : much of the data checking fns need to be betterified,
            a la isArray().
 */

/* BUILD_JS_PACKAGES: library code
   fn: copyObject
   deps: isNull, isUndefined, isBoolean, isNumber, isString, isFunction, isArray, isGenericObject, isSubclassObject
 */

function copyObject (data) {
    var to_data;
    if (isNull(data)) {
        to_data = null;
    } 
    else if (isUndefined(data)) {
        to_data = undefined;
    } 
    else if (isBoolean(data) || isNumber(data) || isString(data)) {
        to_data = data;
    } 
    else if (isFunction(data)) {
        to_data = eval(data.toString());
    } 
    else if (isArray(data)) {
        to_data = [ ];
        for (var i = 0; i < data.length; i++) {
            to_data[i] = copyObject (data[i]);
        }
    } 
    else if (isGenericObject(data))  {
        to_data = {};
        for (var key in data) {
            to_data[key] = copyObject (data[key]);
        }
    } 
    else if (isSubclassObject(data)) {
        return data;
    } 
    else {
        alert("Unknown data type");
        return data;
    }

    return to_data;
}

/* /BUILD_JS_PACKAGES */


/* BUILD_JS_PACKAGES: library code
   fn: dumper
   deps: _getDumperData
 */

/* Simulates, in JS, Perl's Data::Dumper function */

function dumper (obj, compact)
{
    var data = _getDumperData (obj, null, compact);
    //alert(data);
    //data = _formatDumperData(data);
    return data;
}

/* /BUILD_JS_PACKAGES */


/* BUILD_JS_PACKAGES: library code
   fn: objToJson
   deps: dumper
 */

/* returns a json string for any js data structure */

function objToJson (obj)
{
    return '(' + dumper (obj, true) + ')';
}

/* /BUILD_JS_PACKAGES */


/* BUILD_JS_PACKAGES: library code
   fn: _getDumperData
   deps: isNull, isUndefined, isBoolean, isNumber, isRegExp, isString, isFunction, isArray, isGenericObject, isSubclassObject, _calcPadding, _formatObj
 */

var orig_lev = 0;

function _getDumperData (data, lev, compact)
{
    lev = lev || orig_lev;
    
    if (isNull (data)) {
        return _calcPadding (lev, compact) + "null";
    }
    else if (isUndefined (data)) {
        return _calcPadding (lev, compact) + "undefined";
    }
    else if (isBoolean (data) || isNumber (data) || isRegExp (data)) {
        return _calcPadding (lev, compact) + data;
    }
    else if (isString (data)) {
        data = data.replace(/\\/g, "\\\\" );
        data = data.replace(/\'/g, "\\'");
        return _calcPadding (lev, compact) + "'" + data + "'";
    }
    else if (isFunction (data)) {
        return _calcPadding (lev, compact) + data.toString();
    }
    else if (isArray (data)) {
        var array_vals = [ ];
        var array_sep = compact ? ',' : ', ';
        for (var i = 0; i < data.length; i++) {
            array_vals.push(_getDumperData (data[i], lev+1, compact));
        }
        return _formatObj (array_vals.join(array_sep), '[', ']', lev+1, compact);
    } 
    else if (isGenericObject (data) || isSubclassObject (data)) {
        var hash_vals = [ ];
        var hash_sep = compact ? ',' : ', ';
        var hash_keyval_sep = compact ? ':' : ': ';
        for (var key in data) { 
            if (typeof data[key] == "function") {
                continue;
            }
            hash_vals.push(_getDumperData (key, lev+1, compact) + hash_keyval_sep + 
                           _getDumperData (data[key], lev+1, compact));
        }
        return _formatObj (hash_vals.join(hash_sep), '{', '}', lev+1, compact);
    } 
    else if (isSubclassObject (data)) {
        return _calcPadding (lev, compact) + "(subclass object)";
    }
    else {
        return _calcPadding (lev, compact) + "(unknown data type)";
    }
}

/* /BUILD_JS_PACKAGES */


/* BUILD_JS_PACKAGES: library code
   fn: _formatObj
   deps: _calcPadding, 
 */

function _formatObj (obj, open_sep, close_sep, this_lev, compact)
{
    if (compact) {
        return open_sep + obj + close_sep;
    }
    else {
        var pad = _calcPadding (this_lev, compact);
        return "\n" + pad + open_sep + "\n" + obj + "\n" + pad + close_sep;
    }
}

/* /BUILD_JS_PACKAGES */


/* BUILD_JS_PACKAGES: library code
   fn: _calcPadding
 */

function _calcPadding (this_lev, compact)
{
    var pad = compact ? '' : '   ';
    var str = '';
    for (var i = this_lev; i > 0; i--) {
        str += pad;
    }
    return str;
}

/* /BUILD_JS_PACKAGES */


/* BUILD_JS_PACKAGES: library code
   fn: _insertLineBreaks
 */

function _insertLineBreaks(str)
{
  if (str.length > 50)
    return "\n" + str + "\n";
  else
    return str;
}

/* /BUILD_JS_PACKAGES */


/* BUILD_JS_PACKAGES: library code
   fn: _formatDumperData
   deps: _calcPadding
 */

function _formatDumperData(data_str)
{
  var pad = '  ';
  var in_quoted_str = false;
  var formatted_str = '';
  var data = data_str.split("");
  for (var i = 0; i < data.length; i++)
  { 
    alert(formatted_str + "\n" + data[i] + "\n\nquoted? " + in_quoted_str)
    if (! data[i])
      continue;
    if (data[i] == "'" || data[i] == '"')
    {
      if (! in_quoted_str)
        in_quoted_str = true;
      else
        in_quoted_str = false;
      formatted_str += data[i];
    }
    else if (in_quoted_str)
      formatted_str += data[i];
    else if (data[i].match(/[\[\{]/))
    {
      formatted_str += _calcPadding(lev,pad) + data[i] + "\n";
      lev++;
    }
    else if (data[i].match(/[\]\}]/))
    {
      lev--;
      formatted_str += "\n" + _calcPadding(lev,pad) + data[i] + "\n";      
    }
    else
      formatted_str += data[i]
  }
  return formatted_str;
}

/* /BUILD_JS_PACKAGES */

/* A series of data-type determination functions */
 
// (test code)
// var vnull = null;
// var vundef = undefined;
// var vbool = true;
// var vstring1 = "foobar";
// var vstring2 = new String("foobar");
// var vregex = /^blah/;
// var vnumber = 77;
// var vfunc = _getDumperData;
// var varray = [ 'this', 'that' ];
// var vhash = { foo: 'bar', foobar: 'this' };
// var voption = new Option("this", "that");
// var vdate = new Date();
//
// var str = '';
// str += "null? " + isNull(vnull);
// str += "\nundef? " + isUndefined(vundef);
// str += "\nbool? " + isBoolean(vbool);
// str += "\nstring? " + isString(vstring1);
// str += "\nstring? " + isString(vstring2);
// str += "\nregex? " + isRegExp(vregex);
// str += "\nnumber? " + isNumber(vnumber);
// str += "\nfunc? " + isFunction(vfunc);
// str += "\narray? " + isArray(varray);
// str += "\nhash? " + isGenericObject(vhash);
// str += "\noption? " + isSubclassObject(voption);
// str += "\ndate? " + isSubclassObject(vdate);
//
// alert(str);


/* BUILD_JS_PACKAGES: library code
   fn: isNull
 */

function isNull(data) { 
    try {
        return (data == null);
    }
    catch(e) { return false; };
}

/* /BUILD_JS_PACKAGES */


/* BUILD_JS_PACKAGES: library code
   fn: isUndefined
 */

function isUndefined(data) { 
    try {
        return (typeof data == "undefined");
    }
    catch(e) { return false; };
}

/* /BUILD_JS_PACKAGES */


/* BUILD_JS_PACKAGES: library code
   fn: isNullOrUndefined
 */

function isNullOrUndefined(data) { 
    return (isNull(data) || isUndefined(data));
}

/* /BUILD_JS_PACKAGES */


/* BUILD_JS_PACKAGES: library code
   fn: isBoolean
 */

function isBoolean(data) { 
  return (typeof data == "boolean");
}

/* /BUILD_JS_PACKAGES */


/* BUILD_JS_PACKAGES: library code
   fn: isString
 */

function isString(data) { 
    try {
        return (typeof data == "string");
    }
    catch(e) { return false; };
}

/* /BUILD_JS_PACKAGES */


/* BUILD_JS_PACKAGES: library code
   fn: isRegExp
 */

function isRegExp(data) { 
  return (data.constructor && data.constructor.toString().match(/regexp/i) != null);
}

/* /BUILD_JS_PACKAGES */


/* BUILD_JS_PACKAGES: library code
   fn: isNumber
 */

function isNumber(data) { 
  return (data.constructor && data.constructor.toString().match(/number/i) != null);
}

/* /BUILD_JS_PACKAGES */


/* BUILD_JS_PACKAGES: library code
   fn: isFunction
 */

function isFunction(data) {
  return (typeof data == "function");
}

/* /BUILD_JS_PACKAGES */


/* BUILD_JS_PACKAGES: library code
   fn: isArray
 */

function isArray(data) {
    try {
        return (data.constructor.toString().match(/array/i) == null) ? false : true;
    }
    catch(e) { return false; };
}

/* /BUILD_JS_PACKAGES */


/* BUILD_JS_PACKAGES: library code
   fn: isGenericObject
   deps: isArray
 */

function isGenericObject(data) { 
    try {
				var is_object = (data.constructor.toString() != {}.constructor.toString()) ? false : true;
        return is_object && ! isArray(data);
    }
    catch(e) { return false; };
}

/* /BUILD_JS_PACKAGES */


/* BUILD_JS_PACKAGES: library code
   fn: isSubclassObject
   deps: isGenericObject
 */

function isSubclassObject(data) {
  return (typeof data == "object" && ! isGenericObject(data));
}

/* /BUILD_JS_PACKAGES */


/* Like map in perl, iterates over given array evaluating code
   and pushing the results onto returned array.  The index var
   'i' is magic, and is used when referencing the current item 
   of the given array, and so must be passed in literally (since 
   there is no default var $_ in js.
   
   Example:  var array1 = [ "foo", "bar", 1, false, [ '%', '###' ], { that: 'this', foobar: 'you' } ];
             var array2 = map( '(typeof array[i] == "object") ? array[i] : undefined;' , array1 );
   Returns:  [ [ '%', '###' ], { that: 'this', foobar: 'you' } ]
*/

/* BUILD_JS_PACKAGES: library code
   fn: map
 */

function map(code_string,array)
{
  var results_array = [ ];
  for (var i = 0; i < array.length; i++) {
    var code_string_eval = eval(code_string);
    if (code_string_eval !== "undefined") {
      results_array.push( code_string_eval );
    }
  }
  return results_array;
}

/* /BUILD_JS_PACKAGES */


/* Like grep in perl, iterates over given array grepping (i.e.
   regex matching) for given pattern in each element and pushing
   those that satisfy the regex onto returned array.  The matching 
   is only performed on those elements of the given array which are 
   strings or numbers (which are converted into strings for the purpose
   of matching.
 
   Example:  var array1 = [ 444, 44466, '444', '44466' ];;
             var array2 = grep(/^444$/, array1);
   Returns:  [ 444, '444' ]
 */

/* BUILD_JS_PACKAGES: library code
   fn: grep
 */

function grep(regex,array)
{
  var results_array = [ ];
  for (var i = 0; i < array.length; i++)
    if ( ( typeof array[i] == "string" || 
           ( array[i].constructor && 
             ( array[i].constructor.toString().match(/number/i) || 
               array[i].constructor.toString().match(/string/i)
             )
           )
         )
       )
      if (String(array[i]).match(regex)) {
        results_array.push(array[i]);
      }
  return results_array;
}

/* /BUILD_JS_PACKAGES */


/* BUILD_JS_PACKAGES: library code
   fn: array_search
 */

function array_search (val, array) 
{
    var i = array.length;
    while (i--) {
        if (array[i] && array[i] === val) {
            break;
        }
    }
        
    return i;
}

/* /BUILD_JS_PACKAGES */


/* BUILD_JS_PACKAGES: library code
   fn: hash_keys
 */

function hash_keys(hash)
{
  var keys = [ ];
  for (var key in hash)
    keys.push(key);
  return keys;
}

/* /BUILD_JS_PACKAGES */


/* BUILD_JS_PACKAGES: library code
   fn: hash_has_keys
 */

function hash_has_keys(hash)
{
    for (var key in hash) {
        return true;
    }
    return false;
}

/* /BUILD_JS_PACKAGES */


/* BUILD_JS_PACKAGES: library code
   fn: hash_values
 */

function hash_values(hash)
{
  var vals = [ ];
  for (var key in hash)
    vals.push(hash[key]);
  return vals;
}

/* /BUILD_JS_PACKAGES */


/* BUILD_JS_PACKAGES: library code
   fn: array_contains
 */

function array_contains(item,array)
{
  for (var i = 0; i < array.length; i++)
    if (item == array[i])
      return true;
  return false;
}

/* /BUILD_JS_PACKAGES */


/* BUILD_JS_PACKAGES: library code
   fn: isEmpty
 */

var WHITESPACE = " \t\n\r";  // a constant

function isEmpty(s)
{
  var i;
  // Is s empty?
  if ((s == null) || (s.length == 0)) return true;

  // Search through string's characters one by one
  // until we find a non-whitespace character.
  // When we do, return false; if we don't, return true.

  for (i = 0; i < s.length; i++)
    {
      // Check that current character isn't whitespace.
      var c = s.charAt(i);

      if (WHITESPACE.indexOf(c) == -1) return false;
    }

  // All characters are whitespace.
   return true;
 }

/* /BUILD_JS_PACKAGES */


/* BUILD_JS_PACKAGES: library code
   fn: translateXMLObjToJSObj
 */

function translateXMLObjToJSObj (xml_obj)
{
    var node_name = xml_obj.nodeName;
    var this_obj;

    // #text node, skip
    if (node_name == "#text") {
        xml_obj = xml_obj.nextSibling;
        node_name = xml_obj.nodeName;
    }

    if (node_name == "hash" || node_name == "array" || node_name == "hashref" || node_name == "arrayref") {
        var this_obj = (node_name == "hash" || node_name == "hashref") ? { } : [ ];

        for (var i = 0; i < xml_obj.childNodes.length; i++) {
            var this_node      = xml_obj.childNodes[i];
            var this_node_name = this_node.nodeName;

            // skip #text nodes that Mozilla browsers liberally sprinkle
            // throughout the DOM (also below in item node)
            if (this_node_name == "#text") {
                continue;
            }
        
            // see if this item is a container, or a data value
            if (this_node_name == "item") {
                var key_or_index = this_node.attributes[0].value;
                var first_child  = this_node.firstChild; 
                
                // #text node, skip
                if (first_child != null && first_child.nodeName == "#text") {
                    first_child = first_child.nextSibling;
                }

                // hash or array node
                if (first_child != null && (first_child.nodeName == "hash" || first_child.nodeName == "array" || first_child.nodeName == "hashref" || first_child.nodeName == "arrayref")) {
                    // should only have key property
                    var node_value = translateXMLObjToJSObj(first_child);
                    if      (node_name == "hash" || node_name == "hashref")  this_obj[key_or_index] = node_value;
                    else if (node_name == "array" || node_name == "arrayref") this_obj[parseInt(key_or_index)] = node_value;
                }
                // item value node
                else {
                    // should only have text node as a single child, or empty
                    var item_value = this_node.firstChild ? this_node.firstChild.nodeValue : "";
                    if      (node_name == "hash" || node_name == "hashref")  this_obj[key_or_index] = item_value;
                    else if (node_name == "array" || node_name == "arrayref") this_obj[parseInt(key_or_index)] = item_value;
                }
            }
        }
    }
    else if (node_name == "scalar") {
        // should only have text node as a single child, or empty
        var node_value = xml_obj.childNodes[0].nodeValue || null;
        this_obj = node_value != null ? eval ("(" + node_value + ")") : null;
    }
  
    return this_obj;
}

/* /BUILD_JS_PACKAGES */


/* BUILD_JS_PACKAGES: library code
   fn: translateSegmentedJSONXMLObjToJSObj
 */

function translateSegmentedJSONXMLObjToJSObj (xml_obj)
{
    var node_name = xml_obj.nodeName;
  
    var segmented_json = [ ];
    
    if (node_name == "array" || node_name == "arrayref") {
        var this_obj = (node_name == "hash") ? { } : [ ];

        for (var i = 0; i < xml_obj.childNodes.length; i++) {
            var this_node      = xml_obj.childNodes[i];
            var this_node_name = this_node.nodeName;

            // see if this item is a container, or a data value
            if (this_node_name == "item") {
                var array_index = this_node.attributes[0].value;
                var first_child  = this_node.firstChild; 

                if (first_child) {
                    segmented_json.push (first_child.nodeValue || "");
                }
            }
        }
    }
    
    var js_string = segmented_json.join("");

    return js_string ? eval ("(" + js_string + ")") : null;
}

/* /BUILD_JS_PACKAGES */


/*
var str='';

function showDOM(obj) {
showDOMNode(obj,'ROOT');
// DOMWin=window.open('./dom.html','DOMWin',
// 'location=no,'
// +'menubar=no,'
// +'personalbar=no,'
// +'status=no,'
// +'scrollbars=yes,'
// +'toolbar=no');
// DOMWin.document.write(str.fixed());
// document.write(str.fixed());
return str;
}

function showDOMNode(obj,label) {
var nc=obj.childNodes.length;
var tag=obj.nodeName;
var id=obj.id;
if (tag == '#text') {
str=str+label+': \''+obj.nodeValue+'\'';
} else if (tag == '#document') {
str=str+label+': document (URL='+obj.location+')';
} else {
str=str+label+': ['+tag;
if (id!='') {
str=str+' id='+id;
}
str=str+']';
}
if (nc>0) {
str=str+' with '+nc+' childNode(s):\n';
for(var n=0;n<nc;n++) {
showDOMNode(obj.childNodes[n],'  '+label+'.'+n);
}
} else {
str=str+'(empty)\n';
}
}
*/

// useful for converting an array of arrays derived from perl 
// (where inputting an array of the form [ [ key1, value1, key2, value2, ... ], ... ]
// is the only way to preserve "hash order" in the inner arrays, since a hash's 
// keys are randomly accessed in perl) into arrays or hashes in javascript
// 
// i.e. selectors where key: value pair order is important

//var test_arrays = [ ["key1", "value1", "key2", "value2"], ["key3", "value3"] ];
//var test_hash = { key1: "value1", key2: "value2", key3: "value3" };

/* BUILD_JS_PACKAGES: library code
   fn: convertKeyvalArraysToHash
   deps: convertKeyvalArrayToHash
 */

function convertKeyvalArraysToHash(array)
{
  var new_array = [ ];
  for (var i = 0; i < array.length; i++) {
    var hash = convertKeyvalArrayToHash(array[i]);
    new_array.push(hash);
  }

  return new_array;
}

/* /BUILD_JS_PACKAGES */


/* BUILD_JS_PACKAGES: library code
   fn: convertKeyvalArrayToHash
 */

// converts a single [ key1, value1, key2, value2, ... ] array
// into a hash
function convertKeyvalArrayToHash(array)
{
  if (array.length % 2 != 0)
    return;
    
  var hash = { };
  for (var i = 0; i < array.length; i += 2)
    hash[array[i]] = array[i+1];
    
  return hash;
}

/* /BUILD_JS_PACKAGES */


/* BUILD_JS_PACKAGES: library code
   fn: convertHashToKeyvalArray
 */

//alert("test_arrays = " + dumper(test_arrays) + "\n\nhash = " + dumper(convertKeyvalArraysToHash(test_arrays)));

// the opposite of above, i.e. it converts an ordered
// js hash to an array of the form [ key1, value1, key2, value2, ... ]
function convertHashToKeyvalArray(hash)
{
  var array = [ ];
  for (var key in hash) {
    // why doens't array.push(key, hash[key]) work???
    array.push(key);
    array.push(hash[key]);
  }
  
  return array;
}

/* BUILD_JS_PACKAGES: library code
   fn: keyvalArrayLookup
 */

//alert("test_hash = " + dumper(test_hash) + "\narray = " + dumper(convertHashToKeyvalArray(test_hash)));

// treats a keyval array of the form [ key1, value1, key2, value2, ... ]
// as if it were a hash, e.g. returning value2 when key2 is input
function keyvalArrayLookup(array,key)
{
  if (array.length % 2 != 0)
    return;
    
  for (var i = 0; i < array.length; i++)
    if (array[i] == key)
      return array[i+1];
}

/* /BUILD_JS_PACKAGES */


/* BUILD_JS_PACKAGES: library code
   fn: checkForHashEquality
 */

// checks if one hash is identical (keys and vals match) to another,
// excluding those keys in ignore_hash
function checkForHashEquality(hash1,hash2,ignore_hash)
{
  var key;
  if (ignore_hash == null)
    ignore_hash = { };

  for (key in hash1) {
    if (! ignore_hash[key] && hash1[key] != hash2[key])
      return false;
  }
  
  for (key in hash2) {
    if (! ignore_hash[key] && hash1[key] != hash2[key])
      return false;
  }

  return true;
}

/* /BUILD_JS_PACKAGES */


/* BUILD_JS_PACKAGES: library code
   fn: alertp
 */

/**
 * given an array of lines to display in an alert, displays messages in a 
 * paginated format
 */
function alertp(msg_array,max_display)
{
  if (isString(msg_array))
    msg_array = msg_array.split(/\n+/);
  max_display = max_display || 65;
  for (var i = 1; i <= Math.ceil(msg_array.length/max_display); i++) {
    if (! confirm( msg_array.slice(((i-1)*max_display), (i*max_display-1)).join("\n") ) )
      break;
  }
}

/* /BUILD_JS_PACKAGES */


/* BUILD_JS_PACKAGES: library code
   fn: toFixedTruncated
 */

if (! Number.prototype.toFixedTruncated) {
  Number.prototype.toFixedTruncated = toFixedTruncated;
}

function toFixedTruncated(num_decimals) {
  if (String(num_decimals).match(/^\d+$/)) {
    // return integer portion
    if (num_decimals == 0)
      return parseInt(this);
    else {
      // return number padded with zeroes in decimal
      for (var i = 1; i <= num_decimals; i++) {
        var multiplier = Math.pow(10,i);
        if ( (parseInt(this * multiplier))/multiplier == this &&
             i < num_decimals
           ) {
          return this.toFixed(num_decimals);
        }
      }
      
      // truncate decimal (without rounding like toFixed())
      var multiplier = Math.pow(10,num_decimals);
      return (parseInt(this * multiplier))/multiplier;
    }
  }
  // unrecognized num_decimals, return original number
  else {
    return this;
  }
}

/* /BUILD_JS_PACKAGES */


/* BUILD_JS_PACKAGES: library code
   fn: dumperForm
   deps: getValue hash_keys
 */

/**
 * given an array of lines to display in an alert, displays messages in a 
 * paginated format
 */
function dumperForm(form)
{
  var str = "";
  var vals = { };
  for (var i = 0; i < form.elements.length; i++) {
    var value = getValue(form, form.elements[i].name);
    if (value)
      vals[form.elements[i].name] = value;
  }
      
  var sorted_keys = hash_keys(vals).sort();
  
  for (var i = 0; i < sorted_keys.length; i++)
    str += sorted_keys[i] + ": " + vals[sorted_keys[i]] + "\n";
  
  return str;
}

/* BUILD_JS_PACKAGES: library code
   fn: getChildNodesRecursively
 */

/**
 * returns all childNodes under a DOM object, excluding empty text nodes
 */
function getChildNodesRecursively(obj)
{
    var nodes = [ ];
    if (! obj.childNodes) {
        return nodes;
    }
    else {
        for (var i = 0; i < obj.childNodes.length; i++) {
            if (childNodes[i].getAttribute) {
                nodes.push(childNodes[i]);
                nodes.push(getChildNodesRecursively(childNodes[i]));
            }
        }
    }
  
    return nodes;
}

/* /BUILD_JS_PACKAGES */


/* BUILD_JS_PACKAGES: library code
   fn: charCodeIn
 */

Number.prototype.charCodeIn = charCodeIn;

function charCodeIn(regexp)
{
    return this.valueOf() > 255 ? false : regexp.test(String.fromCharCode(this.valueOf()));
}

/* /BUILD_JS_PACKAGES */


/*
var test_hash1 = { foo: "bar", thiss: "that", something: 5 };
var test_hash2 = { foo: "bar", thiss: "that", something: 5 };
var test_hash2a = { foo: "bar", thiss: "thatx", something: 5, other: "foo" };
var test_hash2b = { foo: "bar", thiss: "that", something: 5, other: "foo" };

var str = '';
str += dumper(test_hash1) + "\n" + dumper(test_hash2) + "\nequal? " + checkForHashEquality(test_hash1,test_hash2) + "\n\n";
str += dumper(test_hash1) + "\n" + dumper(test_hash2a) + "\nequal? " + checkForHashEquality(test_hash1,test_hash2a) + "\n\n";
str += dumper(test_hash1) + "\n" + dumper(test_hash2b) + "\nequal? " + checkForHashEquality(test_hash1,test_hash2b) + "\n\n";
str += dumper(test_hash1) + "\n" + dumper(test_hash2b) + "\nequal? " + checkForHashEquality(test_hash1,test_hash2b,{other:1}) + "\n\n";
alertp(str);
*/

/*
sub translatePerlObjToJSObj
{
  my ($obj,$as_strings) = @_;
  if (ref $obj eq 'HASH')
  {
    my @hash_vals = ();
    foreach my $key (sort keys %{$obj})
    {
      # escape backslashes, then single-quotes
      my $orig_key = $key;
      $key =~ s/\\/\\\\/g;
      $key =~ s/\'/\\\'/g;
      push @hash_vals, "'$key'" . ":" . translatePerlObjToJSObj ($obj->{$orig_key},$as_strings);
    }
    return '{' . join(",",@hash_vals) . '}';
  }
  elsif (ref $obj eq 'ARRAY')
  {
    my @array_vals = ();
    foreach my $val (@{$obj})
    {
      push @array_vals, translatePerlObjToJSObj ($val,$as_strings);
    }
    return '[' . join(",",@array_vals) . ']';
  }
  elsif (!ref $obj)
  {
    unless ( $as_strings )
    {
      if ($obj =~ m/^[+-]?\d+\.?$/ ||
        $obj =~ m/^[+-]?\d*\.\d+$/)
      {
        return $obj;
      }
    }
    # escape backslashes, then single-quotes
    $obj =~ s/\\/\\\\/g;
    $obj =~ s/\'/\\\'/g;
    $obj = ($obj ne 'true' && $obj ne 'false') ? "'" . $obj . "'" : $obj;
    return $obj;
  }
  else
  {
    die "Unable to translate perl object to javascript: $obj";
  }
}
*/

