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