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