1if not modules then modules = { } end modules ['util-jsn'] = {
2 version = 1.001,
3 comment = "companion to m-json.mkiv",
4 author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
5 copyright = "PRAGMA ADE / ConTeXt Development Team",
6 license = "see context related readme files"
7}
8
9
10
11
12
13
14
15
16
17
18
19
20local P, V, R, S, C, Cc, Cs, Ct, Cf, Cg = lpeg.P, lpeg.V, lpeg.R, lpeg.S, lpeg.C, lpeg.Cc, lpeg.Cs, lpeg.Ct, lpeg.Cf, lpeg.Cg
21local lpegmatch = lpeg.match
22local format, gsub = string.format, string.gsub
23local formatters = string.formatters
24local utfchar = utf.char
25local concat, sortedkeys = table.concat, table.sortedkeys
26
27local tonumber, tostring, rawset, type, next = tonumber, tostring, rawset, type, next
28
29local json = utilities.json or { }
30utilities.json = json
31
32do
33
34
35
36 local lbrace = P("{")
37 local rbrace = P("}")
38 local lparent = P("[")
39 local rparent = P("]")
40 local comma = P(",")
41 local colon = P(":")
42 local dquote = P('"')
43
44 local whitespace = lpeg.patterns.whitespace
45 local optionalws = whitespace^0
46
47 local escapes = {
48 ["b"] = "\010",
49 ["f"] = "\014",
50 ["n"] = "\n",
51 ["r"] = "\r",
52 ["t"] = "\t",
53 }
54
55
56
57 local escape_un = P("\\u")/"" * (C(R("09","AF","af")^-4) / function(s)
58 return utfchar(tonumber(s,16))
59 end)
60
61 local escape_bs = P([[\]]) / "" * (P(1) / escapes)
62
63 local jstring = dquote * Cs((escape_un + escape_bs + (1-dquote))^0) * dquote
64 local jtrue = P("true") * Cc(true)
65 local jfalse = P("false") * Cc(false)
66 local jnull = P("null") * Cc(nil)
67 local jnumber = (1-whitespace-rparent-rbrace-comma)^1 / tonumber
68
69 local key = jstring
70
71 local jsonconverter = { "value",
72 hash = lbrace * Cf(Ct("") * (V("pair") * (comma * V("pair"))^0 + optionalws),rawset) * rbrace,
73 pair = Cg(optionalws * key * optionalws * colon * V("value")),
74 array = Ct(lparent * (V("value") * (comma * V("value"))^0 + optionalws) * rparent),
75
76 value = optionalws * (jstring + V("hash") + V("array") + jtrue + jfalse + jnull + jnumber) * optionalws,
77 }
78
79
80
81
82
83
84
85
86
87
88
89 function json.tolua(str)
90 return lpegmatch(jsonconverter,str)
91 end
92
93 function json.load(filename)
94 local data = io.loaddata(filename)
95 if data then
96 return lpegmatch(jsonconverter,data)
97 end
98 end
99
100end
101
102do
103
104
105
106
107
108 local escaper
109
110 local f_start_hash = formatters[ '%w{' ]
111 local f_start_array = formatters[ '%w[' ]
112 local f_start_hash_new = formatters[ "\n" .. '%w{' ]
113 local f_start_array_new = formatters[ "\n" .. '%w[' ]
114 local f_start_hash_key = formatters[ "\n" .. '%w"%s" : {' ]
115 local f_start_array_key = formatters[ "\n" .. '%w"%s" : [' ]
116
117 local f_stop_hash = formatters[ "\n" .. '%w}' ]
118 local f_stop_array = formatters[ "\n" .. '%w]' ]
119
120 local f_key_val_seq = formatters[ "\n" .. '%w"%s" : %s' ]
121 local f_key_val_str = formatters[ "\n" .. '%w"%s" : "%s"' ]
122 local f_key_val_num = f_key_val_seq
123 local f_key_val_yes = formatters[ "\n" .. '%w"%s" : true' ]
124 local f_key_val_nop = formatters[ "\n" .. '%w"%s" : false' ]
125 local f_key_val_null = formatters[ "\n" .. '%w"%s" : null' ]
126
127 local f_val_num = formatters[ "\n" .. '%w%s' ]
128 local f_val_str = formatters[ "\n" .. '%w"%s"' ]
129 local f_val_yes = formatters[ "\n" .. '%wtrue' ]
130 local f_val_nop = formatters[ "\n" .. '%wfalse' ]
131 local f_val_null = formatters[ "\n" .. '%wnull' ]
132 local f_val_empty = formatters[ "\n" .. '%w{ }' ]
133 local f_val_seq = f_val_num
134
135
136
137 local t = { }
138 local n = 0
139
140 local function is_simple_table(tt)
141 local l = #tt
142 if l > 0 then
143 for i=1,l do
144 if type(tt[i]) == "table" then
145 return false
146 end
147 end
148 local nn = n
149 n = n + 1 t[n] = "[ "
150 for i=1,l do
151 if i > 1 then
152 n = n + 1 t[n] = ", "
153 end
154 local v = tt[i]
155 local tv = type(v)
156 if tv == "number" then
157 n = n + 1 t[n] = v
158 elseif tv == "string" then
159 n = n + 1 t[n] = '"'
160 n = n + 1 t[n] = lpegmatch(escaper,v) or v
161 n = n + 1 t[n] = '"'
162 elseif tv == "boolean" then
163 n = n + 1 t[n] = v and "true" or "false"
164 elseif v then
165 n = n + 1 t[n] = tostring(v)
166 else
167 n = n + 1 t[n] = "null"
168 end
169 end
170 n = n + 1 t[n] = " ]"
171 local s = concat(t,"",nn+1,n)
172 n = nn
173 return s
174 end
175 return false
176 end
177
178 local function tojsonpp(root,name,depth,level,size)
179 if root then
180 local indexed = size > 0
181 n = n + 1
182 if level == 0 then
183 if indexed then
184 t[n] = f_start_array(depth)
185 else
186 t[n] = f_start_hash(depth)
187 end
188 elseif name then
189 if tn == "string" then
190 name = lpegmatch(escaper,name) or name
191 elseif tn ~= "number" then
192 name = tostring(name)
193 end
194 if indexed then
195 t[n] = f_start_array_key(depth,name)
196 else
197 t[n] = f_start_hash_key(depth,name)
198 end
199 else
200 if indexed then
201 t[n] = f_start_array_new(depth)
202 else
203 t[n] = f_start_hash_new(depth)
204 end
205 end
206 depth = depth + 1
207 if indexed then
208 for i=1,size do
209 if i > 1 then
210 n = n + 1 t[n] = ","
211 end
212 local v = root[i]
213 local tv = type(v)
214 if tv == "number" then
215 n = n + 1 t[n] = f_val_num(depth,v)
216 elseif tv == "string" then
217 v = lpegmatch(escaper,v) or v
218 n = n + 1 t[n] = f_val_str(depth,v)
219 elseif tv == "table" then
220 if next(v) then
221 local st = is_simple_table(v)
222 if st then
223 n = n + 1 t[n] = f_val_seq(depth,st)
224 else
225 tojsonpp(v,nil,depth,level+1,#v)
226 end
227 else
228 n = n + 1
229 t[n] = f_val_empty(depth)
230 end
231 elseif tv == "boolean" then
232 n = n + 1
233 if v then
234 t[n] = f_val_yes(depth,v)
235 else
236 t[n] = f_val_nop(depth,v)
237 end
238 else
239 n = n + 1
240 t[n] = f_val_null(depth)
241 end
242 end
243 elseif next(root) then
244 local sk = sortedkeys(root)
245 for i=1,#sk do
246 if i > 1 then
247 n = n + 1 t[n] = ","
248 end
249 local k = sk[i]
250 local v = root[k]
251 local tv = type(v)
252 local tk = type(k)
253 if tv == "number" then
254 if tk == "number" then
255 n = n + 1 t[n] = f_key_val_num(depth,k,v)
256 elseif tk == "string" then
257 k = lpegmatch(escaper,k) or k
258 n = n + 1 t[n] = f_key_val_num(depth,k,v)
259 end
260 elseif tv == "string" then
261 if tk == "number" then
262 v = lpegmatch(escaper,v) or v
263 n = n + 1 t[n] = f_key_val_str(depth,k,v)
264 elseif tk == "string" then
265 k = lpegmatch(escaper,k) or k
266 v = lpegmatch(escaper,v) or v
267 n = n + 1 t[n] = f_key_val_str(depth,k,v)
268 end
269 elseif tv == "table" then
270 local l = #v
271 if l > 0 then
272 local st = is_simple_table(v)
273 if not st then
274 tojsonpp(v,k,depth,level+1,l)
275 elseif tk == "number" then
276 n = n + 1 t[n] = f_key_val_seq(depth,k,st)
277 elseif tk == "string" then
278 k = lpegmatch(escaper,k) or k
279 n = n + 1 t[n] = f_key_val_seq(depth,k,st)
280 end
281 elseif next(v) then
282 tojsonpp(v,k,depth,level+1,0)
283 end
284 elseif tv == "boolean" then
285 if tk == "number" then
286 n = n + 1
287 if v then
288 t[n] = f_key_val_yes(depth,k)
289 else
290 t[n] = f_key_val_nop(depth,k)
291 end
292 elseif tk == "string" then
293 k = lpegmatch(escaper,k) or k
294 n = n + 1
295 if v then
296 t[n] = f_key_val_yes(depth,k)
297 else
298 t[n] = f_key_val_nop(depth,k)
299 end
300 end
301 else
302 if tk == "number" then
303 n = n + 1
304 t[n] = f_key_val_null(depth,k)
305 elseif tk == "string" then
306 k = lpegmatch(escaper,k) or k
307 n = n + 1
308 t[n] = f_key_val_null(depth,k)
309 end
310 end
311 end
312 end
313 n = n + 1
314 if indexed then
315 t[n] = f_stop_array(depth-1)
316 else
317 t[n] = f_stop_hash(depth-1)
318 end
319 end
320 end
321
322 local function tojson(value,n)
323 local kind = type(value)
324 if kind == "table" then
325 local done = false
326 local size = #value
327 if size == 0 then
328 for k, v in next, value do
329 if done then
330
331 n = n + 1 ; t[n] = ',"'
332 else
333
334 n = n + 1 ; t[n] = '{"'
335 done = true
336 end
337 n = n + 1 ; t[n] = lpegmatch(escaper,k) or k
338 n = n + 1 ; t[n] = '":'
339 t, n = tojson(v,n)
340 end
341 if done then
342 n = n + 1 ; t[n] = "}"
343 else
344 n = n + 1 ; t[n] = "{}"
345 end
346 elseif size == 1 then
347
348 n = n + 1 ; t[n] = "["
349 t, n = tojson(value[1],n)
350 n = n + 1 ; t[n] = "]"
351 else
352 for i=1,size do
353 if done then
354 n = n + 1 ; t[n] = ","
355 else
356 n = n + 1 ; t[n] = "["
357 done = true
358 end
359 t, n = tojson(value[i],n)
360 end
361 n = n + 1 ; t[n] = "]"
362 end
363 elseif kind == "string" then
364 n = n + 1 ; t[n] = '"'
365 n = n + 1 ; t[n] = lpegmatch(escaper,value) or value
366 n = n + 1 ; t[n] = '"'
367 elseif kind == "number" then
368 n = n + 1 ; t[n] = value
369 elseif kind == "boolean" then
370 n = n + 1 ; t[n] = tostring(value)
371 else
372 n = n + 1 ; t[n] = "null"
373 end
374 return t, n
375 end
376
377
378
379 local function jsontostring(value,pretty)
380
381 local kind = type(value)
382 if kind == "table" then
383 if not escaper then
384 local escapes = {
385 ["\\"] = "\\u005C",
386 ["\""] = "\\u0022",
387 }
388 for i=0,0x1F do
389 escapes[utfchar(i)] = format("\\u%04X",i)
390 end
391 escaper = Cs( (
392 (R('\0\x20') + S('\"\\')) / escapes
393 + P(1)
394 )^1 )
395
396 end
397
398 t = { }
399 n = 0
400 if pretty then
401 tojsonpp(value,name,0,0,#value)
402 value = concat(t,"",1,n)
403 else
404 t, n = tojson(value,0)
405 value = concat(t,"",1,n)
406 end
407 t = nil
408 n = 0
409 return value
410 elseif kind == "string" or kind == "number" then
411 return lpegmatch(escaper,value) or value
412 else
413 return tostring(value)
414 end
415 end
416
417 json.tostring = jsontostring
418
419 function json.tojson(value)
420 return jsontostring(value,true)
421 end
422
423end
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443return json
444 |