ui/vendor/json-sanitizer/json-sanitizer.js

192 lines
4.9 KiB
JavaScript

/**
* Sanitize a JSON-like string containing. For example changes JavaScript
* notation into JSON notation.
* This function for example changes a string like "{a: 2, 'b': {c: 'd'}"
* into '{"a": 2, "b": {"c": "d"}'
* @param {string} jsString
* @returns {string} json
*/
(function () {
function generateModule(name, values) {
define(name, [], function () {
'use strict';
return values;
});
}
generateModule('json-sanitizer', {
'default': function sanitize(jsString) {
// escape all single and double quotes inside strings
var chars = [];
var i = 0;
//If JSON starts with a function (characters/digits/"_-"), remove this function.
//This is useful for "stripping" JSONP objects to become JSON
//For example: /* some comment */ function_12321321 ( [{"a":"b"}] ); => [{"a":"b"}]
var match = jsString.match(/^\s*(\/\*(.|[\r\n])*?\*\/)?\s*[\da-zA-Z_$]+\s*\(([\s\S]*)\)\s*;?\s*$/);
if (match) {
jsString = match[3];
}
// helper functions to get the current/prev/next character
function curr() { return jsString.charAt(i); }
function next() { return jsString.charAt(i + 1); }
function prev() { return jsString.charAt(i - 1); }
// get the last parsed non-whitespace character
function lastNonWhitespace() {
var p = chars.length - 1;
while (p >= 0) {
var pp = chars[p];
if (pp !== ' ' && pp !== '\n' && pp !== '\r' && pp !== '\t') { // non whitespace
return pp;
}
p--;
}
return '';
}
// skip a block comment '/* ... */'
function skipBlockComment() {
i += 2;
while (i < jsString.length && (curr() !== '*' || next() !== '/')) {
i++;
}
i += 2;
}
// skip a comment '// ...'
function skipComment() {
i += 2;
while (i < jsString.length && (curr() !== '\n')) {
i++;
}
}
// parse single or double quoted string
function parseString(quote) {
chars.push('"');
i++;
var c = curr();
while (i < jsString.length && c !== quote) {
if (c === '"' && prev() !== '\\') {
// unescaped double quote, escape it
chars.push('\\');
}
// handle escape character
if (c === '\\') {
i++;
c = curr();
// remove the escape character when followed by a single quote ', not needed
if (c !== '\'') {
chars.push('\\');
}
}
chars.push(c);
i++;
c = curr();
}
if (c === quote) {
chars.push('"');
i++;
}
}
// parse an unquoted key
function parseKey() {
var specialValues = ['null', 'true', 'false'];
var key = '';
var c = curr();
var regexp = /[a-zA-Z_$\d]/; // letter, number, underscore, dollar character
while (regexp.test(c)) {
key += c;
i++;
c = curr();
}
if (specialValues.indexOf(key) === -1) {
chars.push('"' + key + '"');
}
else {
chars.push(key);
}
}
// parse an unquoted value
function parseValue() {
var specialValues = ['null', 'true', 'false'];
var key = '';
var c = curr();
while (i < jsString.length && [',', '}', ']'].indexOf(c) === -1) {
key += c;
i++;
c = curr();
}
key = key.trim()
if (specialValues.indexOf(key) === -1 && !(key + '').match(/^([0-9]+\.)?[0-9]*$/)) {
chars.push('"' + key + '"');
}
else {
chars.push(key);
}
}
function isTrailingComma() {
var idx = i+1;
var c = jsString.charAt(idx);
while ( idx < jsString.length && [' ','\n','\r','\t'].indexOf(c) >= 0) {
idx++;
c = jsString.charAt(idx);
}
if ( ['}',']'].indexOf(c) >= 0 ) {
return true;
}
return false;
}
while (i < jsString.length) {
var c = curr();
if (c === '/' && next() === '*') {
skipBlockComment();
}
else if (c === '/' && next() === '/') {
skipComment();
}
else if (c === '\'' || c === '"') {
parseString(c);
}
else if (/[a-zA-Z_$]/.test(c) && ['{', ','].indexOf(lastNonWhitespace()) !== -1) {
// an unquoted object key (like a in '{a:2}')
parseKey();
}
else if (c.trim() !== '' && ['[', '{'].indexOf(c) === -1 && ':' === lastNonWhitespace()) {
// an unquoted object value (like b in '{a:b}')
parseValue();
}
else if ( c === ',' && isTrailingComma() ) {
i++;
}
else {
chars.push(c);
i++;
}
}
return chars.join('');
}
});
})();