// @tag enterprise
/**
* @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];
//
if (!amfx) {
Ext.warn.log("No AMFX tag in message");
}
if (amfx.getAttribute('ver') != "3") {
Ext.Error.raise("Unsupported AMFX version: " + amfx.getAttribute('ver'));
}
//
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));
}
//
Ext.Error.raise("Unknown tag type: " + node.tagName);
//
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 `