2 LuCI - Lua Configuration Interface
4 Copyright 2008 Steven Barth <steven@midlink.org>
5 Copyright 2008 Jo-Philipp Wich <xm@leipzig.freifunk.net>
7 Licensed under the Apache License, Version 2.0 (the "License");
8 you may not use this file except in compliance with the License.
9 You may obtain a copy of the License at
11 http://www.apache.org/licenses/LICENSE-2.0
17 null will be decoded to luci.json.null if first parameter of Decoder() is true
20 decoder = luci.json.Decoder()
21 luci.ltn12.pump.all(luci.ltn12.source.string("decodableJSON"), decoder:sink())
22 luci.util.dumptable(decoder:get())
25 does not support unicode conversion \uXXYY with XX != 00 will be ignored
30 Accepts numbers, strings, nil, booleans as they are
31 Accepts luci.json.null as replacement for nil
32 Accepts full associative and full numerically indexed tables
33 Mixed tables will loose their associative values during conversion
34 Iterator functions will be encoded as an array of their return values
35 Non-iterator functions will probably corrupt the encoder
38 encoder = luci.json.Encoder(encodableData)
39 luci.ltn12.pump.all(encoder:source(), luci.ltn12.sink.file(io.open("someFile", w)))
42 local util = require "luci.util"
43 local table = require "table"
44 local string = require "string"
45 local coroutine = require "coroutine"
48 local tonumber = tonumber
49 local tostring = tostring
56 local getmetatable = getmetatable
60 --- Null replacement function
67 Encoder = util.class()
69 --- Creates a new Encoder.
70 -- @param data Data to be encoded.
71 -- @param buffersize Buffersize of returned data.
72 -- @param fastescape Use non-standard escaping (don't escape control chars)
73 function Encoder.__init__(self, data, buffersize, fastescape)
75 self.buffersize = buffersize or 512
77 self.fastescape = fastescape
79 getmetatable(self).__call = Encoder.source
82 --- Create an LTN12 source from the encoder object
83 -- @return LTN12 source
84 function Encoder.source(self)
85 local source = coroutine.create(self.dispatch)
87 local res, data = coroutine.resume(source, self, self.data, true)
96 function Encoder.dispatch(self, data, start)
97 local parser = self.parsers[type(data)]
102 if #self.buffer > 0 then
103 coroutine.yield(self.buffer)
110 function Encoder.put(self, chunk)
111 if self.buffersize < 2 then
112 corountine.yield(chunk)
114 if #self.buffer + #chunk > self.buffersize then
116 local fbuffer = self.buffersize - #self.buffer
118 coroutine.yield(self.buffer .. chunk:sub(written + 1, fbuffer))
121 while #chunk - written > self.buffersize do
122 fbuffer = written + self.buffersize
123 coroutine.yield(chunk:sub(written + 1, fbuffer))
127 self.buffer = chunk:sub(written + 1)
129 self.buffer = self.buffer .. chunk
134 function Encoder.parse_nil(self)
138 function Encoder.parse_bool(self, obj)
139 self:put(obj and "true" or "false")
142 function Encoder.parse_number(self, obj)
143 self:put(tostring(obj))
146 function Encoder.parse_string(self, obj)
147 if self.fastescape then
148 self:put('"' .. obj:gsub('\\', '\\\\'):gsub('"', '\\"') .. '"')
153 return '\\u00%02x' % char:byte()
160 function Encoder.parse_iter(self, obj)
162 return self:put("null")
165 if type(obj) == "table" and (#obj == 0 and next(obj)) then
169 for key, entry in pairs(obj) do
170 first = first or self:put(",")
171 first = first and false
172 self:parse_string(tostring(key))
182 if type(obj) == "table" then
183 for i, entry in pairs(obj) do
184 first = first or self:put(",")
185 first = first and nil
190 first = first or self:put(",")
191 first = first and nil
201 ['nil'] = Encoder.parse_nil,
202 ['table'] = Encoder.parse_iter,
203 ['number'] = Encoder.parse_number,
204 ['string'] = Encoder.parse_string,
205 ['boolean'] = Encoder.parse_bool,
206 ['function'] = Encoder.parse_iter
211 Decoder = util.class()
213 --- Create a new Decoder object.
214 -- @param customnull User luci.json.null instead of nil
215 function Decoder.__init__(self, customnull)
216 self.cnull = customnull
217 getmetatable(self).__call = Decoder.sink
220 --- Create an LTN12 sink from the decoder object.
221 -- @return LTN12 sink
222 function Decoder.sink(self)
223 local sink = coroutine.create(self.dispatch)
225 return coroutine.resume(sink, self, ...)
230 --- Get the decoded data packets
231 -- @return Decoded data
232 function Decoder.get(self)
236 function Decoder.dispatch(self, chunk, src_err, strict)
237 local robject, object
241 while chunk and #chunk < 1 do
245 assert(not strict or chunk, "Unexpected EOS")
246 if not chunk then break end
248 local char = chunk:sub(1, 1)
249 local parser = self.parsers[char]
250 or (char:match("%s") and self.parse_space)
251 or (char:match("[0-9-]") and self.parse_number)
252 or error("Unexpected char '%s'" % char)
254 chunk, robject = parser(self, chunk)
256 if parser ~= self.parse_space then
257 assert(not oset, "Scope violation: Too many objects")
267 assert(not src_err, src_err)
268 assert(oset, "Unexpected EOS")
274 function Decoder.fetch(self)
275 local tself, chunk, src_err = coroutine.yield()
276 assert(chunk or not src_err, src_err)
281 function Decoder.fetch_atleast(self, chunk, bytes)
282 while #chunk < bytes do
283 local nchunk = self:fetch()
284 assert(nchunk, "Unexpected EOS")
285 chunk = chunk .. nchunk
292 function Decoder.fetch_until(self, chunk, pattern)
293 local start = chunk:find(pattern)
296 local nchunk = self:fetch()
297 assert(nchunk, "Unexpected EOS")
298 chunk = chunk .. nchunk
299 start = chunk:find(pattern)
306 function Decoder.parse_space(self, chunk)
307 local start = chunk:find("[^%s]")
314 start = chunk:find("[^%s]")
317 return chunk:sub(start)
321 function Decoder.parse_literal(self, chunk, literal, value)
322 chunk = self:fetch_atleast(chunk, #literal)
323 assert(chunk:sub(1, #literal) == literal, "Invalid character sequence")
324 return chunk:sub(#literal + 1), value
328 function Decoder.parse_null(self, chunk)
329 return self:parse_literal(chunk, "null", self.cnull and null)
333 function Decoder.parse_true(self, chunk)
334 return self:parse_literal(chunk, "true", true)
338 function Decoder.parse_false(self, chunk)
339 return self:parse_literal(chunk, "false", false)
343 function Decoder.parse_number(self, chunk)
344 local chunk, start = self:fetch_until(chunk, "[^0-9eE.+-]")
345 local number = tonumber(chunk:sub(1, start - 1))
346 assert(number, "Invalid number specification")
347 return chunk:sub(start), number
351 function Decoder.parse_string(self, chunk)
354 assert(chunk:sub(1, 1) == '"', 'Expected "')
358 local spos = chunk:find('[\\"]')
360 str = str .. chunk:sub(1, spos - 1)
362 local char = chunk:sub(spos, spos)
363 if char == '"' then -- String end
364 chunk = chunk:sub(spos + 1)
366 elseif char == "\\" then -- Escape sequence
367 chunk, object = self:parse_escape(chunk:sub(spos))
373 assert(chunk, "Unexpected EOS while parsing a string")
381 function Decoder.parse_escape(self, chunk)
383 chunk = self:fetch_atleast(chunk:sub(2), 1)
384 local char = chunk:sub(1, 1)
389 elseif char == "\\" then
391 elseif char == "u" then
392 chunk = self:fetch_atleast(chunk, 4)
393 local s1, s2 = chunk:sub(1, 2), chunk:sub(3, 4)
394 s1, s2 = tonumber(s1, 16), tonumber(s2, 16)
395 assert(s1 and s2, "Invalid Unicode character")
397 -- ToDo: Unicode support
398 return chunk:sub(5), s1 == 0 and string.char(s2) or ""
399 elseif char == "/" then
401 elseif char == "b" then
403 elseif char == "f" then
405 elseif char == "n" then
407 elseif char == "r" then
409 elseif char == "t" then
412 error("Unexpected escaping sequence '\\%s'" % char)
417 function Decoder.parse_array(self, chunk)
422 local chunk, object = self:parse_delimiter(chunk, "%]")
429 chunk, object = self:dispatch(chunk, nil, true)
430 table.insert(array, nextp, object)
433 chunk, object = self:parse_delimiter(chunk, ",%]")
434 assert(object, "Delimiter expected")
441 function Decoder.parse_object(self, chunk)
446 local chunk, object = self:parse_delimiter(chunk, "}")
453 chunk = self:parse_space(chunk)
454 assert(chunk, "Unexpected EOS")
456 chunk, name = self:parse_string(chunk)
458 chunk, object = self:parse_delimiter(chunk, ":")
459 assert(object, "Separator expected")
461 chunk, object = self:dispatch(chunk, nil, true)
464 chunk, object = self:parse_delimiter(chunk, ",}")
465 assert(object, "Delimiter expected")
472 function Decoder.parse_delimiter(self, chunk, delimiter)
474 chunk = self:fetch_atleast(chunk, 1)
475 local char = chunk:sub(1, 1)
476 if char:match("%s") then
477 chunk = self:parse_space(chunk)
478 assert(chunk, "Unexpected EOS")
479 elseif char:match("[%s]" % delimiter) then
480 return chunk:sub(2), char
489 ['"'] = Decoder.parse_string,
490 ['t'] = Decoder.parse_true,
491 ['f'] = Decoder.parse_false,
492 ['n'] = Decoder.parse_null,
493 ['['] = Decoder.parse_array,
494 ['{'] = Decoder.parse_object