| OLD | NEW |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 goog.provide('cvox.ChromeVoxJSON'); | 5 goog.provide('cvox.ChromeVoxJSON'); |
| 6 | 6 |
| 7 | 7 |
| 8 /** | 8 /** |
| 9 * @fileoverview A simple wrapper around the JSON APIs. | 9 * @fileoverview A simple wrapper around the JSON APIs. |
| 10 * If it is possible to use the browser's built in native JSON, then | 10 * If it is possible to use the browser's built in native JSON, then |
| (...skipping 25 matching lines...) Expand all Loading... |
| 36 * | 36 * |
| 37 * See http://www.JSON.org/js.html | 37 * See http://www.JSON.org/js.html |
| 38 */ | 38 */ |
| 39 (function() { | 39 (function() { |
| 40 function f(n) { | 40 function f(n) { |
| 41 // Format integers to have at least two digits. | 41 // Format integers to have at least two digits. |
| 42 return n < 10 ? '0' + n : n; | 42 return n < 10 ? '0' + n : n; |
| 43 } | 43 } |
| 44 | 44 |
| 45 if (typeof Date.prototype.toJSON !== 'function') { | 45 if (typeof Date.prototype.toJSON !== 'function') { |
| 46 | |
| 47 Date.prototype.toJSON = function(key) { | 46 Date.prototype.toJSON = function(key) { |
| 48 | 47 |
| 49 return isFinite(this.valueOf()) ? | 48 return isFinite(this.valueOf()) ? |
| 50 this.getUTCFullYear() + '-' + | 49 this.getUTCFullYear() + '-' + f(this.getUTCMonth() + 1) + '-' + |
| 51 f(this.getUTCMonth() + 1) + '-' + | 50 f(this.getUTCDate()) + 'T' + f(this.getUTCHours()) + ':' + |
| 52 f(this.getUTCDate()) + 'T' + | 51 f(this.getUTCMinutes()) + ':' + f(this.getUTCSeconds()) + 'Z' : |
| 53 f(this.getUTCHours()) + ':' + | 52 'null'; |
| 54 f(this.getUTCMinutes()) + ':' + | |
| 55 f(this.getUTCSeconds()) + 'Z' : 'null'; | |
| 56 }; | 53 }; |
| 57 | 54 |
| 58 Boolean.prototype.toJSON = | 55 Boolean.prototype.toJSON = Number.prototype.toJSON = |
| 59 Number.prototype.toJSON = | 56 String.prototype.toJSON = function(key) { |
| 60 String.prototype.toJSON = function(key) { | 57 return /** @type {string} */ (this.valueOf()); |
| 61 return /** @type {string} */ (this.valueOf()); | 58 }; |
| 62 }; | |
| 63 } | 59 } |
| 64 | 60 |
| 65 var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u
202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, | 61 var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u
202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, |
| 66 escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b
5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, | 62 escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b
5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, |
| 67 gap, | 63 gap, |
| 68 indent, | 64 indent, |
| 69 meta = { // table of character substitutions | 65 meta = { // table of character substitutions |
| 70 '\b': '\\b', | 66 '\b': '\\b', |
| 71 '\t': '\\t', | 67 '\t': '\\t', |
| 72 '\n': '\\n', | 68 '\n': '\\n', |
| 73 '\f': '\\f', | 69 '\f': '\\f', |
| 74 '\r': '\\r', | 70 '\r': '\\r', |
| 75 '"' : '\\"', | 71 '"' : '\\"', |
| 76 '\\': '\\\\' | 72 '\\': '\\\\' |
| 77 }, | 73 }, |
| 78 rep; | 74 rep; |
| 79 | 75 |
| 80 | 76 |
| 81 function quote(string) { | 77 function quote(string) { |
| 82 | |
| 83 // If the string contains no control characters, no quote characters, and | 78 // If the string contains no control characters, no quote characters, and |
| 84 // no backslash characters, then we can safely slap some quotes around it. | 79 // no backslash characters, then we can safely slap some quotes around it. |
| 85 // Otherwise we must also replace the offending characters with safe | 80 // Otherwise we must also replace the offending characters with safe |
| 86 // escape sequences. | 81 // escape sequences. |
| 87 | 82 |
| 88 escapable.lastIndex = 0; | 83 escapable.lastIndex = 0; |
| 89 return escapable.test(string) ? | 84 return escapable.test(string) ? '"' + |
| 90 '"' + string.replace(escapable, function(a) { | 85 string.replace( |
| 91 var c = meta[a]; | 86 escapable, |
| 92 return typeof c === 'string' ? c : | 87 function(a) { |
| 93 '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); | 88 var c = meta[a]; |
| 94 }) + '"' : | 89 return typeof c === 'string' ? c : |
| 95 '"' + string + '"'; | 90 '\\u' + |
| 91 ('0000' + a.charCodeAt(0).toString(16)).slice(-4); |
| 92 }) + |
| 93 '"' : |
| 94 '"' + string + '"'; |
| 96 } | 95 } |
| 97 | 96 |
| 98 | 97 |
| 99 function str(key, holder) { | 98 function str(key, holder) { |
| 100 | |
| 101 // Produce a string from holder[key]. | 99 // Produce a string from holder[key]. |
| 102 | 100 |
| 103 var i, // The loop counter. | 101 var i, // The loop counter. |
| 104 k, // The member key. | 102 k, // The member key. |
| 105 v, // The member value. | 103 v, // The member value. |
| 106 length, | 104 length, mind = gap, partial, value = holder[key]; |
| 107 mind = gap, | |
| 108 partial, | |
| 109 value = holder[key]; | |
| 110 | 105 |
| 111 // If the value has a toJSON method, call it to obtain a replacement | 106 // If the value has a toJSON method, call it to obtain a replacement |
| 112 // value. | 107 // value. |
| 113 | 108 |
| 114 if (value && typeof value === 'object' && | 109 if (value && typeof value === 'object' && |
| 115 typeof value.toJSON === 'function') { | 110 typeof value.toJSON === 'function') { |
| 116 value = value.toJSON(key); | 111 value = value.toJSON(key); |
| 117 } | 112 } |
| 118 | 113 |
| 119 // If we were called with a replacer function, then call the replacer to | 114 // If we were called with a replacer function, then call the replacer to |
| 120 // obtain a replacement value. | 115 // obtain a replacement value. |
| 121 | 116 |
| 122 if (typeof rep === 'function') { | 117 if (typeof rep === 'function') { |
| 123 value = rep.call(holder, key, value); | 118 value = rep.call(holder, key, value); |
| 124 } | 119 } |
| 125 | 120 |
| 126 // What happens next depends on the value's type. | 121 // What happens next depends on the value's type. |
| 127 | 122 |
| 128 switch (typeof value) { | 123 switch (typeof value) { |
| 129 case 'string': | 124 case 'string': |
| 130 return quote(value); | 125 return quote(value); |
| 131 | 126 |
| 132 case 'number': | 127 case 'number': |
| 133 // JSON numbers must be finite. Encode non-finite numbers as null. | 128 // JSON numbers must be finite. Encode non-finite numbers as null. |
| 134 return isFinite(value) ? String(value) : 'null'; | 129 return isFinite(value) ? String(value) : 'null'; |
| 135 | 130 |
| 136 case 'boolean': | 131 case 'boolean': |
| 137 case 'null': | 132 case 'null': |
| 138 // If the value is a boolean or null, convert it to a string. Note: | 133 // If the value is a boolean or null, convert it to a string. Note: |
| 139 // typeof null does not produce 'null'. The case is included here in | 134 // typeof null does not produce 'null'. The case is included here in |
| 140 // the remote chance that this gets fixed someday. | 135 // the remote chance that this gets fixed someday. |
| 141 return String(value); | 136 return String(value); |
| 142 | 137 |
| 143 // If the type is 'object', we might be dealing with an object or an | 138 // If the type is 'object', we might be dealing with an object or an |
| 144 // array or null. | 139 // array or null. |
| 145 | 140 |
| 146 case 'object': | 141 case 'object': |
| 147 | 142 |
| 148 // Due to a specification blunder in ECMAScript, typeof null is | 143 // Due to a specification blunder in ECMAScript, typeof null is |
| 149 // 'object', so watch out for that case. | 144 // 'object', so watch out for that case. |
| 150 | 145 |
| 151 if (!value) { | 146 if (!value) { |
| 152 return 'null'; | 147 return 'null'; |
| 153 } | 148 } |
| 154 | 149 |
| 155 // Make an array to hold the partial results of stringifying this | 150 // Make an array to hold the partial results of stringifying this |
| 156 // object value. | 151 // object value. |
| 157 | 152 |
| 158 gap += indent; | 153 gap += indent; |
| 159 partial = []; | 154 partial = []; |
| 160 | 155 |
| 161 // Is the value an array? | 156 // Is the value an array? |
| 162 | 157 |
| 163 if (Object.prototype.toString.apply(value) === '[object Array]') { | 158 if (Object.prototype.toString.apply(value) === '[object Array]') { |
| 164 | |
| 165 // The value is an array. Stringify every element. Use null as a | 159 // The value is an array. Stringify every element. Use null as a |
| 166 // placeholder for non-JSON values. | 160 // placeholder for non-JSON values. |
| 167 | 161 |
| 168 length = value.length; | 162 length = value.length; |
| 169 for (i = 0; i < length; i += 1) { | 163 for (i = 0; i < length; i += 1) { |
| 170 partial[i] = str(i, value) || 'null'; | 164 partial[i] = str(i, value) || 'null'; |
| 171 } | 165 } |
| 172 | 166 |
| 173 // Join all of the elements together, separated with commas, and | 167 // Join all of the elements together, separated with commas, and |
| 174 // wrap them in brackets. | 168 // wrap them in brackets. |
| 175 | 169 |
| 176 v = partial.length === 0 ? '[]' : | 170 v = partial.length === 0 ? |
| 177 gap ? '[\n' + gap + | 171 '[]' : |
| 178 partial.join(',\n' + gap) + '\n' + | 172 gap ? |
| 179 mind + ']' : | 173 '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' : |
| 180 '[' + partial.join(',') + ']'; | 174 '[' + partial.join(',') + ']'; |
| 181 gap = mind; | 175 gap = mind; |
| 182 return v; | 176 return v; |
| 183 } | 177 } |
| 184 | 178 |
| 185 // If the replacer is an array, use it to select the members to be | 179 // If the replacer is an array, use it to select the members to be |
| 186 // stringified. | 180 // stringified. |
| 187 | 181 |
| 188 if (rep && typeof rep === 'object') { | 182 if (rep && typeof rep === 'object') { |
| 189 length = rep.length; | 183 length = rep.length; |
| 190 for (i = 0; i < length; i += 1) { | 184 for (i = 0; i < length; i += 1) { |
| 191 k = rep[i]; | 185 k = rep[i]; |
| 192 if (typeof k === 'string') { | 186 if (typeof k === 'string') { |
| 193 v = str(k, value); | 187 v = str(k, value); |
| 194 if (v) { | 188 if (v) { |
| 195 partial.push(quote(k) + (gap ? ': ' : ':') + v); | 189 partial.push(quote(k) + (gap ? ': ' : ':') + v); |
| 196 } | 190 } |
| 197 } | 191 } |
| 198 } | 192 } |
| 199 } else { | 193 } else { |
| 200 | |
| 201 // Otherwise, iterate through all of the keys in the object. | 194 // Otherwise, iterate through all of the keys in the object. |
| 202 for (k in value) { | 195 for (k in value) { |
| 203 if (Object.hasOwnProperty.call(value, k)) { | 196 if (Object.hasOwnProperty.call(value, k)) { |
| 204 v = str(k, value); | 197 v = str(k, value); |
| 205 if (v) { | 198 if (v) { |
| 206 partial.push(quote(k) + (gap ? ': ' : ':') + v); | 199 partial.push(quote(k) + (gap ? ': ' : ':') + v); |
| 207 } | 200 } |
| 208 } | 201 } |
| 209 } | 202 } |
| 210 } | 203 } |
| 211 | 204 |
| 212 // Join all of the member texts together, separated with commas, | 205 // Join all of the member texts together, separated with commas, |
| 213 // and wrap them in braces. | 206 // and wrap them in braces. |
| 214 | 207 |
| 215 v = partial.length === 0 ? '{}' : | 208 v = partial.length === 0 ? |
| 216 gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + | 209 '{}' : |
| 217 mind + '}' : '{' + partial.join(',') + '}'; | 210 gap ? |
| 211 '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' : |
| 212 '{' + partial.join(',') + '}'; |
| 218 gap = mind; | 213 gap = mind; |
| 219 return v; | 214 return v; |
| 220 } | 215 } |
| 221 } | 216 } |
| 222 | 217 |
| 223 // If the JSON object does not yet have a stringify method, give it one. | 218 // If the JSON object does not yet have a stringify method, give it one. |
| 224 | 219 |
| 225 if (typeof cvox.ChromeVoxJSON.stringify !== 'function') { | 220 if (typeof cvox.ChromeVoxJSON.stringify !== 'function') { |
| 226 /** | 221 /** |
| 227 * @param {*} value Input object. | 222 * @param {*} value Input object. |
| (...skipping 28 matching lines...) Expand all Loading... |
| 256 } else if (typeof space === 'string') { | 251 } else if (typeof space === 'string') { |
| 257 indent = space; | 252 indent = space; |
| 258 } | 253 } |
| 259 | 254 |
| 260 // If there is a replacer, it must be a function or an array. | 255 // If there is a replacer, it must be a function or an array. |
| 261 // Otherwise, throw an error. | 256 // Otherwise, throw an error. |
| 262 | 257 |
| 263 rep = replacer; | 258 rep = replacer; |
| 264 if (replacer && typeof replacer !== 'function' && | 259 if (replacer && typeof replacer !== 'function' && |
| 265 (typeof replacer !== 'object' || | 260 (typeof replacer !== 'object' || |
| 266 typeof replacer.length !== 'number')) { | 261 typeof replacer.length !== 'number')) { |
| 267 throw new Error('JSON.stringify'); | 262 throw new Error('JSON.stringify'); |
| 268 } | 263 } |
| 269 | 264 |
| 270 // Make a fake root object containing our value under the key of ''. | 265 // Make a fake root object containing our value under the key of ''. |
| 271 // Return the result of stringifying the value. | 266 // Return the result of stringifying the value. |
| 272 | 267 |
| 273 return str('', {'': value}); | 268 return str('', {'': value}); |
| 274 }; | 269 }; |
| 275 } | 270 } |
| 276 | 271 |
| 277 | 272 |
| 278 // If the JSON object does not yet have a parse method, give it one. | 273 // If the JSON object does not yet have a parse method, give it one. |
| 279 | 274 |
| 280 if (typeof cvox.ChromeVoxJSON.parse !== 'function') { | 275 if (typeof cvox.ChromeVoxJSON.parse !== 'function') { |
| 281 /** | 276 /** |
| 282 * @param {string} text The string to parse. | 277 * @param {string} text The string to parse. |
| 283 * @param {(function(string, *) : *|null)=} reviver Reviver function. | 278 * @param {(function(string, *) : *|null)=} reviver Reviver function. |
| 284 * @return {*} The JSON object. | 279 * @return {*} The JSON object. |
| 285 */ | 280 */ |
| 286 cvox.ChromeVoxJSON.parse = function(text, reviver) { | 281 cvox.ChromeVoxJSON.parse = function(text, reviver) { |
| 287 | 282 |
| 288 // The parse method takes a text and an optional reviver function, and | 283 // The parse method takes a text and an optional reviver function, and |
| 289 // returns a JavaScript value if the text is a valid JSON text. | 284 // returns a JavaScript value if the text is a valid JSON text. |
| 290 | 285 |
| 291 var j; | 286 var j; |
| 292 | 287 |
| 293 function walk(holder, key) { | 288 function walk(holder, key) { |
| 294 | |
| 295 // The walk method is used to recursively walk the resulting structure | 289 // The walk method is used to recursively walk the resulting structure |
| 296 // so that modifications can be made. | 290 // so that modifications can be made. |
| 297 | 291 |
| 298 var k, v, value = holder[key]; | 292 var k, v, value = holder[key]; |
| 299 if (value && typeof value === 'object') { | 293 if (value && typeof value === 'object') { |
| 300 for (k in value) { | 294 for (k in value) { |
| 301 if (Object.hasOwnProperty.call(value, k)) { | 295 if (Object.hasOwnProperty.call(value, k)) { |
| 302 v = walk(value, k); | 296 v = walk(value, k); |
| 303 if (v !== undefined) { | 297 if (v !== undefined) { |
| 304 value[k] = v; | 298 value[k] = v; |
| 305 } else { | 299 } else { |
| 306 delete value[k]; | 300 delete value[k]; |
| 307 } | 301 } |
| 308 } | 302 } |
| 309 } | 303 } |
| 310 } | 304 } |
| 311 return reviver.call(holder, key, value); | 305 return reviver.call(holder, key, value); |
| 312 } | 306 } |
| 313 | 307 |
| 314 | 308 |
| 315 // Parsing happens in four stages. In the first stage, we replace | 309 // Parsing happens in four stages. In the first stage, we replace |
| 316 // certain Unicode characters with escape sequences. JavaScript handles | 310 // certain Unicode characters with escape sequences. JavaScript handles |
| 317 // many characters incorrectly, either silently deleting them, or | 311 // many characters incorrectly, either silently deleting them, or |
| 318 // treating them as line endings. | 312 // treating them as line endings. |
| 319 | 313 |
| 320 text = String(text); | 314 text = String(text); |
| 321 cx.lastIndex = 0; | 315 cx.lastIndex = 0; |
| 322 if (cx.test(text)) { | 316 if (cx.test(text)) { |
| 323 text = text.replace(cx, function(a) { | 317 text = text.replace(cx, function(a) { |
| 324 return '\\u' + | 318 return '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); |
| 325 ('0000' + a.charCodeAt(0).toString(16)).slice(-4); | |
| 326 }); | 319 }); |
| 327 } | 320 } |
| 328 | 321 |
| 329 // In the second stage, we run the text against regular expressions that | 322 // In the second stage, we run the text against regular expressions that |
| 330 // look for non-JSON patterns. We are especially concerned with '()' and | 323 // look for non-JSON patterns. We are especially concerned with '()' and |
| 331 // 'new' because they can cause invocation, and '=' because it can cause | 324 // 'new' because they can cause invocation, and '=' because it can cause |
| 332 // mutation. But just to be safe, we want to reject all unexpected | 325 // mutation. But just to be safe, we want to reject all unexpected |
| 333 // forms. | 326 // forms. |
| 334 // We split the second stage into 4 regexp operations in order to work | 327 // We split the second stage into 4 regexp operations in order to work |
| 335 // around crippling inefficiencies in IE's and Safari's regexp engines. | 328 // around crippling inefficiencies in IE's and Safari's regexp engines. |
| 336 // First we replace the JSON backslash pairs with '@' (a non-JSON | 329 // First we replace the JSON backslash pairs with '@' (a non-JSON |
| 337 // character). Second, we replace all simple value tokens with ']' | 330 // character). Second, we replace all simple value tokens with ']' |
| 338 // characters. Third, we delete all open brackets that follow a colon or | 331 // characters. Third, we delete all open brackets that follow a colon or |
| 339 // comma or that begin the text. Finally, we look to see that the | 332 // comma or that begin the text. Finally, we look to see that the |
| 340 // remaining characters are only whitespace or ']' or ',' or ':' or '{' | 333 // remaining characters are only whitespace or ']' or ',' or ':' or '{' |
| 341 // or '}'. If that is so, then the text is safe for eval. | 334 // or '}'. If that is so, then the text is safe for eval. |
| 342 | 335 |
| 343 if (/^[\],:{}\s]*$/. | 336 if (/^[\],:{}\s]*$/.test( |
| 344 test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@'). | 337 text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') |
| 345 replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?
/g, ']'). | 338 .replace( |
| 346 replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { | 339 /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\
-]?\d+)?/g, |
| 347 | 340 ']') |
| 341 .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { |
| 348 // In the third stage we use the eval function to compile the text | 342 // In the third stage we use the eval function to compile the text |
| 349 // into a JavaScript structure. The '{' operator is subject to a | 343 // into a JavaScript structure. The '{' operator is subject to a |
| 350 // syntactic ambiguity in JavaScript: it can begin a block or an | 344 // syntactic ambiguity in JavaScript: it can begin a block or an |
| 351 // object literal. We wrap the text in parens to eliminate the | 345 // object literal. We wrap the text in parens to eliminate the |
| 352 // ambiguity. | 346 // ambiguity. |
| 353 | 347 |
| 354 j = eval('(' + text + ')'); | 348 j = eval('(' + text + ')'); |
| 355 | 349 |
| 356 // In the optional fourth stage, we recursively walk the new | 350 // In the optional fourth stage, we recursively walk the new |
| 357 // structure, passing each name/value pair to a reviver function for | 351 // structure, passing each name/value pair to a reviver function for |
| 358 // possible transformation. | 352 // possible transformation. |
| 359 return typeof reviver === 'function' ? walk({'': j}, '') : j; | 353 return typeof reviver === 'function' ? walk({'': j}, '') : j; |
| 360 } | 354 } |
| 361 | 355 |
| 362 // If the text is not JSON parseable, then a SyntaxError is thrown. | 356 // If the text is not JSON parseable, then a SyntaxError is thrown. |
| 363 | 357 |
| 364 throw new SyntaxError('JSON.parse'); | 358 throw new SyntaxError('JSON.parse'); |
| 365 }; | 359 }; |
| 366 } | 360 } |
| 367 }()); | 361 }()); |
| 368 } | 362 } |
| OLD | NEW |