json.scm
1 | ;;;; Copyright (C) 2019, 2020 Julien Lepiller <julien@lepiller.eu> |
2 | ;;;; |
3 | ;;;; This library is free software; you can redistribute it and/or |
4 | ;;;; modify it under the terms of the GNU Lesser General Public |
5 | ;;;; License as published by the Free Software Foundation; either |
6 | ;;;; version 3 of the License, or (at your option) any later version. |
7 | ;;;; |
8 | ;;;; This library is distributed in the hope that it will be useful, |
9 | ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of |
10 | ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
11 | ;;;; Lesser General Public License for more details. |
12 | ;;;; |
13 | ;;;; You should have received a copy of the GNU Lesser General Public |
14 | ;;;; License along with this library; if not, write to the Free Software |
15 | ;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
16 | ;;;; |
17 | |
18 | (define-module (jsonld json) |
19 | #:use-module (json) |
20 | #:use-module (jsonld download) |
21 | #:use-module (jsonld iri) |
22 | #:use-module (web client) |
23 | #:use-module (web response) |
24 | #:use-module (web uri) |
25 | #:use-module (rnrs bytevectors) |
26 | #:use-module (ice-9 match) |
27 | #:use-module (srfi srfi-1) |
28 | #:use-module (srfi srfi-9) |
29 | #:export (absolute-iri? |
30 | alist-set |
31 | alist-remove |
32 | alist-sort-by-key |
33 | array-add |
34 | array-append |
35 | blank-node? |
36 | for-each-pair |
37 | gen-delim? |
38 | graph-object? |
39 | json-array? |
40 | json-has-key? |
41 | json-keyword? |
42 | json-null |
43 | json-null? |
44 | json-object? |
45 | jsonld-error->string |
46 | keyword-form? |
47 | list-object? |
48 | make-jsonld-options |
49 | merge-json |
50 | node-object? |
51 | not-null-or-false |
52 | processing-mode-1.0? |
53 | relative-iri? |
54 | same-json? |
55 | identical-json? |
56 | scalar? |
57 | scalar-array? |
58 | set-object? |
59 | simple-graph-object? |
60 | string-array?)) |
61 | |
62 | ;; This module defines a bunch of functions used to test or modify json |
63 | ;; documents. |
64 | |
65 | (define-syntax for-each-pair |
66 | (syntax-rules () |
67 | ((_ thunk alist) |
68 | (for-each |
69 | (match-lambda |
70 | ((k . v) (thunk k v))) |
71 | alist)))) |
72 | |
73 | (define (alist-set alist key value) |
74 | "Return a new alist that is the same as @var{alist}, but whose @var{key} |
75 | is now associated with @var{value}. This removes the old association of |
76 | @var{key} if any." |
77 | (match alist |
78 | (() (list (cons key value))) |
79 | (((k . v) alist ...) |
80 | (if (equal? k key) |
81 | (cons (cons key value) alist) |
82 | (cons (cons k v) (alist-set alist key value)))))) |
83 | |
84 | (define (alist-remove alist key) |
85 | "Return a new alist that is the same as @var{alist}, but whose @var{key} |
86 | is not associated with anything anymore." |
87 | (match alist |
88 | (() '()) |
89 | (((k . v) alist ...) |
90 | (if (equal? key k) |
91 | alist |
92 | (cons (cons k v) (alist-remove alist key)))))) |
93 | |
94 | (define (alist-key-less e1 e2) |
95 | (match `(,e1 . ,e2) |
96 | (((k1 . v1) . (k2 . v2)) |
97 | (string<=? k1 k2)))) |
98 | |
99 | (define* (alist-sort-by-key alist #:key (less alist-key-less)) |
100 | "Sort an alist @var{alist} by its keys." |
101 | (sort alist less)) |
102 | |
103 | (define (scalar? v) |
104 | "Whether a value is a scalar value, in the sense of the Json specification." |
105 | (or (number? v) (string? v) (member v (list #t #f)))) |
106 | |
107 | (define (blank-node? node) |
108 | "Whether a value is a blank node identifier, in the sense of the JsonLD |
109 | specification." |
110 | (and (string? node) (> (string-length node) 1) (equal? (substring node 0 2) "_:"))) |
111 | |
112 | (define (json-keyword? k) |
113 | "Whether a value @var{k} is a keyword, in the sense of the JsonLD specification, |
114 | version 1.1." |
115 | (member |
116 | k |
117 | '(":" "@base" "@container" "@context" "@direction" "@graph" "@id" "@import" |
118 | "@included" "@index" "@json" "@language" "@list" "@nest" "@none" |
119 | "@prefix" "@propagate" "@protected" "@reverse" "@set" "@type" "@value" |
120 | "@version" "@vocab"))) |
121 | |
122 | (define (json-array? v) |
123 | "Whether a value is a Json array." |
124 | (and (array? v) (not (string? v)))) |
125 | |
126 | (define (json-null? v) |
127 | (equal? v 'null)) |
128 | |
129 | (define json-null 'null) |
130 | |
131 | (define (json-object? v) |
132 | "Whether a value is a Json object." |
133 | (and (list? v) (not (json-null? v)))) |
134 | |
135 | (define (not-null-or-false v) |
136 | (and (not (json-null? v)) v)) |
137 | |
138 | (define (json-has-key? obj key) |
139 | "Whether a Json object @var{obj} has a @var{key}." |
140 | (let loop ((obj obj)) |
141 | (match obj |
142 | (((k . v) obj ...) |
143 | (or (equal? k key) (loop obj))) |
144 | (_ #f)))) |
145 | |
146 | (define (graph-object? v) |
147 | (and (json-has-key? v "@graph") |
148 | (null? (filter (lambda (kp) (not (member (car kp) '("@graph" "@id" "@index")))) |
149 | v)))) |
150 | |
151 | (define (simple-graph-object? v) |
152 | (and (graph-object? v) |
153 | (not (json-has-key? v "@id")))) |
154 | |
155 | (define (list-object? v) |
156 | (and (json-has-key? v "@list") |
157 | (null? (filter (lambda (kp) (not (member (car kp) '("@list" "@index")))) |
158 | v)))) |
159 | |
160 | (define (set-object? v) |
161 | (and (json-has-key? v "@list") |
162 | (null? (filter (lambda (kp) (not (member (car kp) '("@list" "@index")))) |
163 | v)))) |
164 | |
165 | (define (absolute-iri? value) |
166 | "Whether a value is an absolute IRI." |
167 | (and (string? value) |
168 | ;; XXX: actually, must be percent-encoded |
169 | (not (string-any #\space value)) |
170 | ;; XXX: actually, this doesn't accept "ρ.ηλ" for instance |
171 | (string->uri value))) |
172 | |
173 | (define (relative-iri? value) |
174 | "Whether a value is a relative IRI." |
175 | (and (string? value) (string->uri-reference value))) |
176 | |
177 | (define (string-array? v) |
178 | (and (array? v) (null? (filter (lambda (v) (not (string? v))) (array->list v))))) |
179 | |
180 | (define (scalar-array? v) |
181 | (and (array? v) (null? (filter (lambda (v) (not (scalar? v))) (array->list v))))) |
182 | |
183 | (define (array-add element array) |
184 | (let ((array (or array #()))) |
185 | (list->array 1 (append (array->list array) (list element))))) |
186 | |
187 | (define (array-append a1 a2) |
188 | (let ((a1 (or a1 #())) |
189 | (a2 (or a2 #()))) |
190 | (list->array 1 (append (array->list a1) (array->list a2))))) |
191 | |
192 | (define (merge-json a b) |
193 | (match b |
194 | (() a) |
195 | (((k . v) b ...) |
196 | (if (json-has-key? a k) |
197 | (merge-json a b) |
198 | (merge-json (cons (cons k v) a) b))))) |
199 | |
200 | (define (keyword-form? k) |
201 | (and |
202 | (string? k) |
203 | (match (string->list k) |
204 | (((? (lambda (k) (eq? k #\@)) l) |
205 | (? (lambda (m) (char-set-contains? char-set:letter m)) m) ...) |
206 | ;; only if there is actually something after @ |
207 | (> (string-length k) 1)) |
208 | (_ #f)))) |
209 | |
210 | (define (gen-delim? s) |
211 | (string-every (char-set #\: #\/ #\? #\# #\[ #\] #\@) s)) |
212 | |
213 | (define (processing-mode-1.0? mode) |
214 | (member mode '("jsonld-1.0" "json-ld-1.0"))) |
215 | |
216 | (define (node-object? o) |
217 | (and (json-object? o) |
218 | (not (json-has-key? o "@value")) |
219 | (not (json-has-key? o "@list")) |
220 | (not (json-has-key? o "@set")))) |
221 | |
222 | (define (has-identical-keys-of json other) |
223 | (let loop ((json json) (result #t)) |
224 | (match json |
225 | (#f (not other)) |
226 | (() result) |
227 | (((key . value) json ...) |
228 | (loop json (and result (identical-json? value (assoc-ref other key)))))))) |
229 | |
230 | (define (has-identical-values json other) |
231 | (let loop ((json json) (other other) (result #t)) |
232 | (match json |
233 | (() (if (null? other) result #f)) |
234 | ((v json ...) |
235 | (match other |
236 | (() #f) |
237 | ((v2 other ...) |
238 | (loop json other (and result (identical-json? v v2))))))))) |
239 | |
240 | (define (identical-json? json other) |
241 | "Compare two Json documents and returns whether they are the same, comparing |
242 | the keys, their values, their order and their presence in both documents. |
243 | This variant compares the value of blank nodes." |
244 | (match json |
245 | ((? array? json) |
246 | (and |
247 | (array? other) |
248 | (has-identical-values (array->list json) (array->list other)))) |
249 | ((? list? json) |
250 | (and (list? other) (has-identical-keys-of json other) |
251 | (has-identical-keys-of other json))) |
252 | (_ (equal? json other)))) |
253 | |
254 | (define (has-keys-of json other equivalences) |
255 | (let loop ((json json) (equivalences equivalences)) |
256 | (match json |
257 | (#f (and (not other) equivalences)) |
258 | (() equivalences) |
259 | (((key . value) json ...) |
260 | (if (blank-node? key) |
261 | (if (assoc-ref equivalences key) |
262 | (loop json (included-json? |
263 | value |
264 | (assoc-ref |
265 | other (assoc-ref equivalences key)) equivalences)) |
266 | (let loop2 ((candidates (filter (lambda (e) (blank-node? (car e))) |
267 | other))) |
268 | (match candidates |
269 | (() #f) |
270 | (((k . v) candidates ...) |
271 | (let ((res (included-json? |
272 | value |
273 | v |
274 | (cons (cons key k) equivalences)))) |
275 | (if res res (loop2 candidates))))))) |
276 | (loop json (included-json? value (assoc-ref other key) equivalences))))))) |
277 | |
278 | (define (has-same-values json other equivalences) |
279 | (let loop ((json json) (other other) (equivalences equivalences)) |
280 | (match json |
281 | (() (if (null? other) equivalences #f)) |
282 | ((v json ...) |
283 | (match other |
284 | (() #f) |
285 | ((v2 other ...) |
286 | (loop json other (included-json? v v2 equivalences)))))))) |
287 | |
288 | (define (included-json? json other equivalences) |
289 | (match json |
290 | ((? json-array? json) |
291 | (and |
292 | (array? other) |
293 | (has-same-values (array->list json) (array->list other) equivalences))) |
294 | ((? list? json) |
295 | (and (list? other) (has-keys-of json other equivalences))) |
296 | ((? blank-node? json) |
297 | (and (blank-node? other) |
298 | (if (assoc-ref json equivalences) |
299 | (and (equal? (assoc-ref json equivalences) other) equivalences) |
300 | (cons (cons json other) equivalences)))) |
301 | (_ (and (equal? json other) equivalences)))) |
302 | |
303 | (define (same-json? json other) |
304 | "Compare two Json documents and returns whether they are the same, comparing |
305 | the keys, their values, their order and their presence in both documents. |
306 | This variant tries to map blank nodes from one object to the other." |
307 | (and (included-json? json other '()) (included-json? other json '()))) |
308 | |
309 | (define (jsonld-error->string err) |
310 | "Convert an error to a string." |
311 | (match err |
312 | ('colliding-keywords "colliding keywords") |
313 | ('conflicting-indexes "conflicting indexes") |
314 | ('cyclic-iri-mapping "cyclic IRI mapping") |
315 | ('invalid-@id-value "invalid @id value") |
316 | ('invalid-@index-value "invalid @index value") |
317 | ('invalid-@nest-value "invalid @nest value") |
318 | ('invalid-@prefix-value "invalid @prefix value") |
319 | ('invalid-@propagate-value "invalid @propagate value") |
320 | ('invalid-@protected-value "invalid @protected value") |
321 | ('invalid-@reverse-value "invalid @reverse value") |
322 | ('invalid-@import-value "invalid @import value") |
323 | ('invalid-@version-value "invalid @version value") |
324 | ('invalid-base-iri "invalid base IRI") |
325 | ('invalid-base-direction "invalid base direction") |
326 | ('invalid-container-mapping "invalid container mapping") |
327 | ('invalid-context-entry "invalid context entry") |
328 | ('invalid-context-nullification "invalid context nullification") |
329 | ('invalid-default-language "invalid default language") |
330 | ('invalid-@included-value "invalid @included value") |
331 | ('invalid-iri-mapping "invalid IRI mapping") |
332 | ('invalid-json-literal "invalid JSON literal") |
333 | ('invalid-keyword-alias "invalid keyword alias") |
334 | ('invalid-language-map-value "invalid language map value") |
335 | ('invalid-language-mapping "invalid language mapping") |
336 | ('invalid-language-tagged-string "invalid language-tagged string") |
337 | ('invalid-language-tagged-value "invalid language-tagged value") |
338 | ('invalid-local-context "invalid local context") |
339 | ('invalid-remote-context "invalid remote context") |
340 | ('invalid-reverse-property "invalid reverse property") |
341 | ('invalid-reverse-property-map "invalid reverse property map") |
342 | ('invalid-reverse-property-value "invalid reverse property value") |
343 | ('invalid-scoped-context "invalid scoped context") |
344 | ('invalid-script-element "invalid script element") |
345 | ('invalid-set-or-list-object "invalid set or list object") |
346 | ('invalid-term-definition "invalid term definition") |
347 | ('invalid-type-mapping "invalid type mapping") |
348 | ('invalid-type-value "invalid type value") |
349 | ('invalid-typed-value "invalid typed value") |
350 | ('invalid-value-object "invalid value object") |
351 | ('invalid-value-object-value "invalid value object value") |
352 | ('invalid-vocab-mapping "invalid vocab mapping") |
353 | ('iri-confused-with-prefix "IRI confused with prefix") |
354 | ('keyword-redefinition "keyword redefinition") |
355 | ('loading-document-failed "loading document failed") |
356 | ('loading-remote-context-failed "loading remote context failed") |
357 | ('multiple-context-link-headers "multiple context link headers") |
358 | ('processing-mode-conflict "processing mode conflict") |
359 | ('protected-term-redefinition "protected term redefinition") |
360 | ('context-overflow "context overflow") |
361 | (_ (format #f "unknown error ~a" err)))) |
362 |