00001 var sax = require('sax')
00002 , dateFormatter = require('./date_formatter')
00003
00004 var Deserializer = function(encoding) {
00005 this.type = null
00006 this.responseType = null
00007 this.stack = []
00008 this.marks = []
00009 this.data = []
00010 this.methodname = null
00011 this.encoding = encoding || 'utf8'
00012 this.value = false
00013 this.callback = null
00014 this.error = null
00015
00016 this.parser = sax.createStream()
00017 this.parser.on('opentag', this.onOpentag.bind(this))
00018 this.parser.on('closetag', this.onClosetag.bind(this))
00019 this.parser.on('text', this.onText.bind(this))
00020 this.parser.on('cdata', this.onCDATA.bind(this))
00021 this.parser.on('end', this.onDone.bind(this))
00022 this.parser.on('error', this.onError.bind(this))
00023 }
00024
00025 Deserializer.prototype.deserializeMethodResponse = function(stream, callback) {
00026 var that = this
00027
00028 this.callback = function(error, result) {
00029 if (error) {
00030 callback(error)
00031 }
00032 else if (result.length > 1) {
00033 callback(new Error('Response has more than one param'))
00034 }
00035 else if (that.type !== 'methodresponse') {
00036 callback(new Error('Not a method response'))
00037 }
00038 else if (!that.responseType) {
00039 callback(new Error('Invalid method response'))
00040 }
00041 else {
00042 callback(null, result[0])
00043 }
00044 }
00045
00046 stream.setEncoding(this.encoding)
00047 stream.on('error', this.onError.bind(this))
00048 stream.pipe(this.parser)
00049 }
00050
00051 Deserializer.prototype.deserializeMethodCall = function(stream, callback) {
00052 var that = this
00053
00054 this.callback = function(error, result) {
00055 if (error) {
00056 callback(error)
00057 }
00058 else if (that.type !== 'methodcall') {
00059 callback(new Error('Not a method call'))
00060 }
00061 else if (!that.methodname) {
00062 callback(new Error('Method call did not contain a method name'))
00063 }
00064 else {
00065 callback(null, that.methodname, result)
00066 }
00067 }
00068
00069 stream.setEncoding(this.encoding)
00070 stream.on('error', this.onError.bind(this))
00071 stream.pipe(this.parser)
00072 }
00073
00074 Deserializer.prototype.onDone = function() {
00075 var that = this
00076
00077 if (!this.error) {
00078 if (this.type === null || this.marks.length) {
00079 this.callback(new Error('Invalid XML-RPC message'))
00080 }
00081 else if (this.responseType === 'fault') {
00082 var createFault = function(fault) {
00083 var error = new Error('XML-RPC fault' + (fault.faultString ? ': ' + fault.faultString : ''))
00084 error.code = fault.faultCode
00085 error.faultCode = fault.faultCode
00086 error.faultString = fault.faultString
00087 return error
00088 }
00089 this.callback(createFault(this.stack[0]))
00090 }
00091 else {
00092 this.callback(undefined, this.stack)
00093 }
00094 }
00095 }
00096
00097
00098
00099
00100
00101
00102
00103
00104
00105
00106 Deserializer.prototype.onError = function(msg) {
00107 if (!this.error) {
00108 if (typeof msg === 'string') {
00109 this.error = new Error(msg)
00110 }
00111 else {
00112 this.error = msg
00113 }
00114 this.callback(this.error)
00115 }
00116 }
00117
00118 Deserializer.prototype.push = function(value) {
00119 this.stack.push(value)
00120 }
00121
00122
00123
00124
00125
00126 Deserializer.prototype.onOpentag = function(node) {
00127 if (node.name === 'ARRAY' || node.name === 'STRUCT') {
00128 this.marks.push(this.stack.length)
00129 }
00130 this.data = []
00131 this.value = (node.name === 'VALUE')
00132 }
00133
00134 Deserializer.prototype.onText = function(text) {
00135 this.data.push(text)
00136 }
00137
00138 Deserializer.prototype.onCDATA = function(cdata) {
00139 this.data.push(cdata)
00140 }
00141
00142 Deserializer.prototype.onClosetag = function(el) {
00143 var data = this.data.join('')
00144 try {
00145 switch(el) {
00146 case 'BOOLEAN':
00147 this.endBoolean(data)
00148 break
00149 case 'INT':
00150 case 'I4':
00151 this.endInt(data)
00152 break
00153 case 'I8':
00154 this.endI8(data)
00155 break
00156 case 'DOUBLE':
00157 this.endDouble(data)
00158 break
00159 case 'STRING':
00160 case 'NAME':
00161 this.endString(data)
00162 break
00163 case 'ARRAY':
00164 this.endArray(data)
00165 break
00166 case 'STRUCT':
00167 this.endStruct(data)
00168 break
00169 case 'BASE64':
00170 this.endBase64(data)
00171 break
00172 case 'DATETIME.ISO8601':
00173 this.endDateTime(data)
00174 break
00175 case 'VALUE':
00176 this.endValue(data)
00177 break
00178 case 'PARAMS':
00179 this.endParams(data)
00180 break
00181 case 'FAULT':
00182 this.endFault(data)
00183 break
00184 case 'METHODRESPONSE':
00185 this.endMethodResponse(data)
00186 break
00187 case 'METHODNAME':
00188 this.endMethodName(data)
00189 break
00190 case 'METHODCALL':
00191 this.endMethodCall(data)
00192 break
00193 case 'NIL':
00194 this.endNil(data)
00195 break
00196 case 'DATA':
00197 case 'PARAM':
00198 case 'MEMBER':
00199
00200 break
00201 default:
00202 this.onError('Unknown XML-RPC tag \'' + el + '\'')
00203 break
00204 }
00205 }
00206 catch (e) {
00207 this.onError(e)
00208 }
00209 }
00210
00211 Deserializer.prototype.endNil = function(data) {
00212 this.push(null)
00213 this.value = false
00214 }
00215
00216 Deserializer.prototype.endBoolean = function(data) {
00217 if (data === '1') {
00218 this.push(true)
00219 }
00220 else if (data === '0') {
00221 this.push(false)
00222 }
00223 else {
00224 throw new Error('Illegal boolean value \'' + data + '\'')
00225 }
00226 this.value = false
00227 }
00228
00229 Deserializer.prototype.endInt = function(data) {
00230 var value = parseInt(data, 10)
00231 if (isNaN(value)) {
00232 throw new Error('Expected an integer but got \'' + data + '\'')
00233 }
00234 else {
00235 this.push(value)
00236 this.value = false
00237 }
00238 }
00239
00240 Deserializer.prototype.endDouble = function(data) {
00241 var value = parseFloat(data)
00242 if (isNaN(value)) {
00243 throw new Error('Expected a double but got \'' + data + '\'')
00244 }
00245 else {
00246 this.push(value)
00247 this.value = false
00248 }
00249 }
00250
00251 Deserializer.prototype.endString = function(data) {
00252 this.push(data)
00253 this.value = false
00254 }
00255
00256 Deserializer.prototype.endArray = function(data) {
00257 var mark = this.marks.pop()
00258 this.stack.splice(mark, this.stack.length - mark, this.stack.slice(mark))
00259 this.value = false
00260 }
00261
00262 Deserializer.prototype.endStruct = function(data) {
00263 var mark = this.marks.pop()
00264 , struct = {}
00265 , items = this.stack.slice(mark)
00266 , i = 0
00267
00268 for (; i < items.length; i += 2) {
00269 struct[items[i]] = items[i + 1]
00270 }
00271 this.stack.splice(mark, this.stack.length - mark, struct)
00272 this.value = false
00273 }
00274
00275 Deserializer.prototype.endBase64 = function(data) {
00276 var buffer = new Buffer(data, 'base64')
00277 this.push(buffer)
00278 this.value = false
00279 }
00280
00281 Deserializer.prototype.endDateTime = function(data) {
00282 var date = dateFormatter.decodeIso8601(data)
00283 this.push(date)
00284 this.value = false
00285 }
00286
00287 var isInteger = /^-?\d+$/
00288 Deserializer.prototype.endI8 = function(data) {
00289 if (!isInteger.test(data)) {
00290 throw new Error('Expected integer (I8) value but got \'' + data + '\'')
00291 }
00292 else {
00293 this.endString(data)
00294 }
00295 }
00296
00297 Deserializer.prototype.endValue = function(data) {
00298 if (this.value) {
00299 this.endString(data)
00300 }
00301 }
00302
00303 Deserializer.prototype.endParams = function(data) {
00304 this.responseType = 'params'
00305 }
00306
00307 Deserializer.prototype.endFault = function(data) {
00308 this.responseType = 'fault'
00309 }
00310
00311 Deserializer.prototype.endMethodResponse = function(data) {
00312 this.type = 'methodresponse'
00313 }
00314
00315 Deserializer.prototype.endMethodName = function(data) {
00316 this.methodname = data
00317 }
00318
00319 Deserializer.prototype.endMethodCall = function(data) {
00320 this.type = 'methodcall'
00321 }
00322
00323 module.exports = Deserializer
00324