artdaq_node_server  v1_00_09
 All Classes Namespaces Files Variables Pages
deserializer.js
1 var sax = require('sax')
2  , dateFormatter = require('./date_formatter')
3 
4 var Deserializer = function(encoding) {
5  this.type = null
6  this.responseType = null
7  this.stack = []
8  this.marks = []
9  this.data = []
10  this.methodname = null
11  this.encoding = encoding || 'utf8'
12  this.value = false
13  this.callback = null
14  this.error = null
15 
16  this.parser = sax.createStream()
17  this.parser.on('opentag', this.onOpentag.bind(this))
18  this.parser.on('closetag', this.onClosetag.bind(this))
19  this.parser.on('text', this.onText.bind(this))
20  this.parser.on('cdata', this.onCDATA.bind(this))
21  this.parser.on('end', this.onDone.bind(this))
22  this.parser.on('error', this.onError.bind(this))
23 }
24 
25 Deserializer.prototype.deserializeMethodResponse = function(stream, callback) {
26  var that = this
27 
28  this.callback = function(error, result) {
29  if (error) {
30  callback(error)
31  }
32  else if (result.length > 1) {
33  callback(new Error('Response has more than one param'))
34  }
35  else if (that.type !== 'methodresponse') {
36  callback(new Error('Not a method response'))
37  }
38  else if (!that.responseType) {
39  callback(new Error('Invalid method response'))
40  }
41  else {
42  callback(null, result[0])
43  }
44  }
45 
46  stream.setEncoding(this.encoding)
47  stream.on('error', this.onError.bind(this))
48  stream.pipe(this.parser)
49 }
50 
51 Deserializer.prototype.deserializeMethodCall = function(stream, callback) {
52  var that = this
53 
54  this.callback = function(error, result) {
55  if (error) {
56  callback(error)
57  }
58  else if (that.type !== 'methodcall') {
59  callback(new Error('Not a method call'))
60  }
61  else if (!that.methodname) {
62  callback(new Error('Method call did not contain a method name'))
63  }
64  else {
65  callback(null, that.methodname, result)
66  }
67  }
68 
69  stream.setEncoding(this.encoding)
70  stream.on('error', this.onError.bind(this))
71  stream.pipe(this.parser)
72 }
73 
74 Deserializer.prototype.onDone = function() {
75  var that = this
76 
77  if (!this.error) {
78  if (this.type === null || this.marks.length) {
79  this.callback(new Error('Invalid XML-RPC message'))
80  }
81  else if (this.responseType === 'fault') {
82  var createFault = function(fault) {
83  var error = new Error('XML-RPC fault' + (fault.faultString ? ': ' + fault.faultString : ''))
84  error.code = fault.faultCode
85  error.faultCode = fault.faultCode
86  error.faultString = fault.faultString
87  return error
88  }
89  this.callback(createFault(this.stack[0]))
90  }
91  else {
92  this.callback(undefined, this.stack)
93  }
94  }
95 }
96 
97 // TODO:
98 // Error handling needs a little thinking. There are two different kinds of
99 // errors:
100 // 1. Low level errors like network, stream or xml errors. These don't
101 // require special treatment. They only need to be forwarded. The IO
102 // is already stopped in these cases.
103 // 2. Protocol errors: Invalid tags, invalid values &c. These happen in
104 // our code and we should tear down the IO and stop parsing.
105 // Currently all errors end here. Guess I'll split it up.
106 Deserializer.prototype.onError = function(msg) {
107  if (!this.error) {
108  if (typeof msg === 'string') {
109  this.error = new Error(msg)
110  }
111  else {
112  this.error = msg
113  }
114  this.callback(this.error)
115  }
116 }
117 
118 Deserializer.prototype.push = function(value) {
119  this.stack.push(value)
120 }
121 
122 //==============================================================================
123 // SAX Handlers
124 //==============================================================================
125 
126 Deserializer.prototype.onOpentag = function(node) {
127  if (node.name === 'ARRAY' || node.name === 'STRUCT') {
128  this.marks.push(this.stack.length)
129  }
130  this.data = []
131  this.value = (node.name === 'VALUE')
132 }
133 
134 Deserializer.prototype.onText = function(text) {
135  this.data.push(text)
136 }
137 
138 Deserializer.prototype.onCDATA = function(cdata) {
139  this.data.push(cdata)
140 }
141 
142 Deserializer.prototype.onClosetag = function(el) {
143  var data = this.data.join('')
144  try {
145  switch(el) {
146  case 'BOOLEAN':
147  this.endBoolean(data)
148  break
149  case 'INT':
150  case 'I4':
151  this.endInt(data)
152  break
153  case 'I8':
154  this.endI8(data)
155  break
156  case 'DOUBLE':
157  this.endDouble(data)
158  break
159  case 'STRING':
160  case 'NAME':
161  this.endString(data)
162  break
163  case 'ARRAY':
164  this.endArray(data)
165  break
166  case 'STRUCT':
167  this.endStruct(data)
168  break
169  case 'BASE64':
170  this.endBase64(data)
171  break
172  case 'DATETIME.ISO8601':
173  this.endDateTime(data)
174  break
175  case 'VALUE':
176  this.endValue(data)
177  break
178  case 'PARAMS':
179  this.endParams(data)
180  break
181  case 'FAULT':
182  this.endFault(data)
183  break
184  case 'METHODRESPONSE':
185  this.endMethodResponse(data)
186  break
187  case 'METHODNAME':
188  this.endMethodName(data)
189  break
190  case 'METHODCALL':
191  this.endMethodCall(data)
192  break
193  case 'NIL':
194  this.endNil(data)
195  break
196  case 'DATA':
197  case 'PARAM':
198  case 'MEMBER':
199  // Ignored by design
200  break
201  default:
202  this.onError('Unknown XML-RPC tag \'' + el + '\'')
203  break
204  }
205  }
206  catch (e) {
207  this.onError(e)
208  }
209 }
210 
211 Deserializer.prototype.endNil = function(data) {
212  this.push(null)
213  this.value = false
214 }
215 
216 Deserializer.prototype.endBoolean = function(data) {
217  if (data === '1') {
218  this.push(true)
219  }
220  else if (data === '0') {
221  this.push(false)
222  }
223  else {
224  throw new Error('Illegal boolean value \'' + data + '\'')
225  }
226  this.value = false
227 }
228 
229 Deserializer.prototype.endInt = function(data) {
230  var value = parseInt(data, 10)
231  if (isNaN(value)) {
232  throw new Error('Expected an integer but got \'' + data + '\'')
233  }
234  else {
235  this.push(value)
236  this.value = false
237  }
238 }
239 
240 Deserializer.prototype.endDouble = function(data) {
241  var value = parseFloat(data)
242  if (isNaN(value)) {
243  throw new Error('Expected a double but got \'' + data + '\'')
244  }
245  else {
246  this.push(value)
247  this.value = false
248  }
249 }
250 
251 Deserializer.prototype.endString = function(data) {
252  this.push(data)
253  this.value = false
254 }
255 
256 Deserializer.prototype.endArray = function(data) {
257  var mark = this.marks.pop()
258  this.stack.splice(mark, this.stack.length - mark, this.stack.slice(mark))
259  this.value = false
260 }
261 
262 Deserializer.prototype.endStruct = function(data) {
263  var mark = this.marks.pop()
264  , struct = {}
265  , items = this.stack.slice(mark)
266  , i = 0
267 
268  for (; i < items.length; i += 2) {
269  struct[items[i]] = items[i + 1]
270  }
271  this.stack.splice(mark, this.stack.length - mark, struct)
272  this.value = false
273 }
274 
275 Deserializer.prototype.endBase64 = function(data) {
276  var buffer = new Buffer(data, 'base64')
277  this.push(buffer)
278  this.value = false
279 }
280 
281 Deserializer.prototype.endDateTime = function(data) {
282  var date = dateFormatter.decodeIso8601(data)
283  this.push(date)
284  this.value = false
285 }
286 
287 var isInteger = /^-?\d+$/
288 Deserializer.prototype.endI8 = function(data) {
289  if (!isInteger.test(data)) {
290  throw new Error('Expected integer (I8) value but got \'' + data + '\'')
291  }
292  else {
293  this.endString(data)
294  }
295 }
296 
297 Deserializer.prototype.endValue = function(data) {
298  if (this.value) {
299  this.endString(data)
300  }
301 }
302 
303 Deserializer.prototype.endParams = function(data) {
304  this.responseType = 'params'
305 }
306 
307 Deserializer.prototype.endFault = function(data) {
308  this.responseType = 'fault'
309 }
310 
311 Deserializer.prototype.endMethodResponse = function(data) {
312  this.type = 'methodresponse'
313 }
314 
315 Deserializer.prototype.endMethodName = function(data) {
316  this.methodname = data
317 }
318 
319 Deserializer.prototype.endMethodCall = function(data) {
320  this.type = 'methodcall'
321 }
322 
323 module.exports = Deserializer
324