From 55cf3cafb7068dda4e0e51c351490052deee76a4 Mon Sep 17 00:00:00 2001 From: Matias Pequeno Date: Thu, 7 Mar 2024 18:07:47 +0100 Subject: [PATCH] Removed JSON-js submodule and integrated the modified json3 directly (#1135) --- .github/workflows/ci.yml | 2 - .gitmodules | 3 - .npmignore | 1 - README.md | 1 - vendor/JSON-js | 1 - vendor/JSON-js/README | 40 ++ vendor/JSON-js/cycle.js | 181 +++++++ vendor/JSON-js/json2.js | 499 +++++++++++++++++++ vendor/JSON-js/json3.js | 763 +++++++++++++++++++++++++++++ vendor/JSON-js/json_parse.js | 356 ++++++++++++++ vendor/JSON-js/json_parse_state.js | 405 +++++++++++++++ 11 files changed, 2244 insertions(+), 8 deletions(-) delete mode 100644 .gitmodules delete mode 160000 vendor/JSON-js create mode 100644 vendor/JSON-js/README create mode 100644 vendor/JSON-js/cycle.js create mode 100644 vendor/JSON-js/json2.js create mode 100644 vendor/JSON-js/json3.js create mode 100644 vendor/JSON-js/json_parse.js create mode 100644 vendor/JSON-js/json_parse_state.js diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 635d56fd9..68e16bf0f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,8 +28,6 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 - with: - submodules: recursive - name: Set up node ${{ matrix.node }} uses: actions/setup-node@v4 diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 9426ba67f..000000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "vendor/JSON-js"] - path = vendor/JSON-js - url = https://github.com/rollbar/JSON-js.git diff --git a/.npmignore b/.npmignore index 9f61da936..260cb5c97 100644 --- a/.npmignore +++ b/.npmignore @@ -2,7 +2,6 @@ node_modules bower_components .idea vendor/*.min.js -vendor/JSON-js/.git test/*.bundle.js* sauce_connect.log release diff --git a/README.md b/README.md index 61ad7be64..2d229de6c 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,6 @@ For bug reports, please [open an issue on GitHub](https://github.com/rollbar/rol To set up a development environment, you'll need Node.js and npm. -1. `git submodule update --init` 2. `npm install -D` 3. `make` diff --git a/vendor/JSON-js b/vendor/JSON-js deleted file mode 160000 index acb5c4da0..000000000 --- a/vendor/JSON-js +++ /dev/null @@ -1 +0,0 @@ -Subproject commit acb5c4da08dd40ee117df73229add0c8d9b7dacb diff --git a/vendor/JSON-js/README b/vendor/JSON-js/README new file mode 100644 index 000000000..6ca2c8c7d --- /dev/null +++ b/vendor/JSON-js/README @@ -0,0 +1,40 @@ +JSON in JavaScript + + +Douglas Crockford +douglas@crockford.com + +2015-05-03 + + +JSON is a light-weight, language independent, data interchange format. +See http://www.JSON.org/ + +The files in this collection implement JSON encoders/decoders in JavaScript. + +JSON became a built-in feature of JavaScript when the ECMAScript Programming +Language Standard - Fifth Edition was adopted by the ECMA General Assembly +in December 2009. Most of the files in this collection are for applications +that are expected to run in obsolete web browsers. For most purposes, json2.js +is the best choice. + + +json2.js: This file creates a JSON property in the global object, if there +isn't already one, setting its value to an object containing a stringify +method and a parse method. The parse method uses the eval method to do the +parsing, guarding it with several regular expressions to defend against +accidental code execution hazards. On current browsers, this file does nothing, +preferring the built-in JSON object. There is no reason to use this file unless +fate compels you to support IE8, which is something that no one should ever +have to do again. + +json_parse.js: This file contains an alternative JSON parse function that +uses recursive descent instead of eval. + +json_parse_state.js: This files contains an alternative JSON parse function that +uses a state machine instead of eval. + +cycle.js: This file contains two functions, JSON.decycle and JSON.retrocycle, +which make it possible to encode cyclical structures and dags in JSON, and to +then recover them. This is a capability that is not provided by ES5. JSONPath +is used to represent the links. [http://GOESSNER.net/articles/JsonPath/] diff --git a/vendor/JSON-js/cycle.js b/vendor/JSON-js/cycle.js new file mode 100644 index 000000000..0c7904f20 --- /dev/null +++ b/vendor/JSON-js/cycle.js @@ -0,0 +1,181 @@ +/* + cycle.js + 2017-02-07 + + Public Domain. + + NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. + + This code should be minified before deployment. + See http://javascript.crockford.com/jsmin.html + + USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO + NOT CONTROL. +*/ + +// The file uses the WeakMap feature of ES6. + +/*jslint es6, eval */ + +/*property + $ref, decycle, forEach, get, indexOf, isArray, keys, length, push, + retrocycle, set, stringify, test +*/ + +if (typeof JSON.decycle !== "function") { + JSON.decycle = function decycle(object, replacer) { + "use strict"; + +// Make a deep copy of an object or array, assuring that there is at most +// one instance of each object or array in the resulting structure. The +// duplicate references (which might be forming cycles) are replaced with +// an object of the form + +// {"$ref": PATH} + +// where the PATH is a JSONPath string that locates the first occurance. + +// So, + +// var a = []; +// a[0] = a; +// return JSON.stringify(JSON.decycle(a)); + +// produces the string '[{"$ref":"$"}]'. + +// If a replacer function is provided, then it will be called for each value. +// A replacer function receives a value and returns a replacement value. + +// JSONPath is used to locate the unique object. $ indicates the top level of +// the object or array. [NUMBER] or [STRING] indicates a child element or +// property. + + var objects = new WeakMap(); // object to path mappings + + return (function derez(value, path) { + +// The derez function recurses through the object, producing the deep copy. + + var old_path; // The path of an earlier occurance of value + var nu; // The new object or array + +// If a replacer function was provided, then call it to get a replacement value. + + if (replacer !== undefined) { + value = replacer(value); + } + +// typeof null === "object", so go on if this value is really an object but not +// one of the weird builtin objects. + + if ( + typeof value === "object" && value !== null && + !(value instanceof Boolean) && + !(value instanceof Date) && + !(value instanceof Number) && + !(value instanceof RegExp) && + !(value instanceof String) + ) { + +// If the value is an object or array, look to see if we have already +// encountered it. If so, return a {"$ref":PATH} object. This uses an +// ES6 WeakMap. + + old_path = objects.get(value); + if (old_path !== undefined) { + return {$ref: old_path}; + } + +// Otherwise, accumulate the unique value and its path. + + objects.set(value, path); + +// If it is an array, replicate the array. + + if (Array.isArray(value)) { + nu = []; + value.forEach(function (element, i) { + nu[i] = derez(element, path + "[" + i + "]"); + }); + } else { + +// If it is an object, replicate the object. + + nu = {}; + Object.keys(value).forEach(function (name) { + nu[name] = derez( + value[name], + path + "[" + JSON.stringify(name) + "]" + ); + }); + } + return nu; + } + return value; + }(object, "$")); + }; +} + + +if (typeof JSON.retrocycle !== "function") { + JSON.retrocycle = function retrocycle($) { + "use strict"; + +// Restore an object that was reduced by decycle. Members whose values are +// objects of the form +// {$ref: PATH} +// are replaced with references to the value found by the PATH. This will +// restore cycles. The object will be mutated. + +// The eval function is used to locate the values described by a PATH. The +// root object is kept in a $ variable. A regular expression is used to +// assure that the PATH is extremely well formed. The regexp contains nested +// * quantifiers. That has been known to have extremely bad performance +// problems on some browsers for very long strings. A PATH is expected to be +// reasonably short. A PATH is allowed to belong to a very restricted subset of +// Goessner's JSONPath. + +// So, +// var s = '[{"$ref":"$"}]'; +// return JSON.retrocycle(JSON.parse(s)); +// produces an array containing a single element which is the array itself. + + var px = /^\$(?:\[(?:\d+|"(?:[^\\"\u0000-\u001f]|\\([\\"\/bfnrt]|u[0-9a-zA-Z]{4}))*")\])*$/; + + (function rez(value) { + +// The rez function walks recursively through the object looking for $ref +// properties. When it finds one that has a value that is a path, then it +// replaces the $ref object with a reference to the value that is found by +// the path. + + if (value && typeof value === "object") { + if (Array.isArray(value)) { + value.forEach(function (element, i) { + if (typeof element === "object" && element !== null) { + var path = element.$ref; + if (typeof path === "string" && px.test(path)) { + value[i] = eval(path); + } else { + rez(element); + } + } + }); + } else { + Object.keys(value).forEach(function (name) { + var item = value[name]; + if (typeof item === "object" && item !== null) { + var path = item.$ref; + if (typeof path === "string" && px.test(path)) { + value[name] = eval(path); + } else { + rez(item); + } + } + }); + } + } + }($)); + return $; + }; +} diff --git a/vendor/JSON-js/json2.js b/vendor/JSON-js/json2.js new file mode 100644 index 000000000..67b7b99af --- /dev/null +++ b/vendor/JSON-js/json2.js @@ -0,0 +1,499 @@ +// json2.js +// 2016-10-28 +// Public Domain. +// NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. +// See http://www.JSON.org/js.html +// This code should be minified before deployment. +// See http://javascript.crockford.com/jsmin.html + +// USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO +// NOT CONTROL. + +// This file creates a global JSON object containing two methods: stringify +// and parse. This file provides the ES5 JSON capability to ES3 systems. +// If a project might run on IE8 or earlier, then this file should be included. +// This file does nothing on ES5 systems. + +// JSON.stringify(value, replacer, space) +// value any JavaScript value, usually an object or array. +// replacer an optional parameter that determines how object +// values are stringified for objects. It can be a +// function or an array of strings. +// space an optional parameter that specifies the indentation +// of nested structures. If it is omitted, the text will +// be packed without extra whitespace. If it is a number, +// it will specify the number of spaces to indent at each +// level. If it is a string (such as "\t" or " "), +// it contains the characters used to indent at each level. +// This method produces a JSON text from a JavaScript value. +// When an object value is found, if the object contains a toJSON +// method, its toJSON method will be called and the result will be +// stringified. A toJSON method does not serialize: it returns the +// value represented by the name/value pair that should be serialized, +// or undefined if nothing should be serialized. The toJSON method +// will be passed the key associated with the value, and this will be +// bound to the value. + +// For example, this would serialize Dates as ISO strings. + +// Date.prototype.toJSON = function (key) { +// function f(n) { +// // Format integers to have at least two digits. +// return (n < 10) +// ? "0" + n +// : n; +// } +// return this.getUTCFullYear() + "-" + +// f(this.getUTCMonth() + 1) + "-" + +// f(this.getUTCDate()) + "T" + +// f(this.getUTCHours()) + ":" + +// f(this.getUTCMinutes()) + ":" + +// f(this.getUTCSeconds()) + "Z"; +// }; + +// You can provide an optional replacer method. It will be passed the +// key and value of each member, with this bound to the containing +// object. The value that is returned from your method will be +// serialized. If your method returns undefined, then the member will +// be excluded from the serialization. + +// If the replacer parameter is an array of strings, then it will be +// used to select the members to be serialized. It filters the results +// such that only members with keys listed in the replacer array are +// stringified. + +// Values that do not have JSON representations, such as undefined or +// functions, will not be serialized. Such values in objects will be +// dropped; in arrays they will be replaced with null. You can use +// a replacer function to replace those with JSON values. + +// JSON.stringify(undefined) returns undefined. + +// The optional space parameter produces a stringification of the +// value that is filled with line breaks and indentation to make it +// easier to read. + +// If the space parameter is a non-empty string, then that string will +// be used for indentation. If the space parameter is a number, then +// the indentation will be that many spaces. + +// Example: + +// text = JSON.stringify(["e", {pluribus: "unum"}]); +// // text is '["e",{"pluribus":"unum"}]' + +// text = JSON.stringify(["e", {pluribus: "unum"}], null, "\t"); +// // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' + +// text = JSON.stringify([new Date()], function (key, value) { +// return this[key] instanceof Date +// ? "Date(" + this[key] + ")" +// : value; +// }); +// // text is '["Date(---current time---)"]' + +// JSON.parse(text, reviver) +// This method parses a JSON text to produce an object or array. +// It can throw a SyntaxError exception. + +// The optional reviver parameter is a function that can filter and +// transform the results. It receives each of the keys and values, +// and its return value is used instead of the original value. +// If it returns what it received, then the structure is not modified. +// If it returns undefined then the member is deleted. + +// Example: + +// // Parse the text. Values that look like ISO date strings will +// // be converted to Date objects. + +// myData = JSON.parse(text, function (key, value) { +// var a; +// if (typeof value === "string") { +// a = +// /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); +// if (a) { +// return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], +// +a[5], +a[6])); +// } +// } +// return value; +// }); + +// myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { +// var d; +// if (typeof value === "string" && +// value.slice(0, 5) === "Date(" && +// value.slice(-1) === ")") { +// d = new Date(value.slice(5, -1)); +// if (d) { +// return d; +// } +// } +// return value; +// }); + +// This is a reference implementation. You are free to copy, modify, or +// redistribute. + +/*jslint + eval, for, this +*/ + +/*property + JSON, apply, call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, + getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, + lastIndex, length, parse, prototype, push, replace, slice, stringify, + test, toJSON, toString, valueOf +*/ + +var setupCustomJSON = function(JSON) { + + var rx_one = /^[\],:{}\s]*$/; + var rx_two = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g; + var rx_three = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g; + var rx_four = /(?:^|:|,)(?:\s*\[)+/g; + var rx_escapable = /[\\"\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; + var rx_dangerous = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; + + function f(n) { + // Format integers to have at least two digits. + return n < 10 + ? "0" + n + : n; + } + + function this_value() { + return this.valueOf(); + } + + if (typeof Date.prototype.toJSON !== "function") { + + Date.prototype.toJSON = function () { + + return isFinite(this.valueOf()) + ? this.getUTCFullYear() + "-" + + f(this.getUTCMonth() + 1) + "-" + + f(this.getUTCDate()) + "T" + + f(this.getUTCHours()) + ":" + + f(this.getUTCMinutes()) + ":" + + f(this.getUTCSeconds()) + "Z" + : null; + }; + + Boolean.prototype.toJSON = this_value; + Number.prototype.toJSON = this_value; + String.prototype.toJSON = this_value; + } + + var gap; + var indent; + var meta; + var rep; + + + function quote(string) { + +// If the string contains no control characters, no quote characters, and no +// backslash characters, then we can safely slap some quotes around it. +// Otherwise we must also replace the offending characters with safe escape +// sequences. + + rx_escapable.lastIndex = 0; + return rx_escapable.test(string) + ? "\"" + string.replace(rx_escapable, function (a) { + var c = meta[a]; + return typeof c === "string" + ? c + : "\\u" + ("0000" + a.charCodeAt(0).toString(16)).slice(-4); + }) + "\"" + : "\"" + string + "\""; + } + + + function str(key, holder) { + +// Produce a string from holder[key]. + + var i; // The loop counter. + var k; // The member key. + var v; // The member value. + var length; + var mind = gap; + var partial; + var value = holder[key]; + +// If the value has a toJSON method, call it to obtain a replacement value. + + if (value && typeof value === "object" && + typeof value.toJSON === "function") { + value = value.toJSON(key); + } + +// If we were called with a replacer function, then call the replacer to +// obtain a replacement value. + + if (typeof rep === "function") { + value = rep.call(holder, key, value); + } + +// What happens next depends on the value's type. + + switch (typeof value) { + case "string": + return quote(value); + + case "number": + +// JSON numbers must be finite. Encode non-finite numbers as null. + + return isFinite(value) + ? String(value) + : "null"; + + case "boolean": + case "null": + +// If the value is a boolean or null, convert it to a string. Note: +// typeof null does not produce "null". The case is included here in +// the remote chance that this gets fixed someday. + + return String(value); + +// If the type is "object", we might be dealing with an object or an array or +// null. + + case "object": + +// Due to a specification blunder in ECMAScript, typeof null is "object", +// so watch out for that case. + + if (!value) { + return "null"; + } + +// Make an array to hold the partial results of stringifying this object value. + + gap += indent; + partial = []; + +// Is the value an array? + + if (Object.prototype.toString.apply(value) === "[object Array]") { + +// The value is an array. Stringify every element. Use null as a placeholder +// for non-JSON values. + + length = value.length; + for (i = 0; i < length; i += 1) { + partial[i] = str(i, value) || "null"; + } + +// Join all of the elements together, separated with commas, and wrap them in +// brackets. + + v = partial.length === 0 + ? "[]" + : gap + ? "[\n" + gap + partial.join(",\n" + gap) + "\n" + mind + "]" + : "[" + partial.join(",") + "]"; + gap = mind; + return v; + } + +// If the replacer is an array, use it to select the members to be stringified. + + if (rep && typeof rep === "object") { + length = rep.length; + for (i = 0; i < length; i += 1) { + if (typeof rep[i] === "string") { + k = rep[i]; + v = str(k, value); + if (v) { + partial.push(quote(k) + ( + gap + ? ": " + : ":" + ) + v); + } + } + } + } else { + +// Otherwise, iterate through all of the keys in the object. + + for (k in value) { + if (Object.prototype.hasOwnProperty.call(value, k)) { + v = str(k, value); + if (v) { + partial.push(quote(k) + ( + gap + ? ": " + : ":" + ) + v); + } + } + } + } + +// Join all of the member texts together, separated with commas, +// and wrap them in braces. + + v = partial.length === 0 + ? "{}" + : gap + ? "{\n" + gap + partial.join(",\n" + gap) + "\n" + mind + "}" + : "{" + partial.join(",") + "}"; + gap = mind; + return v; + } + } + +// If the JSON object does not yet have a stringify method, give it one. + + if (typeof JSON.stringify !== "function") { + meta = { // table of character substitutions + "\b": "\\b", + "\t": "\\t", + "\n": "\\n", + "\f": "\\f", + "\r": "\\r", + "\"": "\\\"", + "\\": "\\\\" + }; + JSON.stringify = function (value, replacer, space) { + +// The stringify method takes a value and an optional replacer, and an optional +// space parameter, and returns a JSON text. The replacer can be a function +// that can replace values, or an array of strings that will select the keys. +// A default replacer method can be provided. Use of the space parameter can +// produce text that is more easily readable. + + var i; + gap = ""; + indent = ""; + +// If the space parameter is a number, make an indent string containing that +// many spaces. + + if (typeof space === "number") { + for (i = 0; i < space; i += 1) { + indent += " "; + } + +// If the space parameter is a string, it will be used as the indent string. + + } else if (typeof space === "string") { + indent = space; + } + +// If there is a replacer, it must be a function or an array. +// Otherwise, throw an error. + + rep = replacer; + if (replacer && typeof replacer !== "function" && + (typeof replacer !== "object" || + typeof replacer.length !== "number")) { + throw new Error("JSON.stringify"); + } + +// Make a fake root object containing our value under the key of "". +// Return the result of stringifying the value. + + return str("", {"": value}); + }; + } + + +// If the JSON object does not yet have a parse method, give it one. + + if (typeof JSON.parse !== "function") { + JSON.parse = function (text, reviver) { + +// The parse method takes a text and an optional reviver function, and returns +// a JavaScript value if the text is a valid JSON text. + + var j; + + function walk(holder, key) { + +// The walk method is used to recursively walk the resulting structure so +// that modifications can be made. + + var k; + var v; + var value = holder[key]; + if (value && typeof value === "object") { + for (k in value) { + if (Object.prototype.hasOwnProperty.call(value, k)) { + v = walk(value, k); + if (v !== undefined) { + value[k] = v; + } else { + delete value[k]; + } + } + } + } + return reviver.call(holder, key, value); + } + + +// Parsing happens in four stages. In the first stage, we replace certain +// Unicode characters with escape sequences. JavaScript handles many characters +// incorrectly, either silently deleting them, or treating them as line endings. + + text = String(text); + rx_dangerous.lastIndex = 0; + if (rx_dangerous.test(text)) { + text = text.replace(rx_dangerous, function (a) { + return "\\u" + + ("0000" + a.charCodeAt(0).toString(16)).slice(-4); + }); + } + +// In the second stage, we run the text against regular expressions that look +// for non-JSON patterns. We are especially concerned with "()" and "new" +// because they can cause invocation, and "=" because it can cause mutation. +// But just to be safe, we want to reject all unexpected forms. + +// We split the second stage into 4 regexp operations in order to work around +// crippling inefficiencies in IE's and Safari's regexp engines. First we +// replace the JSON backslash pairs with "@" (a non-JSON character). Second, we +// replace all simple value tokens with "]" characters. Third, we delete all +// open brackets that follow a colon or comma or that begin the text. Finally, +// we look to see that the remaining characters are only whitespace or "]" or +// "," or ":" or "{" or "}". If that is so, then the text is safe for eval. + + if ( + rx_one.test( + text + .replace(rx_two, "@") + .replace(rx_three, "]") + .replace(rx_four, "") + ) + ) { + +// In the third stage we use the eval function to compile the text into a +// JavaScript structure. The "{" operator is subject to a syntactic ambiguity +// in JavaScript: it can begin a block or an object literal. We wrap the text +// in parens to eliminate the ambiguity. + + j = eval("(" + text + ")"); + +// In the optional fourth stage, we recursively walk the new structure, passing +// each name/value pair to a reviver function for possible transformation. + + return (typeof reviver === "function") + ? walk({"": j}, "") + : j; + } + +// If the text is not JSON parseable, then a SyntaxError is thrown. + + throw new SyntaxError("JSON.parse"); + }; + } +} + +module.exports = setupCustomJSON; diff --git a/vendor/JSON-js/json3.js b/vendor/JSON-js/json3.js new file mode 100644 index 000000000..ee12038ff --- /dev/null +++ b/vendor/JSON-js/json3.js @@ -0,0 +1,763 @@ +// json3.js +// 2017-02-21 +// Public Domain. +// NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. +// See http://www.JSON.org/js.html +// This code should be minified before deployment. +// See http://javascript.crockford.com/jsmin.html + +// USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO +// NOT CONTROL. + +// This file creates a global JSON object containing two methods: stringify +// and parse. This file provides the ES5 JSON capability to ES3 systems. +// If a project might run on IE8 or earlier, then this file should be included. +// This file does nothing on ES5 systems. + +// JSON.stringify(value, replacer, space) +// value any JavaScript value, usually an object or array. +// replacer an optional parameter that determines how object +// values are stringified for objects. It can be a +// function or an array of strings. +// space an optional parameter that specifies the indentation +// of nested structures. If it is omitted, the text will +// be packed without extra whitespace. If it is a number, +// it will specify the number of spaces to indent at each +// level. If it is a string (such as "\t" or " "), +// it contains the characters used to indent at each level. +// This method produces a JSON text from a JavaScript value. +// When an object value is found, if the object contains a toJSON +// method, its toJSON method will be called and the result will be +// stringified. A toJSON method does not serialize: it returns the +// value represented by the name/value pair that should be serialized, +// or undefined if nothing should be serialized. The toJSON method +// will be passed the key associated with the value, and this will be +// bound to the value. + +// For example, this would serialize Dates as ISO strings. + +// Date.prototype.toJSON = function (key) { +// function f(n) { +// // Format integers to have at least two digits. +// return (n < 10) +// ? "0" + n +// : n; +// } +// return this.getUTCFullYear() + "-" + +// f(this.getUTCMonth() + 1) + "-" + +// f(this.getUTCDate()) + "T" + +// f(this.getUTCHours()) + ":" + +// f(this.getUTCMinutes()) + ":" + +// f(this.getUTCSeconds()) + "Z"; +// }; + +// You can provide an optional replacer method. It will be passed the +// key and value of each member, with this bound to the containing +// object. The value that is returned from your method will be +// serialized. If your method returns undefined, then the member will +// be excluded from the serialization. + +// If the replacer parameter is an array of strings, then it will be +// used to select the members to be serialized. It filters the results +// such that only members with keys listed in the replacer array are +// stringified. + +// Values that do not have JSON representations, such as undefined or +// functions, will not be serialized. Such values in objects will be +// dropped; in arrays they will be replaced with null. You can use +// a replacer function to replace those with JSON values. + +// JSON.stringify(undefined) returns undefined. + +// The optional space parameter produces a stringification of the +// value that is filled with line breaks and indentation to make it +// easier to read. + +// If the space parameter is a non-empty string, then that string will +// be used for indentation. If the space parameter is a number, then +// the indentation will be that many spaces. + +// Example: + +// text = JSON.stringify(["e", {pluribus: "unum"}]); +// // text is '["e",{"pluribus":"unum"}]' + +// text = JSON.stringify(["e", {pluribus: "unum"}], null, "\t"); +// // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' + +// text = JSON.stringify([new Date()], function (key, value) { +// return this[key] instanceof Date +// ? "Date(" + this[key] + ")" +// : value; +// }); +// // text is '["Date(---current time---)"]' + +// JSON.parse(text, reviver) +// This method parses a JSON text to produce an object or array. +// It can throw a SyntaxError exception. +// This has been modified to use JSON-js/json_parse_state.js as the +// parser instead of the one built around eval found in JSON-js/json2.js + +// The optional reviver parameter is a function that can filter and +// transform the results. It receives each of the keys and values, +// and its return value is used instead of the original value. +// If it returns what it received, then the structure is not modified. +// If it returns undefined then the member is deleted. + +// Example: + +// // Parse the text. Values that look like ISO date strings will +// // be converted to Date objects. + +// myData = JSON.parse(text, function (key, value) { +// var a; +// if (typeof value === "string") { +// a = +// /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); +// if (a) { +// return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], +// +a[5], +a[6])); +// } +// } +// return value; +// }); + +// myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { +// var d; +// if (typeof value === "string" && +// value.slice(0, 5) === "Date(" && +// value.slice(-1) === ")") { +// d = new Date(value.slice(5, -1)); +// if (d) { +// return d; +// } +// } +// return value; +// }); + +// This is a reference implementation. You are free to copy, modify, or +// redistribute. + +/*jslint + for, this + */ + +/*property + JSON, apply, call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, + getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, + lastIndex, length, parse, prototype, push, replace, slice, stringify, + test, toJSON, toString, valueOf + */ + +var setupCustomJSON = function(JSON) { + + var rx_one = /^[\],:{}\s]*$/; + var rx_two = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g; + var rx_three = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g; + var rx_four = /(?:^|:|,)(?:\s*\[)+/g; + var rx_escapable = /[\\"\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; + var rx_dangerous = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; + + function f(n) { + // Format integers to have at least two digits. + return n < 10 + ? "0" + n + : n; + } + + function this_value() { + return this.valueOf(); + } + + if (typeof Date.prototype.toJSON !== "function") { + + Date.prototype.toJSON = function () { + + return isFinite(this.valueOf()) + ? this.getUTCFullYear() + "-" + + f(this.getUTCMonth() + 1) + "-" + + f(this.getUTCDate()) + "T" + + f(this.getUTCHours()) + ":" + + f(this.getUTCMinutes()) + ":" + + f(this.getUTCSeconds()) + "Z" + : null; + }; + + Boolean.prototype.toJSON = this_value; + Number.prototype.toJSON = this_value; + String.prototype.toJSON = this_value; + } + + var gap; + var indent; + var meta; + var rep; + + + function quote(string) { + + // If the string contains no control characters, no quote characters, and no + // backslash characters, then we can safely slap some quotes around it. + // Otherwise we must also replace the offending characters with safe escape + // sequences. + + rx_escapable.lastIndex = 0; + return rx_escapable.test(string) + ? "\"" + string.replace(rx_escapable, function (a) { + var c = meta[a]; + return typeof c === "string" + ? c + : "\\u" + ("0000" + a.charCodeAt(0).toString(16)).slice(-4); + }) + "\"" + : "\"" + string + "\""; + } + + + function str(key, holder) { + + // Produce a string from holder[key]. + + var i; // The loop counter. + var k; // The member key. + var v; // The member value. + var length; + var mind = gap; + var partial; + var value = holder[key]; + + // If the value has a toJSON method, call it to obtain a replacement value. + + if (value && typeof value === "object" && + typeof value.toJSON === "function") { + value = value.toJSON(key); + } + + // If we were called with a replacer function, then call the replacer to + // obtain a replacement value. + + if (typeof rep === "function") { + value = rep.call(holder, key, value); + } + + // What happens next depends on the value's type. + + switch (typeof value) { + case "string": + return quote(value); + + case "number": + + // JSON numbers must be finite. Encode non-finite numbers as null. + + return isFinite(value) + ? String(value) + : "null"; + + case "boolean": + case "null": + + // If the value is a boolean or null, convert it to a string. Note: + // typeof null does not produce "null". The case is included here in + // the remote chance that this gets fixed someday. + + return String(value); + + // If the type is "object", we might be dealing with an object or an array or + // null. + + case "object": + + // Due to a specification blunder in ECMAScript, typeof null is "object", + // so watch out for that case. + + if (!value) { + return "null"; + } + + // Make an array to hold the partial results of stringifying this object value. + + gap += indent; + partial = []; + + // Is the value an array? + + if (Object.prototype.toString.apply(value) === "[object Array]") { + + // The value is an array. Stringify every element. Use null as a placeholder + // for non-JSON values. + + length = value.length; + for (i = 0; i < length; i += 1) { + partial[i] = str(i, value) || "null"; + } + + // Join all of the elements together, separated with commas, and wrap them in + // brackets. + + v = partial.length === 0 + ? "[]" + : gap + ? "[\n" + gap + partial.join(",\n" + gap) + "\n" + mind + "]" + : "[" + partial.join(",") + "]"; + gap = mind; + return v; + } + + // If the replacer is an array, use it to select the members to be stringified. + + if (rep && typeof rep === "object") { + length = rep.length; + for (i = 0; i < length; i += 1) { + if (typeof rep[i] === "string") { + k = rep[i]; + v = str(k, value); + if (v) { + partial.push(quote(k) + ( + gap + ? ": " + : ":" + ) + v); + } + } + } + } else { + + // Otherwise, iterate through all of the keys in the object. + + for (k in value) { + if (Object.prototype.hasOwnProperty.call(value, k)) { + v = str(k, value); + if (v) { + partial.push(quote(k) + ( + gap + ? ": " + : ":" + ) + v); + } + } + } + } + + // Join all of the member texts together, separated with commas, + // and wrap them in braces. + + v = partial.length === 0 + ? "{}" + : gap + ? "{\n" + gap + partial.join(",\n" + gap) + "\n" + mind + "}" + : "{" + partial.join(",") + "}"; + gap = mind; + return v; + } + } + + // If the JSON object does not yet have a stringify method, give it one. + + if (typeof JSON.stringify !== "function") { + meta = { // table of character substitutions + "\b": "\\b", + "\t": "\\t", + "\n": "\\n", + "\f": "\\f", + "\r": "\\r", + "\"": "\\\"", + "\\": "\\\\" + }; + JSON.stringify = function (value, replacer, space) { + + // The stringify method takes a value and an optional replacer, and an optional + // space parameter, and returns a JSON text. The replacer can be a function + // that can replace values, or an array of strings that will select the keys. + // A default replacer method can be provided. Use of the space parameter can + // produce text that is more easily readable. + + var i; + gap = ""; + indent = ""; + + // If the space parameter is a number, make an indent string containing that + // many spaces. + + if (typeof space === "number") { + for (i = 0; i < space; i += 1) { + indent += " "; + } + + // If the space parameter is a string, it will be used as the indent string. + + } else if (typeof space === "string") { + indent = space; + } + + // If there is a replacer, it must be a function or an array. + // Otherwise, throw an error. + + rep = replacer; + if (replacer && typeof replacer !== "function" && + (typeof replacer !== "object" || + typeof replacer.length !== "number")) { + throw new Error("JSON.stringify"); + } + + // Make a fake root object containing our value under the key of "". + // Return the result of stringifying the value. + + return str("", {"": value}); + }; + } + + + // If the JSON object does not yet have a parse method, give it one. + + if (typeof JSON.parse !== "function") { + JSON.parse = (function () { + + // This function creates a JSON parse function that uses a state machine rather + // than the dangerous eval function to parse a JSON text. + + var state; // The state of the parser, one of + // 'go' The starting state + // 'ok' The final, accepting state + // 'firstokey' Ready for the first key of the object or + // the closing of an empty object + // 'okey' Ready for the next key of the object + // 'colon' Ready for the colon + // 'ovalue' Ready for the value half of a key/value pair + // 'ocomma' Ready for a comma or closing } + // 'firstavalue' Ready for the first value of an array or + // an empty array + // 'avalue' Ready for the next value of an array + // 'acomma' Ready for a comma or closing ] + var stack; // The stack, for controlling nesting. + var container; // The current container object or array + var key; // The current key + var value; // The current value + var escapes = { // Escapement translation table + "\\": "\\", + "\"": "\"", + "/": "/", + "t": "\t", + "n": "\n", + "r": "\r", + "f": "\f", + "b": "\b" + }; + var string = { // The actions for string tokens + go: function () { + state = "ok"; + }, + firstokey: function () { + key = value; + state = "colon"; + }, + okey: function () { + key = value; + state = "colon"; + }, + ovalue: function () { + state = "ocomma"; + }, + firstavalue: function () { + state = "acomma"; + }, + avalue: function () { + state = "acomma"; + } + }; + var number = { // The actions for number tokens + go: function () { + state = "ok"; + }, + ovalue: function () { + state = "ocomma"; + }, + firstavalue: function () { + state = "acomma"; + }, + avalue: function () { + state = "acomma"; + } + }; + var action = { + + // The action table describes the behavior of the machine. It contains an + // object for each token. Each object contains a method that is called when + // a token is matched in a state. An object will lack a method for illegal + // states. + + "{": { + go: function () { + stack.push({state: "ok"}); + container = {}; + state = "firstokey"; + }, + ovalue: function () { + stack.push({container: container, state: "ocomma", key: key}); + container = {}; + state = "firstokey"; + }, + firstavalue: function () { + stack.push({container: container, state: "acomma"}); + container = {}; + state = "firstokey"; + }, + avalue: function () { + stack.push({container: container, state: "acomma"}); + container = {}; + state = "firstokey"; + } + }, + "}": { + firstokey: function () { + var pop = stack.pop(); + value = container; + container = pop.container; + key = pop.key; + state = pop.state; + }, + ocomma: function () { + var pop = stack.pop(); + container[key] = value; + value = container; + container = pop.container; + key = pop.key; + state = pop.state; + } + }, + "[": { + go: function () { + stack.push({state: "ok"}); + container = []; + state = "firstavalue"; + }, + ovalue: function () { + stack.push({container: container, state: "ocomma", key: key}); + container = []; + state = "firstavalue"; + }, + firstavalue: function () { + stack.push({container: container, state: "acomma"}); + container = []; + state = "firstavalue"; + }, + avalue: function () { + stack.push({container: container, state: "acomma"}); + container = []; + state = "firstavalue"; + } + }, + "]": { + firstavalue: function () { + var pop = stack.pop(); + value = container; + container = pop.container; + key = pop.key; + state = pop.state; + }, + acomma: function () { + var pop = stack.pop(); + container.push(value); + value = container; + container = pop.container; + key = pop.key; + state = pop.state; + } + }, + ":": { + colon: function () { + if (Object.hasOwnProperty.call(container, key)) { + throw new SyntaxError("Duplicate key '" + key + "\""); + } + state = "ovalue"; + } + }, + ",": { + ocomma: function () { + container[key] = value; + state = "okey"; + }, + acomma: function () { + container.push(value); + state = "avalue"; + } + }, + "true": { + go: function () { + value = true; + state = "ok"; + }, + ovalue: function () { + value = true; + state = "ocomma"; + }, + firstavalue: function () { + value = true; + state = "acomma"; + }, + avalue: function () { + value = true; + state = "acomma"; + } + }, + "false": { + go: function () { + value = false; + state = "ok"; + }, + ovalue: function () { + value = false; + state = "ocomma"; + }, + firstavalue: function () { + value = false; + state = "acomma"; + }, + avalue: function () { + value = false; + state = "acomma"; + } + }, + "null": { + go: function () { + value = null; + state = "ok"; + }, + ovalue: function () { + value = null; + state = "ocomma"; + }, + firstavalue: function () { + value = null; + state = "acomma"; + }, + avalue: function () { + value = null; + state = "acomma"; + } + } + }; + + function debackslashify(text) { + + // Remove and replace any backslash escapement. + + return text.replace(/\\(?:u(.{4})|([^u]))/g, function (ignore, b, c) { + return b + ? String.fromCharCode(parseInt(b, 16)) + : escapes[c]; + }); + } + + return function (source, reviver) { + + // A regular expression is used to extract tokens from the JSON text. + // The extraction process is cautious. + + var result; + var tx = /^[\u0020\t\n\r]*(?:([,:\[\]{}]|true|false|null)|(-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)|"((?:[^\r\n\t\\\"]|\\(?:["\\\/trnfb]|u[0-9a-fA-F]{4}))*)")/; + + // Set the starting state. + + state = "go"; + + // The stack records the container, key, and state for each object or array + // that contains another object or array while processing nested structures. + + stack = []; + + // If any error occurs, we will catch it and ultimately throw a syntax error. + + try { + + // For each token... + + while (true) { + result = tx.exec(source); + if (!result) { + break; + } + + // result is the result array from matching the tokenizing regular expression. + // result[0] contains everything that matched, including any initial whitespace. + // result[1] contains any punctuation that was matched, or true, false, or null. + // result[2] contains a matched number, still in string form. + // result[3] contains a matched string, without quotes but with escapement. + + if (result[1]) { + + // Token: Execute the action for this state and token. + + action[result[1]][state](); + + } else if (result[2]) { + + // Number token: Convert the number string into a number value and execute + // the action for this state and number. + + value = +result[2]; + number[state](); + } else { + + // String token: Replace the escapement sequences and execute the action for + // this state and string. + + value = debackslashify(result[3]); + string[state](); + } + + // Remove the token from the string. The loop will continue as long as there + // are tokens. This is a slow process, but it allows the use of ^ matching, + // which assures that no illegal tokens slip through. + + source = source.slice(result[0].length); + } + + // If we find a state/token combination that is illegal, then the action will + // cause an error. We handle the error by simply changing the state. + + } catch (e) { + state = e; + } + + // The parsing is finished. If we are not in the final "ok" state, or if the + // remaining source contains anything except whitespace, then we did not have + //a well-formed JSON text. + + if (state !== "ok" || (/[^\u0020\t\n\r]/.test(source))) { + throw (state instanceof SyntaxError) + ? state + : new SyntaxError("JSON"); + } + + // If there is a reviver function, we recursively walk the new structure, + // passing each name/value pair to the reviver function for possible + // transformation, starting with a temporary root object that holds the current + // value in an empty key. If there is not a reviver function, we simply return + // that value. + + return (typeof reviver === "function") + ? (function walk(holder, key) { + var k; + var v; + var val = holder[key]; + if (val && typeof val === "object") { + for (k in value) { + if (Object.prototype.hasOwnProperty.call(val, k)) { + v = walk(val, k); + if (v !== undefined) { + val[k] = v; + } else { + delete val[k]; + } + } + } + } + return reviver.call(holder, key, val); + }({"": value}, "")) + : value; + }; + }()); + } +} + +module.exports = setupCustomJSON; diff --git a/vendor/JSON-js/json_parse.js b/vendor/JSON-js/json_parse.js new file mode 100644 index 000000000..6fcb79650 --- /dev/null +++ b/vendor/JSON-js/json_parse.js @@ -0,0 +1,356 @@ +/* + json_parse.js + 2016-05-02 + + Public Domain. + + NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. + + This file creates a json_parse function. + + json_parse(text, reviver) + This method parses a JSON text to produce an object or array. + It can throw a SyntaxError exception. + + The optional reviver parameter is a function that can filter and + transform the results. It receives each of the keys and values, + and its return value is used instead of the original value. + If it returns what it received, then the structure is not modified. + If it returns undefined then the member is deleted. + + Example: + + // Parse the text. Values that look like ISO date strings will + // be converted to Date objects. + + myData = json_parse(text, function (key, value) { + var a; + if (typeof value === "string") { + a = +/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); + if (a) { + return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], + +a[5], +a[6])); + } + } + return value; + }); + + This is a reference implementation. You are free to copy, modify, or + redistribute. + + This code should be minified before deployment. + See http://javascript.crockford.com/jsmin.html + + USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO + NOT CONTROL. +*/ + +/*jslint for */ + +/*property + at, b, call, charAt, f, fromCharCode, hasOwnProperty, message, n, name, + prototype, push, r, t, text +*/ + +var json_parse = (function () { + "use strict"; + +// This is a function that can parse a JSON text, producing a JavaScript +// data structure. It is a simple, recursive descent parser. It does not use +// eval or regular expressions, so it can be used as a model for implementing +// a JSON parser in other languages. + +// We are defining the function inside of another function to avoid creating +// global variables. + + var at; // The index of the current character + var ch; // The current character + var escapee = { + "\"": "\"", + "\\": "\\", + "/": "/", + b: "\b", + f: "\f", + n: "\n", + r: "\r", + t: "\t" + }; + var text; + + var error = function (m) { + +// Call error when something is wrong. + + throw { + name: "SyntaxError", + message: m, + at: at, + text: text + }; + }; + + var next = function (c) { + +// If a c parameter is provided, verify that it matches the current character. + + if (c && c !== ch) { + error("Expected '" + c + "' instead of '" + ch + "'"); + } + +// Get the next character. When there are no more characters, +// return the empty string. + + ch = text.charAt(at); + at += 1; + return ch; + }; + + var number = function () { + +// Parse a number value. + + var value; + var string = ""; + + if (ch === "-") { + string = "-"; + next("-"); + } + while (ch >= "0" && ch <= "9") { + string += ch; + next(); + } + if (ch === ".") { + string += "."; + while (next() && ch >= "0" && ch <= "9") { + string += ch; + } + } + if (ch === "e" || ch === "E") { + string += ch; + next(); + if (ch === "-" || ch === "+") { + string += ch; + next(); + } + while (ch >= "0" && ch <= "9") { + string += ch; + next(); + } + } + value = +string; + if (!isFinite(value)) { + error("Bad number"); + } else { + return value; + } + }; + + var string = function () { + +// Parse a string value. + + var hex; + var i; + var value = ""; + var uffff; + +// When parsing for string values, we must look for " and \ characters. + + if (ch === "\"") { + while (next()) { + if (ch === "\"") { + next(); + return value; + } + if (ch === "\\") { + next(); + if (ch === "u") { + uffff = 0; + for (i = 0; i < 4; i += 1) { + hex = parseInt(next(), 16); + if (!isFinite(hex)) { + break; + } + uffff = uffff * 16 + hex; + } + value += String.fromCharCode(uffff); + } else if (typeof escapee[ch] === "string") { + value += escapee[ch]; + } else { + break; + } + } else { + value += ch; + } + } + } + error("Bad string"); + }; + + var white = function () { + +// Skip whitespace. + + while (ch && ch <= " ") { + next(); + } + }; + + var word = function () { + +// true, false, or null. + + switch (ch) { + case "t": + next("t"); + next("r"); + next("u"); + next("e"); + return true; + case "f": + next("f"); + next("a"); + next("l"); + next("s"); + next("e"); + return false; + case "n": + next("n"); + next("u"); + next("l"); + next("l"); + return null; + } + error("Unexpected '" + ch + "'"); + }; + + var value; // Place holder for the value function. + + var array = function () { + +// Parse an array value. + + var arr = []; + + if (ch === "[") { + next("["); + white(); + if (ch === "]") { + next("]"); + return arr; // empty array + } + while (ch) { + arr.push(value()); + white(); + if (ch === "]") { + next("]"); + return arr; + } + next(","); + white(); + } + } + error("Bad array"); + }; + + var object = function () { + +// Parse an object value. + + var key; + var obj = {}; + + if (ch === "{") { + next("{"); + white(); + if (ch === "}") { + next("}"); + return obj; // empty object + } + while (ch) { + key = string(); + white(); + next(":"); + if (Object.hasOwnProperty.call(obj, key)) { + error("Duplicate key '" + key + "'"); + } + obj[key] = value(); + white(); + if (ch === "}") { + next("}"); + return obj; + } + next(","); + white(); + } + } + error("Bad object"); + }; + + value = function () { + +// Parse a JSON value. It could be an object, an array, a string, a number, +// or a word. + + white(); + switch (ch) { + case "{": + return object(); + case "[": + return array(); + case "\"": + return string(); + case "-": + return number(); + default: + return (ch >= "0" && ch <= "9") + ? number() + : word(); + } + }; + +// Return the json_parse function. It will have access to all of the above +// functions and variables. + + return function (source, reviver) { + var result; + + text = source; + at = 0; + ch = " "; + result = value(); + white(); + if (ch) { + error("Syntax error"); + } + +// If there is a reviver function, we recursively walk the new structure, +// passing each name/value pair to the reviver function for possible +// transformation, starting with a temporary root object that holds the result +// in an empty key. If there is not a reviver function, we simply return the +// result. + + return (typeof reviver === "function") + ? (function walk(holder, key) { + var k; + var v; + var val = holder[key]; + if (val && typeof val === "object") { + for (k in val) { + if (Object.prototype.hasOwnProperty.call(val, k)) { + v = walk(val, k); + if (v !== undefined) { + val[k] = v; + } else { + delete val[k]; + } + } + } + } + return reviver.call(holder, key, val); + }({"": result}, "")) + : result; + }; +}()); diff --git a/vendor/JSON-js/json_parse_state.js b/vendor/JSON-js/json_parse_state.js new file mode 100644 index 000000000..5241e45c7 --- /dev/null +++ b/vendor/JSON-js/json_parse_state.js @@ -0,0 +1,405 @@ +/* + json_parse_state.js + 2016-05-02 + + Public Domain. + + NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. + + This file creates a json_parse function. + + json_parse(text, reviver) + This method parses a JSON text to produce an object or array. + It can throw a SyntaxError exception. + + The optional reviver parameter is a function that can filter and + transform the results. It receives each of the keys and values, + and its return value is used instead of the original value. + If it returns what it received, then the structure is not modified. + If it returns undefined then the member is deleted. + + Example: + + // Parse the text. Values that look like ISO date strings will + // be converted to Date objects. + + myData = json_parse(text, function (key, value) { + var a; + if (typeof value === "string") { + a = +/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); + if (a) { + return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], + +a[5], +a[6])); + } + } + return value; + }); + + This is a reference implementation. You are free to copy, modify, or + redistribute. + + This code should be minified before deployment. + See http://javascript.crockford.com/jsmin.html + + USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO + NOT CONTROL. +*/ + +/*jslint for */ + +/*property + acomma, avalue, b, call, colon, container, exec, f, false, firstavalue, + firstokey, fromCharCode, go, hasOwnProperty, key, length, n, null, ocomma, + okey, ovalue, pop, prototype, push, r, replace, slice, state, t, test, + true +*/ + +var json_parse = (function () { + "use strict"; + +// This function creates a JSON parse function that uses a state machine rather +// than the dangerous eval function to parse a JSON text. + + var state; // The state of the parser, one of + // 'go' The starting state + // 'ok' The final, accepting state + // 'firstokey' Ready for the first key of the object or + // the closing of an empty object + // 'okey' Ready for the next key of the object + // 'colon' Ready for the colon + // 'ovalue' Ready for the value half of a key/value pair + // 'ocomma' Ready for a comma or closing } + // 'firstavalue' Ready for the first value of an array or + // an empty array + // 'avalue' Ready for the next value of an array + // 'acomma' Ready for a comma or closing ] + var stack; // The stack, for controlling nesting. + var container; // The current container object or array + var key; // The current key + var value; // The current value + var escapes = { // Escapement translation table + "\\": "\\", + "\"": "\"", + "/": "/", + "t": "\t", + "n": "\n", + "r": "\r", + "f": "\f", + "b": "\b" + }; + var string = { // The actions for string tokens + go: function () { + state = "ok"; + }, + firstokey: function () { + key = value; + state = "colon"; + }, + okey: function () { + key = value; + state = "colon"; + }, + ovalue: function () { + state = "ocomma"; + }, + firstavalue: function () { + state = "acomma"; + }, + avalue: function () { + state = "acomma"; + } + }; + var number = { // The actions for number tokens + go: function () { + state = "ok"; + }, + ovalue: function () { + state = "ocomma"; + }, + firstavalue: function () { + state = "acomma"; + }, + avalue: function () { + state = "acomma"; + } + }; + var action = { + +// The action table describes the behavior of the machine. It contains an +// object for each token. Each object contains a method that is called when +// a token is matched in a state. An object will lack a method for illegal +// states. + + "{": { + go: function () { + stack.push({state: "ok"}); + container = {}; + state = "firstokey"; + }, + ovalue: function () { + stack.push({container: container, state: "ocomma", key: key}); + container = {}; + state = "firstokey"; + }, + firstavalue: function () { + stack.push({container: container, state: "acomma"}); + container = {}; + state = "firstokey"; + }, + avalue: function () { + stack.push({container: container, state: "acomma"}); + container = {}; + state = "firstokey"; + } + }, + "}": { + firstokey: function () { + var pop = stack.pop(); + value = container; + container = pop.container; + key = pop.key; + state = pop.state; + }, + ocomma: function () { + var pop = stack.pop(); + container[key] = value; + value = container; + container = pop.container; + key = pop.key; + state = pop.state; + } + }, + "[": { + go: function () { + stack.push({state: "ok"}); + container = []; + state = "firstavalue"; + }, + ovalue: function () { + stack.push({container: container, state: "ocomma", key: key}); + container = []; + state = "firstavalue"; + }, + firstavalue: function () { + stack.push({container: container, state: "acomma"}); + container = []; + state = "firstavalue"; + }, + avalue: function () { + stack.push({container: container, state: "acomma"}); + container = []; + state = "firstavalue"; + } + }, + "]": { + firstavalue: function () { + var pop = stack.pop(); + value = container; + container = pop.container; + key = pop.key; + state = pop.state; + }, + acomma: function () { + var pop = stack.pop(); + container.push(value); + value = container; + container = pop.container; + key = pop.key; + state = pop.state; + } + }, + ":": { + colon: function () { + if (Object.hasOwnProperty.call(container, key)) { + throw new SyntaxError("Duplicate key '" + key + "\""); + } + state = "ovalue"; + } + }, + ",": { + ocomma: function () { + container[key] = value; + state = "okey"; + }, + acomma: function () { + container.push(value); + state = "avalue"; + } + }, + "true": { + go: function () { + value = true; + state = "ok"; + }, + ovalue: function () { + value = true; + state = "ocomma"; + }, + firstavalue: function () { + value = true; + state = "acomma"; + }, + avalue: function () { + value = true; + state = "acomma"; + } + }, + "false": { + go: function () { + value = false; + state = "ok"; + }, + ovalue: function () { + value = false; + state = "ocomma"; + }, + firstavalue: function () { + value = false; + state = "acomma"; + }, + avalue: function () { + value = false; + state = "acomma"; + } + }, + "null": { + go: function () { + value = null; + state = "ok"; + }, + ovalue: function () { + value = null; + state = "ocomma"; + }, + firstavalue: function () { + value = null; + state = "acomma"; + }, + avalue: function () { + value = null; + state = "acomma"; + } + } + }; + + function debackslashify(text) { + +// Remove and replace any backslash escapement. + + return text.replace(/\\(?:u(.{4})|([^u]))/g, function (ignore, b, c) { + return b + ? String.fromCharCode(parseInt(b, 16)) + : escapes[c]; + }); + } + + return function (source, reviver) { + +// A regular expression is used to extract tokens from the JSON text. +// The extraction process is cautious. + + var result; + var tx = /^[\u0020\t\n\r]*(?:([,:\[\]{}]|true|false|null)|(-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)|"((?:[^\r\n\t\\\"]|\\(?:["\\\/trnfb]|u[0-9a-fA-F]{4}))*)")/; + +// Set the starting state. + + state = "go"; + +// The stack records the container, key, and state for each object or array +// that contains another object or array while processing nested structures. + + stack = []; + +// If any error occurs, we will catch it and ultimately throw a syntax error. + + try { + +// For each token... + + while (true) { + result = tx.exec(source); + if (!result) { + break; + } + +// result is the result array from matching the tokenizing regular expression. +// result[0] contains everything that matched, including any initial whitespace. +// result[1] contains any punctuation that was matched, or true, false, or null. +// result[2] contains a matched number, still in string form. +// result[3] contains a matched string, without quotes but with escapement. + + if (result[1]) { + +// Token: Execute the action for this state and token. + + action[result[1]][state](); + + } else if (result[2]) { + +// Number token: Convert the number string into a number value and execute +// the action for this state and number. + + value = +result[2]; + number[state](); + } else { + +// String token: Replace the escapement sequences and execute the action for +// this state and string. + + value = debackslashify(result[3]); + string[state](); + } + +// Remove the token from the string. The loop will continue as long as there +// are tokens. This is a slow process, but it allows the use of ^ matching, +// which assures that no illegal tokens slip through. + + source = source.slice(result[0].length); + } + +// If we find a state/token combination that is illegal, then the action will +// cause an error. We handle the error by simply changing the state. + + } catch (e) { + state = e; + } + +// The parsing is finished. If we are not in the final "ok" state, or if the +// remaining source contains anything except whitespace, then we did not have +//a well-formed JSON text. + + if (state !== "ok" || (/[^\u0020\t\n\r]/.test(source))) { + throw (state instanceof SyntaxError) + ? state + : new SyntaxError("JSON"); + } + +// If there is a reviver function, we recursively walk the new structure, +// passing each name/value pair to the reviver function for possible +// transformation, starting with a temporary root object that holds the current +// value in an empty key. If there is not a reviver function, we simply return +// that value. + + return (typeof reviver === "function") + ? (function walk(holder, key) { + var k; + var v; + var val = holder[key]; + if (val && typeof val === "object") { + for (k in value) { + if (Object.prototype.hasOwnProperty.call(val, k)) { + v = walk(val, k); + if (v !== undefined) { + val[k] = v; + } else { + delete val[k]; + } + } + } + } + return reviver.call(holder, key, val); + }({"": value}, "")) + : value; + }; +}());