object-to-rdf.scm
| 1 | ;;;; Copyright (C) 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 object-to-rdf) |
| 19 | #:use-module (ice-9 match) |
| 20 | #:use-module (ice-9 regex) |
| 21 | #:use-module (jsonld deserialize-jsonld) |
| 22 | #:use-module (jsonld iri) |
| 23 | #:use-module (jsonld json) |
| 24 | #:use-module (jsonld list-to-rdf) |
| 25 | #:use-module (json) |
| 26 | #:use-module ((rdf rdf) #:hide (blank-node?)) |
| 27 | #:use-module (srfi srfi-1) |
| 28 | #:export (object-to-rdf |
| 29 | well-formed-language-tag?)) |
| 30 | |
| 31 | (define (canonical-double value) |
| 32 | (let ((exponent (inexact->exact (floor (log10 value))))) |
| 33 | (string-append |
| 34 | (number->string (exact->inexact (/ value (expt 10 exponent)))) |
| 35 | "E" |
| 36 | (number->string exponent)))) |
| 37 | |
| 38 | (define (canonical-json value) |
| 39 | (cond |
| 40 | ((member value `(#t #f ,json-null)) |
| 41 | value) |
| 42 | ((string? value) value) |
| 43 | ((number? value) |
| 44 | (if (integer? (inexact->exact value)) |
| 45 | (inexact->exact value) |
| 46 | value)) |
| 47 | ((list? value) |
| 48 | (map |
| 49 | (match-lambda |
| 50 | ((k . v) |
| 51 | (cons k (canonical-json v)))) |
| 52 | (alist-sort-by-key value))) |
| 53 | ((array? value) |
| 54 | (list->array |
| 55 | 1 |
| 56 | (fold-right |
| 57 | (lambda (val result) |
| 58 | (cons (canonical-json val) result)) |
| 59 | '() |
| 60 | (array->list value)))))) |
| 61 | |
| 62 | (define (well-formed-language-tag? tag) |
| 63 | (let ((match (string-match "[a-z]{1,8}(-[0-9a-z]{1,8})*" tag))) |
| 64 | (if match |
| 65 | (let* ((match (cadr (array->list match))) |
| 66 | (fst (car match)) |
| 67 | (snd (cdr match))) |
| 68 | (and (equal? fst 0) |
| 69 | (equal? snd (string-length tag)))) |
| 70 | #f))) |
| 71 | |
| 72 | (define* (object-to-rdf generate-blank-node rdf-direction item list-triples) |
| 73 | (let ((result json-null)) |
| 74 | (cond |
| 75 | ;; 1 |
| 76 | ((and (node-object? item) (not (well-formed? (assoc-ref item "@id")))) |
| 77 | (set! result json-null)) |
| 78 | ;; 2 |
| 79 | ((node-object? item) |
| 80 | (set! result (assoc-ref item "@id"))) |
| 81 | ;; 3 |
| 82 | ((list-object? item) |
| 83 | (let* ((res (list-to-rdf generate-blank-node rdf-direction |
| 84 | (array->list (assoc-ref item "@list")) |
| 85 | list-triples)) |
| 86 | (lst (assoc-ref res "list-triples")) |
| 87 | (res (assoc-ref res "result"))) |
| 88 | (set! result res) |
| 89 | (set! list-triples lst))) |
| 90 | (else |
| 91 | ;; 4 |
| 92 | (let ((value (assoc-ref item "@value")) |
| 93 | (datatype (or (assoc-ref item "@type") json-null))) |
| 94 | (cond |
| 95 | ;; 6 |
| 96 | ((and (not (json-null? datatype)) (not (equal? datatype "@json")) |
| 97 | (not (well-formed? datatype))) |
| 98 | (set! result json-null)) |
| 99 | ;; 7 |
| 100 | ((and |
| 101 | (json-has-key? item "@language") |
| 102 | (not (well-formed-language-tag? (assoc-ref item "@language")))) |
| 103 | (set! result json-null)) |
| 104 | (else |
| 105 | ;; 8 |
| 106 | (when (equal? datatype "@json") |
| 107 | (set! value (scm->json-string (canonical-json value))) |
| 108 | (set! datatype (rdf-iri "JSON"))) |
| 109 | (cond |
| 110 | ;; 9 |
| 111 | ((boolean? value) |
| 112 | (when (json-null? datatype) |
| 113 | (set! datatype (xsd-iri "boolean"))) |
| 114 | (set! value (if value "true" "false"))) |
| 115 | ;; 10 |
| 116 | ((and (number? value) |
| 117 | (or (not (integer? (inexact->exact value))) |
| 118 | (>= (abs value) (expt 10 21)) |
| 119 | (equal? datatype (xsd-iri "double")))) |
| 120 | (when (json-null? datatype) |
| 121 | (set! datatype (xsd-iri "double"))) |
| 122 | (set! value (canonical-double value))) |
| 123 | ;; 11 |
| 124 | ((number? value) |
| 125 | (set! value (number->string (inexact->exact value))) |
| 126 | (when (json-null? datatype) |
| 127 | (set! datatype (xsd-iri "integer")))) |
| 128 | ;; 12 |
| 129 | ((json-null? datatype) |
| 130 | (set! datatype |
| 131 | (if (json-has-key? item "@language") |
| 132 | (rdf-iri "langString") |
| 133 | (xsd-iri "string"))))) |
| 134 | (if (and (json-has-key? item "@direction") (not (equal? rdf-direction #f))) |
| 135 | ;; 13 |
| 136 | (let* ((language (or (not-null-or-false (assoc-ref item "@language")) "")) |
| 137 | (language (string-downcase language))) |
| 138 | (if (equal? rdf-direction "i18n-datatype") |
| 139 | ;; 13.2 |
| 140 | (begin |
| 141 | (set! datatype |
| 142 | (string-append "https://www.w3.org/ns/i18n#" language "_" |
| 143 | (assoc-ref item "@direction"))) |
| 144 | (set! result |
| 145 | (make-rdf-literal value datatype #f))) |
| 146 | ;; 13.3 |
| 147 | (when (equal? rdf-direction "compound-literal") |
| 148 | (let ((literal (blank-node->rdf-blank-node |
| 149 | (generate-blank-node json-null)))) |
| 150 | ;; 13.3.2 |
| 151 | (set! list-triples |
| 152 | (cons |
| 153 | ;; XXX: the spec says "of the @value in item as object", |
| 154 | ;; but should it be "value" instead? |
| 155 | (make-rdf-triple literal (rdf-iri "value") |
| 156 | (make-rdf-literal |
| 157 | (assoc-ref item "@value") |
| 158 | (xsd-iri "string") |
| 159 | #f)) |
| 160 | list-triples)) |
| 161 | ;; 13.3.3 |
| 162 | (when (json-has-key? item "@language") |
| 163 | (set! list-triples |
| 164 | (cons |
| 165 | (make-rdf-triple literal (rdf-iri "language") |
| 166 | (make-rdf-literal |
| 167 | language |
| 168 | (xsd-iri "string") |
| 169 | #f)) |
| 170 | list-triples))) |
| 171 | (set! list-triples |
| 172 | (cons |
| 173 | (make-rdf-triple literal (rdf-iri "direction") |
| 174 | (make-rdf-literal |
| 175 | (assoc-ref item "@direction") |
| 176 | (xsd-iri "string") |
| 177 | #f)) |
| 178 | list-triples)) |
| 179 | (set! result literal))))) |
| 180 | ;; 14 |
| 181 | (set! result |
| 182 | (make-rdf-literal value datatype (assoc-ref item "@language"))))))))) |
| 183 | (when (blank-node? result) |
| 184 | (set! result (blank-node->rdf-blank-node result))) |
| 185 | `(("result" . ,result) |
| 186 | ("list-triples" . ,list-triples)))) |
| 187 |