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 |