~bac/juju-gui/trunkcopy

« back to all changes in this revision

Viewing changes to lib/yui/build/dataschema-xml/dataschema-xml.js

  • Committer: kapil.foss at gmail
  • Date: 2012-07-13 18:45:59 UTC
  • Revision ID: kapil.foss@gmail.com-20120713184559-2xl7be17egsrz0c9
reshape

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
/*
2
 
YUI 3.5.1 (build 22)
3
 
Copyright 2012 Yahoo! Inc. All rights reserved.
4
 
Licensed under the BSD License.
5
 
http://yuilibrary.com/license/
6
 
*/
7
 
YUI.add('dataschema-xml', function(Y) {
8
 
 
9
 
/**
10
 
Provides a DataSchema implementation which can be used to work with XML data.
11
 
 
12
 
@module dataschema
13
 
@submodule dataschema-xml
14
 
**/
15
 
 
16
 
/**
17
 
Provides a DataSchema implementation which can be used to work with XML data.
18
 
 
19
 
See the `apply` method for usage.
20
 
 
21
 
@class DataSchema.XML
22
 
@extends DataSchema.Base
23
 
@static
24
 
**/
25
 
var Lang = Y.Lang,
26
 
 
27
 
    okNodeType = {
28
 
        1 : true,
29
 
        9 : true,
30
 
        11: true
31
 
    },
32
 
 
33
 
    SchemaXML;
34
 
 
35
 
SchemaXML = {
36
 
 
37
 
    ////////////////////////////////////////////////////////////////////////////
38
 
    //
39
 
    // DataSchema.XML static methods
40
 
    //
41
 
    ////////////////////////////////////////////////////////////////////////////
42
 
    /**
43
 
    Applies a schema to an XML data tree, returning a normalized object with
44
 
    results in the `results` property. Additional information can be parsed out
45
 
    of the XML for inclusion in the `meta` property of the response object.  If
46
 
    an error is encountered during processing, an `error` property will be
47
 
    added.
48
 
 
49
 
    Field data in the nodes captured by the XPath in _schema.resultListLocator_
50
 
    is extracted with the field identifiers described in _schema.resultFields_.
51
 
    Field identifiers are objects with the following properties:
52
 
 
53
 
      * `key`    : <strong>(required)</strong> The desired property name to use
54
 
            store the retrieved value in the result object.  If `locator` is
55
 
            not specified, `key` is also used as the XPath locator (String)
56
 
      * `locator`: The XPath locator to the node or attribute within each
57
 
            result node found by _schema.resultListLocator_ containing the
58
 
            desired field data (String)
59
 
      * `parser` : A function or the name of a function on `Y.Parsers` used
60
 
            to convert the input value into a normalized type.  Parser
61
 
            functions are passed the value as input and are expected to
62
 
            return a value.
63
 
      * `schema` : Used to retrieve nested field data into an array for
64
 
            assignment as the result field value.  This object follows the same
65
 
            conventions as _schema_.
66
 
 
67
 
    If no value parsing or nested parsing is needed, you can use XPath locators
68
 
    (strings) instead of field identifiers (objects) -- see example below.
69
 
 
70
 
    `response.results` will contain an array of objects with key:value pairs.
71
 
    The keys are the field identifier `key`s, and the values are the data
72
 
    values extracted from the nodes or attributes found by the field `locator`
73
 
    (or `key` fallback).
74
 
    
75
 
    To extract additional information from the XML, include an array of
76
 
    XPath locators in _schema.metaFields_.  The collected values will be
77
 
    stored in `response.meta` with the XPath locator as keys.
78
 
 
79
 
    @example
80
 
        var schema = {
81
 
                resultListLocator: '//produce/item',
82
 
                resultFields: [
83
 
                    {
84
 
                        locator: 'name',
85
 
                        key: 'name'
86
 
                    },
87
 
                    {
88
 
                        locator: 'color',
89
 
                        key: 'color',
90
 
                        parser: function (val) { return val.toUpperCase(); }
91
 
                    }
92
 
                ]
93
 
            };
94
 
 
95
 
        // Assumes data like
96
 
        // <inventory>
97
 
        //   <produce>
98
 
        //     <item><name>Banana</name><color>yellow</color></item>
99
 
        //     <item><name>Orange</name><color>orange</color></item>
100
 
        //     <item><name>Eggplant</name><color>purple</color></item>
101
 
        //   </produce>
102
 
        // </inventory>
103
 
 
104
 
        var response = Y.DataSchema.JSON.apply(schema, data);
105
 
 
106
 
        // response.results[0] is { name: "Banana", color: "YELLOW" }
107
 
     
108
 
    @method apply
109
 
    @param {Object} schema Schema to apply.  Supported configuration
110
 
        properties are:
111
 
      @param {String} [schema.resultListLocator] XPath locator for the
112
 
          XML nodes that contain the data to flatten into `response.results`
113
 
      @param {Array} [schema.resultFields] Field identifiers to
114
 
          locate/assign values in the response records. See above for
115
 
          details.
116
 
      @param {Array} [schema.metaFields] XPath locators to extract extra
117
 
          non-record related information from the XML data
118
 
    @param {XMLDoc} data XML data to parse
119
 
    @return {Object} An Object with properties `results` and `meta`
120
 
    @static
121
 
    **/
122
 
    apply: function(schema, data) {
123
 
        var xmldoc = data, // unnecessary variables
124
 
            data_out = { results: [], meta: {} };
125
 
 
126
 
        if (xmldoc && okNodeType[xmldoc.nodeType] && schema) {
127
 
            // Parse results data
128
 
            data_out = SchemaXML._parseResults(schema, xmldoc, data_out);
129
 
 
130
 
            // Parse meta data
131
 
            data_out = SchemaXML._parseMeta(schema.metaFields, xmldoc, data_out);
132
 
        } else {
133
 
            data_out.error = new Error("XML schema parse failure");
134
 
        }
135
 
 
136
 
        return data_out;
137
 
    },
138
 
 
139
 
    /**
140
 
     * Get an XPath-specified value for a given field from an XML node or document.
141
 
     *
142
 
     * @method _getLocationValue
143
 
     * @param field {String | Object} Field definition.
144
 
     * @param context {Object} XML node or document to search within.
145
 
     * @return {Object} Data value or null.
146
 
     * @static
147
 
     * @protected
148
 
     */
149
 
    _getLocationValue: function(field, context) {
150
 
        var locator = field.locator || field.key || field,
151
 
            xmldoc = context.ownerDocument || context,
152
 
            result, res, value = null;
153
 
 
154
 
        try {
155
 
            result = SchemaXML._getXPathResult(locator, context, xmldoc);
156
 
            while ((res = result.iterateNext())) {
157
 
                value = res.textContent || res.value || res.text || res.innerHTML || null;
158
 
            }
159
 
 
160
 
            // FIXME: Why defer to a method that is mixed into this object?
161
 
            // DSchema.Base is mixed into DSchema.XML (et al), so
162
 
            // DSchema.XML.parse(...) will work.  This supports the use case
163
 
            // where DSchema.Base.parse is changed, and that change is then
164
 
            // seen by all DSchema.* implementations, but does not support the
165
 
            // case where redefining DSchema.XML.parse changes behavior. In
166
 
            // fact, DSchema.XML.parse is never even called.
167
 
            return Y.DataSchema.Base.parse.call(this, value, field);
168
 
        } catch (e) {
169
 
        }
170
 
 
171
 
        return null;
172
 
    },
173
 
 
174
 
    /**
175
 
     * Fetches the XPath-specified result for a given location in an XML node
176
 
     * or document.
177
 
     * 
178
 
     * @method _getXPathResult
179
 
     * @param locator {String} The XPath location.
180
 
     * @param context {Object} XML node or document to search within.
181
 
     * @param xmldoc {Object} XML document to resolve namespace.
182
 
     * @return {Object} Data collection or null.
183
 
     * @static
184
 
     * @protected
185
 
     */
186
 
    _getXPathResult: function(locator, context, xmldoc) {
187
 
        // Standards mode
188
 
        if (! Lang.isUndefined(xmldoc.evaluate)) {
189
 
            return xmldoc.evaluate(locator, context, xmldoc.createNSResolver(context.ownerDocument ? context.ownerDocument.documentElement : context.documentElement), 0, null);
190
 
        }
191
 
        // IE mode
192
 
        else {
193
 
            var values=[], locatorArray = locator.split(/\b\/\b/), i=0, l=locatorArray.length, location, subloc, m, isNth;
194
 
            
195
 
            // XPath is supported
196
 
            try {
197
 
                // this fixes the IE 5.5+ issue where childnode selectors begin at 0 instead of 1
198
 
                xmldoc.setProperty("SelectionLanguage", "XPath");
199
 
                values = context.selectNodes(locator);
200
 
            }
201
 
            // Fallback for DOM nodes and fragments
202
 
            catch (e) {
203
 
                // Iterate over each locator piece
204
 
                for (; i<l && context; i++) {
205
 
                    location = locatorArray[i];
206
 
 
207
 
                    // grab nth child []
208
 
                    if ((location.indexOf("[") > -1) && (location.indexOf("]") > -1)) {
209
 
                        subloc = location.slice(location.indexOf("[")+1, location.indexOf("]"));
210
 
                        //XPath is 1-based while DOM is 0-based
211
 
                        subloc--;
212
 
                        context = context.children[subloc];
213
 
                        isNth = true;
214
 
                    }
215
 
                    // grab attribute value @
216
 
                    else if (location.indexOf("@") > -1) {
217
 
                        subloc = location.substr(location.indexOf("@"));
218
 
                        context = subloc ? context.getAttribute(subloc.replace('@', '')) : context;
219
 
                    }
220
 
                    // grab that last instance of tagName
221
 
                    else if (-1 < location.indexOf("//")) {
222
 
                        subloc = context.getElementsByTagName(location.substr(2));
223
 
                        context = subloc.length ? subloc[subloc.length - 1] : null;
224
 
                    }
225
 
                    // find the last matching location in children
226
 
                    else if (l != i + 1) {
227
 
                        for (m=context.childNodes.length-1; 0 <= m; m-=1) {
228
 
                            if (location === context.childNodes[m].tagName) {
229
 
                                context = context.childNodes[m];
230
 
                                m = -1;
231
 
                            }
232
 
                        }
233
 
                    }
234
 
                }
235
 
                
236
 
                if (context) {
237
 
                    // attribute
238
 
                    if (Lang.isString(context)) {
239
 
                        values[0] = {value: context};
240
 
                    }
241
 
                    // nth child
242
 
                    else if (isNth) {
243
 
                        values[0] = {value: context.innerHTML};
244
 
                    }
245
 
                    // all children
246
 
                    else {
247
 
                        values = Y.Array(context.childNodes, 0, true);
248
 
                    }
249
 
                }
250
 
            }
251
 
 
252
 
            // returning a mock-standard object for IE
253
 
            return {
254
 
                index: 0,
255
 
                
256
 
                iterateNext: function() {
257
 
                    if (this.index >= this.values.length) {return undefined;}
258
 
                    var result = this.values[this.index];
259
 
                    this.index += 1;
260
 
                    return result;
261
 
                },
262
 
 
263
 
                values: values
264
 
            };
265
 
        }
266
 
    },
267
 
 
268
 
    /**
269
 
     * Schema-parsed result field.
270
 
     *
271
 
     * @method _parseField
272
 
     * @param field {String | Object} Required. Field definition.
273
 
     * @param result {Object} Required. Schema parsed data object.
274
 
     * @param context {Object} Required. XML node or document to search within.
275
 
     * @static
276
 
     * @protected
277
 
     */
278
 
    _parseField: function(field, result, context) {
279
 
        var key = field.key || field,
280
 
            parsed;
281
 
 
282
 
        if (field.schema) {
283
 
            parsed = { results: [], meta: {} };
284
 
            parsed = SchemaXML._parseResults(field.schema, context, parsed);
285
 
 
286
 
            result[key] = parsed.results;
287
 
        } else {
288
 
            result[key] = SchemaXML._getLocationValue(field, context);
289
 
        }
290
 
    },
291
 
 
292
 
    /**
293
 
     * Parses results data according to schema
294
 
     *
295
 
     * @method _parseMeta
296
 
     * @param xmldoc_in {Object} XML document parse.
297
 
     * @param data_out {Object} In-progress schema-parsed data to update.
298
 
     * @return {Object} Schema-parsed data.
299
 
     * @static
300
 
     * @protected
301
 
     */
302
 
    _parseMeta: function(metaFields, xmldoc_in, data_out) {
303
 
        if(Lang.isObject(metaFields)) {
304
 
            var key,
305
 
                xmldoc = xmldoc_in.ownerDocument || xmldoc_in;
306
 
 
307
 
            for(key in metaFields) {
308
 
                if (metaFields.hasOwnProperty(key)) {
309
 
                    data_out.meta[key] = SchemaXML._getLocationValue(metaFields[key], xmldoc);
310
 
                }
311
 
            }
312
 
        }
313
 
        return data_out;
314
 
    },
315
 
 
316
 
    /**
317
 
     * Schema-parsed result to add to results list.
318
 
     *
319
 
     * @method _parseResult
320
 
     * @param fields {Array} Required. A collection of field definition.
321
 
     * @param context {Object} Required. XML node or document to search within.
322
 
     * @return {Object} Schema-parsed data.
323
 
     * @static
324
 
     * @protected
325
 
     */
326
 
    _parseResult: function(fields, context) {
327
 
        var result = {}, j;
328
 
 
329
 
        // Find each field value
330
 
        for (j=fields.length-1; 0 <= j; j--) {
331
 
            SchemaXML._parseField(fields[j], result, context);
332
 
        }
333
 
 
334
 
        return result;
335
 
    },
336
 
 
337
 
    /**
338
 
     * Schema-parsed list of results from full data
339
 
     *
340
 
     * @method _parseResults
341
 
     * @param schema {Object} Schema to parse against.
342
 
     * @param context {Object} XML node or document to parse.
343
 
     * @param data_out {Object} In-progress schema-parsed data to update.
344
 
     * @return {Object} Schema-parsed data.
345
 
     * @static
346
 
     * @protected
347
 
     */
348
 
    _parseResults: function(schema, context, data_out) {
349
 
        if (schema.resultListLocator && Lang.isArray(schema.resultFields)) {
350
 
            var xmldoc = context.ownerDocument || context,
351
 
                fields = schema.resultFields,
352
 
                results = [],
353
 
                node, nodeList, i=0;
354
 
 
355
 
            if (schema.resultListLocator.match(/^[:\-\w]+$/)) {
356
 
                nodeList = context.getElementsByTagName(schema.resultListLocator);
357
 
                
358
 
                // loop through each result node
359
 
                for (i = nodeList.length - 1; i >= 0; --i) {
360
 
                    results[i] = SchemaXML._parseResult(fields, nodeList[i]);
361
 
                }
362
 
            } else {
363
 
                nodeList = SchemaXML._getXPathResult(schema.resultListLocator, context, xmldoc);
364
 
 
365
 
                // loop through the nodelist
366
 
                while ((node = nodeList.iterateNext())) {
367
 
                    results[i] = SchemaXML._parseResult(fields, node);
368
 
                    i += 1;
369
 
                }
370
 
            }
371
 
 
372
 
            if (results.length) {
373
 
                data_out.results = results;
374
 
            } else {
375
 
                data_out.error = new Error("XML schema result nodes retrieval failure");
376
 
            }
377
 
        }
378
 
        return data_out;
379
 
    }
380
 
};
381
 
 
382
 
Y.DataSchema.XML = Y.mix(SchemaXML, Y.DataSchema.Base);
383
 
 
384
 
 
385
 
}, '3.5.1' ,{requires:['dataschema-base']});