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