// Uses CommonJS, AMD or browser globals to create a module. // Based on: https://github.com/umdjs/umd/blob/master/commonjsStrict.js (function (root, factory) { if (typeof define === 'function' && define.amd) { // AMD. Register as an anonymous module. define(['exports'], factory); } else if (typeof exports === 'object') { // CommonJS factory(exports); } else { // Browser globals factory((root.rison = {})); } }(this, function (exports) { var rison = exports; ////////////////////////////////////////////////// // // the stringifier is based on // http://json.org/json.js as of 2006-04-28 from json.org // the parser is based on // http://osteele.com/sources/openlaszlo/json // if (typeof rison == 'undefined') window.rison = {}; /** * rules for an uri encoder that is more tolerant than encodeURIComponent * * encodeURIComponent passes ~!*()-_.' * * we also allow ,:@$/ * */ rison.uri_ok = { // ok in url paths and in form query args '~': true, '!': true, '*': true, '(': true, ')': true, '-': true, '_': true, '.': true, ',': true, ':': true, '@': true, '$': true, "'": true, '/': true }; /* * we divide the uri-safe glyphs into three sets * - used by rison ' ! : ( ) , * - not common in strings, reserved * @ $ & ; = * * we define as anything that's not forbidden */ /** * punctuation characters that are legal inside ids. */ // this var isn't actually used //rison.idchar_punctuation = "_-./~"; (function () { var l = []; for (var hi = 0; hi < 16; hi++) { for (var lo = 0; lo < 16; lo++) { if (hi+lo === 0) continue; var c = String.fromCharCode(hi*16 + lo); if (! /\w|[-_.\/~]/.test(c)) l.push('\\u00' + hi.toString(16) + lo.toString(16)); } } /** * characters that are illegal inside ids. * and classes are illegal in ids. * */ rison.not_idchar = l.join(''); //idcrx = new RegExp('[' + rison.not_idchar + ']'); //console.log('NOT', (idcrx.test(' ')) ); })(); //rison.not_idchar = " \t\r\n\"<>[]{}'!=:(),*@$;&"; rison.not_idchar = " '!:(),*@$"; /** * characters that are illegal as the start of an id * this is so ids can't look like numbers. */ rison.not_idstart = '-0123456789'; (function () { var idrx = '[^' + rison.not_idstart + rison.not_idchar + '][^' + rison.not_idchar + ']*'; rison.id_ok = new RegExp('^' + idrx + '$'); // regexp to find the end of an id when parsing // g flag on the regexp is necessary for iterative regexp.exec() rison.next_id = new RegExp(idrx, 'g'); })(); /** * this is like encodeURIComponent() but quotes fewer characters. * * @see rison.uri_ok * * encodeURIComponent passes ~!*()-_.' * rison.quote also passes ,:@$/ * and quotes " " as "+" instead of "%20" */ rison.quote = function(x) { if (/^[-A-Za-z0-9~!*()_.',:@$\/]*$/.test(x)) return x; return encodeURIComponent(x) .replace(/%2C/g, ',') .replace(/%3A/g, ':') .replace(/%40/g, '@') .replace(/%24/g, '$') .replace(/%2F/g, '/') .replace(/%20/g, '+'); }; // // based on json.js 2006-04-28 from json.org // license: http://www.json.org/license.html // // hacked by nix for use in uris. // (function () { var sq = { // url-ok but quoted in strings "'": true, '!': true }, enc = function (v) { if (v && typeof v.toJSON === 'function') v = v.toJSON(); var fn = s[typeof v]; if (fn) return fn(v); }, s = { array: function (x) { var a = ['!('], b, f, i, l = x.length, v; for (i = 0; i < l; i += 1) { v = enc(x[i]); if (typeof v == 'string') { if (b) { a[a.length] = ','; } a[a.length] = v; b = true; } } a[a.length] = ')'; return a.join(''); }, 'boolean': function (x) { if (x) return '!t'; return '!f'; }, 'null': function () { return '!n'; }, number: function (x) { if (!isFinite(x)) return '!n'; // strip '+' out of exponent, '-' is ok though return String(x).replace(/\+/,''); }, object: function (x) { if (x) { if (x instanceof Array) { return s.array(x); } // WILL: will this work on non-Firefox browsers? if (typeof x.__prototype__ === 'object' && typeof x.__prototype__.encode_rison !== 'undefined') return x.encode_rison(); var a = ['('], b, i, v, k, ki, ks=[]; for (i in x) ks[ks.length] = i; ks.sort(); for (ki = 0; ki < ks.length; ki++) { i = ks[ki]; v = enc(x[i]); if (typeof v == 'string') { if (b) { a[a.length] = ','; } k = isNaN(parseInt(i)) ? s.string(i) : s.number(i) a.push(k, ':', v); b = true; } } a[a.length] = ')'; return a.join(''); } return '!n'; }, string: function (x) { if (x === '') return "''"; if (rison.id_ok.test(x)) return x; x = x.replace(/(['!])/g, function(a, b) { if (sq[b]) return '!'+b; return b; }); return "'" + x + "'"; }, undefined: function () { // ignore undefined just like JSON return; } }; /** * rison-encode a javascript structure * * implemementation based on Douglas Crockford's json.js: * http://json.org/json.js as of 2006-04-28 from json.org * */ rison.encode = function (v) { return enc(v); }; /** * rison-encode a javascript object without surrounding parens * */ rison.encode_object = function (v) { if (typeof v != 'object' || v === null || v instanceof Array) throw new Error('rison.encode_object expects an object argument'); var r = s[typeof v](v); return r.substring(1, r.length-1); }; /** * rison-encode a javascript array without surrounding parens * */ rison.encode_array = function (v) { if (!(v instanceof Array)) throw new Error('rison.encode_array expects an array argument'); var r = s[typeof v](v); return r.substring(2, r.length-1); }; /** * rison-encode and uri-encode a javascript structure * */ rison.encode_uri = function (v) { return rison.quote(s[typeof v](v)); }; })(); // // based on openlaszlo-json and hacked by nix for use in uris. // // Author: Oliver Steele // Copyright: Copyright 2006 Oliver Steele. All rights reserved. // Homepage: http://osteele.com/sources/openlaszlo/json // License: MIT License. // Version: 1.0 /** * parse a rison string into a javascript structure. * * this is the simplest decoder entry point. * * based on Oliver Steele's OpenLaszlo-JSON * http://osteele.com/sources/openlaszlo/json */ rison.decode = function(r) { var errcb = function(e) { throw Error('rison decoder error: ' + e); }; var p = new rison.parser(errcb); return p.parse(r); }; /** * parse an o-rison string into a javascript structure. * * this simply adds parentheses around the string before parsing. */ rison.decode_object = function(r) { return rison.decode('('+r+')'); }; /** * parse an a-rison string into a javascript structure. * * this simply adds array markup around the string before parsing. */ rison.decode_array = function(r) { return rison.decode('!('+r+')'); }; /** * construct a new parser object for reuse. * * @constructor * @class A Rison parser class. You should probably * use rison.decode instead. * @see rison.decode */ rison.parser = function (errcb) { this.errorHandler = errcb; }; /** * a string containing acceptable whitespace characters. * by default the rison decoder tolerates no whitespace. * to accept whitespace set rison.parser.WHITESPACE = " \t\n\r\f"; */ rison.parser.WHITESPACE = ''; // expose this as-is? rison.parser.prototype.setOptions = function (options) { if (options['errorHandler']) this.errorHandler = options.errorHandler; }; /** * parse a rison string into a javascript structure. */ rison.parser.prototype.parse = function (str) { this.string = str; this.index = 0; this.message = null; var value = this.readValue(); if (!this.message && this.next()) value = this.error("unable to parse string as rison: '" + rison.encode(str) + "'"); if (this.message && this.errorHandler) this.errorHandler(this.message, this.index); return value; }; rison.parser.prototype.error = function (message) { if (typeof(console) != 'undefined') console.log('rison parser error: ', message); this.message = message; return undefined; }; rison.parser.prototype.readValue = function () { var c = this.next(); var fn = c && this.table[c]; if (fn) return fn.apply(this); // fell through table, parse as an id var s = this.string; var i = this.index-1; // Regexp.lastIndex may not work right in IE before 5.5? // g flag on the regexp is also necessary rison.next_id.lastIndex = i; var m = rison.next_id.exec(s); // console.log('matched id', i, r.lastIndex); if (m.length > 0) { var id = m[0]; this.index = i+id.length; return id; // a string } if (c) return this.error("invalid character: '" + c + "'"); return this.error('empty expression'); }; rison.parser.parse_array = function (parser) { var ar = []; var c; while ((c = parser.next()) !== ')') { if (!c) return parser.error("unmatched '!('"); if (ar.length) { if (c !== ',') parser.error("missing ','"); } else if (c === ',') { return parser.error("extra ','"); } else --parser.index; var n = parser.readValue(); if (typeof n == 'undefined') return undefined; ar.push(n); } return ar; }; rison.parser.bangs = { t: true, f: false, n: null, '(': rison.parser.parse_array }; rison.parser.prototype.table = { '!': function () { var s = this.string; var c = s.charAt(this.index++); if (!c) return this.error('"!" at end of input'); var x = rison.parser.bangs[c]; if (typeof(x) == 'function') { return x.call(null, this); } else if (typeof(x) == 'undefined') { return this.error('unknown literal: "!' + c + '"'); } return x; }, '(': function () { var o = {}; var c; var count = 0; while ((c = this.next()) !== ')') { if (count) { if (c !== ',') this.error("missing ','"); } else if (c === ',') { return this.error("extra ','"); } else --this.index; var k = this.readValue(); if (typeof k == 'undefined') return undefined; if (this.next() !== ':') return this.error("missing ':'"); var v = this.readValue(); if (typeof v == 'undefined') return undefined; o[k] = v; count++; } return o; }, "'": function () { var s = this.string; var i = this.index; var start = i; var segments = []; var c; while ((c = s.charAt(i++)) !== "'") { //if (i == s.length) return this.error('unmatched "\'"'); if (!c) return this.error('unmatched "\'"'); if (c === '!') { if (start < i-1) segments.push(s.slice(start, i-1)); c = s.charAt(i++); if ("!'".indexOf(c) >= 0) { segments.push(c); } else { return this.error('invalid string escape: "!'+c+'"'); } start = i; } } if (start < i-1) segments.push(s.slice(start, i-1)); this.index = i; return segments.length === 1 ? segments[0] : segments.join(''); }, // Also any digit. The statement that follows this table // definition fills in the digits. '-': function () { var s = this.string; var i = this.index; var start = i-1; var state = 'int'; var permittedSigns = '-'; var transitions = { 'int+.': 'frac', 'int+e': 'exp', 'frac+e': 'exp' }; do { var c = s.charAt(i++); if (!c) break; if ('0' <= c && c <= '9') continue; if (permittedSigns.indexOf(c) >= 0) { permittedSigns = ''; continue; } state = transitions[state+'+'+c.toLowerCase()]; if (state === 'exp') permittedSigns = '-'; } while (state); this.index = --i; s = s.slice(start, i); if (s === '-') return this.error('invalid number'); return Number(s); } }; // copy table['-'] to each of table[i] | i <- '0'..'9': (function (table) { for (var i = 0; i <= 9; i++) table[String(i)] = table['-']; })(rison.parser.prototype.table); // return the next non-whitespace character, or undefined rison.parser.prototype.next = function () { var c; var s = this.string; var i = this.index; do { if (i === s.length) return undefined; c = s.charAt(i++); } while (rison.parser.WHITESPACE.indexOf(c) >= 0); this.index = i; return c; }; // End of UMD module wrapper }));