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

572 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;
}
});