Source file object.ml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
(** Objects pre-implementation and graph convenient functions.
Convenient functions to implement {!Types.object_}s, including
a virtual {!class-object_} class performing queries on its internal
graph.
*)
(** {2 Errors} *)
type E.error +=
| Could_not_parse_graph of Iri.t * Ldp.Ct.t * string
| Http_error of Iri.t * Cohttp.Code.meth * string
let () = E.register_string_of_error
(function
| Could_not_parse_graph (iri, ct, msg) ->
Some (Printf.sprintf "Could not parse graph from %s [%s]: %s"
(Iri.to_string iri) (Ldp.Ct.to_string ct) msg)
| Http_error (iri, meth, msg) ->
Some (Printf.sprintf "HTTP %s %s: %s"
(Cohttp.Code.string_of_method meth) (Iri.to_string iri) msg)
| _ -> None
)
(** {2 Content-types and mime-types} *)
(** [application/json] *)
let ct_json =
match Ldp.Ct.of_string "application/json" with
| Ok m -> m
| Error _ -> assert false
let mime_json = Ldp.Ct.to_mime ct_json
(** [application/ld+json] *)
let ct_jsonld =
match Ldp.Ct.of_string "application/ld+json" with
| Ok m -> m
| Error _ -> assert false
let mime_jsonld = Ldp.Ct.to_mime ct_jsonld
(** [application/activity+json] *)
let ct_activity_json =
match Ldp.Ct.of_string "application/activity+json" with
| Ok m -> m
| Error _ -> assert false
let mime_activity_json = Ldp.Ct.to_mime ct_activity_json
(** List of accepted content-types: json-ld, activity_json, json, turtle, xmlrdf. *)
let accept_rdf_cts = Ldp.Ct.[ct_jsonld ; ct_turtle ; ct_xmlrdf ; ct_json ; ct_activity_json ]
(** {2 JSON remote loader and parser} *)
(**/**)
let cache_dir_created = ref false
(**/**)
let jsonld_load_remote ?(loader=Rdf_json_ld.T.load_remote_curl) cache_dir =
match cache_dir with
| None ->
(fun iri ->
match Iri.Map.find_opt iri Jsonld_static.contexts with
| Some str -> Lwt.return_ok str
| None -> loader iri
)
| Some dir ->
if not !cache_dir_created then
(
let _ = Sys.command (Printf.sprintf "mkdir -p %s" (Filename.quote dir)) in
cache_dir_created := true
);
let f iri =
match Iri.Map.find_opt iri Jsonld_static.contexts with
| Some str -> Lwt.return_ok str
| None ->
let sha = Cryptokit.Hash.sha256 () in
sha#add_string (Iri.to_string iri) ;
let e = Cryptokit.Hexa.encode () in
e#put_string sha#result ;
let file = Filename.concat dir e#get_string in
match%lwt Lwt_unix.file_exists file with
| true ->
let%lwt str = Lwt_io.(with_file ~mode:Input file read) in
Lwt.return_ok str
| false ->
match%lwt loader iri with
| Ok str ->
let%lwt () = Lwt_io.(with_file ~mode:Output file
(fun oc -> write oc str))
in
Lwt.return_ok str
| x -> Lwt.return x
in
f
let jsonld_string_to_graph ?(g=Rdf.Graph.open_graph (Iri.of_string "")) options str =
let json = Rdf_json_ld.J.from_string_exn str in
let%lwt (ds,root) = Rdf_json_ld.Json_ld.to_rdf options json g in
Rdf.Ds.merge_to_default ds;
Lwt.return (ds.Rdf.Ds.default, root)
let jsonld_parser ?jsonld_loader cache_dir =
let options = Rdf_json_ld.T.options (jsonld_load_remote ?loader:jsonld_loader cache_dir) in
fun g ct body ->
try%lwt
let%lwt (g,root) = jsonld_string_to_graph ~g options body in
Lwt.return_ok (g, root)
with
| Rdf_json_ld.T.Error e ->
let msg = Rdf_json_ld.T.string_of_error e in
Log.debug (fun m -> m "%s" body);
let iri = g.Rdf.Graph.name () in
Lwt.return_error (Could_not_parse_graph (iri, ct, msg))
let parse_graph ?jsonld_loader cache_dir =
let jsonld_parser = jsonld_parser ?jsonld_loader cache_dir in
fun iri ct body ->
let g = Rdf.Graph.open_graph iri in
match ct with
| _ when Ldp.Ct.(has_mime ct mime_turtle) ->
(try Rdf.Ttl.from_string g ~base:iri body; Lwt.return_ok (g, None)
with Rdf.Ttl.Error e ->
let msg = Rdf.Ttl.string_of_error e in
Log.debug (fun m -> m "%s" body);
Lwt.return_error (Could_not_parse_graph (iri, ct, msg))
)
| _ when Ldp.Ct.(has_mime ct mime_xmlrdf) ->
(try Rdf.Xml.from_string g ~base:iri body; Lwt.return_ok (g, None)
with Rdf.Xml.Invalid_rdf msg ->
Log.debug (fun m -> m "%s" body);
Lwt.return_error (Could_not_parse_graph (iri, ct, msg))
)
| _ when Ldp.Ct.(has_mime ct mime_jsonld)
|| Ldp.Ct.(has_mime ct mime_activity_json) ->
jsonld_parser g ct body
| _ -> Lwt.return_error (Could_not_parse_graph (iri, ct, "No parser"))
(** A module with convenient function to query an object_'s graph. *)
module G = struct
module G = Rdf.Graph
let iri_obj_option g sub pred =
match G.iri_objects_of g ~sub ~pred with
| [] -> None
| h :: _ -> Some h
let first_iri_obj g sub pred =
match iri_obj_option g sub pred with
| None ->
Log.warn (fun m -> m "%a has no %a" Rdf.Term.pp_term sub Iri.pp pred);
Iri.of_string ""
| Some iri -> iri
let first_int_obj g sub pred =
let lits = G.literal_objects_of g ~sub ~pred in
let p lit =
match lit.Rdf.Term.lit_type with
| Some t ->
Iri.equal t Rdf.Rdf_.xsd_integer
|| Iri.equal t Rdf.Rdf_.xsd_int
| None -> false
in
match List.filter p lits with
| [] -> None
| lit :: _ ->
match int_of_string lit.lit_value with
| n -> Some n
| exception _ -> None
let lit_is_string lit =
match lit.Rdf.Term.lit_type with
| None -> true
| Some t ->
Iri.equal t Rdf.Rdf_.xsd_string
|| Iri.equal t Rdf.Rdf_.dt_langString
let lit_is_string_nolang lit =
match lit.Rdf.Term.lit_language, lit.Rdf.Term.lit_type with
| Some _, _ -> false
| None, None -> true
| None, Some t -> Iri.equal t Rdf.Rdf_.xsd_string
let lit_is_datetime lit =
match lit.Rdf.Term.lit_type with
| None -> false
| Some t -> Iri.equal t Rdf.Rdf_.xsd_datetime
let first_string_obj g sub pred =
let lits = G.literal_objects_of g ~sub ~pred in
match List.filter lit_is_string lits with
| [] -> None
| lit :: _ -> Some lit.lit_value
let first_string_nolang_obj g sub pred =
let lits = G.literal_objects_of g ~sub ~pred in
match List.filter lit_is_string_nolang lits with
| [] -> None
| lit :: _ -> Some lit.lit_value
let lit_is_bool lit =
match lit.Rdf.Term.lit_type with
| None -> false
| Some t -> Iri.equal t Rdf.Rdf_.xsd_boolean
let first_bool_obj g sub pred =
let lits = G.literal_objects_of g ~sub ~pred in
match List.filter lit_is_bool lits with
| [] -> None
| lit :: _ -> Some (Rdf.Term.bool_of_literal lit)
let first_ct_obj g sub pred =
match first_string_obj g sub pred with
| None -> None
| Some str ->
match Ldp.Ct.mime_of_string str with
| Ok ct -> Some ct
| _ -> None
let first_datetime_obj g sub pred =
let lits = G.literal_objects_of g ~sub ~pred in
match List.filter lit_is_datetime lits with
| [] -> None
| lit :: _ ->
match Rdf.Term.datetime_of_literal lit with
| dt -> Some dt
| exception _ -> None
let string_list_obj g sub pred =
let l = g.G.objects_of ~sub ~pred in
let l = List.flatten (List.map (G.to_list g) l) in
let l = G.only_literals l in
let l = List.filter lit_is_string l in
List.map (fun lit -> lit.Rdf.Term.lit_value) l
let term_is_id = function
| Rdf.Term.Literal _ | Blank -> false
| _ -> true
let link_or_object_list create g sub pred =
let objs = g.G.objects_of ~sub ~pred in
List.fold_left
(fun acc -> function
| term when term_is_id term -> (create term) :: acc
| _ -> acc)
[]
objs
let link_or_object_ordered_list create g sub pred =
match g.G.find ~sub ~pred () with
| [] -> []
| (_,_,head) :: _ ->
let l = G.to_list g head in
let l = List.filter term_is_id l in
List.map create l
let link_or_object_option create g sub pred =
let objs = List.filter term_is_id (g.G.objects_of ~sub ~pred) in
match objs with
| [] -> None
| term :: _ -> Some (create term)
let string_lang_map_obj g sub pred =
let objs = G.literal_objects_of g ~sub ~pred in
let f acc lit =
match lit.Rdf.Term.lit_language with
| Some lang -> Smap.add lang lit.lit_value acc
| None -> acc
in
List.fold_left f Smap.empty objs
end
(** {2 Implementations of objects} *)
(**/**)
module AS = Rdf.Activitystreams
(**/**)
let none_if_iri_only id g =
match id, g with
| _, None -> None
| Rdf.Term.Iri iri, Some g ->
if g.Rdf.Graph.exists ~sub:id () then
let g2 = Utils.graph_keep_only_from
~keep:(fun _ -> true) g id
in
(Some g2)
else
None
| _ -> g
(** Implementation of a link. *)
class link g id : Types.link =
object(self)
method id = id
method height = G.first_int_obj g id AS.height
method href = match G.iri_obj_option g id AS.href with
| None -> G.first_iri_obj g id AS.url
| Some x -> x
method hreflang = G.first_string_obj g id AS.hreflang
method media_type = G.first_ct_obj g id AS.mediaType
method name = G.first_string_obj g id AS.name
method rel = G.string_list_obj g id AS.rel
method width = G.first_int_obj g id AS.width
method g = g
end
(** A pre-implementation of an {!Types.object_}. Dereferencing and casting
methods remain to be implemented. *)
class virtual object_ = fun ?g (id:Types.id) ->
object(self)
val mutable g = (g : Rdf.Graph.graph option)
val mutable id = id
method id = id
method iri = match self#id with
| Rdf.Term.Iri i -> i
| id ->
match self#url with
| [] ->
Log.warn (fun m -> m "Id of %a is not an IRI and no url found" Rdf.Term.pp_term id);
Iri.of_string ""
| li :: _ -> Types.iri_of_liri li
method g = g
method private g_ = match g with
| Some g -> g
| None ->
Log.warn (fun m -> m "%a: no g, creating empty graph" Iri.pp self#iri);
let iri = match id with Iri i -> i | _ -> Iri.of_string "" in
Rdf.Graph.open_graph iri
method is_empty =
match self#g with
| None -> true
| Some g -> g.Rdf.Graph.find ~sub:id () = []
method as_link = new link self#g_ id
method pp ppf () =
Format.fprintf ppf "%a:@.%s"
Rdf.Term.pp_term self#id
(match g with None -> "no graph" | Some g -> Rdf.Ttl.to_string g)
method private link_or_object term : [`L of Types.link | `O of Types.object_] =
let g = self#g_ in
match Rdf.Graph.types_of g term with
| t :: _ when Iri.equal t AS.c_Link -> `L (new link g term)
| _ -> `O (self#new_object ?g:(none_if_iri_only term self#g) term)
method private link_or_image term : [`L of Types.link | `I of Types.image] =
let g = self#g_ in
match Rdf.Graph.types_of g term with
| t :: _ when Iri.equal t AS.c_Link -> `L (new link g term)
| _ -> `I (self#new_image ?g:(none_if_iri_only term self#g) term)
method private link_or_iri_option pred : [`L of Types.link | `I of Iri.t] option =
let g = self#g_ in
match g.Rdf.Graph.objects_of ~sub:id ~pred with
| [] ->
None
| term :: _ ->
match Rdf.Graph.types_of g term with
| t :: _ when Iri.equal t AS.c_Link -> Some (`L (new link g term))
| _ ->
match term with
| Rdf.Term.Iri iri -> Some (`I iri)
| _ -> None
method private link_or_iri_list pred : [`L of Types.link | `I of Iri.t] list =
let g = self#g_ in
let objs = g.Rdf.Graph.objects_of ~sub:id ~pred in
let f acc term =
match Rdf.Graph.types_of g term with
| t :: _ when Iri.equal t AS.c_Link -> (`L (new link g term)) :: acc
| _ ->
match term with
| Rdf.Term.Iri iri -> (`I iri) :: acc
| _ -> acc
in
List.fold_left f [] objs
method type_ =
let iri = G.first_iri_obj self#g_ id Rdf.Rdf_.type_ in
iri
method as_id = G.iri_obj_option self#g_ id AS.id
method attachment = G.link_or_object_list self#link_or_object self#g_ id AS.attachment
method attributed_to = G.link_or_object_option self#link_or_object self#g_ id AS.attributedTo
method audience = G.link_or_object_option self#link_or_object self#g_ id AS.audience
method likes = (None : Types.collection option)
method shares = (None : Types.collection option)
method content = G.first_string_nolang_obj self#g_ id AS.content
method content_map = G.string_lang_map_obj self#g_ id AS.content
method name = G.first_string_nolang_obj self#g_ id AS.name
method name_map = G.string_lang_map_obj self#g_ id AS.name
method end_time = G.first_datetime_obj self#g_ id AS.endTime
method generator = G.link_or_object_option self#link_or_object self#g_ id AS.generator
method icon = G.link_or_object_list self#link_or_image self#g_ id AS.icon
method image = G.link_or_object_list self#link_or_image self#g_ id AS.image
method in_reply_to = G.link_or_object_list self#link_or_object self#g_ id AS.inReplyTo
method preview = G.link_or_object_option self#link_or_object self#g_ id AS.preview
method published = G.first_datetime_obj self#g_ id AS.published
method replies =
Option.map (fun iri -> self#new_collection
?g:(none_if_iri_only (Rdf.Term.Iri iri) self#g) (Rdf.Term.Iri iri))
(G.iri_obj_option self#g_ id AS.replies)
method start_time = G.first_datetime_obj self#g_ id AS.startTime
method summary = G.first_string_nolang_obj self#g_ id AS.summary
method summary_map = G.string_lang_map_obj self#g_ id AS.summary
method tag = G.link_or_object_list self#link_or_object self#g_ id AS.tag
method updated = G.first_datetime_obj self#g_ id AS.updated
method url = self#link_or_iri_list AS.url
method to_ = G.link_or_object_list self#link_or_object self#g_ id AS.to_
method bto = G.link_or_object_list self#link_or_object self#g_ id AS.bto
method cc = G.link_or_object_list self#link_or_object self#g_ id AS.cc
method bcc = G.link_or_object_list self#link_or_object self#g_ id AS.bcc
method media_type = G.first_ct_obj self#g_ id AS.mediaType
(** collection methods *)
method total_items = Option.value ~default:0 (G.first_int_obj self#g_ id AS.totalItems)
method current = self#link_or_iri_option AS.current
method first = self#link_or_iri_option AS.first
method last = self#link_or_iri_option AS.last
method items =
let ordered = Iri.equal self#type_ AS.c_OrderedCollection
|| Iri.equal self#type_ AS.c_OrderedCollectionPage
in
match self#first with
| None ->
let f = if ordered
then G.link_or_object_ordered_list
else G.link_or_object_list
in
Lwt.return (Lwt_stream.of_list
(f self#link_or_object self#g_ id AS.items))
| Some li ->
let next = ref (Some li) in
let items = ref [] in
let push_next_items () =
match !next with
| None -> Lwt.return_unit
| Some x ->
let iri = match x with
| `L l -> l#href
| `I iri -> iri
in
Log.debug (fun m -> m "new_collection page %a" Iri.pp iri);
let page = self#new_collection_page (Rdf.Term.Iri iri) in
let%lwt () = page#dereference in
let%lwt l = let%lwt st = page#items in Lwt_stream.to_list st in
items := l;
next := page#next;
Log.debug (fun m -> m "next = %s"
(match !next with
| None -> "None"
| Some (`L l) -> Iri.to_string l#href
| Some (`I iri) -> Iri.to_string iri
));
Lwt.return_unit
in
let rec f () =
match !items, !next with
| h::q, _ -> items := q; Lwt.return_some h
| [], None -> Lwt.return_none
| [], Some li -> let%lwt () = push_next_items () in f ()
in
let st = Lwt_stream.from f in
Lwt.return st
(** collection_page methods *)
method part_of = self#link_or_iri_option AS.partOf
method next = self#link_or_iri_option AS.next
method prev = self#link_or_iri_option AS.prev
(** ordered_collection_page methods *)
method start_index = G.first_int_obj self#g_ id AS.startIndex
(** activity methods *)
method actor = G.link_or_object_option self#link_or_object self#g_ id AS.actor
method object_ = G.link_or_object_option
(fun term -> self#new_object ?g:(none_if_iri_only term g) term) self#g_ id AS.object_
method target = G.link_or_object_option self#link_or_object self#g_ id AS.target
method origin = G.link_or_object_option self#link_or_object self#g_ id AS.origin
method result = G.link_or_object_option self#link_or_object self#g_ id AS.result
method instrument = G.link_or_object_list self#link_or_object self#g_ id AS.instrument
method as_object = (self :> Types.object_)
method as_collection = (self :> Types.collection)
method as_ordered_collection = (self :> Types.ordered_collection)
method as_collection_page = (self :> Types.collection_page)
method as_ordered_collection_page = (self :> Types.ordered_collection_page)
method as_activity = (self :> Types.activity)
method virtual private new_object : ?g:Rdf.Graph.graph -> Types.id -> Types.object_
method virtual private new_activity : ?g:Rdf.Graph.graph -> Types.id -> Types.activity
method virtual private new_image : ?g:Rdf.Graph.graph -> Types.id -> Types.image
method virtual private new_collection : ?g:Rdf.Graph.graph -> Types.id -> Types.collection
method virtual private new_ordered_collection : ?g:Rdf.Graph.graph -> Types.id -> Types.ordered_collection
method virtual private new_collection_page : ?g:Rdf.Graph.graph -> Types.id -> Types.collection_page
method virtual private new_ordered_collection_page : ?g:Rdf.Graph.graph -> Types.id -> Types.ordered_collection_page
method virtual dereference : unit Lwt.t
end
(** [pp ppf o] pretty-prints the given [object_] to [ppf]. *)
let pp ppf o = o#pp ppf ()
(** {2 Convenient functions} *)
(** [graph_roots g] returns the list of root nodes, i.e. nodes which do
not appear as object in edges of the graph. Some nodes are filtered
out (blank nodes having a blank node as type, which are sent by Mastodon).
*)
let graph_roots =
let q =
[%sparql {| PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
SELECT DISTINCT ?root
{ ?root ?p ?o
MINUS { ?x ?y ?root }
}
|}]
in
fun g ->
let ds = Rdf.Ds.simple_dataset g in
let sols = Rdf.Sparql.select ~base:(g.name()) ds q in
let roots = List.map (fun sol -> Rdf.Sparql.get_term sol "root") sols in
List.filter (fun sub ->
match g.Rdf.Graph.objects_of ~sub ~pred:Rdf.Rdf_.type_ with
| [Rdf.Term.Blank_ _] -> false
| _ -> true)
roots
let activity_types = Iri.Set.of_list
Rdf.Activitystreams.[
c_Accept ; c_Add ; c_Announce ; c_Arrive ;
c_Block ; c_Create ; c_Delete ; c_Dislike ;
c_Flag ; c_Follow ; c_Ignore ; c_Invite ;
c_Join ; c_Leave ; c_Like ; c_Listen ;
c_Move ; c_Offer ; c_Question ; c_Reject ;
c_Read ; c_Remove ; c_TentativeReject ;
c_TentativeAccept ; c_Travel ; c_Undo ;
c_Update ; c_View ;
]
(** [is_activity typ] returns whether [typ] corresponds to an activity type.*)
let is_activity typ = Iri.Set.mem typ activity_types
(** [map_graph f g] returns a new graph with all triples mapped with [f].
[f] is also applied to the graph name (an IRI) to get the name of the new graph
(as an IRI). *)
let map_graph f g =
let triples = g.Rdf.Graph.find () in
let g2 =
let name = match f (Rdf.Term.Iri (g.name ())) with
| Rdf.Term.Iri i -> i
| _ -> g.name ()
in
Rdf.Graph.open_graph name
in
List.iter (fun (sub, pred, obj) ->
let sub = f sub in
let obj = f obj in
g2.add_triple ~sub ~pred ~obj)
triples;
g2
(** For each relation [to_], [bto], [cc], [bcc] and [audience] of [src],
[copy_addresses ~src ~dst ~dstid] adds a triple in graph [dst] with
[dstid] as subjet and the same predicates (to, cc, ...) and objects.
Returns [true] if at least one address was copied in [dst] (but does
not check if such a triple already existed).
*)
let copy_addresses ~src ~(dst:Rdf.Graph.graph) ~dstid =
let copied_one = ref false in
let copy pred = function
| `O obj -> dst.add_triple ~sub:dstid ~pred ~obj:obj#id; copied_one:=true
| `L link -> dst.add_triple ~sub:dstid ~pred ~obj:(Rdf.Term.Iri link#href); copied_one:=true
in
List.iter (copy AS.to_) src#to_ ;
List.iter (copy AS.bto) src#bto ;
List.iter (copy AS.cc) src#cc ;
List.iter (copy AS.bcc) src#bcc ;
Option.iter (copy AS.audience) src#audience;
!copied_one
(** [remove_addresses g sub] removes all triples of [g] with subject [sub]
and [to], [bto], [cc], [bcc] or [audience] predicate. *)
let remove_addresses (g:Rdf.Graph.graph) sub =
List.iter (fun pred ->
List.iter g.rem_triple_t (g.find ~sub ~pred ()))
[ AS.to_ ; AS.bto ; AS.cc ; AS.bcc ; AS.audience ]
(** [id_of_link_or_object (`L link)] returns [Iri link#href].
[id_of_link_or_object (`O obj)] returns [obj#id]. *)
let id_of_link_or_object = function
| `L link -> Rdf.Term.Iri link#href
| `O o -> o#id
(** [remove_bto_bcc g] removes all triples having [bto] and [bcc] as predicate. *)
let remove_bto_bcc (g:Rdf.Graph.graph) =
List.iter g.rem_triple_t (g.find ~pred:AS.bto());
List.iter g.rem_triple_t (g.find ~pred:AS.bcc())