    // @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.raise("Byte array contains a non-number: " + array[i]  + " in index: " + i);                }                if (array[i] < 0 || array[i] > 255) {                    Ext.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;    }});