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