Форк Rambox
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

//<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>