skypefacebook-workplaceoutlookemailmicrosoft-teamsdiscordmessengercustom-servicesmacoslinuxwindowsinboxwhatsappicloudtweetdeckhipchattelegramhangoutsslackgmail
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
444 lines
15 KiB
444 lines
15 KiB
//<feature amf> |
|
/** |
|
* @class Ext.data.amf.XmlDecoder |
|
* This class parses an XML-based AMFX message and returns the deserialized |
|
* objects. You should not need to use this class directly. It's mostly used by |
|
* the AMFX Direct implementation. |
|
* To decode a message, first construct a Decoder: |
|
* |
|
* decoder = Ext.create('Ext.data.amf.XmlDecoder'); |
|
* |
|
* Then ask it to read in the message : |
|
* |
|
* resp = decoder.readAmfxMessage(str); |
|
* |
|
* For more information on working with AMF data please refer to the |
|
* [AMF Guide](#/guide/amf). |
|
*/ |
|
Ext.define('Ext.data.amf.XmlDecoder', { |
|
|
|
alias: 'data.amf.xmldecoder', |
|
|
|
statics: { |
|
|
|
/** |
|
* Parses an xml string and returns an xml document |
|
* @private |
|
* @param {String} xml |
|
*/ |
|
readXml: function(xml) { |
|
var doc; |
|
|
|
if (window.DOMParser) { |
|
doc = (new DOMParser()).parseFromString(xml, "text/xml"); |
|
} else { |
|
doc = new ActiveXObject("Microsoft.XMLDOM"); |
|
doc.loadXML(xml); |
|
} |
|
|
|
return doc; |
|
}, |
|
|
|
/** |
|
* parses a node containing a byte array in hexadecimal format, returning the reconstructed array. |
|
* @param {HTMLElement/XMLElement} node the node |
|
* @return {Array} a byte array |
|
*/ |
|
readByteArray: function(node) { |
|
var bytes = [], |
|
c, i, str; |
|
str = node.firstChild.nodeValue; |
|
for (i = 0; i < str.length; i = i + 2) { |
|
c = str.substr(i, 2); |
|
bytes.push(parseInt(c, 16)); |
|
} |
|
return bytes; |
|
}, |
|
|
|
/** |
|
* Deserializes an AMF3 binary object from a byte array |
|
* @param {Array} bytes the byte array containing one AMF3-encoded value |
|
* @return {Object} the decoded value |
|
*/ |
|
readAMF3Value: function(bytes) { |
|
var packet; |
|
packet = Ext.create('Ext.data.amf.Packet'); |
|
return packet.decodeValue(bytes); |
|
}, |
|
|
|
/** |
|
* Accepts Flex-style UID and decodes the number in the first four bytes (8 hex digits) of data. |
|
* @param {String} messageId the message ID |
|
* @return {Number} the transaction ID |
|
*/ |
|
decodeTidFromFlexUID: function(messageId) { |
|
var str; |
|
str = messageId.substr(0,8); |
|
return parseInt(str, 16); |
|
} |
|
|
|
}, |
|
|
|
/** |
|
* Creates new encoder. |
|
* @param {Object} config Configuration options |
|
*/ |
|
constructor: function(config) { |
|
this.initConfig(config); |
|
this.clear(); |
|
}, |
|
|
|
/** |
|
* Clears the accumulated data and reference tables |
|
*/ |
|
clear: function() { |
|
// reset reference counters |
|
this.objectReferences=[]; |
|
this.traitsReferences=[]; |
|
this.stringReferences=[]; |
|
}, |
|
|
|
/** |
|
* Reads and returns a decoded AMFX packet. |
|
* @param {String} xml the xml of the message |
|
* @return {Object} the response object containing the message |
|
*/ |
|
readAmfxMessage: function(xml) { |
|
var doc, amfx, body, |
|
i, resp={}; |
|
this.clear(); // reset counters |
|
doc = Ext.data.amf.XmlDecoder.readXml(xml); |
|
amfx = doc.getElementsByTagName('amfx')[0]; |
|
//<debug> |
|
if (!amfx) { |
|
Ext.warn.log("No AMFX tag in message"); |
|
} |
|
if (amfx.getAttribute('ver') != "3") { |
|
Ext.Error.raise("Unsupported AMFX version: " + amfx.getAttribute('ver')); |
|
} |
|
//</debug> |
|
body = amfx.getElementsByTagName('body')[0]; |
|
resp.targetURI = body.getAttribute('targetURI'); |
|
resp.responseURI = body.getAttribute('responseURI'); // most likely empty string |
|
for (i = 0; i < body.childNodes.length; i++) { |
|
if (body.childNodes.item(i).nodeType != 1) { |
|
// only process element nodes, ignore white space and text nodes |
|
continue; |
|
} |
|
resp.message = this.readValue(body.childNodes.item(i)); |
|
break; // no need to keep iterating |
|
} |
|
return resp; |
|
}, |
|
|
|
/** |
|
* Parses an HTML element returning the appropriate JavaScript value from the AMFX data. |
|
* @param {HTMLElement} node the node to parse |
|
* @return {Object} a JavaScript object or value |
|
*/ |
|
readValue: function(node) { |
|
var val; |
|
if (typeof node.normalize === 'function') { |
|
node.normalize(); |
|
} |
|
// 2DO: handle references! |
|
if (node.tagName == "null") { |
|
return null; |
|
} else if (node.tagName == "true") { |
|
return true; |
|
} else if (node.tagName == "false") { |
|
return false; |
|
} else if (node.tagName == "string") { |
|
return this.readString(node); |
|
} else if (node.tagName == "int") { |
|
return parseInt(node.firstChild.nodeValue); |
|
} else if (node.tagName == "double") { |
|
return parseFloat(node.firstChild.nodeValue); |
|
} else if (node.tagName == "date") { |
|
val = new Date(parseFloat(node.firstChild.nodeValue)); |
|
// record in object reference table |
|
this.objectReferences.push(val); |
|
return val; |
|
} else if (node.tagName == "dictionary") { |
|
return this.readDictionary(node); |
|
} else if (node.tagName == "array") { |
|
return this.readArray(node); |
|
} else if (node.tagName == "ref") { |
|
return this.readObjectRef(node); |
|
} else if (node.tagName == "object") { |
|
return this.readObject(node); |
|
} else if (node.tagName == "xml") { |
|
// the CDATA content of the node is a parseable XML document. parse it. |
|
return Ext.data.amf.XmlDecoder.readXml(node.firstChild.nodeValue); |
|
} else if (node.tagName == "bytearray") { |
|
// 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 |
|
return Ext.data.amf.XmlDecoder.readAMF3Value(Ext.data.amf.XmlDecoder.readByteArray(node)); |
|
} |
|
//<debug> |
|
Ext.Error.raise("Unknown tag type: " + node.tagName); |
|
//</debug> |
|
return null; |
|
}, |
|
|
|
/** |
|
* Reads a string or string reference and return the value |
|
* @param {HTMLElement/XMLElement} node the node containing a string object |
|
* @return {String} the parsed string |
|
*/ |
|
readString: function(node) { |
|
var val; |
|
if (node.getAttributeNode('id')) { |
|
return this.stringReferences[parseInt(node.getAttribute('id'))]; |
|
} |
|
val = (node.firstChild ? node.firstChild.nodeValue : "") || ""; |
|
this.stringReferences.push(val); |
|
return val; |
|
}, |
|
|
|
/** |
|
* Parses and returns an ordered list of trait names |
|
* @param {HTMLElement/XMLElement} node the traits node from the XML doc |
|
* @return {Array} an array of ordered trait names or null if it's an externalizable object |
|
*/ |
|
readTraits: function(node) { |
|
var traits = [], i, rawtraits; |
|
if (node === null) { |
|
return null; |
|
} |
|
if (node.getAttribute('externalizable') == "true") { |
|
// no traits since it's an externalizable or a null object. |
|
return null; |
|
} |
|
if (node.getAttributeNode('id')) { |
|
// return traits reference |
|
return this.traitsReferences[parseInt(node.getAttributeNode('id').value)]; |
|
} |
|
/* // empty anonymous objects still seem to get their empty traits in the reference table |
|
if (!node.hasChildNodes()) { |
|
var className = node.parentNode.getElementsByTagName('type'); |
|
if (className.length == 0) { |
|
return traits; // special case of an anonymous object with no traits. Does not get reference counted |
|
} |
|
} |
|
*/ |
|
rawtraits = node.childNodes; |
|
for (i = 0; i < rawtraits.length; i++) { |
|
if (rawtraits.item(i).nodeType != 1) { |
|
// only process element nodes, ignore white space and text nodes |
|
continue; |
|
} |
|
// this will be a string, but let the readValue function handle it nonetheless |
|
traits.push(this.readValue(rawtraits.item(i))); |
|
} |
|
|
|
// register traits in ref table: |
|
this.traitsReferences.push(traits); |
|
return traits; |
|
}, |
|
|
|
/** |
|
* Parses and return an object / array / dictionary / date from reference |
|
* @param {HTMLElement/XMLElement} node the ref node |
|
* @return {Object} the previously instantiated object referred to by the ref node |
|
*/ |
|
readObjectRef: function(node) { |
|
var id; |
|
id = parseInt(node.getAttribute('id')); |
|
return this.objectReferences[id]; |
|
}, |
|
|
|
/** |
|
* Parses and returns an AMFX object. |
|
* @param {HTMLElement/XMLElement} the `<object>` node to parse |
|
* @return {Object} the deserialized object |
|
*/ |
|
readObject: function(node) { |
|
var obj, |
|
traits = [], |
|
traitsNode, |
|
i, j, n, |
|
key, val, |
|
klass = null, className; |
|
|
|
className = node.getAttribute('type'); |
|
if (className) { |
|
klass = Ext.ClassManager.getByAlias('amfx.' + className); // check if special case for class |
|
} |
|
obj = klass ? new klass() : (className ? {$className: className} : {}); // if there is no klass, mark the classname for easier parsing of returned results |
|
|
|
// check if we need special handling for this class |
|
if ((!klass) && this.converters[className]) { |
|
obj = this.converters[className](this,node); |
|
return obj; // we're done |
|
} |
|
|
|
traitsNode = node.getElementsByTagName('traits')[0]; |
|
traits = this.readTraits(traitsNode); |
|
//<debug> |
|
if (traits === null) { |
|
Ext.Error.raise("No support for externalizable object: " + className); |
|
} |
|
//</debug> |
|
// Register object if ref table, in case there's a cyclical reference coming |
|
this.objectReferences.push(obj); |
|
|
|
|
|
// 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 |
|
j = 0; |
|
for (i = 0; i < node.childNodes.length; i++) { |
|
n = node.childNodes.item(i); |
|
if (n.nodeType != 1) { |
|
// Ignore text nodes and non-element nodes |
|
continue; |
|
} |
|
if (n.tagName == "traits") { |
|
// ignore the traits node. We've already covered it. |
|
continue; |
|
} |
|
key = traits[j]; |
|
val = this.readValue(n); |
|
j = j + 1; |
|
obj[key] = val; |
|
//<debug> |
|
if (j > traits.length) { |
|
Ext.Error.raise("Too many items for object, not enough traits: " + className); |
|
} |
|
//</debug> |
|
} |
|
return obj; |
|
}, |
|
|
|
/** |
|
* Parses and returns an AMFX array. |
|
* @param {HTMLElement/XMLElement} node the array node |
|
* @return {Array} the deserialized array |
|
*/ |
|
readArray: function(node) { |
|
var arr=[], |
|
n,i,j,l,name, val, len, childnodes, cn; |
|
|
|
// register array in object references table before we parse, in case of circular references |
|
this.objectReferences.push(arr); |
|
|
|
len = parseInt(node.getAttributeNode('length').value); |
|
i = 0; |
|
// the length only accounts for the ordinal values. For the rest, we'll read them as ECMA key-value pairs |
|
for (l = 0; l < node.childNodes.length; l++) { |
|
n = node.childNodes.item(l); |
|
if (n.nodeType != 1) { |
|
// Ignore text nodes and non-element nodes |
|
continue; |
|
} |
|
if (n.tagName == "item") { |
|
// parse item node |
|
name = n.getAttributeNode('name').value; |
|
childnodes = n.childNodes; |
|
for (j = 0; j < childnodes.length; j++) { |
|
cn = childnodes.item(j); |
|
if (cn.nodeType != 1) { |
|
// Ignore text nodes and non-element nodes |
|
continue; |
|
} |
|
val = this.readValue(cn); |
|
break; // out of loop. We've found our value |
|
} |
|
arr[name] = val; |
|
} else { |
|
// ordinal node |
|
arr[i] = this.readValue(n); |
|
i++; |
|
//<debug> |
|
if (i > len) { |
|
Ext.Error.raise("Array has more items than declared length: " + i + " > " + len); |
|
} |
|
//</debug> |
|
} |
|
} |
|
//<debug> |
|
if (i < len) { |
|
Ext.Error.raise("Array has less items than declared length: " + i + " < " + len); |
|
} |
|
//</debug> |
|
return arr; |
|
}, |
|
|
|
/** |
|
* Parses and returns an AMFX dictionary. |
|
* @param {HTMLElement/XMLElement} node the `<dictionary>` node |
|
* @return {Object} a javascript object with the dictionary value-pair elements |
|
*/ |
|
readDictionary: function(node) { |
|
// For now, handle regular objects |
|
var dict = {}, |
|
key, val, |
|
i, j, n, len; |
|
|
|
len = parseInt(node.getAttribute('length')); |
|
// Register dictionary in the ref table, in case there's a cyclical reference coming |
|
this.objectReferences.push(dict); |
|
|
|
|
|
// now find pairs of keys and values |
|
key = null; |
|
val = null; |
|
j = 0; |
|
for (i = 0; i < node.childNodes.length; i++) { |
|
n = node.childNodes.item(i); |
|
if (n.nodeType != 1) { |
|
// Ignore text nodes and non-element nodes |
|
continue; |
|
} |
|
if (!key) { |
|
key = this.readValue(n); |
|
continue; // next element is the value |
|
} |
|
val = this.readValue(n); |
|
j = j + 1; |
|
dict[key] = val; |
|
key = null; |
|
val = null; |
|
} |
|
//<debug> |
|
if (j != len) { |
|
Ext.Error.raise("Incorrect number of dictionary values: " + j + " != " + len); |
|
} |
|
//</debug> |
|
return dict; |
|
}, |
|
|
|
|
|
/** |
|
* Converts externalizable flex objects with a source array to a regular array. |
|
* @private |
|
*/ |
|
convertObjectWithSourceField: function(node) { |
|
var i, n, val; |
|
for (i = 0; i < node.childNodes.length; i++) { |
|
n = node.childNodes.item(i); |
|
if (n.tagName == "bytearray") { |
|
val = this.readValue(n); |
|
this.objectReferences.push(val); |
|
return val; |
|
} |
|
} |
|
return null; // we shouldn't reach here, but just in case |
|
}, |
|
|
|
/** |
|
* Converters used in converting specific typed Flex classes to JavaScript usable form. |
|
* @private |
|
*/ |
|
|
|
converters: { |
|
'flex.messaging.io.ArrayCollection': function(decoder,node) { |
|
return decoder.convertObjectWithSourceField(node); |
|
}, |
|
'mx.collections.ArrayList': function(decoder,node) { |
|
return decoder.convertObjectWithSourceField(node); |
|
}, |
|
'mx.collections.ArrayCollection': function(decoder,node) { |
|
return decoder.convertObjectWithSourceField(node); |
|
} |
|
} |
|
}); |
|
//</feature>
|