web/res/douglascrockford-JSON-js/json_parse_state.js
changeset 534 0a2505c3b547
parent 533 d824146a9589
child 535 8276f3ff7a3f
equal deleted inserted replaced
533:d824146a9589 534:0a2505c3b547
     1 /*
       
     2     http://www.JSON.org/json_parse_state.js
       
     3     2011-02-23
       
     4 
       
     5     Public Domain.
       
     6 
       
     7     NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
       
     8 
       
     9     This file creates a json_parse function.
       
    10 
       
    11         json_parse(text, reviver)
       
    12             This method parses a JSON text to produce an object or array.
       
    13             It can throw a SyntaxError exception.
       
    14 
       
    15             The optional reviver parameter is a function that can filter and
       
    16             transform the results. It receives each of the keys and values,
       
    17             and its return value is used instead of the original value.
       
    18             If it returns what it received, then the structure is not modified.
       
    19             If it returns undefined then the member is deleted.
       
    20 
       
    21             Example:
       
    22 
       
    23             // Parse the text. Values that look like ISO date strings will
       
    24             // be converted to Date objects.
       
    25 
       
    26             myData = json_parse(text, function (key, value) {
       
    27                 var a;
       
    28                 if (typeof value === 'string') {
       
    29                     a =
       
    30 /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
       
    31                     if (a) {
       
    32                         return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
       
    33                             +a[5], +a[6]));
       
    34                     }
       
    35                 }
       
    36                 return value;
       
    37             });
       
    38 
       
    39     This is a reference implementation. You are free to copy, modify, or
       
    40     redistribute.
       
    41 
       
    42     This code should be minified before deployment.
       
    43     See http://javascript.crockford.com/jsmin.html
       
    44 
       
    45     USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
       
    46     NOT CONTROL.
       
    47 */
       
    48 
       
    49 /*jslint regexp: false*/
       
    50 
       
    51 /*members "", "\"", ",", "\/", ":", "[", "\\", "]", acomma, avalue, b,
       
    52     call, colon, container, exec, f, false, firstavalue, firstokey,
       
    53     fromCharCode, go, hasOwnProperty, key, length, n, null, ocomma, okey,
       
    54     ovalue, pop, prototype, push, r, replace, slice, state, t, test, true,
       
    55     value, "{", "}"
       
    56 */
       
    57 
       
    58 var json_parse = (function () {
       
    59     "use strict";
       
    60 
       
    61 // This function creates a JSON parse function that uses a state machine rather
       
    62 // than the dangerous eval function to parse a JSON text.
       
    63 
       
    64     var state,      // The state of the parser, one of
       
    65                     // 'go'         The starting state
       
    66                     // 'ok'         The final, accepting state
       
    67                     // 'firstokey'  Ready for the first key of the object or
       
    68                     //              the closing of an empty object
       
    69                     // 'okey'       Ready for the next key of the object
       
    70                     // 'colon'      Ready for the colon
       
    71                     // 'ovalue'     Ready for the value half of a key/value pair
       
    72                     // 'ocomma'     Ready for a comma or closing }
       
    73                     // 'firstavalue' Ready for the first value of an array or
       
    74                     //              an empty array
       
    75                     // 'avalue'     Ready for the next value of an array
       
    76                     // 'acomma'     Ready for a comma or closing ]
       
    77         stack,      // The stack, for controlling nesting.
       
    78         container,  // The current container object or array
       
    79         key,        // The current key
       
    80         value,      // The current value
       
    81         escapes = { // Escapement translation table
       
    82             '\\': '\\',
       
    83             '"': '"',
       
    84             '/': '/',
       
    85             't': '\t',
       
    86             'n': '\n',
       
    87             'r': '\r',
       
    88             'f': '\f',
       
    89             'b': '\b'
       
    90         },
       
    91         string = {   // The actions for string tokens
       
    92             go: function () {
       
    93                 state = 'ok';
       
    94             },
       
    95             firstokey: function () {
       
    96                 key = value;
       
    97                 state = 'colon';
       
    98             },
       
    99             okey: function () {
       
   100                 key = value;
       
   101                 state = 'colon';
       
   102             },
       
   103             ovalue: function () {
       
   104                 state = 'ocomma';
       
   105             },
       
   106             firstavalue: function () {
       
   107                 state = 'acomma';
       
   108             },
       
   109             avalue: function () {
       
   110                 state = 'acomma';
       
   111             }
       
   112         },
       
   113         number = {   // The actions for number tokens
       
   114             go: function () {
       
   115                 state = 'ok';
       
   116             },
       
   117             ovalue: function () {
       
   118                 state = 'ocomma';
       
   119             },
       
   120             firstavalue: function () {
       
   121                 state = 'acomma';
       
   122             },
       
   123             avalue: function () {
       
   124                 state = 'acomma';
       
   125             }
       
   126         },
       
   127         action = {
       
   128 
       
   129 // The action table describes the behavior of the machine. It contains an
       
   130 // object for each token. Each object contains a method that is called when
       
   131 // a token is matched in a state. An object will lack a method for illegal
       
   132 // states.
       
   133 
       
   134             '{': {
       
   135                 go: function () {
       
   136                     stack.push({state: 'ok'});
       
   137                     container = {};
       
   138                     state = 'firstokey';
       
   139                 },
       
   140                 ovalue: function () {
       
   141                     stack.push({container: container, state: 'ocomma', key: key});
       
   142                     container = {};
       
   143                     state = 'firstokey';
       
   144                 },
       
   145                 firstavalue: function () {
       
   146                     stack.push({container: container, state: 'acomma'});
       
   147                     container = {};
       
   148                     state = 'firstokey';
       
   149                 },
       
   150                 avalue: function () {
       
   151                     stack.push({container: container, state: 'acomma'});
       
   152                     container = {};
       
   153                     state = 'firstokey';
       
   154                 }
       
   155             },
       
   156             '}': {
       
   157                 firstokey: function () {
       
   158                     var pop = stack.pop();
       
   159                     value = container;
       
   160                     container = pop.container;
       
   161                     key = pop.key;
       
   162                     state = pop.state;
       
   163                 },
       
   164                 ocomma: function () {
       
   165                     var pop = stack.pop();
       
   166                     container[key] = value;
       
   167                     value = container;
       
   168                     container = pop.container;
       
   169                     key = pop.key;
       
   170                     state = pop.state;
       
   171                 }
       
   172             },
       
   173             '[': {
       
   174                 go: function () {
       
   175                     stack.push({state: 'ok'});
       
   176                     container = [];
       
   177                     state = 'firstavalue';
       
   178                 },
       
   179                 ovalue: function () {
       
   180                     stack.push({container: container, state: 'ocomma', key: key});
       
   181                     container = [];
       
   182                     state = 'firstavalue';
       
   183                 },
       
   184                 firstavalue: function () {
       
   185                     stack.push({container: container, state: 'acomma'});
       
   186                     container = [];
       
   187                     state = 'firstavalue';
       
   188                 },
       
   189                 avalue: function () {
       
   190                     stack.push({container: container, state: 'acomma'});
       
   191                     container = [];
       
   192                     state = 'firstavalue';
       
   193                 }
       
   194             },
       
   195             ']': {
       
   196                 firstavalue: function () {
       
   197                     var pop = stack.pop();
       
   198                     value = container;
       
   199                     container = pop.container;
       
   200                     key = pop.key;
       
   201                     state = pop.state;
       
   202                 },
       
   203                 acomma: function () {
       
   204                     var pop = stack.pop();
       
   205                     container.push(value);
       
   206                     value = container;
       
   207                     container = pop.container;
       
   208                     key = pop.key;
       
   209                     state = pop.state;
       
   210                 }
       
   211             },
       
   212             ':': {
       
   213                 colon: function () {
       
   214                     if (Object.hasOwnProperty.call(container, key)) {
       
   215                         throw new SyntaxError('Duplicate key "' + key + '"');
       
   216                     }
       
   217                     state = 'ovalue';
       
   218                 }
       
   219             },
       
   220             ',': {
       
   221                 ocomma: function () {
       
   222                     container[key] = value;
       
   223                     state = 'okey';
       
   224                 },
       
   225                 acomma: function () {
       
   226                     container.push(value);
       
   227                     state = 'avalue';
       
   228                 }
       
   229             },
       
   230             'true': {
       
   231                 go: function () {
       
   232                     value = true;
       
   233                     state = 'ok';
       
   234                 },
       
   235                 ovalue: function () {
       
   236                     value = true;
       
   237                     state = 'ocomma';
       
   238                 },
       
   239                 firstavalue: function () {
       
   240                     value = true;
       
   241                     state = 'acomma';
       
   242                 },
       
   243                 avalue: function () {
       
   244                     value = true;
       
   245                     state = 'acomma';
       
   246                 }
       
   247             },
       
   248             'false': {
       
   249                 go: function () {
       
   250                     value = false;
       
   251                     state = 'ok';
       
   252                 },
       
   253                 ovalue: function () {
       
   254                     value = false;
       
   255                     state = 'ocomma';
       
   256                 },
       
   257                 firstavalue: function () {
       
   258                     value = false;
       
   259                     state = 'acomma';
       
   260                 },
       
   261                 avalue: function () {
       
   262                     value = false;
       
   263                     state = 'acomma';
       
   264                 }
       
   265             },
       
   266             'null': {
       
   267                 go: function () {
       
   268                     value = null;
       
   269                     state = 'ok';
       
   270                 },
       
   271                 ovalue: function () {
       
   272                     value = null;
       
   273                     state = 'ocomma';
       
   274                 },
       
   275                 firstavalue: function () {
       
   276                     value = null;
       
   277                     state = 'acomma';
       
   278                 },
       
   279                 avalue: function () {
       
   280                     value = null;
       
   281                     state = 'acomma';
       
   282                 }
       
   283             }
       
   284         };
       
   285 
       
   286     function debackslashify(text) {
       
   287 
       
   288 // Remove and replace any backslash escapement.
       
   289 
       
   290         return text.replace(/\\(?:u(.{4})|([^u]))/g, function (a, b, c) {
       
   291             return b ? String.fromCharCode(parseInt(b, 16)) : escapes[c];
       
   292         });
       
   293     }
       
   294 
       
   295     return function (source, reviver) {
       
   296 
       
   297 // A regular expression is used to extract tokens from the JSON text.
       
   298 // The extraction process is cautious.
       
   299 
       
   300         var r,          // The result of the exec method.
       
   301             tx = /^[\x20\t\n\r]*(?:([,:\[\]{}]|true|false|null)|(-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)|"((?:[^\r\n\t\\\"]|\\(?:["\\\/trnfb]|u[0-9a-fA-F]{4}))*)")/;
       
   302 
       
   303 // Set the starting state.
       
   304 
       
   305         state = 'go';
       
   306 
       
   307 // The stack records the container, key, and state for each object or array
       
   308 // that contains another object or array while processing nested structures.
       
   309 
       
   310         stack = [];
       
   311 
       
   312 // If any error occurs, we will catch it and ultimately throw a syntax error.
       
   313 
       
   314         try {
       
   315 
       
   316 // For each token...
       
   317 
       
   318             for (;;) {
       
   319                 r = tx.exec(source);
       
   320                 if (!r) {
       
   321                     break;
       
   322                 }
       
   323 
       
   324 // r is the result array from matching the tokenizing regular expression.
       
   325 //  r[0] contains everything that matched, including any initial whitespace.
       
   326 //  r[1] contains any punctuation that was matched, or true, false, or null.
       
   327 //  r[2] contains a matched number, still in string form.
       
   328 //  r[3] contains a matched string, without quotes but with ecapement.
       
   329 
       
   330                 if (r[1]) {
       
   331 
       
   332 // Token: Execute the action for this state and token.
       
   333 
       
   334                     action[r[1]][state]();
       
   335 
       
   336                 } else if (r[2]) {
       
   337 
       
   338 // Number token: Convert the number string into a number value and execute
       
   339 // the action for this state and number.
       
   340 
       
   341                     value = +r[2];
       
   342                     number[state]();
       
   343                 } else {
       
   344 
       
   345 // String token: Replace the escapement sequences and execute the action for
       
   346 // this state and string.
       
   347 
       
   348                     value = debackslashify(r[3]);
       
   349                     string[state]();
       
   350                 }
       
   351 
       
   352 // Remove the token from the string. The loop will continue as long as there
       
   353 // are tokens. This is a slow process, but it allows the use of ^ matching,
       
   354 // which assures that no illegal tokens slip through.
       
   355 
       
   356                 source = source.slice(r[0].length);
       
   357             }
       
   358 
       
   359 // If we find a state/token combination that is illegal, then the action will
       
   360 // cause an error. We handle the error by simply changing the state.
       
   361 
       
   362         } catch (e) {
       
   363             state = e;
       
   364         }
       
   365 
       
   366 // The parsing is finished. If we are not in the final 'ok' state, or if the
       
   367 // remaining source contains anything except whitespace, then we did not have
       
   368 //a well-formed JSON text.
       
   369 
       
   370         if (state !== 'ok' || /[^\x20\t\n\r]/.test(source)) {
       
   371             throw state instanceof SyntaxError ? state : new SyntaxError('JSON');
       
   372         }
       
   373 
       
   374 // If there is a reviver function, we recursively walk the new structure,
       
   375 // passing each name/value pair to the reviver function for possible
       
   376 // transformation, starting with a temporary root object that holds the current
       
   377 // value in an empty key. If there is not a reviver function, we simply return
       
   378 // that value.
       
   379 
       
   380         return typeof reviver === 'function' ? (function walk(holder, key) {
       
   381             var k, v, value = holder[key];
       
   382             if (value && typeof value === 'object') {
       
   383                 for (k in value) {
       
   384                     if (Object.prototype.hasOwnProperty.call(value, k)) {
       
   385                         v = walk(value, k);
       
   386                         if (v !== undefined) {
       
   387                             value[k] = v;
       
   388                         } else {
       
   389                             delete value[k];
       
   390                         }
       
   391                     }
       
   392                 }
       
   393             }
       
   394             return reviver.call(holder, key, value);
       
   395         }({'': value}, '')) : value;
       
   396     };
       
   397 }());