]> git.proxmox.com Git - extjs.git/blob - extjs/packages/amf/src/data/amf/XmlEncoder.js
add extjs 6.0.1 sources
[extjs.git] / extjs / packages / amf / src / data / amf / XmlEncoder.js
1 // @tag enterprise
2 /**
3 * @class Ext.data.amf.XmlEncoder
4 * This class serializes data in the Action Message Format XML (AMFX) format.
5 * It can write simple and complex objects, to be used in conjunction with an
6 * AMFX-compliant server.
7 * To create an encoded XMl, first construct an Encoder:
8 *
9 * var encoder = Ext.create('Ext.data.amf.XmlEncoder');
10 *
11 * Then use the writer methods to out data to the :
12 *
13 * encoder.writeObject(1);
14 * encoder.writeObject({a: "b"});
15 *
16 * And access the data through the #bytes property:
17 * encoder.body;
18 *
19 * You can also reset the class to start a new body:
20 *
21 * encoder.clear();
22 *
23 * Current limitations:
24 * AMF3 format (format:3)
25 * - Each object is written out explicitly, not using the reference tables
26 * supported by the AMFX format. This means the function does NOT support
27 * circular reference objects.
28 * - Objects that aren't Arrays, Dates, Strings, Document (XML) or primitive
29 * values will be written out as anonymous objects with dynamic data.
30 * - If the object has a $flexType field, that field will be used in signifying
31 * the object-type as an attribute, instead of being passed as data.
32 * - There's no JavaScript equivalent to the ByteArray type in ActionScript,
33 * hence data will never be searialized as ByteArrays by the writeObject
34 * function. A writeByteArray method is provided for writing out ByteArray objects.
35 *
36 * For more information on working with AMF data please refer to the
37 * [AMF Guide](#/guide/amf).
38 */
39
40 Ext.define('Ext.data.amf.XmlEncoder', {
41
42 alias: 'data.amf.xmlencoder',
43
44 /**
45 * @property {String} body - The output string
46 */
47 body: "",
48
49 statics: {
50 /**
51 * Utility function to generate a flex-friendly UID
52 * @param {Number} id used in the first 8 chars of the id. If not provided, a random number will be used.
53 * @return {String} a string-encoded opaque UID
54 */
55 generateFlexUID: function(id) {
56 var uid = "",
57 i, j, t;
58 if (id === undefined) {
59 id = Ext.Number.randomInt(0, 0xffffffff);
60 }
61 // The format of a UID is XXXXXXXX-XXXX-XXXX-XXXX-YYYYYYYYXXXX
62 // where each X is a random hex digit and each Y is a hex digit from the least significant part of a time stamp.
63 t = (id + 0x100000000).toString(16).toUpperCase(); // padded
64 uid = t.substr(t.length - 8, 8); // last 8 chars
65
66 for (j = 0; j < 3; j++) {
67 // 3 -XXXX segments
68 uid += "-";
69 for (i = 0; i < 4; i++) {
70 uid += Ext.Number.randomInt(0, 15).toString(16).toUpperCase();
71 }
72 }
73 uid += "-";
74 // add timestamp
75 t = new Number(new Date()).valueOf().toString(16).toUpperCase(); // get the String representation of milliseconds in hex format
76 j = 0;
77 if (t.length < 8) { // pad with "0" if needed
78 for (i = 0; i < t.length - 8; i++) {
79 j++;
80 uid += "0";
81 }
82 }
83 // actual timestamp:
84 uid += t.substr(-(8-j)); // last few chars
85 // and last 4 random digits
86 for (i = 0; i < 4; i++) {
87 uid += Ext.Number.randomInt(0, 15).toString(16).toUpperCase();
88 }
89 return uid;
90 }
91 },
92
93 /**
94 * Creates new encoder.
95 * @param {Object} config Configuration options
96 */
97 constructor: function(config) {
98 this.initConfig(config);
99 this.clear();
100 },
101
102 /**
103 * Clears the accumulated data, starting with an empty string
104 */
105 clear: function() {
106 this.body = "";
107 },
108
109 /**
110 * Returns the encoding for undefined (which is the same as the encoding for null)
111 */
112 encodeUndefined: function() {
113 return this.encodeNull();
114 },
115
116 /**
117 * Writes the undefined value to the string
118 */
119 writeUndefined: function() {
120 this.write(this.encodeUndefined());
121 },
122
123 /**
124 * Returns the encoding for null
125 */
126 encodeNull: function() {
127 return "<null />";
128 },
129
130 /**
131 * Writes the null value to the string
132 */
133 writeNull: function() {
134 this.write(this.encodeNull());
135 },
136
137 /**
138 * Returns an encoded boolean
139 * @param {Boolean} val a boolean value
140 */
141 encodeBoolean: function(val) {
142 var str;
143 if (val) {
144 str = "<true />";
145 } else {
146 str = "<false />";
147 }
148 return str;
149 },
150
151 /**
152 * Writes a boolean value to the string
153 * @param {Boolean} val a boolean value
154 */
155 writeBoolean: function(val) {
156 this.write(this.encodeBoolean(val));
157 },
158
159
160 /**
161 * Returns an encoded string
162 * @param {String} str the string to encode
163 */
164 encodeString: function(str) {
165 var ret;
166 if (str === "") {
167 ret = "<string />";
168 } else {
169 ret ="<string>"+str+"</string>";
170 }
171 return ret;
172 },
173
174 /**
175 * Writes a string tag with the string content.
176 * @param {String} str the string to encode
177 */
178 writeString: function(str) {
179 this.write(this.encodeString(str));
180 },
181
182 /**
183 * Returns an encoded int
184 * @param {Number} num the integer to encode
185 */
186 encodeInt: function(num) {
187 return "<int>" + num.toString() + "</int>";
188 },
189
190 /**
191 * Writes a int tag with the content.
192 * @param {Number} num the integer to encode
193 */
194 writeInt: function(num) {
195 this.write(this.encodeInt(num));
196 },
197
198 /**
199 * Returns an encoded double
200 * @param {Number} num the double to encode
201 */
202 encodeDouble: function(num) {
203 return "<double>" + num.toString() + "</double>";
204 },
205
206 /**
207 * Writes a double tag with the content.
208 * @param {Number} num the double to encode
209 */
210 writeDouble: function(num) {
211 this.write(this.encodeDouble(num));
212 },
213
214 /**
215 * Returns an encoded number. Decides wheter to use int or double encoding.
216 * @param {Number} num the number to encode
217 */
218 encodeNumber: function(num) {
219 var maxInt = 0x1fffffff,
220 minSignedInt = -0xfffffff;
221 //<debug>
222 if (typeof(num) !== "number" && !(num instanceof Number)) {
223 Ext.log.warn("Encoder: writeNumber argument is not numeric. Can't coerce.");
224 }
225 // </debug>
226
227 // switch to the primitive value for handling:
228 if (num instanceof Number) {
229 num = num.valueOf();
230 }
231 // Determine if this is an integer or a float.
232 if (num % 1 === 0 && num >= minSignedInt && num <= maxInt) {
233 // The number has no decimal point and is within bounds. Let's encode it.
234 return this.encodeInt(num);
235 } else {
236 return this.encodeDouble(num);
237 }
238 },
239
240 /**
241 * Writes a number, deciding if to use int or double as the tag
242 * @param {Number} num the number to encode
243 */
244 writeNumber: function(num) {
245 this.write(this.encodeNumber(num));
246 },
247
248 /**
249 * Encode a date
250 * @param {Date} date the date to encode
251 */
252 encodeDate: function(date) {
253 return "<date>" + (new Number(date)).toString() + "</date>";
254 },
255
256 /**
257 * Write a date to the string
258 * @param {Date} date the date to encode
259 */
260 writeDate: function(date) {
261 this.write(this.encodeDate(date));
262 },
263
264 /**
265 * @private
266 * Encodes one ECMA array element
267 * @param {String} key the name of the element
268 * @param {Object} value the value of the element
269 * @return {String} the encoded key-value pair
270 */
271 encodeEcmaElement: function(key, value) {
272 var str = '<item name="' + key.toString() + '">' + this.encodeObject(value) + '</item>';
273 return str;
274 },
275
276 /**
277 * Encodes an array, marking it as an ECMA array if it has associative (non-ordinal) indices
278 * @param {Array} array the array to encode
279 */
280 encodeArray: function(array) {
281 var ordinals=[],
282 firstNonOrdinal,
283 ecmaElements=[],
284 length = array.length, // length is of ordinal section only
285 i, str;
286 for (i in array) {
287 if (Ext.isNumeric(i) && (i % 1 == 0)) {
288 //this is an integer. Add to ordinals array
289 ordinals[i] = this.encodeObject(array[i]);
290 } else {
291 ecmaElements.push(this.encodeEcmaElement(i, array[i]));
292 }
293 }
294 firstNonOrdinal=ordinals.length;
295 // now, check if we have consecutive numbers in the ordinals array
296 for (i = 0; i < ordinals.length; i++) {
297 if (ordinals[i] === undefined) {
298 // we have a gap in the array. Mark it - the rest of the items become ECMA elements
299 firstNonOrdinal = i;
300 break;
301 }
302 }
303 if (firstNonOrdinal < ordinals.length) {
304 // transfer some of the elements to the ecma array
305 for (i = firstNonOrdinals; i < ordinals.length; i++) {
306 if (ordinals[i] !== undefined) {
307 ecmaElements.push(this.encodeEcmaElement(i, ordinals[i]));
308 }
309 }
310 ordinals = ordinals.slice(0, firstNonOrdinal);
311 }
312
313 // finally start constructing the string
314 str = '<array length="' + ordinals.length + '"';
315 if (ecmaElements.length > 0) {
316 str += ' ecma="true"';
317 }
318 str += '>';
319
320 // first add the oridnals in consecutive order:
321 for (i = 0; i < ordinals.length; i++) { // iterate by counting since we need to guarantee the order
322 str += ordinals[i];
323 }
324 // Now add ECMA items
325 for (i in ecmaElements) {
326 str += ecmaElements[i];
327 }
328 // And close the array:
329 str += '</array>';
330 return str;
331 },
332
333 /**
334 * Writes an array to the string, marking it as an ECMA array if it has associative (non-ordinal) indices
335 * @param {Array} array the array to encode
336 */
337 writeArray: function(array) {
338 this.write(this.encodeArray(array));
339 },
340
341 /**
342 * Encodes an xml document into a CDATA section
343 * @param {XMLElement/HTMLElement} xml an XML document or element (Document type in some browsers)
344 */
345 encodeXml: function(xml) {
346 var str = this.convertXmlToString(xml);
347 return "<xml><![CDATA[" + str + "]]></xml>";
348 },
349
350 /**
351 * Write an XML document to the string
352 * @param {XMLElement/HTMLElement} xml an XML document or element (Document type in some browsers)
353 */
354 writeXml: function(xml) {
355 this.write(this.encodeXml(xml));
356 },
357
358 /**
359 * Encodes a generic object into AMFX format. If a <tt>$flexType</tt> member is defined, list that as the object type.
360 * @param {Object} obj the object to encode
361 * @return {String} the encoded text
362 */
363 encodeGenericObject: function(obj) {
364 var traits = [],
365 values = [],
366 flexType = null,
367 i, str;
368 for (i in obj) {
369 if (i == "$flexType") {
370 flexType = obj[i];
371 } else {
372 traits.push(this.encodeString(new String(i)));
373 values.push(this.encodeObject(obj[i]));
374 }
375 }
376 if (flexType) {
377 str = '<object type="' +flexType + '">';
378 } else {
379 str="<object>";
380 }
381 if (traits.length > 0) {
382 str += "<traits>";
383 str += traits.join("");
384 str += "</traits>";
385 } else {
386 str += "<traits />";
387 }
388 str += values.join("");
389 str += "</object>";
390 return str;
391 },
392
393 /**
394 * Writes a generic object to the string. If a <tt>$flexType</tt> member is defined, list that as the object type.
395 * @param {Object} obj the object to encode
396 */
397 writeGenericObject: function(obj) {
398 this.write(this.encodeGenericObject(obj));
399 },
400
401 /**
402 * Encodes a byte arrat in AMFX format
403 * @param {Array} array the byte array to encode
404 */
405 encodeByteArray: function(array) {
406 var str, i, h;
407 if (array.length > 0) {
408 str = "<bytearray>";
409 for (i = 0; i < array.length; i++) {
410 //<debug>
411 if (!Ext.isNumber(array[i])) {
412 Ext.raise("Byte array contains a non-number: " + array[i] + " in index: " + i);
413 }
414 if (array[i] < 0 || array[i] > 255) {
415 Ext.raise("Byte array value out of bounds: " + array[i]);
416 }
417 //</debug>
418 h = array[i].toString(16).toUpperCase();
419 if (array[i] < 0x10) {
420 h = "0" + h;
421 }
422 str += h;
423 }
424 str += "</bytearray>";
425 } else {
426 str = "<bytearray />";
427 }
428 return str;
429 },
430
431 /**
432 * Writes an AMFX byte array to the string. This is for convenience only and is not called automatically by writeObject.
433 * @param {Array} array the byte array to encode
434 */
435 writeByteArray: function(array) {
436 this.write(this.encodeByteArray(array));
437 },
438
439 /**
440 * encode the appropriate data item. Supported types:
441 * - undefined
442 * - null
443 * - boolean
444 * - integer
445 * - double
446 * - UTF-8 string
447 * - XML Document (identified by being instaneof Document. Can be generated with: new DOMParser()).parseFromString(xml, "text/xml");
448 * - Date
449 * - Array
450 * - Generic object
451 * @param {Object} item A primitive or object to write to the stream
452 * @return {String} the encoded object in AMFX format
453 */
454 encodeObject: function(item) {
455 var t = typeof(item);
456 //Ext.log("Writing " + item + " of type " + t);
457 if (t === "undefined") {
458 return this.encodeUndefined();
459 } else if (item === null) { // can't check type since typeof(null) returns "object"
460 return this.encodeNull();
461 } else if (Ext.isBoolean(item)) {
462 return this.encodeBoolean(item);
463 } else if (Ext.isString(item)) {
464 return this.encodeString(item);
465 } else if (t === "number" || item instanceof Number) { // Can't use Ext.isNumeric since it accepts strings as well
466 return this.encodeNumber(item);
467 } else if (t === "object") {
468 // Figure out which object this is
469 if (item instanceof Date) {
470 return this.encodeDate(item);
471 } else if (Ext.isArray(item)) {
472 return this.encodeArray(item);
473 } else if (this.isXmlDocument(item)) {
474 return this.encodeXml(item);
475 } else {
476 // Treat this as a generic object with name/value pairs of data.
477 return this.encodeGenericObject(item);
478 }
479 } else {
480 //<debug>
481 Ext.log.warn("AMFX Encoder: Unknown item type " + t + " can't be written to stream: " + item);
482 //</debug>
483 }
484 return null; // if we reached here, return null
485 },
486
487 /**
488 * Writes the appropriate data item to the string. Supported types:
489 * - undefined
490 * - null
491 * - boolean
492 * - integer
493 * - double
494 * - UTF-8 string
495 * - XML Document (identified by being instaneof Document. Can be generated with: new DOMParser()).parseFromString(xml, "text/xml");
496 * - Date
497 * - Array
498 * - Generic object
499 * @param {Object} item A primitive or object to write to the stream
500 */
501 writeObject: function(item) {
502 this.write(this.encodeObject(item));
503 },
504
505 /**
506 * Encodes an AMFX remoting message with the AMFX envelope.
507 * @param {Ext.data.amf.RemotingMessage} message the message to pass on to serialize.
508 */
509 encodeAmfxRemotingPacket: function(message) {
510 var msg, str;
511 str = '<amfx ver="3" xmlns="http://www.macromedia.com/2005/amfx"><body>';
512 str += message.encodeMessage();
513 str += '</body></amfx>';
514 return str;
515 },
516
517 /**
518 * Writes an AMFX remoting message with the AMFX envelope to the string.
519 * @param {Ext.data.amf.RemotingMessage} message the message to pass on to serialize.
520 */
521 writeAmfxRemotingPacket: function(params) {
522 this.write(this.encodeAmfxRemotingPacket(params));
523 },
524
525 /**
526 * Converts an XML Document object to a string.
527 * @param {Object} xml XML document to convert (typically Document object)
528 * @return {String} A string representing the document
529 * @private
530 */
531 convertXmlToString: function(xml) {
532 var str;
533 if (window.XMLSerializer) {
534 // this is not IE, so:
535 str = new window.XMLSerializer().serializeToString(xml);
536 } else {
537 //no XMLSerializer, might be an old version of IE
538 str = xml.xml;
539 }
540 return str;
541 },
542
543 /**
544 * Tries to determine if an object is an XML document
545 * @param {Object} item to identify
546 * @return {Boolean} true if it's an XML document, false otherwise
547 */
548 isXmlDocument: function(item) {
549 // We can't test if Document is defined since IE just throws an exception. Instead rely on the DOMParser object
550 if (window.DOMParser) {
551 if (Ext.isDefined(item.doctype)) {
552 return true;
553 }
554 }
555 // Otherwise, check if it has an XML field
556 if (Ext.isString(item.xml)) {
557 // and we can get the xml
558 return true;
559 }
560 return false;
561 },
562
563 /**
564 * Appends a string to the body of the message
565 * @param {String} str the string to append
566 * @private
567 */
568 write: function(str) {
569 this.body += str;
570 }
571 });