Skip to main content

swls_lang_rdf_base/
traits.rs

1use std::{borrow::Cow, collections::HashSet};
2
3// Re-export canonical model types from the turtle crate.
4// NOTE: TurtleSimpleError is intentionally NOT re-exported — we define our own
5// below, adding a Parse(IriParseError) variant needed by TriplesBuilder.
6pub use rdf_parsers::model::{
7    Base, BlankNode, Literal, NamedNode, RDFLiteral, StringStyle, Term, Triple, Turtle,
8    TurtlePrefix, Variable, PO,
9};
10use swls_core::{
11    lsp_types::Url,
12    prelude::{MyQuad, MyTerm, Spanned, TermContext, Triples2},
13    util::resolve_iri,
14};
15use tracing::warn;
16
17// ── Based trait ──────────────────────────────────────────────────────────────
18
19pub trait Based {
20    fn get_base(&self) -> &str;
21    fn prefixes(&self) -> &[Spanned<TurtlePrefix>];
22}
23
24impl Based for Turtle {
25    fn get_base(&self) -> &str {
26        self.set_base.as_deref().unwrap_or("")
27    }
28    fn prefixes(&self) -> &[Spanned<TurtlePrefix>] {
29        &self.prefixes
30    }
31}
32
33// ── NamedNode extension trait ─────────────────────────────────────────────────
34
35pub trait NamedNodeExt {
36    /// Fully expand a (possibly prefixed) named node to an absolute IRI string.
37    fn expand<T: Based>(&self, turtle: &T) -> Option<String>;
38    fn expand_step<T: Based>(&self, turtle: &T, done: HashSet<String>) -> Option<String>;
39}
40
41impl NamedNodeExt for NamedNode {
42    fn expand<T: Based>(&self, turtle: &T) -> Option<String> {
43        let out = self.expand_step(turtle, HashSet::new())?;
44        let base = Url::parse(turtle.get_base()).ok()?;
45        let url = base.join(&out).ok()?;
46        Some(url.to_string())
47    }
48
49    fn expand_step<T: Based>(&self, turtle: &T, mut done: HashSet<String>) -> Option<String> {
50        match self {
51            Self::Full(s, _) => Some(s.clone()),
52            Self::Prefixed {
53                prefix,
54                value,
55                computed,
56                ..
57            } => {
58                // A pre-resolved IRI (currently from JSON-LD `@context` expansion,
59                // whose semantics are not plain prefix substitution) is
60                // authoritative — use it verbatim instead of re-expanding
61                // `prefix`/`value` against the prefix map.
62                if let Some(computed) = computed {
63                    return Some(computed.clone());
64                }
65                if done.contains(prefix.as_str()) {
66                    return None;
67                }
68                done.insert(prefix.clone());
69                let prefix_entry = turtle
70                    .prefixes()
71                    .iter()
72                    .find(|x| x.prefix.as_str() == prefix.as_str())?;
73                let expanded = NamedNodeExt::expand_step(prefix_entry.value.value(), turtle, done)?;
74                Some(format!("{}{}", expanded, value))
75            }
76            Self::A(_) => Some("http://www.w3.org/1999/02/22-rdf-syntax-ns#type".to_string()),
77            Self::Invalid => None,
78        }
79    }
80}
81
82// ── TriplesBuilder ────────────────────────────────────────────────────────────
83
84pub struct TriplesBuilder<'a, T> {
85    pub triples: Vec<MyQuad<'a>>,
86    blank_node: Box<dyn FnMut(std::ops::Range<usize>) -> MyTerm<'a>>,
87    base: String,
88    based: &'a T,
89}
90
91impl<'a, T: Based> TriplesBuilder<'a, T> {
92    pub fn new(based: &'a T, base: String) -> Self {
93        let mut count = 0;
94        let blank_node = Box::new(move |span: std::ops::Range<usize>| {
95            count += 1;
96            MyTerm::blank_node(format!("internal_bnode_{}", count), span)
97        });
98        Self {
99            triples: vec![],
100            blank_node,
101            base,
102            based,
103        }
104    }
105
106    fn handle_po(
107        &mut self,
108        pos: &'a [Spanned<PO>],
109        span: std::ops::Range<usize>,
110        subject: MyTerm<'a>,
111    ) -> Result<(), TurtleSimpleError> {
112        if pos.is_empty() {
113            // Subject exists but has no predicate-object pairs (e.g. an incomplete statement).
114            // Push a placeholder triple so that TripleTarget::Subject is returned for any
115            // cursor position within this statement's span.
116            self.triples.push(MyQuad {
117                subject: subject.clone(),
118                predicate: MyTerm::invalid(0..0),
119                object: MyTerm::invalid(0..0),
120                span: span.clone(),
121            });
122        }
123        let mut first = true;
124        for Spanned(PO { predicate, object }, span2) in pos.iter() {
125            let this_span = if first {
126                first = false;
127                span.clone()
128            } else {
129                span2.clone()
130            };
131
132            let predicate_term = match predicate.value() {
133                Term::NamedNode(NamedNode::Invalid) => MyTerm::invalid(predicate.span().clone()),
134                Term::NamedNode(nn) => {
135                    if let Some(node) = NamedNodeExt::expand_step(nn, self.based, HashSet::new())
136                        .map(|n| resolve_iri(&self.base, &n))
137                    {
138                        MyTerm::named_node(node, predicate.span().clone())
139                    } else {
140                        MyTerm::invalid(predicate.span().clone())
141                    }
142                }
143                _ => MyTerm::invalid(predicate.span().clone()),
144            };
145
146            if object.is_empty() {
147                // Incomplete statement (e.g. `<> foaf:` with no object yet).
148                // Push a placeholder triple so get_current_triple can still detect
149                // the predicate position and return property completions.
150                let end = predicate.span().end;
151                self.triples.push(MyQuad {
152                    subject: subject.clone(),
153                    predicate: predicate_term.clone(),
154                    object: MyTerm::invalid(end..end),
155                    span: this_span.clone(),
156                });
157            }
158            let mut first_object = true;
159            for o in object.iter() {
160                let this_span = if first_object {
161                    first_object = false;
162                    this_span.clone()
163                } else {
164                    o.span().clone()
165                };
166                let obj = self.term_to_my_term(Ok(o.as_ref()))?;
167                self.triples.push(MyQuad {
168                    subject: subject.clone(),
169                    predicate: predicate_term.clone(),
170                    object: obj,
171                    span: this_span,
172                });
173            }
174        }
175        Ok(())
176    }
177
178    fn term_to_my_term(
179        &mut self,
180        term: Result<Spanned<&'a Term>, MyTerm<'a>>,
181    ) -> Result<MyTerm<'a>, TurtleSimpleError> {
182        let object = match term {
183            Ok(Spanned(Term::Variable(Variable(var, _)), span)) => MyTerm::variable(var, span),
184            Ok(Spanned(Term::NamedNode(NamedNode::Invalid), span)) => MyTerm::invalid(span),
185            Ok(Spanned(Term::NamedNode(nn), span)) => {
186                match NamedNodeExt::expand_step(nn, self.based, HashSet::new()) {
187                    Some(n) => MyTerm::named_node(resolve_iri(&self.base, &n), span),
188                    None => MyTerm::invalid(span),
189                }
190            }
191            Ok(Spanned(Term::Literal(Literal::RDF(lit)), span)) => {
192                let term_context = match (&lit.lang, &lit.ty) {
193                    (Some(l), _) => TermContext::LangTag(Cow::Owned(l.value().clone())),
194                    (_, Some(dt)) => {
195                        if let Some(dt) =
196                            NamedNodeExt::expand_step(dt.value(), self.based, HashSet::new())
197                        {
198                            TermContext::DataType(dt.into())
199                        } else {
200                            TermContext::None
201                        }
202                    }
203                    _ => TermContext::None,
204                };
205                MyTerm::literal(lit.value.to_string(), span, term_context)
206            }
207            Ok(Spanned(Term::Literal(Literal::Boolean(bool)), span)) => {
208                let term_context =
209                    TermContext::DataType("http://www.w3.org/2001/XMLSchema#boolean".into());
210                MyTerm::literal(bool.to_string(), span, term_context)
211            }
212            Ok(Spanned(Term::Literal(Literal::Numeric(num)), span)) => {
213                let term_context =
214                    TermContext::DataType("http://www.w3.org/2001/XMLSchema#integer".into());
215                MyTerm::literal(num.to_string(), span, term_context)
216            }
217            Ok(Spanned(Term::BlankNode(bn), span)) => match bn {
218                BlankNode::Named(v, _) => MyTerm::blank_node(v, span),
219                BlankNode::Unnamed(v, _, _) => {
220                    let out = (self.blank_node)(span.clone());
221                    self.handle_po(v, span, out.clone())?;
222                    out
223                }
224                BlankNode::Invalid => {
225                    return Err(TurtleSimpleError::UnexpectedBase(
226                        "Unexpected invalid blank for object",
227                    ))
228                }
229            },
230            Ok(Spanned(Term::Collection(terms), span)) => self.handle_collection(terms, span)?,
231            Ok(Spanned(Term::Invalid, span)) => MyTerm::invalid(span),
232            Err(x) => x,
233        };
234        Ok(object)
235    }
236
237    fn handle_collection(
238        &mut self,
239        collection: &'a [Spanned<Term>],
240        span: std::ops::Range<usize>,
241    ) -> Result<MyTerm<'a>, TurtleSimpleError> {
242        let mut prev = MyTerm::named_node(
243            "http://www.w3.org/1999/02/22-rdf-syntax-ns#nil",
244            span.end..span.end,
245        );
246        for Spanned(term, s) in collection.iter().rev() {
247            let next = (self.blank_node)(s.clone());
248            let obj = self.term_to_my_term(Ok(Spanned(term, s.clone())))?;
249            self.triples.push(MyQuad {
250                subject: next.clone(),
251                predicate: MyTerm::named_node(
252                    "http://www.w3.org/1999/02/22-rdf-syntax-ns#first",
253                    prev.span.start..prev.span.start,
254                ),
255                object: obj,
256                span: span.clone(),
257            });
258            self.triples.push(MyQuad {
259                subject: next.clone(),
260                predicate: MyTerm::named_node(
261                    "http://www.w3.org/1999/02/22-rdf-syntax-ns#rest",
262                    s.start..s.start,
263                ),
264                object: prev,
265                span: s.clone(),
266            });
267            prev = next;
268        }
269        Ok(prev)
270    }
271
272    pub fn ingest(
273        &mut self,
274        Spanned(ref triple, span): &'a Spanned<Triple>,
275    ) -> Result<(), TurtleSimpleError> {
276        let sub = match triple.subject.value() {
277            Term::BlankNode(BlankNode::Named(vs, _)) => {
278                MyTerm::blank_node(vs, triple.subject.span().clone())
279            }
280            Term::BlankNode(BlankNode::Unnamed(vs, _, _)) => {
281                let out = (self.blank_node)(triple.subject.span().clone());
282                self.handle_po(vs, triple.subject.span().clone(), out.clone())?;
283                out
284            }
285            Term::NamedNode(NamedNode::Invalid) => MyTerm::invalid(triple.subject.span().clone()),
286            Term::NamedNode(nn) => {
287                match NamedNodeExt::expand_step(nn, self.based, HashSet::new()) {
288                    Some(n) => MyTerm::named_node(
289                        resolve_iri(&self.base, &n),
290                        triple.subject.span().clone(),
291                    ),
292                    None => MyTerm::invalid(triple.subject.span().clone()),
293                }
294            }
295            Term::Invalid => MyTerm::invalid(triple.subject.span().clone()),
296            Term::Variable(var) => MyTerm::variable(&var.0, triple.subject.span().clone()),
297            x => {
298                warn!("Failed, unexpected {}", x.ty());
299                return Err(TurtleSimpleError::UnexpectedBaseString(format!(
300                    "Unexpected {}",
301                    x.ty()
302                )));
303            }
304        };
305        self.handle_po(&triple.po, span.clone(), sub)?;
306        Ok(())
307    }
308}
309
310#[derive(Debug)]
311pub enum TurtleSimpleError {
312    UnexpectedBase(&'static str),
313    UnexpectedBaseString(String),
314}
315
316// ── Turtle extension trait ────────────────────────────────────────────────────
317pub trait TurtleExt {
318    fn get_simple_triples<'a>(&'a self) -> Result<Triples2<'a>, TurtleSimpleError>;
319    fn into_triples<'a>(&self, triples: Vec<MyQuad<'a>>) -> Triples2<'a>;
320    fn shorten(&self, url: &str) -> Option<String>;
321}
322
323// ── TurtlePrefix extension trait ──────────────────────────────────────────────
324pub trait TurtlePrefixExt {
325    fn shorten<T: Based>(&self, turtle: &T, url: &str) -> Option<String>;
326}
327
328impl TurtlePrefixExt for TurtlePrefix {
329    fn shorten<T: Based>(&self, turtle: &T, url: &str) -> Option<String> {
330        let prefix_url = NamedNodeExt::expand(self.value.value(), turtle)?;
331        let short = url.strip_prefix(&prefix_url)?;
332        Some(format!("{}:{}", self.prefix.as_str(), short))
333    }
334}
335
336impl TurtleExt for Turtle {
337    fn get_simple_triples<'a>(&'a self) -> Result<Triples2<'a>, TurtleSimpleError> {
338        let base = match &self.base {
339            Some(Spanned(Base(_, Spanned(named_node, _)), _)) => {
340                NamedNodeExt::expand_step(named_node, self, HashSet::new()).ok_or(
341                    TurtleSimpleError::UnexpectedBase("Expected valid named node base"),
342                )?
343            }
344            None => self.set_base.clone().unwrap_or_default(),
345        };
346
347        let mut builder = TriplesBuilder::new(self, base);
348
349        for t in &self.triples {
350            if let Err(e) = builder.ingest(t) {
351                tracing::error!("Failed parsing triple {}: {:?}", t.value(), e);
352            }
353        }
354
355        Ok(self.into_triples(builder.triples))
356    }
357
358    fn into_triples<'a>(&self, triples: Vec<MyQuad<'a>>) -> Triples2<'a> {
359        tracing::debug!("Results in {} triples", triples.len());
360        let base = match &self.base {
361            Some(Spanned(Base(_, Spanned(named_node, span)), _)) => {
362                NamedNodeExt::expand_step(named_node, self, HashSet::new())
363                    .map(|st| MyTerm::named_node(st, span.clone()))
364            }
365            None => Some(MyTerm::named_node(
366                self.set_base.clone().unwrap_or_default(),
367                0..0,
368            )),
369        };
370        let base_url = self.set_base.clone().unwrap_or_default();
371        Triples2 {
372            triples,
373            base,
374            base_url,
375        }
376    }
377
378    fn shorten(&self, url: &str) -> Option<String> {
379        self.prefixes
380            .iter()
381            .find_map(|p| TurtlePrefixExt::shorten(p.value(), self, url))
382    }
383}