macoslinuxwindowsinboxwhatsappicloudtweetdeckhipchattelegramhangoutsslackgmailskypefacebook-workplaceoutlookemailmicrosoft-teamsdiscordmessengercustom-services
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.
571 lines
18 KiB
571 lines
18 KiB
// @tag enterprise |
|
/** |
|
* @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.body; |
|
* |
|
* 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', |
|
|
|
/** |
|
* @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 = Ext.Number.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 += Ext.Number.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 += Ext.Number.randomInt(0, 15).toString(16).toUpperCase(); |
|
} |
|
return uid; |
|
} |
|
}, |
|
|
|
/** |
|
* 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.body = ""; |
|
}, |
|
|
|
/** |
|
* 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.body += str; |
|
} |
|
});
|
|
|