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