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