guile-fediverse/activitystreams/ontology.scm

ontology.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 (activitystreams ontology)
19
  #:use-module (ice-9 match)
20
  #:use-module (srfi srfi-1)
21
  #:use-module (srfi srfi-9)
22
  #:use-module (jsonld)
23
  #:use-module (jsonld json)
24
  #:export (make-ontology
25
            ontology?
26
            ontology-context
27
            ontology-datatypes
28
            ontology-properties
29
30
            make-as-type
31
            as-type?
32
            as-type-label
33
            as-type-uri
34
            as-type-comment
35
            as-type-subclass-of
36
            build-as-type
37
38
            make-as-property
39
            as-property?
40
            as-property-label
41
            as-property-uri
42
            as-property-domain
43
            as-property-range
44
            as-property-functional?
45
            as-property-subproperty-of
46
            as-property-comment
47
            build-as-property
48
49
            make-as-document
50
            as-document?
51
            as-document-types
52
            as-document-properties
53
54
            make-as-string
55
            as-string?
56
            as-string-value
57
            as-string-language
58
            as-string-direction
59
60
            make-as-typed-value
61
            as-typed-value?
62
            as-typed-value-value
63
            as-typed-value-type
64
65
            merge-ontologies
66
            subproperty?
67
            subtype?
68
            as-ref
69
            json->as-document
70
            uri->as-document
71
            as-document->json
72
            as-document->graphviz))
73
74
(define-record-type <ontology>
75
  (make-ontology context datatypes properties)
76
  ontology?
77
  (context    ontology-context)
78
  (datatypes  ontology-datatypes)
79
  (properties ontology-properties))
80
81
(define as-vocab "https://www.w3.org/ns/activitystreams#")
82
83
(define-record-type as-type
84
  (make-as-type label uri comment subclass-of)
85
  as-type?
86
  (label       as-type-label)
87
  (uri         as-type-uri)
88
  (comment     as-type-comment)
89
  (subclass-of as-type-subclass-of))
90
91
(define* (build-as-type label #:key (uri (string-append as-vocab label))
92
                        (comment "") (subclass-of '()))
93
  (make-as-type label uri comment subclass-of))
94
95
(define-record-type as-property
96
  (make-as-property label uri range domain functional? subproperty-of comment)
97
  as-property?
98
  (label          as-property-label)
99
  (uri            as-property-uri)
100
  (domain         as-property-domain)
101
  (range          as-property-range)
102
  (functional?    as-property-functional?)
103
  (subproperty-of as-property-subproperty-of)
104
  (comment        as-property-comment))
105
106
(define* (build-as-property label domain range
107
                            #:key (uri (string-append as-vocab label))
108
                            (functional? #f) (subproperty-of '()) (comment ""))
109
  (make-as-property label uri range domain functional? subproperty-of comment))
110
111
(define-record-type as-document
112
  (make-as-document types properties)
113
  as-document?
114
  (types      as-document-types)
115
  (properties as-document-properties))
116
117
(define-record-type as-string
118
  (make-as-string value language direction)
119
  as-string?
120
  (value     as-string-value)
121
  (language  as-string-language)
122
  (direction as-string-direction))
123
124
(define-record-type as-typed-value
125
  (make-as-typed-value value type)
126
  as-typed-value?
127
  (value as-typed-value-value)
128
  (type  as-typed-value-type))
129
130
(define (uniq lst)
131
  (let loop ((lst lst) (result '()))
132
    (match lst
133
      (() result)
134
      ((elem lst ...)
135
       (if (member elem result)
136
           (loop lst result)
137
           (loop lst (cons elem result)))))))
138
139
(define (alist-set lst key value)
140
  (match lst
141
    (() `((,key . ,value)))
142
    (((k . v) lst ...)
143
     (if (equal? k key)
144
         (cons (cons key value) lst)
145
         (cons (cons k v) (alist-set lst key value))))))
146
147
(define (merge-domains d1 d2)
148
  (uniq
149
    (if (list? d1)
150
        (if (list? d2)
151
            (append d1 d2)
152
            (cons d2 d1))
153
        (if (list? d2)
154
            (cons d1 d2)
155
            (list d1 d2)))))
156
157
(define (merge-ranges r1 r2)
158
  (uniq
159
    (if (list? r1)
160
        (if (list? r2)
161
            (append r1 r2)
162
            (cons r2 r1))
163
        (if (list? r2)
164
            (cons r1 r2)
165
            (list r1 r2)))))
166
167
(define (fix-types datatypes)
168
  (define (fix-datatype type)
169
    (if (as-type? type)
170
        (let ((candidates
171
                (filter (lambda (t) (equal? (as-type-uri t) (as-type-uri type)))
172
                        datatypes)))
173
          (if (null? candidates)
174
              type
175
              (car candidates)))
176
        type))
177
178
  (let loop ((to-fix datatypes) (result '()))
179
    (match to-fix
180
      (() result)
181
      ((type to-fix ...)
182
       (loop
183
         to-fix
184
         (cons
185
           (make-as-type
186
             (as-type-label type)
187
             (as-type-uri type)
188
             (as-type-comment type)
189
             (map fix-datatype (as-type-subclass-of type)))
190
           result))))))
191
192
(define (merge-datatypes datatypes)
193
  (let loop ((result '()) (datatypes (apply append datatypes)))
194
    (match datatypes
195
      (() (map cdr result))
196
      ((type datatypes ...)
197
       (loop
198
         (let ((previous (assoc-ref result (as-type-uri type))))
199
           (if previous
200
               (alist-set result
201
                 (as-type-uri type)
202
                 (make-as-type
203
                   (as-type-label type)
204
                   (as-type-uri type)
205
                   (or (as-type-comment previous) (as-type-comment type))
206
                   (uniq (append (as-type-subclass-of previous)
207
                                 (as-type-subclass-of type)))))
208
               (cons (cons (as-type-uri type) type) result)))
209
         datatypes)))))
210
211
(define (fix-properties datatypes properties)
212
  (define (fix-datatype type)
213
    (if (as-type? type)
214
        (let ((candidates
215
                (filter (lambda (t) (equal? (as-type-uri t) (as-type-uri type)))
216
                        datatypes)))
217
          (if (null? candidates)
218
              type
219
              (car candidates)))
220
        type))
221
222
  (define (fix-property prop)
223
    (if (as-property? prop)
224
        (let ((candidates
225
                (filter (lambda (p) (equal? (as-property-uri p) (as-property-uri prop)))
226
                        properties)))
227
          (if (null? candidates)
228
              prop
229
              (car candidates)))
230
        prop))
231
232
  (let loop ((to-fix properties) (result '()))
233
    (match to-fix
234
      (() result)
235
      ((prop to-fix ...)
236
       (let ((domain (as-property-domain prop))
237
             (range (as-property-range prop)))
238
         (loop
239
           to-fix
240
           (cons
241
             (make-as-property
242
               (as-property-label prop)
243
               (as-property-uri prop)
244
               (if (list? domain)
245
                   (map fix-property (map fix-datatype domain))
246
                   (fix-property (fix-datatype domain)))
247
               (if (list? range)
248
                   (map fix-property (map fix-datatype range))
249
                   (fix-property (fix-datatype range)))
250
               (as-property-functional? prop)
251
               (map fix-property (as-property-subproperty-of prop))
252
               (as-property-comment prop))
253
             result)))))))
254
255
(define (merge-properties properties)
256
  (let loop ((result '()) (properties (apply append properties)))
257
    (match properties
258
      (() (map cdr result))
259
      ((prop properties ...)
260
       (loop
261
         (let ((previous (assoc-ref result (as-property-uri prop))))
262
           (if previous
263
               (alist-set result
264
                 (as-property-uri prop)
265
                 (make-as-property
266
                   (as-property-label prop)
267
                   (as-property-uri prop)
268
                   (merge-domains (as-property-domain previous)
269
                                  (as-property-domain prop))
270
                   (merge-ranges (as-property-range previous)
271
                                 (as-property-range prop))
272
                   (and (as-property-functional? previous)
273
                        (as-property-functional? prop))
274
                   (uniq (append (as-property-subproperty-of previous)
275
                                 (as-property-subproperty-of prop)))
276
                   (or (as-property-comment previous)
277
                       (as-property-comment prop))))
278
               (cons (cons (as-property-uri prop) prop) result)))
279
         properties)))))
280
281
(define* (merge-ontologies . ontologies)
282
  (let ((datatypes (merge-datatypes (map ontology-datatypes ontologies)))
283
        (properties (merge-properties (map ontology-properties ontologies))))
284
    (make-ontology
285
      (filter (lambda (a) a) (append-map ontology-context ontologies))
286
      (fix-types datatypes)
287
      (fix-properties datatypes properties))))
288
289
(define (subproperty? property other)
290
  "Is @code{property} a subproperty of @code{other}?"
291
  (or
292
    (equal? property other)
293
    (equal? (as-property-uri property) other)
294
    (let loop ((superproperties (as-property-subproperty-of property)))
295
      (match superproperties
296
        (() #f)
297
        ((superproperty superproperties ...)
298
         (if (subproperty? superproperty other)
299
             #t
300
             (loop superproperties)))))))
301
302
(define (subtype? type other)
303
  "Is @code{type} a subtype of @code{other}?"
304
  (or
305
    (equal? type other)
306
    (let loop ((supertypes (as-type-subclass-of type)))
307
      (match supertypes
308
        (() #f)
309
        ((supertype supertypes ...)
310
         (if (subtype? supertype other)
311
             #t
312
             (loop supertypes)))))))
313
314
(define (as-ref document key)
315
  "Takes a parsed document and returns the value associated with the property.
316
This takes care of subproperties: if you look for a property that's not in the
317
document directly, but the document has a subproperty of it, this will be
318
returned.  The key must be a proper label as defined in the ontology."
319
  (define (is-candidate kv)
320
    (match kv
321
      ((k . v)
322
       (subproperty? k key))))
323
  (let ((candidates (filter is-candidate document)))
324
    (map cdr candidates)))
325
326
(define (json->as-document ontology document)
327
  (define (uri->datatype type)
328
    (let ((candidates (filter (lambda (t) (equal? (as-type-uri t) type))
329
                              (ontology-datatypes ontology))))
330
      (cond
331
        ((null? candidates)
332
         #f)
333
        ((> (length candidates) 1)
334
         (throw 'multiple-datatypes-with-same-uri candidates))
335
        (else
336
         (car candidates)))))
337
338
  (define (uri->property property)
339
    (let ((candidates (filter (lambda (p) (equal? (as-property-uri p) property))
340
                              (ontology-properties ontology))))
341
      (cond
342
        ((null? candidates)
343
         #f)
344
        ((> (length candidates) 1)
345
         (throw 'multiple-properties-with-same-uri candidates))
346
        (else (car candidates)))))
347
348
  (define (scalar->as-value value)
349
    (cond
350
      ((or (json-has-key? value "@language") (json-has-key? value "@direction"))
351
       (make-as-string
352
         (assoc-ref value "@value")
353
         (assoc-ref value "@language")
354
         (assoc-ref value "@direction")))
355
      ((json-has-key? value "@type")
356
       (let* ((types (assoc-ref value "@type"))
357
              (types (if (string? types) (list types) (array->list types)))
358
              (types (map uri->datatype types)))
359
         (make-as-typed-value (assoc-ref value "@value") types)))
360
      (else
361
        (assoc-ref value "@value"))))
362
363
  (cond
364
    ((scalar? document)
365
     document)
366
    ((json-has-key? document "@value")
367
     (scalar->as-value document))
368
    ((json-array? document)
369
     ;; XXX: this filter is not correct if one of the values is the litteral
370
     ;; "false"
371
     (list->array 1
372
       (filter
373
         (lambda (a) a)
374
         (map (lambda (doc) (json->as-document ontology doc))
375
              (array->list document)))))
376
    ((json-has-key? document "@type")
377
     (let* ((types (assoc-ref document "@type"))
378
            (types (if (string? types) (list types) (array->list types)))
379
            (types (filter (lambda (a) a) (map uri->datatype types))))
380
       (if (null? types)
381
           #f
382
           (make-as-document
383
             types
384
             (filter
385
               (lambda (a) a)
386
               (map
387
                 (match-lambda
388
                   ((key . value)
389
                    (let ((property (uri->property key))
390
                          (value (json->as-document ontology value)))
391
                      (if (and property (not (equal? key "@type")) value)
392
                          (cons (uri->property key) value)
393
                          #f))))
394
                 document))))))
395
    (else
396
      (map
397
        (match-lambda
398
          ((key . value)
399
           (cons key (json->as-document ontology value))))
400
        document))))
401
402
(define (uri->as-document ontology uri)
403
  (json->as-document ontology (expand uri)))
404
405
(define (as-document->json ontology doc)
406
  (define (as-document->proper-json doc)
407
    (append
408
      `(("@type" . ,(list->array 1 (map as-type-uri (as-document-types doc)))))
409
      (map
410
        (match-lambda
411
          ((key . value)
412
           (cons (as-property-uri key) (as-value->proper-json value))))
413
        (as-document-properties doc))))
414
415
  (define (as-value->proper-json doc)
416
    (cond
417
      ((as-document? doc)
418
       (as-document->proper-json doc))
419
      ((list? doc)
420
       (map
421
         (match-lambda
422
           ((key . value)
423
            (cons
424
              (if (string? key)
425
                  key
426
                  (as-property-uri key))
427
              (as-value->proper-json value))))
428
         doc))
429
      ((string? doc)
430
       doc)
431
      ((array? doc)
432
       (list->array 1 (map as-value->proper-json (array->list doc))))
433
      ((as-typed-value? doc)
434
       `(("@value" . ,(as-typed-value-value doc))
435
         ("@type" . ,(as-type-uri (as-typed-value-type doc)))))
436
      ((as-string? doc)
437
       `(("@value" . ,(as-string-value doc))
438
         ,@(if (as-string-direction doc)
439
               `(("@direction" . ,(as-string-direction doc)))
440
               '())
441
         ,@(if (as-string-language doc)
442
               `(("@language" . ,(as-string-language doc)))
443
               '())))
444
      (else doc)))
445
446
  (compact (as-value->proper-json doc)
447
           `(("@context" . ,(list->array 1 (ontology-context ontology))))))
448
449
(define* (as-document->graphviz doc #:key (label "n"))
450
  (cond
451
    ((as-document? doc)
452
     (let* ((id (as-ref (as-document-properties doc) "@id"))
453
            (id (if (string? id) id (if (null? id) "" (car id))))
454
            (types (as-document-types doc))
455
            (name (if (null? types)
456
                      id
457
                      (string-append id " ("
458
                                     (string-join (map as-type-label types) ", ")
459
                                     ")"))))
460
       (format #t "  ~a [label=\"~a\"];~%" label name)
461
       (let loop ((children (as-document-properties doc)) (suffix 0))
462
         (match children
463
           (() (format #t "~%"))
464
           (((key . value) children ...)
465
            (let ((child-label (string-append label (number->string suffix))))
466
              (format #t "  ~a -> ~a [label=\"~a\"];~%"
467
                      label child-label
468
                      (if (string? key) key (as-property-label key)))
469
              (as-document->graphviz value #:label child-label)
470
              (loop children (+ suffix 1))))))))
471
    ((list? doc)
472
     (format #t "  ~a [label=\"\"];~%" label)
473
     (let loop ((children doc) (suffix 0))
474
       (match children
475
         (() (format #t "~%"))
476
         (((key . value) children ...)
477
          (let ((child-label (string-append label (number->string suffix))))
478
            (format #t "  ~a -> ~a [label=\"~a\"];~%"
479
                    label child-label
480
                    (if (string? key) key (as-property-label key)))
481
            (as-document->graphviz value #:label child-label)
482
            (loop children (+ suffix 1)))))))
483
    ((string? doc)
484
     (format #t "  ~a [label=\"~a\"];~%" label doc))
485
    ((array? doc)
486
     (let loop ((children (array->list doc)) (suffix 0))
487
       (match children
488
         (() (format #t "~%"))
489
         ((value children ...)
490
          (let ((child-label (string-append label (number->string suffix))))
491
            (format #t "  ~a -> ~a;~%" label child-label)
492
            (as-document->graphviz value #:label child-label)
493
            (loop children (+ suffix 1)))))))
494
    ((as-typed-value? doc)
495
     (format #t "  ~a [label=\"~a\"];~%"
496
             label (string-append (as-typed-value-value doc) "^^"
497
                                (as-type-label (as-typed-value-type doc)))))
498
    ((as-string? doc)
499
     (let* ((str (as-string-value doc))
500
            (str (if (or (as-string-language doc) (as-string-direction doc))
501
                     (string-append str "@")
502
                     str))
503
            (str (string-append str (as-string-language doc)))
504
            (str (if (as-string-direction doc)
505
                     (string-append str "_" (as-string-direction doc))
506
                     str)))
507
     (format #t "  ~a [label=\"~a\"];~%"
508
             label str)))
509
    (else doc)))
510