src/ztfy/myams/resources/js/ext/jquery-xmlrpc.js
changeset 127 cf00ea83daa7
equal deleted inserted replaced
126:3868baf80db1 127:cf00ea83daa7
       
     1 /*jshint browser:true */
       
     2 /*global jQuery */
       
     3 (function($) {
       
     4 	"use strict";
       
     5 
       
     6 	var XmlRpcFault = function() {
       
     7 		Error.apply(this, arguments);
       
     8 	};
       
     9 	XmlRpcFault.prototype = new Error();
       
    10 	XmlRpcFault.prototype.type = 'XML-RPC fault';
       
    11 
       
    12 	var xmlrpc = $.xmlrpc = function(url, settings) {
       
    13 
       
    14 		if (arguments.length === 2) {
       
    15 			settings.url = url;
       
    16 		} else {
       
    17 			settings = url;
       
    18 			url = settings.url;
       
    19 		}
       
    20 
       
    21 		settings.dataType = 'xml json';
       
    22 		settings.type = 'POST';
       
    23 		settings.contentType = 'text/xml';
       
    24 		settings.converters = {'xml json': xmlrpc.parseDocument};
       
    25 
       
    26 		var xmlDoc = xmlrpc.document(settings.methodName, settings.params || []);
       
    27 
       
    28 		if ("XMLSerializer" in window) {
       
    29 			settings.data = new window.XMLSerializer().serializeToString(xmlDoc);
       
    30 		} else {
       
    31 			// IE does not have XMLSerializer
       
    32 			settings.data = xmlDoc.xml;
       
    33 		}
       
    34 
       
    35 		return $.ajax(settings);
       
    36 	};
       
    37 
       
    38 	/**
       
    39 	* Make an XML document node.
       
    40 	*/
       
    41 	xmlrpc.createXMLDocument = function () {
       
    42 
       
    43 		if (document.implementation && "createDocument" in document.implementation) {
       
    44 			// Most browsers support createDocument
       
    45 			return document.implementation.createDocument(null, null, null);
       
    46 
       
    47 		} else {
       
    48 			// IE uses ActiveXObject instead of the above.
       
    49 			var i, length, activeX = [
       
    50 				"MSXML6.DomDocument", "MSXML3.DomDocument",
       
    51 				"MSXML2.DomDocument", "MSXML.DomDocument", "Microsoft.XmlDom"
       
    52 			];
       
    53 			for (i = 0, length = activeX.length; i < length; i++) {
       
    54 				try {
       
    55 					return new ActiveXObject(activeX[i]);
       
    56 				} catch(_) {}
       
    57 			}
       
    58 		}
       
    59 	};
       
    60 
       
    61 	/**
       
    62 	* Make an XML-RPC document from a method name and a set of parameters
       
    63 	*/
       
    64 	xmlrpc.document = function(name, params) {
       
    65 		var doc = xmlrpc.createXMLDocument();
       
    66 
       
    67 
       
    68 		var $xml = function(name) {
       
    69 			return $(doc.createElement(name));
       
    70 		};
       
    71 
       
    72 		var $methodName = $xml('methodName').text(name);
       
    73 		var $params = $xml('params').append($.map(params, function(param) {
       
    74 			var $value = $xml('value').append(xmlrpc.toXmlRpc(param, $xml));
       
    75 			return $xml('param').append($value);
       
    76 		}));
       
    77 		var $methodCall = $xml('methodCall').append($methodName, $params);
       
    78 		doc.appendChild($methodCall.get(0));
       
    79 		return doc;
       
    80 	};
       
    81 
       
    82 	var _isInt = function(x) {
       
    83 		return (x === parseInt(x, 10)) && !isNaN(x);
       
    84 	};
       
    85 
       
    86 	/**
       
    87 	* Take a JavaScript value, and return an XML node representing the value
       
    88 	* in XML-RPC style. If the value is one of the `XmlRpcType`s, that type is
       
    89 	* used. Otherwise, a best guess is made as to its type. The best guess is
       
    90 	* good enough in the vast majority of cases.
       
    91 	*/
       
    92 	xmlrpc.toXmlRpc = function(item, $xml) {
       
    93 
       
    94 		if (item instanceof XmlRpcType) {
       
    95 			return item.toXmlRpc($xml);
       
    96 		}
       
    97 
       
    98 		var types = $.xmlrpc.types;
       
    99 		var type = $.type(item);
       
   100 
       
   101 		switch (type) {
       
   102 			case "undefined":
       
   103 			case "null":
       
   104 				return types.nil.encode(item, $xml);
       
   105 
       
   106 			case "date":
       
   107 				return types['datetime.iso8601'].encode(item, $xml);
       
   108 
       
   109 			case "object":
       
   110 				if (item instanceof ArrayBuffer) {
       
   111 					return types.base64.encode(item, $xml);
       
   112 				} else {
       
   113 					return types.struct.encode(item, $xml);
       
   114 				}
       
   115 				break;
       
   116 
       
   117 
       
   118 			case "number":
       
   119 				// Ints and Floats encode differently
       
   120 				if (_isInt(item)) {
       
   121 					return types['int'].encode(item, $xml);
       
   122 				} else {
       
   123 					return types['double'].encode(item, $xml);
       
   124 				}
       
   125 				break;
       
   126 
       
   127 			case "array":
       
   128 			case "boolean":
       
   129 			case "string":
       
   130 				return types[type].encode(item, $xml);
       
   131 
       
   132 			default:
       
   133 				throw new Error("Unknown type", item);
       
   134 		}
       
   135 	};
       
   136 
       
   137 	/**
       
   138 	* Take an XML-RPC document and decode it to an equivalent JavaScript
       
   139 	* representation.
       
   140 	*
       
   141 	* If the XML-RPC document represents a fault, then an equivalent
       
   142 	* XmlRpcFault will be thrown instead
       
   143 	*/
       
   144 	xmlrpc.parseDocument = function(doc) {
       
   145 		var $doc = $(doc);
       
   146 		var $response = $doc.children('methodresponse');
       
   147 
       
   148 		var $fault = $response.find('> fault');
       
   149 		if ($fault.length === 0) {
       
   150 			var $params = $response.find('> params > param > value > *');
       
   151 			var json = $params.toArray().map(xmlrpc.parseNode);
       
   152 			return json;
       
   153 		} else {
       
   154 			var fault = xmlrpc.parseNode($fault.find('> value > *').get(0));
       
   155 			var err = new XmlRpcFault(fault.faultString);
       
   156 			err.msg = err.message = fault.faultString;
       
   157 			err.type = err.code = fault.faultCode;
       
   158 			throw err;
       
   159 		}
       
   160 	};
       
   161 
       
   162 	/*
       
   163 	* Take an XML-RPC node, and return the JavaScript equivalent
       
   164 	*/
       
   165 	xmlrpc.parseNode = function(node) {
       
   166 
       
   167 		// Some XML-RPC services return empty <value /> elements. This is not
       
   168 		// legal XML-RPC, but we may as well handle it.
       
   169 		if (node === undefined) {
       
   170 			return null;
       
   171 		}
       
   172 		var nodename = node.nodeName.toLowerCase();
       
   173 		if (nodename in xmlrpc.types) {
       
   174 			return xmlrpc.types[nodename].decode(node);
       
   175 		} else {
       
   176 			throw new Error('Unknown type ' + nodename);
       
   177 		}
       
   178 	};
       
   179 
       
   180 	/*
       
   181 	* Take a <value> node, and return the JavaScript equivalent.
       
   182 	*/
       
   183 	xmlrpc.parseValue = function(value) {
       
   184 		var child = $(value).children()[0];
       
   185 		if (child) {
       
   186 			// Child nodes should be decoded.
       
   187 			return xmlrpc.parseNode(child);
       
   188 		} else {
       
   189 			// If no child nodes, the value is a plain text node.
       
   190 			return $(value).text();
       
   191 		}
       
   192 	};
       
   193 
       
   194 	var XmlRpcType = function() { };
       
   195 
       
   196 	$.xmlrpc.types = {};
       
   197 
       
   198 	/**
       
   199 	* Make a XML-RPC type. We use these to encode and decode values. You can
       
   200 	* also force a values type using this. See `$.xmlrpc.force()`
       
   201 	*/
       
   202 	xmlrpc.makeType = function(tagName, simple, encode, decode) {
       
   203 		var Type;
       
   204 
       
   205 		Type = function(value) {
       
   206 			this.value = value;
       
   207 		};
       
   208 		Type.prototype = new XmlRpcType();
       
   209 		Type.prototype.tagName = tagName;
       
   210 
       
   211 		if (simple) {
       
   212 			var simpleEncode = encode, simpleDecode = decode;
       
   213 			encode = function(value, $xml) {
       
   214 				var text = simpleEncode(value);
       
   215 				return $xml(Type.tagName).text(text);
       
   216 			};
       
   217 			decode = function(node) {
       
   218 				return simpleDecode($(node).text(), node);
       
   219 			};
       
   220 		}
       
   221 		Type.prototype.toXmlRpc = function($xml) {
       
   222 			return Type.encode(this.value, $xml);
       
   223 		};
       
   224 
       
   225 		Type.tagName = tagName;
       
   226 		Type.encode = encode;
       
   227 		Type.decode = decode;
       
   228 
       
   229 		xmlrpc.types[tagName.toLowerCase()] = Type;
       
   230 	};
       
   231 
       
   232 
       
   233 	// Number types
       
   234 	var _fromInt = function(value) { return '' + Math.floor(value); };
       
   235 	var _toInt = function(text, _) { return parseInt(text, 10); };
       
   236 
       
   237 	xmlrpc.makeType('int', true, _fromInt, _toInt);
       
   238 	xmlrpc.makeType('i4', true, _fromInt, _toInt);
       
   239 	xmlrpc.makeType('i8', true, _fromInt, _toInt);
       
   240 	xmlrpc.makeType('i16', true, _fromInt, _toInt);
       
   241 	xmlrpc.makeType('i32', true, _fromInt, _toInt);
       
   242 
       
   243 	xmlrpc.makeType('double', true, String, function(text) {
       
   244 		return parseFloat(text, 10);
       
   245 	});
       
   246 
       
   247 	// String type. Fairly simple
       
   248 	xmlrpc.makeType('string', true, String, String);
       
   249 
       
   250 	// Boolean type. True == '1', False == '0'
       
   251 	xmlrpc.makeType('boolean', true, function(value) {
       
   252 		return value ? '1' : '0';
       
   253 	}, function(text) {
       
   254 		return text === '1';
       
   255 	});
       
   256 
       
   257 	// Dates are a little trickier
       
   258 	var _pad = function(n) { return n<10 ? '0'+n : n; };
       
   259 
       
   260 	xmlrpc.makeType('dateTime.iso8601', true, function(d) {
       
   261 		return [
       
   262 			d.getUTCFullYear(), '-', _pad(d.getUTCMonth()+1), '-',
       
   263 			_pad(d.getUTCDate()), 'T', _pad(d.getUTCHours()), ':',
       
   264 			_pad(d.getUTCMinutes()), ':', _pad(d.getUTCSeconds()), 'Z'
       
   265 		].join('');
       
   266 	}, function(text) {
       
   267 		return new Date(text);
       
   268 	});
       
   269 
       
   270 	// Go between a base64 string and an ArrayBuffer
       
   271 	xmlrpc.binary = (function() {
       
   272 		var pad = '=';
       
   273 		var toChars = ('ABCDEFGHIJKLMNOPQRSTUVWXYZ' +
       
   274 			'abcdefghijklmnopqrstuvwxyz0123456789+/').split("");
       
   275 		var fromChars = toChars.reduce(function(acc, chr, i) {
       
   276 			acc[chr] = i;
       
   277 			return acc;
       
   278 		}, {});
       
   279 
       
   280 		/*
       
   281 		* In the following, three bytes are added together into a 24-bit
       
   282 		* number, which is then split up in to 4 6-bit numbers - or vice versa.
       
   283 		* That is why there is lots of shifting by multiples of 6 and 8, and
       
   284 		* the magic numbers 3 and 4.
       
   285 		*
       
   286 		* The modulo 64 is for converting to base 64, and the modulo 256 is for
       
   287 		* converting to 8-bit numbers.
       
   288 		*/
       
   289 		return {
       
   290 			toBase64: function(ab) {
       
   291 				var acc = [];
       
   292 
       
   293 				var int8View = new Uint8Array(ab);
       
   294 				var int8Index = 0, int24;
       
   295 				for (; int8Index < int8View.length; int8Index += 3) {
       
   296 
       
   297 					// Grab three bytes
       
   298 					int24 =
       
   299 						(int8View[int8Index + 0] << 16) +
       
   300 						(int8View[int8Index + 1] << 8) +
       
   301 						(int8View[int8Index + 2] << 0);
       
   302 
       
   303 					// Push four chars
       
   304 					acc.push(toChars[(int24 >> 18) % 64]);
       
   305 					acc.push(toChars[(int24 >> 12) % 64]);
       
   306 					acc.push(toChars[(int24 >> 6) % 64]);
       
   307 					acc.push(toChars[(int24 >> 0)% 64]);
       
   308 				}
       
   309 
       
   310 				// Set the last few characters to the padding character
       
   311 				var padChars = 3 - ((ab.byteLength % 3) || 3);
       
   312 				while (padChars--) {
       
   313 					acc[acc.length - padChars - 1] = pad;
       
   314 				}
       
   315 
       
   316 				return acc.join('');
       
   317 			},
       
   318 
       
   319 			fromBase64: function(base64) {
       
   320 				var base64Len = base64.length;
       
   321 
       
   322 				// Work out the length of the data, accommodating for padding
       
   323 				var abLen = (base64Len / 4) * 3;
       
   324 				if (base64.charAt(base64Len - 1) === pad) { abLen--; }
       
   325 				if (base64.charAt(base64Len - 2) === pad) { abLen--; }
       
   326 
       
   327 				// Make the ArrayBuffer, and an Int8Array to work with it
       
   328 				var ab = new ArrayBuffer(abLen);
       
   329 				var int8View = new Uint8Array(ab);
       
   330 
       
   331 				var base64Index = 0, int8Index = 0, int24;
       
   332 				for (; base64Index < base64Len; base64Index += 4, int8Index += 3) {
       
   333 
       
   334 					// Grab four chars
       
   335 					int24 =
       
   336 						(fromChars[base64[base64Index + 0]] << 18) +
       
   337 						(fromChars[base64[base64Index + 1]] << 12) +
       
   338 						(fromChars[base64[base64Index + 2]] << 6) +
       
   339 						(fromChars[base64[base64Index + 3]] << 0);
       
   340 
       
   341 					// Push three bytes
       
   342 					int8View[int8Index + 0] = (int24 >> 16) % 256;
       
   343 					int8View[int8Index + 1] = (int24 >> 8) % 256;
       
   344 					int8View[int8Index + 2] = (int24 >> 0) % 256;
       
   345 
       
   346 				}
       
   347 
       
   348 				return ab;
       
   349 			}
       
   350 		};
       
   351 	})();
       
   352 
       
   353 	xmlrpc.makeType('base64', true, function(ab) {
       
   354 		return xmlrpc.binary.toBase64(ab);
       
   355 	}, function(text) {
       
   356 		return xmlrpc.binary.fromBase64(text);
       
   357 	});
       
   358 
       
   359 	// Nil/null
       
   360 	xmlrpc.makeType('nil', false,
       
   361 		function(val, $xml) { return $xml('nil'); },
       
   362 		function(_) { return null; }
       
   363 	);
       
   364 
       
   365 	// Structs/Objects
       
   366 	xmlrpc.makeType('struct', false, function(value, $xml) {
       
   367 		var $struct = $xml('struct');
       
   368 
       
   369 		$.each(value, function(name, value) {
       
   370 			var $name = $xml('name').text(name);
       
   371 			var $value = $xml('value').append(xmlrpc.toXmlRpc(value, $xml));
       
   372 			$struct.append($xml('member').append($name, $value));
       
   373 		});
       
   374 
       
   375 		return $struct;
       
   376 
       
   377 	}, function(node) {
       
   378 		return $(node)
       
   379 			.find('> member')
       
   380 			.toArray()
       
   381 			.reduce(function(struct, el) {
       
   382 				var $el = $(el);
       
   383 				var key = $el.find('> name').text();
       
   384 				var value = xmlrpc.parseValue($el.find('> value'));
       
   385 
       
   386 				struct[key] = value;
       
   387 				return struct;
       
   388 			}, {});
       
   389 
       
   390 	});
       
   391 
       
   392 	// Arrays
       
   393 	xmlrpc.makeType('array', false, function(value, $xml) {
       
   394 		var $array = $xml('array');
       
   395 		var $data = $xml('data');
       
   396 		$.each(value, function(i, val) {
       
   397 			$data.append($xml('value').append(xmlrpc.toXmlRpc(val, $xml)));
       
   398 		});
       
   399 		$array.append($data);
       
   400 		return $array;
       
   401 	}, function(node) {
       
   402 		return $(node).find('> data > value').toArray()
       
   403 			.map(xmlrpc.parseValue);
       
   404 	});
       
   405 
       
   406 
       
   407 	/**
       
   408 	* Force a value to an XML-RPC type. All the usual XML-RPC types are
       
   409 	* supported
       
   410 	*/
       
   411 	xmlrpc.force = function(type, value) {
       
   412 		return new xmlrpc.types[type](value);
       
   413 	};
       
   414 
       
   415 })(jQuery);