]>
Commit | Line | Data |
---|---|---|
6527f429 DM |
1 | // @tag enterprise\r |
2 | /**\r | |
3 | * @class Ext.data.amf.XmlDecoder\r | |
4 | * This class parses an XML-based AMFX message and returns the deserialized\r | |
5 | * objects. You should not need to use this class directly. It's mostly used by\r | |
6 | * the AMFX Direct implementation.\r | |
7 | * To decode a message, first construct a Decoder:\r | |
8 | *\r | |
9 | * decoder = Ext.create('Ext.data.amf.XmlDecoder');\r | |
10 | *\r | |
11 | * Then ask it to read in the message :\r | |
12 | *\r | |
13 | * resp = decoder.readAmfxMessage(str);\r | |
14 | *\r | |
15 | * For more information on working with AMF data please refer to the\r | |
16 | * [AMF Guide](#/guide/amf).\r | |
17 | */\r | |
18 | Ext.define('Ext.data.amf.XmlDecoder', {\r | |
19 | \r | |
20 | alias: 'data.amf.xmldecoder',\r | |
21 | \r | |
22 | statics: {\r | |
23 | \r | |
24 | /**\r | |
25 | * Parses an xml string and returns an xml document\r | |
26 | * @private\r | |
27 | * @param {String} xml\r | |
28 | */\r | |
29 | readXml: function(xml) {\r | |
30 | var doc;\r | |
31 | \r | |
32 | if (window.DOMParser) {\r | |
33 | doc = (new DOMParser()).parseFromString(xml, "text/xml");\r | |
34 | } else {\r | |
35 | doc = new ActiveXObject("Microsoft.XMLDOM");\r | |
36 | doc.loadXML(xml);\r | |
37 | }\r | |
38 | \r | |
39 | return doc;\r | |
40 | },\r | |
41 | \r | |
42 | /**\r | |
43 | * parses a node containing a byte array in hexadecimal format, returning the reconstructed array.\r | |
44 | * @param {HTMLElement/XMLElement} node the node\r | |
45 | * @return {Array} a byte array\r | |
46 | */\r | |
47 | readByteArray: function(node) {\r | |
48 | var bytes = [],\r | |
49 | c, i, str;\r | |
50 | str = node.firstChild.nodeValue;\r | |
51 | for (i = 0; i < str.length; i = i + 2) {\r | |
52 | c = str.substr(i, 2);\r | |
53 | bytes.push(parseInt(c, 16));\r | |
54 | }\r | |
55 | return bytes;\r | |
56 | },\r | |
57 | \r | |
58 | /**\r | |
59 | * Deserializes an AMF3 binary object from a byte array\r | |
60 | * @param {Array} bytes the byte array containing one AMF3-encoded value\r | |
61 | * @return {Object} the decoded value\r | |
62 | */\r | |
63 | readAMF3Value: function(bytes) {\r | |
64 | var packet;\r | |
65 | packet = Ext.create('Ext.data.amf.Packet');\r | |
66 | return packet.decodeValue(bytes);\r | |
67 | },\r | |
68 | \r | |
69 | /**\r | |
70 | * Accepts Flex-style UID and decodes the number in the first four bytes (8 hex digits) of data.\r | |
71 | * @param {String} messageId the message ID\r | |
72 | * @return {Number} the transaction ID\r | |
73 | */\r | |
74 | decodeTidFromFlexUID: function(messageId) {\r | |
75 | var str;\r | |
76 | str = messageId.substr(0,8);\r | |
77 | return parseInt(str, 16);\r | |
78 | }\r | |
79 | \r | |
80 | },\r | |
81 | \r | |
82 | /**\r | |
83 | * Creates new encoder.\r | |
84 | * @param {Object} config Configuration options\r | |
85 | */\r | |
86 | constructor: function(config) {\r | |
87 | this.initConfig(config);\r | |
88 | this.clear();\r | |
89 | },\r | |
90 | \r | |
91 | /**\r | |
92 | * Clears the accumulated data and reference tables\r | |
93 | */\r | |
94 | clear: function() {\r | |
95 | // reset reference counters\r | |
96 | this.objectReferences=[];\r | |
97 | this.traitsReferences=[];\r | |
98 | this.stringReferences=[];\r | |
99 | },\r | |
100 | \r | |
101 | /**\r | |
102 | * Reads and returns a decoded AMFX packet.\r | |
103 | * @param {String} xml the xml of the message\r | |
104 | * @return {Object} the response object containing the message\r | |
105 | */\r | |
106 | readAmfxMessage: function(xml) {\r | |
107 | var doc, amfx, body,\r | |
108 | i, resp={};\r | |
109 | this.clear(); // reset counters\r | |
110 | doc = Ext.data.amf.XmlDecoder.readXml(xml);\r | |
111 | amfx = doc.getElementsByTagName('amfx')[0];\r | |
112 | //<debug>\r | |
113 | if (!amfx) {\r | |
114 | Ext.warn.log("No AMFX tag in message");\r | |
115 | }\r | |
116 | if (amfx.getAttribute('ver') != "3") {\r | |
117 | Ext.raise("Unsupported AMFX version: " + amfx.getAttribute('ver'));\r | |
118 | }\r | |
119 | //</debug>\r | |
120 | body = amfx.getElementsByTagName('body')[0];\r | |
121 | resp.targetURI = body.getAttribute('targetURI');\r | |
122 | resp.responseURI = body.getAttribute('responseURI'); // most likely empty string\r | |
123 | for (i = 0; i < body.childNodes.length; i++) {\r | |
124 | if (body.childNodes.item(i).nodeType != 1) {\r | |
125 | // only process element nodes, ignore white space and text nodes\r | |
126 | continue;\r | |
127 | }\r | |
128 | resp.message = this.readValue(body.childNodes.item(i));\r | |
129 | break; // no need to keep iterating\r | |
130 | }\r | |
131 | return resp;\r | |
132 | },\r | |
133 | \r | |
134 | /**\r | |
135 | * Parses an HTML element returning the appropriate JavaScript value from the AMFX data.\r | |
136 | * @param {HTMLElement} node The node to parse\r | |
137 | * @return {Object} a JavaScript object or value\r | |
138 | */\r | |
139 | readValue: function(node) {\r | |
140 | var val;\r | |
141 | if (typeof node.normalize === 'function') {\r | |
142 | node.normalize();\r | |
143 | }\r | |
144 | // 2DO: handle references!\r | |
145 | if (node.tagName == "null") {\r | |
146 | return null;\r | |
147 | } else if (node.tagName == "true") {\r | |
148 | return true;\r | |
149 | } else if (node.tagName == "false") {\r | |
150 | return false;\r | |
151 | } else if (node.tagName == "string") {\r | |
152 | return this.readString(node);\r | |
153 | } else if (node.tagName == "int") {\r | |
154 | return parseInt(node.firstChild.nodeValue);\r | |
155 | } else if (node.tagName == "double") {\r | |
156 | return parseFloat(node.firstChild.nodeValue);\r | |
157 | } else if (node.tagName == "date") {\r | |
158 | val = new Date(parseFloat(node.firstChild.nodeValue));\r | |
159 | // record in object reference table\r | |
160 | this.objectReferences.push(val);\r | |
161 | return val;\r | |
162 | } else if (node.tagName == "dictionary") {\r | |
163 | return this.readDictionary(node);\r | |
164 | } else if (node.tagName == "array") {\r | |
165 | return this.readArray(node);\r | |
166 | } else if (node.tagName == "ref") {\r | |
167 | return this.readObjectRef(node);\r | |
168 | } else if (node.tagName == "object") {\r | |
169 | return this.readObject(node);\r | |
170 | } else if (node.tagName == "xml") {\r | |
171 | // the CDATA content of the node is a parseable XML document. parse it.\r | |
172 | return Ext.data.amf.XmlDecoder.readXml(node.firstChild.nodeValue);\r | |
173 | } else if (node.tagName == "bytearray") {\r | |
174 | // a byte array is usually an AMF stream. Parse it to a byte array, then pass through the AMF decoder to get the objects inside\r | |
175 | return Ext.data.amf.XmlDecoder.readAMF3Value(Ext.data.amf.XmlDecoder.readByteArray(node));\r | |
176 | }\r | |
177 | //<debug>\r | |
178 | Ext.raise("Unknown tag type: " + node.tagName);\r | |
179 | //</debug>\r | |
180 | return null;\r | |
181 | },\r | |
182 | \r | |
183 | /**\r | |
184 | * Reads a string or string reference and return the value\r | |
185 | * @param {HTMLElement/XMLElement} node the node containing a string object\r | |
186 | * @return {String} the parsed string\r | |
187 | */\r | |
188 | readString: function(node) {\r | |
189 | var val;\r | |
190 | if (node.getAttributeNode('id')) {\r | |
191 | return this.stringReferences[parseInt(node.getAttribute('id'))];\r | |
192 | }\r | |
193 | val = (node.firstChild ? node.firstChild.nodeValue : "") || "";\r | |
194 | this.stringReferences.push(val);\r | |
195 | return val;\r | |
196 | },\r | |
197 | \r | |
198 | /**\r | |
199 | * Parses and returns an ordered list of trait names\r | |
200 | * @param {HTMLElement/XMLElement} node the traits node from the XML doc\r | |
201 | * @return {Array} an array of ordered trait names or null if it's an externalizable object\r | |
202 | */\r | |
203 | readTraits: function(node) {\r | |
204 | var traits = [], i, rawtraits;\r | |
205 | if (node === null) {\r | |
206 | return null;\r | |
207 | }\r | |
208 | if (node.getAttribute('externalizable') == "true") {\r | |
209 | // no traits since it's an externalizable or a null object.\r | |
210 | return null;\r | |
211 | }\r | |
212 | if (node.getAttributeNode('id')) {\r | |
213 | // return traits reference\r | |
214 | return this.traitsReferences[parseInt(node.getAttributeNode('id').value)];\r | |
215 | }\r | |
216 | /* // empty anonymous objects still seem to get their empty traits in the reference table\r | |
217 | if (!node.hasChildNodes()) {\r | |
218 | var className = node.parentNode.getElementsByTagName('type');\r | |
219 | if (className.length == 0) {\r | |
220 | return traits; // special case of an anonymous object with no traits. Does not get reference counted\r | |
221 | }\r | |
222 | }\r | |
223 | */\r | |
224 | rawtraits = node.childNodes;\r | |
225 | for (i = 0; i < rawtraits.length; i++) {\r | |
226 | if (rawtraits.item(i).nodeType != 1) {\r | |
227 | // only process element nodes, ignore white space and text nodes\r | |
228 | continue;\r | |
229 | }\r | |
230 | // this will be a string, but let the readValue function handle it nonetheless\r | |
231 | traits.push(this.readValue(rawtraits.item(i)));\r | |
232 | }\r | |
233 | \r | |
234 | // register traits in ref table:\r | |
235 | this.traitsReferences.push(traits);\r | |
236 | return traits;\r | |
237 | },\r | |
238 | \r | |
239 | /**\r | |
240 | * Parses and return an object / array / dictionary / date from reference\r | |
241 | * @param {HTMLElement/XMLElement} node the ref node\r | |
242 | * @return {Object} the previously instantiated object referred to by the ref node\r | |
243 | */\r | |
244 | readObjectRef: function(node) {\r | |
245 | var id;\r | |
246 | id = parseInt(node.getAttribute('id'));\r | |
247 | return this.objectReferences[id];\r | |
248 | },\r | |
249 | \r | |
250 | /**\r | |
251 | * Parses and returns an AMFX object.\r | |
252 | * @param {HTMLElement/XMLElement} node the `<object>` node to parse\r | |
253 | * @return {Object} the deserialized object\r | |
254 | */\r | |
255 | readObject: function(node) {\r | |
256 | var obj,\r | |
257 | traits = [],\r | |
258 | traitsNode,\r | |
259 | i, j, n,\r | |
260 | key, val,\r | |
261 | klass = null, className;\r | |
262 | \r | |
263 | className = node.getAttribute('type');\r | |
264 | if (className) {\r | |
265 | klass = Ext.ClassManager.getByAlias('amfx.' + className); // check if special case for class\r | |
266 | }\r | |
267 | obj = klass ? new klass() : (className ? {$className: className} : {}); // if there is no klass, mark the classname for easier parsing of returned results\r | |
268 | \r | |
269 | // check if we need special handling for this class\r | |
270 | if ((!klass) && this.converters[className]) {\r | |
271 | obj = this.converters[className](this,node);\r | |
272 | return obj; // we're done\r | |
273 | }\r | |
274 | \r | |
275 | traitsNode = node.getElementsByTagName('traits')[0];\r | |
276 | traits = this.readTraits(traitsNode);\r | |
277 | //<debug>\r | |
278 | if (traits === null) {\r | |
279 | Ext.raise("No support for externalizable object: " + className);\r | |
280 | }\r | |
281 | //</debug>\r | |
282 | // Register object if ref table, in case there's a cyclical reference coming\r | |
283 | this.objectReferences.push(obj);\r | |
284 | \r | |
285 | \r | |
286 | // Now we expect an item for each trait name we have. We assume it's an ordered list. We'll skip the first (traits) tag\r | |
287 | j = 0;\r | |
288 | for (i = 0; i < node.childNodes.length; i++) {\r | |
289 | n = node.childNodes.item(i);\r | |
290 | if (n.nodeType != 1) {\r | |
291 | // Ignore text nodes and non-element nodes\r | |
292 | continue;\r | |
293 | }\r | |
294 | if (n.tagName == "traits") {\r | |
295 | // ignore the traits node. We've already covered it.\r | |
296 | continue;\r | |
297 | }\r | |
298 | key = traits[j];\r | |
299 | val = this.readValue(n);\r | |
300 | j = j + 1;\r | |
301 | obj[key] = val;\r | |
302 | //<debug>\r | |
303 | if (j > traits.length) {\r | |
304 | Ext.raise("Too many items for object, not enough traits: " + className);\r | |
305 | }\r | |
306 | //</debug>\r | |
307 | }\r | |
308 | return obj;\r | |
309 | },\r | |
310 | \r | |
311 | /**\r | |
312 | * Parses and returns an AMFX array.\r | |
313 | * @param {HTMLElement/XMLElement} node the array node\r | |
314 | * @return {Array} the deserialized array\r | |
315 | */\r | |
316 | readArray: function(node) {\r | |
317 | var arr=[],\r | |
318 | n,i,j,l,name, val, len, childnodes, cn;\r | |
319 | \r | |
320 | // register array in object references table before we parse, in case of circular references\r | |
321 | this.objectReferences.push(arr);\r | |
322 | \r | |
323 | len = parseInt(node.getAttributeNode('length').value);\r | |
324 | i = 0;\r | |
325 | // the length only accounts for the ordinal values. For the rest, we'll read them as ECMA key-value pairs\r | |
326 | for (l = 0; l < node.childNodes.length; l++) {\r | |
327 | n = node.childNodes.item(l);\r | |
328 | if (n.nodeType != 1) {\r | |
329 | // Ignore text nodes and non-element nodes\r | |
330 | continue;\r | |
331 | }\r | |
332 | if (n.tagName == "item") {\r | |
333 | // parse item node\r | |
334 | name = n.getAttributeNode('name').value;\r | |
335 | childnodes = n.childNodes;\r | |
336 | for (j = 0; j < childnodes.length; j++) {\r | |
337 | cn = childnodes.item(j);\r | |
338 | if (cn.nodeType != 1) {\r | |
339 | // Ignore text nodes and non-element nodes\r | |
340 | continue;\r | |
341 | }\r | |
342 | val = this.readValue(cn);\r | |
343 | break; // out of loop. We've found our value\r | |
344 | }\r | |
345 | arr[name] = val;\r | |
346 | } else {\r | |
347 | // ordinal node\r | |
348 | arr[i] = this.readValue(n);\r | |
349 | i++;\r | |
350 | //<debug>\r | |
351 | if (i > len) {\r | |
352 | Ext.raise("Array has more items than declared length: " + i + " > " + len);\r | |
353 | }\r | |
354 | //</debug>\r | |
355 | }\r | |
356 | }\r | |
357 | //<debug>\r | |
358 | if (i < len) {\r | |
359 | Ext.raise("Array has less items than declared length: " + i + " < " + len);\r | |
360 | }\r | |
361 | //</debug>\r | |
362 | return arr;\r | |
363 | },\r | |
364 | \r | |
365 | /**\r | |
366 | * Parses and returns an AMFX dictionary.\r | |
367 | * @param {HTMLElement/XMLElement} node the `<dictionary>` node\r | |
368 | * @return {Object} a javascript object with the dictionary value-pair elements\r | |
369 | */\r | |
370 | readDictionary: function(node) {\r | |
371 | // For now, handle regular objects\r | |
372 | var dict = {},\r | |
373 | key, val,\r | |
374 | i, j, n, len;\r | |
375 | \r | |
376 | len = parseInt(node.getAttribute('length'));\r | |
377 | // Register dictionary in the ref table, in case there's a cyclical reference coming\r | |
378 | this.objectReferences.push(dict);\r | |
379 | \r | |
380 | \r | |
381 | // now find pairs of keys and values\r | |
382 | key = null;\r | |
383 | val = null;\r | |
384 | j = 0;\r | |
385 | for (i = 0; i < node.childNodes.length; i++) {\r | |
386 | n = node.childNodes.item(i);\r | |
387 | if (n.nodeType != 1) {\r | |
388 | // Ignore text nodes and non-element nodes\r | |
389 | continue;\r | |
390 | }\r | |
391 | if (!key) {\r | |
392 | key = this.readValue(n);\r | |
393 | continue; // next element is the value\r | |
394 | }\r | |
395 | val = this.readValue(n);\r | |
396 | j = j + 1;\r | |
397 | dict[key] = val;\r | |
398 | key = null;\r | |
399 | val = null;\r | |
400 | }\r | |
401 | //<debug>\r | |
402 | if (j != len) {\r | |
403 | Ext.raise("Incorrect number of dictionary values: " + j + " != " + len);\r | |
404 | }\r | |
405 | //</debug>\r | |
406 | return dict;\r | |
407 | },\r | |
408 | \r | |
409 | \r | |
410 | /**\r | |
411 | * Converts externalizable flex objects with a source array to a regular array.\r | |
412 | * @private\r | |
413 | */\r | |
414 | convertObjectWithSourceField: function(node) {\r | |
415 | var i, n, val;\r | |
416 | for (i = 0; i < node.childNodes.length; i++) {\r | |
417 | n = node.childNodes.item(i);\r | |
418 | if (n.tagName == "bytearray") {\r | |
419 | val = this.readValue(n);\r | |
420 | this.objectReferences.push(val);\r | |
421 | return val;\r | |
422 | }\r | |
423 | }\r | |
424 | return null; // we shouldn't reach here, but just in case\r | |
425 | },\r | |
426 | \r | |
427 | /**\r | |
428 | * Converters used in converting specific typed Flex classes to JavaScript usable form.\r | |
429 | * @private\r | |
430 | */\r | |
431 | \r | |
432 | converters: {\r | |
433 | 'flex.messaging.io.ArrayCollection': function(decoder,node) {\r | |
434 | return decoder.convertObjectWithSourceField(node);\r | |
435 | },\r | |
436 | 'mx.collections.ArrayList': function(decoder,node) {\r | |
437 | return decoder.convertObjectWithSourceField(node);\r | |
438 | },\r | |
439 | 'mx.collections.ArrayCollection': function(decoder,node) {\r | |
440 | return decoder.convertObjectWithSourceField(node);\r | |
441 | }\r | |
442 | }\r | |
443 | });\r |