src/cm/media/js/lib/yui/yui_3.10.3/build/dataschema-json/dataschema-json-debug.js
changeset 525 89ef5ed3c48b
equal deleted inserted replaced
524:322d0feea350 525:89ef5ed3c48b
       
     1 /*
       
     2 YUI 3.10.3 (build 2fb5187)
       
     3 Copyright 2013 Yahoo! Inc. All rights reserved.
       
     4 Licensed under the BSD License.
       
     5 http://yuilibrary.com/license/
       
     6 */
       
     7 
       
     8 YUI.add('dataschema-json', function (Y, NAME) {
       
     9 
       
    10 /**
       
    11 Provides a DataSchema implementation which can be used to work with JSON data.
       
    12 
       
    13 @module dataschema
       
    14 @submodule dataschema-json
       
    15 **/
       
    16 
       
    17 /**
       
    18 Provides a DataSchema implementation which can be used to work with JSON data.
       
    19 
       
    20 See the `apply` method for usage.
       
    21 
       
    22 @class DataSchema.JSON
       
    23 @extends DataSchema.Base
       
    24 @static
       
    25 **/
       
    26 var LANG = Y.Lang,
       
    27     isFunction = LANG.isFunction,
       
    28     isObject   = LANG.isObject,
       
    29     isArray    = LANG.isArray,
       
    30     // TODO: I don't think the calls to Base.* need to be done via Base since
       
    31     // Base is mixed into SchemaJSON.  Investigate for later.
       
    32     Base       = Y.DataSchema.Base,
       
    33 
       
    34     SchemaJSON;
       
    35     
       
    36 SchemaJSON = {
       
    37 
       
    38 /////////////////////////////////////////////////////////////////////////////
       
    39 //
       
    40 // DataSchema.JSON static methods
       
    41 //
       
    42 /////////////////////////////////////////////////////////////////////////////
       
    43     /**
       
    44      * Utility function converts JSON locator strings into walkable paths
       
    45      *
       
    46      * @method getPath
       
    47      * @param locator {String} JSON value locator.
       
    48      * @return {String[]} Walkable path to data value.
       
    49      * @static
       
    50      */
       
    51     getPath: function(locator) {
       
    52         var path = null,
       
    53             keys = [],
       
    54             i = 0;
       
    55 
       
    56         if (locator) {
       
    57             // Strip the ["string keys"] and [1] array indexes
       
    58             // TODO: the first two steps can probably be reduced to one with
       
    59             // /\[\s*(['"])?(.*?)\1\s*\]/g, but the array indices would be
       
    60             // stored as strings.  This is not likely an issue.
       
    61             locator = locator.
       
    62                 replace(/\[\s*(['"])(.*?)\1\s*\]/g,
       
    63                 function (x,$1,$2) {keys[i]=$2;return '.@'+(i++);}).
       
    64                 replace(/\[(\d+)\]/g,
       
    65                 function (x,$1) {keys[i]=parseInt($1,10)|0;return '.@'+(i++);}).
       
    66                 replace(/^\./,''); // remove leading dot
       
    67 
       
    68             // Validate against problematic characters.
       
    69             // commented out because the path isn't sent to eval, so it
       
    70             // should be safe. I'm not sure what makes a locator invalid.
       
    71             //if (!/[^\w\.\$@]/.test(locator)) {
       
    72             path = locator.split('.');
       
    73             for (i=path.length-1; i >= 0; --i) {
       
    74                 if (path[i].charAt(0) === '@') {
       
    75                     path[i] = keys[parseInt(path[i].substr(1),10)];
       
    76                 }
       
    77             }
       
    78             /*}
       
    79             else {
       
    80                 Y.log("Invalid locator: " + locator, "error", "dataschema-json");
       
    81             }
       
    82             */
       
    83         }
       
    84         return path;
       
    85     },
       
    86 
       
    87     /**
       
    88      * Utility function to walk a path and return the value located there.
       
    89      *
       
    90      * @method getLocationValue
       
    91      * @param path {String[]} Locator path.
       
    92      * @param data {String} Data to traverse.
       
    93      * @return {Object} Data value at location.
       
    94      * @static
       
    95      */
       
    96     getLocationValue: function (path, data) {
       
    97         var i = 0,
       
    98             len = path.length;
       
    99         for (;i<len;i++) {
       
   100             if (isObject(data) && (path[i] in data)) {
       
   101                 data = data[path[i]];
       
   102             } else {
       
   103                 data = undefined;
       
   104                 break;
       
   105             }
       
   106         }
       
   107         return data;
       
   108     },
       
   109 
       
   110     /**
       
   111     Applies a schema to an array of data located in a JSON structure, returning
       
   112     a normalized object with results in the `results` property. Additional
       
   113     information can be parsed out of the JSON for inclusion in the `meta`
       
   114     property of the response object.  If an error is encountered during
       
   115     processing, an `error` property will be added.
       
   116 
       
   117     The input _data_ is expected to be an object or array.  If it is a string,
       
   118     it will be passed through `Y.JSON.parse()`.
       
   119 
       
   120     If _data_ contains an array of data records to normalize, specify the
       
   121     _schema.resultListLocator_ as a dot separated path string just as you would
       
   122     reference it in JavaScript.  So if your _data_ object has a record array at
       
   123     _data.response.results_, use _schema.resultListLocator_ =
       
   124     "response.results". Bracket notation can also be used for array indices or
       
   125     object properties (e.g. "response['results']");  This is called a "path
       
   126     locator"
       
   127 
       
   128     Field data in the result list is extracted with field identifiers in
       
   129     _schema.resultFields_.  Field identifiers are objects with the following
       
   130     properties:
       
   131 
       
   132       * `key`   : <strong>(required)</strong> The path locator (String)
       
   133       * `parser`: A function or the name of a function on `Y.Parsers` used
       
   134             to convert the input value into a normalized type.  Parser
       
   135             functions are passed the value as input and are expected to
       
   136             return a value.
       
   137 
       
   138     If no value parsing is needed, you can use path locators (strings) 
       
   139     instead of field identifiers (objects) -- see example below.
       
   140 
       
   141     If no processing of the result list array is needed, _schema.resultFields_
       
   142     can be omitted; the `response.results` will point directly to the array.
       
   143 
       
   144     If the result list contains arrays, `response.results` will contain an
       
   145     array of objects with key:value pairs assuming the fields in
       
   146     _schema.resultFields_ are ordered in accordance with the data array
       
   147     values.
       
   148 
       
   149     If the result list contains objects, the identified _schema.resultFields_
       
   150     will be used to extract a value from those objects for the output result.
       
   151 
       
   152     To extract additional information from the JSON, include an array of
       
   153     path locators in _schema.metaFields_.  The collected values will be
       
   154     stored in `response.meta`.
       
   155 
       
   156 
       
   157     @example
       
   158         // Process array of arrays
       
   159         var schema = {
       
   160                 resultListLocator: 'produce.fruit',
       
   161                 resultFields: [ 'name', 'color' ]
       
   162             },
       
   163             data = {
       
   164                 produce: {
       
   165                     fruit: [
       
   166                         [ 'Banana', 'yellow' ],
       
   167                         [ 'Orange', 'orange' ],
       
   168                         [ 'Eggplant', 'purple' ]
       
   169                     ]
       
   170                 }
       
   171             };
       
   172 
       
   173         var response = Y.DataSchema.JSON.apply(schema, data);
       
   174 
       
   175         // response.results[0] is { name: "Banana", color: "yellow" }
       
   176 
       
   177         
       
   178         // Process array of objects + some metadata
       
   179         schema.metaFields = [ 'lastInventory' ];
       
   180 
       
   181         data = {
       
   182             produce: {
       
   183                 fruit: [
       
   184                     { name: 'Banana', color: 'yellow', price: '1.96' },
       
   185                     { name: 'Orange', color: 'orange', price: '2.04' },
       
   186                     { name: 'Eggplant', color: 'purple', price: '4.31' }
       
   187                 ]
       
   188             },
       
   189             lastInventory: '2011-07-19'
       
   190         };
       
   191 
       
   192         response = Y.DataSchema.JSON.apply(schema, data);
       
   193 
       
   194         // response.results[0] is { name: "Banana", color: "yellow" }
       
   195         // response.meta.lastInventory is '2001-07-19'
       
   196 
       
   197 
       
   198         // Use parsers
       
   199         schema.resultFields = [
       
   200             {
       
   201                 key: 'name',
       
   202                 parser: function (val) { return val.toUpperCase(); }
       
   203             },
       
   204             {
       
   205                 key: 'price',
       
   206                 parser: 'number' // Uses Y.Parsers.number
       
   207             }
       
   208         ];
       
   209 
       
   210         response = Y.DataSchema.JSON.apply(schema, data);
       
   211 
       
   212         // Note price was converted from a numeric string to a number
       
   213         // response.results[0] looks like { fruit: "BANANA", price: 1.96 }
       
   214      
       
   215     @method apply
       
   216     @param {Object} [schema] Schema to apply.  Supported configuration
       
   217         properties are:
       
   218       @param {String} [schema.resultListLocator] Path locator for the
       
   219           location of the array of records to flatten into `response.results`
       
   220       @param {Array} [schema.resultFields] Field identifiers to
       
   221           locate/assign values in the response records. See above for
       
   222           details.
       
   223       @param {Array} [schema.metaFields] Path locators to extract extra
       
   224           non-record related information from the data object.
       
   225     @param {Object|Array|String} data JSON data or its string serialization.
       
   226     @return {Object} An Object with properties `results` and `meta`
       
   227     @static
       
   228     **/
       
   229     apply: function(schema, data) {
       
   230         var data_in = data,
       
   231             data_out = { results: [], meta: {} };
       
   232 
       
   233         // Convert incoming JSON strings
       
   234         if (!isObject(data)) {
       
   235             try {
       
   236                 data_in = Y.JSON.parse(data);
       
   237             }
       
   238             catch(e) {
       
   239                 data_out.error = e;
       
   240                 return data_out;
       
   241             }
       
   242         }
       
   243 
       
   244         if (isObject(data_in) && schema) {
       
   245             // Parse results data
       
   246             data_out = SchemaJSON._parseResults.call(this, schema, data_in, data_out);
       
   247 
       
   248             // Parse meta data
       
   249             if (schema.metaFields !== undefined) {
       
   250                 data_out = SchemaJSON._parseMeta(schema.metaFields, data_in, data_out);
       
   251             }
       
   252         }
       
   253         else {
       
   254             Y.log("JSON data could not be schema-parsed: " + Y.dump(data) + " " + Y.dump(data), "error", "dataschema-json");
       
   255             data_out.error = new Error("JSON schema parse failure");
       
   256         }
       
   257 
       
   258         return data_out;
       
   259     },
       
   260 
       
   261     /**
       
   262      * Schema-parsed list of results from full data
       
   263      *
       
   264      * @method _parseResults
       
   265      * @param schema {Object} Schema to parse against.
       
   266      * @param json_in {Object} JSON to parse.
       
   267      * @param data_out {Object} In-progress parsed data to update.
       
   268      * @return {Object} Parsed data object.
       
   269      * @static
       
   270      * @protected
       
   271      */
       
   272     _parseResults: function(schema, json_in, data_out) {
       
   273         var getPath  = SchemaJSON.getPath,
       
   274             getValue = SchemaJSON.getLocationValue,
       
   275             path     = getPath(schema.resultListLocator),
       
   276             results  = path ?
       
   277                         (getValue(path, json_in) ||
       
   278                          // Fall back to treat resultListLocator as a simple key
       
   279                             json_in[schema.resultListLocator]) :
       
   280                         // Or if no resultListLocator is supplied, use the input
       
   281                         json_in;
       
   282 
       
   283         if (isArray(results)) {
       
   284             // if no result fields are passed in, then just take
       
   285             // the results array whole-hog Sometimes you're getting
       
   286             // an array of strings, or want the whole object, so
       
   287             // resultFields don't make sense.
       
   288             if (isArray(schema.resultFields)) {
       
   289                 data_out = SchemaJSON._getFieldValues.call(this, schema.resultFields, results, data_out);
       
   290             } else {
       
   291                 data_out.results = results;
       
   292             }
       
   293         } else if (schema.resultListLocator) {
       
   294             data_out.results = [];
       
   295             data_out.error = new Error("JSON results retrieval failure");
       
   296             Y.log("JSON data could not be parsed: " + Y.dump(json_in), "error", "dataschema-json");
       
   297         }
       
   298 
       
   299         return data_out;
       
   300     },
       
   301 
       
   302     /**
       
   303      * Get field data values out of list of full results
       
   304      *
       
   305      * @method _getFieldValues
       
   306      * @param fields {Array} Fields to find.
       
   307      * @param array_in {Array} Results to parse.
       
   308      * @param data_out {Object} In-progress parsed data to update.
       
   309      * @return {Object} Parsed data object.
       
   310      * @static
       
   311      * @protected
       
   312      */
       
   313     _getFieldValues: function(fields, array_in, data_out) {
       
   314         var results = [],
       
   315             len = fields.length,
       
   316             i, j,
       
   317             field, key, locator, path, parser, val,
       
   318             simplePaths = [], complexPaths = [], fieldParsers = [],
       
   319             result, record;
       
   320 
       
   321         // First collect hashes of simple paths, complex paths, and parsers
       
   322         for (i=0; i<len; i++) {
       
   323             field = fields[i]; // A field can be a simple string or a hash
       
   324             key = field.key || field; // Find the key
       
   325             locator = field.locator || key; // Find the locator
       
   326 
       
   327             // Validate and store locators for later
       
   328             path = SchemaJSON.getPath(locator);
       
   329             if (path) {
       
   330                 if (path.length === 1) {
       
   331                     simplePaths.push({
       
   332                         key : key,
       
   333                         path: path[0]
       
   334                     });
       
   335                 } else {
       
   336                     complexPaths.push({
       
   337                         key    : key,
       
   338                         path   : path,
       
   339                         locator: locator
       
   340                     });
       
   341                 }
       
   342             } else {
       
   343                 Y.log("Invalid key syntax: " + key, "warn", "dataschema-json");
       
   344             }
       
   345 
       
   346             // Validate and store parsers for later
       
   347             //TODO: use Y.DataSchema.parse?
       
   348             parser = (isFunction(field.parser)) ?
       
   349                         field.parser :
       
   350                         Y.Parsers[field.parser + ''];
       
   351 
       
   352             if (parser) {
       
   353                 fieldParsers.push({
       
   354                     key   : key,
       
   355                     parser: parser
       
   356                 });
       
   357             }
       
   358         }
       
   359 
       
   360         // Traverse list of array_in, creating records of simple fields,
       
   361         // complex fields, and applying parsers as necessary
       
   362         for (i=array_in.length-1; i>=0; --i) {
       
   363             record = {};
       
   364             result = array_in[i];
       
   365             if(result) {
       
   366                 // Cycle through complexLocators
       
   367                 for (j=complexPaths.length - 1; j>=0; --j) {
       
   368                     path = complexPaths[j];
       
   369                     val = SchemaJSON.getLocationValue(path.path, result);
       
   370                     if (val === undefined) {
       
   371                         val = SchemaJSON.getLocationValue([path.locator], result);
       
   372                         // Fail over keys like "foo.bar" from nested parsing
       
   373                         // to single token parsing if a value is found in
       
   374                         // results["foo.bar"]
       
   375                         if (val !== undefined) {
       
   376                             simplePaths.push({
       
   377                                 key:  path.key,
       
   378                                 path: path.locator
       
   379                             });
       
   380                             // Don't try to process the path as complex
       
   381                             // for further results
       
   382                             complexPaths.splice(i,1);
       
   383                             continue;
       
   384                         }
       
   385                     }
       
   386 
       
   387                     record[path.key] = Base.parse.call(this,
       
   388                         (SchemaJSON.getLocationValue(path.path, result)), path);
       
   389                 }
       
   390 
       
   391                 // Cycle through simpleLocators
       
   392                 for (j = simplePaths.length - 1; j >= 0; --j) {
       
   393                     path = simplePaths[j];
       
   394                     // Bug 1777850: The result might be an array instead of object
       
   395                     record[path.key] = Base.parse.call(this,
       
   396                             ((result[path.path] === undefined) ?
       
   397                             result[j] : result[path.path]), path);
       
   398                 }
       
   399 
       
   400                 // Cycle through fieldParsers
       
   401                 for (j=fieldParsers.length-1; j>=0; --j) {
       
   402                     key = fieldParsers[j].key;
       
   403                     record[key] = fieldParsers[j].parser.call(this, record[key]);
       
   404                     // Safety net
       
   405                     if (record[key] === undefined) {
       
   406                         record[key] = null;
       
   407                     }
       
   408                 }
       
   409                 results[i] = record;
       
   410             }
       
   411         }
       
   412         data_out.results = results;
       
   413         return data_out;
       
   414     },
       
   415 
       
   416     /**
       
   417      * Parses results data according to schema
       
   418      *
       
   419      * @method _parseMeta
       
   420      * @param metaFields {Object} Metafields definitions.
       
   421      * @param json_in {Object} JSON to parse.
       
   422      * @param data_out {Object} In-progress parsed data to update.
       
   423      * @return {Object} Schema-parsed meta data.
       
   424      * @static
       
   425      * @protected
       
   426      */
       
   427     _parseMeta: function(metaFields, json_in, data_out) {
       
   428         if (isObject(metaFields)) {
       
   429             var key, path;
       
   430             for(key in metaFields) {
       
   431                 if (metaFields.hasOwnProperty(key)) {
       
   432                     path = SchemaJSON.getPath(metaFields[key]);
       
   433                     if (path && json_in) {
       
   434                         data_out.meta[key] = SchemaJSON.getLocationValue(path, json_in);
       
   435                     }
       
   436                 }
       
   437             }
       
   438         }
       
   439         else {
       
   440             data_out.error = new Error("JSON meta data retrieval failure");
       
   441         }
       
   442         return data_out;
       
   443     }
       
   444 };
       
   445 
       
   446 // TODO: Y.Object + mix() might be better here
       
   447 Y.DataSchema.JSON = Y.mix(SchemaJSON, Base);
       
   448 
       
   449 
       
   450 }, '3.10.3', {"requires": ["dataschema-base", "json"]});