outlookemailmicrosoft-teamsdiscordmessengercustom-servicesmacoslinuxwindowsinboxwhatsappicloudtweetdeckhipchattelegramhangoutsslackgmailskypefacebook-workplace
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.
583 lines
18 KiB
583 lines
18 KiB
9 years ago
|
//<feature amf>
|
||
|
/**
|
||
|
* @class Ext.data.amf.XmlEncoder
|
||
|
* This class serializes data in the Action Message Format XML (AMFX) format.
|
||
|
* It can write simple and complex objects, to be used in conjunction with an
|
||
|
* AMFX-compliant server.
|
||
|
* To create an encoded XMl, first construct an Encoder:
|
||
|
*
|
||
|
* var encoder = Ext.create('Ext.data.amf.XmlEncoder');
|
||
|
*
|
||
|
* Then use the writer methods to out data to the :
|
||
|
*
|
||
|
* encoder.writeObject(1);
|
||
|
* encoder.writeObject({a: "b"});
|
||
|
*
|
||
|
* And access the data through the #bytes property:
|
||
|
* encoder.getBody();
|
||
|
*
|
||
|
* You can also reset the class to start a new body:
|
||
|
*
|
||
|
* encoder.clear();
|
||
|
*
|
||
|
* Current limitations:
|
||
|
* AMF3 format (format:3)
|
||
|
* - Each object is written out explicitly, not using the reference tables
|
||
|
* supported by the AMFX format. This means the function does NOT support
|
||
|
* circular reference objects.
|
||
|
* - Objects that aren't Arrays, Dates, Strings, Document (XML) or primitive
|
||
|
* values will be written out as anonymous objects with dynamic data.
|
||
|
* - If the object has a $flexType field, that field will be used in signifying
|
||
|
* the object-type as an attribute, instead of being passed as data.
|
||
|
* - There's no JavaScript equivalent to the ByteArray type in ActionScript,
|
||
|
* hence data will never be searialized as ByteArrays by the writeObject
|
||
|
* function. A writeByteArray method is provided for writing out ByteArray objects.
|
||
|
*
|
||
|
* For more information on working with AMF data please refer to the
|
||
|
* [AMF Guide](#/guide/amf).
|
||
|
*/
|
||
|
|
||
|
Ext.define('Ext.data.amf.XmlEncoder', {
|
||
|
|
||
|
alias: 'data.amf.xmlencoder',
|
||
|
|
||
|
config: {
|
||
|
/**
|
||
|
* @property {String} body - The output string
|
||
|
*/
|
||
|
body: ""
|
||
|
},
|
||
|
|
||
|
statics: {
|
||
|
/**
|
||
|
* Utility function to generate a flex-friendly UID
|
||
|
* @param {Number} id used in the first 8 chars of the id. If not provided, a random number will be used.
|
||
|
* @return {String} a string-encoded opaque UID
|
||
|
*/
|
||
|
generateFlexUID: function(id) {
|
||
|
var uid = "",
|
||
|
i, j, t;
|
||
|
if (id === undefined) {
|
||
|
id = this.randomInt(0, 0xffffffff);
|
||
|
}
|
||
|
// The format of a UID is XXXXXXXX-XXXX-XXXX-XXXX-YYYYYYYYXXXX
|
||
|
// where each X is a random hex digit and each Y is a hex digit from the least significant part of a time stamp.
|
||
|
t = (id + 0x100000000).toString(16).toUpperCase(); // padded
|
||
|
uid = t.substr(t.length - 8, 8); // last 8 chars
|
||
|
|
||
|
for (j = 0; j < 3; j++) {
|
||
|
// 3 -XXXX segments
|
||
|
uid += "-";
|
||
|
for (i = 0; i < 4; i++) {
|
||
|
uid += this.randomInt(0, 15).toString(16).toUpperCase();
|
||
|
}
|
||
|
}
|
||
|
uid += "-";
|
||
|
// add timestamp
|
||
|
t = new Number(new Date()).valueOf().toString(16).toUpperCase(); // get the String representation of milliseconds in hex format
|
||
|
j = 0;
|
||
|
if (t.length < 8) { // pad with "0" if needed
|
||
|
for (i = 0; i < t.length - 8; i++) {
|
||
|
j++;
|
||
|
uid += "0";
|
||
|
}
|
||
|
}
|
||
|
// actual timestamp:
|
||
|
uid += t.substr(-(8-j)); // last few chars
|
||
|
// and last 4 random digits
|
||
|
for (i = 0; i < 4; i++) {
|
||
|
uid += this.randomInt(0, 15).toString(16).toUpperCase();
|
||
|
}
|
||
|
return uid;
|
||
|
},
|
||
|
/**
|
||
|
* Returns a random integer between the specified range (inclusive)
|
||
|
* @param {Number} from Lowest value to return.
|
||
|
* @param {Number} to Highst value to return.
|
||
|
* @return {Number} A random integer within the specified range.
|
||
|
*/
|
||
|
randomInt: function (from, to) {
|
||
|
return Math.floor(Math.random() * (to - from + 1) + from);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Creates new encoder.
|
||
|
* @param {Object} config Configuration options
|
||
|
*/
|
||
|
constructor: function(config) {
|
||
|
this.initConfig(config);
|
||
|
this.clear();
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Clears the accumulated data, starting with an empty string
|
||
|
*/
|
||
|
clear: function() {
|
||
|
this.setBody("");
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Returns the encoding for undefined (which is the same as the encoding for null)
|
||
|
*/
|
||
|
encodeUndefined: function() {
|
||
|
return this.encodeNull();
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Writes the undefined value to the string
|
||
|
*/
|
||
|
writeUndefined: function() {
|
||
|
this.write(this.encodeUndefined());
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Returns the encoding for null
|
||
|
*/
|
||
|
encodeNull: function() {
|
||
|
return "<null />";
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Writes the null value to the string
|
||
|
*/
|
||
|
writeNull: function() {
|
||
|
this.write(this.encodeNull());
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Returns an encoded boolean
|
||
|
* @param {Boolean} val a boolean value
|
||
|
*/
|
||
|
encodeBoolean: function(val) {
|
||
|
var str;
|
||
|
if (val) {
|
||
|
str = "<true />";
|
||
|
} else {
|
||
|
str = "<false />";
|
||
|
}
|
||
|
return str;
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Writes a boolean value to the string
|
||
|
* @param {Boolean} val a boolean value
|
||
|
*/
|
||
|
writeBoolean: function(val) {
|
||
|
this.write(this.encodeBoolean(val));
|
||
|
},
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Returns an encoded string
|
||
|
* @param {String} str the string to encode
|
||
|
*/
|
||
|
encodeString: function(str) {
|
||
|
var ret;
|
||
|
if (str === "") {
|
||
|
ret = "<string />";
|
||
|
} else {
|
||
|
ret ="<string>"+str+"</string>";
|
||
|
}
|
||
|
return ret;
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Writes a string tag with the string content.
|
||
|
* @param {String} str the string to encode
|
||
|
*/
|
||
|
writeString: function(str) {
|
||
|
this.write(this.encodeString(str));
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Returns an encoded int
|
||
|
* @param {Number} num the integer to encode
|
||
|
*/
|
||
|
encodeInt: function(num) {
|
||
|
return "<int>" + num.toString() + "</int>";
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Writes a int tag with the content.
|
||
|
* @param {Number} num the integer to encode
|
||
|
*/
|
||
|
writeInt: function(num) {
|
||
|
this.write(this.encodeInt(num));
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Returns an encoded double
|
||
|
* @param {Number} num the double to encode
|
||
|
*/
|
||
|
encodeDouble: function(num) {
|
||
|
return "<double>" + num.toString() + "</double>";
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Writes a double tag with the content.
|
||
|
* @param {Number} num the double to encode
|
||
|
*/
|
||
|
writeDouble: function(num) {
|
||
|
this.write(this.encodeDouble(num));
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Returns an encoded number. Decides wheter to use int or double encoding.
|
||
|
* @param {Number} num the number to encode
|
||
|
*/
|
||
|
encodeNumber: function(num) {
|
||
|
var maxInt = 0x1fffffff,
|
||
|
minSignedInt = -0xfffffff;
|
||
|
//<debug>
|
||
|
if (typeof(num) !== "number" && !(num instanceof Number)) {
|
||
|
Ext.log.warn("Encoder: writeNumber argument is not numeric. Can't coerce.");
|
||
|
}
|
||
|
// </debug>
|
||
|
|
||
|
// switch to the primitive value for handling:
|
||
|
if (num instanceof Number) {
|
||
|
num = num.valueOf();
|
||
|
}
|
||
|
// Determine if this is an integer or a float.
|
||
|
if (num % 1 === 0 && num >= minSignedInt && num <= maxInt) {
|
||
|
// The number has no decimal point and is within bounds. Let's encode it.
|
||
|
return this.encodeInt(num);
|
||
|
} else {
|
||
|
return this.encodeDouble(num);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Writes a number, deciding if to use int or double as the tag
|
||
|
* @param {Number} num the number to encode
|
||
|
*/
|
||
|
writeNumber: function(num) {
|
||
|
this.write(this.encodeNumber(num));
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Encode a date
|
||
|
* @param {Date} date the date to encode
|
||
|
*/
|
||
|
encodeDate: function(date) {
|
||
|
return "<date>" + (new Number(date)).toString() + "</date>";
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Write a date to the string
|
||
|
* @param {Date} date the date to encode
|
||
|
*/
|
||
|
writeDate: function(date) {
|
||
|
this.write(this.encodeDate(date));
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* @private
|
||
|
* Encodes one ECMA array element
|
||
|
* @param {String} key the name of the element
|
||
|
* @param {Object} value the value of the element
|
||
|
* @return {String} the encoded key-value pair
|
||
|
*/
|
||
|
encodeEcmaElement: function(key, value) {
|
||
|
var str = '<item name="' + key.toString() + '">' + this.encodeObject(value) + '</item>';
|
||
|
return str;
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Encodes an array, marking it as an ECMA array if it has associative (non-ordinal) indices
|
||
|
* @param {Array} array the array to encode
|
||
|
*/
|
||
|
encodeArray: function(array) {
|
||
|
var ordinals=[],
|
||
|
firstNonOrdinal,
|
||
|
ecmaElements=[],
|
||
|
length = array.length, // length is of ordinal section only
|
||
|
i, str;
|
||
|
for (i in array) {
|
||
|
if (Ext.isNumeric(i) && (i % 1 == 0)) {
|
||
|
//this is an integer. Add to ordinals array
|
||
|
ordinals[i] = this.encodeObject(array[i]);
|
||
|
} else {
|
||
|
ecmaElements.push(this.encodeEcmaElement(i, array[i]));
|
||
|
}
|
||
|
}
|
||
|
firstNonOrdinal=ordinals.length;
|
||
|
// now, check if we have consecutive numbers in the ordinals array
|
||
|
for (i = 0; i < ordinals.length; i++) {
|
||
|
if (ordinals[i] === undefined) {
|
||
|
// we have a gap in the array. Mark it - the rest of the items become ECMA elements
|
||
|
firstNonOrdinal = i;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if (firstNonOrdinal < ordinals.length) {
|
||
|
// transfer some of the elements to the ecma array
|
||
|
for (i = firstNonOrdinals; i < ordinals.length; i++) {
|
||
|
if (ordinals[i] !== undefined) {
|
||
|
ecmaElements.push(this.encodeEcmaElement(i, ordinals[i]));
|
||
|
}
|
||
|
}
|
||
|
ordinals = ordinals.slice(0, firstNonOrdinal);
|
||
|
}
|
||
|
|
||
|
// finally start constructing the string
|
||
|
str = '<array length="' + ordinals.length + '"';
|
||
|
if (ecmaElements.length > 0) {
|
||
|
str += ' ecma="true"';
|
||
|
}
|
||
|
str += '>';
|
||
|
|
||
|
// first add the oridnals in consecutive order:
|
||
|
for (i = 0; i < ordinals.length; i++) { // iterate by counting since we need to guarantee the order
|
||
|
str += ordinals[i];
|
||
|
}
|
||
|
// Now add ECMA items
|
||
|
for (i in ecmaElements) {
|
||
|
str += ecmaElements[i];
|
||
|
}
|
||
|
// And close the array:
|
||
|
str += '</array>';
|
||
|
return str;
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Writes an array to the string, marking it as an ECMA array if it has associative (non-ordinal) indices
|
||
|
* @param {Array} array the array to encode
|
||
|
*/
|
||
|
writeArray: function(array) {
|
||
|
this.write(this.encodeArray(array));
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Encodes an xml document into a CDATA section
|
||
|
* @param {XMLElement/HTMLElement} xml an XML document or element (Document type in some browsers)
|
||
|
*/
|
||
|
encodeXml: function(xml) {
|
||
|
var str = this.convertXmlToString(xml);
|
||
|
return "<xml><![CDATA[" + str + "]]></xml>";
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Write an XML document to the string
|
||
|
* @param {XMLElement/HTMLElement} xml an XML document or element (Document type in some browsers)
|
||
|
*/
|
||
|
writeXml: function(xml) {
|
||
|
this.write(this.encodeXml(xml));
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Encodes a generic object into AMFX format. If a <tt>$flexType</tt> member is defined, list that as the object type.
|
||
|
* @param {Object} obj the object to encode
|
||
|
* @return {String} the encoded text
|
||
|
*/
|
||
|
encodeGenericObject: function(obj) {
|
||
|
var traits = [],
|
||
|
values = [],
|
||
|
flexType = null,
|
||
|
i, str;
|
||
|
for (i in obj) {
|
||
|
if (i == "$flexType") {
|
||
|
flexType = obj[i];
|
||
|
} else {
|
||
|
traits.push(this.encodeString(new String(i)));
|
||
|
values.push(this.encodeObject(obj[i]));
|
||
|
}
|
||
|
}
|
||
|
if (flexType) {
|
||
|
str = '<object type="' +flexType + '">';
|
||
|
} else {
|
||
|
str="<object>";
|
||
|
}
|
||
|
if (traits.length > 0) {
|
||
|
str += "<traits>";
|
||
|
str += traits.join("");
|
||
|
str += "</traits>";
|
||
|
} else {
|
||
|
str += "<traits />";
|
||
|
}
|
||
|
str += values.join("");
|
||
|
str += "</object>";
|
||
|
return str;
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Writes a generic object to the string. If a <tt>$flexType</tt> member is defined, list that as the object type.
|
||
|
* @param {Object} obj the object to encode
|
||
|
*/
|
||
|
writeGenericObject: function(obj) {
|
||
|
this.write(this.encodeGenericObject(obj));
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Encodes a byte arrat in AMFX format
|
||
|
* @param {Array} array the byte array to encode
|
||
|
*/
|
||
|
encodeByteArray: function(array) {
|
||
|
var str, i, h;
|
||
|
if (array.length > 0) {
|
||
|
str = "<bytearray>";
|
||
|
for (i = 0; i < array.length; i++) {
|
||
|
//<debug>
|
||
|
if (!Ext.isNumber(array[i])) {
|
||
|
Ext.Error.raise("Byte array contains a non-number: " + array[i] + " in index: " + i);
|
||
|
}
|
||
|
if (array[i] < 0 || array[i] > 255) {
|
||
|
Ext.Error.raise("Byte array value out of bounds: " + array[i]);
|
||
|
}
|
||
|
//</debug>
|
||
|
h = array[i].toString(16).toUpperCase();
|
||
|
if (array[i] < 0x10) {
|
||
|
h = "0" + h;
|
||
|
}
|
||
|
str += h;
|
||
|
}
|
||
|
str += "</bytearray>";
|
||
|
} else {
|
||
|
str = "<bytearray />";
|
||
|
}
|
||
|
return str;
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Writes an AMFX byte array to the string. This is for convenience only and is not called automatically by writeObject.
|
||
|
* @param {Array} array the byte array to encode
|
||
|
*/
|
||
|
writeByteArray: function(array) {
|
||
|
this.write(this.encodeByteArray(array));
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* encode the appropriate data item. Supported types:
|
||
|
* - undefined
|
||
|
* - null
|
||
|
* - boolean
|
||
|
* - integer
|
||
|
* - double
|
||
|
* - UTF-8 string
|
||
|
* - XML Document (identified by being instaneof Document. Can be generated with: new DOMParser()).parseFromString(xml, "text/xml");
|
||
|
* - Date
|
||
|
* - Array
|
||
|
* - Generic object
|
||
|
* @param {Object} item A primitive or object to write to the stream
|
||
|
* @return {String} the encoded object in AMFX format
|
||
|
*/
|
||
|
encodeObject: function(item) {
|
||
|
var t = typeof(item);
|
||
|
//Ext.log("Writing " + item + " of type " + t);
|
||
|
if (t === "undefined") {
|
||
|
return this.encodeUndefined();
|
||
|
} else if (item === null) { // can't check type since typeof(null) returns "object"
|
||
|
return this.encodeNull();
|
||
|
} else if (Ext.isBoolean(item)) {
|
||
|
return this.encodeBoolean(item);
|
||
|
} else if (Ext.isString(item)) {
|
||
|
return this.encodeString(item);
|
||
|
} else if (t === "number" || item instanceof Number) { // Can't use Ext.isNumeric since it accepts strings as well
|
||
|
return this.encodeNumber(item);
|
||
|
} else if (t === "object") {
|
||
|
// Figure out which object this is
|
||
|
if (item instanceof Date) {
|
||
|
return this.encodeDate(item);
|
||
|
} else if (Ext.isArray(item)) {
|
||
|
return this.encodeArray(item);
|
||
|
} else if (this.isXmlDocument(item)) {
|
||
|
return this.encodeXml(item);
|
||
|
} else {
|
||
|
// Treat this as a generic object with name/value pairs of data.
|
||
|
return this.encodeGenericObject(item);
|
||
|
}
|
||
|
} else {
|
||
|
//<debug>
|
||
|
Ext.log.warn("AMFX Encoder: Unknown item type " + t + " can't be written to stream: " + item);
|
||
|
//</debug>
|
||
|
}
|
||
|
return null; // if we reached here, return null
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Writes the appropriate data item to the string. Supported types:
|
||
|
* - undefined
|
||
|
* - null
|
||
|
* - boolean
|
||
|
* - integer
|
||
|
* - double
|
||
|
* - UTF-8 string
|
||
|
* - XML Document (identified by being instaneof Document. Can be generated with: new DOMParser()).parseFromString(xml, "text/xml");
|
||
|
* - Date
|
||
|
* - Array
|
||
|
* - Generic object
|
||
|
* @param {Object} item A primitive or object to write to the stream
|
||
|
*/
|
||
|
writeObject: function(item) {
|
||
|
this.write(this.encodeObject(item));
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Encodes an AMFX remoting message with the AMFX envelope.
|
||
|
* @param {Ext.data.amf.RemotingMessage} message the message to pass on to serialize.
|
||
|
*/
|
||
|
encodeAmfxRemotingPacket: function(message) {
|
||
|
var msg, str;
|
||
|
str = '<amfx ver="3" xmlns="http://www.macromedia.com/2005/amfx"><body>';
|
||
|
str += message.encodeMessage();
|
||
|
str += '</body></amfx>';
|
||
|
return str;
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Writes an AMFX remoting message with the AMFX envelope to the string.
|
||
|
* @param {Ext.data.amf.RemotingMessage} message the message to pass on to serialize.
|
||
|
*/
|
||
|
writeAmfxRemotingPacket: function(params) {
|
||
|
this.write(this.encodeAmfxRemotingPacket(params));
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Converts an XML Document object to a string.
|
||
|
* @param {Object} xml XML document to convert (typically Document object)
|
||
|
* @return {String} A string representing the document
|
||
|
* @private
|
||
|
*/
|
||
|
convertXmlToString: function(xml) {
|
||
|
var str;
|
||
|
if (window.XMLSerializer) {
|
||
|
// this is not IE, so:
|
||
|
str = new window.XMLSerializer().serializeToString(xml);
|
||
|
} else {
|
||
|
//no XMLSerializer, might be an old version of IE
|
||
|
str = xml.xml;
|
||
|
}
|
||
|
return str;
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Tries to determine if an object is an XML document
|
||
|
* @param {Object} item to identify
|
||
|
* @return {Boolean} true if it's an XML document, false otherwise
|
||
|
*/
|
||
|
isXmlDocument: function(item) {
|
||
|
// We can't test if Document is defined since IE just throws an exception. Instead rely on the DOMParser object
|
||
|
if (window.DOMParser) {
|
||
|
if (Ext.isDefined(item.doctype)) {
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
// Otherwise, check if it has an XML field
|
||
|
if (Ext.isString(item.xml)) {
|
||
|
// and we can get the xml
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Appends a string to the body of the message
|
||
|
* @param {String} str the string to append
|
||
|
* @private
|
||
|
*/
|
||
|
write: function(str) {
|
||
|
this.setBody(this.getBody() + str);
|
||
|
}
|
||
|
});
|
||
|
//</feature>
|