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