root/luci/branches/luci-0.10/libs/json/luasrc/json.lua

Revision 6989, 12.3 KB (checked in by jow, 13 months ago)

luci-0.10: merge r6988

  • Property svn:keywords set to Id
Line 
1--[[
2LuCI - Lua Configuration Interface
3
4Copyright 2008 Steven Barth <steven@midlink.org>
5Copyright 2008 Jo-Philipp Wich <xm@leipzig.freifunk.net>
6
7Licensed under the Apache License, Version 2.0 (the "License");
8you may not use this file except in compliance with the License.
9You may obtain a copy of the License at
10
11http://www.apache.org/licenses/LICENSE-2.0
12
13$Id$
14
15Decoder:
16    Info:
17        null will be decoded to luci.json.null if first parameter of Decoder() is true
18   
19    Example:
20        decoder = luci.json.Decoder()
21        luci.ltn12.pump.all(luci.ltn12.source.string("decodableJSON"), decoder:sink())
22        luci.util.dumptable(decoder:get())
23       
24    Known issues:
25        does not support unicode conversion \uXXYY with XX != 00 will be ignored
26       
27           
28Encoder:
29    Info:
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
36   
37    Example:
38        encoder = luci.json.Encoder(encodableData)
39        luci.ltn12.pump.all(encoder:source(), luci.ltn12.sink.file(io.open("someFile", w)))
40]]--
41
42local util      = require "luci.util"
43local table     = require "table"
44local string    = require "string"
45local coroutine = require "coroutine"
46
47local assert    = assert
48local tonumber  = tonumber
49local tostring  = tostring
50local error     = error
51local type      = type
52local pairs     = pairs
53local ipairs    = ipairs
54local next      = next
55local pcall     = pcall
56
57local getmetatable = getmetatable
58
59--- LuCI JSON-Library
60-- @cstyle  instance
61module "luci.json"
62
63
64--- Directly decode a JSON string
65-- @param json JSON-String
66-- @return Lua object
67function decode(json, ...)
68    local a = ActiveDecoder(function() return nil end, ...)
69    a.chunk = json
70    local s, obj = pcall(a.get, a)
71    return s and obj or nil
72end
73
74
75--- Direcly encode a Lua object into a JSON string.
76-- @param obj Lua Object
77-- @return JSON string
78function encode(obj, ...)
79    local out = {}
80    local e = Encoder(obj, 1, ...):source()
81    local chnk, err
82    repeat
83        chnk, err = e()
84        out[#out+1] = chnk
85    until not chnk
86    return not err and table.concat(out) or nil
87end
88
89
90--- Null replacement function
91-- @return null
92function null()
93    return null
94end
95
96--- Create a new JSON-Encoder.
97-- @class   function
98-- @name    Encoder
99-- @param data          Lua-Object to be encoded.
100-- @param buffersize    Blocksize of returned data source.
101-- @param fastescape    Use non-standard escaping (don't escape control chars)
102-- @return JSON-Encoder
103Encoder = util.class()
104
105function Encoder.__init__(self, data, buffersize, fastescape)
106    self.data = data
107    self.buffersize = buffersize or 512
108    self.buffer = ""
109    self.fastescape = fastescape
110   
111    getmetatable(self).__call = Encoder.source
112end
113
114--- Create an LTN12 source providing the encoded JSON-Data.
115-- @return LTN12 source
116function Encoder.source(self)
117    local source = coroutine.create(self.dispatch)
118    return function()
119        local res, data = coroutine.resume(source, self, self.data, true)
120        if res then
121            return data
122        else
123            return nil, data
124        end
125    end 
126end
127
128function Encoder.dispatch(self, data, start)
129    local parser = self.parsers[type(data)]
130   
131    parser(self, data)
132   
133    if start then
134        if #self.buffer > 0 then
135            coroutine.yield(self.buffer)
136        end
137       
138        coroutine.yield()
139    end
140end
141
142function Encoder.put(self, chunk)
143    if self.buffersize < 2 then
144        coroutine.yield(chunk)
145    else
146        if #self.buffer + #chunk > self.buffersize then
147            local written = 0
148            local fbuffer = self.buffersize - #self.buffer
149
150            coroutine.yield(self.buffer .. chunk:sub(written + 1, fbuffer))
151            written = fbuffer
152           
153            while #chunk - written > self.buffersize do
154                fbuffer = written + self.buffersize
155                coroutine.yield(chunk:sub(written + 1, fbuffer))
156                written = fbuffer
157            end 
158           
159            self.buffer = chunk:sub(written + 1)
160        else
161            self.buffer = self.buffer .. chunk
162        end
163    end
164end
165
166function Encoder.parse_nil(self)
167    self:put("null")
168end
169
170function Encoder.parse_bool(self, obj)
171    self:put(obj and "true" or "false")
172end
173
174function Encoder.parse_number(self, obj)
175    self:put(tostring(obj))
176end
177
178function Encoder.parse_string(self, obj)
179    if self.fastescape then
180        self:put('"' .. obj:gsub('\\', '\\\\'):gsub('"', '\\"') .. '"')
181    else
182        self:put('"' ..
183            obj:gsub('[%c\\"]',
184                function(char)
185                    return '\\u00%02x' % char:byte()
186                end
187            )
188        .. '"')
189    end
190end
191
192function Encoder.parse_iter(self, obj)
193    if obj == null then
194        return self:put("null")
195    end
196
197    if type(obj) == "table" and (#obj == 0 and next(obj)) then
198        self:put("{")
199        local first = true
200       
201        for key, entry in pairs(obj) do
202            first = first or self:put(",")
203            first = first and false
204            self:parse_string(tostring(key))
205            self:put(":")
206            self:dispatch(entry)
207        end
208       
209        self:put("}")       
210    else
211        self:put("[")
212        local first = true
213       
214        if type(obj) == "table" then
215            for i=1, #obj do
216                first = first or self:put(",")
217                first = first and nil
218                self:dispatch(obj[i])
219            end
220        else
221            for entry in obj do
222                first = first or self:put(",")
223                first = first and nil
224                self:dispatch(entry)
225            end     
226        end
227       
228        self:put("]")   
229    end
230end
231
232Encoder.parsers = {
233    ['nil']      = Encoder.parse_nil,
234    ['table']    = Encoder.parse_iter,
235    ['number']   = Encoder.parse_number,
236    ['string']   = Encoder.parse_string,
237    ['boolean']  = Encoder.parse_bool,
238    ['function'] = Encoder.parse_iter
239} 
240
241
242--- Create a new JSON-Decoder.
243-- @class   function
244-- @name    Decoder
245-- @param customnull Use luci.json.null instead of nil for decoding null
246-- @return JSON-Decoder
247Decoder = util.class()
248
249function Decoder.__init__(self, customnull)
250    self.cnull = customnull
251    getmetatable(self).__call = Decoder.sink
252end
253
254--- Create an LTN12 sink from the decoder object which accepts the JSON-Data.
255-- @return LTN12 sink
256function Decoder.sink(self)
257    local sink = coroutine.create(self.dispatch)
258    return function(...)
259        return coroutine.resume(sink, self, ...)
260    end
261end
262
263
264--- Get the decoded data packets after the rawdata has been sent to the sink.
265-- @return Decoded data
266function Decoder.get(self)
267    return self.data
268end
269
270function Decoder.dispatch(self, chunk, src_err, strict)
271    local robject, object
272    local oset = false
273     
274    while chunk do
275        while chunk and #chunk < 1 do
276            chunk = self:fetch()
277        end
278       
279        assert(not strict or chunk, "Unexpected EOS")
280        if not chunk then break end
281       
282        local char   = chunk:sub(1, 1)
283        local parser = self.parsers[char]
284         or (char:match("%s")     and self.parse_space)
285         or (char:match("[0-9-]") and self.parse_number)
286         or error("Unexpected char '%s'" % char)
287       
288        chunk, robject = parser(self, chunk)
289       
290        if parser ~= self.parse_space then
291            assert(not oset, "Scope violation: Too many objects")
292            object = robject
293            oset = true
294       
295            if strict then
296                return chunk, object
297            end
298        end
299    end
300   
301    assert(not src_err, src_err)
302    assert(oset, "Unexpected EOS")
303   
304    self.data = object
305end
306
307
308function Decoder.fetch(self)
309    local tself, chunk, src_err = coroutine.yield()
310    assert(chunk or not src_err, src_err)
311    return chunk
312end
313
314
315function Decoder.fetch_atleast(self, chunk, bytes)
316    while #chunk < bytes do
317        local nchunk = self:fetch()
318        assert(nchunk, "Unexpected EOS")
319        chunk = chunk .. nchunk
320    end
321   
322    return chunk
323end
324
325
326function Decoder.fetch_until(self, chunk, pattern)
327    local start = chunk:find(pattern)
328
329    while not start do
330        local nchunk = self:fetch()
331        assert(nchunk, "Unexpected EOS")
332        chunk = chunk .. nchunk
333        start = chunk:find(pattern)
334    end
335
336    return chunk, start
337end
338
339
340function Decoder.parse_space(self, chunk)
341    local start = chunk:find("[^%s]")
342   
343    while not start do
344        chunk = self:fetch()
345        if not chunk then
346            return nil
347        end
348        start = chunk:find("[^%s]")
349    end
350   
351    return chunk:sub(start)
352end
353
354
355function Decoder.parse_literal(self, chunk, literal, value)
356    chunk = self:fetch_atleast(chunk, #literal) 
357    assert(chunk:sub(1, #literal) == literal, "Invalid character sequence")
358    return chunk:sub(#literal + 1), value
359end
360
361
362function Decoder.parse_null(self, chunk)
363    return self:parse_literal(chunk, "null", self.cnull and null)
364end
365
366
367function Decoder.parse_true(self, chunk)
368    return self:parse_literal(chunk, "true", true)
369end
370
371
372function Decoder.parse_false(self, chunk)
373    return self:parse_literal(chunk, "false", false)
374end
375
376
377function Decoder.parse_number(self, chunk)
378    local chunk, start = self:fetch_until(chunk, "[^0-9eE.+-]")
379    local number = tonumber(chunk:sub(1, start - 1))
380    assert(number, "Invalid number specification")
381    return chunk:sub(start), number
382end
383
384
385function Decoder.parse_string(self, chunk)
386    local str = ""
387    local object = nil
388    assert(chunk:sub(1, 1) == '"', 'Expected "')
389    chunk = chunk:sub(2)
390
391    while true do
392        local spos = chunk:find('[\\"]')
393        if spos then
394            str = str .. chunk:sub(1, spos - 1)
395           
396            local char = chunk:sub(spos, spos)
397            if char == '"' then             -- String end
398                chunk = chunk:sub(spos + 1)
399                break
400            elseif char == "\\" then        -- Escape sequence
401                chunk, object = self:parse_escape(chunk:sub(spos))
402                str = str .. object
403            end
404        else
405            str = str .. chunk
406            chunk = self:fetch()
407            assert(chunk, "Unexpected EOS while parsing a string")     
408        end
409    end
410
411    return chunk, str
412end
413
414
415function Decoder.parse_escape(self, chunk)
416    local str = ""
417    chunk = self:fetch_atleast(chunk:sub(2), 1)
418    local char = chunk:sub(1, 1)
419    chunk = chunk:sub(2)
420   
421    if char == '"' then
422        return chunk, '"'
423    elseif char == "\\" then
424        return chunk, "\\"
425    elseif char == "u" then
426        chunk = self:fetch_atleast(chunk, 4)
427        local s1, s2 = chunk:sub(1, 2), chunk:sub(3, 4)
428        s1, s2 = tonumber(s1, 16), tonumber(s2, 16)
429        assert(s1 and s2, "Invalid Unicode character")
430       
431        -- ToDo: Unicode support
432        return chunk:sub(5), s1 == 0 and string.char(s2) or ""
433    elseif char == "/" then
434        return chunk, "/"
435    elseif char == "b" then
436        return chunk, "\b"
437    elseif char == "f" then
438        return chunk, "\f"
439    elseif char == "n" then
440        return chunk, "\n"
441    elseif char == "r" then
442        return chunk, "\r"
443    elseif char == "t" then
444        return chunk, "\t"
445    else
446        error("Unexpected escaping sequence '\\%s'" % char)
447    end
448end
449
450
451function Decoder.parse_array(self, chunk)
452    chunk = chunk:sub(2)
453    local array = {}
454    local nextp = 1
455   
456    local chunk, object = self:parse_delimiter(chunk, "%]")
457   
458    if object then
459        return chunk, array
460    end
461   
462    repeat
463        chunk, object = self:dispatch(chunk, nil, true)
464        table.insert(array, nextp, object)
465        nextp = nextp + 1
466       
467        chunk, object = self:parse_delimiter(chunk, ",%]")
468        assert(object, "Delimiter expected")
469    until object == "]"
470
471    return chunk, array
472end
473
474
475function Decoder.parse_object(self, chunk)
476    chunk = chunk:sub(2)
477    local array = {}
478    local name
479
480    local chunk, object = self:parse_delimiter(chunk, "}")
481
482    if object then
483        return chunk, array
484    end
485
486    repeat
487        chunk = self:parse_space(chunk)
488        assert(chunk, "Unexpected EOS")
489       
490        chunk, name   = self:parse_string(chunk)
491       
492        chunk, object = self:parse_delimiter(chunk, ":")
493        assert(object, "Separator expected")
494       
495        chunk, object = self:dispatch(chunk, nil, true)
496        array[name] = object
497
498        chunk, object = self:parse_delimiter(chunk, ",}")
499        assert(object, "Delimiter expected")
500    until object == "}"
501
502    return chunk, array
503end
504
505
506function Decoder.parse_delimiter(self, chunk, delimiter)
507    while true do
508        chunk = self:fetch_atleast(chunk, 1)
509        local char = chunk:sub(1, 1)
510        if char:match("%s") then
511            chunk = self:parse_space(chunk)
512            assert(chunk, "Unexpected EOS")
513        elseif char:match("[%s]" % delimiter) then
514            return chunk:sub(2), char
515        else
516            return chunk, nil
517        end
518    end
519end
520
521
522Decoder.parsers = { 
523    ['"'] = Decoder.parse_string,
524    ['t'] = Decoder.parse_true,
525    ['f'] = Decoder.parse_false,
526    ['n'] = Decoder.parse_null,
527    ['['] = Decoder.parse_array,
528    ['{'] = Decoder.parse_object
529}
530
531
532--- Create a new Active JSON-Decoder.
533-- @class   function
534-- @name    ActiveDecoder
535-- @param   customnull  Use luci.json.null instead of nil for decoding null
536-- @return  Active JSON-Decoder
537ActiveDecoder = util.class(Decoder)
538
539function ActiveDecoder.__init__(self, source, customnull)
540    Decoder.__init__(self, customnull)
541    self.source = source
542    self.chunk = nil
543    getmetatable(self).__call = self.get
544end
545
546
547--- Fetches one JSON-object from given source
548-- @return Decoded object
549function ActiveDecoder.get(self)
550    local chunk, src_err, object
551    if not self.chunk then
552        chunk, src_err = self.source()
553    else
554        chunk = self.chunk
555    end
556
557    self.chunk, object = self:dispatch(chunk, src_err, true)
558    return object
559end
560
561
562function ActiveDecoder.fetch(self)
563    local chunk, src_err = self.source()
564    assert(chunk or not src_err, src_err)
565    return chunk
566end
Note: See TracBrowser for help on using the browser.