context-processing.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 context-processing) |
19 | #:use-module (jsonld context) |
20 | #:use-module (jsonld create-term-definition) |
21 | #:use-module (jsonld download) |
22 | #:use-module (jsonld iri) |
23 | #:use-module (jsonld iri-expansion) |
24 | #:use-module (jsonld json) |
25 | #:use-module (jsonld options) |
26 | #:use-module (json) |
27 | #:use-module (web uri) |
28 | #:use-module (rnrs bytevectors) |
29 | #:use-module (ice-9 match) |
30 | #:use-module (srfi srfi-9) |
31 | #:export (context-processing)) |
32 | |
33 | (define* (context-processing |
34 | active-context local-context base-url |
35 | #:key (remote-contexts '()) (override-protected? #f) (propagate? #t) |
36 | (validate-scoped-context? #t) |
37 | (options (new-jsonld-options))) |
38 | "Process a context. This is an implementation of the context processing |
39 | algorithm defined in the JsonLD API specification. |
40 | |
41 | See @url{https://www.w3.org/TR/2014/REC-json-ld-api-20140116}." |
42 | ;; 1 |
43 | (define result active-context) |
44 | ;; 2 |
45 | (when (json-has-key? local-context "@propagate") |
46 | (set! propagate? (assoc-ref local-context "@propagate"))) |
47 | ;; 3 |
48 | (unless (or propagate? (active-context-previous active-context)) |
49 | (set! result (update-active-context result #:previous active-context))) |
50 | ;; 4 |
51 | (if (json-array? local-context) |
52 | (set! local-context (array->list local-context)) |
53 | (set! local-context (list local-context))) |
54 | ;; 5 |
55 | (for-each |
56 | (lambda (context) |
57 | (match context |
58 | ;; 5.1 |
59 | (#nil |
60 | (unless (or override-protected? |
61 | (null? (filter |
62 | term-definition-protected? |
63 | (map cdr (active-context-definitions result))))) |
64 | (throw 'invalid-context-nullification)) |
65 | (let ((original-base (active-context-original-base active-context))) |
66 | (if propagate? |
67 | (set! result (new-active-context #:previous result |
68 | #:base original-base |
69 | #:original-base original-base)) |
70 | (set! result (new-active-context #:base original-base |
71 | #:original-base original-base))))) |
72 | ;; 5.2 |
73 | ((? string? context) |
74 | ;; 5.2.1 |
75 | (set! context (resolve-iri base-url context)) |
76 | (unless (absolute-iri? context) |
77 | (throw 'loading-document-failed context)) |
78 | ;; 5.2.2 |
79 | ;; A processor-defined limit, here we choose 10 |
80 | (when (> (length remote-contexts) 10) |
81 | (throw 'context-overflow-error)) |
82 | (set! remote-contexts (cons context remote-contexts)) |
83 | ;; 5.2.3 |
84 | (when validate-scoped-context? |
85 | ;; 5.2.4 |
86 | (let ((context-document #f)) |
87 | (catch #t |
88 | (lambda () |
89 | (set! context-document |
90 | ((jsonld-options-document-loader options) |
91 | context |
92 | #:profile "http://www.w3.org/ns/json-ld#context" |
93 | #:request-profile "http://www.w3.org/ns/json-ld#context")) |
94 | (set! context (json-document-document context-document))) |
95 | (lambda (key . value) |
96 | (apply throw 'loading-remote-context-failed key value))) |
97 | ;; 5.2.5 |
98 | (if (json-has-key? context "@context") |
99 | (set! context (assoc-ref context "@context")) |
100 | (throw 'invalid-remote-context)) |
101 | ;; 5.2.6 |
102 | (set! result (context-processing |
103 | result context |
104 | (json-document-document-url context-document) |
105 | #:remote-contexts remote-contexts |
106 | #:validate-scoped-context? validate-scoped-context? |
107 | #:options options))))) |
108 | ;; 5.4: if it's a Json object (also see below, if it's not, for 5.3) |
109 | ((? json-object? context) |
110 | ;; 5.5: If it has an @version key... |
111 | (when (json-has-key? context "@version") |
112 | ;; 5.5.1 |
113 | (unless (equal? (assoc-ref context "@version") 1.1) |
114 | (throw 'invalid-@version-value (assoc-ref context "@version"))) |
115 | ;; 5.5.2 |
116 | (when (processing-mode-1.0? (jsonld-options-processing-mode options)) |
117 | (throw 'processing-mode-conflict))) |
118 | |
119 | ;; 5.6: If it has an @import key... |
120 | (when (json-has-key? context "@import") |
121 | ;; 5.6.1 |
122 | (when (processing-mode-1.0? (jsonld-options-processing-mode options)) |
123 | (throw 'invalid-context-entry)) |
124 | ;; 5.6.2 |
125 | (unless (string? (assoc-ref context "@import")) |
126 | (throw 'invalid-@import-value)) |
127 | ;; 5.6.3 |
128 | (let ((import (resolve-iri |
129 | base-url |
130 | (assoc-ref context "@import")))) |
131 | ;; 5.6.4 |
132 | (catch #t |
133 | (lambda () |
134 | (set! import ((jsonld-options-document-loader options) |
135 | import |
136 | #:profile "http://www.w3.org/ns/json-ld#context" |
137 | #:request-profile "http://www.w3.org/ns/json-ld#context")) |
138 | (set! import (json-document-document import))) |
139 | (lambda (key . value) |
140 | (apply throw 'loading-remote-context-failed key value))) |
141 | ;; 5.6.6 |
142 | (unless (and (json-has-key? import "@context") |
143 | (json-object? (assoc-ref import "@context"))) |
144 | (throw 'invalid-remote-context import)) |
145 | (let ((import-context (assoc-ref import "@context"))) |
146 | ;; 5.6.7 |
147 | (when (json-has-key? import-context "@import") |
148 | (throw 'invalid-context-entry)) |
149 | ;; 5.6.8 |
150 | (set! context (merge-json context import-context))))) |
151 | |
152 | ;; 5.7: If it has an @base key... |
153 | (when (and (json-has-key? context "@base") (null? remote-contexts)) |
154 | ;; 5.7.1 |
155 | (let ((value (assoc-ref context "@base"))) |
156 | (cond |
157 | ;; 5.7.2 |
158 | ((equal? value #nil) |
159 | (set! result (update-active-context result #:base #nil))) |
160 | ;; 5.7.3 |
161 | ((absolute-iri? value) |
162 | (set! result (update-active-context result #:base value))) |
163 | (else |
164 | (let ((iri (resolve-iri (active-context-base result) value))) |
165 | (if (and (absolute-iri? iri) |
166 | (active-context-base result)) |
167 | ;; 5.7.4 |
168 | (set! result (update-active-context result #:base iri)) |
169 | ;; 5.7.5 |
170 | (throw 'invalid-base-iri iri (active-context-base result)))))))) |
171 | |
172 | ;; 5.8: If it has an @vocab key... |
173 | (when (json-has-key? context "@vocab") |
174 | ;; 5.8.1 |
175 | (let ((value (assoc-ref context "@vocab"))) |
176 | (if (equal? value #nil) |
177 | ;; 5.8.2 |
178 | (set! result (update-active-context result #:vocab #f)) |
179 | ;; 5.8.3 |
180 | (let ((value |
181 | (assoc-ref |
182 | (iri-expansion result value |
183 | #:vocab? #t |
184 | #:document-relative? #t) |
185 | "iri"))) |
186 | (if (or (absolute-iri? value) (blank-node? value)) |
187 | (set! result (update-active-context result #:vocab value)) |
188 | (throw 'invalid-vocab-mapping value)))))) |
189 | |
190 | ;; 5.9: If it has an @language key... |
191 | (when (json-has-key? context "@language") |
192 | ;; 5.9.1 |
193 | (let ((value (assoc-ref context "@language"))) |
194 | (if (equal? value #nil) |
195 | ;; 5.9.2 |
196 | (set! result (update-active-context result #:language #f)) |
197 | ;; 5.9.3 |
198 | (if (string? value) |
199 | (set! result (update-active-context result #:language value)) |
200 | (throw 'invalid-default-language))))) |
201 | |
202 | ;; 5.10: If it has an @direction key... |
203 | (when (json-has-key? context "@direction") |
204 | ;; 5.10.1 |
205 | (when (processing-mode-1.0? (jsonld-options-processing-mode options)) |
206 | (throw 'invalid-context-entry)) |
207 | (let ((value (assoc-ref context "@direction"))) |
208 | (if (equal? value #nil) |
209 | ;; 5.10.2 |
210 | (set! result (update-active-context result #:direction #f)) |
211 | ;; 5.10.3 |
212 | (if (member value '("ltr" "rtl")) |
213 | (set! result (update-active-context result #:direction value)) |
214 | (throw 'invalid-base-direction value))))) |
215 | |
216 | ;; 5.11: If it has an @propagate key... |
217 | (when (json-has-key? context "@propagate") |
218 | ;; 5.11.1 |
219 | (when (processing-mode-1.0? (jsonld-options-processing-mode options)) |
220 | (throw 'invalid-context-entry)) |
221 | ;; 5.11.2 |
222 | (unless (member (assoc-ref context "@propagate") '(#t #f)) |
223 | (throw 'invalid-@propagate-value))) |
224 | |
225 | ;; and now loop over key-value pair that are not @base, @vocab nor @language |
226 | ;; 5.12 |
227 | (let ((defined '())) |
228 | ;; 5.13 |
229 | (for-each-pair |
230 | (lambda (key value) |
231 | (unless (member key '("@base" "@direction" "@import" "@language" |
232 | "@propagate" "@protected" "@version" |
233 | "@vocab")) |
234 | (let* ((term-res (create-term-definition |
235 | result context key defined |
236 | #:base-url base-url |
237 | #:protected? (assoc-ref context "@protected") |
238 | #:override-protected? override-protected? |
239 | #:remote-contexts remote-contexts |
240 | #:validate-scoped-context? validate-scoped-context? |
241 | #:options options))) |
242 | (set! defined (assoc-ref term-res "defined")) |
243 | (set! result (assoc-ref term-res "active-context"))))) |
244 | context))) |
245 | ;; 5.3: if it's not a Json object |
246 | (_ (throw 'invalid-local-context context)))) |
247 | local-context) |
248 | result) |
249 |