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 |