| // Taken from https://github.com/Nanonid/rison at 917679fb6cafa15e2a186cd5a47163792899b321 |
| /* jshint ignore:start */ |
| // 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 |
| * <rison> - used by rison ' ! : ( ) , |
| * <reserved> - not common in strings, reserved * @ $ & ; = |
| * |
| * we define <identifier> 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. |
| * <rison> and <reserved> 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 (x) { |
| 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, f, i, v, 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] = ','; |
| } |
| a.push(s.string(i), ':', 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 (x) { |
| // 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 |
| })); |