~ubuntu-branches/ubuntu/utopic/moodle/utopic

« back to all changes in this revision

Viewing changes to lib/yuilib/3.9.1/build/dataschema-json/dataschema-json.js

  • Committer: Package Import Robot
  • Author(s): Thijs Kinkhorst
  • Date: 2014-05-12 16:10:38 UTC
  • mfrom: (36.1.3 sid)
  • Revision ID: package-import@ubuntu.com-20140512161038-puyqf65k4e0s8ytz
Tags: 2.6.3-1
New upstream release.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
/* YUI 3.9.1 (build 5852) Copyright 2013 Yahoo! Inc. http://yuilibrary.com/license/ */
2
 
YUI.add('dataschema-json', function (Y, NAME) {
3
 
 
4
 
/**
5
 
Provides a DataSchema implementation which can be used to work with JSON data.
6
 
 
7
 
@module dataschema
8
 
@submodule dataschema-json
9
 
**/
10
 
 
11
 
/**
12
 
Provides a DataSchema implementation which can be used to work with JSON data.
13
 
 
14
 
See the `apply` method for usage.
15
 
 
16
 
@class DataSchema.JSON
17
 
@extends DataSchema.Base
18
 
@static
19
 
**/
20
 
var LANG = Y.Lang,
21
 
    isFunction = LANG.isFunction,
22
 
    isObject   = LANG.isObject,
23
 
    isArray    = LANG.isArray,
24
 
    // TODO: I don't think the calls to Base.* need to be done via Base since
25
 
    // Base is mixed into SchemaJSON.  Investigate for later.
26
 
    Base       = Y.DataSchema.Base,
27
 
 
28
 
    SchemaJSON;
29
 
    
30
 
SchemaJSON = {
31
 
 
32
 
/////////////////////////////////////////////////////////////////////////////
33
 
//
34
 
// DataSchema.JSON static methods
35
 
//
36
 
/////////////////////////////////////////////////////////////////////////////
37
 
    /**
38
 
     * Utility function converts JSON locator strings into walkable paths
39
 
     *
40
 
     * @method getPath
41
 
     * @param locator {String} JSON value locator.
42
 
     * @return {String[]} Walkable path to data value.
43
 
     * @static
44
 
     */
45
 
    getPath: function(locator) {
46
 
        var path = null,
47
 
            keys = [],
48
 
            i = 0;
49
 
 
50
 
        if (locator) {
51
 
            // Strip the ["string keys"] and [1] array indexes
52
 
            // TODO: the first two steps can probably be reduced to one with
53
 
            // /\[\s*(['"])?(.*?)\1\s*\]/g, but the array indices would be
54
 
            // stored as strings.  This is not likely an issue.
55
 
            locator = locator.
56
 
                replace(/\[\s*(['"])(.*?)\1\s*\]/g,
57
 
                function (x,$1,$2) {keys[i]=$2;return '.@'+(i++);}).
58
 
                replace(/\[(\d+)\]/g,
59
 
                function (x,$1) {keys[i]=parseInt($1,10)|0;return '.@'+(i++);}).
60
 
                replace(/^\./,''); // remove leading dot
61
 
 
62
 
            // Validate against problematic characters.
63
 
            // commented out because the path isn't sent to eval, so it
64
 
            // should be safe. I'm not sure what makes a locator invalid.
65
 
            //if (!/[^\w\.\$@]/.test(locator)) {
66
 
            path = locator.split('.');
67
 
            for (i=path.length-1; i >= 0; --i) {
68
 
                if (path[i].charAt(0) === '@') {
69
 
                    path[i] = keys[parseInt(path[i].substr(1),10)];
70
 
                }
71
 
            }
72
 
            /*}
73
 
            else {
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
 
            data_out.error = new Error("JSON schema parse failure");
248
 
        }
249
 
 
250
 
        return data_out;
251
 
    },
252
 
 
253
 
    /**
254
 
     * Schema-parsed list of results from full data
255
 
     *
256
 
     * @method _parseResults
257
 
     * @param schema {Object} Schema to parse against.
258
 
     * @param json_in {Object} JSON to parse.
259
 
     * @param data_out {Object} In-progress parsed data to update.
260
 
     * @return {Object} Parsed data object.
261
 
     * @static
262
 
     * @protected
263
 
     */
264
 
    _parseResults: function(schema, json_in, data_out) {
265
 
        var getPath  = SchemaJSON.getPath,
266
 
            getValue = SchemaJSON.getLocationValue,
267
 
            path     = getPath(schema.resultListLocator),
268
 
            results  = path ?
269
 
                        (getValue(path, json_in) ||
270
 
                         // Fall back to treat resultListLocator as a simple key
271
 
                            json_in[schema.resultListLocator]) :
272
 
                        // Or if no resultListLocator is supplied, use the input
273
 
                        json_in;
274
 
 
275
 
        if (isArray(results)) {
276
 
            // if no result fields are passed in, then just take
277
 
            // the results array whole-hog Sometimes you're getting
278
 
            // an array of strings, or want the whole object, so
279
 
            // resultFields don't make sense.
280
 
            if (isArray(schema.resultFields)) {
281
 
                data_out = SchemaJSON._getFieldValues.call(this, schema.resultFields, results, data_out);
282
 
            } else {
283
 
                data_out.results = results;
284
 
            }
285
 
        } else if (schema.resultListLocator) {
286
 
            data_out.results = [];
287
 
            data_out.error = new Error("JSON results retrieval failure");
288
 
        }
289
 
 
290
 
        return data_out;
291
 
    },
292
 
 
293
 
    /**
294
 
     * Get field data values out of list of full results
295
 
     *
296
 
     * @method _getFieldValues
297
 
     * @param fields {Array} Fields to find.
298
 
     * @param array_in {Array} Results to parse.
299
 
     * @param data_out {Object} In-progress parsed data to update.
300
 
     * @return {Object} Parsed data object.
301
 
     * @static
302
 
     * @protected
303
 
     */
304
 
    _getFieldValues: function(fields, array_in, data_out) {
305
 
        var results = [],
306
 
            len = fields.length,
307
 
            i, j,
308
 
            field, key, locator, path, parser, val,
309
 
            simplePaths = [], complexPaths = [], fieldParsers = [],
310
 
            result, record;
311
 
 
312
 
        // First collect hashes of simple paths, complex paths, and parsers
313
 
        for (i=0; i<len; i++) {
314
 
            field = fields[i]; // A field can be a simple string or a hash
315
 
            key = field.key || field; // Find the key
316
 
            locator = field.locator || key; // Find the locator
317
 
 
318
 
            // Validate and store locators for later
319
 
            path = SchemaJSON.getPath(locator);
320
 
            if (path) {
321
 
                if (path.length === 1) {
322
 
                    simplePaths.push({
323
 
                        key : key,
324
 
                        path: path[0]
325
 
                    });
326
 
                } else {
327
 
                    complexPaths.push({
328
 
                        key    : key,
329
 
                        path   : path,
330
 
                        locator: locator
331
 
                    });
332
 
                }
333
 
            } else {
334
 
            }
335
 
 
336
 
            // Validate and store parsers for later
337
 
            //TODO: use Y.DataSchema.parse?
338
 
            parser = (isFunction(field.parser)) ?
339
 
                        field.parser :
340
 
                        Y.Parsers[field.parser + ''];
341
 
 
342
 
            if (parser) {
343
 
                fieldParsers.push({
344
 
                    key   : key,
345
 
                    parser: parser
346
 
                });
347
 
            }
348
 
        }
349
 
 
350
 
        // Traverse list of array_in, creating records of simple fields,
351
 
        // complex fields, and applying parsers as necessary
352
 
        for (i=array_in.length-1; i>=0; --i) {
353
 
            record = {};
354
 
            result = array_in[i];
355
 
            if(result) {
356
 
                // Cycle through complexLocators
357
 
                for (j=complexPaths.length - 1; j>=0; --j) {
358
 
                    path = complexPaths[j];
359
 
                    val = SchemaJSON.getLocationValue(path.path, result);
360
 
                    if (val === undefined) {
361
 
                        val = SchemaJSON.getLocationValue([path.locator], result);
362
 
                        // Fail over keys like "foo.bar" from nested parsing
363
 
                        // to single token parsing if a value is found in
364
 
                        // results["foo.bar"]
365
 
                        if (val !== undefined) {
366
 
                            simplePaths.push({
367
 
                                key:  path.key,
368
 
                                path: path.locator
369
 
                            });
370
 
                            // Don't try to process the path as complex
371
 
                            // for further results
372
 
                            complexPaths.splice(i,1);
373
 
                            continue;
374
 
                        }
375
 
                    }
376
 
 
377
 
                    record[path.key] = Base.parse.call(this,
378
 
                        (SchemaJSON.getLocationValue(path.path, result)), path);
379
 
                }
380
 
 
381
 
                // Cycle through simpleLocators
382
 
                for (j = simplePaths.length - 1; j >= 0; --j) {
383
 
                    path = simplePaths[j];
384
 
                    // Bug 1777850: The result might be an array instead of object
385
 
                    record[path.key] = Base.parse.call(this,
386
 
                            ((result[path.path] === undefined) ?
387
 
                            result[j] : result[path.path]), path);
388
 
                }
389
 
 
390
 
                // Cycle through fieldParsers
391
 
                for (j=fieldParsers.length-1; j>=0; --j) {
392
 
                    key = fieldParsers[j].key;
393
 
                    record[key] = fieldParsers[j].parser.call(this, record[key]);
394
 
                    // Safety net
395
 
                    if (record[key] === undefined) {
396
 
                        record[key] = null;
397
 
                    }
398
 
                }
399
 
                results[i] = record;
400
 
            }
401
 
        }
402
 
        data_out.results = results;
403
 
        return data_out;
404
 
    },
405
 
 
406
 
    /**
407
 
     * Parses results data according to schema
408
 
     *
409
 
     * @method _parseMeta
410
 
     * @param metaFields {Object} Metafields definitions.
411
 
     * @param json_in {Object} JSON to parse.
412
 
     * @param data_out {Object} In-progress parsed data to update.
413
 
     * @return {Object} Schema-parsed meta data.
414
 
     * @static
415
 
     * @protected
416
 
     */
417
 
    _parseMeta: function(metaFields, json_in, data_out) {
418
 
        if (isObject(metaFields)) {
419
 
            var key, path;
420
 
            for(key in metaFields) {
421
 
                if (metaFields.hasOwnProperty(key)) {
422
 
                    path = SchemaJSON.getPath(metaFields[key]);
423
 
                    if (path && json_in) {
424
 
                        data_out.meta[key] = SchemaJSON.getLocationValue(path, json_in);
425
 
                    }
426
 
                }
427
 
            }
428
 
        }
429
 
        else {
430
 
            data_out.error = new Error("JSON meta data retrieval failure");
431
 
        }
432
 
        return data_out;
433
 
    }
434
 
};
435
 
 
436
 
// TODO: Y.Object + mix() might be better here
437
 
Y.DataSchema.JSON = Y.mix(SchemaJSON, Base);
438
 
 
439
 
 
440
 
}, '3.9.1', {"requires": ["dataschema-base", "json"]});