// @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 "";
},
/**
* 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 = "";
} else {
str = "";
}
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 = "";
} else {
ret =""+str+"";
}
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 "" + num.toString() + "";
},
/**
* 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 "" + num.toString() + "";
},
/**
* 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;
//
if (typeof(num) !== "number" && !(num instanceof Number)) {
Ext.log.warn("Encoder: writeNumber argument is not numeric. Can't coerce.");
}
//
// 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 "" + (new Number(date)).toString() + "";
},
/**
* 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 = '' + this.encodeObject(value) + '';
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 = ' 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 += '';
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 "";
},
/**
* 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 $flexType 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 = '