]> git.proxmox.com Git - extjs.git/blame - extjs/packages/amf/src/data/amf/XmlDecoder.js
add extjs 6.0.1 sources
[extjs.git] / extjs / packages / amf / src / data / amf / XmlDecoder.js
CommitLineData
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
18Ext.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