lang_jsonld/lang/
triples.rs

1use std::{borrow::Cow, ops::Range};
2
3use lsp_core::prelude::*;
4
5use crate::lang::parser::{Json, ObjectMember};
6
7fn visit_obj(json: &Spanned<Json>, f: &mut dyn FnMut(&[Spanned<ObjectMember>], &Range<usize>)) {
8    match json.value() {
9        Json::Array(vec) => {
10            for v in vec.iter() {
11                visit_obj(v, f);
12            }
13        }
14        Json::Object(vec) => f(&vec, json.span()),
15        _ => {}
16    }
17}
18
19fn get_str(tok: &Token) -> Option<&str> {
20    match tok {
21        Token::Str(x, _) => Some(x),
22        _ => None,
23    }
24}
25
26fn correct_field(obj: &ObjectMember, field: &str) -> bool {
27    let f = obj.field();
28    match f.value() {
29        Token::Str(st, _) => st == field,
30        _ => false,
31    }
32}
33
34fn find_field<'a>(
35    mem: &'a [Spanned<ObjectMember>],
36    field: &str,
37) -> Option<(&'a Spanned<Json>, &'a Range<usize>)> {
38    mem.iter()
39        .find(|x| correct_field(x, field))
40        .and_then(|x| x.json_value().map(|y| (y, x.field().span())))
41}
42
43pub fn derive_prefixes(json: &Spanned<Json>, base: &lsp_types::Url) -> Prefixes {
44    let mut options: Vec<(Cow<str>, Cow<str>)> = Vec::new();
45
46    // Extract prefixes
47    visit_obj(json, &mut |mem, _| {
48        let Some((ctx, _)) = find_field(mem, "@context") else {
49            return;
50        };
51
52        visit_obj(ctx, &mut |mems, _| {
53            for mem in mems {
54                let Some(key) = get_str(mem.field()) else {
55                    continue;
56                };
57
58                let Some(value) = mem
59                    .json_value()
60                    .and_then(|x| x.as_ref().try_map(|x| x.token()))
61                    .and_then(|x| x.try_map(|x| get_str(x)))
62                else {
63                    continue;
64                };
65
66                options.push((Cow::Owned(key.to_string()), Cow::Owned(value.to_string())));
67            }
68        });
69    });
70
71    // Expand prefixes
72    let mut changed = true;
73    let mut steps = 0;
74    while changed && steps < 5 {
75        changed = false;
76        for i in 0..options.len() {
77            let new_expaned = if let Some(pref) = options[i].1.find(':') {
78                let prefix = &options[i].1[..pref];
79                let Some((_, expaned)) = options.iter().find(|x| x.0 == prefix) else {
80                    continue;
81                };
82                format!("{}{}", expaned, &options[i].1[pref + 1..])
83            } else {
84                continue;
85            };
86            options[i].1 = Cow::from(new_expaned);
87            changed = true;
88        }
89        steps += 1;
90    }
91    let mut out = Vec::new();
92
93    for (k, v) in options {
94        if let Some(url) = lsp_types::Url::parse(&v).ok() {
95            out.push(Prefix {
96                prefix: k.into_owned(),
97                url,
98            });
99        }
100    }
101
102    Prefixes(out, base.clone())
103}
104
105fn shorten_span(span: &Range<usize>) -> Range<usize> {
106    span.start + 1..span.end - 1
107}
108
109fn derive_triples_sub(
110    json: &Spanned<Json>,
111    prefixes: &Prefixes,
112    out: &mut Vec<MyQuad<'static>>,
113    bn_f: &mut dyn FnMut(Range<usize>) -> MyTerm<'static>,
114) -> Option<MyTerm<'static>> {
115    let mut subj_out = None;
116    visit_obj(json, &mut |mems, span| {
117        let subj = find_field(mems, "@id")
118            .and_then(|x| x.0.as_ref().try_map(|x| x.token()))
119            .and_then(|x| x.try_map(|x| prefixes.expand_json(x)))
120            .map(|Spanned(v, r)| MyTerm::named_node(v, shorten_span(&r)))
121            .unwrap_or_else(|| bn_f(span.clone()));
122
123        if let Some((ctx, _)) = find_field(mems, "@graph") {
124            derive_triples_sub(ctx, prefixes, out, bn_f);
125        }
126        if let Some((mem, span)) = find_field(mems, "@type") {
127            let object = match mem {
128                Spanned(Json::Token(tok), span) => {
129                    if let Some(st) = prefixes.expand_json(&tok) {
130                        MyTerm::named_node(st, span.clone())
131                    } else {
132                        MyTerm::invalid(span.clone())
133                    }
134                }
135                json => derive_triples_sub(json, prefixes, out, bn_f)
136                    .unwrap_or_else(|| MyTerm::invalid(json.span().clone())),
137            };
138
139            out.push(MyQuad {
140                subject: subj.clone(),
141                predicate: MyTerm::named_node(
142                    "http://www.w3.org/1999/02/22-rdf-syntax-ns#type",
143                    span.clone(),
144                ),
145                object,
146                span: mem.span().clone(),
147            });
148        }
149        for mem in mems {
150            let field = mem.field();
151            let st = get_str(&field);
152            if st.map(|x| x.starts_with("@")).unwrap_or(false) {
153                continue;
154            }
155
156            let Some(pred) = prefixes
157                .expand_json(&field)
158                .or_else(|| st.map(|x| x.to_string()))
159            else {
160                continue;
161            };
162            let pred = MyTerm::named_node(pred, shorten_span(field.span()));
163
164            // get value
165            let object = match mem.json_value() {
166                None => MyTerm::invalid(0..0),
167                // TODO: this might not be a literal, should look it up in the context
168                Some(Spanned(Json::Token(tok), span)) => match tok {
169                    Token::Str(x, _) => MyTerm::literal(x.clone(), span.clone()),
170                    _ => MyTerm::invalid(span.clone()),
171                },
172                Some(json) => derive_triples_sub(json, prefixes, out, bn_f)
173                    .unwrap_or_else(|| MyTerm::invalid(json.span().clone())),
174            };
175
176            out.push(MyQuad {
177                subject: subj.clone(),
178                predicate: pred,
179                object,
180                span: mem.span().clone(),
181            });
182        }
183
184        subj_out = Some(subj);
185    });
186    subj_out
187}
188
189pub fn derive_triples(json: &Spanned<Json>, prefixes: &Prefixes) -> Vec<MyQuad<'static>> {
190    let mut bn_count = 0;
191    let mut bn = move |span| {
192        let out = MyTerm::blank_node(format!("_:{}", bn_count), span);
193        bn_count += 1;
194        out
195    };
196    let mut out = Vec::new();
197    derive_triples_sub(json, prefixes, &mut out, &mut bn);
198    out
199}
200
201#[cfg(test)]
202mod tests {
203
204    use lsp_core::prelude::{MyQuad, Spanned};
205    use sophia_api::term::{Term, TermKind};
206
207    use super::{derive_prefixes, derive_triples};
208    use crate::lang::{
209        parser::{parse, Json},
210        tokenizer::tokenize,
211    };
212
213    fn parse_json(st: &str) -> Option<Spanned<Json>> {
214        let (tok, es) = tokenize(st);
215        if !es.is_empty() {
216            return None;
217        }
218        let (jsonld, es) = parse(st, tok);
219        if !es.is_empty() {
220            return None;
221        }
222        Some(jsonld)
223    }
224
225    #[test]
226    fn simple_context_foaf_1() {
227        let st = r#" { 
228            "@context": {"foaf": "http://xmlns.com/foaf/0.1/"},
229            "@id": "http://example.com/ns#me",
230            "foaf:name": "Arthur"
231        } "#;
232        let url = lsp_types::Url::parse("memory://test.jsonld").unwrap();
233
234        let json = parse_json(st).expect("valid json");
235        let prefixes = derive_prefixes(&json, &url);
236
237        assert_eq!(prefixes.0.len(), 1);
238        let foaf_prefix = prefixes
239            .iter()
240            .find(|x| x.prefix == "foaf")
241            .expect("foaf prefix");
242        assert_eq!(foaf_prefix.url.as_str(), "http://xmlns.com/foaf/0.1/");
243    }
244
245    #[test]
246    fn simple_context_foaf_2() {
247        let st = r#" { 
248            "@context": [ {"foaf": "http://xmlns.com/foaf/0.1/"} ],
249            "@id": "http://example.com/ns#me",
250            "foaf:name": "Arthur"
251        } "#;
252        let url = lsp_types::Url::parse("memory://test.jsonld").unwrap();
253
254        let json = parse_json(st).expect("valid json");
255        let prefixes = derive_prefixes(&json, &url);
256
257        assert_eq!(prefixes.0.len(), 1);
258
259        let foaf_prefix = prefixes
260            .iter()
261            .find(|x| x.prefix == "foaf")
262            .expect("foaf prefix");
263        assert_eq!(foaf_prefix.url.as_str(), "http://xmlns.com/foaf/0.1/");
264    }
265
266    #[test]
267    fn simple_context_foaf_3() {
268        let st = r#" { 
269            "@context": {"foaf": "http://xmlns.com/foaf/0.1/", "name": "foaf:name"},
270            "@id": "http://example.com/ns#me",
271            "name": "Arthur"
272        } "#;
273        let url = lsp_types::Url::parse("memory://test.jsonld").unwrap();
274
275        let json = parse_json(st).expect("valid json");
276        let prefixes = derive_prefixes(&json, &url);
277
278        assert_eq!(prefixes.0.len(), 2);
279        let name_prefix = prefixes
280            .iter()
281            .find(|x| x.prefix == "name")
282            .expect("name prefix");
283        assert_eq!(name_prefix.url.as_str(), "http://xmlns.com/foaf/0.1/name");
284
285        let foaf_prefix = prefixes
286            .iter()
287            .find(|x| x.prefix == "foaf")
288            .expect("foaf prefix");
289        assert_eq!(foaf_prefix.url.as_str(), "http://xmlns.com/foaf/0.1/");
290    }
291
292    #[test]
293    fn simple_context_foaf_3_ignore_extra_ctx() {
294        let st = r#" { 
295            "@context": [ {"foaf": "http://xmlns.com/foaf/0.1/"}, "http://xmlns.com/foaf/0.1/context.jsonld" ],
296            "@id": "http://example.com/ns#me",
297            "foaf:name": "Arthur"
298        } "#;
299        let url = lsp_types::Url::parse("memory://test.jsonld").unwrap();
300
301        let json = parse_json(st).expect("valid json");
302        let prefixes = derive_prefixes(&json, &url);
303
304        assert_eq!(prefixes.0.len(), 1);
305        let foaf_prefix = prefixes
306            .iter()
307            .find(|x| x.prefix == "foaf")
308            .expect("foaf prefix");
309        assert_eq!(foaf_prefix.url.as_str(), "http://xmlns.com/foaf/0.1/");
310    }
311
312    #[test]
313    fn derive_simple_triples() {
314        let st = r#" { 
315            "@context": {"foaf": "http://xmlns.com/foaf/0.1/"} ,
316            "@id": "http://example.com/ns#me",
317            "foaf:name": "Arthur"
318        } "#;
319        let url = lsp_types::Url::parse("memory://test.jsonld").unwrap();
320
321        let json = parse_json(st).expect("valid json");
322        let prefixes = derive_prefixes(&json, &url);
323        let triples = derive_triples(&json, &prefixes);
324
325        assert_eq!(triples.len(), 1);
326        let MyQuad {
327            subject,
328            predicate,
329            object,
330            ..
331        } = &triples[0];
332
333        assert_eq!(subject.as_str(), "http://example.com/ns#me");
334        assert_eq!(subject.kind(), TermKind::Iri);
335        assert_eq!(predicate.as_str(), "http://xmlns.com/foaf/0.1/name");
336        assert_eq!(predicate.kind(), TermKind::Iri);
337        assert_eq!(object.as_str(), "Arthur");
338        assert_eq!(object.kind(), TermKind::Literal);
339    }
340
341    #[test]
342    fn derive_simple_triples_type() {
343        let st = r#" { 
344            "@context": {"foaf": "http://xmlns.com/foaf/0.1/"} ,
345            "@id": "http://example.com/ns#me",
346            "@type": "http://example.com/ns#my_type"
347        } "#;
348        let url = lsp_types::Url::parse("memory://test.jsonld").unwrap();
349
350        let json = parse_json(st).expect("valid json");
351        let prefixes = derive_prefixes(&json, &url);
352        let triples = derive_triples(&json, &prefixes);
353
354        assert_eq!(triples.len(), 1);
355        let MyQuad {
356            subject,
357            predicate,
358            object,
359            ..
360        } = &triples[0];
361
362        assert_eq!(subject.as_str(), "http://example.com/ns#me");
363        assert_eq!(subject.kind(), TermKind::Iri);
364        assert_eq!(
365            predicate.as_str(),
366            "http://www.w3.org/1999/02/22-rdf-syntax-ns#type"
367        );
368        assert_eq!(predicate.kind(), TermKind::Iri);
369        assert_eq!(object.as_str(), "http://example.com/ns#my_type");
370        assert_eq!(object.kind(), TermKind::Iri);
371    }
372
373    #[test]
374    fn derive_simple_triples_bn() {
375        let st = r#" { 
376            "@context": {"foaf": "http://xmlns.com/foaf/0.1/"} ,
377            "foaf:name": "Arthur"
378        } "#;
379        let url = lsp_types::Url::parse("memory://test.jsonld").unwrap();
380
381        let json = parse_json(st).expect("valid json");
382        let prefixes = derive_prefixes(&json, &url);
383        let triples = derive_triples(&json, &prefixes);
384
385        assert_eq!(triples.len(), 1);
386        let MyQuad {
387            subject,
388            predicate,
389            object,
390            ..
391        } = &triples[0];
392
393        assert_eq!(subject.kind(), TermKind::BlankNode);
394        assert_eq!(predicate.as_str(), "http://xmlns.com/foaf/0.1/name");
395        assert_eq!(predicate.kind(), TermKind::Iri);
396        assert_eq!(object.as_str(), "Arthur");
397        assert_eq!(object.kind(), TermKind::Literal);
398    }
399
400    #[test]
401    fn derive_simple_triples_graph() {
402        let st = r#" { 
403            "@context": {"foaf": "http://xmlns.com/foaf/0.1/"} ,
404            "@graph": [ {
405                "@id": "http://example.com/ns#me",
406                "foaf:name": "Arthur"
407            } ]
408        } "#;
409        let url = lsp_types::Url::parse("memory://test.jsonld").unwrap();
410
411        let json = parse_json(st).expect("valid json");
412        let prefixes = derive_prefixes(&json, &url);
413        let triples = derive_triples(&json, &prefixes);
414
415        assert_eq!(triples.len(), 1);
416        let MyQuad {
417            subject,
418            predicate,
419            object,
420            ..
421        } = &triples[0];
422
423        assert_eq!(subject.as_str(), "http://example.com/ns#me");
424        assert_eq!(subject.kind(), TermKind::Iri);
425        assert_eq!(predicate.as_str(), "http://xmlns.com/foaf/0.1/name");
426        assert_eq!(predicate.kind(), TermKind::Iri);
427        assert_eq!(object.as_str(), "Arthur");
428        assert_eq!(object.kind(), TermKind::Literal);
429    }
430
431    #[test]
432    fn derive_simple_triples_bn_graph() {
433        let st = r#" {
434            "@context": {"foaf": "http://xmlns.com/foaf/0.1/"} ,
435            "@graph": [ {
436                "foaf:name": "Arthur"
437            } ]
438        } "#;
439        let url = lsp_types::Url::parse("memory://test.jsonld").unwrap();
440
441        let json = parse_json(st).expect("valid json");
442        let prefixes = derive_prefixes(&json, &url);
443        let triples = derive_triples(&json, &prefixes);
444
445        assert_eq!(triples.len(), 1);
446        let MyQuad {
447            subject,
448            predicate,
449            object,
450            ..
451        } = &triples[0];
452
453        assert_eq!(subject.kind(), TermKind::BlankNode);
454        assert_eq!(predicate.as_str(), "http://xmlns.com/foaf/0.1/name");
455        assert_eq!(predicate.kind(), TermKind::Iri);
456        assert_eq!(object.as_str(), "Arthur");
457        assert_eq!(object.kind(), TermKind::Literal);
458    }
459
460    #[test]
461    fn derive_simple_triples_deep() {
462        let st = r#" { 
463            "@context": {"foaf": "http://xmlns.com/foaf/0.1/"} ,
464            "@id": "http://example.com/ns#me",
465            "foaf:friend": {
466                "foaf:name": "Arthur",
467                "foaf:friend": {
468                    "foaf:name": "Julian"
469                }
470            }
471        } "#;
472        let url = lsp_types::Url::parse("memory://test.jsonld").unwrap();
473
474        let json = parse_json(st).expect("valid json");
475        let prefixes = derive_prefixes(&json, &url);
476        let triples = derive_triples(&json, &prefixes);
477
478        assert_eq!(triples.len(), 4);
479        let MyQuad {
480            subject,
481            predicate,
482            object,
483            ..
484        } = triples
485            .iter()
486            .find(|x| x.subject.as_str() == "http://example.com/ns#me")
487            .expect("my triple");
488        assert_eq!(subject.as_str(), "http://example.com/ns#me");
489        assert_eq!(subject.kind(), TermKind::Iri);
490        assert_eq!(predicate.as_str(), "http://xmlns.com/foaf/0.1/friend");
491        assert_eq!(predicate.kind(), TermKind::Iri);
492        assert_eq!(object.kind(), TermKind::BlankNode);
493
494        let friend: Vec<_> = triples.iter().filter(|x| &x.subject == object).collect();
495        assert_eq!(friend.len(), 2);
496        let friend_name = &friend
497            .iter()
498            .find(|x| x.predicate.as_str() == "http://xmlns.com/foaf/0.1/name")
499            .expect("friend name")
500            .object;
501        assert_eq!(friend_name.as_str(), "Arthur");
502        assert_eq!(friend_name.kind(), TermKind::Literal);
503
504        let friend_friend = &friend
505            .iter()
506            .find(|x| x.predicate.as_str() == "http://xmlns.com/foaf/0.1/friend")
507            .expect("friend name")
508            .object;
509        assert_eq!(friend_friend.kind(), TermKind::BlankNode);
510
511        let friend_friend: Vec<_> = triples
512            .iter()
513            .filter(|x| &x.subject == friend_friend)
514            .collect();
515        assert_eq!(friend_friend.len(), 1);
516        assert_eq!(friend_friend[0].object.as_str(), "Julian");
517        assert_eq!(friend_friend[0].object.kind(), TermKind::Literal);
518    }
519}