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