Skip to main content

swls_core/util/
triple.rs

1use std::{
2    borrow::{Borrow, Cow},
3    hash::Hash,
4    sync::Arc,
5    usize,
6};
7
8use bevy_ecs::prelude::*;
9use derive_more::{AsMut, AsRef, Deref, DerefMut};
10use sophia_api::{
11    prelude::{Any, Dataset},
12    quad::Quad,
13    term::{matcher::TermMatcher, BnodeId, GraphName, IriRef, Term, TermKind},
14    MownStr,
15};
16use tracing::{debug, instrument};
17
18use crate::{
19    components::{PositionComponent, RopeC},
20    prelude::DynLang,
21    util::{
22        ns::{owl, rdfs},
23        position_to_offset,
24    },
25};
26
27/// [`Component`] used to indicate the term type of currently targeted
28/// [`Token`](`crate::prelude::Token`) in the Triple.
29#[derive(Debug, PartialEq)]
30pub enum TripleTarget {
31    Subject,
32    Predicate,
33    Object,
34    Graph,
35}
36
37/// [`Component`] used to indicate the currently targeted [`MyQuad<'static>`] during a request.
38#[derive(Component, Debug)]
39pub struct TripleComponent {
40    pub triple: MyQuad<'static>,
41    pub target: TripleTarget,
42}
43
44impl TripleComponent {
45    pub fn kind(&self) -> TermKind {
46        let target = match self.target {
47            TripleTarget::Subject => self.triple.s().kind(),
48            TripleTarget::Predicate => self.triple.p().kind(),
49            TripleTarget::Object => self.triple.o().kind(),
50            TripleTarget::Graph => self
51                .triple
52                .g()
53                .map(|x| x.kind())
54                .unwrap_or(sophia_api::term::TermKind::Triple),
55        };
56        target
57    }
58
59    pub fn term(&self) -> Option<&MyTerm<'static>> {
60        let target = match self.target {
61            TripleTarget::Subject => self.triple.s(),
62            TripleTarget::Predicate => self.triple.p(),
63            TripleTarget::Object => self.triple.o(),
64            TripleTarget::Graph => return None,
65        };
66        Some(target)
67    }
68}
69
70/// [`Component`] containing all derived Triples from the documents.
71///
72/// These triples are used to derive properties and classes and other things.
73#[derive(Component, AsRef, Deref, AsMut, DerefMut, Debug)]
74pub struct Triples(pub Arc<Vec<MyQuad<'static>>>);
75
76impl Triples {
77    pub fn object<'s, S, P>(&'s self, subj: S, pred: P) -> Option<&'s MyTerm<'s>>
78    where
79        S: TermMatcher + 's,
80        P: TermMatcher + 's,
81    {
82        self.0
83            .quads_matching(
84                subj,
85                pred,
86                sophia_api::prelude::Any,
87                sophia_api::prelude::Any,
88            )
89            .flatten()
90            .next()
91            .map(|x| x.o())
92    }
93
94    pub fn objects<'s, S, P>(&'s self, subj: S, pred: P) -> impl Iterator<Item = &'s MyTerm<'s>>
95    where
96        S: TermMatcher + 's,
97        P: TermMatcher + 's,
98    {
99        self.0
100            .quads_matching(
101                subj,
102                pred,
103                sophia_api::prelude::Any,
104                sophia_api::prelude::Any,
105            )
106            .flatten()
107            .map(|x| x.o())
108    }
109}
110
111#[instrument(skip(query, commands))]
112pub fn get_current_triple(
113    query: Query<
114        (Entity, &PositionComponent, &Triples, &RopeC, &DynLang),
115        Changed<PositionComponent>,
116    >,
117    mut commands: Commands,
118) {
119    for (e, position, triples, rope, lang) in &query {
120        commands.entity(e).remove::<TripleComponent>();
121
122        let Some(offset) = position_to_offset(position.0, &rope.0) else {
123            debug!("Couldn't transform to an offset");
124            continue;
125        };
126
127        // First try: find the narrowest triple whose span inclusively contains the cursor.
128        // Using inclusive end (<=) so the cursor at exactly span.end (e.g. after a partially
129        // written predicate or object) still matches.
130
131        if let Some((target, _, t)) = triples
132            .iter()
133            .flat_map(|t| {
134                [
135                    (TripleTarget::Subject, &t.subject.span, t),
136                    (TripleTarget::Predicate, &t.predicate.span, t),
137                    (TripleTarget::Object, &t.object.span, t),
138                ]
139            })
140            .into_iter()
141            .filter(|x| x.1.contains(&offset))
142            .min_by_key(|x| x.1.end - x.1.start)
143        {
144            tracing::debug!(
145                "Narrowest triple at {} Component {:?} {} {:?} {} {:?} {} {:?} .",
146                offset,
147                target,
148                t.subject,
149                t.subject.span,
150                t.predicate,
151                t.predicate.span,
152                t.object,
153                t.object.span
154            );
155            commands.entity(e).insert(TripleComponent {
156                triple: t.clone(),
157                target,
158            });
159        } else {
160            let found = triples
161                .0
162                .iter()
163                .filter(|triple| triple.span.start <= offset && offset <= triple.span.end)
164                .min_by_key(|x| x.span.end - x.span.start);
165
166            // Fallback: if no triple spans the cursor, use the closest preceding triple
167            // (the one whose span.end is greatest but still ≤ offset). This handles the case
168            // where the cursor is in predicate or object position but the parser only emitted
169            // the preceding complete triple (e.g. after writing a new subject on a new line).
170            if let Some(t) = found.or_else(|| {
171                triples
172                    .0
173                    .iter()
174                    .filter(|triple| triple.span.end <= offset)
175                    .max_by_key(|x| x.span.end)
176            }) {
177                let object_written = t.object.span.start < t.object.span.end;
178                let predicate_written = t.predicate.span.start < t.predicate.span.end;
179                let target = if object_written && offset > t.object.span.end {
180                    // Cursor is past the object – likely after `;`, starting a new predicate.
181                    TripleTarget::Predicate
182                } else if predicate_written && offset >= t.predicate.span.end {
183                    TripleTarget::Object
184                } else if offset >= t.subject.span.end {
185                    TripleTarget::Predicate
186                } else {
187                    lang.default_position()
188                };
189                tracing::debug!(
190                    "Triple at {} Component {:?} {} {:?} {} {:?} {} {:?} .",
191                    offset,
192                    target,
193                    t.subject,
194                    t.subject.span,
195                    t.predicate,
196                    t.predicate.span,
197                    t.object,
198                    t.object.span
199                );
200                commands.entity(e).insert(TripleComponent {
201                    triple: t.clone(),
202                    target,
203                });
204            } else {
205                commands.entity(e).remove::<TripleComponent>();
206                debug!("No current triple found");
207            }
208        }
209    }
210}
211
212#[derive(Debug, Clone)]
213pub struct MyQuad<'a> {
214    pub subject: MyTerm<'a>,
215    pub predicate: MyTerm<'a>,
216    pub object: MyTerm<'a>,
217    pub span: std::ops::Range<usize>,
218}
219impl<'b, 'a> TryFrom<&'b MyQuad<'a>> for oxigraph::model::Quad {
220    type Error = ();
221
222    fn try_from(value: &'b MyQuad<'a>) -> std::result::Result<Self, Self::Error> {
223        let subject = oxigraph::model::Term::try_from(&value.subject)?;
224        let predicate = oxigraph::model::Term::try_from(&value.predicate)?;
225        let object = oxigraph::model::Term::try_from(&value.object)?;
226
227        let subject = oxigraph::model::NamedOrBlankNode::try_from(subject).map_err(|_| ())?;
228        let predicate = oxigraph::model::NamedNode::try_from(predicate).map_err(|_| ())?;
229
230        Ok(oxigraph::model::Quad::new(
231            subject,
232            predicate,
233            object,
234            oxigraph::model::GraphName::default(),
235        ))
236    }
237}
238
239impl<'a> MyQuad<'a> {
240    pub fn into_oxi_graph(
241        &self,
242        graph: impl TryInto<oxigraph::model::Term>,
243    ) -> Result<oxigraph::model::Quad, ()> {
244        let graph = graph.try_into().map_err(|_| ())?;
245        let graph = oxigraph::model::NamedOrBlankNode::try_from(graph).map_err(|_| ())?;
246        let graph = oxigraph::model::GraphName::from(graph);
247
248        self.into_oxi(Some(graph))
249    }
250
251    pub fn into_oxi(
252        &self,
253        graph: Option<oxigraph::model::GraphName>,
254    ) -> Result<oxigraph::model::Quad, ()> {
255        let subject = oxigraph::model::Term::try_from(&self.subject)?;
256        let predicate = oxigraph::model::Term::try_from(&self.predicate)?;
257        let object = oxigraph::model::Term::try_from(&self.object)?;
258        let graph = graph.unwrap_or_default();
259
260        let subject = oxigraph::model::NamedOrBlankNode::try_from(subject).map_err(|_| ())?;
261        let predicate = oxigraph::model::NamedNode::try_from(predicate).map_err(|_| ())?;
262
263        Ok(oxigraph::model::Quad::new(
264            subject, predicate, object, graph,
265        ))
266    }
267}
268
269impl<'a> std::fmt::Display for MyQuad<'a> {
270    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
271        write!(
272            f,
273            "{} {} {}. # {:?}",
274            self.subject, self.predicate, self.object, self.span
275        )
276    }
277}
278
279impl<'a> MyQuad<'a> {
280    pub fn to_owned(&self) -> MyQuad<'static> {
281        MyQuad {
282            subject: self.subject.to_owned(),
283            predicate: self.predicate.to_owned(),
284            object: self.object.to_owned(),
285            span: self.span.clone(),
286        }
287    }
288}
289impl<'a, 'b> TryFrom<&'b MyTerm<'a>> for oxigraph::model::Term {
290    type Error = ();
291
292    fn try_from(value: &'b MyTerm<'a>) -> std::result::Result<Self, Self::Error> {
293        use oxigraph::{model as M, model::Term as T};
294        let output = match &value.ty {
295            Some(TermKind::Iri) => T::NamedNode(M::NamedNode::new(value.as_str()).map_err(|_| ())?),
296            Some(TermKind::Literal) => {
297                if let Some(dt) = value.datatype() {
298                    let dt = M::NamedNode::new(dt.as_str()).map_err(|_| ())?;
299                    T::Literal(M::Literal::new_typed_literal(value.value.as_ref(), dt))
300                } else if let Some(lang) = value.language_tag() {
301                    T::Literal(
302                        M::Literal::new_language_tagged_literal(
303                            value.value.as_ref(),
304                            lang.as_str(),
305                        )
306                        .map_err(|_| ())?,
307                    )
308                } else {
309                    T::Literal(M::Literal::new_simple_literal(value.value.as_ref()))
310                }
311            }
312            Some(TermKind::BlankNode) => {
313                T::BlankNode(M::BlankNode::new(value.value.as_ref()).map_err(|_| ())?)
314            }
315            _ => {
316                return Err(());
317            }
318        };
319        return Ok(output);
320    }
321}
322
323impl<'a> Quad for MyQuad<'a> {
324    type Term = MyTerm<'a>;
325
326    fn s(&self) -> sophia_api::quad::QBorrowTerm<'_, Self> {
327        self.subject.borrow_term()
328    }
329
330    fn p(&self) -> sophia_api::quad::QBorrowTerm<'_, Self> {
331        self.predicate.borrow_term()
332    }
333
334    fn o(&self) -> sophia_api::quad::QBorrowTerm<'_, Self> {
335        self.object.borrow_term()
336    }
337
338    fn g(&self) -> GraphName<sophia_api::quad::QBorrowTerm<'_, Self>> {
339        None
340    }
341
342    fn to_spog(self) -> sophia_api::quad::Spog<Self::Term> {
343        ([self.subject, self.predicate, self.object], None)
344    }
345}
346// pub type MyQuad<'a> = ([MyTerm<'a>; 3], GraphName<MyTerm<'a>>);
347
348#[derive(Debug, Clone, PartialEq, Hash, Eq)]
349pub enum TermContext<'a> {
350    None,
351    DataType(Cow<'a, str>),
352    LangTag(Cow<'a, str>),
353}
354impl<'a> TermContext<'a> {
355    pub fn to_owned(&self) -> TermContext<'static> {
356        match self {
357            TermContext::None => TermContext::None,
358            TermContext::DataType(cow) => TermContext::DataType(Cow::Owned(cow.to_string())),
359            TermContext::LangTag(cow) => TermContext::LangTag(Cow::Owned(cow.to_string())),
360        }
361    }
362}
363
364#[derive(Debug, Clone, Eq)]
365pub struct MyTerm<'a> {
366    pub value: Cow<'a, str>,
367    pub ty: Option<TermKind>,
368    pub span: std::ops::Range<usize>,
369    pub context: TermContext<'a>,
370}
371impl<'a> PartialOrd for MyTerm<'a> {
372    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
373        match self.value.partial_cmp(&other.value) {
374            Some(core::cmp::Ordering::Equal) => {}
375            ord => return ord,
376        }
377        self.ty.partial_cmp(&other.ty)
378    }
379}
380impl<'a> Ord for MyTerm<'a> {
381    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
382        match self.value.cmp(&other.value) {
383            core::cmp::Ordering::Equal => {}
384            ord => return ord,
385        }
386        self.ty.cmp(&other.ty)
387    }
388}
389
390impl Hash for MyTerm<'_> {
391    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
392        // Ignore span
393        self.value.hash(state);
394        self.ty.hash(state);
395    }
396}
397
398impl PartialEq for MyTerm<'_> {
399    fn eq(&self, other: &Self) -> bool {
400        // Ignore span
401        other.value == self.value && other.ty == self.ty
402    }
403}
404
405impl<'a> std::fmt::Display for MyTerm<'a> {
406    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
407        match self.kind() {
408            TermKind::Iri => write!(f, "<{}>", self.value),
409            TermKind::Literal => write!(f, "\"{}\"", self.value),
410            TermKind::BlankNode => write!(f, "_:{}", self.value),
411            TermKind::Triple => write!(f, "<{}>", self.value),
412            TermKind::Variable => write!(f, "?{}", self.value),
413        }
414    }
415}
416
417impl<'a> MyTerm<'a> {
418    pub fn to_owned(&self) -> MyTerm<'static> {
419        let value = Cow::Owned(self.value.to_string());
420        MyTerm {
421            value,
422            ty: self.ty.clone(),
423            span: self.span.clone(),
424            context: self.context.to_owned(),
425        }
426    }
427    pub fn variable<T: Into<Cow<'a, str>>>(value: T, span: std::ops::Range<usize>) -> Self {
428        Self {
429            value: value.into(),
430            ty: TermKind::Variable.into(),
431            span,
432            context: TermContext::None,
433        }
434    }
435    pub fn named_node<T: Into<Cow<'a, str>>>(value: T, span: std::ops::Range<usize>) -> Self {
436        Self {
437            value: value.into(),
438            ty: TermKind::Iri.into(),
439            span,
440            context: TermContext::None,
441        }
442    }
443    pub fn blank_node<T: Into<Cow<'a, str>>>(value: T, span: std::ops::Range<usize>) -> Self {
444        Self {
445            value: value.into(),
446            ty: TermKind::BlankNode.into(),
447            span,
448            context: TermContext::None,
449        }
450    }
451    pub fn literal<T: Into<Cow<'a, str>>>(
452        value: T,
453        span: std::ops::Range<usize>,
454        context: TermContext<'a>,
455    ) -> Self {
456        Self {
457            value: value.into(),
458            ty: TermKind::Literal.into(),
459            span,
460            context,
461        }
462    }
463
464    pub fn invalid(span: std::ops::Range<usize>) -> Self {
465        Self {
466            value: Cow::default(),
467            ty: TermKind::Iri.into(),
468            span,
469
470            context: TermContext::None,
471        }
472    }
473
474    pub fn as_str(&'a self) -> &'a str {
475        &self.value
476    }
477}
478
479impl<'a> Term for MyTerm<'a> {
480    type BorrowTerm<'x>
481        = &'x Self
482    where
483        Self: 'x;
484
485    fn kind(&self) -> sophia_api::term::TermKind {
486        self.ty.unwrap_or(TermKind::Triple)
487    }
488
489    fn borrow_term(&self) -> Self::BorrowTerm<'_> {
490        self
491    }
492
493    fn iri(&self) -> Option<sophia_api::term::IriRef<sophia_api::MownStr<'_>>> {
494        self.is_iri()
495            .then(|| IriRef::new_unchecked(MownStr::from_ref(&self.value)))
496    }
497
498    fn bnode_id(&self) -> Option<sophia_api::term::BnodeId<sophia_api::MownStr<'_>>> {
499        self.is_blank_node()
500            .then(|| BnodeId::new_unchecked(MownStr::from_ref(&self.value)))
501    }
502
503    fn lexical_form(&self) -> Option<sophia_api::MownStr<'_>> {
504        self.is_literal().then(|| MownStr::from_ref(&self.value))
505    }
506
507    fn datatype(&self) -> Option<sophia_api::term::IriRef<sophia_api::MownStr<'_>>> {
508        None
509    }
510
511    fn language_tag(&self) -> Option<sophia_api::term::LanguageTag<sophia_api::MownStr<'_>>> {
512        None
513    }
514
515    fn variable(&self) -> Option<sophia_api::term::VarName<sophia_api::MownStr<'_>>> {
516        panic!("MyTerm does not supported variables")
517    }
518
519    fn triple(&self) -> Option<[Self::BorrowTerm<'_>; 3]> {
520        panic!("MyTerm does not supported triples")
521    }
522
523    fn to_triple(self) -> Option<[Self; 3]>
524    where
525        Self: Sized,
526    {
527        panic!("MyTerm does not supported triples")
528    }
529}
530
531impl<'a> Borrow<str> for &MyTerm<'a> {
532    fn borrow(&self) -> &str {
533        &self.value
534    }
535}
536
537#[derive(Default, Debug)]
538pub struct Triples2<'a> {
539    pub base_url: String,
540    pub triples: Vec<MyQuad<'a>>,
541    pub base: Option<MyTerm<'a>>,
542}
543
544impl<'a> Triples2<'a> {
545    pub fn to_owned(&self) -> Triples2<'static> {
546        let triples = self.triples.iter().map(|q| q.to_owned()).collect();
547        let base: Option<MyTerm<'static>> = self.base.as_ref().map(|x| x.to_owned());
548
549        Triples2 {
550            base,
551            triples,
552            base_url: self.base_url.clone(),
553        }
554    }
555
556    pub fn imports(&self, cb: impl FnMut(IriRef<MownStr<'_>>) -> ()) {
557        if let Some(ref base) = self.base {
558            self.triples
559                .quads_matching([base], [owl::imports], Any, Any)
560                .flatten()
561                .flat_map(|s| s.o().iri())
562                .for_each(cb);
563        }
564    }
565
566    pub fn sub_class_of(&self, mut cb: impl FnMut(IriRef<MownStr<'_>>, IriRef<MownStr<'_>>) -> ()) {
567        self.triples
568            .quads_matching(Any, [rdfs::subClassOf], Any, Any)
569            .flatten()
570            .flat_map(|s| match (s.s().iri(), s.o().iri()) {
571                (Some(s), Some(o)) => Some((s, o)),
572                _ => None,
573            })
574            .for_each(|(x, y)| cb(x, y));
575    }
576}
577
578impl<'a> std::ops::Deref for Triples2<'a> {
579    type Target = Vec<MyQuad<'a>>;
580
581    fn deref(&self) -> &Self::Target {
582        &self.triples
583    }
584}
585
586impl<'a> PartialEq for MyQuad<'a> {
587    fn eq(&self, other: &Self) -> bool {
588        self.subject == other.subject
589            && self.predicate == other.predicate
590            && self.object == other.object
591    }
592}